@hagicode/cli 0.1.2 → 0.2.0

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,199 +1,555 @@
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
- `repos/cli` contains the npm-based `hagi` command for proposal session management.
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), 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`
12
+ - Node.js 24+
11
13
  - Access to a HagiCode backend API
12
14
 
13
15
  ## Installation
14
16
 
17
+ Use the published npm package for normal CLI usage. You do not need to clone this repository to run `hagi`.
18
+
19
+ ### Zero-install usage
20
+
15
21
  ```bash
16
- cd repos/cli
17
- npm install
18
- npm run build
22
+ npx @hagicode/cli --help
23
+ npx @hagicode/cli project list --json
19
24
  ```
20
25
 
21
- `npm run build` now performs a Vite 8 production bundle for Node 20+ and emits the publish contract at `dist/cli.js`, `dist/main.js`, plus any runtime-required shared chunks under `dist/chunks/`.
22
- Release builds are minified, tree-shaken, and exclude `.map` files.
26
+ ### Project-local install
27
+
28
+ ```bash
29
+ npm install @hagicode/cli
30
+ npx @hagicode/cli proposal list --json
31
+ ```
23
32
 
24
- Run the built CLI with:
33
+ ### Optional global install
25
34
 
26
35
  ```bash
27
- npx hagi --help
28
- node ./dist/cli.js --help
36
+ npm install -g @hagicode/cli
37
+ hagi --help
29
38
  ```
30
39
 
31
- For local development without building:
40
+ ## Quick Start
41
+
42
+ The examples below use `hagi ...` after the package is available on your `PATH`. For one-off execution, replace `hagi` with `npx @hagicode/cli`.
32
43
 
33
44
  ```bash
