Releases: ucsandman/LegCli
Release list
Leg 0.16.1: Three things that nagged every session, and the sharecard
Three things that nagged every session, and the sharecard.
- The terminal no longer claims "no board is reading claude usage" while
one is. A 429 from the usage endpoint backs the board's poller off for up
to ten minutes; the terminal read the stale record as "no board", said so,
and started its own reads, one more request a minute at an endpoint already
refusing. The poller now writesnext_poll_aton every attempt, a refusal
included, and the terminal stands down while that promise is not overdue
(boardIsPolling, src/usage.mjs). A board that is genuinely gone still
gets the old line and the old fallback. leg claudeopens the board only when nobody is looking at it.
/api/healthreportsviewers, the count of browser tabs on the event
stream; a second terminal on a board that is already on screen adds no tab.
A guarded board (share on) says no count and opens as before.- A second claude login fits its row on the capacity strip. The name
column was a fixed 7ch, and "claude/work" ran into its own track. The
column is now as wide as the longest login on the strip, set by strip.js,
so every row's track still starts flush. - The sharecard reaches X. robots.txt said
Disallow: /ogto keep the
card's source page out of the index, and that prefix also blocked
/og.png, so X drew a bare link for four days. The line is/og$with an
explicitAllow: /og.png; the card's foot no longer overflows the frame
and the image URL is?v=5.
Leg 0.16.0: The board reads as one product: a polish pass inside the 2026-09-15 direction, a new…
The board reads as one product: a polish pass inside the 2026-09-15
direction, a new mark, and a first-run screen.
- One colour rule, kept everywhere. Login names in the capacity strip,
the rung in a terminal row's register and the rungs in the ladder editor are
text beside an identity dot; the colour no longer lands on the word. A
warning sentence is amber only on a row that is waiting on you. The accent
blue appears on Land when it can run and on Start once a task is typed, and
nowhere else. The capacity strip is an aligned table (dot, name, track,
figure) instead of a ragged sentence. - Terminal rows. The prompt is clamped to two lines (Details holds the
rest), the clock sits over one quiet row of buttons, a Land that cannot run
is not drawn (its reason is said once above the panel), and End is quiet at
rest. The register moved up to 15px and the scaffolding floor from 13 to
14px. A row whose state changed since the last render is lit for 1.4 s, the
one motion the rows own; rows carry a faint hover. Narrow rows lost 264px of
empty height each: aflex-basis: 24chmeant for the row direction had been
applied as height once the row stacked. The 420px horizontal overflow is
gone. - One control language. The background entry is the terminals panel's
footer, its choices quiet chips inside the sentence. Ledger cells share one
anatomy with their verbs (View, Browse, New card) on one baseline; Settings
is a row in the same grammar; disclosures carry a CSS chevron instead of a
typed>; the Floor and Board links are buttons. - The verdict is a link. When the sentence names a terminal, pressing it
scrolls to that row and focuses its prompt. - First run. A board with no terminal shows one lit panel:
leg claude,
leg codexandleg agyeach with a Copy, and one line on what appears
with the first turn. - The floor. An empty station is one line, heading beside sentence, so
seven empty stations are seven lines rather than seven screens. - The mark. A tile with the L cut as hip, knee and foot replaces the 🦿
emoji on the board, the floor, the favicon, the tab badge and the site.
The emoji rendered as whichever system emoji font the machine had. - Docs. Nine screenshots retaken against this build; DESIGN.md and the
board guide describe the new row and strip; the OG image carries the mark.
Leg 0.15.1: Your own Claude Code status line comes back
Your own Claude Code status line comes back.
- Your status line runs first.
leg claudepasses astatusLinethrough
--settings, and that key replaces yours rather than merging. Leg has
always read yourstatusLinecommand, but it only ever used itspadding;
the command itself never ran, so on a build that honours a--settings
status line you saw Leg's row instead of your own. The command is now kept
on the session record and Leg's status-line hook runs it with the same JSON
Claude Code sends, printing its rows above Leg's one line. Reported by a
baton user on Discord. Claude Code 2.1.278 on Windows still shows its
built-in status line in place of any custom one passed this way, so the 5h
and 7d numbers keep coming from the usage endpoint (docs/faq.md).
Leg 0.15.0: A second login keeps the conversation, and the runtime does less work per minute
A second login keeps the conversation, and the runtime does less work per
minute.
- A hand-off to your other claude login keeps the conversation.
leg accounts add claude <name>now junctions Claude Code'sprojectsstore
into the account beside the harness directories, so the second login sees
the same conversations and the same auto-memory as the first. At a weekly
or Fable wall the terminal moves to that login withclaude --resume <id>
under itsCLAUDE_CONFIG_DIRand no bundle prompt, and the timeline says
claude/fable → claude/work (kept the conversation). One rule decides it
for the terminal and for the board's picker (keepsConversationin
src/usage.mjs): a same-login downshift, or another login whose home can
see the transcript file, checked at the switch. An account made by an
older Leg gets the junction the next time it starts.leg historylists a
junctioned store once. codex still takes the bundle across logins. leg digest: what happened while you were away. The default window is
8 hours (--since 2d,--since 30m, an ISO time;--json;GET /api/digest?since=on the board, owner only). The first line is the volume
it was read from, then what needs you (a live terminal waiting on a
question, a card parked for a human, a failed card, a lost terminal, in
that order), then one block per repository with every terminal, card and
landing that moved and the events worth a line, and last the walls
standing now. Read only, over the records already on disk; a window with
nothing in it says so with its counts.- The runtime does less work per minute, measured before and after. A
profiler (fsandchild_processcounters plus--cpu-prof) sat on every
path first; the numbers are medians on one Windows 11 box, Node 24, with a
fake agent, an isolatedLEG_HOMEand a board on 4800–4899, and every
change carries a test that was seen failing without it.leg --versionand every other command loaded 68 modules beforemain()
ran.bin/leg.mjsnow imports each command group inside its own branch,
src/limits.mjsloads its fixture tree on first use, and the launcher
asks a new leaf (src/scheduler-status.mjs) whether the scheduler runs
instead of pulling in the orchestrator.leg --version: 127 → 52 ms
wall, 79 → 0 ms CPU after Node's own boot, 254 → 4 fs calls, 68 → 1
project module;leg sessions ls116 → 75 ms;leg card ls117 → 70 ms
(test/cli-lazy.test.mjs).- An idle terminal spawned 59.5 git processes a minute (six per poll round)
and blocked its own event loop 4.4–10.3 s of every minute doing it. One
git status --porcelain=v2 --branch(src/git.mjs) now carries the
head, the branch, the dirty list and the upstream's own ahead count:
10.9 git processes a minute, 2.1 s of git wall time, terminal CPU 1.7 →
0.1 s a minute (test/git-status.test.mjs,test/attach-perf.test.mjs). - The first
leg <agent>of the day waited for the board it had just
started (polling/api/healthfor about a second) before the agent got
its first instruction. The agent starts at once and the wait runs behind
it; a session that exits within a second still claims the board's
pidfile before it goes. Time to the agent on that launch: 1,220 → 261 ms. - The two-minute bundle checkpoint ran the python CLI synchronously inside
the poll tick, freezing limit detection and every board button for as
long as it took (up to its 120 s timeout). It runs beside the tick now,
one at a time, and the hand-off save waits for a checkpoint still
writing. installedAgents()ran<agent> --versionfor every agent onPATHat
every launch (an 8 s budget each); the answer is kept for a day in
$LEG_HOME/installed.json, keyed by the resolved bin. ALEG_<AGENT>_BIN
override is never cached.- An idle board with 43 terminals and one page open rebuilt the whole
sessions view every 10 s whether or not a byte had changed: 7,294 fs
calls, 8 git processes and 1.1 CPU seconds a minute for nothing. The
health tick now asks the same stat fingerprint the watcher asks, runs the
liveness pass itself (a runner that died moves no file), and pushes only
when the answer changed; a card that starts or stops waiting on a human
forces the one push the fingerprint cannot see. Idle: 7,294 → 4,824 fs
calls, 8.2 → 0 git processes, 1.14 → 0.54 CPU seconds and 6 → 0 sessions
pushes a minute (what remains is the liveness pass and the scheduler's
own tick). One
sessions view also read the usage files 143 times (once per rung per
terminal, then again for capacity and the accounts panel) and probed three
paths per terminal for a synthesis file; one reader per view and one
readdirper checkout:/api/sessions53 → 34 ms, 511 → 212 fs calls per
answer, and each row drops seven runner-bookkeeping fields nothing on the
board read (8% off every push;GET /api/sessions/<id>keeps the whole
record). - A cold
/api/worktreesran up to forty git processes on the board's one
event loop: 9–25 s in which no stylesheet, click or SSE frame was served.
The list is gathered throughexecFile, four at a time, one refresh per
query, and a caller that arrives while a refresh runs gets the last list.
Worst/api/healthduring a cold call: 11.8 → 3.2 s; the rest is the
history index's synchronouslistHistory, still open./api/trunk
(polled every 2 s by the floor) is cached 15 s and cleared by any card
change or landing: 27.5 → 0.5 ms. A terminal's detail drawer ran onegit ls-filesper file it touched every 3 s; one process for the list: 1,247 →
305 ms. updateSessiontakes the lock budgetrun.jsonalready had (250 tries, a
10 s steal) instead of running unlocked after 1.2 s;control.jsonis
cleared under its lock at exit;src/handoff.mjstakesscrubfrom
src/redact.mjsinstead of the card runner; the 12-leg stop names the
bundle andleg resumeinstead of promising a hand-off a fresh launch
never performed.
- The README's hand-off step 3 said one bundle per leg. The code has
written one bundle per session, updated in place, since 0.2.0; the sentence
now says so.docs/concepts.mdand thesrc/sessions.mjsheader also said
the runner was the only writer ofsession.json; the usage poller, the
claude hooks and a board action patch it too, under one lock, and both now
say that.
Leg 0.14.0: The 14-day trial is back
The 14-day trial is back.
- Free for 14 days, no key, no card. The first
leg <agent>on a machine
starts a 14-day trial with every gate open, Team'sleg shareincluded, and
records it in$LEG_HOME/trial.json.leg license statusshows the days
left without starting the clock. After the trial, or with a refused key once
it is over, a session exits 4 with the price and the 30-day guarantee, which
stays as the second net after buying. Deleting the file resets the clock,
which is the known limit of an offline trial and is accepted. The license
agreement gains the trial clause; the site, README, support page and
llms.txtsay "free for 14 days" where they said "no trial". - The README stops calling the repository private. It has been public at
github.com/ucsandman/legcli; two sentences still said otherwise.
Leg 0.13.1: A one-line fix in the terminal and the marketing site brought up to what 0.13.0 does
A one-line fix in the terminal and the marketing site brought up to what 0.13.0 does.
- The terminal's opening line names rungs.
next: claude/opus → claude/sonnet → codex → agy, through the samerungLabelevery other
surface uses; it readnext: claude → claude → codex → agyon 0.12.0. - The site says what is underneath the handoff. legcli.com is reorganised
around the product as it is now: the sample transcript shows a Fable wall
answered by Opus keeping the conversation and a login wall answered by Codex
from the bundle; new sections for the ladder, background cards (End as a
card, Run in the background, Take over),leg history, the portable
harness and custom adapters in one compact group, and a Never spent
without you column beside what is never touched. The structured data's
softwareVersionhad been stuck at 0.8.0; the OG image gains Grok; the FAQ
no longer says a shared board has no TLS.
Leg 0.13.0: The first day on 0.12.0 with three real terminals found five things
The first day on 0.12.0 with three real terminals found five things. Usage
polling moves out of the terminals into the board, so the endpoint stops
answering 429 and the timeline stops repeating it. Every rung of a ladder can
now name a model from a catalog Leg reads off the installed CLIs, and the new
card form is rebuilt around that. The board holds still under a reader with a
row expanded. The floor is a page you can start work from. And a page whose
files are newer than the process serving them says so.
- One usage poll per login, in the board. Every claude terminal used to
ask Claude's usage endpoint once a minute on its own; three terminals plus
Claude Code's own polling meant a 429 every other minute, and the terminal's
timeline loggedclaude usage unavailable: usage endpoint 429: {...}each
time the answer flipped. The board process now polls each login once per
LEG_USAGE_POLL_MS(60s), backs off to ten minutes on any refusal and snaps
back on the first good answer, and writes the two windows onto every active
session of that login. A refusal is recorded once on the usage record
(error,error_since, owner only) with one status line,claude usage unavailable since 9:03 AM: usage endpoint 429: rate_limit_error, and one
claude usage is back; it never erases the measured buckets. Terminals
poll nothing, so a terminal started before this release keeps its old
minute-by-minute poll until it is restarted. - A model catalog, read from the CLIs you have.
GET /api/modelslists
what each provider can run today: claude's aliases (fable, opus, sonnet,
haiku); codex'smodels_cache.jsonentries withvisibility: listplus the
default fromconfig.toml(gpt-6-astra, gpt-5.6-sol, gpt-5.6-terra,
gpt-5.6-luna, gpt-5.5 on the machine this shipped from);agy modelsand
grok models, each run at most once an hour behind the answer and cached
under<LEG_HOME>/models/. A rung's model is validated by shape for every
provider and by membership for claude only, so next month's codex model is
not refused and a saved ladder is never silently reset to the default. - The new card form, rebuilt. Task first, then the repository as a picker
of known repos or a typed path, the branch to cut from, and Who runs it:
one row per rung with provider, model, permissions, ask before start and max
turns, reorderable, prefilled from your ladder, with Save as my default
ladder writing it back to Settings. Advanced holds the workflow, scripted
first agent, leases, merge method, test command and title. The dialog posts
its chain as one object per rung, soclaude/fablethenclaude/opusis
finally two legs and not one. Two columns at 900px, one below; it scrolls on
a short window instead of hiding Create card below the fold. - The one-line entry row is never empty. It derives its rungs from
handoff_orderwhen a preferences file predates ladders, the ladder noun
opens both the rung and the model selects, and with only metered providers
and spending off it says so instead of "no agent is configured". - The board holds still. With a row expanded, a text selection live, the
pointer on a row or focus in the list, the needs-you re-sort is computed but
not drawn until you come out (hover and focus release after 30s on their
own). The expansion is never detached from the document, so its selection,
scroll offsets and open confirm row survive every 3-second poll; the
region's top is anchored against rows above it changing height; the
timeline appends new lines above you without moving what you read, and a
status line repeated word for word within a minute folds into one with
×N.scripts/board-jump-probe.mjsis the regression harness: it printed a
205px drift on 0.12.0 and 0px now. - The floor is a page you start work from. The four login panels move
behind the same Capacity and models disclosure as on the board, under
the same one-line strip; the Run in the background entry sits under it
and posts exactly what the board's does; Running, Waiting on you, Queued,
Backlog and Done today are Background-style rows with counts in their
headings, a queued row says its position and what it waits for, and j/k and
Enter work as on the board.More settingscarries the typed task to the
board's dialog through/#new-card=. The strip and the entry row live once,
insrc/board/strip.jsandsrc/board/entry.js, loaded by both pages. - A page newer than its process says so.
/api/healthanswers with the
process version; the page files carry theirs. When they differ the board and
the floor printThis board process runs leg 0.10.0 and the page files are 0.13.0. Restart it to match: leg down && leg up, which is the state a board
left running across annpm i -glands in, and the state that hid every
0.12.0 feature on the first morning.
Leg 0.12.0: The board is rebuilt around the two questions you actually open it to answer: which…
The board is rebuilt around the two questions you actually open it to answer:
which terminal needs you, and which model is about to run out. Usage stops
being a region and becomes one strip plus a property of every row; a hand-off
can now change the model instead of the CLI; and background work comes back as
rows you can read at a glance instead of a ledger you have to dig through.
- The board top: a capacity strip, and the login panels behind one
disclosure. Under the verdict, one strip prints each login's binding
bucket rather than whichever window happened to be stored:claude 63% 6:01 PM,codex back Sat 10:11 PM,agy no figure,grok no reading. The
four login panels are not rewritten, they move intact behind Capacity and
models with every gauge, notch andaria-valuetextthey had, and the
claude panel head grows a model rail whose chips readfable 63%and
opus 12%. Every branch ofverdictLines()is now asserted under
VERDICT_CHbytest/board-verdict.test.mjs, so a 52px headline can no
longer wrap to three lines. Measured on the seeded board at 1280: the first
terminal row sits at 536px, where it used to sit at 1382px. - A terminal row says what it is doing, on which model. The register reads
waiting on you baton on main dirty 3 ahead 2 claude/fable, and a quiet
agent carriesquiet 5m.aheadis onegit rev-list --counton the
existing poll; the model is the alias the leg resolved to, updated from the
transcript for claude so a silent fallback off Fable becomes visible, and
never a guessed default. Under the prompt the row prints its binding bucket,
63% of the fable week, and past the warn threshold the rung that would keep
the terminal. - A terminal waiting on a human says so, everywhere. Leg now wires Claude
Code'sNotificationhook (permission_prompt,idle_prompt,
agent_needs_input). The row's status word becomeswaiting on youand it
carries the question verbatim:waiting on you: permission to run Bash(git push origin HEAD), asked 2m ago. That sorts the row first, counts it in the
region head (4 running, 2 waiting on you, 4 share the claude login) and in
the tab, which becomes(2) Legwith a dotted favicon. Codex, agy and grok
publish no such signal, so their rows sayquiet Nmand never claim to be
waiting.permission_promptfires after about six seconds and mostly when
you look away, so this is a reliable notice, not an instant one. - A hand-off can now change the model, not just the CLI. Destinations are
rungs of(agent, account, model). The ladder's default isclaude/fable,
claude/opus,claude/sonnet, then each remaining installed agent, and a
terminal's expansion prints it:now: claude / fable, then claude / opus, then claude / sonnet, then codex, then agywithfirst eligible now: claude / opusunder it. A claude downshift with a known session id starts
claude --resume <id> --model <alias>and skips the bundle, so the
conversation survives the switch; every other rung is primed from the bundle
as before.leg ladderprints and edits the same thing from a terminal
(ls,set <n> <agent>[/<account>[/<model>]],rm <n>,spend on|off),
andleg sessions handoff <id> --to claude/default/opusnames a rung. - The wall is attributed to what it actually walled. A Fable limit walls
Fable, not the login:walls{}is keyed by model andlimited_untilis left
alone, soclaude/sonnetkeeps working. A session, weekly or spend limit is
account scoped, and a same-login model rung is then refused as a wasted
switch with the reason on the row. Wording Leg cannot parse walls the whole
login and prints the evidence it failed on rather than guessing. - Nothing spends money unless you said it could.
may_spendis off. An
automatic hand-off skips any rung that bills credits or a metered balance and
writes the reason to the ledger; the Settings sentence is A rung that spends
usage credits or metered balance may be taken by an automatic hand-off, and
today it adds Usage credits are off, so there is nothing to spend through
the wall. rather than offering a dead control. Areserveper login holds a
floor back from automatic hand-offs only: An automatic hand-off skips a rung
past the floor; a hand-off you press yourself still takes it, and the picker
says so. Climbing back is a radio with the rule printed under it: Leg never
interrupts a running turn to climb. - Cards are terminals you are not sitting at. Live cards are rows in a
Background panel directly under Terminals, in the terminal row's
register with a measured work stat and the same button grid; a running card
saysno message until this leg ends, started 8:15 PM, because-p --output-format jsonis mute until the leg exits. Finished cards fall into
one ledger line,10 finished cards, 5 done, 5 failed, last 8:15 PM, so ten
done cards are one row and not ten. Starting one is a single field,
Run in the background:, over an inferred sentence whose nouns are buttons
(in recruiting-tool on main, with claude/fable then claude/opus then
claude/sonnet then codex then agy, build only); the old thirteen-field
dialog is still there as More settings. - Two verbs for leaving and coming back. The End confirm row grows
End, and keep going as a card: it writes the bundle, hands the
terminal's own worktree to a card with its lineage, and says where the work
went. On a card, Take over pauses it and hands back the one command a
browser cannot run,leg claude --resume-card <id>, in an interactive
terminal primed from the card's bundle. - A time figure, or nothing, and never a time without its sample count.
burn()reads a per-bucket history ring and prints a rate only with at least
three samples spanning ten minutes inside the current window; the estimate is
the endpoint slope, capped at the reset, and a flat or falling line prints
nothing at all. Where it can speak, the row and the verdict say about 2h 40m
of Fable left, from 9 samples over 4h. A reading that comes back unchanged
more than ten minutes after the last sample is still recorded, so a flat hour
is a measured zero rather than a starved gate.
Fixes:
- Fixed: the only live Claude wall Leg ever captured was classified as no
progress. The fixture pattern matchedYou've hit your Fable limitand
Anthropic now writesYou've reached your Fable limit, so a real wall on the
headless path scored{outcome: "no_progress", signal: "none"}and the
terminal sat on a dead login. The pattern takes both wordings, and
fixtures/limits/claude/claude-fable-limit.jsonis a newobserved-live
fixture produced from the captured response rather than from the docs. - Fixed: the board printed the wrong percentage for a claude login. The tap
kept onlyfive_hourandseven_dayout of the usage response and threw the
rest away, so a login whose binding bucket was the Fable week was reported at
the account-wide figure. The wholelimits[]array is now read into
buckets[], and the strip, the verdict and the row all print the bucket that
will actually stop you. - Fixed: a seeded board's rows were live controls on real repositories.
scripts/seed-wes-board.mjsnamed real paths, and a click on a seeded row
cut a worktree in a real checkout. The seed now namesC:\Projects-seed\...,
realistic in shape and impossible in fact.docs/ERRORS.mdcarries the
entry.
Migration:
preferences.jsonkeepshandoff_orderand now derives it from the ladder,
sovalidHandoffOrder,requireHandoffOrderand every older terminal keep
working unchanged. It gainshandoff_ladder(rungs of{agent, account, model, when, cost}),may_spend(defaultfalse),climb_back(default
next-handoff, ornever),reserve({agent: percent}),
notify_terminal(defaulttrue) andnotify_board(defaultfalse). A
barehandoff_orderexpands into onemodel: null, when: alwaysrung per
agent, so behaviour is unchanged until you edit a rung.- Usage records under
$LEG_HOME/usage/gainbuckets,walls,history,
extra_usageandfacts. An older Leg ignores them; a record without them
falls back to the two windows it has always had. - Session records gain
model,waitingandahead, all nullable. On a
shared board all three are dropped from someone else's row (waitingcarries
the verbatim question,modelandaheaddescribe this machine's usage and
someone else's work). Your own terminal is never redacted, so you keep all
three on it whichever role you hold. playwrightis a dev dependency now, soscripts/board-shots.mjsruns from
a freshnpm install. It is not shipped in the package.
Still assumed, and marked as such:
- Whether
codex resume <id> -m <model>composes. Theresumesubcommand and
the-mflag are each verified fromcodex --help; putting them together is
not. A codex rung therefore ships primed from the bundle, and only the claude
rungs claim to keep the conversation. - Whether codex, agy and grok leave an OSC 2 terminal title alone once the
child starts drawing. That is why naming the terminal tab is not in this
release: the browser tab badge needs no such assumption, and ships.
Leg 0.11.0: grok is a card adapter, not just a terminal
- grok is a card adapter, not just a terminal.
--chain grokworks. Its
flags were read fromgrok --helpon grok 1.0.34 (3736acbc8658) and its
result envelope out of the shipped binary, both on 2026-09-17, replacing the
guessed field names the unregistered adapter carried. The prompt now travels
by--prompt-file(a hand-off prompt carries the whole bundle summary, and
Windows caps a command line near 32k) and--cwdis passed explicitly,
because grok can run against a shared leader process and a leg must edit its
own worktree. The probe reached the account and came back with a real wall. - A grok terminal at the wall now hands off. An exhausted Grok Build
account answers402 Payment Required: Grok Build usage balance exhausted,
and none of the rate-limit strings the tap looked for appear in it. The
terminal sat on a dead login instead of moving on. Both the tap
(src/taps/grok.mjs) and the leg classifier (a new observed-live fixture)
now read it as a limit. Found by running the probe against an exhausted
account on 2026-09-17. - Any CLI can be a card agent, from a JSON spec and no code.
leg adapter template --name muse > muse.json,leg adapter add muse.json, then
--chain muse,claude.leg adapter check museprints the exact command line
a leg would run before one does;leg adapter list|show|rmare the rest.
Placeholders ({{prompt}},{{promptFile}},{{cwd}},{{mode}},
{{model}},{{resume}},{{maxTurns}},{{runDir}}) fill the argv, and a
group in the args is dropped whole when a placeholder inside it is unset, so
an absent model never leaves a bare--model. The flags that turn a
supervised agent into an unsupervised one are refused whatever a spec says.
A broken spec is reported by name and reason, never thrown, so one typo
cannot stop the board. See docs/adapters.md. - Hand off now can name its destination. The button still takes the next
option in the order; Details → Hand off now to lists every destination
with the reason a greyed one cannot be picked, andleg sessions handoff <id> --to codexis the same choice from a terminal. A destination that is
not on the chain, not installed, or at its wall is refused at the click with
the reset time. If the one you picked walls between the click and the
hand-off, the work continues down the order and the terminal says which
agent took it instead, rather than going somewhere else in silence. - TLS for a shared board.
leg share on --tls-cert <file> --tls-key <file>,
orLEG_TLS_CERT/LEG_TLS_KEY. Leg issues no certificate: a self-signed
pair teaches everyone to click through a warning, which is worse than
plaintext on a network that is already private, so it takes a pair you have
(tailscale cert <machine>.<tailnet>.ts.netis one command). Half a pair, a
missing file or an empty one stops the board with exit 3 rather than quietly
serving plaintext. The loopback companion stays http, because the certificate
is for the shared name and that traffic never leaves the machine. - An
operatorrole, between owner and guest. An operator runs the
pipeline board — adds, runs, approves, reassigns and kills cards — and their
own terminals, and sees nothing that describes this machine: no settings, no
home path, no repository paths, no conversation index, no audit.leg share add dana --role operator. What each role may reach is decided in one place
(mayUseCards/mayUseMachineinsrc/share.mjs) instead of per endpoint. - An audit trail: who did what, across every terminal and every card.
Settings → Audit trail on the board,GET /api/auditfor a script, owner
only. Hand-offs, landings, approvals, reassignments and kills, newest first,
each with the person or agent that did it. It reads what the ledger already
recorded and stores nothing new, and every answer carries how much it read
(14 terminals and 3 cards, 812 events read) so an empty trail cannot be
mistaken for a quiet week. - Fixed: a guest's own terminal leaked the owner's reset times. The new
hand-off destination list carried the exact reset timestamp of every
account, and a guest owns their own terminal, so it reached them through it.
A guest and an operator now get the destinations and the coarse reason
(at its usage limit) without the timestamp. Caught by the share-security
suite in the same change. - Fixed: the board slowed down as soon as a custom adapter existed.
/api/healthasks every adapter where its binary is, and reading the spec
directory on each of those calls put a readdir, a read and a JSON parse per
spec on the event loop the terminals lane is pushed from. The parsed list is
now cached against the directory's entries with a one-second floor, so a
spec added or removed is still seen at once.
Leg 0.10.0: The board is responsive again, leg starts straight away, and the board opens every time
-
The board is responsive again,
legstarts straight away, and the board
opens every time. One running terminal was enough to saturate the board
server: it rewrites its record every few seconds and takes a control lock
about once a second, and the watcher over the sessions directory rebuilt the
whole terminals view — over a second ofgit— for every one of those
touches, including the lock files and the temp files atomic writes leave
behind. The server spent all its time in that loop, so it answered a
stylesheet in fourteen seconds and/api/healthin seven. Four consequences,
all fixed:- The watcher now drops locks and temp files by name, and checks the rest
against a stat fingerprint of the files the view is actually built from,
because a lock taken inside a session directory changes that directory's
own mtime and arrives as an event naming nothing else. What survives is
floored to one rebuild per interval.canLandis cached per record
revision with staggered expiries, so twenty worktrees are never re-read in
one pass. The terminals view went from 2,000 ms to 115 ms; on a board with
66 sessions/api/healthwent from 14.2 s to 2.4 ms and/board.cssfrom
14.2 s to 1.5 ms. leg claudetreated a board too busy to answer a health probe as no board
at all, spawned a second server that could only die ofEADDRINUSE, then
polled the dead child for fifteen seconds. A listener on the port is now a
board:legattaches to it.- The board is opened whether or not this terminal is the one that started
it. Previously the browser was only opened on the path that spawned the
server, so with a board already running nothing ever opened. - One push rebuilt the terminals grid three times (two listeners registered
forleg:sessionsand one for the legacy alias, with the payload parsed
twice per push and one dispatch outside the staleness guard). Every button
was destroyed and recreated several times a second. One push now rebuilds
once, and an open confirm row is left alone until it is answered.
- The watcher now drops locks and temp files by name, and checks the rest
-
Remove, Remove record, End and Land work. Confirming any of them did
nothing at all.confirmRowclearspendingConfirmand then calls its
callback, and the callback readpendingConfirm.action— off the variable it
had just cleared. Every Yes on the Terminals panel threw
TypeError: Cannot read properties of null (reading 'action')into the
console and never reached the request. The callback now closes over a
snapshot taken before the row is built. -
Every conversation on this machine (
leg history,leg worktrees, the
board's Conversations cell). One read-only index over the conversations
Claude Code, Codex, Grok, Antigravity and Copilot CLI keep in their own
stores, plus Leg's own sessions, deduplicated (a session Leg started and
the same conversation in the agent's store are one row, markedleg; the
rest areexternal). List, filter by agent, repository or text, open one
(its last messages, read from the tail of the transcript only when asked),
andleg history continue <id>to start a supervised leg on it where the
installed CLI documents resume-by-id (claude, codex, grok, agy; copilot
lists and reads but is not an agent Leg runs). Nothing in an agent's store
is moved or written; Leg writes only~/.leg/history/index.json, refreshed
incrementally by size and mtime, and never opens a SQLite file. Every
checkout Leg can see, git's and its own and the ones conversations ran in,
with owner, uncommitted count, orphaned, stale and missing verdicts, read
only. On a shared board the whole group is the owner's. Redaction learned
the shapes another agent's transcript carries (Stripe, Google, xAI, npm,
GitLab, Hugging Face keys, JWTs, private-key blocks, basic auth, URL
credentials,password=), and the board token joined the values it strips.
Support matrix and every file read: docs/history.md. -
History, after its review. The board's Conversations cell pages past
150 rows (the total no longer shrank with each cursor page); the board
refreshes a stale index in a child process instead of its own event loop;
a cwd on an unreachable network share no longer stalls a refresh; an
Antigravity retitle or a touched presence lock is seen by an incremental
refresh, and only that conversation is re-read; an older Leg session id
still opens the conversation it was one leg of;leg historyprints a
failed refresh whatever the last index still lists,refreshfails cleanly
when another Leg holds the lock,--limit 0is a usage error (--all),
--offsetworks, a Leg-only row's id prints whole; a malformed id on
/api/history/<id>is a 400; an account name that is a path is ignored;
titles are cut to 200 as documented and go through the held-value
redaction too; the Copilot chip has a colour. Redaction no longer eats
Basic authentication/authorization,cache_key = ...or
refresh_token: string, and a value never crosses a line break.