@ucsandman/legcli 0.10.0 → 0.12.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 (70) hide show
  1. package/CHANGELOG.md +212 -0
  2. package/README.md +158 -67
  3. package/bin/leg.mjs +168 -18
  4. package/docs/DECISIONS.md +10 -0
  5. package/docs/DEMO.md +20 -14
  6. package/docs/DEVIATIONS.md +1 -0
  7. package/docs/ERRORS.md +94 -0
  8. package/docs/ROADMAP-v2.md +69 -11
  9. package/docs/VOCABULARY.md +27 -0
  10. package/docs/adapters.md +93 -11
  11. package/docs/board-guide.md +401 -66
  12. package/docs/cli-contracts.md +235 -22
  13. package/docs/concepts.md +167 -19
  14. package/docs/configuration.md +113 -5
  15. package/docs/faq.md +21 -5
  16. package/docs/getting-started.md +15 -11
  17. package/docs/redesign-2026-09-17.md +477 -0
  18. package/docs/screenshots/background-1280.png +0 -0
  19. package/docs/screenshots/board-400px.png +0 -0
  20. package/docs/screenshots/board-details-open.png +0 -0
  21. package/docs/screenshots/board-drawer.png +0 -0
  22. package/docs/screenshots/board-handoff.png +0 -0
  23. package/docs/screenshots/board-running.png +0 -0
  24. package/docs/screenshots/capacity-drawer-1280.png +0 -0
  25. package/docs/screenshots/settings-ladder-1280.png +0 -0
  26. package/docs/screenshots/terminals-1280.png +0 -0
  27. package/fixtures/limits/claude/claude-fable-limit.json +11 -0
  28. package/fixtures/limits/claude/claude-model-limit.json +1 -1
  29. package/fixtures/limits/claude/claude-session-limit.json +1 -1
  30. package/fixtures/limits/claude/claude-weekly-limit.json +1 -1
  31. package/fixtures/limits/grok/grok-balance-exhausted.json +11 -0
  32. package/fixtures/live/claude/resume-model-probe.json +20 -0
  33. package/fixtures/live/claude/usage-oauth.json +87 -0
  34. package/fixtures/live/grok/cmd.txt +1 -1
  35. package/fixtures/live/grok/parsed.json +6 -3
  36. package/fixtures/live/grok/run.json +22 -10
  37. package/fixtures/verified.json +8 -1
  38. package/package.json +3 -2
  39. package/scripts/build-docs-site.mjs +4 -4
  40. package/scripts/probe.mjs +2 -1
  41. package/scripts/seed-fake-cards.mjs +59 -6
  42. package/scripts/seed-wes-board.mjs +81 -12
  43. package/src/accounts.mjs +6 -1
  44. package/src/adapters/cli.mjs +130 -0
  45. package/src/adapters/custom.mjs +271 -0
  46. package/src/adapters/grok.mjs +51 -10
  47. package/src/adapters/index.mjs +34 -7
  48. package/src/attach.mjs +350 -42
  49. package/src/audit.mjs +118 -0
  50. package/src/board/audit.js +123 -0
  51. package/src/board/board.css +134 -9
  52. package/src/board/board.js +482 -106
  53. package/src/board/index.html +89 -7
  54. package/src/board/sessions.js +1371 -113
  55. package/src/buckets.mjs +101 -0
  56. package/src/cards.mjs +9 -1
  57. package/src/chain.mjs +13 -0
  58. package/src/hook.mjs +7 -1
  59. package/src/ledger.mjs +10 -2
  60. package/src/orchestrator.mjs +13 -4
  61. package/src/preferences.mjs +214 -5
  62. package/src/scheduler.mjs +24 -1
  63. package/src/server.mjs +615 -50
  64. package/src/sessions.mjs +17 -1
  65. package/src/share.mjs +66 -6
  66. package/src/taps/claude-usage.mjs +91 -2
  67. package/src/taps/claude.mjs +144 -5
  68. package/src/taps/codex.mjs +23 -3
  69. package/src/taps/grok.mjs +4 -0
  70. package/src/usage.mjs +424 -13