34
- cd repos/cli
35
- npm run dev -- proposal list --json
45
+ hagi --help
46
+ hagi project list --json
47
+ hagi proposal list --json
48
+ hagi vault list --json
49
+ hagi chat list --json
50
+ hagi hagitask list
51
+ hagi hagitask create --title "Auto commit" --project-id <project-id> --prompt-id auto-compose-commit.en-US
36
52
  ```
37
53
 
38
- ## Release automation
54
+ ## Embedded Skills
39
55
 
40
- `repos/cli/.github/workflows/release-drafter.yml` maintains the next GitHub draft release for `@hagicode/cli`.
41
- `repos/cli/.github/workflows/npm-publish.yml` now publishes two channels with GitHub Actions OIDC plus provenance:
56
+ If you are integrating Hagi CLI into an agent workflow, start from the embedded skill package in this repository:
42
57
 
43
- - pushes to `main` publish a unique development prerelease under the npm `dev` dist-tag
44
- - only published GitHub Releases for strict `vX.Y.Z` tags publish the stable package under the npm `latest` dist-tag
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
45
61
 
46
- ### Draft release flow
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.
47
63
 
48
- 1. Label each PR with one semantic version label: `major`, `minor`, or `patch`.
49
- 2. Add at least one release note category label:
50
- - `breaking-change` for breaking release note callouts
51
- - `feature` or `enhancement` for the `Features` section
52
- - `bug` for the `Fixes` section
53
- - `docs` or `documentation` for the `Documentation` section
54
- - `chore` or `dependencies` for the `Maintenance` section
55
- 3. Add `skip-changelog` only when the PR should stay out of the draft notes entirely, such as pure CI noise or internal housekeeping.
56
- 4. Merge the labeled PR into `main`, or update the PR while it is open.
57
- 5. Let Release Drafter refresh the GitHub draft release name, tag, and categorized notes.
58
- 6. Review the draft release in GitHub before creating the next stable tag.
64
+ ## Shared Runtime Flags
59
65
 
60
- Release Drafter manages draft release metadata only. Draft review and publish remain native GitHub Release operations.
66
+ Every command family supports the same global runtime flags:
61
67
 
62
- ### Current label coverage
68
+ - `--base-url <url>`
69
+ - `--token <token>`
70
+ - `--lang <lang>`
71
+ - `--json`
63
72
 
64
- At implementation time, the public `HagiCode-org/cli` label set already included `bug`, `documentation`, and `enhancement`.
65
- The following planned release labels were still missing and should be created before maintainers rely on the full contract:
73
+ Example:
66
74
 
67
- - `major`
68
- - `minor`
69
- - `patch`
70
- - `breaking-change`
71
- - `feature`
72
- - `docs`
73
- - `chore`
74
- - `dependencies`
75
- - `skip-changelog`
75
+ ```bash
76
+ hagi --base-url https://api.example.com --token "$HAGI_TOKEN" project list --json
77
+ ```
76
78
 
77
- ### Trusted Publisher prerequisites
79
+ ## Connection Setup
78
80
 
79
- Before the first automated release, an npm package owner must bind the package to the repository workflow:
81
+ Every operation command resolves the backend base URL on each invocation, in this order:
80
82
 
81
- 1. Open the npm package settings for `@hagicode/cli`.
82
- 2. Add a Trusted Publisher for the GitHub repository `HagiCode-org/cli`.
83
- 3. Set the workflow file to `.github/workflows/npm-publish.yml`.
84
- 4. Keep the package access level public so `npm publish --access public` can succeed.
83
+ 1. `--base-url <url>` flag
84
+ 2. `HAGI_API_BASE_URL` environment variable
85
+ 3. `baseUrl` in the local config file: `$XDG_CONFIG_HOME/hagi/config.json`, or `~/.config/hagi/config.json` when `XDG_CONFIG_HOME` is unset
85
86
 
86
- The workflow requests `contents: read` and `id-token: write`. It does not use a long-lived `NPM_TOKEN`.
87
+ ```json
88
+ {
89
+ "baseUrl": "https://hagicode.example.com"
90
+ }
91
+ ```
87
92
 
88
- ### Local release verification
93
+ If none of the three is set, the command exits with code `2` and sends no backend request.
89
94
 
90
- Run the same stable release gates locally before publishing a GitHub Release:
95
+ Operation commands send requests without a credential by default. Pass `--token` (or set `HAGI_API_TOKEN`) only when your backend requires one.
91
96
 
92
- ```bash
93
- cd repos/cli
94
- version="$(npm run --silent publish:verify-release -- v0.1.0)"
95
- npm version --no-git-tag-version "$version"
96
- npm run build
97
- npm run typecheck
98
- npm test
99
- npm run pack:check
97
+ When the backend cannot be reached, the CLI names the base URL and exits with code `60`. A backend HTTP error is reported differently: it prints the HTTP status and the backend error message, and exits with a status-mapped code.
98
+
99
+ ```text
100
+ $ hagi --base-url http://127.0.0.1:1 project list
101
+ Cannot reach HagiCode backend at http://127.0.0.1:1. Check that the backend is running and the base URL is correct.
102
+
103
+ $ echo $?
104
+ 60
100
105
  ```
101
106
 
102
- `publish:verify-release` validates the strict stable tag format and resolves the stable package version that CI will stamp into `package.json` immediately before build and publish.
103
- `pack:check` runs `npm pack --dry-run --json` and rejects missing bundled entrypoints, `.map` files, and legacy source-mirrored output such as `dist/runtime/**` or `dist/generated/api/models/**`.
104
- `publish:resolve-dev-version` derives the CI-only prerelease version used for `main` branch publishes, for example `0.1.0-dev.<run>.<attempt>.<sha>`.
107
+ ## Command Overview
108
+
109
+ ```text
110
+ hagi
111
+ |-- project list | repositories | create | update | delete
112
+ |-- proposal list | create | optimize-description | generate | annotate | execute | archive | status | complete | send
113
+ |-- vault list | create | update | delete
114
+ | `-- files list | preview
115
+ |-- chat list | create | archive | delete | send
116
+ |-- hagitask list | schema | create | send
117
+ |-- plan list | get | create | update | enable | disable | delete | run
118
+ `-- skills local list | enable | disable | uninstall
119
+ gallery search | detail | install
120
+ trusted list | upsert | delete | reset
121
+ ```
105
122
 
