A CLI for Forgejo, focusing on issue management and inspired by the gh cli tool
  • TypeScript 97.7%
  • JavaScript 2.3%
Find a file
2026-08-23 06:23:09 +01:00
src Add repository command to view repository details in CLI 2026-08-15 11:00:57 +01:00
test Enhance authentication and browser handling: add host verification warning, improve URL validation, and implement safe command parsing 2026-08-15 10:30:39 +01:00
.gitignore Asked claude to build me a forgejo CLI inspired by the github cli 2026-08-15 09:45:31 +01:00
api-spec.json Added a copy of the API spec as Copilot cannot fetch it 2026-08-23 06:23:09 +01:00
package-lock.json Asked claude to build me a forgejo CLI inspired by the github cli 2026-08-15 09:45:31 +01:00
package.json Asked claude to build me a forgejo CLI inspired by the github cli 2026-08-15 09:45:31 +01:00
README.md Add repository command to view repository details in CLI 2026-08-15 11:00:57 +01:00
tsconfig.json Asked claude to build me a forgejo CLI inspired by the github cli 2026-08-15 09:45:31 +01:00

fj — a Forgejo CLI

fj is a command line interface for Forgejo, modelled on GitHub's gh. It covers the issue and pull request surface of the Forgejo API v1: issues, pull requests, comments, labels, milestones, reactions, attachments, dependencies, pins, subscriptions and time tracking — plus a raw fj api escape hatch.

Written in TypeScript, runs on Node 20+, and depends only on commander.

Install

npm install
npm run build
npm link          # puts `fj` on your PATH

Or run it without linking: node dist/src/index.js <args>.

Authenticate

Create a token at https://<your-host>/user/settings/applications with at least read:user, write:issue and write:repository scopes. Remote hosts must use HTTPS; plain HTTP is rejected except for localhost development. Then:

fj auth login --host codeberg.org            # prompts for the token
echo "$TOKEN" | fj auth login --host codeberg.org --with-token
fj auth status
fj auth token --show                        # show the raw token only when you explicitly opt in

Tokens are shown masked by default to reduce accidental disclosure. Credentials are stored with 0600 permissions in:

Platform Path
Linux/macOS $XDG_CONFIG_HOME/fj/config.json (default ~/.config/fj/config.json)
Windows %APPDATA%\fj\config.json

Choosing the repository

Every repo-scoped command resolves its target in this order:

  1. --repo OWNER/REPO (also accepts a full URL, or host/owner/repo)

  2. FJ_REPO

  3. the git remote of the current checkout (origin, then upstream; override with --remote)

  4. default_repo from the config file

