@sleds/cli 0.1.18 → 0.1.19

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 +205 -103
  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);
@@ -852,21 +896,6 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
852
896
  } catch {
853
897
  return { hit: false, next: prior && prior.path === path ? prior : { path, offset: 0 } };
854
898
  }
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
899
  }, seatWindowBody2 = (w) => ({
871
900
  capturedAt: w.capturedAt,
872
901
  ...w.fiveHour ? { fiveHour: w.fiveHour } : {},
@@ -1364,7 +1393,7 @@ var seatWindowFromStdin = (text, capturedAt) => {
1364
1393
  return null;
1365
1394
  }
1366
1395
  };
1367
- var formatSeatFragment = (w) => {
1396
+ var formatSeatFragment = (w, now = Date.now()) => {
1368
1397
  if (!w)
1369
1398
  return null;
1370
1399
  const parts = [];
@@ -1376,6 +1405,17 @@ var formatSeatFragment = (w) => {
1376
1405
  parts.push(`spend ${Math.round(w.spendLimit.usedPct)}%`);
1377
1406
  if (parts.length === 0)
1378
1407
  return null;
1408
+ const hot = [
1409
+ ["wk", w.sevenDay],
1410
+ ["5h", w.fiveHour],
1411
+ ["spend", w.spendLimit]
1412
+ ].filter((x) => x[1] !== undefined && x[1].usedPct >= APPROACHING_PCT).sort((a, b) => b[1].usedPct - a[1].usedPct)[0];
1413
+ if (hot) {
1414
+ const mins = Math.max(0, Math.round((hot[1].resetsAt - now) / 60000));
1415
+ const reset = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? `${mins % 60}m` : ""}` : `${mins}m`;
1416
+ const verb = hot[1].usedPct >= 100 ? "at the wall" : `${Math.round(hot[1].usedPct)}%`;
1417
+ return `⚠ seat ${hot[0]} ${verb}${SEP}resets ${reset}${SEP}/sled handoff · sleds defer`;
1418
+ }
1379
1419
  return `seat ${parts.join(SEP)}`;
1380
1420
  };
1381
1421
  var APPROACHING_PCT = 90;
@@ -1431,6 +1471,23 @@ var parseSeatState = (raw) => {
1431
1471
  }
1432
1472
  out.warned = warned;
1433
1473
  }
1474
+ if (isObject5(raw.told)) {
1475
+ const told = {};
1476
+ for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
1477
+ const w = raw.told[bar];
1478
+ if (!isObject5(w))
1479
+ continue;
1480
+ const entry = {};
1481
+ const ap = num3(w.approaching);
1482
+ const hit = num3(w.hit);
1483
+ if (ap !== undefined)
1484
+ entry.approaching = ap;
1485
+ if (hit !== undefined)
1486
+ entry.hit = hit;
1487
+ told[bar] = entry;
1488
+ }
1489
+ out.told = told;
1490
+ }
1434
1491
  if (isObject5(raw.transcriptScan) && typeof raw.transcriptScan.path === "string") {
1435
1492
  const offset = num3(raw.transcriptScan.offset);
1436
1493
  const hitAt = num3(raw.transcriptScan.hitAt);
@@ -1613,22 +1670,6 @@ var scanTranscriptForCredits = (path, prior) => {
1613
1670
  return { hit: false, next: prior && prior.path === path ? prior : { path, offset: 0 } };
1614
1671
  }
1615
1672
  };
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
1673
  var seatWindowBody = (w) => ({
1633
1674
  capturedAt: w.capturedAt,
1634
1675
  ...w.fiveHour ? { fiveHour: w.fiveHour } : {},
@@ -2919,13 +2960,15 @@ var hooks = (o) => {
2919
2960
  const had = hasSeatHook(settings);
2920
2961
  const hadSpec = hasSpecHook(settings);
2921
2962
  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));
2963
+ const hadPrompt = hasPromptHook(settings);
2964
+ const withSpec = applyPromptHook(applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env)), o.promptEventCommand ?? sledsCommand("prompt-event", o.home, o.env));
2923
2965
  const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
2924
2966
  writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
2925
2967
  log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
2926
2968
  if (hadLine)
2927
2969
  log(`refreshed statusLine in ${settingsPath(o.home)}`);
2928
2970
  log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
2971
+ log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o.home)}`);
2929
2972
  return 0;
2930
2973
  };
2931
2974
 
@@ -3080,13 +3123,15 @@ var hooks2 = (o) => {
3080
3123
  const had = hasSeatHook(settings);
3081
3124
  const hadSpec = hasSpecHook(settings);
3082
3125
  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));
3126
+ const hadPrompt = hasPromptHook(settings);
3127
+ const withSpec = applyPromptHook(applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env)), o.promptEventCommand ?? sledsCommand("prompt-event", o.home, o.env));
3084
3128
  const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