106
- ### Stable release flow
123
+ ## Example Setup
107
124
 
108
- 1. Make sure the draft release maintained by Release Drafter already reflects the intended tag such as `v0.1.0`.
109
- 2. Run the local verification commands above.
110
- 3. Commit and push the release-ready source tree.
111
- 4. Publish the matching GitHub draft release for the stable tag.
112
- 5. Confirm the GitHub Actions summary shows build, typecheck, test, package inspection, version stamping, and `npm publish --tag latest --provenance --access public`.
125
+ The examples in the family sections run in order against a local backend. They assume:
113
126
 
114
- ## Environment Variables
127
+ - the backend listens on `http://127.0.0.1:35167`
128
+ - `hagi` is on `PATH`
129
+ - `json_field` reads one field from `--json` output
115
130
 
116
- The CLI resolves runtime settings centrally before every command. Explicit flags always override environment variables.
131
+ ```bash
132
+ export HAGI_API_BASE_URL=http://127.0.0.1:35167
133
+ json_field() { node -pe "JSON.parse(require('node:fs').readFileSync(0, 'utf8'))$1"; }
117
134
 
118
- | Variable | Purpose |
119
- | --- | --- |
120
- | `HAGI_API_BASE_URL` | Backend base URL. Defaults to `http://127.0.0.1:35168` when unset. |
121
- | `HAGI_API_TOKEN` | Bearer token attached as `Authorization: Bearer <token>`. |
122
- | `HAGI_API_LANG` | Shared `Accept-Language` header value. |
135
+ mkdir -p /tmp/cli-examples/repos/cli
136
+ PROJECT_ID=$(hagi project create --name "CLI Examples" --path /tmp/cli-examples --json | json_field '.project.id')
137
+ ```
123
138
 
124
- Global flags:
139
+ ## Project Commands
125
140
 
126
- - `--base-url <url>`
127
- - `--token <token>`
128
- - `--lang <lang>`
129
- - `--json`
141
+ List projects through the aggregation endpoint:
130
142
 
131
- ## Generate the API Client
143
+ ```bash
144
+ hagi project list
145
+ hagi project list --json
146
+ ```
147
+
148
+ 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:
149
+
150
+ ```bash
151
+ hagi project repositories --id "$PROJECT_ID"
152
+ hagi project repositories --id "$PROJECT_ID" --json
153
+ ```
132
154
 
133
- The CLI owns its own OpenAPI configuration in `openapi-ts.config.json`.
155
+ Create a project. The backend creates the directory if it does not exist. The new id is read from `project.id`:
134
156
 
135
157
  ```bash
136
- cd repos/cli
137
- npm run generate:api
138
- npm run generate:api:once
158
+ DEMO_PROJECT_ID=$(hagi project create \
159
+ --name "CLI Demo" \
160
+ --path /tmp/cli-demo \
161
+ --description "Automation sandbox" \
162
+ --json | json_field '.project.id')
139
163
  ```
140
164
 
141
- Default Swagger source:
165
+ Update only part of a project record. The CLI resolves current project data first so omitted fields are preserved:
142
166
 
143
- - `http://127.0.0.1:35168/swagger/v1/swagger.json`
167
+ ```bash
168
+ hagi project update --id "$DEMO_PROJECT_ID" --description "Updated description"
169
+ hagi project update --id "$DEMO_PROJECT_ID" --name "CLI Demo renamed"
170
+ ```
144
171
 
145
- When the backend cannot export Swagger in the current environment, keep `src/generated/api/**` aligned with the same controller contract used by `repos/web`, then regenerate when Swagger becomes available again.
172
+ Delete the demo project:
146
173
 
147
- `generate:api:once` matches the frontend workflow: it reuses an existing Swagger host when one is already up, otherwise it starts `repos/hagicode-core/src/PCode.SwaggerHost/PCode.SwaggerHost.csproj`, waits for Swagger readiness, runs the normal generator once, then stops the temporary host.
174
+ ```bash
175
+ hagi project delete --id "$DEMO_PROJECT_ID"
176
+ ```
148
177
 
