forgejo-cli (fj)
fj is a CLI for Forgejo instances (self-hosted or Codeberg), written in Rust.
It mirrors gh's workflows: issues, PRs, repos, releases, actions, orgs, users.
Full command tree: references/commands.md
Setup
brew install forgejo-cli # macOS (already installed on this machine, v0.6.0)
fj completion zsh # optional: generate shell completions
Verify: fj --help lists top-level commands. There is no --version flag.
Authentication
Two ways (see fj auth --help):
fj auth login— OAuth in the browser. Only works for a built-in list of public instances (Codeberg, code.forgejo.org, Disroot, …). Self-hosted instances are not on the list — use method 2 instead.fj auth add-token— application token. For self-hosted servers:- Create a token at
https://$FORGEJO_HOST/user/settings/applications(grant it broad permissions —fj's feature set follows the token scope). - Run
fj -H "$FORGEJO_HOST" auth add-tokenand paste the token (or pass it as an argument; it's also read from stdin).
- Create a token at
Other auth commands: fj auth list (which instances you're logged into),
fj auth logout, fj auth use-ssh (prefer SSH for supported instances).
Verify login: fj -H "$FORGEJO_HOST" whoami prints your username
or an error (unauthorized: token is required = not logged in).
Picking the instance and repo
fj resolves the instance from, in order: the -H, --host <HOST> flag, then
the git remote of the current directory (-C, --cwd changes that directory).
-H <host>— target a specific host (e.g.-H "$FORGEJO_HOST")-R, --remote <REMOTE>— pick which local git remote to use (defaultorigin)-r, --repo <owner/name>— operate on a specific repo (e.g.-r <owner>/<repo>)
Per-machine instance
Export the instance host once per machine in your shell profile:
export FORGEJO_HOST=your-forgejo.example.com
Always pass -H "$FORGEJO_HOST" explicitly. The repo remotes can be SSH
to a private address (ssh://git@<lan-ip>:222/...), and without -H fj
would auto-detect that instead of the public HTTPS host. Use
fj -H "$FORGEJO_HOST" <cmd> everywhere; cloning/pushing still happens via
the SSH remote (git clone/git push are untouched by fj).
Repo/issue IDs are either a bare number (42) or owner-qualified
(<owner>/<repo>#42). PR IDs are usually optional when the
current branch is tracking the PR; prefix with ^ to target the parent/upstream
repo from a fork (e.g. fj pr checkout ^16).
Core workflows
Issues
fj issue create "Title" # --body or $EDITOR; --template <file> if repo requires it
fj issue create "Title" --web # create in the browser
fj issue templates # list available templates
fj issue edit <id> title "New title" # or `body`; blank → $EDITOR
fj issue edit <id> labels --add "bug" --remove "wontfix"
fj issue assign <id> user1 user2 # / fj issue unassign
fj issue comment <id> "text" # / fj issue edit <id> comment <idx>
fj issue view <id> [comments|comment <idx>]
fj issue search # list/search issues in the repo
fj issue close <id> --with-msg "Done" # optional closing comment
fj issue browse <id>
Pull requests
git switch -c feature && git commit -m "..."
git push --set-upstream origin feature
fj pr create "This is a PR!" # --body / --body-file / $EDITOR
fj pr create "WIP: ..." # "WIP: " prefix marks the PR as draft
fj pr create -A # --autofill: title/body from commits
fj pr create --base ^ "PR against upstream" # ^ = parent repo (works from forks)
fj pr create -aA # --agit + --autofill; --agit files the PR
# straight from local commits, no push needed
fj pr view | fj pr status --wait # status: mergeability + CI, --wait blocks
fj pr checkout ^16 # checkout a PR locally
fj pr edit labels --add "Priority/High"
fj pr merge -M rebase --delete # -M merge|rebase|rebase-merge|squash|manual
fj pr close <id> --with-msg "outdated"
fj pr review list # NOTE: only `list` exists; can't submit reviews yet
Repos, releases, tags, actions
fj repo create example --description "..." --private [--push --remote codeberg]
fj repo fork forgejo/forgejo --name myfork
fj repo migrate <URL> <NAME> --include issues,prs,labels,releases # only *to* Forgejo
fj repo view | fj repo readme | fj repo clone <repo> [PATH]
fj repo labels ... # manage repo-level issue labels
fj repo star | unstar | star-status | watch | unwatch
fj repo edit | fj repo units ... # settings; rename is web-only
fj release create|edit|delete|list|view|browse|asset ...
fj tag create|delete|list|view ...
fj actions tasks # list workflow runs
fj actions dispatch <workflow.yml> <ref> --inputs key=value
fj actions variables list|create <NAME> <VALUE>|delete <NAME>
fj actions secrets list|create|delete # same shape; values never editor-typed
Users & orgs
fj user view [user] | user repos | user orgs | user activity | user search
fj user follow | unfollow | block | unblock
fj user key upload <path-to-.pub> | key list | key view | key delete
fj user gpg ... # needs `gpg` on PATH
fj org create <name> [--full-name --visibility --email --website]
fj org view | org list | org members | org activity
fj org repo create <org> <repo>
fj org label add <name> <hexcolor> | list | edit | delete
fj org team create <org> <team> --read-permissions ... --write-permissions ...
fj org team member add|rm|list | org team repo add|rm|list
Gotchas (verified against v0.6.0)
- Wiki vs binary: the wiki documents
fj auth add-key; the installed binary usesfj auth add-token. Thefj auth loginerror message also saysadd-key— that text is stale too. Trustfj <cmd> --helpover both. fj auth loginonly works for a built-in list of public instances (Codeberg, code.forgejo.org, Disroot, …). For self-hosted hosts it fails with "Your installation of fj doesn't supportloginfor …" — that's expected; usefj auth add-tokeninstead.- No
--versionflag —fj --versionerrors; usefj versioninstead. - Omitting
--body/--body-fileopens$EDITOR. - If a repo disables blank issues,
--template <file>is required on create. fj pr merge -m <msg>takes an optional value;-tsets the merge commit title.- First-time SSH hosts must be in
~/.ssh/known_hostsor you getinvalid or unknown remote ssh hostkey— fix withssh-keyscan -H <host> >> ~/.ssh/known_hosts. - Repo migration is one-way: only into Forgejo. Use GitHub's tooling to go the other way.
--style minimaldisables fancy output/colors (good for piping).