@ucsandman/legcli 0.15.0 → 0.16.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 +54 -0
- package/README.md +5 -3
- package/docs/DEVIATIONS.md +1 -0
- package/docs/ERRORS.md +39 -0
- package/docs/adapters.md +5 -2
- package/docs/board-guide.md +33 -29
- package/docs/cli-contracts.md +14 -6
- package/docs/faq.md +4 -3
- package/docs/screenshots/board-400px.png +0 -0
- package/docs/screenshots/board-details-open.png +0 -0
- package/docs/screenshots/board-empty.png +0 -0
- package/docs/screenshots/board-handoff.png +0 -0
- package/docs/screenshots/capacity-drawer-1280.png +0 -0
- package/docs/screenshots/floor.png +0 -0
- package/docs/screenshots/new-card-dialog.png +0 -0
- package/docs/screenshots/settings-ladder-1280.png +0 -0
- package/docs/screenshots/terminals-1280.png +0 -0
- package/package.json +1 -1
- package/scripts/npm-publish-gate.mjs +4 -0
- package/scripts/release-notes.mjs +51 -0
- package/src/board/board.css +177 -55
- package/src/board/board.js +5 -2
- package/src/board/entry.js +4 -4
- package/src/board/favicon.svg +1 -1
- package/src/board/floor.html +2 -2
- package/src/board/floor.js +4 -1
- package/src/board/history.js +1 -1
- package/src/board/index.html +22 -8
- package/src/board/sessions.js +55 -14
- package/src/board/strip.js +1 -1
- package/src/hook.mjs +5 -4
- package/src/taps/claude-usage.mjs +5 -4
- package/src/taps/claude.mjs +34 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.0 (2026-09-19)
|
|
4
|
+
|
|
5
|
+
The board reads as one product: a polish pass inside the 2026-09-15
|
|
6
|
+
direction, a new mark, and a first-run screen.
|
|
7
|
+
|
|
8
|
+
- **One colour rule, kept everywhere.** Login names in the capacity strip,
|
|
9
|
+
the rung in a terminal row's register and the rungs in the ladder editor are
|
|
10
|
+
text beside an identity dot; the colour no longer lands on the word. A
|
|
11
|
+
warning sentence is amber only on a row that is waiting on you. The accent
|
|
12
|
+
blue appears on Land when it can run and on Start once a task is typed, and
|
|
13
|
+
nowhere else. The capacity strip is an aligned table (dot, name, track,
|
|
14
|
+
figure) instead of a ragged sentence.
|
|
15
|
+
- **Terminal rows.** The prompt is clamped to two lines (Details holds the
|
|
16
|
+
rest), the clock sits over one quiet row of buttons, a Land that cannot run
|
|
17
|
+
is not drawn (its reason is said once above the panel), and End is quiet at
|
|
18
|
+
rest. The register moved up to 15px and the scaffolding floor from 13 to
|
|
19
|
+
14px. A row whose state changed since the last render is lit for 1.4 s, the
|
|
20
|
+
one motion the rows own; rows carry a faint hover. Narrow rows lost 264px of
|
|
21
|
+
empty height each: a `flex-basis: 24ch` meant for the row direction had been
|
|
22
|
+
applied as height once the row stacked. The 420px horizontal overflow is
|
|
23
|
+
gone.
|
|
24
|
+
- **One control language.** The background entry is the terminals panel's
|
|
25
|
+
footer, its choices quiet chips inside the sentence. Ledger cells share one
|
|
26
|
+
anatomy with their verbs (View, Browse, New card) on one baseline; Settings
|
|
27
|
+
is a row in the same grammar; disclosures carry a CSS chevron instead of a
|
|
28
|
+
typed `>`; the Floor and Board links are buttons.
|
|
29
|
+
- **The verdict is a link.** When the sentence names a terminal, pressing it
|
|
30
|
+
scrolls to that row and focuses its prompt.
|
|
31
|
+
- **First run.** A board with no terminal shows one lit panel: `leg claude`,
|
|
32
|
+
`leg codex` and `leg agy` each with a Copy, and one line on what appears
|
|
33
|
+
with the first turn.
|
|
34
|
+
- **The floor.** An empty station is one line, heading beside sentence, so
|
|
35
|
+
seven empty stations are seven lines rather than seven screens.
|
|
36
|
+
- **The mark.** A tile with the L cut as hip, knee and foot replaces the 🦿
|
|
37
|
+
emoji on the board, the floor, the favicon, the tab badge and the site.
|
|
38
|
+
The emoji rendered as whichever system emoji font the machine had.
|
|
39
|
+
- **Docs.** Nine screenshots retaken against this build; DESIGN.md and the
|
|
40
|
+
board guide describe the new row and strip; the OG image carries the mark.
|
|
41
|
+
|
|
42
|
+
## 0.15.1 (2026-09-19)
|
|
43
|
+
|
|
44
|
+
Your own Claude Code status line comes back.
|
|
45
|
+
|
|
46
|
+
- **Your status line runs first.** `leg claude` passes a `statusLine` through
|
|
47
|
+
`--settings`, and that key replaces yours rather than merging. Leg has
|
|
48
|
+
always read your `statusLine` command, but it only ever used its `padding`;
|
|
49
|
+
the command itself never ran, so on a build that honours a `--settings`
|
|
50
|
+
status line you saw Leg's row instead of your own. The command is now kept
|
|
51
|
+
on the session record and Leg's status-line hook runs it with the same JSON
|
|
52
|
+
Claude Code sends, printing its rows above Leg's one line. Reported by a
|
|
53
|
+
baton user on Discord. Claude Code 2.1.278 on Windows still shows its
|
|
54
|
+
built-in status line in place of any custom one passed this way, so the 5h
|
|
55
|
+
and 7d numbers keep coming from the usage endpoint (docs/faq.md).
|
|
56
|
+
|
|
3
57
|
## 0.15.0 (2026-09-18)
|
|
4
58
|
|
|
5
59
|
A second login keeps the conversation, and the runtime does less work per
|
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
|
|
@@ -420,8 +420,10 @@ with nothing in it says so with its counts.
|
|
|
420
420
|
first, each row with the short sha, the subject, a `repo@branch` chip, and
|
|
421
421
|
when plus who. A commit a Land put there says `landed by <agent> (<id tail>)`.
|
|
422
422
|
- **Buttons**, in a fixed order that never reflows: Land, Hand off now,
|
|
423
|
-
Details, End
|
|
424
|
-
|
|
423
|
+
Details, End, under the clock at the right of the row. Land is drawn only
|
|
424
|
+
when it can run; its reason is said once above the panel. Once a session has
|
|
425
|
+
ended, Remove and Remove record take End's place. Details opens an expansion
|
|
426
|
+
in flow under the panel.
|
|
425
427
|
**Hand off now** takes the first open rung of the ladder. To name the
|
|
426
428
|
destination instead, open Details and use **Hand off now to**, which lists
|
|
427
429
|
every rung with its model, whether it keeps the conversation, and the reason
|
package/docs/DEVIATIONS.md
CHANGED
|
@@ -104,6 +104,7 @@ shape differs from what the plan assumed. Same columns as the table above.
|
|
|
104
104
|
|------|------|---------------|-------------|-----|
|
|
105
105
|
| 2026-09-11 | bin/leg.mjs `simulate-limit` | "sends a real StopFailure payload with error rate_limit through src/hook.mjs" | the payload carries `baton_simulated: true`; the hook records the wall with a 2-minute reset instead of the default 5 h, never keeps it as live evidence, and the event says `(simulated)`; agy gets the RESOURCE_EXHAUSTED line appended to its Leg-owned session log; codex is refused (its wall lives in a rollout file Leg never writes) with `leg sessions handoff` as the alternative | a test must never wall the real claude login for five hours, and a simulated payload must never flip a docs row to observed-live |
|
|
106
106
|
| 2026-09-11 | src/live-capture.mjs | "save that payload under fixtures/live/" | `fixtures/live/<agent>/limit-<signal>.json` only in a dev clone (the checkout has `.git` and `fixtures/live/`), else `~/.leg/live/`; `LEG_LIVE_DIR` overrides; first arrival per (agent, signal) wins; docs rows flip through `scripts/live-limits.mjs` markers (`<!-- live:<agent>/<signal> -->`) in the same call | an installed package has no writable fixtures directory, and a doc row must flip from evidence on disk, not from memory |
|
|
107
|
+
| 2026-09-19 | src/taps/claude.mjs `writeSettings`, src/hook.mjs | the user's own `statusLine` command runs first (claimed in the file header since baton 0.2.0) | it was read only for its `padding`; the command is now kept on the session record (`user_statusline`) and the status-line hook runs it with the same stdin, printing its rows above Leg's | a baton user reported on Discord (2026-09-19) that baton replaced the status line they had configured; on a build that honours a `--settings` status line, Leg's row displaced theirs |
|
|
107
108
|
| 2026-09-11 | src/attach.mjs `spawnSpec` | the next agent starts with no args | `BATON_<AGENT>_ARGS` (space-separated) is prepended to a leg Leg starts itself after a hand-off; the user's own `baton <agent> …` args still never carry over | the live test had to keep the handed-off leg on a cheap model, and a user has the same need for a chain |
|
|
108
109
|
| 2026-09-11 | src/taps/codex.mjs | the first `role: user` message is the task | a message starting `# AGENTS.md instructions` is skipped like `<environment_context>` | observed live 2026-09-11: codex prepends the AGENTS.md block as the first user message of every rollout, so the card's task read as the instructions file |
|
|
109
110
|
| 2026-09-11 | src/attach.mjs, src/wait.mjs | all out: print the reset times and exit 3 | status `waiting` with `session.waiting = { agent, account, resets_at, since }`; a one-line countdown on stderr (rewritten in place on a TTY, once a minute otherwise); at the reset the chooser runs again and the first option back starts from the bundle; Ctrl-C or a board End quits with exit 3 and an `ended` event "quit while waiting" | the terminal is where the work is; quitting hands the human a restart to type at the reset time |
|
package/docs/ERRORS.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
What broke, why, and what fixed it. One entry per failure, newest first. A first
|
|
4
4
|
occurrence has to be written down or a repeat is never countable.
|
|
5
5
|
|
|
6
|
+
## 2026-09-19: eleven versions reached npm and none reached the GitHub releases page
|
|
7
|
+
|
|
8
|
+
**Fixed by making the publish job create the tag and the GitHub release itself,
|
|
9
|
+
from the version's CHANGELOG.md section, and by backfilling v0.7.0 to v0.15.1.**
|
|
10
|
+
|
|
11
|
+
The releases page stopped at v0.6.0 (2026-09-15, the pre-rename name) while npm went on to
|
|
12
|
+
0.15.1. Releases 0.2.0 to 0.6.0 had been made by hand with `gh release create`,
|
|
13
|
+
batched on 09-11 and 09-15; once `publish-npm` in `.github/workflows/ci.yml`
|
|
14
|
+
took over the npm side on 09-14, nothing tagged or drafted anything, and no
|
|
15
|
+
check noticed, because a green publish job and a stale releases page look the
|
|
16
|
+
same from the terminal. Wes found it on the page. The rule that holds is a
|
|
17
|
+
mechanism, not a note: the publish job now runs `scripts/release-notes.mjs`
|
|
18
|
+
and `gh release create v<version>` in the same job as `npm publish` (the job
|
|
19
|
+
has `contents: write` for that one step), and `scripts/npm-publish-gate.mjs`
|
|
20
|
+
refuses a version with no `## <version>` section in CHANGELOG.md, so a
|
|
21
|
+
release nobody wrote up never reaches npm either. `test/release-notes.test.mjs`
|
|
22
|
+
pins the extraction. The backfilled tags point at the commit each npm version
|
|
23
|
+
was published from (`npm view @ucsandman/legcli time` matched against
|
|
24
|
+
`git log --first-parent --before`), not at the `[RELEASE]` commit, since three
|
|
25
|
+
of those needed a follow-up fix before the gate let them through. 0.6.1 and
|
|
26
|
+
0.8.1 have CHANGELOG sections but never reached npm, so they have no release.
|
|
27
|
+
|
|
6
28
|
## 2026-09-18: 0.14.0 pushed, CI green everywhere, and npm still served 0.13.1
|
|
7
29
|
|
|
8
30
|
**Fixed by putting the `repository` block back into the lockfile root
|
|
@@ -687,3 +709,20 @@ it), and check whether a board was listening on 4747 at the time.
|
|
|
687
709
|
- **The lesson that generalises.** A seeded board is safe to look at and not
|
|
688
710
|
safe to click: any row that names a path that exists is a live control on
|
|
689
711
|
that path. Seed paths must be realistic in shape and impossible in fact.
|
|
712
|
+
|
|
713
|
+
## A width became 264px of height on every narrow row (2026-09-19)
|
|
714
|
+
|
|
715
|
+
- **What happened.** `.term-row > .term-body { flex-basis: 24ch }` gave the
|
|
716
|
+
prompt column a starting width on the wide board. Under 760px the row
|
|
717
|
+
stacks (`flex-direction: column`), the same declaration became a starting
|
|
718
|
+
HEIGHT, and every terminal row carried about 264px of empty space under its
|
|
719
|
+
last line. It shipped in 0.13.0 and survived three review passes because
|
|
720
|
+
each one eyeballed the 400px screenshot instead of measuring it.
|
|
721
|
+
- **Fix.** `flex-basis: auto` inside the narrow block. Narrow rows on the
|
|
722
|
+
seeded board went from 549/467/442/442 to 426/365/330/266.
|
|
723
|
+
- **The lesson that generalises.** A flex-basis is axis-relative; any rule
|
|
724
|
+
that sets one on a container whose direction flips at a breakpoint needs
|
|
725
|
+
the opposite value in that breakpoint. And the shot script's per-row
|
|
726
|
+
heights found it in one run when three visual reviews had not: the polish
|
|
727
|
+
loop is `_shot.mjs` numbers first, picture second (`.design/board-v5/`,
|
|
728
|
+
local only).
|
package/docs/adapters.md
CHANGED
|
@@ -55,8 +55,11 @@ documentation say docs-only.
|
|
|
55
55
|
`src/hook.mjs`.
|
|
56
56
|
- **Why not the status line.** Leg writes a `statusLine` entry into the same
|
|
57
57
|
settings file that would record `rate_limits.five_hour.used_percentage` and
|
|
58
|
-
`resets_at`, and
|
|
59
|
-
|
|
58
|
+
`resets_at`, and runs your own `statusLine` command first, its rows above
|
|
59
|
+
Leg's one (since 0.15.1; before that your command was read and never run,
|
|
60
|
+
so a build that honours the key showed Leg's row instead of yours). Claude
|
|
61
|
+
Code 2.1.268 and 2.1.278 did not run it when it was tried on this machine
|
|
62
|
+
(2026-09-11, 2026-09-19): an `echo` command
|
|
60
63
|
passed through `--settings` and again through a project
|
|
61
64
|
`.claude/settings.local.json` left the built-in status line in place, while
|
|
62
65
|
hooks from the same `--settings` file fired. No artifact of that check was
|
package/docs/board-guide.md
CHANGED
|
@@ -14,24 +14,23 @@ The visual system, and why it is what it is, is `DESIGN.md` at the repo root.
|
|
|
14
14
|
## Screenshots
|
|
15
15
|
|
|
16
16
|
`docs/screenshots/` (listed here so you know what exists before you look for
|
|
17
|
-
one). Nine were retaken on 2026-09-
|
|
18
|
-
`scripts/seed-wes-board.mjs
|
|
19
|
-
|
|
20
|
-
below:
|
|
17
|
+
one). Nine were retaken on 2026-09-19 against the polish pass (0.16.0), on a
|
|
18
|
+
board seeded by `scripts/seed-wes-board.mjs`; the rest are from the 2026-09-17
|
|
19
|
+
and 2026-09-15 sweeps and are noted below:
|
|
21
20
|
|
|
22
21
|
```
|
|
23
|
-
terminals-1280.png the whole board at 1280 px (2026-09-
|
|
24
|
-
board-400px.png the same board at 400 px (2026-09-
|
|
25
|
-
capacity-drawer-1280.png the strip with Capacity and models open (2026-09-
|
|
26
|
-
board-details-open.png a terminal row with its expansion open (2026-09-
|
|
27
|
-
board-handoff.png one row waiting on you, with the question (2026-09-
|
|
22
|
+
terminals-1280.png the whole board at 1280 px (2026-09-19)
|
|
23
|
+
board-400px.png the same board at 400 px (2026-09-19)
|
|
24
|
+
capacity-drawer-1280.png the strip with Capacity and models open (2026-09-19)
|
|
25
|
+
board-details-open.png a terminal row with its expansion open (2026-09-19)
|
|
26
|
+
board-handoff.png one row waiting on you, with the question (2026-09-19)
|
|
28
27
|
background-1280.png the Background panel and the one-line entry (2026-09-17)
|
|
29
|
-
settings-ladder-1280.png the ladder editor in Settings (2026-09-
|
|
30
|
-
board-empty.png no
|
|
28
|
+
settings-ladder-1280.png the ladder editor in Settings (2026-09-19)
|
|
29
|
+
board-empty.png the first-run panel: no terminal yet, three commands with Copy (2026-09-19)
|
|
31
30
|
board-running.png one card running its first agent (2026-09-17)
|
|
32
31
|
board-drawer.png that card expanded: where, runs, take over, timeline (2026-09-17)
|
|
33
32
|
board-done.png the card finished, on the agent that finished it
|
|
34
|
-
floor.png /floor with nothing queued
|
|
33
|
+
floor.png /floor with nothing queued, every station one line (2026-09-19)
|
|
35
34
|
floor-landing.png /floor with every lane full
|
|
36
35
|
floor-final-1280.png /floor after a card finished
|
|
37
36
|
floor-final-400.png the same at 400 px
|
|
@@ -42,18 +41,20 @@ demo-2-limit-hit.png limit hit, bundle written, the row waiting on you
|
|
|
42
41
|
demo-3-handoff-bundle.png the row expanded: bundle path, run signal, timeline
|
|
43
42
|
demo-4-codex-running.png leg 2 running on fake-codex
|
|
44
43
|
demo-5-done.png done, both legs on the chain
|
|
44
|
+
new-card-dialog.png More settings: the task and who runs it (2026-09-19)
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
The five `demo-*.png` are one run of the sequence in [DEMO.md](DEMO.md), all at
|
|
48
48
|
1280 px.
|
|
49
49
|
|
|
50
|
-
**Still stale, as of 2026-09-
|
|
51
|
-
`share-guest-1280.png`, `board-
|
|
52
|
-
`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
**Still stale, as of 2026-09-19:** `share-owner-1280.png`,
|
|
51
|
+
`share-guest-1280.png`, `board-done.png`, `background-1280.png`,
|
|
52
|
+
`board-running.png`, `board-drawer.png`, `floor-landing.png`, the two
|
|
53
|
+
`floor-final-*.png` and the five `demo-*.png` predate the 0.16.0 polish pass:
|
|
54
|
+
their rows still show the 2x2 button grid, coloured login words and the old
|
|
55
|
+
mark, and the oldest of them show the pre-strip top of the board. What each
|
|
56
|
+
one documents is still true. Retake them with the commands below rather than
|
|
57
|
+
trusting the chrome in any of those pictures.
|
|
57
58
|
|
|
58
59
|
To retake one, seed a board with the shape a real one has and drive it to the
|
|
59
60
|
state the shot needs:
|
|
@@ -174,7 +175,7 @@ reset, the source, any wall and the age of the reading, the same sentence the
|
|
|
174
175
|
gauges carry. A login with no percentage is not a `meter` at all, because
|
|
175
176
|
`aria-valuenow` would have to be a number nobody measured.
|
|
176
177
|
|
|
177
|
-
**`Capacity and models
|
|
178
|
+
**`Capacity and models`**, the disclosure at the end of the strip, opens a drawer holding
|
|
178
179
|
the login panels unchanged, plus a rail of model chips on each panel head:
|
|
179
180
|
`fable 63%` for a measured bucket, `fable out until 9:14 PM` for a model at its
|
|
180
181
|
wall, and a bare model name where Leg has a name but no figure. A percentage is
|
|
@@ -307,16 +308,18 @@ been at it, and what you can do about it.
|
|
|
307
308
|
stops and fable continues from the bundle.`, or `from the conversation` when
|
|
308
309
|
that rung is one a `--resume` keeps. Confirming posts the hand-off with the
|
|
309
310
|
model on it; a rung that walled between the draw and the press comes back as
|
|
310
|
-
the server's own 409 sentence in the row. It is a link and not a
|
|
311
|
-
the
|
|
311
|
+
the server's own 409 sentence in the row. It is a link and not a fourth button:
|
|
312
|
+
the button row is the shipped shape and does not reflow.
|
|
312
313
|
- **The clock**: elapsed since the session started (`4h 24m`), and the session's
|
|
313
314
|
short id. The id used to print as `claude-7f3a` immediately after the word
|
|
314
315
|
`claude`; the prefix is the agent name twice and it is gone.
|
|
315
|
-
- **The buttons**,
|
|
316
|
-
place: Land, Hand off now, Details, End. Land is
|
|
317
|
-
it can actually run,
|
|
318
|
-
|
|
319
|
-
|
|
316
|
+
- **The buttons**, one row under the clock at the right, so every row's
|
|
317
|
+
controls sit in the same place: Land, Hand off now, Details, End. Land is
|
|
318
|
+
drawn only when it can actually run, and then it is the one primary button
|
|
319
|
+
on the board; when it cannot, the row does not draw it and its reason is
|
|
320
|
+
printed once above the panel, never left in a tooltip. Hand off now, Details
|
|
321
|
+
and End are quiet controls: the accent means "this can land" and nothing
|
|
322
|
+
else.
|
|
320
323
|
|
|
321
324
|
**A terminal waiting on a human says so, and the tab says it too.** Claude
|
|
322
325
|
Code's Notification hook writes `waiting` on the record when it puts up a
|
|
@@ -417,7 +420,8 @@ one off.`, because two waiters on one terminal is the failure to avoid.
|
|
|
417
420
|
### Terminal buttons
|
|
418
421
|
|
|
419
422
|
The order is fixed, Land, Hand off now, Details, End, and it never reflows by
|
|
420
|
-
availability: a button that does not apply is omitted, never moved.
|
|
423
|
+
availability: a button that does not apply is omitted, never moved. A Land that
|
|
424
|
+
cannot run is omitted too, with its reason hoisted above the panel.
|
|
421
425
|
|
|
422
426
|
| button | shown when | what it does |
|
|
423
427
|
|--------|------------|--------------|
|
|
@@ -555,7 +559,7 @@ raised surface here would compete with the terminals that are live. Each is a
|
|
|
555
559
|
heading, a line of detail and a button that opens the detail below the row.
|
|
556
560
|
|
|
557
561
|
- **N finished**, the terminals that have ended or been lost, `4 lost, 2 ended,
|
|
558
|
-
in leg, costclaw, declick`. **View
|
|
562
|
+
in leg, costclaw, declick`. **View** opens them as full rows. On a real
|
|
559
563
|
board after a day's work this is most of the list, which is exactly why it is a
|
|
560
564
|
count and not the list.
|
|
561
565
|
- **N landed**, what has landed on trunk across every repo the board can see,
|
package/docs/cli-contracts.md
CHANGED
|
@@ -246,10 +246,16 @@ variable and `LEG_SESSION` (source: src/attach.mjs, src/env.mjs).
|
|
|
246
246
|
- Attach: `claude <args> -n "leg#<short id> <repo>/<branch>" --settings <LEG_HOME>/sessions/<id>/claude-settings.json`
|
|
247
247
|
(source: src/attach.mjs `spawnSpec`; src/taps/claude.mjs `writeSettings`).
|
|
248
248
|
Hooks in a `--settings` file merge with the user's rather than replacing
|
|
249
|
-
them; `statusLine` is the one key that replaces, so Leg
|
|
250
|
-
command
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
them; `statusLine` is the one key that replaces, so Leg keeps the user's own
|
|
250
|
+
command on the session record (`user_statusline`) and its status-line hook
|
|
251
|
+
runs that command with the same stdin JSON, printing its rows above Leg's
|
|
252
|
+
one (source: code.claude.com/docs/en/settings, docs/en/statusline "Display
|
|
253
|
+
multiple lines"; src/taps/claude.mjs `userStatusLine`, `userStatusLineText`;
|
|
254
|
+
src/hook.mjs). Before 0.15.1 the user's command was read only for its
|
|
255
|
+
`padding` and never run, so a build that honours a `--settings` status line
|
|
256
|
+
showed Leg's row in place of the user's (reported by a baton user,
|
|
257
|
+
2026-09-19). observed-live 2026-09-11: a Leg session ran with every user
|
|
258
|
+
hook still firing.
|
|
253
259
|
- Terminal title: `-n, --name <name>` "Set a display name for this session
|
|
254
260
|
(shown in the prompt box, /resume picker, and terminal title)" —
|
|
255
261
|
fixtures/help/claude.txt line 132. It sits in the general Options block, not
|
|
@@ -327,11 +333,13 @@ variable and `LEG_SESSION` (source: src/attach.mjs, src/env.mjs).
|
|
|
327
333
|
path can also be run live with `leg sessions simulate-limit` (the same
|
|
328
334
|
payload through `src/hook.mjs`, marked `leg_simulated`, never kept as
|
|
329
335
|
evidence).
|
|
330
|
-
- Status line, not usable on 2.1.268: a custom `statusLine` command passed
|
|
336
|
+
- Status line, not usable on 2.1.268 or 2.1.278: a custom `statusLine` command passed
|
|
331
337
|
through `--settings`, and again through a project
|
|
332
338
|
`.claude/settings.local.json`, was not run at all when it was tried; an
|
|
333
339
|
`echo` command at both levels left the built-in status line in place while
|
|
334
|
-
hooks from the same `--settings` file fired (
|
|
340
|
+
hooks from the same `--settings` file fired (probed again 2026-09-19 on
|
|
341
|
+
2.1.278 with a screenshot of two haiku sessions, not kept; 0 of 87 session
|
|
342
|
+
hook logs on this machine hold a statusline entry; note in the
|
|
335
343
|
src/taps/claude-usage.mjs header, 2026-09-11). Leg still writes the
|
|
336
344
|
`statusLine` entry, which records the
|
|
337
345
|
same `rate_limits.five_hour` / `seven_day` fields
|
package/docs/faq.md
CHANGED
|
@@ -20,14 +20,15 @@ launch, and does not count as a usage limit.
|
|
|
20
20
|
|
|
21
21
|
**Why does Leg poll an endpoint for claude's usage instead of reading the
|
|
22
22
|
status line?**
|
|
23
|
-
Because Claude Code 2.1.268 did not run a custom status line from a settings
|
|
24
|
-
file Leg controls when this was tried on 2026-09-11 (recorded in
|
|
23
|
+
Because Claude Code 2.1.268 and 2.1.278 did not run a custom status line from a settings
|
|
24
|
+
file Leg controls when this was tried on 2026-09-11 and 2026-09-19 (recorded in
|
|
25
25
|
[DEVIATIONS.md](DEVIATIONS.md)); hooks from the same `--settings` file did
|
|
26
26
|
fire. So the numbers come from
|
|
27
27
|
`GET api.anthropic.com/api/oauth/usage` with the login Claude Code already
|
|
28
28
|
stored, which is the same data `/usage` shows. Leg still writes the
|
|
29
29
|
`statusLine` entry, so the endpoint poll becomes a fallback the moment a build
|
|
30
|
-
honours it, and your own status-line command
|
|
30
|
+
honours it, and your own status-line command runs first, its rows above
|
|
31
|
+
Leg's one, either way (since 0.15.1). See
|
|
31
32
|
[adapters.md](adapters.md#claude) and `src/taps/claude-usage.mjs`.
|
|
32
33
|
|
|
33
34
|
**Why does codex get no hook when claude does?**
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ucsandman/legcli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Usage-limit monitor and automatic handoff for Claude Code, Codex, agy and Grok. Type leg claude|codex|agy|grok and get the same interactive agent with a board alongside, auto-approve on by default, usage tracking per agent and account, a live context handoff bundle, and at the limit the next agent continuing in the same terminal. 14-day free trial, then $79 once, 30-day money-back guarantee.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { appendFileSync, readFileSync } from 'node:fs'
|
|
8
8
|
import { dirname, join } from 'node:path'
|
|
9
9
|
import { fileURLToPath } from 'node:url'
|
|
10
|
+
import { releaseSection } from './release-notes.mjs'
|
|
10
11
|
|
|
11
12
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
12
13
|
const EXPECTED_REPOSITORY = 'https://github.com/ucsandman/legcli.git'
|
|
@@ -103,6 +104,9 @@ try {
|
|
|
103
104
|
if (alias.repository?.url !== EXPECTED_REPOSITORY) {
|
|
104
105
|
fail('alias package repository metadata does not match the npm trusted publisher binding')
|
|
105
106
|
}
|
|
107
|
+
// The GitHub release ci.yml creates after the publish is this section, so a
|
|
108
|
+
// version nobody wrote up in CHANGELOG.md never reaches npm either.
|
|
109
|
+
releaseSection(readFileSync(join(root, 'CHANGELOG.md'), 'utf8'), pkg.version)
|
|
106
110
|
|
|
107
111
|
const primaryPublished = await registryStatus(pkg.name, pkg.version)
|
|
108
112
|
const aliasPublished = await registryStatus(alias.name, alias.version)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// release-notes — the CHANGELOG.md section for one version, for the GitHub
|
|
3
|
+
// release CI creates right after it publishes that version to npm.
|
|
4
|
+
//
|
|
5
|
+
// node scripts/release-notes.mjs 0.15.1 the section body (no heading)
|
|
6
|
+
// node scripts/release-notes.mjs 0.15.1 --title "Leg 0.15.1: <first sentence>"
|
|
7
|
+
//
|
|
8
|
+
// Exits 1 with a sentence when CHANGELOG.md has no `## <version> (` heading, so
|
|
9
|
+
// the publish gate refuses a version nobody wrote up.
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { dirname, join } from 'node:path'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
|
|
14
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
15
|
+
|
|
16
|
+
export function releaseSection(changelog, version) {
|
|
17
|
+
const lines = changelog.split(/\r?\n/)
|
|
18
|
+
const heading = new RegExp(`^## ${version.replace(/\./g, '\\.')}(\\s|$)`)
|
|
19
|
+
const start = lines.findIndex((l) => heading.test(l))
|
|
20
|
+
if (start === -1) throw new Error(`CHANGELOG.md has no "## ${version}" section; write the release up before publishing it`)
|
|
21
|
+
let end = lines.findIndex((l, i) => i > start && /^## /.test(l))
|
|
22
|
+
if (end === -1) end = lines.length
|
|
23
|
+
const body = lines.slice(start + 1, end).join('\n').trim()
|
|
24
|
+
if (!body) throw new Error(`CHANGELOG.md's "## ${version}" section is empty`)
|
|
25
|
+
return body
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// "Leg <version>: <the section's first sentence, one line, no trailing stop>",
|
|
29
|
+
// capped so the release list stays readable. Older sections open with a
|
|
30
|
+
// bullet instead of a summary paragraph; the bullet's first sentence serves.
|
|
31
|
+
export function releaseTitle(changelog, version, { product = 'Leg', max = 100 } = {}) {
|
|
32
|
+
const body = releaseSection(changelog, version)
|
|
33
|
+
const paragraph = body.split(/\n\s*\n/)[0].replace(/^[-*]\s+/, '').replace(/\*\*|`/g, '').replace(/\s+/g, ' ').trim()
|
|
34
|
+
const first = (paragraph.match(/^.*?[.!?](?=\s|$)/) ?? [paragraph])[0].replace(/[.!?:;,]$/, '')
|
|
35
|
+
let title = `${product} ${version}: ${first}`
|
|
36
|
+
if (title.length > max) title = title.slice(0, max - 1).replace(/\s+\S*$/, '') + '…'
|
|
37
|
+
return title
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
41
|
+
const args = process.argv.slice(2)
|
|
42
|
+
const version = args.find((a) => !a.startsWith('--'))
|
|
43
|
+
if (!version) { console.error('usage: node scripts/release-notes.mjs <version> [--title]'); process.exit(2) }
|
|
44
|
+
try {
|
|
45
|
+
const changelog = readFileSync(join(root, 'CHANGELOG.md'), 'utf8')
|
|
46
|
+
process.stdout.write((args.includes('--title') ? releaseTitle(changelog, version) : releaseSection(changelog, version)) + '\n')
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error(e.message)
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
}
|