149
178
  ## Proposal Commands
150
179
 
151
- `proposal list` queries proposal sessions through `GET /api/Sessions` with `type=Proposal`.
180
+ List proposal sessions for the example project:
152
181
 
153
182
  ```bash
154
- hagi proposal list --project-id <project-id>
183
+ hagi proposal list --project-id "$PROJECT_ID"
155
184
  hagi proposal list --include-archived --json
156
185
  ```
157
186
 
158
- `proposal create` only creates the proposal session. It does not trigger any later lifecycle API.
187
+ 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).
188
+
189
+ Create a proposal session only. The new id is read from `session.sessionId`:
190
+
191
+ ```bash
192
+ PROPOSAL_ID=$(hagi proposal create \
193
+ --project-id "$PROJECT_ID" \
194
+ --chief-complaint "Repository scope
195
+ Writable repositories:
196
+ - repos/cli
197
+ Read-only repositories:
198
+ - none
199
+
200
+ Add CLI completion for session workflows" \
201
+ --default-title \
202
+ --json | json_field '.session.sessionId')
203
+ ```
204
+
205
+ Update status and send a follow-up message:
206
+
207
+ ```bash
208
+ hagi proposal status --session-id "$PROPOSAL_ID" --status Reviewing
209
+ hagi proposal send --session-id "$PROPOSAL_ID" --content "Please continue"
210
+ ```
211
+
212
+ 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:
159
213
 
160
214
  ```bash
161
- hagi proposal create \
162
- --project-id <project-id> \
163
- --chief-complaint "Add npm commands for proposal APIs" \
164
- --default-title
215
+ hagi proposal optimize-description --session-id "$PROPOSAL_ID" --description "Add CLI completion for session workflows" --default-title
216
+ hagi proposal generate --session-id "$PROPOSAL_ID" --proposal-content-language en-US --enable-explore-mode
217
+
218
+ cat > annotations.json <<'EOF'
219
+ {"files":[{"fileName":"proposal.md","filePath":"proposal.md"}]}
220
+ EOF
221
+ hagi proposal annotate --session-id "$PROPOSAL_ID" --input annotations.json
222
+ cat annotations.json | hagi proposal annotate --session-id "$PROPOSAL_ID" --input - --json
223
+
224
+ hagi proposal execute --session-id "$PROPOSAL_ID"
225
+ hagi proposal complete --session-id "$PROPOSAL_ID"
226
+ hagi proposal archive --session-id "$PROPOSAL_ID"
165
227
  ```
166
228
 
167
- `proposal generate-name` only generates the proposal name for an existing session.
229
+ ### Explicit Lifecycle Sequence
230
+
231
+ The CLI keeps proposal lifecycle actions explicit and non-interactive. Each command maps to one backend action:
232
+
233
+ 1. `hagi proposal list`
234
+ 2. `hagi proposal create`
235
+ 3. `hagi proposal optimize-description --session-id <session-id> --description <text>` (sets the title, description, and proposal name)
236
+ 4. `hagi proposal generate --session-id <session-id> --proposal-content-language <language>` (optionally `--enable-explore-mode`)
237
+ 5. `hagi proposal annotate --session-id <session-id> --input <file>` (optional review loop)
238
+ 6. `hagi proposal execute --session-id <session-id>`
239
+ 7. `hagi proposal complete --session-id <session-id>`
240
+ 8. `hagi proposal archive --session-id <session-id>`
241
+
242
+ The CLI may suggest a next command, but it never chains later lifecycle APIs automatically.
243
+
244
+ Each command maps to one backend action. The CLI may suggest a next command, but it never chains later lifecycle APIs automatically.
245
+
246
+ ## Vault Commands
247
+
248
+ Create a vault with a local folder to browse. The new id is read from `vault.id`:
168
249
 
