@ucsandman/legcli 0.11.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 +142 -0
- package/README.md +95 -65
- package/bin/leg.mjs +123 -14
- package/docs/DECISIONS.md +10 -0
- package/docs/DEMO.md +20 -14
- package/docs/DEVIATIONS.md +1 -0
- package/docs/ERRORS.md +41 -0
- package/docs/ROADMAP-v2.md +50 -5
- package/docs/VOCABULARY.md +27 -0
- package/docs/board-guide.md +401 -66
- package/docs/cli-contracts.md +199 -5
- package/docs/concepts.md +167 -19
- package/docs/configuration.md +58 -0
- 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/live/claude/resume-model-probe.json +20 -0
- package/fixtures/live/claude/usage-oauth.json +87 -0
- package/fixtures/verified.json +1 -1
- package/package.json +3 -2
- package/scripts/seed-fake-cards.mjs +59 -6
- package/scripts/seed-wes-board.mjs +81 -12
- package/src/accounts.mjs +6 -1
- package/src/attach.mjs +328 -46
- package/src/audit.mjs +1 -1
- package/src/board/board.css +119 -9
- package/src/board/board.js +482 -106
- package/src/board/index.html +67 -7
- package/src/board/sessions.js +1347 -117
- 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 +546 -47
- package/src/sessions.mjs +17 -1
- package/src/taps/claude-usage.mjs +91 -2
- package/src/taps/claude.mjs +144 -5
- package/src/taps/codex.mjs +23 -3
- package/src/usage.mjs +407 -12
package/src/audit.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { readEvents as readCardEvents } from './ledger.mjs'
|
|
|
17
17
|
// the running commentary. A `status` line is commentary; a hand-off is not.
|
|
18
18
|
export const AUDITED = [
|
|
19
19
|
'handoff', 'handoff_requested', 'handed_off', 'landed', 'land', 'bounced', 'killed',
|
|
20
|
-
'approved', 'approval_needed', 'reassigned', 'paused', 'resumed', 'ended', 'done',
|
|
20
|
+
'approved', 'approval_needed', 'reassigned', 'paused', 'resumed', 'taken_over', 'ended', 'done',
|
|
21
21
|
'failed', 'trust', 'harness', 'worktree', 'station_done', 'leg_started', 'rerun',
|
|
22
22
|
]
|
|
23
23
|
|
package/src/board/board.css
CHANGED
|
@@ -205,11 +205,56 @@ code { font-family: var(--mono); font-size: 0.94em; }
|
|
|
205
205
|
.sched-status { color: var(--text-3); }
|
|
206
206
|
|
|
207
207
|
/* ---- the verdict: the largest thing on the board is a sentence ---------- */
|
|
208
|
-
|
|
208
|
+
/* 40/24 rather than 56/56, and a 17px sub rather than 21px: the verdict stays
|
|
209
|
+
the largest thing on the page and costs about 180px instead of 335, which is
|
|
210
|
+
what put the first terminal row a thousand pixels down. The 26ch measure is
|
|
211
|
+
the character budget's other half: 300 sampled sentences per length still
|
|
212
|
+
fit two 56.16px lines at 60 characters there, and sessions.js holds every
|
|
213
|
+
branch at 56. */
|
|
214
|
+
.verdict { padding-block: 40px 24px; }
|
|
209
215
|
.verdict h1 { font-size: var(--t-4); font-weight: var(--w-head); line-height: 1.08; letter-spacing: -.028em; max-width: 26ch; text-wrap: balance; }
|
|
210
|
-
.verdict p { margin-top:
|
|
216
|
+
.verdict p { margin-top: 8px; font-size: var(--t-1); line-height: 1.5; color: var(--text-2); max-width: 54ch; text-wrap: pretty; }
|
|
211
217
|
.verdict p:empty { display: none; }
|
|
212
218
|
|
|
219
|
+
/* ---- the capacity strip: one band, one token per login ------------------ */
|
|
220
|
+
/* The binding bucket and nothing else. The instrument is 120x6: enough to see
|
|
221
|
+
a login crossing its reserve at a glance, too small to compete with the
|
|
222
|
+
sentence above it. */
|
|
223
|
+
.capacity { margin-bottom: 4px; }
|
|
224
|
+
/* the strip itself never wraps: the disclosure keeps the strip's own line at
|
|
225
|
+
the right (A.2) and the tokens wrap inside their own box, so four logins
|
|
226
|
+
that need two rows of tokens cost no third line for one button. */
|
|
227
|
+
.capacity-strip { display: flex; align-items: center; gap: 12px 32px; flex-wrap: nowrap; min-height: 44px; font-size: var(--t-0); }
|
|
228
|
+
.cap-tokens { display: flex; align-items: center; gap: 12px 32px; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }
|
|
229
|
+
.cap-token { display: inline-flex; align-items: center; gap: 10px; min-height: 24px; }
|
|
230
|
+
.cap-name { font-weight: var(--w-head); color: var(--text-2); }
|
|
231
|
+
.cap-name.id-claude { color: var(--id-claude); }
|
|
232
|
+
.cap-name.id-codex { color: var(--id-codex); }
|
|
233
|
+
.cap-name.id-agy { color: var(--id-agy); }
|
|
234
|
+
.cap-name.id-grok { color: var(--id-grok); }
|
|
235
|
+
.cap-name.id-copilot { color: var(--id-copilot); }
|
|
236
|
+
.cap-name.id-fake { color: var(--id-fake); }
|
|
237
|
+
.cap-track { position: relative; flex: none; width: 120px; height: 6px; background: var(--track); border-radius: 999px; box-shadow: var(--sink); overflow: hidden; }
|
|
238
|
+
.cap-fill { position: absolute; inset: 0 auto 0 0; border-radius: 999px; background: var(--calm); }
|
|
239
|
+
.cap-figure { color: var(--text-2); white-space: nowrap; }
|
|
240
|
+
/* a login at its wall is the one place a word IS the failure, so it may carry
|
|
241
|
+
the colour; a percentage never does, the track carries that */
|
|
242
|
+
.cap-figure.is-out { color: var(--danger-text); }
|
|
243
|
+
.cap-figure--none { color: var(--text-3); }
|
|
244
|
+
.cap-open { margin-left: auto; flex: none; }
|
|
245
|
+
.capacity-drawer { margin-top: 16px; }
|
|
246
|
+
|
|
247
|
+
/* the model rail on a panel head inside the drawer: a measured percentage, or
|
|
248
|
+
a wall in words, never one printed as the other */
|
|
249
|
+
.model-rail { display: inline-flex; align-items: baseline; gap: 14px; flex-wrap: wrap; font-size: var(--t--1); color: var(--text-3); }
|
|
250
|
+
.model-chip { white-space: nowrap; }
|
|
251
|
+
.model-chip.is-out { color: var(--danger-text); }
|
|
252
|
+
/* an open model this login has a live terminal on is a control: pressing it
|
|
253
|
+
puts that rung at the top of the terminal's ladder. The one the terminal is
|
|
254
|
+
already on, and a walled one, stay text: there is nothing to press. */
|
|
255
|
+
.model-chip--pick { font-size: var(--t--1); min-height: 28px; }
|
|
256
|
+
.model-chip.is-current { color: var(--text-2); }
|
|
257
|
+
|
|
213
258
|
/* ---- panel: a raised object, not a drawn rectangle ---------------------- */
|
|
214
259
|
.panel { position: relative; background: var(--e2); border: 1px solid var(--edge); border-radius: var(--r-panel); padding: 32px; box-shadow: var(--lift); }
|
|
215
260
|
.panel--lit { box-shadow: var(--lift-hi); }
|
|
@@ -252,7 +297,7 @@ code { font-family: var(--mono); font-size: 0.94em; }
|
|
|
252
297
|
.reading-sub--lead { margin-top: 22px; }
|
|
253
298
|
|
|
254
299
|
/* ---- section heads ------------------------------------------------------ */
|
|
255
|
-
.section-head { padding-block:
|
|
300
|
+
.section-head { padding-block: 32px 16px; }
|
|
256
301
|
.section-head h2 { font-size: var(--t-2); font-weight: var(--w-head); letter-spacing: -.012em; }
|
|
257
302
|
.section-head .region-meta { margin-top: 8px; font-size: var(--t-0); color: var(--text-3); }
|
|
258
303
|
.section-head--quiet { padding-block: 56px 16px; display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
|
|
@@ -274,6 +319,28 @@ code { font-family: var(--mono); font-size: 0.94em; }
|
|
|
274
319
|
.term-clock { flex: none; display: flex; align-items: baseline; justify-content: flex-end; gap: 14px; font-size: var(--t--1); color: var(--text-3); white-space: nowrap; }
|
|
275
320
|
.term-register { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap; font-size: var(--t--1); color: var(--text-3); }
|
|
276
321
|
.term-where { color: var(--text-2); }
|
|
322
|
+
/* the register's data tokens (A.4 rows 7 to 9). They are one line with the
|
|
323
|
+
status word, in the register's own muted tone: what changed and how long it
|
|
324
|
+
has been quiet are observations, not demands. The model token is the one
|
|
325
|
+
fact here a reader looks for by name, so it takes the identity colour and
|
|
326
|
+
the mono face the other machine strings on this page use. */
|
|
327
|
+
.term-dirty, .term-ahead, .term-quiet { color: var(--text-3); }
|
|
328
|
+
.term-model { font-family: var(--mono); }
|
|
329
|
+
/* the files line and the row's capacity phrase share one line, the phrase at
|
|
330
|
+
the right (A.2). The phrase is per MODEL, which is a fact about this row;
|
|
331
|
+
the region head carries the share clause for the login. */
|
|
332
|
+
.term-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 8px 24px; flex-wrap: wrap; }
|
|
333
|
+
.term-meta .files { flex: 1 1 24ch; min-width: 0; }
|
|
334
|
+
/* the phrase wraps rather than pushing the row sideways: with a forecast it is
|
|
335
|
+
`about 2h 40m of fable left, from 9 samples over 4h`, two and a half times
|
|
336
|
+
the percentage form, and a nowrap run that long overflows the 400px board. */
|
|
337
|
+
.term-capacity { margin-top: 10px; flex: none; display: inline-flex; align-items: baseline; gap: 10px; font-size: var(--t--1); color: var(--text-3); }
|
|
338
|
+
/* the climb is a link beside the figure that explains it, never a fifth button
|
|
339
|
+
in the 2x2 grid, which is the shipped shape */
|
|
340
|
+
.term-climb { font-size: var(--t--1); }
|
|
341
|
+
/* the keyboard ring: a row the keys act on, marked on the edge the eye scans
|
|
342
|
+
down rather than by a fill that would compete with is-urgent */
|
|
343
|
+
.term.is-focused { box-shadow: inset 3px 0 0 var(--focus); }
|
|
277
344
|
.term-when { color: var(--text-3); min-width: 6ch; text-align: right; }
|
|
278
345
|
.term-id { color: var(--text-3); min-width: 4ch; text-align: right; font-family: var(--mono); }
|
|
279
346
|
.term-actions { flex: none; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; width: 272px; }
|
|
@@ -502,8 +569,26 @@ code { font-family: var(--mono); font-size: 0.94em; }
|
|
|
502
569
|
/* ---- chain rail, order rows, confirm ------------------------------------ */
|
|
503
570
|
.chain-rail { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: var(--t-0); color: var(--text-3); }
|
|
504
571
|
.cap-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; font-size: var(--t-0); color: var(--text-3); margin-top: 8px; }
|
|
505
|
-
|
|
506
|
-
.
|
|
572
|
+
/* the ladder editor: one rung per row, numbered, with its when, its cost word
|
|
573
|
+
and three buttons. The name takes the free space so every row's controls line
|
|
574
|
+
up in one column whatever the rung is called. */
|
|
575
|
+
.ladder-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; font-size: var(--t-0); flex-wrap: wrap; }
|
|
576
|
+
.ladder-num { flex: none; color: var(--text-3); font-variant-numeric: tabular-nums; }
|
|
577
|
+
.ladder-name { flex: 1 1 12ch; min-width: 10ch; }
|
|
578
|
+
.ladder-rule { flex: 0 0 auto; width: 22ch; display: flex; align-items: center; gap: 8px; }
|
|
579
|
+
.ladder-cost { flex: none; font-size: var(--t--1); color: var(--text-3); }
|
|
580
|
+
/* the cost word is a column of its own so the three buttons line up down the
|
|
581
|
+
list whatever a rung costs */
|
|
582
|
+
.ladder-costcol { width: 12ch; }
|
|
583
|
+
.ladder-when { flex: none; min-height: 36px; font-size: var(--t--1); }
|
|
584
|
+
.ladder-pct { flex: none; width: 7ch; min-height: 36px; font-size: var(--t--1); }
|
|
585
|
+
/* a stored 0 or 100 is shown as it is and says what it does: the editor never
|
|
586
|
+
rewrites a number nobody chose. It wraps the rule cell rather than widening
|
|
587
|
+
it, so the columns beside it do not move. */
|
|
588
|
+
.ladder-flag { flex: 1 0 100%; font-size: var(--t--1); }
|
|
589
|
+
.ladder-add { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 0 2px; font-size: var(--t-0); border-top: 1px solid var(--line); }
|
|
590
|
+
.ladder-policy { display: grid; gap: 8px; margin-top: 18px; border: 0; padding: 0; }
|
|
591
|
+
.ladder-policy legend { font-size: var(--t-0); font-weight: var(--w-head); color: var(--text-2); padding: 0; }
|
|
507
592
|
/* the label on each agent row in the New card dialog */
|
|
508
593
|
.fallback-row-title { font-size: var(--t-0); font-weight: var(--w-head); color: var(--text-2); }
|
|
509
594
|
.confirm-row { margin-top: 12px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; background: var(--e4); border-radius: var(--r-control); padding: 12px 14px; font-size: var(--t-0); }
|
|
@@ -537,6 +622,22 @@ input[type="text"], input[type="password"], textarea, select { font: inherit; fo
|
|
|
537
622
|
input[type="checkbox"] { accent-color: var(--accent); width: 16px; height: 16px; }
|
|
538
623
|
fieldset { border: 1px solid var(--edge); border-radius: var(--r-well); padding: 18px; display: grid; gap: 12px; }
|
|
539
624
|
legend { font-size: var(--t-0); font-weight: var(--w-head); color: var(--text-2); padding-inline: 6px; }
|
|
625
|
+
|
|
626
|
+
/* the two notification toggles (spec E). A checkbox and its sentence on one
|
|
627
|
+
line, with the help text under it in the field colour the rest of Settings
|
|
628
|
+
uses; a disabled toggle carries its reason in that help text, never in a
|
|
629
|
+
tooltip, because the reason is the whole point of the disabled state. */
|
|
630
|
+
.notify .field-help { margin-top: -4px; }
|
|
631
|
+
.notify-toggle { display: flex; align-items: baseline; gap: 10px; font-size: var(--t-0); font-weight: var(--w-head); color: var(--text-2); }
|
|
632
|
+
.notify-toggle input[disabled] { cursor: not-allowed; }
|
|
633
|
+
.notify-toggle:has(input[disabled]) { color: var(--text-3); }
|
|
634
|
+
|
|
635
|
+
/* the keyboard map: a small panel over the page, opened by ? and closed by ?
|
|
636
|
+
or Escape. Every row names a key and the button that key presses. */
|
|
637
|
+
.keymap { position: fixed; right: 24px; bottom: 24px; z-index: 20; max-width: 380px; background: var(--e2); border: 1px solid var(--edge); border-radius: var(--r-panel); box-shadow: var(--lift-hi); padding: 22px; }
|
|
638
|
+
.keymap-list { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; margin-bottom: 18px; font-size: var(--t-0); }
|
|
639
|
+
.keymap-key { font-family: var(--mono); color: var(--text); white-space: nowrap; }
|
|
640
|
+
.keymap-what { color: var(--text-2); }
|
|
540
641
|
.form-row { display: grid; gap: 8px; margin-top: 18px; }
|
|
541
642
|
.form-error { background: var(--e1); border-left: 3px solid var(--danger); border-radius: var(--r-control); padding: 12px 14px; font-size: var(--t-0); color: var(--danger-text); }
|
|
542
643
|
.advanced-options { margin-top: 22px; }
|
|
@@ -565,9 +666,18 @@ dialog::backdrop { background: rgba(0,0,0,.6); }
|
|
|
565
666
|
=========================================================================== */
|
|
566
667
|
@media (max-width: 760px) {
|
|
567
668
|
.wrap { --t-4: 2rem; --t-3: 1.75rem; --t-2: 1.1875rem; --t--1: 0.9375rem; }
|
|
568
|
-
.verdict { padding-block:
|
|
669
|
+
.verdict { padding-block: 32px 20px; }
|
|
569
670
|
.verdict h1 { max-width: none; letter-spacing: -.02em; }
|
|
570
|
-
.verdict p { margin-top:
|
|
671
|
+
.verdict p { margin-top: 12px; }
|
|
672
|
+
/* the tracks go and the tokens stack, one login per 24px line: at this width
|
|
673
|
+
120px of instrument costs the percentage its own line, and the percentage
|
|
674
|
+
with its state word carries the whole fact on its own */
|
|
675
|
+
.capacity-strip { display: block; }
|
|
676
|
+
.cap-tokens { display: block; }
|
|
677
|
+
.cap-token { display: flex; min-height: 24px; gap: 8px; }
|
|
678
|
+
.cap-track { display: none; }
|
|
679
|
+
.cap-figure { margin-left: auto; }
|
|
680
|
+
.cap-open { margin-left: 0; margin-top: 12px; }
|
|
571
681
|
.panel { padding: 22px; border-radius: 14px; }
|
|
572
682
|
.logins-pair { grid-template-columns: 1fr; }
|
|
573
683
|
.gauge { gap: 14px; flex-wrap: wrap; }
|
|
@@ -579,8 +689,8 @@ dialog::backdrop { background: rgba(0,0,0,.6); }
|
|
|
579
689
|
.gauge--noread::after { width: 68px; }
|
|
580
690
|
.gauge-block + .gauge-block { margin-top: 22px; }
|
|
581
691
|
code { font-size: 1em; }
|
|
582
|
-
.section-head { padding-block:
|
|
583
|
-
.section-head--quiet { padding-block:
|
|
692
|
+
.section-head { padding-block: 24px 16px; }
|
|
693
|
+
.section-head--quiet { padding-block: 24px 16px; }
|
|
584
694
|
.term { padding: 22px; }
|
|
585
695
|
.term-row { flex-direction: column; align-items: stretch; gap: 14px; }
|
|
586
696
|
.term-clock { order: -1; width: auto; justify-content: flex-end; gap: 14px; }
|