3085
3129
  writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
3086
3130
  log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
3087
3131
  if (hadLine)
3088
3132
  log(`refreshed statusLine in ${settingsPath(o.home)}`);
3089
3133
  log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
3134
+ log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o.home)}`);
3090
3135
  return 0;
3091
3136
  };
3092
3137
 
@@ -3298,6 +3343,7 @@ var init = async (o) => {
3298
3343
  refreshStatusLine: false,
3299
3344
  seatEventCommand: o.seatEventCommand,
3300
3345
  specEventCommand: o.specEventCommand,
3346
+ promptEventCommand: o.promptEventCommand,
3301
3347
  log: (line) => wrote.push(line),
3302
3348
  error
3303
3349
  });
@@ -3818,6 +3864,93 @@ var on = (o) => {
3818
3864
  return 0;
3819
3865
  };
3820
3866
 
3867
+ // src/commands/promptEvent.ts
3868
+ init_config();
3869
+
3870
+ // src/commands/seatEvent.ts
3871
+ init_config();
3872
+ init_meter();
3873
+ var HOOK_STDIN_TIMEOUT_MS = 1000;
3874
+
3875
+ // src/commands/promptEvent.ts
3876
+ var LABEL = {
3877
+ fiveHour: "5-hour",
3878
+ sevenDay: "weekly",
3879
+ spendLimit: "spend",
3880
+ credits: "usage credits"
3881
+ };
3882
+ var fmtIn = (ms) => {
3883
+ const m = Math.max(1, Math.round(ms / 60000));
3884
+ if (m < 60)
3885
+ return `${m} min`;
3886
+ const h = Math.floor(m / 60);
3887
+ const r = m % 60;
3888
+ return r ? `${h} h ${r} min` : `${h} h`;
3889
+ };
3890
+ var untold = (state) => {
3891
+ const out = [];
3892
+ for (const bar of ["credits", "spendLimit", "sevenDay", "fiveHour"]) {
3893
+ const w = state.warned?.[bar];
3894
+ if (!w)
3895
+ continue;
3896
+ for (const stage of ["hit", "approaching"]) {
3897
+ const at = w[stage];
3898
+ if (at === undefined)
3899
+ continue;
3900
+ if ((state.told?.[bar]?.[stage] ?? -1) >= at)
3901
+ continue;
3902
+ out.push({ bar, stage, resetsAt: at });
3903
+ }
3904
+ }
3905
+ return out;
3906
+ };
3907
+ 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).";
3908
+ 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.`;
3909
+ 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.`;
3910
+ 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.`;
3911
+ var promptEvent = async (o) => {
3912
+ const log = o.log ?? console.log;
3913
+ const error = o.error ?? console.error;
3914
+ const now = (o.now ?? Date.now)();
3915
+ if (o.stdin === undefined)
3916
+ await readStdin(HOOK_STDIN_TIMEOUT_MS);
3917
+ let state;
3918
+ try {
3919
+ state = readSeatState(o.home);
3920
+ } catch {
3921
+ return 0;
3922
+ }
3923
+ const pending = untold(state);
3924
+ if (pending.length === 0)
3925
+ return 0;
3926
+ const hasKey = readConfig(o.home).org?.capabilities.upstreamKey !== false;
3927
+ const told = { ...state.told ?? {} };
3928
+ const mark = (bar, stage, at) => {
3929
+ told[bar] = { ...told[bar] ?? {}, [stage]: at };
3930
+ };
3931
+ let code = 0;
3932
+ const lines = [];
3933
+ for (const e of pending) {
3934
+ mark(e.bar, e.stage, e.resetsAt);
3935
+ if (e.bar === "credits") {
3936
+ if (code !== 2) {
3937
+ error(creditsBlockText(hasKey));
3938
+ code = 2;
3939
+ }
3940
+ continue;
3941
+ }
3942
+ const pct = state.window?.[e.bar]?.usedPct;
3943
+ lines.push(e.stage === "approaching" ? approachingContext(e.bar, pct, e.resetsAt, hasKey, now) : hitContext(e.bar, e.resetsAt, hasKey, now));
3944
+ }
3945
+ try {
3946
+ writeSeatState(o.home, { ...state, told });
3947
+ } catch {}
3948
+ if (code === 0 && lines.length > 0)
3949
+ log(lines.join(`
3950
+ `));
3951
+ return code;
3952
+ };
3953
+
3821
3954
  // src/commands/run.ts
3822
3955
  init_config();
3823
3956
  init_meter();
