@sleds/cli 0.1.22 → 0.1.23
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 +527 -140
- package/package.json +1 -1
package/dist/sleds.mjs
CHANGED
|
@@ -305,7 +305,26 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
|
|
|
305
305
|
if (!list.some(isSledsPromptHook))
|
|
306
306
|
return { ...settings };
|
|
307
307
|
return withEventHooks(settings, PROMPT_EVENT, list.filter((e) => !isSledsPromptHook(e)));
|
|
308
|
-
}, hasPromptHook = (settings) => eventHooks(settings, PROMPT_EVENT).some(isSledsPromptHook),
|
|
308
|
+
}, hasPromptHook = (settings) => eventHooks(settings, PROMPT_EVENT).some(isSledsPromptHook), SESSION_EVENT = "SessionStart", SESSION_EVENT_TIMEOUT_S = 3, STOP_EVENT = "Stop", STOP_EVENT_TIMEOUT_S = 3, isSledsHookFor = (sub) => (entry) => {
|
|
309
|
+
if (!isObject5(entry) || !Array.isArray(entry.hooks))
|
|
310
|
+
return false;
|
|
311
|
+
const re = new RegExp(`\\b${sub}\\b`);
|
|
312
|
+
return entry.hooks.some((h) => isObject5(h) && h.type === "command" && typeof h.command === "string" && re.test(h.command));
|
|
313
|
+
}, isSledsSessionHook, isSledsStopHook, sessionHookEntry = (command) => ({
|
|
314
|
+
hooks: [{ type: "command", command, timeout: SESSION_EVENT_TIMEOUT_S }]
|
|
315
|
+
}), stopHookEntry = (command) => ({
|
|
316
|
+
hooks: [{ type: "command", command, timeout: STOP_EVENT_TIMEOUT_S }]
|
|
317
|
+
}), applyOnce = (settings, event, ours, entry) => {
|
|
318
|
+
const list = eventHooks(settings, event);
|
|
319
|
+
const idx = list.findIndex(ours);
|
|
320
|
+
const next = idx === -1 ? [...list, entry] : list.map((e, i) => i === idx ? entry : e);
|
|
321
|
+
return withEventHooks(settings, event, next);
|
|
322
|
+
}, removeOurs = (settings, event, ours) => {
|
|
323
|
+
const list = eventHooks(settings, event);
|
|
324
|
+
if (!list.some(ours))
|
|
325
|
+
return { ...settings };
|
|
326
|
+
return withEventHooks(settings, event, list.filter((e) => !ours(e)));
|
|
327
|
+
}, applySessionHook = (settings, command) => applyOnce(settings, SESSION_EVENT, isSledsSessionHook, sessionHookEntry(command)), removeSessionHook = (settings) => removeOurs(settings, SESSION_EVENT, isSledsSessionHook), hasSessionHook = (settings) => eventHooks(settings, SESSION_EVENT).some(isSledsSessionHook), applyStopHook = (settings, command) => applyOnce(settings, STOP_EVENT, isSledsStopHook, stopHookEntry(command)), removeStopHook = (settings) => removeOurs(settings, STOP_EVENT, isSledsStopHook), hasStopHook = (settings) => eventHooks(settings, STOP_EVENT).some(isSledsStopHook), 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)}
|
|
309
328
|
`, hasClaudeCode = (home) => existsSync(claudeDir(home)), readSettings = (home) => {
|
|
310
329
|
const p2 = settingsPath(home);
|
|
311
330
|
if (!existsSync(p2))
|
|
@@ -338,7 +357,10 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
|
|
|
338
357
|
writeFileSync(tokenPath(home), `${token}
|
|
339
358
|
`, { mode: 384 });
|
|
340
359
|
};
|
|
341
|
-
var init_settings = () => {
|
|
360
|
+
var init_settings = __esm(() => {
|
|
361
|
+
isSledsSessionHook = isSledsHookFor("session-event");
|
|
362
|
+
isSledsStopHook = isSledsHookFor("stop-event");
|
|
363
|
+
});
|
|
342
364
|
|
|
343
365
|
// src/config.ts
|
|
344
366
|
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -565,17 +587,17 @@ __export(exports_seat, {
|
|
|
565
587
|
});
|
|
566
588
|
import {
|
|
567
589
|
closeSync as closeSync2,
|
|
568
|
-
existsSync as
|
|
569
|
-
mkdirSync as
|
|
590
|
+
existsSync as existsSync17,
|
|
591
|
+
mkdirSync as mkdirSync14,
|
|
570
592
|
openSync as openSync2,
|
|
571
|
-
readFileSync as
|
|
593
|
+
readFileSync as readFileSync14,
|
|
572
594
|
readSync as readSync2,
|
|
573
|
-
renameSync as
|
|
595
|
+
renameSync as renameSync7,
|
|
574
596
|
statSync as statSync3,
|
|
575
|
-
writeFileSync as
|
|
597
|
+
writeFileSync as writeFileSync14
|
|
576
598
|
} from "node:fs";
|
|
577
|
-
import { join as
|
|
578
|
-
var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_MS2 = 1500, STDIN_TIMEOUT_MS2 = 300,
|
|
599
|
+
import { join as join15 } from "node:path";
|
|
600
|
+
var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_MS2 = 1500, STDIN_TIMEOUT_MS2 = 300, isObject14 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num7 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, EPOCH_MS_THRESHOLD2 = 100000000000, parseResetsAt2 = (v) => {
|
|
579
601
|
const n = num7(v);
|
|
580
602
|
if (n !== undefined) {
|
|
581
603
|
if (n <= 0)
|
|
@@ -588,7 +610,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
588
610
|
}
|
|
589
611
|
return;
|
|
590
612
|
}, parseBar2 = (v) => {
|
|
591
|
-
if (!
|
|
613
|
+
if (!isObject14(v))
|
|
592
614
|
return;
|
|
593
615
|
const usedPct = num7(v.used_percentage) ?? num7(v.usedPct);
|
|
594
616
|
const resetsAt = parseResetsAt2(v.resets_at ?? v.resetsAt);
|
|
@@ -596,14 +618,14 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
596
618
|
return;
|
|
597
619
|
return { usedPct, resetsAt };
|
|
598
620
|
}, parseSeatWindow2 = (raw, capturedAt) => {
|
|
599
|
-
if (!
|
|
621
|
+
if (!isObject14(raw))
|
|
600
622
|
return null;
|
|
601
|
-
const limits =
|
|
623
|
+
const limits = isObject14(raw.rate_limits) ? raw.rate_limits : raw;
|
|
602
624
|
const fiveHour = parseBar2(limits.five_hour ?? limits.fiveHour);
|
|
603
625
|
const sevenDay = parseBar2(limits.seven_day ?? limits.sevenDay);
|
|
604
626
|
const spendLimit = parseBar2(limits.spend_limit ?? limits.spendLimit);
|
|
605
627
|
const models = {};
|
|
606
|
-
if (
|
|
628
|
+
if (isObject14(limits.models))
|
|
607
629
|
for (const [k, v] of Object.entries(limits.models)) {
|
|
608
630
|
const bar = parseBar2(v);
|
|
609
631
|
if (bar)
|
|
@@ -658,15 +680,15 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
658
680
|
return `⚠ seat ${hot[0]} ${verb}${SEP}resets ${reset}${SEP}/sled handoff · sleds defer`;
|
|
659
681
|
}
|
|
660
682
|
return `seat ${parts.join(SEP)}`;
|
|
661
|
-
}, APPROACHING_PCT2 = 90, seatStatePath2 = (home) =>
|
|
662
|
-
if (!
|
|
683
|
+
}, APPROACHING_PCT2 = 90, seatStatePath2 = (home) => join15(configDir(home), "seat.json"), parseWindowStored2 = (v) => {
|
|
684
|
+
if (!isObject14(v))
|
|
663
685
|
return;
|
|
664
686
|
const capturedAt = num7(v.capturedAt);
|
|
665
687
|
if (capturedAt === undefined)
|
|
666
688
|
return;
|
|
667
689
|
return parseSeatWindow2(v, capturedAt) ?? undefined;
|
|
668
690
|
}, parseSeatState2 = (raw) => {
|
|
669
|
-
if (!
|
|
691
|
+
if (!isObject14(raw))
|
|
670
692
|
return {};
|
|
671
693
|
const out = {};
|
|
672
694
|
const window = parseWindowStored2(raw.window);
|
|
@@ -675,7 +697,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
675
697
|
const lastPostedAt = num7(raw.lastPostedAt);
|
|
676
698
|
if (lastPostedAt !== undefined)
|
|
677
699
|
out.lastPostedAt = lastPostedAt;
|
|
678
|
-
if (
|
|
700
|
+
if (isObject14(raw.lastPosted)) {
|
|
679
701
|
const lp = {};
|
|
680
702
|
const f = num7(raw.lastPosted.fiveHour);
|
|
681
703
|
const s = num7(raw.lastPosted.sevenDay);
|
|
@@ -691,11 +713,11 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
691
713
|
const lastWallResetsAt = num7(raw.lastWallResetsAt);
|
|
692
714
|
if (lastWallResetsAt !== undefined)
|
|
693
715
|
out.lastWallResetsAt = lastWallResetsAt;
|
|
694
|
-
if (
|
|
716
|
+
if (isObject14(raw.warned)) {
|
|
695
717
|
const warned = {};
|
|
696
718
|
for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
|
|
697
719
|
const w = raw.warned[bar];
|
|
698
|
-
if (!
|
|
720
|
+
if (!isObject14(w))
|
|
699
721
|
continue;
|
|
700
722
|
const entry = {};
|
|
701
723
|
const ap = num7(w.approaching);
|
|
@@ -708,11 +730,11 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
708
730
|
}
|
|
709
731
|
out.warned = warned;
|
|
710
732
|
}
|
|
711
|
-
if (
|
|
733
|
+
if (isObject14(raw.told)) {
|
|
712
734
|
const told = {};
|
|
713
735
|
for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
|
|
714
736
|
const w = raw.told[bar];
|
|
715
|
-
if (!
|
|
737
|
+
if (!isObject14(w))
|
|
716
738
|
continue;
|
|
717
739
|
const entry = {};
|
|
718
740
|
const ap = num7(w.approaching);
|
|
@@ -725,7 +747,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
725
747
|
}
|
|
726
748
|
out.told = told;
|
|
727
749
|
}
|
|
728
|
-
if (
|
|
750
|
+
if (isObject14(raw.transcriptScan) && typeof raw.transcriptScan.path === "string") {
|
|
729
751
|
const offset = num7(raw.transcriptScan.offset);
|
|
730
752
|
const hitAt = num7(raw.transcriptScan.hitAt);
|
|
731
753
|
out.transcriptScan = {
|
|
@@ -737,9 +759,9 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
737
759
|
const session = parseSeatSession2(raw.session);
|
|
738
760
|
if (session)
|
|
739
761
|
out.session = session;
|
|
740
|
-
if (
|
|
762
|
+
if (isObject14(raw.cockpit) && raw.cockpit.kind === "cmux") {
|
|
741
763
|
const c = { kind: "cmux" };
|
|
742
|
-
if (
|
|
764
|
+
if (isObject14(raw.cockpit.lines)) {
|
|
743
765
|
const lines = {};
|
|
744
766
|
for (const [k, v] of Object.entries(raw.cockpit.lines))
|
|
745
767
|
if (typeof v === "string")
|
|
@@ -749,9 +771,9 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
749
771
|
if (raw.cockpit.progress === true)
|
|
750
772
|
c.progress = true;
|
|
751
773
|
const f = raw.cockpit.feed;
|
|
752
|
-
if (
|
|
774
|
+
if (isObject14(f) && num7(f.at) !== undefined) {
|
|
753
775
|
const strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
754
|
-
const j =
|
|
776
|
+
const j = isObject14(f.jobs) ? f.jobs : {};
|
|
755
777
|
c.feed = {
|
|
756
778
|
at: num7(f.at),
|
|
757
779
|
handoffs: strs(f.handoffs),
|
|
@@ -767,7 +789,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
767
789
|
}
|
|
768
790
|
out.cockpit = c;
|
|
769
791
|
}
|
|
770
|
-
if (
|
|
792
|
+
if (isObject14(raw.estimate)) {
|
|
771
793
|
const e = raw.estimate;
|
|
772
794
|
const project = typeof e.project === "string" ? e.project : undefined;
|
|
773
795
|
const [lo, hi, lm, hm, at] = [e.lowUsd, e.highUsd, e.lowMin, e.highMin, e.at].map(num7);
|
|
@@ -776,7 +798,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
776
798
|
}
|
|
777
799
|
return out;
|
|
778
800
|
}, parseSeatSession2 = (v) => {
|
|
779
|
-
if (!
|
|
801
|
+
if (!isObject14(v) || typeof v.id !== "string" || !v.id)
|
|
780
802
|
return;
|
|
781
803
|
const at = num7(v.at);
|
|
782
804
|
if (at === undefined)
|
|
@@ -793,12 +815,12 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
793
815
|
return null;
|
|
794
816
|
try {
|
|
795
817
|
const raw = JSON.parse(trimmed);
|
|
796
|
-
if (!
|
|
818
|
+
if (!isObject14(raw) || typeof raw.session_id !== "string" || !raw.session_id)
|
|
797
819
|
return null;
|
|
798
820
|
const out = { id: raw.session_id, at: now };
|
|
799
821
|
if (typeof raw.cwd === "string" && raw.cwd)
|
|
800
822
|
out.cwd = raw.cwd;
|
|
801
|
-
const model =
|
|
823
|
+
const model = isObject14(raw.model) ? raw.model.id : raw.model;
|
|
802
824
|
if (typeof model === "string" && model)
|
|
803
825
|
out.model = model;
|
|
804
826
|
return out;
|
|
@@ -819,21 +841,21 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
819
841
|
return pick.length ? Math.min(...pick) : undefined;
|
|
820
842
|
}, readSeatState2 = (home) => {
|
|
821
843
|
const p2 = seatStatePath2(home);
|
|
822
|
-
if (!
|
|
844
|
+
if (!existsSync17(p2))
|
|
823
845
|
return {};
|
|
824
846
|
try {
|
|
825
|
-
return parseSeatState2(JSON.parse(
|
|
847
|
+
return parseSeatState2(JSON.parse(readFileSync14(p2, "utf8")));
|
|
826
848
|
} catch {
|
|
827
849
|
return {};
|
|
828
850
|
}
|
|
829
851
|
}, writeSeatState2 = (home, state) => {
|
|
830
852
|
const dir = configDir(home);
|
|
831
|
-
|
|
853
|
+
mkdirSync14(dir, { recursive: true, mode: 448 });
|
|
832
854
|
const target = seatStatePath2(home);
|
|
833
855
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
834
|
-
|
|
856
|
+
writeFileSync14(tmp, `${JSON.stringify(state)}
|
|
835
857
|
`, { mode: 384 });
|
|
836
|
-
|
|
858
|
+
renameSync7(tmp, target);
|
|
837
859
|
}, pctOf2 = (b) => b?.usedPct, shouldPostSeat2 = (state, next, now) => {
|
|
838
860
|
if (state.lastPostedAt === undefined || !state.lastPosted)
|
|
839
861
|
return true;
|
|
@@ -900,7 +922,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
900
922
|
}, CREDITS_PATTERN2, creditsHitIn2 = (text) => CREDITS_PATTERN2.test(text), transcriptPathFromStdin2 = (text) => {
|
|
901
923
|
try {
|
|
902
924
|
const raw = JSON.parse(text.trim());
|
|
903
|
-
return
|
|
925
|
+
return isObject14(raw) && typeof raw.transcript_path === "string" && raw.transcript_path ? raw.transcript_path : null;
|
|
904
926
|
} catch {
|
|
905
927
|
return null;
|
|
906
928
|
}
|
|
@@ -935,6 +957,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
935
957
|
}
|
|
936
958
|
}, seatWindowBody2 = (w) => ({
|
|
937
959
|
capturedAt: w.capturedAt,
|
|
960
|
+
source: "claude",
|
|
938
961
|
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
939
962
|
...w.sevenDay ? { sevenDay: w.sevenDay } : {},
|
|
940
963
|
...w.spendLimit ? { spendLimit: w.spendLimit } : {}
|
|
@@ -951,7 +974,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
|
|
|
951
974
|
} catch {
|
|
952
975
|
return false;
|
|
953
976
|
}
|
|
954
|
-
}, postSeatWindow2 = (o2, w) => postJson2(o2, "/proxy/seat", seatWindowBody2(w)), postSeatWall2 = (o2, input) => postJson2(o2, "/proxy/seat-wall", input), SEAT_WALL_LINE2 = "Seat limit hit. sleds on --until-reset to continue on the platform key, or sleds defer to resume here at the reset.", SEAT_WALL_LINE_NO_KEY2 = "Seat limit hit. sleds defer resumes this session on this Mac at the reset; /sled 5 to hand off — this org has no platform key to switch to.", seatWallLine2 = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE2 : SEAT_WALL_LINE_NO_KEY2, readStdin2 = (timeoutMs, stdin = process.stdin) => new Promise((resolve6) => {
|
|
977
|
+
}, postSeatWindow2 = (o2, w) => postJson2(o2, "/proxy/seat", seatWindowBody2(w)), postSeatWall2 = (o2, input) => postJson2(o2, "/proxy/seat-wall", { source: "claude", ...input }), SEAT_WALL_LINE2 = "Seat limit hit. sleds on --until-reset to continue on the platform key, or sleds defer to resume here at the reset.", SEAT_WALL_LINE_NO_KEY2 = "Seat limit hit. sleds defer resumes this session on this Mac at the reset; /sled 5 to hand off — this org has no platform key to switch to.", seatWallLine2 = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE2 : SEAT_WALL_LINE_NO_KEY2, readStdin2 = (timeoutMs, stdin = process.stdin) => new Promise((resolve6) => {
|
|
955
978
|
if (stdin.isTTY) {
|
|
956
979
|
resolve6("");
|
|
957
980
|
return;
|
|
@@ -1178,7 +1201,7 @@ var GUIDES = [
|
|
|
1178
1201
|
},
|
|
1179
1202
|
{
|
|
1180
1203
|
do: "Mint a seat for each person",
|
|
1181
|
-
where: "Settings ›
|
|
1204
|
+
where: "Settings › People › `Mint a seat` under the person",
|
|
1182
1205
|
note: "A seat is one person's token at the proxy. It shows once; hand it to the person, never paste it in chat."
|
|
1183
1206
|
},
|
|
1184
1207
|
{
|
|
@@ -1259,7 +1282,7 @@ var GUIDES = [
|
|
|
1259
1282
|
{
|
|
1260
1283
|
do: "To keep every PR in sync: add the ledger workflow to each repo",
|
|
1261
1284
|
where: "`.github/workflows/sleds-ledger.yml` from the SLEDS repo, with a repo secret `SLEDS_TOKEN`",
|
|
1262
|
-
note: "Mint a seat for CI (Settings ›
|
|
1285
|
+
note: "Mint a seat for CI (Settings › People, under a service person) and store its token as the secret. Then every PR is sized when it opens and closed on merge with the `SLEDS-Actual:` line from its body — the est → actual column on the Sled. It comments on the PR; leave it out while strictly read-only."
|
|
1263
1286
|
},
|
|
1264
1287
|
{
|
|
1265
1288
|
do: "When you are ready to write: add a `sled` label to the repos",
|
|
@@ -2076,7 +2099,25 @@ function fetchClient(o2) {
|
|
|
2076
2099
|
};
|
|
2077
2100
|
}
|
|
2078
2101
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
2079
|
-
var SERVER_INFO = {
|
|
2102
|
+
var SERVER_INFO = {
|
|
2103
|
+
name: "sleds",
|
|
2104
|
+
title: "SLEDS",
|
|
2105
|
+
version: "0.1.23",
|
|
2106
|
+
description: "The cost and limits of AI agent work — estimate, hand off, and track what a task takes.",
|
|
2107
|
+
websiteUrl: "https://sleds.ai",
|
|
2108
|
+
icons: [
|
|
2109
|
+
{ src: "https://agent.sleds.ai/icon.png", mimeType: "image/png", sizes: ["192x192"] },
|
|
2110
|
+
{ src: "https://agent.sleds.ai/icon.svg", mimeType: "image/svg+xml", sizes: ["any"] }
|
|
2111
|
+
]
|
|
2112
|
+
};
|
|
2113
|
+
var SERVER_INSTRUCTIONS = [
|
|
2114
|
+
"SLEDS is the ledger of engineering work: every piece is sized before it runs (a cost and time band, never a point figure) and closed with a measured actual after. You are the head; SLEDS holds the record. The person may be a PM or designer describing something they want built, or an engineer about to build — read which from the conversation.",
|
|
2115
|
+
'When someone describes work for engineering (a feature, a fix, a spec, a PRD, "can we…"): offer once to put it on the sled. If they say yes, use `intake` — it drafts the brief, asks only what it cannot answer (who it is for and why now, what done looks like, the smallest version, the worth) two questions at a time, and writes nothing until they confirm. Never guess worth: leave it blank and say so. Afterwards tell them in plain words that it is on the sled, the band, and whether it waits for product\'s yes. They never need to learn a verb.',
|
|
2116
|
+
"When you are about to build something that will take more than a few minutes: `status` first if a row may already exist for it; otherwise `estimate` with a stable `source` (kind · project · doc · item) before the first line of work, and say the band. When it lands: `actual` with the same `source`, the state, where the output lives (`ref`), the model, and your session link (`sessionId`). Do not pass `minutes` unless a person stated them — SLEDS measures the time itself. A follow-up to something already landed is a new `item` under the same doc, never an overwrite. An `actual` with no estimate still records — the row is created and shown as landed unsized — but the band you skipped is the point, so estimate first.",
|
|
2117
|
+
"If SLEDS says there is no estimate for the source, size it first and send the actual again. If it says unreachable, try once more; if that fails, move on and tell the person the row is still owed. Never invent a number to get past an error."
|
|
2118
|
+
].join(`
|
|
2119
|
+
|
|
2120
|
+
`);
|
|
2080
2121
|
var MCP_TIMEOUT_MS = 5000;
|
|
2081
2122
|
var sourceSchema = {
|
|
2082
2123
|
type: "object",
|
|
@@ -2116,12 +2157,12 @@ var TOOLS = [
|
|
|
2116
2157
|
},
|
|
2117
2158
|
{
|
|
2118
2159
|
name: "estimate",
|
|
2119
|
-
description: "
|
|
2160
|
+
description: "Call this BEFORE starting any build that will take more than a few minutes — before the first line of work, not after. Sizes the task: the org estimator's cost and time band with confidence and a spend cap, never a point figure. Pass a stable `source` (kind · project · doc · item) so the row has a home on the sled and the later `actual` finds it. Recorded, so actuals calibrate the next estimate.",
|
|
2120
2161
|
inputSchema: specSchema
|
|
2121
2162
|
},
|
|
2122
2163
|
{
|
|
2123
2164
|
name: "intake",
|
|
2124
|
-
description: "
|
|
2165
|
+
description: 'Use this when a person describes work they want engineering to do — a feature, a fix, a spec, a PRD, "can we…" — and has said yes to putting it on the sled. Sends the ask to engineering from this conversation (spec 13). Pass what the person sent — a paragraph, a spec, a PRD — plus any links. Without `confirm` nothing is written: you get the brief drafted from it and the questions still open (who it is for and why now, what done looks like, the smallest version, the worth, what it touches) — ask at most two at a time, in your own words, and skip any the person already answered. Put their answers in `answers`, show them the brief, and when they say yes call again with `confirm: true`: the row is filed on the sled, sized, with their name on it. A brief without a why and a done is a draft and will not file. Worth is never guessed.',
|
|
2125
2166
|
inputSchema: {
|
|
2126
2167
|
type: "object",
|
|
2127
2168
|
properties: {
|
|
@@ -2243,7 +2284,7 @@ var TOOLS = [
|
|
|
2243
2284
|
},
|
|
2244
2285
|
{
|
|
2245
2286
|
name: "status",
|
|
2246
|
-
description: "Where is it —
|
|
2287
|
+
description: "Where is it — call this first when you may be picking up work that already has a row (a ticket key, a spec section, a hand-off), so you estimate nothing twice. One paragraph per piece of work: its stage on the sled (waiting for product · deferred · ready · on the sled · landed), who has it, the size band, the worth, the tree when it has milestones, and the PR when it landed. Pass q (a tracker key like ENG-482, or a word from the title); with nothing, the rows you filed through intake, newest first.",
|
|
2247
2288
|
inputSchema: {
|
|
2248
2289
|
type: "object",
|
|
2249
2290
|
properties: {
|
|
@@ -2298,7 +2339,7 @@ var TOOLS = [
|
|
|
2298
2339
|
},
|
|
2299
2340
|
{
|
|
2300
2341
|
name: "actual",
|
|
2301
|
-
description: "
|
|
2342
|
+
description: "Call this WHEN THE WORK LANDS (merged, shipped, done) — the last thing you do on a build, every time, with the same `source` the estimate was filed under. Records what happened: state (started · done · merged · shipped), where the output lives (`ref`: PR url, commit, doc), the model, who did it, dollars and tokens when something counted them, time by phase. Minutes are MEASURED by SLEDS from the pickup (or the estimate) to now — do not invent a number; pass `minutes` only when a person stated it. Pass `sessionId` (your session link) so the session's token spend joins the row. If SLEDS answers that no estimate is on file, call `estimate` with this source first, then send this again.",
|
|
2302
2343
|
inputSchema: {
|
|
2303
2344
|
type: "object",
|
|
2304
2345
|
properties: {
|
|
@@ -2317,7 +2358,14 @@ var TOOLS = [
|
|
|
2317
2358
|
},
|
|
2318
2359
|
required: ["kind", "project"]
|
|
2319
2360
|
},
|
|
2320
|
-
minutes: {
|
|
2361
|
+
minutes: {
|
|
2362
|
+
type: "number",
|
|
2363
|
+
description: "Only when a person stated the minutes or you timed them with a clock. Otherwise leave it out: SLEDS measures from the pickup (or the estimate) to now and records that; a number you pass is kept beside it as 'said'. On a row that already landed, a number here corrects the record."
|
|
2364
|
+
},
|
|
2365
|
+
sessionId: {
|
|
2366
|
+
type: "string",
|
|
2367
|
+
description: "This session's id or link (the `Claude-Session:` line you are told to sign with, e.g. https://claude.ai/code/session_…). Lets the session's token spend join the row."
|
|
2368
|
+
},
|
|
2321
2369
|
usd: {
|
|
2322
2370
|
type: ["number", "null"],
|
|
2323
2371
|
description: "Dollars spent on the platform key, when known. Pass null to clear a figure recorded by mistake (seat work carries seatCreditsUsd, never usd)."
|
|
@@ -2406,6 +2454,30 @@ var isObject4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
|
2406
2454
|
var num3 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
2407
2455
|
var str4 = (v) => typeof v === "string" ? v : undefined;
|
|
2408
2456
|
var errorMessage = (j) => isObject4(j?.error) ? str4(j.error.message) : undefined;
|
|
2457
|
+
async function postSaying(ctx, path, body, nothing) {
|
|
2458
|
+
const r = await ctx.client.postFull(path, body);
|
|
2459
|
+
if (!r)
|
|
2460
|
+
return { fail: { text: `${nothing} ${UNREACHABLE.text}`, isError: true } };
|
|
2461
|
+
const j = r.json ?? {};
|
|
2462
|
+
if (r.status >= 200 && r.status < 300)
|
|
2463
|
+
return { json: j };
|
|
2464
|
+
if (r.status >= 500)
|
|
2465
|
+
return {
|
|
2466
|
+
fail: {
|
|
2467
|
+
text: `${nothing} ${errorMessage(j) ?? "Control plane unreachable"} (HTTP ${r.status}) → try once more in a moment, then move on and say so.`,
|
|
2468
|
+
isError: true
|
|
2469
|
+
}
|
|
2470
|
+
};
|
|
2471
|
+
const why = errorMessage(j) ?? str4(j.reason) ?? `HTTP ${r.status}`;
|
|
2472
|
+
return { fail: { text: `${nothing} ${why}.`, isError: true } };
|
|
2473
|
+
}
|
|
2474
|
+
function sourceText(source) {
|
|
2475
|
+
return [
|
|
2476
|
+
`${str4(source.kind) ?? "?"} ${str4(source.project) ?? "?"}`,
|
|
2477
|
+
str4(source.doc),
|
|
2478
|
+
str4(source.item)
|
|
2479
|
+
].filter(Boolean).join(" › ");
|
|
2480
|
+
}
|
|
2409
2481
|
async function usageTool(ctx) {
|
|
2410
2482
|
if (ctx.usage)
|
|
2411
2483
|
return ctx.usage();
|
|
@@ -2440,13 +2512,10 @@ async function estimateTool(ctx, args) {
|
|
|
2440
2512
|
if (!spec)
|
|
2441
2513
|
return { text: "Nothing to estimate. Pass spec: what the job should do.", isError: true };
|
|
2442
2514
|
const source = isObject4(args.source) && str4(args.source.kind) && str4(args.source.project) ? args.source : undefined;
|
|
2443
|
-
const
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
});
|
|
2448
|
-
if (!r)
|
|
2449
|
-
return UNREACHABLE;
|
|
2515
|
+
const res = await postSaying(ctx, "/proxy/estimate", { spec, model: str4(args.model), ...source ? { source } : {} }, "Nothing was estimated.");
|
|
2516
|
+
if ("fail" in res)
|
|
2517
|
+
return res.fail;
|
|
2518
|
+
const r = res.json;
|
|
2450
2519
|
const band = isObject4(r.band) ? r.band : {};
|
|
2451
2520
|
const line = bandLine(band) ?? "band unreadable";
|
|
2452
2521
|
const method = str4(band.method) ?? "plan";
|
|
@@ -2590,9 +2659,10 @@ async function overnightTool(ctx, args) {
|
|
|
2590
2659
|
const spec = str4(args.spec)?.trim();
|
|
2591
2660
|
if (!spec)
|
|
2592
2661
|
return { text: "Nothing to queue. Pass spec: what the job should do.", isError: true };
|
|
2593
|
-
const
|
|
2594
|
-
if (
|
|
2595
|
-
return
|
|
2662
|
+
const res = await postSaying(ctx, "/proxy/overnight", { spec, model: str4(args.model) }, "Could not queue the job.");
|
|
2663
|
+
if ("fail" in res)
|
|
2664
|
+
return res.fail;
|
|
2665
|
+
const r = res.json;
|
|
2596
2666
|
const jobId = str4(r.jobId) ?? "unknown";
|
|
2597
2667
|
const now = (ctx.now ?? Date.now)();
|
|
2598
2668
|
const at = num3(r.scheduledAt);
|
|
@@ -2919,13 +2989,14 @@ async function deferTool(ctx, args) {
|
|
|
2919
2989
|
const at = atRaw ? parseAt(atRaw, now) : undefined;
|
|
2920
2990
|
if (atRaw && at === undefined)
|
|
2921
2991
|
return { text: "`at` takes +2h, +90m or an ISO time.", isError: true };
|
|
2922
|
-
const
|
|
2992
|
+
const res = await postSaying(ctx, "/proxy/defer", {
|
|
2923
2993
|
...at !== undefined ? { resetAt: at } : {},
|
|
2924
2994
|
...str4(args.model) ? { model: str4(args.model) } : {},
|
|
2925
2995
|
host: "mcp"
|
|
2926
|
-
});
|
|
2927
|
-
if (
|
|
2928
|
-
return
|
|
2996
|
+
}, "Nothing was deferred.");
|
|
2997
|
+
if ("fail" in res)
|
|
2998
|
+
return res.fail;
|
|
2999
|
+
const r = res.json;
|
|
2929
3000
|
const jobId = str4(r.jobId) ?? "unknown";
|
|
2930
3001
|
const when = num3(r.scheduledAt);
|
|
2931
3002
|
const startsIn = when !== undefined && when > now + 60000 ? `starts in ${shortDuration(when - now)}` : "starts at the next musher tick (within 5 min)";
|
|
@@ -2933,6 +3004,13 @@ async function deferTool(ctx, args) {
|
|
|
2933
3004
|
text: `Deferred. Job ${jobId} ${startsIn}. With no platform key it runs on the seat's own Mac (sleds musher must be installed there — sleds doctor checks); with a key, in the cloud. Watch it with the jobs tool or /sled jobs.${str4(args.task) ? ` Note kept: ${str4(args.task)}` : ""}`
|
|
2934
3005
|
};
|
|
2935
3006
|
}
|
|
3007
|
+
function sessionIdOf(v) {
|
|
3008
|
+
const s = str4(v)?.trim();
|
|
3009
|
+
if (!s)
|
|
3010
|
+
return;
|
|
3011
|
+
const m = s.match(/(session_[A-Za-z0-9]+)/);
|
|
3012
|
+
return m?.[1] ?? (/^[A-Za-z0-9_-]{8,}$/.test(s) ? s : undefined);
|
|
3013
|
+
}
|
|
2936
3014
|
async function actualTool(ctx, args) {
|
|
2937
3015
|
if (!ctx.client)
|
|
2938
3016
|
return NOT_SET_UP;
|
|
@@ -2955,32 +3033,54 @@ async function actualTool(ctx, args) {
|
|
|
2955
3033
|
...num3(args.tokensIn) !== undefined ? { tokensIn: num3(args.tokensIn) } : {},
|
|
2956
3034
|
...num3(args.tokensOut) !== undefined ? { tokensOut: num3(args.tokensOut) } : {},
|
|
2957
3035
|
...str4(args.by) ? { by: str4(args.by) } : {},
|
|
3036
|
+
...sessionIdOf(args.sessionId) ? { sessionId: sessionIdOf(args.sessionId) } : {},
|
|
2958
3037
|
...str4(args.ref) ? { ref: str4(args.ref) } : {},
|
|
2959
3038
|
...phases && Object.keys(phases).length > 0 ? { phases } : {},
|
|
2960
3039
|
...num3(args.seatCreditsUsd) !== undefined ? { seatCreditsUsd: num3(args.seatCreditsUsd) } : {}
|
|
2961
3040
|
};
|
|
2962
|
-
const
|
|
3041
|
+
const body = {
|
|
2963
3042
|
source,
|
|
2964
|
-
...num3(args.minutes) !== undefined ? {
|
|
3043
|
+
...num3(args.minutes) !== undefined ? { saidMinutes: num3(args.minutes) } : {},
|
|
2965
3044
|
...num3(args.usd) !== undefined ? { actualUsd: num3(args.usd) } : {},
|
|
2966
3045
|
...args.usd === null ? { actualUsd: null } : {},
|
|
2967
3046
|
...str4(args.state) ? { state: str4(args.state) } : {},
|
|
2968
3047
|
...Object.keys(work).length > 0 ? { work } : {}
|
|
2969
|
-
}
|
|
2970
|
-
|
|
3048
|
+
};
|
|
3049
|
+
const res = await ctx.client.postFull("/proxy/estimate/actual", body);
|
|
3050
|
+
if (!res)
|
|
2971
3051
|
return { text: `Nothing recorded. ${UNREACHABLE.text}`, isError: true };
|
|
2972
|
-
|
|
3052
|
+
const r = res.json ?? {};
|
|
3053
|
+
if (res.status === 404 || r.ok === false) {
|
|
3054
|
+
const src = isObject4(source) ? sourceText(source) : "that source";
|
|
2973
3055
|
return {
|
|
2974
|
-
text: `
|
|
3056
|
+
text: `Nothing recorded: no estimate on file for ${src}. Size it first — call estimate with the same source and a one-line spec of what was built — then send this actual again; SLEDS will measure the time from that estimate. Do not invent minutes to get past this.`,
|
|
2975
3057
|
isError: true
|
|
2976
3058
|
};
|
|
3059
|
+
}
|
|
3060
|
+
if (res.status >= 500)
|
|
3061
|
+
return {
|
|
3062
|
+
text: `Nothing recorded. ${errorMessage(r) ?? "Control plane unreachable"} (HTTP ${res.status}) → try once more in a moment, then move on and say the actual is still owed.`,
|
|
3063
|
+
isError: true
|
|
3064
|
+
};
|
|
3065
|
+
if (res.status < 200 || res.status >= 300)
|
|
3066
|
+
return {
|
|
3067
|
+
text: `Nothing recorded: ${errorMessage(r) ?? `HTTP ${res.status}`}.`,
|
|
3068
|
+
isError: true
|
|
3069
|
+
};
|
|
3070
|
+
const recorded = num3(r.minutes);
|
|
3071
|
+
const from = str4(r.elapsedFrom);
|
|
3072
|
+
const unsized = r.unsized === true;
|
|
2977
3073
|
const bits = [
|
|
2978
|
-
num3(
|
|
3074
|
+
recorded !== undefined ? `${recorded} min${from ? ` measured from the ${from}` : ""}${num3(r.saidMinutes) !== undefined && num3(r.saidMinutes) !== recorded ? ` (you said ${num3(r.saidMinutes)})` : ""}` : phases ? "phases recorded" : null,
|
|
2979
3075
|
num3(args.usd) !== undefined ? usd2(num3(args.usd)) : args.usd === null ? "$ cleared" : null,
|
|
2980
3076
|
num3(args.tokensIn) !== undefined || num3(args.tokensOut) !== undefined ? `${(num3(args.tokensIn) ?? 0) + (num3(args.tokensOut) ?? 0)} tokens` : null,
|
|
2981
3077
|
str4(args.model),
|
|
2982
3078
|
str4(args.state) ?? "done"
|
|
2983
3079
|
].filter(Boolean);
|
|
3080
|
+
if (unsized)
|
|
3081
|
+
return {
|
|
3082
|
+
text: `Recorded: ${bits.join(" · ")}. No estimate came first, so the row was created from this actual and the Sled shows it as landed unsized — the time could only be measured from now, so pass \`minutes\` only if a person states them. Next time, size before you start: \`estimate\` with this source before the first line.`
|
|
3083
|
+
};
|
|
2984
3084
|
return { text: `Recorded: ${bits.join(" · ")}. Projects shows it beside the estimate.` };
|
|
2985
3085
|
}
|
|
2986
3086
|
async function buddyTool(ctx, args) {
|
|
@@ -3089,7 +3189,8 @@ async function handleLine(line, ctx) {
|
|
|
3089
3189
|
return response(id, {
|
|
3090
3190
|
protocolVersion: requested ?? PROTOCOL_VERSION,
|
|
3091
3191
|
capabilities: { tools: {} },
|
|
3092
|
-
serverInfo: SERVER_INFO
|
|
3192
|
+
serverInfo: SERVER_INFO,
|
|
3193
|
+
instructions: SERVER_INSTRUCTIONS
|
|
3093
3194
|
});
|
|
3094
3195
|
}
|
|
3095
3196
|
case "ping":
|
|
@@ -3872,6 +3973,7 @@ var scanTranscriptForCredits = (path, prior) => {
|
|
|
3872
3973
|
};
|
|
3873
3974
|
var seatWindowBody = (w) => ({
|
|
3874
3975
|
capturedAt: w.capturedAt,
|
|
3976
|
+
source: "claude",
|
|
3875
3977
|
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
3876
3978
|
...w.sevenDay ? { sevenDay: w.sevenDay } : {},
|
|
3877
3979
|
...w.spendLimit ? { spendLimit: w.spendLimit } : {}
|
|
@@ -3891,7 +3993,7 @@ var postJson = async (o2, path, body) => {
|
|
|
3891
3993
|
}
|
|
3892
3994
|
};
|
|
3893
3995
|
var postSeatWindow = (o2, w) => postJson(o2, "/proxy/seat", seatWindowBody(w));
|
|
3894
|
-
var postSeatWall = (o2, input) => postJson(o2, "/proxy/seat-wall", input);
|
|
3996
|
+
var postSeatWall = (o2, input) => postJson(o2, "/proxy/seat-wall", { source: "claude", ...input });
|
|
3895
3997
|
var SEAT_WALL_LINE = "Seat limit hit. sleds on --until-reset to continue on the platform key, or sleds defer to resume here at the reset.";
|
|
3896
3998
|
var SEAT_WALL_LINE_NO_KEY = "Seat limit hit. sleds defer resumes this session on this Mac at the reset; /sled 5 to hand off — this org has no platform key to switch to.";
|
|
3897
3999
|
var seatWallLine = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE : SEAT_WALL_LINE_NO_KEY;
|
|
@@ -4507,11 +4609,14 @@ async function runDoctor(o2) {
|
|
|
4507
4609
|
if (settings) {
|
|
4508
4610
|
const seat2 = hasSeatHook(settings);
|
|
4509
4611
|
const spec = hasSpecHook(settings);
|
|
4612
|
+
const session = hasSessionHook(settings);
|
|
4613
|
+
const stop = hasStopHook(settings);
|
|
4614
|
+
const all = seat2 && spec && session && stop;
|
|
4510
4615
|
checks.push({
|
|
4511
|
-
level:
|
|
4616
|
+
level: all ? "ok" : "warn",
|
|
4512
4617
|
name: "hooks",
|
|
4513
|
-
detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"}`,
|
|
4514
|
-
fix:
|
|
4618
|
+
detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"} · session-event ${session ? "on" : "missing"} · stop-event ${stop ? "on" : "missing"}`,
|
|
4619
|
+
fix: all ? undefined : "sleds hooks"
|
|
4515
4620
|
});
|
|
4516
4621
|
const want = env.SLEDS_HOME?.trim() ? resolve3(home, env.SLEDS_HOME.trim()) : undefined;
|
|
4517
4622
|
const cmds = sledsCommandsIn(settings);
|
|
@@ -4638,6 +4743,88 @@ var doctor = async (o2) => {
|
|
|
4638
4743
|
|
|
4639
4744
|
// src/mcp.ts
|
|
4640
4745
|
init_meter();
|
|
4746
|
+
|
|
4747
|
+
// src/owed.ts
|
|
4748
|
+
init_config();
|
|
4749
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
4750
|
+
import { dirname as dirname4, join as join8 } from "node:path";
|
|
4751
|
+
var owedPath = (home) => join8(configDir(home), "owed.json");
|
|
4752
|
+
var sessionPath = (home) => join8(configDir(home), "session.json");
|
|
4753
|
+
var OWED_TTL_MS = 3 * 24 * 60 * 60000;
|
|
4754
|
+
var str5 = (v) => typeof v === "string" ? v : undefined;
|
|
4755
|
+
function sourceKey(s) {
|
|
4756
|
+
const trim = (x) => x.trim().slice(0, 120);
|
|
4757
|
+
const kind = str5(s.kind) ?? "manual";
|
|
4758
|
+
const project = trim(str5(s.project) ?? "");
|
|
4759
|
+
const doc = str5(s.doc);
|
|
4760
|
+
const item = str5(s.item);
|
|
4761
|
+
return `${kind}:${project}${doc ? `/${trim(doc)}` : ""}${item ? `#${trim(item)}` : ""}`;
|
|
4762
|
+
}
|
|
4763
|
+
function sourceText2(s) {
|
|
4764
|
+
return [`${str5(s.kind) ?? "?"} ${str5(s.project) ?? "?"}`, str5(s.doc), str5(s.item)].filter(Boolean).join(" › ");
|
|
4765
|
+
}
|
|
4766
|
+
var readJson = (p2) => {
|
|
4767
|
+
if (!existsSync9(p2))
|
|
4768
|
+
return null;
|
|
4769
|
+
try {
|
|
4770
|
+
return JSON.parse(readFileSync8(p2, "utf8"));
|
|
4771
|
+
} catch {
|
|
4772
|
+
return null;
|
|
4773
|
+
}
|
|
4774
|
+
};
|
|
4775
|
+
var writeJson = (p2, v) => {
|
|
4776
|
+
mkdirSync7(dirname4(p2), { recursive: true, mode: 448 });
|
|
4777
|
+
const tmp = `${p2}.${process.pid}.tmp`;
|
|
4778
|
+
writeFileSync7(tmp, `${JSON.stringify(v, null, 2)}
|
|
4779
|
+
`, { mode: 384 });
|
|
4780
|
+
renameSync5(tmp, p2);
|
|
4781
|
+
};
|
|
4782
|
+
var readOwed = (home, now = Date.now()) => {
|
|
4783
|
+
const f = readJson(owedPath(home));
|
|
4784
|
+
const rows = Array.isArray(f?.rows) ? f.rows : [];
|
|
4785
|
+
return rows.filter((r) => r && typeof r.key === "string" && now - r.ts < OWED_TTL_MS);
|
|
4786
|
+
};
|
|
4787
|
+
var writeOwed = (home, rows) => writeJson(owedPath(home), { rows });
|
|
4788
|
+
var readSession = (home) => {
|
|
4789
|
+
const s = readJson(sessionPath(home));
|
|
4790
|
+
return s && typeof s.sessionId === "string" ? s : null;
|
|
4791
|
+
};
|
|
4792
|
+
var writeSession = (home, s) => writeJson(sessionPath(home), s);
|
|
4793
|
+
function withEstimated(rows, a) {
|
|
4794
|
+
const key = sourceKey(a.source);
|
|
4795
|
+
const row2 = {
|
|
4796
|
+
key,
|
|
4797
|
+
source: a.source,
|
|
4798
|
+
...a.title ? { title: a.title } : {},
|
|
4799
|
+
ts: a.now,
|
|
4800
|
+
...a.sessionId ? { sessionId: a.sessionId } : {}
|
|
4801
|
+
};
|
|
4802
|
+
return [...rows.filter((r) => r.key !== key), row2];
|
|
4803
|
+
}
|
|
4804
|
+
function withActual(rows, a) {
|
|
4805
|
+
if (a.state === "started")
|
|
4806
|
+
return rows;
|
|
4807
|
+
const key = sourceKey(a.source);
|
|
4808
|
+
return rows.filter((r) => r.key !== key);
|
|
4809
|
+
}
|
|
4810
|
+
function owedBySession(rows, sessionId) {
|
|
4811
|
+
return rows.filter((r) => r.sessionId === sessionId && r.askedAt === undefined);
|
|
4812
|
+
}
|
|
4813
|
+
function markAsked(rows, keys, now) {
|
|
4814
|
+
const set = new Set(keys);
|
|
4815
|
+
return rows.map((r) => set.has(r.key) ? { ...r, askedAt: now } : r);
|
|
4816
|
+
}
|
|
4817
|
+
var ago = (ms) => {
|
|
4818
|
+
const m = Math.round(ms / 60000);
|
|
4819
|
+
if (m < 1)
|
|
4820
|
+
return "just now";
|
|
4821
|
+
if (m < 60)
|
|
4822
|
+
return `${m} min ago`;
|
|
4823
|
+
const h = Math.round(m / 60);
|
|
4824
|
+
return h < 48 ? `${h} h ago` : `${Math.round(h / 24)} d ago`;
|
|
4825
|
+
};
|
|
4826
|
+
|
|
4827
|
+
// src/mcp.ts
|
|
4641
4828
|
async function usageTool2(deps) {
|
|
4642
4829
|
if (!meterSource(deps.home))
|
|
4643
4830
|
return { text: "SLEDS is not set up on this machine. Run `sleds init` first.", isError: true };
|
|
@@ -4661,9 +4848,49 @@ async function usageTool2(deps) {
|
|
|
4661
4848
|
return { text: lines.join(`
|
|
4662
4849
|
`) };
|
|
4663
4850
|
}
|
|
4851
|
+
var isObj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4852
|
+
function trackingClient(client, home, now) {
|
|
4853
|
+
const after = (path, body, status) => {
|
|
4854
|
+
if (status < 200 || status >= 300 || !isObj2(body.source))
|
|
4855
|
+
return;
|
|
4856
|
+
try {
|
|
4857
|
+
if (path === "/proxy/estimate") {
|
|
4858
|
+
const spec = typeof body.spec === "string" ? body.spec : "";
|
|
4859
|
+
writeOwed(home, withEstimated(readOwed(home, now()), {
|
|
4860
|
+
source: body.source,
|
|
4861
|
+
title: spec.split(`
|
|
4862
|
+
`)[0]?.trim().slice(0, 120) || undefined,
|
|
4863
|
+
now: now(),
|
|
4864
|
+
sessionId: readSession(home)?.sessionId
|
|
4865
|
+
}));
|
|
4866
|
+
} else if (path === "/proxy/estimate/actual") {
|
|
4867
|
+
writeOwed(home, withActual(readOwed(home, now()), {
|
|
4868
|
+
source: body.source,
|
|
4869
|
+
state: typeof body.state === "string" ? body.state : undefined
|
|
4870
|
+
}));
|
|
4871
|
+
}
|
|
4872
|
+
} catch {}
|
|
4873
|
+
};
|
|
4874
|
+
return {
|
|
4875
|
+
async post(path, body) {
|
|
4876
|
+
const r = await client.post(path, body);
|
|
4877
|
+
if (r)
|
|
4878
|
+
after(path, body, 200);
|
|
4879
|
+
return r;
|
|
4880
|
+
},
|
|
4881
|
+
async postFull(path, body) {
|
|
4882
|
+
const r = await client.postFull(path, body);
|
|
4883
|
+
if (r)
|
|
4884
|
+
after(path, body, r.status);
|
|
4885
|
+
return r;
|
|
4886
|
+
},
|
|
4887
|
+
get: (path) => client.get(path)
|
|
4888
|
+
};
|
|
4889
|
+
}
|
|
4664
4890
|
function contextFor(deps) {
|
|
4665
4891
|
const src = meterSource(deps.home);
|
|
4666
|
-
const
|
|
4892
|
+
const now = deps.now ?? Date.now;
|
|
4893
|
+
const client = src ? trackingClient(fetchClient({ ...src, fetchFn: deps.fetchFn, timeoutMs: deps.timeoutMs }), deps.home, now) : null;
|
|
4667
4894
|
return { client, now: deps.now, usage: () => usageTool2(deps) };
|
|
4668
4895
|
}
|
|
4669
4896
|
var callTool2 = (name, args, deps) => callTool(name, args, contextFor(deps));
|
|
@@ -4710,7 +4937,10 @@ var hooks = (o2) => {
|
|
|
4710
4937
|
const hadSpec = hasSpecHook(settings);
|
|
4711
4938
|
const withSeat = applySeatHook(settings, o2.seatEventCommand ?? sledsCommand("seat-event", o2.home, o2.env));
|
|
4712
4939
|
const hadPrompt = hasPromptHook(settings);
|
|
4713
|
-
const
|
|
4940
|
+
const hadSession = hasSessionHook(settings);
|
|
4941
|
+
const hadStop = hasStopHook(settings);
|
|
4942
|
+
const withPrompt = applyPromptHook(applySpecHook(withSeat, o2.specEventCommand ?? sledsCommand("spec-event", o2.home, o2.env)), o2.promptEventCommand ?? sledsCommand("prompt-event", o2.home, o2.env));
|
|
4943
|
+
const withSpec = applyStopHook(applySessionHook(withPrompt, o2.sessionEventCommand ?? sledsCommand("session-event", o2.home, o2.env)), o2.stopEventCommand ?? sledsCommand("stop-event", o2.home, o2.env));
|
|
4714
4944
|
const hadLine = o2.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
|
|
4715
4945
|
writeSettings(o2.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o2.home, o2.env)).settings : withSpec);
|
|
4716
4946
|
log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o2.home)}`);
|
|
@@ -4718,24 +4948,35 @@ var hooks = (o2) => {
|
|
|
4718
4948
|
log(`refreshed statusLine in ${settingsPath(o2.home)}`);
|
|
4719
4949
|
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o2.home)}`);
|
|
4720
4950
|
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o2.home)}`);
|
|
4951
|
+
log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(o2.home)}`);
|
|
4952
|
+
log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(o2.home)}`);
|
|
4721
4953
|
return 0;
|
|
4722
4954
|
};
|
|
4723
4955
|
|
|
4724
4956
|
// src/commands/init.ts
|
|
4725
4957
|
init_config();
|
|
4726
4958
|
init_settings();
|
|
4727
|
-
import { mkdirSync as
|
|
4959
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
4728
4960
|
import { createInterface } from "node:readline";
|
|
4729
4961
|
|
|
4730
4962
|
// src/skill.ts
|
|
4731
4963
|
var SKILL_MD = `---
|
|
4732
4964
|
name: sled
|
|
4733
|
-
description:
|
|
4965
|
+
description: The SLEDS loop for engineering work — size a build before starting it, record the actual when it lands, and hand work to SLEDS past a rate limit or credit wall (continue on the org's key, hold until reset, run overnight at half price). Use when the user says /sled, is about to start a build, picks up a ticket or a spec section, merges or ships something, hits a usage limit, mentions their window reset, asks what ran overnight, or asks to defer or schedule agent work.
|
|
4734
4966
|
---
|
|
4735
4967
|
|
|
4736
4968
|
# SLEDS
|
|
4737
4969
|
|
|
4738
|
-
|
|
4970
|
+
SLEDS is the ledger of engineering work: every piece is sized before it runs and closed with a measured actual after. In this session the seat is the person's; SLEDS holds the record. It also carries a session past a wall and runs whole tasks overnight with the Musher, a night-shift orchestrator that answers the questions an agent would otherwise stop on.
|
|
4971
|
+
|
|
4972
|
+
## The loop (every build, no exceptions)
|
|
4973
|
+
|
|
4974
|
+
1. **Before picking up work that may already have a row** — a ticket key, a spec section, a hand-off — call \`status\` with the key or a word from the title. If it is on the sled, take it; do not estimate it twice.
|
|
4975
|
+
2. **Before the first line of any build that takes more than a few minutes** — call \`estimate\` with a stable \`source\` (kind · project · doc · item, e.g. \`repo · sleds · docs/specs/11-the-seam.md › slice 2\`). Say the band to the person. Never a point figure, never your own guess.
|
|
4976
|
+
3. **When it lands** (merged, shipped, done) — call \`actual\` with the same \`source\`: the state, where the output lives (\`ref\`), the model, and your session link (\`sessionId\`). Do **not** pass \`minutes\` unless the person stated them: SLEDS measures the time from the pickup or the estimate. A follow-up to something already landed is a new \`item\` under the same doc, never an overwrite.
|
|
4977
|
+
4. If SLEDS says there is no estimate for the source, size it first and send the actual again. If it says unreachable, try once more, then move on and say the actual is still owed. Never invent a number to get past an error.
|
|
4978
|
+
|
|
4979
|
+
The SessionStart hook tells you what this machine still owes; the Stop hook will ask once if you estimated and never posted the actual. Both are \`sleds hooks\`.
|
|
4739
4980
|
|
|
4740
4981
|
## Hit a limit on your seat
|
|
4741
4982
|
|
|
@@ -4873,7 +5114,10 @@ var hooks2 = (o2) => {
|
|
|
4873
5114
|
const hadSpec = hasSpecHook(settings);
|
|
4874
5115
|
const withSeat = applySeatHook(settings, o2.seatEventCommand ?? sledsCommand("seat-event", o2.home, o2.env));
|
|
4875
5116
|
const hadPrompt = hasPromptHook(settings);
|
|
4876
|
-
const
|
|
5117
|
+
const hadSession = hasSessionHook(settings);
|
|
5118
|
+
const hadStop = hasStopHook(settings);
|
|
5119
|
+
const withPrompt = applyPromptHook(applySpecHook(withSeat, o2.specEventCommand ?? sledsCommand("spec-event", o2.home, o2.env)), o2.promptEventCommand ?? sledsCommand("prompt-event", o2.home, o2.env));
|
|
5120
|
+
const withSpec = applyStopHook(applySessionHook(withPrompt, o2.sessionEventCommand ?? sledsCommand("session-event", o2.home, o2.env)), o2.stopEventCommand ?? sledsCommand("stop-event", o2.home, o2.env));
|
|
4877
5121
|
const hadLine = o2.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
|
|
4878
5122
|
writeSettings(o2.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o2.home, o2.env)).settings : withSpec);
|
|
4879
5123
|
log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o2.home)}`);
|
|
@@ -4881,6 +5125,8 @@ var hooks2 = (o2) => {
|
|
|
4881
5125
|
log(`refreshed statusLine in ${settingsPath(o2.home)}`);
|
|
4882
5126
|
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o2.home)}`);
|
|
4883
5127
|
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o2.home)}`);
|
|
5128
|
+
log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(o2.home)}`);
|
|
5129
|
+
log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(o2.home)}`);
|
|
4884
5130
|
return 0;
|
|
4885
5131
|
};
|
|
4886
5132
|
|
|
@@ -5093,6 +5339,8 @@ var init = async (o2) => {
|
|
|
5093
5339
|
seatEventCommand: o2.seatEventCommand,
|
|
5094
5340
|
specEventCommand: o2.specEventCommand,
|
|
5095
5341
|
promptEventCommand: o2.promptEventCommand,
|
|
5342
|
+
sessionEventCommand: o2.sessionEventCommand,
|
|
5343
|
+
stopEventCommand: o2.stopEventCommand,
|
|
5096
5344
|
log: (line) => wrote.push(line),
|
|
5097
5345
|
error
|
|
5098
5346
|
});
|
|
@@ -5112,8 +5360,8 @@ var init = async (o2) => {
|
|
|
5112
5360
|
...statusLine2.prior !== undefined ? { priorStatusLine: statusLine2.prior } : {}
|
|
5113
5361
|
});
|
|
5114
5362
|
wrote.push(`saved token and proxy URL to ${configPath(o2.home)} (0600)`);
|
|
5115
|
-
|
|
5116
|
-
|
|
5363
|
+
mkdirSync8(skillDir(o2.home), { recursive: true });
|
|
5364
|
+
writeFileSync8(skillPath(o2.home), SKILL_MD);
|
|
5117
5365
|
wrote.push(`installed skill at ${skillPath(o2.home)}`);
|
|
5118
5366
|
if (o2.installMusher) {
|
|
5119
5367
|
const r = o2.installMusher();
|
|
@@ -5199,20 +5447,20 @@ var meter = async (o2) => {
|
|
|
5199
5447
|
init_config();
|
|
5200
5448
|
init_meter();
|
|
5201
5449
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5202
|
-
import { existsSync as
|
|
5450
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5203
5451
|
import { hostname as hostname2 } from "node:os";
|
|
5204
|
-
import { dirname as
|
|
5452
|
+
import { dirname as dirname5 } from "node:path";
|
|
5205
5453
|
var defaultWhich3 = (cmd) => {
|
|
5206
5454
|
const path = process.env.PATH ?? "";
|
|
5207
5455
|
for (const dir of path.split(":").filter(Boolean)) {
|
|
5208
5456
|
const p2 = `${dir}/${cmd}`;
|
|
5209
|
-
if (
|
|
5457
|
+
if (existsSync10(p2))
|
|
5210
5458
|
return p2;
|
|
5211
5459
|
}
|
|
5212
5460
|
return null;
|
|
5213
5461
|
};
|
|
5214
5462
|
var findClaude2 = (home, configured, which) => {
|
|
5215
|
-
if (configured &&
|
|
5463
|
+
if (configured && existsSync10(configured))
|
|
5216
5464
|
return configured;
|
|
5217
5465
|
const found = which("claude");
|
|
5218
5466
|
if (found)
|
|
@@ -5223,7 +5471,7 @@ var findClaude2 = (home, configured, which) => {
|
|
|
5223
5471
|
"/usr/local/bin/claude",
|
|
5224
5472
|
`${home}/.npm-global/bin/claude`
|
|
5225
5473
|
])
|
|
5226
|
-
if (
|
|
5474
|
+
if (existsSync10(p2))
|
|
5227
5475
|
return p2;
|
|
5228
5476
|
return null;
|
|
5229
5477
|
};
|
|
@@ -5239,8 +5487,8 @@ var musherInstall = (o2) => {
|
|
|
5239
5487
|
const plist = musherPlistPath(o2.home, env);
|
|
5240
5488
|
const label = musherLabel(env);
|
|
5241
5489
|
const dir = configDir(o2.home, env);
|
|
5242
|
-
|
|
5243
|
-
|
|
5490
|
+
mkdirSync9(dirname5(plist), { recursive: true });
|
|
5491
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
5244
5492
|
const execPath = o2.sledsArgv[0] ?? process.execPath;
|
|
5245
5493
|
const agentEnv = {
|
|
5246
5494
|
PATH: musherPath(o2.home, execPath, env.PATH ?? ""),
|
|
@@ -5249,9 +5497,9 @@ var musherInstall = (o2) => {
|
|
|
5249
5497
|
...env.CLAUDE_CONFIG_DIR ? { CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR } : {}
|
|
5250
5498
|
};
|
|
5251
5499
|
const exec = o2.exec ?? defaultExecSync;
|
|
5252
|
-
if (
|
|
5500
|
+
if (existsSync10(plist))
|
|
5253
5501
|
exec("launchctl", ["unload", plist]);
|
|
5254
|
-
|
|
5502
|
+
writeFileSync9(plist, buildMusherPlist({ sledsArgv: o2.sledsArgv, env: agentEnv, logPath: musherLogPath(dir), label }));
|
|
5255
5503
|
const r = exec("launchctl", ["load", plist]);
|
|
5256
5504
|
if (!r.ok) {
|
|
5257
5505
|
error(`launchctl load failed: ${r.stderr.trim() || "unknown"}`);
|
|
@@ -5270,7 +5518,7 @@ var musherUninstall = (o2) => {
|
|
|
5270
5518
|
const env = o2.env ?? process.env;
|
|
5271
5519
|
const plist = musherPlistPath(o2.home, env);
|
|
5272
5520
|
const exec = o2.exec ?? defaultExecSync;
|
|
5273
|
-
if (
|
|
5521
|
+
if (existsSync10(plist)) {
|
|
5274
5522
|
exec("launchctl", ["unload", plist]);
|
|
5275
5523
|
rmSync2(plist, { force: true });
|
|
5276
5524
|
log(`musher removed: ${musherLabel(env)}`);
|
|
@@ -5305,7 +5553,7 @@ var musherStatus = async (o2) => {
|
|
|
5305
5553
|
const env = o2.env ?? process.env;
|
|
5306
5554
|
const plist = musherPlistPath(o2.home, env);
|
|
5307
5555
|
const cfg = readConfig(o2.home);
|
|
5308
|
-
log(
|
|
5556
|
+
log(existsSync10(plist) ? `agent ${musherLabel(env)} installed (${plist})` : "agent not installed — sleds musher --install");
|
|
5309
5557
|
const claude = findClaude2(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich3);
|
|
5310
5558
|
log(`claude ${claude ?? "not found — sleds musher --claude /path/to/claude"}`);
|
|
5311
5559
|
log(`mode ${cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
@@ -5417,10 +5665,10 @@ var musher = async (o2) => {
|
|
|
5417
5665
|
init_config();
|
|
5418
5666
|
|
|
5419
5667
|
// src/schedule.ts
|
|
5420
|
-
import { existsSync as
|
|
5421
|
-
import { join as
|
|
5668
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync10, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5669
|
+
import { join as join9 } from "node:path";
|
|
5422
5670
|
var REVERT_LABEL = "ai.sleds.revert";
|
|
5423
|
-
var plistPath = (home) =>
|
|
5671
|
+
var plistPath = (home) => join9(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
|
|
5424
5672
|
var xmlEscape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5425
5673
|
var buildLaunchdPlist = (sledsArgv, deadline) => {
|
|
5426
5674
|
const args = [...sledsArgv, "off"].map((a) => ` <string>${xmlEscape2(a)}</string>`).join(`
|
|
@@ -5459,9 +5707,9 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
5459
5707
|
if (d.platform === "darwin") {
|
|
5460
5708
|
const p2 = plistPath(d.home);
|
|
5461
5709
|
try {
|
|
5462
|
-
|
|
5710
|
+
mkdirSync10(join9(d.home, "Library", "LaunchAgents"), { recursive: true });
|
|
5463
5711
|
d.exec("launchctl", ["unload", p2]);
|
|
5464
|
-
|
|
5712
|
+
writeFileSync10(p2, buildLaunchdPlist(d.sledsArgv, deadline));
|
|
5465
5713
|
const r = d.exec("launchctl", ["load", p2]);
|
|
5466
5714
|
if (r.ok)
|
|
5467
5715
|
return { kind: "launchd", detail: `launchd agent ${REVERT_LABEL}` };
|
|
@@ -5488,7 +5736,7 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
5488
5736
|
var cancelRevert = (d, kind) => {
|
|
5489
5737
|
if (kind === "launchd" || d.platform === "darwin") {
|
|
5490
5738
|
const p2 = plistPath(d.home);
|
|
5491
|
-
if (
|
|
5739
|
+
if (existsSync11(p2)) {
|
|
5492
5740
|
d.exec("launchctl", ["unload", p2]);
|
|
5493
5741
|
rmSync3(p2, { force: true });
|
|
5494
5742
|
}
|
|
@@ -5801,14 +6049,14 @@ var seatEvent = async (o2) => {
|
|
|
5801
6049
|
|
|
5802
6050
|
// src/commands/seatSync.ts
|
|
5803
6051
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5804
|
-
import { existsSync as
|
|
6052
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync10, readdirSync as readdirSync2, writeFileSync as writeFileSync11 } from "node:fs";
|
|
5805
6053
|
import { platform } from "node:os";
|
|
5806
|
-
import { join as
|
|
6054
|
+
import { join as join11 } from "node:path";
|
|
5807
6055
|
|
|
5808
6056
|
// src/cookieDb.ts
|
|
5809
6057
|
import { copyFileSync, rmSync as rmSync4 } from "node:fs";
|
|
5810
6058
|
import { tmpdir } from "node:os";
|
|
5811
|
-
import { join as
|
|
6059
|
+
import { join as join10 } from "node:path";
|
|
5812
6060
|
var QUERY = "SELECT name, hex(encrypted_value) AS ev FROM cookies WHERE host_key LIKE '%claude.ai%'";
|
|
5813
6061
|
var isBun = () => typeof globalThis.Bun !== "undefined";
|
|
5814
6062
|
async function queryRows(dbPath) {
|
|
@@ -5837,7 +6085,7 @@ function nodeSqlite() {
|
|
|
5837
6085
|
return mod;
|
|
5838
6086
|
}
|
|
5839
6087
|
async function readCookieRows(dbPath) {
|
|
5840
|
-
const copy =
|
|
6088
|
+
const copy = join10(tmpdir(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
|
|
5841
6089
|
copyFileSync(dbPath, copy);
|
|
5842
6090
|
try {
|
|
5843
6091
|
const rows = await queryRows(copy);
|
|
@@ -6113,7 +6361,7 @@ function findFile(root, name) {
|
|
|
6113
6361
|
const stack = [root];
|
|
6114
6362
|
while (stack.length > 0) {
|
|
6115
6363
|
const dir = stack.pop();
|
|
6116
|
-
if (!dir || !
|
|
6364
|
+
if (!dir || !existsSync12(dir))
|
|
6117
6365
|
continue;
|
|
6118
6366
|
let entries;
|
|
6119
6367
|
try {
|
|
@@ -6122,7 +6370,7 @@ function findFile(root, name) {
|
|
|
6122
6370
|
continue;
|
|
6123
6371
|
}
|
|
6124
6372
|
for (const e of entries) {
|
|
6125
|
-
const p2 =
|
|
6373
|
+
const p2 = join11(dir, e.name);
|
|
6126
6374
|
if (e.isDirectory())
|
|
6127
6375
|
stack.push(p2);
|
|
6128
6376
|
else if (e.name === name)
|
|
@@ -6133,8 +6381,8 @@ function findFile(root, name) {
|
|
|
6133
6381
|
}
|
|
6134
6382
|
function findCookieDb(home, source) {
|
|
6135
6383
|
const b = BROWSERS[source.browser];
|
|
6136
|
-
const root =
|
|
6137
|
-
if (!
|
|
6384
|
+
const root = join11(home, APP_SUPPORT, b.appDir);
|
|
6385
|
+
if (!existsSync12(root))
|
|
6138
6386
|
throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
|
|
6139
6387
|
if (!b.profiled) {
|
|
6140
6388
|
const db2 = findFile(root, "Cookies");
|
|
@@ -6142,15 +6390,15 @@ function findCookieDb(home, source) {
|
|
|
6142
6390
|
throw new Error(`no cookie store under ${root}`);
|
|
6143
6391
|
return db2;
|
|
6144
6392
|
}
|
|
6145
|
-
const localStatePath =
|
|
6146
|
-
const localState =
|
|
6393
|
+
const localStatePath = join11(root, "Local State");
|
|
6394
|
+
const localState = existsSync12(localStatePath) ? readFileSync10(localStatePath, "utf8") : null;
|
|
6147
6395
|
const wanted = source.profile ?? "Default";
|
|
6148
6396
|
const { dir, ambiguous } = resolveProfileDir(localState, wanted);
|
|
6149
6397
|
if (ambiguous)
|
|
6150
6398
|
throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
|
|
6151
6399
|
if (!dir)
|
|
6152
6400
|
throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
|
|
6153
|
-
const db = cookieDbCandidates(
|
|
6401
|
+
const db = cookieDbCandidates(join11(root, dir)).find((p2) => existsSync12(p2));
|
|
6154
6402
|
if (!db)
|
|
6155
6403
|
throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
|
|
6156
6404
|
return db;
|
|
@@ -6193,7 +6441,7 @@ function fetchSeatUsage(jar, org, now, run2) {
|
|
|
6193
6441
|
}
|
|
6194
6442
|
function launchAgentPlist(bun, script, home, source) {
|
|
6195
6443
|
const label = launchLabel(source);
|
|
6196
|
-
const log =
|
|
6444
|
+
const log = join11(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
|
|
6197
6445
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
6198
6446
|
const args = [bun, script, "seat-sync", ...sourceArgs(source)].map((a) => ` <string>${esc(a)}</string>`).join(`
|
|
6199
6447
|
`);
|
|
@@ -6215,7 +6463,7 @@ ${args}
|
|
|
6215
6463
|
`;
|
|
6216
6464
|
}
|
|
6217
6465
|
function plistPath2(home, source) {
|
|
6218
|
-
return
|
|
6466
|
+
return join11(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
|
|
6219
6467
|
}
|
|
6220
6468
|
function install(home, argv, source, log) {
|
|
6221
6469
|
const bun = argv[0] ?? process.execPath;
|
|
@@ -6224,10 +6472,10 @@ function install(home, argv, source, log) {
|
|
|
6224
6472
|
log("cannot resolve the sleds script path to install the timer");
|
|
6225
6473
|
return 1;
|
|
6226
6474
|
}
|
|
6227
|
-
const dir =
|
|
6228
|
-
|
|
6475
|
+
const dir = join11(home, "Library", "LaunchAgents");
|
|
6476
|
+
mkdirSync11(dir, { recursive: true });
|
|
6229
6477
|
const path = plistPath2(home, source);
|
|
6230
|
-
|
|
6478
|
+
writeFileSync11(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
|
|
6231
6479
|
try {
|
|
6232
6480
|
execFileSync2("launchctl", ["unload", path], { stdio: "ignore" });
|
|
6233
6481
|
} catch {}
|
|
@@ -6240,7 +6488,7 @@ function uninstall(home, source, log) {
|
|
|
6240
6488
|
try {
|
|
6241
6489
|
execFileSync2("launchctl", ["unload", "-w", path], { stdio: "ignore" });
|
|
6242
6490
|
} catch {}
|
|
6243
|
-
log(
|
|
6491
|
+
log(existsSync12(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
|
|
6244
6492
|
return 0;
|
|
6245
6493
|
}
|
|
6246
6494
|
var seatSync = async (o2) => {
|
|
@@ -6334,9 +6582,62 @@ async function postSeat(proxyUrl, token, body, fetchFn) {
|
|
|
6334
6582
|
}
|
|
6335
6583
|
}
|
|
6336
6584
|
|
|
6585
|
+
// src/commands/sessionEvent.ts
|
|
6586
|
+
init_config();
|
|
6587
|
+
var parseSessionPayload = (raw) => {
|
|
6588
|
+
try {
|
|
6589
|
+
const j = JSON.parse(raw);
|
|
6590
|
+
return typeof j === "object" && j !== null ? j : {};
|
|
6591
|
+
} catch {
|
|
6592
|
+
return {};
|
|
6593
|
+
}
|
|
6594
|
+
};
|
|
6595
|
+
function sessionContext(a) {
|
|
6596
|
+
const who = [a.email, a.org ? `workspace ${a.org}` : undefined].filter(Boolean).join(" · ");
|
|
6597
|
+
const lines = [
|
|
6598
|
+
`SLEDS seat${who ? `: ${who}` : ""}. The loop: \`status\` before picking up work that may already have a row; \`estimate\` with a source before building anything that takes more than a few minutes; \`actual\` with the same source when it lands — never invent minutes, SLEDS measures them. A follow-up is a new item under the same doc.`
|
|
6599
|
+
];
|
|
6600
|
+
if (a.owed.length > 0) {
|
|
6601
|
+
const shown = [...a.owed].sort((x, y) => y.ts - x.ts).slice(0, 5);
|
|
6602
|
+
lines.push(`Estimated from this machine and not yet landed (${a.owed.length}): ${shown.map((r) => `${r.title ? `${r.title} — ` : ""}${sourceText2(r.source)} (${ago(a.now - r.ts)})`).join("; ")}${a.owed.length > shown.length ? "; …" : ""}. If one of these is what you are continuing, post its actual when it lands; if it was abandoned, say so and it can be withdrawn.`);
|
|
6603
|
+
}
|
|
6604
|
+
return lines.join(`
|
|
6605
|
+
`);
|
|
6606
|
+
}
|
|
6607
|
+
var sessionEvent = async (o2) => {
|
|
6608
|
+
const log = o2.log ?? console.log;
|
|
6609
|
+
const now = (o2.now ?? Date.now)();
|
|
6610
|
+
const raw = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
|
|
6611
|
+
const payload = parseSessionPayload(raw);
|
|
6612
|
+
if (payload.session_id) {
|
|
6613
|
+
try {
|
|
6614
|
+
writeSession(o2.home, {
|
|
6615
|
+
sessionId: payload.session_id,
|
|
6616
|
+
startedAt: now,
|
|
6617
|
+
...payload.source ? { source: payload.source } : {}
|
|
6618
|
+
});
|
|
6619
|
+
} catch {}
|
|
6620
|
+
}
|
|
6621
|
+
let email;
|
|
6622
|
+
let org;
|
|
6623
|
+
try {
|
|
6624
|
+
const cfg = readConfig(o2.home);
|
|
6625
|
+
email = cfg.email;
|
|
6626
|
+
org = cfg.org?.slug;
|
|
6627
|
+
} catch {}
|
|
6628
|
+
let owed = [];
|
|
6629
|
+
try {
|
|
6630
|
+
owed = readOwed(o2.home, now);
|
|
6631
|
+
} catch {
|
|
6632
|
+
owed = [];
|
|
6633
|
+
}
|
|
6634
|
+
log(sessionContext({ email, org, owed, now }));
|
|
6635
|
+
return 0;
|
|
6636
|
+
};
|
|
6637
|
+
|
|
6337
6638
|
// src/commands/specEvent.ts
|
|
6338
6639
|
init_meter();
|
|
6339
|
-
import { readFileSync as
|
|
6640
|
+
import { readFileSync as readFileSync11, statSync as statSync2 } from "node:fs";
|
|
6340
6641
|
import { basename as basename2, isAbsolute, relative } from "node:path";
|
|
6341
6642
|
var HOOK_STDIN_TIMEOUT_MS3 = 1000;
|
|
6342
6643
|
var ESTIMATE_TIMEOUT_MS = 4000;
|
|
@@ -6392,7 +6693,7 @@ function readFileOrNull(path) {
|
|
|
6392
6693
|
try {
|
|
6393
6694
|
if (statSync2(path).size > 2000000)
|
|
6394
6695
|
return null;
|
|
6395
|
-
return
|
|
6696
|
+
return readFileSync11(path, "utf8");
|
|
6396
6697
|
} catch {
|
|
6397
6698
|
return null;
|
|
6398
6699
|
}
|
|
@@ -6452,7 +6753,7 @@ var specEvent = async (o2) => {
|
|
|
6452
6753
|
// src/commands/status.ts
|
|
6453
6754
|
init_config();
|
|
6454
6755
|
init_settings();
|
|
6455
|
-
import { existsSync as
|
|
6756
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
6456
6757
|
var checkHealth2 = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
6457
6758
|
try {
|
|
6458
6759
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
@@ -6526,7 +6827,7 @@ var status = async (o2) => {
|
|
|
6526
6827
|
const log = o2.log ?? console.log;
|
|
6527
6828
|
const nowMs = (o2.now ?? Date.now)();
|
|
6528
6829
|
const cfg = readConfig(o2.home);
|
|
6529
|
-
const settings =
|
|
6830
|
+
const settings = existsSync13(settingsPath(o2.home)) ? readSettings(o2.home) : {};
|
|
6530
6831
|
const inSettings = readProxy(settings);
|
|
6531
6832
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
6532
6833
|
const token = inSettings?.token ?? cfg.token;
|
|
@@ -6700,17 +7001,68 @@ var announce = (o2, events) => {
|
|
|
6700
7001
|
}));
|
|
6701
7002
|
};
|
|
6702
7003
|
|
|
7004
|
+
// src/commands/stopEvent.ts
|
|
7005
|
+
var parseStopPayload = (raw) => {
|
|
7006
|
+
try {
|
|
7007
|
+
const j = JSON.parse(raw);
|
|
7008
|
+
return typeof j === "object" && j !== null ? j : {};
|
|
7009
|
+
} catch {
|
|
7010
|
+
return {};
|
|
7011
|
+
}
|
|
7012
|
+
};
|
|
7013
|
+
function stopReason(rows) {
|
|
7014
|
+
const named = rows.map((r) => `${r.title ? `${r.title} — ` : ""}${sourceText2(r.source)}`).join("; ");
|
|
7015
|
+
return `SLEDS: this session estimated ${rows.length === 1 ? "a row" : `${rows.length} rows`} and no actual was posted: ${named}. If the work landed, call \`actual\` with the same source, the state, where the output lives (ref) and your session link — no minutes, SLEDS measures them. If it did not land, say so in one line (it stays on the sled as ready) and stop again. Do not invent a number.`;
|
|
7016
|
+
}
|
|
7017
|
+
function decideStop(a) {
|
|
7018
|
+
if (a.stopHookActive || !a.sessionId)
|
|
7019
|
+
return { block: false };
|
|
7020
|
+
const due = owedBySession(a.rows, a.sessionId);
|
|
7021
|
+
if (due.length === 0)
|
|
7022
|
+
return { block: false };
|
|
7023
|
+
return {
|
|
7024
|
+
block: true,
|
|
7025
|
+
reason: stopReason(due),
|
|
7026
|
+
rows: markAsked(a.rows, due.map((r) => r.key), a.now)
|
|
7027
|
+
};
|
|
7028
|
+
}
|
|
7029
|
+
var stopEvent = async (o2) => {
|
|
7030
|
+
const log = o2.log ?? console.log;
|
|
7031
|
+
const now = (o2.now ?? Date.now)();
|
|
7032
|
+
const raw = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
|
|
7033
|
+
const payload = parseStopPayload(raw);
|
|
7034
|
+
let rows;
|
|
7035
|
+
try {
|
|
7036
|
+
rows = readOwed(o2.home, now);
|
|
7037
|
+
} catch {
|
|
7038
|
+
return 0;
|
|
7039
|
+
}
|
|
7040
|
+
const d = decideStop({
|
|
7041
|
+
rows,
|
|
7042
|
+
sessionId: payload.session_id,
|
|
7043
|
+
stopHookActive: payload.stop_hook_active === true,
|
|
7044
|
+
now
|
|
7045
|
+
});
|
|
7046
|
+
if (!d.block)
|
|
7047
|
+
return 0;
|
|
7048
|
+
try {
|
|
7049
|
+
writeOwed(o2.home, d.rows);
|
|
7050
|
+
} catch {}
|
|
7051
|
+
log(JSON.stringify({ decision: "block", reason: d.reason }));
|
|
7052
|
+
return 0;
|
|
7053
|
+
};
|
|
7054
|
+
|
|
6703
7055
|
// src/commands/uninstall.ts
|
|
6704
7056
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
6705
|
-
import { existsSync as
|
|
7057
|
+
import { existsSync as existsSync15, rmSync as rmSync5 } from "node:fs";
|
|
6706
7058
|
|
|
6707
7059
|
// src/codex.ts
|
|
6708
7060
|
init_config();
|
|
6709
|
-
import { existsSync as
|
|
6710
|
-
import { dirname as
|
|
7061
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "node:fs";
|
|
7062
|
+
import { dirname as dirname6, join as join12 } from "node:path";
|
|
6711
7063
|
var PROVIDER_ID2 = "sleds";
|
|
6712
|
-
var codexDir2 = (home) =>
|
|
6713
|
-
var codexConfigPath2 = (home) =>
|
|
7064
|
+
var codexDir2 = (home) => join12(home, ".codex");
|
|
7065
|
+
var codexConfigPath2 = (home) => join12(codexDir2(home), "config.toml");
|
|
6714
7066
|
function removeCodexProvider2(toml, o2 = {}) {
|
|
6715
7067
|
const lines = toml.split(`
|
|
6716
7068
|
`);
|
|
@@ -6739,13 +7091,13 @@ function removeCodexProvider2(toml, o2 = {}) {
|
|
|
6739
7091
|
}
|
|
6740
7092
|
function uninstallCodex(home) {
|
|
6741
7093
|
const path = codexConfigPath2(home);
|
|
6742
|
-
if (!
|
|
7094
|
+
if (!existsSync14(path))
|
|
6743
7095
|
return null;
|
|
6744
|
-
const prior =
|
|
7096
|
+
const prior = readFileSync12(path, "utf8");
|
|
6745
7097
|
const next = removeCodexProvider2(prior);
|
|
6746
7098
|
if (next === prior)
|
|
6747
7099
|
return null;
|
|
6748
|
-
|
|
7100
|
+
writeFileSync12(path, next, { mode: 384 });
|
|
6749
7101
|
return `removed the "${PROVIDER_ID2}" provider from ${path}`;
|
|
6750
7102
|
}
|
|
6751
7103
|
|
|
@@ -6762,7 +7114,7 @@ var uninstall2 = (o2) => {
|
|
|
6762
7114
|
(o2.log ?? console.log)(` ${codexLine}`);
|
|
6763
7115
|
const log = o2.log ?? console.log;
|
|
6764
7116
|
const removed = [];
|
|
6765
|
-
if (
|
|
7117
|
+
if (existsSync15(settingsPath(o2.home))) {
|
|
6766
7118
|
let settings = readSettings(o2.home);
|
|
6767
7119
|
let changed = false;
|
|
6768
7120
|
if (hasEitherKey(settings)) {
|
|
@@ -6795,22 +7147,32 @@ var uninstall2 = (o2) => {
|
|
|
6795
7147
|
changed = true;
|
|
6796
7148
|
removed.push(`hooks.PostToolUse spec-event entry from ${settingsPath(o2.home)}`);
|
|
6797
7149
|
}
|
|
7150
|
+
if (hasSessionHook(settings)) {
|
|
7151
|
+
settings = removeSessionHook(settings);
|
|
7152
|
+
changed = true;
|
|
7153
|
+
removed.push(`hooks.SessionStart session-event entry from ${settingsPath(o2.home)}`);
|
|
7154
|
+
}
|
|
7155
|
+
if (hasStopHook(settings)) {
|
|
7156
|
+
settings = removeStopHook(settings);
|
|
7157
|
+
changed = true;
|
|
7158
|
+
removed.push(`hooks.Stop stop-event entry from ${settingsPath(o2.home)}`);
|
|
7159
|
+
}
|
|
6798
7160
|
if (changed)
|
|
6799
7161
|
writeSettings(o2.home, settings);
|
|
6800
7162
|
}
|
|
6801
7163
|
for (const file of [meterCachePath(o2.home), seatStatePath(o2.home)]) {
|
|
6802
|
-
if (
|
|
7164
|
+
if (existsSync15(file)) {
|
|
6803
7165
|
rmSync5(file, { force: true });
|
|
6804
7166
|
removed.push(file);
|
|
6805
7167
|
}
|
|
6806
7168
|
}
|
|
6807
7169
|
const skill = skillDir(o2.home);
|
|
6808
|
-
if (
|
|
7170
|
+
if (existsSync15(skill)) {
|
|
6809
7171
|
rmSync5(skill, { recursive: true, force: true });
|
|
6810
7172
|
removed.push(skill);
|
|
6811
7173
|
}
|
|
6812
7174
|
const plist = musherPlistPath(o2.home);
|
|
6813
|
-
if (
|
|
7175
|
+
if (existsSync15(plist)) {
|
|
6814
7176
|
(o2.unloadAgent ?? defaultUnload)(plist);
|
|
6815
7177
|
rmSync5(plist, { force: true });
|
|
6816
7178
|
removed.push(`launchd agent ${musherLabel()}`);
|
|
@@ -6882,13 +7244,13 @@ var verify = async (o2) => {
|
|
|
6882
7244
|
|
|
6883
7245
|
// src/config.ts
|
|
6884
7246
|
init_settings();
|
|
6885
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
6886
|
-
import { join as
|
|
7247
|
+
import { chmodSync as chmodSync2, existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync13 } from "node:fs";
|
|
7248
|
+
import { join as join13, resolve as resolve4 } from "node:path";
|
|
6887
7249
|
var configDir2 = (home, env = process.env) => {
|
|
6888
7250
|
const o2 = env.SLEDS_HOME?.trim();
|
|
6889
|
-
return o2 ? resolve4(home, o2) :
|
|
7251
|
+
return o2 ? resolve4(home, o2) : join13(home, ".config", "sleds");
|
|
6890
7252
|
};
|
|
6891
|
-
var configPath2 = (home) =>
|
|
7253
|
+
var configPath2 = (home) => join13(configDir2(home), "config.json");
|
|
6892
7254
|
var isObject12 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
6893
7255
|
var optString2 = (v) => typeof v === "string" ? v : undefined;
|
|
6894
7256
|
var parsePriorEnv2 = (v) => {
|
|
@@ -6991,9 +7353,9 @@ var parseOrgInfo2 = (raw) => {
|
|
|
6991
7353
|
};
|
|
6992
7354
|
var readConfig2 = (home) => {
|
|
6993
7355
|
const p2 = configPath2(home);
|
|
6994
|
-
if (!
|
|
7356
|
+
if (!existsSync16(p2))
|
|
6995
7357
|
return {};
|
|
6996
|
-
return parseConfig2(JSON.parse(
|
|
7358
|
+
return parseConfig2(JSON.parse(readFileSync13(p2, "utf8")));
|
|
6997
7359
|
};
|
|
6998
7360
|
|
|
6999
7361
|
// src/schedule.ts
|
|
@@ -7006,7 +7368,7 @@ var defaultExec = (cmd, args) => {
|
|
|
7006
7368
|
};
|
|
7007
7369
|
|
|
7008
7370
|
// src/settings.ts
|
|
7009
|
-
import { dirname as
|
|
7371
|
+
import { dirname as dirname7, join as join14, resolve as resolve5 } from "node:path";
|
|
7010
7372
|
var withSeatEnv2 = (command, home, env = process.env) => {
|
|
7011
7373
|
const parts = [];
|
|
7012
7374
|
const sh = env.SLEDS_HOME?.trim();
|
|
@@ -7019,6 +7381,15 @@ var withSeatEnv2 = (command, home, env = process.env) => {
|
|
|
7019
7381
|
return parts.join(" ");
|
|
7020
7382
|
};
|
|
7021
7383
|
var shellWord2 = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
|
|
7384
|
+
var isObject13 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7385
|
+
var isSledsHookFor2 = (sub) => (entry) => {
|
|
7386
|
+
if (!isObject13(entry) || !Array.isArray(entry.hooks))
|
|
7387
|
+
return false;
|
|
7388
|
+
const re = new RegExp(`\\b${sub}\\b`);
|
|
7389
|
+
return entry.hooks.some((h) => isObject13(h) && h.type === "command" && typeof h.command === "string" && re.test(h.command));
|
|
7390
|
+
};
|
|
7391
|
+
var isSledsSessionHook2 = isSledsHookFor2("session-event");
|
|
7392
|
+
var isSledsStopHook2 = isSledsHookFor2("stop-event");
|
|
7022
7393
|
|
|
7023
7394
|
// src/index.ts
|
|
7024
7395
|
var USAGE = `usage: sleds <command> [options]
|
|
@@ -7072,6 +7443,10 @@ commands:
|
|
|
7072
7443
|
statusline Print the one-line meter Claude Code shows (installed by init; 10s cache).
|
|
7073
7444
|
hooks Register the Claude Code Notification hook for seat limits (init does this too).
|
|
7074
7445
|
seat-event Claude Code hook: record a seat wall and say how to continue. Reads hook JSON on stdin.
|
|
7446
|
+
session-event Claude Code SessionStart hook: states the loop and the rows this machine still
|
|
7447
|
+
owes before the first turn. Reads hook JSON on stdin.
|
|
7448
|
+
stop-event Claude Code Stop hook: a session that estimated a row and posted no actual is asked
|
|
7449
|
+
once, then let go. Reads hook JSON on stdin.
|
|
7075
7450
|
prompt-event Claude Code UserPromptSubmit hook: at 90 % of a seat bar the seat speaks up in the
|
|
7076
7451
|
session (once per bar per reset); out of usage credits blocks the prompt once with the exits.
|
|
7077
7452
|
spec-event Claude Code PostToolUse hook: when a spec-shaped markdown file is written, size it
|
|
@@ -7127,6 +7502,8 @@ var shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}
|
|
|
7127
7502
|
var statusLineCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "statusline"].map(shellQuote).join(" "), home);
|
|
7128
7503
|
var seatEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "seat-event"].map(shellQuote).join(" "), home);
|
|
7129
7504
|
var promptEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "prompt-event"].map(shellQuote).join(" "), home);
|
|
7505
|
+
var sessionEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "session-event"].map(shellQuote).join(" "), home);
|
|
7506
|
+
var stopEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "stop-event"].map(shellQuote).join(" "), home);
|
|
7130
7507
|
var specEventCommand = (argv, home = homedir()) => withSeatEnv2([...argv, "spec-event"].map(shellQuote).join(" "), home);
|
|
7131
7508
|
var main = async (argv) => {
|
|
7132
7509
|
const { cmd, flags } = parseArgs(argv);
|
|
@@ -7199,7 +7576,9 @@ var main = async (argv) => {
|
|
|
7199
7576
|
statusLineCommand: statusLineCommand(sledsArgv()),
|
|
7200
7577
|
seatEventCommand: seatEventCommand(sledsArgv()),
|
|
7201
7578
|
specEventCommand: specEventCommand(sledsArgv()),
|
|
7202
|
-
promptEventCommand: promptEventCommand(sledsArgv())
|
|
7579
|
+
promptEventCommand: promptEventCommand(sledsArgv()),
|
|
7580
|
+
sessionEventCommand: sessionEventCommand(sledsArgv()),
|
|
7581
|
+
stopEventCommand: stopEventCommand(sledsArgv())
|
|
7203
7582
|
});
|
|
7204
7583
|
}
|
|
7205
7584
|
case "hooks":
|
|
@@ -7207,7 +7586,9 @@ var main = async (argv) => {
|
|
|
7207
7586
|
home,
|
|
7208
7587
|
seatEventCommand: seatEventCommand(sledsArgv()),
|
|
7209
7588
|
specEventCommand: specEventCommand(sledsArgv()),
|
|
7210
|
-
promptEventCommand: promptEventCommand(sledsArgv())
|
|
7589
|
+
promptEventCommand: promptEventCommand(sledsArgv()),
|
|
7590
|
+
sessionEventCommand: sessionEventCommand(sledsArgv()),
|
|
7591
|
+
stopEventCommand: stopEventCommand(sledsArgv())
|
|
7211
7592
|
});
|
|
7212
7593
|
case "seat-event":
|
|
7213
7594
|
return seatEvent({ home });
|
|
@@ -7215,6 +7596,10 @@ var main = async (argv) => {
|
|
|
7215
7596
|
return specEvent({ home });
|
|
7216
7597
|
case "prompt-event":
|
|
7217
7598
|
return promptEvent({ home });
|
|
7599
|
+
case "session-event":
|
|
7600
|
+
return sessionEvent({ home });
|
|
7601
|
+
case "stop-event":
|
|
7602
|
+
return stopEvent({ home });
|
|
7218
7603
|
case "on":
|
|
7219
7604
|
return on({
|
|
7220
7605
|
home,
|
|
@@ -7258,7 +7643,7 @@ ${renderHelpText(argv[1])}`);
|
|
|
7258
7643
|
case "version":
|
|
7259
7644
|
case "--version":
|
|
7260
7645
|
case "-v":
|
|
7261
|
-
console.log("sleds 0.1.
|
|
7646
|
+
console.log("sleds 0.1.23");
|
|
7262
7647
|
return 0;
|
|
7263
7648
|
default:
|
|
7264
7649
|
console.error(`unknown command: ${cmd}
|
|
@@ -7289,6 +7674,8 @@ export {
|
|
|
7289
7674
|
parseArgs,
|
|
7290
7675
|
promptEventCommand,
|
|
7291
7676
|
seatEventCommand,
|
|
7677
|
+
sessionEventCommand,
|
|
7292
7678
|
specEventCommand,
|
|
7293
|
-
statusLineCommand
|
|
7679
|
+
statusLineCommand,
|
|
7680
|
+
stopEventCommand
|
|
7294
7681
|
};
|