@ucsandman/legcli 0.13.1 → 0.15.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 +115 -0
- package/LICENSE +2 -0
- package/README.md +62 -22
- package/bin/leg.mjs +108 -65
- package/docs/DECISIONS.md +10 -1
- package/docs/DEVIATIONS.md +33 -0
- package/docs/ERRORS.md +22 -0
- package/docs/ROADMAP-v2.md +12 -1
- package/docs/concepts.md +52 -14
- package/docs/configuration.md +5 -3
- package/docs/review-2026-09-18.md +172 -0
- package/fixtures/verified.json +1 -1
- package/package.json +2 -2
- package/src/accounts.mjs +67 -7
- package/src/attach.mjs +184 -48
- package/src/board/board.js +1 -1
- package/src/board/floor.js +1 -1
- package/src/bundle.mjs +33 -11
- package/src/digest.mjs +197 -0
- package/src/git.mjs +97 -0
- package/src/handoff.mjs +20 -2
- package/src/history/common.mjs +3 -1
- package/src/history/providers/claude.mjs +5 -1
- package/src/history/worktrees.mjs +105 -25
- package/src/launcher.mjs +1 -1
- package/src/license.mjs +38 -13
- package/src/limits.mjs +19 -1
- package/src/scheduler-status.mjs +22 -0
- package/src/scheduler.mjs +3 -14
- package/src/server.mjs +171 -38
- package/src/session-detail.mjs +25 -2
- package/src/sessions.mjs +18 -3
- package/src/synthesis.mjs +23 -4
- package/src/usage.mjs +27 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,120 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0 (2026-09-18)
|
|
4
|
+
|
|
5
|
+
A second login keeps the conversation, and the runtime does less work per
|
|
6
|
+
minute.
|
|
7
|
+
|
|
8
|
+
- **A hand-off to your other claude login keeps the conversation.** `leg
|
|
9
|
+
accounts add claude <name>` now junctions Claude Code's `projects` store
|
|
10
|
+
into the account beside the harness directories, so the second login sees
|
|
11
|
+
the same conversations and the same auto-memory as the first. At a weekly
|
|
12
|
+
or Fable wall the terminal moves to that login with `claude --resume <id>`
|
|
13
|
+
under its `CLAUDE_CONFIG_DIR` and no bundle prompt, and the timeline says
|
|
14
|
+
`claude/fable → claude/work (kept the conversation)`. One rule decides it
|
|
15
|
+
for the terminal and for the board's picker (`keepsConversation` in
|
|
16
|
+
`src/usage.mjs`): a same-login downshift, or another login whose home can
|
|
17
|
+
see the transcript file, checked at the switch. An account made by an
|
|
18
|
+
older Leg gets the junction the next time it starts. `leg history` lists a
|
|
19
|
+
junctioned store once. codex still takes the bundle across logins.
|
|
20
|
+
- **`leg digest`: what happened while you were away.** The default window is
|
|
21
|
+
8 hours (`--since 2d`, `--since 30m`, an ISO time; `--json`; `GET
|
|
22
|
+
/api/digest?since=` on the board, owner only). The first line is the volume
|
|
23
|
+
it was read from, then what needs you (a live terminal waiting on a
|
|
24
|
+
question, a card parked for a human, a failed card, a lost terminal, in
|
|
25
|
+
that order), then one block per repository with every terminal, card and
|
|
26
|
+
landing that moved and the events worth a line, and last the walls
|
|
27
|
+
standing now. Read only, over the records already on disk; a window with
|
|
28
|
+
nothing in it says so with its counts.
|
|
29
|
+
- **The runtime does less work per minute, measured before and after.** A
|
|
30
|
+
profiler (`fs` and `child_process` counters plus `--cpu-prof`) sat on every
|
|
31
|
+
path first; the numbers are medians on one Windows 11 box, Node 24, with a
|
|
32
|
+
fake agent, an isolated `LEG_HOME` and a board on 4800–4899, and every
|
|
33
|
+
change carries a test that was seen failing without it.
|
|
34
|
+
- `leg --version` and every other command loaded 68 modules before `main()`
|
|
35
|
+
ran. `bin/leg.mjs` now imports each command group inside its own branch,
|
|
36
|
+
`src/limits.mjs` loads its fixture tree on first use, and the launcher
|
|
37
|
+
asks a new leaf (`src/scheduler-status.mjs`) whether the scheduler runs
|
|
38
|
+
instead of pulling in the orchestrator. `leg --version`: 127 → 52 ms
|
|
39
|
+
wall, 79 → 0 ms CPU after Node's own boot, 254 → 4 fs calls, 68 → 1
|
|
40
|
+
project module; `leg sessions ls` 116 → 75 ms; `leg card ls` 117 → 70 ms
|
|
41
|
+
(`test/cli-lazy.test.mjs`).
|
|
42
|
+
- An idle terminal spawned 59.5 git processes a minute (six per poll round)
|
|
43
|
+
and blocked its own event loop 4.4–10.3 s of every minute doing it. One
|
|
44
|
+
`git status --porcelain=v2 --branch` (`src/git.mjs`) now carries the
|
|
45
|
+
head, the branch, the dirty list and the upstream's own ahead count:
|
|
46
|
+
10.9 git processes a minute, 2.1 s of git wall time, terminal CPU 1.7 →
|
|
47
|
+
0.1 s a minute (`test/git-status.test.mjs`, `test/attach-perf.test.mjs`).
|
|
48
|
+
- The first `leg <agent>` of the day waited for the board it had just
|
|
49
|
+
started (polling `/api/health` for about a second) before the agent got
|
|
50
|
+
its first instruction. The agent starts at once and the wait runs behind
|
|
51
|
+
it; a session that exits within a second still claims the board's
|
|
52
|
+
pidfile before it goes. Time to the agent on that launch: 1,220 → 261 ms.
|
|
53
|
+
- The two-minute bundle checkpoint ran the python CLI synchronously inside
|
|
54
|
+
the poll tick, freezing limit detection and every board button for as
|
|
55
|
+
long as it took (up to its 120 s timeout). It runs beside the tick now,
|
|
56
|
+
one at a time, and the hand-off save waits for a checkpoint still
|
|
57
|
+
writing.
|
|
58
|
+
- `installedAgents()` ran `<agent> --version` for every agent on `PATH` at
|
|
59
|
+
every launch (an 8 s budget each); the answer is kept for a day in
|
|
60
|
+
`$LEG_HOME/installed.json`, keyed by the resolved bin. A `LEG_<AGENT>_BIN`
|
|
61
|
+
override is never cached.
|
|
62
|
+
- An idle board with 43 terminals and one page open rebuilt the whole
|
|
63
|
+
sessions view every 10 s whether or not a byte had changed: 7,294 fs
|
|
64
|
+
calls, 8 git processes and 1.1 CPU seconds a minute for nothing. The
|
|
65
|
+
health tick now asks the same stat fingerprint the watcher asks, runs the
|
|
66
|
+
liveness pass itself (a runner that died moves no file), and pushes only
|
|
67
|
+
when the answer changed; a card that starts or stops waiting on a human
|
|
68
|
+
forces the one push the fingerprint cannot see. Idle: 7,294 → 4,824 fs
|
|
69
|
+
calls, 8.2 → 0 git processes, 1.14 → 0.54 CPU seconds and 6 → 0 sessions
|
|
70
|
+
pushes a minute (what remains is the liveness pass and the scheduler's
|
|
71
|
+
own tick). One
|
|
72
|
+
sessions view also read the usage files 143 times (once per rung per
|
|
73
|
+
terminal, then again for capacity and the accounts panel) and probed three
|
|
74
|
+
paths per terminal for a synthesis file; one reader per view and one
|
|
75
|
+
`readdir` per checkout: `/api/sessions` 53 → 34 ms, 511 → 212 fs calls per
|
|
76
|
+
answer, and each row drops seven runner-bookkeeping fields nothing on the
|
|
77
|
+
board read (8% off every push; `GET /api/sessions/<id>` keeps the whole
|
|
78
|
+
record).
|
|
79
|
+
- A cold `/api/worktrees` ran up to forty git processes on the board's one
|
|
80
|
+
event loop: 9–25 s in which no stylesheet, click or SSE frame was served.
|
|
81
|
+
The list is gathered through `execFile`, four at a time, one refresh per
|
|
82
|
+
query, and a caller that arrives while a refresh runs gets the last list.
|
|
83
|
+
Worst `/api/health` during a cold call: 11.8 → 3.2 s; the rest is the
|
|
84
|
+
history index's synchronous `listHistory`, still open. `/api/trunk`
|
|
85
|
+
(polled every 2 s by the floor) is cached 15 s and cleared by any card
|
|
86
|
+
change or landing: 27.5 → 0.5 ms. A terminal's detail drawer ran one `git
|
|
87
|
+
ls-files` per file it touched every 3 s; one process for the list: 1,247 →
|
|
88
|
+
305 ms.
|
|
89
|
+
- `updateSession` takes the lock budget `run.json` already had (250 tries, a
|
|
90
|
+
10 s steal) instead of running unlocked after 1.2 s; `control.json` is
|
|
91
|
+
cleared under its lock at exit; `src/handoff.mjs` takes `scrub` from
|
|
92
|
+
`src/redact.mjs` instead of the card runner; the 12-leg stop names the
|
|
93
|
+
bundle and `leg resume` instead of promising a hand-off a fresh launch
|
|
94
|
+
never performed.
|
|
95
|
+
- **The README's hand-off step 3 said one bundle per leg.** The code has
|
|
96
|
+
written one bundle per session, updated in place, since 0.2.0; the sentence
|
|
97
|
+
now says so. `docs/concepts.md` and the `src/sessions.mjs` header also said
|
|
98
|
+
the runner was the only writer of `session.json`; the usage poller, the
|
|
99
|
+
claude hooks and a board action patch it too, under one lock, and both now
|
|
100
|
+
say that.
|
|
101
|
+
|
|
102
|
+
## 0.14.0 (2026-09-18)
|
|
103
|
+
|
|
104
|
+
The 14-day trial is back.
|
|
105
|
+
|
|
106
|
+
- **Free for 14 days, no key, no card.** The first `leg <agent>` on a machine
|
|
107
|
+
starts a 14-day trial with every gate open, Team's `leg share` included, and
|
|
108
|
+
records it in `$LEG_HOME/trial.json`. `leg license status` shows the days
|
|
109
|
+
left without starting the clock. After the trial, or with a refused key once
|
|
110
|
+
it is over, a session exits 4 with the price and the 30-day guarantee, which
|
|
111
|
+
stays as the second net after buying. Deleting the file resets the clock,
|
|
112
|
+
which is the known limit of an offline trial and is accepted. The license
|
|
113
|
+
agreement gains the trial clause; the site, README, support page and
|
|
114
|
+
`llms.txt` say "free for 14 days" where they said "no trial".
|
|
115
|
+
- **The README stops calling the repository private.** It has been public at
|
|
116
|
+
github.com/ucsandman/legcli; two sentences still said otherwise.
|
|
117
|
+
|
|
3
118
|
## 0.13.1 (2026-09-18)
|
|
4
119
|
|
|
5
120
|
A one-line fix in the terminal and the marketing site brought up to what 0.13.0 does.
|
package/LICENSE
CHANGED
|
@@ -9,6 +9,8 @@ install or use Leg.
|
|
|
9
9
|
|
|
10
10
|
1. License. When you buy a license we grant you a non-exclusive,
|
|
11
11
|
non-transferable license to install and use Leg as follows.
|
|
12
|
+
Trial: before buying, one person may install and use Leg to evaluate it
|
|
13
|
+
for 14 days from its first use on a machine, on the Team terms below.
|
|
12
14
|
Personal: one named person, on any number of machines that person uses.
|
|
13
15
|
Team: one named person per seat purchased. A Team license also covers use
|
|
14
16
|
of the shared board (leg share) by the people holding seats. A Personal
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ files and regions). `leg claude --model opus` is
|
|
|
23
23
|
1. **A board.** Opened once in your browser, reused after that. Every Leg
|
|
24
24
|
session in every terminal is a row on it, and the row's register reads
|
|
25
25
|
status, repo on branch, uncommitted and unpushed counts, agent and model,
|
|
26
|
-
and how long it has been quiet: `waiting on you
|
|
26
|
+
and how long it has been quiet: `waiting on you leg on main dirty 3
|
|
27
27
|
ahead 2 claude/fable`. Under that come the prompt, the one thing worth
|
|
28
28
|
knowing, the files it is touching and the usage bucket that will stop it.
|
|
29
29
|
Capacity is one strip at the top rather than a region, with the login
|
|
@@ -138,8 +138,8 @@ agent name passes straight through (`leg codex -m gpt-5.3-codex-spark`,
|
|
|
138
138
|
through unchanged, and your settings file is never edited: Leg's hooks ride
|
|
139
139
|
in a separate per-session `--settings` file.
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
Leg is commercial, source-available software, and the source is on
|
|
142
|
+
[GitHub](https://github.com/ucsandman/legcli): every `.mjs` file that runs is in the package you just installed, at
|
|
143
143
|
`$(npm root -g)/legcli/src`, and the license lets you read it and modify
|
|
144
144
|
your own copy. There is nothing compiled, minified or bundled to see through.
|
|
145
145
|
|
|
@@ -179,12 +179,20 @@ presenting it as current.
|
|
|
179
179
|
3. **Bundle.** Leg writes structured notes (task, the last messages from the
|
|
180
180
|
transcript, `git diff --stat`, dirty files, files edited this session, recent
|
|
181
181
|
commits, why it stopped) and runs `context-handoff-bundle save --repo-local`
|
|
182
|
-
with one slug per
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
with one slug per session, updated in place (`--update <slug>`) at every
|
|
183
|
+
checkpoint, about every two minutes while the session is active, and at
|
|
184
|
+
every warning, limit and hand-off. If the session maintained `.leg/SYNTHESIS-<session-id>.md`,
|
|
185
185
|
Leg inlines it into the resume file as a `## Synthesis` section ahead of the raw dump.
|
|
186
|
-
4. **Switch.** The agent process is stopped
|
|
187
|
-
|
|
186
|
+
4. **Switch.** The agent process is stopped and the terminal is restored. Two
|
|
187
|
+
claude destinations keep the conversation itself instead of the bundle: a
|
|
188
|
+
weaker model on the same login (`claude --resume <id> --model <alias>`),
|
|
189
|
+
and another claude login you added with `leg accounts add`, which sees the
|
|
190
|
+
same transcript through the `projects` junction the account carries
|
|
191
|
+
(`claude --resume <id>` under that login's `CLAUDE_CONFIG_DIR`). A weekly
|
|
192
|
+
or Fable wall on one login then continues on the other with the
|
|
193
|
+
conversation it already had, and the timeline says `kept the
|
|
194
|
+
conversation`. Every other rung starts in the same terminal with a short
|
|
195
|
+
pointer prompt:
|
|
188
196
|
read `.leg/RESUME-<session-id>.md` (the `context-handoff-bundle load`
|
|
189
197
|
output, the `## Synthesis` section if present, and the reason for the switch),
|
|
190
198
|
check `git status` and `git diff`, continue, do not ask the human to restate
|
|
@@ -361,6 +369,23 @@ it prints how much it read beside the answer (`14 terminals and 3 cards, 812
|
|
|
361
369
|
events read`) so an empty trail cannot be mistaken for a quiet week. Owner
|
|
362
370
|
only: the trail names repositories and people.
|
|
363
371
|
|
|
372
|
+
### What happened while you were away
|
|
373
|
+
|
|
374
|
+
`leg digest` (default window 8 hours; `--since 2d`, `--since 30m`, or an ISO
|
|
375
|
+
time; `--json` for the record; `GET /api/digest?since=` on the board, owner
|
|
376
|
+
only) is the trail read the other way round: grouped by repository, what
|
|
377
|
+
needs you first. The first line is the volume it was read from (`3
|
|
378
|
+
terminals, 2 cards, 1 landing, 412 events read (66 sessions and 9 cards on
|
|
379
|
+
disk)`), then `needs you`: a live terminal waiting on a question, a card
|
|
380
|
+
parked for a human, a card that failed, a terminal that was lost, in that
|
|
381
|
+
order and each with how long ago. Then one block per repository: every
|
|
382
|
+
terminal that moved in the window with its login and model, state, turns,
|
|
383
|
+
files, commits ahead and the events worth a line (a wall, a hand-off and
|
|
384
|
+
whether it kept the conversation, an all-out wait, the end), every card with
|
|
385
|
+
its last event, every landing with who pressed Land, and last the walls
|
|
386
|
+
standing right now with their reset times. Nothing new is recorded; a window
|
|
387
|
+
with nothing in it says so with its counts.
|
|
388
|
+
|
|
364
389
|
## The board
|
|
365
390
|
|
|
366
391
|
`leg <agent>` opens it; `leg open` reopens it; `leg down` stops it.
|
|
@@ -445,19 +470,30 @@ matrix (which agents list, show messages, continue) and every file read are in
|
|
|
445
470
|
|
|
446
471
|
Optional. `leg accounts add claude work` creates
|
|
447
472
|
`~/.leg/accounts/claude/work`, junctions your `hooks`, `skills`, `agents`,
|
|
448
|
-
`commands`, `plugins`, `rules`, `scripts`, `output-styles
|
|
449
|
-
copies `settings.json`, `CLAUDE.md` and the status-line
|
|
450
|
-
your real `~/.claude` before every launch), and prints
|
|
473
|
+
`commands`, `plugins`, `rules`, `scripts`, `output-styles`, `tools` and
|
|
474
|
+
`projects` into it, copies `settings.json`, `CLAUDE.md` and the status-line
|
|
475
|
+
scripts (refreshed from your real `~/.claude` before every launch), and prints
|
|
476
|
+
one line to paste:
|
|
451
477
|
|
|
452
478
|
```
|
|
453
479
|
$env:CLAUDE_CONFIG_DIR='C:\Users\you\.leg\accounts\claude\work'; claude auth login
|
|
454
480
|
```
|
|
455
481
|
|
|
482
|
+
`projects` is Claude Code's conversation store, so the second login sees the
|
|
483
|
+
same conversations and the same auto-memory as the first, and a hand-off from
|
|
484
|
+
one login to the other keeps the conversation: the terminal moves to `work`
|
|
485
|
+
with `claude --resume <id>` and no bundle prompt. That is the whole point of a
|
|
486
|
+
second 20x login when the Fable or the weekly window on the first one is out.
|
|
487
|
+
An account made by an older Leg gets the junction the next time it starts.
|
|
488
|
+
Claude Code writes that directory; Leg only reads it.
|
|
489
|
+
|
|
456
490
|
Same for codex (`CODEX_HOME`; `config.toml`, `AGENTS.md`, `skills`, `prompts`,
|
|
457
|
-
`rules`, `plugins`, `agents`, `hooks`, `memories` shared
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
491
|
+
`rules`, `plugins`, `agents`, `hooks`, `memories` shared; a codex hand-off
|
|
492
|
+
still takes the bundle, because `codex resume` under a second `CODEX_HOME`
|
|
493
|
+
has not been observed). agy 1.2.0 has no config-directory override, so it
|
|
494
|
+
stays one account. Only the login lives in the account directory;
|
|
495
|
+
`leg accounts rm` removes the junctions and the directory and never touches
|
|
496
|
+
your real home.
|
|
461
497
|
|
|
462
498
|
The terms, as published (effective dates below):
|
|
463
499
|
|
|
@@ -537,6 +573,8 @@ leg history [ls] [--provider p] [--repo r] [--search q] [--managed|--external] [
|
|
|
537
573
|
every conversation on this machine, Leg's own and the agents' own (read only)
|
|
538
574
|
leg history show <id> [--messages n] [--json] | continue <id> [agent args…] | refresh [--full] | providers
|
|
539
575
|
leg worktrees [--repo <path>] [--no-dirty] [--json] every checkout: git's, Leg's, the conversations' (read only)
|
|
576
|
+
leg digest [--since 8h|2d|<iso>] [--json] what happened while you were away: needs-you first, then every
|
|
577
|
+
terminal, card, landing and wall in the window, by repository (read only)
|
|
540
578
|
leg accounts ls logins and their 5h/7d usage
|
|
541
579
|
leg accounts add <claude|codex|grok> <name> | rm <agent> <name> | terms
|
|
542
580
|
leg harness status|inspect|check|explain|history [--json] the portable harness, read-only
|
|
@@ -697,8 +735,8 @@ drift. CI waits for the Ubuntu and Windows test matrix, validates both
|
|
|
697
735
|
when that version is missing and newer than the stable `latest`. Existing
|
|
698
736
|
versions skip cleanly; older, prerelease, lockstep, and registry-error cases
|
|
699
737
|
fail the job. The npm trusted publisher is bound to `ucsandman/legcli` and
|
|
700
|
-
`.github/workflows/ci.yml` (bind `leg-agents` the same way).
|
|
701
|
-
|
|
738
|
+
`.github/workflows/ci.yml` (bind `leg-agents` the same way). Publication
|
|
739
|
+
uses `--provenance=false`.
|
|
702
740
|
|
|
703
741
|
## Privacy and attribution
|
|
704
742
|
|
|
@@ -714,17 +752,19 @@ Leg is commercial software under the [Leg License Agreement](LICENSE).
|
|
|
714
752
|
It ships as readable JavaScript so you can see what it does on your machine,
|
|
715
753
|
and you may modify it for your own use, but not redistribute it or work
|
|
716
754
|
around the license check. Versions 0.2.0 and 0.3.0 were published under MIT
|
|
717
|
-
and remain available. The version in this source tree is 0.
|
|
755
|
+
and remain available. The version in this source tree is 0.15.0; see
|
|
718
756
|
[npm](https://www.npmjs.com/package/legcli) for published versions and
|
|
719
757
|
[CHANGELOG.md](CHANGELOG.md) for release notes.
|
|
720
758
|
|
|
721
|
-
|
|
759
|
+
The first 14 days are a **free trial**: install it, type `leg claude`, and
|
|
760
|
+
everything in both plans is on, with no key and no card. The clock starts on
|
|
761
|
+
your first session and `leg license status` shows what is left. After that
|
|
762
|
+
it needs a license: **Personal, $79 once**, one human on any number of
|
|
722
763
|
machines, every release for 12 months and the version you have keeps working
|
|
723
764
|
after that; **Team, $12 per seat per month**, Personal plus `leg share` for
|
|
724
765
|
more than one human on the board. Buy at the site, then
|
|
725
|
-
`leg license activate <key>`. There is
|
|
726
|
-
|
|
727
|
-
and ask for a refund if it does not earn its place. A key is a signed token
|
|
766
|
+
`leg license activate <key>`. There is also a **30-day money-back
|
|
767
|
+
guarantee** after buying, no reason required. A key is a signed token
|
|
728
768
|
checked offline with the public key in `src/license.mjs`; only a Team key
|
|
729
769
|
renewal talks to the site. The bare agent CLIs are never affected by any of
|
|
730
770
|
this; only what Leg adds is licensed.
|