169
250
  ```bash
170
- hagi proposal generate-name --session-id <session-id>
251
+ mkdir -p /tmp/cli-vault-notes/docs
252
+ printf '# Notes\n' > /tmp/cli-vault-notes/docs/readme.md
253
+
254
+ VAULT_ID=$(hagi vault create \
255
+ --name "Main Notes" \
256
+ --type folder \
257
+ --physical-path /tmp/cli-vault-notes \
258
+ --json | json_field '.vault.id')
171
259
  ```
172
260
 
173
- `proposal generate` only starts proposal generation.
261
+ List registered vaults:
174
262
 
175
263
  ```bash
176
- hagi proposal generate --session-id <session-id> --enable-explore-mode
177
- hagi proposal generate --session-id <session-id> --hero-id <hero-id>
264
+ hagi vault list
265
+ hagi vault list --json
178
266
  ```
179
267
 
180
- `proposal execute` only starts proposal execution.
268
+ Update only part of a vault record. The CLI resolves the current vault snapshot first so omitted fields are preserved:
181
269
 
182
270
  ```bash
183
- hagi proposal execute --session-id <session-id>
271
+ hagi vault update --id "$VAULT_ID" --name "Renamed Vault"
184
272
  ```
185
273
 
186
- ## Explicit Lifecycle Sequence
274
+ Browse vault files and preview supported content:
187
275
 
188
- The CLI keeps proposal lifecycle actions explicit and non-interactive:
276
+ ```bash
277
+ hagi vault files list --id "$VAULT_ID"
278
+ hagi vault files list --id "$VAULT_ID" --path docs --json
279
+ hagi vault files preview --id "$VAULT_ID" --path docs/readme.md
280
+ ```
189
281
 
190
- 1. `hagi proposal list`
191
- 2. `hagi proposal create`
192
- 3. `hagi proposal generate-name`
193
- 4. `hagi proposal generate`
194
- 5. `hagi proposal execute`
282
+ Delete a vault and request backend cleanup of its local files:
283
+
284
+ ```bash
285
+ hagi vault delete --id "$VAULT_ID" --delete-local-files --json
286
+ ```
287
+
288
+ ## Chat Commands
289
+
290
+ Create a chat session. The new id is read from `session.sessionId`:
291
+
292
+ ```bash
293
+ CHAT_ID=$(hagi chat create \
294
+ --title "Repository Q&A" \
295
+ --project-id "$PROJECT_ID" \
296
+ --json | json_field '.session.sessionId')
297
+ ```
195
298
 
