@hagicode/cli 0.6.1 → 0.7.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/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./chunks/main-De3wSyDS.js");s.runCli().then(e=>{e!==0&&(process.exitCode=e)}).catch(e=>{const n=e instanceof Error?e.stack??e.message:String(e);process.stderr.write(`${n}
2
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./chunks/main-B6wZS0Ah.js");s.runCli().then(e=>{e!==0&&(process.exitCode=e)}).catch(e=>{const n=e instanceof Error?e.stack??e.message:String(e);process.stderr.write(`${n}
3
3
  `),process.exitCode=s.EXIT_CODES.UNKNOWN});
package/dist/main.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/main-De3wSyDS.js");exports.createCliApp=e.createCliApp;exports.runCli=e.runCli;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/main-B6wZS0Ah.js");exports.createCliApp=e.createCliApp;exports.runCli=e.runCli;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "HagiCode CLI for project, proposal, vault, chat, and AutoTask management.",
5
5
  "homepage": "https://github.com/HagiCode-org/cli#readme",
6
6
  "bugs": {
@@ -8,7 +8,7 @@
8
8
  "research": "2504283ea845440c7909ff1da20bfb70f846d55ca16565740168848cfb8060b1",
9
9
  "acceptance": "11ca06794d0ec19e87d1a4e883a67014563e9750b9fa62a8bedd43a69d52ca65",
10
10
  "plan": "bcc4c0509f2971083775db2b3678f41c55f16e69deba2d994d3ed636f314d917",
11
- "run": "aaf8a667c163afea0796a8c80d5760ee689923a76b0c7f1855e55405d9b0b5d0",
11
+ "run": "ea3fc7f5c54230cef95b09595cb296432f733bdbe8cbe42c40cf73f6f6b900e4",
12
12
  "amend": "be621d1221a85656851548aee1e9e9d78480288bd856baf1bdbf50cb3317f49b"
13
13
  }
14
14
  }
@@ -4,17 +4,34 @@ You are the orchestrator. You dispatch work and keep the record. You do not plan
4
4
 
5
5
  `<root>` is the registered project path given in the run's input. Every command is `hagi saga <command> <saga> --path <root>` and works from any directory. Every command that changes a Saga also takes `--token <token>`: the token that `claim` printed.
6
6
 
7
+ The run's options may name these, with the defaults shown:
8
+ - `<provider>` (default `claude`): the provider whose quota a Worker spends when no provider hint applies.
9
+ - `<threshold>` (default `10`): the remaining percent at or below which you hold dispatch.
10
+ - `<provider-hints>` (default none): pairs `<key>=<provider>`, separated by commas. `<key>` is a hero id or a Worker role (`implementation`, `verification` or `tuning`); `<provider>` is a provider id such as `claude` or `codex`. A hint says which provider a hero's executor spends.
11
+
7
12
  0. **Claim first.** `hagi saga claim <saga> --session <your session id> --path <root> --json`. Keep the printed token for every later command. If it refuses because another owner holds the Saga, stop and report it. Never take over by age, and never write a paused snapshot over another owner's progress.
8
13
  1. **Freeze.** `hagi saga freeze <saga> --path <root> --token <token>`. If it refuses (open questions, open amendments, a changed contract), run `hagi saga set-status <saga> --state paused --note "<reason>" --path <root> --token <token>`, stop and report the reasons. Do not edit contract files and do not decide open questions. A first freeze writes revision 1. After an amendment was merged it writes the next revision, chained to the previous one, and reopens the items whose acceptance command changed.
9
14
  2. **Check before every item and at every heartbeat.** `hagi saga assert-owner <saga> --path <root> --token <token>` before each dispatch, then `hagi saga check <saga> --phase run --path <root>`. If a check fails, pause the same way and report. An open amendment also means: pause.
15
+ - **Quota before every Worker.** Before you create any Worker (step 3, and a tuning worker in step 4), resolve the provider that Worker spends, in this order, and use the first that applies:
16
+ 1. the hint for the Worker's hero id, when the run input names the hero that Worker runs;
17
+ 2. the hint for the Worker's role;
18
+ 3. `<provider>`.
19
+ Use the provider id from the hint exactly as written. Never translate an executor type name into a provider id.
20
+ - Then run `hagi usage limits --wait-until-above <threshold> --provider <P> --max-wait 480 --json`, where `<P>` is the resolved provider. It reads that provider's short limit windows (six hours or less) and waits for at most 480 seconds (eight minutes) for them to rise above the threshold. After a ready result it may add a stagger of up to 120 seconds. Act on the exit code and `guard.state`:
21
+ - exit `0`, `guard.state` `ready`: dispatch the Worker.
22
+ - exit `0`, `guard.state` `low`: do not dispatch. This is not a failure and not a pause. Add `guard.waitedSeconds` of this call to the wait total of this dispatch, record the reading (step 6), and run the command again. When the wait total of this dispatch reaches 21600 seconds (six hours), pause the Saga instead, with the reason: provider `<P>`, its remaining percent and its `resetsAtUtc`. Write the paused snapshot and stop as in step 9.
23
+ - exit `34`, no usable figure: dispatch the Worker, log `quota unknown` with the provider in `orchestrator-log.md`, and read again before the next Worker.
24
+ - Any other exit code: pause the same way as a failed check, and report it.
25
+ - The JSON output's `windows` lists each window with `remainingPercent` and `resetsAtUtc`. Record the provider, the remaining percent and the reset time for every dispatch and every held dispatch (step 6).
10
26
  3. **Work one item at a time** (`hagi saga items <saga> --path <root>` lists them with their commands), unless the goal allows parallel items. For each item:
27
+ - Before each implementation, verification and tuning Worker, not once per item, run the quota check of step 2 for the provider that Worker spends.
11
28
  - Create each Worker with `hagi saga session create --saga <saga> --path <root> --title "<ITEM> <role>" --role <role> --master <your session id> --item <ITEM>`, where `<role>` is `implementation` or `verification`. This command takes the token from `SAGA_EXECUTION_TOKEN`, set to the token that `claim` printed, and refuses `--token`. It creates the chat session with its Saga metadata and records it in `sessions.md`. Then give the Worker its instructions below.
12
29
  - An implementation worker writes code and tests only. Give it the goal, the item, the scope and the safety rules, the project root and its report path.
13
30
  - A separate verification worker runs the item's command from `acceptance.md` in the project root, writes the output to `<root>/.hagicode/sagas/<saga>/runtime/evidence/<ITEM>.log`, and writes `<root>/.hagicode/sagas/<saga>/runtime/reports/<ITEM>.md` with the line `Status: PASS` (or `状态: PASS`) only if the exit code is 0. It changes no files.
14
31
  - Only `hagi saga tick <saga> <ITEM> --path <root> --token <token>` ticks an item. It refuses without a PASS report and an evidence file.
15
- 4. **On a failed verification, classify it.** An environment fault (missing module, port in use, quota) does not count. A real failure counts: give a tuning worker the failure log path, then verify again. After 3 real failures run `hagi saga block <saga> <ITEM> "<reason>" --path <root> --token <token>` and move on.
32
+ 4. **On a failed verification, classify it.** An environment fault (missing module, port in use) does not count. A quota fault does not count either: a worker that stopped because its provider's quota was exhausted is not given to a tuning worker and is not counted. Re-dispatch the same Worker role with the same instructions, unchanged, once the step-2 quota check for that Worker's own provider (the provider it used, not the default) returns exit `0` with `guard.state` `ready`. A real failure counts: give a tuning worker the failure log path, then verify again. After 3 real failures run `hagi saga block <saga> <ITEM> "<reason>" --path <root> --token <token>` and move on.
16
33
  5. **After a revision reopened items,** also re-verify any item whose command is the full regression: its own command did not change, so it was not reopened.
17
- 6. **Keep the record.** Append a line to `<root>/.hagicode/sagas/<saga>/runtime/orchestrator-log.md` after every dispatch, verification and tick. `hagi saga session create` records each Worker session it creates in `<root>/.hagicode/sagas/<saga>/runtime/sessions.md`, so do not append those sessions to that file by hand. Run `hagi saga set-status <saga> --state running --note "<heartbeat>" --path <root> --token <token>` at every heartbeat, and `hagi saga status <saga> --path <root> --json` to read the projection at the end.
34
+ 6. **Keep the record.** Append a line to `<root>/.hagicode/sagas/<saga>/runtime/orchestrator-log.md` after every dispatch, held dispatch, verification and tick. Every dispatch line and every held dispatch line carries the quota reading it was made on: the provider, the remaining percent, the `resetsAtUtc` and the seconds waited. `hagi saga session create` records each Worker session it creates in `<root>/.hagicode/sagas/<saga>/runtime/sessions.md`, so do not append those sessions to that file by hand. Run `hagi saga set-status <saga> --state running --note "<heartbeat>" --path <root> --token <token>` at every heartbeat, and put the latest quota reading (provider, remaining percent, reset time and seconds waited) in that note. Run `hagi saga status <saga> --path <root> --json` to read the projection at the end.
18
35
  7. **Archive each settled item's sessions.** Once an item is ticked or blocked and its workers have stopped, archive the worker sessions you created for it: `hagi chat archive --session-id <session id> --json`. Take the IDs from `<root>/.hagicode/sagas/<saga>/runtime/sessions.md`. Archive only sessions you created in this run: never your own orchestrator session, a session that is still running, a session you did not create, or the Saga itself (`hagi saga archive` is not part of a run). Archiving leaves the reports, evidence and `sessions.md` record in `runtime/` untouched. Log each archived ID in `orchestrator-log.md`. If an archive call fails, log the error and go on: do not retry in a loop and do not pause for it.
19
- 8. **Never leave a command hanging.** Every shell command must end on its own: never read from standard input, and give waits a limit. If something does not answer, report it instead of waiting forever.
20
- 9. **Stop** when every item is ticked or blocked, when a pause condition appears, or when the human's run options say so. Write the terminal snapshot first (`set-status --state done` only when every active item is verified, otherwise `paused` with the reason). Once every worker has stopped, archive any remaining worker sessions as in step 7, then run `hagi saga release <saga> --path <root> --token <token> --workers-settled`. Never archive the Saga itself and do not commit. Report the revision, every item with its state and evidence path, the blocked items with reasons, the sessions you created and which of them you archived, and the next step for a human.
36
+ 8. **Never leave a command hanging.** Every shell command must end on its own: never read from standard input, and give waits a limit. If something does not answer, report it instead of waiting forever. Every quota check is bounded as in step 2, and a dispatch that has waited six hours in total on quota pauses the Saga.
37
+ 9. **Stop** when every item is ticked or blocked, when a pause condition appears, or when the human's run options say so. Write the terminal snapshot first (`set-status --state done` only when every active item is verified, otherwise `paused` with the reason; a quota pause names the provider, the remaining percent and the reset time). Once every worker has stopped, archive any remaining worker sessions as in step 7, then run `hagi saga release <saga> --path <root> --token <token> --workers-settled`. Never archive the Saga itself and do not commit. Report the revision, every item with its state and evidence path, the blocked items with reasons, the sessions you created and which of them you archived, and the next step for a human.
@@ -101,6 +101,7 @@ hagi
101
101
  |-- skills local list | enable | disable | uninstall
102
102
  | gallery search | detail | install
103
103
  | trusted list | upsert | delete | reset
104
+ |-- usage limits [--provider] [--wait-until-above <percent> --max-wait <seconds> --jitter-seconds <seconds>]
104
105
  `-- agent lock acquire | status | renew | heartbeat | release | requisition | list