package/CHANGELOG.md CHANGED
@@ -1,5 +1,217 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0 (2026-09-17)
4
+
5
+ The board is rebuilt around the two questions you actually open it to answer:
6
+ which terminal needs you, and which model is about to run out. Usage stops
7
+ being a region and becomes one strip plus a property of every row; a hand-off
8
+ can now change the model instead of the CLI; and background work comes back as
9
+ rows you can read at a glance instead of a ledger you have to dig through.
10
+
11
+ - **The board top: a capacity strip, and the login panels behind one
12
+ disclosure.** Under the verdict, one strip prints each login's *binding*
13
+ bucket rather than whichever window happened to be stored: `claude 63%
14
+ 6:01 PM`, `codex back Sat 10:11 PM`, `agy no figure`, `grok no reading`. The
15
+ four login panels are not rewritten, they move intact behind **Capacity and
16
+ models** with every gauge, notch and `aria-valuetext` they had, and the
17
+ claude panel head grows a model rail whose chips read `fable 63%` and
18
+ `opus 12%`. Every branch of `verdictLines()` is now asserted under
19
+ `VERDICT_CH` by `test/board-verdict.test.mjs`, so a 52px headline can no
20
+ longer wrap to three lines. Measured on the seeded board at 1280: the first
21
+ terminal row sits at 536px, where it used to sit at 1382px.
22
+ - **A terminal row says what it is doing, on which model.** The register reads
23
+ `waiting on you baton on main dirty 3 ahead 2 claude/fable`, and a quiet
24
+ agent carries `quiet 5m`. `ahead` is one `git rev-list --count` on the
25
+ existing poll; the model is the alias the leg resolved to, updated from the
26
+ transcript for claude so a silent fallback off Fable becomes visible, and
27
+ never a guessed default. Under the prompt the row prints its binding bucket,
28
+ `63% of the fable week`, and past the warn threshold the rung that would keep
29
+ the terminal.
30
+ - **A terminal waiting on a human says so, everywhere.** Leg now wires Claude
31
+ Code's `Notification` hook (`permission_prompt`, `idle_prompt`,
32
+ `agent_needs_input`). The row's status word becomes `waiting on you` and it
33
+ carries the question verbatim: `waiting on you: permission to run Bash(git
34
+ push origin HEAD), asked 2m ago`. That sorts the row first, counts it in the
35
+ region head (`4 running, 2 waiting on you, 4 share the claude login`) and in
36
+ the tab, which becomes `(2) Leg` with a dotted favicon. Codex, agy and grok
37
+ publish no such signal, so their rows say `quiet Nm` and never claim to be
38
+ waiting. `permission_prompt` fires after about six seconds and mostly when
39
+ you look away, so this is a reliable notice, not an instant one.
40
+ - **A hand-off can now change the model, not just the CLI.** Destinations are
41
+ rungs of `(agent, account, model)`. The ladder's default is `claude/fable`,
42
+ `claude/opus`, `claude/sonnet`, then each remaining installed agent, and a
43
+ terminal's expansion prints it: `now: claude / fable, then claude / opus,
44
+ then claude / sonnet, then codex, then agy` with `first eligible now:
45
+ claude / opus` under it. A claude downshift with a known session id starts
46
+ `claude --resume <id> --model <alias>` and skips the bundle, so the
47
+ conversation survives the switch; every other rung is primed from the bundle
48
+ as before. `leg ladder` prints and edits the same thing from a terminal
49
+ (`ls`, `set <n> <agent>[/<account>[/<model>]]`, `rm <n>`, `spend on|off`),
50
+ and `leg sessions handoff <id> --to claude/default/opus` names a rung.
51
+ - **The wall is attributed to what it actually walled.** A Fable limit walls
52
+ Fable, not the login: `walls{}` is keyed by model and `limited_until` is left
53
+ alone, so `claude/sonnet` keeps working. A session, weekly or spend limit is
54
+ account scoped, and a same-login model rung is then refused as a wasted
55
+ switch with the reason on the row. Wording Leg cannot parse walls the whole
56
+ login and prints the evidence it failed on rather than guessing.
57
+ - **Nothing spends money unless you said it could.** `may_spend` is off. An
58
+ automatic hand-off skips any rung that bills credits or a metered balance and
59
+ writes the reason to the ledger; the Settings sentence is *A rung that spends
60
+ usage credits or metered balance may be taken by an automatic hand-off*, and
61
+ today it adds *Usage credits are off, so there is nothing to spend through
62
+ the wall.* rather than offering a dead control. A `reserve` per login holds a
63
+ floor back from automatic hand-offs only: *An automatic hand-off skips a rung
64
+ past the floor; a hand-off you press yourself still takes it, and the picker
65
+ says so.* Climbing back is a radio with the rule printed under it: *Leg never
66
+ interrupts a running turn to climb.*
67
+ - **Cards are terminals you are not sitting at.** Live cards are rows in a
68
+ **Background** panel directly under Terminals, in the terminal row's
69
+ register with a measured work stat and the same button grid; a running card
70
+ says `no message until this leg ends, started 8:15 PM`, because `-p
71
+ --output-format json` is mute until the leg exits. Finished cards fall into
72
+ one ledger line, `10 finished cards, 5 done, 5 failed, last 8:15 PM`, so ten
73
+ done cards are one row and not ten. Starting one is a single field,
74
+ `Run in the background:`, over an inferred sentence whose nouns are buttons
75
+ (*in recruiting-tool on main, with claude/fable then claude/opus then
76
+ claude/sonnet then codex then agy, build only*); the old thirteen-field
77
+ dialog is still there as **More settings**.
78
+ - **Two verbs for leaving and coming back.** The End confirm row grows
79
+ **End, and keep going as a card**: it writes the bundle, hands the
80
+ terminal's own worktree to a card with its lineage, and says where the work
81
+ went. On a card, **Take over** pauses it and hands back the one command a
82
+ browser cannot run, `leg claude --resume-card <id>`, in an interactive
83
+ terminal primed from the card's bundle.
84
+ - **A time figure, or nothing, and never a time without its sample count.**
85
+ `burn()` reads a per-bucket history ring and prints a rate only with at least
86
+ three samples spanning ten minutes inside the current window; the estimate is
87
+ the endpoint slope, capped at the reset, and a flat or falling line prints
88
+ nothing at all. Where it can speak, the row and the verdict say *about 2h 40m
89
+ of Fable left, from 9 samples over 4h*. A reading that comes back unchanged
90
+ more than ten minutes after the last sample is still recorded, so a flat hour
91
+ is a measured zero rather than a starved gate.
92
+
93
+ Fixes:
94
+
95
+ - **Fixed: the only live Claude wall Leg ever captured was classified as no
96
+ progress.** The fixture pattern matched `You've hit your Fable limit` and
97
+ Anthropic now writes `You've reached your Fable limit`, so a real wall on the
98
+ headless path scored `{outcome: "no_progress", signal: "none"}` and the
99
+ terminal sat on a dead login. The pattern takes both wordings, and
100
+ `fixtures/limits/claude/claude-fable-limit.json` is a new `observed-live`
101
+ fixture produced from the captured response rather than from the docs.
102
+ - **Fixed: the board printed the wrong percentage for a claude login.** The tap
103
+ kept only `five_hour` and `seven_day` out of the usage response and threw the
104
+ rest away, so a login whose binding bucket was the Fable week was reported at
105
+ the account-wide figure. The whole `limits[]` array is now read into
106
+ `buckets[]`, and the strip, the verdict and the row all print the bucket that
107
+ will actually stop you.
108
+ - **Fixed: a seeded board's rows were live controls on real repositories.**
109
+ `scripts/seed-wes-board.mjs` named real paths, and a click on a seeded row
110
+ cut a worktree in a real checkout. The seed now names `C:\Projects-seed\...`,
111
+ realistic in shape and impossible in fact. `docs/ERRORS.md` carries the
112
+ entry.
113
+
114
+ Migration:
115
+
116
+ - `preferences.json` keeps `handoff_order` and now derives it from the ladder,
117
+ so `validHandoffOrder`, `requireHandoffOrder` and every older terminal keep
118
+ working unchanged. It gains `handoff_ladder` (rungs of `{agent, account,
119
+ model, when, cost}`), `may_spend` (default `false`), `climb_back` (default
120
+ `next-handoff`, or `never`), `reserve` (`{agent: percent}`),
121
+ `notify_terminal` (default `true`) and `notify_board` (default `false`). A
122
+ bare `handoff_order` expands into one `model: null, when: always` rung per
123
+ agent, so behaviour is unchanged until you edit a rung.
124
+ - Usage records under `$LEG_HOME/usage/` gain `buckets`, `walls`, `history`,
125
+ `extra_usage` and `facts`. An older Leg ignores them; a record without them
126
+ falls back to the two windows it has always had.
127
+ - Session records gain `model`, `waiting` and `ahead`, all nullable. On a
128
+ shared board all three are dropped from someone else's row (`waiting` carries
129
+ the verbatim question, `model` and `ahead` describe this machine's usage and
130
+ someone else's work). Your own terminal is never redacted, so you keep all
131
+ three on it whichever role you hold.
132
+ - `playwright` is a dev dependency now, so `scripts/board-shots.mjs` runs from
133
+ a fresh `npm install`. It is not shipped in the package.
134
+
135
+ Still assumed, and marked as such:
136
+
137
+ - Whether `codex resume <id> -m <model>` composes. The `resume` subcommand and
138
+ the `-m` flag are each verified from `codex --help`; putting them together is
139
+ not. A codex rung therefore ships primed from the bundle, and only the claude
140
+ rungs claim to keep the conversation.
141
+ - Whether codex, agy and grok leave an OSC 2 terminal title alone once the
142
+ child starts drawing. That is why naming the terminal tab is not in this
143
+ release: the browser tab badge needs no such assumption, and ships.
144
+
145
+ ## 0.11.0 (2026-09-17)
146
+
147
+ - **grok is a card adapter, not just a terminal.** `--chain grok` works. Its
148
+ flags were read from `grok --help` on grok 1.0.34 (`3736acbc8658`) and its
149
+ result envelope out of the shipped binary, both on 2026-09-17, replacing the
150
+ guessed field names the unregistered adapter carried. The prompt now travels
151
+ by `--prompt-file` (a hand-off prompt carries the whole bundle summary, and
152
+ Windows caps a command line near 32k) and `--cwd` is passed explicitly,
153
+ because grok can run against a shared leader process and a leg must edit its
154
+ own worktree. The probe reached the account and came back with a real wall.
155
+ - **A grok terminal at the wall now hands off.** An exhausted Grok Build
156
+ account answers `402 Payment Required: Grok Build usage balance exhausted`,
157
+ and none of the rate-limit strings the tap looked for appear in it. The
158
+ terminal sat on a dead login instead of moving on. Both the tap
159
+ (`src/taps/grok.mjs`) and the leg classifier (a new observed-live fixture)
160
+ now read it as a limit. Found by running the probe against an exhausted
161
+ account on 2026-09-17.
162
+ - **Any CLI can be a card agent, from a JSON spec and no code.** `leg adapter
163
+ template --name muse > muse.json`, `leg adapter add muse.json`, then
164
+ `--chain muse,claude`. `leg adapter check muse` prints the exact command line
165
+ a leg would run before one does; `leg adapter list|show|rm` are the rest.
166
+ Placeholders (`{{prompt}}`, `{{promptFile}}`, `{{cwd}}`, `{{mode}}`,
167
+ `{{model}}`, `{{resume}}`, `{{maxTurns}}`, `{{runDir}}`) fill the argv, and a
168
+ group in the args is dropped whole when a placeholder inside it is unset, so
169
+ an absent model never leaves a bare `--model`. The flags that turn a
170
+ supervised agent into an unsupervised one are refused whatever a spec says.
171
+ A broken spec is reported by name and reason, never thrown, so one typo
172
+ cannot stop the board. See [docs/adapters.md](docs/adapters.md#custom-adapters).
173
+ - **Hand off now can name its destination.** The button still takes the next
174
+ option in the order; Details → **Hand off now to** lists every destination
175
+ with the reason a greyed one cannot be picked, and `leg sessions handoff
176
+ <id> --to codex` is the same choice from a terminal. A destination that is
177
+ not on the chain, not installed, or at its wall is refused at the click with
178
+ the reset time. If the one you picked walls between the click and the
179
+ hand-off, the work continues down the order and the terminal says which
180
+ agent took it instead, rather than going somewhere else in silence.
181
+ - **TLS for a shared board.** `leg share on --tls-cert <file> --tls-key <file>`,
182
+ or `LEG_TLS_CERT` / `LEG_TLS_KEY`. Leg issues no certificate: a self-signed
183
+ pair teaches everyone to click through a warning, which is worse than
184
+ plaintext on a network that is already private, so it takes a pair you have
185
+ (`tailscale cert <machine>.<tailnet>.ts.net` is one command). Half a pair, a
186
+ missing file or an empty one stops the board with exit 3 rather than quietly
187
+ serving plaintext. The loopback companion stays http, because the certificate
188
+ is for the shared name and that traffic never leaves the machine.
189
+ - **An `operator` role, between owner and guest.** An operator runs the
190
+ pipeline board — adds, runs, approves, reassigns and kills cards — and their
191
+ own terminals, and sees nothing that describes this machine: no settings, no
192
+ home path, no repository paths, no conversation index, no audit. `leg share
193
+ add dana --role operator`. What each role may reach is decided in one place
194
+ (`mayUseCards` / `mayUseMachine` in `src/share.mjs`) instead of per endpoint.
195
+ - **An audit trail: who did what, across every terminal and every card.**
196
+ Settings → **Audit trail** on the board, `GET /api/audit` for a script, owner
197
+ only. Hand-offs, landings, approvals, reassignments and kills, newest first,
198
+ each with the person or agent that did it. It reads what the ledger already
199
+ recorded and stores nothing new, and every answer carries how much it read
200
+ (`14 terminals and 3 cards, 812 events read`) so an empty trail cannot be
201
+ mistaken for a quiet week.
202
+ - **Fixed: a guest's own terminal leaked the owner's reset times.** The new
203
+ hand-off destination list carried the exact reset timestamp of every
204
+ account, and a guest owns their own terminal, so it reached them through it.
205
+ A guest and an operator now get the destinations and the coarse reason
206
+ (`at its usage limit`) without the timestamp. Caught by the share-security
207
+ suite in the same change.
208
+ - **Fixed: the board slowed down as soon as a custom adapter existed.**
209
+ `/api/health` asks every adapter where its binary is, and reading the spec
210
+ directory on each of those calls put a readdir, a read and a JSON parse per
211
+ spec on the event loop the terminals lane is pushed from. The parsed list is
212
+ now cached against the directory's entries with a one-second floor, so a
213
+ spec added or removed is still seen at once.
214
+
3
215
  ## 0.10.0 (2026-09-17)
4
216
 
5
217
  - **The board is responsive again, `leg` starts straight away, and the board
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
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
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)
14
+ ![The Leg board at 1280px: a headline naming the terminal that has waited on you longest; under it a capacity strip with claude at 63 percent of its fable week, codex back on Saturday, agy with no figure and grok with no reading, and a button that opens the login panels; then four terminal rows, each with its status, repo and branch, uncommitted and unpushed counts, agent and model, the prompt, the one thing worth knowing and four buttons; then a Background panel of live cards and a one-line field for starting another; then counts for finished terminals, what landed, conversations and finished cards](https://legcli.com/img/docs/terminals-1280.png)
15
15
 
16
16
  You keep using your coding agents exactly as you do today, in any terminal,
17
17
  from your own config directory: Leg adds its hooks in a separate per-session
@@ -21,11 +21,16 @@ files and regions). `leg claude --model opus` is
21
21
  `claude --model opus` with four things running alongside it:
