@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.
- package/CHANGELOG.md +212 -0
- package/README.md +158 -67
- package/bin/leg.mjs +168 -18
- package/docs/DECISIONS.md +10 -0
- package/docs/DEMO.md +20 -14
- package/docs/DEVIATIONS.md +1 -0
- package/docs/ERRORS.md +94 -0
- package/docs/ROADMAP-v2.md +69 -11
- package/docs/VOCABULARY.md +27 -0
- package/docs/adapters.md +93 -11
- package/docs/board-guide.md +401 -66
- package/docs/cli-contracts.md +235 -22
- package/docs/concepts.md +167 -19
- package/docs/configuration.md +113 -5
- package/docs/faq.md +21 -5
- package/docs/getting-started.md +15 -11
- package/docs/redesign-2026-09-17.md +477 -0
- package/docs/screenshots/background-1280.png +0 -0
- package/docs/screenshots/board-400px.png +0 -0
- package/docs/screenshots/board-details-open.png +0 -0
- package/docs/screenshots/board-drawer.png +0 -0
- package/docs/screenshots/board-handoff.png +0 -0
- package/docs/screenshots/board-running.png +0 -0
- package/docs/screenshots/capacity-drawer-1280.png +0 -0
- package/docs/screenshots/settings-ladder-1280.png +0 -0
- package/docs/screenshots/terminals-1280.png +0 -0
- package/fixtures/limits/claude/claude-fable-limit.json +11 -0
- package/fixtures/limits/claude/claude-model-limit.json +1 -1
- package/fixtures/limits/claude/claude-session-limit.json +1 -1
- package/fixtures/limits/claude/claude-weekly-limit.json +1 -1
- package/fixtures/limits/grok/grok-balance-exhausted.json +11 -0
- package/fixtures/live/claude/resume-model-probe.json +20 -0
- package/fixtures/live/claude/usage-oauth.json +87 -0
- package/fixtures/live/grok/cmd.txt +1 -1
- package/fixtures/live/grok/parsed.json +6 -3
- package/fixtures/live/grok/run.json +22 -10
- package/fixtures/verified.json +8 -1
- package/package.json +3 -2
- package/scripts/build-docs-site.mjs +4 -4
- package/scripts/probe.mjs +2 -1
- package/scripts/seed-fake-cards.mjs +59 -6
- package/scripts/seed-wes-board.mjs +81 -12
- package/src/accounts.mjs +6 -1
- package/src/adapters/cli.mjs +130 -0
- package/src/adapters/custom.mjs +271 -0
- package/src/adapters/grok.mjs +51 -10
- package/src/adapters/index.mjs +34 -7
- package/src/attach.mjs +350 -42
- package/src/audit.mjs +118 -0
- package/src/board/audit.js +123 -0
- package/src/board/board.css +134 -9
- package/src/board/board.js +482 -106
- package/src/board/index.html +89 -7
- package/src/board/sessions.js +1371 -113
- package/src/buckets.mjs +101 -0
- package/src/cards.mjs +9 -1
- package/src/chain.mjs +13 -0
- package/src/hook.mjs +7 -1
- package/src/ledger.mjs +10 -2
- package/src/orchestrator.mjs +13 -4
- package/src/preferences.mjs +214 -5
- package/src/scheduler.mjs +24 -1
- package/src/server.mjs +615 -50
- package/src/sessions.mjs +17 -1
- package/src/share.mjs +66 -6
- package/src/taps/claude-usage.mjs +91 -2
- package/src/taps/claude.mjs +144 -5
- package/src/taps/codex.mjs +23 -3
- package/src/taps/grok.mjs +4 -0
- 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
|
-

|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
and
|
|
40
|
-
|
|
41
|
-
|
|
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. **
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
198
|
-
at <time>`. Ctrl-C (or End on the
|
|
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
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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.
|
|
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
|
-
- **
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
`
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
`
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
347
|
-
|
|
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>
|
|
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), `
|
|
492
|
-
|
|
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
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
|
518
|
-
`
|
|
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.
|
|
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
|
|