@ucsandman/legcli 0.8.0 → 0.10.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/NOTICE +8 -0
  3. package/README.md +639 -560
  4. package/bin/fake-agent.mjs +4 -4
  5. package/bin/leg.mjs +43 -12
  6. package/docs/DECISIONS.md +20 -2
  7. package/docs/ERRORS.md +205 -0
  8. package/docs/README.md +5 -1
  9. package/docs/REUSE.md +1 -1
  10. package/docs/VOCABULARY.md +22 -0
  11. package/docs/board-guide.md +33 -1
  12. package/docs/cli-contracts.md +36 -1
  13. package/docs/concepts.md +42 -3
  14. package/docs/configuration.md +23 -1
  15. package/docs/faq.md +19 -0
  16. package/docs/getting-started.md +272 -251
  17. package/docs/harness.md +319 -0
  18. package/docs/history.md +172 -0
  19. package/docs/runtime-tap.md +156 -0
  20. package/fixtures/verified.json +1 -1
  21. package/package.json +7 -3
  22. package/scripts/build-docs-site.mjs +18 -4
  23. package/scripts/check-branding.mjs +118 -0
  24. package/scripts/check-claims.mjs +1 -1
  25. package/scripts/license-sign.mjs +1 -1
  26. package/scripts/limits-table.mjs +1 -1
  27. package/scripts/live-limits.mjs +1 -1
  28. package/scripts/npm-publish-gate.mjs +114 -0
  29. package/scripts/probe.mjs +4 -3
  30. package/scripts/seed-fake-cards.mjs +4 -3
  31. package/scripts/seed-floor-board.mjs +5 -4
  32. package/scripts/seed-wes-board.mjs +5 -4
  33. package/scripts/stripe-setup.mjs +1 -1
  34. package/scripts/sync-harness-engine.mjs +159 -0
  35. package/scripts/sync-leg-agents.mjs +127 -0
  36. package/src/accounts.mjs +6 -4
  37. package/src/adapters/codex.mjs +1 -1
  38. package/src/attach.mjs +125 -23
  39. package/src/auth.mjs +2 -2
  40. package/src/board/board.css +23 -1
  41. package/src/board/board.js +17 -5
  42. package/src/board/history.js +377 -0
  43. package/src/board/index.html +33 -0
  44. package/src/board/sessions.js +95 -7
  45. package/src/bundle.mjs +54 -8
  46. package/src/chain.mjs +1 -1
  47. package/src/contract.mjs +4 -3
  48. package/src/fsx.mjs +5 -2
  49. package/src/handoff.mjs +6 -6
  50. package/src/harness/cli.mjs +281 -0
  51. package/src/harness/fingerprint.mjs +68 -0
  52. package/src/harness/index.mjs +407 -0
  53. package/src/harness/registry.mjs +124 -0
  54. package/src/harness/vendor/agnostic-ai/LICENSE +21 -0
  55. package/src/harness/vendor/agnostic-ai/UPSTREAM.json +30 -0
  56. package/src/harness/vendor/agnostic-ai/core/safety/guards.json +96 -0
  57. package/src/harness/vendor/agnostic-ai/core/templates/targets.json +252 -0
  58. package/src/harness/vendor/agnostic-ai/engine/harness/README.md +199 -0
  59. package/src/harness/vendor/agnostic-ai/engine/harness/apply.cjs +247 -0
  60. package/src/harness/vendor/agnostic-ai/engine/harness/bundle.cjs +243 -0
  61. package/src/harness/vendor/agnostic-ai/engine/harness/capture.cjs +119 -0
  62. package/src/harness/vendor/agnostic-ai/engine/harness/common.cjs +375 -0
  63. package/src/harness/vendor/agnostic-ai/engine/harness/index.cjs +55 -0
  64. package/src/harness/vendor/agnostic-ai/engine/harness/sources/claude.cjs +330 -0
  65. package/src/harness/vendor/agnostic-ai/engine/harness/sources/codex.cjs +314 -0
  66. package/src/harness/vendor/agnostic-ai/engine/harness/status.cjs +171 -0
  67. package/src/harness/vendor/agnostic-ai/engine/harness/targets/agy.cjs +113 -0
  68. package/src/harness/vendor/agnostic-ai/engine/harness/targets/claude.cjs +158 -0
  69. package/src/harness/vendor/agnostic-ai/engine/harness/targets/codex.cjs +832 -0
  70. package/src/harness/vendor/agnostic-ai/engine/harness/targets/cursor.cjs +87 -0
  71. package/src/harness/vendor/agnostic-ai/engine/harness/targets/gemini.cjs +128 -0
  72. package/src/harness/vendor/agnostic-ai/engine/harness/targets/generic.cjs +424 -0
  73. package/src/harness/vendor/agnostic-ai/engine/harness/toml.cjs +149 -0
  74. package/src/harness/vendor/agnostic-ai/engine/hooks/shim.cjs +431 -0
  75. package/src/history/cli.mjs +159 -0
  76. package/src/history/common.mjs +119 -0
  77. package/src/history/index.mjs +429 -0
  78. package/src/history/providers/agy.mjs +91 -0
  79. package/src/history/providers/claude.mjs +161 -0
  80. package/src/history/providers/codex.mjs +133 -0
  81. package/src/history/providers/copilot.mjs +94 -0
  82. package/src/history/providers/grok.mjs +138 -0
  83. package/src/history/worktrees.mjs +116 -0
  84. package/src/hook.mjs +49 -49
  85. package/src/land.mjs +7 -35
  86. package/src/launcher.mjs +38 -26
  87. package/src/ledger.mjs +6 -6
  88. package/src/license.mjs +10 -9
  89. package/src/live-capture.mjs +1 -1
  90. package/src/mergequeue.mjs +5 -5
  91. package/src/orchestrator.mjs +28 -4
  92. package/src/preferences.mjs +37 -3
  93. package/src/redact.mjs +24 -6
  94. package/src/resume.mjs +17 -15
  95. package/src/runner.mjs +2 -2
  96. package/src/scheduler.mjs +1 -1
  97. package/src/server.mjs +224 -18
  98. package/src/session-detail.mjs +15 -1
  99. package/src/sessions.mjs +15 -3
  100. package/src/share.mjs +2 -2
  101. package/src/stations/agent.mjs +1 -1
  102. package/src/sync/dashclaw.mjs +4 -4
  103. package/src/synthesis.mjs +165 -0
  104. package/src/taps/agy.mjs +2 -2
  105. package/src/taps/claude-usage.mjs +1 -1
  106. package/src/taps/claude.mjs +177 -170
  107. package/src/taps/codex.mjs +286 -286
  108. package/src/taps/grok.mjs +2 -2
  109. package/src/taps/mod.mjs +340 -0
  110. package/src/trust.mjs +205 -36
  111. package/src/usage.mjs +5 -1
  112. package/src/worktree.mjs +6 -5
  113. package/fixtures/live/agy/attempt-1-scratch-workspace.out.log +0 -1
  114. package/fixtures/live/agy/err.log +0 -0
  115. package/fixtures/live/agy/out.log +0 -1
  116. package/fixtures/live/agy/supervisor.log +0 -2
  117. package/fixtures/live/claude/err.log +0 -0
  118. package/fixtures/live/claude/out.log +0 -1
  119. package/fixtures/live/claude/supervisor.log +0 -2
  120. package/fixtures/live/codex/err.log +0 -1
  121. package/fixtures/live/codex/out.log +0 -8
  122. package/fixtures/live/codex/supervisor.log +0 -2
  123. package/fixtures/live/grok/err.log +0 -32
  124. package/fixtures/live/grok/out.log +0 -7
  125. package/fixtures/live/grok/supervisor.log +0 -2
