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
))