@@ -3882,8 +4015,7 @@ var run = async (o) => {
3882
4015
  // src/commands/seatEvent.ts
3883
4016
  init_config();
3884
4017
  init_meter();
3885
- var HOOK_STDIN_TIMEOUT_MS = 1000;
3886
- var NOTIFICATION_TITLE = "sleds";
4018
+ var HOOK_STDIN_TIMEOUT_MS2 = 1000;
3887
4019
  var isObject8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3888
4020
  var notificationType = (text) => {
3889
4021
  try {
@@ -3895,20 +4027,10 @@ var notificationType = (text) => {
3895
4027
  return;
3896
4028
  }
3897
4029
  };
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
4030
  var seatEvent = async (o) => {
3909
4031
  const log = o.log ?? console.log;
3910
4032
  const now = (o.now ?? Date.now)();
3911
- const input = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
4033
+ const input = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS2);
3912
4034
  const kind = notificationType(input);
3913
4035
  try {
3914
4036
  const state = readSeatState(o.home);
@@ -3925,11 +4047,6 @@ var seatEvent = async (o) => {
3925
4047
  } catch {}
3926
4048
  const line = seatWallLine(readConfig(o.home).org?.capabilities.upstreamKey !== false);
3927
4049
  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
4050
  return 0;
3934
4051
  };
3935
4052
 
@@ -4472,7 +4589,7 @@ async function postSeat(proxyUrl, token, body, fetchFn) {
4472
4589
  init_meter();
4473
4590
  import { readFileSync as readFileSync10, statSync as statSync2 } from "node:fs";
4474
4591
  import { basename as basename2, isAbsolute, relative } from "node:path";
4475
- var HOOK_STDIN_TIMEOUT_MS2 = 1000;
4592
+ var HOOK_STDIN_TIMEOUT_MS3 = 1000;
4476
4593
  var ESTIMATE_TIMEOUT_MS = 4000;
4477
4594
  var MIN_SPEC_CHARS = 200;
4478
4595
  var MAX_SPEC_CHARS = 12000;
@@ -4533,7 +4650,7 @@ function readFileOrNull(path) {
4533
4650
  }
4534
4651
  var specEvent = async (o) => {
4535
4652
  const log = o.log ?? console.log;
4536
- const raw = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS2);
4653
+ const raw = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS3);
4537
4654
  const input = raw ? parseHookInput(raw) : null;
4538
4655
  if (!input || !isSpecPath(input.filePath))
4539
4656
  return 0;
@@ -4701,25 +4818,7 @@ var status = async (o) => {
4701
4818
  };
4702
4819
 
4703
4820
  // src/commands/statusline.ts
4704
- init_config();
4705
- init_meter();
4706
-
4707
- // src/commands/seatEvent.ts
4708
- init_config();
4709
4821
  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
4822
  var statusline = async (o) => {
4724
4823
  const log = o.log ?? console.log;
4725
4824
  const now = (o.now ?? Date.now)();
@@ -4742,10 +4841,10 @@ var statusline = async (o) => {
4742
4841
  writeSeatState(o.home, credits.event ? markWarned(next, [credits.event]) : next);
4743
4842
  }
4744
4843
  if (credits.event)
4745
- posts = announce(o, [credits.event], now);
4844
+ posts = announce(o, [credits.event]);
4746
4845
  }
4747
4846
  } catch {}
4748
- const seatFragment = formatSeatFragment(window);
4847
+ const seatFragment = formatSeatFragment(window, now);
4749
4848
  let line;
4750
4849
  try {
4751
4850
  const meter2 = await loadMeter({
@@ -4778,7 +4877,7 @@ var rememberSeat = (o, window, session, now, input) => {
4778
4877
  if (credits.event)
4779
4878
  events.push(credits.event);
4780
4879
  state = markWarned(state, events);
4781
- posts.push(...announce(o, events, now));
4880
+ posts.push(...announce(o, events));
4782
4881
  writeSeatState(o.home, state);
4783
4882
  return posts;
4784
4883
  };
@@ -4792,29 +4891,19 @@ var creditsEvent = (o, prior, input, now) => {
4792
4891
  return { event: null, scan: next };
4793
4892
  return { event: { bar: "credits", stage: "hit", usedPct: 100, resetsAt: now }, scan: next };
4794
4893
  };
4795
- var announce = (o, events, now) => {
4894
+ var announce = (o, events) => {
4796
4895
  if (events.length === 0)
4797
4896
  return [];
4798
4897
  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;
4898
+ if (!src)
4899
+ return [];
4900
+ return events.map((e) => postSeatWall({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
4901
+ bar: e.bar,
4902
+ stage: e.stage,
4903
+ usedPct: e.usedPct,
4904
+ ...e.bar === "credits" ? {} : { resetsAt: e.resetsAt },
4905
+ notification: e.bar === "credits" ? "transcript" : "statusline"
4906
+ }));
4818
4907
  };
4819
4908
 
4820
4909
  // src/commands/uninstall.ts
@@ -4902,6 +4991,11 @@ var uninstall2 = (o) => {
4902
4991
  changed = true;
4903
4992
  removed.push(`hooks.Notification seat-event entry from ${settingsPath(o.home)}`);
4904
4993
  }
4994
+ if (hasPromptHook(settings)) {
4995
+ settings = removePromptHook(settings);
4996
+ changed = true;
4997
+ removed.push(`hooks.UserPromptSubmit prompt-event entry from ${settingsPath(o.home)}`);
4998
+ }
4905
4999
  if (hasSpecHook(settings)) {
4906
5000
  settings = removeSpecHook(settings);
4907
5001
  changed = true;
@@ -5184,6 +5278,8 @@ commands:
5184
5278
  statusline Print the one-line meter Claude Code shows (installed by init; 10s cache).
5185
5279
  hooks Register the Claude Code Notification hook for seat limits (init does this too).
5186
5280
  seat-event Claude Code hook: record a seat wall and say how to continue. Reads hook JSON on stdin.
5281
+ prompt-event Claude Code UserPromptSubmit hook: at 90 % of a seat bar the seat speaks up in the
5282
+ session (once per bar per reset); out of usage credits blocks the prompt once with the exits.
5187
5283
  spec-event Claude Code PostToolUse hook: when a spec-shaped markdown file is written, size it
5188
5284
  (POST /proxy/estimate with the file path as the project). Silent unless it estimates.
5189
5285
  --version Print the CLI version.
@@ -5236,6 +5332,7 @@ var sledsArgv = () => {
5236
5332
  var shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
5237
5333
  var statusLineCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "statusline"].map(shellQuote).join(" "), home);
5238
5334
  var seatEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "seat-event"].map(shellQuote).join(" "), home);
5335
+ var promptEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "prompt-event"].map(shellQuote).join(" "), home);
5239
5336
  var specEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "spec-event"].map(shellQuote).join(" "), home);