22
22
 
23
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.
24
+ session in every terminal is a row on it, and the row's register reads
25
+ status, repo on branch, uncommitted and unpushed counts, agent and model,
26
+ and how long it has been quiet: `waiting on you baton on main dirty 3
27
+ ahead 2 claude/fable`. Under that come the prompt, the one thing worth
28
+ knowing, the files it is touching and the usage bucket that will stop it.
29
+ Capacity is one strip at the top rather than a region, with the login
30
+ panels and their gauges behind **Capacity and models**. Two sessions
31
+ editing the same file in one repo are flagged on both rows, and a second
32
+ session in a checkout that already has one gets its own worktree and a
33
+ **Land** button instead of writing over the first.
29
34
  2. **Usage tracking** per agent and account, from what each CLI already
30
35
  exposes: Claude Code's usage endpoint and its `StopFailure` hook, Codex's
31
36
  read-only app-server rate-limit read, agy's log, and Grok's billing proxy endpoint.
@@ -33,12 +38,18 @@ files and regions). `leg claude --model opus` is
33
38
  refreshed as the session goes, so the work is always ready to hand off.
34
39
  4. **The handoff itself.** Near the limit you get a warning. At the limit Leg
35
40
  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.
41
+ terminal from that bundle. The options are a ladder of rungs, and a rung is
42
+ an agent, a login and a model: the default is `claude/fable`,
43
+ `claude/opus`, `claude/sonnet`, then each remaining installed agent. A
44
+ Fable limit walls Fable and not the login, so the first move is usually to
45
+ another model on the same subscription, and for claude that move is
46
+ `claude --resume <id> --model opus`, which keeps the conversation instead
47
+ of replaying the bundle. Only when every claude rung is out does it move to
48
+ another CLI. Settings holds the ladder for new terminals, `leg ladder`
49
+ edits it from a shell, and **Details** on a row edits the copy that
50
+ terminal is running. Nothing is retyped. When every rung is out, Leg tells
51
+ you which resets first and when, waits for that reset with a countdown, and
52
+ starts that agent from the bundle.
42
53
 
