@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 +640 -109
- package/dist/chunks/main-BjVumWEX.js +260 -0
- package/dist/cli.js +1 -1
- package/dist/main.js +1 -1
- package/package.json +26 -13
- package/resources/saga/install/gitignore.template +1 -0
- package/resources/saga/manifest.json +14 -0
- package/resources/saga/prompts/acceptance.md +15 -0
- package/resources/saga/prompts/amend.md +10 -0
- package/resources/saga/prompts/goal.md +23 -0
- package/resources/saga/prompts/plan.md +16 -0
- package/resources/saga/prompts/research.md +16 -0
- package/resources/saga/prompts/run.md +20 -0
- package/skills/README.md +52 -0
- package/skills/hagi/SKILL.md +128 -0
- package/skills/hagi/agents/openai.yaml +7 -0
- package/skills/hagi/references/cli-usage.md +363 -0
- package/skills/hagi/references/local-development.md +96 -0
- package/skills/hagi/references/openspec-workflow.md +130 -0
- package/skills/hagi/references/proposal-flow.md +210 -0
- package/skills/hagi/references/resource-lock.md +135 -0
- package/skills/hagi/references/saga.md +260 -0
- package/skills/hagi/references/session-data.md +172 -0
- package/skills/hagi/references/super-commit.md +106 -0
- package/skills/hagi/references/validation.md +164 -0
- package/dist/chunks/main-CUEYUrFk.js +0 -75
package/README.md
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# HagiCode CLI
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@hagicode/cli)
|
|
3
|
+
[](https://www.npmjs.com/package/@hagicode/cli)
|
|
4
|
+
[](https://www.npmjs.com/package/@hagicode/cli)
|
|
5
|
+
[](https://www.npmjs.com/package/@hagicode/cli)
|
|
6
|
+
[](https://nodejs.org/)
|
|
4
7
|
|
|
5
|
-
`@hagicode/cli` provides the published `hagi` command
|
|
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
|
|
10
|
-
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 /
|
|
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
|
|
97
|
-
hagi project update --id
|
|
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
|
|
226
|
+
Delete the demo project:
|
|
101
227
|
|
|
102
228
|
```bash
|
|
103
|
-
hagi project delete --id
|
|
229
|
+
hagi project delete --id "$DEMO_PROJECT_ID"
|
|
104
230
|
```
|
|
105
231
|
|
|
106
|
-
##
|
|
232
|
+
## MonoSpecs Commands
|
|
107
233
|
|
|
108
|
-
|
|
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
|
|
112
|
-
hagi
|
|
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
|
-
|
|
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
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
292
|
+
Update status and send a follow-up message:
|
|
131
293
|
|
|
132
294
|
```bash
|
|
133
|
-
hagi proposal
|
|
134
|
-
|
|
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
|
-
|
|
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
|
|
143
|
-
hagi proposal
|
|
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
|
-
|
|
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
|
|
150
|
-
|
|
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
|
-
|
|
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
|
|
157
|
-
hagi proposal
|
|
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
|
|
169
|
-
4. `hagi proposal
|
|
170
|
-
5. `hagi proposal
|
|
171
|
-
6. `hagi proposal
|
|
172
|
-
7. `hagi proposal
|
|
173
|
-
8. `hagi proposal archive
|
|
174
|
-
|
|
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
|
-
|
|
425
|
+
Send a message, archive, and delete:
|
|
188
426
|
|
|
189
427
|
```bash
|
|
190
|
-
hagi chat
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
|
200
|
-
hagi
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
210
|
-
--title "
|
|
211
|
-
--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
|
-
--
|
|
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
|
-
|
|
524
|
+
Read one HagiTask session, or wait until its status settles, and read its messages:
|
|
221
525
|
|
|
222
526
|
```bash
|
|
223
|
-
hagi
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
- `
|
|
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 |
|