No description
  • Zig 97.7%
  • Shell 2.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Arzaroth ceaca814fd
All checks were successful
CI / gate (push) Successful in 3m1s
Release / release (push) Successful in 9m57s
[master] chore(release): 0.4.0
2026-09-29 22:25:55 +02:00
.claude/skills [feature/gh-parity] docs: gh parity in the brain, README, changelog, roadmap, todo and release skill 2026-09-29 13:38:44 +02:00
.github/workflows [feature/todo] chore(ci): pin actions/checkout to v4.4.0's commit, the same on GitHub and Forgejo's action mirrors 2026-09-29 14:23:34 +02:00
brain [feature/live-fixes] docs: the max review's fixes in the brain and changelog 2026-09-29 22:20:08 +02:00
mise-tasks [feature/installer] test: the gate runs install.sh offline against a fixture release; smith --version without HOME 2026-09-29 20:24:42 +02:00
src [feature/live-fixes] test: max-review gaps - a TZif zone file's footer, local versus UTC days, a fall-back midnight, EST5EDT, impossible dates and years, overflowing TZ rules, merge retry counts, the milestone in issue view, variable delete --yes, every repo list filter, pflag booleans 2026-09-29 22:19:43 +02:00
.gitignore [feature/gh-parity] build: strip release builds, mise run dist cross-compiles the release archives 2026-09-29 13:35:21 +02:00
.mise.toml [feature/installer] chore(check): the gate lints install.sh and fails when its Zig version drifts from .mise.toml 2026-09-29 19:35:23 +02:00
build.zig [feature/installer] feat(build): -Dversion overrides the version smith reports, for development builds 2026-09-29 19:28:56 +02:00
build.zig.zon [master] chore(release): 0.4.0 2026-09-29 22:25:55 +02:00
CHANGELOG.md [master] chore(release): 0.4.0 2026-09-29 22:25:55 +02:00
CLAUDE.md [feature/todo] docs: config and alias surface, the pager, prompts, create's last step, filtered paging, blocked runs, the 204 fix and the release workflow in the brain, README, changelog and CLAUDE.md 2026-09-29 14:29:28 +02:00
install.sh [feature/installer] fix(install): max-review fixes - everything runs from main, https only through redirects, server tags and shas are checked before use, the binary is staged under a random name, Ctrl-C stops, clear errors with hints, up-to-date and upgrade messages, a PATH hint that knows fish and shadowing 2026-09-29 20:24:42 +02:00
LICENSE [master] chore: zig skeleton, mise toolchain and tasks, ci 2026-09-29 03:00:49 +02:00
README.md [feature/installer] docs: max-review fixes - what SHA256SUMS does and does not prove, uninstalling, unsupported systems, the offline check; cross-forge verification in the todo 2026-09-29 20:24:42 +02:00
ROADMAP.md [feature/todo] docs(roadmap): the config and alias surface shipped; --latest is out of Forgejo's reach 2026-09-29 14:29:48 +02:00
TODO.md [master] docs(todo): pr view's wording for merged pull requests 2026-09-29 22:25:07 +02:00

smith

A command-line client for Forgejo, in the spirit of gh and glab: clone repositories, open, review and merge pull requests, work issues, follow Actions runs, cut releases, without leaving the terminal.

Status: feature complete against gh's command surface, wherever Forgejo's API can back it (ROADMAP.md says what cannot).

Why another one