43
54
  Subscription logins only: Leg strips `ANTHROPIC_API_KEY`,
44
55
  `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_CUSTOM_HEADERS`,
@@ -183,21 +194,23 @@ presenting it as current.
183
194
  both are stamped with the commit and the live terminals they describe.
184
195
  `claude "<prompt>"`, `codex "<prompt>"` and `agy -i "<prompt>"` all open the
185
196
  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.
197
+ 5. **The ladder.** A rung is an agent, a login and a model, and Leg walks from
198
+ rung 1 every time. Other models on the same login come first, then other
199
+ accounts of the same agent, then every other agent in the saved order, each
200
+ tried once. It is a priority list, not a rotation: put agy at the bottom and
201
+ agy is the last rung from a Claude terminal and from a Codex terminal alike.
202
+ The board prints the exact sequence with the rung running now marked, plus
203
+ the first rung eligible from current install and limit state. Use **Change
204
+ the ladder** in a terminal's expansion to change that terminal, `leg ladder`
205
+ to do it from a shell, or Settings to set the default copied by new
206
+ terminals. A rung whose CLI is missing, whose wall has not reset, or that
207
+ shares the window that is already out, is skipped with its reason.
195
208
  6. **All out.** The terminal prints each option with its reset time, soonest
196
209
  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.
