credentials.md gains the forge-token row and drops the claim that the forge token never passes through the store. setup.md says plainly that an agent spawned on the hive alone, ruth's bootstrap included, now gets no forge user from anything. CLI references regenerated. Refs #3782
353 lines
25 KiB
Markdown
353 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
|
|
autogenerated 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: `$HIVE_FORGE_TOKEN_FILE` (the token the agent fetched from
|
|
the swarm secret store), else `$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.
|
|
`client::Client::from_env` assumes no single repo by default.
|
|
|
|
## Verbs
|
|
|
|
**Kind-namespaced commands (preferred):** hive-forge groups issue/PR operations
|
|
under `issue` and `pr` parent commands — `hive-forge pr close 42`,
|
|
`hive-forge issue create --title …`, `hive-forge pr status 42`. The
|
|
`pr <verb>` / `issue <verb>` forms validate the number's kind (for example `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).
|
|
|
|
```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 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 issue 42's dependencies (issues it's blocked by)
|
|
hive-forge issue dependency 42 add 40 41 # issue 42 is now blocked by 40 and 41
|
|
hive-forge issue dependency 42 remove 40 # drop the dependency on 40
|
|
hive-forge pr dependency 55 add 42 # same verb, PR-scoped (Forgejo shares the issue/PR index)
|
|
hive-forge issue reaction 42 # list issue 42's reactions
|
|
hive-forge issue reaction 42 add heart # react to issue 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 pr diff 42 # unified diff (lockfile hunks collapsed by default)
|
|
hive-forge pr 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 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 --branch foo # re-run CI without an empty commit (dispatches a fresh run; --run n also works unless that run was PR-triggered; --pr refuses, see below)
|
|
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
|
|
|
|
Multiple 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` autodetects 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), for example to catch up a long thread; for PRs, review bodies (approve/request-changes/comment summaries) come along 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 | `pr diff <n>` | unified diff |
|
|
| Is this PR ready to merge — mergeable + CI + reviews | `pr status <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`)
|
|
|
|
<!-- vale write-good.Passive = NO -->
|
|
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:
|
|
<!-- vale write-good.Passive = YES -->
|
|
|
|
```
|
|
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)
|
|
```
|
|
|
|
<!-- vale write-good.Passive = NO -->
|
|
`clone` derives the dest dir from the repo basename (override with a
|
|
positional arg); it passes `--branch` / `--depth` 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`.
|
|
<!-- vale write-good.Passive = YES -->
|
|
|
|
`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 (`<n>`, positional — `--pr <n>` also still works):
|
|
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 a PR number (0 only when CI is green
|
|
AND the PR is mergeable AND no review requests changes), so it composes:
|
|
`hive-forge pr status 42 && echo ready`. `--sha` mirrors the CI
|
|
verdict alone.
|
|
|
|
```
|
|
hive-forge pr status 42
|
|
# PR #42: feat(...): ... # lint:allow: sample CLI output, not a real tag
|
|
# 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 **can't 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), enables branch protection (operator-team merge approval
|
|
required — you can't self-merge), and returns the clone URL 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 it initialises an agent's config
|
|
repo.
|
|
|
|
**`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
|
|
|
|
<!-- vale write-good.Passive = NO -->
|
|
- **Read-before-comment guard:** `comment` refuses to post when forge still
|
|
has an unread notification for the thread (that is, 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 (hive-forge prints the
|
|
canonical URL itself).
|
|
- `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 keys that route 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 verb tries 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) 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 `--run <n>` (the same run number `ci-log` /
|
|
`artifact-get` take — resolves the branch + workflow from that run) or
|
|
`--branch <name>` (directly). `--pr <n>`, and `--run <n>` against a run
|
|
that was itself PR-triggered, both refuse instead of dispatching:
|
|
a workflow-dispatch run writes no commit status, so it can't clear a
|
|
red `(pull_request)` check on that PR's sha — re-run from the web UI
|
|
instead. `--workflow <file>` picks the workflow file for `--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, this path ignores `<n>`/`--comment`.
|
|
- **Don't** 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`, hive-forge applies labels as a follow-up call once
|
|
it parses the PR number 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 isn't 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.
|
|
<!-- vale write-good.Passive = YES -->
|