@hagicode/cli 0.1.0-dev.5.1.b85b83a → 0.1.0-dev.52.1.13cffe6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,20 @@
1
1
  # HagiCode CLI
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/%40hagicode%2Fcli?logo=npm)](https://www.npmjs.com/package/@hagicode/cli)
3
+ [![npm version](https://img.shields.io/npm/v/%40hagicode%2Fcli?logo=npm&color=cb3837)](https://www.npmjs.com/package/@hagicode/cli)
4
+ [![npm downloads](https://img.shields.io/npm/dm/%40hagicode%2Fcli?logo=npm&color=2d8cf0)](https://www.npmjs.com/package/@hagicode/cli)
5
+ [![license](https://img.shields.io/npm/l/%40hagicode%2Fcli?color=ffd43b)](https://www.npmjs.com/package/@hagicode/cli)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D24.0.0-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
4
7
 
5
- `@hagicode/cli` provides the published `hagi` command for project, proposal, chat, and AutoTask management against a HagiCode backend API.
8
+ `@hagicode/cli` provides the published `hagi` command, which lets you drive a HagiCode backend from a terminal, scripts, or an AI agent without opening the app. Manage projects, proposals (generate, execute, archive, and send), local Sagas, vaults and their files, chat sessions, HagiTasks, scheduled plans, and skills (local list, enable, disable and uninstall, gallery search and install, and trusted providers). Every command supports a shared `--json` output for scripting, and an embedded Hagi skill under [`skills/`](skills/) guides agent workflows.
6
9
 
7
10
  ## Requirements
8
11
 
9
- - Node.js 20+
10
- - npm compatible with `10.9.2`
11
- - Access to a HagiCode backend API
12
+ - Node.js 24+
13
+ - Access to a HagiCode backend API for backend-selected commands; Saga commands with `--path` work locally without one, except `saga session create`, which needs the backend to find the registered project
12
14
 
13
15
  ## Installation
14
16
 
15
- Use the published npm package for normal CLI usage. You do not need to clone `repos/cli` to run `hagi`.
17
+ Use the published npm package for normal CLI usage. You do not need to clone this repository to run `hagi`.
16
18
 
17
19
  ### Zero-install usage
18
20
 
@@ -43,10 +45,22 @@ The examples below use `hagi ...` after the package is available on your `PATH`.
43
45
  hagi --help
44
46
  hagi project list --json
45
47
  hagi proposal list --json
48
+ hagi vault list --json
46
49
  hagi chat list --json
47
- hagi autotask create --title "Auto commit" --project-id <project-id> --prompt-id auto-compose-commit.en-US
50
+ hagi hagitask list
51
+ hagi hagitask create --title "Auto commit" --project-id <project-id> --prompt-id auto-compose-commit.en-US
48
52
  ```
49
53
 
54
+ ## Embedded Skills
55
+
56
+ If you are integrating Hagi CLI into an agent workflow, start from the embedded skill package in this repository:
57
+
58
+ - [`skills/README.md`](skills/README.md) - repository-level discovery entrypoint
59
+ - [`skills/hagi/SKILL.md`](skills/hagi/SKILL.md) - canonical Hagi skill contract
60
+ - [`skills/hagi/references/`](skills/hagi/references/) - focused package usage, session data, proposal flow, maintainer, and validation guidance
61
+
62
+ The same folder is also shipped as the HagiCode server's built-in skill `hagi`, so an agent running inside a HagiCode session can answer questions about the user's own projects, proposals, sessions, vaults and plans without setup. Edit `skills/hagi/` here; the built-in copy is recopied from it.
63
+
50
64
  ## Shared Runtime Flags
51
65
 
52
66
  Every command family supports the same global runtime flags:
@@ -62,14 +76,118 @@ Example:
62
76
  hagi --base-url https://api.example.com --token "$HAGI_TOKEN" project list --json
63
77
  ```
64
78
 
79
+ ## Saga Commands
80
+
81
+ Each Saga command requires exactly one project selector: `--path <directory>` or `--project-id <id>`. Missing, empty,
82
+ or conflicting selectors exit with code `2`. `--path` is resolved on the CLI host, relative to the current directory,
83
+ and does not read backend configuration or contact the backend; backend flags do not change local behavior. The one
84
+ exception is `saga session create`, which looks up the registered project through the backend (see below). Quote paths
85
+ that contain spaces.
86
+
87
+ ```bash
88
+ hagi saga init --path "./My Project"
89
+ hagi saga method run --json
90
+ hagi saga new demo --path "./My Project" --json
91
+ hagi saga list --path "./My Project"
92
+ hagi saga status demo --project-id <project-id> --json
93
+ hagi saga items demo --path "./My Project" --json
94
+ hagi saga claim demo --path "./My Project" --session orchestrator-1 --json
95
+ hagi saga check demo --path "./My Project" --phase run --json
96
+ hagi saga tick demo T1 --path "./My Project" --token "$SAGA_EXECUTION_TOKEN" --json
97
+ ```
98
+
99
+ Write and check commands (`claim`, `assert-owner`, `release`, `freeze`, `check`, `tick`, `block`, `set-status`, `amend`,
100
+ `archive`) run in the CLI itself. The CLI installs only the ignore rule and reads the six method prompts from its own package with `hagi saga method <name>`; there is no standalone Saga tool and no project-local copy of the methods. Mutating commands read the execution token
101
+ from `--token` or `SAGA_EXECUTION_TOKEN`; with `--project-id`, `--token` is rejected because it is the backend token, so use
102
+ `SAGA_EXECUTION_TOKEN` there. Exit code `0` means success, `1` a refusal or failed check, and `2` a usage error.
103
+
104
+ `saga session create --saga <id> --path <root> --title <title> --role implementation|verification --master <session-id> [--item <id>]`
105
+ creates one Worker chat session and records it in `runtime/sessions.md`. Unlike the other Saga commands it calls the
106
+ backend even with `--path`: the registered project whose real path matches `<root>` supplies the project ID, and no match
107
+ or several matches is a refusal. The execution token comes only from `SAGA_EXECUTION_TOKEN`; `--token` is refused with
108
+ either selector. The session carries `saga.*` metadata. The command sends no message and does not archive the session.
109
+ See the [Saga command reference](skills/hagi/references/saga.md#worker-sessions) for the options, metadata keys and exit codes.
110
+
111
+ With `--project-id`, the CLI uses the normal base URL, token, and language settings to resolve the project through the
112
+ backend. The returned project path must exist on the machine running the CLI; it is not a remote filesystem path.
113
+
114
+ `saga init` installs only the runtime ignore rule in `.hagicode/sagas/.gitignore`, merged additively, and creates no
115
+ `prompts/` directory. Existing project-local `prompts/` files are left untouched and are no longer read. `saga method <name>`
116
+ prints a published method (`goal`, `research`, `acceptance`, `plan`, `run`, `amend`) from the installed package, verified
117
+ against its manifest, with `--json` returning `{ name, sha256, content }`. `freeze` records the method digests in
118
+ `contract.lock`, and `check --phase run --json` reports method drift as a notice that never fails the check. Initialization
119
+ refuses redirected or unsafe targets and leaves identical resources untouched on repeat runs. See the
120
+ [Saga command reference](skills/hagi/references/saga.md) for outcomes, safety behavior, and JSON details.
121
+
122
+ For offline use, install the package or use a cached `npx` package. An uncached `npx @hagicode/cli` invocation may need
123
+ network access to download the package even though `saga ... --path` itself does not use a backend, except `saga session create`, which does.
124
+
125
+ ## Connection Setup
126
+
127
+ Every backend operation resolves the base URL on each invocation, in this order:
128
+
129
+ 1. `--base-url <url>` flag
130
+ 2. `HAGI_API_BASE_URL` environment variable
131
+ 3. `baseUrl` in the local config file: `$XDG_CONFIG_HOME/hagi/config.json`, or `~/.config/hagi/config.json` when `XDG_CONFIG_HOME` is unset
132
+
133
+ ```json
134
+ {
135
+ "baseUrl": "https://hagicode.example.com",
136
+ "steer": false
137
+ }
138
+ ```
139
+
140
+ If none of the three is set, a backend operation exits with code `2` and sends no backend request. Saga commands using
141
+ `--path` bypass this resolution and do not read backend configuration, except `saga session create`, which resolves its project through the backend. The `steer` key is the default for `hagi chat send`,
142
+ `hagi proposal send`, and `hagi hagitask send` (see [Steer](#steer-send-option)); it must be `true` or `false`.
143
+
144
+ Operation commands send requests without a credential by default. Pass `--token` (or set `HAGI_API_TOKEN`) only when your backend requires one.
145
+
146
+ When the backend cannot be reached, the CLI names the base URL and exits with code `60`. A backend failure is reported differently: it prints the HTTP-equivalent status of the gRPC status and the backend error message, and exits with a status-mapped code.
147
+
148
+ ```text
149
+ $ hagi --base-url http://127.0.0.1:1 project list
150
+ Cannot reach HagiCode backend at http://127.0.0.1:1. Check that the backend is running and the base URL is correct.
151
+
152
+ $ echo $?
153
+ 60
154
+ ```
155
+
65
156
  ## Command Overview
66
157
 
67
158
  ```text
68
159
  hagi
69
- |-- project list | create | update | delete
70
- |-- proposal list | create | generate-name | optimize-description | generate | annotate | execute | archive | status | complete | send
160
+ |-- project list | repositories | create | update | delete
161
+ | `-- monospecs show | init | set | validate | reload | clone | remove-checkout
162
+ |-- proposal list | create | optimize-description | generate | annotate | execute | archive | status | complete | send | get | messages
163
+ |-- saga init | new | list | status | items | claim | assert-owner | release | freeze | check | tick | block | set-status | amend | archive
164
+ |-- vault list | create | update | delete
165
+ | `-- files list | preview
71
166
  |-- chat list | create | archive | delete | send
72
- `-- autotask create | send
167
+ |-- hagitask list | schema | create | send | get | messages
168
+ |-- plan list | get | create | update | enable | disable | delete | run
169
+ |-- skills local list | enable | disable | uninstall
170
+ | gallery search | detail | install
171
+ | trusted list | upsert | delete | reset
172
+ `-- agent lock acquire | status | renew | heartbeat | release | requisition | list
173
+ ```
174
+
175
+ The `send` commands of `chat`, `proposal`, and `hagitask` also take `--steer` and `--no-steer`, and `HAGI_SEND_STEER` sets the default. See [Steer](#steer-send-option).
176
+
177
+ ## Example Setup
178
+
179
+ The examples in the family sections run in order against a local backend. They assume:
180
+
181
+ - the backend listens on `http://127.0.0.1:35167`
182
+ - `hagi` is on `PATH`
183
+ - `json_field` reads one field from `--json` output
184
+
185
+ ```bash
186
+ export HAGI_API_BASE_URL=http://127.0.0.1:35167
187
+ json_field() { node -pe "JSON.parse(require('node:fs').readFileSync(0, 'utf8'))$1"; }
188
+
189
+ mkdir -p /tmp/cli-examples/repos/cli
190
+ PROJECT_ID=$(hagi project create --name "CLI Examples" --path /tmp/cli-examples --json | json_field '.project.id')
73
191
  ```
74
192
 
75
193
  ## Project Commands
@@ -81,102 +199,222 @@ hagi project list
81
199
  hagi project list --json
82
200
  ```
83
201
 
84
- Create a project:
202
+ List the repositories of one project. The read-only command prints each repository's name, path relative to the project root, display name, type and branch. These paths are the values a proposal's repository scope names:
203
+
204
+ ```bash
205
+ hagi project repositories --id "$PROJECT_ID"
206
+ hagi project repositories --id "$PROJECT_ID" --json
207
+ ```
208
+
209
+ Create a project. The backend creates the directory if it does not exist. The new id is read from `project.id`:
85
210
 
86
211
  ```bash
87
- hagi project create \
212
+ DEMO_PROJECT_ID=$(hagi project create \
88
213
  --name "CLI Demo" \
89
- --path /workspace/cli-demo \
90
- --description "Automation sandbox"
214
+ --path /tmp/cli-demo \
215
+ --description "Automation sandbox" \
216
+ --json | json_field '.project.id')
91
217
  ```
92
218
 
93
219
  Update only part of a project record. The CLI resolves current project data first so omitted fields are preserved:
94
220
 
95
221
  ```bash
96
- hagi project update --id <project-id> --description "Updated description"
97
- hagi project update --id <project-id> --name "Renamed project" --path /workspace/new-path
222
+ hagi project update --id "$DEMO_PROJECT_ID" --description "Updated description"
223
+ hagi project update --id "$DEMO_PROJECT_ID" --name "CLI Demo renamed"
98
224
  ```
99
225
 
100
- Delete a project:
226
+ Delete the demo project:
101
227
 
102
228
  ```bash
103
- hagi project delete --id <project-id>
229
+ hagi project delete --id "$DEMO_PROJECT_ID"
104
230
  ```
105
231
 
106
- ## Proposal Commands
232
+ ## MonoSpecs Commands
107
233
 
108
- List proposal sessions:
234
+ `project monospecs` manages a project's MonoSpecs configuration (`monospecs.yaml`) and the local checkouts of its repositories. The CLI only sends requests: the backend reads and writes the files and runs Git on its own host, so `--base-url` may point at a remote backend.
235
+
236
+ | Command | Purpose |
237
+ | --- | --- |
238
+ | `show --id <id>` | Print the version, repo directory, config path, commit-when-archive flag and one row per repository with its local Git state. `--yaml` adds the YAML preview. |
239
+ | `init --id <id>` | Initialize the configuration. An initialized project returns its existing document. |
240
+ | `set --id <id>` | Change metadata or repositories. Options: `--version`, `--repo-directory`, `--commit-when-archive <true\|false>`, repeatable `--add-repository <path>` and `--remove-repository <path>`, and `--clone`. `--url`, `--name`, `--display-name` and `--type` apply to a single `--add-repository`. |
241
+ | `validate --id <id>` | Validate the configuration. Exits `30` and still prints every error when it is invalid. |
242
+ | `reload --id <id>` | Reread `monospecs.yaml` on the backend host and refresh the Git state. |
243
+ | `clone --id <id>` | Clone configured repositories that are missing. Repeatable `--path <path>` selects repositories; `--include-optional` adds optional ones when no `--path` is given. |
244
+ | `remove-checkout --id <id> --path <path>` | Delete the local checkout of one configured repository. `--force` allows a checkout with uncommitted changes, unpushed commits or no upstream branch. |
109
245
 
110
246
  ```bash
111
- hagi proposal list --project-id <project-id>
112
- hagi proposal list --include-archived --json
247
+ hagi project monospecs show --id "$PROJECT_ID"
248
+ hagi project monospecs set --id "$PROJECT_ID" --commit-when-archive true
249
+ hagi project monospecs set --id "$PROJECT_ID" --add-repository repos/api --url https://github.com/example/api.git --clone
250
+ hagi project monospecs set --id "$PROJECT_ID" --remove-repository repos/api
251
+ hagi project monospecs validate --id "$PROJECT_ID" --json
252
+ hagi project monospecs clone --id "$PROJECT_ID" --path repos/web
253
+ hagi project monospecs remove-checkout --id "$PROJECT_ID" --path repos/web
113
254
  ```
114
255
 
115
- Create a proposal session only:
256
+ Safety rules:
257
+
258
+ - `set` reads the current document, applies only the options given and saves the complete document. Local Git state (branch, sync state and similar) is never written back; `code` and `metadata` are kept.
259
+ - `set` rejects a duplicate `--add-repository`, a `--remove-repository` that is not configured, and companion options with more than one addition, before anything is saved. With `--clone`, the configuration is saved first; a failed clone leaves it saved and exits `30`.
260
+ - `clone` and `remove-checkout` act only on repositories listed in the configuration. The backend confines paths to the project root and clones only over `https`, `http`, `ssh` and `git`, so a `file://` URL or a plain path is reported as `failed`.
261
+ - `remove-checkout` refuses a checkout with uncommitted changes, unpushed commits or no upstream unless `--force` is given, and it never removes a path that is not a standalone repository root. It deletes a directory on the backend host and leaves `monospecs.yaml` unchanged.
262
+
263
+ Exit codes for these commands: `2` usage error (no request was sent), `20` backend validation error (every field error is listed), `24` project not found, `29` another clone or removal is running or a checkout is blocked, `30` invalid configuration or a failed clone.
264
+
265
+ ## Proposal Commands
266
+
267
+ List proposal sessions for the example project:
116
268
 
117
269
  ```bash
118
- hagi proposal create \
119
- --project-id <project-id> \
120
- --chief-complaint "Add CLI completion for session workflows" \
121
- --default-title
270
+ hagi proposal list --project-id "$PROJECT_ID"
271
+ hagi proposal list --include-archived --json
122
272
  ```
123
273
 
124
- Generate a proposal name explicitly:
274
+ Every proposal names its writable repositories and its read-only repositories. Take the paths from `hagi project repositories --id "$PROJECT_ID" --json`, and begin the chief complaint with a `Repository scope` section: a `Writable repositories:` list and a `Read-only repositories:` list, one `- <path>` entry per line, with `- none` for an empty list. The scope is a text convention in the chief complaint; `proposal create` has no scope flags. The format and a worked example are in [`skills/hagi/references/proposal-flow.md`](skills/hagi/references/proposal-flow.md#repository-scope).
275
+
276
+ Create a proposal session only. The new id is read from `session.sessionId`:
125
277
 
126
278
  ```bash
127
- hagi proposal generate-name --session-id <session-id>
279
+ PROPOSAL_ID=$(hagi proposal create \
280
+ --project-id "$PROJECT_ID" \
281
+ --chief-complaint "Repository scope
282
+ Writable repositories:
283
+ - repos/cli
284
+ Read-only repositories:
285
+ - none
286
+
287
+ Add CLI completion for session workflows" \
288
+ --default-title \
289
+ --json | json_field '.session.sessionId')
128
290
  ```
129
291
 
130
- Optimize the description explicitly:
292
+ Update status and send a follow-up message:
131
293
 
132
294
  ```bash
133
- hagi proposal optimize-description \
134
- --session-id <session-id> \
135
- --description "Improve the proposal framing" \
136
- --optimization-direction "Focus on implementation risk"
295
+ hagi proposal status --session-id "$PROPOSAL_ID" --status Reviewing
296
+ hagi proposal send --session-id "$PROPOSAL_ID" --content "Please continue"
137
297
  ```
138
298
 
139
- Generate and execute the proposal as separate steps:
299
+ Steer the message into the running turn, from the environment, or with a normal send that overrides the environment:
140
300
 
141
301
  ```bash
142
- hagi proposal generate --session-id <session-id> --enable-explore-mode
143
- hagi proposal execute --session-id <session-id>
302
+ hagi proposal send --session-id "$PROPOSAL_ID" --content "Please continue" --steer
303
+ HAGI_SEND_STEER=1 hagi proposal send --session-id "$PROPOSAL_ID" --content "Please continue"
304
+ hagi proposal send --session-id "$PROPOSAL_ID" --content "Please continue" --no-steer
144
305
  ```
145
306
 
146
- Submit structured annotation payloads from a file or stdin:
307
+ The next steps are AI lifecycle actions and need a backend with an AI executor. `optimize-description` writes the optimized title, description, and proposal name to the session in one backend step, and `generate` needs that name. Without the AI executor, or before the name exists, the backend answers with a 400 that names the missing state:
147
308
 
148
309
  ```bash
149
- hagi proposal annotate --session-id <session-id> --input annotations.json
150
- cat annotations.json | hagi proposal annotate --session-id <session-id> --input - --json
310
+ hagi proposal optimize-description --session-id "$PROPOSAL_ID" --description "Add CLI completion for session workflows" --default-title
311
+ hagi proposal generate --session-id "$PROPOSAL_ID" --proposal-content-language en-US --enable-explore-mode
312
+
313
+ cat > annotations.json <<'EOF'
314
+ {"files":[{"fileName":"proposal.md","filePath":"proposal.md"}]}
315
+ EOF
316
+ hagi proposal annotate --session-id "$PROPOSAL_ID" --input annotations.json
317
+ cat annotations.json | hagi proposal annotate --session-id "$PROPOSAL_ID" --input - --json
318
+
319
+ hagi proposal execute --session-id "$PROPOSAL_ID"
320
+ hagi proposal complete --session-id "$PROPOSAL_ID"
321
+ hagi proposal archive --session-id "$PROPOSAL_ID"
151
322
  ```
152
323
 
153
- Archive, update status, complete, or send a follow-up message:
324
+ ### Follow a Run
325
+
326
+ `proposal get` reads one session. With `--wait` it polls until the status settles, so the next lifecycle command runs at the right time. `proposal messages` reads the session's messages, newest first.
154
327
 
155
328
  ```bash
156
- hagi proposal archive --session-id <session-id> --hero-id <hero-id>
157
- hagi proposal status --session-id <session-id> --status Reviewing
158
- hagi proposal complete --session-id <session-id>
159
- hagi proposal send --session-id <session-id> --content "Please continue"
329
+ hagi proposal get --session-id "$PROPOSAL_ID" --wait --json
330
+ hagi proposal messages --session-id "$PROPOSAL_ID" --last 1 --json
160
331
  ```
161
332
 
333
+ The options are the same as `hagitask get`. WaitingForReply counts as settled, because a person must answer. Which stage each command starts is in [references/openspec-workflow.md](skills/hagi/references/openspec-workflow.md).
334
+
162
335
  ### Explicit Lifecycle Sequence
163
336
 
164
- The CLI keeps proposal lifecycle actions explicit and non-interactive:
337
+ The CLI keeps proposal lifecycle actions explicit and non-interactive. Each command maps to one backend action:
165
338
 
166
339
  1. `hagi proposal list`
167
340
  2. `hagi proposal create`
168
- 3. `hagi proposal generate-name`
169
- 4. `hagi proposal optimize-description` (optional)
170
- 5. `hagi proposal generate`
171
- 6. `hagi proposal annotate` (optional review loop)
172
- 7. `hagi proposal execute`
173
- 8. `hagi proposal archive`
174
- 9. `hagi proposal complete`
341
+ 3. `hagi proposal optimize-description --session-id <session-id> --description <text>` (sets the title, description, and proposal name)
342
+ 4. `hagi proposal generate --session-id <session-id> --proposal-content-language <language>` (optionally `--enable-explore-mode`)
343
+ 5. `hagi proposal annotate --session-id <session-id> --input <file>` (optional review loop)
344
+ 6. `hagi proposal execute --session-id <session-id>`
345
+ 7. `hagi proposal complete --session-id <session-id>`
346
+ 8. `hagi proposal archive --session-id <session-id>`
347
+
348
+ The CLI may suggest a next command, but it never chains later lifecycle APIs automatically.
175
349
 
176
350
  Each command maps to one backend action. The CLI may suggest a next command, but it never chains later lifecycle APIs automatically.
177
351
 
352
+ `proposal create` takes the same `--metadata` and `--metadata-json` options as `chat create`, with the same rules and limits; see [Session Metadata](#session-metadata). A master marker and worker markers, for example:
353
+
354
+ ```bash
355
+ MASTER_ID=$(hagi chat create --title "Batch master" --project-id "$PROJECT_ID" --metadata role=master --metadata batch=2026-11-07 --json | json_field '.session.sessionId')
356
+ hagi proposal create --project-id "$PROJECT_ID" --chief-complaint "Repository scope
357
+ Writable repositories:
358
+ - none
359
+ Read-only repositories:
360
+ - none
361
+
362
+ Tighten the settings page copy" --default-title --metadata role=worker --metadata master="$MASTER_ID" --json
363
+ ```
364
+
365
+ ## Vault Commands
366
+
367
+ Create a vault with a local folder to browse. The new id is read from `vault.id`:
368
+
369
+ ```bash
370
+ mkdir -p /tmp/cli-vault-notes/docs
371
+ printf '# Notes\n' > /tmp/cli-vault-notes/docs/readme.md
372
+
373
+ VAULT_ID=$(hagi vault create \
374
+ --name "Main Notes" \
375
+ --type folder \
376
+ --physical-path /tmp/cli-vault-notes \
377
+ --json | json_field '.vault.id')
378
+ ```
379
+
380
+ List registered vaults:
381
+
382
+ ```bash
383
+ hagi vault list
384
+ hagi vault list --json
385
+ ```
386
+
387
+ Update only part of a vault record. The CLI resolves the current vault snapshot first so omitted fields are preserved:
388
+
389
+ ```bash
390
+ hagi vault update --id "$VAULT_ID" --name "Renamed Vault"
391
+ ```
392
+
393
+ Browse vault files and preview supported content:
394
+
395
+ ```bash
396
+ hagi vault files list --id "$VAULT_ID"
397
+ hagi vault files list --id "$VAULT_ID" --path docs --json
398
+ hagi vault files preview --id "$VAULT_ID" --path docs/readme.md
399
+ ```
400
+
401
+ Delete a vault and request backend cleanup of its local files:
402
+
403
+ ```bash
404
+ hagi vault delete --id "$VAULT_ID" --delete-local-files --json
405
+ ```
406
+
178
407
  ## Chat Commands
179
408
 
409
+ Create a chat session. The new id is read from `session.sessionId`:
410
+
411
+ ```bash
412
+ CHAT_ID=$(hagi chat create \
413
+ --title "Repository Q&A" \
414
+ --project-id "$PROJECT_ID" \
415
+ --json | json_field '.session.sessionId')
416
+ ```
417
+
180
418
  List chat sessions:
181
419
 
182
420
  ```bash
@@ -184,115 +422,405 @@ hagi chat list --active-only
184
422
  hagi chat list --include-archived --json
185
423
  ```
186
424
 
187
- Create a chat session:
425
+ Send a message, archive, and delete:
188
426
 
189
427
  ```bash
190
- hagi chat create \
191
- --title "Repository Q&A" \
192
- --project-id <project-id> \
193
- --executor-type CodexCli
428
+ hagi chat send --session-id "$CHAT_ID" --content "Summarize the current diff"
429
+ hagi chat send --session-id "$CHAT_ID" --content "Summarize the current diff" --steer
430
+ HAGI_SEND_STEER=1 hagi chat send --session-id "$CHAT_ID" --content "Summarize the current diff"
431
+ hagi chat send --session-id "$CHAT_ID" --content "Summarize the current diff" --no-steer
432
+ hagi chat archive --session-id "$CHAT_ID"
433
+ hagi chat delete --session-id "$CHAT_ID"
434
+ ```
435
+
436
+ ### Session Metadata
437
+
438
+ `chat create` and `proposal create` take write-once metadata entries, for example the role markers an orchestrator uses to find its sessions again:
439
+
440
+ ```bash
441
+ hagi chat create --title "Master" --project-id "$PROJECT_ID" --metadata role=master --metadata batch=2026-11-07 --json
442
+ hagi chat create --title "Worker 1" --project-id "$PROJECT_ID" --metadata role=worker --metadata master="$MASTER_ID" --json
443
+ printf '{"role":"worker","master":"%s"}' "$MASTER_ID" | hagi chat create --title "Worker 2" --project-id "$PROJECT_ID" --metadata-json - --json
444
+ ```
445
+
446
+ - `--metadata key=value` is repeatable. The text is split at the first `=`, so a value may contain `=`.
447
+ - `--metadata-json <file|->` reads a JSON object of string values from a file, or from stdin with `-`. It is repeatable.
448
+ - A key may appear once across both options. A missing `=`, an empty key, a repeated key, or JSON that is not an object of string values is a usage error: the command exits `2` and sends no request.
449
+ - Limits, checked by the backend: at most 16 entries; a key of 1 to 64 characters matching `^[a-z][a-z0-9._-]{0,63}$`; a value of 1 to 512 characters with no control characters. A rejection exits with the backend's message as it is, and the session is not created.
450
+ - Metadata is write-once: it is set when the session is created and cannot be changed later. Every reader of the session can see it, so do not put secrets in it.
451
+ - The entries come back as `userMetadata` in the `--json` output of `chat create`, `chat list`, `proposal create` and `proposal get`. The human output adds one `userMetadata` line, keys sorted, when there are entries. A session without metadata has no `userMetadata` field.
452
+
453
+
454
+ ## HagiTask Commands
455
+
456
+ A HagiTask is a preset task with a structured input. Read its schema first, write the input from the schema, then create the session. `hagitask list` shows the active tasks:
457
+
458
+ ```bash
459
+ hagi hagitask list
460
+ hagi hagitask list --json
194
461
  ```
195
462
 
196
- Archive, delete, or send a message:
463
+ `hagitask schema` shows one task's target scope, commands, parameters, requirements and scenes. Each required parameter is marked `yes`. The `--json` output adds the input JSON Schema and an example request body:
197
464
 
198
465
  ```bash
199
- hagi chat archive --session-id <session-id>
200
- hagi chat delete --session-id <session-id>
201
- hagi chat send --session-id <session-id> --content "Summarize the current diff"
466
+ hagi hagitask schema --task-id <task-id>
467
+ hagi hagitask schema --task-id <task-id> --json
468
+ ```
469
+
470
+ Write the input file. Its keys are the parameter keys the schema lists under `inputs`, plus the `targets` and `targetScopes` keys when the schema's scope lists targets. For example, `input.json` for a task with one required input:
471
+
472
+ ```json
473
+ {
474
+ "inputs": {
475
+ "commitScope": "cli"
476
+ }
477
+ }
202
478
  ```
203
479
 
204
- ## AutoTask Commands
480
+ Create the session from that task and input. The new id is read from `session.sessionId`:
481
+
482
+ ```bash
483
+ HAGITASK_ID=$(hagi hagitask create \
484
+ --title "Auto task" \
485
+ --project-id "$PROJECT_ID" \
486
+ --task-id <task-id> \
487
+ --input input.json \
488
+ --json | json_field '.session.sessionId')
489
+ ```
205
490
 
206
- Create an AutoTask session with package-first automation flags:
491
+ Super Commit is a prompt, not a preset task. It has no schema and takes no `--input`. Start it with its prompt id, and pass the Git options it accepts:
207
492
 
208
493
  ```bash
209
- hagi autotask create \
210
- --title "Auto compose commit" \
211
- --project-id <project-id> \
494
+ SUPER_COMMIT_ID=$(hagi hagitask create \
495
+ --title "Commit the CLI changes" \
496
+ --project-id "$PROJECT_ID" \
212
497
  --prompt-id auto-compose-commit.en-US \
213
498
  --repository-path repos/cli \
214
- --need-push \
215
499
  --target-branch-mode new-custom \
216
500
  --target-branch-name feature/cli-completion \
217
- --script-key autotask.auto-compose-commit
501
+ --json | json_field '.session.sessionId')
502
+ ```
503
+
504
+ Start a Super Commit only when the user asked for it. Add `--need-push` only when the user asked for a push. See [references/super-commit.md](skills/hagi/references/super-commit.md) for the options, the branch modes, the result and the explicit-request rule.
505
+
506
+ Use `--prompt-id` instead of `--task-id` for a prompt template with no structured input. `--input` needs `--task-id`. Input is read from a file, or from stdin with `--input -`. The CLI only checks that the input is a JSON object. The backend checks the keys and values. It rejects a key that the schema does not list, and a missing required value. Its error is printed unchanged, and no session is reported as created. `--hero-selection-mode` takes `first` (the default) or `random`.
507
+
508
+ The backend must have a hero available for the task's script. Without one, `hagitask create` fails with a 400 whose error is `no-executable-hero`.
509
+
510
+ Send a follow-up message to the HagiTask session:
511
+
512
+ ```bash
513
+ hagi hagitask send --session-id "$HAGITASK_ID" --content "Retry with a smaller diff"
514
+ ```
515
+
516
+ Steer the message into the running task, from the environment, or with a normal send that overrides the environment:
517
+
518
+ ```bash
519
+ hagi hagitask send --session-id "$HAGITASK_ID" --content "Retry with a smaller diff" --steer
520
+ HAGI_SEND_STEER=1 hagi hagitask send --session-id "$HAGITASK_ID" --content "Retry with a smaller diff"
521
+ hagi hagitask send --session-id "$HAGITASK_ID" --content "Retry with a smaller diff" --no-steer
218
522
  ```
219
523
 
220
- Send a follow-up message to an AutoTask session:
524
+ Read one HagiTask session, or wait until its status settles, and read its messages:
221
525
 
222
526
  ```bash
223
- hagi autotask send --session-id <session-id> --content "Retry with a smaller diff"
527
+ hagi hagitask get --session-id "$SUPER_COMMIT_ID" --wait --timeout 900 --json
528
+ hagi hagitask messages --session-id "$SUPER_COMMIT_ID" --last 1 --json
224
529
  ```
225
530
 
226
- ## Maintainer Workflow (`repos/cli` only)
531
+ `hagitask get` reads one session. With `--wait` it polls every `--interval` seconds (default 5) until the status is no longer Optimizing, Openspecing, Executing or Archiving. `--timeout` (default 900) and `--grace` (default 10, `0` disables) are seconds, and `--timeout`, `--interval` and `--grace` need `--wait`. `--interval` must be less than `--timeout`. A timeout exits `31` and prints the last status. `hagitask messages --last <n>` reads the n newest messages; `--json` prints the list unchanged.
227
532
 
228
- Use the commands in this section only when you are editing or validating the CLI source tree inside this repository.
533
+ **Migration from AutoTask:** `hagi hagitask create` and `hagi hagitask send` replace `hagi autotask create` and `hagi autotask send`. There is no `autotask` alias, so an `autotask` command now fails as an unknown command. Read the task schema with `hagitask schema` to find the input keys that replace the old flags.
229
534
 
230
- Build the local repository output:
535
+ ## Plan Commands
536
+
537
+ A scheduled plan saves a proposal or HagiTask input with a schedule and an agent pool. The backend runs it on that schedule. `plan list` and `plan get` show the plans that exist:
231
538
 
232
539
  ```bash
233
- cd repos/cli
234
- npm install
235
- npm run build
236
- node ./dist/cli.js --help
540
+ hagi plan list
541
+ hagi plan list --json
542
+ hagi plan get --id "$PLAN_ID"
543
+ hagi plan get --id "$PLAN_ID" --json | jq .plan
544
+ ```
545
+
546
+ `plan list` prints one row per plan with its schedule summary, and an explicit message when there are none. `--json` prints `{ "plans": [...] }`, where each entry carries `id`, `type`, `name`, `enabled`, `state`, `nextRunAt`, `schedule`, `agentPool` and `payload` exactly as the backend returns them. `plan get --json` prints `{ "plan": {...} }` with the same fields.
547
+
548
+ ### Create a plan
549
+
550
+ `plan create` reads a plan document with `--input <file>` or `--input -` for stdin. A recurring HagiTask plan:
551
+
552
+ ```json
553
+ {
554
+ "type": "hagitask",
555
+ "name": "Nightly review",
556
+ "enabled": true,
557
+ "schedule": {
558
+ "mode": "recurring",
559
+ "timeZone": "Asia/Shanghai",
560
+ "startDate": "2026-05-01",
561
+ "endDate": "2026-05-31",
562
+ "timeOfDay": "02:00",
563
+ "repeat": { "rule": "daily", "weekdays": [] }
564
+ },
565
+ "agentPool": { "strategy": "random", "agents": [ { "heroId": "5de02573-0000-4000-8000-000000000001" } ] },
566
+ "payload": {
567
+ "projectId": "11111111-2222-3333-4444-555555555555",
568
+ "presetTaskId": "nightly-review"
569
+ }
570
+ }
237
571
  ```
238
572
 
239
- For local development without building:
573
+ A one-time proposal plan runs once at `runAt` and has no time zone or repeat fields:
574
+
575
+ ```json
576
+ {
577
+ "type": "proposal",
578
+ "name": "Weekly triage",
579
+ "schedule": { "mode": "once", "runAt": "2026-06-12T10:30:00Z" },
580
+ "payload": {
581
+ "title": "Weekly triage",
582
+ "chiefComplaint": "Triage the open issues and propose the next sprint",
583
+ "projectId": "11111111-2222-3333-4444-555555555555"
584
+ }
585
+ }
586
+ ```
240
587
 
241
588
  ```bash
242
- cd repos/cli
243
- npm run dev -- proposal list --json
244
- npm run dev -- chat list --json
589
+ hagi plan create --input nightly-review.json
590
+ hagi plan create --input weekly-triage.json --disabled
591
+ PLAN_ID=$(hagi plan create --input nightly-review.json --json | json_field '.plan.id')
245
592
  ```
246
593
 
247
- ## Release Automation
594
+ A plan is **enabled by default**. `create` sends `enabled: true` unless the document sets `enabled` or you pass `--disabled`. `--name` and `--hero-id` replace the document's `name` and agent pool, and `--hero-id` builds the single-agent `random` pool. Server-owned fields (`id`, `createdAt`, `updatedAt`, `state`, `nextRunAt`) in the document are ignored.
248
595
 
249
- `repos/cli/.github/workflows/release-drafter.yml` maintains the next GitHub draft release for `@hagicode/cli`.
250
- `repos/cli/.github/workflows/npm-publish.yml` publishes two channels with GitHub Actions OIDC plus provenance:
596
+ ### Update, enable, disable and delete
251
597
 
252
- - pushes to `main` publish a unique development prerelease under the npm `dev` dist-tag
253
- - published GitHub Releases for strict `vX.Y.Z` tags publish the stable package under the npm `latest` dist-tag
598
+ `plan update` reads the current plan, applies your changes and sends the full plan back, so fields you do not mention keep their values:
254
599
 
255
- ### Local Release Verification
600
+ ```bash
601
+ hagi plan update --id "$PLAN_ID" --name "Nightly review (EU)"
602
+ hagi plan update --id "$PLAN_ID" --input changes.json
603
+ hagi plan update --id "$PLAN_ID" --hero-id 5de02573-0000-4000-8000-000000000002 --enabled
604
+ ```
256
605
 
257
- Run the stable release gates locally before publishing a GitHub Release:
606
+ `--input` on update is a **shallow merge** over the top-level keys of the plan (`type`, `name`, `enabled`, `schedule`, `agentPool`, `payload`). Each key in the file replaces the current value as a whole, and keys missing from the file are kept. A `schedule` in the file replaces the whole current schedule, so write the complete object, without the fields you want removed (for example `endDate`). Passing `--enabled` and `--disabled` together is a usage error, and so is an update with no change.
607
+
608
+ `enable` and `disable` change only the enabled flag. When the plan already has that flag, nothing is sent and the output says so:
609
+
610
+ ```bash
611
+ hagi plan disable --id "$PLAN_ID"
612
+ hagi plan enable --id "$PLAN_ID" --json
613
+ ```
614
+
615
+ Delete a plan. It never runs again:
616
+
617
+ ```bash
618
+ hagi plan delete --id "$PLAN_ID"
619
+ hagi plan delete --id "$PLAN_ID" --json
620
+ ```
621
+
622
+ To change a plan with a text editor, take the current document, edit it and feed it back. Server-owned fields are dropped automatically:
258
623
 
259
624
  ```bash
260
- cd repos/cli
261
- version="$(npm run --silent publish:verify-release -- v0.1.0)"
262
- npm version --no-git-tag-version "$version"
263
- npm run build
264
- npm run typecheck
265
- npm test
266
- npm run pack:check
625
+ hagi plan get --id "$PLAN_ID" --json | jq .plan > plan.json
626
+ # edit plan.json, then:
627
+ hagi plan update --id "$PLAN_ID" --input plan.json
267
628
  ```
268
629
 
630
+ When the backend rejects a plan with 400 (exit code `20`), the CLI prints the summary and then one line per problem, naming its field:
631
+
632
+ ```text
633
+ Scheduled plan is invalid. (status=400)
634
+ - agentPool: A HagiTask plan needs an agent pool with one agent.
635
+ ```
636
+
637
+ Use `--json` to get the full backend body under `error.details`.
638
+
639
+ ### Run a plan now
640
+
641
+ `plan run` creates a session from the plan's stored inputs immediately, without waiting for the schedule. It waits for the session and prints its summary, or `{ "session": {...} }` with `--json`:
642
+
643
+ ```bash
644
+ hagi plan run --id "$PLAN_ID"
645
+ ```
646
+
647
+ `plan run --background` starts the same run and returns as soon as the backend accepts it, before the session exists. It prints the plan id and `accepted: true`. The session shows up in the normal session list:
648
+
649
+ ```bash
650
+ hagi plan run --id "$PLAN_ID" --background --json
651
+ ```
652
+
653
+ A manual run does not change the plan's state or next run time. It works for disabled and completed plans, and the next automatic run still happens on the original schedule.
654
+
655
+ ## Skills Commands
656
+
657
+ List installed local skills. The table shows each skill's origin (`user` or `built-in`), whether a built-in skill is enabled, its version, scope, agents and path. A user skill shows `-` in the Enabled column, because availability does not apply to it:
658
+
659
+ ```bash
660
+ hagi skills local list
661
+ hagi skills local list --json
662
+ ```
663
+
664
+ Turn a built-in skill on or off by name. The change applies to agent runs that start afterwards; a run already in progress keeps its skills. A name that is not a built-in skill exits with code `30`, and the output carries the backend message:
665
+
666
+ ```bash
667
+ hagi skills local enable --skill-name hagi
668
+ hagi skills local disable --skill-name hagi --json
669
+ ```
670
+
671
+ Built-in skills cannot be uninstalled. Use `skills local disable` to stop one from loading. Remove a user skill by name:
672
+
673
+ ```bash
674
+ SKILL_NAME=demo-skill
675
+ hagi skills local uninstall --skill-name "$SKILL_NAME"
676
+ ```
677
+
678
+ The uninstall result reports a status such as `Succeeded` or `PrerequisiteMissing`, with the backend message. Passing a built-in skill's name returns the backend's refusal unchanged.
679
+
680
+ Skill recommendation and install progress streaming are not part of the CLI. `skills gallery search`, `detail` and `install` are the discovery and install path.
681
+
682
+ Search the gallery and read one skill:
683
+
684
+ ```bash
685
+ hagi skills gallery search --query react
686
+ hagi skills gallery search --query react --json
687
+ hagi skills gallery detail --id vercel-react-best-practices --source vercel-labs/agent-skills
688
+ ```
689
+
690
+ `gallery detail` also shows third-party audit ratings for the skill, supplied by skills.sh (providers such as `ath`, `socket` and `snyk`). In `--json` output every detail field is unchanged and an `audit` object is added. It lists one entry per provider that supplied data; `risk`, `alerts`, `score` and `analyzedAt` appear only when the backend has them:
691
+
692
+ ```json
693
+ {
694
+ "id": "vercel-react-best-practices",
695
+ "source": "vercel-labs/agent-skills",
696
+ "...": "all other detail fields",
697
+ "audit": {
698
+ "providers": [
699
+ { "provider": "ath", "risk": "low", "alerts": 0, "score": 92, "analyzedAt": "2026-10-30T08:00:00Z" },
700
+ { "provider": "socket", "risk": "medium", "alerts": 2 }
701
+ ]
702
+ }
703
+ }
704
+ ```
705
+
706
+ In human output the detail table is followed by one line per provider, for example `skills.sh audit (socket): risk=medium, alerts=2, score=--, analyzedAt=--`, where `--` marks a value the backend did not supply.
707
+
708
+ The audit is best-effort. If the audit call fails, times out or has no data, the detail still prints and the command exits `0`; in `--json` output `audit.providers` is then `[]`, never missing. The backend bounds the audit wait, and the CLI adds no timeout of its own. A failure of the detail call itself fails the command as before, and the audit never hides it. The audit does not change the install command preview or whether the skill is installable.
709
+
710
+ Start a managed install of a gallery skill. This writes the skill into your local skill directories, so run it only when you intend to install it:
711
+
712
+ ```bash
713
+ hagi skills gallery install \
714
+ --id vercel-react-best-practices \
715
+ --source vercel-labs/agent-skills \
716
+ --skill-slug vercel-react-best-practices
717
+ ```
718
+
719
+ Manage the trusted provider directory. `upsert` reads a JSON document from `--input <file>` or `--input -`:
720
+
721
+ ```bash
722
+ hagi skills trusted list
723
+ cat > provider.json <<'EOF'
724
+ {"providerId":"example-provider","displayName":"Example","isEnabled":true,"sortOrder":50,"matchRules":[{"matchType":"owner","value":"example"}]}
725
+ EOF
726
+ hagi skills trusted upsert --input provider.json
727
+ cat provider.json | hagi skills trusted upsert --input - --json
728
+ hagi skills trusted delete --provider-id example-provider
729
+ hagi skills trusted reset
730
+ ```
731
+
732
+ ## Agent Lock Commands
733
+
734
+ `hagi agent lock` lets several agents take turns on one shared resource, such as a game server that only one agent may use at a time. The backend keeps a fair queue per lock; the lock is cooperative and cannot stop an action already running on the remote resource. There is no top-level `hagi lock`. The agent-facing protocol is in [`skills/hagi/references/resource-lock.md`](skills/hagi/references/resource-lock.md).
735
+
736
+ ```bash
737
+ hagi agent lock acquire --lock-id eu-1/duckov --owner sess-7f2c
738
+ hagi agent lock acquire --server eu-1 --game duckov --owner sess-7f2c --wait --timeout 900
739
+ hagi agent lock status --lock-id eu-1/duckov --owner sess-7f2c --json
740
+ hagi agent lock heartbeat --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
741
+ hagi agent lock release --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
742
+ hagi agent lock requisition --lock-id eu-1/duckov --owner sess-7f2c --reason "User order: take the lock now"
743
+ hagi agent lock list --prefix eu-1/
744
+ ```
745
+
746
+ - Lock: `--lock-id <id>` (one to four `/`-separated segments) or `--server <name> --game <name>`, which means `<server>/<game>`. Giving both forms, or only one of `--server` and `--game`, exits `2` before any request.
747
+ - Owner: `--owner <id>`, then `HAGI_LOCK_OWNER`, then `HAGI_SESSION_ID`. An agent passes its own session ID. The CLI never invents or stores an owner ID; with none set it exits `2`.
748
+ - `acquire` takes the lock or joins the queue and exits `0` for `Holding` and `Queued`. `acquire --wait` long-polls until the owner holds the lock (exit `0`), the `--timeout` passes (default 900 seconds, exit `31`, the queue entry it created is released), or the entry is dropped (exit `32`). Ctrl-C releases the entry it created and never a lock held before. `acquire` has no force option.
749
+ - `status` reads one owner's state (`Holding`, `Queued`, `Expired`, `Requisitioned`, `None`) and exits `0` for all of them; `--wait-seconds` long-polls. `renew` sends one renewal. `release` exits `0` whether or not anything was released. `list` shows the busy locks; `--prefix` filters them.
750
+ - `heartbeat` renews at once and then every `--interval` seconds (default 120), is quiet on success (`--verbose` prints each beat), prints a line and exits `32` when the lock is lost, retries transport errors with backoff and never reports them as loss. SIGINT or SIGTERM ends it with exit `0` and does not release. Run it under a Monitor or another watcher.
751
+ - `requisition` requires `--reason` (1 to 200 characters), takes the lock at once, and displaces the holder, who is told by its next heartbeat. Use it only when a user explicitly ordered a forced take.
752
+ - Answers show the valid-until time, the 24-hour hard cap, the check-in window and, for a queued owner, the turn window.
753
+
754
+ | Timing | Default |
755
+ | --- | --- |
756
+ | Feedback timeout (no heartbeat releases the lock) | 10 minutes |
757
+ | Maximum hold | 24 hours |
758
+ | Heartbeat interval | 2 minutes |
759
+ | Claim window after a handoff | 30 seconds |
760
+ | Waiter TTL | 60 seconds |
761
+ | `acquire --wait` timeout | 900 seconds |
762
+
763
+ Agent loop: `acquire`, read the windows, start `heartbeat` under a watcher before any work (or, when queued, a continuous `acquire --wait` watcher), stop the heartbeat when done and then `release`, and stop using the resource as soon as a command exits `32`. After losing track, repeat `acquire` with the same owner; it is safe.
764
+
765
+ ## Steer (send option)
766
+
767
+ `hagi chat send`, `hagi proposal send`, and `hagi hagitask send` deliver a message in one of two ways:
768
+
769
+ - **Normal send** (default): the message waits behind the running turn, as before.
770
+ - **Steer**: the message is inserted into the running turn through the backend's `SteerSession` RPC. The CLI makes one call.
771
+
772
+ Steer is off unless you ask for it. The first source that is set decides, in this order:
773
+
774
+ 1. `--steer` (on) or `--no-steer` (off) on the command.
775
+ 2. `HAGI_SEND_STEER`: `1`, `true`, `yes`, `on` turn Steer on; `0`, `false`, `no`, `off` turn it off. Case and surrounding spaces are ignored. Any other value exits with code `2` and sends no request.
776
+ 3. `"steer": true` or `"steer": false` in the config file. Any other type exits with code `2` and names the file.
777
+ 4. The default, `false`.
778
+
779
+ A lower source is not read once a higher one is set, so `--no-steer` works even when `HAGI_SEND_STEER` holds a value the CLI does not recognize.
780
+
781
+ ### Outcomes
782
+
783
+ - `steered`: a running task took the message. It does not guarantee the text was used: the backend can return a steered message to the queue when the run ends.
784
+ - `queued`: no running task took the message, so it was sent as the next message. This is a success and exits with code `0`. The backend also answers `queued` for an idle session, where the message starts a run at once.
785
+
786
+ Human output adds `steer`, `effective`, `outcome`, and `messageId` rows, and a line saying no running task took the message for `queued`. `--json` keeps `sessionId`, `message`, and `requested`, and adds a `steer` object: `{ requested, source, effective, outcome?, messageId?, reason? }`. `source` is `flag`, `env`, `config`, or `default`. `reason` appears only when Steer was requested but not used. On the Steer path `message` is `Steered` or `Queued`.
787
+
788
+ ### Conflicts and fallback
789
+
790
+ Steer carries text only, so it cannot be combined with `--mode` or `--executor-type`:
791
+
792
+ - `--steer` with either option exits with code `2` and sends nothing.
793
+ - When Steer comes only from `HAGI_SEND_STEER` or the config file, the command sends a normal message with the options and records the reason in `steer.reason`.
794
+ - When Steer comes only from `HAGI_SEND_STEER` or the config file and the backend does not implement `SteerSession`, the command sends a normal message and records the reason `the backend does not support Steer`. An explicit `--steer` reports the backend error instead.
795
+
796
+ A queued Steer is always sent in `Edit` mode. A session that must keep another mode should send with `--no-steer --mode <mode>`.
797
+
798
+ Steer works with the executors the backend supports. For any other executor, the backend answers `queued`.
799
+
800
+ ### Unattended messaging
801
+
802
+ For session-to-session messages sent by an orchestrator without a person watching, set `HAGI_SEND_STEER=1` in its environment. Add `--no-steer` to a single call that must go through the normal queue.
803
+
269
804
  ## Environment Variables
270
805
 
271
806
  The CLI resolves runtime settings centrally before every command. Explicit flags always override environment variables.
272
807
 
273
808
  | Variable | Purpose |
274
809
  | --- | --- |
275
- | `HAGI_API_BASE_URL` | Backend base URL. Defaults to `http://127.0.0.1:35168` when unset. |
810
+ | `HAGI_API_BASE_URL` | Backend base URL, used when `--base-url` is not given. Overrides the config file. Required when neither is set. |
276
811
  | `HAGI_API_TOKEN` | Bearer token attached as `Authorization: Bearer <token>`. |
277
812
  | `HAGI_API_LANG` | Shared `Accept-Language` header value. |
813
+ | `HAGI_SEND_STEER` | Turns Steer on (`1`, `true`, `yes`, `on`) or off (`0`, `false`, `no`, `off`) for the send commands, ignoring case and surrounding spaces. Any other value exits with code `2`. Overridden by `--steer` / `--no-steer`. Order: flag, then this variable, then the `steer` config key, then `false`. |
278
814
 
279
- ## Generate the API Client
280
-
281
- The CLI owns its own OpenAPI configuration in `openapi-ts.config.json`.
282
-
283
- ```bash
284
- cd repos/cli
285
- npm run generate:api
286
- npm run generate:api:once
287
- ```
815
+ ## Backend Transport
288
816
 
289
- Default Swagger source:
817
+ The CLI talks to the backend over gRPC-Web only. It appends `/grpc` to the base URL (for example `http://127.0.0.1:35167/grpc`) and calls the `hagicode.*.v1` services; no REST route is used. gRPC-Web runs over HTTP/1.1, so it works against the plain-HTTP local backend and through reverse proxies. The bearer token, `Accept-Language` and the `X-Hagi-Client` marker travel as request headers. The backend therefore has to expose its `/grpc` endpoints.
290
818
 
291
- - `http://127.0.0.1:35168/swagger/v1/swagger.json`
819
+ gRPC status codes keep the exit codes below: `InvalidArgument` is `20`, `Unauthenticated` `21`, `PermissionDenied` `23`, `NotFound` `24`, `FailedPrecondition`, `AlreadyExists` and `Aborted` `29`, other client-side statuses `40`, and `Internal`, `Unavailable`, `DeadlineExceeded` and the rest `50`. The status line in error output (`status=404`) shows the HTTP-equivalent number.
292
820
 
293
821
  ## Exit Codes
294
822
 
295
- Stable non-zero exits used by the CLI:
823
+ Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of the gRPC status the backend answered with:
296
824
 
297
825
  | Exit code | Meaning |
298
826
  | --- | --- |
@@ -302,6 +830,9 @@ Stable non-zero exits used by the CLI:
302
830
  | `23` | HTTP 403 |
303
831
  | `24` | HTTP 404 |
304
832
  | `29` | HTTP 409 |
833
+ | `30` | Operation failed: the backend returned a refusal, for example `skills local enable` or `disable` on a name that is not a built-in skill, an invalid MonoSpecs configuration from `project monospecs validate`, or a failed clone from `project monospecs clone` or `set --clone` |
834
+ | `31` | Wait timed out: `--wait` on `proposal get`, `hagitask get` or `agent lock acquire` reached `--timeout` before the session settled. The last status is printed. Not a success, and not a backend error |
835
+ | `32` | Lock lost: a `hagi agent lock` command learned that the owner no longer holds the lock (expired, taken back at the 24-hour cap, stale epoch, requisitioned). Stop using the resource. Distinct from `31` |
305
836
  | `40` | Other HTTP 4xx |
306
837
  | `50` | HTTP 5xx |
307
838
  | `60` | Transport or connection failure |