`docs/tools/forge.md` said the flat forms "still work as hidden back-compat aliases" and named `pr-create` and `pr-status` as its examples. Both refuse now: nine flat verbs parse only far enough to print the namespaced form that replaced them. The doc had no way to be read correctly, because the seven that do still work are hidden too and `--help` shows neither group. Naming the split here is the only place the difference is visible. At the enum the kind-agnostic seven now read as staying rather than as "still work", and `credential-helper` is marked as belonging to neither group: it is hidden because git invokes it, so a sweep reading `hide = true` as "deprecated" would remove checkout auth. Refs #3974.
355 lines
25 KiB
Markdown
355 lines
25 KiB
Markdown
# hive-forge CLI
|
|
|
|
`hive-forge` is the Forgejo API wrapper available in every agent
|
|
container (installed via `nix/agent-modules/forge.nix`, on `PATH` as a
|
|
proper Rust binary). Use it instead of ad-hoc curl pipelines.
|
|
|
|
This page is the curated guide. For the exhaustive flag-by-flag reference
|
|
auto-generated from the binary's own command tree, see
|
|
[`forge-cli.md`](forge-cli.md) — emitted by the hidden `hive-forge
|
|
markdown-docs` subcommand and kept in lockstep with the code by the
|
|
`hive-forge-docs` flake check (CI fails if the committed copy drifts).
|
|
Regenerate with `nix build .#hive-forge && ./result/bin/hive-forge
|
|
markdown-docs > docs/tools/forge-cli.md`.
|
|
|
|
## Credentials and repo defaults
|
|
|
|
- Credentials: `$HYPERHIVE_STATE_DIR/forge-token`
|
|
- Active repo resolves, highest priority first: global `-r/--repo` flag
|
|
> the `origin` remote of the cwd's git checkout > `$HIVE_FORGE_REPO`
|
|
(last-resort override, unset by default) > a hard error. No single
|
|
repo is assumed by default — see `client::Client::from_env`.
|
|
|
|
## Verbs
|
|
|
|
**Kind-namespaced commands (preferred):** issue/PR operations are grouped
|
|
under `issue` and `pr` parent commands — `hive-forge pr close 42`,
|
|
`hive-forge issue create --title …`, `hive-forge pr status --pr 42`. The
|
|
`pr <verb>` / `issue <verb>` forms validate the number's kind (e.g. `pr close`
|
|
refuses an issue number, which the old generic `close` couldn't). Run
|
|
`hive-forge pr --help` / `hive-forge issue --help` for the full subcommand
|
|
list (show/create/edit/status/merge/reviews/commits/diff/view/comment/
|
|
comments/close/reopen/labels/assign/dependency/reaction/timeline as applicable).
|
|
|
|
The flat forms are two different groups, and `--help` hides both, so the
|
|
difference cannot be seen from the CLI and is stated here instead:
|
|
|
|
- **Renamed — these no longer run.** `issue-create` `issue-edit` `pr-create`
|
|
`pr-status` `pr-merge` `pr-reviews` `pr-commits` `pr-assign-reviewer` `diff`
|
|
parse only far enough to print the namespaced form that replaced them, and
|
|
are removed outright in a later release.
|
|
- **Kind-agnostic — these stay.** `view` `comment` `comments` `close` `assign`
|
|
`labels` `timeline` are the only way to act on a number without already
|
|
knowing whether it is an issue or a PR, which no namespaced form offers.
|
|
Hidden, but supported; prefer `issue <verb>` / `pr <verb>` when the kind is
|
|
known, since those validate it.
|
|
|
|
```bash
|
|
hive-forge pr close 42 # close a PR (kind-validated)
|
|
hive-forge pr reopen 42 # reopen a closed PR (kind-validated)
|
|
hive-forge issue close 42 # close an issue (kind-validated)
|
|
hive-forge issue reopen 42 # reopen a closed issue (kind-validated)
|
|
hive-forge pr status --pr 42 # PR health (mergeable / CI / reviews)
|
|
hive-forge issue create --title "..." --body "..."
|
|
# --- flat aliases below remain valid (hidden) ---
|
|
hive-forge view 42 # title + body + comments (marks thread read)
|
|
hive-forge comments 42 # list all comments (human-readable; marks thread read); for PRs, review bodies merged in
|
|
hive-forge comments 42 --tail 10 # last 10 comments (count-then-page; efficient on long threads)
|
|
hive-forge --json comments 42 # same as above, JSON array (global flag)
|
|
hive-forge comment 42 --body "..." # post comment (inline body)
|
|
hive-forge comment 42 --body-file - <<EOF # ...or pipe a HEREDOC
|
|
multi-line body
|
|
EOF
|
|
hive-forge comment 42 --body "..." --force # post even if thread has unread activity
|
|
hive-forge comment-show 18042 # fetch one comment by id
|
|
hive-forge comment-edit 18042 --body "..." # edit a comment
|
|
hive-forge assign 42 damocles
|
|
hive-forge close 42
|
|
hive-forge labels 42 add feature
|
|
hive-forge issue dependency 42 # list #42's dependencies (issues it's blocked by)
|
|
hive-forge issue dependency 42 add 40 41 # #42 is now blocked by #40 and #41
|
|
hive-forge issue dependency 42 remove 40 # drop the #40 dependency link
|
|
hive-forge pr dependency 55 add 42 # same verb, PR-scoped (Forgejo shares the issue/PR index)
|
|
hive-forge issue reaction 42 # list #42's reactions
|
|
hive-forge issue reaction 42 add heart # react to #42 with :heart:
|
|
hive-forge issue reaction 42 --comment 18042 add +1 # react to a specific comment instead
|
|
hive-forge issue reaction 42 remove heart # remove your own :heart: reaction
|
|
hive-forge issue reaction 1 --list-allowed # this instance's actual configured shortcodes
|
|
hive-forge issue-create --title "..." --body "..."
|
|
hive-forge issue-create --title "..." --body "..." --label area/ops --label type/bug # repeatable
|
|
hive-forge issue-edit 42 --title "new title"
|
|
hive-forge pr edit 42 --title "new title" # same edit, PR-kind-validated
|
|
hive-forge pr 42 # PR metadata as JSON
|
|
hive-forge pr-create --title "..." --head my-branch --push # also `git push forge my-branch`
|
|
hive-forge pr-create --title "..." --head my-branch --label area/ops # repeatable, same as issue-create
|
|
hive-forge pr-reviews 42 # list reviews; inline comments included per review
|
|
hive-forge pr-reviews 42 --approve # submit APPROVED review
|
|
hive-forge pr-reviews 42 --request-changes -m "msg" # submit REQUEST_CHANGES review
|
|
hive-forge pr-reviews 42 --comment -m "msg" # submit COMMENT review
|
|
hive-forge pr assign-reviewer 42 argus # set argus as a requested reviewer on the PR
|
|
hive-forge pr assign-reviewer 42 argus --remove # withdraw the request
|
|
hive-forge pr assign-committer 42 damocles # add damocles to the PR's assignee list
|
|
hive-forge diff 42 # unified diff (lockfile hunks collapsed by default)
|
|
hive-forge diff 42 --full # include unfiltered lockfile hunks
|
|
hive-forge list # open issues/PRs
|
|
hive-forge list --kind pr --state all --page 2 # page 2 of all PRs (walk --page 1,2,… with --limit as page size for a repo-wide sweep)
|
|
hive-forge list --search "trust bundle" --state all # full-text over title AND body — the duplicate check
|
|
hive-forge list --milestone 11 --state all # what's left in a milestone (name or id, repeatable)
|
|
hive-forge milestone # list milestones
|
|
hive-forge branches deployed/ # filter branches by pattern
|
|
hive-forge tree-sha main # git tree SHA for a ref
|
|
hive-forge -r other-org/other-repo pr 7 # target a different repo
|
|
hive-forge lint unassigned # open issues/PRs with no assignee
|
|
hive-forge lint no-reviewer # PRs with zero formally requested reviewers
|
|
hive-forge lint no-reviewer --reviewer argus # PRs where argus specifically isn't a requested reviewer
|
|
hive-forge lint stale-branches --days 14 # branches with no recent activity, each with its merge outcome (PR #n merged / closed unmerged / no PR)
|
|
hive-forge lint assignments # per-assignee open item count
|
|
hive-forge lint unlabeled --scope type # open issues/PRs with no exclusive type/* label (any scope works, e.g. --scope area)
|
|
hive-forge pr-status --pr 42 # PR health: mergeable, CI, reviews, last comment (exit 0 = ready)
|
|
hive-forge pr-status --sha <sha> # CI-only fast path for an explicit commit sha
|
|
hive-forge pr-merge 42 # merge (refuses unless mergeable + CI not red + no changes-requested); deletes head branch
|
|
hive-forge pr-merge 42 --method rebase # rebase-merge instead of a merge commit (no squash option)
|
|
hive-forge pr-merge 42 --keep-branch --force # keep the head branch; override the readiness gate
|
|
hive-forge timeline 42 # audit trail: closes, label changes, assignments, commit refs
|
|
hive-forge attach-issue 42 /path/to/file # upload a file attachment to an issue; prints download URL
|
|
hive-forge attach-comment 18042 /path/to/file # upload a file attachment to a comment; prints download URL
|
|
hive-forge attachment-get <uuid> # download an attachment; prints resolved path to stdout
|
|
hive-forge artifact-get pr1ma-paper-pdf --run 51 # download a CI run's Actions artifact zip (run number from the run-page URL)
|
|
hive-forge ci-log --run 51 # print a CI run's job step logs (run number from the run-page URL); --job i / --step i to narrow
|
|
hive-forge ci-rerun --pr 42 # re-run CI without an empty commit (dispatches a fresh run; --run n / --branch name also work)
|
|
hive-forge subscription --watch # subscribe to repo notifications
|
|
hive-forge subscription --unwatch # unsubscribe
|
|
hive-forge subscription --list # list every repo you watch (audit the notification firehose)
|
|
hive-forge -r internal/knowledge clone # clone with creds auto-injected
|
|
hive-forge -r internal/knowledge pr-create --agit --topic foo --title "..." # open PR via AGit (no fork)
|
|
# --- repo management ---
|
|
hive-forge repo-create my-repo # create under authenticated user; prints URL
|
|
hive-forge repo-create my-repo --org myorg --private --auto-init # under an org, private, with initial commit
|
|
hive-forge repo-add-collaborator alice # add write collaborator (default permission)
|
|
hive-forge repo-add-collaborator alice --permission read # read-only collaborator
|
|
hive-forge repo-labels # list every label defined on the repo
|
|
hive-forge repo-labels bug # filter by name substring
|
|
hive-forge repo-labels --json # full label objects (id, name, color, description)
|
|
```
|
|
|
|
### Which read verb when
|
|
|
|
Several verbs read an issue/PR and look overlapping, but each has a
|
|
distinct output shape — pick by what you need:
|
|
|
|
| Need | Verb | Shape |
|
|
| ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| The full human view of one item — title, body, **and** every comment in order | `view <n>` | prose dump; **marks the thread read** (clears the read-before-comment guard). Top-level `view` auto-detects issue-or-PR by number; scoped `issue view <n>` / `pr view <n>` do the same when you already know the kind |
|
|
| Just the structured metadata of one item (state, labels, assignees, head sha…) | `issue <n>` / `pr <n>` | JSON object |
|
|
| Only the comments (skip the body), e.g. to catch up a long thread; for PRs, review bodies (approve/request-changes/comment summaries) are merged in too | `comments <n>` [`--tail N`] | comment + review-body list; **marks read** |
|
|
| One specific comment by its id | `comment-show <id>` | single comment |
|
|
| The audit trail — closes, label/assignee changes, commit refs, merges (no comment bodies) | `timeline <n>` | event list |
|
|
| The code change | `diff <n>` | unified diff |
|
|
| Is this PR ready to merge — mergeable + CI + reviews | `pr-status --pr <n>` | health view; exit code = merge-readiness |
|
|
| The review verdicts + inline review comments | `pr-reviews <n>` | review list |
|
|
|
|
Rule of thumb: **`view` first** when you're about to act on a thread (it
|
|
shows everything and clears the unread guard so your follow-up `comment`
|
|
isn't rejected); the JSON verbs (`issue`/`pr`) when a script needs one
|
|
field; `pr-status` when the only question is "can I merge it".
|
|
|
|
### Contributing to a read-only repo (`clone` + `pr-create --agit`)
|
|
|
|
Agents are read-only collaborators on some repos (e.g.
|
|
`internal/knowledge`) and so can't push branches. Forgejo's AGit flow
|
|
lets a read-only user open a PR by pushing the current `HEAD` to the
|
|
magic ref `refs/for/<base>/<topic>`. Two verbs cover the workflow:
|
|
|
|
```
|
|
hive-forge -r internal/knowledge clone # clone, auth handled for you
|
|
cd knowledge
|
|
# add / edit / delete any files, then commit normally
|
|
git add -A && git commit -m "add foo runbook"
|
|
hive-forge -r internal/knowledge pr-create --agit \
|
|
--topic foo-runbook \ # groups pushes into ONE PR; reuse to update it
|
|
--title "add foo runbook" \
|
|
[--body "details"] # PR description (also accepts --body-file)
|
|
```
|
|
|
|
`clone` derives the dest dir from the repo basename (override with a
|
|
positional arg); `--branch` / `--depth` are passed through. The clone
|
|
URL and the `origin` remote it leaves behind carry no credentials —
|
|
`clone` instead configures `origin`'s `credential.helper` to invoke
|
|
`hive-forge credential-helper` (a hidden verb, not meant to be run by
|
|
hand), which git calls fresh on every fetch/push. That's what lets
|
|
`pr-create --agit` (default remote `origin`) push without re-auth,
|
|
without a durable token sitting in the checkout's `.git/config`.
|
|
|
|
`pr-create --agit` prints the PR URL. Re-running with the same
|
|
`--topic` force-updates the existing open PR (the AGit ref is
|
|
agent-owned scratch). Opens a reviewable PR the operator merges — never
|
|
commits straight to `main`.
|
|
|
|
`hive-forge <verb> --help` prints the full signature for any verb.
|
|
|
|
### `pr-status`
|
|
|
|
One-stop PR health view (`--pr <n>`): mergeable state, CI checks,
|
|
requested reviewers + review verdicts, and the last-comment timestamp —
|
|
the things you need to know whether a PR is ready to merge (CI must pass
|
|
before merge). `--sha <sha>` is a CI-only fast path for a raw commit.
|
|
|
|
A failing/erroring CI context prints its job link. The process exit code
|
|
is a **merge-readiness** verdict for `--pr` (0 only when CI is green AND
|
|
the PR is mergeable AND no review requests changes), so it composes:
|
|
`hive-forge pr-status --pr 42 && echo ready`. `--sha` mirrors the CI
|
|
verdict alone.
|
|
|
|
```
|
|
hive-forge pr-status --pr 42
|
|
# PR #42: feat(...): ...
|
|
# state: open (mergeable: yes)
|
|
# CI: e39a87ea3949: ✅ success (1 context(s))
|
|
# ✅ CI / nix flake check (pull_request): success — Successful in 1m50s
|
|
# reviewers: (none requested)
|
|
# reviews: ✅ argus: APPROVED
|
|
# last comment: 2026-06-05T19:13:28+02:00 by argus
|
|
```
|
|
|
|
Note: review verdicts come from _formal_ Forgejo reviews (the
|
|
approve / request-changes API). Reviewers who post their verdict as a
|
|
plain comment show under `last comment`, not `reviews`.
|
|
|
|
### Repo management
|
|
|
|
Agents **cannot create repos directly via forge token** — Forgejo
|
|
disables push-to-create and the agent token doesn't have the Create
|
|
scope. Two paths exist depending on where the repo should live:
|
|
|
|
**Agent repos (`agents/<name>`)** — Use the `mcp__hyperhive__create_repo`
|
|
MCP tool (requires the `forge` tool group). hive-c0re creates the repo in
|
|
the c0re-owned `agents/` org, adds you as a write collaborator (not
|
|
owner), and enables branch protection (operator-team merge approval
|
|
required — you cannot self-merge). Clone URL is returned immediately.
|
|
This is the standard path for agents that need a working repo.
|
|
|
|
**Other repos** — Use the CLI verbs below (`repo-create` / `repo-add-collaborator`).
|
|
These use the agent's own forge token so the repo lands under the agent's
|
|
user account or an org the agent belongs to.
|
|
|
|
**`repo-create <name>`** — create a repo under the authenticated user
|
|
and print its URL. Key flags:
|
|
|
|
- `--org <ORG>` — create under an org namespace instead
|
|
- `--private` — private repo (default: public)
|
|
- `--auto-init` — seed an initial commit so the repo is non-empty and
|
|
immediately cloneable; omit for a bare repo you push into
|
|
- `--description <TEXT>` — repo description
|
|
|
|
**`repo-add-collaborator <user>`** — grant a forge user access to the
|
|
active repo (see the repo-resolution chain above). Companion to
|
|
`repo-create`. The
|
|
`--permission` flag accepts `read` / `write` (default) / `admin`.
|
|
`hive-c0re` uses this internally when an agent's config repo is
|
|
initialised.
|
|
|
|
**`repo-labels [PATTERN]`** — list every label defined on the repo,
|
|
optionally filtered by a name substring (case-sensitive). Distinct from
|
|
`labels <n>` which shows the labels on one specific issue/PR. Use this
|
|
to discover valid label names before triaging or to audit the label set.
|
|
`--json` emits full label objects (id, name, color, description).
|
|
|
|
## Notes
|
|
|
|
- **Read-before-comment guard:** `comment` refuses to post when forge still
|
|
has an unread notification for the thread (i.e. someone commented since you
|
|
last read it). This prevents accidentally replying to old activity without
|
|
seeing the new context. Read the thread with `hive-forge view <n>` or
|
|
`hive-forge comments <n>` (which mark the notification read and clear the
|
|
block), or pass `--force` to skip the guard.
|
|
- `comment --body "..."` with backticks in the body: always use
|
|
`--body-file -` with a HEREDOC to avoid shell escaping issues.
|
|
- `pr-create --push` also runs `git push forge <head>` and suppresses
|
|
the post-push "Create a pull request" hint (we print the canonical
|
|
URL ourselves).
|
|
- `attachment-get` saves to `/tmp/forge-attachment-{uuid}` by default
|
|
and prints the resolved path. Pass `-o -` to stream to stdout.
|
|
- `artifact-get <name> --run <n>` downloads a CI Actions artifact. `<n>`
|
|
is the run number from the run-page URL (`/actions/runs/<n>`, which
|
|
`pr-status` surfaces as a CI context's target_url). Forgejo 15 serves
|
|
artifacts only via the web route, not REST, and that route is keyed by
|
|
the run's internal global id, so the verb translates the run number
|
|
first. Saves a zip to `/tmp/forge-artifact-<name>.zip` by default; pass
|
|
`-o -` to stream to stdout.
|
|
- `ci-log --run <n> [--job i] [--step i] [--attempt n]` prints a CI
|
|
run's job step logs. `<n>` is the run number from the run-page URL
|
|
(same value `artifact-get` takes; `pr-status` surfaces it as a CI
|
|
context's target_url). Two log sources, tried in **completeness
|
|
order**: the **durable persisted-log download** the run page's "view
|
|
raw logs" link uses (`…/runs/<n>/jobs/<job>/attempt/<a>/logs`, a flat
|
|
whole-job log) is tried first — complete once it exists, which covers
|
|
any run that has already finished; it's only absent while the job is
|
|
still running, in which case the verb falls back to the web run-view
|
|
**streamer** the run page polls (rich per-step framing, but only a
|
|
snapshot of the live `act_runner` task record, so a still-buffering
|
|
multi-minute phase can come back thin). Passing `--step` reverses
|
|
that order — only the streamer honors per-step framing (the persisted
|
|
log is flat), so `--step` goes straight to the streamer and an
|
|
out-of-range index surfaces as a hard error instead of silently
|
|
falling back. `--job` selects the job (0-based, default 0);
|
|
`--attempt` picks the run attempt for the durable path (default 1;
|
|
re-runs increment it). `--json` wraps the output.
|
|
- `ci-rerun` re-runs CI without pushing an empty commit (the old
|
|
retrigger path, which littered PR history). Forgejo has no token-usable
|
|
REST endpoint to re-run an _existing_ run (the run-page rerun buttons
|
|
are CSRF-gated web routes a token POST 404s), so this dispatches a
|
|
**fresh** run of the workflow via the workflow-dispatch API
|
|
(`POST …/actions/workflows/<workflow>/dispatches {"ref":"<branch>"}`).
|
|
Resolve the branch with exactly one of: `--pr <n>` (the PR's head
|
|
branch), `--run <n>` (the same run number `ci-log` / `artifact-get`
|
|
take — resolves the branch + workflow from that run), or `--branch
|
|
<name>` (directly). `--workflow <file>` picks the workflow file for
|
|
`--pr` / `--branch` (default `ci.yml`). Dispatch re-runs the whole
|
|
workflow — there is no single-job variant.
|
|
- `issue dependency <n> add <dep...>` / `pr dependency <n> add <dep...>`
|
|
set "blocked by" links via Forgejo's dependency feature — the
|
|
operator's preferred way to track blocking relationships over labels
|
|
(labels can go stale; dependencies are structured and show in the
|
|
forge UI's dependency panel). Same-repo only, matching the web UI (no
|
|
cross-repo dependency support). `list` (the default action) and `add`/
|
|
`remove` all print the resulting dependency list as JSON.
|
|
- `issue reaction <n> add <content>` / `pr reaction <n> add <content>` add
|
|
an emoji reaction (Forgejo's shortcode vocabulary — `+1`, `heart`,
|
|
`rocket`, …, same set the web UI's picker offers, not a raw emoji
|
|
character); `--comment <id>` targets a specific comment's reactions
|
|
instead of the issue/PR itself. `list`/`add`/`remove` all print the
|
|
resulting reaction list as JSON, same shape as `dependency`. `view`,
|
|
`issue`, `pr`, and `comment-show` always show a reaction summary;
|
|
`comments --show-reactions` adds one per comment shown (opt-in — one
|
|
extra request per comment, no server-side inline count). `reaction
|
|
<n> --list-allowed` prints the instance's actual configured shortcode
|
|
list (`GET /settings/ui`'s `allowed_reactions`) instead of guessing —
|
|
instance-global, `<n>`/`--comment` are ignored on this path.
|
|
- Do NOT use raw `curl` for forge access -- the CLI handles auth,
|
|
error checking, and output formatting.
|
|
- `issue-create --label <name>` / `pr-create --label <name>` are
|
|
repeatable and take the same spelling `labels <n> add` does. **An
|
|
unknown name is an error, not a silent drop** — the command fails
|
|
listing the names that didn't resolve plus every label the repo has,
|
|
so it's fixable from the message without a second call. On
|
|
`pr-create --agit`, labels are applied as a follow-up call once the
|
|
PR number is parsed back out of the push output (the AGit push
|
|
itself has no label field), so they're silently skipped if that
|
|
parse fails — same fallback as the deferred multi-line body.
|
|
- `list --label <name>` / `list --milestone <name>` are repeatable and
|
|
validated the same way, for a sharper reason: the forge **discards** a
|
|
filter value it doesn't recognise, so a typo returns the
|
|
**unfiltered** list rather than an empty one. That doesn't waste a
|
|
query, it inverts the answer — "is anything open in this milestone"
|
|
comes back as every open issue. `--milestone` takes a title or an id,
|
|
and closed milestones count as valid (filtering on a shipped one is a
|
|
normal query).
|
|
- ⚠️ `--assignee` / `--author` are deliberately **not** validated: someone
|
|
who has left still legitimately appears on old issues, so a login that
|
|
isn't a current member is not necessarily a typo.
|
|
- `list --limit N` is a *request*: the forge clamps page size to its own
|
|
`api.MAX_RESPONSE_ITEMS` (50 by default), so `--limit 400` returns at
|
|
most 50 rows. The stderr trailer reports the real total from the
|
|
response's `X-Total-Count` (`… 50 of 187 shown … 137 more`), so trust
|
|
the trailer, not the row count, when deciding whether you've seen
|
|
everything.
|