rickub/clipublic Fork
⑂ main
Commits
⬇ Clone ▾
git clone https://git.rickub.com/rickub/cli.git
git clone ssh://git@rickub.com/rickub/cli.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
Merge feat/staleness-notice into main aa87859 · on main · rickub · 5d ago
README.md

rickub CLI

rickub is the command-line interface to a rickub git host —
the smartest git in the universe, on the command line. It is a thin, standalone
HTTP client for the rickub JSON API (/api/v1), authenticated with a personal
access token (PAT). It imports none of the server's code: a clean client boundary.

Install

Download a release. Binaries for linux (amd64, arm64) and macos (amd64,
arm64) are attached to each release at https://rickub.com/rickub/cli/releases.
Assets are served at releases/download/<tag>/<asset>, and releases/latest
redirects to the newest release:

# newest release tag, e.g. v0.1.0 (or set it by hand)
tag=$(curl -fsSI https://rickub.com/rickub/cli/releases/latest | awk 'tolower($1)=="location:"{print $2}' | tr -d '\r' | sed 's#.*/##')
v=${tag#v}
os=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m); case "$arch" in x86_64) arch=amd64 ;; aarch64) arch=arm64 ;; esac

base=https://rickub.com/rickub/cli/releases/download/$tag
curl -fsSLO "$base/rickub_${v}_${os}_${arch}.tar.gz" && curl -fsSLO "$base/SHA256SUMS"
sha256sum -c SHA256SUMS --ignore-missing      # shasum -a 256 -c on macOS
tar -xzf "rickub_${v}_${os}_${arch}.tar.gz" rickub
sudo install rickub /usr/local/bin/rickub

Each archive holds the rickub binary plus README.md and LICENSE.

Build from source (Go 1.26+):

git clone ssh://git@rickub.com/rickub/cli.git
cd cli
go build -o rickub .   # produces ./rickub at the repo root

To stamp a version into the binary (releases use the bare version, no v):

go build -ldflags "-X rickub.com/rickub/cli/cmd.Version=$(cat VERSION)-dev" -o rickub .

Verify:

./rickub version

Authenticate

# browser (device) flow — the default. Prints a code, opens your browser,
# waits for you to approve the sign-in while logged in to the website, and
# stores the PAT the server mints. Nothing is copy-pasted.
rickub auth login
rickub auth login --host https://dev.rickub.com   # any rickub host
rickub auth login --scope read                    # a read-only token
rickub auth login --no-browser                    # print the URL, don't open it

To use an existing PAT from Settings → Tokens instead, prefer one of the two
forms that keep the secret out of your shell:

# 1. environment variable — nothing is written to disk
export RICKUB_TOKEN=rickub_pat_xxx
rickub repo list

# 2. stdin, for `auth login` to verify and store it
echo "$RICKUB_PAT" | rickub auth login --with-token --host https://rickub.com

There is also a --token rickub_pat_xxx flag on any command. Use it only when
neither of the above fits: arguments are visible to every process on the
machine via ps, and land in your shell history and in CI logs.

The stored token lives in ~/.config/rickub/config.yaml (mode 0600). The
browser flow mints a normal PAT named "CLI device login" — revoke it any time
in Settings → Tokens.

rickub auth status    # show the active host, where the token came from, and verify it
rickub auth logout    # remove the stored token for the active host

Tokens are bound to their host

A token stored by auth login is saved under the host it was verified
against
and is only ever sent back to that host. Pointing the CLI at a
different server — --host, RICKUB_HOST, or a typo — will not hand your
production credential to it; you get "no token stored for that host" instead.
Log in per host as needed:

rickub auth login --host https://rickub.com          # stored for rickub.com
rickub auth login --host http://localhost:3000       # stored separately
rickub auth logout --host http://localhost:3000      # removes only that one

A token you pass explicitly with --token or RICKUB_TOKEN is always honoured
for whatever host is in effect — that is your call to make, not the config's.

The CLI also prints a warning to stderr before sending a token to a host over
plain http://, unless that host is loopback (localhost, 127.0.0.1, [::1]),
where the request never reaches the network.

Config & environment

Effective host and token are resolved with this precedence (first wins):