196
- Each command maps to one backend action. The CLI prints the suggested next command, but it never chains later lifecycle APIs automatically.
299
+ List chat sessions:
300
+
301
+ ```bash
302
+ hagi chat list --active-only
303
+ hagi chat list --include-archived --json
304
+ ```
305
+
306
+ Send a message, archive, and delete:
307
+
308
+ ```bash
309
+ hagi chat send --session-id "$CHAT_ID" --content "Summarize the current diff"
310
+ hagi chat archive --session-id "$CHAT_ID"
311
+ hagi chat delete --session-id "$CHAT_ID"
312
+ ```
313
+
314
+ ## HagiTask Commands
315
+
316
+ 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:
317
+
318
+ ```bash
319
+ hagi hagitask list
320
+ hagi hagitask list --json
321
+ ```
322
+
323
+ `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:
324
+
325
+ ```bash
326
+ hagi hagitask schema --task-id compose-commit
327
+ hagi hagitask schema --task-id compose-commit --json
328
+ ```
329
+
330
+ 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:
331
+
332
+ ```json
333
+ {
334
+ "inputs": {
335
+ "commitScope": "cli"
336
+ }
337
+ }
338
+ ```
339
+
340
+ Create the session from that task and input. The new id is read from `session.sessionId`:
341
+
342
+ ```bash
343
+ HAGITASK_ID=$(hagi hagitask create \
344
+ --title "Auto compose commit" \
345
+ --project-id "$PROJECT_ID" \
346
+ --task-id compose-commit \
347
+ --input input.json \
348
+ --repository-path repos/cli \
349
+ --need-push \
350
+ --target-branch-mode new-custom \
351
+ --target-branch-name feature/cli-completion \
352
+ --json | json_field '.session.sessionId')
353
+ ```
354
+
355
+ 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`.
356
+
357
+ 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`.
358
+
359
+ Send a follow-up message to the HagiTask session:
360
+
361
+ ```bash
362
+ hagi hagitask send --session-id "$HAGITASK_ID" --content "Retry with a smaller diff"
363
+ ```
364
+
365
+ **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.
366
+
367
+ ## Plan Commands
368
+
369
+ 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:
370
+
371
+ ```bash
372
+ hagi plan list
373
+ hagi plan list --json
374
+ hagi plan get --id "$PLAN_ID"
375
+ hagi plan get --id "$PLAN_ID" --json | jq .plan
376
+ ```
377
+
378
+ `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.
379
+
380
+ ### Create a plan
381
+
382
+ `plan create` reads a plan document with `--input <file>` or `--input -` for stdin. A recurring HagiTask plan:
383
+
384
+ ```json
385
+ {
386
+ "type": "hagitask",
387
+ "name": "Nightly review",
388
+ "enabled": true,
389
+ "schedule": {
390
+ "mode": "recurring",
391
+ "timeZone": "Asia/Shanghai",
392
+ "startDate": "2026-05-01",
393
+ "endDate": "2026-05-31",
394
+ "timeOfDay": "02:00",
395
+ "repeat": { "rule": "daily", "weekdays": [] }
396
+ },
397
+ "agentPool": { "strategy": "random", "agents": [ { "heroId": "5de02573-0000-4000-8000-000000000001" } ] },
398
+ "payload": {
399
+ "projectId": "11111111-2222-3333-4444-555555555555",
400
+ "presetTaskId": "nightly-review"
401
+ }
402
+ }
403
+ ```
404
+
405
+ A one-time proposal plan runs once at `runAt` and has no time zone or repeat fields:
406
+
407
+ ```json
408
+ {
409
+ "type": "proposal",
410
+ "name": "Weekly triage",
411
+ "schedule": { "mode": "once", "runAt": "2026-06-12T10:30:00Z" },
412
+ "payload": {
413
+ "title": "Weekly triage",
414
+ "chiefComplaint": "Triage the open issues and propose the next sprint",
415
+ "projectId": "11111111-2222-3333-4444-555555555555"
416
+ }
417
+ }
418
+ ```
419
+
420
+ ```bash
421
+ hagi plan create --input nightly-review.json
422
+ hagi plan create --input weekly-triage.json --disabled
423
+ PLAN_ID=$(hagi plan create --input nightly-review.json --json | json_field '.plan.id')
424
+ ```
425
+
426
+ 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.
427
+
428
+ ### Update, enable, disable and delete
429
+
430
+ `plan update` reads the current plan, applies your changes and sends the full plan back, so fields you do not mention keep their values:
431
+
432
+ ```bash
433
+ hagi plan update --id "$PLAN_ID" --name "Nightly review (EU)"
434
+ hagi plan update --id "$PLAN_ID" --input changes.json
435
+ hagi plan update --id "$PLAN_ID" --hero-id 5de02573-0000-4000-8000-000000000002 --enabled
436
+ ```
437
+
438
+ `--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.
439
+
440
+ `enable` and `disable` change only the enabled flag. When the plan already has that flag, nothing is sent and the output says so:
441
+
442
+ ```bash
443
+ hagi plan disable --id "$PLAN_ID"
444
+ hagi plan enable --id "$PLAN_ID" --json
445
+ ```
446
+
447
+ Delete a plan. It never runs again:
448
+
449
+ ```bash
450
+ hagi plan delete --id "$PLAN_ID"
451
+ hagi plan delete --id "$PLAN_ID" --json
452
+ ```
453
+
454
+ To change a plan with a text editor, take the current document, edit it and feed it back. Server-owned fields are dropped automatically:
455
+
456
+ ```bash
457
+ hagi plan get --id "$PLAN_ID" --json | jq .plan > plan.json
458
+ # edit plan.json, then:
459
+ hagi plan update --id "$PLAN_ID" --input plan.json
460
+ ```
461
+
462
+ 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:
463
+
464
+ ```text
465
+ Scheduled plan is invalid. (status=400)
466
+ - agentPool: A HagiTask plan needs an agent pool with one agent.
467
+ ```
468
+
469
+ Use `--json` to get the full backend body under `error.details`.
470
+
471
+ ### Run a plan now
472
+
473
+ `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`:
474
+
475
+ ```bash
476
+ hagi plan run --id "$PLAN_ID"
477
+ ```
478
+
479
+ `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:
480
+
481
+ ```bash
482
+ hagi plan run --id "$PLAN_ID" --background --json
483
+ ```
484
+
485
+ 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.
486
+
487
+ ## Skills Commands
488
+
489
+ 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:
490
+
491
+ ```bash
492
+ hagi skills local list
493
+ hagi skills local list --json
494
+ ```
495
+
496
+ 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:
497
+
498
+ ```bash
499
+ hagi skills local enable --skill-name hagi
500
+ hagi skills local disable --skill-name hagi --json
501
+ ```
502
+
503
+ Built-in skills cannot be uninstalled. Use `skills local disable` to stop one from loading. Remove a user skill by name:
504
+
505
+ ```bash
506
+ SKILL_NAME=demo-skill
507
+ hagi skills local uninstall --skill-name "$SKILL_NAME"
508
+ ```
509
+
510
+ 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.
511
+
512
+ Skill recommendation and install progress streaming are not part of the CLI. `skills gallery search`, `detail` and `install` are the discovery and install path.
513
+
514
+ Search the gallery and read one skill:
515
+
516
+ ```bash
517
+ hagi skills gallery search --query react
518
+ hagi skills gallery search --query react --json
519
+ hagi skills gallery detail --id vercel-react-best-practices --source vercel-labs/agent-skills
520
+ ```
521
+
522
+ 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:
523
+
524
+ ```bash
525
+ hagi skills gallery install \
526
+ --id vercel-react-best-practices \
527
+ --source vercel-labs/agent-skills \
528
+ --skill-slug vercel-react-best-practices
529
+ ```
530
+
531
+ Manage the trusted provider directory. `upsert` reads a JSON document from `--input <file>` or `--input -`:
532
+
533
+ ```bash
534
+ hagi skills trusted list
535
+ cat > provider.json <<'EOF'
536
+ {"providerId":"example-provider","displayName":"Example","isEnabled":true,"sortOrder":50,"matchRules":[{"matchType":"owner","value":"example"}]}
537
+ EOF
538
+ hagi skills trusted upsert --input provider.json
539
+ cat provider.json | hagi skills trusted upsert --input - --json
540
+ hagi skills trusted delete --provider-id example-provider
541
+ hagi skills trusted reset
542
+ ```
543
+
544
+ ## Environment Variables
545
+
546
+ The CLI resolves runtime settings centrally before every command. Explicit flags always override environment variables.
547
+
548
+ | Variable | Purpose |
549
+ | --- | --- |
550
+ | `HAGI_API_BASE_URL` | Backend base URL, used when `--base-url` is not given. Overrides the config file. Required when neither is set. |
551
+ | `HAGI_API_TOKEN` | Bearer token attached as `Authorization: Bearer <token>`. |
552
+ | `HAGI_API_LANG` | Shared `Accept-Language` header value. |
197
553
 
198
554
  ## Exit Codes
199
555
 
@@ -207,6 +563,7 @@ Stable non-zero exits used by the CLI:
207
563
  | `23` | HTTP 403 |
208
564
  | `24` | HTTP 404 |
209
565
  | `29` | HTTP 409 |
566
+ | `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 |
210
567
  | `40` | Other HTTP 4xx |
211
568
  | `50` | HTTP 5xx |
212
569
  | `60` | Transport or connection failure |