@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.
- package/dist/sleds.mjs +205 -103
- 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);
|
|
@@ -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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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]
|
|
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
|
|
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
|
|
4894
|
+
var announce = (o, events) => {
|
|
4796
4895
|
if (events.length === 0)
|
|
4797
4896
|
return [];
|
|
4798
4897
|
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;
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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",
|