@@ -189,6 +189,27 @@ stderr are 0 bytes, and codex's is the one stdin notice. observed-live.
189
189
  out of `src/adapters/index.mjs` until `grok login` has been completed on the
190
190
  machine and `node scripts/probe.mjs --adapter grok --repo <toy>` passes.
191
191
 
192
+ ## `leg harness` (the portable harness)
193
+
194
+ What the command reads and writes per client, each fact from the engine's
195
+ adapter source (`src/harness/vendor/agnostic-ai/engine/harness/{sources,targets}/*.cjs`,
196
+ byte for byte the Agnostic AI engine) and verified by
197
+ `test/harness-*.test.mjs` against fixture homes on 2026-09-16.
198
+
199
+ | client | read as a source | written as a destination | shim |
200
+ |---|---|---|---|
201
+ | Claude Code | `~/.claude/CLAUDE.md` (+ `@imports` inside the home), `SOUL.md`, the hooks and permissions of its settings file, the `mcpServers` of `~/.claude.json` and `~/.claude/.mcp.json` (the oauth block is never read), `agents/*.md`, `commands/*.md`, `skills/*/SKILL.md` | `~/.claude/leg-rules.md` + one `@` line appended to `CLAUDE.md`; owned hook groups and permission entries in its settings file; owned servers in `~/.claude.json`; `agents/`, `commands/`, skill links | none: the bundle is Claude's dialect |
202
+ | Codex CLI | `~/.codex/AGENTS.md`, `config.toml` (`hooks.*`, `mcp_servers.*`), `agents/*.toml`, `prompts/*.md`, `skills/`, `rules/*.rules` (per-invocation absolute-path approvals skipped; `auth.json` never read) | `AGENTS.md` (whole file, owned), `config.toml` regions `hooks` (with `[hooks.state]` trust hashes, self-tested), `skills` (duplicate disables), `mcp`; `agents/*.toml`; `prompts/*.md`; skill links; `rules/leg-harness.rules` prefix rules | none |
203
+ | Antigravity CLI | not a source | `~/.gemini/GEMINI.md` (owned), the `leg-harness` key in `~/.gemini/config/hooks.json`, `mcp_config.json` servers, `config/agents`, `config/commands`, `config/skills` links | the engine's hook shim, chained with `++` |
204
+ | Gemini CLI | not a source | `GEMINI.md`, owned hook groups in its settings file, `commands/*.toml`, `mcpServers`, skill links | shim |
205
+ | Grok CLI | no | no (reported `unsupported`) | |
206
+
207
+ Exit codes: `0` fine; `1` stale or attention (`check`, `sync`) or a doctor
208
+ failure; `2` usage; `3` not captured, no source, or consent declined
209
+ (`enable` without `--yes` and without a terminal). `--json` on `status`,
210
+ `inspect`, `sync`, `check`, `explain`, `history`, `doctor` prints the same
211
+ record the board reads.
212
+
192
213
  ## Interactive taps