5240
5337
  var main = async (argv) => {
5241
5338
  const { cmd, flags } = parseArgs(argv);
@@ -5307,19 +5404,23 @@ var main = async (argv) => {
5307
5404
  prompt: promptStdin,
5308
5405
  statusLineCommand: statusLineCommand(sledsArgv()),
5309
5406
  seatEventCommand: seatEventCommand(sledsArgv()),
5310
- specEventCommand: specEventCommand(sledsArgv())
5407
+ specEventCommand: specEventCommand(sledsArgv()),
5408
+ promptEventCommand: promptEventCommand(sledsArgv())
5311
5409
  });
5312
5410
  }
5313
5411
  case "hooks":
5314
5412
  return hooks({
5315
5413
  home,
5316
5414
  seatEventCommand: seatEventCommand(sledsArgv()),
5317
- specEventCommand: specEventCommand(sledsArgv())
5415
+ specEventCommand: specEventCommand(sledsArgv()),
5416
+ promptEventCommand: promptEventCommand(sledsArgv())
5318
5417
  });
5319
5418
  case "seat-event":
5320
- return seatEvent({ home, platform: platform2(), exec: defaultExec });
5419
+ return seatEvent({ home });
5321
5420
  case "spec-event":
5322
5421
  return specEvent({ home });
5422
+ case "prompt-event":
5423
+ return promptEvent({ home });
5323
5424
  case "on":
5324
5425
  return on({
5325
5426
  home,
@@ -5352,7 +5453,7 @@ var main = async (argv) => {
5352
5453
  case "seat-sync":
5353
5454
  return seatSync({ home, argv: process.argv.slice(3) });
5354
5455
  case "statusline":
5355
- return statusline({ home, platform: platform2(), exec: defaultExec });
5456
+ return statusline({ home });
5356
5457
  case "help":
5357
5458
  case "--help":
5358
5459
  case "-h":
@@ -5361,7 +5462,7 @@ var main = async (argv) => {
5361
5462
  case "version":
5362
5463
  case "--version":
5363
5464
  case "-v":
5364
- console.log("sleds 0.1.18");
5465
+ console.log("sleds 0.1.19");
5365
5466
  return 0;
5366
5467
  default:
5367
5468
  console.error(`unknown command: ${cmd}
@@ -5390,6 +5491,7 @@ if (isMain()) {
5390
5491
  }
5391
5492
  export {
5392
5493
  parseArgs,
5494
+ promptEventCommand,
5393
5495
  seatEventCommand,
5394
5496
  specEventCommand,
5395
5497
  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.19",
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",