Value Precedence
host --host flag → RICKUB_HOST env → config file → https://rickub.com
token --token flag → RICKUB_TOKEN env → config file entry for that host

Point the CLI at a dev instance with --host http://localhost:3000 (or set
RICKUB_HOST). XDG_CONFIG_HOME is honoured for the config file location.

The config file looks like this:

host: https://rickub.com
hosts:
  https://rickub.com:
    token: rickub_pat_…
  http://localhost:3000:
    token: rickub_pat_…

Command reference

Every command supports --help, and --json for raw JSON output instead of a table.
Every --body/-b takes literal text or - to read the body from stdin (a
heredoc works; trailing newlines, CRLF included, are trimmed). A body that is
empty after resolution is refused where one is required, never posted.

rickub auth login|status|logout

rickub repo list [--user H | --org H] [--page N] [--per-page N]
rickub repo create <name> [--org H] [--public|--private] [-d desc]
rickub repo view <owner/repo>
rickub repo edit <owner/repo> [--visibility public|private] [-d desc] [--default-branch B]
rickub repo rename <owner/repo> <new-name>       # needs ADMIN; breaks every existing clone URL
rickub repo transfer <owner/repo> <new-owner> [--yes]
                                                 # needs ADMIN here and create rights there;
                                                 # confirms first — you may not be able to move it back
rickub repo delete <owner/repo> [--yes]
rickub repo clone <owner/repo> [dir] [--protocol ssh|https] [-- git-args…]
rickub repo files <owner/repo> [path] [--ref R]
rickub repo cat <owner/repo> <path> [--ref R]
rickub repo commits <owner/repo> [ref] [--page N] [--per-page N]
rickub repo compare <owner/repo> <base...head>
rickub repo collaborator list <owner/repo>
rickub repo collaborator add  <owner/repo> <user> [--permission read|write|admin]
rickub repo collaborator remove <owner/repo> <user>

rickub pr list   [-R owner/repo] [--state open|closed|merged|all]
rickub pr view   <number> [-R owner/repo]
rickub pr create [-R owner/repo] --base B --head H --title T [-b body] [--draft] [--head-owner O --head-repo R]
rickub pr ready  <number> [-R owner/repo] [--undo]       # draft → ready for review (or back)
rickub pr merge  <number> [-R owner/repo] [--method merge|squash|ff-only]
rickub pr close  <number> [-R owner/repo]
rickub pr reopen <number> [-R owner/repo]
rickub pr edit   <number> [-R owner/repo] [-t "title"] [-b "text" | -b -]
                  # correct the title and/or the description in one call (author or
                  # repo writer); a field you do not name is left untouched, and
                  # passing neither flag is refused without sending a request
rickub pr comment <number> [-R owner/repo] -b "text"
                  # whole-MR conversation comment (any reader)
rickub pr comment <number> [-R owner/repo] -b "text" --file PATH --line N
                  [--side new|old] [--intent info|request_change|kudos] [--commit SHA]
                  # line-anchored review comment on the diff (needs WRITE access)
rickub pr comment <number> [-R owner/repo] -b "text" --reply-to <comment-id>
                  # reply in an existing line-comment thread
rickub pr line-comments <number> [-R owner/repo] [--page N] [--per-page N]
                  # list the MR's line comments with their ids, anchors and state
rickub pr resolve <number> <comment-id> [-R owner/repo] [--reopen]
rickub pr delete-comment <number> <comment-id> [-R owner/repo]
                  # retract a conversation comment (soft delete: a tombstone stays);
                  # ids are shown by `rickub pr view <number>`

rickub run list     [-R owner/repo]
rickub run view     <number> [-R owner/repo]
rickub run logs     <number> [-R owner/repo] [--job NAME | --failed]
                    # --job prints one job (case-insensitive; an unknown name is an
                    # error listing the run's real jobs, never the whole log);
                    # --failed prints every job whose status is not success.
                    # Both compose with --json.
rickub run rerun    <number> [-R owner/repo]
rickub run cancel   <number> [-R owner/repo]
rickub run dispatch [-R owner/repo] [--ref B]
rickub run watch    <number> [-R owner/repo] [--interval 2s] [--timeout 30m] [--logs]
                    # follow until terminal; exit 0 on success, 1 otherwise