210
+ agent from the bundle when it arrives. The row says `waiting for <agent>
211
+ at <time>`. Ctrl-C (or End on the row) quits with exit 3 instead.
199
212
 
200
- You can force a handoff any time: the **Hand off now** button on the card, or
213
+ You can force a handoff any time: the **Hand off now** button on the row, or
201
214
  `leg sessions handoff <id>`. Verified on this machine: `leg claude` opened
202
215
  the real Claude Code TUI with Leg's hooks firing into the session log, the
203
216
  usage poll recorded 36 % of the 5h window and 74 % of the 7d window, the
@@ -216,10 +229,12 @@ row flipped to observed-live (`node scripts/live-limits.mjs`). The same
216
229
  capture is wired for codex `usage_limit_exceeded` and agy `RESOURCE_EXHAUSTED`;
217
230
  no payload for either has been kept yet.
218
231
 
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.
232
+ Terminals started by this version can change their ladder while they run:
233
+ Settings holds the default for new terminals, and **Details** on a row edits
234
+ the copy that terminal is running. An older terminal stays on the order it
235
+ started with; its row says a restart is needed and can save the desired
236
+ default for the next launch. A normal agent exit ends the terminal. It does
237
+ not trigger a handoff.
223
238
 
224
239
  ### The working environment travels too (optional)