193
214
 
194
215
  What `leg claude|codex|agy|grok` reads while the real interactive CLI runs. Same
@@ -370,6 +391,20 @@ in [VOCABULARY.md](VOCABULARY.md). Board routes: `GET /api/sessions`,
370
391
  `POST /api/sessions/:id/end`, `DELETE /api/sessions/:id`, with the list pushed
371
392
  as the SSE `sessions` event (source: src/server.mjs, src/board/sessions.js).
372
393
 
394
+ ### History index
395
+
396
+ `<LEG_HOME>/history/index.json` is the one file `leg history` writes: per
397
+ provider, per transcript, size, mtime and a small scrubbed record (source:
398
+ src/history/index.mjs). What each provider reads from its CLI's own store,
399
+ with the observed file and field names, is the header of each
400
+ `src/history/providers/<name>.mjs`; the support matrix and the resume
401
+ commands quoted from each CLI's `--help` (observed-live 2026-09-16: `claude
402
+ --resume <id>`, `codex resume <id>`, `grok --resume <id>`, `agy --conversation
403
+ <id>`; copilot has none Leg has verified) are in [history.md](history.md).
404
+ Board routes, owner only: `GET /api/history`, `GET /api/history/:id`,
405
+ `GET /api/history/providers`, `POST /api/history/refresh`,
406
+ `GET /api/worktrees` (source: src/server.mjs, src/board/history.js).
407
+
373
408
  ## Limit signals
374
409
 
375
410
  Recorded in `fixtures/limits/<group>/<id>.json` and classified by
@@ -393,7 +428,7 @@ non-zero exit → `failed`. Every outcome except `completed`, `auth_failed` and
393
428
  `killed` asks the chain to hand off.
394
429
 
395
430
  <!-- limits-table:start -->
396
- Generated by `node scripts/limits-table.mjs` from 22 fixtures (4 observed-live, 18 docs-only). Classification `limit` hands the card to the next agent as a usage limit; `auth` is a failed launch (never a limit); `launch` is a failed launch that the next agent may still try; `budget` is a turn or spend cap set by Baton itself; `info` must never classify as a limit.
431
+ Generated by `node scripts/limits-table.mjs` from 22 fixtures (4 observed-live, 18 docs-only). Classification `limit` hands the card to the next agent as a usage limit; `auth` is a failed launch (never a limit); `launch` is a failed launch that the next agent may still try; `budget` is a turn or spend cap set by Leg itself; `info` must never classify as a limit.
397
432
 
398
433
  | id | adapter | class | where | source | text (excerpt) | produced by |
399
434
  |----|---------|-------|-------|--------|----------------|-------------|
package/docs/concepts.md CHANGED
@@ -108,12 +108,14 @@ things in order (`src/attach.mjs`, `src/bundle.mjs`):
108
108
  it again at the transition and during all-out waiting. Machine Settings is
109
109
  copied only when a new terminal starts.
110
110
  3. **Switch.** The agent process is stopped and the terminal restored. The
111
- bundle's `context-handoff-bundle load <id>` output is written to
111
+ bundle's `context-handoff-bundle load <id>` output (with the `## Synthesis`
112
+ section prepended if `.leg/SYNTHESIS-<session-id>.md` is present) is written to
112
113
  `.leg/RESUME-<session-id>.md` and copied to `.leg/RESUME.md`, and the next