105
106
  ```
106
107
 
@@ -342,6 +343,26 @@ hagi agent lock list --prefix eu-1/
342
343
  - Timing: 10-minute feedback timeout, 24-hour maximum hold, 2-minute heartbeat interval, 30-second claim window, 60-second waiter TTL.
343
344
  - Never run `requisition` unless the user explicitly ordered a forced take.
344
345
 
346
+ ## Usage commands
347
+
348
+ `hagi usage limits` reads the backend's Token Monitor usage: each provider's limit windows, with the remaining percent and the reset time of each. It is read-only and reads quota only from the backend, never from local files.
349
+
350
+ ```bash
351
+ hagi usage limits --json
352
+ hagi usage limits --provider claude --json
353
+ hagi usage limits --provider claude --wait-until-above 10 --max-wait 480 --json
354
+ ```
355
+
356
+ - `--provider <id>` keeps one provider, for example `claude` or `codex`.
357
+ - `--wait-until-above <percent>` waits until every short window (six hours or less) of `--provider` is above that remaining percent. It requires `--provider`. Weekly windows are reported but never hold the wait. The wait sleeps toward the blocking window's reset, re-reads at least every 5 minutes, and stops after `--max-wait` seconds (default `1800`, at most `21600`; `0` checks once). A read that fails is retried three times, 30 seconds apart, within the same limit.
358
+ - `--jitter-seconds <seconds>` (default `120`, at most `3600`; `0` disables) sets a random delay after a wait that ended `ready`, so runs that waited on the same reset do not start together. It applies only with `--wait-until-above`.
359
+ - `--max-wait` and `--jitter-seconds` without `--wait-until-above` are refused with exit `2` before any backend call.
360
+ - The JSON output has `state` (`ready`, `low`, or `unknown`; `null` for a plain read that has figures), `reason`, `windows` (each with `provider`, `kind`, `remainingPercent`, `resetsAtUtc`, `minutesToReset`, `windowMinutes`, `watched`) and `guard`, which is `null` for a plain read and otherwise holds `state`, `thresholdPercent`, `waitedSeconds`, `jitterSeconds`, `checkedAtUtc` and `blockers`.
361
+ - Low quota is a successful reading: `ready` and `low` both exit `0`, so a shell chain does not stop on low quota. Read `state` (or `guard.state`) from `--json`, or the first word of the text (`READY`, `LOW`), to tell them apart. Only an unusable reading exits `34`.
362
+ - The text output begins `READY`, `LOW`, `UNAVAILABLE` for a wait, and `READ` or `UNAVAILABLE` for a plain read. It names the blocking windows and their reset times, or the reason.
363
+
364
+ The saga orchestrator runs this wait before every Worker it dispatches; see [`saga.md`](saga.md).
365
+
345
366
  ## Exit codes
346
367
 
347
368
  Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of the gRPC status the backend answered with:
@@ -357,6 +378,7 @@ Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of t
357
378
  | `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, or a failed MonoSpecs clone |
