- TypeScript 97.7%
- JavaScript 2.3%
| src | ||
| test | ||
| .gitignore | ||
| api-spec.json | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
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:
-
--repo OWNER/REPO(also accepts a full URL, orhost/owner/repo) -
FJ_REPO -
the git remote of the current checkout (
origin, thenupstream; override with--remote) -
default_repofrom 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
fjdoes not implement them.fj issue viewdoes 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 relevanceand the cross-repositoryfj issue searchfilters (--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.