@sleds/cli 0.1.25 → 0.1.26
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 +1517 -739
- package/package.json +1 -1
package/dist/sleds.mjs
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __returnValue = (v) => v;
|
|
6
|
+
function __exportSetter(name, newValue) {
|
|
7
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
8
|
+
}
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
set: __exportSetter.bind(all, name)
|
|
16
|
+
});
|
|
13
17
|
};
|
|
18
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
19
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
14
20
|
|
|
15
21
|
// ../../packages/shared/src/mcp/format.ts
|
|
16
22
|
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, str = (v) => typeof v === "string" ? v : undefined, parseMeter = (raw) => {
|
|
@@ -152,8 +158,8 @@ var init_format = __esm(() => {
|
|
|
152
158
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
153
159
|
import { dirname, join, resolve } from "node:path";
|
|
154
160
|
var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN", claudeDir = (home, env = process.env) => {
|
|
155
|
-
const
|
|
156
|
-
return
|
|
161
|
+
const o2 = env.CLAUDE_CONFIG_DIR?.trim();
|
|
162
|
+
return o2 ? resolve(home, o2) : join(home, ".claude");
|
|
157
163
|
}, hasHomeOverride = (env = process.env) => Boolean(env.SLEDS_HOME?.trim() || env.CLAUDE_CONFIG_DIR?.trim()), settingsPath = (home) => join(claudeDir(home), "settings.json"), sledsCommand = (sub, home, env = process.env) => withSeatEnv(`sleds ${sub}`, home, env), withSeatEnv = (command, home, env = process.env) => {
|
|
158
164
|
const parts = [];
|
|
159
165
|
const sh = env.SLEDS_HOME?.trim();
|
|
@@ -183,8 +189,8 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
|
|
|
183
189
|
visit(settings.hooks);
|
|
184
190
|
return out;
|
|
185
191
|
}, bakedHome = (command) => command.match(/(?:^|\s)SLEDS_HOME=('([^']*)'|(\S+))/)?.[2] ?? command.match(/(?:^|\s)SLEDS_HOME=(\S+)/)?.[1], shellWord = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`, backupPath = (home) => join(claudeDir(home), "settings.json.sleds-backup"), tokenPath = (home, env = process.env) => {
|
|
186
|
-
const
|
|
187
|
-
return
|
|
192
|
+
const o2 = env.SLEDS_HOME?.trim();
|
|
193
|
+
return o2 ? join(resolve(home, o2), ".sleds-token") : join(home, ".sleds-token");
|
|
188
194
|
}, skillDir = (home) => join(claudeDir(home), "skills", "sled"), skillPath = (home) => join(skillDir(home), "SKILL.md"), isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), envOf = (settings) => {
|
|
189
195
|
const env = settings.env;
|
|
190
196
|
return isObject5(env) ? env : {};
|
|
@@ -320,12 +326,12 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
|
|
|
320
326
|
return withEventHooks(settings, event, list.filter((e) => !ours(e)));
|
|
321
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)}
|
|
322
328
|
`, hasClaudeCode = (home) => existsSync(claudeDir(home)), readSettings = (home) => {
|
|
323
|
-
const
|
|
324
|
-
if (!existsSync(
|
|
329
|
+
const p2 = settingsPath(home);
|
|
330
|
+
if (!existsSync(p2))
|
|
325
331
|
return {};
|
|
326
|
-
const parsed = JSON.parse(readFileSync(
|
|
332
|
+
const parsed = JSON.parse(readFileSync(p2, "utf8"));
|
|
327
333
|
if (!isObject5(parsed))
|
|
328
|
-
throw new Error(`${
|
|
334
|
+
throw new Error(`${p2} is not a JSON object`);
|
|
329
335
|
return parsed;
|
|
330
336
|
}, writeSettings = (home, settings) => {
|
|
331
337
|
mkdirSync(claudeDir(home), { recursive: true });
|
|
@@ -341,10 +347,10 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
|
|
|
341
347
|
writeFileSync(dst, readFileSync(src));
|
|
342
348
|
return true;
|
|
343
349
|
}, readTokenFile = (home) => {
|
|
344
|
-
const
|
|
345
|
-
if (!existsSync(
|
|
350
|
+
const p2 = tokenPath(home);
|
|
351
|
+
if (!existsSync(p2))
|
|
346
352
|
return null;
|
|
347
|
-
const t = readFileSync(
|
|
353
|
+
const t = readFileSync(p2, "utf8").trim();
|
|
348
354
|
return t.length > 0 ? t : null;
|
|
349
355
|
}, writeTokenFile = (home, token) => {
|
|
350
356
|
mkdirSync(dirname(tokenPath(home)), { recursive: true, mode: 448 });
|
|
@@ -360,8 +366,8 @@ var init_settings = __esm(() => {
|
|
|
360
366
|
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
361
367
|
import { join as join2, resolve as resolve2 } from "node:path";
|
|
362
368
|
var configDir = (home, env = process.env) => {
|
|
363
|
-
const
|
|
364
|
-
return
|
|
369
|
+
const o2 = env.SLEDS_HOME?.trim();
|
|
370
|
+
return o2 ? resolve2(home, o2) : join2(home, ".config", "sleds");
|
|
365
371
|
}, configPath = (home) => join2(configDir(home), "config.json"), isObject6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), optString = (v) => typeof v === "string" ? v : undefined, parsePriorEnv = (v) => {
|
|
366
372
|
if (!isObject6(v))
|
|
367
373
|
return;
|
|
@@ -438,15 +444,15 @@ var configDir = (home, env = process.env) => {
|
|
|
438
444
|
return;
|
|
439
445
|
const name = optString(raw.name);
|
|
440
446
|
const slug = optString(raw.slug);
|
|
441
|
-
const
|
|
447
|
+
const plan2 = raw.plan;
|
|
442
448
|
const c = raw.capabilities;
|
|
443
449
|
const src = c.upstreamSource;
|
|
444
|
-
if (name === undefined || slug === undefined ||
|
|
450
|
+
if (name === undefined || slug === undefined || plan2 !== "team" && plan2 !== "enterprise" && plan2 !== "api" && plan2 !== "individual" || src !== "own" && src !== "shared" && src !== "none")
|
|
445
451
|
return;
|
|
446
452
|
return {
|
|
447
453
|
name,
|
|
448
454
|
slug,
|
|
449
|
-
plan,
|
|
455
|
+
plan: plan2,
|
|
450
456
|
capabilities: {
|
|
451
457
|
upstreamKey: c.upstreamKey === true,
|
|
452
458
|
adminKey: c.adminKey === true,
|
|
@@ -456,10 +462,10 @@ var configDir = (home, env = process.env) => {
|
|
|
456
462
|
mode: raw.mode === "proxy-always" ? "proxy-always" : "seat-first"
|
|
457
463
|
};
|
|
458
464
|
}, readConfig = (home) => {
|
|
459
|
-
const
|
|
460
|
-
if (!existsSync2(
|
|
465
|
+
const p2 = configPath(home);
|
|
466
|
+
if (!existsSync2(p2))
|
|
461
467
|
return {};
|
|
462
|
-
return parseConfig(JSON.parse(readFileSync2(
|
|
468
|
+
return parseConfig(JSON.parse(readFileSync2(p2, "utf8")));
|
|
463
469
|
}, writeConfig = (home, cfg) => {
|
|
464
470
|
const dir = configDir(home);
|
|
465
471
|
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
@@ -478,11 +484,11 @@ var init_config = __esm(() => {
|
|
|
478
484
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
479
485
|
import { join as join4 } from "node:path";
|
|
480
486
|
var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num4 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, meterCachePath = (home) => join4(configDir(home), "meter.json"), readMeterCache = (home, now) => {
|
|
481
|
-
const
|
|
482
|
-
if (!existsSync4(
|
|
487
|
+
const p2 = meterCachePath(home);
|
|
488
|
+
if (!existsSync4(p2))
|
|
483
489
|
return null;
|
|
484
490
|
try {
|
|
485
|
-
const raw = JSON.parse(readFileSync4(
|
|
491
|
+
const raw = JSON.parse(readFileSync4(p2, "utf8"));
|
|
486
492
|
if (!isObject7(raw))
|
|
487
493
|
return null;
|
|
488
494
|
const fetchedAt = num4(raw.fetchedAt);
|
|
@@ -501,12 +507,12 @@ var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof
|
|
|
501
507
|
writeFileSync4(tmp, `${JSON.stringify(file)}
|
|
502
508
|
`, { mode: 384 });
|
|
503
509
|
renameSync3(tmp, target);
|
|
504
|
-
}, fetchMeter = async (
|
|
505
|
-
const fetchFn =
|
|
510
|
+
}, fetchMeter = async (o2) => {
|
|
511
|
+
const fetchFn = o2.fetchFn ?? fetch;
|
|
506
512
|
try {
|
|
507
|
-
const res = await fetchFn(`${
|
|
508
|
-
headers: { authorization: `Bearer ${
|
|
509
|
-
signal: AbortSignal.timeout(
|
|
513
|
+
const res = await fetchFn(`${o2.proxyUrl}/proxy/meter`, {
|
|
514
|
+
headers: { authorization: `Bearer ${o2.token}` },
|
|
515
|
+
signal: AbortSignal.timeout(o2.timeoutMs ?? METER_TIMEOUT_MS)
|
|
510
516
|
});
|
|
511
517
|
if (!res.ok)
|
|
512
518
|
return null;
|
|
@@ -521,18 +527,18 @@ var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof
|
|
|
521
527
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
522
528
|
const token = inSettings?.token ?? cfg.token;
|
|
523
529
|
return proxyUrl && token ? { proxyUrl, token } : null;
|
|
524
|
-
}, loadMeter = async (
|
|
525
|
-
const now =
|
|
526
|
-
const cached = readMeterCache(
|
|
530
|
+
}, loadMeter = async (o2) => {
|
|
531
|
+
const now = o2.now ?? Date.now();
|
|
532
|
+
const cached = readMeterCache(o2.home, now);
|
|
527
533
|
if (cached)
|
|
528
534
|
return cached;
|
|
529
|
-
const src = meterSource(
|
|
535
|
+
const src = meterSource(o2.home);
|
|
530
536
|
if (!src)
|
|
531
537
|
return null;
|
|
532
|
-
const fresh = await fetchMeter({ ...src, fetchFn:
|
|
538
|
+
const fresh = await fetchMeter({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs });
|
|
533
539
|
if (fresh) {
|
|
534
540
|
try {
|
|
535
|
-
writeMeterCache(
|
|
541
|
+
writeMeterCache(o2.home, fresh, now);
|
|
536
542
|
} catch {}
|
|
537
543
|
}
|
|
538
544
|
return fresh;
|
|
@@ -545,9 +551,432 @@ var init_meter = __esm(() => {
|
|
|
545
551
|
});
|
|
546
552
|
|
|
547
553
|
// src/seat.ts
|
|
548
|
-
var
|
|
554
|
+
var exports_seat = {};
|
|
555
|
+
__export(exports_seat, {
|
|
556
|
+
APPROACHING_PCT: () => APPROACHING_PCT2,
|
|
557
|
+
CREDITS_PATTERN: () => CREDITS_PATTERN2,
|
|
558
|
+
SEAT_POST_MIN_DELTA_PCT: () => SEAT_POST_MIN_DELTA_PCT2,
|
|
559
|
+
SEAT_POST_MIN_INTERVAL_MS: () => SEAT_POST_MIN_INTERVAL_MS2,
|
|
560
|
+
SEAT_POST_TIMEOUT_MS: () => SEAT_POST_TIMEOUT_MS2,
|
|
561
|
+
SEAT_WALL_LINE: () => SEAT_WALL_LINE2,
|
|
562
|
+
SEAT_WALL_LINE_NO_KEY: () => SEAT_WALL_LINE_NO_KEY2,
|
|
563
|
+
STDIN_TIMEOUT_MS: () => STDIN_TIMEOUT_MS2,
|
|
564
|
+
bindingResetAt: () => bindingResetAt2,
|
|
565
|
+
creditsHitIn: () => creditsHitIn2,
|
|
566
|
+
formatSeatFragment: () => formatSeatFragment2,
|
|
567
|
+
markPosted: () => markPosted2,
|
|
568
|
+
markWarned: () => markWarned2,
|
|
569
|
+
parseResetsAt: () => parseResetsAt2,
|
|
570
|
+
parseSeatState: () => parseSeatState2,
|
|
571
|
+
parseSeatWindow: () => parseSeatWindow2,
|
|
572
|
+
postSeatWall: () => postSeatWall2,
|
|
573
|
+
postSeatWindow: () => postSeatWindow2,
|
|
574
|
+
readSeatState: () => readSeatState2,
|
|
575
|
+
readStdin: () => readStdin2,
|
|
576
|
+
scanTranscriptForCredits: () => scanTranscriptForCredits2,
|
|
577
|
+
seatSessionFromStdin: () => seatSessionFromStdin2,
|
|
578
|
+
seatStatePath: () => seatStatePath2,
|
|
579
|
+
seatWallDue: () => seatWallDue,
|
|
580
|
+
seatWallEvents: () => seatWallEvents2,
|
|
581
|
+
seatWallLine: () => seatWallLine2,
|
|
582
|
+
seatWindowBody: () => seatWindowBody2,
|
|
583
|
+
seatWindowFromStdin: () => seatWindowFromStdin2,
|
|
584
|
+
shouldPostSeat: () => shouldPostSeat2,
|
|
585
|
+
transcriptPathFromStdin: () => transcriptPathFromStdin2,
|
|
586
|
+
writeSeatState: () => writeSeatState2
|
|
587
|
+
});
|
|
588
|
+
import {
|
|
589
|
+
closeSync as closeSync2,
|
|
590
|
+
existsSync as existsSync18,
|
|
591
|
+
mkdirSync as mkdirSync14,
|
|
592
|
+
openSync as openSync2,
|
|
593
|
+
readFileSync as readFileSync15,
|
|
594
|
+
readSync as readSync2,
|
|
595
|
+
renameSync as renameSync7,
|
|
596
|
+
statSync as statSync3,
|
|
597
|
+
writeFileSync as writeFileSync14
|
|
598
|
+
} from "node:fs";
|
|
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, isObject15 = (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) => {
|
|
601
|
+
const n2 = num7(v);
|
|
602
|
+
if (n2 !== undefined) {
|
|
603
|
+
if (n2 <= 0)
|
|
604
|
+
return;
|
|
605
|
+
return n2 >= EPOCH_MS_THRESHOLD2 ? Math.round(n2) : Math.round(n2 * 1000);
|
|
606
|
+
}
|
|
607
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
608
|
+
const parsed = Date.parse(v);
|
|
609
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}, parseBar2 = (v) => {
|
|
613
|
+
if (!isObject15(v))
|
|
614
|
+
return;
|
|
615
|
+
const usedPct = num7(v.used_percentage) ?? num7(v.usedPct);
|
|
616
|
+
const resetsAt = parseResetsAt2(v.resets_at ?? v.resetsAt);
|
|
617
|
+
if (usedPct === undefined || usedPct < 0 || resetsAt === undefined)
|
|
618
|
+
return;
|
|
619
|
+
return { usedPct, resetsAt };
|
|
620
|
+
}, parseSeatWindow2 = (raw, capturedAt) => {
|
|
621
|
+
if (!isObject15(raw))
|
|
622
|
+
return null;
|
|
623
|
+
const limits = isObject15(raw.rate_limits) ? raw.rate_limits : raw;
|
|
624
|
+
const fiveHour = parseBar2(limits.five_hour ?? limits.fiveHour);
|
|
625
|
+
const sevenDay = parseBar2(limits.seven_day ?? limits.sevenDay);
|
|
626
|
+
const spendLimit = parseBar2(limits.spend_limit ?? limits.spendLimit);
|
|
627
|
+
const models = {};
|
|
628
|
+
if (isObject15(limits.models))
|
|
629
|
+
for (const [k, v] of Object.entries(limits.models)) {
|
|
630
|
+
const bar = parseBar2(v);
|
|
631
|
+
if (bar)
|
|
632
|
+
models[k] = bar;
|
|
633
|
+
}
|
|
634
|
+
const usedCreditsUsd = num7(limits.usedCreditsUsd);
|
|
635
|
+
const hasModels = Object.keys(models).length > 0;
|
|
636
|
+
if (!fiveHour && !sevenDay && !spendLimit && !hasModels && usedCreditsUsd === undefined)
|
|
637
|
+
return null;
|
|
638
|
+
const out = { capturedAt };
|
|
639
|
+
if (fiveHour)
|
|
640
|
+
out.fiveHour = fiveHour;
|
|
641
|
+
if (sevenDay)
|
|
642
|
+
out.sevenDay = sevenDay;
|
|
643
|
+
if (spendLimit)
|
|
644
|
+
out.spendLimit = spendLimit;
|
|
645
|
+
if (hasModels)
|
|
646
|
+
out.models = models;
|
|
647
|
+
if (usedCreditsUsd !== undefined)
|
|
648
|
+
out.usedCreditsUsd = usedCreditsUsd;
|
|
649
|
+
return out;
|
|
650
|
+
}, seatWindowFromStdin2 = (text, capturedAt) => {
|
|
651
|
+
const trimmed = text.trim();
|
|
652
|
+
if (!trimmed)
|
|
653
|
+
return null;
|
|
654
|
+
try {
|
|
655
|
+
return parseSeatWindow2(JSON.parse(trimmed), capturedAt);
|
|
656
|
+
} catch {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}, formatSeatFragment2 = (w, now = Date.now()) => {
|
|
660
|
+
if (!w)
|
|
661
|
+
return null;
|
|
662
|
+
const parts = [];
|
|
663
|
+
if (w.sevenDay)
|
|
664
|
+
parts.push(`wk ${Math.round(w.sevenDay.usedPct)}%`);
|
|
665
|
+
if (w.fiveHour)
|
|
666
|
+
parts.push(`5h ${Math.round(w.fiveHour.usedPct)}%`);
|
|
667
|
+
if (w.spendLimit)
|
|
668
|
+
parts.push(`spend ${Math.round(w.spendLimit.usedPct)}%`);
|
|
669
|
+
if (parts.length === 0)
|
|
670
|
+
return null;
|
|
671
|
+
const hot = [
|
|
672
|
+
["wk", w.sevenDay],
|
|
673
|
+
["5h", w.fiveHour],
|
|
674
|
+
["spend", w.spendLimit]
|
|
675
|
+
].filter((x) => x[1] !== undefined && x[1].usedPct >= APPROACHING_PCT2).sort((a, b) => b[1].usedPct - a[1].usedPct)[0];
|
|
676
|
+
if (hot) {
|
|
677
|
+
const mins = Math.max(0, Math.round((hot[1].resetsAt - now) / 60000));
|
|
678
|
+
const reset = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? `${mins % 60}m` : ""}` : `${mins}m`;
|
|
679
|
+
const verb = hot[1].usedPct >= 100 ? "at the wall" : `${Math.round(hot[1].usedPct)}%`;
|
|
680
|
+
return `⚠ seat ${hot[0]} ${verb}${SEP}resets ${reset}${SEP}/sled handoff · sleds defer`;
|
|
681
|
+
}
|
|
682
|
+
return `seat ${parts.join(SEP)}`;
|
|
683
|
+
}, APPROACHING_PCT2 = 90, seatStatePath2 = (home) => join15(configDir(home), "seat.json"), parseWindowStored2 = (v) => {
|
|
684
|
+
if (!isObject15(v))
|
|
685
|
+
return;
|
|
686
|
+
const capturedAt = num7(v.capturedAt);
|
|
687
|
+
if (capturedAt === undefined)
|
|
688
|
+
return;
|
|
689
|
+
return parseSeatWindow2(v, capturedAt) ?? undefined;
|
|
690
|
+
}, parseSeatState2 = (raw) => {
|
|
691
|
+
if (!isObject15(raw))
|
|
692
|
+
return {};
|
|
693
|
+
const out = {};
|
|
694
|
+
const window = parseWindowStored2(raw.window);
|
|
695
|
+
if (window)
|
|
696
|
+
out.window = window;
|
|
697
|
+
const lastPostedAt = num7(raw.lastPostedAt);
|
|
698
|
+
if (lastPostedAt !== undefined)
|
|
699
|
+
out.lastPostedAt = lastPostedAt;
|
|
700
|
+
if (isObject15(raw.lastPosted)) {
|
|
701
|
+
const lp = {};
|
|
702
|
+
const f = num7(raw.lastPosted.fiveHour);
|
|
703
|
+
const s = num7(raw.lastPosted.sevenDay);
|
|
704
|
+
const sp = num7(raw.lastPosted.spendLimit);
|
|
705
|
+
if (f !== undefined)
|
|
706
|
+
lp.fiveHour = f;
|
|
707
|
+
if (s !== undefined)
|
|
708
|
+
lp.sevenDay = s;
|
|
709
|
+
if (sp !== undefined)
|
|
710
|
+
lp.spendLimit = sp;
|
|
711
|
+
out.lastPosted = lp;
|
|
712
|
+
}
|
|
713
|
+
const lastWallResetsAt = num7(raw.lastWallResetsAt);
|
|
714
|
+
if (lastWallResetsAt !== undefined)
|
|
715
|
+
out.lastWallResetsAt = lastWallResetsAt;
|
|
716
|
+
if (isObject15(raw.warned)) {
|
|
717
|
+
const warned = {};
|
|
718
|
+
for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
|
|
719
|
+
const w = raw.warned[bar];
|
|
720
|
+
if (!isObject15(w))
|
|
721
|
+
continue;
|
|
722
|
+
const entry = {};
|
|
723
|
+
const ap = num7(w.approaching);
|
|
724
|
+
const hit = num7(w.hit);
|
|
725
|
+
if (ap !== undefined)
|
|
726
|
+
entry.approaching = ap;
|
|
727
|
+
if (hit !== undefined)
|
|
728
|
+
entry.hit = hit;
|
|
729
|
+
warned[bar] = entry;
|
|
730
|
+
}
|
|
731
|
+
out.warned = warned;
|
|
732
|
+
}
|
|
733
|
+
if (isObject15(raw.told)) {
|
|
734
|
+
const told = {};
|
|
735
|
+
for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
|
|
736
|
+
const w = raw.told[bar];
|
|
737
|
+
if (!isObject15(w))
|
|
738
|
+
continue;
|
|
739
|
+
const entry = {};
|
|
740
|
+
const ap = num7(w.approaching);
|
|
741
|
+
const hit = num7(w.hit);
|
|
742
|
+
if (ap !== undefined)
|
|
743
|
+
entry.approaching = ap;
|
|
744
|
+
if (hit !== undefined)
|
|
745
|
+
entry.hit = hit;
|
|
746
|
+
told[bar] = entry;
|
|
747
|
+
}
|
|
748
|
+
out.told = told;
|
|
749
|
+
}
|
|
750
|
+
if (isObject15(raw.transcriptScan) && typeof raw.transcriptScan.path === "string") {
|
|
751
|
+
const offset = num7(raw.transcriptScan.offset);
|
|
752
|
+
const hitAt = num7(raw.transcriptScan.hitAt);
|
|
753
|
+
out.transcriptScan = {
|
|
754
|
+
path: raw.transcriptScan.path,
|
|
755
|
+
offset: offset ?? 0,
|
|
756
|
+
...hitAt !== undefined ? { hitAt } : {}
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
const session = parseSeatSession2(raw.session);
|
|
760
|
+
if (session)
|
|
761
|
+
out.session = session;
|
|
762
|
+
if (isObject15(raw.cockpit) && raw.cockpit.kind === "cmux") {
|
|
763
|
+
const c = { kind: "cmux" };
|
|
764
|
+
if (isObject15(raw.cockpit.lines)) {
|
|
765
|
+
const lines = {};
|
|
766
|
+
for (const [k, v] of Object.entries(raw.cockpit.lines))
|
|
767
|
+
if (typeof v === "string")
|
|
768
|
+
lines[k] = v;
|
|
769
|
+
c.lines = lines;
|
|
770
|
+
}
|
|
771
|
+
if (raw.cockpit.progress === true)
|
|
772
|
+
c.progress = true;
|
|
773
|
+
const f = raw.cockpit.feed;
|
|
774
|
+
if (isObject15(f) && num7(f.at) !== undefined) {
|
|
775
|
+
const strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
776
|
+
const j = isObject15(f.jobs) ? f.jobs : {};
|
|
777
|
+
c.feed = {
|
|
778
|
+
at: num7(f.at),
|
|
779
|
+
handoffs: strs(f.handoffs),
|
|
780
|
+
jobs: {
|
|
781
|
+
scheduled: num7(j.scheduled) ?? 0,
|
|
782
|
+
running: num7(j.running) ?? 0,
|
|
783
|
+
delivered: num7(j.delivered) ?? 0,
|
|
784
|
+
failed: num7(j.failed) ?? 0
|
|
785
|
+
},
|
|
786
|
+
delivered: strs(f.delivered),
|
|
787
|
+
failed: strs(f.failed)
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
out.cockpit = c;
|
|
791
|
+
}
|
|
792
|
+
if (isObject15(raw.estimate)) {
|
|
793
|
+
const e = raw.estimate;
|
|
794
|
+
const project = typeof e.project === "string" ? e.project : undefined;
|
|
795
|
+
const [lo, hi, lm, hm, at] = [e.lowUsd, e.highUsd, e.lowMin, e.highMin, e.at].map(num7);
|
|
796
|
+
if (project && lo !== undefined && hi !== undefined && lm !== undefined && hm !== undefined && at !== undefined)
|
|
797
|
+
out.estimate = { project, lowUsd: lo, highUsd: hi, lowMin: lm, highMin: hm, at };
|
|
798
|
+
}
|
|
799
|
+
return out;
|
|
800
|
+
}, parseSeatSession2 = (v) => {
|
|
801
|
+
if (!isObject15(v) || typeof v.id !== "string" || !v.id)
|
|
802
|
+
return;
|
|
803
|
+
const at = num7(v.at);
|
|
804
|
+
if (at === undefined)
|
|
805
|
+
return;
|
|
806
|
+
const out = { id: v.id, at };
|
|
807
|
+
if (typeof v.cwd === "string" && v.cwd)
|
|
808
|
+
out.cwd = v.cwd;
|
|
809
|
+
if (typeof v.model === "string" && v.model)
|
|
810
|
+
out.model = v.model;
|
|
811
|
+
return out;
|
|
812
|
+
}, seatSessionFromStdin2 = (text, now) => {
|
|
813
|
+
const trimmed = text.trim();
|
|
814
|
+
if (!trimmed)
|
|
815
|
+
return null;
|
|
816
|
+
try {
|
|
817
|
+
const raw = JSON.parse(trimmed);
|
|
818
|
+
if (!isObject15(raw) || typeof raw.session_id !== "string" || !raw.session_id)
|
|
819
|
+
return null;
|
|
820
|
+
const out = { id: raw.session_id, at: now };
|
|
821
|
+
if (typeof raw.cwd === "string" && raw.cwd)
|
|
822
|
+
out.cwd = raw.cwd;
|
|
823
|
+
const model = isObject15(raw.model) ? raw.model.id : raw.model;
|
|
824
|
+
if (typeof model === "string" && model)
|
|
825
|
+
out.model = model;
|
|
826
|
+
return out;
|
|
827
|
+
} catch {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
}, bindingResetAt2 = (w, now) => {
|
|
831
|
+
if (!w)
|
|
832
|
+
return;
|
|
833
|
+
const bars = [
|
|
834
|
+
...w.fiveHour ? [w.fiveHour] : [],
|
|
835
|
+
...w.sevenDay ? [w.sevenDay] : [],
|
|
836
|
+
...w.spendLimit ? [w.spendLimit] : [],
|
|
837
|
+
...Object.values(w.models ?? {})
|
|
838
|
+
].filter((b) => b.resetsAt > now);
|
|
839
|
+
const full = bars.filter((b) => b.usedPct >= 100);
|
|
840
|
+
const pick = (full.length ? full : bars).map((b) => b.resetsAt);
|
|
841
|
+
return pick.length ? Math.min(...pick) : undefined;
|
|
842
|
+
}, readSeatState2 = (home) => {
|
|
843
|
+
const p2 = seatStatePath2(home);
|
|
844
|
+
if (!existsSync18(p2))
|
|
845
|
+
return {};
|
|
846
|
+
try {
|
|
847
|
+
return parseSeatState2(JSON.parse(readFileSync15(p2, "utf8")));
|
|
848
|
+
} catch {
|
|
849
|
+
return {};
|
|
850
|
+
}
|
|
851
|
+
}, writeSeatState2 = (home, state) => {
|
|
852
|
+
const dir = configDir(home);
|
|
853
|
+
mkdirSync14(dir, { recursive: true, mode: 448 });
|
|
854
|
+
const target = seatStatePath2(home);
|
|
855
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
856
|
+
writeFileSync14(tmp, `${JSON.stringify(state)}
|
|
857
|
+
`, { mode: 384 });
|
|
858
|
+
renameSync7(tmp, target);
|
|
859
|
+
}, pctOf2 = (b) => b?.usedPct, shouldPostSeat2 = (state, next, now) => {
|
|
860
|
+
if (state.lastPostedAt === undefined || !state.lastPosted)
|
|
861
|
+
return true;
|
|
862
|
+
if (now - state.lastPostedAt >= SEAT_POST_MIN_INTERVAL_MS2)
|
|
863
|
+
return true;
|
|
864
|
+
const pairs = [
|
|
865
|
+
[state.lastPosted.fiveHour, pctOf2(next.fiveHour)],
|
|
866
|
+
[state.lastPosted.sevenDay, pctOf2(next.sevenDay)],
|
|
867
|
+
[state.lastPosted.spendLimit, pctOf2(next.spendLimit)]
|
|
868
|
+
];
|
|
869
|
+
for (const [prev, cur] of pairs) {
|
|
870
|
+
if (prev === undefined && cur === undefined)
|
|
871
|
+
continue;
|
|
872
|
+
if (prev === undefined || cur === undefined)
|
|
873
|
+
return true;
|
|
874
|
+
if (Math.abs(cur - prev) >= SEAT_POST_MIN_DELTA_PCT2)
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
return false;
|
|
878
|
+
}, markPosted2 = (state, w, now) => {
|
|
879
|
+
const lastPosted = {};
|
|
880
|
+
if (w.fiveHour)
|
|
881
|
+
lastPosted.fiveHour = w.fiveHour.usedPct;
|
|
882
|
+
if (w.sevenDay)
|
|
883
|
+
lastPosted.sevenDay = w.sevenDay.usedPct;
|
|
884
|
+
if (w.spendLimit)
|
|
885
|
+
lastPosted.spendLimit = w.spendLimit.usedPct;
|
|
886
|
+
return { ...state, window: w, lastPostedAt: now, lastPosted };
|
|
887
|
+
}, seatWallDue = (state, w) => {
|
|
888
|
+
if (!w?.sevenDay || w.sevenDay.usedPct < 100)
|
|
889
|
+
return false;
|
|
890
|
+
return state.lastWallResetsAt === undefined || state.lastWallResetsAt < w.sevenDay.resetsAt;
|
|
891
|
+
}, seatWallEvents2 = (state, w) => {
|
|
892
|
+
if (!w)
|
|
893
|
+
return [];
|
|
894
|
+
const out = [];
|
|
895
|
+
const bars = [
|
|
896
|
+
["fiveHour", w.fiveHour],
|
|
897
|
+
["sevenDay", w.sevenDay],
|
|
898
|
+
["spendLimit", w.spendLimit]
|
|
899
|
+
];
|
|
900
|
+
for (const [bar, b] of bars) {
|
|
901
|
+
if (!b)
|
|
902
|
+
continue;
|
|
903
|
+
const seen = state.warned?.[bar] ?? {};
|
|
904
|
+
const weeklyOld = bar === "sevenDay" && state.lastWallResetsAt !== undefined && state.lastWallResetsAt >= b.resetsAt;
|
|
905
|
+
if (b.usedPct >= APPROACHING_PCT2 && (seen.approaching ?? -1) < b.resetsAt && !weeklyOld)
|
|
906
|
+
out.push({ bar, stage: "approaching", usedPct: b.usedPct, resetsAt: b.resetsAt });
|
|
907
|
+
if (b.usedPct >= 100 && (seen.hit ?? -1) < b.resetsAt && !weeklyOld)
|
|
908
|
+
out.push({ bar, stage: "hit", usedPct: b.usedPct, resetsAt: b.resetsAt });
|
|
909
|
+
}
|
|
910
|
+
return out;
|
|
911
|
+
}, markWarned2 = (state, events) => {
|
|
912
|
+
if (events.length === 0)
|
|
913
|
+
return state;
|
|
914
|
+
const warned = { ...state.warned ?? {} };
|
|
915
|
+
let lastWallResetsAt = state.lastWallResetsAt;
|
|
916
|
+
for (const e of events) {
|
|
917
|
+
warned[e.bar] = { ...warned[e.bar] ?? {}, [e.stage]: e.resetsAt };
|
|
918
|
+
if (e.bar === "sevenDay" && e.stage === "hit")
|
|
919
|
+
lastWallResetsAt = e.resetsAt;
|
|
920
|
+
}
|
|
921
|
+
return { ...state, warned, ...lastWallResetsAt !== undefined ? { lastWallResetsAt } : {} };
|
|
922
|
+
}, CREDITS_PATTERN2, creditsHitIn2 = (text) => CREDITS_PATTERN2.test(text), transcriptPathFromStdin2 = (text) => {
|
|
923
|
+
try {
|
|
924
|
+
const raw = JSON.parse(text.trim());
|
|
925
|
+
return isObject15(raw) && typeof raw.transcript_path === "string" && raw.transcript_path ? raw.transcript_path : null;
|
|
926
|
+
} catch {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
}, scanTranscriptForCredits2 = (path, prior) => {
|
|
930
|
+
const from = prior && prior.path === path ? prior.offset : 0;
|
|
931
|
+
try {
|
|
932
|
+
const size = statSync3(path).size;
|
|
933
|
+
if (size <= from)
|
|
934
|
+
return {
|
|
935
|
+
hit: false,
|
|
936
|
+
next: { path, offset: size, ...prior?.hitAt ? { hitAt: prior.hitAt } : {} }
|
|
937
|
+
};
|
|
938
|
+
const start = Math.max(from, size - 256 * 1024);
|
|
939
|
+
const fd = openSync2(path, "r");
|
|
940
|
+
try {
|
|
941
|
+
const buf = Buffer.alloc(size - start);
|
|
942
|
+
readSync2(fd, buf, 0, buf.length, start);
|
|
943
|
+
const hit = creditsHitIn2(buf.toString("utf8"));
|
|
944
|
+
return {
|
|
945
|
+
hit,
|
|
946
|
+
next: {
|
|
947
|
+
path,
|
|
948
|
+
offset: size,
|
|
949
|
+
...hit ? { hitAt: Date.now() } : prior?.hitAt ? { hitAt: prior.hitAt } : {}
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
} finally {
|
|
953
|
+
closeSync2(fd);
|
|
954
|
+
}
|
|
955
|
+
} catch {
|
|
956
|
+
return { hit: false, next: prior && prior.path === path ? prior : { path, offset: 0 } };
|
|
957
|
+
}
|
|
958
|
+
}, seatWindowBody2 = (w) => ({
|
|
959
|
+
capturedAt: w.capturedAt,
|
|
960
|
+
source: "claude",
|
|
961
|
+
...w.fiveHour ? { fiveHour: w.fiveHour } : {},
|
|
962
|
+
...w.sevenDay ? { sevenDay: w.sevenDay } : {},
|
|
963
|
+
...w.spendLimit ? { spendLimit: w.spendLimit } : {}
|
|
964
|
+
}), postJson2 = async (o2, path, body) => {
|
|
965
|
+
const fetchFn = o2.fetchFn ?? fetch;
|
|
966
|
+
try {
|
|
967
|
+
const res = await fetchFn(`${o2.proxyUrl}${path}`, {
|
|
968
|
+
method: "POST",
|
|
969
|
+
headers: { authorization: `Bearer ${o2.token}`, "content-type": "application/json" },
|
|
970
|
+
body: JSON.stringify(body),
|
|
971
|
+
signal: AbortSignal.timeout(o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS2)
|
|
972
|
+
});
|
|
973
|
+
return res.ok;
|
|
974
|
+
} catch {
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
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) => {
|
|
549
978
|
if (stdin.isTTY) {
|
|
550
|
-
|
|
979
|
+
resolve6("");
|
|
551
980
|
return;
|
|
552
981
|
}
|
|
553
982
|
let data = "";
|
|
@@ -557,7 +986,7 @@ var SEAT_POST_MIN_INTERVAL_MS2, readStdin2 = (timeoutMs, stdin = process.stdin)
|
|
|
557
986
|
return;
|
|
558
987
|
settled = true;
|
|
559
988
|
clearTimeout(timer);
|
|
560
|
-
|
|
989
|
+
resolve6(data);
|
|
561
990
|
};
|
|
562
991
|
const timer = setTimeout(finish, timeoutMs);
|
|
563
992
|
stdin.setEncoding("utf8");
|
|
@@ -572,6 +1001,7 @@ var init_seat = __esm(() => {
|
|
|
572
1001
|
init_config();
|
|
573
1002
|
init_meter();
|
|
574
1003
|
SEAT_POST_MIN_INTERVAL_MS2 = 5 * 60000;
|
|
1004
|
+
CREDITS_PATTERN2 = /out of usage credits|cc_cli_limit_message/i;
|
|
575
1005
|
});
|
|
576
1006
|
|
|
577
1007
|
// src/index.ts
|
|
@@ -757,28 +1187,34 @@ var GUIDES = [
|
|
|
757
1187
|
id: "board",
|
|
758
1188
|
title: "Read the board",
|
|
759
1189
|
audience: "everyone",
|
|
760
|
-
lede: "
|
|
1190
|
+
lede: "Work is the items: the pool of sized work and what landed this week. Sleds is who pulls them — a person or an agent on its own — and Seats what each sled pulls with.",
|
|
761
1191
|
steps: [
|
|
762
1192
|
{
|
|
763
|
-
do: "Open `
|
|
764
|
-
where: "
|
|
765
|
-
note: "Five lanes: waiting for product · ready · moving · landed this week · worth landed. Click a lane to narrow the page; `Mine` shows your sled, the
|
|
1193
|
+
do: "Open `Work` and read the strip",
|
|
1194
|
+
where: "Work",
|
|
1195
|
+
note: "Five lanes: waiting for product · ready · moving · landed this week · worth landed. Click a lane to narrow the page; `Mine` shows your sled, the items proposed for you and the asks you filed."
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
do: "Read a pool item: the band, the worth, the chip, the readiness",
|
|
1199
|
+
note: "`proposed` names the sled the rules (and the model) suggest, with why; `waiting for product` needs a yes; `N gaps` says what is missing before anyone could start; an amber note says a related or duplicate item exists. `Take it` puts it on your sled; an approver can `Put it on <name>'s sled`."
|
|
766
1200
|
},
|
|
767
1201
|
{
|
|
768
|
-
do: "Read a
|
|
769
|
-
|
|
1202
|
+
do: "Read a sled card: the seats, the jobs, the band spent",
|
|
1203
|
+
where: "Sleds",
|
|
1204
|
+
note: "Each seat is one agent on one platform with its own wall; the bar is the window used. A job says which seat it runs on and, at a wall, `paused at a wall` with `Move to <seat>` when the sled has headroom elsewhere."
|
|
770
1205
|
},
|
|
771
1206
|
{
|
|
772
|
-
do: "
|
|
773
|
-
note: "
|
|
1207
|
+
do: "Open an item for the whole story",
|
|
1208
|
+
note: "The modal: the description, the links and files, the brief and who filed it, the child items, the questions, the thread — and every verb that applies to you. `copy as markdown` is the whole item as one document."
|
|
774
1209
|
},
|
|
775
1210
|
{
|
|
776
|
-
do: "
|
|
777
|
-
|
|
1211
|
+
do: "File one from here when chat is not at hand",
|
|
1212
|
+
where: "Work › + New item",
|
|
1213
|
+
note: "Title, why, done — the same brief your AI files from chat — then the description, repos, due, worth and a parent if it has one. Sized on file."
|
|
778
1214
|
}
|
|
779
1215
|
],
|
|
780
|
-
check: "Your own sled shows under `Mine` with the jobs you hold; a landed
|
|
781
|
-
scope: "A product viewer sees
|
|
1216
|
+
check: "Your own sled shows under `Mine` with the jobs you hold; a landed item shows its measured time and verdict under `Landed this week` and on Record › Receipts.",
|
|
1217
|
+
scope: "A product viewer sees sleds and holders, never seats, models or spend; the words on the board are the words in chat — item, sled, seat, job, wall, receipt."
|
|
782
1218
|
},
|
|
783
1219
|
{
|
|
784
1220
|
id: "workspace",
|
|
@@ -852,7 +1288,7 @@ var GUIDES = [
|
|
|
852
1288
|
{
|
|
853
1289
|
do: "Optional: SLEDS for Chrome — the meter, and the Team roster and credits when you administer the plan",
|
|
854
1290
|
where: "The zip your admin sends › unzip › `chrome://extensions` › Developer mode › `Load unpacked` › the `dist` folder",
|
|
855
|
-
note: "Then right-click the icon › Options: Proxy URL `https://proxy.sleds.ai`, the same seat token, platform Anthropic, tick the consent box, Save. It reads your seat's bars from claude.ai — the
|
|
1291
|
+
note: "Then right-click the icon › Options: Proxy URL `https://proxy.sleds.ai`, the same seat token, platform Anthropic, tick the consent box, Save. It reads your seat's bars from claude.ai — the quota the proxy cannot see — and reports them even when Claude Code is closed. Chrome nags about developer-mode extensions on start until the Web Store listing; dismiss it."
|
|
856
1292
|
}
|
|
857
1293
|
],
|
|
858
1294
|
check: "`/sled usage` answers with your name and workspace; the status line in Claude Code shows the same bars."
|
|
@@ -904,7 +1340,7 @@ var GUIDES = [
|
|
|
904
1340
|
{
|
|
905
1341
|
do: "Connect the site and name the projects to mirror",
|
|
906
1342
|
where: "Settings › Integrations › Jira › `Connect`",
|
|
907
|
-
note: "`Site` is the host (`acme.atlassian.net`). `Projects to mirror, read-only` takes project keys (`WORK, CORE`): their open issues become sized
|
|
1343
|
+
note: "`Site` is the host (`acme.atlassian.net`). `Projects to mirror, read-only` takes project keys (`WORK, CORE`): their open issues become sized items in the pool on Sleds, epics with their children, and an issue that reaches Done lands its row — pulled every 30 minutes or with `Sync now`. Leave `Project key intake files into` blank for read-only; set it later and intake files a Jira issue and `plan` files sub-tasks."
|
|
908
1344
|
},
|
|
909
1345
|
{
|
|
910
1346
|
do: "Prove the token with `Send test` and the issue key left blank",
|
|
@@ -917,13 +1353,13 @@ var GUIDES = [
|
|
|
917
1353
|
}
|
|
918
1354
|
],
|
|
919
1355
|
check: "The Jira tile says connected with the token's last four, the blank Send test names the account the token belongs to, and after `Sync now` the pool on Sleds lists the mirrored project's open issues with a band on each.",
|
|
920
|
-
scope: "With the project key blank and no `sled` labels in use, SLEDS never creates or comments on a Jira issue. The mirror only reads;
|
|
1356
|
+
scope: "With the project key blank and no `sled` labels in use, SLEDS never creates or comments on a Jira issue. The mirror only reads; items carry the tracker's own state, labels, priority and assignee, never edited here."
|
|
921
1357
|
},
|
|
922
1358
|
{
|
|
923
1359
|
id: "slack",
|
|
924
1360
|
title: "Connect Slack",
|
|
925
1361
|
audience: "admin",
|
|
926
|
-
lede: "One button. SLEDS posts hand-offs, walls and digests to a channel of yours
|
|
1362
|
+
lede: "One button. SLEDS posts hand-offs, walls and digests to a channel of yours, DMs the person a thing is for, and answers `@SLEDS` in any channel — with nothing to create, copy or paste.",
|
|
927
1363
|
steps: [
|
|
928
1364
|
{
|
|
929
1365
|
do: "Add to Slack",
|
|
@@ -935,6 +1371,11 @@ var GUIDES = [
|
|
|
935
1371
|
where: "In Slack, in that channel: `/invite @SLEDS`",
|
|
936
1372
|
note: "Posting to the channel works from the moment you allowed; reading and replying in a private channel needs the bot as a member."
|
|
937
1373
|
},
|
|
1374
|
+
{
|
|
1375
|
+
do: "Talk to it in a channel",
|
|
1376
|
+
where: "Any channel the bot is in: `@SLEDS where is QW-142` · `@SLEDS what's open` · `@SLEDS item QW-142` · `@SLEDS create a ticket for this`",
|
|
1377
|
+
note: "It answers in the thread. `create a ticket for this` under someone's message reads that message as the ask, posts the draft in the thread with the questions still open, takes the replies as answers, and files on *yes* — with the person who asked as the filer. A workspace installed before Sept 28 clicks Add to Slack once more so the app has the channel scopes."
|
|
1378
|
+
},
|
|
938
1379
|
{
|
|
939
1380
|
do: "Send one test post",
|
|
940
1381
|
where: "Settings › Integrations › Slack › `Send test`",
|
|
@@ -952,7 +1393,7 @@ var GUIDES = [
|
|
|
952
1393
|
id: "approval",
|
|
953
1394
|
title: "Turn on product's yes",
|
|
954
1395
|
audience: "admin",
|
|
955
|
-
lede: "An approval step before engineering can take
|
|
1396
|
+
lede: "An approval step before engineering can take an item: it waits until someone product-side says yes, or defers it with a note.",
|
|
956
1397
|
steps: [
|
|
957
1398
|
{
|
|
958
1399
|
do: "Choose who needs a yes",
|
|
@@ -966,7 +1407,7 @@ var GUIDES = [
|
|
|
966
1407
|
},
|
|
967
1408
|
{
|
|
968
1409
|
do: "Approvers decide from the board or the Overview",
|
|
969
|
-
where: "Overview › `Waiting on product`, or the
|
|
1410
|
+
where: "Overview › `Waiting on product`, or the item on Work",
|
|
970
1411
|
note: "`Approve` opens the gate; `Defer` keeps it closed and carries the note back to the filer, who answers in the brief and it comes back. In chat the same is `approve {project, decision, note?}`."
|
|
971
1412
|
},
|
|
972
1413
|
{
|
|
@@ -974,7 +1415,7 @@ var GUIDES = [
|
|
|
974
1415
|
note: "An approved epic can be planned in chat (`plan`): its milestones are sized, filed under it, and inherit the yes — they do not queue again. The epic's cap is the sum."
|
|
975
1416
|
}
|
|
976
1417
|
],
|
|
977
|
-
check: "File an ask from chat (`intake`): its
|
|
1418
|
+
check: "File an ask from chat (`intake`): its item shows waiting for product in the pool on Work, the approvers see it under Waiting on product, and `run` on it is refused until someone approves."
|
|
978
1419
|
}
|
|
979
1420
|
];
|
|
980
1421
|
function findGuides(filter) {
|
|
@@ -1030,7 +1471,7 @@ var VERBS = [
|
|
|
1030
1471
|
want: "See my overnight jobs and the team's open hand-offs",
|
|
1031
1472
|
mcp: "jobs · handoffs",
|
|
1032
1473
|
claudeCode: "/sled jobs · `sleds handoffs`",
|
|
1033
|
-
app: "
|
|
1474
|
+
app: "Work › Moving (the runs on each sled) · Record › Receipts once they land",
|
|
1034
1475
|
how: "Newest first: id, status, route, cost. Hand-offs meant for you are listed first."
|
|
1035
1476
|
},
|
|
1036
1477
|
{
|
|
@@ -1038,8 +1479,8 @@ var VERBS = [
|
|
|
1038
1479
|
group: "meter",
|
|
1039
1480
|
want: "Ask where a piece of work is",
|
|
1040
1481
|
mcp: "status {q?} — a tracker key or a word from the title; nothing for what you filed",
|
|
1041
|
-
app: "
|
|
1042
|
-
how: "One paragraph per
|
|
1482
|
+
app: "Work (the strip and the item's stage: waiting for product · deferred · ready · moving · landed)",
|
|
1483
|
+
how: "One paragraph per item: its stage, who has it, the band, the worth, the tree when it has milestones, the PR when it landed. Yours by default; a lead widens to the team."
|
|
1043
1484
|
},
|
|
1044
1485
|
{
|
|
1045
1486
|
id: "touches",
|
|
@@ -1052,18 +1493,66 @@ var VERBS = [
|
|
|
1052
1493
|
{
|
|
1053
1494
|
id: "attach",
|
|
1054
1495
|
group: "meter",
|
|
1055
|
-
want: "Put the spec, the prototype or a
|
|
1056
|
-
mcp: "attach {project, name, content, mime?, kind?} — intake takes the same as `attachments` when filing",
|
|
1057
|
-
app: "
|
|
1058
|
-
how: "The file itself lives in SLEDS, not only in the chat that made it: a spec as markdown, a prototype as one HTML page (opens and renders in its own tab), a screenshot. Up to 4 MB; the same name again replaces. Anyone on the board opens it from the
|
|
1496
|
+
want: "Put the spec, the prototype, a screenshot or a link on the item so the team can open it",
|
|
1497
|
+
mcp: "attach {project, name, content, mime?, kind?} · attach {project, url, title?} — intake takes the same as `attachments` when filing",
|
|
1498
|
+
app: "Work › the item › Links",
|
|
1499
|
+
how: "The file itself lives in SLEDS, not only in the chat that made it: a spec as markdown, a prototype as one HTML page (opens and renders in its own tab), a screenshot — or a link to a doc, a Figma, a Loom. Up to 4 MB; the same name again replaces. Anyone on the board opens it from the item; the person who added it, or an admin, can remove it."
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
id: "update",
|
|
1503
|
+
group: "meter",
|
|
1504
|
+
want: "Change an item — its title, why, done, description, due day, what it sits under, or what it touches",
|
|
1505
|
+
mcp: "update {project, ask?, why?, done?, smallest?, touches?, demand?, description?, due?, parent?, repos?, sensitivity?}",
|
|
1506
|
+
app: "Work › the item › click a field",
|
|
1507
|
+
how: "The filer, an approver or an admin changes an item from chat or by clicking the field in the app; every change says who and through what (via Claude · Cowork, via ChatGPT, in the app) and the record keeps before and after. A change to the ask, done or smallest marks the band stale with a Re-size at hand — never re-sized silently. A mirrored ticket is the tracker's word and is not changed here."
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
id: "archive",
|
|
1511
|
+
group: "meter",
|
|
1512
|
+
want: "Put an item away without deleting it",
|
|
1513
|
+
mcp: "archive {project, undo?}",
|
|
1514
|
+
app: "Work › the item › ⋯ › Archive · the archived chip on Work",
|
|
1515
|
+
how: "Archived items leave Work, the sleds and the counts, and keep everything — unarchive brings one back whole. The filer, an approver or an admin. Delete (⋯ › Delete…) is an admin's and only for a hand-filed item that never ran; a mirrored ticket and a landed item are archived, never deleted."
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
id: "item",
|
|
1519
|
+
group: "meter",
|
|
1520
|
+
want: "Read an item whole — what an engineer or an agent needs to pick it up cold",
|
|
1521
|
+
mcp: "item {q}",
|
|
1522
|
+
app: "Work › the item · Copy as markdown",
|
|
1523
|
+
how: "One markdown document: the brief, the description, the repo(s), the links and files, what sits under it, the open questions, the thread, and the readiness line — what is still missing before someone could start. It is what an agent reads before building from an item, and what the readiness check reads."
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
id: "check",
|
|
1527
|
+
group: "meter",
|
|
1528
|
+
want: "Know whether an item says enough to start — and what is missing",
|
|
1529
|
+
mcp: "check {project}",
|
|
1530
|
+
app: "Work › the item › Readiness › Check",
|
|
1531
|
+
how: "Readiness has two layers. The rules run on every read and cost nothing: no why, no done, no repo, an open question, not sized, waiting for product's yes, or thin. The check is a small model on the workspace's platform key reading the item as an engineer would and naming what it would have to ask before starting; its answer goes on the thread as a comment from SLEDS and rides on the item's readiness from then on. Nothing here judges the idea — only whether the item says enough."
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
id: "profile",
|
|
1535
|
+
group: "meter",
|
|
1536
|
+
want: "Know how hard an item is, what it touches, and which model tier fits",
|
|
1537
|
+
mcp: "item {q} (the Profile line) · update {project, sensitivity, sensitivityNote?}",
|
|
1538
|
+
app: "Work › the item › Profile",
|
|
1539
|
+
how: "Spec 18. Rules, never a model: complexity (low · medium · high, with reasons — the kind of work, the steps that want a premium model, files, repos, gaps) maps to a tier (economy · default · premium; economy is Haiku). Sensitivity (public · internal · restricted) is the most restrictive of what a person said, what the repo's `.sled/config.yml` says (`dataClass`, `restrictedPaths`, `publicPaths`), what the product map says, and what the rules detect in the item's own words — personal data, secret shapes, bulk customer data. Anyone can raise it; only an approver can lower it, with a note, on the record. A restricted item's text never goes to a cloud model: no readiness check, no rerank. Routing on it comes with Policy › Routing (P2)."
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
id: "comment",
|
|
1543
|
+
group: "meter",
|
|
1544
|
+
want: "Leave a note on an item's thread — for the team, or as your agent's record of what it did",
|
|
1545
|
+
mcp: "comment {project, md}",
|
|
1546
|
+
app: "Work › the item › Thread",
|
|
1547
|
+
how: "The thread is one conversation between the team and its agents: a comment from a person, a note from their Claude, a size from the estimator, a run's receipt, a question and its answer — in order, each signed with who and through what."
|
|
1059
1548
|
},
|
|
1060
1549
|
{
|
|
1061
1550
|
id: "answer",
|
|
1062
1551
|
group: "meter",
|
|
1063
1552
|
want: "Answer a question engineering asked about something I filed",
|
|
1064
1553
|
mcp: "answer {answer, project?, id?} — nothing to see what is waiting on you",
|
|
1065
|
-
app: "
|
|
1066
|
-
how: "An approver asks on the
|
|
1554
|
+
app: "Work › the item › Questions",
|
|
1555
|
+
how: "An approver asks on the item; you hear by bell, DM and email. Your reply, here or on the item, goes on the record and the asker hears at once. A worth question answered with a level or a band sets the item's worth."
|
|
1067
1556
|
},
|
|
1068
1557
|
{
|
|
1069
1558
|
id: "estimate",
|
|
@@ -1071,7 +1560,7 @@ var VERBS = [
|
|
|
1071
1560
|
want: "Size a task before running it",
|
|
1072
1561
|
mcp: "estimate {spec, source?}",
|
|
1073
1562
|
claudeCode: "/sled estimate · `sleds spec-event` fires on save of a spec file",
|
|
1074
|
-
app: "
|
|
1563
|
+
app: "Work (items appear in the pool as specs and issues are sized)",
|
|
1075
1564
|
how: "A cost and time band with confidence and a spend cap — never a point figure. Recorded; the actual calibrates the next one."
|
|
1076
1565
|
},
|
|
1077
1566
|
{
|
|
@@ -1080,7 +1569,7 @@ var VERBS = [
|
|
|
1080
1569
|
want: "Say what the work is worth to the business",
|
|
1081
1570
|
mcp: "a `Worth:` line in the spec you estimate (`Worth: high · $5k–20k · Acme renewal`)",
|
|
1082
1571
|
claudeCode: "`SLEDS-Worth:` line in the PR body",
|
|
1083
|
-
app: "
|
|
1572
|
+
app: "Work › the item › Worth…",
|
|
1084
1573
|
how: "A level, dollars when you know them, a note. Yours to state; the tracker's priority stands in at low confidence; blank stays blank."
|
|
1085
1574
|
},
|
|
1086
1575
|
{
|
|
@@ -1088,15 +1577,15 @@ var VERBS = [
|
|
|
1088
1577
|
group: "send",
|
|
1089
1578
|
want: "Send an ask to engineering from the conversation I already have open",
|
|
1090
1579
|
mcp: "intake {text, links?, answers?, confirm?, parent?}",
|
|
1091
|
-
app: "
|
|
1092
|
-
how: "Your AI reads what you sent and asks only what it cannot answer — who it is for, what done looks like, the smallest version, the worth, what it touches, how many have asked — then shows the brief. On yes the
|
|
1580
|
+
app: "Work (the item lands in the pool sized, filed by you, at waiting or ready)",
|
|
1581
|
+
how: "Your AI reads what you sent and asks only what it cannot answer — who it is for, what done looks like, the smallest version, the worth, what it touches, how many have asked — then shows the brief. On yes the item is filed on the sled and sized. Nothing is written before yes; worth is never guessed. `parent` files it under an epic or the ask it spun off from, so the parent's tree cost is true."
|
|
1093
1582
|
},
|
|
1094
1583
|
{
|
|
1095
1584
|
id: "plan",
|
|
1096
1585
|
group: "send",
|
|
1097
1586
|
want: "Turn an approved epic into milestones I can send one at a time",
|
|
1098
1587
|
mcp: "plan {q | project, milestones?, confirm?} — the epic first, then the drafted list",
|
|
1099
|
-
app: "
|
|
1588
|
+
app: "Work (the epic expands into its child items; the tree under its band) · Approve · plan it",
|
|
1100
1589
|
how: "Your AI drafts 3–7 vertical slices from the epic's brief — each something a person could use on its own — and shows them. On yes SLEDS sizes each, files it under the epic with the epic's worth and approval (a Jira sub-task when the epic lives there), and says what the tree will cost. Nothing is filed before yes."
|
|
1101
1590
|
},
|
|
1102
1591
|
{
|
|
@@ -1104,15 +1593,15 @@ var VERBS = [
|
|
|
1104
1593
|
group: "send",
|
|
1105
1594
|
want: "Give product's yes on an ask — or say not now, and why",
|
|
1106
1595
|
mcp: "approve {q, decision: approve | defer, note?}",
|
|
1107
|
-
app: "
|
|
1108
|
-
how: "Off by default. When Policy says asks from intake (or every
|
|
1596
|
+
app: "Work › Approve / Defer on a waiting item · Overview › Waiting on product · Policy › Product's yes",
|
|
1597
|
+
how: "Off by default. When Policy says asks from intake (or every item) wait for product, an item sits at waiting until an approver — the named approvers, else an owner or admin — approves it; nobody can run it before. Defer keeps it closed and sends the note to the filer; they answer in the brief and it comes back."
|
|
1109
1598
|
},
|
|
1110
1599
|
{
|
|
1111
1600
|
id: "dispatch",
|
|
1112
1601
|
group: "send",
|
|
1113
1602
|
want: "Ask where a piece of work should go, then send it there",
|
|
1114
1603
|
mcp: "dispatch {q, run?: now | park | overnight, to?} — suggests with no run",
|
|
1115
|
-
app: "
|
|
1604
|
+
app: "Work › Take it (onto your own sled) · Work › the item › Send to… · Put it on <name>'s sled",
|
|
1116
1605
|
how: "Now on the seat with the most weekly headroom, overnight at half price, parked for the team, or not worth it yet — with the reasons. Nothing runs until you say run."
|
|
1117
1606
|
},
|
|
1118
1607
|
{
|
|
@@ -1121,7 +1610,7 @@ var VERBS = [
|
|
|
1121
1610
|
want: "Send work to a teammate or a buddy",
|
|
1122
1611
|
mcp: "handoff {task, to?, note?}",
|
|
1123
1612
|
claudeCode: "/sled handoff · the wall popup",
|
|
1124
|
-
app: "
|
|
1613
|
+
app: "Work › the item › Send to… · extension › Park hand-off",
|
|
1125
1614
|
how: "Parks a self-contained brief (the picker starts cold). Named by email — a teammate, or a paired buddy in another workspace — or anyone on the team."
|
|
1126
1615
|
},
|
|
1127
1616
|
{
|
|
@@ -1130,7 +1619,7 @@ var VERBS = [
|
|
|
1130
1619
|
want: "Take back a hand-off nobody has picked up",
|
|
1131
1620
|
mcp: "withdraw {handoffId}",
|
|
1132
1621
|
claudeCode: "`sleds withdraw <id>`",
|
|
1133
|
-
app: "
|
|
1622
|
+
app: "Work › the item › Withdraw",
|
|
1134
1623
|
how: "Only the seat that parked it can withdraw it. Unpicked hand-offs are reminded after a day and expire after three."
|
|
1135
1624
|
},
|
|
1136
1625
|
{
|
|
@@ -1139,7 +1628,7 @@ var VERBS = [
|
|
|
1139
1628
|
want: "Pick up work someone sent me",
|
|
1140
1629
|
mcp: "handoffs → pickup {handoffId}",
|
|
1141
1630
|
claudeCode: "/sled pickup <id> · `sleds pickup <id>`",
|
|
1142
|
-
app: "
|
|
1631
|
+
app: "Work › Take it · extension › Take it",
|
|
1143
1632
|
how: "A parked task comes back verbatim; a walled session comes back as its first and last messages. Runs on your seat, metered to your workspace."
|
|
1144
1633
|
},
|
|
1145
1634
|
{
|
|
@@ -1157,7 +1646,7 @@ var VERBS = [
|
|
|
1157
1646
|
want: "Run this tonight at half price",
|
|
1158
1647
|
mcp: "overnight {spec, source?}",
|
|
1159
1648
|
claudeCode: "/sled overnight",
|
|
1160
|
-
app: "wall popup › Run overnight · Sleds › the Night shift
|
|
1649
|
+
app: "wall popup › Run overnight · Sleds › the Night shift sled",
|
|
1161
1650
|
how: "Sized first; the cap bounds the spend. Runs in the workspace's overnight window on the batch tier; `/sled resume <id>` pulls it back in."
|
|
1162
1651
|
},
|
|
1163
1652
|
{
|
|
@@ -1165,7 +1654,7 @@ var VERBS = [
|
|
|
1165
1654
|
group: "defer",
|
|
1166
1655
|
want: "Pull a finished overnight job into my session",
|
|
1167
1656
|
claudeCode: "/sled resume <id>",
|
|
1168
|
-
app: "Sleds › the Night shift
|
|
1657
|
+
app: "Sleds › the Night shift sled › the item",
|
|
1169
1658
|
how: "The delivered result, with its record of which model built it — which model built it, signed by the workspace key."
|
|
1170
1659
|
},
|
|
1171
1660
|
{
|
|
@@ -1174,15 +1663,15 @@ var VERBS = [
|
|
|
1174
1663
|
want: "Record what a piece of work actually took",
|
|
1175
1664
|
mcp: "actual {source, minutes?, usd?, model?, ref?}",
|
|
1176
1665
|
claudeCode: "`SLEDS-Actual: 45 min · $7` in the PR body — the ledger posts it on merge",
|
|
1177
|
-
app: "Record › Receipts (fills in on merge or delivery) ·
|
|
1178
|
-
how: "Closes the estimate: est → actual per
|
|
1666
|
+
app: "Record › Receipts (fills in on merge or delivery) · Work › Landed this week",
|
|
1667
|
+
how: "Closes the estimate: est → actual per item, and the calibration for the next band. A delivered job closes its own."
|
|
1179
1668
|
},
|
|
1180
1669
|
{
|
|
1181
1670
|
id: "verify",
|
|
1182
1671
|
group: "close",
|
|
1183
1672
|
want: "Check which model built something",
|
|
1184
1673
|
claudeCode: "`sleds verify <jobId | SLEDS-Attest line>`",
|
|
1185
|
-
app: "Record › Receipts › the
|
|
1674
|
+
app: "Record › Receipts › the item's ref",
|
|
1186
1675
|
how: "Ed25519 over the job, model, outputs and time; verified locally against the workspace's public key."
|
|
1187
1676
|
},
|
|
1188
1677
|
{
|
|
@@ -1234,6 +1723,10 @@ var VERBS = [
|
|
|
1234
1723
|
}
|
|
1235
1724
|
];
|
|
1236
1725
|
var FAQ = [
|
|
1726
|
+
{
|
|
1727
|
+
q: "Item, sled, seat — what are the words?",
|
|
1728
|
+
a: "An item is a unit of work — a feature, a fix, an ask — with a band and a worth; children are child items. A sled is who the work is on: a person, or an agent on its own. A seat is what a sled pulls with: an agent on a platform with a credential (Claude on a Team plan, Codex on an API key), each with its own wall. Work is the page of items, Sleds the page of sleds, Seats the capacity under them."
|
|
1729
|
+
},
|
|
1237
1730
|
{
|
|
1238
1731
|
q: "What does SLEDS see, and what does it keep?",
|
|
1239
1732
|
a: "Requests stream through the proxy to your own Claude or OpenAI seat and back; we keep the numbers — tokens, model, cost, when — and the decisions, never your prompts or code. Two features carry content because they cannot work without it, and both are yours to turn off: a hand-off parks the conversation for a teammate (encrypted, expires in 7 days); an estimate reads a spec to size it. Your tracker and Slack receive what you already see in them."
|
|
@@ -1447,17 +1940,17 @@ function renderBrief(b) {
|
|
|
1447
1940
|
function renderBriefMarkdown(b, open, extra = {}) {
|
|
1448
1941
|
const cell = (t) => t.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
|
|
1449
1942
|
const rows = ["| | | |", "|---|---|---|"];
|
|
1450
|
-
const
|
|
1943
|
+
const field2 = (label, f) => {
|
|
1451
1944
|
if (f)
|
|
1452
1945
|
rows.push(`| **${label}** | ${cell(f.text)} | ${fromWord(f)} |`);
|
|
1453
1946
|
};
|
|
1454
1947
|
rows.push(`| **Ask** | ${cell(b.ask.text)} | ${fromWord(b.ask)} |`);
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1948
|
+
field2("Why", b.why);
|
|
1949
|
+
field2("Done", b.done);
|
|
1950
|
+
field2("Smallest", b.smallest);
|
|
1458
1951
|
rows.push(`| **Worth** | ${b.worth ? cell(worthLabel(b.worth)) : "_not stated_"} | ${b.worth ? "stated" : ""} |`);
|
|
1459
|
-
|
|
1460
|
-
|
|
1952
|
+
field2("Touches", b.touches);
|
|
1953
|
+
field2("Demand", b.demand);
|
|
1461
1954
|
if (b.due)
|
|
1462
1955
|
rows.push(`| **Due** | ${b.due} | from your words |`);
|
|
1463
1956
|
const out = ["**Brief — draft**", "", ...rows];
|
|
@@ -1561,6 +2054,8 @@ var STOP = new Set([
|
|
|
1561
2054
|
"up",
|
|
1562
2055
|
"via"
|
|
1563
2056
|
]);
|
|
2057
|
+
// ../../packages/shared/src/profile/profile.ts
|
|
2058
|
+
var LIGHT = new Set(["summarize", "research", "walkthrough", "docs"]);
|
|
1564
2059
|
// ../../packages/shared/src/dispatch/plan.ts
|
|
1565
2060
|
init_format();
|
|
1566
2061
|
|
|
@@ -1666,29 +2161,50 @@ init_format();
|
|
|
1666
2161
|
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1667
2162
|
var str2 = (v) => typeof v === "string" ? v : undefined;
|
|
1668
2163
|
var num2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
1669
|
-
function statusName(
|
|
1670
|
-
const source = isObject2(
|
|
2164
|
+
function statusName(row2) {
|
|
2165
|
+
const source = isObject2(row2.source) ? row2.source : {};
|
|
1671
2166
|
const kind = str2(source.kind);
|
|
1672
2167
|
const doc = str2(source.doc);
|
|
1673
|
-
const title = str2(
|
|
2168
|
+
const title = str2(row2.title) ?? str2(row2.project) ?? "this row";
|
|
1674
2169
|
if ((kind === "linear" || kind === "github" || kind === "jira") && doc)
|
|
1675
2170
|
return `${doc} · ${title}`;
|
|
1676
|
-
const key = str2(
|
|
2171
|
+
const key = str2(row2.key);
|
|
1677
2172
|
return key ? `${key} · ${title}` : title;
|
|
1678
2173
|
}
|
|
1679
|
-
function stateTail(
|
|
1680
|
-
const retired = str2(
|
|
2174
|
+
function stateTail(row2) {
|
|
2175
|
+
const retired = str2(row2.retired);
|
|
1681
2176
|
if (retired)
|
|
1682
2177
|
return ` Retired: ${retired}.`;
|
|
1683
|
-
if (
|
|
2178
|
+
if (row2.stale === true)
|
|
1684
2179
|
return " Stale: no movement in 14 days.";
|
|
1685
2180
|
return "";
|
|
1686
2181
|
}
|
|
1687
|
-
function statusLine(
|
|
1688
|
-
return `${statusBody(
|
|
2182
|
+
function statusLine(row2, now) {
|
|
2183
|
+
return `${statusBody(row2, now)}${trackerTail(row2)}${stateTail(row2)}${readinessTail(row2)}${profileTail(row2)}${assetsTail(row2)}`;
|
|
1689
2184
|
}
|
|
1690
|
-
function
|
|
1691
|
-
const
|
|
2185
|
+
function profileTail(row2) {
|
|
2186
|
+
const p2 = isObject2(row2.profile) ? row2.profile : null;
|
|
2187
|
+
if (!p2)
|
|
2188
|
+
return "";
|
|
2189
|
+
const line = str2(p2.line);
|
|
2190
|
+
if (!line)
|
|
2191
|
+
return "";
|
|
2192
|
+
const why = Array.isArray(p2.reasons) ? p2.reasons.filter((x) => typeof x === "string") : [];
|
|
2193
|
+
const restricted = str2(p2.sensitivity) === "restricted";
|
|
2194
|
+
return ` Profile: ${line}${why.length && (restricted || str2(p2.source) !== "default") ? ` (${why.slice(0, 2).join("; ")})` : ""}.${restricted ? " Restricted: cloud routes close for it once Policy › Routing says so; nothing is held yet." : ""}`;
|
|
2195
|
+
}
|
|
2196
|
+
function readinessTail(row2) {
|
|
2197
|
+
const r = isObject2(row2.readiness) ? row2.readiness : null;
|
|
2198
|
+
if (!r || str2(r.state) !== "gaps")
|
|
2199
|
+
return "";
|
|
2200
|
+
const line = str2(r.line);
|
|
2201
|
+
if (!line)
|
|
2202
|
+
return "";
|
|
2203
|
+
const repos = Array.isArray(row2.repos) ? row2.repos.filter((x) => typeof x === "string") : [];
|
|
2204
|
+
return ` Readiness: ${line}.${repos.length ? ` Repo: ${repos.join(", ")}.` : ""} \`item\` reads the whole thing; \`check\` asks the model.`;
|
|
2205
|
+
}
|
|
2206
|
+
function assetsTail(row2) {
|
|
2207
|
+
const assets = Array.isArray(row2.assets) ? row2.assets.filter(isObject2) : [];
|
|
1692
2208
|
if (assets.length === 0)
|
|
1693
2209
|
return "";
|
|
1694
2210
|
const parts = assets.map((a) => {
|
|
@@ -1699,8 +2215,8 @@ function assetsTail(row) {
|
|
|
1699
2215
|
});
|
|
1700
2216
|
return ` On the row: ${parts.join(" · ")}.`;
|
|
1701
2217
|
}
|
|
1702
|
-
function trackerTail(
|
|
1703
|
-
const source = isObject2(
|
|
2218
|
+
function trackerTail(row2) {
|
|
2219
|
+
const source = isObject2(row2.source) ? row2.source : {};
|
|
1704
2220
|
const kind = str2(source.kind);
|
|
1705
2221
|
if (kind !== "github" && kind !== "jira" && kind !== "linear")
|
|
1706
2222
|
return "";
|
|
@@ -1742,36 +2258,36 @@ function trackerTail(row) {
|
|
|
1742
2258
|
const where = kind === "github" ? "GitHub" : kind === "jira" ? "Jira" : "Linear";
|
|
1743
2259
|
return ` In ${where}: ${bits.join(" · ")}.`;
|
|
1744
2260
|
}
|
|
1745
|
-
function statusBody(
|
|
1746
|
-
const name = statusName(
|
|
1747
|
-
const stage = str2(
|
|
1748
|
-
const handoff = isObject2(
|
|
1749
|
-
const work = isObject2(
|
|
1750
|
-
const worth = isObject2(
|
|
1751
|
-
const band = bandLine(
|
|
2261
|
+
function statusBody(row2, now) {
|
|
2262
|
+
const name = statusName(row2);
|
|
2263
|
+
const stage = str2(row2.stage) ?? "ready";
|
|
2264
|
+
const handoff = isObject2(row2.handoff) ? row2.handoff : null;
|
|
2265
|
+
const work = isObject2(row2.work) ? row2.work : null;
|
|
2266
|
+
const worth = isObject2(row2.worth) ? row2.worth : null;
|
|
2267
|
+
const band = bandLine(row2.band);
|
|
1752
2268
|
const sized = band ? `Sized ${band}` : "Not sized";
|
|
1753
2269
|
const worthText = worth ? `worth ${worthLabel(worth)}` : "worth not stated";
|
|
1754
2270
|
const ago = (ts) => num2(ts) !== undefined ? `${shortDuration(Math.max(0, now - num2(ts)))} ago` : "";
|
|
1755
2271
|
if (stage === "landed") {
|
|
1756
|
-
const mins = num2(
|
|
1757
|
-
const
|
|
2272
|
+
const mins = num2(row2.actualMinutes);
|
|
2273
|
+
const dollars2 = num2(row2.actualUsd);
|
|
1758
2274
|
const credits = work ? num2(work.seatCreditsUsd) : undefined;
|
|
1759
2275
|
const bits = [
|
|
1760
2276
|
mins !== undefined ? `${Math.round(mins)} min` : null,
|
|
1761
|
-
|
|
2277
|
+
dollars2 !== undefined && dollars2 > 0 ? usd2(dollars2) : null,
|
|
1762
2278
|
credits !== undefined ? `${usd2(credits)} credits` : null,
|
|
1763
2279
|
work ? str2(work.model) : null
|
|
1764
2280
|
].filter((b) => Boolean(b));
|
|
1765
2281
|
const ref = work ? str2(work.ref) : undefined;
|
|
1766
2282
|
return `${name} — landed${bits.length ? `: ${bits.join(" · ")}` : ""}${ref ? ` · ${ref}` : ""}. ${sized}, ${worthText}.`;
|
|
1767
2283
|
}
|
|
1768
|
-
const tree = isObject2(
|
|
2284
|
+
const tree = isObject2(row2.tree) ? row2.tree : null;
|
|
1769
2285
|
const treeText = treeLine(tree);
|
|
1770
2286
|
if (stage === "on_sled" && handoff) {
|
|
1771
2287
|
const who = str2(handoff.pickedBy) ?? "someone";
|
|
1772
2288
|
return `${name} — on the sled: ${who} has it (picked ${ago(handoff.at)}). ${sized}, ${worthText}.${treeText} You will hear when it lands.`;
|
|
1773
2289
|
}
|
|
1774
|
-
const approval = isObject2(
|
|
2290
|
+
const approval = isObject2(row2.approval) ? row2.approval : null;
|
|
1775
2291
|
if (stage === "deferred") {
|
|
1776
2292
|
const by = approval ? str2(approval.by) ?? "product" : "product";
|
|
1777
2293
|
const note = approval ? str2(approval.note) : undefined;
|
|
@@ -1787,7 +2303,7 @@ function statusBody(row, now) {
|
|
|
1787
2303
|
const to = str2(handoff.to);
|
|
1788
2304
|
return `${name} — ready, parked ${to ? `for ${to}` : "for anyone on the team"} ${ago(handoff.at)}; nobody has taken it yet. ${sized}, ${worthText}.${okText}${treeText}`;
|
|
1789
2305
|
}
|
|
1790
|
-
return `${name} — ready; nobody has it yet. ${sized}, ${worthText}.${okText}${treeText} An engineer takes it from the
|
|
2306
|
+
return `${name} — ready; nobody has it yet. ${sized}, ${worthText}.${okText}${treeText} An engineer takes it from the pool (Work › Take it, or /sled pickup after a Send to…).`;
|
|
1791
2307
|
}
|
|
1792
2308
|
function treeLine(tree) {
|
|
1793
2309
|
if (!tree)
|
|
@@ -2014,11 +2530,11 @@ a{color:var(--peri)}
|
|
|
2014
2530
|
</script></body></html>`;
|
|
2015
2531
|
|
|
2016
2532
|
// ../../packages/shared/src/mcp/server.ts
|
|
2017
|
-
function fetchClient(
|
|
2018
|
-
const fetchFn =
|
|
2019
|
-
const base =
|
|
2020
|
-
const headers = { "content-type": "application/json", authorization: `Bearer ${
|
|
2021
|
-
const timeout = () => AbortSignal.timeout(
|
|
2533
|
+
function fetchClient(o2) {
|
|
2534
|
+
const fetchFn = o2.fetchFn ?? fetch;
|
|
2535
|
+
const base = o2.proxyUrl.replace(/\/+$/, "");
|
|
2536
|
+
const headers = { "content-type": "application/json", authorization: `Bearer ${o2.token}` };
|
|
2537
|
+
const timeout = () => AbortSignal.timeout(o2.timeoutMs ?? MCP_TIMEOUT_MS);
|
|
2022
2538
|
return {
|
|
2023
2539
|
async post(path, body) {
|
|
2024
2540
|
try {
|
|
@@ -2081,17 +2597,17 @@ var SERVER_INFO = {
|
|
|
2081
2597
|
]
|
|
2082
2598
|
};
|
|
2083
2599
|
var SERVER_INSTRUCTIONS = [
|
|
2084
|
-
"SLEDS is the
|
|
2085
|
-
'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, how many
|
|
2086
|
-
"When you are about to build something that will take more than a few minutes: `status` first if
|
|
2087
|
-
"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 tell the person the
|
|
2600
|
+
"SLEDS is where the business meets engineering: every piece of work 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 what they want built, or an engineer about to build — read which.",
|
|
2601
|
+
'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, how many asked) two at a time, writes nothing until they confirm. Never guess worth. It also reads the product map — what the ask touches, which product docs it names, what on the board collides with it; read it back plainly, let them correct it (`touches` answers the same for any question). You judge; the map only says what is there. About a page that exists? Ask for its URL (in `links`) and a screenshot to mock the change on. Anything with a screen: offer once a clickable prototype in the workspace\'s design system when intake names one — it is the ask, not the spec. Spec, prototype, screenshot go on the item as files (`attachments` on intake, `attach` after) so anyone on the board can open them; a link only they can. Change a filed ask here, not from Slack. Say it is on the sled, the band, whether it waits for product\'s yes. They never need to learn a verb.',
|
|
2602
|
+
"When you are about to build something that will take more than a few minutes: `status` first if an item may exist for it; `item` reads it whole; 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 time. A follow-up to landed work is a new `item` under the same doc, never an overwrite. An `actual` with no estimate still records, but the band you skipped is the point, so estimate first.",
|
|
2603
|
+
"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 tell the person the item is still owed. Never invent a number to get past an error."
|
|
2088
2604
|
].join(`
|
|
2089
2605
|
|
|
2090
2606
|
`);
|
|
2091
2607
|
var MCP_TIMEOUT_MS = 5000;
|
|
2092
2608
|
var sourceSchema = {
|
|
2093
2609
|
type: "object",
|
|
2094
|
-
description: "Where this item lives, so the
|
|
2610
|
+
description: "Where this item lives, so the Work page groups it: a Claude Project doc, a repo spec, a Linear/GitHub issue. Same source + same text on a later call returns the same estimate (idempotent sync).",
|
|
2095
2611
|
properties: {
|
|
2096
2612
|
kind: {
|
|
2097
2613
|
type: "string",
|
|
@@ -2127,13 +2643,13 @@ var TOOLS = [
|
|
|
2127
2643
|
},
|
|
2128
2644
|
{
|
|
2129
2645
|
name: "estimate",
|
|
2130
|
-
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
|
|
2646
|
+
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 item has a home on the sled and the later `actual` finds it. Recorded, so actuals calibrate the next estimate.",
|
|
2131
2647
|
inputSchema: specSchema
|
|
2132
2648
|
},
|
|
2133
2649
|
{
|
|
2134
2650
|
name: "intake",
|
|
2135
2651
|
_meta: { ui: { resourceUri: INTAKE_VIEW_URI } },
|
|
2136
|
-
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 (the URL of the page it is about, a prototype you made). If it concerns a page or feature that already exists, ask for its URL and a screenshot first — the screenshot is what you mock the change on. 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 and where it would live, how many have asked) — 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
|
|
2652
|
+
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 (the URL of the page it is about, a prototype you made). If it concerns a page or feature that already exists, ask for its URL and a screenshot first — the screenshot is what you mock the change on. 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 and where it would live, how many have asked) — 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 item 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.',
|
|
2137
2653
|
inputSchema: {
|
|
2138
2654
|
type: "object",
|
|
2139
2655
|
properties: {
|
|
@@ -2193,7 +2709,7 @@ var TOOLS = [
|
|
|
2193
2709
|
added: {
|
|
2194
2710
|
type: "array",
|
|
2195
2711
|
items: { type: "string" },
|
|
2196
|
-
description: "What you added on the way, one line each — milestones you drafted ('M1 — the posting form only'), a decision you flagged for product, the nearest landed work you found. Shown on the
|
|
2712
|
+
description: "What you added on the way, one line each — milestones you drafted ('M1 — the posting form only'), a decision you flagged for product, the nearest landed work you found. Shown on the item as the AI's contribution; an approver reads it before saying yes."
|
|
2197
2713
|
},
|
|
2198
2714
|
due: {
|
|
2199
2715
|
type: "string",
|
|
@@ -2206,6 +2722,15 @@ var TOOLS = [
|
|
|
2206
2722
|
parent: {
|
|
2207
2723
|
type: "string",
|
|
2208
2724
|
description: "The exact project key (from status) of the row this ask belongs under — an epic's milestone, or a spin-off found while building. The parent's tree cost then includes it."
|
|
2725
|
+
},
|
|
2726
|
+
repos: {
|
|
2727
|
+
type: "array",
|
|
2728
|
+
items: { type: "string" },
|
|
2729
|
+
description: "The repo(s) the work lands in, owner/name, when the person named one. Otherwise the product map's hit is used."
|
|
2730
|
+
},
|
|
2731
|
+
workspace: {
|
|
2732
|
+
type: "string",
|
|
2733
|
+
description: "Spec 19: the workspace (a board inside the org — by name or slug) the item is for. The draft lists the org's workspaces when it has more than one and asks; the person may skip it, and the item is placed later on the Work page."
|
|
2209
2734
|
}
|
|
2210
2735
|
},
|
|
2211
2736
|
required: ["text"]
|
|
@@ -2243,7 +2768,7 @@ var TOOLS = [
|
|
|
2243
2768
|
},
|
|
2244
2769
|
{
|
|
2245
2770
|
name: "approve",
|
|
2246
|
-
description: "Product's yes on an ask (or 'not now' with a note). Only when the workspace gates asks (Policy › product's yes: intake or always) do
|
|
2771
|
+
description: "Product's yes on an ask (or 'not now' with a note). Only when the workspace gates asks (Policy › product's yes: intake or always) do items wait; then an engineer cannot run one until product approves it. decision 'approve' opens the gate; 'defer' keeps it closed and sends the note back to the filer through status. The seat must be an approver: a named approver in Policy, else an owner or admin. Pass q (a tracker key or a word from the title) or the exact project key from status.",
|
|
2247
2772
|
inputSchema: {
|
|
2248
2773
|
type: "object",
|
|
2249
2774
|
properties: {
|
|
@@ -2293,7 +2818,7 @@ var TOOLS = [
|
|
|
2293
2818
|
},
|
|
2294
2819
|
{
|
|
2295
2820
|
name: "status",
|
|
2296
|
-
description: "Where is it — call this first when you may be picking up work that already has
|
|
2821
|
+
description: "Where is it — call this first when you may be picking up work that already has an item (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.",
|
|
2297
2822
|
inputSchema: {
|
|
2298
2823
|
type: "object",
|
|
2299
2824
|
properties: {
|
|
@@ -2306,7 +2831,7 @@ var TOOLS = [
|
|
|
2306
2831
|
},
|
|
2307
2832
|
{
|
|
2308
2833
|
name: "touches",
|
|
2309
|
-
description: "What the product says an ask reaches: the areas of the product (from the workspace's repos), the product's own documents it names, and the
|
|
2834
|
+
description: "What the product says an ask reaches: the areas of the product (from the workspace's repos), the product's own documents it names, and the items on the board it collides with. Call with `text` — the ask, or a question like 'if we change the 4-hour minimum, what else is affected?'. Read it back in plain words; you judge, the map only says what is there. `intake` calls this on its own while drafting.",
|
|
2310
2835
|
inputSchema: {
|
|
2311
2836
|
type: "object",
|
|
2312
2837
|
properties: {
|
|
@@ -2317,14 +2842,16 @@ var TOOLS = [
|
|
|
2317
2842
|
},
|
|
2318
2843
|
{
|
|
2319
2844
|
name: "attach",
|
|
2320
|
-
description: "Put a file
|
|
2845
|
+
description: "Put a file or a link on an item so anyone on the board can open it — a spec or PRD as markdown, a prototype you made as one HTML page (it opens and renders in its own tab), a screenshot; or a URL (a doc, a Figma, a Loom) with `url` instead of a file. Pass the item's key (QW-142, from status, or what intake said). Files up to 4 MB; the same name again replaces the earlier one. `intake` takes the same files as `attachments` when filing.",
|
|
2321
2846
|
inputSchema: {
|
|
2322
2847
|
type: "object",
|
|
2323
2848
|
properties: {
|
|
2324
|
-
project: {
|
|
2849
|
+
project: { type: "string", description: "The item's key — QW-142 — or its project key." },
|
|
2850
|
+
url: {
|
|
2325
2851
|
type: "string",
|
|
2326
|
-
description: "
|
|
2852
|
+
description: "A link instead of a file: the URL. `title` names it."
|
|
2327
2853
|
},
|
|
2854
|
+
title: { type: "string", description: "With url: what to call the link." },
|
|
2328
2855
|
name: {
|
|
2329
2856
|
type: "string",
|
|
2330
2857
|
description: "File name with extension: spec.md, prototype.html, before.png."
|
|
@@ -2341,12 +2868,83 @@ var TOOLS = [
|
|
|
2341
2868
|
},
|
|
2342
2869
|
encoding: { type: "string", enum: ["text", "base64"] }
|
|
2343
2870
|
},
|
|
2344
|
-
required: ["project"
|
|
2871
|
+
required: ["project"]
|
|
2872
|
+
}
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
name: "update",
|
|
2876
|
+
description: "Change an item on the sled — its title (`ask`), why, done, smallest, touches, demand, its description (markdown: the prose the brief does not hold), its due day, which item it sits under, the repo(s) it lands in, or its sensitivity (public · internal · restricted). Pass the item's key and only the fields that change. The filer, an approver or an admin may; a mirrored ticket cannot be changed here. A change to the ask, done or smallest marks the band stale — say so, and offer to re-size. Everything you write is signed as coming through you.",
|
|
2877
|
+
inputSchema: {
|
|
2878
|
+
type: "object",
|
|
2879
|
+
properties: {
|
|
2880
|
+
project: { type: "string", description: "The item's key — QW-142 — or its project key." },
|
|
2881
|
+
ask: { type: "string", description: "The title: what they want, one line." },
|
|
2882
|
+
why: { type: "string" },
|
|
2883
|
+
done: { type: "string" },
|
|
2884
|
+
smallest: { type: "string" },
|
|
2885
|
+
touches: { type: "string" },
|
|
2886
|
+
demand: { type: "string" },
|
|
2887
|
+
description: {
|
|
2888
|
+
type: "string",
|
|
2889
|
+
description: "Markdown. Links and images inline. Empty clears it."
|
|
2890
|
+
},
|
|
2891
|
+
due: { type: ["string", "null"], description: "YYYY-MM-DD, or null to clear." },
|
|
2892
|
+
parent: {
|
|
2893
|
+
type: ["string", "null"],
|
|
2894
|
+
description: "The parent item's key, or null for none."
|
|
2895
|
+
},
|
|
2896
|
+
repos: {
|
|
2897
|
+
type: "array",
|
|
2898
|
+
items: { type: "string" },
|
|
2899
|
+
description: "The repo(s) the work lands in, owner/name. Replaces the list; [] clears it."
|
|
2900
|
+
},
|
|
2901
|
+
sensitivity: {
|
|
2902
|
+
type: ["string", "null"],
|
|
2903
|
+
enum: ["public", "internal", "restricted", null],
|
|
2904
|
+
description: "The person's word on what the work touches: public · internal · restricted. Anyone can raise it; only an approver can lower it below what the rules found, and then `sensitivityNote` says why. Null clears the word and the rules stand."
|
|
2905
|
+
},
|
|
2906
|
+
sensitivityNote: { type: "string", description: "Why, when lowering the class." }
|
|
2907
|
+
},
|
|
2908
|
+
required: ["project"]
|
|
2909
|
+
}
|
|
2910
|
+
},
|
|
2911
|
+
{
|
|
2912
|
+
name: "item",
|
|
2913
|
+
description: "Read one item whole, as one markdown document — everything an engineer or an agent needs to pick the work up cold: the brief, the description, the repo(s), the links and files, what sits under it, the open questions, the thread, and its readiness (what is still missing before someone could start). Pass the item's key (QW-142) or its title. Call this before building from an item, and before `check`.",
|
|
2914
|
+
inputSchema: {
|
|
2915
|
+
type: "object",
|
|
2916
|
+
properties: {
|
|
2917
|
+
q: { type: "string", description: "The item's key — QW-142 — or its title." }
|
|
2918
|
+
},
|
|
2919
|
+
required: ["q"]
|
|
2920
|
+
}
|
|
2921
|
+
},
|
|
2922
|
+
{
|
|
2923
|
+
name: "check",
|
|
2924
|
+
description: "Ask SLEDS to check an item's readiness with a model: it reads the item as an engineer would and names what it would have to ask before starting. The answer lands on the item's thread within a minute (and on `item` / `status` after). The rules-based readiness (no why, no done, no repo, open questions, unsized) is on every read for free; this is the second opinion, on the workspace's platform key. Pass the item's key.",
|
|
2925
|
+
inputSchema: {
|
|
2926
|
+
type: "object",
|
|
2927
|
+
properties: {
|
|
2928
|
+
project: { type: "string", description: "The item's key — QW-142 — or its project key." }
|
|
2929
|
+
},
|
|
2930
|
+
required: ["project"]
|
|
2931
|
+
}
|
|
2932
|
+
},
|
|
2933
|
+
{
|
|
2934
|
+
name: "comment",
|
|
2935
|
+
description: "Say something on an item's thread — a note, a finding, what you did, a question for the team. Markdown. It shows on the item for everyone with who wrote it and that it came through you. For a question that needs the filer's answer use `answer`'s counterpart on the row (an approver's `ask`); this is the plain thread.",
|
|
2936
|
+
inputSchema: {
|
|
2937
|
+
type: "object",
|
|
2938
|
+
properties: {
|
|
2939
|
+
project: { type: "string", description: "The item's key — QW-142 — or its project key." },
|
|
2940
|
+
md: { type: "string", description: "The comment, markdown." }
|
|
2941
|
+
},
|
|
2942
|
+
required: ["project", "md"]
|
|
2345
2943
|
}
|
|
2346
2944
|
},
|
|
2347
2945
|
{
|
|
2348
2946
|
name: "answer",
|
|
2349
|
-
description: "Answer a question engineering or product asked on a row you filed. Call with nothing to see what is waiting on you (each with its id and the
|
|
2947
|
+
description: "Answer a question engineering or product asked on a row you filed. Call with nothing to see what is waiting on you (each with its id and the item); call with `answer` (and `project` or `id` when more than one is open) to put your reply on the row — it becomes part of the record and the person who asked hears at once. When someone says 'tell them…' or 'the answer is…' about a question SLEDS relayed, this is the tool.",
|
|
2350
2948
|
inputSchema: {
|
|
2351
2949
|
type: "object",
|
|
2352
2950
|
properties: {
|
|
@@ -2406,7 +3004,7 @@ var TOOLS = [
|
|
|
2406
3004
|
},
|
|
2407
3005
|
{
|
|
2408
3006
|
name: "actual",
|
|
2409
|
-
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
|
|
3007
|
+
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 item. If SLEDS answers that no estimate is on file, call `estimate` with this source first, then send this again.",
|
|
2410
3008
|
inputSchema: {
|
|
2411
3009
|
type: "object",
|
|
2412
3010
|
properties: {
|
|
@@ -2480,6 +3078,18 @@ var TOOLS = [
|
|
|
2480
3078
|
}
|
|
2481
3079
|
}
|
|
2482
3080
|
},
|
|
3081
|
+
{
|
|
3082
|
+
name: "archive",
|
|
3083
|
+
description: "Put an item away — off Work, the sleds and the counts, reversible (undo: true brings it back whole). The filer, an approver or an admin. An open hand-off on it is withdrawn and it comes off its sled. Nothing is deleted; deleting is an admin's, in the app.",
|
|
3084
|
+
inputSchema: {
|
|
3085
|
+
type: "object",
|
|
3086
|
+
properties: {
|
|
3087
|
+
project: { type: "string", description: "The item's key (QW-142) or project." },
|
|
3088
|
+
undo: { type: "boolean", description: "Bring an archived item back." }
|
|
3089
|
+
},
|
|
3090
|
+
required: ["project"]
|
|
3091
|
+
}
|
|
3092
|
+
},
|
|
2483
3093
|
{
|
|
2484
3094
|
name: "withdraw",
|
|
2485
3095
|
description: "Take back a hand-off you parked before anyone picks it up (the id from the handoffs tool). Only the seat that parked it can withdraw it.",
|
|
@@ -2609,12 +3219,12 @@ function byModelText(raw) {
|
|
|
2609
3219
|
if (!Array.isArray(raw))
|
|
2610
3220
|
return "";
|
|
2611
3221
|
const parts = [];
|
|
2612
|
-
for (const
|
|
2613
|
-
if (!isObject4(
|
|
3222
|
+
for (const row2 of raw) {
|
|
3223
|
+
if (!isObject4(row2) || !isObject4(row2.standard))
|
|
2614
3224
|
continue;
|
|
2615
|
-
const model = str4(
|
|
2616
|
-
const low = num3(
|
|
2617
|
-
const high = num3(
|
|
3225
|
+
const model = str4(row2.model);
|
|
3226
|
+
const low = num3(row2.standard.lowUsd);
|
|
3227
|
+
const high = num3(row2.standard.highUsd);
|
|
2618
3228
|
if (!model || low === undefined || high === undefined)
|
|
2619
3229
|
continue;
|
|
2620
3230
|
const m = /^claude-([a-z]+)-(\d+)(?:-(\d+))?/.exec(model);
|
|
@@ -2665,6 +3275,13 @@ function briefAnswers(raw) {
|
|
|
2665
3275
|
}
|
|
2666
3276
|
return out;
|
|
2667
3277
|
}
|
|
3278
|
+
async function readWorkspaces(ctx) {
|
|
3279
|
+
if (!ctx.client)
|
|
3280
|
+
return [];
|
|
3281
|
+
const r = await ctx.client.get("/proxy/workspaces");
|
|
3282
|
+
const list = r && Array.isArray(r.workspaces) ? r.workspaces : [];
|
|
3283
|
+
return list.map((w) => isObject4(w) ? { slug: str4(w.slug) ?? "", name: str4(w.name) ?? "" } : null).filter((w) => w !== null && w.slug !== "" && w.name !== "");
|
|
3284
|
+
}
|
|
2668
3285
|
async function readTouches(ctx, text) {
|
|
2669
3286
|
if (!ctx.client || !text.trim())
|
|
2670
3287
|
return null;
|
|
@@ -2673,8 +3290,9 @@ async function readTouches(ctx, text) {
|
|
|
2673
3290
|
return null;
|
|
2674
3291
|
const mapped = r.mapped === true;
|
|
2675
3292
|
const areas = Array.isArray(r.areas) ? r.areas : [];
|
|
3293
|
+
const repos = [...new Set(areas.map((a) => str4(a.repo)).filter((x) => Boolean(x)))];
|
|
2676
3294
|
const touchesLine = areas.length ? areas.map((a) => `${str4(a.area) ?? ""}${str4(a.repo) ? ` (${str4(a.repo).split("/").pop()})` : ""}`).filter(Boolean).join(" · ") : null;
|
|
2677
|
-
return { text: str4(r.text) ?? "", touchesLine, mapped };
|
|
3295
|
+
return { text: str4(r.text) ?? "", touchesLine, mapped, repos };
|
|
2678
3296
|
}
|
|
2679
3297
|
async function touchesTool(ctx, args) {
|
|
2680
3298
|
if (!ctx.client)
|
|
@@ -2705,23 +3323,28 @@ async function intakeTool(ctx, args) {
|
|
|
2705
3323
|
if (map?.touchesLine && !brief.touches)
|
|
2706
3324
|
brief.touches = { text: map.touchesLine, from: "product" };
|
|
2707
3325
|
const open = openQuestions(brief);
|
|
3326
|
+
const workspaces = await readWorkspaces(ctx);
|
|
3327
|
+
const workspaceArg = str4(args.workspace)?.trim() || undefined;
|
|
3328
|
+
const workspaceQuestion = workspaces.length > 1 && !workspaceArg ? `Which workspace is this for — ${workspaces.map((w) => w.name).join(", ")}? (skip if you don't know; product or an engineer will place it)` : null;
|
|
2708
3329
|
if (args.confirm !== true) {
|
|
2709
|
-
const
|
|
3330
|
+
const needed2 = missingToFile(brief);
|
|
2710
3331
|
const attachments = parseAttachments(args.attachments).map((a) => a.name || "file");
|
|
2711
3332
|
const structured = {
|
|
2712
3333
|
kind: "draft",
|
|
2713
3334
|
brief: briefForView(brief),
|
|
2714
3335
|
open,
|
|
2715
|
-
needed,
|
|
3336
|
+
needed: needed2,
|
|
2716
3337
|
map: map?.text ?? null,
|
|
2717
3338
|
links,
|
|
2718
|
-
attachments
|
|
3339
|
+
attachments,
|
|
3340
|
+
...workspaceQuestion ? { workspaces: workspaces.map((w) => w.name), workspaceQuestion } : {}
|
|
2719
3341
|
};
|
|
2720
3342
|
const lines = [
|
|
2721
3343
|
renderBriefMarkdown(brief, open, { map: map?.text ?? null, files: attachments }),
|
|
2722
3344
|
"",
|
|
3345
|
+
...workspaceQuestion ? [`Also ask: ${workspaceQuestion} Pass the answer as \`workspace\`.`, ""] : [],
|
|
2723
3346
|
open.length > 0 ? "Show the person the table as is, then ask at most two of the open questions at a time, in your own words; skip what they already said." : "Show the person the table as is.",
|
|
2724
|
-
|
|
3347
|
+
needed2.length > 0 ? `Nothing is filed yet. Filing needs: ${needed2.join(", ")}. When they say yes to the brief, call intake again with the same text, their answers, and confirm: true.` : "Nothing is filed yet. When they say yes, call intake again with the same text, their answers, and confirm: true.",
|
|
2725
3348
|
"",
|
|
2726
3349
|
"```json sleds-draft",
|
|
2727
3350
|
JSON.stringify(structured),
|
|
@@ -2743,9 +3366,12 @@ async function intakeTool(ctx, args) {
|
|
|
2743
3366
|
const dueArg = typeof args.due === "string" && /^\d{4}-\d{2}-\d{2}$/.test(args.due) ? args.due : null;
|
|
2744
3367
|
const due = dueArg ?? parseDue([text, ...Object.values(answers), ...added].filter(Boolean).join(`
|
|
2745
3368
|
`));
|
|
3369
|
+
const repos = Array.isArray(args.repos) ? args.repos.filter((x) => typeof x === "string").map((x) => x.trim()).filter(Boolean) : typeof args.repos === "string" ? args.repos.split(/[,\s]+/).filter(Boolean) : map?.repos ?? [];
|
|
2746
3370
|
const r = await ctx.client.postFull("/proxy/intake", {
|
|
2747
3371
|
brief: { ...brief, intake: { asked, added, related: [] }, ...due ? { due } : {} },
|
|
2748
|
-
...parent ? { parent } : {}
|
|
3372
|
+
...parent ? { parent } : {},
|
|
3373
|
+
...workspaceArg ? { workspace: workspaceArg } : {},
|
|
3374
|
+
...repos.length ? { repos, reposStated: Array.isArray(args.repos) || typeof args.repos === "string" } : {}
|
|
2749
3375
|
});
|
|
2750
3376
|
if (!r)
|
|
2751
3377
|
return { text: `Nothing was filed. ${UNREACHABLE.text}`, isError: true };
|
|
@@ -2877,9 +3503,18 @@ async function attachTool(ctx, args) {
|
|
|
2877
3503
|
const project = str4(args.project)?.trim();
|
|
2878
3504
|
if (!project)
|
|
2879
3505
|
return {
|
|
2880
|
-
text: "Which
|
|
3506
|
+
text: "Which item? Pass project: the key status shows (or what intake said).",
|
|
2881
3507
|
isError: true
|
|
2882
3508
|
};
|
|
3509
|
+
const url = str4(args.url)?.trim();
|
|
3510
|
+
if (url) {
|
|
3511
|
+
const res = await postSaying(ctx, "/proxy/item/link", { project, url, ...str4(args.title) ? { title: str4(args.title) } : {} }, "Could not add the link.");
|
|
3512
|
+
if ("fail" in res)
|
|
3513
|
+
return res.fail;
|
|
3514
|
+
return {
|
|
3515
|
+
text: res.json.added === false ? `That link is already on ${project}.` : `Linked on ${project}: ${url}. Anyone on the board can open it from the item.`
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
2883
3518
|
const files = parseAttachments([args]);
|
|
2884
3519
|
if (files.length === 0)
|
|
2885
3520
|
return {
|
|
@@ -2894,6 +3529,137 @@ async function attachTool(ctx, args) {
|
|
|
2894
3529
|
text: `On the row ${project}: ${first.name}${first.url ? ` — ${first.url}` : ""}. Anyone on the board can open it from the row.`
|
|
2895
3530
|
};
|
|
2896
3531
|
}
|
|
3532
|
+
async function updateTool(ctx, args) {
|
|
3533
|
+
if (!ctx.client)
|
|
3534
|
+
return NOT_SET_UP;
|
|
3535
|
+
const project = str4(args.project)?.trim();
|
|
3536
|
+
if (!project)
|
|
3537
|
+
return { text: "Which item? Pass project: its key (QW-142).", isError: true };
|
|
3538
|
+
const fields = [
|
|
3539
|
+
"ask",
|
|
3540
|
+
"why",
|
|
3541
|
+
"done",
|
|
3542
|
+
"smallest",
|
|
3543
|
+
"touches",
|
|
3544
|
+
"demand",
|
|
3545
|
+
"description",
|
|
3546
|
+
"due",
|
|
3547
|
+
"parent"
|
|
3548
|
+
];
|
|
3549
|
+
const patch = {};
|
|
3550
|
+
for (const f of fields) {
|
|
3551
|
+
if (args[f] === null && (f === "due" || f === "parent"))
|
|
3552
|
+
patch[f] = null;
|
|
3553
|
+
else if (typeof args[f] === "string")
|
|
3554
|
+
patch[f] = args[f];
|
|
3555
|
+
}
|
|
3556
|
+
if (Array.isArray(args.repos))
|
|
3557
|
+
patch.repos = args.repos.filter((x) => typeof x === "string");
|
|
3558
|
+
else if (typeof args.repos === "string")
|
|
3559
|
+
patch.repos = args.repos.split(/[,\s]+/).filter(Boolean);
|
|
3560
|
+
if (args.sensitivity === null)
|
|
3561
|
+
patch.sensitivity = null;
|
|
3562
|
+
else if (typeof args.sensitivity === "string" && /^(public|internal|restricted)$/.test(args.sensitivity)) {
|
|
3563
|
+
patch.sensitivity = args.sensitivity;
|
|
3564
|
+
if (typeof args.sensitivityNote === "string" && args.sensitivityNote.trim())
|
|
3565
|
+
patch.sensitivityNote = args.sensitivityNote.trim();
|
|
3566
|
+
}
|
|
3567
|
+
if (Object.keys(patch).length === 0)
|
|
3568
|
+
return {
|
|
3569
|
+
text: "Nothing to change. Pass the fields that change: ask, why, done, smallest, touches, demand, description, due, parent, repos.",
|
|
3570
|
+
isError: true
|
|
3571
|
+
};
|
|
3572
|
+
const res = await postSaying(ctx, "/proxy/item/update", { project, ...patch }, "Could not update the item.");
|
|
3573
|
+
if ("fail" in res)
|
|
3574
|
+
return res.fail;
|
|
3575
|
+
const changed = Array.isArray(res.json.changed) ? res.json.changed.filter((x) => typeof x === "string") : [];
|
|
3576
|
+
const key = str4(res.json.key) ?? project;
|
|
3577
|
+
if (changed.length === 0)
|
|
3578
|
+
return { text: `${key}: nothing differed from what is on the item.` };
|
|
3579
|
+
const stale = res.json.sizeStale === true;
|
|
3580
|
+
return {
|
|
3581
|
+
text: `${key} updated: ${changed.join(", ")}.${stale ? " The band was sized before this change — say so, and offer to re-size (estimate with the same source)." : ""}`
|
|
3582
|
+
};
|
|
3583
|
+
}
|
|
3584
|
+
async function archiveTool(ctx, args) {
|
|
3585
|
+
if (!ctx.client)
|
|
3586
|
+
return NOT_SET_UP;
|
|
3587
|
+
const project = str4(args.project)?.trim();
|
|
3588
|
+
if (!project)
|
|
3589
|
+
return { text: "Which item? Pass project: its key (QW-142).", isError: true };
|
|
3590
|
+
const undo = args.undo === true;
|
|
3591
|
+
const res = await postSaying(ctx, "/proxy/item/archive", { project, undo }, undo ? "Could not unarchive the item." : "Could not archive the item.");
|
|
3592
|
+
if ("fail" in res)
|
|
3593
|
+
return res.fail;
|
|
3594
|
+
const key = str4(res.json.key) ?? project;
|
|
3595
|
+
const title = str4(res.json.title) ?? "";
|
|
3596
|
+
if (res.json.changed !== true)
|
|
3597
|
+
return { text: `${key} was ${undo ? "not archived" : "already archived"} — nothing changed.` };
|
|
3598
|
+
return {
|
|
3599
|
+
text: undo ? `${key} is back on the board${title ? ` — ${title}` : ""}.` : `${key} archived${title ? ` — ${title}` : ""}. Off Work and the sleds; archive with undo: true brings it back.`
|
|
3600
|
+
};
|
|
3601
|
+
}
|
|
3602
|
+
async function itemTool(ctx, args) {
|
|
3603
|
+
if (!ctx.client)
|
|
3604
|
+
return NOT_SET_UP;
|
|
3605
|
+
const q = str4(args.q)?.trim() || str4(args.project)?.trim();
|
|
3606
|
+
if (!q)
|
|
3607
|
+
return { text: "Which item? Pass q: its key (QW-142) or its title.", isError: true };
|
|
3608
|
+
const r = await ctx.client.get(`/proxy/item?q=${encodeURIComponent(q)}`);
|
|
3609
|
+
if (!r)
|
|
3610
|
+
return UNREACHABLE;
|
|
3611
|
+
if (r.found === false)
|
|
3612
|
+
return {
|
|
3613
|
+
text: `Nothing on the sled answers to "${q}". Try the key (QW-142) or a word from the title with status.`,
|
|
3614
|
+
isError: true
|
|
3615
|
+
};
|
|
3616
|
+
const md = str4(r.markdown) ?? "";
|
|
3617
|
+
const readiness = isObject4(r.readiness) ? r.readiness : null;
|
|
3618
|
+
const line = readiness ? str4(readiness.line) : undefined;
|
|
3619
|
+
const state = readiness ? str4(readiness.state) : undefined;
|
|
3620
|
+
const profile = isObject4(r.profile) ? r.profile : null;
|
|
3621
|
+
const restricted = profile ? str4(profile.sensitivity) === "restricted" : false;
|
|
3622
|
+
const tail = state === "gaps" ? `
|
|
3623
|
+
|
|
3624
|
+
---
|
|
3625
|
+
Before anyone starts: ${line}. Fill the gaps with \`update\` (why, done, repos…)${restricted ? "" : " or ask `check` for the model's read"}.` : state === "ready" ? `
|
|
3626
|
+
|
|
3627
|
+
---
|
|
3628
|
+
Ready to pick up.${restricted ? "" : " `check` gives the model's second read if you want one."}` : "";
|
|
3629
|
+
const note = restricted ? `
|
|
3630
|
+
|
|
3631
|
+
This item is restricted: its text stays in the workspace — no model check, no rerank — and once Policy › Routing is on, only a local route may run it. Say so before building on a cloud seat.` : "";
|
|
3632
|
+
return { text: `${md}${tail}${note}` };
|
|
3633
|
+
}
|
|
3634
|
+
async function checkTool(ctx, args) {
|
|
3635
|
+
if (!ctx.client)
|
|
3636
|
+
return NOT_SET_UP;
|
|
3637
|
+
const project = str4(args.project)?.trim() || str4(args.q)?.trim();
|
|
3638
|
+
if (!project)
|
|
3639
|
+
return { text: "Which item? Pass project: its key (QW-142).", isError: true };
|
|
3640
|
+
const res = await postSaying(ctx, "/proxy/item/check", { project }, "Could not start the check.");
|
|
3641
|
+
if ("fail" in res)
|
|
3642
|
+
return res.fail;
|
|
3643
|
+
return {
|
|
3644
|
+
text: `Checking ${str4(res.json.key) ?? project}. The model's read lands on the item's thread in under a minute — \`item\` shows it, and the item's readiness carries its gaps from then on.`
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
async function commentTool(ctx, args) {
|
|
3648
|
+
if (!ctx.client)
|
|
3649
|
+
return NOT_SET_UP;
|
|
3650
|
+
const project = str4(args.project)?.trim();
|
|
3651
|
+
const md = str4(args.md)?.trim();
|
|
3652
|
+
if (!project)
|
|
3653
|
+
return { text: "Which item? Pass project: its key (QW-142).", isError: true };
|
|
3654
|
+
if (!md)
|
|
3655
|
+
return { text: "Nothing to say. Pass md.", isError: true };
|
|
3656
|
+
const res = await postSaying(ctx, "/proxy/item/comment", { project, md }, "Could not post the comment.");
|
|
3657
|
+
if ("fail" in res)
|
|
3658
|
+
return res.fail;
|
|
3659
|
+
return {
|
|
3660
|
+
text: `On ${str4(res.json.key) ?? project}'s thread. The team sees it on the item, signed as through you.`
|
|
3661
|
+
};
|
|
3662
|
+
}
|
|
2897
3663
|
async function overnightTool(ctx, args) {
|
|
2898
3664
|
if (!ctx.client)
|
|
2899
3665
|
return NOT_SET_UP;
|
|
@@ -3008,8 +3774,8 @@ async function dispatchTool(ctx, args) {
|
|
|
3008
3774
|
}
|
|
3009
3775
|
return { text: reply };
|
|
3010
3776
|
}
|
|
3011
|
-
const
|
|
3012
|
-
const title = str4(
|
|
3777
|
+
const row2 = isObject4(j.row) ? j.row : {};
|
|
3778
|
+
const title = str4(row2.title) ?? "the row";
|
|
3013
3779
|
if (action.kind === "parked") {
|
|
3014
3780
|
const dest = str4(action.to);
|
|
3015
3781
|
return {
|
|
@@ -3052,9 +3818,9 @@ async function approveTool(ctx, args) {
|
|
|
3052
3818
|
isError: true
|
|
3053
3819
|
};
|
|
3054
3820
|
const j = r.json ?? {};
|
|
3055
|
-
const
|
|
3056
|
-
const title = str4(
|
|
3057
|
-
const filedBy = str4(
|
|
3821
|
+
const row2 = isObject4(j.row) ? j.row : {};
|
|
3822
|
+
const title = str4(row2.title) ?? "the row";
|
|
3823
|
+
const filedBy = str4(row2.filedBy);
|
|
3058
3824
|
if (decision === "approved")
|
|
3059
3825
|
return {
|
|
3060
3826
|
text: `${title}: approved — it is ready on the sled${filedBy ? `; ${filedBy} is told` : ""}. dispatch says where it should go; an engineer takes it with Take it or /sled pickup.`
|
|
@@ -3111,7 +3877,7 @@ function questionsText(open, now) {
|
|
|
3111
3877
|
return "Nothing is waiting on your answer.";
|
|
3112
3878
|
const lines = open.map((q) => {
|
|
3113
3879
|
const at = typeof q.at === "number" ? ` · ${shortDuration(Math.max(0, now - q.at))} ago` : "";
|
|
3114
|
-
return `- ${str4(q.by) ?? "someone"} asked on "${str4(q.title) ?? str4(q.project) ?? "
|
|
3880
|
+
return `- ${str4(q.by) ?? "someone"} asked on "${str4(q.title) ?? str4(q.project) ?? "an item"}"${at}: ${str4(q.question) ?? ""} (id ${str4(q.id) ?? "?"})`;
|
|
3115
3881
|
});
|
|
3116
3882
|
return `${open.length === 1 ? "One question is" : `${open.length} questions are`} waiting on you:
|
|
3117
3883
|
${lines.join(`
|
|
@@ -3124,10 +3890,10 @@ async function answerTool(ctx, args) {
|
|
|
3124
3890
|
const now = (ctx.now ?? Date.now)();
|
|
3125
3891
|
const answer = str4(args.answer)?.trim();
|
|
3126
3892
|
if (!answer) {
|
|
3127
|
-
const
|
|
3128
|
-
if (!
|
|
3893
|
+
const r2 = await ctx.client.get("/proxy/questions");
|
|
3894
|
+
if (!r2)
|
|
3129
3895
|
return UNREACHABLE;
|
|
3130
|
-
const open = Array.isArray(
|
|
3896
|
+
const open = Array.isArray(r2.open) ? r2.open.filter(isObject4) : [];
|
|
3131
3897
|
return { text: questionsText(open, now) };
|
|
3132
3898
|
}
|
|
3133
3899
|
const project = str4(args.project)?.trim() || undefined;
|
|
@@ -3160,7 +3926,7 @@ async function handoffsTool(ctx) {
|
|
|
3160
3926
|
if (handoffs.length === 0)
|
|
3161
3927
|
return { text: "No open hand-offs on your team. Park one with the handoff tool." };
|
|
3162
3928
|
const now = (ctx.now ?? Date.now)();
|
|
3163
|
-
const packets = handoffs.map((h) => str4(h.packet)).filter((
|
|
3929
|
+
const packets = handoffs.map((h) => str4(h.packet)).filter((p2) => Boolean(p2)).join(`
|
|
3164
3930
|
|
|
3165
3931
|
---
|
|
3166
3932
|
|
|
@@ -3305,15 +4071,15 @@ async function actualTool(ctx, args) {
|
|
|
3305
4071
|
text: "`source` is required — the same one the estimate was filed under.",
|
|
3306
4072
|
isError: true
|
|
3307
4073
|
};
|
|
3308
|
-
const
|
|
4074
|
+
const iso2 = (v) => {
|
|
3309
4075
|
const t = str4(v) ? Date.parse(str4(v)) : Number.NaN;
|
|
3310
4076
|
return Number.isFinite(t) ? t : undefined;
|
|
3311
4077
|
};
|
|
3312
4078
|
const phasesRaw = typeof args.phases === "object" && args.phases !== null ? args.phases : undefined;
|
|
3313
4079
|
const phases = phasesRaw ? Object.fromEntries(["discussMin", "buildMin", "deployMin", "testMin"].map((k) => [k, num3(phasesRaw[k])]).filter(([, v]) => v !== undefined)) : undefined;
|
|
3314
4080
|
const work = {
|
|
3315
|
-
...
|
|
3316
|
-
...
|
|
4081
|
+
...iso2(args.startedAt) !== undefined ? { startedAt: iso2(args.startedAt) } : {},
|
|
4082
|
+
...iso2(args.finishedAt) !== undefined ? { finishedAt: iso2(args.finishedAt) } : {},
|
|
3317
4083
|
...str4(args.model) ? { model: str4(args.model) } : {},
|
|
3318
4084
|
...num3(args.tokensIn) !== undefined ? { tokensIn: num3(args.tokensIn) } : {},
|
|
3319
4085
|
...num3(args.tokensOut) !== undefined ? { tokensOut: num3(args.tokensOut) } : {},
|
|
@@ -3364,7 +4130,7 @@ async function actualTool(ctx, args) {
|
|
|
3364
4130
|
].filter(Boolean);
|
|
3365
4131
|
if (unsized)
|
|
3366
4132
|
return {
|
|
3367
|
-
text: `Recorded: ${bits.join(" · ")}. No estimate came first, so the
|
|
4133
|
+
text: `Recorded: ${bits.join(" · ")}. No estimate came first, so the item 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.`
|
|
3368
4134
|
};
|
|
3369
4135
|
return { text: `Recorded: ${bits.join(" · ")}. Projects shows it beside the estimate.` };
|
|
3370
4136
|
}
|
|
@@ -3373,10 +4139,10 @@ async function buddyTool(ctx, args) {
|
|
|
3373
4139
|
return NOT_SET_UP;
|
|
3374
4140
|
const email = str4(args.email)?.trim();
|
|
3375
4141
|
if (!email) {
|
|
3376
|
-
const
|
|
3377
|
-
if (!
|
|
4142
|
+
const r2 = await ctx.client.get("/proxy/buddy");
|
|
4143
|
+
if (!r2)
|
|
3378
4144
|
return UNREACHABLE;
|
|
3379
|
-
const list = Array.isArray(
|
|
4145
|
+
const list = Array.isArray(r2.buddies) ? r2.buddies : [];
|
|
3380
4146
|
if (list.length === 0)
|
|
3381
4147
|
return {
|
|
3382
4148
|
text: "No buddies yet. `buddy {email}` sends someone an invite; once they accept, `handoff {to: email}` reaches them in their own org."
|
|
@@ -3387,10 +4153,10 @@ ${lines.join(`
|
|
|
3387
4153
|
`)}` };
|
|
3388
4154
|
}
|
|
3389
4155
|
if (args.remove === true) {
|
|
3390
|
-
const
|
|
3391
|
-
if (!
|
|
4156
|
+
const r2 = await ctx.client.postFull("/proxy/buddy", { email, remove: true });
|
|
4157
|
+
if (!r2)
|
|
3392
4158
|
return UNREACHABLE;
|
|
3393
|
-
return { text:
|
|
4159
|
+
return { text: r2.json?.ok ? `Unpaired from ${email}.` : `${email} was not a buddy.` };
|
|
3394
4160
|
}
|
|
3395
4161
|
const r = await ctx.client.postFull("/proxy/buddy", { email });
|
|
3396
4162
|
if (!r)
|
|
@@ -3422,6 +4188,16 @@ async function callTool(name, args, ctx) {
|
|
|
3422
4188
|
return touchesTool(ctx, args);
|
|
3423
4189
|
case "attach":
|
|
3424
4190
|
return attachTool(ctx, args);
|
|
4191
|
+
case "update":
|
|
4192
|
+
return updateTool(ctx, args);
|
|
4193
|
+
case "archive":
|
|
4194
|
+
return archiveTool(ctx, args);
|
|
4195
|
+
case "comment":
|
|
4196
|
+
return commentTool(ctx, args);
|
|
4197
|
+
case "item":
|
|
4198
|
+
return itemTool(ctx, args);
|
|
4199
|
+
case "check":
|
|
4200
|
+
return checkTool(ctx, args);
|
|
3425
4201
|
case "answer":
|
|
3426
4202
|
return answerTool(ctx, args);
|
|
3427
4203
|
case "dispatch":
|
|
@@ -3576,7 +4352,7 @@ ${out.startsWith(`
|
|
|
3576
4352
|
${providerBlock(proxyUrl)}
|
|
3577
4353
|
`;
|
|
3578
4354
|
}
|
|
3579
|
-
function removeCodexProvider(toml,
|
|
4355
|
+
function removeCodexProvider(toml, o2 = {}) {
|
|
3580
4356
|
const lines = toml.split(`
|
|
3581
4357
|
`);
|
|
3582
4358
|
const out = [];
|
|
@@ -3589,7 +4365,7 @@ function removeCodexProvider(toml, o = {}) {
|
|
|
3589
4365
|
}
|
|
3590
4366
|
let text = out.join(`
|
|
3591
4367
|
`);
|
|
3592
|
-
if (!
|
|
4368
|
+
if (!o2.keepPrior) {
|
|
3593
4369
|
const prior = text.match(/^# sleds-prior-model-provider = "([^"]*)"\s*\n/m);
|
|
3594
4370
|
if (prior) {
|
|
3595
4371
|
text = text.replace(prior[0], "");
|
|
@@ -3606,22 +4382,22 @@ function topLevel(toml) {
|
|
|
3606
4382
|
const i = toml.search(/^\s*\[/m);
|
|
3607
4383
|
return i === -1 ? toml : toml.slice(0, i);
|
|
3608
4384
|
}
|
|
3609
|
-
function initCodex(
|
|
4385
|
+
function initCodex(o2) {
|
|
3610
4386
|
const wrote = [];
|
|
3611
|
-
const path = codexConfigPath(
|
|
4387
|
+
const path = codexConfigPath(o2.home);
|
|
3612
4388
|
const prior = existsSync3(path) ? readFileSync3(path, "utf8") : "";
|
|
3613
4389
|
mkdirSync3(dirname2(path), { recursive: true });
|
|
3614
|
-
writeFileSync3(path, applyCodexProvider(prior,
|
|
3615
|
-
wrote.push(`${prior ? "updated" : "created"} ${path}: provider "${PROVIDER_ID}" → ${
|
|
3616
|
-
const cfg = readConfig(
|
|
3617
|
-
writeConfig(
|
|
4390
|
+
writeFileSync3(path, applyCodexProvider(prior, o2.proxyUrl), { mode: 384 });
|
|
4391
|
+
wrote.push(`${prior ? "updated" : "created"} ${path}: provider "${PROVIDER_ID}" → ${o2.proxyUrl}/v1 (key from $${TOKEN_ENV}), model_provider = "${PROVIDER_ID}"`);
|
|
4392
|
+
const cfg = readConfig(o2.home);
|
|
4393
|
+
writeConfig(o2.home, {
|
|
3618
4394
|
...cfg,
|
|
3619
|
-
token:
|
|
3620
|
-
proxyUrl:
|
|
4395
|
+
token: o2.token,
|
|
4396
|
+
proxyUrl: o2.proxyUrl,
|
|
3621
4397
|
agentKind: "codex",
|
|
3622
|
-
...
|
|
4398
|
+
...o2.email ? { email: o2.email } : {}
|
|
3623
4399
|
});
|
|
3624
|
-
wrote.push(`saved token and proxy URL to ${configPath(
|
|
4400
|
+
wrote.push(`saved token and proxy URL to ${configPath(o2.home)} (0600)`);
|
|
3625
4401
|
return wrote;
|
|
3626
4402
|
}
|
|
3627
4403
|
function runCodex(home, args, deps = {}) {
|
|
@@ -3667,7 +4443,7 @@ var musherLabel = (env = process.env) => {
|
|
|
3667
4443
|
return `${MUSHER_LABEL}.${tag}`;
|
|
3668
4444
|
};
|
|
3669
4445
|
var musherPlistPath = (home, env = process.env) => join5(home, "Library", "LaunchAgents", `${musherLabel(env)}.plist`);
|
|
3670
|
-
var musherLogPath = (
|
|
4446
|
+
var musherLogPath = (configDir2) => join5(configDir2, "musher.log");
|
|
3671
4447
|
var xmlEscape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3672
4448
|
var musherPath = (home, execPath, currentPath = "") => {
|
|
3673
4449
|
const parts = [
|
|
@@ -3683,11 +4459,11 @@ var musherPath = (home, execPath, currentPath = "") => {
|
|
|
3683
4459
|
];
|
|
3684
4460
|
return [...new Set(parts)].join(":");
|
|
3685
4461
|
};
|
|
3686
|
-
var buildMusherPlist = (
|
|
3687
|
-
const label =
|
|
3688
|
-
const args = [...
|
|
4462
|
+
var buildMusherPlist = (o2) => {
|
|
4463
|
+
const label = o2.label ?? MUSHER_LABEL;
|
|
4464
|
+
const args = [...o2.sledsArgv, "musher", "tick"].map((a) => ` <string>${xmlEscape(a)}</string>`).join(`
|
|
3689
4465
|
`);
|
|
3690
|
-
const env = Object.entries(
|
|
4466
|
+
const env = Object.entries(o2.env).filter(([, v]) => v !== "").map(([k, v]) => ` <key>${xmlEscape(k)}</key>
|
|
3691
4467
|
<string>${xmlEscape(v)}</string>`).join(`
|
|
3692
4468
|
`);
|
|
3693
4469
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -3709,9 +4485,9 @@ ${env}
|
|
|
3709
4485
|
<key>RunAtLoad</key>
|
|
3710
4486
|
<true/>
|
|
3711
4487
|
<key>StandardOutPath</key>
|
|
3712
|
-
<string>${xmlEscape(
|
|
4488
|
+
<string>${xmlEscape(o2.logPath)}</string>
|
|
3713
4489
|
<key>StandardErrorPath</key>
|
|
3714
|
-
<string>${xmlEscape(
|
|
4490
|
+
<string>${xmlEscape(o2.logPath)}</string>
|
|
3715
4491
|
</dict>
|
|
3716
4492
|
</plist>
|
|
3717
4493
|
`;
|
|
@@ -3728,15 +4504,15 @@ var NO_SESSION_PROMPT = [
|
|
|
3728
4504
|
"SLEDS musher: a deferred job started on this seat, but no Claude Code session was recorded",
|
|
3729
4505
|
"to resume. Do not guess at work. Reply with one line saying so, and stop."
|
|
3730
4506
|
].join(" ");
|
|
3731
|
-
var claudeArgs = (
|
|
4507
|
+
var claudeArgs = (o2) => [
|
|
3732
4508
|
"-p",
|
|
3733
|
-
|
|
3734
|
-
...
|
|
3735
|
-
...
|
|
4509
|
+
o2.sessionId ? RESUME_PROMPT : NO_SESSION_PROMPT,
|
|
4510
|
+
...o2.sessionId ? ["--resume", o2.sessionId] : [],
|
|
4511
|
+
...o2.model ? ["--model", o2.model] : [],
|
|
3736
4512
|
"--output-format",
|
|
3737
4513
|
"json",
|
|
3738
4514
|
"--permission-mode",
|
|
3739
|
-
|
|
4515
|
+
o2.permissionMode
|
|
3740
4516
|
];
|
|
3741
4517
|
var parseClaudeOutput = (stdout, exitCode) => {
|
|
3742
4518
|
const lines = stdout.split(`
|
|
@@ -3766,11 +4542,11 @@ var parseClaudeOutput = (stdout, exitCode) => {
|
|
|
3766
4542
|
const tail = lines.slice(-5).join(" ").slice(0, SUMMARY_MAX);
|
|
3767
4543
|
return { ok: false, summary: tail || `claude exited ${exitCode ?? "without a code"}` };
|
|
3768
4544
|
};
|
|
3769
|
-
var defaultExecCapture = (cmd, args,
|
|
4545
|
+
var defaultExecCapture = (cmd, args, o2) => {
|
|
3770
4546
|
const r = spawnSync2(cmd, [...args], {
|
|
3771
|
-
cwd:
|
|
4547
|
+
cwd: o2.cwd,
|
|
3772
4548
|
encoding: "utf8",
|
|
3773
|
-
timeout:
|
|
4549
|
+
timeout: o2.timeoutMs,
|
|
3774
4550
|
maxBuffer: 64 * 1024 * 1024,
|
|
3775
4551
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3776
4552
|
env: { ...process.env, SLEDS_MUSHER: "1" }
|
|
@@ -3782,16 +4558,16 @@ var defaultExecCapture = (cmd, args, o) => {
|
|
|
3782
4558
|
...r.error ? { error: r.error.message } : {}
|
|
3783
4559
|
};
|
|
3784
4560
|
};
|
|
3785
|
-
var musherClient = (
|
|
3786
|
-
const fetchFn =
|
|
3787
|
-
const headers = { authorization: `Bearer ${
|
|
4561
|
+
var musherClient = (o2) => {
|
|
4562
|
+
const fetchFn = o2.fetchFn ?? fetch;
|
|
4563
|
+
const headers = { authorization: `Bearer ${o2.token}`, "content-type": "application/json" };
|
|
3788
4564
|
const call = async (path, body) => {
|
|
3789
4565
|
try {
|
|
3790
|
-
const res = await fetchFn(`${
|
|
4566
|
+
const res = await fetchFn(`${o2.proxyUrl}${path}`, {
|
|
3791
4567
|
method: body === undefined ? "GET" : "POST",
|
|
3792
4568
|
headers,
|
|
3793
4569
|
...body === undefined ? {} : { body: JSON.stringify(body) },
|
|
3794
|
-
signal: AbortSignal.timeout(
|
|
4570
|
+
signal: AbortSignal.timeout(o2.timeoutMs ?? 1e4)
|
|
3795
4571
|
});
|
|
3796
4572
|
if (!res.ok)
|
|
3797
4573
|
return null;
|
|
@@ -3833,33 +4609,33 @@ var collectOutputs = (commits, summary) => {
|
|
|
3833
4609
|
out.push(`pr:${m[0]}`);
|
|
3834
4610
|
return out.slice(0, 50);
|
|
3835
4611
|
};
|
|
3836
|
-
var runJob = async (
|
|
3837
|
-
const log =
|
|
3838
|
-
const sessionId =
|
|
3839
|
-
const cwd =
|
|
3840
|
-
const started = await
|
|
4612
|
+
var runJob = async (o2) => {
|
|
4613
|
+
const log = o2.log ?? (() => {});
|
|
4614
|
+
const sessionId = o2.job.claudeSessionId ?? o2.fallbackSession?.id ?? null;
|
|
4615
|
+
const cwd = o2.job.cwd ?? o2.fallbackSession?.cwd ?? undefined;
|
|
4616
|
+
const started = await o2.client.start(o2.job.jobId, {
|
|
3841
4617
|
...sessionId ? { claudeSessionId: sessionId } : {},
|
|
3842
4618
|
...cwd ? { cwd } : {},
|
|
3843
|
-
host:
|
|
4619
|
+
host: o2.host
|
|
3844
4620
|
});
|
|
3845
4621
|
if (!started)
|
|
3846
4622
|
return { kind: "skipped", reason: "control plane unreachable" };
|
|
3847
4623
|
if (!started.ok)
|
|
3848
4624
|
return { kind: "skipped", reason: started.reason };
|
|
3849
|
-
const model =
|
|
3850
|
-
const args = claudeArgs({ sessionId, model, permissionMode:
|
|
3851
|
-
log(`musher: running job ${
|
|
3852
|
-
const exec =
|
|
4625
|
+
const model = o2.job.model ?? started.model ?? o2.fallbackSession?.model ?? null;
|
|
4626
|
+
const args = claudeArgs({ sessionId, model, permissionMode: o2.permissionMode });
|
|
4627
|
+
log(`musher: running job ${o2.job.jobId} (${sessionId ? `resume ${sessionId}` : "no session"}${model ? ` · ${model}` : ""}${cwd ? ` · ${cwd}` : ""})`);
|
|
4628
|
+
const exec = o2.exec ?? defaultExecCapture;
|
|
3853
4629
|
const headBefore = gitHead(cwd, exec);
|
|
3854
4630
|
const t0 = Date.now();
|
|
3855
|
-
const r = exec(
|
|
4631
|
+
const r = exec(o2.claudePath, args, { cwd, timeoutMs: o2.timeoutMs });
|
|
3856
4632
|
const durationMs = Date.now() - t0;
|
|
3857
|
-
const result = r.error ? { ok: false, summary: `could not run ${
|
|
4633
|
+
const result = r.error ? { ok: false, summary: `could not run ${o2.claudePath}: ${r.error}` } : parseClaudeOutput(r.stdout, r.status);
|
|
3858
4634
|
if (!r.error && r.status === null)
|
|
3859
|
-
result.summary = `timed out after ${Math.round(
|
|
4635
|
+
result.summary = `timed out after ${Math.round(o2.timeoutMs / 60000)} min. ${result.summary}`.trim();
|
|
3860
4636
|
const outputs = collectOutputs(gitNewCommits(cwd, headBefore, gitHead(cwd, exec), exec), result.summary);
|
|
3861
|
-
await
|
|
3862
|
-
jobId:
|
|
4637
|
+
await o2.client.finish({
|
|
4638
|
+
jobId: o2.job.jobId,
|
|
3863
4639
|
runId: started.runId,
|
|
3864
4640
|
ok: result.ok,
|
|
3865
4641
|
summary: result.summary,
|
|
@@ -3868,7 +4644,7 @@ var runJob = async (o) => {
|
|
|
3868
4644
|
durationMs: result.durationMs ?? durationMs,
|
|
3869
4645
|
...result.sessionId ?? sessionId ? { claudeSessionId: result.sessionId ?? sessionId } : {}
|
|
3870
4646
|
});
|
|
3871
|
-
log(`musher: job ${
|
|
4647
|
+
log(`musher: job ${o2.job.jobId} ${result.ok ? "delivered" : "failed"} in ${Math.round(durationMs / 1000)}s`);
|
|
3872
4648
|
return { kind: "ran", ok: result.ok, summary: result.summary, durationMs, outputs };
|
|
3873
4649
|
};
|
|
3874
4650
|
var verifyAttestation = (payload, sig, publicKeyB64) => {
|
|
@@ -4172,11 +4948,11 @@ var bindingResetAt = (w, now) => {
|
|
|
4172
4948
|
return pick.length ? Math.min(...pick) : undefined;
|
|
4173
4949
|
};
|
|
4174
4950
|
var readSeatState = (home) => {
|
|
4175
|
-
const
|
|
4176
|
-
if (!existsSync5(
|
|
4951
|
+
const p2 = seatStatePath(home);
|
|
4952
|
+
if (!existsSync5(p2))
|
|
4177
4953
|
return {};
|
|
4178
4954
|
try {
|
|
4179
|
-
return parseSeatState(JSON.parse(readFileSync5(
|
|
4955
|
+
return parseSeatState(JSON.parse(readFileSync5(p2, "utf8")));
|
|
4180
4956
|
} catch {
|
|
4181
4957
|
return {};
|
|
4182
4958
|
}
|
|
@@ -4301,28 +5077,28 @@ var seatWindowBody = (w) => ({
|
|
|
4301
5077
|
...w.sevenDay ? { sevenDay: w.sevenDay } : {},
|
|
4302
5078
|
...w.spendLimit ? { spendLimit: w.spendLimit } : {}
|
|
4303
5079
|
});
|
|
4304
|
-
var postJson = async (
|
|
4305
|
-
const fetchFn =
|
|
5080
|
+
var postJson = async (o2, path, body) => {
|
|
5081
|
+
const fetchFn = o2.fetchFn ?? fetch;
|
|
4306
5082
|
try {
|
|
4307
|
-
const res = await fetchFn(`${
|
|
5083
|
+
const res = await fetchFn(`${o2.proxyUrl}${path}`, {
|
|
4308
5084
|
method: "POST",
|
|
4309
|
-
headers: { authorization: `Bearer ${
|
|
5085
|
+
headers: { authorization: `Bearer ${o2.token}`, "content-type": "application/json" },
|
|
4310
5086
|
body: JSON.stringify(body),
|
|
4311
|
-
signal: AbortSignal.timeout(
|
|
5087
|
+
signal: AbortSignal.timeout(o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS)
|
|
4312
5088
|
});
|
|
4313
5089
|
return res.ok;
|
|
4314
5090
|
} catch {
|
|
4315
5091
|
return false;
|
|
4316
5092
|
}
|
|
4317
5093
|
};
|
|
4318
|
-
var postSeatWindow = (
|
|
4319
|
-
var postSeatWall = (
|
|
5094
|
+
var postSeatWindow = (o2, w) => postJson(o2, "/proxy/seat", seatWindowBody(w));
|
|
5095
|
+
var postSeatWall = (o2, input) => postJson(o2, "/proxy/seat-wall", { source: "claude", ...input });
|
|
4320
5096
|
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.";
|
|
4321
5097
|
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.";
|
|
4322
5098
|
var seatWallLine = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE : SEAT_WALL_LINE_NO_KEY;
|
|
4323
|
-
var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((
|
|
5099
|
+
var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) => {
|
|
4324
5100
|
if (stdin.isTTY) {
|
|
4325
|
-
|
|
5101
|
+
resolve3("");
|
|
4326
5102
|
return;
|
|
4327
5103
|
}
|
|
4328
5104
|
let data = "";
|
|
@@ -4332,7 +5108,7 @@ var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve) => {
|
|
|
4332
5108
|
return;
|
|
4333
5109
|
settled = true;
|
|
4334
5110
|
clearTimeout(timer);
|
|
4335
|
-
|
|
5111
|
+
resolve3(data);
|
|
4336
5112
|
};
|
|
4337
5113
|
const timer = setTimeout(finish, timeoutMs);
|
|
4338
5114
|
stdin.setEncoding("utf8");
|
|
@@ -4450,21 +5226,21 @@ var parseFlags = (argv) => {
|
|
|
4450
5226
|
}
|
|
4451
5227
|
return flags;
|
|
4452
5228
|
};
|
|
4453
|
-
var defer = async (
|
|
4454
|
-
const log =
|
|
4455
|
-
const error =
|
|
4456
|
-
const now = (
|
|
4457
|
-
const flags = parseFlags(
|
|
4458
|
-
const client =
|
|
4459
|
-
const src = meterSource(
|
|
4460
|
-
return src ? musherClient({ ...src, fetchFn:
|
|
5229
|
+
var defer = async (o2) => {
|
|
5230
|
+
const log = o2.log ?? console.log;
|
|
5231
|
+
const error = o2.error ?? console.error;
|
|
5232
|
+
const now = (o2.now ?? Date.now)();
|
|
5233
|
+
const flags = parseFlags(o2.argv);
|
|
5234
|
+
const client = o2.client ?? (() => {
|
|
5235
|
+
const src = meterSource(o2.home);
|
|
5236
|
+
return src ? musherClient({ ...src, fetchFn: o2.fetchFn }) : null;
|
|
4461
5237
|
})();
|
|
4462
5238
|
if (!client) {
|
|
4463
5239
|
error("No seat on this machine. Run `sleds init` first.");
|
|
4464
5240
|
return 1;
|
|
4465
5241
|
}
|
|
4466
|
-
const state = readSeatState(
|
|
4467
|
-
const piped =
|
|
5242
|
+
const state = readSeatState(o2.home);
|
|
5243
|
+
const piped = o2.stdin ? seatSessionFromStdin(o2.stdin, now) : null;
|
|
4468
5244
|
const remembered = piped ?? state.session;
|
|
4469
5245
|
const sessionId = flags.session ?? remembered?.id;
|
|
4470
5246
|
const cwd = flags.cwd ?? remembered?.cwd;
|
|
@@ -4485,7 +5261,7 @@ var defer = async (o) => {
|
|
|
4485
5261
|
...model ? { model } : {},
|
|
4486
5262
|
...sessionId ? { claudeSessionId: sessionId } : {},
|
|
4487
5263
|
...cwd ? { cwd } : {},
|
|
4488
|
-
host:
|
|
5264
|
+
host: o2.host ?? hostname()
|
|
4489
5265
|
});
|
|
4490
5266
|
if (!r) {
|
|
4491
5267
|
error("Could not reach the SLEDS proxy; nothing was scheduled.");
|
|
@@ -4494,8 +5270,8 @@ var defer = async (o) => {
|
|
|
4494
5270
|
const when = r.scheduledAt <= now + 60000 ? "as soon as the musher ticks (within 5 min)" : `${formatLocal(r.scheduledAt)} (in ${formatDuration(r.scheduledAt - now)})`;
|
|
4495
5271
|
log(`Deferred. This Mac resumes ${sessionId ? `session ${sessionId.slice(0, 8)}…` : "the work"}${model ? ` on ${model}` : ""} ${when}.`);
|
|
4496
5272
|
log(` job ${r.jobId}${cwd ? ` · ${cwd}` : ""} · runs on your seat, no key · sleds musher --status to watch`);
|
|
4497
|
-
const cfg = readConfig(
|
|
4498
|
-
if (process.platform === "darwin" && !existsSync6(musherPlistPath(
|
|
5273
|
+
const cfg = readConfig(o2.home);
|
|
5274
|
+
if (process.platform === "darwin" && !existsSync6(musherPlistPath(o2.home)) && cfg.musher?.agent !== "launchd")
|
|
4499
5275
|
log(` The musher agent is not installed on this Mac: run \`sleds musher --install\` once, or \`sleds run ${r.jobId}\` by hand at the reset.`);
|
|
4500
5276
|
log(" Keep this Mac awake at that time (System Settings → Battery → prevent sleeping on power adapter).");
|
|
4501
5277
|
return 0;
|
|
@@ -4675,7 +5451,7 @@ var renderCockpit = (cockpit, window, events, prior, now = Date.now(), extra = {
|
|
|
4675
5451
|
`/sled pickup ${newHandoffs[0]}`,
|
|
4676
5452
|
...ws(cockpit)
|
|
4677
5453
|
]);
|
|
4678
|
-
for (const id of extra.feed.delivered.filter((
|
|
5454
|
+
for (const id of extra.feed.delivered.filter((id2) => !(before?.delivered ?? []).includes(id2)))
|
|
4679
5455
|
argv.push([
|
|
4680
5456
|
"log",
|
|
4681
5457
|
"--level",
|
|
@@ -4685,7 +5461,7 @@ var renderCockpit = (cockpit, window, events, prior, now = Date.now(), extra = {
|
|
|
4685
5461
|
`overnight job done · /sled resume ${id}`,
|
|
4686
5462
|
...ws(cockpit)
|
|
4687
5463
|
]);
|
|
4688
|
-
for (const id of extra.feed.failed.filter((
|
|
5464
|
+
for (const id of extra.feed.failed.filter((id2) => !(before?.failed ?? []).includes(id2)))
|
|
4689
5465
|
argv.push([
|
|
4690
5466
|
"notify",
|
|
4691
5467
|
"--title",
|
|
@@ -4742,15 +5518,15 @@ var feedFromJobs = (body, me, now) => {
|
|
|
4742
5518
|
}
|
|
4743
5519
|
return f;
|
|
4744
5520
|
};
|
|
4745
|
-
var fetchFeed = async (
|
|
5521
|
+
var fetchFeed = async (o2, now) => {
|
|
4746
5522
|
try {
|
|
4747
|
-
const res = await (
|
|
4748
|
-
headers: { authorization: `Bearer ${
|
|
4749
|
-
signal: AbortSignal.timeout(
|
|
5523
|
+
const res = await (o2.fetchFn ?? fetch)(`${o2.proxyUrl}/proxy/jobs`, {
|
|
5524
|
+
headers: { authorization: `Bearer ${o2.token}` },
|
|
5525
|
+
signal: AbortSignal.timeout(o2.timeoutMs ?? 1500)
|
|
4750
5526
|
});
|
|
4751
5527
|
if (!res.ok)
|
|
4752
5528
|
return null;
|
|
4753
|
-
return feedFromJobs(await res.json(),
|
|
5529
|
+
return feedFromJobs(await res.json(), o2.email, now);
|
|
4754
5530
|
} catch {
|
|
4755
5531
|
return null;
|
|
4756
5532
|
}
|
|
@@ -4786,9 +5562,9 @@ init_meter();
|
|
|
4786
5562
|
var defaultWhich = (cmd) => {
|
|
4787
5563
|
const path = process.env.PATH ?? "";
|
|
4788
5564
|
for (const dir of path.split(":").filter(Boolean)) {
|
|
4789
|
-
const
|
|
4790
|
-
if (existsSync7(
|
|
4791
|
-
return
|
|
5565
|
+
const p2 = `${dir}/${cmd}`;
|
|
5566
|
+
if (existsSync7(p2))
|
|
5567
|
+
return p2;
|
|
4792
5568
|
}
|
|
4793
5569
|
return null;
|
|
4794
5570
|
};
|
|
@@ -4798,14 +5574,14 @@ var findClaude = (home, configured, which) => {
|
|
|
4798
5574
|
const found = which("claude");
|
|
4799
5575
|
if (found)
|
|
4800
5576
|
return found;
|
|
4801
|
-
for (const
|
|
5577
|
+
for (const p2 of [
|
|
4802
5578
|
`${home}/.local/bin/claude`,
|
|
4803
5579
|
"/opt/homebrew/bin/claude",
|
|
4804
5580
|
"/usr/local/bin/claude",
|
|
4805
5581
|
`${home}/.npm-global/bin/claude`
|
|
4806
5582
|
])
|
|
4807
|
-
if (existsSync7(
|
|
4808
|
-
return
|
|
5583
|
+
if (existsSync7(p2))
|
|
5584
|
+
return p2;
|
|
4809
5585
|
return null;
|
|
4810
5586
|
};
|
|
4811
5587
|
var musherAgentState = (home, env = process.env) => {
|
|
@@ -4825,7 +5601,8 @@ var musherAgentState = (home, env = process.env) => {
|
|
|
4825
5601
|
// src/commands/status.ts
|
|
4826
5602
|
init_config();
|
|
4827
5603
|
init_settings();
|
|
4828
|
-
var
|
|
5604
|
+
var HEALTH_TIMEOUT_MS = 4000;
|
|
5605
|
+
var checkHealth = async (url, fetchFn = fetch, timeoutMs = HEALTH_TIMEOUT_MS) => {
|
|
4829
5606
|
try {
|
|
4830
5607
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
4831
5608
|
return res.ok ? "ok" : "unreachable";
|
|
@@ -4868,10 +5645,10 @@ function defaultWhich2(cmd) {
|
|
|
4868
5645
|
return [];
|
|
4869
5646
|
}
|
|
4870
5647
|
}
|
|
4871
|
-
async function runDoctor(
|
|
4872
|
-
const env =
|
|
5648
|
+
async function runDoctor(o2) {
|
|
5649
|
+
const env = o2.env ?? process.env;
|
|
4873
5650
|
const checks = [];
|
|
4874
|
-
const home =
|
|
5651
|
+
const home = o2.home;
|
|
4875
5652
|
const overrides = [
|
|
4876
5653
|
env.SLEDS_HOME?.trim() ? `SLEDS_HOME=${env.SLEDS_HOME.trim()}` : null,
|
|
4877
5654
|
env.CLAUDE_CONFIG_DIR?.trim() ? `CLAUDE_CONFIG_DIR=${env.CLAUDE_CONFIG_DIR.trim()}` : null
|
|
@@ -4921,7 +5698,7 @@ async function runDoctor(o) {
|
|
|
4921
5698
|
}
|
|
4922
5699
|
const src = meterSource(home);
|
|
4923
5700
|
if (src) {
|
|
4924
|
-
const health = await checkHealth(src.proxyUrl,
|
|
5701
|
+
const health = await checkHealth(src.proxyUrl, o2.fetchFn, o2.timeoutMs);
|
|
4925
5702
|
checks.push({
|
|
4926
5703
|
level: health === "ok" ? "ok" : "fail",
|
|
4927
5704
|
name: "proxy",
|
|
@@ -4930,15 +5707,15 @@ async function runDoctor(o) {
|
|
|
4930
5707
|
});
|
|
4931
5708
|
}
|
|
4932
5709
|
if (settings) {
|
|
4933
|
-
const
|
|
5710
|
+
const seat2 = hasSeatHook(settings);
|
|
4934
5711
|
const spec = hasSpecHook(settings);
|
|
4935
5712
|
const session = hasSessionHook(settings);
|
|
4936
5713
|
const stop = hasStopHook(settings);
|
|
4937
|
-
const all =
|
|
5714
|
+
const all = seat2 && spec && session && stop;
|
|
4938
5715
|
checks.push({
|
|
4939
5716
|
level: all ? "ok" : "warn",
|
|
4940
5717
|
name: "hooks",
|
|
4941
|
-
detail: `seat-event ${
|
|
5718
|
+
detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"} · session-event ${session ? "on" : "missing"} · stop-event ${stop ? "on" : "missing"}`,
|
|
4942
5719
|
fix: all ? undefined : "sleds hooks"
|
|
4943
5720
|
});
|
|
4944
5721
|
const want = env.SLEDS_HOME?.trim() ? resolve3(home, env.SLEDS_HOME.trim()) : undefined;
|
|
@@ -4969,18 +5746,18 @@ async function runDoctor(o) {
|
|
|
4969
5746
|
name: "cockpit",
|
|
4970
5747
|
detail: cockpit ? `${cockpit.kind}${cockpit.workspaceId ? ` (workspace ${cockpit.workspaceId})` : ""} — seat line in the sidebar, ring at the wall` : "none detected (run inside cmux to mirror the seat into its sidebar)"
|
|
4971
5748
|
});
|
|
4972
|
-
const nodeVersion =
|
|
5749
|
+
const nodeVersion = o2.nodeVersion ?? process.version;
|
|
4973
5750
|
checks.push({
|
|
4974
5751
|
level: nodeOk(nodeVersion) ? "ok" : "warn",
|
|
4975
5752
|
name: "node",
|
|
4976
5753
|
detail: `${nodeVersion}${nodeOk(nodeVersion) ? "" : ` — seat-sync needs ≥ ${NODE_MIN} (node:sqlite)`}`,
|
|
4977
5754
|
fix: nodeOk(nodeVersion) ? undefined : `brew install node@22 && echo 'export PATH="/opt/homebrew/opt/node@22/bin:$PATH"' >> ~/.zshrc`
|
|
4978
5755
|
});
|
|
4979
|
-
const which = (
|
|
4980
|
-
const stale = which.filter((
|
|
5756
|
+
const which = (o2.which ?? defaultWhich2)("sleds");
|
|
5757
|
+
const stale = which.filter((p2) => {
|
|
4981
5758
|
try {
|
|
4982
|
-
const target = lstatSync(
|
|
4983
|
-
return /\/src\/index\.ts$/.test(target) || /\.bun\/bin\/sleds$/.test(
|
|
5759
|
+
const target = lstatSync(p2).isSymbolicLink() ? readlinkSync(p2) : p2;
|
|
5760
|
+
return /\/src\/index\.ts$/.test(target) || /\.bun\/bin\/sleds$/.test(p2);
|
|
4984
5761
|
} catch {
|
|
4985
5762
|
return false;
|
|
4986
5763
|
}
|
|
@@ -5002,7 +5779,7 @@ async function runDoctor(o) {
|
|
|
5002
5779
|
detail: age === null ? "no report yet — run one Claude Code turn on this seat" : `last report ${age < 1 ? "just now" : `${age}m ago`} via the status line${seat.window?.sevenDay ? ` · wk ${Math.round(seat.window.sevenDay.usedPct)}%` : ""}`,
|
|
5003
5780
|
fix: age === null ? "open claude, say hi, exit — the status line posts the seat's quota" : undefined
|
|
5004
5781
|
});
|
|
5005
|
-
if ((
|
|
5782
|
+
if ((o2.platform ?? process.platform) === "darwin") {
|
|
5006
5783
|
const agent = musherAgentState(home, env);
|
|
5007
5784
|
checks.push(!agent.installed ? {
|
|
5008
5785
|
level: "warn",
|
|
@@ -5019,14 +5796,14 @@ async function runDoctor(o) {
|
|
|
5019
5796
|
name: "musher",
|
|
5020
5797
|
detail: `launchd agent ${musherLabel(env)}, every 5 min`
|
|
5021
5798
|
});
|
|
5022
|
-
const claude = findClaude(home, readConfig(home).musher?.claudePath, (c) => (
|
|
5799
|
+
const claude = findClaude(home, readConfig(home).musher?.claudePath, (c) => (o2.which ?? defaultWhich2)(c)[0] ?? null);
|
|
5023
5800
|
checks.push(claude ? { level: "ok", name: "claude cli", detail: claude } : {
|
|
5024
5801
|
level: "warn",
|
|
5025
5802
|
name: "claude cli",
|
|
5026
5803
|
detail: "`claude` not on PATH — the musher cannot run Claude Code",
|
|
5027
5804
|
fix: 'sleds musher --claude "$(which claude)"'
|
|
5028
5805
|
});
|
|
5029
|
-
const sleep = (
|
|
5806
|
+
const sleep = (o2.pmset ?? defaultPmset)();
|
|
5030
5807
|
if (sleep !== null)
|
|
5031
5808
|
checks.push(sleep.acSleepMinutes === 0 ? {
|
|
5032
5809
|
level: "ok",
|
|
@@ -5056,9 +5833,9 @@ var defaultPmset = () => {
|
|
|
5056
5833
|
return null;
|
|
5057
5834
|
}
|
|
5058
5835
|
};
|
|
5059
|
-
var doctor = async (
|
|
5060
|
-
const log =
|
|
5061
|
-
const checks = await runDoctor(
|
|
5836
|
+
var doctor = async (o2) => {
|
|
5837
|
+
const log = o2.log ?? console.log;
|
|
5838
|
+
const checks = await runDoctor(o2);
|
|
5062
5839
|
for (const line of formatDoctor(checks))
|
|
5063
5840
|
log(line);
|
|
5064
5841
|
return checks.some((c) => c.level === "fail") ? 1 : 0;
|
|
@@ -5086,21 +5863,21 @@ function sourceKey(s) {
|
|
|
5086
5863
|
function sourceText2(s) {
|
|
5087
5864
|
return [`${str5(s.kind) ?? "?"} ${str5(s.project) ?? "?"}`, str5(s.doc), str5(s.item)].filter(Boolean).join(" › ");
|
|
5088
5865
|
}
|
|
5089
|
-
var readJson = (
|
|
5090
|
-
if (!existsSync9(
|
|
5866
|
+
var readJson = (p2) => {
|
|
5867
|
+
if (!existsSync9(p2))
|
|
5091
5868
|
return null;
|
|
5092
5869
|
try {
|
|
5093
|
-
return JSON.parse(readFileSync8(
|
|
5870
|
+
return JSON.parse(readFileSync8(p2, "utf8"));
|
|
5094
5871
|
} catch {
|
|
5095
5872
|
return null;
|
|
5096
5873
|
}
|
|
5097
5874
|
};
|
|
5098
|
-
var writeJson = (
|
|
5099
|
-
mkdirSync7(dirname4(
|
|
5100
|
-
const tmp = `${
|
|
5875
|
+
var writeJson = (p2, v) => {
|
|
5876
|
+
mkdirSync7(dirname4(p2), { recursive: true, mode: 448 });
|
|
5877
|
+
const tmp = `${p2}.${process.pid}.tmp`;
|
|
5101
5878
|
writeFileSync7(tmp, `${JSON.stringify(v, null, 2)}
|
|
5102
5879
|
`, { mode: 384 });
|
|
5103
|
-
renameSync5(tmp,
|
|
5880
|
+
renameSync5(tmp, p2);
|
|
5104
5881
|
};
|
|
5105
5882
|
var readOwed = (home, now = Date.now()) => {
|
|
5106
5883
|
const f = readJson(owedPath(home));
|
|
@@ -5115,14 +5892,14 @@ var readSession = (home) => {
|
|
|
5115
5892
|
var writeSession = (home, s) => writeJson(sessionPath(home), s);
|
|
5116
5893
|
function withEstimated(rows, a) {
|
|
5117
5894
|
const key = sourceKey(a.source);
|
|
5118
|
-
const
|
|
5895
|
+
const row2 = {
|
|
5119
5896
|
key,
|
|
5120
5897
|
source: a.source,
|
|
5121
5898
|
...a.title ? { title: a.title } : {},
|
|
5122
5899
|
ts: a.now,
|
|
5123
5900
|
...a.sessionId ? { sessionId: a.sessionId } : {}
|
|
5124
5901
|
};
|
|
5125
|
-
return [...rows.filter((r) => r.key !== key),
|
|
5902
|
+
return [...rows.filter((r) => r.key !== key), row2];
|
|
5126
5903
|
}
|
|
5127
5904
|
function withActual(rows, a) {
|
|
5128
5905
|
if (a.state === "started")
|
|
@@ -5225,59 +6002,59 @@ var callTool2 = (name, args, deps) => callTool(name, args, contextFor(deps));
|
|
|
5225
6002
|
var handleLine2 = (line, deps) => handleLine(line, contextFor(deps));
|
|
5226
6003
|
|
|
5227
6004
|
// src/commands/handoffs.ts
|
|
5228
|
-
var handoffsCmd = async (
|
|
5229
|
-
const r = await callTool2("handoffs", {}, { home:
|
|
5230
|
-
(r.isError ?
|
|
6005
|
+
var handoffsCmd = async (o2) => {
|
|
6006
|
+
const r = await callTool2("handoffs", {}, { home: o2.home });
|
|
6007
|
+
(r.isError ? o2.error ?? console.error : o2.log ?? console.log)(r.text);
|
|
5231
6008
|
return r.isError ? 1 : 0;
|
|
5232
6009
|
};
|
|
5233
|
-
var pickupCmd = async (
|
|
5234
|
-
const id =
|
|
6010
|
+
var pickupCmd = async (o2) => {
|
|
6011
|
+
const id = o2.argv[0];
|
|
5235
6012
|
if (!id) {
|
|
5236
|
-
(
|
|
6013
|
+
(o2.error ?? console.error)("usage: sleds pickup <handoffId> (ids from `sleds handoffs`)");
|
|
5237
6014
|
return 2;
|
|
5238
6015
|
}
|
|
5239
|
-
const r = await callTool2("pickup", { handoffId: id }, { home:
|
|
5240
|
-
(r.isError ?
|
|
6016
|
+
const r = await callTool2("pickup", { handoffId: id }, { home: o2.home });
|
|
6017
|
+
(r.isError ? o2.error ?? console.error : o2.log ?? console.log)(r.text);
|
|
5241
6018
|
return r.isError ? 1 : 0;
|
|
5242
6019
|
};
|
|
5243
|
-
var withdrawCmd = async (
|
|
5244
|
-
const id =
|
|
6020
|
+
var withdrawCmd = async (o2) => {
|
|
6021
|
+
const id = o2.argv[0];
|
|
5245
6022
|
if (!id) {
|
|
5246
|
-
(
|
|
6023
|
+
(o2.error ?? console.error)("usage: sleds withdraw <handoffId> (one you parked)");
|
|
5247
6024
|
return 2;
|
|
5248
6025
|
}
|
|
5249
|
-
const r = await callTool2("withdraw", { handoffId: id }, { home:
|
|
5250
|
-
(r.isError ?
|
|
6026
|
+
const r = await callTool2("withdraw", { handoffId: id }, { home: o2.home });
|
|
6027
|
+
(r.isError ? o2.error ?? console.error : o2.log ?? console.log)(r.text);
|
|
5251
6028
|
return r.isError ? 1 : 0;
|
|
5252
6029
|
};
|
|
5253
6030
|
|
|
5254
6031
|
// src/commands/hooks.ts
|
|
5255
6032
|
init_settings();
|
|
5256
|
-
var hooks = (
|
|
5257
|
-
const log =
|
|
5258
|
-
const error =
|
|
5259
|
-
if (!hasClaudeCode(
|
|
5260
|
-
error(`Claude Code not found (${claudeDir(
|
|
6033
|
+
var hooks = (o2) => {
|
|
6034
|
+
const log = o2.log ?? console.log;
|
|
6035
|
+
const error = o2.error ?? console.error;
|
|
6036
|
+
if (!hasClaudeCode(o2.home)) {
|
|
6037
|
+
error(`Claude Code not found (${claudeDir(o2.home)} does not exist). Install it, then re-run.`);
|
|
5261
6038
|
return 1;
|
|
5262
6039
|
}
|
|
5263
|
-
const settings = readSettings(
|
|
6040
|
+
const settings = readSettings(o2.home);
|
|
5264
6041
|
const had = hasSeatHook(settings);
|
|
5265
6042
|
const hadSpec = hasSpecHook(settings);
|
|
5266
|
-
const withSeat = applySeatHook(settings,
|
|
6043
|
+
const withSeat = applySeatHook(settings, o2.seatEventCommand ?? sledsCommand("seat-event", o2.home, o2.env));
|
|
5267
6044
|
const hadPrompt = hasPromptHook(settings);
|
|
5268
6045
|
const hadSession = hasSessionHook(settings);
|
|
5269
6046
|
const hadStop = hasStopHook(settings);
|
|
5270
|
-
const withPrompt = applyPromptHook(applySpecHook(withSeat,
|
|
5271
|
-
const withSpec = applyStopHook(applySessionHook(withPrompt,
|
|
5272
|
-
const hadLine =
|
|
5273
|
-
writeSettings(
|
|
5274
|
-
log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(
|
|
6047
|
+
const withPrompt = applyPromptHook(applySpecHook(withSeat, o2.specEventCommand ?? sledsCommand("spec-event", o2.home, o2.env)), o2.promptEventCommand ?? sledsCommand("prompt-event", o2.home, o2.env));
|
|
6048
|
+
const withSpec = applyStopHook(applySessionHook(withPrompt, o2.sessionEventCommand ?? sledsCommand("session-event", o2.home, o2.env)), o2.stopEventCommand ?? sledsCommand("stop-event", o2.home, o2.env));
|
|
6049
|
+
const hadLine = o2.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
|
|
6050
|
+
writeSettings(o2.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o2.home, o2.env)).settings : withSpec);
|
|
6051
|
+
log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o2.home)}`);
|
|
5275
6052
|
if (hadLine)
|
|
5276
|
-
log(`refreshed statusLine in ${settingsPath(
|
|
5277
|
-
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(
|
|
5278
|
-
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(
|
|
5279
|
-
log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(
|
|
5280
|
-
log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(
|
|
6053
|
+
log(`refreshed statusLine in ${settingsPath(o2.home)}`);
|
|
6054
|
+
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o2.home)}`);
|
|
6055
|
+
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o2.home)}`);
|
|
6056
|
+
log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(o2.home)}`);
|
|
6057
|
+
log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(o2.home)}`);
|
|
5281
6058
|
return 0;
|
|
5282
6059
|
};
|
|
5283
6060
|
|
|
@@ -5295,7 +6072,7 @@ description: The SLEDS loop for engineering work — size a build before startin
|
|
|
5295
6072
|
|
|
5296
6073
|
# SLEDS
|
|
5297
6074
|
|
|
5298
|
-
SLEDS is the
|
|
6075
|
+
SLEDS is where the business meets engineering: every piece of work 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.
|
|
5299
6076
|
|
|
5300
6077
|
## The loop (every build, no exceptions)
|
|
5301
6078
|
|
|
@@ -5391,7 +6168,7 @@ Resume a parked job. Show the unanswered question and its options, take the answ
|
|
|
5391
6168
|
|
|
5392
6169
|
## /sled sync-backlog — price the backlog
|
|
5393
6170
|
|
|
5394
|
-
With the \`sleds\` MCP server available, size every open issue so the
|
|
6171
|
+
With the \`sleds\` MCP server available, size every open issue so the Work page shows the backlog in dollars and hours, not T-shirt sizes or points. SLEDS is not a tracker and never the system of record: Linear and GitHub keep the issues; SLEDS mirrors their words (cycle, labels, priority, assignee, state) and adds the price.
|
|
5395
6172
|
|
|
5396
6173
|
1. Find the issues. In Cowork with a Claude Project attached: each spec doc, each phase or slice inside a spec, and each line of a backlog list (a numbered or bulleted list under a heading like Backlog, Next, To do, Roadmap). With a Linear or GitHub MCP available: the open issues of the team / repo the user names, with their cycle or milestone, labels, priority, assignee and state. Skip anything done, closed or shipped.
|
|
5397
6174
|
2. For each issue call the \`estimate\` tool with \`spec\` = the title on the first line, then the body (quote the issue or doc, do not paraphrase), and \`source\` = \`{ kind: "linear" | "github" | "claude-project", project: <team, repo or Project name>, doc: <issue key like ENG-42 or #123, or the doc path>, item: <heading or line, only when a doc holds many>, url, cycle, labels, priority, assignee, state }\` — the tracker's values verbatim. The estimator dedupes: an unchanged issue returns its existing estimate, so re-running is safe.
|
|
@@ -5483,9 +6260,9 @@ function parsePoll(body) {
|
|
|
5483
6260
|
};
|
|
5484
6261
|
}
|
|
5485
6262
|
function openInBrowser(url) {
|
|
5486
|
-
const
|
|
5487
|
-
const cmd =
|
|
5488
|
-
const args =
|
|
6263
|
+
const p2 = platform();
|
|
6264
|
+
const cmd = p2 === "darwin" ? "open" : p2 === "win32" ? "cmd" : "xdg-open";
|
|
6265
|
+
const args = p2 === "win32" ? ["/c", "start", "", url] : [url];
|
|
5489
6266
|
try {
|
|
5490
6267
|
const r = spawnSync3(cmd, args, { stdio: "ignore", timeout: 5000 });
|
|
5491
6268
|
return r.error === undefined && r.status === 0;
|
|
@@ -5494,17 +6271,17 @@ function openInBrowser(url) {
|
|
|
5494
6271
|
}
|
|
5495
6272
|
}
|
|
5496
6273
|
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
5497
|
-
async function deviceFlow(
|
|
5498
|
-
const fetchFn =
|
|
5499
|
-
const log =
|
|
5500
|
-
const sleep =
|
|
5501
|
-
const now =
|
|
6274
|
+
async function deviceFlow(o2) {
|
|
6275
|
+
const fetchFn = o2.fetchFn ?? fetch;
|
|
6276
|
+
const log = o2.log ?? console.log;
|
|
6277
|
+
const sleep = o2.sleep ?? defaultSleep;
|
|
6278
|
+
const now = o2.now ?? Date.now;
|
|
5502
6279
|
let started = null;
|
|
5503
6280
|
try {
|
|
5504
|
-
const res = await fetchFn(`${
|
|
6281
|
+
const res = await fetchFn(`${o2.proxyUrl}/device/start`, {
|
|
5505
6282
|
method: "POST",
|
|
5506
6283
|
headers: { "content-type": "application/json" },
|
|
5507
|
-
body: JSON.stringify({ agent:
|
|
6284
|
+
body: JSON.stringify({ agent: o2.agent ?? "claude-code", host: o2.host ?? hostname2() }),
|
|
5508
6285
|
signal: AbortSignal.timeout(20000)
|
|
5509
6286
|
});
|
|
5510
6287
|
if (res.status === 429)
|
|
@@ -5517,7 +6294,7 @@ async function deviceFlow(o) {
|
|
|
5517
6294
|
}
|
|
5518
6295
|
if (!started)
|
|
5519
6296
|
return { ok: false, reason: "bad_answer" };
|
|
5520
|
-
const opened =
|
|
6297
|
+
const opened = o2.noBrowser ? false : (o2.openBrowser ?? openInBrowser)(started.verifyUrl);
|
|
5521
6298
|
log("");
|
|
5522
6299
|
log(opened ? "Opening your browser to sign in to SLEDS." : "Open this in your browser to sign in to SLEDS:");
|
|
5523
6300
|
log(` ${started.verifyUrl}`);
|
|
@@ -5531,7 +6308,7 @@ async function deviceFlow(o) {
|
|
|
5531
6308
|
await sleep(intervalMs);
|
|
5532
6309
|
let body;
|
|
5533
6310
|
try {
|
|
5534
|
-
const res = await fetchFn(`${
|
|
6311
|
+
const res = await fetchFn(`${o2.proxyUrl}/device/poll`, {
|
|
5535
6312
|
method: "POST",
|
|
5536
6313
|
headers: { "content-type": "application/json" },
|
|
5537
6314
|
body: JSON.stringify({ deviceCode: started.deviceCode }),
|
|
@@ -5559,41 +6336,41 @@ async function deviceFlow(o) {
|
|
|
5559
6336
|
|
|
5560
6337
|
// src/commands/hooks.ts
|
|
5561
6338
|
init_settings();
|
|
5562
|
-
var hooks2 = (
|
|
5563
|
-
const log =
|
|
5564
|
-
const error =
|
|
5565
|
-
if (!hasClaudeCode(
|
|
5566
|
-
error(`Claude Code not found (${claudeDir(
|
|
6339
|
+
var hooks2 = (o2) => {
|
|
6340
|
+
const log = o2.log ?? console.log;
|
|
6341
|
+
const error = o2.error ?? console.error;
|
|
6342
|
+
if (!hasClaudeCode(o2.home)) {
|
|
6343
|
+
error(`Claude Code not found (${claudeDir(o2.home)} does not exist). Install it, then re-run.`);
|
|
5567
6344
|
return 1;
|
|
5568
6345
|
}
|
|
5569
|
-
const settings = readSettings(
|
|
6346
|
+
const settings = readSettings(o2.home);
|
|
5570
6347
|
const had = hasSeatHook(settings);
|
|
5571
6348
|
const hadSpec = hasSpecHook(settings);
|
|
5572
|
-
const withSeat = applySeatHook(settings,
|
|
6349
|
+
const withSeat = applySeatHook(settings, o2.seatEventCommand ?? sledsCommand("seat-event", o2.home, o2.env));
|
|
5573
6350
|
const hadPrompt = hasPromptHook(settings);
|
|
5574
6351
|
const hadSession = hasSessionHook(settings);
|
|
5575
6352
|
const hadStop = hasStopHook(settings);
|
|
5576
|
-
const withPrompt = applyPromptHook(applySpecHook(withSeat,
|
|
5577
|
-
const withSpec = applyStopHook(applySessionHook(withPrompt,
|
|
5578
|
-
const hadLine =
|
|
5579
|
-
writeSettings(
|
|
5580
|
-
log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(
|
|
6353
|
+
const withPrompt = applyPromptHook(applySpecHook(withSeat, o2.specEventCommand ?? sledsCommand("spec-event", o2.home, o2.env)), o2.promptEventCommand ?? sledsCommand("prompt-event", o2.home, o2.env));
|
|
6354
|
+
const withSpec = applyStopHook(applySessionHook(withPrompt, o2.sessionEventCommand ?? sledsCommand("session-event", o2.home, o2.env)), o2.stopEventCommand ?? sledsCommand("stop-event", o2.home, o2.env));
|
|
6355
|
+
const hadLine = o2.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
|
|
6356
|
+
writeSettings(o2.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o2.home, o2.env)).settings : withSpec);
|
|
6357
|
+
log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o2.home)}`);
|
|
5581
6358
|
if (hadLine)
|
|
5582
|
-
log(`refreshed statusLine in ${settingsPath(
|
|
5583
|
-
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(
|
|
5584
|
-
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(
|
|
5585
|
-
log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(
|
|
5586
|
-
log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(
|
|
6359
|
+
log(`refreshed statusLine in ${settingsPath(o2.home)}`);
|
|
6360
|
+
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o2.home)}`);
|
|
6361
|
+
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o2.home)}`);
|
|
6362
|
+
log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(o2.home)}`);
|
|
6363
|
+
log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(o2.home)}`);
|
|
5587
6364
|
return 0;
|
|
5588
6365
|
};
|
|
5589
6366
|
|
|
5590
6367
|
// src/commands/init.ts
|
|
5591
6368
|
var DEFAULT_PROXY_URL = "https://proxy.sleds.ai";
|
|
5592
|
-
var promptStdin = (question) => new Promise((
|
|
6369
|
+
var promptStdin = (question) => new Promise((resolve4) => {
|
|
5593
6370
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
5594
6371
|
rl.question(question, (answer) => {
|
|
5595
6372
|
rl.close();
|
|
5596
|
-
|
|
6373
|
+
resolve4(answer.trim());
|
|
5597
6374
|
});
|
|
5598
6375
|
});
|
|
5599
6376
|
async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch) {
|
|
@@ -5690,70 +6467,70 @@ async function probeProxy(url, fetchFn = fetch, opts = {}) {
|
|
|
5690
6467
|
}
|
|
5691
6468
|
return false;
|
|
5692
6469
|
}
|
|
5693
|
-
var resolveToken = async (
|
|
5694
|
-
const env =
|
|
5695
|
-
if (
|
|
5696
|
-
return { token:
|
|
6470
|
+
var resolveToken = async (o2) => {
|
|
6471
|
+
const env = o2.env ?? process.env;
|
|
6472
|
+
if (o2.token)
|
|
6473
|
+
return { token: o2.token.trim(), source: "flag" };
|
|
5697
6474
|
if (env.SLEDS_TOKEN)
|
|
5698
6475
|
return { token: env.SLEDS_TOKEN.trim(), source: "env" };
|
|
5699
|
-
const fromFile = readTokenFile(
|
|
6476
|
+
const fromFile = readTokenFile(o2.home);
|
|
5700
6477
|
if (fromFile)
|
|
5701
6478
|
return { token: fromFile, source: "file" };
|
|
5702
|
-
if (
|
|
6479
|
+
if (o2.nonInteractive)
|
|
5703
6480
|
return null;
|
|
5704
|
-
const url = normalizeProxyUrl(
|
|
6481
|
+
const url = normalizeProxyUrl(o2.url ?? env.SLEDS_PROXY_URL ?? readConfig(o2.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
5705
6482
|
if (!url)
|
|
5706
6483
|
return null;
|
|
5707
|
-
const log =
|
|
5708
|
-
const err =
|
|
5709
|
-
if (!
|
|
5710
|
-
const
|
|
6484
|
+
const log = o2.log ?? console.log;
|
|
6485
|
+
const err = o2.error ?? console.error;
|
|
6486
|
+
if (!o2.signup) {
|
|
6487
|
+
const r2 = await deviceFlow({
|
|
5711
6488
|
proxyUrl: url,
|
|
5712
6489
|
agent: "claude-code",
|
|
5713
|
-
host:
|
|
5714
|
-
fetchFn:
|
|
5715
|
-
openBrowser:
|
|
5716
|
-
noBrowser:
|
|
5717
|
-
sleep:
|
|
6490
|
+
host: o2.host,
|
|
6491
|
+
fetchFn: o2.fetchFn,
|
|
6492
|
+
openBrowser: o2.openBrowser ?? openInBrowser,
|
|
6493
|
+
noBrowser: o2.noBrowser,
|
|
6494
|
+
sleep: o2.sleep,
|
|
5718
6495
|
log
|
|
5719
6496
|
});
|
|
5720
|
-
if (!
|
|
5721
|
-
err(deviceFailureText(
|
|
6497
|
+
if (!r2.ok) {
|
|
6498
|
+
err(deviceFailureText(r2.reason));
|
|
5722
6499
|
return null;
|
|
5723
6500
|
}
|
|
5724
|
-
return { token:
|
|
6501
|
+
return { token: r2.approved.token, source: "device", approved: r2.approved };
|
|
5725
6502
|
}
|
|
5726
|
-
if (!
|
|
6503
|
+
if (!o2.prompt)
|
|
5727
6504
|
return null;
|
|
5728
|
-
let team =
|
|
5729
|
-
if (team === null && !
|
|
5730
|
-
const which = (await
|
|
6505
|
+
let team = o2.team?.trim() || null;
|
|
6506
|
+
if (team === null && !o2.email) {
|
|
6507
|
+
const which = (await o2.prompt("Set up SLEDS for (1) just you, or (2) your team? [1/2]: ")).trim();
|
|
5731
6508
|
if (which === "2") {
|
|
5732
|
-
team = (await
|
|
6509
|
+
team = (await o2.prompt("Team name (e.g. your company): ")).trim();
|
|
5733
6510
|
if (!team)
|
|
5734
6511
|
return null;
|
|
5735
6512
|
}
|
|
5736
6513
|
}
|
|
5737
|
-
const email = (
|
|
6514
|
+
const email = (o2.email ?? await o2.prompt(team ? "Your work email: " : "Your email: ")).trim();
|
|
5738
6515
|
if (!email.includes("@"))
|
|
5739
6516
|
return null;
|
|
5740
6517
|
if (team) {
|
|
5741
|
-
const
|
|
5742
|
-
const
|
|
5743
|
-
if (!
|
|
5744
|
-
err(signupFailureText(
|
|
6518
|
+
const credential2 = (await o2.prompt("Your organization's Anthropic API key, if you have one (sk-ant-…; press enter to skip — a Team plan without one still works, walls offer hold and hand-off): ")).trim();
|
|
6519
|
+
const r2 = await signupTeam(url, email, team, credential2 || null, o2.fetchFn);
|
|
6520
|
+
if (!r2.ok) {
|
|
6521
|
+
err(signupFailureText(r2));
|
|
5745
6522
|
return null;
|
|
5746
6523
|
}
|
|
5747
|
-
log(`Created ${team} with you as owner (${email}).${
|
|
5748
|
-
if (
|
|
6524
|
+
log(`Created ${team} with you as owner (${email}).${credential2 ? " Your platform key is stored encrypted as the org's upstream key." : " No platform key yet — add one on the dashboard (Admin → Keys) to unlock continue / overnight / switch at a wall."}`);
|
|
6525
|
+
if (r2.verifyPending)
|
|
5749
6526
|
log(`Check ${email} for a verification link and click it — inviting members unlocks then (an unverified team is removed after 48 h; your own seat works meanwhile).`);
|
|
5750
6527
|
log("Invite your team on the dashboard (Members → invite by email, Admin → Mint a seat); each person runs `sleds init --token …`.");
|
|
5751
|
-
return { token:
|
|
6528
|
+
return { token: r2.token, source: "signup" };
|
|
5752
6529
|
}
|
|
5753
|
-
const credential = (await
|
|
6530
|
+
const credential = (await o2.prompt("Your Anthropic API key (sk-ant-…, stored encrypted, spends only your account): ")).trim();
|
|
5754
6531
|
if (!credential)
|
|
5755
6532
|
return null;
|
|
5756
|
-
const r = await signupIndividual(url, email, credential,
|
|
6533
|
+
const r = await signupIndividual(url, email, credential, o2.fetchFn);
|
|
5757
6534
|
if (!r.ok) {
|
|
5758
6535
|
err(signupFailureText(r));
|
|
5759
6536
|
return null;
|
|
@@ -5761,66 +6538,66 @@ var resolveToken = async (o) => {
|
|
|
5761
6538
|
log(r.created ? `Created your individual seat (${email}). Your key is stored encrypted and is the only key this seat spends.` : `Re-issued your individual seat (${email}); the previous token is revoked.`);
|
|
5762
6539
|
return { token: r.token, source: "signup" };
|
|
5763
6540
|
};
|
|
5764
|
-
var init = async (
|
|
5765
|
-
const log =
|
|
5766
|
-
const error =
|
|
5767
|
-
const env =
|
|
5768
|
-
if (!hasClaudeCode(
|
|
5769
|
-
error(`Claude Code not found (${claudeDir(
|
|
6541
|
+
var init = async (o2) => {
|
|
6542
|
+
const log = o2.log ?? console.log;
|
|
6543
|
+
const error = o2.error ?? console.error;
|
|
6544
|
+
const env = o2.env ?? process.env;
|
|
6545
|
+
if (!hasClaudeCode(o2.home)) {
|
|
6546
|
+
error(`Claude Code not found (${claudeDir(o2.home)} does not exist). Install it, then re-run.`);
|
|
5770
6547
|
return 1;
|
|
5771
6548
|
}
|
|
5772
|
-
const resolved = await resolveToken(
|
|
6549
|
+
const resolved = await resolveToken(o2);
|
|
5773
6550
|
if (!resolved) {
|
|
5774
|
-
error(
|
|
6551
|
+
error(o2.signup ? "Signup did not complete. Nothing was changed." : "No seat token. Run `sleds init` without --non-interactive to sign in through the browser, or pass --token / set SLEDS_TOKEN.");
|
|
5775
6552
|
return 2;
|
|
5776
6553
|
}
|
|
5777
6554
|
if (!isSeatToken(resolved.token)) {
|
|
5778
6555
|
error("Invalid seat token: expected something like sled_xxxxxxxx.");
|
|
5779
6556
|
return 2;
|
|
5780
6557
|
}
|
|
5781
|
-
const url = normalizeProxyUrl(
|
|
6558
|
+
const url = normalizeProxyUrl(o2.url ?? env.SLEDS_PROXY_URL ?? readConfig(o2.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
5782
6559
|
if (!url) {
|
|
5783
|
-
error(`Invalid proxy URL: ${JSON.stringify(
|
|
6560
|
+
error(`Invalid proxy URL: ${JSON.stringify(o2.url ?? env.SLEDS_PROXY_URL ?? "")}. Expected https://proxy.sleds.ai.`);
|
|
5784
6561
|
return 2;
|
|
5785
6562
|
}
|
|
5786
|
-
if (!
|
|
6563
|
+
if (!o2.noProbe && !await probeProxy(url, o2.fetchFn)) {
|
|
5787
6564
|
error(`No SLEDS proxy answered at ${url}/healthz. Check the URL (or pass --no-probe to save it anyway).`);
|
|
5788
6565
|
return 2;
|
|
5789
6566
|
}
|
|
5790
6567
|
const wrote = [];
|
|
5791
|
-
const seatInfo = await fetchSeatInfo(url, resolved.token,
|
|
6568
|
+
const seatInfo = await fetchSeatInfo(url, resolved.token, o2.fetchFn);
|
|
5792
6569
|
const org = seatInfo.org;
|
|
5793
|
-
const wiring =
|
|
6570
|
+
const wiring = o2.proxyAlways ? "proxy-always" : o2.seatFirst ? "seat-first" : org?.mode ?? "proxy-always";
|
|
5794
6571
|
if (wiring === "proxy-always" && org && !org.capabilities.upstreamKey) {
|
|
5795
6572
|
error(`${org.name} has no platform key for the proxy to forward with; --proxy-always would break every request. Using seat-first.`);
|
|
5796
6573
|
}
|
|
5797
6574
|
const finalWiring = wiring === "proxy-always" && org && !org.capabilities.upstreamKey ? "seat-first" : wiring;
|
|
5798
|
-
if (backupSettings(
|
|
5799
|
-
wrote.push(`backed up settings to ${backupPath(
|
|
5800
|
-
const current = readSettings(
|
|
6575
|
+
if (backupSettings(o2.home))
|
|
6576
|
+
wrote.push(`backed up settings to ${backupPath(o2.home)}`);
|
|
6577
|
+
const current = readSettings(o2.home);
|
|
5801
6578
|
const wired = finalWiring === "proxy-always" ? applyProxy(current, { url, token: resolved.token }) : removeProxy(current);
|
|
5802
|
-
const
|
|
5803
|
-
writeSettings(
|
|
5804
|
-
wrote.push(finalWiring === "proxy-always" ? `set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(
|
|
5805
|
-
wrote.push(
|
|
6579
|
+
const statusLine2 = applyStatusLine(wired, o2.statusLineCommand ?? sledsCommand("statusline", o2.home, o2.env));
|
|
6580
|
+
writeSettings(o2.home, statusLine2.settings);
|
|
6581
|
+
wrote.push(finalWiring === "proxy-always" ? `set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o2.home)} (everything through the proxy)` : `left Claude Code on your seat (no ANTHROPIC_BASE_URL in ${settingsPath(o2.home)}); \`sleds on\` switches to the proxy at a wall`);
|
|
6582
|
+
wrote.push(statusLine2.prior === undefined ? `set statusLine in ${settingsPath(o2.home)}` : `set statusLine in ${settingsPath(o2.home)} (previous one kept for uninstall)`);
|
|
5806
6583
|
hooks2({
|
|
5807
|
-
home:
|
|
5808
|
-
env:
|
|
6584
|
+
home: o2.home,
|
|
6585
|
+
env: o2.env,
|
|
5809
6586
|
refreshStatusLine: false,
|
|
5810
|
-
seatEventCommand:
|
|
5811
|
-
specEventCommand:
|
|
5812
|
-
promptEventCommand:
|
|
5813
|
-
sessionEventCommand:
|
|
5814
|
-
stopEventCommand:
|
|
6587
|
+
seatEventCommand: o2.seatEventCommand,
|
|
6588
|
+
specEventCommand: o2.specEventCommand,
|
|
6589
|
+
promptEventCommand: o2.promptEventCommand,
|
|
6590
|
+
sessionEventCommand: o2.sessionEventCommand,
|
|
6591
|
+
stopEventCommand: o2.stopEventCommand,
|
|
5815
6592
|
log: (line) => wrote.push(line),
|
|
5816
6593
|
error
|
|
5817
6594
|
});
|
|
5818
6595
|
if (resolved.source !== "file") {
|
|
5819
|
-
writeTokenFile(
|
|
5820
|
-
wrote.push(`saved token to ${tokenPath(
|
|
6596
|
+
writeTokenFile(o2.home, resolved.token);
|
|
6597
|
+
wrote.push(`saved token to ${tokenPath(o2.home)} (0600)`);
|
|
5821
6598
|
}
|
|
5822
|
-
const prior = readConfig(
|
|
5823
|
-
writeConfig(
|
|
6599
|
+
const prior = readConfig(o2.home);
|
|
6600
|
+
writeConfig(o2.home, {
|
|
5824
6601
|
...prior,
|
|
5825
6602
|
token: resolved.token,
|
|
5826
6603
|
proxyUrl: url,
|
|
@@ -5828,14 +6605,14 @@ var init = async (o) => {
|
|
|
5828
6605
|
agentKind: "claude-code",
|
|
5829
6606
|
...seatInfo.email ? { email: seatInfo.email } : {},
|
|
5830
6607
|
...org ? { org } : {},
|
|
5831
|
-
...
|
|
6608
|
+
...statusLine2.prior !== undefined ? { priorStatusLine: statusLine2.prior } : {}
|
|
5832
6609
|
});
|
|
5833
|
-
wrote.push(`saved token and proxy URL to ${configPath(
|
|
5834
|
-
mkdirSync8(skillDir(
|
|
5835
|
-
writeFileSync8(skillPath(
|
|
5836
|
-
wrote.push(`installed skill at ${skillPath(
|
|
5837
|
-
if (
|
|
5838
|
-
const r =
|
|
6610
|
+
wrote.push(`saved token and proxy URL to ${configPath(o2.home)} (0600)`);
|
|
6611
|
+
mkdirSync8(skillDir(o2.home), { recursive: true });
|
|
6612
|
+
writeFileSync8(skillPath(o2.home), SKILL_MD);
|
|
6613
|
+
wrote.push(`installed skill at ${skillPath(o2.home)}`);
|
|
6614
|
+
if (o2.installMusher) {
|
|
6615
|
+
const r = o2.installMusher();
|
|
5839
6616
|
if (r)
|
|
5840
6617
|
wrote.push(r);
|
|
5841
6618
|
}
|
|
@@ -5872,9 +6649,9 @@ var init = async (o) => {
|
|
|
5872
6649
|
};
|
|
5873
6650
|
|
|
5874
6651
|
// src/commands/mcp.ts
|
|
5875
|
-
var mcp = async (
|
|
5876
|
-
const stdin =
|
|
5877
|
-
const write =
|
|
6652
|
+
var mcp = async (o2) => {
|
|
6653
|
+
const stdin = o2.stdin ?? process.stdin;
|
|
6654
|
+
const write = o2.write ?? ((line) => process.stdout.write(`${line}
|
|
5878
6655
|
`));
|
|
5879
6656
|
let buffer = "";
|
|
5880
6657
|
for await (const chunk of stdin) {
|
|
@@ -5884,7 +6661,7 @@ var mcp = async (o) => {
|
|
|
5884
6661
|
while (nl !== -1) {
|
|
5885
6662
|
const line = buffer.slice(0, nl);
|
|
5886
6663
|
buffer = buffer.slice(nl + 1);
|
|
5887
|
-
const out = await handleLine2(line, { home:
|
|
6664
|
+
const out = await handleLine2(line, { home: o2.home });
|
|
5888
6665
|
if (out !== null)
|
|
5889
6666
|
write(out);
|
|
5890
6667
|
nl = buffer.indexOf(`
|
|
@@ -5892,7 +6669,7 @@ var mcp = async (o) => {
|
|
|
5892
6669
|
}
|
|
5893
6670
|
}
|
|
5894
6671
|
if (buffer.trim().length > 0) {
|
|
5895
|
-
const out = await handleLine2(buffer, { home:
|
|
6672
|
+
const out = await handleLine2(buffer, { home: o2.home });
|
|
5896
6673
|
if (out !== null)
|
|
5897
6674
|
write(out);
|
|
5898
6675
|
}
|
|
@@ -5902,15 +6679,15 @@ var mcp = async (o) => {
|
|
|
5902
6679
|
// src/commands/meter.ts
|
|
5903
6680
|
init_config();
|
|
5904
6681
|
init_meter();
|
|
5905
|
-
var meter = async (
|
|
5906
|
-
const log =
|
|
5907
|
-
const now = (
|
|
5908
|
-
const src = meterSource(
|
|
6682
|
+
var meter = async (o2) => {
|
|
6683
|
+
const log = o2.log ?? console.log;
|
|
6684
|
+
const now = (o2.now ?? Date.now)();
|
|
6685
|
+
const src = meterSource(o2.home);
|
|
5909
6686
|
if (!src) {
|
|
5910
|
-
log(`SLEDS is not set up (${configPath(
|
|
6687
|
+
log(`SLEDS is not set up (${configPath(o2.home)}). Run \`sleds init\` first.`);
|
|
5911
6688
|
return 1;
|
|
5912
6689
|
}
|
|
5913
|
-
const m = await loadMeter({ home:
|
|
6690
|
+
const m = await loadMeter({ home: o2.home, now, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs });
|
|
5914
6691
|
if (!m) {
|
|
5915
6692
|
log(`sleds offline. ${src.proxyUrl} did not answer → \`sleds status\` for the proxy health.`);
|
|
5916
6693
|
return 2;
|
|
@@ -5930,9 +6707,9 @@ import { dirname as dirname5 } from "node:path";
|
|
|
5930
6707
|
var defaultWhich3 = (cmd) => {
|
|
5931
6708
|
const path = process.env.PATH ?? "";
|
|
5932
6709
|
for (const dir of path.split(":").filter(Boolean)) {
|
|
5933
|
-
const
|
|
5934
|
-
if (existsSync10(
|
|
5935
|
-
return
|
|
6710
|
+
const p2 = `${dir}/${cmd}`;
|
|
6711
|
+
if (existsSync10(p2))
|
|
6712
|
+
return p2;
|
|
5936
6713
|
}
|
|
5937
6714
|
return null;
|
|
5938
6715
|
};
|
|
@@ -5942,68 +6719,68 @@ var findClaude2 = (home, configured, which) => {
|
|
|
5942
6719
|
const found = which("claude");
|
|
5943
6720
|
if (found)
|
|
5944
6721
|
return found;
|
|
5945
|
-
for (const
|
|
6722
|
+
for (const p2 of [
|
|
5946
6723
|
`${home}/.local/bin/claude`,
|
|
5947
6724
|
"/opt/homebrew/bin/claude",
|
|
5948
6725
|
"/usr/local/bin/claude",
|
|
5949
6726
|
`${home}/.npm-global/bin/claude`
|
|
5950
6727
|
])
|
|
5951
|
-
if (existsSync10(
|
|
5952
|
-
return
|
|
6728
|
+
if (existsSync10(p2))
|
|
6729
|
+
return p2;
|
|
5953
6730
|
return null;
|
|
5954
6731
|
};
|
|
5955
|
-
var musherInstall = (
|
|
5956
|
-
const log =
|
|
5957
|
-
const error =
|
|
5958
|
-
const
|
|
5959
|
-
if (
|
|
5960
|
-
error(`sleds musher --install needs launchd (macOS). On ${
|
|
6732
|
+
var musherInstall = (o2) => {
|
|
6733
|
+
const log = o2.log ?? console.log;
|
|
6734
|
+
const error = o2.error ?? console.error;
|
|
6735
|
+
const platform2 = o2.platform ?? process.platform;
|
|
6736
|
+
if (platform2 !== "darwin") {
|
|
6737
|
+
error(`sleds musher --install needs launchd (macOS). On ${platform2}, run \`sleds musher tick\` from cron every 5 minutes.`);
|
|
5961
6738
|
return 1;
|
|
5962
6739
|
}
|
|
5963
|
-
const env =
|
|
5964
|
-
const plist = musherPlistPath(
|
|
6740
|
+
const env = o2.env ?? process.env;
|
|
6741
|
+
const plist = musherPlistPath(o2.home, env);
|
|
5965
6742
|
const label = musherLabel(env);
|
|
5966
|
-
const dir = configDir(
|
|
6743
|
+
const dir = configDir(o2.home, env);
|
|
5967
6744
|
mkdirSync9(dirname5(plist), { recursive: true });
|
|
5968
6745
|
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
5969
|
-
const execPath =
|
|
6746
|
+
const execPath = o2.sledsArgv[0] ?? process.execPath;
|
|
5970
6747
|
const agentEnv = {
|
|
5971
|
-
PATH: musherPath(
|
|
5972
|
-
HOME:
|
|
6748
|
+
PATH: musherPath(o2.home, execPath, env.PATH ?? ""),
|
|
6749
|
+
HOME: o2.home,
|
|
5973
6750
|
...env.SLEDS_HOME ? { SLEDS_HOME: env.SLEDS_HOME } : {},
|
|
5974
6751
|
...env.CLAUDE_CONFIG_DIR ? { CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR } : {}
|
|
5975
6752
|
};
|
|
5976
|
-
const exec =
|
|
6753
|
+
const exec = o2.exec ?? defaultExecSync;
|
|
5977
6754
|
if (existsSync10(plist))
|
|
5978
6755
|
exec("launchctl", ["unload", plist]);
|
|
5979
|
-
writeFileSync9(plist, buildMusherPlist({ sledsArgv:
|
|
6756
|
+
writeFileSync9(plist, buildMusherPlist({ sledsArgv: o2.sledsArgv, env: agentEnv, logPath: musherLogPath(dir), label }));
|
|
5980
6757
|
const r = exec("launchctl", ["load", plist]);
|
|
5981
6758
|
if (!r.ok) {
|
|
5982
6759
|
error(`launchctl load failed: ${r.stderr.trim() || "unknown"}`);
|
|
5983
6760
|
return 1;
|
|
5984
6761
|
}
|
|
5985
|
-
const cfg = readConfig(
|
|
5986
|
-
writeConfig(
|
|
5987
|
-
const claude = findClaude2(
|
|
6762
|
+
const cfg = readConfig(o2.home);
|
|
6763
|
+
writeConfig(o2.home, { ...cfg, musher: { ...cfg.musher ?? {}, agent: "launchd" } });
|
|
6764
|
+
const claude = findClaude2(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich3);
|
|
5988
6765
|
log(`musher installed: launchd agent ${label}, every 5 min · log ${musherLogPath(dir)}`);
|
|
5989
6766
|
if (!claude)
|
|
5990
6767
|
log("warn: `claude` not found on PATH — set it with `sleds musher --claude /path/to/claude`");
|
|
5991
6768
|
return 0;
|
|
5992
6769
|
};
|
|
5993
|
-
var musherUninstall = (
|
|
5994
|
-
const log =
|
|
5995
|
-
const env =
|
|
5996
|
-
const plist = musherPlistPath(
|
|
5997
|
-
const exec =
|
|
6770
|
+
var musherUninstall = (o2) => {
|
|
6771
|
+
const log = o2.log ?? console.log;
|
|
6772
|
+
const env = o2.env ?? process.env;
|
|
6773
|
+
const plist = musherPlistPath(o2.home, env);
|
|
6774
|
+
const exec = o2.exec ?? defaultExecSync;
|
|
5998
6775
|
if (existsSync10(plist)) {
|
|
5999
6776
|
exec("launchctl", ["unload", plist]);
|
|
6000
6777
|
rmSync2(plist, { force: true });
|
|
6001
6778
|
log(`musher removed: ${musherLabel(env)}`);
|
|
6002
6779
|
} else
|
|
6003
6780
|
log("musher was not installed.");
|
|
6004
|
-
const cfg = readConfig(
|
|
6781
|
+
const cfg = readConfig(o2.home);
|
|
6005
6782
|
if (cfg.musher)
|
|
6006
|
-
writeConfig(
|
|
6783
|
+
writeConfig(o2.home, { ...cfg, musher: { ...cfg.musher, agent: "none" } });
|
|
6007
6784
|
return 0;
|
|
6008
6785
|
};
|
|
6009
6786
|
var formatMusherStatus = (list, now) => {
|
|
@@ -6018,23 +6795,23 @@ var formatMusherStatus = (list, now) => {
|
|
|
6018
6795
|
lines.push("nothing deferred on this seat.");
|
|
6019
6796
|
return lines;
|
|
6020
6797
|
};
|
|
6021
|
-
var clientFor = (
|
|
6022
|
-
if (
|
|
6023
|
-
return
|
|
6024
|
-
const src = meterSource(
|
|
6025
|
-
return src ? musherClient({ ...src, fetchFn:
|
|
6026
|
-
};
|
|
6027
|
-
var musherStatus = async (
|
|
6028
|
-
const log =
|
|
6029
|
-
const now = (
|
|
6030
|
-
const env =
|
|
6031
|
-
const plist = musherPlistPath(
|
|
6032
|
-
const cfg = readConfig(
|
|
6798
|
+
var clientFor = (o2) => {
|
|
6799
|
+
if (o2.client)
|
|
6800
|
+
return o2.client;
|
|
6801
|
+
const src = meterSource(o2.home);
|
|
6802
|
+
return src ? musherClient({ ...src, fetchFn: o2.fetchFn }) : null;
|
|
6803
|
+
};
|
|
6804
|
+
var musherStatus = async (o2) => {
|
|
6805
|
+
const log = o2.log ?? console.log;
|
|
6806
|
+
const now = (o2.now ?? Date.now)();
|
|
6807
|
+
const env = o2.env ?? process.env;
|
|
6808
|
+
const plist = musherPlistPath(o2.home, env);
|
|
6809
|
+
const cfg = readConfig(o2.home);
|
|
6033
6810
|
log(existsSync10(plist) ? `agent ${musherLabel(env)} installed (${plist})` : "agent not installed — sleds musher --install");
|
|
6034
|
-
const claude = findClaude2(
|
|
6811
|
+
const claude = findClaude2(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich3);
|
|
6035
6812
|
log(`claude ${claude ?? "not found — sleds musher --claude /path/to/claude"}`);
|
|
6036
6813
|
log(`mode ${cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
6037
|
-
const client = clientFor(
|
|
6814
|
+
const client = clientFor(o2);
|
|
6038
6815
|
if (!client) {
|
|
6039
6816
|
log("seat not initialised (sleds init)");
|
|
6040
6817
|
return 1;
|
|
@@ -6048,9 +6825,9 @@ var musherStatus = async (o) => {
|
|
|
6048
6825
|
log(line);
|
|
6049
6826
|
return 0;
|
|
6050
6827
|
};
|
|
6051
|
-
var musherTick = async (
|
|
6052
|
-
const log =
|
|
6053
|
-
const client = clientFor(
|
|
6828
|
+
var musherTick = async (o2) => {
|
|
6829
|
+
const log = o2.log ?? console.log;
|
|
6830
|
+
const client = clientFor(o2);
|
|
6054
6831
|
if (!client) {
|
|
6055
6832
|
log("musher: seat not initialised; nothing to do");
|
|
6056
6833
|
return 1;
|
|
@@ -6062,19 +6839,19 @@ var musherTick = async (o) => {
|
|
|
6062
6839
|
}
|
|
6063
6840
|
if (list.due.length === 0)
|
|
6064
6841
|
return 0;
|
|
6065
|
-
const cfg = readConfig(
|
|
6066
|
-
const claude = findClaude2(
|
|
6067
|
-
const seat = readSeatState(
|
|
6842
|
+
const cfg = readConfig(o2.home);
|
|
6843
|
+
const claude = findClaude2(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich3) ?? "claude";
|
|
6844
|
+
const seat = readSeatState(o2.home);
|
|
6068
6845
|
for (const job of list.due) {
|
|
6069
6846
|
const out = await runJob({
|
|
6070
6847
|
job,
|
|
6071
6848
|
client,
|
|
6072
6849
|
fallbackSession: seat.session,
|
|
6073
|
-
host:
|
|
6850
|
+
host: o2.host ?? hostname3(),
|
|
6074
6851
|
claudePath: claude,
|
|
6075
6852
|
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
6076
6853
|
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
6077
|
-
exec:
|
|
6854
|
+
exec: o2.execCapture,
|
|
6078
6855
|
log
|
|
6079
6856
|
});
|
|
6080
6857
|
if (out.kind === "skipped")
|
|
@@ -6082,9 +6859,9 @@ var musherTick = async (o) => {
|
|
|
6082
6859
|
}
|
|
6083
6860
|
return 0;
|
|
6084
6861
|
};
|
|
6085
|
-
var musherConfigure = (
|
|
6086
|
-
const log =
|
|
6087
|
-
const cfg = readConfig(
|
|
6862
|
+
var musherConfigure = (o2, flags) => {
|
|
6863
|
+
const log = o2.log ?? console.log;
|
|
6864
|
+
const cfg = readConfig(o2.home);
|
|
6088
6865
|
const musher = { ...cfg.musher ?? {} };
|
|
6089
6866
|
if (flags.claude)
|
|
6090
6867
|
musher.claudePath = flags.claude;
|
|
@@ -6093,12 +6870,12 @@ var musherConfigure = (o, flags) => {
|
|
|
6093
6870
|
if (flags.timeout) {
|
|
6094
6871
|
const n = Number.parseInt(flags.timeout, 10);
|
|
6095
6872
|
if (!Number.isFinite(n) || n <= 0) {
|
|
6096
|
-
(
|
|
6873
|
+
(o2.error ?? console.error)("--timeout takes minutes, a positive number");
|
|
6097
6874
|
return 1;
|
|
6098
6875
|
}
|
|
6099
6876
|
musher.timeoutMinutes = n;
|
|
6100
6877
|
}
|
|
6101
|
-
writeConfig(
|
|
6878
|
+
writeConfig(o2.home, { ...cfg, musher });
|
|
6102
6879
|
log(`musher settings: claude ${musher.claudePath ?? "(PATH)"} · mode ${musher.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${musher.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
6103
6880
|
return 0;
|
|
6104
6881
|
};
|
|
@@ -6108,8 +6885,8 @@ var defaultExecSync = (cmd, args) => {
|
|
|
6108
6885
|
return { ok: false, stderr: r.error.message };
|
|
6109
6886
|
return { ok: r.status === 0, stderr: r.stderr ?? "" };
|
|
6110
6887
|
};
|
|
6111
|
-
var musher = async (
|
|
6112
|
-
const args = [...
|
|
6888
|
+
var musher = async (o2) => {
|
|
6889
|
+
const args = [...o2.argv];
|
|
6113
6890
|
const flags = {};
|
|
6114
6891
|
const positional = [];
|
|
6115
6892
|
for (let i = 0;i < args.length; i++) {
|
|
@@ -6125,16 +6902,16 @@ var musher = async (o) => {
|
|
|
6125
6902
|
positional.push(a);
|
|
6126
6903
|
}
|
|
6127
6904
|
if (flags.install)
|
|
6128
|
-
return musherInstall(
|
|
6905
|
+
return musherInstall(o2);
|
|
6129
6906
|
if (flags.uninstall)
|
|
6130
|
-
return musherUninstall(
|
|
6907
|
+
return musherUninstall(o2);
|
|
6131
6908
|
if (flags.claude || flags.mode || flags.timeout)
|
|
6132
|
-
return musherConfigure(
|
|
6909
|
+
return musherConfigure(o2, flags);
|
|
6133
6910
|
if (positional[0] === "tick")
|
|
6134
|
-
return musherTick(
|
|
6911
|
+
return musherTick(o2);
|
|
6135
6912
|
if (flags.status || positional.length === 0)
|
|
6136
|
-
return musherStatus(
|
|
6137
|
-
(
|
|
6913
|
+
return musherStatus(o2);
|
|
6914
|
+
(o2.error ?? console.error)(`sleds musher: unknown argument ${positional[0]}`);
|
|
6138
6915
|
return 1;
|
|
6139
6916
|
};
|
|
6140
6917
|
|
|
@@ -6182,12 +6959,12 @@ var systemdCalendar = (deadline) => `${deadline.getFullYear()}-${pad2(deadline.g
|
|
|
6182
6959
|
var scheduleRevert = (d, deadlineMs) => {
|
|
6183
6960
|
const deadline = new Date(deadlineMs);
|
|
6184
6961
|
if (d.platform === "darwin") {
|
|
6185
|
-
const
|
|
6962
|
+
const p2 = plistPath(d.home);
|
|
6186
6963
|
try {
|
|
6187
6964
|
mkdirSync10(join9(d.home, "Library", "LaunchAgents"), { recursive: true });
|
|
6188
|
-
d.exec("launchctl", ["unload",
|
|
6189
|
-
writeFileSync10(
|
|
6190
|
-
const r = d.exec("launchctl", ["load",
|
|
6965
|
+
d.exec("launchctl", ["unload", p2]);
|
|
6966
|
+
writeFileSync10(p2, buildLaunchdPlist(d.sledsArgv, deadline));
|
|
6967
|
+
const r = d.exec("launchctl", ["load", p2]);
|
|
6191
6968
|
if (r.ok)
|
|
6192
6969
|
return { kind: "launchd", detail: `launchd agent ${REVERT_LABEL}` };
|
|
6193
6970
|
return { kind: "manual", detail: `launchctl load failed: ${r.stderr.trim() || "unknown"}` };
|
|
@@ -6212,10 +6989,10 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
6212
6989
|
};
|
|
6213
6990
|
var cancelRevert = (d, kind) => {
|
|
6214
6991
|
if (kind === "launchd" || d.platform === "darwin") {
|
|
6215
|
-
const
|
|
6216
|
-
if (existsSync11(
|
|
6217
|
-
d.exec("launchctl", ["unload",
|
|
6218
|
-
rmSync3(
|
|
6992
|
+
const p2 = plistPath(d.home);
|
|
6993
|
+
if (existsSync11(p2)) {
|
|
6994
|
+
d.exec("launchctl", ["unload", p2]);
|
|
6995
|
+
rmSync3(p2, { force: true });
|
|
6219
6996
|
}
|
|
6220
6997
|
}
|
|
6221
6998
|
if (kind === "systemd" || d.platform === "linux") {
|
|
@@ -6225,15 +7002,15 @@ var cancelRevert = (d, kind) => {
|
|
|
6225
7002
|
|
|
6226
7003
|
// src/commands/off.ts
|
|
6227
7004
|
init_settings();
|
|
6228
|
-
var off = (
|
|
6229
|
-
const log =
|
|
6230
|
-
const cfg = readConfig(
|
|
6231
|
-
const settings = readSettings(
|
|
7005
|
+
var off = (o2) => {
|
|
7006
|
+
const log = o2.log ?? console.log;
|
|
7007
|
+
const cfg = readConfig(o2.home);
|
|
7008
|
+
const settings = readSettings(o2.home);
|
|
6232
7009
|
const wasOn = cfg.mode === "on" || isPointedAtProxy(settings);
|
|
6233
7010
|
if (wasOn)
|
|
6234
|
-
writeSettings(
|
|
6235
|
-
if (
|
|
6236
|
-
cancelRevert(
|
|
7011
|
+
writeSettings(o2.home, switchOff(settings, cfg.priorEnv ?? {}));
|
|
7012
|
+
if (o2.scheduler)
|
|
7013
|
+
cancelRevert(o2.scheduler, cfg.schedule);
|
|
6237
7014
|
const next = {
|
|
6238
7015
|
...cfg,
|
|
6239
7016
|
mode: "off",
|
|
@@ -6243,7 +7020,7 @@ var off = (o) => {
|
|
|
6243
7020
|
priorEnv: undefined,
|
|
6244
7021
|
schedule: undefined
|
|
6245
7022
|
};
|
|
6246
|
-
writeConfig(
|
|
7023
|
+
writeConfig(o2.home, next);
|
|
6247
7024
|
if (!wasOn) {
|
|
6248
7025
|
log("Sleds already off. Claude Code is on your seat.");
|
|
6249
7026
|
return 0;
|
|
@@ -6262,19 +7039,19 @@ init_settings();
|
|
|
6262
7039
|
var DEFAULT_PROXY_URL2 = "https://proxy.sleds.ai";
|
|
6263
7040
|
|
|
6264
7041
|
// src/commands/on.ts
|
|
6265
|
-
var on = (
|
|
6266
|
-
const log =
|
|
6267
|
-
const error =
|
|
6268
|
-
const env =
|
|
6269
|
-
const now = (
|
|
6270
|
-
if (!hasClaudeCode(
|
|
6271
|
-
error(`Claude Code not found (${claudeDir(
|
|
7042
|
+
var on = (o2) => {
|
|
7043
|
+
const log = o2.log ?? console.log;
|
|
7044
|
+
const error = o2.error ?? console.error;
|
|
7045
|
+
const env = o2.env ?? process.env;
|
|
7046
|
+
const now = (o2.now ?? Date.now)();
|
|
7047
|
+
if (!hasClaudeCode(o2.home)) {
|
|
7048
|
+
error(`Claude Code not found (${claudeDir(o2.home)} does not exist). Install it, then re-run.`);
|
|
6272
7049
|
return 1;
|
|
6273
7050
|
}
|
|
6274
|
-
const cfg = readConfig(
|
|
6275
|
-
const token = cfg.token ?? readTokenFile(
|
|
7051
|
+
const cfg = readConfig(o2.home);
|
|
7052
|
+
const token = cfg.token ?? readTokenFile(o2.home) ?? env.SLEDS_TOKEN?.trim();
|
|
6276
7053
|
if (!token) {
|
|
6277
|
-
error(`No seat token in ${configPath(
|
|
7054
|
+
error(`No seat token in ${configPath(o2.home)}. Run \`sleds init\` first.`);
|
|
6278
7055
|
return 2;
|
|
6279
7056
|
}
|
|
6280
7057
|
if (!isSeatToken(token)) {
|
|
@@ -6285,28 +7062,28 @@ var on = (o) => {
|
|
|
6285
7062
|
error(`${cfg.org.name} has no platform key, so there is no platform key to switch to. Hold until the reset, or hand off with /sled 5. An admin can add a key on the dashboard.`);
|
|
6286
7063
|
return 2;
|
|
6287
7064
|
}
|
|
6288
|
-
const url = (
|
|
7065
|
+
const url = (o2.url ?? cfg.proxyUrl ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL2).replace(/\/+$/, "");
|
|
6289
7066
|
let untilResetMs;
|
|
6290
|
-
if (
|
|
7067
|
+
if (o2.untilReset !== undefined) {
|
|
6291
7068
|
try {
|
|
6292
|
-
untilResetMs = parseUntilReset(
|
|
7069
|
+
untilResetMs = parseUntilReset(o2.untilReset, now);
|
|
6293
7070
|
} catch (err) {
|
|
6294
7071
|
error(err instanceof Error ? err.message : String(err));
|
|
6295
7072
|
return 2;
|
|
6296
7073
|
}
|
|
6297
7074
|
}
|
|
6298
|
-
const settings = readSettings(
|
|
7075
|
+
const settings = readSettings(o2.home);
|
|
6299
7076
|
const priorEnv = cfg.mode === "on" && cfg.priorEnv ? cfg.priorEnv : capturePriorEnv(settings);
|
|
6300
|
-
const backedUp = backupSettings(
|
|
6301
|
-
writeSettings(
|
|
7077
|
+
const backedUp = backupSettings(o2.home);
|
|
7078
|
+
writeSettings(o2.home, switchOn(settings, { url, token }));
|
|
6302
7079
|
let schedule;
|
|
6303
7080
|
let scheduleNote;
|
|
6304
|
-
if (untilResetMs !== undefined && !
|
|
7081
|
+
if (untilResetMs !== undefined && !o2.noSchedule && o2.scheduler && hasHomeOverride(env)) {
|
|
6305
7082
|
error("SLEDS_HOME / CLAUDE_CONFIG_DIR is set: the auto-revert timer would act on the default seat. Re-run with --no-schedule and run `sleds off` yourself at the reset.");
|
|
6306
7083
|
return 2;
|
|
6307
7084
|
}
|
|
6308
|
-
if (untilResetMs !== undefined && !
|
|
6309
|
-
const r = scheduleRevert(
|
|
7085
|
+
if (untilResetMs !== undefined && !o2.noSchedule && o2.scheduler) {
|
|
7086
|
+
const r = scheduleRevert(o2.scheduler, untilResetMs);
|
|
6310
7087
|
schedule = r.kind;
|
|
6311
7088
|
scheduleNote = r.kind === "manual" ? r.detail : undefined;
|
|
6312
7089
|
}
|
|
@@ -6322,19 +7099,19 @@ var on = (o) => {
|
|
|
6322
7099
|
next.untilReset = new Date(untilResetMs).toISOString();
|
|
6323
7100
|
else
|
|
6324
7101
|
next.untilReset = undefined;
|
|
6325
|
-
next.reason =
|
|
7102
|
+
next.reason = o2.reason?.trim() || undefined;
|
|
6326
7103
|
next.schedule = schedule;
|
|
6327
|
-
writeConfig(
|
|
7104
|
+
writeConfig(o2.home, next);
|
|
6328
7105
|
log(`Sleds on. Claude Code now runs on the platform key through ${url}. Restart claude to apply.`);
|
|
6329
7106
|
if (untilResetMs !== undefined) {
|
|
6330
|
-
log(`Reverts at ${formatLocal(untilResetMs,
|
|
7107
|
+
log(`Reverts at ${formatLocal(untilResetMs, o2.tz)} — run \`sleds off\` earlier if you want.`);
|
|
6331
7108
|
if (schedule === undefined || schedule === "manual") {
|
|
6332
7109
|
const why = scheduleNote ? ` (${scheduleNote})` : "";
|
|
6333
7110
|
log(`Auto-revert not scheduled${why}. Run \`sleds off\` after the reset.`);
|
|
6334
7111
|
}
|
|
6335
7112
|
}
|
|
6336
7113
|
if (backedUp)
|
|
6337
|
-
log(`Backed up ${settingsPath(
|
|
7114
|
+
log(`Backed up ${settingsPath(o2.home)} to ${backupPath(o2.home)}.`);
|
|
6338
7115
|
return 0;
|
|
6339
7116
|
};
|
|
6340
7117
|
|
|
@@ -6382,22 +7159,22 @@ var exitsLine = (hasUpstreamKey) => hasUpstreamKey ? "`/sled handoff <what you w
|
|
|
6382
7159
|
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.`;
|
|
6383
7160
|
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.`;
|
|
6384
7161
|
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.`;
|
|
6385
|
-
var promptEvent = async (
|
|
6386
|
-
const log =
|
|
6387
|
-
const error =
|
|
6388
|
-
const now = (
|
|
6389
|
-
if (
|
|
7162
|
+
var promptEvent = async (o2) => {
|
|
7163
|
+
const log = o2.log ?? console.log;
|
|
7164
|
+
const error = o2.error ?? console.error;
|
|
7165
|
+
const now = (o2.now ?? Date.now)();
|
|
7166
|
+
if (o2.stdin === undefined)
|
|
6390
7167
|
await readStdin(HOOK_STDIN_TIMEOUT_MS);
|
|
6391
7168
|
let state;
|
|
6392
7169
|
try {
|
|
6393
|
-
state = readSeatState(
|
|
7170
|
+
state = readSeatState(o2.home);
|
|
6394
7171
|
} catch {
|
|
6395
7172
|
return 0;
|
|
6396
7173
|
}
|
|
6397
7174
|
const pending = untold(state);
|
|
6398
7175
|
if (pending.length === 0)
|
|
6399
7176
|
return 0;
|
|
6400
|
-
const hasKey = readConfig(
|
|
7177
|
+
const hasKey = readConfig(o2.home).org?.capabilities.upstreamKey !== false;
|
|
6401
7178
|
const told = { ...state.told ?? {} };
|
|
6402
7179
|
const mark = (bar, stage, at) => {
|
|
6403
7180
|
told[bar] = { ...told[bar] ?? {}, [stage]: at };
|
|
@@ -6417,7 +7194,7 @@ var promptEvent = async (o) => {
|
|
|
6417
7194
|
lines.push(e.stage === "approaching" ? approachingContext(e.bar, pct, e.resetsAt, hasKey, now) : hitContext(e.bar, e.resetsAt, hasKey, now));
|
|
6418
7195
|
}
|
|
6419
7196
|
try {
|
|
6420
|
-
writeSeatState(
|
|
7197
|
+
writeSeatState(o2.home, { ...state, told });
|
|
6421
7198
|
} catch {}
|
|
6422
7199
|
if (code === 0 && lines.length > 0)
|
|
6423
7200
|
log(lines.join(`
|
|
@@ -6429,16 +7206,16 @@ var promptEvent = async (o) => {
|
|
|
6429
7206
|
init_config();
|
|
6430
7207
|
init_meter();
|
|
6431
7208
|
import { hostname as hostname4 } from "node:os";
|
|
6432
|
-
var run = async (
|
|
6433
|
-
const log =
|
|
6434
|
-
const error =
|
|
6435
|
-
if (!
|
|
7209
|
+
var run = async (o2) => {
|
|
7210
|
+
const log = o2.log ?? console.log;
|
|
7211
|
+
const error = o2.error ?? console.error;
|
|
7212
|
+
if (!o2.jobId) {
|
|
6436
7213
|
error("usage: sleds run <jobId>");
|
|
6437
7214
|
return 2;
|
|
6438
7215
|
}
|
|
6439
|
-
const client =
|
|
6440
|
-
const src = meterSource(
|
|
6441
|
-
return src ? musherClient({ ...src, fetchFn:
|
|
7216
|
+
const client = o2.client ?? (() => {
|
|
7217
|
+
const src = meterSource(o2.home);
|
|
7218
|
+
return src ? musherClient({ ...src, fetchFn: o2.fetchFn }) : null;
|
|
6442
7219
|
})();
|
|
6443
7220
|
if (!client) {
|
|
6444
7221
|
error("No seat on this machine. Run `sleds init` first.");
|
|
@@ -6449,13 +7226,13 @@ var run = async (o) => {
|
|
|
6449
7226
|
error("Could not reach the SLEDS proxy.");
|
|
6450
7227
|
return 1;
|
|
6451
7228
|
}
|
|
6452
|
-
const job = [...list.due, ...list.upcoming].find((j) => j.jobId ===
|
|
7229
|
+
const job = [...list.due, ...list.upcoming].find((j) => j.jobId === o2.jobId);
|
|
6453
7230
|
if (!job) {
|
|
6454
|
-
error(`Job ${
|
|
7231
|
+
error(`Job ${o2.jobId} is not a scheduled local job on this seat (sleds musher --status lists them).`);
|
|
6455
7232
|
return 1;
|
|
6456
7233
|
}
|
|
6457
|
-
const cfg = readConfig(
|
|
6458
|
-
const claude = findClaude(
|
|
7234
|
+
const cfg = readConfig(o2.home);
|
|
7235
|
+
const claude = findClaude(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich);
|
|
6459
7236
|
if (!claude) {
|
|
6460
7237
|
error("`claude` not found. Install Claude Code, or `sleds musher --claude /path/to/claude`.");
|
|
6461
7238
|
return 1;
|
|
@@ -6463,12 +7240,12 @@ var run = async (o) => {
|
|
|
6463
7240
|
const out = await runJob({
|
|
6464
7241
|
job,
|
|
6465
7242
|
client,
|
|
6466
|
-
fallbackSession: readSeatState(
|
|
6467
|
-
host:
|
|
7243
|
+
fallbackSession: readSeatState(o2.home).session,
|
|
7244
|
+
host: o2.host ?? hostname4(),
|
|
6468
7245
|
claudePath: claude,
|
|
6469
7246
|
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
6470
7247
|
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
6471
|
-
exec:
|
|
7248
|
+
exec: o2.execCapture,
|
|
6472
7249
|
log
|
|
6473
7250
|
});
|
|
6474
7251
|
if (out.kind === "skipped") {
|
|
@@ -6501,25 +7278,25 @@ var notificationType = (text) => {
|
|
|
6501
7278
|
return;
|
|
6502
7279
|
}
|
|
6503
7280
|
};
|
|
6504
|
-
var seatEvent = async (
|
|
6505
|
-
const log =
|
|
6506
|
-
const now = (
|
|
6507
|
-
const input =
|
|
7281
|
+
var seatEvent = async (o2) => {
|
|
7282
|
+
const log = o2.log ?? console.log;
|
|
7283
|
+
const now = (o2.now ?? Date.now)();
|
|
7284
|
+
const input = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS2);
|
|
6508
7285
|
const kind = notificationType(input);
|
|
6509
7286
|
try {
|
|
6510
|
-
const state = readSeatState(
|
|
7287
|
+
const state = readSeatState(o2.home);
|
|
6511
7288
|
const week = state.window?.sevenDay;
|
|
6512
|
-
const src = meterSource(
|
|
7289
|
+
const src = meterSource(o2.home);
|
|
6513
7290
|
if (src) {
|
|
6514
|
-
await postSeatWall({ ...src, fetchFn:
|
|
7291
|
+
await postSeatWall({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
|
|
6515
7292
|
...week ? { resetsAt: week.resetsAt, usedPct: week.usedPct } : {},
|
|
6516
7293
|
...kind ? { notification: kind } : {}
|
|
6517
7294
|
});
|
|
6518
7295
|
}
|
|
6519
7296
|
if (week && week.resetsAt > now)
|
|
6520
|
-
writeSeatState(
|
|
7297
|
+
writeSeatState(o2.home, { ...state, lastWallResetsAt: week.resetsAt });
|
|
6521
7298
|
} catch {}
|
|
6522
|
-
const line = seatWallLine(readConfig(
|
|
7299
|
+
const line = seatWallLine(readConfig(o2.home).org?.capabilities.upstreamKey !== false);
|
|
6523
7300
|
log(line);
|
|
6524
7301
|
return 0;
|
|
6525
7302
|
};
|
|
@@ -6539,11 +7316,11 @@ var isBun = () => typeof globalThis.Bun !== "undefined";
|
|
|
6539
7316
|
async function queryRows(dbPath) {
|
|
6540
7317
|
if (isBun()) {
|
|
6541
7318
|
const { Database } = await import("bun:sqlite");
|
|
6542
|
-
const
|
|
7319
|
+
const conn2 = new Database(dbPath, { readonly: true });
|
|
6543
7320
|
try {
|
|
6544
|
-
return
|
|
7321
|
+
return conn2.query(QUERY).all();
|
|
6545
7322
|
} finally {
|
|
6546
|
-
|
|
7323
|
+
conn2.close();
|
|
6547
7324
|
}
|
|
6548
7325
|
}
|
|
6549
7326
|
const mod = nodeSqlite();
|
|
@@ -6649,19 +7426,19 @@ function parseSeatSyncArgs(argv) {
|
|
|
6649
7426
|
return { error: `--profile does not apply to --browser ${source.browser}` };
|
|
6650
7427
|
return { source };
|
|
6651
7428
|
}
|
|
6652
|
-
function resolveProfileDir(localStateJson,
|
|
7429
|
+
function resolveProfileDir(localStateJson, profile3) {
|
|
6653
7430
|
let cache = {};
|
|
6654
7431
|
if (localStateJson) {
|
|
6655
7432
|
try {
|
|
6656
7433
|
const raw = JSON.parse(localStateJson);
|
|
6657
|
-
const
|
|
6658
|
-
if (
|
|
6659
|
-
cache =
|
|
7434
|
+
const p2 = isObject11(raw) && isObject11(raw.profile) ? raw.profile : null;
|
|
7435
|
+
if (p2 && isObject11(p2.info_cache))
|
|
7436
|
+
cache = p2.info_cache;
|
|
6660
7437
|
} catch {}
|
|
6661
7438
|
}
|
|
6662
|
-
if (Object.hasOwn(cache,
|
|
6663
|
-
return { dir:
|
|
6664
|
-
const want =
|
|
7439
|
+
if (Object.hasOwn(cache, profile3))
|
|
7440
|
+
return { dir: profile3 };
|
|
7441
|
+
const want = profile3.toLowerCase();
|
|
6665
7442
|
const hits = [];
|
|
6666
7443
|
for (const [dir, info] of Object.entries(cache)) {
|
|
6667
7444
|
if (!isObject11(info))
|
|
@@ -6677,7 +7454,7 @@ function resolveProfileDir(localStateJson, profile) {
|
|
|
6677
7454
|
return { dir: hits[0] ?? null };
|
|
6678
7455
|
if (hits.length > 1)
|
|
6679
7456
|
return { dir: null, ambiguous: hits };
|
|
6680
|
-
return { dir: Object.keys(cache).length === 0 ?
|
|
7457
|
+
return { dir: Object.keys(cache).length === 0 ? profile3 : null };
|
|
6681
7458
|
}
|
|
6682
7459
|
function cookieDbCandidates(profileDir) {
|
|
6683
7460
|
return [`${profileDir}/Network/Cookies`, `${profileDir}/Cookies`];
|
|
@@ -6847,11 +7624,11 @@ function findFile(root, name) {
|
|
|
6847
7624
|
continue;
|
|
6848
7625
|
}
|
|
6849
7626
|
for (const e of entries) {
|
|
6850
|
-
const
|
|
7627
|
+
const p2 = join11(dir, e.name);
|
|
6851
7628
|
if (e.isDirectory())
|
|
6852
|
-
stack.push(
|
|
7629
|
+
stack.push(p2);
|
|
6853
7630
|
else if (e.name === name)
|
|
6854
|
-
return
|
|
7631
|
+
return p2;
|
|
6855
7632
|
}
|
|
6856
7633
|
}
|
|
6857
7634
|
return null;
|
|
@@ -6862,10 +7639,10 @@ function findCookieDb(home, source) {
|
|
|
6862
7639
|
if (!existsSync12(root))
|
|
6863
7640
|
throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
|
|
6864
7641
|
if (!b.profiled) {
|
|
6865
|
-
const
|
|
6866
|
-
if (!
|
|
7642
|
+
const db2 = findFile(root, "Cookies");
|
|
7643
|
+
if (!db2)
|
|
6867
7644
|
throw new Error(`no cookie store under ${root}`);
|
|
6868
|
-
return
|
|
7645
|
+
return db2;
|
|
6869
7646
|
}
|
|
6870
7647
|
const localStatePath = join11(root, "Local State");
|
|
6871
7648
|
const localState = existsSync12(localStatePath) ? readFileSync10(localStatePath, "utf8") : null;
|
|
@@ -6875,7 +7652,7 @@ function findCookieDb(home, source) {
|
|
|
6875
7652
|
throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
|
|
6876
7653
|
if (!dir)
|
|
6877
7654
|
throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
|
|
6878
|
-
const db = cookieDbCandidates(join11(root, dir)).find((
|
|
7655
|
+
const db = cookieDbCandidates(join11(root, dir)).find((p2) => existsSync12(p2));
|
|
6879
7656
|
if (!db)
|
|
6880
7657
|
throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
|
|
6881
7658
|
return db;
|
|
@@ -6906,8 +7683,8 @@ var curlUsage = (org, cookie) => {
|
|
|
6906
7683
|
return { status: 0, body: "" };
|
|
6907
7684
|
}
|
|
6908
7685
|
};
|
|
6909
|
-
function fetchSeatUsage(jar, org, now,
|
|
6910
|
-
const { status, body } =
|
|
7686
|
+
function fetchSeatUsage(jar, org, now, run2) {
|
|
7687
|
+
const { status, body } = run2(org, cookieHeader(jar, org));
|
|
6911
7688
|
if (status !== 200)
|
|
6912
7689
|
return { usage: null, status };
|
|
6913
7690
|
try {
|
|
@@ -6968,24 +7745,24 @@ function uninstall(home, source, log) {
|
|
|
6968
7745
|
log(existsSync12(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
|
|
6969
7746
|
return 0;
|
|
6970
7747
|
}
|
|
6971
|
-
var seatSync = async (
|
|
6972
|
-
const log =
|
|
6973
|
-
const now = (
|
|
6974
|
-
const parsed = parseSeatSyncArgs(
|
|
7748
|
+
var seatSync = async (o2) => {
|
|
7749
|
+
const log = o2.log ?? console.log;
|
|
7750
|
+
const now = (o2.now ?? Date.now)();
|
|
7751
|
+
const parsed = parseSeatSyncArgs(o2.argv ?? process.argv.slice(3));
|
|
6975
7752
|
if ("error" in parsed) {
|
|
6976
7753
|
log(`seat-sync: ${parsed.error}`);
|
|
6977
7754
|
return 1;
|
|
6978
7755
|
}
|
|
6979
7756
|
const { source } = parsed;
|
|
6980
7757
|
if (source.uninstall)
|
|
6981
|
-
return uninstall(
|
|
7758
|
+
return uninstall(o2.home, source, log);
|
|
6982
7759
|
if (source.install)
|
|
6983
|
-
return install(
|
|
6984
|
-
if (platform2() !== "darwin" && !
|
|
7760
|
+
return install(o2.home, [process.execPath, process.argv[1] ?? ""], source, log);
|
|
7761
|
+
if (platform2() !== "darwin" && !o2.readCookieRows) {
|
|
6985
7762
|
log("seat-sync is macOS-only for now.");
|
|
6986
7763
|
return 2;
|
|
6987
7764
|
}
|
|
6988
|
-
const saved = meterSource(
|
|
7765
|
+
const saved = meterSource(o2.home);
|
|
6989
7766
|
const proxyUrl = saved?.proxyUrl;
|
|
6990
7767
|
const token = source.token ?? saved?.token;
|
|
6991
7768
|
if (!proxyUrl || !token) {
|
|
@@ -6994,13 +7771,13 @@ var seatSync = async (o) => {
|
|
|
6994
7771
|
}
|
|
6995
7772
|
let rows;
|
|
6996
7773
|
try {
|
|
6997
|
-
rows =
|
|
7774
|
+
rows = o2.readCookieRows ? o2.readCookieRows(o2.home, source) : await readCookieRowsBun(o2.home, source);
|
|
6998
7775
|
} catch (e) {
|
|
6999
7776
|
log(`seat-sync: ${e instanceof Error ? e.message : String(e)}`);
|
|
7000
7777
|
return 1;
|
|
7001
7778
|
}
|
|
7002
7779
|
const service = BROWSERS[source.browser].keychainService;
|
|
7003
|
-
const pw = (
|
|
7780
|
+
const pw = (o2.safeStorageKey ?? keychainKey)(service);
|
|
7004
7781
|
if (!pw) {
|
|
7005
7782
|
log(`seat-sync: could not read the "${service}" key from your Keychain.`);
|
|
7006
7783
|
return 1;
|
|
@@ -7020,16 +7797,16 @@ var seatSync = async (o) => {
|
|
|
7020
7797
|
log("seat-sync: no active org in the session cookie (pass --org <uuid>).");
|
|
7021
7798
|
return 1;
|
|
7022
7799
|
}
|
|
7023
|
-
const { usage, status } = fetchSeatUsage(jar, org, now,
|
|
7800
|
+
const { usage, status } = fetchSeatUsage(jar, org, now, o2.runUsage ?? curlUsage);
|
|
7024
7801
|
if (!usage) {
|
|
7025
7802
|
const why = status === 403 ? "claude.ai refused the read (403) — session expired, or this org is not readable by this session." : status === 0 ? "could not reach claude.ai." : `unexpected HTTP ${status}.`;
|
|
7026
7803
|
log(`seat-sync: usage read failed — ${why}`);
|
|
7027
7804
|
return 2;
|
|
7028
7805
|
}
|
|
7029
7806
|
try {
|
|
7030
|
-
writeSeatState(
|
|
7807
|
+
writeSeatState(o2.home, { ...readSeatState(o2.home), window: usageAsWindow(usage) });
|
|
7031
7808
|
} catch {}
|
|
7032
|
-
const posted = await postSeat(proxyUrl, token, seatSyncBody(usage),
|
|
7809
|
+
const posted = await postSeat(proxyUrl, token, seatSyncBody(usage), o2.fetchFn ?? fetch);
|
|
7033
7810
|
const wk = usage.sevenDay ? `wk ${Math.round(usage.sevenDay.usedPct)}%` : "wk —";
|
|
7034
7811
|
const models = Object.keys(usage.models).length;
|
|
7035
7812
|
const credits = usage.usedCreditsUsd !== undefined ? ` · $${usage.usedCreditsUsd.toFixed(2)}` : "";
|
|
@@ -7081,14 +7858,14 @@ function sessionContext(a) {
|
|
|
7081
7858
|
return lines.join(`
|
|
7082
7859
|
`);
|
|
7083
7860
|
}
|
|
7084
|
-
var sessionEvent = async (
|
|
7085
|
-
const log =
|
|
7086
|
-
const now = (
|
|
7087
|
-
const raw =
|
|
7861
|
+
var sessionEvent = async (o2) => {
|
|
7862
|
+
const log = o2.log ?? console.log;
|
|
7863
|
+
const now = (o2.now ?? Date.now)();
|
|
7864
|
+
const raw = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
|
|
7088
7865
|
const payload = parseSessionPayload(raw);
|
|
7089
7866
|
if (payload.session_id) {
|
|
7090
7867
|
try {
|
|
7091
|
-
writeSession(
|
|
7868
|
+
writeSession(o2.home, {
|
|
7092
7869
|
sessionId: payload.session_id,
|
|
7093
7870
|
startedAt: now,
|
|
7094
7871
|
...payload.source ? { source: payload.source } : {}
|
|
@@ -7098,13 +7875,13 @@ var sessionEvent = async (o) => {
|
|
|
7098
7875
|
let email;
|
|
7099
7876
|
let org;
|
|
7100
7877
|
try {
|
|
7101
|
-
const cfg = readConfig(
|
|
7878
|
+
const cfg = readConfig(o2.home);
|
|
7102
7879
|
email = cfg.email;
|
|
7103
7880
|
org = cfg.org?.slug;
|
|
7104
7881
|
} catch {}
|
|
7105
7882
|
let owed = [];
|
|
7106
7883
|
try {
|
|
7107
|
-
owed = readOwed(
|
|
7884
|
+
owed = readOwed(o2.home, now);
|
|
7108
7885
|
} catch {
|
|
7109
7886
|
owed = [];
|
|
7110
7887
|
}
|
|
@@ -7149,13 +7926,13 @@ function parseHookInput(raw) {
|
|
|
7149
7926
|
}
|
|
7150
7927
|
if (typeof parsed !== "object" || parsed === null)
|
|
7151
7928
|
return null;
|
|
7152
|
-
const
|
|
7153
|
-
const toolName = typeof
|
|
7154
|
-
const input = typeof
|
|
7929
|
+
const p2 = parsed;
|
|
7930
|
+
const toolName = typeof p2.tool_name === "string" ? p2.tool_name : "";
|
|
7931
|
+
const input = typeof p2.tool_input === "object" && p2.tool_input !== null ? p2.tool_input : {};
|
|
7155
7932
|
const filePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
7156
7933
|
if (!filePath)
|
|
7157
7934
|
return null;
|
|
7158
|
-
const cwd = typeof
|
|
7935
|
+
const cwd = typeof p2.cwd === "string" ? p2.cwd : "";
|
|
7159
7936
|
return { toolName, filePath, cwd };
|
|
7160
7937
|
}
|
|
7161
7938
|
function projectKey(filePath, cwd) {
|
|
@@ -7175,16 +7952,16 @@ function readFileOrNull(path) {
|
|
|
7175
7952
|
return null;
|
|
7176
7953
|
}
|
|
7177
7954
|
}
|
|
7178
|
-
var specEvent = async (
|
|
7179
|
-
const log =
|
|
7180
|
-
const raw =
|
|
7955
|
+
var specEvent = async (o2) => {
|
|
7956
|
+
const log = o2.log ?? console.log;
|
|
7957
|
+
const raw = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS3);
|
|
7181
7958
|
const input = raw ? parseHookInput(raw) : null;
|
|
7182
7959
|
if (!input || !isSpecPath(input.filePath))
|
|
7183
7960
|
return 0;
|
|
7184
|
-
const src = meterSource(
|
|
7961
|
+
const src = meterSource(o2.home);
|
|
7185
7962
|
if (!src)
|
|
7186
7963
|
return 0;
|
|
7187
|
-
const body = (
|
|
7964
|
+
const body = (o2.readFile ?? readFileOrNull)(input.filePath);
|
|
7188
7965
|
if (!body || !looksLikeSpec(body))
|
|
7189
7966
|
return 0;
|
|
7190
7967
|
const project = projectKey(input.filePath, input.cwd);
|
|
@@ -7194,7 +7971,7 @@ var specEvent = async (o) => {
|
|
|
7194
7971
|
repoHint: input.cwd ? basename2(input.cwd) : undefined
|
|
7195
7972
|
};
|
|
7196
7973
|
try {
|
|
7197
|
-
const res = await (
|
|
7974
|
+
const res = await (o2.fetchFn ?? fetch)(`${src.proxyUrl}/proxy/estimate`, {
|
|
7198
7975
|
method: "POST",
|
|
7199
7976
|
headers: { authorization: `Bearer ${src.token}`, "content-type": "application/json" },
|
|
7200
7977
|
body: JSON.stringify({ spec, project }),
|
|
@@ -7209,15 +7986,15 @@ var specEvent = async (o) => {
|
|
|
7209
7986
|
log(`sleds: ${project} sized at $${b.lowUsd.toFixed(2)}–${b.highUsd.toFixed(2)} · ${min(b.lowMinutes)}–${min(b.highMinutes)} min`);
|
|
7210
7987
|
if (typeof b.lowMinutes === "number" && typeof b.highMinutes === "number") {
|
|
7211
7988
|
try {
|
|
7212
|
-
writeSeatState(
|
|
7213
|
-
...readSeatState(
|
|
7989
|
+
writeSeatState(o2.home, {
|
|
7990
|
+
...readSeatState(o2.home),
|
|
7214
7991
|
estimate: {
|
|
7215
7992
|
project,
|
|
7216
7993
|
lowUsd: b.lowUsd,
|
|
7217
7994
|
highUsd: b.highUsd,
|
|
7218
7995
|
lowMin: b.lowMinutes,
|
|
7219
7996
|
highMin: b.highMinutes,
|
|
7220
|
-
at: (
|
|
7997
|
+
at: (o2.now ?? Date.now)()
|
|
7221
7998
|
}
|
|
7222
7999
|
});
|
|
7223
8000
|
} catch {}
|
|
@@ -7231,7 +8008,8 @@ var specEvent = async (o) => {
|
|
|
7231
8008
|
init_config();
|
|
7232
8009
|
init_settings();
|
|
7233
8010
|
import { existsSync as existsSync13 } from "node:fs";
|
|
7234
|
-
var
|
|
8011
|
+
var HEALTH_TIMEOUT_MS2 = 4000;
|
|
8012
|
+
var checkHealth2 = async (url, fetchFn = fetch, timeoutMs = HEALTH_TIMEOUT_MS2) => {
|
|
7235
8013
|
try {
|
|
7236
8014
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
7237
8015
|
return res.ok ? "ok" : "unreachable";
|
|
@@ -7239,7 +8017,7 @@ var checkHealth2 = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
|
7239
8017
|
return "unreachable";
|
|
7240
8018
|
}
|
|
7241
8019
|
};
|
|
7242
|
-
var fetchProxyStatus = async (url, fetchFn = fetch, timeoutMs =
|
|
8020
|
+
var fetchProxyStatus = async (url, fetchFn = fetch, timeoutMs = HEALTH_TIMEOUT_MS2) => {
|
|
7243
8021
|
try {
|
|
7244
8022
|
const res = await fetchFn(`${url}/status`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
7245
8023
|
if (!res.ok)
|
|
@@ -7300,16 +8078,16 @@ var formatStatus = (s) => {
|
|
|
7300
8078
|
}
|
|
7301
8079
|
return lines;
|
|
7302
8080
|
};
|
|
7303
|
-
var status = async (
|
|
7304
|
-
const log =
|
|
7305
|
-
const nowMs = (
|
|
7306
|
-
const cfg = readConfig(
|
|
7307
|
-
const settings = existsSync13(settingsPath(
|
|
8081
|
+
var status = async (o2) => {
|
|
8082
|
+
const log = o2.log ?? console.log;
|
|
8083
|
+
const nowMs = (o2.now ?? Date.now)();
|
|
8084
|
+
const cfg = readConfig(o2.home);
|
|
8085
|
+
const settings = existsSync13(settingsPath(o2.home)) ? readSettings(o2.home) : {};
|
|
7308
8086
|
const inSettings = readProxy(settings);
|
|
7309
8087
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
7310
8088
|
const token = inSettings?.token ?? cfg.token;
|
|
7311
8089
|
if (!proxyUrl && !token && cfg.mode === undefined) {
|
|
7312
|
-
log(`SLEDS is not set up (${configPath(
|
|
8090
|
+
log(`SLEDS is not set up (${configPath(o2.home)} and ${settingsPath(o2.home)}).`);
|
|
7313
8091
|
log("Run `sleds init` first.");
|
|
7314
8092
|
return 1;
|
|
7315
8093
|
}
|
|
@@ -7317,8 +8095,8 @@ var status = async (o) => {
|
|
|
7317
8095
|
mode: cfg.mode ?? "off",
|
|
7318
8096
|
pointed: inSettings !== null,
|
|
7319
8097
|
nowMs,
|
|
7320
|
-
settingsFile: settingsPath(
|
|
7321
|
-
configFile: configPath(
|
|
8098
|
+
settingsFile: settingsPath(o2.home),
|
|
8099
|
+
configFile: configPath(o2.home)
|
|
7322
8100
|
};
|
|
7323
8101
|
if (proxyUrl)
|
|
7324
8102
|
info.proxyUrl = proxyUrl;
|
|
@@ -7338,8 +8116,8 @@ var status = async (o) => {
|
|
|
7338
8116
|
};
|
|
7339
8117
|
if (cfg.reason)
|
|
7340
8118
|
info.reason = cfg.reason;
|
|
7341
|
-
if (
|
|
7342
|
-
info.tz =
|
|
8119
|
+
if (o2.tz)
|
|
8120
|
+
info.tz = o2.tz;
|
|
7343
8121
|
const since = cfg.since ? Date.parse(cfg.since) : Number.NaN;
|
|
7344
8122
|
if (!Number.isNaN(since))
|
|
7345
8123
|
info.sinceMs = since;
|
|
@@ -7348,8 +8126,8 @@ var status = async (o) => {
|
|
|
7348
8126
|
info.untilResetMs = until;
|
|
7349
8127
|
if (proxyUrl) {
|
|
7350
8128
|
const [health, proxy] = await Promise.all([
|
|
7351
|
-
checkHealth2(proxyUrl,
|
|
7352
|
-
fetchProxyStatus(proxyUrl,
|
|
8129
|
+
checkHealth2(proxyUrl, o2.fetchFn, o2.timeoutMs),
|
|
8130
|
+
fetchProxyStatus(proxyUrl, o2.fetchFn, o2.timeoutMs)
|
|
7353
8131
|
]);
|
|
7354
8132
|
info.health = health;
|
|
7355
8133
|
info.proxy = proxy;
|
|
@@ -7362,62 +8140,62 @@ var status = async (o) => {
|
|
|
7362
8140
|
// src/commands/statusline.ts
|
|
7363
8141
|
init_config();
|
|
7364
8142
|
init_meter();
|
|
7365
|
-
var statusline = async (
|
|
7366
|
-
const log =
|
|
7367
|
-
const now = (
|
|
7368
|
-
const input =
|
|
8143
|
+
var statusline = async (o2) => {
|
|
8144
|
+
const log = o2.log ?? console.log;
|
|
8145
|
+
const now = (o2.now ?? Date.now)();
|
|
8146
|
+
const input = o2.stdin ?? await readStdin(STDIN_TIMEOUT_MS);
|
|
7369
8147
|
const window = seatWindowFromStdin(input, now);
|
|
7370
8148
|
const session = seatSessionFromStdin(input, now);
|
|
7371
8149
|
let posts = [];
|
|
7372
8150
|
let events = [];
|
|
7373
8151
|
try {
|
|
7374
8152
|
if (window)
|
|
7375
|
-
({ posts, events } = rememberSeat(
|
|
8153
|
+
({ posts, events } = rememberSeat(o2, window, session, now, input));
|
|
7376
8154
|
else {
|
|
7377
|
-
const st = readSeatState(
|
|
7378
|
-
const credits = creditsEvent(
|
|
8155
|
+
const st = readSeatState(o2.home);
|
|
8156
|
+
const credits = creditsEvent(o2, st, input, now);
|
|
7379
8157
|
if (session || credits.scan) {
|
|
7380
8158
|
const next = {
|
|
7381
8159
|
...st,
|
|
7382
8160
|
...session ? { session } : {},
|
|
7383
8161
|
...credits.scan ? { transcriptScan: credits.scan } : {}
|
|
7384
8162
|
};
|
|
7385
|
-
writeSeatState(
|
|
8163
|
+
writeSeatState(o2.home, credits.event ? markWarned(next, [credits.event]) : next);
|
|
7386
8164
|
}
|
|
7387
8165
|
if (credits.event) {
|
|
7388
8166
|
events = [credits.event];
|
|
7389
|
-
posts = announce(
|
|
8167
|
+
posts = announce(o2, events);
|
|
7390
8168
|
}
|
|
7391
8169
|
}
|
|
7392
8170
|
} catch {}
|
|
7393
8171
|
const seatFragment = formatSeatFragment(window, now);
|
|
7394
8172
|
let line;
|
|
7395
8173
|
try {
|
|
7396
|
-
const
|
|
7397
|
-
home:
|
|
8174
|
+
const meter2 = await loadMeter({
|
|
8175
|
+
home: o2.home,
|
|
7398
8176
|
now,
|
|
7399
|
-
fetchFn:
|
|
7400
|
-
timeoutMs:
|
|
8177
|
+
fetchFn: o2.fetchFn,
|
|
8178
|
+
timeoutMs: o2.timeoutMs
|
|
7401
8179
|
});
|
|
7402
|
-
line = formatMeterLine(
|
|
8180
|
+
line = formatMeterLine(meter2, now, seatFragment);
|
|
7403
8181
|
} catch {
|
|
7404
8182
|
line = formatMeterLine(null, now, seatFragment);
|
|
7405
8183
|
}
|
|
7406
8184
|
log(line);
|
|
7407
|
-
posts.push(speakToCockpit(
|
|
8185
|
+
posts.push(speakToCockpit(o2, window, events, now));
|
|
7408
8186
|
await Promise.allSettled(posts);
|
|
7409
8187
|
return 0;
|
|
7410
8188
|
};
|
|
7411
|
-
var speakToCockpit = async (
|
|
7412
|
-
const cockpit = detectCockpit(
|
|
8189
|
+
var speakToCockpit = async (o2, window, events, now) => {
|
|
8190
|
+
const cockpit = detectCockpit(o2.env ?? process.env);
|
|
7413
8191
|
if (!cockpit)
|
|
7414
8192
|
return;
|
|
7415
8193
|
try {
|
|
7416
|
-
const st = readSeatState(
|
|
8194
|
+
const st = readSeatState(o2.home);
|
|
7417
8195
|
let feed;
|
|
7418
|
-
const src = meterSource(
|
|
8196
|
+
const src = meterSource(o2.home);
|
|
7419
8197
|
if (src && now - (st.cockpit?.feed?.at ?? 0) >= FEED_MIN_INTERVAL_MS) {
|
|
7420
|
-
const got = await fetchFeed({ ...src, email: readConfig(
|
|
8198
|
+
const got = await fetchFeed({ ...src, email: readConfig(o2.home).email, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs }, now);
|
|
7421
8199
|
if (got)
|
|
7422
8200
|
feed = got;
|
|
7423
8201
|
}
|
|
@@ -7428,32 +8206,32 @@ var speakToCockpit = async (o, window, events, now) => {
|
|
|
7428
8206
|
const changed = r.argv.length > 0 || feed !== undefined;
|
|
7429
8207
|
if (!changed)
|
|
7430
8208
|
return;
|
|
7431
|
-
writeSeatState(
|
|
8209
|
+
writeSeatState(o2.home, { ...readSeatState(o2.home), cockpit: r.state });
|
|
7432
8210
|
if (r.argv.length)
|
|
7433
|
-
await runCockpit(cockpit, r.argv, { spawn:
|
|
8211
|
+
await runCockpit(cockpit, r.argv, { spawn: o2.spawn });
|
|
7434
8212
|
} catch {}
|
|
7435
8213
|
};
|
|
7436
|
-
var rememberSeat = (
|
|
7437
|
-
const prior = readSeatState(
|
|
8214
|
+
var rememberSeat = (o2, window, session, now, input) => {
|
|
8215
|
+
const prior = readSeatState(o2.home);
|
|
7438
8216
|
let state = { ...prior, window, ...session ? { session } : {} };
|
|
7439
|
-
const src = meterSource(
|
|
8217
|
+
const src = meterSource(o2.home);
|
|
7440
8218
|
const posts = [];
|
|
7441
8219
|
if (src && shouldPostSeat(prior, window, now)) {
|
|
7442
8220
|
state = markPosted(state, window, now);
|
|
7443
|
-
posts.push(postSeatWindow({ ...src, fetchFn:
|
|
8221
|
+
posts.push(postSeatWindow({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, window));
|
|
7444
8222
|
}
|
|
7445
8223
|
const events = seatWallEvents(prior, window);
|
|
7446
|
-
const credits = creditsEvent(
|
|
8224
|
+
const credits = creditsEvent(o2, prior, input, now);
|
|
7447
8225
|
if (credits.scan)
|
|
7448
8226
|
state = { ...state, transcriptScan: credits.scan };
|
|
7449
8227
|
if (credits.event)
|
|
7450
8228
|
events.push(credits.event);
|
|
7451
8229
|
state = markWarned(state, events);
|
|
7452
|
-
posts.push(...announce(
|
|
7453
|
-
writeSeatState(
|
|
8230
|
+
posts.push(...announce(o2, events));
|
|
8231
|
+
writeSeatState(o2.home, state);
|
|
7454
8232
|
return { posts, events };
|
|
7455
8233
|
};
|
|
7456
|
-
var creditsEvent = (
|
|
8234
|
+
var creditsEvent = (o2, prior, input, now) => {
|
|
7457
8235
|
const path = transcriptPathFromStdin(input);
|
|
7458
8236
|
if (!path)
|
|
7459
8237
|
return { event: null, scan: undefined };
|
|
@@ -7463,13 +8241,13 @@ var creditsEvent = (o, prior, input, now) => {
|
|
|
7463
8241
|
return { event: null, scan: next };
|
|
7464
8242
|
return { event: { bar: "credits", stage: "hit", usedPct: 100, resetsAt: now }, scan: next };
|
|
7465
8243
|
};
|
|
7466
|
-
var announce = (
|
|
8244
|
+
var announce = (o2, events) => {
|
|
7467
8245
|
if (events.length === 0)
|
|
7468
8246
|
return [];
|
|
7469
|
-
const src = meterSource(
|
|
8247
|
+
const src = meterSource(o2.home);
|
|
7470
8248
|
if (!src)
|
|
7471
8249
|
return [];
|
|
7472
|
-
return events.map((e) => postSeatWall({ ...src, fetchFn:
|
|
8250
|
+
return events.map((e) => postSeatWall({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
|
|
7473
8251
|
bar: e.bar,
|
|
7474
8252
|
stage: e.stage,
|
|
7475
8253
|
usedPct: e.usedPct,
|
|
@@ -7491,7 +8269,7 @@ function sumTranscript(text) {
|
|
|
7491
8269
|
let sub = 0;
|
|
7492
8270
|
let messages = 0;
|
|
7493
8271
|
const byModel = new Map;
|
|
7494
|
-
const
|
|
8272
|
+
const agents2 = new Set;
|
|
7495
8273
|
let anySidechain = false;
|
|
7496
8274
|
for (const line of text.split(`
|
|
7497
8275
|
`)) {
|
|
@@ -7521,14 +8299,14 @@ function sumTranscript(text) {
|
|
|
7521
8299
|
const cacheRead = n(u.cache_read_input_tokens);
|
|
7522
8300
|
const cacheWrite = n(u.cache_creation_input_tokens);
|
|
7523
8301
|
messages++;
|
|
7524
|
-
const
|
|
7525
|
-
if (
|
|
7526
|
-
byModel.set(
|
|
8302
|
+
const model2 = typeof m.model === "string" ? m.model : undefined;
|
|
8303
|
+
if (model2)
|
|
8304
|
+
byModel.set(model2, (byModel.get(model2) ?? 0) + output);
|
|
7527
8305
|
if (j.isSidechain === true) {
|
|
7528
8306
|
anySidechain = true;
|
|
7529
8307
|
sub += input + output + cacheRead + cacheWrite;
|
|
7530
8308
|
if (typeof j.agentId === "string")
|
|
7531
|
-
|
|
8309
|
+
agents2.add(j.agentId);
|
|
7532
8310
|
} else {
|
|
7533
8311
|
turn.input += input;
|
|
7534
8312
|
turn.output += output;
|
|
@@ -7546,7 +8324,7 @@ function sumTranscript(text) {
|
|
|
7546
8324
|
subagentTokens: sub,
|
|
7547
8325
|
messages,
|
|
7548
8326
|
...model ? { model } : {},
|
|
7549
|
-
subagentRuns:
|
|
8327
|
+
subagentRuns: agents2.size > 0 ? agents2.size : anySidechain ? 1 : 0
|
|
7550
8328
|
};
|
|
7551
8329
|
}
|
|
7552
8330
|
function sessionBody(sessionId, sum, a) {
|
|
@@ -7568,15 +8346,15 @@ function sessionBody(sessionId, sum, a) {
|
|
|
7568
8346
|
|
|
7569
8347
|
// src/commands/stopEvent.ts
|
|
7570
8348
|
var SESSION_POST_TIMEOUT_MS = 2500;
|
|
7571
|
-
async function postTranscript(
|
|
8349
|
+
async function postTranscript(o2, payload, now) {
|
|
7572
8350
|
const sessionId = payload.session_id?.trim();
|
|
7573
8351
|
const path = payload.transcript_path?.trim();
|
|
7574
8352
|
if (!sessionId || !path)
|
|
7575
8353
|
return false;
|
|
7576
|
-
const src = meterSource(
|
|
8354
|
+
const src = meterSource(o2.home);
|
|
7577
8355
|
if (!src)
|
|
7578
8356
|
return false;
|
|
7579
|
-
const read =
|
|
8357
|
+
const read = o2.readFile ?? ((p2) => existsSync14(p2) ? readFileSync12(p2, "utf8") : null);
|
|
7580
8358
|
let text;
|
|
7581
8359
|
try {
|
|
7582
8360
|
text = read(path);
|
|
@@ -7588,7 +8366,7 @@ async function postTranscript(o, payload, now) {
|
|
|
7588
8366
|
const sum = sumTranscript(text);
|
|
7589
8367
|
if (sum.messages === 0)
|
|
7590
8368
|
return false;
|
|
7591
|
-
const fetchFn =
|
|
8369
|
+
const fetchFn = o2.fetchFn ?? fetch;
|
|
7592
8370
|
try {
|
|
7593
8371
|
const res = await fetchFn(`${src.proxyUrl.replace(/\/+$/, "")}/proxy/sessions`, {
|
|
7594
8372
|
method: "POST",
|
|
@@ -7616,24 +8394,24 @@ function stopReason(rows) {
|
|
|
7616
8394
|
function decideStop(a) {
|
|
7617
8395
|
if (a.stopHookActive || !a.sessionId)
|
|
7618
8396
|
return { block: false };
|
|
7619
|
-
const
|
|
7620
|
-
if (
|
|
8397
|
+
const due2 = owedBySession(a.rows, a.sessionId);
|
|
8398
|
+
if (due2.length === 0)
|
|
7621
8399
|
return { block: false };
|
|
7622
8400
|
return {
|
|
7623
8401
|
block: true,
|
|
7624
|
-
reason: stopReason(
|
|
7625
|
-
rows: markAsked(a.rows,
|
|
8402
|
+
reason: stopReason(due2),
|
|
8403
|
+
rows: markAsked(a.rows, due2.map((r) => r.key), a.now)
|
|
7626
8404
|
};
|
|
7627
8405
|
}
|
|
7628
|
-
var stopEvent = async (
|
|
7629
|
-
const log =
|
|
7630
|
-
const now = (
|
|
7631
|
-
const raw =
|
|
8406
|
+
var stopEvent = async (o2) => {
|
|
8407
|
+
const log = o2.log ?? console.log;
|
|
8408
|
+
const now = (o2.now ?? Date.now)();
|
|
8409
|
+
const raw = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
|
|
7632
8410
|
const payload = parseStopPayload(raw);
|
|
7633
|
-
await postTranscript(
|
|
8411
|
+
await postTranscript(o2, payload, now);
|
|
7634
8412
|
let rows;
|
|
7635
8413
|
try {
|
|
7636
|
-
rows = readOwed(
|
|
8414
|
+
rows = readOwed(o2.home, now);
|
|
7637
8415
|
} catch {
|
|
7638
8416
|
return 0;
|
|
7639
8417
|
}
|
|
@@ -7646,7 +8424,7 @@ var stopEvent = async (o) => {
|
|
|
7646
8424
|
if (!d.block)
|
|
7647
8425
|
return 0;
|
|
7648
8426
|
try {
|
|
7649
|
-
writeOwed(
|
|
8427
|
+
writeOwed(o2.home, d.rows);
|
|
7650
8428
|
} catch {}
|
|
7651
8429
|
log(JSON.stringify({ decision: "block", reason: d.reason }));
|
|
7652
8430
|
return 0;
|
|
@@ -7663,7 +8441,7 @@ import { dirname as dirname6, join as join12 } from "node:path";
|
|
|
7663
8441
|
var PROVIDER_ID2 = "sleds";
|
|
7664
8442
|
var codexDir2 = (home) => join12(home, ".codex");
|
|
7665
8443
|
var codexConfigPath2 = (home) => join12(codexDir2(home), "config.toml");
|
|
7666
|
-
function removeCodexProvider2(toml,
|
|
8444
|
+
function removeCodexProvider2(toml, o2 = {}) {
|
|
7667
8445
|
const lines = toml.split(`
|
|
7668
8446
|
`);
|
|
7669
8447
|
const out = [];
|
|
@@ -7676,7 +8454,7 @@ function removeCodexProvider2(toml, o = {}) {
|
|
|
7676
8454
|
}
|
|
7677
8455
|
let text = out.join(`
|
|
7678
8456
|
`);
|
|
7679
|
-
if (!
|
|
8457
|
+
if (!o2.keepPrior) {
|
|
7680
8458
|
const prior = text.match(/^# sleds-prior-model-provider = "([^"]*)"\s*\n/m);
|
|
7681
8459
|
if (prior) {
|
|
7682
8460
|
text = text.replace(prior[0], "");
|
|
@@ -7708,72 +8486,72 @@ init_settings();
|
|
|
7708
8486
|
var defaultUnload = (plist) => {
|
|
7709
8487
|
spawnSync5("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
7710
8488
|
};
|
|
7711
|
-
var uninstall2 = (
|
|
7712
|
-
const codexLine = uninstallCodex(
|
|
8489
|
+
var uninstall2 = (o2) => {
|
|
8490
|
+
const codexLine = uninstallCodex(o2.home);
|
|
7713
8491
|
if (codexLine)
|
|
7714
|
-
(
|
|
7715
|
-
const log =
|
|
8492
|
+
(o2.log ?? console.log)(` ${codexLine}`);
|
|
8493
|
+
const log = o2.log ?? console.log;
|
|
7716
8494
|
const removed = [];
|
|
7717
|
-
if (existsSync16(settingsPath(
|
|
7718
|
-
let settings = readSettings(
|
|
8495
|
+
if (existsSync16(settingsPath(o2.home))) {
|
|
8496
|
+
let settings = readSettings(o2.home);
|
|
7719
8497
|
let changed = false;
|
|
7720
8498
|
if (hasEitherKey(settings)) {
|
|
7721
8499
|
settings = removeProxy(settings);
|
|
7722
8500
|
changed = true;
|
|
7723
|
-
removed.push(`env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN from ${settingsPath(
|
|
8501
|
+
removed.push(`env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN from ${settingsPath(o2.home)}`);
|
|
7724
8502
|
}
|
|
7725
8503
|
if (isSledsStatusLine(settings[STATUS_LINE_KEY])) {
|
|
7726
|
-
const cfg = readConfig(
|
|
8504
|
+
const cfg = readConfig(o2.home);
|
|
7727
8505
|
settings = removeStatusLine(settings, cfg.priorStatusLine);
|
|
7728
8506
|
changed = true;
|
|
7729
|
-
removed.push(cfg.priorStatusLine === undefined ? `statusLine from ${settingsPath(
|
|
8507
|
+
removed.push(cfg.priorStatusLine === undefined ? `statusLine from ${settingsPath(o2.home)}` : `statusLine from ${settingsPath(o2.home)} (previous one restored)`);
|
|
7730
8508
|
if (cfg.priorStatusLine !== undefined) {
|
|
7731
8509
|
const { priorStatusLine: _restored, ...rest } = cfg;
|
|
7732
|
-
writeConfig(
|
|
8510
|
+
writeConfig(o2.home, rest);
|
|
7733
8511
|
}
|
|
7734
8512
|
}
|
|
7735
8513
|
if (hasSeatHook(settings)) {
|
|
7736
8514
|
settings = removeSeatHook(settings);
|
|
7737
8515
|
changed = true;
|
|
7738
|
-
removed.push(`hooks.Notification seat-event entry from ${settingsPath(
|
|
8516
|
+
removed.push(`hooks.Notification seat-event entry from ${settingsPath(o2.home)}`);
|
|
7739
8517
|
}
|
|
7740
8518
|
if (hasPromptHook(settings)) {
|
|
7741
8519
|
settings = removePromptHook(settings);
|
|
7742
8520
|
changed = true;
|
|
7743
|
-
removed.push(`hooks.UserPromptSubmit prompt-event entry from ${settingsPath(
|
|
8521
|
+
removed.push(`hooks.UserPromptSubmit prompt-event entry from ${settingsPath(o2.home)}`);
|
|
7744
8522
|
}
|
|
7745
8523
|
if (hasSpecHook(settings)) {
|
|
7746
8524
|
settings = removeSpecHook(settings);
|
|
7747
8525
|
changed = true;
|
|
7748
|
-
removed.push(`hooks.PostToolUse spec-event entry from ${settingsPath(
|
|
8526
|
+
removed.push(`hooks.PostToolUse spec-event entry from ${settingsPath(o2.home)}`);
|
|
7749
8527
|
}
|
|
7750
8528
|
if (hasSessionHook(settings)) {
|
|
7751
8529
|
settings = removeSessionHook(settings);
|
|
7752
8530
|
changed = true;
|
|
7753
|
-
removed.push(`hooks.SessionStart session-event entry from ${settingsPath(
|
|
8531
|
+
removed.push(`hooks.SessionStart session-event entry from ${settingsPath(o2.home)}`);
|
|
7754
8532
|
}
|
|
7755
8533
|
if (hasStopHook(settings)) {
|
|
7756
8534
|
settings = removeStopHook(settings);
|
|
7757
8535
|
changed = true;
|
|
7758
|
-
removed.push(`hooks.Stop stop-event entry from ${settingsPath(
|
|
8536
|
+
removed.push(`hooks.Stop stop-event entry from ${settingsPath(o2.home)}`);
|
|
7759
8537
|
}
|
|
7760
8538
|
if (changed)
|
|
7761
|
-
writeSettings(
|
|
8539
|
+
writeSettings(o2.home, settings);
|
|
7762
8540
|
}
|
|
7763
|
-
for (const file of [meterCachePath(
|
|
8541
|
+
for (const file of [meterCachePath(o2.home), seatStatePath(o2.home)]) {
|
|
7764
8542
|
if (existsSync16(file)) {
|
|
7765
8543
|
rmSync5(file, { force: true });
|
|
7766
8544
|
removed.push(file);
|
|
7767
8545
|
}
|
|
7768
8546
|
}
|
|
7769
|
-
const skill = skillDir(
|
|
8547
|
+
const skill = skillDir(o2.home);
|
|
7770
8548
|
if (existsSync16(skill)) {
|
|
7771
8549
|
rmSync5(skill, { recursive: true, force: true });
|
|
7772
8550
|
removed.push(skill);
|
|
7773
8551
|
}
|
|
7774
|
-
const plist = musherPlistPath(
|
|
8552
|
+
const plist = musherPlistPath(o2.home);
|
|
7775
8553
|
if (existsSync16(plist)) {
|
|
7776
|
-
(
|
|
8554
|
+
(o2.unloadAgent ?? defaultUnload)(plist);
|
|
7777
8555
|
rmSync5(plist, { force: true });
|
|
7778
8556
|
removed.push(`launchd agent ${musherLabel()}`);
|
|
7779
8557
|
}
|
|
@@ -7784,7 +8562,7 @@ var uninstall2 = (o) => {
|
|
|
7784
8562
|
for (const r of removed)
|
|
7785
8563
|
log(` ${r}`);
|
|
7786
8564
|
}
|
|
7787
|
-
log(`Kept ${tokenPath(
|
|
8565
|
+
log(`Kept ${tokenPath(o2.home)} (delete it yourself if you want the token gone).`);
|
|
7788
8566
|
return 0;
|
|
7789
8567
|
};
|
|
7790
8568
|
var hasEitherKey = (settings) => {
|
|
@@ -7803,17 +8581,17 @@ var jobIdFromTarget = (target) => {
|
|
|
7803
8581
|
return m[1];
|
|
7804
8582
|
return /^[A-Za-z0-9_-]{3,64}$/.test(t) ? t : null;
|
|
7805
8583
|
};
|
|
7806
|
-
var verify = async (
|
|
7807
|
-
const log =
|
|
7808
|
-
const error =
|
|
7809
|
-
const jobId =
|
|
8584
|
+
var verify = async (o2) => {
|
|
8585
|
+
const log = o2.log ?? console.log;
|
|
8586
|
+
const error = o2.error ?? console.error;
|
|
8587
|
+
const jobId = o2.target ? jobIdFromTarget(o2.target) : null;
|
|
7810
8588
|
if (!jobId) {
|
|
7811
8589
|
error("usage: sleds verify <jobId> (or paste the SLEDS-Attest line)");
|
|
7812
8590
|
return 2;
|
|
7813
8591
|
}
|
|
7814
|
-
const client =
|
|
7815
|
-
const src = meterSource(
|
|
7816
|
-
return src ? musherClient({ ...src, fetchFn:
|
|
8592
|
+
const client = o2.client ?? (() => {
|
|
8593
|
+
const src = meterSource(o2.home);
|
|
8594
|
+
return src ? musherClient({ ...src, fetchFn: o2.fetchFn }) : null;
|
|
7817
8595
|
})();
|
|
7818
8596
|
if (!client) {
|
|
7819
8597
|
error("No seat on this machine. Run `sleds init` first.");
|
|
@@ -7847,8 +8625,8 @@ init_settings();
|
|
|
7847
8625
|
import { chmodSync as chmodSync2, existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync14, renameSync as renameSync6, writeFileSync as writeFileSync13 } from "node:fs";
|
|
7848
8626
|
import { join as join13, resolve as resolve4 } from "node:path";
|
|
7849
8627
|
var configDir2 = (home, env = process.env) => {
|
|
7850
|
-
const
|
|
7851
|
-
return
|
|
8628
|
+
const o2 = env.SLEDS_HOME?.trim();
|
|
8629
|
+
return o2 ? resolve4(home, o2) : join13(home, ".config", "sleds");
|
|
7852
8630
|
};
|
|
7853
8631
|
var configPath2 = (home) => join13(configDir2(home), "config.json");
|
|
7854
8632
|
var isObject13 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -7907,9 +8685,9 @@ var parseConfig2 = (raw) => {
|
|
|
7907
8685
|
const org = parseOrgInfo2(raw.org);
|
|
7908
8686
|
if (org)
|
|
7909
8687
|
cfg.org = org;
|
|
7910
|
-
const
|
|
7911
|
-
if (
|
|
7912
|
-
cfg.musher =
|
|
8688
|
+
const musher2 = parseMusher2(raw.musher);
|
|
8689
|
+
if (musher2)
|
|
8690
|
+
cfg.musher = musher2;
|
|
7913
8691
|
return cfg;
|
|
7914
8692
|
};
|
|
7915
8693
|
var parseMusher2 = (raw) => {
|
|
@@ -7932,16 +8710,16 @@ var parseOrgInfo2 = (raw) => {
|
|
|
7932
8710
|
if (!isObject13(raw) || !isObject13(raw.capabilities))
|
|
7933
8711
|
return;
|
|
7934
8712
|
const name = optString2(raw.name);
|
|
7935
|
-
const
|
|
7936
|
-
const
|
|
8713
|
+
const slug2 = optString2(raw.slug);
|
|
8714
|
+
const plan2 = raw.plan;
|
|
7937
8715
|
const c = raw.capabilities;
|
|
7938
8716
|
const src = c.upstreamSource;
|
|
7939
|
-
if (name === undefined ||
|
|
8717
|
+
if (name === undefined || slug2 === undefined || plan2 !== "team" && plan2 !== "enterprise" && plan2 !== "api" && plan2 !== "individual" || src !== "own" && src !== "shared" && src !== "none")
|
|
7940
8718
|
return;
|
|
7941
8719
|
return {
|
|
7942
8720
|
name,
|
|
7943
|
-
slug,
|
|
7944
|
-
plan,
|
|
8721
|
+
slug: slug2,
|
|
8722
|
+
plan: plan2,
|
|
7945
8723
|
capabilities: {
|
|
7946
8724
|
upstreamKey: c.upstreamKey === true,
|
|
7947
8725
|
adminKey: c.adminKey === true,
|
|
@@ -7952,10 +8730,10 @@ var parseOrgInfo2 = (raw) => {
|
|
|
7952
8730
|
};
|
|
7953
8731
|
};
|
|
7954
8732
|
var readConfig2 = (home) => {
|
|
7955
|
-
const
|
|
7956
|
-
if (!existsSync17(
|
|
8733
|
+
const p2 = configPath2(home);
|
|
8734
|
+
if (!existsSync17(p2))
|
|
7957
8735
|
return {};
|
|
7958
|
-
return parseConfig2(JSON.parse(readFileSync14(
|
|
8736
|
+
return parseConfig2(JSON.parse(readFileSync14(p2, "utf8")));
|
|
7959
8737
|
};
|
|
7960
8738
|
|
|
7961
8739
|
// src/schedule.ts
|
|
@@ -8092,8 +8870,8 @@ var stringFlag = (flags, name) => {
|
|
|
8092
8870
|
var hookStdin = async () => {
|
|
8093
8871
|
if (process.stdin.isTTY)
|
|
8094
8872
|
return;
|
|
8095
|
-
await Promise.resolve().then(() => init_seat());
|
|
8096
|
-
const text = await
|
|
8873
|
+
const { readStdin: readStdin3 } = await Promise.resolve().then(() => (init_seat(), exports_seat));
|
|
8874
|
+
const text = await readStdin3(300);
|
|
8097
8875
|
return text.trim() ? text : undefined;
|
|
8098
8876
|
};
|
|
8099
8877
|
var sledsArgv = () => {
|
|
@@ -8246,7 +9024,7 @@ ${renderHelpText(argv[1])}`);
|
|
|
8246
9024
|
case "version":
|
|
8247
9025
|
case "--version":
|
|
8248
9026
|
case "-v":
|
|
8249
|
-
console.log("sleds 0.1.
|
|
9027
|
+
console.log("sleds 0.1.26");
|
|
8250
9028
|
return 0;
|
|
8251
9029
|
default:
|
|
8252
9030
|
console.error(`unknown command: ${cmd}
|