@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
package/README.md CHANGED
@@ -1,560 +1,639 @@
1
- # leg
2
-
3
- **Type `leg claude`, `leg codex`, `leg agy` or `leg grok` instead of the bare command. You get the same interactive agent; Leg opens a board next to it, watches the usage limit, keeps a handoff bundle current, and when the limit hits it starts the next agent in the same terminal from that bundle.**
4
-
5
- [![License: commercial](https://img.shields.io/badge/license-commercial-blue.svg)](https://legcli.com/license)
6
- [![Node 22+](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](https://legcli.com/docs/getting-started)
7
- [![Runtime deps: 0](https://img.shields.io/badge/runtime%20deps-0-lightgrey.svg)](https://legcli.com/docs)
8
- [![Local first](https://img.shields.io/badge/runs-on%20your%20machine-informational.svg)](https://legcli.com/docs/configuration)
9
-
10
- ![The Leg board mid-handoff: claude's terminal row changes to "handing off" and reads "handing off to codex, 5h limit reached", while the codex row above it shows what it landed on main](https://legcli.com/img/leg-handoff.gif)
11
-
12
- *Claude hits the five-hour wall. The terminal reads `handing off to codex`, and codex carries on there. Nothing is retyped. ([the full 53-second run](https://legcli.com/#handoff))*
13
-
14
- ![The Leg board at 1280px: a headline reading "All 4 terminals are on claude, and claude has 5% left", under it the staleness of the reading; a lit claude panel with its 7 day gauge at 95 percent past the reserve notch and its 5 hour gauge at 38; half panels for codex, at the wall, and agy, which publishes no figure; four terminal rows with their prompts and buttons; and counts for finished terminals, what landed and background tasks](https://legcli.com/img/docs/terminals-1280.png)
15
-
16
- You keep using your coding agents exactly as you do today, in any terminal,
17
- from your own config directory: Leg adds its hooks in a separate per-session
18
- settings file and never edits yours. `leg claude --model opus` is
19
- `claude --model opus` with four things running alongside it:
20
-
21
- 1. **A board.** Opened once in your browser, reused after that. Every Leg
22
- session in every terminal is a card on it: agent, account, repo and branch,
23
- the task, the files it is touching, its 5h and 7d usage, what has landed on
24
- trunk. Two sessions editing the same file in one repo are flagged on both
25
- cards, and a second session in a checkout that already has one gets its
26
- own worktree and a **Land** button instead of writing over the first.
27
- 2. **Usage tracking** per agent and account, from what each CLI already
28
- exposes: Claude Code's usage endpoint and its `StopFailure` hook, Codex's
29
- read-only app-server rate-limit read, agy's log, and Grok's billing proxy endpoint.
30
- 3. **A context handoff bundle** ([context-handoff-bundle](https://pypi.org/project/context-handoff-bundle/))
31
- refreshed as the session goes, so the work is always ready to hand off.
32
- 4. **The handoff itself.** Near the limit you get a warning. At the limit Leg
33
- saves the bundle, stops the agent, and starts the next option in the same
34
- terminal from that bundle: another login of the same agent if you added
35
- one, otherwise the next agent in the order shown on the terminal card.
36
- The default is claude -> codex -> agy (with grok supported in handoff order),
37
- and Settings changes the default for new terminals. Nothing is retyped.
38
- When every option is out, it tells you which resets first and when, waits
39
- for that reset with a countdown, and starts that agent from the bundle.
40
-
41
- Subscription logins only: Leg strips `ANTHROPIC_API_KEY`,
42
- `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_CUSTOM_HEADERS`,
43
- `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_API_BASE`, `GEMINI_API_KEY`,
44
- `GOOGLE_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, `GOOGLE_GENAI_USE_VERTEXAI`,
45
- `GOOGLE_GENAI_USE_ENTERPRISE`, `GOOGLE_CLOUD_PROJECT`,
46
- `GOOGLE_CLOUD_LOCATION`, `GOOGLE_APPLICATION_CREDENTIALS`, `GROK_API_KEY`,
47
- `XAI_API_KEY`, `CLAUDECODE`, `CLAUDE_CODE_*`, `CLAUDE_EFFORT`, and
48
- `CLAUDE_PLUGIN_DATA` before any agent starts. It then sets
49
- `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0` for a detached
50
- Claude print session. Leg never edits `~/.claude/settings.json` or any other
51
- settings file of yours; its hooks ride in a separate per-session `--settings`
52
- file. The one thing it does write outside `~/.leg` is the folder-trust
53
- answer, below. `leg uninstall` removes only `~/.leg`.
54
-
55
- ### The folder-trust answer
56
-
57
- Each agent CLI asks once, the first time it runs in a directory, whether you
58
- trust that folder, and Claude Code asks a second question when a `CLAUDE.md`
59
- above the repo imports a file from outside it. A handoff fires when the limit
60
- hits, which is usually when nobody is watching, so an agent that stopped on
61
- that prompt would sit there until morning with the bundle already written.
62
-
63
- Before starting an agent, Leg records the same answer you would have given,
64
- for the repository you already chose by typing `leg claude` in it:
65
-
66
- | agent | file | what is written |
67
- |---|---|---|
68
- | claude | `~/.claude.json` (or `$CLAUDE_CONFIG_DIR/.claude.json`) | `projects["<repo>"].hasTrustDialogAccepted: true` |
69
- | claude | the same entry, only when such an import exists | `hasClaudeMdExternalIncludesApproved`, `hasClaudeMdExternalIncludesWarningShown` |
70
- | codex | `~/.codex/config.toml` | `[projects."<repo>"] trust_level = "trusted"` |
71
- | agy | `~/.gemini/trustedFolders.json` | `"<repo>": "TRUST_FOLDER"` |
72
-
73
- For Claude Code this is the documented remedy: its permissions guide says to
74
- set `projects["<path>"].hasTrustDialogAccepted` to `true` in `~/.claude.json`,
75
- where `<path>` is the repository root.
76
-
77
- Leg never creates one of those files: if it is not there, that CLI has not
78
- run as you yet and its own first-run flow is next, with you at the keyboard. It
79
- never rewrites a file to say what it already says, and it never removes what is
80
- already in one. When it approves an external `CLAUDE.md` import it prints the
81
- full path of every file it approved, to the terminal and to the session
82
- timeline on the board, so the approval is on the record rather than invisible.
83
-
84
- Set `LEG_TRUST=never` to switch all of it off and answer the prompts
85
- yourself.
86
-
87
- ## Contents
88
-
89
- - [60-second run](#60-second-run)
90
- - [What Leg reads from each agent](#what-leg-reads-from-each-agent)
91
- - [How a handoff works](#how-a-handoff-works)
92
- - [Two sessions in one repo](#two-sessions-in-one-repo)
93
- - [More than one human](#more-than-one-human)
94
- - [The board](#the-board)
95
- - [Second accounts, and what the terms say](#second-accounts-and-what-the-terms-say)
96
- - [What is and is not touched](#what-is-and-is-not-touched)
97
- - [CLI reference](#cli-reference)
98
- - [Background tasks: the v0.1 extras](#background-tasks-the-v01-extras)
99
- - [Troubleshooting](#troubleshooting)
100
- - [Documentation](#documentation)
101
- - [Contributing](#contributing)
102
- - [License](#license)
103
-
104
- ## 60-second run
105
-
106
- Prerequisites: Node 22 or newer, git, Python 3 with pip, and at least one
107
- logged-in agent CLI (`claude`, `codex`, `agy` or `grok`).
108
-
109
- ```
110
- npm install -g @ucsandman/legcli
111
- pip install -U context-handoff-bundle
112
- cd <any repo>
113
- leg claude
114
- ```
115
-
116
- That is the whole setup. The first `leg <agent>` starts the board on
117
- http://127.0.0.1:4747 and opens it; later sessions reuse it. Anything after the
118
- agent name passes straight through (`leg codex -m gpt-5.3-codex-spark`,
119
- `leg claude --resume`). The agent's own prompt and permission flags pass
120
- through unchanged, and your settings file is never edited: Leg's hooks ride
121
- in a separate per-session `--settings` file.
122
-
123
- The source repository is private. Leg is commercial, source-available
124
- software: every `.mjs` file that runs is in the package you just installed, at
125
- `$(npm root -g)/legcli/src`, and the license lets you read it and modify
126
- your own copy. There is nothing compiled, minified or bundled to see through.
127
-
128
- ## What Leg reads from each agent
129
-
130
- Nothing is guessed from screen scraping. Each tap was read from the CLI's
131
- source or documentation and then checked on a real machine (2026-09-11,
132
- Claude Code 2.1.268, codex-cli 0.153.4, agy 1.2.0); the rightmost column says
133
- which.
134
-
135
- | agent | usage percentages | the wall (limit hit) | how Leg attaches | status |
136
- |-------|-------------------|----------------------|--------------------|--------|
137
- | claude | `GET api.anthropic.com/api/oauth/usage` with the login Claude Code stored, the same data as `/usage` and the built-in status line (`five_hour`, `seven_day`, `utilization`, `resets_at`); polled every 60 s | `StopFailure` hook with `error: rate_limit` ([docs](https://code.claude.com/docs/en/hooks#stopfailure)) | one extra settings file per session via `--settings`, carrying only Leg's own hooks; `autoContinueAtUsageLimit` is set to `false` because Leg owns the handoff | observed live |
138
- | codex | read-only `account/rateLimits/read` through the app-server, polled every 60 s by the board and active attach; windows are identified by duration (300 minutes = 5h, 10080 = 7d) | `task_complete.error.codex_error_info: usage_limit_exceeded`, message "You've hit your usage limit … try again at …" (`codex-rs/protocol/src/error.rs`) | no model turn and no hook are injected; the board reads the CLI backend and records only returned windows | verified by source and regression tests |
139
- | agy | none exposed (agy's own status line fetches a quota summary that is written nowhere) | `RESOURCE_EXHAUSTED`, "it resets in …", "out of quota" in the log | `--log-file` per session; `~/.gemini/antigravity-cli/history.jsonl` gives the prompts and conversation id | observed live (a real `RESOURCE_EXHAUSTED` with its reset was read from the log on 2026-09-11) |
140
- | grok | `GET cli-chat-proxy.grok.com/v1/billing?format=credits` and `GET cli-chat-proxy.grok.com/v1/user?include=subscription` with OAuth token from `~/.grok/auth.json` (`creditUsagePercent`, `currentPeriod` weekly reset); polled every 60 s | `-32003` rate limit error, "You've hit the rate limit for your plan. Try again later.", "Rate limited (429)", `StopFailureKind::RateLimit` (`xai-org/grok-build`) | `--debug-file grok.log` per session; `~/.grok/sessions/<encoded-cwd>/prompt_history.jsonl` gives prompts and session id | verified by source and proxy endpoint |
141
-
142
- Why not Claude Code's status line JSON (`rate_limits.five_hour.used_percentage`):
143
- on 2.1.268 the custom `statusLine` Leg passes through `--settings` did not
144
- run, so the endpoint poll is the source. Leg still writes a `statusLine`
145
- entry that records the same fields, so the moment a build honours it the poll
146
- becomes a fallback.
147
-
148
- For Codex, Leg does not infer availability from a lower percentage: only an
149
- explicit available answer from the backend clears an earlier wall. The board
150
- labels each bar as `<n>% used` and marks an old reading as stale rather than
151
- presenting it as current.
152
-
153
- ## How a handoff works
154
-
155
- 1. **Warning.** At 85 % of any window (`LEG_WARN_PCT`) the session card turns
156
- amber, the event log names the next option, and the terminal bell rings once.
157
- 2. **Limit.** claude: the `StopFailure` hook fires with `rate_limit`. codex: the
158
- rollout reports `usage_limit_exceeded`. agy: the log says
159
- `RESOURCE_EXHAUSTED`. The account is marked walled until the reset the CLI
160
- reported (or the soonest known window reset).
161
- 3. **Bundle.** Leg writes structured notes (task, the last messages from the
162
- transcript, `git diff --stat`, dirty files, files edited this session, recent
163
- commits, why it stopped) and runs `context-handoff-bundle save --repo-local`
164
- with one slug per leg; each save writes its own timestamped bundle next to
165
- the last one. A checkpoint of the same bundle is taken every two minutes
166
- while the session is active.
167
- 4. **Switch.** The agent process is stopped, the terminal is restored, and the
168
- next option starts in the same terminal with a short pointer prompt:
169
- read `.leg/RESUME-<session-id>.md` (the `context-handoff-bundle load`
170
- output plus the reason for the switch), check `git status` and `git diff`,
171
- continue, do not ask the human to restate the task. The same text is copied
172
- to `.leg/RESUME.md`, the file people open by habit, and both are stamped
173
- with the commit and the live terminals they describe. `claude "<prompt>"`, `codex "<prompt>"`
174
- and `agy -i "<prompt>"` all open the normal interactive session with that
175
- first turn.
176
- 5. **Order.** Other accounts of the same agent come first, then every other
177
- agent in the saved order, each tried once. The order is a priority list, not
178
- a rotation: put agy at the bottom and agy is the last option from a Claude
179
- terminal and from a Codex terminal alike. The board shows the exact
180
- sequence with the agent running now skipped, plus the preferred option and
181
- the first option eligible from current install and limit state. Use **Change
182
- order** on a terminal card to change that terminal, or Settings to set the
183
- default copied by new terminals. An option whose CLI is missing or whose
184
- wall has not reset is skipped.
185
- 6. **All out.** The terminal prints each option with its reset time, soonest
186
- first, then stays open with a countdown to the first reset and starts that
187
- agent from the bundle when it arrives. The card says `waiting for <agent>
188
- at <time>`. Ctrl-C (or End on the card) quits with exit 3 instead.
189
-
190
- You can force a handoff any time: the **Hand off now** button on the card, or
191
- `leg sessions handoff <id>`. Verified on this machine: `leg claude` opened
192
- the real Claude Code TUI with Leg's hooks firing into the session log, the
193
- usage poll recorded 36 % of the 5h window and 74 % of the 7d window, the
194
- warning fired at 96 % of the 7d window and named codex as the next option, and
195
- a limit saved the bundle, stopped claude and started codex in the same
196
- terminal with the pointer prompt. A real `StopFailure` arrived on 2026-09-11
197
- and is kept at `fixtures/live/claude/limit-rate_limit.json`; to drive the path
198
- on demand, `leg sessions simulate-limit <id>` sends the same `StopFailure`
199
- `rate_limit` payload Claude Code would send through Leg's hook: verified end
200
- to end on a haiku session, the hook set the limit, the runner saved the
201
- bundle, stopped claude and started codex, which read `.leg/RESUME.md` on its
202
- first turn. The simulated wall clears after two minutes and is never kept as
203
- evidence. The first real `StopFailure` was saved that way, with secrets
204
- scrubbed, at `fixtures/live/claude/limit-rate_limit.json`, and the claude docs
205
- row flipped to observed-live (`node scripts/live-limits.mjs`). The same
206
- capture is wired for codex `usage_limit_exceeded` and agy `RESOURCE_EXHAUSTED`;
207
- no payload for either has been kept yet.
208
-
209
- Terminals started by this version can change order while they run. An older
210
- terminal stays on the order it started with; its card says a restart is needed
211
- and can save the desired default for the next launch. A normal agent exit ends
212
- the terminal. It does not trigger a handoff.
213
-
214
- ## Two sessions in one repo
215
-
216
- Two agents in one working tree write over each other's files. So when you
217
- start `leg codex` in a checkout where `leg claude` is already live, the new
218
- session gets its own git worktree, `<repo>/.leg-worktrees/<session-id>` on
219
- branch `leg/<session-id>`, cut from the branch the checkout has out, and the
220
- terminal prints one line saying where it is. The agent starts there; the card,
221
- the usage tracking and the handoff work the same. `--no-worktree` shares the
222
- checkout on purpose (Leg takes the flag out; the agent never sees it).
223
-
224
- The card of a session with its own worktree has a **Land** button. It sends
225
- the branch through the merge queue: whatever the agent left uncommitted is
226
- committed on the branch, the branch is rebased onto its base, the repo's test
227
- command runs (`package.json` `test`, `pytest`, or none with a warning), and the
228
- base is fast-forwarded, never merged. When a step fails nothing lands and the
229
- card says why: `rebase-conflict` with the files, `tests-red` with the end of
230
- the output, `dirty-trunk` when the checkout has local changes the landing
231
- would overwrite. Local changes it would not touch are left alone. The
232
- landed-on-trunk list says which terminal landed each commit. **Remove** safely
233
- prunes a finished session only when its worktree is clean and its branch is
234
- already on the base. **Remove record** is a separate visible button with a
235
- confirmation: it removes only Leg's saved session record and deliberately
236
- keeps the worktree, branch, unmerged commits, and dirty files.
237
-
238
- Verified live on 2026-09-11 with three haiku sessions in a throwaway repo. The
239
- first stayed in the checkout; the second and third each got a worktree and
240
- appended a line to README.md. Land on the second, clicked on the real board,
241
- ran the repo's tests and fast-forwarded main; Land on the third bounced with
242
- `rebase-conflict` on README.md and kept its commit on its branch; the
243
- landed-on-trunk list named the second terminal (the screenshot above).
244
-
245
- ## More than one human
246
-
247
- Off until you run it. `leg share on` binds the board to your Tailscale
248
- address (or `--bind lan`, or an address you name) and gives every human their
249
- own name and token; until then the board stays on `127.0.0.1` and there is no
250
- token at all.
251
-
252
- ```
253
- leg share on your own link, printed once
254
- leg share add sam sam's link, printed once
255
- leg share who is on the board (never a token again)
256
- leg share rotate sam sam's old link stops working
257
- leg share off back to 127.0.0.1; every link stops working
258
- ```
259
-
260
- A token is kept as a sha256 hash, so a lost link is re-issued, never re-read.
261
- The board takes the token out of the address bar and keeps it in the browser.
262
- Your own browser on this machine needs no token.
263
-
264
- What another human sees is the Terminals region, read-only. Each panel says
265
- whose terminal it is. On a panel that is not theirs there is no prompt, no file
266
- name, no path, no bundle and no event log; what stays is the agent and session
267
- tail, the status word, the sentence `read-only: wes owns this terminal`,
268
- repo@branch, the worktree line, the elapsed clock, and one button,
269
- **Request handoff**. The instrument head prints `not shared` in place of every
270
- percentage. A request lands on the owner's panel as `sam asked to take this
271
- terminal at 11:04 PM` with **Approve sam** and **Dismiss sam**. The background
272
- side of the board (cards, logs, the floor) stays the owner's alone. A terminal
273
- belongs to the human who started it: `LEG_PERSON=sam leg claude` on the
274
- same machine is sam's card, not yours.
275
-
276
- The security pass that goes with it: every `/api` route needs a token, the
277
- event stream included; twenty wrong tokens from one address and that address
278
- waits a minute; one identity gets 600 requests a minute; a guest gets 403 on
279
- everything that is not theirs; and the tests send a bad and a missing token to
280
- every route. There is still no TLS, so keep this on Tailscale or a network you
281
- trust. Verified live on 2026-09-11: two terminals on one machine, one wes's and
282
- one sam's; sam's board showed wes's card with the prompt hidden and only
283
- **Request handoff**, and sam's request reached wes's board (`~/.leg/board.log`:
284
- "hand-off requested by sam").
285
-
286
- ## The board
287
-
288
- `leg <agent>` opens it; `leg open` reopens it; `leg down` stops it.
289
-
290
- - **Instrument head**: one row per login, sticky at the top of the board and of
291
- the floor. Each row carries the 5h and 7d rails, the percentage, when that
292
- window resets and how long that is, a burn-rate sentence under the 5h rail,
293
- where and when Leg read the number, and one word for the state: `under 60`,
294
- `over 60`, `over 85`, `stale <n>m`, `at the wall` or `no reading`. A login
295
- at its wall keeps both rails and gains `at the wall`, `back <day time>` and
296
- `in <duration>` beside them.
297
- - **Terminals**: one full-width panel per session, the ones that need an answer
298
- first. Agent and session tail, the status word, the first prompt as a button,
299
- exactly one sentence (the highest-ranked thing true about the terminal), an
300
- `also:` disclosure naming the rest, the files as comma-separated text,
301
- `repo@branch`, `own worktree, from main` when the session cut its own
302
- worktree, and an elapsed clock. Two live sessions in one repo touching the
303
- same file print `codex (codex-99ab) is changing src/server.mjs in another
304
- checkout; whoever lands second rebases` on both panels, and a panel that needs
305
- you rises one step and says `waiting on you` in place of its status word.
306
- After a Land the sentence is `landed on <base>, <7-char sha>, <n> files,
307
- +<added>/-<removed>`, or `Land was attempted at <time> onto <base> and
308
- bounced: <first line of the reason>. The branch still holds every commit;
309
- nothing was lost.`
310
- - **Landed on main**: one flat list across every repo the board can see, newest
311
- first, each row with the short sha, the subject, a `repo@branch` chip, and
312
- when plus who. A commit a Land put there says `landed by <agent> (<id tail>)`.
313
- - **Buttons**, in a fixed order that never reflows: Land, Hand off now,
314
- Details, End. Once a session has ended, Remove and Remove record take End's
315
- place. Details opens an expansion in flow under the panel.
316
- - Below it, optional **Background tasks** an agent runs in a separate worktree
317
- without joining the terminal conversation (see below), then **Settings**.
318
-
319
- The board reads `~/.leg/sessions/*/session.json` over server-sent events; a
320
- session whose runner process is gone is marked `lost`, never shown as live.
321
-
322
- ## Second accounts, and what the terms say
323
-
324
- Optional. `leg accounts add claude work` creates
325
- `~/.leg/accounts/claude/work`, junctions your `hooks`, `skills`, `agents`,
326
- `commands`, `plugins`, `rules`, `scripts`, `output-styles` and `tools` into it,
327
- copies `settings.json`, `CLAUDE.md` and the status-line scripts (refreshed from
328
- your real `~/.claude` before every launch), and prints one line to paste:
329
-
330
- ```
331
- $env:CLAUDE_CONFIG_DIR='C:\Users\you\.leg\accounts\claude\work'; claude auth login
332
- ```
333
-
334
- Same for codex (`CODEX_HOME`; `config.toml`, `AGENTS.md`, `skills`, `prompts`,
335
- `rules`, `plugins`, `agents`, `hooks`, `memories` shared). agy 1.2.0 has no
336
- config-directory override, so it stays one account. Only the login lives in
337
- the account directory; `leg accounts rm` removes the junctions and the
338
- directory and never touches your real home.
339
-
340
- The terms, as published (effective dates below):
341
-
342
- - Anthropic Consumer Terms (effective 2025-10-08): "You may not share your
343
- Account login information, Anthropic API key, or Account credentials with
344
- anyone else" and you "must not bypass any of our systems or protective
345
- measures."
346
- - Anthropic Usage Policy (effective 2025-09-15): do not "Coordinate malicious
347
- activity across multiple accounts to avoid detection or circumvent product
348
- guardrails" or "Utilize automation in account creation."
349
- - OpenAI Terms of Use (effective 2026-01-01): "You may not share your account
350
- credentials or make your account available to anyone else" and you may not
351
- "circumvent any rate limits or restrictions or bypass any protective
352
- measures."
353
-
354
- Owning two paid subscriptions is not named as prohibited by either. Rotating to
355
- a second account of the same vendor because the first one is rate-limited sits
356
- close to OpenAI's "circumvent any rate limits" wording and Anthropic's
357
- "circumvent product guardrails". Leg's default chain switches vendors
358
- (claude → codex → agy), which is plainly fine. Same-vendor rotation only
359
- happens after you run `leg accounts add`; that is your call.
360
-
361
- ## What is and is not touched
362
-
363
- - **Never edited**: `~/.claude/settings.json`, `~/.claude.json`,
364
- `~/.codex/config.toml`, agy's files, your repo's settings. Claude Code gets
365
- hooks through a per-session `--settings` file under `~/.leg`; codex and
366
- agy get nothing injected.
367
- - **Written in your repo**: `.leg/` (session notes, `RESUME.md` and one
368
- `RESUME-<session-id>.md` per hand-off),
369
- `.context-handoffs/` (the bundles) and `.leg-worktrees/` (a second
370
- session's worktree), all added to `.git/info/exclude`, plus the
371
- `leg/<session-id>` branch of a session with its own worktree. Landing
372
- fast-forwards your branch; nothing is ever pushed.
373
- - **Stripped from every agent's environment**: `ANTHROPIC_API_KEY`,
374
- `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_CUSTOM_HEADERS`,
375
- `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_API_BASE`, `GEMINI_API_KEY`,
376
- `GOOGLE_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, `GOOGLE_GENAI_USE_VERTEXAI`,
377
- `GOOGLE_GENAI_USE_ENTERPRISE`, `GOOGLE_CLOUD_PROJECT`,
378
- `GOOGLE_CLOUD_LOCATION`, `GOOGLE_APPLICATION_CREDENTIALS`, `CLAUDECODE`,
379
- `CLAUDE_CODE_*`, `CLAUDE_EFFORT`, and `CLAUDE_PLUGIN_DATA`. `CLAUDE_CODE_*`
380
- does not include `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS`: Leg sets that one
381
- to `0` for a detached Claude print session.
382
- - **Read but never written or printed**: Claude Code's stored login, sent only
383
- to `api.anthropic.com` for the usage numbers. The ledger scrubs bearer tokens
384
- and key shapes from every line regardless.
385
- - **`leg uninstall --yes`**: removes `~/.leg` (sessions, usage, extra
386
- account directories with their junctions, v0.1 cards, the board pidfile) and
387
- nothing else; then `npm rm -g legcli`.
388
-
389
- ## CLI reference
390
-
391
- ```
392
- leg claude|codex|agy|grok [agent args…] the interactive agent, board alongside, handoff on limit
393
- [--no-worktree] share the checkout with a live session instead of a worktree
394
- leg sessions ls [--json] every session and its usage
395
- leg sessions show|events <id>
396
- leg sessions handoff|end <id> same as the board buttons
397
- leg sessions rm <id> forget an ended session
398
- leg sessions simulate-limit <id> the real limit path without a real wall (claude, agy, grok)
399
- leg accounts ls logins and their 5h/7d usage
400
- leg accounts add <claude|codex|grok> <name> | rm <agent> <name> | terms
401
- leg license the license on this machine, or where to buy one
402
- leg license activate <key> | deactivate | refresh (refresh renews a Team key)
403
- leg share who is on the board (off by default; Team plan)
404
- leg share on [--bind tailscale|lan|<addr>] [--port N] | off
405
- leg share add|rotate|rm <name> one link per human, printed once
406
- leg open | down | status the board
407
- leg uninstall [--yes]
408
- ```
409
-
410
- Environment, all optional: `LEG_HOME` (default `~/.leg`), `LEG_PORT`
411
- (4747), `LEG_ACCOUNT` (start on a named login), `LEG_WARN_PCT` (85),
412
- `LEG_NO_HANDOFF=1` (warn and record, never switch), `LEG_NO_OPEN=1` (do not
413
- open the browser), `LEG_USAGE_POLL_MS` (60000), `LEG_CLAUDE_ARGS` /
414
- `LEG_CODEX_ARGS` / `LEG_AGY_ARGS` / `LEG_GROK_ARGS` (extra args for a leg Leg starts after
415
- a hand-off, e.g. `-m gpt-5.3-codex-spark`), `LEG_CLAUDE_BIN`,
416
- `LEG_CODEX_BIN`, `LEG_AGY_BIN`, `LEG_GROK_BIN`, `LEG_CHB_BIN`, `LEG_PERSON` (whose
417
- terminal this is when the board is shared), `LEG_RATE_MAX` (600 requests a
418
- minute per human) and `LEG_RATE_MAX_FAILURES` (20 wrong tokens per address).
419
-
420
- ## Background tasks: the v0.1 extras
421
-
422
- Version 0.1 was the other way round: you dropped a task card on the board and
423
- Leg ran the agents headless in a git worktree, one per card, with a fallback
424
- chain, path leases, a scheduler and a merge queue. All of that still works and
425
- lives below the terminals lane, but it is no longer the way in.
426
-
427
- The New background card form starts with a repo, task, and real first agent.
428
- **Run now** queues it; turning that off saves a draft in Backlog. The default
429
- **Build only** workflow stops with its changes in the card's worktree and does
430
- not merge them. The Advanced **Build, test, and merge** and **Factory**
431
- workflows include an automatic land station; their labels say so before you
432
- choose them. Fallback agents, permissions, approval gates, turn caps, leases,
433
- trunk, merge method, tests, title, and scripted test/demo adapters are also
434
- under Advanced options.
435
-
436
- - `leg up` boots the board with the scheduler and merge queue and streams
437
- redacted logs; `leg card add --repo <path> --task "<t>" --chain claude,codex --queue`
438
- creates a card; presets `build`, `build-land`, `factory`; station kinds
439
- agent, test, land, human.
440
- - Adapters spawn the CLIs headless as argv, never through a shell, with their
441
- own permission modes and never a bypass flag: `claude -p --output-format json
442
- --permission-mode <m>`, `codex exec --json -s <m> -C <worktree>`,
443
- `agy -p --output-format json --mode <m> --add-dir <worktree>`; `fake`,
444
- `fake-claude`, `fake-codex`, `fake-agy` for tests and demos.
445
- - A leg that ends on a limit signal, a stall, a crash or exit 0 without
446
- `.leg/DONE` hands off with a bundle to the next adapter in the same
447
- worktree; a `land` station rebases, tests and fast-forwards trunk or bounces
448
- the card with the failure in the bundle.
449
- - Optional mirrors, off unless set in `.env`: OpenClaw Workboard
450
- (`LEG_SYNC_WORKBOARD=1`) and DashClaw (`LEG_SYNC_DASHCLAW=1`).
451
-
452
- The full v0.1 story, with the fake-limit demo and the real claude→codex run,
453
- is in [docs/concepts.md](docs/concepts.md), [docs/DEMO.md](docs/DEMO.md),
454
- [docs/real-run.md](docs/real-run.md) and [docs/board-guide.md](docs/board-guide.md).
455
-
456
- ### Network exposure
457
-
458
- Leg binds `127.0.0.1`. `leg share on` is the supported way to listen
459
- anywhere else: it binds your Tailscale or LAN address and every human gets
460
- their own token (see [More than one human](#more-than-one-human)). Without
461
- share, setting `LEG_BIND` to a non-loopback address needs `LEG_TOKEN` too,
462
- or the server refuses to start (exit 3), and requests then need
463
- `Authorization: Bearer <token>`. Tokenless owner access also requires a
464
- loopback hostname (`127.0.0.1`, `localhost`, or `[::1]`), which prevents a
465
- DNS-rebound hostname from inheriting local access. Either way there is no TLS.
466
-
467
- ## Troubleshooting
468
-
469
- - **The board did not open**: `leg open`, or visit http://127.0.0.1:4747.
470
- `~/.leg/board.log` has the server's output.
471
- - **claude's card shows "usage unknown"**: Claude Code has no stored claude.ai
472
- login in that config directory (run `claude auth login`), the stored token
473
- expired (start `claude` once, it refreshes), or the usage endpoint answered
474
- with something Leg does not recognise. The card says which. The wall is
475
- still caught through the hook; only the percentages are missing.
476
- - **codex usage is unavailable or stale**: the read-only Codex app-server quota
477
- request failed or has not completed in the last five minutes. The board
478
- retries every minute; an active Codex session also keeps its rollout tap as a
479
- fallback for percentages and the wall signal.
480
- - **agy's card has no percentage**: expected, agy exposes none. Leg sees the
481
- wall when agy hits it.
482
- - **A session shows `lost`**: the terminal that ran `leg <agent>` is gone
483
- (closed, crashed, machine slept through a kill). Remove it from the board.
484
- - **Nested session**: `leg claude` typed inside a Claude Code shell works;
485
- the parent's `CLAUDECODE` markers are stripped so the child starts.
486
- - **`npm install` dies with `edgesOut`** (clone only): the global npm is older
487
- than Node; run `npx --yes npm@latest install` once.
488
-
489
- More in [docs/faq.md](docs/faq.md).
490
-
491
- ## Documentation
492
-
493
- | guide | read it when |
494
- |-------|--------------|
495
- | [Getting started](docs/getting-started.md) | you want `leg claude` running in five minutes |
496
- | [Concepts](docs/concepts.md) | sessions, accounts, bundles, and the v0.1 cards, stations, chains and leases |
497
- | [Board guide](docs/board-guide.md) | every word, number and button on the board explained |
498
- | [Configuration](docs/configuration.md) | environment variables and options |
499
- | [Adapters](docs/adapters.md) | what each CLI exposes and how Leg attaches to it |
500
- | [CLI contracts](docs/cli-contracts.md) | exact argv per CLI and the limit-signal table with sources |
501
- | [FAQ](docs/faq.md) | a question the others did not answer |
502
- | [Demo](docs/DEMO.md) and [real run](docs/real-run.md) | the v0.1 handoff, fake and real |
503
- | [Vocabulary](docs/VOCABULARY.md) | statuses, outcomes and event types |
504
- | [Roadmap v2](docs/ROADMAP-v2.md) | where this is going |
505
- | [Reuse](docs/REUSE.md) and [deviations](docs/DEVIATIONS.md) | what was ported and every place the plan changed |
506
- | [Website](site/) | the public page: static HTML in `site/`, preview with `python -m http.server 4780 --directory site`, deployed to Vercel from that directory; PRODUCT.md and DESIGN.md at the root carry its brief and tokens |
507
-
508
- ## Contributing
509
-
510
- Issues and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md)
511
- for the dev setup and the rules (zero runtime deps, argv spawns only, no
512
- bypass flags, a privacy check on every commit). Security reports go through
513
- [SECURITY.md](SECURITY.md).
514
-
515
- ```
516
- npm install
517
- npm test # node --test + privacy check
518
- npm run lint
519
- ```
520
-
521
- Any real agent session started only to test Leg runs on the cheapest model
522
- (`leg claude --model haiku`); the live checks in `test/` never start one.
523
-
524
- Maintainer releases use npm trusted publishing with no `NPM_TOKEN`. Bump the
525
- package, lockfile, site metadata and release notes, then push `main`. CI waits
526
- for the Ubuntu and Windows test matrix, validates the exact version against
527
- npm, and publishes only when that version is missing and newer than the stable
528
- `latest`. Existing versions skip cleanly; older, prerelease, and registry-error
529
- cases fail the job. The npm trusted publisher is bound to
530
- `ucsandman/legcli` and `.github/workflows/ci.yml`. The repository stays private,
531
- so publication uses `--provenance=false`.
532
-
533
- ## Privacy and attribution
534
-
535
- Parts of the runner, ledger and git snapshot were ported from a private
536
- repository that was MIT licensed (see [NOTICE](NOTICE) and
537
- [docs/REUSE.md](docs/REUSE.md)), with chat identifiers, machine paths and
538
- personal names removed. The test suite runs a privacy check on every commit,
539
- and the fixtures store home paths as `~`.
540
-
541
- ## License and pricing
542
-
543
- Leg is commercial software under the [Leg License Agreement](LICENSE).
544
- It ships as readable JavaScript so you can see what it does on your machine,
545
- and you may modify it for your own use, but not redistribute it or work
546
- around the license check. Versions 0.2.0 and 0.3.0 were published under MIT
547
- and remain available. The version in this source tree is 0.8.0; see
548
- [npm](https://www.npmjs.com/package/legcli) for published versions and
549
- [CHANGELOG.md](CHANGELOG.md) for release notes.
550
-
551
- Using it needs a license: **Personal, $79 once**, one human on any number of
552
- machines, every release for 12 months and the version you have keeps working
553
- after that; **Team, $12 per seat per month**, Personal plus `leg share` for
554
- more than one human on the board. Buy at the site, then
555
- `leg license activate <key>`. There is no trial; there is a **30-day
556
- money-back guarantee**, so the way to evaluate Leg is to use it on real work
557
- and ask for a refund if it does not earn its place. A key is a signed token
558
- checked offline with the public key in `src/license.mjs`; only a Team key
559
- renewal talks to the site. The bare agent CLIs are never affected by any of
560
- this; only what Leg adds is licensed.
1
+ # Leg
2
+
3
+ **Type `leg claude`, `leg codex`, `leg agy` or `leg grok` instead of the bare command. You get the same interactive agent; Leg opens a board next to it, watches the usage limit, keeps a handoff bundle current, and when the limit hits it starts the next agent in the same terminal from that bundle.**
4
+
5
+ [![License: commercial](https://img.shields.io/badge/license-commercial-blue.svg)](https://legcli.com/license)
6
+ [![Node 22+](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](https://legcli.com/docs/getting-started)
7
+ [![Runtime deps: 0](https://img.shields.io/badge/runtime%20deps-0-lightgrey.svg)](https://legcli.com/docs)
8
+ [![Local first](https://img.shields.io/badge/runs-on%20your%20machine-informational.svg)](https://legcli.com/docs/configuration)
9
+
10
+ ![The Leg board mid-handoff: claude's terminal row changes to "handing off" and reads "handing off to codex, 5h limit reached", while the codex row above it shows what it landed on main](https://legcli.com/img/leg-handoff.gif)
11
+
12
+ *Claude hits the five-hour wall. The terminal reads `handing off to codex`, and codex carries on there. Nothing is retyped. ([the full 53-second run](https://legcli.com/#handoff))*
13
+
14
+ ![The Leg board at 1280px: a headline reading "All 4 terminals are on claude, and claude has 5% left", under it the staleness of the reading; a lit claude panel with its 7 day gauge at 95 percent past the reserve notch and its 5 hour gauge at 38; half panels for codex, at the wall, and agy, which publishes no figure; four terminal rows with their prompts and buttons; and counts for finished terminals, what landed and background tasks](https://legcli.com/img/docs/terminals-1280.png)
15
+
16
+ You keep using your coding agents exactly as you do today, in any terminal,
17
+ from your own config directory: Leg adds its hooks in a separate per-session
18
+ settings file and never edits yours (unless you turn on the [portable
19
+ harness](docs/harness.md), which writes only marked, backed-up, Leg-owned
20
+ files and regions). `leg claude --model opus` is
21
+ `claude --model opus` with four things running alongside it:
22
+
23
+ 1. **A board.** Opened once in your browser, reused after that. Every Leg
24
+ session in every terminal is a card on it: agent, account, repo and branch,
25
+ the task, the files it is touching, its 5h and 7d usage, what has landed on
26
+ trunk. Two sessions editing the same file in one repo are flagged on both
27
+ cards, and a second session in a checkout that already has one gets its
28
+ own worktree and a **Land** button instead of writing over the first.
29
+ 2. **Usage tracking** per agent and account, from what each CLI already
30
+ exposes: Claude Code's usage endpoint and its `StopFailure` hook, Codex's
31
+ read-only app-server rate-limit read, agy's log, and Grok's billing proxy endpoint.
32
+ 3. **A context handoff bundle** ([context-handoff-bundle](https://pypi.org/project/context-handoff-bundle/))
33
+ refreshed as the session goes, so the work is always ready to hand off.
34
+ 4. **The handoff itself.** Near the limit you get a warning. At the limit Leg
35
+ saves the bundle, stops the agent, and starts the next option in the same
36
+ terminal from that bundle: another login of the same agent if you added
37
+ one, otherwise the next agent in the order shown on the terminal card.
38
+ The default is claude -> codex -> agy (with grok supported in handoff order),
39
+ and Settings changes the default for new terminals. Nothing is retyped.
40
+ When every option is out, it tells you which resets first and when, waits
41
+ for that reset with a countdown, and starts that agent from the bundle.
42
+
43
+ Subscription logins only: Leg strips `ANTHROPIC_API_KEY`,
44
+ `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_CUSTOM_HEADERS`,
45
+ `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_API_BASE`, `GEMINI_API_KEY`,
46
+ `GOOGLE_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, `GOOGLE_GENAI_USE_VERTEXAI`,
47
+ `GOOGLE_GENAI_USE_ENTERPRISE`, `GOOGLE_CLOUD_PROJECT`,
48
+ `GOOGLE_CLOUD_LOCATION`, `GOOGLE_APPLICATION_CREDENTIALS`, `GROK_API_KEY`,
49
+ `XAI_API_KEY`, `CLAUDECODE`, `CLAUDE_CODE_*`, `CLAUDE_EFFORT`, and
50
+ `CLAUDE_PLUGIN_DATA` before any agent starts. It then sets
51
+ `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0` for a detached
52
+ Claude print session. Leg never edits `~/.claude/settings.json` or any other
53
+ settings file of yours; its hooks ride in a separate per-session `--settings`
54
+ file. The one thing it does write outside `~/.leg` by default is the
55
+ folder-trust answer, below; the optional [portable harness](docs/harness.md)
56
+ is the other, and only after `leg harness enable`. `leg uninstall` removes only `~/.leg`.
57
+
58
+ ### The folder-trust answer
59
+
60
+ Each agent CLI asks once, the first time it runs in a directory, whether you
61
+ trust that folder, and Claude Code asks a second question when a `CLAUDE.md`
62
+ above the repo imports a file from outside it. A handoff fires when the limit
63
+ hits, which is usually when nobody is watching, so an agent that stopped on
64
+ that prompt would sit there until morning with the bundle already written.
65
+
66
+ Before starting an agent, Leg records the same answer you would have given,
67
+ for the repository you already chose by typing `leg claude` in it:
68
+
69
+ | agent | file | what is written |
70
+ |---|---|---|
71
+ | claude | `~/.claude.json` (or `$CLAUDE_CONFIG_DIR/.claude.json`) | `projects["<repo>"].hasTrustDialogAccepted: true` |
72
+ | claude | the same entry, only when such an import exists | `hasClaudeMdExternalIncludesApproved`, `hasClaudeMdExternalIncludesWarningShown` |
73
+ | codex | `~/.codex/config.toml` | `[projects."<repo>"] trust_level = "trusted"` |
74
+ | agy | `~/.gemini/antigravity-cli/settings.json` | `trustedWorkspaces: ["<repo>", "<worktree>"]` |
75
+
76
+ For Claude Code this is the documented remedy: its permissions guide says to
77
+ set `projects["<path>"].hasTrustDialogAccepted` to `true` in `~/.claude.json`,
78
+ where `<path>` is the repository root.
79
+
80
+ Leg never creates one of those files: if it is not there, that CLI has not
81
+ run as you yet and its own first-run flow is next, with you at the keyboard. It
82
+ never rewrites a file to say what it already says, and it never removes what is
83
+ already in one. When it approves an external `CLAUDE.md` import it prints the
84
+ full path of every file it approved, to the terminal and to the session
85
+ timeline on the board, so the approval is on the record rather than invisible.
86
+
87
+ Set `LEG_TRUST=never` to switch all of it off and answer the prompts
88
+ yourself.
89
+
90
+ ## Contents
91
+
92
+ - [60-second run](#60-second-run)
93
+ - [What Leg reads from each agent](#what-leg-reads-from-each-agent)
94
+ - [How a handoff works](#how-a-handoff-works)
95
+ - [Two sessions in one repo](#two-sessions-in-one-repo)
96
+ - [More than one human](#more-than-one-human)
97
+ - [The board](#the-board)
98
+ - [Every conversation on this machine](#every-conversation-on-this-machine)
99
+ - [Second accounts, and what the terms say](#second-accounts-and-what-the-terms-say)
100
+ - [What is and is not touched](#what-is-and-is-not-touched)
101
+ - [CLI reference](#cli-reference)
102
+ - [Background tasks: the v0.1 extras](#background-tasks-the-v01-extras)
103
+ - [Troubleshooting](#troubleshooting)
104
+ - [Documentation](#documentation)
105
+ - [Contributing](#contributing)
106
+ - [License](#license)
107
+
108
+ ## 60-second run
109
+
110
+ Prerequisites: Node 22 or newer, git, Python 3 with pip, and at least one
111
+ logged-in agent CLI (`claude`, `codex`, `agy` or `grok`).
112
+
113
+ ```
114
+ npm install -g @ucsandman/legcli
115
+ pip install -U context-handoff-bundle
116
+ cd <any repo>
117
+ leg claude
118
+ ```
119
+
120
+ `npm install -g leg-agents` is the same release: it pins this version of
121
+ `@ucsandman/legcli` and exposes the same `leg` binary.
122
+
123
+ That is the whole setup. The first `leg <agent>` starts the board on
124
+ http://127.0.0.1:4747 and opens it; later sessions reuse it. Anything after the
125
+ agent name passes straight through (`leg codex -m gpt-5.3-codex-spark`,
126
+ `leg claude --resume`). The agent's own prompt and permission flags pass
127
+ through unchanged, and your settings file is never edited: Leg's hooks ride
128
+ in a separate per-session `--settings` file.
129
+
130
+ The source repository is private. Leg is commercial, source-available
131
+ software: every `.mjs` file that runs is in the package you just installed, at
132
+ `$(npm root -g)/legcli/src`, and the license lets you read it and modify
133
+ your own copy. There is nothing compiled, minified or bundled to see through.
134
+
135
+ ## What Leg reads from each agent
136
+
137
+ Nothing is guessed from screen scraping. Each tap was read from the CLI's
138
+ source or documentation and then checked on a real machine (2026-09-11,
139
+ Claude Code 2.1.268, codex-cli 0.153.4, agy 1.2.0); the rightmost column says
140
+ which.
141
+
142
+ | agent | usage percentages | the wall (limit hit) | how Leg attaches | status |
143
+ |-------|-------------------|----------------------|--------------------|--------|
144
+ | claude | `GET api.anthropic.com/api/oauth/usage` with the login Claude Code stored, the same data as `/usage` and the built-in status line (`five_hour`, `seven_day`, `utilization`, `resets_at`); polled every 60 s | `StopFailure` hook with `error: rate_limit` ([docs](https://code.claude.com/docs/en/hooks#stopfailure)) | one extra settings file per session via `--settings`, carrying only Leg's own hooks; `autoContinueAtUsageLimit` is set to `false` because Leg owns the handoff | observed live |
145
+ | codex | read-only `account/rateLimits/read` through the app-server, polled every 60 s by the board and active attach; windows are identified by duration (300 minutes = 5h, 10080 = 7d) | `task_complete.error.codex_error_info: usage_limit_exceeded`, message "You've hit your usage limit … try again at …" (`codex-rs/protocol/src/error.rs`) | no model turn and no hook are injected; the board reads the CLI backend and records only returned windows | verified by source and regression tests |
146
+ | agy | none exposed (agy's own status line fetches a quota summary that is written nowhere) | `RESOURCE_EXHAUSTED`, "it resets in …", "out of quota" in the log | `--log-file` per session; `~/.gemini/antigravity-cli/history.jsonl` gives the prompts and conversation id | observed live (a real `RESOURCE_EXHAUSTED` with its reset was read from the log on 2026-09-11) |
147
+ | grok | `GET cli-chat-proxy.grok.com/v1/billing?format=credits` and `GET cli-chat-proxy.grok.com/v1/user?include=subscription` with OAuth token from `~/.grok/auth.json` (`creditUsagePercent`, `currentPeriod` weekly reset); polled every 60 s | `-32003` rate limit error, "You've hit the rate limit for your plan. Try again later.", "Rate limited (429)", `StopFailureKind::RateLimit` (`xai-org/grok-build`) | `--debug-file grok.log` per session; `~/.grok/sessions/<encoded-cwd>/prompt_history.jsonl` gives prompts and session id | verified by source and proxy endpoint |
148
+
149
+ Why not Claude Code's status line JSON (`rate_limits.five_hour.used_percentage`):
150
+ on 2.1.268 the custom `statusLine` Leg passes through `--settings` did not
151
+ run, so the endpoint poll is the source. Leg still writes a `statusLine`
152
+ entry that records the same fields, so the moment a build honours it the poll
153
+ becomes a fallback.
154
+
155
+ For Codex, Leg does not infer availability from a lower percentage: only an
156
+ explicit available answer from the backend clears an earlier wall. The board
157
+ labels each bar as `<n>% used` and marks an old reading as stale rather than
158
+ presenting it as current.
159
+
160
+ ## How a handoff works
161
+
162
+ 1. **Warning.** At 85 % of any window (`LEG_WARN_PCT`) the session card turns
163
+ amber, the event log names the next option, and the terminal bell rings once.
164
+ 2. **Limit.** claude: the `StopFailure` hook fires with `rate_limit`. codex: the
165
+ rollout reports `usage_limit_exceeded`. agy: the log says
166
+ `RESOURCE_EXHAUSTED`. The account is marked walled until the reset the CLI
167
+ reported (or the soonest known window reset).
168
+ 3. **Bundle.** Leg writes structured notes (task, the last messages from the
169
+ transcript, `git diff --stat`, dirty files, files edited this session, recent
170
+ commits, why it stopped) and runs `context-handoff-bundle save --repo-local`
171
+ with one slug per leg; each save writes its own timestamped bundle next to
172
+ the last one. A checkpoint of the same bundle is taken every two minutes
173
+ while the session is active. If the session maintained `.leg/SYNTHESIS-<session-id>.md`,
174
+ Leg inlines it into the resume file as a `## Synthesis` section ahead of the raw dump.
175
+ 4. **Switch.** The agent process is stopped, the terminal is restored, and the
176
+ next option starts in the same terminal with a short pointer prompt:
177
+ read `.leg/RESUME-<session-id>.md` (the `context-handoff-bundle load`
178
+ output, the `## Synthesis` section if present, and the reason for the switch),
179
+ check `git status` and `git diff`, continue, do not ask the human to restate
180
+ the task. The pointer prompt directs the next agent to read Synthesis first,
181
+ treat ruled-out approaches as settled, and start from the top-ranked next step.
182
+ The same text is copied to `.leg/RESUME.md`, the file people open by habit, and
183
+ both are stamped with the commit and the live terminals they describe.
184
+ `claude "<prompt>"`, `codex "<prompt>"` and `agy -i "<prompt>"` all open the
185
+ normal interactive session with that first turn.
186
+ 5. **Order.** Other accounts of the same agent come first, then every other
187
+ agent in the saved order, each tried once. The order is a priority list, not
188
+ a rotation: put agy at the bottom and agy is the last option from a Claude
189
+ terminal and from a Codex terminal alike. The board shows the exact
190
+ sequence with the agent running now skipped, plus the preferred option and
191
+ the first option eligible from current install and limit state. Use **Change
192
+ order** on a terminal card to change that terminal, or Settings to set the
193
+ default copied by new terminals. An option whose CLI is missing or whose
194
+ wall has not reset is skipped.
195
+ 6. **All out.** The terminal prints each option with its reset time, soonest
196
+ first, then stays open with a countdown to the first reset and starts that
197
+ agent from the bundle when it arrives. The card says `waiting for <agent>
198
+ at <time>`. Ctrl-C (or End on the card) quits with exit 3 instead.
199
+
200
+ You can force a handoff any time: the **Hand off now** button on the card, or
201
+ `leg sessions handoff <id>`. Verified on this machine: `leg claude` opened
202
+ the real Claude Code TUI with Leg's hooks firing into the session log, the
203
+ usage poll recorded 36 % of the 5h window and 74 % of the 7d window, the
204
+ warning fired at 96 % of the 7d window and named codex as the next option, and
205
+ a limit saved the bundle, stopped claude and started codex in the same
206
+ terminal with the pointer prompt. A real `StopFailure` arrived on 2026-09-11
207
+ and is kept at `fixtures/live/claude/limit-rate_limit.json`; to drive the path
208
+ on demand, `leg sessions simulate-limit <id>` sends the same `StopFailure`
209
+ `rate_limit` payload Claude Code would send through Leg's hook: verified end
210
+ to end on a haiku session, the hook set the limit, the runner saved the
211
+ bundle, stopped claude and started codex, which read `.leg/RESUME.md` on its
212
+ first turn. The simulated wall clears after two minutes and is never kept as
213
+ evidence. The first real `StopFailure` was saved that way, with secrets
214
+ scrubbed, at `fixtures/live/claude/limit-rate_limit.json`, and the claude docs
215
+ row flipped to observed-live (`node scripts/live-limits.mjs`). The same
216
+ capture is wired for codex `usage_limit_exceeded` and agy `RESOURCE_EXHAUSTED`;
217
+ no payload for either has been kept yet.
218
+
219
+ Terminals started by this version can change order while they run. An older
220
+ terminal stays on the order it started with; its card says a restart is needed
221
+ and can save the desired default for the next launch. A normal agent exit ends
222
+ the terminal. It does not trigger a handoff.
223
+
224
+ ### The working environment travels too (optional)
225
+
226
+ With the [portable harness](docs/harness.md) enabled (`leg harness enable`,
227
+ off by default), the hand-off also prepares the destination's environment
228
+ before it starts: the source agent's global rules, identity, hooks, skills,
229
+ subagents, slash commands, MCP servers and permissions, rendered into the
230
+ destination's own files as far as it can represent them. The terminal then
231
+ reads, for instance, `codex harness partial · 8/8 components, 3 dropped ·
232
+ 1 file(s) written`, and the terminal's details list what was dropped and why.
233
+ The source is fingerprinted so an unchanged environment costs a few stat
234
+ calls; a policy (`warn`, `sync`, `strict`) says whether a hand-off may write,
235
+ and strict refuses a destination it cannot make safe. Credentials never move
236
+ and which login runs stays the account layer's decision.
237
+
238
+ ## Two sessions in one repo
239
+
240
+ Two agents in one working tree write over each other's files. So when you
241
+ start `leg codex` in a checkout where `leg claude` is already live, the new
242
+ session gets its own git worktree, `<repo>/.leg-worktrees/<session-id>` on
243
+ branch `leg/<session-id>`, cut from the branch the checkout has out, and the
244
+ terminal prints one line saying where it is. The agent starts there; the card,
245
+ the usage tracking and the handoff work the same. `--no-worktree` shares the
246
+ checkout on purpose (Leg takes the flag out; the agent never sees it).
247
+
248
+ The card of a session with its own worktree has a **Land** button. It sends
249
+ the branch through the merge queue: whatever the agent left uncommitted is
250
+ committed on the branch, the branch is rebased onto its base, the repo's test
251
+ command runs (`package.json` `test`, `pytest`, or none with a warning), and the
252
+ base is fast-forwarded, never merged. When a step fails nothing lands and the
253
+ card says why: `rebase-conflict` with the files, `tests-red` with the end of
254
+ the output, `dirty-trunk` when the checkout has local changes the landing
255
+ would overwrite. Local changes it would not touch are left alone. The
256
+ landed-on-trunk list says which terminal landed each commit. **Remove** safely
257
+ prunes a finished session only when its worktree is clean and its branch is
258
+ already on the base. **Remove record** is a separate visible button with a
259
+ confirmation: it removes only Leg's saved session record and deliberately
260
+ keeps the worktree, branch, unmerged commits, and dirty files.
261
+
262
+ Verified live on 2026-09-11 with three haiku sessions in a throwaway repo. The
263
+ first stayed in the checkout; the second and third each got a worktree and
264
+ appended a line to README.md. Land on the second, clicked on the real board,
265
+ ran the repo's tests and fast-forwarded main; Land on the third bounced with
266
+ `rebase-conflict` on README.md and kept its commit on its branch; the
267
+ landed-on-trunk list named the second terminal (the screenshot above).
268
+
269
+ ## More than one human
270
+
271
+ Off until you run it. `leg share on` binds the board to your Tailscale
272
+ address (or `--bind lan`, or an address you name) and gives every human their
273
+ own name and token; until then the board stays on `127.0.0.1` and there is no
274
+ token at all.
275
+
276
+ ```
277
+ leg share on your own link, printed once
278
+ leg share add sam sam's link, printed once
279
+ leg share who is on the board (never a token again)
280
+ leg share rotate sam sam's old link stops working
281
+ leg share off back to 127.0.0.1; every link stops working
282
+ ```
283
+
284
+ A token is kept as a sha256 hash, so a lost link is re-issued, never re-read.
285
+ The board takes the token out of the address bar and keeps it in the browser.
286
+ Your own browser on this machine needs no token.
287
+
288
+ What another human sees is the Terminals region, read-only. Each panel says
289
+ whose terminal it is. On a panel that is not theirs there is no prompt, no file
290
+ name, no path, no bundle and no event log; what stays is the agent and session
291
+ tail, the status word, the sentence `read-only: wes owns this terminal`,
292
+ repo@branch, the worktree line, the elapsed clock, and one button,
293
+ **Request handoff**. The instrument head prints `not shared` in place of every
294
+ percentage. A request lands on the owner's panel as `sam asked to take this
295
+ terminal at 11:04 PM` with **Approve sam** and **Dismiss sam**. The background
296
+ side of the board (cards, logs, the floor) stays the owner's alone. A terminal
297
+ belongs to the human who started it: `LEG_PERSON=sam leg claude` on the
298
+ same machine is sam's card, not yours.
299
+
300
+ The security pass that goes with it: every `/api` route needs a token, the
301
+ event stream included; twenty wrong tokens from one address and that address
302
+ waits a minute; one identity gets 600 requests a minute; a guest gets 403 on
303
+ everything that is not theirs; and the tests send a bad and a missing token to
304
+ every route. There is still no TLS, so keep this on Tailscale or a network you
305
+ trust. Verified live on 2026-09-11: two terminals on one machine, one wes's and
306
+ one sam's; sam's board showed wes's card with the prompt hidden and only
307
+ **Request handoff**, and sam's request reached wes's board (`~/.leg/board.log`:
308
+ "hand-off requested by sam").
309
+
310
+ ## The board
311
+
312
+ `leg <agent>` opens it; `leg open` reopens it; `leg down` stops it.
313
+
314
+ - **Instrument head**: one row per login, sticky at the top of the board and of
315
+ the floor. Each row carries the 5h and 7d rails, the percentage, when that
316
+ window resets and how long that is, a burn-rate sentence under the 5h rail,
317
+ where and when Leg read the number, and one word for the state: `under 60`,
318
+ `over 60`, `over 85`, `stale <n>m`, `at the wall` or `no reading`. A login
319
+ at its wall keeps both rails and gains `at the wall`, `back <day time>` and
320
+ `in <duration>` beside them.
321
+ - **Terminals**: one full-width panel per session, the ones that need an answer
322
+ first. Agent and session tail, the status word, the first prompt as a button,
323
+ exactly one sentence (the highest-ranked thing true about the terminal), an
324
+ `also:` disclosure naming the rest, the files as comma-separated text,
325
+ `repo@branch`, `own worktree, from main` when the session cut its own
326
+ worktree, and an elapsed clock. Two live sessions in one repo touching the
327
+ same file print `codex (codex-99ab) is changing src/server.mjs in another
328
+ checkout; whoever lands second rebases` on both panels, and a panel that needs
329
+ you rises one step and says `waiting on you` in place of its status word.
330
+ After a Land the sentence is `landed on <base>, <7-char sha>, <n> files,
331
+ +<added>/-<removed>`, or `Land was attempted at <time> onto <base> and
332
+ bounced: <first line of the reason>. The branch still holds every commit;
333
+ nothing was lost.`
334
+ - **Landed on main**: one flat list across every repo the board can see, newest
335
+ first, each row with the short sha, the subject, a `repo@branch` chip, and
336
+ when plus who. A commit a Land put there says `landed by <agent> (<id tail>)`.
337
+ - **Buttons**, in a fixed order that never reflows: Land, Hand off now,
338
+ Details, End. Once a session has ended, Remove and Remove record take End's
339
+ place. Details opens an expansion in flow under the panel.
340
+ - Below it, **Conversations**: every coding-agent conversation on this machine
341
+ as a count that opens, the ones Leg started and the ones Claude Code, Codex,
342
+ Grok, Antigravity and Copilot keep in their own stores. Filter by agent,
343
+ search, repository, or only what Leg started; a row opens its last messages
344
+ in place, with the `leg history continue` command to copy where the agent
345
+ can resume by id. The same drawer lists every checkout Leg can see. Then
346
+ optional **Background tasks** an agent runs in a separate worktree without
347
+ joining the terminal conversation (see below), then **Settings**.
348
+
349
+ The board reads `~/.leg/sessions/*/session.json` over server-sent events; a
350
+ session whose runner process is gone is marked `lost`, never shown as live.
351
+
352
+ ## Every conversation on this machine
353
+
354
+ Claude Code, Codex, Grok, Antigravity and Copilot each keep their history in
355
+ their own place. `leg history` is one list over all of them, whether or not
356
+ Leg started the conversation, and `leg worktrees` is one list over every
357
+ checkout: git's, Leg's own, and the ones those conversations ran in.
358
+
359
+ ```
360
+ leg history newest first, every agent; --provider, --repo, --search, --json
361
+ leg history show claude:0fc5 where it ran, its last messages, whether Leg can continue it
362
+ leg history continue claude:0fc5 leg claude --resume <id> in that folder, supervised like any session
363
+ leg worktrees path, repo, branch, exists, uncommitted, owner, conversations, stale
364
+ ```
365
+
366
+ Nothing moves: each agent's store stays where it was, Leg writes only its own
367
+ `~/.leg/history/index.json`, reads transcripts from their head and tail, and
368
+ opens messages only when you open a conversation. A session Leg started and
369
+ the same conversation in the agent's store are one row, marked `leg`; the rest
370
+ are `external`. On a shared board the whole group is the owner's. The support
371
+ matrix (which agents list, show messages, continue) and every file read are in
372
+ [docs/history.md](docs/history.md).
373
+
374
+ ## Second accounts, and what the terms say
375
+
376
+ Optional. `leg accounts add claude work` creates
377
+ `~/.leg/accounts/claude/work`, junctions your `hooks`, `skills`, `agents`,
378
+ `commands`, `plugins`, `rules`, `scripts`, `output-styles` and `tools` into it,
379
+ copies `settings.json`, `CLAUDE.md` and the status-line scripts (refreshed from
380
+ your real `~/.claude` before every launch), and prints one line to paste:
381
+
382
+ ```
383
+ $env:CLAUDE_CONFIG_DIR='C:\Users\you\.leg\accounts\claude\work'; claude auth login
384
+ ```
385
+
386
+ Same for codex (`CODEX_HOME`; `config.toml`, `AGENTS.md`, `skills`, `prompts`,
387
+ `rules`, `plugins`, `agents`, `hooks`, `memories` shared). agy 1.2.0 has no
388
+ config-directory override, so it stays one account. Only the login lives in
389
+ the account directory; `leg accounts rm` removes the junctions and the
390
+ directory and never touches your real home.
391
+
392
+ The terms, as published (effective dates below):
393
+
394
+ - Anthropic Consumer Terms (effective 2025-10-08): "You may not share your
395
+ Account login information, Anthropic API key, or Account credentials with
396
+ anyone else" and you "must not bypass any of our systems or protective
397
+ measures."
398
+ - Anthropic Usage Policy (effective 2025-09-15): do not "Coordinate malicious
399
+ activity across multiple accounts to avoid detection or circumvent product
400
+ guardrails" or "Utilize automation in account creation."
401
+ - OpenAI Terms of Use (effective 2026-01-01): "You may not share your account
402
+ credentials or make your account available to anyone else" and you may not
403
+ "circumvent any rate limits or restrictions or bypass any protective
404
+ measures."
405
+
406
+ Owning two paid subscriptions is not named as prohibited by either. Rotating to
407
+ a second account of the same vendor because the first one is rate-limited sits
408
+ close to OpenAI's "circumvent any rate limits" wording and Anthropic's
409
+ "circumvent product guardrails". Leg's default chain switches vendors
410
+ (claude codex agy), which is plainly fine. Same-vendor rotation only
411
+ happens after you run `leg accounts add`; that is your call.
412
+
413
+ ## What is and is not touched
414
+
415
+ - **Never edited**: `~/.claude/settings.json`, `~/.claude.json`,
416
+ `~/.codex/config.toml`, agy's files, your repo's settings. Claude Code gets
417
+ hooks through a per-session `--settings` file under `~/.leg`; codex and
418
+ agy get nothing injected.
419
+ - **Read, never written**: each agent's own history (`~/.claude/projects`,
420
+ `~/.codex/sessions`, `~/.grok/sessions`, `~/.gemini/antigravity-cli`,
421
+ `~/.copilot/session-state`) for `leg history`; the index it builds lives
422
+ under `~/.leg/history/`, and no SQLite file is ever opened.
423
+ - **Written only after `leg harness enable`** ([the portable harness](docs/harness.md),
424
+ off by default): the destination client's global rules file
425
+ (`~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md`, `~/.claude/leg-rules.md` plus
426
+ one `@` line in `CLAUDE.md`), marked `leg harness` regions inside the
427
+ client's own config files, per-skill directory links, and one file per
428
+ subagent and slash command. Every file carries `GENERATED by Leg harness`,
429
+ every overwrite is backed up under `~/.leg/harness/backups`, a hand-edited
430
+ file is skipped and named, the source client is never written, and no
431
+ credential ever moves (an MCP key becomes `${NAME}`). `leg harness disable`
432
+ stops it and removes nothing.
433
+ - **Written in your repo**: `.leg/` (session notes, `RESUME.md` and one
434
+ `RESUME-<session-id>.md` per hand-off),
435
+ `.context-handoffs/` (the bundles) and `.leg-worktrees/` (a second
436
+ session's worktree), all added to `.git/info/exclude`, plus the
437
+ `leg/<session-id>` branch of a session with its own worktree. Landing
438
+ fast-forwards your branch; nothing is ever pushed.
439
+ - **Stripped from every agent's environment**: `ANTHROPIC_API_KEY`,
440
+ `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_CUSTOM_HEADERS`,
441
+ `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_API_BASE`, `GEMINI_API_KEY`,
442
+ `GOOGLE_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, `GOOGLE_GENAI_USE_VERTEXAI`,
443
+ `GOOGLE_GENAI_USE_ENTERPRISE`, `GOOGLE_CLOUD_PROJECT`,
444
+ `GOOGLE_CLOUD_LOCATION`, `GOOGLE_APPLICATION_CREDENTIALS`, `CLAUDECODE`,
445
+ `CLAUDE_CODE_*`, `CLAUDE_EFFORT`, and `CLAUDE_PLUGIN_DATA`. `CLAUDE_CODE_*`
446
+ does not include `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS`: Leg sets that one
447
+ to `0` for a detached Claude print session.
448
+ - **Read but never written or printed**: Claude Code's stored login, sent only
449
+ to `api.anthropic.com` for the usage numbers. The ledger scrubs bearer tokens
450
+ and key shapes from every line regardless.
451
+ - **`leg uninstall --yes`**: removes `~/.leg` (sessions, usage, extra
452
+ account directories with their junctions, v0.1 cards, the board pidfile) and
453
+ nothing else; then `npm rm -g @ucsandman/legcli`.
454
+
455
+ ## CLI reference
456
+
457
+ ```
458
+ leg claude|codex|agy|grok [agent args…] the interactive agent, board alongside, handoff on limit
459
+ [--no-worktree] share the checkout with a live session instead of a worktree
460
+ leg sessions ls [--json] every session and its usage
461
+ leg sessions show|events <id>
462
+ leg sessions handoff|end <id> same as the board buttons
463
+ leg sessions rm <id> forget an ended session
464
+ leg sessions simulate-limit <id> the real limit path without a real wall (claude, agy, grok)
465
+ leg history [ls] [--provider p] [--repo r] [--search q] [--managed|--external] [--live] [--all] [--json]
466
+ every conversation on this machine, Leg's own and the agents' own (read only)
467
+ leg history show <id> [--messages n] [--json] | continue <id> [agent args…] | refresh [--full] | providers
468
+ leg worktrees [--repo <path>] [--no-dirty] [--json] every checkout: git's, Leg's, the conversations' (read only)
469
+ leg accounts ls logins and their 5h/7d usage
470
+ leg accounts add <claude|codex|grok> <name> | rm <agent> <name> | terms
471
+ leg harness status|inspect|check|explain|history [--json] the portable harness, read-only
472
+ leg harness enable [--source claude|codex] [--policy warn|sync|strict] [--yes]
473
+ leg harness sync [--to codex,agy] [--force] [--dry-run] | diff <client> | doctor
474
+ leg harness capture [claude|codex] | source <client> | policy <mode> | disable
475
+ leg license the license on this machine, or where to buy one
476
+ leg license activate <key> | deactivate | refresh (refresh renews a Team key)
477
+ leg share who is on the board (off by default; Team plan)
478
+ leg share on [--bind tailscale|lan|<addr>] [--port N] | off
479
+ leg share add|rotate|rm <name> one link per human, printed once
480
+ leg open | down | status the board
481
+ leg uninstall [--yes]
482
+ ```
483
+
484
+ Environment, all optional: `LEG_HOME` (default `~/.leg`), `LEG_PORT`
485
+ (4747), `LEG_ACCOUNT` (start on a named login), `LEG_WARN_PCT` (85),
486
+ `LEG_NO_HANDOFF=1` (warn and record, never switch), `LEG_NO_OPEN=1` (do not
487
+ open the browser), `LEG_USAGE_POLL_MS` (60000), `LEG_CLAUDE_ARGS` /
488
+ `LEG_CODEX_ARGS` / `LEG_AGY_ARGS` / `LEG_GROK_ARGS` (extra args for a leg Leg starts after
489
+ a hand-off, e.g. `-m gpt-5.3-codex-spark`), `LEG_CLAUDE_BIN`,
490
+ `LEG_CODEX_BIN`, `LEG_AGY_BIN`, `LEG_GROK_BIN`, `LEG_CHB_BIN`, `LEG_PERSON` (whose
491
+ terminal this is when the board is shared), `LEG_RATE_MAX` (600 requests a
492
+ minute per human) and `LEG_RATE_MAX_FAILURES` (20 wrong tokens per address).
493
+
494
+ ## Background tasks: the v0.1 extras
495
+
496
+ Version 0.1 was the other way round: you dropped a task card on the board and
497
+ Leg ran the agents headless in a git worktree, one per card, with a fallback
498
+ chain, path leases, a scheduler and a merge queue. All of that still works and
499
+ lives below the terminals lane, but it is no longer the way in.
500
+
501
+ The New background card form starts with a repo, task, and real first agent.
502
+ **Run now** queues it; turning that off saves a draft in Backlog. The default
503
+ **Build only** workflow stops with its changes in the card's worktree and does
504
+ not merge them. The Advanced **Build, test, and merge** and **Factory**
505
+ workflows include an automatic land station; their labels say so before you
506
+ choose them. Fallback agents, permissions, approval gates, turn caps, leases,
507
+ trunk, merge method, tests, title, and scripted test/demo adapters are also
508
+ under Advanced options.
509
+
510
+ - `leg up` boots the board with the scheduler and merge queue and streams
511
+ redacted logs; `leg card add --repo <path> --task "<t>" --chain claude,codex --queue`
512
+ creates a card; presets `build`, `build-land`, `factory`; station kinds
513
+ agent, test, land, human.
514
+ - Adapters spawn the CLIs headless as argv, never through a shell, with their
515
+ own permission modes and never a bypass flag: `claude -p --output-format json
516
+ --permission-mode <m>`, `codex exec --json -s <m> -C <worktree>`,
517
+ `agy -p --output-format json --mode <m> --add-dir <worktree>`; `fake`,
518
+ `fake-claude`, `fake-codex`, `fake-agy` for tests and demos.
519
+ - A leg that ends on a limit signal, a stall, a crash or exit 0 without
520
+ `.leg/DONE` hands off with a bundle to the next adapter in the same
521
+ worktree; a `land` station rebases, tests and fast-forwards trunk or bounces
522
+ the card with the failure in the bundle.
523
+ - Optional mirrors, off unless set in `.env`: OpenClaw Workboard
524
+ (`LEG_SYNC_WORKBOARD=1`) and DashClaw (`LEG_SYNC_DASHCLAW=1`).
525
+
526
+ The full v0.1 story, with the fake-limit demo and the real claude→codex run,
527
+ is in [docs/concepts.md](docs/concepts.md), [docs/DEMO.md](docs/DEMO.md),
528
+ [docs/real-run.md](docs/real-run.md) and [docs/board-guide.md](docs/board-guide.md).
529
+
530
+ ### Network exposure
531
+
532
+ Leg binds `127.0.0.1`. `leg share on` is the supported way to listen
533
+ anywhere else: it binds your Tailscale or LAN address and every human gets
534
+ their own token (see [More than one human](#more-than-one-human)). Without
535
+ share, setting `LEG_BIND` to a non-loopback address needs `LEG_TOKEN` too,
536
+ or the server refuses to start (exit 3), and requests then need
537
+ `Authorization: Bearer <token>`. Tokenless owner access also requires a
538
+ loopback hostname (`127.0.0.1`, `localhost`, or `[::1]`), which prevents a
539
+ DNS-rebound hostname from inheriting local access. Either way there is no TLS.
540
+
541
+ ## Troubleshooting
542
+
543
+ - **The board did not open**: `leg open`, or visit http://127.0.0.1:4747.
544
+ `~/.leg/board.log` has the server's output.
545
+ - **claude's card shows "usage unknown"**: Claude Code has no stored claude.ai
546
+ login in that config directory (run `claude auth login`), the stored token
547
+ expired (start `claude` once, it refreshes), or the usage endpoint answered
548
+ with something Leg does not recognise. The card says which. The wall is
549
+ still caught through the hook; only the percentages are missing.
550
+ - **codex usage is unavailable or stale**: the read-only Codex app-server quota
551
+ request failed or has not completed in the last five minutes. The board
552
+ retries every minute; an active Codex session also keeps its rollout tap as a
553
+ fallback for percentages and the wall signal.
554
+ - **agy's card has no percentage**: expected, agy exposes none. Leg sees the
555
+ wall when agy hits it.
556
+ - **A session shows `lost`**: the terminal that ran `leg <agent>` is gone
557
+ (closed, crashed, machine slept through a kill). Remove it from the board.
558
+ - **Nested session**: `leg claude` typed inside a Claude Code shell works;
559
+ the parent's `CLAUDECODE` markers are stripped so the child starts.
560
+ - **`npm install` dies with `edgesOut`** (clone only): the global npm is older
561
+ than Node; run `npx --yes npm@latest install` once.
562
+
563
+ More in [docs/faq.md](docs/faq.md).
564
+
565
+ ## Documentation
566
+
567
+ | guide | read it when |
568
+ |-------|--------------|
569
+ | [Getting started](docs/getting-started.md) | you want `leg claude` running in five minutes |
570
+ | [Concepts](docs/concepts.md) | sessions, accounts, bundles, and the v0.1 cards, stations, chains and leases |
571
+ | [Board guide](docs/board-guide.md) | every word, number and button on the board explained |
572
+ | [Configuration](docs/configuration.md) | environment variables and options |
573
+ | [Portable harness](docs/harness.md) | carrying rules, hooks, skills, agents, commands and MCP servers to the agent a hand-off lands on: what moves, what does not, policies, ownership, secrets |
574
+ | [History](docs/history.md) | `leg history` and `leg worktrees`: every conversation and checkout on this machine across agents, the support matrix, what is read and what is written |
575
+ | [Adapters](docs/adapters.md) | what each CLI exposes and how Leg attaches to it |
576
+ | [CLI contracts](docs/cli-contracts.md) | exact argv per CLI and the limit-signal table with sources |
577
+ | [FAQ](docs/faq.md) | a question the others did not answer |
578
+ | [Demo](docs/DEMO.md) and [real run](docs/real-run.md) | the v0.1 handoff, fake and real |
579
+ | [Vocabulary](docs/VOCABULARY.md) | statuses, outcomes and event types |
580
+ | [Roadmap v2](docs/ROADMAP-v2.md) | where this is going |
581
+ | [Reuse](docs/REUSE.md) and [deviations](docs/DEVIATIONS.md) | what was ported and every place the plan changed |
582
+ | [Website](site/) | the public page: static HTML in `site/`, preview with `python -m http.server 4780 --directory site`, deployed to Vercel from that directory; PRODUCT.md and DESIGN.md at the root carry its brief and tokens |
583
+
584
+ ## Contributing
585
+
586
+ Issues and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md)
587
+ for the dev setup and the rules (zero runtime deps, argv spawns only, no
588
+ bypass flags, a privacy check on every commit). Security reports go through
589
+ [SECURITY.md](SECURITY.md).
590
+
591
+ ```
592
+ npm install
593
+ npm test # node --test + privacy check
594
+ npm run lint
595
+ ```
596
+
597
+ Any real agent session started only to test Leg runs on the cheapest model
598
+ (`leg claude --model haiku`); the live checks in `test/` never start one.
599
+
600
+ Maintainer releases use npm trusted publishing with no `NPM_TOKEN`. Bump the
601
+ package, lockfile, site metadata and release notes, then push `main`.
602
+ `npm version` (and `npm run sync-alias`) writes `packages/leg-agents` to the
603
+ same version and pins `@ucsandman/legcli` to it; `npm test` fails if they
604
+ drift. CI waits for the Ubuntu and Windows test matrix, validates both
605
+ `@ucsandman/legcli` and `leg-agents` against npm, and publishes each only
606
+ when that version is missing and newer than the stable `latest`. Existing
607
+ versions skip cleanly; older, prerelease, lockstep, and registry-error cases
608
+ fail the job. The npm trusted publisher is bound to `ucsandman/legcli` and
609
+ `.github/workflows/ci.yml` (bind `leg-agents` the same way). The repository
610
+ stays private, so publication uses `--provenance=false`.
611
+
612
+ ## Privacy and attribution
613
+
614
+ Parts of the runner, ledger and git snapshot were ported from a private
615
+ repository that was MIT licensed (see [NOTICE](NOTICE) and
616
+ [docs/REUSE.md](docs/REUSE.md)), with chat identifiers, machine paths and
617
+ personal names removed. The test suite runs a privacy check on every commit,
618
+ and the fixtures store home paths as `~`.
619
+
620
+ ## License and pricing
621
+
622
+ Leg is commercial software under the [Leg License Agreement](LICENSE).
623
+ It ships as readable JavaScript so you can see what it does on your machine,
624
+ and you may modify it for your own use, but not redistribute it or work
625
+ around the license check. Versions 0.2.0 and 0.3.0 were published under MIT
626
+ and remain available. The version in this source tree is 0.8.0; see
627
+ [npm](https://www.npmjs.com/package/legcli) for published versions and
628
+ [CHANGELOG.md](CHANGELOG.md) for release notes.
629
+
630
+ Using it needs a license: **Personal, $79 once**, one human on any number of
631
+ machines, every release for 12 months and the version you have keeps working
632
+ after that; **Team, $12 per seat per month**, Personal plus `leg share` for
633
+ more than one human on the board. Buy at the site, then
634
+ `leg license activate <key>`. There is no trial; there is a **30-day
635
+ money-back guarantee**, so the way to evaluate Leg is to use it on real work
636
+ and ask for a refund if it does not earn its place. A key is a signed token
637
+ checked offline with the public key in `src/license.mjs`; only a Team key
638
+ renewal talks to the site. The bare agent CLIs are never affected by any of
639
+ this; only what Leg adds is licensed.