UNPKG

@impulselab/hepha

Version:

Command-line interface for the Hepha platform.

342 lines (257 loc) • 13.4 kB
--- name: cli description: > Drive the Hephaistos code factory from a terminal or a script with the `hepha` CLI: create a session from a prompt, follow its transcript, answer its question rounds, read the delivery, and escalate guardrail approvals to a human. Load when the task mentions hepha, Hephaistos, HEPHA_API_KEY, `hepha run`, exit code 3, waiting_human, awaiting_approval, question batches, or the `/api/v1` public API. metadata: type: core library: "@impulselab/hepha" library_version: "0.2" sources: - "impulse-studio/hephaistos:apps/cli/README.md" - "impulse-studio/hephaistos:apps/cli/src/commands/*.ts" - "impulse-studio/hephaistos:apps/cli/src/lib/follow.ts" - "impulse-studio/hephaistos:apps/cli/src/lib/session-outcome.ts" - "impulse-studio/hephaistos:packages/database/constants.ts" - "impulse-studio/hephaistos:packages/server/src/routers/v1/*/router.ts" - "impulse-studio/hephaistos:packages/server/src/routers/v1/schemas.ts" --- # Hephaistos from the command line ## What the platform does Hephaistos is a code factory: a demand goes in, a **session** carries it to a delivered result. One session is one accountable run; it owns an isolated cloud sandbox, a transcript, and a lifecycle you can drive from this CLI. | Task type | What the session produces | | -------------- | ----------------------------------------------------------- | | `implement` | A change on an existing repo, delivered as a pull request | | `new-project` | A new repo scaffolded, provisioned, planned and implemented | | `orchestrator` | A coordinated change split across delegated tasks | | `design` | A published design bundle and a public preview link | | `automate` | An automation created or edited, left inactive for a human | Three platform behaviours decide how a caller must drive a session: - **The question cycle is infinite.** An agent that is not confident asks instead of guessing. The session moves to `waiting_human` and stays there forever until a round is answered; it costs nothing and never times out. Answering is a normal scope, so a script or an agent can do it. - **Four guardrails are human-only**: merge a pull request, touch production, reply to an external client, push to a main branch. The session moves to `awaiting_approval` and an API key can read the request but never decide it. - **Delivery is proven, not claimed.** A finished code session carries a PR URL, a verification report (tests + recorded proof) and its CI status: read them to judge the result. Sessions run in parallel on the same repo without conflict: isolation is by branch, and collisions are resolved at PR time. ## Setup ```bash npm install --global @impulselab/hepha # Agents: a key minted by a human with `hepha keys create`. export HEPHA_API_KEY=hepha_xxxxxxxxxxxxxxxxxxxxxxxx hepha whoami --json ``` `whoami --json` reports the scopes actually in play; read it first and fail early instead of halfway through a task. | Variable | Effect | | --------------------------------- | --------------------------------------------------------------------------------------------------------- | | `HEPHA_API_KEY` | Act as an agent. **Takes precedence** over a stored `hepha login`. | | `HEPHA_TOKEN` | A device-flow token, instead of `~/.hepha/config.json`. | | `HEPHA_URL` | Platform base URL (a dev box points it at `http://localhost:3000`). | | `HEPHA_TIMEOUT_MS` | Per-request timeout, default `30000`. | | `HEPHA_FOLLOW_MAX_OUTAGE_SECONDS` | Make `--follow` give up this long after its first failure. Unset, it waits for the platform to come back. | Humans sign in with `hepha login` (device authorization, approved in a browser). It is the one interactive command and the one that does not honour `--json`. An agent should never run it. Scopes are `resource:action`: `sessions:read`, `sessions:write`, `sessions:export`, `questions:read`, `questions:answer`, `approvals:read`, `catalog:read`, `pull_requests:read`. A key created without `--scope` is read-only across every resource, and scopes can never be widened, only replaced by a new key. ## Core patterns ### Start a session and follow it to its outcome ```bash hepha repos --json # repoFullName must exist in the catalog hepha run "Add rate limiting to the public API" \ --repo impulse-studio/hephaistos \ --task-type implement \ --follow ``` `run --follow` creates the session, streams the transcript, and returns the moment the run finishes or needs a person. The exit code carries the outcome so nothing has to be parsed: | Code | Meaning | | ---- | ------------------------------------------------------- | | `0` | Session completed | | `1` | Failed, killed or abstained (message on stderr) | | `2` | Usage error (unknown flag, missing argument, bad value) | | `3` | **Blocked on a human**: a question round or a guardrail | ### Branch a script on the outcome ```bash result=$(hepha run "$PROMPT" --repo "$REPO" --follow --json) status=$? session=$(jq -r '.id // .result.id // empty' <<<"$result") case "$status" in 0) hepha sessions pr "$session" --json ;; 3) hepha questions list --session "$session" --json ;; *) exit "$status" ;; esac ``` Under `--json` the session is the document on success and rides in `.result` on a non-zero exit, so `.id // .result.id` names the session either way; the id is never printed anywhere else. Exit `3` is not a failure: it is the platform asking for input. Answer the round (or escalate the guardrail), then keep following. ### Answer a question round ```bash hepha questions list --json # batches waiting on you hepha questions show <batchId> # ids, options, recommendation hepha questions answer <batchId> \ --option q_01=2 \ --text q_02="Use Postgres, not SQLite" ``` `--option <questionId>=<optionId>[,<optionId>]` selects; `--text <questionId>=…` adds free text; both may target the same question. For an agent, one flag carries the whole round: ```bash hepha questions answer <batchId> \ --answers '[{"questionId":"q_01","selectedOptionIds":["2"]},{"questionId":"q_02","freeText":"Use Postgres"}]' ``` Then resume following: `hepha sessions logs <id> --follow`. ### Poll a transcript incrementally ```bash hepha sessions logs <id> --after 128 --limit 200 --json ``` `--after <sequence>` returns only what came after that event. The page carries `nextCursor` (pass it back as `--after`) and `hasMore`, which is `false` when you are caught up. `--follow` blocks until the session finishes or parks. ### Collect the delivery ```bash hepha sessions get <id> --json # status, prUrl, playbook hepha sessions verify <id> --json # test report and recorded proof hepha sessions pr <id> --json # CI status of the delivered PR hepha sessions checks <id> # re-run the repo's checks ``` ### Hand a guardrail to a human ```bash hepha approvals list --json # readable with approvals:read hepha approvals show <id> --json ``` Approving or rejecting requires a human session (`hepha login`), so an agent stops here and reports the pending approval to a person. ## Command reference | Command | Notes | | ----------------------------------------------------------------- | ------------------------------------------------------------------------ | | `hepha run "<prompt>"` | `--repo --branch --task-type --model --harness --skill --title --follow` | | `hepha sessions list` | `--status active\|waiting\|done --repo --search --page --limit --all` | | `hepha sessions get\|logs\|send\|pause\|resume\|kill\|trash <id>` | `logs --after <sequence> --follow`, `kill --reason "…"` | | `hepha sessions checks\|pr\|verify <id>` | Re-run checks · CI status · proof artifacts | | `hepha questions list\|show\|answer\|cancel` | `list --session <id>` | | `hepha approvals list\|show\|approve\|reject` | `approve`/`reject` are human-session only | | `hepha repos \| branches <owner/repo> \| models \| skills` | The catalog to read before creating a session | | `hepha keys list\|create\|revoke` | Human-session only | | `hepha login \| logout \| whoami` | `login` is interactive | Global flags: `--json` on every command that returns data, `--quiet` to drop the streamed transcript while following, `--version`, `--help`. ## Common Mistakes ### [CRITICAL] Retrying `hepha run` after exit 3 Wrong: ```bash hepha run "$PROMPT" --repo "$REPO" --follow || hepha run "$PROMPT" --repo "$REPO" --follow ``` Correct: ```bash hepha run "$PROMPT" --repo "$REPO" --follow status=$? case "$status" in 0) ;; # delivered 3) hepha questions list --json ;; # answer it, then follow again *) exit "$status" ;; # 1 failed · 2 usage: never remapped esac ``` Exit 3 means the existing session is parked on a question or a guardrail. Re-running creates a second session on the same repo while the first one waits forever. Source: apps/cli/src/lib/session-outcome.ts ### [HIGH] Approving a guardrail with an API key Wrong: ```bash export HEPHA_API_KEY=hepha_xxx hepha approvals approve "$GUARDRAIL_ID" ``` Correct: ```bash hepha approvals show "$GUARDRAIL_ID" --json # read it, then tell a human ``` `approvals:decide` is not a scope that exists; the route answers `403` to any key. `HEPHA_API_KEY` also takes precedence over a stored login, so a human whose shell exports it gets `403` on every human-only command until it is unset. Source: packages/server/src/routers/v1/approvals/router.ts (humanSessionMiddleware) ### [HIGH] Parsing the human output instead of `--json` Wrong: ```bash hepha sessions get "$ID" | grep -i "pr:" | awk '{print $2}' ``` Correct: ```bash hepha sessions get "$ID" --json | jq -r '.prUrl // empty' ``` The aligned text is padded and coloured for a terminal. Under `--json` stdout is one parseable document, including failures: `{"ok":false,"error":{…}}`, with the exit code unchanged. Source: apps/cli/src/lib/cli-output.ts ### [HIGH] Re-reading the whole transcript on every poll Wrong: ```bash while :; do hepha sessions logs "$ID" --json; sleep 5; done ``` Correct: ```bash after=0 while :; do page=$(hepha sessions logs "$ID" --after "$after" --limit 200 --json) after=$(jq -r "[$after, .nextCursor // 0, (.items[]?.sequence)] | max" <<<"$page") [ "$(jq -r '.hasMore' <<<"$page")" = "true" ] || sleep 3 done ``` Without `--after`, every poll re-downloads the transcript from the start and the consumer re-processes events it has already seen. Advance on the highest of both signals: `nextCursor` is `null` on the last page even when that page carried events, and an event the API cannot render is dropped from `items` while still consuming a sequence; following either one alone stalls the cursor or spins on the same page. Source: apps/cli/src/lib/follow.ts · packages/server/src/services/session-events-query.service.ts ### [MEDIUM] Sending `--answers` as an object Wrong: ```bash hepha questions answer "$BATCH" --answers '{"q_01":"2"}' ``` Correct: ```bash hepha questions answer "$BATCH" --answers '[{"questionId":"q_01","selectedOptionIds":["2"]}]' ``` `--answers` takes a non-empty JSON **array** of `{questionId, selectedOptionIds?, freeText?}`, and an entry carrying neither an option nor free text answers nothing; both fail with exit 2 before any request is sent. Source: apps/cli/src/commands/collect-answers.ts ### [MEDIUM] Creating a key without scopes, then writing with it Wrong: ```bash hepha keys create "ci bot" export HEPHA_API_KEY=hepha_xxx hepha run "…" --repo "$REPO" # 403 ``` Correct: ```bash hepha keys create "ci bot" --scope sessions:read --scope sessions:write \ --scope questions:read --scope questions:answer \ --scope approvals:read --scope catalog:read ``` A key created without `--scope` is read-only, and passing any scope **replaces** that default instead of adding to it, so the list has to carry everything the loop touches: write on sessions, both question scopes (a round nobody can answer parks forever) and `approvals:read`, without which the agent cannot even read the guardrail it is supposed to escalate. Source: packages/database/constants.ts · packages/server/src/routers/api-keys/mutations/create-api-key.ts