@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.
Files changed (2) hide show
  1. package/dist/sleds.mjs +604 -107
  2. 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), 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)}
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 withSpec = applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env));
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 withSpec = applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env));
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 HOOK_STDIN_TIMEOUT_MS = 1000;
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(HOOK_STDIN_TIMEOUT_MS);
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 HOOK_STDIN_TIMEOUT_MS2 = 1000;
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(HOOK_STDIN_TIMEOUT_MS2);
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
- posts = announce(o, [credits.event], now);
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, now));
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, now) => {
5289
+ var announce = (o, events) => {
4796
5290
  if (events.length === 0)
4797
5291
  return [];
4798
5292
  const src = meterSource(o.home);
4799
- const hasKey = readConfig(o.home).org?.capabilities.upstreamKey !== false;
4800
- const platform2 = o.platform ?? process.platform;
4801
- const posts = [];
4802
- for (const e of events) {
4803
- if (platform2 === "darwin" && o.exec) {
4804
- try {
4805
- o.exec("osascript", osascriptArgs2(NOTIFICATION_TITLE2, seatWallBanner(e, hasKey, now)));
4806
- } catch {}
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, platform: platform2(), exec: defaultExec });
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, platform: platform2(), exec: defaultExec });
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.18");
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.18",
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",