113
114
  agent starts in the same terminal with a short pointer prompt as its first
114
115
  positional argument: `claude "<prompt>"`, `codex "<prompt>"`,
115
- `agy -i "<prompt>"`. The prompt names the per-session file, and says to check
116
- `git status` and `git diff`, continue, and not ask the human to restate the
116
+ `agy -i "<prompt>"`. The prompt names the per-session file, directs the agent
117
+ to read Synthesis first and treat ruled-out approaches as settled, and says to
118
+ check `git status` and `git diff`, continue, and not ask the human to restate the
117
119
  task.
118
120
  4. **All out.** If every option is walled, Leg prints each one with its reset
119
121
  time, soonest first, then waits in the terminal with a one-line countdown
@@ -123,6 +125,33 @@ things in order (`src/attach.mjs`, `src/bundle.mjs`):
123
125
  (`{ agent, account, resets_at, since }`) and shows status `waiting`. Ctrl-C
124
126
  in the terminal, or End on the card, quits with exit 3.
125
127
 
128
+ ## The portable harness
129
+
130
+ Off by default. `leg harness enable` adds a step to the hand-off, between
131
+ **Choose** and **Switch**: the destination's working environment is prepared
132
+ before the destination starts. The source client (the one you actually
133
+ configure: Claude Code or Codex) is captured into a client-neutral bundle
134
+ under `~/.leg/harness/bundle/` (rules with their imports inlined, identity,
135
+ hooks, skills, subagents, slash commands, MCP servers with every credential
136
+ replaced by `${NAME}`, permissions), and that bundle is rendered into the
137
+ destination client's own files: `AGENTS.md`, `config.toml` regions and hook
138
+ trust entries for Codex; `GEMINI.md`, `hooks.json` and `mcp_config.json` for
139
+ agy; `leg-rules.md` plus one import line, hook groups and MCP servers for
140
+ Claude Code. Skills are linked, not copied. What a destination cannot
141
+ represent is dropped with a reason, and the session records exactly what
142
+ transferred (`session.harness`, the `harness` event, the **Harness** section
143
+ of the drawer).
144
+
145
+ The saved policy decides what an unattended hand-off may do: `warn` reports
146
+ and writes nothing, `sync` writes managed state when that is safe, `strict`
147
+ refuses a destination it cannot make safe and tries the next option. A
148
+ fingerprint of the source's files makes an unchanged environment free to
149
+ check. Everything Leg writes this way carries `GENERATED by Leg harness`, sits
150
+ in a marked region or an owned key inside files you also own, is backed up
151
+ before it is overwritten, and is skipped when you hand-edited it. The source
152
+ client is never written; credentials never move; which login runs is still the
153
+ account layer's decision. The whole contract is in [harness.md](harness.md).
154
+
126
155
  ## The resume pointer
