Commit3f4b1b1aRecorded20 Mar 2026Repositoryfjo

Initial fjo CLI and MCP server

Changed
 dev-redirects.sgl    |   9 ++++++++
 package.sgl          |  56 +++++++++++++++++++++++++++++++++++++++++++++
 src/fjo/config.sgl   |  30 ++++++++++++++++++++++++
 src/fjo/issues.sgl   | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/fjo/main.sgl     | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/fjo/pulls.sgl    | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/fjo/releases.sgl | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/fjo/repos.sgl    | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 930 insertions(+)
Diff
dev-redirects.sgladded
@@ -0,0 +1,9 @@
+1
;; Development redirects — point dependencies at local checkouts
+2
(redirects
+3
repos: (list
+4
(for-repo
+5
url: "codeberg:sigil/sigil"
+6
use: (from-path dir: "../sigil"))
+7
(for-repo
+8
url: "codeberg:sigil/sigil-forgejo"
+9
use: (from-path dir: "../sigil-forgejo"))))
package.sgladded
@@ -0,0 +1,56 @@
+1
;;; fjo - Forgejo/Codeberg management CLI and MCP server
+2
;;;
+3
;;; A dual-mode tool for managing repositories, issues, pull requests,
+4
;;; and releases on Forgejo/Codeberg instances. Usable as a CLI by humans
+5
;;; and as an MCP server by AI agents.
+6
+7
(define sigil-repo "codeberg:sigil/sigil")
+8
(define forgejo-repo "codeberg:sigil/sigil-forgejo")
+9
+10
(package
+11
name: "fjo"
+12
version: "0.1.0"
+13
description: "Forgejo/Codeberg management CLI and MCP server"
+14
url: "https://codeberg.org/sigil/fjo"
+15
license: "BSD-3-Clause"
+16
authors: (list "David Wilson <[email protected]>")
+17
+18
entry: '(fjo main)
+19
bundle-name: "fjo"
+20
+21
configs: (list
+22
(config
+23
name: 'dev
+24
output-dir: "build/dev"
+25
debug?: #t
+26
optimize: 0
+27
bundle?: #t)
+28
(config
+29
name: 'release
+30
output-dir: "build/release"
+31
static?: #t
+32
debug?: #f
+33
optimize: 2
+34
bundle?: #t))
+35
+36
provides: (list
+37
mcp-server: #{ name: "fjo"
+38
args: #["serve"]
+39
env: #{ FORGEJO_URL: "https://codeberg.org" }
+40
secret-env: #{ FORGEJO_TOKEN: #t } })
+41
+42
dependencies: (list
+43
(from-git url: sigil-repo package: "sigil-run")
+44
(from-git url: sigil-repo package: "sigil-stdlib")
+45
(from-git url: sigil-repo package: "sigil-tls")
+46
(from-git url: sigil-repo package: "sigil-http")
+47
(from-git url: sigil-repo package: "sigil-mcp")
+48
(from-git url: forgejo-repo package: "sigil-forgejo"))
+49
+50
tasks: (list
+51
(task
+52
name: 'build
+53
description: "Compile fjo modules"
+54
steps: (list
+55
(compile-sigil-modules sources: "src/**/*.sgl"
+56
output-dir: (config-output-subdir "lib"))))))
src/fjo/config.sgladded
@@ -0,0 +1,30 @@
+1
;;; (fjo config) - Configuration management.
+2
;;;
+3
;;; Reads Forgejo credentials and instance URL from environment variables.
+4
+5
(define-library (fjo config)
+6
(import (sigil core)
+7
(sigil string)
+8
(sigil process))
+9
(export fjo-token
+10
fjo-base-url)
+11
(begin
+12
+13
;;; Get the Forgejo API token.
+14
;;;
+15
;;; Raises an error if FORGEJO_TOKEN is not set.
+16
(define (fjo-token)
+17
(: -> string?)
+18
(let ((val (getenv "FORGEJO_TOKEN")))
+19
(if (and val (not (string=? val "")))
+20
val
+21
(error "FORGEJO_TOKEN environment variable must be set"))))
+22
+23
;;; Get the Forgejo instance base URL.
+24
;;;
+25
;;; Defaults to https://codeberg.org if FORGEJO_URL is not set.
+26
(define (fjo-base-url)
+27
(: -> string?)
+28
(or (getenv "FORGEJO_URL") "https://codeberg.org"))
+29
+30
))
src/fjo/issues.sgladded
@@ -0,0 +1,168 @@
+1
;;; (fjo issues) - Issue management tools.
+2
;;;
+3
;;; MCP tools and CLI handlers for listing, creating, and commenting
+4
;;; on issues.
+5
+6
(define-library (fjo issues)
+7
(import (sigil core)
+8
(sigil string)
+9
(sigil json)
+10
(sigil array)
+11
(sigil mcp server)
+12
(forgejo issues)
+13
(fjo config))
+14
(export register-issue-tools!
+15
tool-issues-list
+16
tool-issues-get
+17
tool-issues-create
+18
tool-issues-comment)
+19
(begin
+20
+21
;; ============================================================
+22
;; Tool Schemas
+23
;; ============================================================
+24
+25
(define issues-list-schema
+26
'((type . "object")
+27
(properties . ((owner . ((type . "string")
+28
(description . "Repository owner")))
+29
(repo . ((type . "string")
+30
(description . "Repository name")))))
+31
(required . ("owner" "repo"))))
+32
+33
(define issues-get-schema
+34
'((type . "object")
+35
(properties . ((owner . ((type . "string")
+36
(description . "Repository owner")))
+37
(repo . ((type . "string")
+38
(description . "Repository name")))
+39
(number . ((type . "integer")
+40
(description . "Issue number")))))
+41
(required . ("owner" "repo" "number"))))
+42
+43
(define issues-create-schema
+44
'((type . "object")
+45
(properties . ((owner . ((type . "string")
+46
(description . "Repository owner")))
+47
(repo . ((type . "string")
+48
(description . "Repository name")))
+49
(title . ((type . "string")
+50
(description . "Issue title")))
+51
(body . ((type . "string")
+52
(description . "Issue body (markdown)")))))
+53
(required . ("owner" "repo" "title"))))
+54
+55
(define issues-comment-schema
+56
'((type . "object")
+57
(properties . ((owner . ((type . "string")
+58
(description . "Repository owner")))
+59
(repo . ((type . "string")
+60
(description . "Repository name")))
+61
(number . ((type . "integer")
+62
(description . "Issue number")))
+63
(body . ((type . "string")
+64
(description . "Comment body (markdown)")))))
+65
(required . ("owner" "repo" "number" "body"))))
+66
+67
;; ============================================================
+68
;; Formatting
+69
;; ============================================================
+70
+71
(define (format-issue issue)
+72
(let ((number (dict-ref issue number:))
+73
(title (dict-ref issue title:))
+74
(state (dict-ref issue state:))
+75
(comments (dict-ref issue comments: 0)))
+76
(string-append
+77
"#" (number->string number)
+78
" [" state "] "
+79
title
+80
(if (> comments 0)
+81
(string-append " (" (number->string comments) " comments)")
+82
""))))
+83
+84
;; ============================================================
+85
;; Tool Handlers
+86
;; ============================================================
+87
+88
(define (tool-issues-list args)
+89
(let* ((owner (dict-ref args owner:))
+90
(repo (dict-ref args repo:))
+91
(issues (forgejo-list-issues (fjo-base-url) (fjo-token)
+92
owner repo)))
+93
(if (or (not issues) (= (array-length issues) 0))
+94
(string-append "No open issues in " owner "/" repo ".")
+95
(string-append
+96
"Issues in " owner "/" repo ":\n\n"
+97
(string-join (map format-issue (array->list issues)) "\n")))))
+98
+99
(define (tool-issues-get args)
+100
(let* ((owner (dict-ref args owner:))
+101
(repo (dict-ref args repo:))
+102
(number (dict-ref args number:))
+103
(issue (forgejo-get-issue (fjo-base-url) (fjo-token)
+104
owner repo number)))
+105
(if (not issue)
+106
(string-append "Issue #" (number->string number) " not found.")
+107
(let ((title (dict-ref issue title:))
+108
(state (dict-ref issue state:))
+109
(body (dict-ref issue body: ""))
+110
(user (dict-ref (dict-ref issue user:) login: "unknown")))
+111
(string-append
+112
"#" (number->string number) " " title "\n"
+113
"State: " state " | Author: " user "\n\n"
+114
(or body ""))))))
+115
+116
(define (tool-issues-create args)
+117
(let* ((owner (dict-ref args owner:))
+118
(repo (dict-ref args repo:))
+119
(title (dict-ref args title:))
+120
(body (dict-ref args body: ""))
+121
(result (forgejo-create-issue (fjo-base-url) (fjo-token)
+122
owner repo title body)))
+123
(if result
+124
(string-append "Created issue #"
+125
(number->string (dict-ref result number:))
+126
": " title)
+127
"Failed to create issue.")))
+128
+129
(define (tool-issues-comment args)
+130
(let* ((owner (dict-ref args owner:))
+131
(repo (dict-ref args repo:))
+132
(number (dict-ref args number:))
+133
(body (dict-ref args body:)))
+134
(forgejo-create-issue-comment (fjo-base-url) (fjo-token)
+135
owner repo number body)
+136
(string-append "Added comment to issue #"
+137
(number->string number) ".")))
+138
+139
;; ============================================================
+140
;; MCP Registration
+141
;; ============================================================
+142
+143
(define (register-issue-tools! server register)
+144
(register server
+145
"fjo/issues-list"
+146
"List open issues in a repository."
+147
issues-list-schema
+148
tool-issues-list)
+149
+150
(register server
+151
"fjo/issues-get"
+152
"Get details of a specific issue."
+153
issues-get-schema
+154
tool-issues-get)
+155
+156
(register server
+157
"fjo/issues-create"
+158
"Create a new issue in a repository."
+159
issues-create-schema
+160
tool-issues-create)
+161
+162
(register server
+163
"fjo/issues-comment"
+164
"Add a comment to an issue."
+165
issues-comment-schema
+166
tool-issues-comment))
+167
+168
))
src/fjo/main.sgladded
@@ -0,0 +1,174 @@
+1
;;; (fjo main) - Entry point for fjo CLI and MCP server.
+2
;;;
+3
;;; Dispatches between MCP server mode (`fjo serve`) and CLI mode
+4
;;; (`fjo <command> <args>`).
+5
+6
(define-library (fjo main)
+7
(import (sigil core)
+8
(sigil string)
+9
(sigil array)
+10
(sigil process)
+11
(sigil mcp server)
+12
(fjo config)
+13
(fjo repos)
+14
(fjo issues)
+15
(fjo pulls)
+16
(fjo releases))
+17
(export main)
+18
(begin
+19
+20
;; ============================================================
+21
;; MCP Server
+22
;; ============================================================
+23
+24
(define (run-server)
+25
(let ((server (mcp-server name: "fjo" version: "0.1.0")))
+26
(register-repo-tools! server mcp-server-register-tool!)
+27
(register-issue-tools! server mcp-server-register-tool!)
+28
(register-pull-tools! server mcp-server-register-tool!)
+29
(register-release-tools! server mcp-server-register-tool!)
+30
(mcp-server-run server)))
+31
+32
;; ============================================================
+33
;; CLI
+34
;; ============================================================
+35
+36
(define (print-usage)
+37
(display "fjo - Forgejo/Codeberg management tool\n\n")
+38
(display "Usage:\n")
+39
(display " fjo serve Start MCP server\n")
+40
(display " fjo repos list <org> List repos in org\n")
+41
(display " fjo repos info <owner> <repo> Repo details\n")
+42
(display " fjo repos create <org> <name> [desc] Create repo\n")
+43
(display " fjo topics list <owner> <repo> List topics\n")
+44
(display " fjo topics set <owner> <repo> <t>... Set topics\n")
+45
(display " fjo topics add <owner> <repo> <topic> Add topic\n")
+46
(display " fjo issues list <owner> <repo> List issues\n")
+47
(display " fjo issues get <owner> <repo> <num> Get issue\n")
+48
(display " fjo issues create <owner> <repo> <t> Create issue\n")
+49
(display " fjo prs list <owner> <repo> List PRs\n")
+50
(display " fjo prs get <owner> <repo> <num> Get PR\n")
+51
(display " fjo releases list <owner> <repo> List releases\n\n")
+52
(display "Environment:\n")
+53
(display " FORGEJO_TOKEN API token (required)\n")
+54
(display " FORGEJO_URL Instance URL (default: https://codeberg.org)\n"))
+55
+56
(define (cli-dispatch args)
+57
(if (< (length args) 2)
+58
(begin (print-usage) (exit 1))
+59
(let ((group (car args))
+60
(cmd (cadr args))
+61
(rest (cddr args)))
+62
(cond
+63
;; repos
+64
((and (string=? group "repos") (string=? cmd "list")
+65
(>= (length rest) 1))
+66
(display (tool-repos-list #{ org: (car rest) }))
+67
(newline))
+68
+69
((and (string=? group "repos") (string=? cmd "info")
+70
(>= (length rest) 2))
+71
(display (tool-repos-info #{ owner: (car rest)
+72
repo: (cadr rest) }))
+73
(newline))
+74
+75
((and (string=? group "repos") (string=? cmd "create")
+76
(>= (length rest) 2))
+77
(display (tool-repos-create
+78
#{ org: (car rest)
+79
name: (cadr rest)
+80
description: (if (>= (length rest) 3)
+81
(caddr rest) "") }))
+82
(newline))
+83
+84
;; topics
+85
((and (string=? group "topics") (string=? cmd "list")
+86
(>= (length rest) 2))
+87
(display (tool-topics-list #{ owner: (car rest)
+88
repo: (cadr rest) }))
+89
(newline))
+90
+91
((and (string=? group "topics") (string=? cmd "set")
+92
(>= (length rest) 3))
+93
(display (tool-topics-set
+94
#{ owner: (car rest)
+95
repo: (cadr rest)
+96
topics: (list->array (cddr rest)) }))
+97
(newline))
+98
+99
((and (string=? group "topics") (string=? cmd "add")
+100
(>= (length rest) 3))
+101
(display (tool-topics-add
+102
#{ owner: (car rest)
+103
repo: (cadr rest)
+104
topic: (caddr rest) }))
+105
(newline))
+106
+107
;; issues
+108
((and (string=? group "issues") (string=? cmd "list")
+109
(>= (length rest) 2))
+110
(display (tool-issues-list #{ owner: (car rest)
+111
repo: (cadr rest) }))
+112
(newline))
+113
+114
((and (string=? group "issues") (string=? cmd "get")
+115
(>= (length rest) 3))
+116
(display (tool-issues-get
+117
#{ owner: (car rest)
+118
repo: (cadr rest)
+119
number: (string->number (caddr rest)) }))
+120
(newline))
+121
+122
((and (string=? group "issues") (string=? cmd "create")
+123
(>= (length rest) 3))
+124
(display (tool-issues-create
+125
#{ owner: (car rest)
+126
repo: (cadr rest)
+127
title: (caddr rest)
+128
body: (if (>= (length rest) 4)
+129
(cadddr rest) "") }))
+130
(newline))
+131
+132
;; prs
+133
((and (string=? group "prs") (string=? cmd "list")
+134
(>= (length rest) 2))
+135
(display (tool-pulls-list #{ owner: (car rest)
+136
repo: (cadr rest) }))
+137
(newline))
+138
+139
((and (string=? group "prs") (string=? cmd "get")
+140
(>= (length rest) 3))
+141
(display (tool-pulls-get
+142
#{ owner: (car rest)
+143
repo: (cadr rest)
+144
number: (string->number (caddr rest)) }))
+145
(newline))
+146
+147
;; releases
+148
((and (string=? group "releases") (string=? cmd "list")
+149
(>= (length rest) 2))
+150
(display (tool-releases-list #{ owner: (car rest)
+151
repo: (cadr rest) }))
+152
(newline))
+153
+154
(else
+155
(display (string-append "Unknown command: " group " " cmd "\n"))
+156
(print-usage)
+157
(exit 1))))))
+158
+159
;; ============================================================
+160
;; Entry Point
+161
;; ============================================================
+162
+163
(define (main)
+164
(let ((args (cdr (command-line))))
+165
(cond
+166
((null? args)
+167
(print-usage)
+168
(exit 1))
+169
((string=? (car args) "serve")
+170
(run-server))
+171
(else
+172
(cli-dispatch args)))))
+173
+174
))
src/fjo/pulls.sgladded
@@ -0,0 +1,170 @@
+1
;;; (fjo pulls) - Pull request management tools.
+2
;;;
+3
;;; MCP tools and CLI handlers for listing, creating, and merging
+4
;;; pull requests.
+5
+6
(define-library (fjo pulls)
+7
(import (sigil core)
+8
(sigil string)
+9
(sigil json)
+10
(sigil array)
+11
(sigil mcp server)
+12
(forgejo pulls)
+13
(fjo config))
+14
(export register-pull-tools!
+15
tool-pulls-list
+16
tool-pulls-get
+17
tool-pulls-create
+18
tool-pulls-merge)
+19
(begin
+20
+21
;; ============================================================
+22
;; Tool Schemas
+23
;; ============================================================
+24
+25
(define pulls-list-schema
+26
'((type . "object")
+27
(properties . ((owner . ((type . "string")
+28
(description . "Repository owner")))
+29
(repo . ((type . "string")
+30
(description . "Repository name")))))
+31
(required . ("owner" "repo"))))
+32
+33
(define pulls-get-schema
+34
'((type . "object")
+35
(properties . ((owner . ((type . "string")
+36
(description . "Repository owner")))
+37
(repo . ((type . "string")
+38
(description . "Repository name")))
+39
(number . ((type . "integer")
+40
(description . "Pull request number")))))
+41
(required . ("owner" "repo" "number"))))
+42
+43
(define pulls-create-schema
+44
'((type . "object")
+45
(properties . ((owner . ((type . "string")
+46
(description . "Repository owner")))
+47
(repo . ((type . "string")
+48
(description . "Repository name")))
+49
(title . ((type . "string")
+50
(description . "Pull request title")))
+51
(body . ((type . "string")
+52
(description . "Pull request body (markdown)")))
+53
(head . ((type . "string")
+54
(description . "Source branch")))
+55
(base . ((type . "string")
+56
(description . "Target branch (default: main)")))))
+57
(required . ("owner" "repo" "title" "head"))))
+58
+59
(define pulls-merge-schema
+60
'((type . "object")
+61
(properties . ((owner . ((type . "string")
+62
(description . "Repository owner")))
+63
(repo . ((type . "string")
+64
(description . "Repository name")))
+65
(number . ((type . "integer")
+66
(description . "Pull request number")))))
+67
(required . ("owner" "repo" "number"))))
+68
+69
;; ============================================================
+70
;; Formatting
+71
;; ============================================================
+72
+73
(define (format-pull pr)
+74
(let ((number (dict-ref pr number:))
+75
(title (dict-ref pr title:))
+76
(state (dict-ref pr state:))
+77
(user (dict-ref (dict-ref pr user:) login: "unknown")))
+78
(string-append
+79
"#" (number->string number)
+80
" [" state "] "
+81
title
+82
" (by " user ")")))
+83
+84
;; ============================================================
+85
;; Tool Handlers
+86
;; ============================================================
+87
+88
(define (tool-pulls-list args)
+89
(let* ((owner (dict-ref args owner:))
+90
(repo (dict-ref args repo:))
+91
(pulls (forgejo-list-pulls (fjo-base-url) (fjo-token)
+92
owner repo)))
+93
(if (or (not pulls) (= (array-length pulls) 0))
+94
(string-append "No open pull requests in " owner "/" repo ".")
+95
(string-append
+96
"Pull requests in " owner "/" repo ":\n\n"
+97
(string-join (map format-pull (array->list pulls)) "\n")))))
+98
+99
(define (tool-pulls-get args)
+100
(let* ((owner (dict-ref args owner:))
+101
(repo (dict-ref args repo:))
+102
(number (dict-ref args number:))
+103
(pr (forgejo-get-pull (fjo-base-url) (fjo-token)
+104
owner repo number)))
+105
(if (not pr)
+106
(string-append "Pull request #" (number->string number) " not found.")
+107
(let ((title (dict-ref pr title:))
+108
(state (dict-ref pr state:))
+109
(body (dict-ref pr body: ""))
+110
(user (dict-ref (dict-ref pr user:) login: "unknown"))
+111
(mergeable (dict-ref pr mergeable: #f)))
+112
(string-append
+113
"#" (number->string number) " " title "\n"
+114
"State: " state " | Author: " user
+115
" | Mergeable: " (if mergeable "yes" "no") "\n\n"
+116
(or body ""))))))
+117
+118
(define (tool-pulls-create args)
+119
(let* ((owner (dict-ref args owner:))
+120
(repo (dict-ref args repo:))
+121
(title (dict-ref args title:))
+122
(body (dict-ref args body: ""))
+123
(head (dict-ref args head:))
+124
(base (dict-ref args base: "main"))
+125
(result (forgejo-create-pull (fjo-base-url) (fjo-token)
+126
owner repo title body head base)))
+127
(if result
+128
(string-append "Created PR #"
+129
(number->string (dict-ref result number:))
+130
": " title)
+131
"Failed to create pull request.")))
+132
+133
(define (tool-pulls-merge args)
+134
(let* ((owner (dict-ref args owner:))
+135
(repo (dict-ref args repo:))
+136
(number (dict-ref args number:)))
+137
(forgejo-merge-pull (fjo-base-url) (fjo-token) owner repo number)
+138
(string-append "Merged pull request #"
+139
(number->string number) ".")))
+140
+141
;; ============================================================
+142
;; MCP Registration
+143
;; ============================================================
+144
+145
(define (register-pull-tools! server register)
+146
(register server
+147
"fjo/pulls-list"
+148
"List open pull requests in a repository."
+149
pulls-list-schema
+150
tool-pulls-list)
+151
+152
(register server
+153
"fjo/pulls-get"
+154
"Get details of a specific pull request."
+155
pulls-get-schema
+156
tool-pulls-get)
+157
+158
(register server
+159
"fjo/pulls-create"
+160
"Create a new pull request."
+161
pulls-create-schema
+162
tool-pulls-create)
+163
+164
(register server
+165
"fjo/pulls-merge"
+166
"Merge a pull request."
+167
pulls-merge-schema
+168
tool-pulls-merge))
+169
+170
))
src/fjo/releases.sgladded
@@ -0,0 +1,109 @@
+1
;;; (fjo releases) - Release management tools.
+2
;;;
+3
;;; MCP tools and CLI handlers for listing and creating releases.
+4
+5
(define-library (fjo releases)
+6
(import (sigil core)
+7
(sigil string)
+8
(sigil json)
+9
(sigil array)
+10
(sigil mcp server)
+11
(forgejo releases)
+12
(fjo config))
+13
(export register-release-tools!
+14
tool-releases-list
+15
tool-releases-create)
+16
(begin
+17
+18
;; ============================================================
+19
;; Tool Schemas
+20
;; ============================================================
+21
+22
(define releases-list-schema
+23
'((type . "object")
+24
(properties . ((owner . ((type . "string")
+25
(description . "Repository owner")))
+26
(repo . ((type . "string")
+27
(description . "Repository name")))))
+28
(required . ("owner" "repo"))))
+29
+30
(define releases-create-schema
+31
'((type . "object")
+32
(properties . ((owner . ((type . "string")
+33
(description . "Repository owner")))
+34
(repo . ((type . "string")
+35
(description . "Repository name")))
+36
(tag . ((type . "string")
+37
(description . "Tag name for the release (e.g. v0.1.0)")))
+38
(name . ((type . "string")
+39
(description . "Release title")))
+40
(body . ((type . "string")
+41
(description . "Release notes (markdown)")))))
+42
(required . ("owner" "repo" "tag"))))
+43
+44
;; ============================================================
+45
;; Formatting
+46
;; ============================================================
+47
+48
(define (format-release rel)
+49
(let ((tag (dict-ref rel tag_name:))
+50
(name (dict-ref rel name: ""))
+51
(date (dict-ref rel created_at: ""))
+52
(assets (dict-ref rel assets: #f)))
+53
(string-append
+54
tag
+55
(if (and name (not (string=? name "")) (not (string=? name tag)))
+56
(string-append " - " name)
+57
"")
+58
(if (and date (not (string=? date "")))
+59
(string-append " (" (substring date 0 10) ")")
+60
"")
+61
(if (and assets (> (array-length assets) 0))
+62
(string-append " [" (number->string (array-length assets)) " assets]")
+63
""))))
+64
+65
;; ============================================================
+66
;; Tool Handlers
+67
;; ============================================================
+68
+69
(define (tool-releases-list args)
+70
(let* ((owner (dict-ref args owner:))
+71
(repo (dict-ref args repo:))
+72
(releases (forgejo-list-releases (fjo-base-url) (fjo-token)
+73
owner repo)))
+74
(if (or (not releases) (= (array-length releases) 0))
+75
(string-append "No releases in " owner "/" repo ".")
+76
(string-append
+77
"Releases in " owner "/" repo ":\n\n"
+78
(string-join (map format-release (array->list releases)) "\n")))))
+79
+80
(define (tool-releases-create args)
+81
(let* ((owner (dict-ref args owner:))
+82
(repo (dict-ref args repo:))
+83
(tag (dict-ref args tag:))
+84
(name (dict-ref args name: tag))
+85
(body (dict-ref args body: ""))
+86
(result (forgejo-create-release (fjo-base-url) (fjo-token)
+87
owner repo tag name body)))
+88
(if result
+89
(string-append "Created release " tag " in " owner "/" repo ".")
+90
"Failed to create release.")))
+91
+92
;; ============================================================
+93
;; MCP Registration
+94
;; ============================================================
+95
+96
(define (register-release-tools! server register)
+97
(register server
+98
"fjo/releases-list"
+99
"List releases for a repository."
+100
releases-list-schema
+101
tool-releases-list)
+102
+103
(register server
+104
"fjo/releases-create"
+105
"Create a new release."
+106
releases-create-schema
+107
tool-releases-create))
+108
+109
))
src/fjo/repos.sgladded
@@ -0,0 +1,214 @@
+1
;;; (fjo repos) - Repository management tools.
+2
;;;
+3
;;; MCP tools and CLI handlers for listing, creating, and managing
+4
;;; repositories and their topics.
+5
+6
(define-library (fjo repos)
+7
(import (sigil core)
+8
(sigil string)
+9
(sigil json)
+10
(sigil array)
+11
(sigil mcp server)
+12
(forgejo client)
+13
(forgejo repos)
+14
(forgejo orgs)
+15
(forgejo topics)
+16
(fjo config))
+17
(export register-repo-tools!
+18
tool-repos-list
+19
tool-repos-info
+20
tool-repos-create
+21
tool-topics-list
+22
tool-topics-set
+23
tool-topics-add)
+24
(begin
+25
+26
;; ============================================================
+27
;; Tool Schemas
+28
;; ============================================================
+29
+30
(define repos-list-schema
+31
'((type . "object")
+32
(properties . ((org . ((type . "string")
+33
(description . "Organization name to list repos for")))))
+34
(required . ("org"))))
+35
+36
(define repos-info-schema
+37
'((type . "object")
+38
(properties . ((owner . ((type . "string")
+39
(description . "Repository owner (user or org)")))
+40
(repo . ((type . "string")
+41
(description . "Repository name")))))
+42
(required . ("owner" "repo"))))
+43
+44
(define repos-create-schema
+45
'((type . "object")
+46
(properties . ((org . ((type . "string")
+47
(description . "Organization to create the repo under")))
+48
(name . ((type . "string")
+49
(description . "Repository name")))
+50
(description . ((type . "string")
+51
(description . "Repository description")))))
+52
(required . ("org" "name"))))
+53
+54
(define topics-list-schema
+55
'((type . "object")
+56
(properties . ((owner . ((type . "string")
+57
(description . "Repository owner")))
+58
(repo . ((type . "string")
+59
(description . "Repository name")))))
+60
(required . ("owner" "repo"))))
+61
+62
(define topics-set-schema
+63
'((type . "object")
+64
(properties . ((owner . ((type . "string")
+65
(description . "Repository owner")))
+66
(repo . ((type . "string")
+67
(description . "Repository name")))
+68
(topics . ((type . "array")
+69
(items . ((type . "string")))
+70
(description . "List of topics to set on the repo")))))
+71
(required . ("owner" "repo" "topics"))))
+72
+73
(define topics-add-schema
+74
'((type . "object")
+75
(properties . ((owner . ((type . "string")
+76
(description . "Repository owner")))
+77
(repo . ((type . "string")
+78
(description . "Repository name")))
+79
(topic . ((type . "string")
+80
(description . "Topic to add")))))
+81
(required . ("owner" "repo" "topic"))))
+82
+83
;; ============================================================
+84
;; Formatting
+85
;; ============================================================
+86
+87
(define (format-repo repo)
+88
(let ((name (dict-ref repo name:))
+89
(desc (dict-ref repo description: ""))
+90
(topics (dict-ref repo topics: #f)))
+91
(string-append
+92
name
+93
(if (and desc (not (string=? desc "")))
+94
(string-append " - " desc)
+95
"")
+96
(if (and topics (> (array-length topics) 0))
+97
(string-append " [" (string-join (array->list topics) ", ") "]")
+98
""))))
+99
+100
;; ============================================================
+101
;; Tool Handlers
+102
;; ============================================================
+103
+104
(define (tool-repos-list args)
+105
(let* ((org (dict-ref args org:))
+106
(repos (forgejo-list-org-repos (fjo-base-url) (fjo-token) org)))
+107
(if (or (not repos) (= (array-length repos) 0))
+108
(string-append "No repositories found in " org ".")
+109
(string-append
+110
"Repositories in " org ":\n\n"
+111
(string-join (map format-repo (array->list repos)) "\n")))))
+112
+113
(define (tool-repos-info args)
+114
(let* ((owner (dict-ref args owner:))
+115
(repo-name (dict-ref args repo:))
+116
(repo (forgejo-get-repo (fjo-base-url) (fjo-token) owner repo-name)))
+117
(if (not repo)
+118
(string-append "Repository " owner "/" repo-name " not found.")
+119
(let ((name (dict-ref repo full_name:))
+120
(desc (dict-ref repo description: ""))
+121
(stars (dict-ref repo stars_count: 0))
+122
(forks (dict-ref repo forks_count: 0))
+123
(issues (dict-ref repo open_issues_count: 0))
+124
(url (dict-ref repo html_url: "")))
+125
(string-append
+126
name "\n"
+127
(if (and desc (not (string=? desc "")))
+128
(string-append desc "\n")
+129
"")
+130
"\n"
+131
"Stars: " (number->string stars) "\n"
+132
"Forks: " (number->string forks) "\n"
+133
"Open issues: " (number->string issues) "\n"
+134
"URL: " url)))))
+135
+136
(define (tool-repos-create args)
+137
(let* ((org (dict-ref args org:))
+138
(name (dict-ref args name:))
+139
(desc (dict-ref args description: ""))
+140
(result (forgejo-create-org-repo (fjo-base-url) (fjo-token)
+141
org name desc)))
+142
(if result
+143
(string-append "Created repository " org "/" name ".\n"
+144
"URL: " (dict-ref result html_url: ""))
+145
"Failed to create repository.")))
+146
+147
(define (tool-topics-list args)
+148
(let* ((owner (dict-ref args owner:))
+149
(repo (dict-ref args repo:))
+150
(result (forgejo-list-topics (fjo-base-url) (fjo-token) owner repo))
+151
(topics (if result (dict-ref result topics: #f) #f)))
+152
(if (or (not topics) (= (array-length topics) 0))
+153
(string-append "No topics on " owner "/" repo ".")
+154
(string-append
+155
"Topics on " owner "/" repo ":\n"
+156
(string-join (array->list topics) ", ")))))
+157
+158
(define (tool-topics-set args)
+159
(let* ((owner (dict-ref args owner:))
+160
(repo (dict-ref args repo:))
+161
(topics (array->list (dict-ref args topics:))))
+162
(forgejo-replace-topics (fjo-base-url) (fjo-token) owner repo topics)
+163
(string-append "Set topics on " owner "/" repo ": "
+164
(string-join topics ", "))))
+165
+166
(define (tool-topics-add args)
+167
(let* ((owner (dict-ref args owner:))
+168
(repo (dict-ref args repo:))
+169
(topic (dict-ref args topic:)))
+170
(forgejo-add-topic (fjo-base-url) (fjo-token) owner repo topic)
+171
(string-append "Added topic '" topic "' to " owner "/" repo ".")))
+172
+173
;; ============================================================
+174
;; MCP Registration
+175
;; ============================================================
+176
+177
(define (register-repo-tools! server register)
+178
(register server
+179
"fjo/repos-list"
+180
"List repositories in a Forgejo/Codeberg organization."
+181
repos-list-schema
+182
tool-repos-list)
+183
+184
(register server
+185
"fjo/repos-info"
+186
"Get detailed information about a repository."
+187
repos-info-schema
+188
tool-repos-info)
+189
+190
(register server
+191
"fjo/repos-create"
+192
"Create a new repository under an organization."
+193
repos-create-schema
+194
tool-repos-create)
+195
+196
(register server
+197
"fjo/topics-list"
+198
"List topics on a repository."
+199
topics-list-schema
+200
tool-topics-list)
+201
+202
(register server
+203
"fjo/topics-set"
+204
"Replace all topics on a repository."
+205
topics-set-schema
+206
tool-topics-set)
+207
+208
(register server
+209
"fjo/topics-add"
+210
"Add a topic to a repository."
+211
topics-add-schema
+212
tool-topics-add))
+213
+214
))