225
240
 
@@ -275,12 +290,21 @@ token at all.
275
290
 
276
291
  ```
277
292
  leg share on your own link, printed once
278
- leg share add sam sam's link, printed once
293
+ leg share add sam sam's link, printed once (a guest)
294
+ leg share add dana --role operator dana runs cards, not this machine
279
295
  leg share who is on the board (never a token again)
280
296
  leg share rotate sam sam's old link stops working
281
297
  leg share off back to 127.0.0.1; every link stops working
282
298
  ```
283
299
 
300
+ There are three roles. **owner** is everything: the machine's settings, the
301
+ harness, every terminal, the cards, the history index and the audit trail.
302
+ **operator** is the pipeline board and their own terminals: they add, run,
303
+ approve, reassign and kill cards, and they never see this machine's settings,
304
+ its home path, its repository paths, its conversation index or the audit.
305
+ **guest** is the terminals lane, read-only and redacted, with **Request
306
+ handoff** as their only button.
307
+
284
308
  A token is kept as a sha256 hash, so a lost link is re-issued, never re-read.
285
309
  The board takes the token out of the address bar and keeps it in the browser.
286
310
  Your own browser on this machine needs no token.
@@ -301,32 +325,68 @@ The security pass that goes with it: every `/api` route needs a token, the
301
325
  event stream included; twenty wrong tokens from one address and that address
302
326
  waits a minute; one identity gets 600 requests a minute; a guest gets 403 on
303
327
  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
328
+ every route. Verified live on 2026-09-11: two terminals on one machine, one wes's and
306
329
  one sam's; sam's board showed wes's card with the prompt hidden and only
307
330
  **Request handoff**, and sam's request reached wes's board (`~/.leg/board.log`:
308
331
  "hand-off requested … by sam").
309
332
 
333
+ ### TLS
334
+
335
+ Off unless you hand it a certificate. Leg does not issue one: a self-signed
336
+ pair teaches everyone on the board to click through a warning, which is worse
337
+ than plaintext on a network that is already private. On Tailscale the pair is
338
+ one command:
339
+
340
+ ```
341
+ tailscale cert <machine>.<tailnet>.ts.net
342
+ leg share on --tls-cert <machine>.<tailnet>.ts.net.crt --tls-key <machine>.<tailnet>.ts.net.key
343
+ ```
344
+
345
+ `LEG_TLS_CERT` and `LEG_TLS_KEY` do the same without writing the paths into
346
+ `share.json`, and they win over it. With a pair configured the shared address
347
+ serves https and every printed link says `https://`; the companion listener on
348
+ `127.0.0.1`, which exists so this machine's own browser needs no token, stays
349
+ plain http, because the certificate is for the shared name and loopback traffic
350
+ never leaves the machine. A pair that is half-configured, missing, unreadable
351
+ or empty stops the board with exit 3 rather than quietly serving plaintext.
352
+ Renew the pair, then `leg down && leg up` to pick it up.
353
+
354
+ ### Who did what
355
+
356
+ Settings → **Audit trail** is one list across every terminal and every card,
357
+ newest first: hand-offs, landings, approvals, reassignments, kills, each with
358
+ the person or agent that did it, the repo and the time. Filter by person or by
359
+ kind. It reads what the ledger already recorded, so nothing new is stored, and
360
+ it prints how much it read beside the answer (`14 terminals and 3 cards, 812
361
+ events read`) so an empty trail cannot be mistaken for a quiet week. Owner
362
+ only: the trail names repositories and people.
363
+
310
364
  ## The board
311
365
 