The host is taken from --host, FJ_HOST, the git remote, or default_host. Sub-path installations (https://example.org/forge/owner/repo) are handled.

Issues

# Browse
fj issue list                                  # open issues in the current repo
fj issue list -s all -l bug,ui -a @me -L 100
fj issue list --search "crash on startup" --sort recentupdate
fj issue view 42                               # details
fj issue view 42 --comments                    # with the full thread
fj issue status                                # assigned to / opened by / mentioning you
fj issue search "flaky test" --owner forgejo --created

# Create and change
fj issue create -t "Crash on startup" -b "Steps…" -l bug -a alice -m "v1.2"
fj issue create                                # interactive: prompts, then $EDITOR
fj issue create -t "From a file" -F notes.md
fj issue edit 42 --add-label regression --remove-label needs-triage
fj issue edit 42 --add-assignee bob --milestone "v1.2" --due-date +14d
fj issue edit 42 --editor                      # edit the body in $EDITOR
fj issue close 42 43 -c "Fixed in 1.2.0"
fj issue reopen 42
fj issue delete 42                             # asks for confirmation, -y to skip

# Comments
fj issue comment 42 -b "Thanks for the report"
fj issue comment 42 --editor
fj issue comment 42 --edit-last -b "…typo fixed"
fj issue comment 42 --delete-last
fj issue comments 42                           # list the thread
fj comment edit 987654 -b "new text"           # by comment id
fj comment delete 987654 -y
fj comment react 987654 +1

# Reactions, watching, activity
fj issue react 42 rocket
fj issue react 42 rocket --remove
fj issue react 42 --list
fj issue subscribe 42 / fj issue unsubscribe 42 / fj issue subscribers 42
fj issue timeline 42                           # labels, milestones, renames, refs…

# Pins
fj issue pin 42 / fj issue unpin 42 / fj issue pinned / fj issue move-pin 42 1

# Dependencies (Forgejo-specific)
fj issue dep add 42 41                         # #42 is blocked by #41
fj issue dep add 42 other/repo#7               # cross-repository
fj issue dep list 42 / fj issue dep remove 42 41
fj issue blocks list 42 / add 42 43 / remove 42 43

# Time tracking
fj issue time log 42 1h30m
fj issue time list 42                          # per-entry table plus a total
fj issue time clear 42
fj issue time start 42 / stop 42 / cancel 42   # stopwatch
fj issue time running                          # your running stopwatches

# Attachments
fj issue attachment upload 42 screenshot.png
fj issue attachment list 42
fj issue attachment download 42 1234 -o shot.png
fj issue attachment delete 42 1234

Pull requests

# Browse
fj pr list                                  # open pull requests in the current repo
fj pr list -s all -l bug,ui -L 50
fj pr view 42                               # details
fj pr view 42 --comments                    # with the full thread

# Create and change
fj pr create -t "Add feature" -b "Summary…" --base main --head feature/x
fj pr create -t "Fix startup crash" --editor --draft
fj pr close 42
fj pr reopen 42

fj pr uses the same repository/host resolution rules as the issue commands, so --repo, --host, FJ_REPO, FJ_HOST, and git remotes all work as expected.

Repositories

fj repo view                                # current repo
fj repo view owner/repo                     # another repo by slug
fj repo view https://codeberg.org/owner/repo
fj repo view --json name,description,default_branch

fj repo view prints summary metadata for the current repository or a repo you name explicitly.

Labels and milestones

fj label list
fj label create bug -c d73a4a -d "Something is broken"
fj label edit bug -n defect --color ff0000 --archive
fj label delete defect -y

fj milestone list -s all
fj milestone view "v1.2" --issues
fj milestone create "v1.3" --due-date 2026-12-01
fj milestone edit "v1.3" --title "v1.3.0" --remove-due-date
fj milestone close "v1.3.0"
fj milestone delete "v1.3.0" -y

Scripting

Output adapts to the destination: aligned and coloured on a terminal, tab-separated with no colour when piped — so fj issue list | cut -f1 is stable.

fj issue list --json                                  # full objects
fj issue list --json number,title,labels.name         # projected fields (dotted paths)
fj issue view 42 --json state,milestone.title
fj api "repos/{owner}/{repo}/issues" -q state=closed --paginate
fj api "repos/{owner}/{repo}/issues/42/comments" -f body="from the raw API"
fj api user --jq .login

fj api substitutes {owner}/{repo} from the resolved repository, defaults to POST when fields are given, and supports -X, --input, -i and --paginate.

Environment

Variable Purpose
FJ_TOKEN API token; overrides stored credentials
FJ_HOST default host, e.g. codeberg.org
FJ_REPO default repository, e.g. myorg/myrepo
FJ_EDITOR editor for issue/comment bodies (falls back to VISUAL, EDITOR)
FJ_BROWSER browser used by --web (falls back to BROWSER)
FJ_CONFIG_DIR override the config directory
NO_COLOR / --no-color disable colour
FJ_NO_PROMPT never prompt; fail instead (useful in CI)
FJ_DEBUG print stack traces on unexpected errors

Configuration

fj config list
fj config set default_repo myorg/myrepo
fj config set editor "code --wait"
fj config unset default_repo

Development

npm run build     # tsc -> dist/
npm test          # builds, then runs the node:test suite
npm run watch

Layout: src/api (typed client), src/commands (one module per command group), src/ui (colour, tables, prompts, editor), src/context.ts (repo/host resolution), src/config.ts (credential storage).

Notes and limitations

  • Locking conversations and transferring issues between repositories are not exposed by the Forgejo API v1, so fj does not implement them. fj issue view does report an issue's locked state.
  • Forgejo shares one numbering sequence between issues and pull requests, so fj issue view <n> will happily show a pull request; the header marks it as one.
  • --sort relevance and the cross-repository fj issue search filters (--assigned, --created, --mentioned) require authentication.
  • Verified against the Forgejo API v1 (swagger.v1.json, Forgejo 16 / Gitea 1.22 compatible) and exercised live against codeberg.org.