Forgejo already has tea (Gitea's CLI) and fj (forgejo-cli). smith copies gh's command surface specifically, so muscle memory carries over: smith pr checkout 42, smith run watch, smith issue list --label bug. It is also written in Zig, mostly because why not.

Install

curl -fsSL https://raw.githubusercontent.com/Arzaroth/smith/master/install.sh | sh

This installs the latest release into ~/.local/bin (--dir or SMITH_INSTALL_DIR to change it): a static binary for Linux (x86_64, aarch64) or macOS (Intel, Apple silicon), checked against the release's SHA256SUMS (which catches a corrupted download; it comes from the same release, so it is not a signature). Running it again upgrades in place, or says smith is up to date. Pass options after sh -s --:

curl -fsSL https://raw.githubusercontent.com/Arzaroth/smith/master/install.sh | sh -s -- --version 0.3.0
curl -fsSL https://raw.githubusercontent.com/Arzaroth/smith/master/install.sh | sh -s -- --dev

--dev builds the tip of master from source (a minute or two) with Zig 0.16.0: yours if it is on PATH, else through mise, else a download from ziglang.org checked against its published checksum; smith --version then says <version>-dev+<commit>. --from forgejo downloads from git.arzaroth.com (releases from 0.3.0 on) instead of GitHub. Other systems (Windows, FreeBSD, 32-bit ARM) can build it (below). To uninstall, delete the binary; smith auth logout first removes its tokens from the keyring, and ~/.config/smith holds its settings.

Getting started

smith auth login --hostname git.example.com   # opens your browser to sign in
smith repo clone owner/repo
cd repo

Login opens the instance's sign-in page in your browser, like gh auth login, and renews itself afterwards. Without a browser, or with --password, it asks for your username, password and two-factor code and creates a token for smith; --with-token reads one from standard input for scripts. The token goes to your system keyring (Secret Service on Linux, the keychain on macOS) when there is one. Login also finds the SSH hostname your instance advertises, so [email protected]:owner/repo remotes map back to git.example.com.

Inside a clone, smith works out the host and owner/repo from the git remotes (upstream, then origin; smith repo set-default picks another); -R [HOST/]OWNER/REPO overrides it anywhere. Public repositories on an https remote work without logging in.

Commands

smith pr list | view | diff | create | checkout | merge | checks | review
smith pr status | update-branch | ready | comment | close | reopen | edit
smith issue list | view | create | close | reopen | comment | edit
smith repo clone | view | list | create | fork | edit | sync | archive | delete | set-default
smith run list | view | watch | cancel | download
smith workflow list | run
smith release list | view | create | edit | upload | download | delete
smith label list | create | edit | delete | clone
smith milestone list | view | create | edit | close | reopen | delete
smith secret list | set | delete          # --org ORG or --user for other scopes
smith variable list | get | set | delete
smith search repos | issues | prs
smith status                              # what needs you across the host
smith notification list | read
smith ssh-key | gpg-key list | add | delete
smith org list
smith auth login | status | switch | logout | token
smith alias set | list | delete | import
smith config get | set | unset | list
smith api <endpoint> [-X METHOD] [-f key=value] [-F key=typed] [--paginate]
smith browse [<n> | <path>[:<line>]] [--settings] [--actions]
smith completion bash | zsh | fish
smith help [<command>] | reference | skill

Every command has --help. Commands that show API objects take --json to print them as Forgejo sent them, -q/--jq EXPR to filter them with jq, and -t/--template for a Go-style template:

smith pr list -q '.[].head.ref'
smith release view -t '{{.tag_name}}: {{len .assets}} assets{{"\n"}}'
smith issue list -t '{{range .}}#{{.number}} {{.title}} ({{timeago .updated_at}}){{"\n"}}{{end}}'
smith pr list -t '{{range .}}{{tablerow (printf "#%v" .number | autocolor "green") .title .head.ref}}{{end}}'
  • Drafts are Forgejo's WIP: title prefix; pr ready removes it.
  • pr checks reads commit statuses, so any CI that posts them shows up, not only Forgejo Actions. It exits 1 when a check failed and 8 while one is pending.
  • pr checkout handles pull requests from forks through refs/pull/<n>/head, as pr-<n> when the fork's branch is named like one of yours.
  • Piped, lists print plain numbers, whole text, timestamps and a state column, like gh's machine format. Exit codes follow gh: 1 on failure, 4 when authentication failed, 8 while checks are pending.
  • Deleting anything asks first on a terminal, and needs --yes without one.

Several hosts and accounts

Log in to as many instances as you like; inside a clone, the remotes decide which one a command talks to. Outside one, smith uses the default host: the first you logged in to, until smith auth switch --hostname other.example.

Logging in to the same host as another user adds an account rather than replacing the first. smith auth switch flips between them (--user picks one), smith auth status shows which is active, and auth logout / auth token take --user.

SMITH_TOKEN is only ever sent to the default host, so a token cannot leak to another instance; SMITH_TOKEN_<HOST> sets one for a specific host.

Aliases and preferences

smith alias set co 'pr checkout'
smith alias set bugs 'issue list --label bug --assignee $1'
smith alias set standup '!smith status && smith notification list'
smith config set editor 'nvim'
smith config set pager 'less -R'
smith config set -h git.example.com git_protocol https
smith alias import aliases.yml             # gh's alias file

$1… take the alias's arguments (a missing one is an error), extra arguments are appended, and a leading ! (or --shell) runs the rest with sh. An alias can never take a smith command's name, and replacing one needs --clobber; alias import reads gh's YAML alias file. Preferences (git_protocol for new logins, or per host with -h, editor, browser, pager, and prompt disabled to never ask) and aliases live in config.zon.

Configuration

~/.config/smith/ (or $XDG_CONFIG_HOME/smith, or $SMITH_CONFIG_DIR) holds hosts.zon (mode 0600: hosts and accounts, and tokens when there is no keyring) and config.zon (preferences and aliases).

Variable Effect
SMITH_TOKEN Token for the default host only (its exact name, https unless configured)
SMITH_TOKEN_<HOST> Token for one host, e.g. SMITH_TOKEN_GIT_EXAMPLE_COM
SMITH_HOST Default host when not in a clone
SMITH_CONFIG_DIR Where the config files live
SMITH_KEYRING none to keep tokens in hosts.zon, or secret-tool / security
SMITH_LOGIN_TIMEOUT Seconds the browser login waits (default 300)
SMITH_EDITOR, VISUAL, EDITOR Editor for bodies (config set editor sits after SMITH_EDITOR)
SMITH_BROWSER, BROWSER Browser for --web (config set browser sits after SMITH_BROWSER)
SMITH_PAGER, PAGER Pager for lists, views and diffs on a terminal (config set pager sits between them; cat, or SMITH_PAGER= empty, for none)
SMITH_PROMPT_DISABLED Never prompt, as if config set prompt disabled
SMITH_JQ The jq program --jq runs
NO_COLOR, CLICOLOR_FORCE Colour off, colour on

For coding agents

smith help reference prints every command, flag, exit code and environment variable as one Markdown page, generated from the command tree. smith help skill prints a skill that teaches Claude Code (or any agent that reads SKILL.md files) to use smith: install it once with

mkdir -p ~/.claude/skills/smith
smith help skill > ~/.claude/skills/smith/SKILL.md

Shell completion

smith completion bash > ~/.local/share/bash-completion/completions/smith
smith completion zsh > "${fpath[1]}/_smith"
smith completion fish > ~/.config/fish/completions/smith.fish

Building

Toolchain and tasks come from mise:

mise install          # Zig 0.16.0, shellcheck
mise run build        # zig-out/bin/smith
mise run test         # unit and invocation tests (-Dtest-filter=... for a subset)
mise run check        # the gate: zig fmt --check, shellcheck, tests, ReleaseSafe build
mise run dist         # release archives for Linux and macOS in dist/

The only dependency is the Zig standard library: HTTP, TLS and JSON included, so the binary is static. Git operations shell out to your git, so SSH keys and credential helpers behave exactly as they do in your terminal; --jq uses your jq, and the keyring its command-line helper. How it all fits together is in brain/.

Licence

MIT, see LICENSE.