334 lines
23 KiB
Markdown
334 lines
23 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.
|
|
|
|
## 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 below (`close 42`, `pr-create …`, `pr-status …`, …) still work
|
|
as **hidden back-compat aliases** during the transition and are dropped from
|
|
`--help`; prefer the namespaced form. They'll be removed in a later sweep.
|
|
|
|
```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
|
|
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 with token auto-injected
|
|
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 token
|
|
is injected into the clone's `origin` remote so `pr-create --agit`
|
|
(default remote `origin`) can push without re-auth.
|
|
|
|
`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.
|