312
366
  `leg <agent>` opens it; `leg open` reopens it; `leg down` stops it.
313
367
 
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.
368
+ - **Verdict and capacity strip**: the largest sentence on the page, then one
369
+ strip with a token per login carrying the binding bucket, a short track, the
370
+ percentage and one state word or clock: `claude 63% 6:01 PM`, `codex back
371
+ Sat 10:11 PM`, `agy no figure`, `grok no reading`. **Capacity and models**
372
+ opens a drawer holding the full login panels: the 5h and 7d rails, the reset
373
+ and how long that is, where and when Leg read the number, `at the wall` with
374
+ its `back <day time>`, and on the claude panel a rail of model chips
375
+ (`fable 63%`, `opus 12%`, a walled one saying `out until 9:14 PM`). The
376
+ sticky instrument head is still what `/floor` puts at the top.
377
+ - **Terminals**: one full-width row per session, the ones that need an answer
378
+ first. The register reads the status word, `repo on branch`, `dirty <n>` and
379
+ `ahead <n>`, `agent/model`, `quiet <n>m`, and `own worktree, from main` when
380
+ the session cut its own worktree; then the first prompt as a
381
+ button, exactly one sentence (the highest-ranked thing true about the
382
+ terminal), an `also:` disclosure naming the rest, the files as
383
+ comma-separated text, the binding bucket as `63% of the fable week`, and an
384
+ elapsed clock with the session tail. Two live sessions in one repo touching
385
+ the same file print `codex (codex-99ab) is changing src/server.mjs in
386
+ another checkout; whoever lands second rebases` on both rows. A Claude row
387
+ parked at a permission or idle prompt rises to the top, says `waiting on
388
+ you` in place of its status word, prints the question verbatim, and puts a
389
+ count in the browser tab (`(2) Leg`, with a dot on the favicon).
330
390
  After a Land the sentence is `landed on <base>, <7-char sha>, <n> files,
331
391
  +<added>/-<removed>`, or `Land was attempted at <time> onto <base> and
332
392
  bounced: <first line of the reason>. The branch still holds every commit;
@@ -337,14 +397,24 @@ one sam's; sam's board showed wes's card with the prompt hidden and only
337
397
  - **Buttons**, in a fixed order that never reflows: Land, Hand off now,
338
398
  Details, End. Once a session has ended, Remove and Remove record take End's
339
399
  place. Details opens an expansion in flow under the panel.
400
+ **Hand off now** takes the first open rung of the ladder. To name the
401
+ destination instead, open Details and use **Hand off now to**, which lists
402
+ every rung with its model, whether it keeps the conversation, and the reason
403
+ a greyed one cannot be picked (`at its usage limit, back 11:40`, `shares the
404
+ window that is out, buys nothing`, `not installed on this machine`).
405
+ `leg sessions handoff <id> --to claude/default/opus` is the same choice from
406
+ a terminal. If the rung you picked walls between the click and the hand-off,
407
+ the work still continues down the ladder and the terminal says which one
408
+ took it instead.
340
409
  - Below it, **Conversations**: every coding-agent conversation on this machine
341
410
  as a count that opens, the ones Leg started and the ones Claude Code, Codex,
342
411
  Grok, Antigravity and Copilot keep in their own stores. Filter by agent,
343
412
  search, repository, or only what Leg started; a row opens its last messages
344
413
  in place, with the `leg history continue` command to copy where the agent
345
414
  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**.
415
+ **Settings**. Background tasks are not down here: a live card is a row in
416
+ the **Background** panel directly under Terminals (see below), and only the
417
+ finished ones collapse into a ledger line beside these counts.
348
418
 
349
419
  The board reads `~/.leg/sessions/*/session.json` over server-sent events; a
350
420
  session whose runner process is gone is marked `lost`, never shown as live.
@@ -460,6 +530,7 @@ leg claude|codex|agy|grok [agent args…] the interactive agent, board alongside
460
530
  leg sessions ls [--json] every session and its usage
461
531
  leg sessions show|events <id>
462
532
  leg sessions handoff|end <id> same as the board buttons
533
+ leg sessions handoff <id> --to <agent>[/<account>] hand off to a destination you name
463
534
  leg sessions rm <id> forget an ended session
464
535
  leg sessions simulate-limit <id> the real limit path without a real wall (claude, agy, grok)
465
536
  leg history [ls] [--provider p] [--repo r] [--search q] [--managed|--external] [--live] [--all] [--json]
@@ -474,9 +545,12 @@ leg harness sync [--to codex,agy] [--force] [--dry-run] | diff <client> | doctor
474
545
  leg harness capture [claude|codex] | source <client> | policy <mode> | disable
475
546
  leg license the license on this machine, or where to buy one
476
547
  leg license activate <key> | deactivate | refresh (refresh renews a Team key)
548
+ leg adapter list|show <n>|check <n> every adapter, built-in and custom
549
+ leg adapter template [--name n] a starter spec to fill in
550
+ leg adapter add <file.json> | rm <n> any CLI as a card agent, from JSON
477
551
  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
552
+ leg share on [--bind tailscale|lan|<addr>] [--port N] [--tls-cert <f> --tls-key <f>] | off
553
+ leg share add|rotate|rm <name> [--role owner|operator|guest] one link per human, printed once
480
554
  leg open | down | status the board
481
555
  leg uninstall [--yes]
482
556
  ```
@@ -488,24 +562,32 @@ open the browser), `LEG_USAGE_POLL_MS` (60000), `LEG_CLAUDE_ARGS` /
488
562
  `LEG_CODEX_ARGS` / `LEG_AGY_ARGS` / `LEG_GROK_ARGS` (extra args for a leg Leg starts after
489
563
  a hand-off, e.g. `-m gpt-5.3-codex-spark`), `LEG_CLAUDE_BIN`,
490
564
  `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).
565
+ terminal this is when the board is shared), `LEG_TLS_CERT` / `LEG_TLS_KEY`
566
+ (serve the shared board over https; they win over `share.json`),
567
+ `LEG_RATE_MAX` (600 requests a minute per human) and `LEG_RATE_MAX_FAILURES`
568
+ (20 wrong tokens per address).
493
569
 
494
570
  ## Background tasks: the v0.1 extras
495
571
 
496
572
  Version 0.1 was the other way round: you dropped a task card on the board and
497
573
  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.
574
+ chain, path leases, a scheduler and a merge queue. All of that still runs, and
575
+ a card is now a terminal you are not sitting at: same register, same one
576
+ sentence, same ladder, same bundle. Live cards are rows in the **Background**
577
+ panel directly under Terminals; finished ones fall into one ledger line that
578
+ opens.
579
+
580
+ Starting one is a single field. `Run in the background:` takes the task, and
581
+ the sentence under it is inferred with its nouns as buttons: the repo of the
582
+ terminal you were last in, the saved ladder, and the `build` workflow, which
583
+ stops with its changes in the card's worktree and does not merge them.
584
+ **More settings** is the full form, with the **Build, test, and merge** and
585
+ **Factory** workflows that include an automatic land station, plus fallback
586
+ agents, permissions, approval gates, turn caps, leases, trunk, merge method,
587
+ tests, title, and scripted test and demo adapters. **End, and keep going as a
588
+ card** on a terminal's End row makes a card out of the work in front of you;
589
+ **Take over** on a card gives you the command that turns it back into a
590
+ terminal.
509
591
 
510
592
  - `leg up` boots the board with the scheduler and merge queue and streams
511
593
  redacted logs; `leg card add --repo <path> --task "<t>" --chain claude,codex --queue`
@@ -514,8 +596,17 @@ under Advanced options.
514
596
  - Adapters spawn the CLIs headless as argv, never through a shell, with their
515
597
  own permission modes and never a bypass flag: `claude -p --output-format json
516
598
  --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.
599
+ `agy -p --output-format json --mode <m> --add-dir <worktree>`,
600
+ `grok --prompt-file <f> --output-format json --permission-mode <m> --cwd
601
+ <worktree>`; `fake`, `fake-claude`, `fake-codex`, `fake-agy` for tests and
602
+ demos.
603
+ - **Any other CLI** is a card adapter too, from a JSON spec and no code:
604
+ `leg adapter template --name muse > muse.json`, fill in the command and its
605
+ flags, `leg adapter add muse.json`, then `--chain muse,claude`. `leg adapter
606
+ check muse` prints the exact command line a leg would run before one does.
607
+ See [docs/adapters.md](docs/adapters.md#custom-adapters). A custom adapter
608
+ runs cards; it is not an interactive `leg <agent>` terminal, because that
609
+ needs a usage tap and a wall signal, which only the four above expose.
519
610
  - A leg that ends on a limit signal, a stall, a crash or exit 0 without
520
611
  `.leg/DONE` hands off with a bundle to the next adapter in the same
521
612
  worktree; a `land` station rebases, tests and fast-forwards trunk or bounces
@@ -623,7 +714,7 @@ Leg is commercial software under the [Leg License Agreement](LICENSE).
623
714
  It ships as readable JavaScript so you can see what it does on your machine,
624
715
  and you may modify it for your own use, but not redistribute it or work
625
716
  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
717
+ and remain available. The version in this source tree is 0.12.0; see
627
718
  [npm](https://www.npmjs.com/package/legcli) for published versions and
628
719
  [CHANGELOG.md](CHANGELOG.md) for release notes.
629
720