@sleds/cli 0.1.18 → 0.1.20
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/dist/sleds.mjs +604 -107
- package/package.json +1 -1
package/dist/sleds.mjs
CHANGED
|
@@ -152,7 +152,24 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
|
|
|
152
152
|
if (!list.some(isSledsSpecHook))
|
|
153
153
|
return { ...settings };
|
|
154
154
|
return withEventHooks(settings, POST_TOOL_EVENT, list.filter((e) => !isSledsSpecHook(e)));
|
|
155
|
-
}, hasSpecHook = (settings) => eventHooks(settings, POST_TOOL_EVENT).some(isSledsSpecHook),
|
|
155
|
+
}, hasSpecHook = (settings) => eventHooks(settings, POST_TOOL_EVENT).some(isSledsSpecHook), PROMPT_EVENT = "UserPromptSubmit", PROMPT_EVENT_TIMEOUT_S = 3, isSledsPromptHook = (entry) => {
|
|
156
|
+
if (!isObject(entry) || !Array.isArray(entry.hooks))
|
|
157
|
+
return false;
|
|
158
|
+
return entry.hooks.some((h) => isObject(h) && h.type === "command" && typeof h.command === "string" && /\bprompt-event\b/.test(h.command));
|
|
159
|
+
}, promptHookEntry = (command) => ({
|
|
160
|
+
hooks: [{ type: "command", command, timeout: PROMPT_EVENT_TIMEOUT_S }]
|
|
161
|
+
}), applyPromptHook = (settings, command) => {
|
|
162
|
+
const list = eventHooks(settings, PROMPT_EVENT);
|
|
163
|
+
const idx = list.findIndex(isSledsPromptHook);
|
|
164
|
+
const entry = promptHookEntry(command);
|
|
165
|
+
const next = idx === -1 ? [...list, entry] : list.map((e, i) => i === idx ? entry : e);
|
|
166
|
+
return withEventHooks(settings, PROMPT_EVENT, next);
|
|
167
|
+
}, removePromptHook = (settings) => {
|
|
168
|
+
const list = eventHooks(settings, PROMPT_EVENT);
|
|
169
|
+
if (!list.some(isSledsPromptHook))
|
|
170
|
+
return { ...settings };
|
|
171
|
+
return withEventHooks(settings, PROMPT_EVENT, list.filter((e) => !isSledsPromptHook(e)));
|
|
172
|
+
}, hasPromptHook = (settings) => eventHooks(settings, PROMPT_EVENT).some(isSledsPromptHook), isSeatToken = (token) => /^sled_[A-Za-z0-9_-]+$/.test(token), maskToken = (token) => token.length <= 9 ? `${token.slice(0, 5)}…` : `${token.slice(0, 9)}…`, serialize = (settings) => `${JSON.stringify(settings, null, 2)}
|
|
156
173
|
`, hasClaudeCode = (home) => existsSync(claudeDir(home)), readSettings = (home) => {
|
|
157
174
|
const p = settingsPath(home);
|
|
158
175
|
if (!existsSync(p))
|
|
@@ -537,7 +554,6 @@ __export(exports_seat, {
|
|
|
537
554
|
scanTranscriptForCredits: () => scanTranscriptForCredits2,
|
|
538
555
|
seatSessionFromStdin: () => seatSessionFromStdin2,
|
|
539
556
|
seatStatePath: () => seatStatePath2,
|
|
540
|
-
seatWallBanner: () => seatWallBanner2,
|
|
541
557
|
seatWallDue: () => seatWallDue,
|
|
542
558
|
seatWallEvents: () => seatWallEvents2,
|
|
543
559
|
seatWallLine: () => seatWallLine2,
|
|
@@ -618,7 +634,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
618
634
|
} catch {
|
|
619
635
|
return null;
|
|
620
636
|
}
|
|
621
|
-
}, formatSeatFragment2 = (w) => {
|
|
637
|
+
}, formatSeatFragment2 = (w, now = Date.now()) => {
|
|
622
638
|
if (!w)
|
|
623
639
|
return null;
|
|
624
640
|
const parts = [];
|
|
@@ -630,6 +646,17 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
630
646
|
parts.push(`spend ${Math.round(w.spendLimit.usedPct)}%`);
|
|
631
647
|
if (parts.length === 0)
|
|
632
648
|
return null;
|
|
649
|
+
const hot = [
|
|
650
|
+
["wk", w.sevenDay],
|
|
651
|
+
["5h", w.fiveHour],
|
|
652
|
+
["spend", w.spendLimit]
|
|
653
|
+
].filter((x) => x[1] !== undefined && x[1].usedPct >= APPROACHING_PCT2).sort((a, b) => b[1].usedPct - a[1].usedPct)[0];
|
|
654
|
+
if (hot) {
|
|
655
|
+
const mins = Math.max(0, Math.round((hot[1].resetsAt - now) / 60000));
|
|
656
|
+
const reset = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? `${mins % 60}m` : ""}` : `${mins}m`;
|
|
657
|
+
const verb = hot[1].usedPct >= 100 ? "at the wall" : `${Math.round(hot[1].usedPct)}%`;
|
|
658
|
+
return `⚠ seat ${hot[0]} ${verb}${SEP}resets ${reset}${SEP}/sled handoff · sleds defer`;
|
|
659
|
+
}
|
|
633
660
|
return `seat ${parts.join(SEP)}`;
|
|
634
661
|
}, APPROACHING_PCT2 = 90, seatStatePath2 = (home) => join14(configDir(home), "seat.json"), parseWindowStored2 = (v) => {
|
|
635
662
|
if (!isObject11(v))
|
|
@@ -681,6 +708,23 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
681
708
|
}
|
|
682
709
|
out.warned = warned;
|
|
683
710
|
}
|
|
711
|
+
if (isObject11(raw.told)) {
|
|
712
|
+
const told = {};
|
|
713
|
+
for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
|
|
714
|
+
const w = raw.told[bar];
|
|
715
|
+
if (!isObject11(w))
|
|
716
|
+
continue;
|
|
717
|
+
const entry = {};
|
|
718
|
+
const ap = num6(w.approaching);
|
|
719
|
+
const hit = num6(w.hit);
|
|
720
|
+
if (ap !== undefined)
|
|
721
|
+
entry.approaching = ap;
|
|
722
|
+
if (hit !== undefined)
|
|
723
|
+
entry.hit = hit;
|
|
724
|
+
told[bar] = entry;
|
|
725
|
+
}
|
|
726
|
+
out.told = told;
|
|
727
|
+
}
|
|
684
728
|
if (isObject11(raw.transcriptScan) && typeof raw.transcriptScan.path === "string") {
|
|
685
729
|
const offset = num6(raw.transcriptScan.offset);
|
|
686
730
|
const hitAt = num6(raw.transcriptScan.hitAt);
|
|
@@ -693,6 +737,43 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
693
737
|
const session = parseSeatSession2(raw.session);
|
|
694
738
|
if (session)
|
|
695
739
|
out.session = session;
|
|
740
|
+
if (isObject11(raw.cockpit) && raw.cockpit.kind === "cmux") {
|
|
741
|
+
const c = { kind: "cmux" };
|
|
742
|
+
if (isObject11(raw.cockpit.lines)) {
|
|
743
|
+
const lines = {};
|
|
744
|
+
for (const [k, v] of Object.entries(raw.cockpit.lines))
|
|
745
|
+
if (typeof v === "string")
|
|
746
|
+
lines[k] = v;
|
|
747
|
+
c.lines = lines;
|
|
748
|
+
}
|
|
749
|
+
if (raw.cockpit.progress === true)
|
|
750
|
+
c.progress = true;
|
|
751
|
+
const f = raw.cockpit.feed;
|
|
752
|
+
if (isObject11(f) && num6(f.at) !== undefined) {
|
|
753
|
+
const strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
754
|
+
const j = isObject11(f.jobs) ? f.jobs : {};
|
|
755
|
+
c.feed = {
|
|
756
|
+
at: num6(f.at),
|
|
757
|
+
handoffs: strs(f.handoffs),
|
|
758
|
+
jobs: {
|
|
759
|
+
scheduled: num6(j.scheduled) ?? 0,
|
|
760
|
+
running: num6(j.running) ?? 0,
|
|
761
|
+
delivered: num6(j.delivered) ?? 0,
|
|
762
|
+
failed: num6(j.failed) ?? 0
|
|
763
|
+
},
|
|
764
|
+
delivered: strs(f.delivered),
|
|
765
|
+
failed: strs(f.failed)
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
out.cockpit = c;
|
|
769
|
+
}
|
|
770
|
+
if (isObject11(raw.estimate)) {
|
|
771
|
+
const e = raw.estimate;
|
|
772
|
+
const project = typeof e.project === "string" ? e.project : undefined;
|
|
773
|
+
const [lo, hi, lm, hm, at] = [e.lowUsd, e.highUsd, e.lowMin, e.highMin, e.at].map(num6);
|
|
774
|
+
if (project && lo !== undefined && hi !== undefined && lm !== undefined && hm !== undefined && at !== undefined)
|
|
775
|
+
out.estimate = { project, lowUsd: lo, highUsd: hi, lowMin: lm, highMin: hm, at };
|
|
776
|
+
}
|
|
696
777
|
return out;
|
|
697
778
|
}, parseSeatSession2 = (v) => {
|
|
698
779
|
if (!isObject11(v) || typeof v.id !== "string" || !v.id)
|
|
@@ -852,21 +933,6 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
852
933
|
} catch {
|
|
853
934
|
return { hit: false, next: prior && prior.path === path ? prior : { path, offset: 0 } };
|
|
854
935
|
}
|
|
855
|
-
}, seatWallBanner2 = (e, hasUpstreamKey, now) => {
|
|
856
|
-
const label = {
|
|
857
|
-
fiveHour: "5-hour",
|
|
858
|
-
sevenDay: "weekly",
|
|
859
|
-
spendLimit: "spend",
|
|
860
|
-
credits: "usage credits"
|
|
861
|
-
}[e.bar];
|
|
862
|
-
const mins = Math.max(1, Math.round((e.resetsAt - now) / 60000));
|
|
863
|
-
const reset = e.bar === "credits" ? "" : mins < 60 ? ` · resets in ${mins} min` : ` · resets in ${Math.floor(mins / 60)} h ${mins % 60 ? `${mins % 60} min` : ""}`.trimEnd();
|
|
864
|
-
const exits = hasUpstreamKey ? "/sled handoff · sleds defer · sleds on" : "/sled handoff · sleds defer (no platform key here)";
|
|
865
|
-
if (e.stage === "approaching")
|
|
866
|
-
return `Seat ${label} at ${Math.round(e.usedPct)}%${reset}. Plan the exit now: ${exits}`;
|
|
867
|
-
if (e.bar === "credits")
|
|
868
|
-
return `Out of usage credits. ${exits}`;
|
|
869
|
-
return `Seat ${label} limit hit${reset}. ${exits}`;
|
|
870
936
|
}, seatWindowBody2 = (w) => ({
|
|
871
937
|
capturedAt: w.capturedAt,
|
|
872
938
|
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
@@ -1364,7 +1430,7 @@ var seatWindowFromStdin = (text, capturedAt) => {
|
|
|
1364
1430
|
return null;
|
|
1365
1431
|
}
|
|
1366
1432
|
};
|
|
1367
|
-
var formatSeatFragment = (w) => {
|
|
1433
|
+
var formatSeatFragment = (w, now = Date.now()) => {
|
|
1368
1434
|
if (!w)
|
|
1369
1435
|
return null;
|
|
1370
1436
|
const parts = [];
|
|
@@ -1376,6 +1442,17 @@ var formatSeatFragment = (w) => {
|
|
|
1376
1442
|
parts.push(`spend ${Math.round(w.spendLimit.usedPct)}%`);
|
|
1377
1443
|
if (parts.length === 0)
|
|
1378
1444
|
return null;
|
|
1445
|
+
const hot = [
|
|
1446
|
+
["wk", w.sevenDay],
|
|
1447
|
+
["5h", w.fiveHour],
|
|
1448
|
+
["spend", w.spendLimit]
|
|
1449
|
+
].filter((x) => x[1] !== undefined && x[1].usedPct >= APPROACHING_PCT).sort((a, b) => b[1].usedPct - a[1].usedPct)[0];
|
|
1450
|
+
if (hot) {
|
|
1451
|
+
const mins = Math.max(0, Math.round((hot[1].resetsAt - now) / 60000));
|
|
1452
|
+
const reset = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? `${mins % 60}m` : ""}` : `${mins}m`;
|
|
1453
|
+
const verb = hot[1].usedPct >= 100 ? "at the wall" : `${Math.round(hot[1].usedPct)}%`;
|
|
1454
|
+
return `⚠ seat ${hot[0]} ${verb}${SEP}resets ${reset}${SEP}/sled handoff · sleds defer`;
|
|
1455
|
+
}
|
|
1379
1456
|
return `seat ${parts.join(SEP)}`;
|
|
1380
1457
|
};
|
|
1381
1458
|
var APPROACHING_PCT = 90;
|
|
@@ -1431,6 +1508,23 @@ var parseSeatState = (raw) => {
|
|
|
1431
1508
|
}
|
|
1432
1509
|
out.warned = warned;
|
|
1433
1510
|
}
|
|
1511
|
+
if (isObject5(raw.told)) {
|
|
1512
|
+
const told = {};
|
|
1513
|
+
for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
|
|
1514
|
+
const w = raw.told[bar];
|
|
1515
|
+
if (!isObject5(w))
|
|
1516
|
+
continue;
|
|
1517
|
+
const entry = {};
|
|
1518
|
+
const ap = num3(w.approaching);
|
|
1519
|
+
const hit = num3(w.hit);
|
|
1520
|
+
if (ap !== undefined)
|
|
1521
|
+
entry.approaching = ap;
|
|
1522
|
+
if (hit !== undefined)
|
|
1523
|
+
entry.hit = hit;
|
|
1524
|
+
told[bar] = entry;
|
|
1525
|
+
}
|
|
1526
|
+
out.told = told;
|
|
1527
|
+
}
|
|
1434
1528
|
if (isObject5(raw.transcriptScan) && typeof raw.transcriptScan.path === "string") {
|
|
1435
1529
|
const offset = num3(raw.transcriptScan.offset);
|
|
1436
1530
|
const hitAt = num3(raw.transcriptScan.hitAt);
|
|
@@ -1443,6 +1537,43 @@ var parseSeatState = (raw) => {
|
|
|
1443
1537
|
const session = parseSeatSession(raw.session);
|
|
1444
1538
|
if (session)
|
|
1445
1539
|
out.session = session;
|
|
1540
|
+
if (isObject5(raw.cockpit) && raw.cockpit.kind === "cmux") {
|
|
1541
|
+
const c = { kind: "cmux" };
|
|
1542
|
+
if (isObject5(raw.cockpit.lines)) {
|
|
1543
|
+
const lines = {};
|
|
1544
|
+
for (const [k, v] of Object.entries(raw.cockpit.lines))
|
|
1545
|
+
if (typeof v === "string")
|
|
1546
|
+
lines[k] = v;
|
|
1547
|
+
c.lines = lines;
|
|
1548
|
+
}
|
|
1549
|
+
if (raw.cockpit.progress === true)
|
|
1550
|
+
c.progress = true;
|
|
1551
|
+
const f = raw.cockpit.feed;
|
|
1552
|
+
if (isObject5(f) && num3(f.at) !== undefined) {
|
|
1553
|
+
const strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
1554
|
+
const j = isObject5(f.jobs) ? f.jobs : {};
|
|
1555
|
+
c.feed = {
|
|
1556
|
+
at: num3(f.at),
|
|
1557
|
+
handoffs: strs(f.handoffs),
|
|
1558
|
+
jobs: {
|
|
1559
|
+
scheduled: num3(j.scheduled) ?? 0,
|
|
1560
|
+
running: num3(j.running) ?? 0,
|
|
1561
|
+
delivered: num3(j.delivered) ?? 0,
|
|
1562
|
+
failed: num3(j.failed) ?? 0
|
|
1563
|
+
},
|
|
1564
|
+
delivered: strs(f.delivered),
|
|
1565
|
+
failed: strs(f.failed)
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
out.cockpit = c;
|
|
1569
|
+
}
|
|
1570
|
+
if (isObject5(raw.estimate)) {
|
|
1571
|
+
const e = raw.estimate;
|
|
1572
|
+
const project = typeof e.project === "string" ? e.project : undefined;
|
|
1573
|
+
const [lo, hi, lm, hm, at] = [e.lowUsd, e.highUsd, e.lowMin, e.highMin, e.at].map(num3);
|
|
1574
|
+
if (project && lo !== undefined && hi !== undefined && lm !== undefined && hm !== undefined && at !== undefined)
|
|
1575
|
+
out.estimate = { project, lowUsd: lo, highUsd: hi, lowMin: lm, highMin: hm, at };
|
|
1576
|
+
}
|
|
1446
1577
|
return out;
|
|
1447
1578
|
};
|
|
1448
1579
|
var parseSeatSession = (v) => {
|
|
@@ -1613,22 +1744,6 @@ var scanTranscriptForCredits = (path, prior) => {
|
|
|
1613
1744
|
return { hit: false, next: prior && prior.path === path ? prior : { path, offset: 0 } };
|
|
1614
1745
|
}
|
|
1615
1746
|
};
|
|
1616
|
-
var seatWallBanner = (e, hasUpstreamKey, now) => {
|
|
1617
|
-
const label = {
|
|
1618
|
-
fiveHour: "5-hour",
|
|
1619
|
-
sevenDay: "weekly",
|
|
1620
|
-
spendLimit: "spend",
|
|
1621
|
-
credits: "usage credits"
|
|
1622
|
-
}[e.bar];
|
|
1623
|
-
const mins = Math.max(1, Math.round((e.resetsAt - now) / 60000));
|
|
1624
|
-
const reset = e.bar === "credits" ? "" : mins < 60 ? ` · resets in ${mins} min` : ` · resets in ${Math.floor(mins / 60)} h ${mins % 60 ? `${mins % 60} min` : ""}`.trimEnd();
|
|
1625
|
-
const exits = hasUpstreamKey ? "/sled handoff · sleds defer · sleds on" : "/sled handoff · sleds defer (no platform key here)";
|
|
1626
|
-
if (e.stage === "approaching")
|
|
1627
|
-
return `Seat ${label} at ${Math.round(e.usedPct)}%${reset}. Plan the exit now: ${exits}`;
|
|
1628
|
-
if (e.bar === "credits")
|
|
1629
|
-
return `Out of usage credits. ${exits}`;
|
|
1630
|
-
return `Seat ${label} limit hit${reset}. ${exits}`;
|
|
1631
|
-
};
|
|
1632
1747
|
var seatWindowBody = (w) => ({
|
|
1633
1748
|
capturedAt: w.capturedAt,
|
|
1634
1749
|
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
@@ -1836,11 +1951,281 @@ var defer = async (o) => {
|
|
|
1836
1951
|
};
|
|
1837
1952
|
|
|
1838
1953
|
// src/commands/doctor.ts
|
|
1839
|
-
init_config();
|
|
1840
|
-
init_meter();
|
|
1841
1954
|
import { execFileSync } from "node:child_process";
|
|
1842
1955
|
import { existsSync as existsSync8, lstatSync, readlinkSync } from "node:fs";
|
|
1843
1956
|
import { resolve as resolve3 } from "node:path";
|
|
1957
|
+
|
|
1958
|
+
// src/channels/cockpit.ts
|
|
1959
|
+
var detectCockpit = (env) => {
|
|
1960
|
+
if (env.CMUX_SOCKET_PATH || env.CMUX_WORKSPACE_ID || env.CMUX_SURFACE_ID) {
|
|
1961
|
+
const c = { kind: "cmux" };
|
|
1962
|
+
if (env.CMUX_WORKSPACE_ID)
|
|
1963
|
+
c.workspaceId = env.CMUX_WORKSPACE_ID;
|
|
1964
|
+
if (env.CMUX_SURFACE_ID)
|
|
1965
|
+
c.surfaceId = env.CMUX_SURFACE_ID;
|
|
1966
|
+
if (env.CMUX_SOCKET_PATH)
|
|
1967
|
+
c.socket = env.CMUX_SOCKET_PATH;
|
|
1968
|
+
return c;
|
|
1969
|
+
}
|
|
1970
|
+
return null;
|
|
1971
|
+
};
|
|
1972
|
+
var SEP2 = " · ";
|
|
1973
|
+
var COLOR = {
|
|
1974
|
+
ok: "#6b7280",
|
|
1975
|
+
approaching: "#d97706",
|
|
1976
|
+
wall: "#dc2626"
|
|
1977
|
+
};
|
|
1978
|
+
var PRIORITY = { ok: 20, approaching: 90, wall: 100 };
|
|
1979
|
+
var BAR_LABEL = {
|
|
1980
|
+
sevenDay: "wk",
|
|
1981
|
+
fiveHour: "5h",
|
|
1982
|
+
spendLimit: "spend",
|
|
1983
|
+
credits: "credits"
|
|
1984
|
+
};
|
|
1985
|
+
var EXITS = "/sled handoff · sleds defer · or continue on the workspace key";
|
|
1986
|
+
var resetIn = (resetsAt, now) => {
|
|
1987
|
+
const mins = Math.max(0, Math.round((resetsAt - now) / 60000));
|
|
1988
|
+
return mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? `${mins % 60}m` : ""}` : `${mins}m`;
|
|
1989
|
+
};
|
|
1990
|
+
var cockpitSeatLine = (w, now = Date.now()) => {
|
|
1991
|
+
if (!w)
|
|
1992
|
+
return null;
|
|
1993
|
+
const bars = [
|
|
1994
|
+
["wk", w.sevenDay],
|
|
1995
|
+
["5h", w.fiveHour],
|
|
1996
|
+
["spend", w.spendLimit]
|
|
1997
|
+
].filter((x) => x[1] !== undefined);
|
|
1998
|
+
if (bars.length === 0)
|
|
1999
|
+
return null;
|
|
2000
|
+
const hot = bars.filter(([, b]) => b.usedPct >= APPROACHING_PCT).sort((a, b) => b[1].usedPct - a[1].usedPct)[0];
|
|
2001
|
+
if (!hot) {
|
|
2002
|
+
return { text: bars.map(([n, b]) => `${n} ${Math.round(b.usedPct)}%`).join(SEP2), stage: "ok" };
|
|
2003
|
+
}
|
|
2004
|
+
const [name, bar] = hot;
|
|
2005
|
+
const atWall = bar.usedPct >= 100;
|
|
2006
|
+
return {
|
|
2007
|
+
text: `${atWall ? "" : "⚠ "}${name} ${atWall ? "at the wall" : `${Math.round(bar.usedPct)}%`}${SEP2}resets ${resetIn(bar.resetsAt, now)}`,
|
|
2008
|
+
stage: atWall ? "wall" : "approaching",
|
|
2009
|
+
progress: { value: Math.min(1, bar.usedPct / 100), label: `seat ${name}` }
|
|
2010
|
+
};
|
|
2011
|
+
};
|
|
2012
|
+
var ws = (c) => c.workspaceId ? ["--workspace", c.workspaceId] : [];
|
|
2013
|
+
var keyed = (argv, c, key, text, prior, style, next) => {
|
|
2014
|
+
if (text) {
|
|
2015
|
+
next[key] = text;
|
|
2016
|
+
if (text !== prior)
|
|
2017
|
+
argv.push([
|
|
2018
|
+
"set-status",
|
|
2019
|
+
`sleds.${key}`,
|
|
2020
|
+
text,
|
|
2021
|
+
"--icon",
|
|
2022
|
+
style.icon,
|
|
2023
|
+
...style.color ? ["--color", style.color] : [],
|
|
2024
|
+
"--priority",
|
|
2025
|
+
String(style.priority),
|
|
2026
|
+
...ws(c)
|
|
2027
|
+
]);
|
|
2028
|
+
} else if (prior) {
|
|
2029
|
+
argv.push(["clear-status", `sleds.${key}`, ...ws(c)]);
|
|
2030
|
+
}
|
|
2031
|
+
};
|
|
2032
|
+
var ESTIMATE_TTL_MS = 2 * 60 * 60 * 1000;
|
|
2033
|
+
var usd2 = (n) => n < 10 ? `$${n.toFixed(2)}` : `$${Math.round(n)}`;
|
|
2034
|
+
var hrs = (min) => min < 60 ? `${Math.round(min)}m` : `${(min / 60).toFixed(min < 600 ? 1 : 0)}h`;
|
|
2035
|
+
var cockpitEstimateLine = (e, now) => {
|
|
2036
|
+
if (!e || now - e.at > ESTIMATE_TTL_MS)
|
|
2037
|
+
return null;
|
|
2038
|
+
const proj = e.project.length > 28 ? `…${e.project.slice(-27)}` : e.project;
|
|
2039
|
+
return `est ${usd2(e.lowUsd)}–${usd2(e.highUsd)}${SEP2}${hrs(e.lowMin)}–${hrs(e.highMin)}${SEP2}${proj}`;
|
|
2040
|
+
};
|
|
2041
|
+
var cockpitFeedLines = (f) => {
|
|
2042
|
+
if (!f)
|
|
2043
|
+
return { handoff: null, jobs: null };
|
|
2044
|
+
const n = f.handoffs.length;
|
|
2045
|
+
const handoff = n ? `${n} hand-off${n === 1 ? "" : "s"} waiting${SEP2}/sled pickup` : null;
|
|
2046
|
+
const parts = [];
|
|
2047
|
+
if (f.jobs.running)
|
|
2048
|
+
parts.push(`${f.jobs.running} running`);
|
|
2049
|
+
if (f.jobs.scheduled)
|
|
2050
|
+
parts.push(`${f.jobs.scheduled} scheduled`);
|
|
2051
|
+
if (f.jobs.delivered)
|
|
2052
|
+
parts.push(`${f.jobs.delivered} done`);
|
|
2053
|
+
if (f.jobs.failed)
|
|
2054
|
+
parts.push(`${f.jobs.failed} failed`);
|
|
2055
|
+
return { handoff, jobs: parts.length ? `overnight: ${parts.join(SEP2)}` : null };
|
|
2056
|
+
};
|
|
2057
|
+
var renderCockpit = (cockpit, window, events, prior, now = Date.now(), extra = {}) => {
|
|
2058
|
+
const argv = [];
|
|
2059
|
+
const priorLines = prior?.lines ?? {};
|
|
2060
|
+
const lines = {};
|
|
2061
|
+
const state = { kind: cockpit.kind };
|
|
2062
|
+
const seat = cockpitSeatLine(window, now);
|
|
2063
|
+
keyed(argv, cockpit, "seat", seat?.text ?? null, priorLines.seat, {
|
|
2064
|
+
icon: "⛷",
|
|
2065
|
+
color: COLOR[seat?.stage ?? "ok"],
|
|
2066
|
+
priority: PRIORITY[seat?.stage ?? "ok"]
|
|
2067
|
+
}, lines);
|
|
2068
|
+
if (seat?.progress) {
|
|
2069
|
+
state.progress = true;
|
|
2070
|
+
argv.push([
|
|
2071
|
+
"set-progress",
|
|
2072
|
+
seat.progress.value.toFixed(2),
|
|
2073
|
+
"--label",
|
|
2074
|
+
seat.progress.label,
|
|
2075
|
+
...ws(cockpit)
|
|
2076
|
+
]);
|
|
2077
|
+
} else if (prior?.progress) {
|
|
2078
|
+
argv.push(["clear-progress", ...ws(cockpit)]);
|
|
2079
|
+
}
|
|
2080
|
+
keyed(argv, cockpit, "estimate", cockpitEstimateLine(extra.estimate, now), priorLines.estimate, { icon: "≈", priority: 30 }, lines);
|
|
2081
|
+
const feed = extra.feed ?? prior?.feed;
|
|
2082
|
+
const fl = cockpitFeedLines(feed);
|
|
2083
|
+
keyed(argv, cockpit, "handoff", fl.handoff, priorLines.handoff, { icon: "⇄", color: "#2563eb", priority: 60 }, lines);
|
|
2084
|
+
keyed(argv, cockpit, "jobs", fl.jobs, priorLines.jobs, { icon: "☾", priority: 25 }, lines);
|
|
2085
|
+
if (Object.keys(lines).length)
|
|
2086
|
+
state.lines = lines;
|
|
2087
|
+
if (feed)
|
|
2088
|
+
state.feed = feed;
|
|
2089
|
+
for (const e of events) {
|
|
2090
|
+
const bar = BAR_LABEL[e.bar];
|
|
2091
|
+
if (e.stage === "hit") {
|
|
2092
|
+
const sub = e.bar === "credits" ? "usage credits exhausted" : `resets ${resetIn(e.resetsAt, now)}`;
|
|
2093
|
+
argv.push([
|
|
2094
|
+
"notify",
|
|
2095
|
+
"--title",
|
|
2096
|
+
`SLEDS · seat ${bar} at the wall`,
|
|
2097
|
+
"--subtitle",
|
|
2098
|
+
sub,
|
|
2099
|
+
"--body",
|
|
2100
|
+
EXITS,
|
|
2101
|
+
...ws(cockpit)
|
|
2102
|
+
]);
|
|
2103
|
+
} else {
|
|
2104
|
+
argv.push([
|
|
2105
|
+
"log",
|
|
2106
|
+
"--level",
|
|
2107
|
+
"warning",
|
|
2108
|
+
"--source",
|
|
2109
|
+
"sleds",
|
|
2110
|
+
`${bar} bar ${Math.round(e.usedPct)}% — resets ${resetIn(e.resetsAt, now)}`,
|
|
2111
|
+
...ws(cockpit)
|
|
2112
|
+
]);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
if (extra.feed) {
|
|
2116
|
+
const before = prior?.feed;
|
|
2117
|
+
const newHandoffs = extra.feed.handoffs.filter((id) => !(before?.handoffs ?? []).includes(id));
|
|
2118
|
+
if (newHandoffs.length)
|
|
2119
|
+
argv.push([
|
|
2120
|
+
"notify",
|
|
2121
|
+
"--title",
|
|
2122
|
+
`SLEDS · ${newHandoffs.length === 1 ? "a hand-off is" : `${newHandoffs.length} hand-offs are`} waiting for you`,
|
|
2123
|
+
"--body",
|
|
2124
|
+
`/sled pickup ${newHandoffs[0]}`,
|
|
2125
|
+
...ws(cockpit)
|
|
2126
|
+
]);
|
|
2127
|
+
for (const id of extra.feed.delivered.filter((id2) => !(before?.delivered ?? []).includes(id2)))
|
|
2128
|
+
argv.push([
|
|
2129
|
+
"log",
|
|
2130
|
+
"--level",
|
|
2131
|
+
"success",
|
|
2132
|
+
"--source",
|
|
2133
|
+
"sleds",
|
|
2134
|
+
`overnight job done · /sled resume ${id}`,
|
|
2135
|
+
...ws(cockpit)
|
|
2136
|
+
]);
|
|
2137
|
+
for (const id of extra.feed.failed.filter((id2) => !(before?.failed ?? []).includes(id2)))
|
|
2138
|
+
argv.push([
|
|
2139
|
+
"notify",
|
|
2140
|
+
"--title",
|
|
2141
|
+
"SLEDS · overnight job failed",
|
|
2142
|
+
"--body",
|
|
2143
|
+
`sleds jobs · ${id}`,
|
|
2144
|
+
...ws(cockpit)
|
|
2145
|
+
]);
|
|
2146
|
+
}
|
|
2147
|
+
return { argv, state };
|
|
2148
|
+
};
|
|
2149
|
+
var FEED_MIN_INTERVAL_MS = 60000;
|
|
2150
|
+
var isObj = (v) => typeof v === "object" && v !== null;
|
|
2151
|
+
var feedFromJobs = (body, me, now) => {
|
|
2152
|
+
const f = {
|
|
2153
|
+
at: now,
|
|
2154
|
+
handoffs: [],
|
|
2155
|
+
jobs: { scheduled: 0, running: 0, delivered: 0, failed: 0 },
|
|
2156
|
+
delivered: [],
|
|
2157
|
+
failed: []
|
|
2158
|
+
};
|
|
2159
|
+
if (!isObj(body))
|
|
2160
|
+
return f;
|
|
2161
|
+
for (const h of Array.isArray(body.handoffs) ? body.handoffs.filter(isObj) : []) {
|
|
2162
|
+
const mine = me !== undefined && typeof h.from === "string" && h.from.toLowerCase() === me.toLowerCase();
|
|
2163
|
+
if (h.forMe === true && !mine && typeof h.handoffId === "string")
|
|
2164
|
+
f.handoffs.push(h.handoffId);
|
|
2165
|
+
}
|
|
2166
|
+
for (const j of Array.isArray(body.jobs) ? body.jobs.filter(isObj) : []) {
|
|
2167
|
+
const id = typeof j.jobId === "string" ? j.jobId : undefined;
|
|
2168
|
+
switch (j.status) {
|
|
2169
|
+
case "scheduled":
|
|
2170
|
+
case "queued":
|
|
2171
|
+
case "awaiting_approval":
|
|
2172
|
+
f.jobs.scheduled++;
|
|
2173
|
+
break;
|
|
2174
|
+
case "running":
|
|
2175
|
+
case "estimating":
|
|
2176
|
+
f.jobs.running++;
|
|
2177
|
+
break;
|
|
2178
|
+
case "delivered":
|
|
2179
|
+
f.jobs.delivered++;
|
|
2180
|
+
if (id)
|
|
2181
|
+
f.delivered.push(id);
|
|
2182
|
+
break;
|
|
2183
|
+
case "failed":
|
|
2184
|
+
f.jobs.failed++;
|
|
2185
|
+
if (id)
|
|
2186
|
+
f.failed.push(id);
|
|
2187
|
+
break;
|
|
2188
|
+
default:
|
|
2189
|
+
break;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
return f;
|
|
2193
|
+
};
|
|
2194
|
+
var fetchFeed = async (o, now) => {
|
|
2195
|
+
try {
|
|
2196
|
+
const res = await (o.fetchFn ?? fetch)(`${o.proxyUrl}/proxy/jobs`, {
|
|
2197
|
+
headers: { authorization: `Bearer ${o.token}` },
|
|
2198
|
+
signal: AbortSignal.timeout(o.timeoutMs ?? 1500)
|
|
2199
|
+
});
|
|
2200
|
+
if (!res.ok)
|
|
2201
|
+
return null;
|
|
2202
|
+
return feedFromJobs(await res.json(), o.email, now);
|
|
2203
|
+
} catch {
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
var runCockpit = async (cockpit, argv, opts = {}) => {
|
|
2208
|
+
const spawn = opts.spawn ?? Bun.spawn;
|
|
2209
|
+
const timeoutMs = opts.timeoutMs ?? 300;
|
|
2210
|
+
const bin = cockpit.kind === "cmux" ? "cmux" : cockpit.kind;
|
|
2211
|
+
await Promise.allSettled(argv.map(async (a) => {
|
|
2212
|
+
const proc = spawn([bin, ...a], { stdout: "ignore", stderr: "ignore", stdin: "ignore" });
|
|
2213
|
+
const timer = setTimeout(() => {
|
|
2214
|
+
try {
|
|
2215
|
+
proc.kill();
|
|
2216
|
+
} catch {}
|
|
2217
|
+
}, timeoutMs);
|
|
2218
|
+
try {
|
|
2219
|
+
await proc.exited;
|
|
2220
|
+
} finally {
|
|
2221
|
+
clearTimeout(timer);
|
|
2222
|
+
}
|
|
2223
|
+
}));
|
|
2224
|
+
};
|
|
2225
|
+
|
|
2226
|
+
// src/commands/doctor.ts
|
|
2227
|
+
init_config();
|
|
2228
|
+
init_meter();
|
|
1844
2229
|
init_settings();
|
|
1845
2230
|
|
|
1846
2231
|
// src/commands/musher.ts
|
|
@@ -2024,6 +2409,12 @@ async function runDoctor(o) {
|
|
|
2024
2409
|
fix: skill ? undefined : "sleds init (re-run; keeps your token)"
|
|
2025
2410
|
});
|
|
2026
2411
|
}
|
|
2412
|
+
const cockpit = detectCockpit(env);
|
|
2413
|
+
checks.push({
|
|
2414
|
+
level: "ok",
|
|
2415
|
+
name: "cockpit",
|
|
2416
|
+
detail: cockpit ? `${cockpit.kind}${cockpit.workspaceId ? ` (workspace ${cockpit.workspaceId})` : ""} — seat line in the sidebar, ring at the wall` : "none detected (run inside cmux to mirror the seat into its sidebar)"
|
|
2417
|
+
});
|
|
2027
2418
|
const nodeVersion = o.nodeVersion ?? process.version;
|
|
2028
2419
|
checks.push({
|
|
2029
2420
|
level: nodeOk(nodeVersion) ? "ok" : "warn",
|
|
@@ -2919,13 +3310,15 @@ var hooks = (o) => {
|
|
|
2919
3310
|
const had = hasSeatHook(settings);
|
|
2920
3311
|
const hadSpec = hasSpecHook(settings);
|
|
2921
3312
|
const withSeat = applySeatHook(settings, o.seatEventCommand ?? sledsCommand("seat-event", o.home, o.env));
|
|
2922
|
-
const
|
|
3313
|
+
const hadPrompt = hasPromptHook(settings);
|
|
3314
|
+
const withSpec = applyPromptHook(applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env)), o.promptEventCommand ?? sledsCommand("prompt-event", o.home, o.env));
|
|
2923
3315
|
const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
|
|
2924
3316
|
writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
|
|
2925
3317
|
log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
|
|
2926
3318
|
if (hadLine)
|
|
2927
3319
|
log(`refreshed statusLine in ${settingsPath(o.home)}`);
|
|
2928
3320
|
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
|
|
3321
|
+
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o.home)}`);
|
|
2929
3322
|
return 0;
|
|
2930
3323
|
};
|
|
2931
3324
|
|
|
@@ -3080,13 +3473,15 @@ var hooks2 = (o) => {
|
|
|
3080
3473
|
const had = hasSeatHook(settings);
|
|
3081
3474
|
const hadSpec = hasSpecHook(settings);
|
|
3082
3475
|
const withSeat = applySeatHook(settings, o.seatEventCommand ?? sledsCommand("seat-event", o.home, o.env));
|
|
3083
|
-
const
|
|
3476
|
+
const hadPrompt = hasPromptHook(settings);
|
|
3477
|
+
const withSpec = applyPromptHook(applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env)), o.promptEventCommand ?? sledsCommand("prompt-event", o.home, o.env));
|
|
3084
3478
|
const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
|
|
3085
3479
|
writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
|
|
3086
3480
|
log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
|
|
3087
3481
|
if (hadLine)
|
|
3088
3482
|
log(`refreshed statusLine in ${settingsPath(o.home)}`);
|
|
3089
3483
|
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
|
|
3484
|
+
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o.home)}`);
|
|
3090
3485
|
return 0;
|
|
3091
3486
|
};
|
|
3092
3487
|
|
|
@@ -3298,6 +3693,7 @@ var init = async (o) => {
|
|
|
3298
3693
|
refreshStatusLine: false,
|
|
3299
3694
|
seatEventCommand: o.seatEventCommand,
|
|
3300
3695
|
specEventCommand: o.specEventCommand,
|
|
3696
|
+
promptEventCommand: o.promptEventCommand,
|
|
3301
3697
|
log: (line) => wrote.push(line),
|
|
3302
3698
|
error
|
|
3303
3699
|
});
|
|
@@ -3818,6 +4214,93 @@ var on = (o) => {
|
|
|
3818
4214
|
return 0;
|
|
3819
4215
|
};
|
|
3820
4216
|
|
|
4217
|
+
// src/commands/promptEvent.ts
|
|
4218
|
+
init_config();
|
|
4219
|
+
|
|
4220
|
+
// src/commands/seatEvent.ts
|
|
4221
|
+
init_config();
|
|
4222
|
+
init_meter();
|
|
4223
|
+
var HOOK_STDIN_TIMEOUT_MS = 1000;
|
|
4224
|
+
|
|
4225
|
+
// src/commands/promptEvent.ts
|
|
4226
|
+
var LABEL = {
|
|
4227
|
+
fiveHour: "5-hour",
|
|
4228
|
+
sevenDay: "weekly",
|
|
4229
|
+
spendLimit: "spend",
|
|
4230
|
+
credits: "usage credits"
|
|
4231
|
+
};
|
|
4232
|
+
var fmtIn = (ms) => {
|
|
4233
|
+
const m = Math.max(1, Math.round(ms / 60000));
|
|
4234
|
+
if (m < 60)
|
|
4235
|
+
return `${m} min`;
|
|
4236
|
+
const h = Math.floor(m / 60);
|
|
4237
|
+
const r = m % 60;
|
|
4238
|
+
return r ? `${h} h ${r} min` : `${h} h`;
|
|
4239
|
+
};
|
|
4240
|
+
var untold = (state) => {
|
|
4241
|
+
const out = [];
|
|
4242
|
+
for (const bar of ["credits", "spendLimit", "sevenDay", "fiveHour"]) {
|
|
4243
|
+
const w = state.warned?.[bar];
|
|
4244
|
+
if (!w)
|
|
4245
|
+
continue;
|
|
4246
|
+
for (const stage of ["hit", "approaching"]) {
|
|
4247
|
+
const at = w[stage];
|
|
4248
|
+
if (at === undefined)
|
|
4249
|
+
continue;
|
|
4250
|
+
if ((state.told?.[bar]?.[stage] ?? -1) >= at)
|
|
4251
|
+
continue;
|
|
4252
|
+
out.push({ bar, stage, resetsAt: at });
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
return out;
|
|
4256
|
+
};
|
|
4257
|
+
var exitsLine = (hasUpstreamKey) => hasUpstreamKey ? "`/sled handoff <what you were doing>` hands the work to a teammate or buddy; `sleds defer` resumes this session at the reset; `sleds on` continues now on the workspace's platform key." : "`/sled handoff <what you were doing>` hands the work to a teammate or buddy; `sleds defer` resumes this session at the reset (this workspace has no platform key to switch to).";
|
|
4258
|
+
var approachingContext = (bar, usedPct, resetsAt, hasUpstreamKey, now) => `[SLEDS] This seat is at ${usedPct !== undefined ? `${Math.round(usedPct)}%` : "90%+"} of its ${LABEL[bar]} limit; it resets in ${fmtIn(resetsAt - now)}. Tell the user in one short line before you answer, and offer the exits: ${exitsLine(hasUpstreamKey)} Do not mention this again unless asked.`;
|
|
4259
|
+
var hitContext = (bar, resetsAt, hasUpstreamKey, now) => `[SLEDS] This seat hit its ${LABEL[bar]} limit; it resets in ${fmtIn(resetsAt - now)}. If this reply fails or the user is stuck, the exits are: ${exitsLine(hasUpstreamKey)} Say so in one line.`;
|
|
4260
|
+
var creditsBlockText = (hasUpstreamKey) => `SLEDS: this seat is out of usage credits — the next reply would fail, so this prompt was not sent. ${hasUpstreamKey ? "Exits: /sled handoff <task> (a buddy picks it up), sleds on (continue on the workspace's platform key), or /model to a cheaper model." : "Exits: /sled handoff <task> (a buddy picks it up), sleds defer (resume when credits are topped up), or /model to a cheaper model."} Re-send your prompt after choosing; SLEDS will not block again.`;
|
|
4261
|
+
var promptEvent = async (o) => {
|
|
4262
|
+
const log = o.log ?? console.log;
|
|
4263
|
+
const error = o.error ?? console.error;
|
|
4264
|
+
const now = (o.now ?? Date.now)();
|
|
4265
|
+
if (o.stdin === undefined)
|
|
4266
|
+
await readStdin(HOOK_STDIN_TIMEOUT_MS);
|
|
4267
|
+
let state;
|
|
4268
|
+
try {
|
|
4269
|
+
state = readSeatState(o.home);
|
|
4270
|
+
} catch {
|
|
4271
|
+
return 0;
|
|
4272
|
+
}
|
|
4273
|
+
const pending = untold(state);
|
|
4274
|
+
if (pending.length === 0)
|
|
4275
|
+
return 0;
|
|
4276
|
+
const hasKey = readConfig(o.home).org?.capabilities.upstreamKey !== false;
|
|
4277
|
+
const told = { ...state.told ?? {} };
|
|
4278
|
+
const mark = (bar, stage, at) => {
|
|
4279
|
+
told[bar] = { ...told[bar] ?? {}, [stage]: at };
|
|
4280
|
+
};
|
|
4281
|
+
let code = 0;
|
|
4282
|
+
const lines = [];
|
|
4283
|
+
for (const e of pending) {
|
|
4284
|
+
mark(e.bar, e.stage, e.resetsAt);
|
|
4285
|
+
if (e.bar === "credits") {
|
|
4286
|
+
if (code !== 2) {
|
|
4287
|
+
error(creditsBlockText(hasKey));
|
|
4288
|
+
code = 2;
|
|
4289
|
+
}
|
|
4290
|
+
continue;
|
|
4291
|
+
}
|
|
4292
|
+
const pct = state.window?.[e.bar]?.usedPct;
|
|
4293
|
+
lines.push(e.stage === "approaching" ? approachingContext(e.bar, pct, e.resetsAt, hasKey, now) : hitContext(e.bar, e.resetsAt, hasKey, now));
|
|
4294
|
+
}
|
|
4295
|
+
try {
|
|
4296
|
+
writeSeatState(o.home, { ...state, told });
|
|
4297
|
+
} catch {}
|
|
4298
|
+
if (code === 0 && lines.length > 0)
|
|
4299
|
+
log(lines.join(`
|
|
4300
|
+
`));
|
|
4301
|
+
return code;
|
|
4302
|
+
};
|
|
4303
|
+
|
|
3821
4304
|
// src/commands/run.ts
|
|
3822
4305
|
init_config();
|
|
3823
4306
|
init_meter();
|
|
@@ -3882,8 +4365,7 @@ var run = async (o) => {
|
|
|
3882
4365
|
// src/commands/seatEvent.ts
|
|
3883
4366
|
init_config();
|
|
3884
4367
|
init_meter();
|
|
3885
|
-
var
|
|
3886
|
-
var NOTIFICATION_TITLE = "sleds";
|
|
4368
|
+
var HOOK_STDIN_TIMEOUT_MS2 = 1000;
|
|
3887
4369
|
var isObject8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3888
4370
|
var notificationType = (text) => {
|
|
3889
4371
|
try {
|
|
@@ -3895,20 +4377,10 @@ var notificationType = (text) => {
|
|
|
3895
4377
|
return;
|
|
3896
4378
|
}
|
|
3897
4379
|
};
|
|
3898
|
-
var osascriptArgs = (title, body) => [
|
|
3899
|
-
"-e",
|
|
3900
|
-
"on run argv",
|
|
3901
|
-
"-e",
|
|
3902
|
-
"display notification (item 2 of argv) with title (item 1 of argv)",
|
|
3903
|
-
"-e",
|
|
3904
|
-
"end run",
|
|
3905
|
-
title,
|
|
3906
|
-
body
|
|
3907
|
-
];
|
|
3908
4380
|
var seatEvent = async (o) => {
|
|
3909
4381
|
const log = o.log ?? console.log;
|
|
3910
4382
|
const now = (o.now ?? Date.now)();
|
|
3911
|
-
const input = o.stdin ?? await readStdin(
|
|
4383
|
+
const input = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS2);
|
|
3912
4384
|
const kind = notificationType(input);
|
|
3913
4385
|
try {
|
|
3914
4386
|
const state = readSeatState(o.home);
|
|
@@ -3925,11 +4397,6 @@ var seatEvent = async (o) => {
|
|
|
3925
4397
|
} catch {}
|
|
3926
4398
|
const line = seatWallLine(readConfig(o.home).org?.capabilities.upstreamKey !== false);
|
|
3927
4399
|
log(line);
|
|
3928
|
-
if ((o.platform ?? process.platform) === "darwin" && o.exec) {
|
|
3929
|
-
try {
|
|
3930
|
-
o.exec("osascript", osascriptArgs(NOTIFICATION_TITLE, line));
|
|
3931
|
-
} catch {}
|
|
3932
|
-
}
|
|
3933
4400
|
return 0;
|
|
3934
4401
|
};
|
|
3935
4402
|
|
|
@@ -4472,7 +4939,7 @@ async function postSeat(proxyUrl, token, body, fetchFn) {
|
|
|
4472
4939
|
init_meter();
|
|
4473
4940
|
import { readFileSync as readFileSync10, statSync as statSync2 } from "node:fs";
|
|
4474
4941
|
import { basename as basename2, isAbsolute, relative } from "node:path";
|
|
4475
|
-
var
|
|
4942
|
+
var HOOK_STDIN_TIMEOUT_MS3 = 1000;
|
|
4476
4943
|
var ESTIMATE_TIMEOUT_MS = 4000;
|
|
4477
4944
|
var MIN_SPEC_CHARS = 200;
|
|
4478
4945
|
var MAX_SPEC_CHARS = 12000;
|
|
@@ -4533,7 +5000,7 @@ function readFileOrNull(path) {
|
|
|
4533
5000
|
}
|
|
4534
5001
|
var specEvent = async (o) => {
|
|
4535
5002
|
const log = o.log ?? console.log;
|
|
4536
|
-
const raw = o.stdin ?? await readStdin(
|
|
5003
|
+
const raw = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS3);
|
|
4537
5004
|
const input = raw ? parseHookInput(raw) : null;
|
|
4538
5005
|
if (!input || !isSpecPath(input.filePath))
|
|
4539
5006
|
return 0;
|
|
@@ -4563,6 +5030,21 @@ var specEvent = async (o) => {
|
|
|
4563
5030
|
if (b && typeof b.lowUsd === "number" && typeof b.highUsd === "number") {
|
|
4564
5031
|
const min = (n) => typeof n === "number" ? Math.round(n) : "?";
|
|
4565
5032
|
log(`sleds: ${project} sized at $${b.lowUsd.toFixed(2)}–${b.highUsd.toFixed(2)} · ${min(b.lowMinutes)}–${min(b.highMinutes)} min`);
|
|
5033
|
+
if (typeof b.lowMinutes === "number" && typeof b.highMinutes === "number") {
|
|
5034
|
+
try {
|
|
5035
|
+
writeSeatState(o.home, {
|
|
5036
|
+
...readSeatState(o.home),
|
|
5037
|
+
estimate: {
|
|
5038
|
+
project,
|
|
5039
|
+
lowUsd: b.lowUsd,
|
|
5040
|
+
highUsd: b.highUsd,
|
|
5041
|
+
lowMin: b.lowMinutes,
|
|
5042
|
+
highMin: b.highMinutes,
|
|
5043
|
+
at: (o.now ?? Date.now)()
|
|
5044
|
+
}
|
|
5045
|
+
});
|
|
5046
|
+
} catch {}
|
|
5047
|
+
}
|
|
4566
5048
|
}
|
|
4567
5049
|
} catch {}
|
|
4568
5050
|
return 0;
|
|
@@ -4703,23 +5185,6 @@ var status = async (o) => {
|
|
|
4703
5185
|
// src/commands/statusline.ts
|
|
4704
5186
|
init_config();
|
|
4705
5187
|
init_meter();
|
|
4706
|
-
|
|
4707
|
-
// src/commands/seatEvent.ts
|
|
4708
|
-
init_config();
|
|
4709
|
-
init_meter();
|
|
4710
|
-
var NOTIFICATION_TITLE2 = "sleds";
|
|
4711
|
-
var osascriptArgs2 = (title, body) => [
|
|
4712
|
-
"-e",
|
|
4713
|
-
"on run argv",
|
|
4714
|
-
"-e",
|
|
4715
|
-
"display notification (item 2 of argv) with title (item 1 of argv)",
|
|
4716
|
-
"-e",
|
|
4717
|
-
"end run",
|
|
4718
|
-
title,
|
|
4719
|
-
body
|
|
4720
|
-
];
|
|
4721
|
-
|
|
4722
|
-
// src/commands/statusline.ts
|
|
4723
5188
|
var statusline = async (o) => {
|
|
4724
5189
|
const log = o.log ?? console.log;
|
|
4725
5190
|
const now = (o.now ?? Date.now)();
|
|
@@ -4727,9 +5192,10 @@ var statusline = async (o) => {
|
|
|
4727
5192
|
const window = seatWindowFromStdin(input, now);
|
|
4728
5193
|
const session = seatSessionFromStdin(input, now);
|
|
4729
5194
|
let posts = [];
|
|
5195
|
+
let events = [];
|
|
4730
5196
|
try {
|
|
4731
5197
|
if (window)
|
|
4732
|
-
posts = rememberSeat(o, window, session, now, input);
|
|
5198
|
+
({ posts, events } = rememberSeat(o, window, session, now, input));
|
|
4733
5199
|
else {
|
|
4734
5200
|
const st = readSeatState(o.home);
|
|
4735
5201
|
const credits = creditsEvent(o, st, input, now);
|
|
@@ -4741,11 +5207,13 @@ var statusline = async (o) => {
|
|
|
4741
5207
|
};
|
|
4742
5208
|
writeSeatState(o.home, credits.event ? markWarned(next, [credits.event]) : next);
|
|
4743
5209
|
}
|
|
4744
|
-
if (credits.event)
|
|
4745
|
-
|
|
5210
|
+
if (credits.event) {
|
|
5211
|
+
events = [credits.event];
|
|
5212
|
+
posts = announce(o, events);
|
|
5213
|
+
}
|
|
4746
5214
|
}
|
|
4747
5215
|
} catch {}
|
|
4748
|
-
const seatFragment = formatSeatFragment(window);
|
|
5216
|
+
const seatFragment = formatSeatFragment(window, now);
|
|
4749
5217
|
let line;
|
|
4750
5218
|
try {
|
|
4751
5219
|
const meter2 = await loadMeter({
|
|
@@ -4759,9 +5227,35 @@ var statusline = async (o) => {
|
|
|
4759
5227
|
line = formatMeterLine(null, now, seatFragment);
|
|
4760
5228
|
}
|
|
4761
5229
|
log(line);
|
|
5230
|
+
posts.push(speakToCockpit(o, window, events, now));
|
|
4762
5231
|
await Promise.allSettled(posts);
|
|
4763
5232
|
return 0;
|
|
4764
5233
|
};
|
|
5234
|
+
var speakToCockpit = async (o, window, events, now) => {
|
|
5235
|
+
const cockpit = detectCockpit(o.env ?? process.env);
|
|
5236
|
+
if (!cockpit)
|
|
5237
|
+
return;
|
|
5238
|
+
try {
|
|
5239
|
+
const st = readSeatState(o.home);
|
|
5240
|
+
let feed;
|
|
5241
|
+
const src = meterSource(o.home);
|
|
5242
|
+
if (src && now - (st.cockpit?.feed?.at ?? 0) >= FEED_MIN_INTERVAL_MS) {
|
|
5243
|
+
const got = await fetchFeed({ ...src, email: readConfig(o.home).email, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs }, now);
|
|
5244
|
+
if (got)
|
|
5245
|
+
feed = got;
|
|
5246
|
+
}
|
|
5247
|
+
const r = renderCockpit(cockpit, window, events, st.cockpit, now, {
|
|
5248
|
+
estimate: st.estimate,
|
|
5249
|
+
...feed ? { feed } : {}
|
|
5250
|
+
});
|
|
5251
|
+
const changed = r.argv.length > 0 || feed !== undefined;
|
|
5252
|
+
if (!changed)
|
|
5253
|
+
return;
|
|
5254
|
+
writeSeatState(o.home, { ...readSeatState(o.home), cockpit: r.state });
|
|
5255
|
+
if (r.argv.length)
|
|
5256
|
+
await runCockpit(cockpit, r.argv, { spawn: o.spawn });
|
|
5257
|
+
} catch {}
|
|
5258
|
+
};
|
|
4765
5259
|
var rememberSeat = (o, window, session, now, input) => {
|
|
4766
5260
|
const prior = readSeatState(o.home);
|
|
4767
5261
|
let state = { ...prior, window, ...session ? { session } : {} };
|
|
@@ -4778,9 +5272,9 @@ var rememberSeat = (o, window, session, now, input) => {
|
|
|
4778
5272
|
if (credits.event)
|
|
4779
5273
|
events.push(credits.event);
|
|
4780
5274
|
state = markWarned(state, events);
|
|
4781
|
-
posts.push(...announce(o, events
|
|
5275
|
+
posts.push(...announce(o, events));
|
|
4782
5276
|
writeSeatState(o.home, state);
|
|
4783
|
-
return posts;
|
|
5277
|
+
return { posts, events };
|
|
4784
5278
|
};
|
|
4785
5279
|
var creditsEvent = (o, prior, input, now) => {
|
|
4786
5280
|
const path = transcriptPathFromStdin(input);
|
|
@@ -4792,29 +5286,19 @@ var creditsEvent = (o, prior, input, now) => {
|
|
|
4792
5286
|
return { event: null, scan: next };
|
|
4793
5287
|
return { event: { bar: "credits", stage: "hit", usedPct: 100, resetsAt: now }, scan: next };
|
|
4794
5288
|
};
|
|
4795
|
-
var announce = (o, events
|
|
5289
|
+
var announce = (o, events) => {
|
|
4796
5290
|
if (events.length === 0)
|
|
4797
5291
|
return [];
|
|
4798
5292
|
const src = meterSource(o.home);
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
if (src)
|
|
4809
|
-
posts.push(postSeatWall({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
|
|
4810
|
-
bar: e.bar,
|
|
4811
|
-
stage: e.stage,
|
|
4812
|
-
usedPct: e.usedPct,
|
|
4813
|
-
...e.bar === "credits" ? {} : { resetsAt: e.resetsAt },
|
|
4814
|
-
notification: e.bar === "credits" ? "transcript" : "statusline"
|
|
4815
|
-
}));
|
|
4816
|
-
}
|
|
4817
|
-
return posts;
|
|
5293
|
+
if (!src)
|
|
5294
|
+
return [];
|
|
5295
|
+
return events.map((e) => postSeatWall({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
|
|
5296
|
+
bar: e.bar,
|
|
5297
|
+
stage: e.stage,
|
|
5298
|
+
usedPct: e.usedPct,
|
|
5299
|
+
...e.bar === "credits" ? {} : { resetsAt: e.resetsAt },
|
|
5300
|
+
notification: e.bar === "credits" ? "transcript" : "statusline"
|
|
5301
|
+
}));
|
|
4818
5302
|
};
|
|
4819
5303
|
|
|
4820
5304
|
// src/commands/uninstall.ts
|
|
@@ -4902,6 +5386,11 @@ var uninstall2 = (o) => {
|
|
|
4902
5386
|
changed = true;
|
|
4903
5387
|
removed.push(`hooks.Notification seat-event entry from ${settingsPath(o.home)}`);
|
|
4904
5388
|
}
|
|
5389
|
+
if (hasPromptHook(settings)) {
|
|
5390
|
+
settings = removePromptHook(settings);
|
|
5391
|
+
changed = true;
|
|
5392
|
+
removed.push(`hooks.UserPromptSubmit prompt-event entry from ${settingsPath(o.home)}`);
|
|
5393
|
+
}
|
|
4905
5394
|
if (hasSpecHook(settings)) {
|
|
4906
5395
|
settings = removeSpecHook(settings);
|
|
4907
5396
|
changed = true;
|
|
@@ -5184,6 +5673,8 @@ commands:
|
|
|
5184
5673
|
statusline Print the one-line meter Claude Code shows (installed by init; 10s cache).
|
|
5185
5674
|
hooks Register the Claude Code Notification hook for seat limits (init does this too).
|
|
5186
5675
|
seat-event Claude Code hook: record a seat wall and say how to continue. Reads hook JSON on stdin.
|
|
5676
|
+
prompt-event Claude Code UserPromptSubmit hook: at 90 % of a seat bar the seat speaks up in the
|
|
5677
|
+
session (once per bar per reset); out of usage credits blocks the prompt once with the exits.
|
|
5187
5678
|
spec-event Claude Code PostToolUse hook: when a spec-shaped markdown file is written, size it
|
|
5188
5679
|
(POST /proxy/estimate with the file path as the project). Silent unless it estimates.
|
|
5189
5680
|
--version Print the CLI version.
|
|
@@ -5236,6 +5727,7 @@ var sledsArgv = () => {
|
|
|
5236
5727
|
var shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
|
|
5237
5728
|
var statusLineCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "statusline"].map(shellQuote).join(" "), home);
|
|
5238
5729
|
var seatEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "seat-event"].map(shellQuote).join(" "), home);
|
|
5730
|
+
var promptEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "prompt-event"].map(shellQuote).join(" "), home);
|
|
5239
5731
|
var specEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "spec-event"].map(shellQuote).join(" "), home);
|
|
5240
5732
|
var main = async (argv) => {
|
|
5241
5733
|
const { cmd, flags } = parseArgs(argv);
|
|
@@ -5307,19 +5799,23 @@ var main = async (argv) => {
|
|
|
5307
5799
|
prompt: promptStdin,
|
|
5308
5800
|
statusLineCommand: statusLineCommand(sledsArgv()),
|
|
5309
5801
|
seatEventCommand: seatEventCommand(sledsArgv()),
|
|
5310
|
-
specEventCommand: specEventCommand(sledsArgv())
|
|
5802
|
+
specEventCommand: specEventCommand(sledsArgv()),
|
|
5803
|
+
promptEventCommand: promptEventCommand(sledsArgv())
|
|
5311
5804
|
});
|
|
5312
5805
|
}
|
|
5313
5806
|
case "hooks":
|
|
5314
5807
|
return hooks({
|
|
5315
5808
|
home,
|
|
5316
5809
|
seatEventCommand: seatEventCommand(sledsArgv()),
|
|
5317
|
-
specEventCommand: specEventCommand(sledsArgv())
|
|
5810
|
+
specEventCommand: specEventCommand(sledsArgv()),
|
|
5811
|
+
promptEventCommand: promptEventCommand(sledsArgv())
|
|
5318
5812
|
});
|
|
5319
5813
|
case "seat-event":
|
|
5320
|
-
return seatEvent({ home
|
|
5814
|
+
return seatEvent({ home });
|
|
5321
5815
|
case "spec-event":
|
|
5322
5816
|
return specEvent({ home });
|
|
5817
|
+
case "prompt-event":
|
|
5818
|
+
return promptEvent({ home });
|
|
5323
5819
|
case "on":
|
|
5324
5820
|
return on({
|
|
5325
5821
|
home,
|
|
@@ -5352,7 +5848,7 @@ var main = async (argv) => {
|
|
|
5352
5848
|
case "seat-sync":
|
|
5353
5849
|
return seatSync({ home, argv: process.argv.slice(3) });
|
|
5354
5850
|
case "statusline":
|
|
5355
|
-
return statusline({ home
|
|
5851
|
+
return statusline({ home });
|
|
5356
5852
|
case "help":
|
|
5357
5853
|
case "--help":
|
|
5358
5854
|
case "-h":
|
|
@@ -5361,7 +5857,7 @@ var main = async (argv) => {
|
|
|
5361
5857
|
case "version":
|
|
5362
5858
|
case "--version":
|
|
5363
5859
|
case "-v":
|
|
5364
|
-
console.log("sleds 0.1.
|
|
5860
|
+
console.log("sleds 0.1.20");
|
|
5365
5861
|
return 0;
|
|
5366
5862
|
default:
|
|
5367
5863
|
console.error(`unknown command: ${cmd}
|
|
@@ -5390,6 +5886,7 @@ if (isMain()) {
|
|
|
5390
5886
|
}
|
|
5391
5887
|
export {
|
|
5392
5888
|
parseArgs,
|
|
5889
|
+
promptEventCommand,
|
|
5393
5890
|
seatEventCommand,
|
|
5394
5891
|
specEventCommand,
|
|
5395
5892
|
statusLineCommand
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleds/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "SLEDS CLI \u2014 the cost and limits of AI agent work, decided at the wall. Point Claude Code at a SLEDS proxy; estimate before you run; hold, switch model, or run overnight when you hit a limit.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|