358
379
  | `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 |
359
380
  | `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` |
381
+ | `34` | Quota unavailable: `hagi usage limits` could not read a usable figure, either on a plain read or after a wait's retries. Nothing is known about the quota, so none is claimed. Low quota is not this code: it exits `0` |
360
382
  | `40` | Other HTTP 4xx |
361
383
  | `50` | HTTP 5xx |
362
384
  | `60` | Transport or connection failure |
@@ -108,9 +108,19 @@ and `updatedAt`. `items <saga-id> --json` returns `{ "sagaId": "...", "items": [
108
108
  canonical `id`, `title`, `state`, `cmd`, and `note`.
109
109
 
110
110
  `status <saga-id> --json` returns the `saga-status/2` projection directly, with `schema`, `sagaId`, `revision`, `state`,
111
- `updatedAt`, `note`, `artifacts`, `nextArtifact`, `counts`, `openAmendments`, and `items`. It derives this view from the
112
- Saga files; `updatedAt` is the time of this observation, not the execution heartbeat in `runtime/status.json`. The query
113
- does not refresh or rewrite that stored status, and its default `note` is empty rather than a cached execution note.
111
+ `updatedAt`, `note`, `artifacts`, `nextArtifact`, `counts`, `openAmendments`, and `items`. Counts, artifacts, `nextArtifact`,
112
+ `openAmendments`, and `items` are derived from the Saga files at read time.
113
+
114
+ `state` and `note` come from the stored snapshot in `runtime/status.json` when it is valid: a `state` of `draft`,
115
+ `planned`, `running`, `paused`, or `done`. The reported `state` and `note` are the stored values as written, so a stored
116
+ `paused` or `done` is reported even when the files derive another state. A stored `running` is the one exception: it is
117
+ honored, with its note, only when the derived state (from the contract revision, artifacts, and item counts) is also
118
+ `running`. Otherwise the derived state applies with an empty `note`, because `running` is the state that goes stale when
119
+ an orchestrator exits. A missing file, unparsable JSON, a non-object document, or any other `state` value falls back to
120
+ the derived state with an empty `note`.
121
+
122
+ `updatedAt` is the time of this observation, not the execution heartbeat in `runtime/status.json`. The query
123
+ does not refresh or rewrite that stored status, never repairs it, and creates no `runtime/` file or directory.
114
124
 
115
125
  All three queries are strictly read-only: they do not initialize or repair resources, create runtime files, run the Saga
116
126
  tool, or execute acceptance commands. Missing optional draft files use canonical empty-data behavior; malformed or
@@ -248,6 +258,19 @@ Write and check commands accept `--json`. Success and refusal write one object t
248
258
 
249
259
  Human output prints a one-line outcome and one line per refusal reason.
250
260
 
261
+ ## Quota guard in a run
262
+
263
+ The `run` method reads quota before every Worker it creates, with `hagi usage limits --wait-until-above <threshold> --provider <P> --max-wait 480 --json`. `<threshold>` defaults to `10`, and `<P>` is the provider the Worker spends. The read waits at most 480 seconds per call, then the orchestrator runs it again, and a dispatch that has waited six hours in total on quota pauses the Saga with the provider, remaining percent and reset time as the reason.
264
+
265
+ - Exit `0` with `guard.state` `ready`: the Worker is dispatched.
266
+ - Exit `0` with `guard.state` `low`: the Worker is held, which is not a failure and not a pause. The orchestrator records the reading and reads again.
267
+ - Exit `34`: no usable figure. The orchestrator logs `quota unknown`, dispatches, and reads again before the next Worker.
268
+ - Any other exit code pauses the Saga the same way a failed check does.
269
+
270
+ Which provider a Worker spends follows its hero's executor. The run's options may carry `<provider-hints>`, pairs `<key>=<provider>` separated by commas, where `<key>` is a hero id or a Worker role (`implementation`, `verification`, `tuning`) and `<provider>` is a provider id such as `claude` or `codex`. For each Worker the orchestrator uses the hint for its hero id first, then the hint for its role, then the run's default `<provider>` (`claude` unless the run says otherwise). It uses the id exactly as written. The run's starter should write one hint for every hero whose executor is not the default provider, so a Worker of such a hero is read against the window it will actually spend. A missing or wrong hint reads the wrong window, so it costs an unnecessary hold or a `quota unknown` record, not a stalled run.
271
+
272
+ Every heartbeat note and every dispatch or held-dispatch log line carries the reading it was made on: the provider, the remaining percent, the reset time and the seconds waited. A worker that stops because its provider's quota is exhausted is not a failed verification: it is not counted and not given to a tuning worker, and it is re-dispatched unchanged after the quota read for its own provider returns `ready`. The command itself is documented in [`cli-usage.md`](cli-usage.md#usage-commands).
273
+
251
274
  ## Runtime ignore precondition
252
275
 
253
276
  Before any command writes a file under a Saga's `runtime/` directory (`claim`, `set-status`, `tick`, `block`, `session create`,
@@ -107,8 +107,9 @@ hagi saga items <saga-id> --project-id <project-id> --json
107
107
  ```
108
108
 
109
109
  These queries read the selected project's Saga files without initializing or repairing them, refreshing runtime status,
110
- or executing acceptance commands. Status is a fresh `saga-status/2` observation; its `updatedAt` is not the stored
111
- execution heartbeat in `runtime/status.json`. `init` installs resources and `new` creates a Saga, so run either only
110
+ or executing acceptance commands. Status is a fresh `saga-status/2` observation: its counts and items come from the Saga
111
+ files, and its `state` and `note` may come from a valid stored state in `runtime/status.json` (a stored `running` only
112
+ when the files also derive `running`). Its `updatedAt` is not the stored execution heartbeat in `runtime/status.json`. `init` installs resources and `new` creates a Saga, so run either only
112
113
  when the user explicitly asks for that change. Contract and lifecycle operations run through `hagi saga` with the execution token rules in `references/saga.md`.
113
114
 
114
115
  ### Skills