@sleds/cli 0.1.24 → 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 +1656 -750
- 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",
|
|
@@ -833,7 +1269,7 @@ var GUIDES = [
|
|
|
833
1269
|
{
|
|
834
1270
|
do: "Add the custom connector",
|
|
835
1271
|
where: "claude.ai › Settings › Connectors › `Add custom connector`",
|
|
836
|
-
note: "URL `https://
|
|
1272
|
+
note: "URL `https://mcp.sleds.ai/mcp`. It signs you in with OAuth — the same email your workspace invited."
|
|
837
1273
|
},
|
|
838
1274
|
{
|
|
839
1275
|
do: "Say `/sled usage` in any chat",
|
|
@@ -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
|
{
|
|
@@ -1221,7 +1710,7 @@ var VERBS = [
|
|
|
1221
1710
|
id: "connector",
|
|
1222
1711
|
group: "trust",
|
|
1223
1712
|
want: "Use SLEDS from chat, Cowork or Claude Design",
|
|
1224
|
-
mcp: "add https://
|
|
1713
|
+
mcp: "add https://mcp.sleds.ai/mcp as a connector (OAuth) — then every verb in this column",
|
|
1225
1714
|
how: "The conversation is your cockpit: the same tools, no terminal. Your seat is the account you signed in with."
|
|
1226
1715
|
},
|
|
1227
1716
|
{
|
|
@@ -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)}`;
|
|
2184
|
+
}
|
|
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.`;
|
|
1689
2205
|
}
|
|
1690
|
-
function assetsTail(
|
|
1691
|
-
const assets = Array.isArray(
|
|
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 {
|
|
@@ -2076,22 +2592,22 @@ var SERVER_INFO = {
|
|
|
2076
2592
|
description: "The cost and limits of AI agent work — estimate, hand off, and track what a task takes.",
|
|
2077
2593
|
websiteUrl: "https://sleds.ai",
|
|
2078
2594
|
icons: [
|
|
2079
|
-
{ src: "https://
|
|
2080
|
-
{ src: "https://
|
|
2595
|
+
{ src: "https://mcp.sleds.ai/icon.png", mimeType: "image/png", sizes: ["192x192"] },
|
|
2596
|
+
{ src: "https://mcp.sleds.ai/icon.svg", mimeType: "image/svg+xml", sizes: ["any"] }
|
|
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,20 +4071,20 @@ 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) } : {},
|
|
3320
4086
|
...str4(args.by) ? { by: str4(args.by) } : {},
|
|
3321
|
-
...sessionIdOf(args.sessionId) ? { sessionId: sessionIdOf(args.sessionId) } : {},
|
|
4087
|
+
...sessionIdOf(args.sessionId) ?? ctx.sessionId?.() ? { sessionId: sessionIdOf(args.sessionId) ?? ctx.sessionId?.() } : {},
|
|
3322
4088
|
...str4(args.ref) ? { ref: str4(args.ref) } : {},
|
|
3323
4089
|
...phases && Object.keys(phases).length > 0 ? { phases } : {},
|
|
3324
4090
|
...num3(args.seatCreditsUsd) !== undefined ? { seatCreditsUsd: num3(args.seatCreditsUsd) } : {}
|
|
@@ -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")
|
|
@@ -5214,65 +5991,70 @@ function contextFor(deps) {
|
|
|
5214
5991
|
const src = meterSource(deps.home);
|
|
5215
5992
|
const now = deps.now ?? Date.now;
|
|
5216
5993
|
const client = src ? trackingClient(fetchClient({ ...src, fetchFn: deps.fetchFn, timeoutMs: deps.timeoutMs }), deps.home, now) : null;
|
|
5217
|
-
return {
|
|
5994
|
+
return {
|
|
5995
|
+
client,
|
|
5996
|
+
now: deps.now,
|
|
5997
|
+
usage: () => usageTool2(deps),
|
|
5998
|
+
sessionId: () => readSession(deps.home)?.sessionId
|
|
5999
|
+
};
|
|
5218
6000
|
}
|
|
5219
6001
|
var callTool2 = (name, args, deps) => callTool(name, args, contextFor(deps));
|
|
5220
6002
|
var handleLine2 = (line, deps) => handleLine(line, contextFor(deps));
|
|
5221
6003
|
|
|
5222
6004
|
// src/commands/handoffs.ts
|
|
5223
|
-
var handoffsCmd = async (
|
|
5224
|
-
const r = await callTool2("handoffs", {}, { home:
|
|
5225
|
-
(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);
|
|
5226
6008
|
return r.isError ? 1 : 0;
|
|
5227
6009
|
};
|
|
5228
|
-
var pickupCmd = async (
|
|
5229
|
-
const id =
|
|
6010
|
+
var pickupCmd = async (o2) => {
|
|
6011
|
+
const id = o2.argv[0];
|
|
5230
6012
|
if (!id) {
|
|
5231
|
-
(
|
|
6013
|
+
(o2.error ?? console.error)("usage: sleds pickup <handoffId> (ids from `sleds handoffs`)");
|
|
5232
6014
|
return 2;
|
|
5233
6015
|
}
|
|
5234
|
-
const r = await callTool2("pickup", { handoffId: id }, { home:
|
|
5235
|
-
(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);
|
|
5236
6018
|
return r.isError ? 1 : 0;
|
|
5237
6019
|
};
|
|
5238
|
-
var withdrawCmd = async (
|
|
5239
|
-
const id =
|
|
6020
|
+
var withdrawCmd = async (o2) => {
|
|
6021
|
+
const id = o2.argv[0];
|
|
5240
6022
|
if (!id) {
|
|
5241
|
-
(
|
|
6023
|
+
(o2.error ?? console.error)("usage: sleds withdraw <handoffId> (one you parked)");
|
|
5242
6024
|
return 2;
|
|
5243
6025
|
}
|
|
5244
|
-
const r = await callTool2("withdraw", { handoffId: id }, { home:
|
|
5245
|
-
(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);
|
|
5246
6028
|
return r.isError ? 1 : 0;
|
|
5247
6029
|
};
|
|
5248
6030
|
|
|
5249
6031
|
// src/commands/hooks.ts
|
|
5250
6032
|
init_settings();
|
|
5251
|
-
var hooks = (
|
|
5252
|
-
const log =
|
|
5253
|
-
const error =
|
|
5254
|
-
if (!hasClaudeCode(
|
|
5255
|
-
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.`);
|
|
5256
6038
|
return 1;
|
|
5257
6039
|
}
|
|
5258
|
-
const settings = readSettings(
|
|
6040
|
+
const settings = readSettings(o2.home);
|
|
5259
6041
|
const had = hasSeatHook(settings);
|
|
5260
6042
|
const hadSpec = hasSpecHook(settings);
|
|
5261
|
-
const withSeat = applySeatHook(settings,
|
|
6043
|
+
const withSeat = applySeatHook(settings, o2.seatEventCommand ?? sledsCommand("seat-event", o2.home, o2.env));
|
|
5262
6044
|
const hadPrompt = hasPromptHook(settings);
|
|
5263
6045
|
const hadSession = hasSessionHook(settings);
|
|
5264
6046
|
const hadStop = hasStopHook(settings);
|
|
5265
|
-
const withPrompt = applyPromptHook(applySpecHook(withSeat,
|
|
5266
|
-
const withSpec = applyStopHook(applySessionHook(withPrompt,
|
|
5267
|
-
const hadLine =
|
|
5268
|
-
writeSettings(
|
|
5269
|
-
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)}`);
|
|
5270
6052
|
if (hadLine)
|
|
5271
|
-
log(`refreshed statusLine in ${settingsPath(
|
|
5272
|
-
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(
|
|
5273
|
-
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(
|
|
5274
|
-
log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(
|
|
5275
|
-
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)}`);
|
|
5276
6058
|
return 0;
|
|
5277
6059
|
};
|
|
5278
6060
|
|
|
@@ -5290,7 +6072,7 @@ description: The SLEDS loop for engineering work — size a build before startin
|
|
|
5290
6072
|
|
|
5291
6073
|
# SLEDS
|
|
5292
6074
|
|
|
5293
|
-
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.
|
|
5294
6076
|
|
|
5295
6077
|
## The loop (every build, no exceptions)
|
|
5296
6078
|
|
|
@@ -5386,7 +6168,7 @@ Resume a parked job. Show the unanswered question and its options, take the answ
|
|
|
5386
6168
|
|
|
5387
6169
|
## /sled sync-backlog — price the backlog
|
|
5388
6170
|
|
|
5389
|
-
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.
|
|
5390
6172
|
|
|
5391
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.
|
|
5392
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.
|
|
@@ -5478,9 +6260,9 @@ function parsePoll(body) {
|
|
|
5478
6260
|
};
|
|
5479
6261
|
}
|
|
5480
6262
|
function openInBrowser(url) {
|
|
5481
|
-
const
|
|
5482
|
-
const cmd =
|
|
5483
|
-
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];
|
|
5484
6266
|
try {
|
|
5485
6267
|
const r = spawnSync3(cmd, args, { stdio: "ignore", timeout: 5000 });
|
|
5486
6268
|
return r.error === undefined && r.status === 0;
|
|
@@ -5489,17 +6271,17 @@ function openInBrowser(url) {
|
|
|
5489
6271
|
}
|
|
5490
6272
|
}
|
|
5491
6273
|
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
5492
|
-
async function deviceFlow(
|
|
5493
|
-
const fetchFn =
|
|
5494
|
-
const log =
|
|
5495
|
-
const sleep =
|
|
5496
|
-
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;
|
|
5497
6279
|
let started = null;
|
|
5498
6280
|
try {
|
|
5499
|
-
const res = await fetchFn(`${
|
|
6281
|
+
const res = await fetchFn(`${o2.proxyUrl}/device/start`, {
|
|
5500
6282
|
method: "POST",
|
|
5501
6283
|
headers: { "content-type": "application/json" },
|
|
5502
|
-
body: JSON.stringify({ agent:
|
|
6284
|
+
body: JSON.stringify({ agent: o2.agent ?? "claude-code", host: o2.host ?? hostname2() }),
|
|
5503
6285
|
signal: AbortSignal.timeout(20000)
|
|
5504
6286
|
});
|
|
5505
6287
|
if (res.status === 429)
|
|
@@ -5512,7 +6294,7 @@ async function deviceFlow(o) {
|
|
|
5512
6294
|
}
|
|
5513
6295
|
if (!started)
|
|
5514
6296
|
return { ok: false, reason: "bad_answer" };
|
|
5515
|
-
const opened =
|
|
6297
|
+
const opened = o2.noBrowser ? false : (o2.openBrowser ?? openInBrowser)(started.verifyUrl);
|
|
5516
6298
|
log("");
|
|
5517
6299
|
log(opened ? "Opening your browser to sign in to SLEDS." : "Open this in your browser to sign in to SLEDS:");
|
|
5518
6300
|
log(` ${started.verifyUrl}`);
|
|
@@ -5526,7 +6308,7 @@ async function deviceFlow(o) {
|
|
|
5526
6308
|
await sleep(intervalMs);
|
|
5527
6309
|
let body;
|
|
5528
6310
|
try {
|
|
5529
|
-
const res = await fetchFn(`${
|
|
6311
|
+
const res = await fetchFn(`${o2.proxyUrl}/device/poll`, {
|
|
5530
6312
|
method: "POST",
|
|
5531
6313
|
headers: { "content-type": "application/json" },
|
|
5532
6314
|
body: JSON.stringify({ deviceCode: started.deviceCode }),
|
|
@@ -5554,41 +6336,41 @@ async function deviceFlow(o) {
|
|
|
5554
6336
|
|
|
5555
6337
|
// src/commands/hooks.ts
|
|
5556
6338
|
init_settings();
|
|
5557
|
-
var hooks2 = (
|
|
5558
|
-
const log =
|
|
5559
|
-
const error =
|
|
5560
|
-
if (!hasClaudeCode(
|
|
5561
|
-
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.`);
|
|
5562
6344
|
return 1;
|
|
5563
6345
|
}
|
|
5564
|
-
const settings = readSettings(
|
|
6346
|
+
const settings = readSettings(o2.home);
|
|
5565
6347
|
const had = hasSeatHook(settings);
|
|
5566
6348
|
const hadSpec = hasSpecHook(settings);
|
|
5567
|
-
const withSeat = applySeatHook(settings,
|
|
6349
|
+
const withSeat = applySeatHook(settings, o2.seatEventCommand ?? sledsCommand("seat-event", o2.home, o2.env));
|
|
5568
6350
|
const hadPrompt = hasPromptHook(settings);
|
|
5569
6351
|
const hadSession = hasSessionHook(settings);
|
|
5570
6352
|
const hadStop = hasStopHook(settings);
|
|
5571
|
-
const withPrompt = applyPromptHook(applySpecHook(withSeat,
|
|
5572
|
-
const withSpec = applyStopHook(applySessionHook(withPrompt,
|
|
5573
|
-
const hadLine =
|
|
5574
|
-
writeSettings(
|
|
5575
|
-
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)}`);
|
|
5576
6358
|
if (hadLine)
|
|
5577
|
-
log(`refreshed statusLine in ${settingsPath(
|
|
5578
|
-
log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(
|
|
5579
|
-
log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(
|
|
5580
|
-
log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(
|
|
5581
|
-
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)}`);
|
|
5582
6364
|
return 0;
|
|
5583
6365
|
};
|
|
5584
6366
|
|
|
5585
6367
|
// src/commands/init.ts
|
|
5586
6368
|
var DEFAULT_PROXY_URL = "https://proxy.sleds.ai";
|
|
5587
|
-
var promptStdin = (question) => new Promise((
|
|
6369
|
+
var promptStdin = (question) => new Promise((resolve4) => {
|
|
5588
6370
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
5589
6371
|
rl.question(question, (answer) => {
|
|
5590
6372
|
rl.close();
|
|
5591
|
-
|
|
6373
|
+
resolve4(answer.trim());
|
|
5592
6374
|
});
|
|
5593
6375
|
});
|
|
5594
6376
|
async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch) {
|
|
@@ -5685,70 +6467,70 @@ async function probeProxy(url, fetchFn = fetch, opts = {}) {
|
|
|
5685
6467
|
}
|
|
5686
6468
|
return false;
|
|
5687
6469
|
}
|
|
5688
|
-
var resolveToken = async (
|
|
5689
|
-
const env =
|
|
5690
|
-
if (
|
|
5691
|
-
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" };
|
|
5692
6474
|
if (env.SLEDS_TOKEN)
|
|
5693
6475
|
return { token: env.SLEDS_TOKEN.trim(), source: "env" };
|
|
5694
|
-
const fromFile = readTokenFile(
|
|
6476
|
+
const fromFile = readTokenFile(o2.home);
|
|
5695
6477
|
if (fromFile)
|
|
5696
6478
|
return { token: fromFile, source: "file" };
|
|
5697
|
-
if (
|
|
6479
|
+
if (o2.nonInteractive)
|
|
5698
6480
|
return null;
|
|
5699
|
-
const url = normalizeProxyUrl(
|
|
6481
|
+
const url = normalizeProxyUrl(o2.url ?? env.SLEDS_PROXY_URL ?? readConfig(o2.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
5700
6482
|
if (!url)
|
|
5701
6483
|
return null;
|
|
5702
|
-
const log =
|
|
5703
|
-
const err =
|
|
5704
|
-
if (!
|
|
5705
|
-
const
|
|
6484
|
+
const log = o2.log ?? console.log;
|
|
6485
|
+
const err = o2.error ?? console.error;
|
|
6486
|
+
if (!o2.signup) {
|
|
6487
|
+
const r2 = await deviceFlow({
|
|
5706
6488
|
proxyUrl: url,
|
|
5707
6489
|
agent: "claude-code",
|
|
5708
|
-
host:
|
|
5709
|
-
fetchFn:
|
|
5710
|
-
openBrowser:
|
|
5711
|
-
noBrowser:
|
|
5712
|
-
sleep:
|
|
6490
|
+
host: o2.host,
|
|
6491
|
+
fetchFn: o2.fetchFn,
|
|
6492
|
+
openBrowser: o2.openBrowser ?? openInBrowser,
|
|
6493
|
+
noBrowser: o2.noBrowser,
|
|
6494
|
+
sleep: o2.sleep,
|
|
5713
6495
|
log
|
|
5714
6496
|
});
|
|
5715
|
-
if (!
|
|
5716
|
-
err(deviceFailureText(
|
|
6497
|
+
if (!r2.ok) {
|
|
6498
|
+
err(deviceFailureText(r2.reason));
|
|
5717
6499
|
return null;
|
|
5718
6500
|
}
|
|
5719
|
-
return { token:
|
|
6501
|
+
return { token: r2.approved.token, source: "device", approved: r2.approved };
|
|
5720
6502
|
}
|
|
5721
|
-
if (!
|
|
6503
|
+
if (!o2.prompt)
|
|
5722
6504
|
return null;
|
|
5723
|
-
let team =
|
|
5724
|
-
if (team === null && !
|
|
5725
|
-
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();
|
|
5726
6508
|
if (which === "2") {
|
|
5727
|
-
team = (await
|
|
6509
|
+
team = (await o2.prompt("Team name (e.g. your company): ")).trim();
|
|
5728
6510
|
if (!team)
|
|
5729
6511
|
return null;
|
|
5730
6512
|
}
|
|
5731
6513
|
}
|
|
5732
|
-
const email = (
|
|
6514
|
+
const email = (o2.email ?? await o2.prompt(team ? "Your work email: " : "Your email: ")).trim();
|
|
5733
6515
|
if (!email.includes("@"))
|
|
5734
6516
|
return null;
|
|
5735
6517
|
if (team) {
|
|
5736
|
-
const
|
|
5737
|
-
const
|
|
5738
|
-
if (!
|
|
5739
|
-
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));
|
|
5740
6522
|
return null;
|
|
5741
6523
|
}
|
|
5742
|
-
log(`Created ${team} with you as owner (${email}).${
|
|
5743
|
-
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)
|
|
5744
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).`);
|
|
5745
6527
|
log("Invite your team on the dashboard (Members → invite by email, Admin → Mint a seat); each person runs `sleds init --token …`.");
|
|
5746
|
-
return { token:
|
|
6528
|
+
return { token: r2.token, source: "signup" };
|
|
5747
6529
|
}
|
|
5748
|
-
const credential = (await
|
|
6530
|
+
const credential = (await o2.prompt("Your Anthropic API key (sk-ant-…, stored encrypted, spends only your account): ")).trim();
|
|
5749
6531
|
if (!credential)
|
|
5750
6532
|
return null;
|
|
5751
|
-
const r = await signupIndividual(url, email, credential,
|
|
6533
|
+
const r = await signupIndividual(url, email, credential, o2.fetchFn);
|
|
5752
6534
|
if (!r.ok) {
|
|
5753
6535
|
err(signupFailureText(r));
|
|
5754
6536
|
return null;
|
|
@@ -5756,66 +6538,66 @@ var resolveToken = async (o) => {
|
|
|
5756
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.`);
|
|
5757
6539
|
return { token: r.token, source: "signup" };
|
|
5758
6540
|
};
|
|
5759
|
-
var init = async (
|
|
5760
|
-
const log =
|
|
5761
|
-
const error =
|
|
5762
|
-
const env =
|
|
5763
|
-
if (!hasClaudeCode(
|
|
5764
|
-
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.`);
|
|
5765
6547
|
return 1;
|
|
5766
6548
|
}
|
|
5767
|
-
const resolved = await resolveToken(
|
|
6549
|
+
const resolved = await resolveToken(o2);
|
|
5768
6550
|
if (!resolved) {
|
|
5769
|
-
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.");
|
|
5770
6552
|
return 2;
|
|
5771
6553
|
}
|
|
5772
6554
|
if (!isSeatToken(resolved.token)) {
|
|
5773
6555
|
error("Invalid seat token: expected something like sled_xxxxxxxx.");
|
|
5774
6556
|
return 2;
|
|
5775
6557
|
}
|
|
5776
|
-
const url = normalizeProxyUrl(
|
|
6558
|
+
const url = normalizeProxyUrl(o2.url ?? env.SLEDS_PROXY_URL ?? readConfig(o2.home).proxyUrl ?? DEFAULT_PROXY_URL);
|
|
5777
6559
|
if (!url) {
|
|
5778
|
-
error(`Invalid proxy URL: ${JSON.stringify(
|
|
6560
|
+
error(`Invalid proxy URL: ${JSON.stringify(o2.url ?? env.SLEDS_PROXY_URL ?? "")}. Expected https://proxy.sleds.ai.`);
|
|
5779
6561
|
return 2;
|
|
5780
6562
|
}
|
|
5781
|
-
if (!
|
|
6563
|
+
if (!o2.noProbe && !await probeProxy(url, o2.fetchFn)) {
|
|
5782
6564
|
error(`No SLEDS proxy answered at ${url}/healthz. Check the URL (or pass --no-probe to save it anyway).`);
|
|
5783
6565
|
return 2;
|
|
5784
6566
|
}
|
|
5785
6567
|
const wrote = [];
|
|
5786
|
-
const seatInfo = await fetchSeatInfo(url, resolved.token,
|
|
6568
|
+
const seatInfo = await fetchSeatInfo(url, resolved.token, o2.fetchFn);
|
|
5787
6569
|
const org = seatInfo.org;
|
|
5788
|
-
const wiring =
|
|
6570
|
+
const wiring = o2.proxyAlways ? "proxy-always" : o2.seatFirst ? "seat-first" : org?.mode ?? "proxy-always";
|
|
5789
6571
|
if (wiring === "proxy-always" && org && !org.capabilities.upstreamKey) {
|
|
5790
6572
|
error(`${org.name} has no platform key for the proxy to forward with; --proxy-always would break every request. Using seat-first.`);
|
|
5791
6573
|
}
|
|
5792
6574
|
const finalWiring = wiring === "proxy-always" && org && !org.capabilities.upstreamKey ? "seat-first" : wiring;
|
|
5793
|
-
if (backupSettings(
|
|
5794
|
-
wrote.push(`backed up settings to ${backupPath(
|
|
5795
|
-
const current = readSettings(
|
|
6575
|
+
if (backupSettings(o2.home))
|
|
6576
|
+
wrote.push(`backed up settings to ${backupPath(o2.home)}`);
|
|
6577
|
+
const current = readSettings(o2.home);
|
|
5796
6578
|
const wired = finalWiring === "proxy-always" ? applyProxy(current, { url, token: resolved.token }) : removeProxy(current);
|
|
5797
|
-
const
|
|
5798
|
-
writeSettings(
|
|
5799
|
-
wrote.push(finalWiring === "proxy-always" ? `set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(
|
|
5800
|
-
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)`);
|
|
5801
6583
|
hooks2({
|
|
5802
|
-
home:
|
|
5803
|
-
env:
|
|
6584
|
+
home: o2.home,
|
|
6585
|
+
env: o2.env,
|
|
5804
6586
|
refreshStatusLine: false,
|
|
5805
|
-
seatEventCommand:
|
|
5806
|
-
specEventCommand:
|
|
5807
|
-
promptEventCommand:
|
|
5808
|
-
sessionEventCommand:
|
|
5809
|
-
stopEventCommand:
|
|
6587
|
+
seatEventCommand: o2.seatEventCommand,
|
|
6588
|
+
specEventCommand: o2.specEventCommand,
|
|
6589
|
+
promptEventCommand: o2.promptEventCommand,
|
|
6590
|
+
sessionEventCommand: o2.sessionEventCommand,
|
|
6591
|
+
stopEventCommand: o2.stopEventCommand,
|
|
5810
6592
|
log: (line) => wrote.push(line),
|
|
5811
6593
|
error
|
|
5812
6594
|
});
|
|
5813
6595
|
if (resolved.source !== "file") {
|
|
5814
|
-
writeTokenFile(
|
|
5815
|
-
wrote.push(`saved token to ${tokenPath(
|
|
6596
|
+
writeTokenFile(o2.home, resolved.token);
|
|
6597
|
+
wrote.push(`saved token to ${tokenPath(o2.home)} (0600)`);
|
|
5816
6598
|
}
|
|
5817
|
-
const prior = readConfig(
|
|
5818
|
-
writeConfig(
|
|
6599
|
+
const prior = readConfig(o2.home);
|
|
6600
|
+
writeConfig(o2.home, {
|
|
5819
6601
|
...prior,
|
|
5820
6602
|
token: resolved.token,
|
|
5821
6603
|
proxyUrl: url,
|
|
@@ -5823,14 +6605,14 @@ var init = async (o) => {
|
|
|
5823
6605
|
agentKind: "claude-code",
|
|
5824
6606
|
...seatInfo.email ? { email: seatInfo.email } : {},
|
|
5825
6607
|
...org ? { org } : {},
|
|
5826
|
-
...
|
|
6608
|
+
...statusLine2.prior !== undefined ? { priorStatusLine: statusLine2.prior } : {}
|
|
5827
6609
|
});
|
|
5828
|
-
wrote.push(`saved token and proxy URL to ${configPath(
|
|
5829
|
-
mkdirSync8(skillDir(
|
|
5830
|
-
writeFileSync8(skillPath(
|
|
5831
|
-
wrote.push(`installed skill at ${skillPath(
|
|
5832
|
-
if (
|
|
5833
|
-
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();
|
|
5834
6616
|
if (r)
|
|
5835
6617
|
wrote.push(r);
|
|
5836
6618
|
}
|
|
@@ -5867,9 +6649,9 @@ var init = async (o) => {
|
|
|
5867
6649
|
};
|
|
5868
6650
|
|
|
5869
6651
|
// src/commands/mcp.ts
|
|
5870
|
-
var mcp = async (
|
|
5871
|
-
const stdin =
|
|
5872
|
-
const write =
|
|
6652
|
+
var mcp = async (o2) => {
|
|
6653
|
+
const stdin = o2.stdin ?? process.stdin;
|
|
6654
|
+
const write = o2.write ?? ((line) => process.stdout.write(`${line}
|
|
5873
6655
|
`));
|
|
5874
6656
|
let buffer = "";
|
|
5875
6657
|
for await (const chunk of stdin) {
|
|
@@ -5879,7 +6661,7 @@ var mcp = async (o) => {
|
|
|
5879
6661
|
while (nl !== -1) {
|
|
5880
6662
|
const line = buffer.slice(0, nl);
|
|
5881
6663
|
buffer = buffer.slice(nl + 1);
|
|
5882
|
-
const out = await handleLine2(line, { home:
|
|
6664
|
+
const out = await handleLine2(line, { home: o2.home });
|
|
5883
6665
|
if (out !== null)
|
|
5884
6666
|
write(out);
|
|
5885
6667
|
nl = buffer.indexOf(`
|
|
@@ -5887,7 +6669,7 @@ var mcp = async (o) => {
|
|
|
5887
6669
|
}
|
|
5888
6670
|
}
|
|
5889
6671
|
if (buffer.trim().length > 0) {
|
|
5890
|
-
const out = await handleLine2(buffer, { home:
|
|
6672
|
+
const out = await handleLine2(buffer, { home: o2.home });
|
|
5891
6673
|
if (out !== null)
|
|
5892
6674
|
write(out);
|
|
5893
6675
|
}
|
|
@@ -5897,15 +6679,15 @@ var mcp = async (o) => {
|
|
|
5897
6679
|
// src/commands/meter.ts
|
|
5898
6680
|
init_config();
|
|
5899
6681
|
init_meter();
|
|
5900
|
-
var meter = async (
|
|
5901
|
-
const log =
|
|
5902
|
-
const now = (
|
|
5903
|
-
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);
|
|
5904
6686
|
if (!src) {
|
|
5905
|
-
log(`SLEDS is not set up (${configPath(
|
|
6687
|
+
log(`SLEDS is not set up (${configPath(o2.home)}). Run \`sleds init\` first.`);
|
|
5906
6688
|
return 1;
|
|
5907
6689
|
}
|
|
5908
|
-
const m = await loadMeter({ home:
|
|
6690
|
+
const m = await loadMeter({ home: o2.home, now, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs });
|
|
5909
6691
|
if (!m) {
|
|
5910
6692
|
log(`sleds offline. ${src.proxyUrl} did not answer → \`sleds status\` for the proxy health.`);
|
|
5911
6693
|
return 2;
|
|
@@ -5925,9 +6707,9 @@ import { dirname as dirname5 } from "node:path";
|
|
|
5925
6707
|
var defaultWhich3 = (cmd) => {
|
|
5926
6708
|
const path = process.env.PATH ?? "";
|
|
5927
6709
|
for (const dir of path.split(":").filter(Boolean)) {
|
|
5928
|
-
const
|
|
5929
|
-
if (existsSync10(
|
|
5930
|
-
return
|
|
6710
|
+
const p2 = `${dir}/${cmd}`;
|
|
6711
|
+
if (existsSync10(p2))
|
|
6712
|
+
return p2;
|
|
5931
6713
|
}
|
|
5932
6714
|
return null;
|
|
5933
6715
|
};
|
|
@@ -5937,68 +6719,68 @@ var findClaude2 = (home, configured, which) => {
|
|
|
5937
6719
|
const found = which("claude");
|
|
5938
6720
|
if (found)
|
|
5939
6721
|
return found;
|
|
5940
|
-
for (const
|
|
6722
|
+
for (const p2 of [
|
|
5941
6723
|
`${home}/.local/bin/claude`,
|
|
5942
6724
|
"/opt/homebrew/bin/claude",
|
|
5943
6725
|
"/usr/local/bin/claude",
|
|
5944
6726
|
`${home}/.npm-global/bin/claude`
|
|
5945
6727
|
])
|
|
5946
|
-
if (existsSync10(
|
|
5947
|
-
return
|
|
6728
|
+
if (existsSync10(p2))
|
|
6729
|
+
return p2;
|
|
5948
6730
|
return null;
|
|
5949
6731
|
};
|
|
5950
|
-
var musherInstall = (
|
|
5951
|
-
const log =
|
|
5952
|
-
const error =
|
|
5953
|
-
const
|
|
5954
|
-
if (
|
|
5955
|
-
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.`);
|
|
5956
6738
|
return 1;
|
|
5957
6739
|
}
|
|
5958
|
-
const env =
|
|
5959
|
-
const plist = musherPlistPath(
|
|
6740
|
+
const env = o2.env ?? process.env;
|
|
6741
|
+
const plist = musherPlistPath(o2.home, env);
|
|
5960
6742
|
const label = musherLabel(env);
|
|
5961
|
-
const dir = configDir(
|
|
6743
|
+
const dir = configDir(o2.home, env);
|
|
5962
6744
|
mkdirSync9(dirname5(plist), { recursive: true });
|
|
5963
6745
|
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
5964
|
-
const execPath =
|
|
6746
|
+
const execPath = o2.sledsArgv[0] ?? process.execPath;
|
|
5965
6747
|
const agentEnv = {
|
|
5966
|
-
PATH: musherPath(
|
|
5967
|
-
HOME:
|
|
6748
|
+
PATH: musherPath(o2.home, execPath, env.PATH ?? ""),
|
|
6749
|
+
HOME: o2.home,
|
|
5968
6750
|
...env.SLEDS_HOME ? { SLEDS_HOME: env.SLEDS_HOME } : {},
|
|
5969
6751
|
...env.CLAUDE_CONFIG_DIR ? { CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR } : {}
|
|
5970
6752
|
};
|
|
5971
|
-
const exec =
|
|
6753
|
+
const exec = o2.exec ?? defaultExecSync;
|
|
5972
6754
|
if (existsSync10(plist))
|
|
5973
6755
|
exec("launchctl", ["unload", plist]);
|
|
5974
|
-
writeFileSync9(plist, buildMusherPlist({ sledsArgv:
|
|
6756
|
+
writeFileSync9(plist, buildMusherPlist({ sledsArgv: o2.sledsArgv, env: agentEnv, logPath: musherLogPath(dir), label }));
|
|
5975
6757
|
const r = exec("launchctl", ["load", plist]);
|
|
5976
6758
|
if (!r.ok) {
|
|
5977
6759
|
error(`launchctl load failed: ${r.stderr.trim() || "unknown"}`);
|
|
5978
6760
|
return 1;
|
|
5979
6761
|
}
|
|
5980
|
-
const cfg = readConfig(
|
|
5981
|
-
writeConfig(
|
|
5982
|
-
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);
|
|
5983
6765
|
log(`musher installed: launchd agent ${label}, every 5 min · log ${musherLogPath(dir)}`);
|
|
5984
6766
|
if (!claude)
|
|
5985
6767
|
log("warn: `claude` not found on PATH — set it with `sleds musher --claude /path/to/claude`");
|
|
5986
6768
|
return 0;
|
|
5987
6769
|
};
|
|
5988
|
-
var musherUninstall = (
|
|
5989
|
-
const log =
|
|
5990
|
-
const env =
|
|
5991
|
-
const plist = musherPlistPath(
|
|
5992
|
-
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;
|
|
5993
6775
|
if (existsSync10(plist)) {
|
|
5994
6776
|
exec("launchctl", ["unload", plist]);
|
|
5995
6777
|
rmSync2(plist, { force: true });
|
|
5996
6778
|
log(`musher removed: ${musherLabel(env)}`);
|
|
5997
6779
|
} else
|
|
5998
6780
|
log("musher was not installed.");
|
|
5999
|
-
const cfg = readConfig(
|
|
6781
|
+
const cfg = readConfig(o2.home);
|
|
6000
6782
|
if (cfg.musher)
|
|
6001
|
-
writeConfig(
|
|
6783
|
+
writeConfig(o2.home, { ...cfg, musher: { ...cfg.musher, agent: "none" } });
|
|
6002
6784
|
return 0;
|
|
6003
6785
|
};
|
|
6004
6786
|
var formatMusherStatus = (list, now) => {
|
|
@@ -6013,23 +6795,23 @@ var formatMusherStatus = (list, now) => {
|
|
|
6013
6795
|
lines.push("nothing deferred on this seat.");
|
|
6014
6796
|
return lines;
|
|
6015
6797
|
};
|
|
6016
|
-
var clientFor = (
|
|
6017
|
-
if (
|
|
6018
|
-
return
|
|
6019
|
-
const src = meterSource(
|
|
6020
|
-
return src ? musherClient({ ...src, fetchFn:
|
|
6021
|
-
};
|
|
6022
|
-
var musherStatus = async (
|
|
6023
|
-
const log =
|
|
6024
|
-
const now = (
|
|
6025
|
-
const env =
|
|
6026
|
-
const plist = musherPlistPath(
|
|
6027
|
-
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);
|
|
6028
6810
|
log(existsSync10(plist) ? `agent ${musherLabel(env)} installed (${plist})` : "agent not installed — sleds musher --install");
|
|
6029
|
-
const claude = findClaude2(
|
|
6811
|
+
const claude = findClaude2(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich3);
|
|
6030
6812
|
log(`claude ${claude ?? "not found — sleds musher --claude /path/to/claude"}`);
|
|
6031
6813
|
log(`mode ${cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
6032
|
-
const client = clientFor(
|
|
6814
|
+
const client = clientFor(o2);
|
|
6033
6815
|
if (!client) {
|
|
6034
6816
|
log("seat not initialised (sleds init)");
|
|
6035
6817
|
return 1;
|
|
@@ -6043,9 +6825,9 @@ var musherStatus = async (o) => {
|
|
|
6043
6825
|
log(line);
|
|
6044
6826
|
return 0;
|
|
6045
6827
|
};
|
|
6046
|
-
var musherTick = async (
|
|
6047
|
-
const log =
|
|
6048
|
-
const client = clientFor(
|
|
6828
|
+
var musherTick = async (o2) => {
|
|
6829
|
+
const log = o2.log ?? console.log;
|
|
6830
|
+
const client = clientFor(o2);
|
|
6049
6831
|
if (!client) {
|
|
6050
6832
|
log("musher: seat not initialised; nothing to do");
|
|
6051
6833
|
return 1;
|
|
@@ -6057,19 +6839,19 @@ var musherTick = async (o) => {
|
|
|
6057
6839
|
}
|
|
6058
6840
|
if (list.due.length === 0)
|
|
6059
6841
|
return 0;
|
|
6060
|
-
const cfg = readConfig(
|
|
6061
|
-
const claude = findClaude2(
|
|
6062
|
-
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);
|
|
6063
6845
|
for (const job of list.due) {
|
|
6064
6846
|
const out = await runJob({
|
|
6065
6847
|
job,
|
|
6066
6848
|
client,
|
|
6067
6849
|
fallbackSession: seat.session,
|
|
6068
|
-
host:
|
|
6850
|
+
host: o2.host ?? hostname3(),
|
|
6069
6851
|
claudePath: claude,
|
|
6070
6852
|
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
6071
6853
|
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
6072
|
-
exec:
|
|
6854
|
+
exec: o2.execCapture,
|
|
6073
6855
|
log
|
|
6074
6856
|
});
|
|
6075
6857
|
if (out.kind === "skipped")
|
|
@@ -6077,9 +6859,9 @@ var musherTick = async (o) => {
|
|
|
6077
6859
|
}
|
|
6078
6860
|
return 0;
|
|
6079
6861
|
};
|
|
6080
|
-
var musherConfigure = (
|
|
6081
|
-
const log =
|
|
6082
|
-
const cfg = readConfig(
|
|
6862
|
+
var musherConfigure = (o2, flags) => {
|
|
6863
|
+
const log = o2.log ?? console.log;
|
|
6864
|
+
const cfg = readConfig(o2.home);
|
|
6083
6865
|
const musher = { ...cfg.musher ?? {} };
|
|
6084
6866
|
if (flags.claude)
|
|
6085
6867
|
musher.claudePath = flags.claude;
|
|
@@ -6088,12 +6870,12 @@ var musherConfigure = (o, flags) => {
|
|
|
6088
6870
|
if (flags.timeout) {
|
|
6089
6871
|
const n = Number.parseInt(flags.timeout, 10);
|
|
6090
6872
|
if (!Number.isFinite(n) || n <= 0) {
|
|
6091
|
-
(
|
|
6873
|
+
(o2.error ?? console.error)("--timeout takes minutes, a positive number");
|
|
6092
6874
|
return 1;
|
|
6093
6875
|
}
|
|
6094
6876
|
musher.timeoutMinutes = n;
|
|
6095
6877
|
}
|
|
6096
|
-
writeConfig(
|
|
6878
|
+
writeConfig(o2.home, { ...cfg, musher });
|
|
6097
6879
|
log(`musher settings: claude ${musher.claudePath ?? "(PATH)"} · mode ${musher.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${musher.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
|
|
6098
6880
|
return 0;
|
|
6099
6881
|
};
|
|
@@ -6103,8 +6885,8 @@ var defaultExecSync = (cmd, args) => {
|
|
|
6103
6885
|
return { ok: false, stderr: r.error.message };
|
|
6104
6886
|
return { ok: r.status === 0, stderr: r.stderr ?? "" };
|
|
6105
6887
|
};
|
|
6106
|
-
var musher = async (
|
|
6107
|
-
const args = [...
|
|
6888
|
+
var musher = async (o2) => {
|
|
6889
|
+
const args = [...o2.argv];
|
|
6108
6890
|
const flags = {};
|
|
6109
6891
|
const positional = [];
|
|
6110
6892
|
for (let i = 0;i < args.length; i++) {
|
|
@@ -6120,16 +6902,16 @@ var musher = async (o) => {
|
|
|
6120
6902
|
positional.push(a);
|
|
6121
6903
|
}
|
|
6122
6904
|
if (flags.install)
|
|
6123
|
-
return musherInstall(
|
|
6905
|
+
return musherInstall(o2);
|
|
6124
6906
|
if (flags.uninstall)
|
|
6125
|
-
return musherUninstall(
|
|
6907
|
+
return musherUninstall(o2);
|
|
6126
6908
|
if (flags.claude || flags.mode || flags.timeout)
|
|
6127
|
-
return musherConfigure(
|
|
6909
|
+
return musherConfigure(o2, flags);
|
|
6128
6910
|
if (positional[0] === "tick")
|
|
6129
|
-
return musherTick(
|
|
6911
|
+
return musherTick(o2);
|
|
6130
6912
|
if (flags.status || positional.length === 0)
|
|
6131
|
-
return musherStatus(
|
|
6132
|
-
(
|
|
6913
|
+
return musherStatus(o2);
|
|
6914
|
+
(o2.error ?? console.error)(`sleds musher: unknown argument ${positional[0]}`);
|
|
6133
6915
|
return 1;
|
|
6134
6916
|
};
|
|
6135
6917
|
|
|
@@ -6177,12 +6959,12 @@ var systemdCalendar = (deadline) => `${deadline.getFullYear()}-${pad2(deadline.g
|
|
|
6177
6959
|
var scheduleRevert = (d, deadlineMs) => {
|
|
6178
6960
|
const deadline = new Date(deadlineMs);
|
|
6179
6961
|
if (d.platform === "darwin") {
|
|
6180
|
-
const
|
|
6962
|
+
const p2 = plistPath(d.home);
|
|
6181
6963
|
try {
|
|
6182
6964
|
mkdirSync10(join9(d.home, "Library", "LaunchAgents"), { recursive: true });
|
|
6183
|
-
d.exec("launchctl", ["unload",
|
|
6184
|
-
writeFileSync10(
|
|
6185
|
-
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]);
|
|
6186
6968
|
if (r.ok)
|
|
6187
6969
|
return { kind: "launchd", detail: `launchd agent ${REVERT_LABEL}` };
|
|
6188
6970
|
return { kind: "manual", detail: `launchctl load failed: ${r.stderr.trim() || "unknown"}` };
|
|
@@ -6207,10 +6989,10 @@ var scheduleRevert = (d, deadlineMs) => {
|
|
|
6207
6989
|
};
|
|
6208
6990
|
var cancelRevert = (d, kind) => {
|
|
6209
6991
|
if (kind === "launchd" || d.platform === "darwin") {
|
|
6210
|
-
const
|
|
6211
|
-
if (existsSync11(
|
|
6212
|
-
d.exec("launchctl", ["unload",
|
|
6213
|
-
rmSync3(
|
|
6992
|
+
const p2 = plistPath(d.home);
|
|
6993
|
+
if (existsSync11(p2)) {
|
|
6994
|
+
d.exec("launchctl", ["unload", p2]);
|
|
6995
|
+
rmSync3(p2, { force: true });
|
|
6214
6996
|
}
|
|
6215
6997
|
}
|
|
6216
6998
|
if (kind === "systemd" || d.platform === "linux") {
|
|
@@ -6220,15 +7002,15 @@ var cancelRevert = (d, kind) => {
|
|
|
6220
7002
|
|
|
6221
7003
|
// src/commands/off.ts
|
|
6222
7004
|
init_settings();
|
|
6223
|
-
var off = (
|
|
6224
|
-
const log =
|
|
6225
|
-
const cfg = readConfig(
|
|
6226
|
-
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);
|
|
6227
7009
|
const wasOn = cfg.mode === "on" || isPointedAtProxy(settings);
|
|
6228
7010
|
if (wasOn)
|
|
6229
|
-
writeSettings(
|
|
6230
|
-
if (
|
|
6231
|
-
cancelRevert(
|
|
7011
|
+
writeSettings(o2.home, switchOff(settings, cfg.priorEnv ?? {}));
|
|
7012
|
+
if (o2.scheduler)
|
|
7013
|
+
cancelRevert(o2.scheduler, cfg.schedule);
|
|
6232
7014
|
const next = {
|
|
6233
7015
|
...cfg,
|
|
6234
7016
|
mode: "off",
|
|
@@ -6238,7 +7020,7 @@ var off = (o) => {
|
|
|
6238
7020
|
priorEnv: undefined,
|
|
6239
7021
|
schedule: undefined
|
|
6240
7022
|
};
|
|
6241
|
-
writeConfig(
|
|
7023
|
+
writeConfig(o2.home, next);
|
|
6242
7024
|
if (!wasOn) {
|
|
6243
7025
|
log("Sleds already off. Claude Code is on your seat.");
|
|
6244
7026
|
return 0;
|
|
@@ -6257,19 +7039,19 @@ init_settings();
|
|
|
6257
7039
|
var DEFAULT_PROXY_URL2 = "https://proxy.sleds.ai";
|
|
6258
7040
|
|
|
6259
7041
|
// src/commands/on.ts
|
|
6260
|
-
var on = (
|
|
6261
|
-
const log =
|
|
6262
|
-
const error =
|
|
6263
|
-
const env =
|
|
6264
|
-
const now = (
|
|
6265
|
-
if (!hasClaudeCode(
|
|
6266
|
-
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.`);
|
|
6267
7049
|
return 1;
|
|
6268
7050
|
}
|
|
6269
|
-
const cfg = readConfig(
|
|
6270
|
-
const token = cfg.token ?? readTokenFile(
|
|
7051
|
+
const cfg = readConfig(o2.home);
|
|
7052
|
+
const token = cfg.token ?? readTokenFile(o2.home) ?? env.SLEDS_TOKEN?.trim();
|
|
6271
7053
|
if (!token) {
|
|
6272
|
-
error(`No seat token in ${configPath(
|
|
7054
|
+
error(`No seat token in ${configPath(o2.home)}. Run \`sleds init\` first.`);
|
|
6273
7055
|
return 2;
|
|
6274
7056
|
}
|
|
6275
7057
|
if (!isSeatToken(token)) {
|
|
@@ -6280,28 +7062,28 @@ var on = (o) => {
|
|
|
6280
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.`);
|
|
6281
7063
|
return 2;
|
|
6282
7064
|
}
|
|
6283
|
-
const url = (
|
|
7065
|
+
const url = (o2.url ?? cfg.proxyUrl ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL2).replace(/\/+$/, "");
|
|
6284
7066
|
let untilResetMs;
|
|
6285
|
-
if (
|
|
7067
|
+
if (o2.untilReset !== undefined) {
|
|
6286
7068
|
try {
|
|
6287
|
-
untilResetMs = parseUntilReset(
|
|
7069
|
+
untilResetMs = parseUntilReset(o2.untilReset, now);
|
|
6288
7070
|
} catch (err) {
|
|
6289
7071
|
error(err instanceof Error ? err.message : String(err));
|
|
6290
7072
|
return 2;
|
|
6291
7073
|
}
|
|
6292
7074
|
}
|
|
6293
|
-
const settings = readSettings(
|
|
7075
|
+
const settings = readSettings(o2.home);
|
|
6294
7076
|
const priorEnv = cfg.mode === "on" && cfg.priorEnv ? cfg.priorEnv : capturePriorEnv(settings);
|
|
6295
|
-
const backedUp = backupSettings(
|
|
6296
|
-
writeSettings(
|
|
7077
|
+
const backedUp = backupSettings(o2.home);
|
|
7078
|
+
writeSettings(o2.home, switchOn(settings, { url, token }));
|
|
6297
7079
|
let schedule;
|
|
6298
7080
|
let scheduleNote;
|
|
6299
|
-
if (untilResetMs !== undefined && !
|
|
7081
|
+
if (untilResetMs !== undefined && !o2.noSchedule && o2.scheduler && hasHomeOverride(env)) {
|
|
6300
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.");
|
|
6301
7083
|
return 2;
|
|
6302
7084
|
}
|
|
6303
|
-
if (untilResetMs !== undefined && !
|
|
6304
|
-
const r = scheduleRevert(
|
|
7085
|
+
if (untilResetMs !== undefined && !o2.noSchedule && o2.scheduler) {
|
|
7086
|
+
const r = scheduleRevert(o2.scheduler, untilResetMs);
|
|
6305
7087
|
schedule = r.kind;
|
|
6306
7088
|
scheduleNote = r.kind === "manual" ? r.detail : undefined;
|
|
6307
7089
|
}
|
|
@@ -6317,19 +7099,19 @@ var on = (o) => {
|
|
|
6317
7099
|
next.untilReset = new Date(untilResetMs).toISOString();
|
|
6318
7100
|
else
|
|
6319
7101
|
next.untilReset = undefined;
|
|
6320
|
-
next.reason =
|
|
7102
|
+
next.reason = o2.reason?.trim() || undefined;
|
|
6321
7103
|
next.schedule = schedule;
|
|
6322
|
-
writeConfig(
|
|
7104
|
+
writeConfig(o2.home, next);
|
|
6323
7105
|
log(`Sleds on. Claude Code now runs on the platform key through ${url}. Restart claude to apply.`);
|
|
6324
7106
|
if (untilResetMs !== undefined) {
|
|
6325
|
-
log(`Reverts at ${formatLocal(untilResetMs,
|
|
7107
|
+
log(`Reverts at ${formatLocal(untilResetMs, o2.tz)} — run \`sleds off\` earlier if you want.`);
|
|
6326
7108
|
if (schedule === undefined || schedule === "manual") {
|
|
6327
7109
|
const why = scheduleNote ? ` (${scheduleNote})` : "";
|
|
6328
7110
|
log(`Auto-revert not scheduled${why}. Run \`sleds off\` after the reset.`);
|
|
6329
7111
|
}
|
|
6330
7112
|
}
|
|
6331
7113
|
if (backedUp)
|
|
6332
|
-
log(`Backed up ${settingsPath(
|
|
7114
|
+
log(`Backed up ${settingsPath(o2.home)} to ${backupPath(o2.home)}.`);
|
|
6333
7115
|
return 0;
|
|
6334
7116
|
};
|
|
6335
7117
|
|
|
@@ -6377,22 +7159,22 @@ var exitsLine = (hasUpstreamKey) => hasUpstreamKey ? "`/sled handoff <what you w
|
|
|
6377
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.`;
|
|
6378
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.`;
|
|
6379
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.`;
|
|
6380
|
-
var promptEvent = async (
|
|
6381
|
-
const log =
|
|
6382
|
-
const error =
|
|
6383
|
-
const now = (
|
|
6384
|
-
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)
|
|
6385
7167
|
await readStdin(HOOK_STDIN_TIMEOUT_MS);
|
|
6386
7168
|
let state;
|
|
6387
7169
|
try {
|
|
6388
|
-
state = readSeatState(
|
|
7170
|
+
state = readSeatState(o2.home);
|
|
6389
7171
|
} catch {
|
|
6390
7172
|
return 0;
|
|
6391
7173
|
}
|
|
6392
7174
|
const pending = untold(state);
|
|
6393
7175
|
if (pending.length === 0)
|
|
6394
7176
|
return 0;
|
|
6395
|
-
const hasKey = readConfig(
|
|
7177
|
+
const hasKey = readConfig(o2.home).org?.capabilities.upstreamKey !== false;
|
|
6396
7178
|
const told = { ...state.told ?? {} };
|
|
6397
7179
|
const mark = (bar, stage, at) => {
|
|
6398
7180
|
told[bar] = { ...told[bar] ?? {}, [stage]: at };
|
|
@@ -6412,7 +7194,7 @@ var promptEvent = async (o) => {
|
|
|
6412
7194
|
lines.push(e.stage === "approaching" ? approachingContext(e.bar, pct, e.resetsAt, hasKey, now) : hitContext(e.bar, e.resetsAt, hasKey, now));
|
|
6413
7195
|
}
|
|
6414
7196
|
try {
|
|
6415
|
-
writeSeatState(
|
|
7197
|
+
writeSeatState(o2.home, { ...state, told });
|
|
6416
7198
|
} catch {}
|
|
6417
7199
|
if (code === 0 && lines.length > 0)
|
|
6418
7200
|
log(lines.join(`
|
|
@@ -6424,16 +7206,16 @@ var promptEvent = async (o) => {
|
|
|
6424
7206
|
init_config();
|
|
6425
7207
|
init_meter();
|
|
6426
7208
|
import { hostname as hostname4 } from "node:os";
|
|
6427
|
-
var run = async (
|
|
6428
|
-
const log =
|
|
6429
|
-
const error =
|
|
6430
|
-
if (!
|
|
7209
|
+
var run = async (o2) => {
|
|
7210
|
+
const log = o2.log ?? console.log;
|
|
7211
|
+
const error = o2.error ?? console.error;
|
|
7212
|
+
if (!o2.jobId) {
|
|
6431
7213
|
error("usage: sleds run <jobId>");
|
|
6432
7214
|
return 2;
|
|
6433
7215
|
}
|
|
6434
|
-
const client =
|
|
6435
|
-
const src = meterSource(
|
|
6436
|
-
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;
|
|
6437
7219
|
})();
|
|
6438
7220
|
if (!client) {
|
|
6439
7221
|
error("No seat on this machine. Run `sleds init` first.");
|
|
@@ -6444,13 +7226,13 @@ var run = async (o) => {
|
|
|
6444
7226
|
error("Could not reach the SLEDS proxy.");
|
|
6445
7227
|
return 1;
|
|
6446
7228
|
}
|
|
6447
|
-
const job = [...list.due, ...list.upcoming].find((j) => j.jobId ===
|
|
7229
|
+
const job = [...list.due, ...list.upcoming].find((j) => j.jobId === o2.jobId);
|
|
6448
7230
|
if (!job) {
|
|
6449
|
-
error(`Job ${
|
|
7231
|
+
error(`Job ${o2.jobId} is not a scheduled local job on this seat (sleds musher --status lists them).`);
|
|
6450
7232
|
return 1;
|
|
6451
7233
|
}
|
|
6452
|
-
const cfg = readConfig(
|
|
6453
|
-
const claude = findClaude(
|
|
7234
|
+
const cfg = readConfig(o2.home);
|
|
7235
|
+
const claude = findClaude(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich);
|
|
6454
7236
|
if (!claude) {
|
|
6455
7237
|
error("`claude` not found. Install Claude Code, or `sleds musher --claude /path/to/claude`.");
|
|
6456
7238
|
return 1;
|
|
@@ -6458,12 +7240,12 @@ var run = async (o) => {
|
|
|
6458
7240
|
const out = await runJob({
|
|
6459
7241
|
job,
|
|
6460
7242
|
client,
|
|
6461
|
-
fallbackSession: readSeatState(
|
|
6462
|
-
host:
|
|
7243
|
+
fallbackSession: readSeatState(o2.home).session,
|
|
7244
|
+
host: o2.host ?? hostname4(),
|
|
6463
7245
|
claudePath: claude,
|
|
6464
7246
|
permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
6465
7247
|
timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
|
|
6466
|
-
exec:
|
|
7248
|
+
exec: o2.execCapture,
|
|
6467
7249
|
log
|
|
6468
7250
|
});
|
|
6469
7251
|
if (out.kind === "skipped") {
|
|
@@ -6496,25 +7278,25 @@ var notificationType = (text) => {
|
|
|
6496
7278
|
return;
|
|
6497
7279
|
}
|
|
6498
7280
|
};
|
|
6499
|
-
var seatEvent = async (
|
|
6500
|
-
const log =
|
|
6501
|
-
const now = (
|
|
6502
|
-
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);
|
|
6503
7285
|
const kind = notificationType(input);
|
|
6504
7286
|
try {
|
|
6505
|
-
const state = readSeatState(
|
|
7287
|
+
const state = readSeatState(o2.home);
|
|
6506
7288
|
const week = state.window?.sevenDay;
|
|
6507
|
-
const src = meterSource(
|
|
7289
|
+
const src = meterSource(o2.home);
|
|
6508
7290
|
if (src) {
|
|
6509
|
-
await postSeatWall({ ...src, fetchFn:
|
|
7291
|
+
await postSeatWall({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
|
|
6510
7292
|
...week ? { resetsAt: week.resetsAt, usedPct: week.usedPct } : {},
|
|
6511
7293
|
...kind ? { notification: kind } : {}
|
|
6512
7294
|
});
|
|
6513
7295
|
}
|
|
6514
7296
|
if (week && week.resetsAt > now)
|
|
6515
|
-
writeSeatState(
|
|
7297
|
+
writeSeatState(o2.home, { ...state, lastWallResetsAt: week.resetsAt });
|
|
6516
7298
|
} catch {}
|
|
6517
|
-
const line = seatWallLine(readConfig(
|
|
7299
|
+
const line = seatWallLine(readConfig(o2.home).org?.capabilities.upstreamKey !== false);
|
|
6518
7300
|
log(line);
|
|
6519
7301
|
return 0;
|
|
6520
7302
|
};
|
|
@@ -6534,11 +7316,11 @@ var isBun = () => typeof globalThis.Bun !== "undefined";
|
|
|
6534
7316
|
async function queryRows(dbPath) {
|
|
6535
7317
|
if (isBun()) {
|
|
6536
7318
|
const { Database } = await import("bun:sqlite");
|
|
6537
|
-
const
|
|
7319
|
+
const conn2 = new Database(dbPath, { readonly: true });
|
|
6538
7320
|
try {
|
|
6539
|
-
return
|
|
7321
|
+
return conn2.query(QUERY).all();
|
|
6540
7322
|
} finally {
|
|
6541
|
-
|
|
7323
|
+
conn2.close();
|
|
6542
7324
|
}
|
|
6543
7325
|
}
|
|
6544
7326
|
const mod = nodeSqlite();
|
|
@@ -6644,19 +7426,19 @@ function parseSeatSyncArgs(argv) {
|
|
|
6644
7426
|
return { error: `--profile does not apply to --browser ${source.browser}` };
|
|
6645
7427
|
return { source };
|
|
6646
7428
|
}
|
|
6647
|
-
function resolveProfileDir(localStateJson,
|
|
7429
|
+
function resolveProfileDir(localStateJson, profile3) {
|
|
6648
7430
|
let cache = {};
|
|
6649
7431
|
if (localStateJson) {
|
|
6650
7432
|
try {
|
|
6651
7433
|
const raw = JSON.parse(localStateJson);
|
|
6652
|
-
const
|
|
6653
|
-
if (
|
|
6654
|
-
cache =
|
|
7434
|
+
const p2 = isObject11(raw) && isObject11(raw.profile) ? raw.profile : null;
|
|
7435
|
+
if (p2 && isObject11(p2.info_cache))
|
|
7436
|
+
cache = p2.info_cache;
|
|
6655
7437
|
} catch {}
|
|
6656
7438
|
}
|
|
6657
|
-
if (Object.hasOwn(cache,
|
|
6658
|
-
return { dir:
|
|
6659
|
-
const want =
|
|
7439
|
+
if (Object.hasOwn(cache, profile3))
|
|
7440
|
+
return { dir: profile3 };
|
|
7441
|
+
const want = profile3.toLowerCase();
|
|
6660
7442
|
const hits = [];
|
|
6661
7443
|
for (const [dir, info] of Object.entries(cache)) {
|
|
6662
7444
|
if (!isObject11(info))
|
|
@@ -6672,7 +7454,7 @@ function resolveProfileDir(localStateJson, profile) {
|
|
|
6672
7454
|
return { dir: hits[0] ?? null };
|
|
6673
7455
|
if (hits.length > 1)
|
|
6674
7456
|
return { dir: null, ambiguous: hits };
|
|
6675
|
-
return { dir: Object.keys(cache).length === 0 ?
|
|
7457
|
+
return { dir: Object.keys(cache).length === 0 ? profile3 : null };
|
|
6676
7458
|
}
|
|
6677
7459
|
function cookieDbCandidates(profileDir) {
|
|
6678
7460
|
return [`${profileDir}/Network/Cookies`, `${profileDir}/Cookies`];
|
|
@@ -6842,11 +7624,11 @@ function findFile(root, name) {
|
|
|
6842
7624
|
continue;
|
|
6843
7625
|
}
|
|
6844
7626
|
for (const e of entries) {
|
|
6845
|
-
const
|
|
7627
|
+
const p2 = join11(dir, e.name);
|
|
6846
7628
|
if (e.isDirectory())
|
|
6847
|
-
stack.push(
|
|
7629
|
+
stack.push(p2);
|
|
6848
7630
|
else if (e.name === name)
|
|
6849
|
-
return
|
|
7631
|
+
return p2;
|
|
6850
7632
|
}
|
|
6851
7633
|
}
|
|
6852
7634
|
return null;
|
|
@@ -6857,10 +7639,10 @@ function findCookieDb(home, source) {
|
|
|
6857
7639
|
if (!existsSync12(root))
|
|
6858
7640
|
throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
|
|
6859
7641
|
if (!b.profiled) {
|
|
6860
|
-
const
|
|
6861
|
-
if (!
|
|
7642
|
+
const db2 = findFile(root, "Cookies");
|
|
7643
|
+
if (!db2)
|
|
6862
7644
|
throw new Error(`no cookie store under ${root}`);
|
|
6863
|
-
return
|
|
7645
|
+
return db2;
|
|
6864
7646
|
}
|
|
6865
7647
|
const localStatePath = join11(root, "Local State");
|
|
6866
7648
|
const localState = existsSync12(localStatePath) ? readFileSync10(localStatePath, "utf8") : null;
|
|
@@ -6870,7 +7652,7 @@ function findCookieDb(home, source) {
|
|
|
6870
7652
|
throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
|
|
6871
7653
|
if (!dir)
|
|
6872
7654
|
throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
|
|
6873
|
-
const db = cookieDbCandidates(join11(root, dir)).find((
|
|
7655
|
+
const db = cookieDbCandidates(join11(root, dir)).find((p2) => existsSync12(p2));
|
|
6874
7656
|
if (!db)
|
|
6875
7657
|
throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
|
|
6876
7658
|
return db;
|
|
@@ -6901,8 +7683,8 @@ var curlUsage = (org, cookie) => {
|
|
|
6901
7683
|
return { status: 0, body: "" };
|
|
6902
7684
|
}
|
|
6903
7685
|
};
|
|
6904
|
-
function fetchSeatUsage(jar, org, now,
|
|
6905
|
-
const { status, body } =
|
|
7686
|
+
function fetchSeatUsage(jar, org, now, run2) {
|
|
7687
|
+
const { status, body } = run2(org, cookieHeader(jar, org));
|
|
6906
7688
|
if (status !== 200)
|
|
6907
7689
|
return { usage: null, status };
|
|
6908
7690
|
try {
|
|
@@ -6963,24 +7745,24 @@ function uninstall(home, source, log) {
|
|
|
6963
7745
|
log(existsSync12(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
|
|
6964
7746
|
return 0;
|
|
6965
7747
|
}
|
|
6966
|
-
var seatSync = async (
|
|
6967
|
-
const log =
|
|
6968
|
-
const now = (
|
|
6969
|
-
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));
|
|
6970
7752
|
if ("error" in parsed) {
|
|
6971
7753
|
log(`seat-sync: ${parsed.error}`);
|
|
6972
7754
|
return 1;
|
|
6973
7755
|
}
|
|
6974
7756
|
const { source } = parsed;
|
|
6975
7757
|
if (source.uninstall)
|
|
6976
|
-
return uninstall(
|
|
7758
|
+
return uninstall(o2.home, source, log);
|
|
6977
7759
|
if (source.install)
|
|
6978
|
-
return install(
|
|
6979
|
-
if (platform2() !== "darwin" && !
|
|
7760
|
+
return install(o2.home, [process.execPath, process.argv[1] ?? ""], source, log);
|
|
7761
|
+
if (platform2() !== "darwin" && !o2.readCookieRows) {
|
|
6980
7762
|
log("seat-sync is macOS-only for now.");
|
|
6981
7763
|
return 2;
|
|
6982
7764
|
}
|
|
6983
|
-
const saved = meterSource(
|
|
7765
|
+
const saved = meterSource(o2.home);
|
|
6984
7766
|
const proxyUrl = saved?.proxyUrl;
|
|
6985
7767
|
const token = source.token ?? saved?.token;
|
|
6986
7768
|
if (!proxyUrl || !token) {
|
|
@@ -6989,13 +7771,13 @@ var seatSync = async (o) => {
|
|
|
6989
7771
|
}
|
|
6990
7772
|
let rows;
|
|
6991
7773
|
try {
|
|
6992
|
-
rows =
|
|
7774
|
+
rows = o2.readCookieRows ? o2.readCookieRows(o2.home, source) : await readCookieRowsBun(o2.home, source);
|
|
6993
7775
|
} catch (e) {
|
|
6994
7776
|
log(`seat-sync: ${e instanceof Error ? e.message : String(e)}`);
|
|
6995
7777
|
return 1;
|
|
6996
7778
|
}
|
|
6997
7779
|
const service = BROWSERS[source.browser].keychainService;
|
|
6998
|
-
const pw = (
|
|
7780
|
+
const pw = (o2.safeStorageKey ?? keychainKey)(service);
|
|
6999
7781
|
if (!pw) {
|
|
7000
7782
|
log(`seat-sync: could not read the "${service}" key from your Keychain.`);
|
|
7001
7783
|
return 1;
|
|
@@ -7015,16 +7797,16 @@ var seatSync = async (o) => {
|
|
|
7015
7797
|
log("seat-sync: no active org in the session cookie (pass --org <uuid>).");
|
|
7016
7798
|
return 1;
|
|
7017
7799
|
}
|
|
7018
|
-
const { usage, status } = fetchSeatUsage(jar, org, now,
|
|
7800
|
+
const { usage, status } = fetchSeatUsage(jar, org, now, o2.runUsage ?? curlUsage);
|
|
7019
7801
|
if (!usage) {
|
|
7020
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}.`;
|
|
7021
7803
|
log(`seat-sync: usage read failed — ${why}`);
|
|
7022
7804
|
return 2;
|
|
7023
7805
|
}
|
|
7024
7806
|
try {
|
|
7025
|
-
writeSeatState(
|
|
7807
|
+
writeSeatState(o2.home, { ...readSeatState(o2.home), window: usageAsWindow(usage) });
|
|
7026
7808
|
} catch {}
|
|
7027
|
-
const posted = await postSeat(proxyUrl, token, seatSyncBody(usage),
|
|
7809
|
+
const posted = await postSeat(proxyUrl, token, seatSyncBody(usage), o2.fetchFn ?? fetch);
|
|
7028
7810
|
const wk = usage.sevenDay ? `wk ${Math.round(usage.sevenDay.usedPct)}%` : "wk —";
|
|
7029
7811
|
const models = Object.keys(usage.models).length;
|
|
7030
7812
|
const credits = usage.usedCreditsUsd !== undefined ? ` · $${usage.usedCreditsUsd.toFixed(2)}` : "";
|
|
@@ -7076,14 +7858,14 @@ function sessionContext(a) {
|
|
|
7076
7858
|
return lines.join(`
|
|
7077
7859
|
`);
|
|
7078
7860
|
}
|
|
7079
|
-
var sessionEvent = async (
|
|
7080
|
-
const log =
|
|
7081
|
-
const now = (
|
|
7082
|
-
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);
|
|
7083
7865
|
const payload = parseSessionPayload(raw);
|
|
7084
7866
|
if (payload.session_id) {
|
|
7085
7867
|
try {
|
|
7086
|
-
writeSession(
|
|
7868
|
+
writeSession(o2.home, {
|
|
7087
7869
|
sessionId: payload.session_id,
|
|
7088
7870
|
startedAt: now,
|
|
7089
7871
|
...payload.source ? { source: payload.source } : {}
|
|
@@ -7093,13 +7875,13 @@ var sessionEvent = async (o) => {
|
|
|
7093
7875
|
let email;
|
|
7094
7876
|
let org;
|
|
7095
7877
|
try {
|
|
7096
|
-
const cfg = readConfig(
|
|
7878
|
+
const cfg = readConfig(o2.home);
|
|
7097
7879
|
email = cfg.email;
|
|
7098
7880
|
org = cfg.org?.slug;
|
|
7099
7881
|
} catch {}
|
|
7100
7882
|
let owed = [];
|
|
7101
7883
|
try {
|
|
7102
|
-
owed = readOwed(
|
|
7884
|
+
owed = readOwed(o2.home, now);
|
|
7103
7885
|
} catch {
|
|
7104
7886
|
owed = [];
|
|
7105
7887
|
}
|
|
@@ -7144,13 +7926,13 @@ function parseHookInput(raw) {
|
|
|
7144
7926
|
}
|
|
7145
7927
|
if (typeof parsed !== "object" || parsed === null)
|
|
7146
7928
|
return null;
|
|
7147
|
-
const
|
|
7148
|
-
const toolName = typeof
|
|
7149
|
-
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 : {};
|
|
7150
7932
|
const filePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
7151
7933
|
if (!filePath)
|
|
7152
7934
|
return null;
|
|
7153
|
-
const cwd = typeof
|
|
7935
|
+
const cwd = typeof p2.cwd === "string" ? p2.cwd : "";
|
|
7154
7936
|
return { toolName, filePath, cwd };
|
|
7155
7937
|
}
|
|
7156
7938
|
function projectKey(filePath, cwd) {
|
|
@@ -7170,16 +7952,16 @@ function readFileOrNull(path) {
|
|
|
7170
7952
|
return null;
|
|
7171
7953
|
}
|
|
7172
7954
|
}
|
|
7173
|
-
var specEvent = async (
|
|
7174
|
-
const log =
|
|
7175
|
-
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);
|
|
7176
7958
|
const input = raw ? parseHookInput(raw) : null;
|
|
7177
7959
|
if (!input || !isSpecPath(input.filePath))
|
|
7178
7960
|
return 0;
|
|
7179
|
-
const src = meterSource(
|
|
7961
|
+
const src = meterSource(o2.home);
|
|
7180
7962
|
if (!src)
|
|
7181
7963
|
return 0;
|
|
7182
|
-
const body = (
|
|
7964
|
+
const body = (o2.readFile ?? readFileOrNull)(input.filePath);
|
|
7183
7965
|
if (!body || !looksLikeSpec(body))
|
|
7184
7966
|
return 0;
|
|
7185
7967
|
const project = projectKey(input.filePath, input.cwd);
|
|
@@ -7189,7 +7971,7 @@ var specEvent = async (o) => {
|
|
|
7189
7971
|
repoHint: input.cwd ? basename2(input.cwd) : undefined
|
|
7190
7972
|
};
|
|
7191
7973
|
try {
|
|
7192
|
-
const res = await (
|
|
7974
|
+
const res = await (o2.fetchFn ?? fetch)(`${src.proxyUrl}/proxy/estimate`, {
|
|
7193
7975
|
method: "POST",
|
|
7194
7976
|
headers: { authorization: `Bearer ${src.token}`, "content-type": "application/json" },
|
|
7195
7977
|
body: JSON.stringify({ spec, project }),
|
|
@@ -7204,15 +7986,15 @@ var specEvent = async (o) => {
|
|
|
7204
7986
|
log(`sleds: ${project} sized at $${b.lowUsd.toFixed(2)}–${b.highUsd.toFixed(2)} · ${min(b.lowMinutes)}–${min(b.highMinutes)} min`);
|
|
7205
7987
|
if (typeof b.lowMinutes === "number" && typeof b.highMinutes === "number") {
|
|
7206
7988
|
try {
|
|
7207
|
-
writeSeatState(
|
|
7208
|
-
...readSeatState(
|
|
7989
|
+
writeSeatState(o2.home, {
|
|
7990
|
+
...readSeatState(o2.home),
|
|
7209
7991
|
estimate: {
|
|
7210
7992
|
project,
|
|
7211
7993
|
lowUsd: b.lowUsd,
|
|
7212
7994
|
highUsd: b.highUsd,
|
|
7213
7995
|
lowMin: b.lowMinutes,
|
|
7214
7996
|
highMin: b.highMinutes,
|
|
7215
|
-
at: (
|
|
7997
|
+
at: (o2.now ?? Date.now)()
|
|
7216
7998
|
}
|
|
7217
7999
|
});
|
|
7218
8000
|
} catch {}
|
|
@@ -7226,7 +8008,8 @@ var specEvent = async (o) => {
|
|
|
7226
8008
|
init_config();
|
|
7227
8009
|
init_settings();
|
|
7228
8010
|
import { existsSync as existsSync13 } from "node:fs";
|
|
7229
|
-
var
|
|
8011
|
+
var HEALTH_TIMEOUT_MS2 = 4000;
|
|
8012
|
+
var checkHealth2 = async (url, fetchFn = fetch, timeoutMs = HEALTH_TIMEOUT_MS2) => {
|
|
7230
8013
|
try {
|
|
7231
8014
|
const res = await fetchFn(`${url}/healthz`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
7232
8015
|
return res.ok ? "ok" : "unreachable";
|
|
@@ -7234,7 +8017,7 @@ var checkHealth2 = async (url, fetchFn = fetch, timeoutMs = 1000) => {
|
|
|
7234
8017
|
return "unreachable";
|
|
7235
8018
|
}
|
|
7236
8019
|
};
|
|
7237
|
-
var fetchProxyStatus = async (url, fetchFn = fetch, timeoutMs =
|
|
8020
|
+
var fetchProxyStatus = async (url, fetchFn = fetch, timeoutMs = HEALTH_TIMEOUT_MS2) => {
|
|
7238
8021
|
try {
|
|
7239
8022
|
const res = await fetchFn(`${url}/status`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
7240
8023
|
if (!res.ok)
|
|
@@ -7295,16 +8078,16 @@ var formatStatus = (s) => {
|
|
|
7295
8078
|
}
|
|
7296
8079
|
return lines;
|
|
7297
8080
|
};
|
|
7298
|
-
var status = async (
|
|
7299
|
-
const log =
|
|
7300
|
-
const nowMs = (
|
|
7301
|
-
const cfg = readConfig(
|
|
7302
|
-
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) : {};
|
|
7303
8086
|
const inSettings = readProxy(settings);
|
|
7304
8087
|
const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
|
|
7305
8088
|
const token = inSettings?.token ?? cfg.token;
|
|
7306
8089
|
if (!proxyUrl && !token && cfg.mode === undefined) {
|
|
7307
|
-
log(`SLEDS is not set up (${configPath(
|
|
8090
|
+
log(`SLEDS is not set up (${configPath(o2.home)} and ${settingsPath(o2.home)}).`);
|
|
7308
8091
|
log("Run `sleds init` first.");
|
|
7309
8092
|
return 1;
|
|
7310
8093
|
}
|
|
@@ -7312,8 +8095,8 @@ var status = async (o) => {
|
|
|
7312
8095
|
mode: cfg.mode ?? "off",
|
|
7313
8096
|
pointed: inSettings !== null,
|
|
7314
8097
|
nowMs,
|
|
7315
|
-
settingsFile: settingsPath(
|
|
7316
|
-
configFile: configPath(
|
|
8098
|
+
settingsFile: settingsPath(o2.home),
|
|
8099
|
+
configFile: configPath(o2.home)
|
|
7317
8100
|
};
|
|
7318
8101
|
if (proxyUrl)
|
|
7319
8102
|
info.proxyUrl = proxyUrl;
|
|
@@ -7333,8 +8116,8 @@ var status = async (o) => {
|
|
|
7333
8116
|
};
|
|
7334
8117
|
if (cfg.reason)
|
|
7335
8118
|
info.reason = cfg.reason;
|
|
7336
|
-
if (
|
|
7337
|
-
info.tz =
|
|
8119
|
+
if (o2.tz)
|
|
8120
|
+
info.tz = o2.tz;
|
|
7338
8121
|
const since = cfg.since ? Date.parse(cfg.since) : Number.NaN;
|
|
7339
8122
|
if (!Number.isNaN(since))
|
|
7340
8123
|
info.sinceMs = since;
|
|
@@ -7343,8 +8126,8 @@ var status = async (o) => {
|
|
|
7343
8126
|
info.untilResetMs = until;
|
|
7344
8127
|
if (proxyUrl) {
|
|
7345
8128
|
const [health, proxy] = await Promise.all([
|
|
7346
|
-
checkHealth2(proxyUrl,
|
|
7347
|
-
fetchProxyStatus(proxyUrl,
|
|
8129
|
+
checkHealth2(proxyUrl, o2.fetchFn, o2.timeoutMs),
|
|
8130
|
+
fetchProxyStatus(proxyUrl, o2.fetchFn, o2.timeoutMs)
|
|
7348
8131
|
]);
|
|
7349
8132
|
info.health = health;
|
|
7350
8133
|
info.proxy = proxy;
|
|
@@ -7357,62 +8140,62 @@ var status = async (o) => {
|
|
|
7357
8140
|
// src/commands/statusline.ts
|
|
7358
8141
|
init_config();
|
|
7359
8142
|
init_meter();
|
|
7360
|
-
var statusline = async (
|
|
7361
|
-
const log =
|
|
7362
|
-
const now = (
|
|
7363
|
-
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);
|
|
7364
8147
|
const window = seatWindowFromStdin(input, now);
|
|
7365
8148
|
const session = seatSessionFromStdin(input, now);
|
|
7366
8149
|
let posts = [];
|
|
7367
8150
|
let events = [];
|
|
7368
8151
|
try {
|
|
7369
8152
|
if (window)
|
|
7370
|
-
({ posts, events } = rememberSeat(
|
|
8153
|
+
({ posts, events } = rememberSeat(o2, window, session, now, input));
|
|
7371
8154
|
else {
|
|
7372
|
-
const st = readSeatState(
|
|
7373
|
-
const credits = creditsEvent(
|
|
8155
|
+
const st = readSeatState(o2.home);
|
|
8156
|
+
const credits = creditsEvent(o2, st, input, now);
|
|
7374
8157
|
if (session || credits.scan) {
|
|
7375
8158
|
const next = {
|
|
7376
8159
|
...st,
|
|
7377
8160
|
...session ? { session } : {},
|
|
7378
8161
|
...credits.scan ? { transcriptScan: credits.scan } : {}
|
|
7379
8162
|
};
|
|
7380
|
-
writeSeatState(
|
|
8163
|
+
writeSeatState(o2.home, credits.event ? markWarned(next, [credits.event]) : next);
|
|
7381
8164
|
}
|
|
7382
8165
|
if (credits.event) {
|
|
7383
8166
|
events = [credits.event];
|
|
7384
|
-
posts = announce(
|
|
8167
|
+
posts = announce(o2, events);
|
|
7385
8168
|
}
|
|
7386
8169
|
}
|
|
7387
8170
|
} catch {}
|
|
7388
8171
|
const seatFragment = formatSeatFragment(window, now);
|
|
7389
8172
|
let line;
|
|
7390
8173
|
try {
|
|
7391
|
-
const
|
|
7392
|
-
home:
|
|
8174
|
+
const meter2 = await loadMeter({
|
|
8175
|
+
home: o2.home,
|
|
7393
8176
|
now,
|
|
7394
|
-
fetchFn:
|
|
7395
|
-
timeoutMs:
|
|
8177
|
+
fetchFn: o2.fetchFn,
|
|
8178
|
+
timeoutMs: o2.timeoutMs
|
|
7396
8179
|
});
|
|
7397
|
-
line = formatMeterLine(
|
|
8180
|
+
line = formatMeterLine(meter2, now, seatFragment);
|
|
7398
8181
|
} catch {
|
|
7399
8182
|
line = formatMeterLine(null, now, seatFragment);
|
|
7400
8183
|
}
|
|
7401
8184
|
log(line);
|
|
7402
|
-
posts.push(speakToCockpit(
|
|
8185
|
+
posts.push(speakToCockpit(o2, window, events, now));
|
|
7403
8186
|
await Promise.allSettled(posts);
|
|
7404
8187
|
return 0;
|
|
7405
8188
|
};
|
|
7406
|
-
var speakToCockpit = async (
|
|
7407
|
-
const cockpit = detectCockpit(
|
|
8189
|
+
var speakToCockpit = async (o2, window, events, now) => {
|
|
8190
|
+
const cockpit = detectCockpit(o2.env ?? process.env);
|
|
7408
8191
|
if (!cockpit)
|
|
7409
8192
|
return;
|
|
7410
8193
|
try {
|
|
7411
|
-
const st = readSeatState(
|
|
8194
|
+
const st = readSeatState(o2.home);
|
|
7412
8195
|
let feed;
|
|
7413
|
-
const src = meterSource(
|
|
8196
|
+
const src = meterSource(o2.home);
|
|
7414
8197
|
if (src && now - (st.cockpit?.feed?.at ?? 0) >= FEED_MIN_INTERVAL_MS) {
|
|
7415
|
-
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);
|
|
7416
8199
|
if (got)
|
|
7417
8200
|
feed = got;
|
|
7418
8201
|
}
|
|
@@ -7423,32 +8206,32 @@ var speakToCockpit = async (o, window, events, now) => {
|
|
|
7423
8206
|
const changed = r.argv.length > 0 || feed !== undefined;
|
|
7424
8207
|
if (!changed)
|
|
7425
8208
|
return;
|
|
7426
|
-
writeSeatState(
|
|
8209
|
+
writeSeatState(o2.home, { ...readSeatState(o2.home), cockpit: r.state });
|
|
7427
8210
|
if (r.argv.length)
|
|
7428
|
-
await runCockpit(cockpit, r.argv, { spawn:
|
|
8211
|
+
await runCockpit(cockpit, r.argv, { spawn: o2.spawn });
|
|
7429
8212
|
} catch {}
|
|
7430
8213
|
};
|
|
7431
|
-
var rememberSeat = (
|
|
7432
|
-
const prior = readSeatState(
|
|
8214
|
+
var rememberSeat = (o2, window, session, now, input) => {
|
|
8215
|
+
const prior = readSeatState(o2.home);
|
|
7433
8216
|
let state = { ...prior, window, ...session ? { session } : {} };
|
|
7434
|
-
const src = meterSource(
|
|
8217
|
+
const src = meterSource(o2.home);
|
|
7435
8218
|
const posts = [];
|
|
7436
8219
|
if (src && shouldPostSeat(prior, window, now)) {
|
|
7437
8220
|
state = markPosted(state, window, now);
|
|
7438
|
-
posts.push(postSeatWindow({ ...src, fetchFn:
|
|
8221
|
+
posts.push(postSeatWindow({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, window));
|
|
7439
8222
|
}
|
|
7440
8223
|
const events = seatWallEvents(prior, window);
|
|
7441
|
-
const credits = creditsEvent(
|
|
8224
|
+
const credits = creditsEvent(o2, prior, input, now);
|
|
7442
8225
|
if (credits.scan)
|
|
7443
8226
|
state = { ...state, transcriptScan: credits.scan };
|
|
7444
8227
|
if (credits.event)
|
|
7445
8228
|
events.push(credits.event);
|
|
7446
8229
|
state = markWarned(state, events);
|
|
7447
|
-
posts.push(...announce(
|
|
7448
|
-
writeSeatState(
|
|
8230
|
+
posts.push(...announce(o2, events));
|
|
8231
|
+
writeSeatState(o2.home, state);
|
|
7449
8232
|
return { posts, events };
|
|
7450
8233
|
};
|
|
7451
|
-
var creditsEvent = (
|
|
8234
|
+
var creditsEvent = (o2, prior, input, now) => {
|
|
7452
8235
|
const path = transcriptPathFromStdin(input);
|
|
7453
8236
|
if (!path)
|
|
7454
8237
|
return { event: null, scan: undefined };
|
|
@@ -7458,13 +8241,13 @@ var creditsEvent = (o, prior, input, now) => {
|
|
|
7458
8241
|
return { event: null, scan: next };
|
|
7459
8242
|
return { event: { bar: "credits", stage: "hit", usedPct: 100, resetsAt: now }, scan: next };
|
|
7460
8243
|
};
|
|
7461
|
-
var announce = (
|
|
8244
|
+
var announce = (o2, events) => {
|
|
7462
8245
|
if (events.length === 0)
|
|
7463
8246
|
return [];
|
|
7464
|
-
const src = meterSource(
|
|
8247
|
+
const src = meterSource(o2.home);
|
|
7465
8248
|
if (!src)
|
|
7466
8249
|
return [];
|
|
7467
|
-
return events.map((e) => postSeatWall({ ...src, fetchFn:
|
|
8250
|
+
return events.map((e) => postSeatWall({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
|
|
7468
8251
|
bar: e.bar,
|
|
7469
8252
|
stage: e.stage,
|
|
7470
8253
|
usedPct: e.usedPct,
|
|
@@ -7474,6 +8257,128 @@ var announce = (o, events) => {
|
|
|
7474
8257
|
};
|
|
7475
8258
|
|
|
7476
8259
|
// src/commands/stopEvent.ts
|
|
8260
|
+
init_meter();
|
|
8261
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "node:fs";
|
|
8262
|
+
|
|
8263
|
+
// src/transcript.ts
|
|
8264
|
+
var isObject12 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8265
|
+
var n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
8266
|
+
function sumTranscript(text) {
|
|
8267
|
+
const seen = new Set;
|
|
8268
|
+
const turn = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
8269
|
+
let sub = 0;
|
|
8270
|
+
let messages = 0;
|
|
8271
|
+
const byModel = new Map;
|
|
8272
|
+
const agents2 = new Set;
|
|
8273
|
+
let anySidechain = false;
|
|
8274
|
+
for (const line of text.split(`
|
|
8275
|
+
`)) {
|
|
8276
|
+
const t = line.trim();
|
|
8277
|
+
if (!t)
|
|
8278
|
+
continue;
|
|
8279
|
+
let j;
|
|
8280
|
+
try {
|
|
8281
|
+
j = JSON.parse(t);
|
|
8282
|
+
} catch {
|
|
8283
|
+
continue;
|
|
8284
|
+
}
|
|
8285
|
+
if (!isObject12(j) || j.type !== "assistant" || !isObject12(j.message))
|
|
8286
|
+
continue;
|
|
8287
|
+
const m = j.message;
|
|
8288
|
+
const id = typeof m.id === "string" ? m.id : typeof j.uuid === "string" ? j.uuid : undefined;
|
|
8289
|
+
if (id) {
|
|
8290
|
+
if (seen.has(id))
|
|
8291
|
+
continue;
|
|
8292
|
+
seen.add(id);
|
|
8293
|
+
}
|
|
8294
|
+
const u = isObject12(m.usage) ? m.usage : undefined;
|
|
8295
|
+
if (!u)
|
|
8296
|
+
continue;
|
|
8297
|
+
const input = n(u.input_tokens);
|
|
8298
|
+
const output = n(u.output_tokens);
|
|
8299
|
+
const cacheRead = n(u.cache_read_input_tokens);
|
|
8300
|
+
const cacheWrite = n(u.cache_creation_input_tokens);
|
|
8301
|
+
messages++;
|
|
8302
|
+
const model2 = typeof m.model === "string" ? m.model : undefined;
|
|
8303
|
+
if (model2)
|
|
8304
|
+
byModel.set(model2, (byModel.get(model2) ?? 0) + output);
|
|
8305
|
+
if (j.isSidechain === true) {
|
|
8306
|
+
anySidechain = true;
|
|
8307
|
+
sub += input + output + cacheRead + cacheWrite;
|
|
8308
|
+
if (typeof j.agentId === "string")
|
|
8309
|
+
agents2.add(j.agentId);
|
|
8310
|
+
} else {
|
|
8311
|
+
turn.input += input;
|
|
8312
|
+
turn.output += output;
|
|
8313
|
+
turn.cacheRead += cacheRead;
|
|
8314
|
+
turn.cacheWrite += cacheWrite;
|
|
8315
|
+
}
|
|
8316
|
+
}
|
|
8317
|
+
let model;
|
|
8318
|
+
let best = -1;
|
|
8319
|
+
for (const [k, v] of byModel)
|
|
8320
|
+
if (v > best)
|
|
8321
|
+
[model, best] = [k, v];
|
|
8322
|
+
return {
|
|
8323
|
+
turnTokens: turn,
|
|
8324
|
+
subagentTokens: sub,
|
|
8325
|
+
messages,
|
|
8326
|
+
...model ? { model } : {},
|
|
8327
|
+
subagentRuns: agents2.size > 0 ? agents2.size : anySidechain ? 1 : 0
|
|
8328
|
+
};
|
|
8329
|
+
}
|
|
8330
|
+
function sessionBody(sessionId, sum, a) {
|
|
8331
|
+
return {
|
|
8332
|
+
capturedAt: a.capturedAt,
|
|
8333
|
+
sessions: [
|
|
8334
|
+
{
|
|
8335
|
+
sessionId,
|
|
8336
|
+
...a.title ? { title: a.title.slice(0, 200) } : {},
|
|
8337
|
+
updatedAt: a.capturedAt,
|
|
8338
|
+
subagentTokens: sum.subagentTokens,
|
|
8339
|
+
taskCount: sum.subagentRuns,
|
|
8340
|
+
turnTokens: sum.turnTokens,
|
|
8341
|
+
tasks: []
|
|
8342
|
+
}
|
|
8343
|
+
]
|
|
8344
|
+
};
|
|
8345
|
+
}
|
|
8346
|
+
|
|
8347
|
+
// src/commands/stopEvent.ts
|
|
8348
|
+
var SESSION_POST_TIMEOUT_MS = 2500;
|
|
8349
|
+
async function postTranscript(o2, payload, now) {
|
|
8350
|
+
const sessionId = payload.session_id?.trim();
|
|
8351
|
+
const path = payload.transcript_path?.trim();
|
|
8352
|
+
if (!sessionId || !path)
|
|
8353
|
+
return false;
|
|
8354
|
+
const src = meterSource(o2.home);
|
|
8355
|
+
if (!src)
|
|
8356
|
+
return false;
|
|
8357
|
+
const read = o2.readFile ?? ((p2) => existsSync14(p2) ? readFileSync12(p2, "utf8") : null);
|
|
8358
|
+
let text;
|
|
8359
|
+
try {
|
|
8360
|
+
text = read(path);
|
|
8361
|
+
} catch {
|
|
8362
|
+
return false;
|
|
8363
|
+
}
|
|
8364
|
+
if (!text)
|
|
8365
|
+
return false;
|
|
8366
|
+
const sum = sumTranscript(text);
|
|
8367
|
+
if (sum.messages === 0)
|
|
8368
|
+
return false;
|
|
8369
|
+
const fetchFn = o2.fetchFn ?? fetch;
|
|
8370
|
+
try {
|
|
8371
|
+
const res = await fetchFn(`${src.proxyUrl.replace(/\/+$/, "")}/proxy/sessions`, {
|
|
8372
|
+
method: "POST",
|
|
8373
|
+
headers: { authorization: `Bearer ${src.token}`, "content-type": "application/json" },
|
|
8374
|
+
body: JSON.stringify(sessionBody(sessionId, sum, { capturedAt: now })),
|
|
8375
|
+
signal: AbortSignal.timeout(SESSION_POST_TIMEOUT_MS)
|
|
8376
|
+
});
|
|
8377
|
+
return res.ok;
|
|
8378
|
+
} catch {
|
|
8379
|
+
return false;
|
|
8380
|
+
}
|
|
8381
|
+
}
|
|
7477
8382
|
var parseStopPayload = (raw) => {
|
|
7478
8383
|
try {
|
|
7479
8384
|
const j = JSON.parse(raw);
|
|
@@ -7489,23 +8394,24 @@ function stopReason(rows) {
|
|
|
7489
8394
|
function decideStop(a) {
|
|
7490
8395
|
if (a.stopHookActive || !a.sessionId)
|
|
7491
8396
|
return { block: false };
|
|
7492
|
-
const
|
|
7493
|
-
if (
|
|
8397
|
+
const due2 = owedBySession(a.rows, a.sessionId);
|
|
8398
|
+
if (due2.length === 0)
|
|
7494
8399
|
return { block: false };
|
|
7495
8400
|
return {
|
|
7496
8401
|
block: true,
|
|
7497
|
-
reason: stopReason(
|
|
7498
|
-
rows: markAsked(a.rows,
|
|
8402
|
+
reason: stopReason(due2),
|
|
8403
|
+
rows: markAsked(a.rows, due2.map((r) => r.key), a.now)
|
|
7499
8404
|
};
|
|
7500
8405
|
}
|
|
7501
|
-
var stopEvent = async (
|
|
7502
|
-
const log =
|
|
7503
|
-
const now = (
|
|
7504
|
-
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);
|
|
7505
8410
|
const payload = parseStopPayload(raw);
|
|
8411
|
+
await postTranscript(o2, payload, now);
|
|
7506
8412
|
let rows;
|
|
7507
8413
|
try {
|
|
7508
|
-
rows = readOwed(
|
|
8414
|
+
rows = readOwed(o2.home, now);
|
|
7509
8415
|
} catch {
|
|
7510
8416
|
return 0;
|
|
7511
8417
|
}
|
|
@@ -7518,7 +8424,7 @@ var stopEvent = async (o) => {
|
|
|
7518
8424
|
if (!d.block)
|
|
7519
8425
|
return 0;
|
|
7520
8426
|
try {
|
|
7521
|
-
writeOwed(
|
|
8427
|
+
writeOwed(o2.home, d.rows);
|
|
7522
8428
|
} catch {}
|
|
7523
8429
|
log(JSON.stringify({ decision: "block", reason: d.reason }));
|
|
7524
8430
|
return 0;
|
|
@@ -7526,16 +8432,16 @@ var stopEvent = async (o) => {
|
|
|
7526
8432
|
|
|
7527
8433
|
// src/commands/uninstall.ts
|
|
7528
8434
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
7529
|
-
import { existsSync as
|
|
8435
|
+
import { existsSync as existsSync16, rmSync as rmSync5 } from "node:fs";
|
|
7530
8436
|
|
|
7531
8437
|
// src/codex.ts
|
|
7532
8438
|
init_config();
|
|
7533
|
-
import { existsSync as
|
|
8439
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "node:fs";
|
|
7534
8440
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
7535
8441
|
var PROVIDER_ID2 = "sleds";
|
|
7536
8442
|
var codexDir2 = (home) => join12(home, ".codex");
|
|
7537
8443
|
var codexConfigPath2 = (home) => join12(codexDir2(home), "config.toml");
|
|
7538
|
-
function removeCodexProvider2(toml,
|
|
8444
|
+
function removeCodexProvider2(toml, o2 = {}) {
|
|
7539
8445
|
const lines = toml.split(`
|
|
7540
8446
|
`);
|
|
7541
8447
|
const out = [];
|
|
@@ -7548,7 +8454,7 @@ function removeCodexProvider2(toml, o = {}) {
|
|
|
7548
8454
|
}
|
|
7549
8455
|
let text = out.join(`
|
|
7550
8456
|
`);
|
|
7551
|
-
if (!
|
|
8457
|
+
if (!o2.keepPrior) {
|
|
7552
8458
|
const prior = text.match(/^# sleds-prior-model-provider = "([^"]*)"\s*\n/m);
|
|
7553
8459
|
if (prior) {
|
|
7554
8460
|
text = text.replace(prior[0], "");
|
|
@@ -7563,9 +8469,9 @@ function removeCodexProvider2(toml, o = {}) {
|
|
|
7563
8469
|
}
|
|
7564
8470
|
function uninstallCodex(home) {
|
|
7565
8471
|
const path = codexConfigPath2(home);
|
|
7566
|
-
if (!
|
|
8472
|
+
if (!existsSync15(path))
|
|
7567
8473
|
return null;
|
|
7568
|
-
const prior =
|
|
8474
|
+
const prior = readFileSync13(path, "utf8");
|
|
7569
8475
|
const next = removeCodexProvider2(prior);
|
|
7570
8476
|
if (next === prior)
|
|
7571
8477
|
return null;
|
|
@@ -7580,72 +8486,72 @@ init_settings();
|
|
|
7580
8486
|
var defaultUnload = (plist) => {
|
|
7581
8487
|
spawnSync5("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
7582
8488
|
};
|
|
7583
|
-
var uninstall2 = (
|
|
7584
|
-
const codexLine = uninstallCodex(
|
|
8489
|
+
var uninstall2 = (o2) => {
|
|
8490
|
+
const codexLine = uninstallCodex(o2.home);
|
|
7585
8491
|
if (codexLine)
|
|
7586
|
-
(
|
|
7587
|
-
const log =
|
|
8492
|
+
(o2.log ?? console.log)(` ${codexLine}`);
|
|
8493
|
+
const log = o2.log ?? console.log;
|
|
7588
8494
|
const removed = [];
|
|
7589
|
-
if (
|
|
7590
|
-
let settings = readSettings(
|
|
8495
|
+
if (existsSync16(settingsPath(o2.home))) {
|
|
8496
|
+
let settings = readSettings(o2.home);
|
|
7591
8497
|
let changed = false;
|
|
7592
8498
|
if (hasEitherKey(settings)) {
|
|
7593
8499
|
settings = removeProxy(settings);
|
|
7594
8500
|
changed = true;
|
|
7595
|
-
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)}`);
|
|
7596
8502
|
}
|
|
7597
8503
|
if (isSledsStatusLine(settings[STATUS_LINE_KEY])) {
|
|
7598
|
-
const cfg = readConfig(
|
|
8504
|
+
const cfg = readConfig(o2.home);
|
|
7599
8505
|
settings = removeStatusLine(settings, cfg.priorStatusLine);
|
|
7600
8506
|
changed = true;
|
|
7601
|
-
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)`);
|
|
7602
8508
|
if (cfg.priorStatusLine !== undefined) {
|
|
7603
8509
|
const { priorStatusLine: _restored, ...rest } = cfg;
|
|
7604
|
-
writeConfig(
|
|
8510
|
+
writeConfig(o2.home, rest);
|
|
7605
8511
|
}
|
|
7606
8512
|
}
|
|
7607
8513
|
if (hasSeatHook(settings)) {
|
|
7608
8514
|
settings = removeSeatHook(settings);
|
|
7609
8515
|
changed = true;
|
|
7610
|
-
removed.push(`hooks.Notification seat-event entry from ${settingsPath(
|
|
8516
|
+
removed.push(`hooks.Notification seat-event entry from ${settingsPath(o2.home)}`);
|
|
7611
8517
|
}
|
|
7612
8518
|
if (hasPromptHook(settings)) {
|
|
7613
8519
|
settings = removePromptHook(settings);
|
|
7614
8520
|
changed = true;
|
|
7615
|
-
removed.push(`hooks.UserPromptSubmit prompt-event entry from ${settingsPath(
|
|
8521
|
+
removed.push(`hooks.UserPromptSubmit prompt-event entry from ${settingsPath(o2.home)}`);
|
|
7616
8522
|
}
|
|
7617
8523
|
if (hasSpecHook(settings)) {
|
|
7618
8524
|
settings = removeSpecHook(settings);
|
|
7619
8525
|
changed = true;
|
|
7620
|
-
removed.push(`hooks.PostToolUse spec-event entry from ${settingsPath(
|
|
8526
|
+
removed.push(`hooks.PostToolUse spec-event entry from ${settingsPath(o2.home)}`);
|
|
7621
8527
|
}
|
|
7622
8528
|
if (hasSessionHook(settings)) {
|
|
7623
8529
|
settings = removeSessionHook(settings);
|
|
7624
8530
|
changed = true;
|
|
7625
|
-
removed.push(`hooks.SessionStart session-event entry from ${settingsPath(
|
|
8531
|
+
removed.push(`hooks.SessionStart session-event entry from ${settingsPath(o2.home)}`);
|
|
7626
8532
|
}
|
|
7627
8533
|
if (hasStopHook(settings)) {
|
|
7628
8534
|
settings = removeStopHook(settings);
|
|
7629
8535
|
changed = true;
|
|
7630
|
-
removed.push(`hooks.Stop stop-event entry from ${settingsPath(
|
|
8536
|
+
removed.push(`hooks.Stop stop-event entry from ${settingsPath(o2.home)}`);
|
|
7631
8537
|
}
|
|
7632
8538
|
if (changed)
|
|
7633
|
-
writeSettings(
|
|
8539
|
+
writeSettings(o2.home, settings);
|
|
7634
8540
|
}
|
|
7635
|
-
for (const file of [meterCachePath(
|
|
7636
|
-
if (
|
|
8541
|
+
for (const file of [meterCachePath(o2.home), seatStatePath(o2.home)]) {
|
|
8542
|
+
if (existsSync16(file)) {
|
|
7637
8543
|
rmSync5(file, { force: true });
|
|
7638
8544
|
removed.push(file);
|
|
7639
8545
|
}
|
|
7640
8546
|
}
|
|
7641
|
-
const skill = skillDir(
|
|
7642
|
-
if (
|
|
8547
|
+
const skill = skillDir(o2.home);
|
|
8548
|
+
if (existsSync16(skill)) {
|
|
7643
8549
|
rmSync5(skill, { recursive: true, force: true });
|
|
7644
8550
|
removed.push(skill);
|
|
7645
8551
|
}
|
|
7646
|
-
const plist = musherPlistPath(
|
|
7647
|
-
if (
|
|
7648
|
-
(
|
|
8552
|
+
const plist = musherPlistPath(o2.home);
|
|
8553
|
+
if (existsSync16(plist)) {
|
|
8554
|
+
(o2.unloadAgent ?? defaultUnload)(plist);
|
|
7649
8555
|
rmSync5(plist, { force: true });
|
|
7650
8556
|
removed.push(`launchd agent ${musherLabel()}`);
|
|
7651
8557
|
}
|
|
@@ -7656,7 +8562,7 @@ var uninstall2 = (o) => {
|
|
|
7656
8562
|
for (const r of removed)
|
|
7657
8563
|
log(` ${r}`);
|
|
7658
8564
|
}
|
|
7659
|
-
log(`Kept ${tokenPath(
|
|
8565
|
+
log(`Kept ${tokenPath(o2.home)} (delete it yourself if you want the token gone).`);
|
|
7660
8566
|
return 0;
|
|
7661
8567
|
};
|
|
7662
8568
|
var hasEitherKey = (settings) => {
|
|
@@ -7675,17 +8581,17 @@ var jobIdFromTarget = (target) => {
|
|
|
7675
8581
|
return m[1];
|
|
7676
8582
|
return /^[A-Za-z0-9_-]{3,64}$/.test(t) ? t : null;
|
|
7677
8583
|
};
|
|
7678
|
-
var verify = async (
|
|
7679
|
-
const log =
|
|
7680
|
-
const error =
|
|
7681
|
-
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;
|
|
7682
8588
|
if (!jobId) {
|
|
7683
8589
|
error("usage: sleds verify <jobId> (or paste the SLEDS-Attest line)");
|
|
7684
8590
|
return 2;
|
|
7685
8591
|
}
|
|
7686
|
-
const client =
|
|
7687
|
-
const src = meterSource(
|
|
7688
|
-
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;
|
|
7689
8595
|
})();
|
|
7690
8596
|
if (!client) {
|
|
7691
8597
|
error("No seat on this machine. Run `sleds init` first.");
|
|
@@ -7716,17 +8622,17 @@ var verify = async (o) => {
|
|
|
7716
8622
|
|
|
7717
8623
|
// src/config.ts
|
|
7718
8624
|
init_settings();
|
|
7719
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
8625
|
+
import { chmodSync as chmodSync2, existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync14, renameSync as renameSync6, writeFileSync as writeFileSync13 } from "node:fs";
|
|
7720
8626
|
import { join as join13, resolve as resolve4 } from "node:path";
|
|
7721
8627
|
var configDir2 = (home, env = process.env) => {
|
|
7722
|
-
const
|
|
7723
|
-
return
|
|
8628
|
+
const o2 = env.SLEDS_HOME?.trim();
|
|
8629
|
+
return o2 ? resolve4(home, o2) : join13(home, ".config", "sleds");
|
|
7724
8630
|
};
|
|
7725
8631
|
var configPath2 = (home) => join13(configDir2(home), "config.json");
|
|
7726
|
-
var
|
|
8632
|
+
var isObject13 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7727
8633
|
var optString2 = (v) => typeof v === "string" ? v : undefined;
|
|
7728
8634
|
var parsePriorEnv2 = (v) => {
|
|
7729
|
-
if (!
|
|
8635
|
+
if (!isObject13(v))
|
|
7730
8636
|
return;
|
|
7731
8637
|
const out = {};
|
|
7732
8638
|
const url = optString2(v[BASE_URL_KEY]);
|
|
@@ -7741,7 +8647,7 @@ var parsePriorEnv2 = (v) => {
|
|
|
7741
8647
|
};
|
|
7742
8648
|
var parseSchedule2 = (v) => v === "launchd" || v === "systemd" || v === "manual" ? v : undefined;
|
|
7743
8649
|
var parseConfig2 = (raw) => {
|
|
7744
|
-
if (!
|
|
8650
|
+
if (!isObject13(raw))
|
|
7745
8651
|
return {};
|
|
7746
8652
|
const cfg = {};
|
|
7747
8653
|
const token = optString2(raw.token);
|
|
@@ -7779,13 +8685,13 @@ var parseConfig2 = (raw) => {
|
|
|
7779
8685
|
const org = parseOrgInfo2(raw.org);
|
|
7780
8686
|
if (org)
|
|
7781
8687
|
cfg.org = org;
|
|
7782
|
-
const
|
|
7783
|
-
if (
|
|
7784
|
-
cfg.musher =
|
|
8688
|
+
const musher2 = parseMusher2(raw.musher);
|
|
8689
|
+
if (musher2)
|
|
8690
|
+
cfg.musher = musher2;
|
|
7785
8691
|
return cfg;
|
|
7786
8692
|
};
|
|
7787
8693
|
var parseMusher2 = (raw) => {
|
|
7788
|
-
if (!
|
|
8694
|
+
if (!isObject13(raw))
|
|
7789
8695
|
return;
|
|
7790
8696
|
const out = {};
|
|
7791
8697
|
const pm = optString2(raw.permissionMode);
|
|
@@ -7801,19 +8707,19 @@ var parseMusher2 = (raw) => {
|
|
|
7801
8707
|
return out;
|
|
7802
8708
|
};
|
|
7803
8709
|
var parseOrgInfo2 = (raw) => {
|
|
7804
|
-
if (!
|
|
8710
|
+
if (!isObject13(raw) || !isObject13(raw.capabilities))
|
|
7805
8711
|
return;
|
|
7806
8712
|
const name = optString2(raw.name);
|
|
7807
|
-
const
|
|
7808
|
-
const
|
|
8713
|
+
const slug2 = optString2(raw.slug);
|
|
8714
|
+
const plan2 = raw.plan;
|
|
7809
8715
|
const c = raw.capabilities;
|
|
7810
8716
|
const src = c.upstreamSource;
|
|
7811
|
-
if (name === undefined ||
|
|
8717
|
+
if (name === undefined || slug2 === undefined || plan2 !== "team" && plan2 !== "enterprise" && plan2 !== "api" && plan2 !== "individual" || src !== "own" && src !== "shared" && src !== "none")
|
|
7812
8718
|
return;
|
|
7813
8719
|
return {
|
|
7814
8720
|
name,
|
|
7815
|
-
slug,
|
|
7816
|
-
plan,
|
|
8721
|
+
slug: slug2,
|
|
8722
|
+
plan: plan2,
|
|
7817
8723
|
capabilities: {
|
|
7818
8724
|
upstreamKey: c.upstreamKey === true,
|
|
7819
8725
|
adminKey: c.adminKey === true,
|
|
@@ -7824,10 +8730,10 @@ var parseOrgInfo2 = (raw) => {
|
|
|
7824
8730
|
};
|
|
7825
8731
|
};
|
|
7826
8732
|
var readConfig2 = (home) => {
|
|
7827
|
-
const
|
|
7828
|
-
if (!
|
|
8733
|
+
const p2 = configPath2(home);
|
|
8734
|
+
if (!existsSync17(p2))
|
|
7829
8735
|
return {};
|
|
7830
|
-
return parseConfig2(JSON.parse(
|
|
8736
|
+
return parseConfig2(JSON.parse(readFileSync14(p2, "utf8")));
|
|
7831
8737
|
};
|
|
7832
8738
|
|
|
7833
8739
|
// src/schedule.ts
|
|
@@ -7853,12 +8759,12 @@ var withSeatEnv2 = (command, home, env = process.env) => {
|
|
|
7853
8759
|
return parts.join(" ");
|
|
7854
8760
|
};
|
|
7855
8761
|
var shellWord2 = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
|
|
7856
|
-
var
|
|
8762
|
+
var isObject14 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7857
8763
|
var isSledsHookFor2 = (sub) => (entry) => {
|
|
7858
|
-
if (!
|
|
8764
|
+
if (!isObject14(entry) || !Array.isArray(entry.hooks))
|
|
7859
8765
|
return false;
|
|
7860
8766
|
const re = new RegExp(`\\b${sub}\\b`);
|
|
7861
|
-
return entry.hooks.some((h) =>
|
|
8767
|
+
return entry.hooks.some((h) => isObject14(h) && h.type === "command" && typeof h.command === "string" && re.test(h.command));
|
|
7862
8768
|
};
|
|
7863
8769
|
var isSledsSessionHook2 = isSledsHookFor2("session-event");
|
|
7864
8770
|
var isSledsStopHook2 = isSledsHookFor2("stop-event");
|
|
@@ -7964,8 +8870,8 @@ var stringFlag = (flags, name) => {
|
|
|
7964
8870
|
var hookStdin = async () => {
|
|
7965
8871
|
if (process.stdin.isTTY)
|
|
7966
8872
|
return;
|
|
7967
|
-
await Promise.resolve().then(() => init_seat());
|
|
7968
|
-
const text = await
|
|
8873
|
+
const { readStdin: readStdin3 } = await Promise.resolve().then(() => (init_seat(), exports_seat));
|
|
8874
|
+
const text = await readStdin3(300);
|
|
7969
8875
|
return text.trim() ? text : undefined;
|
|
7970
8876
|
};
|
|
7971
8877
|
var sledsArgv = () => {
|
|
@@ -8118,7 +9024,7 @@ ${renderHelpText(argv[1])}`);
|
|
|
8118
9024
|
case "version":
|
|
8119
9025
|
case "--version":
|
|
8120
9026
|
case "-v":
|
|
8121
|
-
console.log("sleds 0.1.
|
|
9027
|
+
console.log("sleds 0.1.26");
|
|
8122
9028
|
return 0;
|
|
8123
9029
|
default:
|
|
8124
9030
|
console.error(`unknown command: ${cmd}
|