127
156
 
128
157
  `.leg/RESUME.md` is the file humans and other agents open by habit, so Leg
@@ -303,6 +332,16 @@ notes carry six sections in the bundle's own vocabulary:
303
332
  The next leg's prompt starts with the bundle's `load` output (the resume
304
333
  text) followed by the same contract.
305
334
 
335
+ ### The synthesis layer
336
+
337
+ Alongside raw state, Leg supports an agent-maintained judgment record in `.leg/SYNTHESIS-<session-id>.md`. At handoff time, Leg reads this file and inlines it verbatim into `.leg/RESUME-<session-id>.md` and `.leg/RESUME.md` as a `## Synthesis` section before the raw bundle dump.
338
+
339
+ - **Schema v1**: A 2-line header (`synthesis_version: 1`, `session: <id> updated: <ISO-8601 UTC>`) followed by up to four optional sections in fixed order: `## Ruled out`, `## Decisions`, `## Next steps`, `## Open questions` (max 5 bullets each, one line per bullet).
340
+ - **Size cap**: 4 KB. Beyond that, Leg includes the first 4 KB plus a trailing `[synthesis truncated]` marker.
341
+ - **Resilience**: If the header is malformed, Leg still inlines the body prefixed with `[synthesis header invalid, rendering body as-is]`. If the file is absent or empty, no `## Synthesis` section is emitted and handoff degrades to today's raw dump.
342
+ - **Pointer prompt**: Directs the taking-over agent to read the Synthesis section first if present, treat ruled-out approaches as settled, and start from the top-ranked next step.
343
+ - **Board indicator**: Shows a `synthesis` chip on the terminal card when the synthesis file exists and was modified within the last 3 checkpoints.
344
+
306
345
  ## Worktrees
307
346
 
308
347
  Every card runs in its own git worktree: `<repo>/.leg-worktrees/<card-id>`
@@ -29,6 +29,7 @@ These apply to `leg claude|codex|agy|grok`.
29
29
  | `LEG_NO_AUTO_APPROVE` | (unset) | set to `1` to opt out of auto-approve mode | `src/preferences.mjs` |
30
30
  | `LEG_WARN_PCT` | `85` | the percentage of either usage window that turns the card amber, records a `warning` event and rings the terminal bell once | `src/usage.mjs` |
31
31
  | `LEG_NO_HANDOFF` | (unset, hand-off on) | set to `1` to warn and record but never switch agents | `src/attach.mjs` |
32
+ | `LEG_HARNESS_HOME` | the OS home | where the portable harness reads and writes client configuration (`~/.claude`, `~/.codex`, `~/.gemini` under it); the test suite points it at a throwaway directory. `CLAUDE_CONFIG_DIR`, `CODEX_HOME` and `GEMINI_CONFIG_DIR` move one client each, as everywhere else in Leg | `src/harness/registry.mjs` |
32
33
  | `LEG_NO_OPEN` | (unset, opens once) | set to `1` to start the board without opening a browser | `bin/leg.mjs` |
33
34
  | `LEG_NO_BOARD` | (unset) | set to `1` to run a session with no board at all (the record under `$LEG_HOME/sessions/` is still kept; the test suite uses this) | `src/attach.mjs` |
34
35
  | `LEG_WAIT_TICK_MS` | `1000` | how often the all-out countdown redraws and re-checks Ctrl-C / End while waiting for the first reset | `src/attach.mjs` |
@@ -99,7 +100,7 @@ Off unless explicitly enabled; a sync failure never blocks or fails a card
99
100
  ## Seller and checkout site
100
101
 
101
102
  These are for the checkout/key-delivery site and its deployment helpers, not
102
- normal Leg CLI use.
103
+ normal Leg use.
103
104
 
104
105
  | variable | meaning | read in |