rickub issue list    [-R owner/repo] [--state open|closed|all] [--page N]
rickub issue view    <number> [-R owner/repo]
rickub issue create  [-R owner/repo] -t "title" [-b "body" | -b - < file]
rickub issue edit    <number> [-R owner/repo] [-t "title"] [-b "body" | -b - < file]
                     # same rules as `pr edit`: name a field to change it, omit it to keep it
rickub issue close   <number> [-R owner/repo]
rickub issue reopen  <number> [-R owner/repo]
rickub issue comment <number> [-R owner/repo] -b "text"
rickub issue delete-comment <number> <comment-id> [-R owner/repo]
rickub issue label   <number> [-R owner/repo] --labels "bug,help wanted" | --clear
rickub issue milestone <number> [-R owner/repo] --milestone "v1.0" | --clear
rickub issue assign  <number> [-R owner/repo] --user H [--remove]
rickub issue labels  [-R owner/repo]

rickub label list   [-R owner/repo]
rickub label create <name> [-R owner/repo] [--color d73a4a]   # colors come from `label palette`
rickub label edit   <name> [-R owner/repo] [--name new] [--color hex]
rickub label delete <name> [-R owner/repo]
rickub label palette

rickub cache list   [-R owner/repo] [--page N] [--per-page N]    # CI dependency-cache entries
rickub cache delete <key> [-R owner/repo] [--scope refs/heads/main]  # purge a poisoned key (repo admin)

rickub milestone list   [-R owner/repo] [--state open|closed|all]
rickub milestone create [-R owner/repo] -t "v1.0" [-d desc] [--due YYYY-MM-DD]
rickub milestone close  <id> [-R owner/repo]
rickub milestone reopen <id> [-R owner/repo]
rickub milestone delete <id> [-R owner/repo]

rickub org view <handle>
rickub org members <handle>
rickub org teams <handle>

rickub search repos <query> [--page N] [--per-page N]

rickub api <METHOD> <path> [-f key=value] [-F key=value]   # raw escape hatch
rickub browse [owner/repo] [--print]
rickub version

Cloning

repo clone derives the git URL from the API host: ssh://git@<host>/<owner>/<repo>.git
by default, which your SSH keys authenticate, or https://git.<host>/<owner>/<repo>.git
with --protocol https, where the token authenticates that clone (git receives it
through its environment, never on the command line or in .git/config; later
fetches of a private repo over HTTPS need a git credential helper). Merge requests
open ready for review; pass --draft to open a draft, and pr ready flips it.

Repo selector

pr, run, issue, label, milestone, and cache subcommands take -R/--repo owner/repo. When omitted, the repo is
inferred from the current directory's git origin remote (any of https://,
ssh://, or git@host:owner/repo forms).

rickub api — raw escape hatch

Like gh api. PATH is relative to /api/v1 (a leading /api/v1 or / is
optional). --field/-f values are type-inferred (true/false/null/numbers);
--raw-field/-F forces a string. Fields become query parameters for GET/HEAD
and a JSON body otherwise.

rickub api GET /user
rickub api GET search/repos -f q=api
rickub api POST /repos -f name=demo -f visibility=public

Errors & exit codes

API errors are surfaced from the {error:{code,message}} envelope, e.g.
rickub: repository not found (not_found), and the process exits non-zero.

Pointing at a dev host

# run a rickub instance on a spare port, then:
echo "$DEV_PAT" | rickub auth login --with-token --host http://localhost:3000
rickub repo list --user <you>

The dev token is stored separately from your rickub.com token; both stay put.

Security

See SECURITY.md: where the token lives and how it is bound to
its host, the redirect and body-size policy, how git and the browser are
invoked, and what the security workflow checks.

Development

go build ./... && go test ./... && go vet ./... && gofmt -l .

CI runs those on every push and pull request (ci.yml), and security.yml
adds go mod verify, govulncheck, gosec, and staticcheck.

Layout:

main.go              # entrypoint; maps API errors to exit codes
cmd/                 # Cobra command tree (auth, repo, pr, issue, label, milestone, run, cache, org, search, api, browse)
internal/api/        # typed HTTP client for the rickub JSON API
internal/config/     # config load/save + host/token resolution and binding