105
106
  |----------|---------|---------|
@@ -171,6 +172,26 @@ junctions as links, never following them, then deletes the directory. Nothing
171
172
  under your real home is written at any point. Start a session on a named
172
173
  account with `LEG_ACCOUNT=<name>`, or let a limit hand off to it.
173
174
 
175
+ ## The portable harness
176
+
177
+ Off until `leg harness enable`. Its settings live in `~/.leg/preferences.json`
178
+ beside the hand-off order:
179
+
180
+ ```json
181
+ { "harness": { "enabled": true, "policy": "sync", "source": "claude" } }
182
+ ```
183
+
184
+ | key | values | meaning |
185
+ |-----|--------|---------|
186
+ | `enabled` | `true`, `false` (default) | the consent `leg harness enable` records; `leg harness disable` clears it and removes nothing |
187
+ | `policy` | `warn`, `sync` (default on enable), `strict` | what an unattended hand-off may do: report only; write managed state when safe; refuse a destination that cannot be made safe |
188
+ | `source` | `claude`, `codex` | the client whose harness is carried; auto-detected from `~/.claude/CLAUDE.md` then `~/.codex/AGENTS.md` when unset |
189
+
190
+ `~/.leg/harness/policy.json` (optional) names what is deliberately not
191
+ carried: sections to drop, hooks, skills and MCP servers to exclude, each with
192
+ a reason, and the Codex model ladder. State, backups and the evidence trail
193
+ live under `~/.leg/harness/`. Every detail: [harness.md](harness.md).
194
+
174
195
  ## Test and development seams
175
196
 
176
197
  Not meant for normal use; documented for completeness.
@@ -179,6 +200,7 @@ Not meant for normal use; documented for completeness.
179
200
  |----------|---------|---------|
180
201
  | `LEG_SKIP_KILL` | set to `1` to make the supervisor skip actually killing a leg's process (used by tests that need an "unkillable agent") | `src/runner.mjs` |
181
202
  | `LEG_SERVER_SCRIPT` | override the server script path the launcher spawns | `src/launcher.mjs` |
203
+ | `COPILOT_HOME` | where `leg history` looks for the Copilot CLI store instead of `~/.copilot` (Claude, Codex and Grok use their own `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `GROK_HOME`; Antigravity has no variable and is found under the OS home) | `src/history/providers/copilot.mjs` |
182
204
 
183
205
  ## Network exposure
184
206
 
package/docs/faq.md CHANGED
@@ -162,6 +162,25 @@ pre-commit hook both run `scripts/privacy-check.mjs`, which additionally
162
162
  scans the whole tree for a short list of strings specific to the private
163
163
  codebase Leg's runner/ledger were ported from.
164
164
 
165
+ **Does the next agent get my rules, hooks, skills and MCP servers, or just the task?**
166
+ Just the task, unless you turn on the [portable harness](harness.md)
167
+ (`leg harness enable`). Then a hand-off also renders the source client's
168
+ global working agreement, identity, hooks, skills, subagents, slash commands,
169
+ MCP servers and permissions into the destination's own files, as far as that
170
+ client can represent them, and the terminal's details say what was dropped and
171
+ why (Codex has no SSE transport; agy has no `SessionStart` hook; a skill that
172
+ already exists as a real directory is left alone). Credentials never move: an
173
+ MCP key becomes `${NAME}` and you export it for each client. Off by default,
174
+ and `leg harness disable` turns it off without removing anything.
175
+
176
+ **Will the portable harness overwrite a file I edited?**
177
+ No. Every file it writes carries `GENERATED by Leg harness` in its first lines,
178
+ and inside a file you also own it writes a marked `leg harness` region. A
179
+ file whose content changed since Leg wrote it is backed up once under
180
+ `~/.leg/harness/backups` and skipped; the sync reports `attention` with the
181
+ path, and `leg harness sync --force` is the only way it gets replaced. The
182
+ source client is never written at all.
183
+
165
184
  **What happens if I close the terminal instead of Ctrl-C?**
166
185
  The board server and any running agents keep running as detached processes.
167
186
  Run `node bin/leg.mjs down` (or `npm run stop`) from another terminal to