@sleds/cli 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/sleds.mjs +1761 -1158
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -1,22 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
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
- });
3
+ var __esm = (fn, res, err) => () => {
4
+ if (fn)
5
+ try {
6
+ res = fn(fn = 0);
7
+ } catch (e) {
8
+ err = [e];
9
+ }
10
+ if (err)
11
+ throw err[0];
12
+ return res;
17
13
  };
18
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
14
 
21
15
  // ../../packages/shared/src/mcp/format.ts
22
16
  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) => {
@@ -158,8 +152,8 @@ var init_format = __esm(() => {
158
152
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
159
153
  import { dirname, join, resolve } from "node:path";
160
154
  var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN", claudeDir = (home, env = process.env) => {
161
- const o2 = env.CLAUDE_CONFIG_DIR?.trim();
162
- return o2 ? resolve(home, o2) : join(home, ".claude");
155
+ const o = env.CLAUDE_CONFIG_DIR?.trim();
156
+ return o ? resolve(home, o) : join(home, ".claude");
163
157
  }, 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) => {
164
158
  const parts = [];
165
159
  const sh = env.SLEDS_HOME?.trim();
@@ -189,8 +183,8 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
189
183
  visit(settings.hooks);
190
184
  return out;
191
185
  }, 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) => {
192
- const o2 = env.SLEDS_HOME?.trim();
193
- return o2 ? join(resolve(home, o2), ".sleds-token") : join(home, ".sleds-token");
186
+ const o = env.SLEDS_HOME?.trim();
187
+ return o ? join(resolve(home, o), ".sleds-token") : join(home, ".sleds-token");
194
188
  }, 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) => {
195
189
  const env = settings.env;
196
190
  return isObject5(env) ? env : {};
@@ -326,12 +320,12 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
326
320
  return withEventHooks(settings, event, list.filter((e) => !ours(e)));
327
321
  }, 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)}
328
322
  `, hasClaudeCode = (home) => existsSync(claudeDir(home)), readSettings = (home) => {
329
- const p2 = settingsPath(home);
330
- if (!existsSync(p2))
323
+ const p = settingsPath(home);
324
+ if (!existsSync(p))
331
325
  return {};
332
- const parsed = JSON.parse(readFileSync(p2, "utf8"));
326
+ const parsed = JSON.parse(readFileSync(p, "utf8"));
333
327
  if (!isObject5(parsed))
334
- throw new Error(`${p2} is not a JSON object`);
328
+ throw new Error(`${p} is not a JSON object`);
335
329
  return parsed;
336
330
  }, writeSettings = (home, settings) => {
337
331
  mkdirSync(claudeDir(home), { recursive: true });
@@ -347,10 +341,10 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
347
341
  writeFileSync(dst, readFileSync(src));
348
342
  return true;
349
343
  }, readTokenFile = (home) => {
350
- const p2 = tokenPath(home);
351
- if (!existsSync(p2))
344
+ const p = tokenPath(home);
345
+ if (!existsSync(p))
352
346
  return null;
353
- const t = readFileSync(p2, "utf8").trim();
347
+ const t = readFileSync(p, "utf8").trim();
354
348
  return t.length > 0 ? t : null;
355
349
  }, writeTokenFile = (home, token) => {
356
350
  mkdirSync(dirname(tokenPath(home)), { recursive: true, mode: 448 });
@@ -366,8 +360,8 @@ var init_settings = __esm(() => {
366
360
  import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
367
361
  import { join as join2, resolve as resolve2 } from "node:path";
368
362
  var configDir = (home, env = process.env) => {
369
- const o2 = env.SLEDS_HOME?.trim();
370
- return o2 ? resolve2(home, o2) : join2(home, ".config", "sleds");
363
+ const o = env.SLEDS_HOME?.trim();
364
+ return o ? resolve2(home, o) : join2(home, ".config", "sleds");
371
365
  }, 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) => {
372
366
  if (!isObject6(v))
373
367
  return;
@@ -444,15 +438,15 @@ var configDir = (home, env = process.env) => {
444
438
  return;
445
439
  const name = optString(raw.name);
446
440
  const slug = optString(raw.slug);
447
- const plan2 = raw.plan;
441
+ const plan = raw.plan;
448
442
  const c = raw.capabilities;
449
443
  const src = c.upstreamSource;
450
- if (name === undefined || slug === undefined || plan2 !== "team" && plan2 !== "enterprise" && plan2 !== "api" && plan2 !== "individual" || src !== "own" && src !== "shared" && src !== "none")
444
+ if (name === undefined || slug === undefined || plan !== "team" && plan !== "enterprise" && plan !== "api" && plan !== "individual" || src !== "own" && src !== "shared" && src !== "none")
451
445
  return;
452
446
  return {
453
447
  name,
454
448
  slug,
455
- plan: plan2,
449
+ plan,
456
450
  capabilities: {
457
451
  upstreamKey: c.upstreamKey === true,
458
452
  adminKey: c.adminKey === true,
@@ -462,10 +456,10 @@ var configDir = (home, env = process.env) => {
462
456
  mode: raw.mode === "proxy-always" ? "proxy-always" : "seat-first"
463
457
  };
464
458
  }, readConfig = (home) => {
465
- const p2 = configPath(home);
466
- if (!existsSync2(p2))
459
+ const p = configPath(home);
460
+ if (!existsSync2(p))
467
461
  return {};
468
- return parseConfig(JSON.parse(readFileSync2(p2, "utf8")));
462
+ return parseConfig(JSON.parse(readFileSync2(p, "utf8")));
469
463
  }, writeConfig = (home, cfg) => {
470
464
  const dir = configDir(home);
471
465
  mkdirSync2(dir, { recursive: true, mode: 448 });
@@ -484,11 +478,11 @@ var init_config = __esm(() => {
484
478
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "node:fs";
485
479
  import { join as join4 } from "node:path";
486
480
  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) => {
487
- const p2 = meterCachePath(home);
488
- if (!existsSync4(p2))
481
+ const p = meterCachePath(home);
482
+ if (!existsSync4(p))
489
483
  return null;
490
484
  try {
491
- const raw = JSON.parse(readFileSync4(p2, "utf8"));
485
+ const raw = JSON.parse(readFileSync4(p, "utf8"));
492
486
  if (!isObject7(raw))
493
487
  return null;
494
488
  const fetchedAt = num4(raw.fetchedAt);
@@ -507,12 +501,12 @@ var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof
507
501
  writeFileSync4(tmp, `${JSON.stringify(file)}
508
502
  `, { mode: 384 });
509
503
  renameSync3(tmp, target);
510
- }, fetchMeter = async (o2) => {
511
- const fetchFn = o2.fetchFn ?? fetch;
504
+ }, fetchMeter = async (o) => {
505
+ const fetchFn = o.fetchFn ?? fetch;
512
506
  try {
513
- const res = await fetchFn(`${o2.proxyUrl}/proxy/meter`, {
514
- headers: { authorization: `Bearer ${o2.token}` },
515
- signal: AbortSignal.timeout(o2.timeoutMs ?? METER_TIMEOUT_MS)
507
+ const res = await fetchFn(`${o.proxyUrl}/proxy/meter`, {
508
+ headers: { authorization: `Bearer ${o.token}` },
509
+ signal: AbortSignal.timeout(o.timeoutMs ?? METER_TIMEOUT_MS)
516
510
  });
517
511
  if (!res.ok)
518
512
  return null;
@@ -527,18 +521,18 @@ var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof
527
521
  const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
528
522
  const token = inSettings?.token ?? cfg.token;
529
523
  return proxyUrl && token ? { proxyUrl, token } : null;
530
- }, loadMeter = async (o2) => {
531
- const now = o2.now ?? Date.now();
532
- const cached = readMeterCache(o2.home, now);
524
+ }, loadMeter = async (o) => {
525
+ const now = o.now ?? Date.now();
526
+ const cached = readMeterCache(o.home, now);
533
527
  if (cached)
534
528
  return cached;
535
- const src = meterSource(o2.home);
529
+ const src = meterSource(o.home);
536
530
  if (!src)
537
531
  return null;
538
- const fresh = await fetchMeter({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs });
532
+ const fresh = await fetchMeter({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs });
539
533
  if (fresh) {
540
534
  try {
541
- writeMeterCache(o2.home, fresh, now);
535
+ writeMeterCache(o.home, fresh, now);
542
536
  } catch {}
543
537
  }
544
538
  return fresh;
@@ -551,432 +545,9 @@ var init_meter = __esm(() => {
551
545
  });
552
546
 
553
547
  // src/seat.ts
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 existsSync17,
591
- mkdirSync as mkdirSync14,
592
- openSync as openSync2,
593
- readFileSync as readFileSync14,
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, isObject14 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num7 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, EPOCH_MS_THRESHOLD2 = 100000000000, parseResetsAt2 = (v) => {
601
- const n = num7(v);
602
- if (n !== undefined) {
603
- if (n <= 0)
604
- return;
605
- return n >= EPOCH_MS_THRESHOLD2 ? Math.round(n) : Math.round(n * 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 (!isObject14(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 (!isObject14(raw))
622
- return null;
623
- const limits = isObject14(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 (isObject14(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 (!isObject14(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 (!isObject14(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 (isObject14(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 (isObject14(raw.warned)) {
717
- const warned = {};
718
- for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
719
- const w = raw.warned[bar];
720
- if (!isObject14(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 (isObject14(raw.told)) {
734
- const told = {};
735
- for (const bar of ["fiveHour", "sevenDay", "spendLimit", "credits"]) {
736
- const w = raw.told[bar];
737
- if (!isObject14(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 (isObject14(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 (isObject14(raw.cockpit) && raw.cockpit.kind === "cmux") {
763
- const c = { kind: "cmux" };
764
- if (isObject14(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 (isObject14(f) && num7(f.at) !== undefined) {
775
- const strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
776
- const j = isObject14(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 (isObject14(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 (!isObject14(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 (!isObject14(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 = isObject14(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 (!existsSync17(p2))
845
- return {};
846
- try {
847
- return parseSeatState2(JSON.parse(readFileSync14(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 isObject14(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) => {
548
+ var SEAT_POST_MIN_INTERVAL_MS2, readStdin2 = (timeoutMs, stdin = process.stdin) => new Promise((resolve) => {
978
549
  if (stdin.isTTY) {
979
- resolve6("");
550
+ resolve("");
980
551
  return;
981
552
  }
982
553
  let data = "";
@@ -986,7 +557,7 @@ var SEAT_POST_MIN_DELTA_PCT2 = 1, SEAT_POST_MIN_INTERVAL_MS2, SEAT_POST_TIMEOUT_
986
557
  return;
987
558
  settled = true;
988
559
  clearTimeout(timer);
989
- resolve6(data);
560
+ resolve(data);
990
561
  };
991
562
  const timer = setTimeout(finish, timeoutMs);
992
563
  stdin.setEncoding("utf8");
@@ -1001,12 +572,11 @@ var init_seat = __esm(() => {
1001
572
  init_config();
1002
573
  init_meter();
1003
574
  SEAT_POST_MIN_INTERVAL_MS2 = 5 * 60000;
1004
- CREDITS_PATTERN2 = /out of usage credits|cc_cli_limit_message/i;
1005
575
  });
1006
576
 
1007
577
  // src/index.ts
1008
578
  import { realpathSync } from "fs";
1009
- import { homedir, platform as platform2 } from "os";
579
+ import { homedir, platform as platform3 } from "os";
1010
580
  import { fileURLToPath } from "url";
1011
581
  // ../../packages/shared/src/pricing.ts
1012
582
  var p = (input, output, cacheWrite, cacheWrite1h, cacheRead) => ({ input, output, cacheWrite, cacheWrite1h, cacheRead });
@@ -1183,6 +753,33 @@ var AUDIENCE_LABEL = {
1183
753
  everyone: "Each person, once"
1184
754
  };
1185
755
  var GUIDES = [
756
+ {
757
+ id: "board",
758
+ title: "Read the board",
759
+ audience: "everyone",
760
+ lede: "Sleds is the board: the pool of sized work, a sled per seat with the jobs moving on it, and what landed this week.",
761
+ steps: [
762
+ {
763
+ do: "Open `Sleds` and read the strip",
764
+ where: "Sleds",
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 rows proposed for you and the asks you filed."
766
+ },
767
+ {
768
+ do: "Read a pool row: the band, the worth, the chip",
769
+ note: "`proposed` names the seat the rules (and the model) suggest, with why; `waiting for product` needs a yes; an amber note says a related or duplicate row exists. `Take it` puts it on your sled; an approver can `Put it on <seat>'s sled`."
770
+ },
771
+ {
772
+ do: "Read a seat card: the rails, the jobs, the band spent",
773
+ note: "Each rail is one agent on one platform with its own wall; the bar is the window used. A job says which rail it runs on and, at a wall, `paused at a wall` with `Move to <rail>` when the seat has headroom elsewhere."
774
+ },
775
+ {
776
+ do: "Open a row for the whole story",
777
+ note: "The panel: the brief and who filed it, what the AI added, the recommendation, milestones with holders, runs, the receipt when it lands, and every verb that applies to you."
778
+ }
779
+ ],
780
+ check: "Your own sled shows under `Mine` with the jobs you hold; a landed row shows its measured time and verdict under `Landed this week` and on Record › Receipts.",
781
+ scope: "A product viewer sees seats and holders, never rails, models or spend; the words on the board are the words in chat — sled, seat, rail, job, wall, receipt."
782
+ },
1186
783
  {
1187
784
  id: "workspace",
1188
785
  title: "Set up a workspace",
@@ -1236,16 +833,21 @@ var GUIDES = [
1236
833
  {
1237
834
  do: "Add the custom connector",
1238
835
  where: "claude.ai › Settings › Connectors › `Add custom connector`",
1239
- note: "URL `https://agent.sleds.ai/mcp`. It signs you in with OAuth — the same email your workspace invited."
836
+ note: "URL `https://mcp.sleds.ai/mcp`. It signs you in with OAuth — the same email your workspace invited."
1240
837
  },
1241
838
  {
1242
839
  do: "Say `/sled usage` in any chat",
1243
840
  note: "Your seat's bars and credits come back. If it answers that it does not know you, your invite and your Claude sign-in are different addresses — an admin fixes the member row."
1244
841
  },
1245
842
  {
1246
- do: "Claude Code and the terminal: install the CLI and sign in with your seat token",
843
+ do: "Start asks in a Claude Project that has SLEDS on",
844
+ where: "claude.ai › Projects › New project › Connectors: SLEDS",
845
+ note: 'One line of project instructions — "Work described here goes on the sled: use SLEDS intake" — and nobody has to remember the connector. Anywhere else, "put it on the sled" is enough; the brief appearing as a card is the sign SLEDS is on.'
846
+ },
847
+ {
848
+ do: "Claude Code and the terminal: install the CLI and sign in through the browser",
1247
849
  where: "`curl -fsSL sleds.ai/install | sh` then `sleds init`",
1248
- note: "Installs the `/sled` skill and the status line. `sleds doctor` says what is wired."
850
+ note: "`sleds init` opens app.sleds.ai/connect with a code; you sign in there (the same 6-digit code to your work email), approve, and the machine is your seat — nothing to paste, and you are in the workspace on the web in the same motion. Installs the `/sled` skill and the status line; `sleds doctor` says what is wired."
1249
851
  },
1250
852
  {
1251
853
  do: "Optional: SLEDS for Chrome — the meter, and the Team roster and credits when you administer the plan",
@@ -1262,18 +864,9 @@ var GUIDES = [
1262
864
  lede: "One GitHub App installation on your org: SLEDS sees PRs open and merge, and — only when someone asks — files and runs work from issues.",
1263
865
  steps: [
1264
866
  {
1265
- do: "Install the `sleds-ai` GitHub App on your org",
1266
- where: "github.com/apps/sleds-ai › `Install` › your org › `Only select repositories`",
1267
- note: "Pick the engineering repos. Do this signed in as an owner of the org; a personal account's settings page will not list an app installed on the org."
1268
- },
1269
- {
1270
- do: "Copy the installation number",
1271
- note: "The page you land on ends in `/installations/<number>`. That number is the installation id."
1272
- },
1273
- {
1274
- do: "Connect the installation to this workspace",
1275
- where: "Settings › Integrations › GitHub › `Connect`",
1276
- note: "`App installation id` is the number; `Default repo (owner/name)` is where an ask filed from chat would land if it named no repo. The webhook secret is set on the app, not here — leave it blank."
867
+ do: "Install on GitHub",
868
+ where: "Settings › Integrations › GitHub › `Install on GitHub`",
869
+ note: "GitHub's own screen opens: pick your org and `Only select repositories` → the engineering repos. Do this signed in as an owner of the org (GitHub asks a member to request it instead — if that happens, come back and click the button again once an owner has installed). You land back here with the org named on the tile; nothing to copy."
1277
870
  },
1278
871
  {
1279
872
  do: "Read-only for now: skip `Send test`",
@@ -1282,7 +875,7 @@ var GUIDES = [
1282
875
  {
1283
876
  do: "To keep every PR in sync: add the ledger workflow to each repo",
1284
877
  where: "`.github/workflows/sleds-ledger.yml` from the SLEDS repo, with a repo secret `SLEDS_TOKEN`",
1285
- note: "Mint a seat for CI (Settings › People, under a service person) and store its token as the secret. Then every PR is sized when it opens and closed on merge with the `SLEDS-Actual:` line from its body — the est → actual column on the Sled. It comments on the PR; leave it out while strictly read-only."
878
+ note: "Mint a seat for CI (Settings › People, under a service person) and store its token as the secret. Then every PR is sized when it opens and closed on merge with the `SLEDS-Actual:` line from its body — the est → actual column on Record › Receipts. It comments on the PR; leave it out while strictly read-only."
1286
879
  },
1287
880
  {
1288
881
  do: "When you are ready to write: add a `sled` label to the repos",
@@ -1311,7 +904,7 @@ var GUIDES = [
1311
904
  {
1312
905
  do: "Connect the site and name the projects to mirror",
1313
906
  where: "Settings › Integrations › Jira › `Connect`",
1314
- note: "`Site` is the host (`acme.atlassian.net`). `Projects to mirror, read-only` takes project keys (`WORK, CORE`): their open issues become sized rows on the Sled, 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."
907
+ note: "`Site` is the host (`acme.atlassian.net`). `Projects to mirror, read-only` takes project keys (`WORK, CORE`): their open issues become sized rows 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."
1315
908
  },
1316
909
  {
1317
910
  do: "Prove the token with `Send test` and the issue key left blank",
@@ -1323,47 +916,48 @@ var GUIDES = [
1323
916
  note: "URL `https://<your deployment>.convex.site/jira/webhook` with a secret; events `issue created`, `issue updated`. Paste the same secret in `Webhook secret` on the Connect form. It acts only on issues labelled `sled`, `sled:now` or `intake`, or moved to `Ready for Sleds`."
1324
917
  }
1325
918
  ],
1326
- 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 Sled lists the mirrored project's open issues with a band on each.",
919
+ 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.",
1327
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; rows carry the tracker's own state, labels, priority and assignee, never edited here."
1328
921
  },
1329
922
  {
1330
923
  id: "slack",
1331
924
  title: "Connect Slack",
1332
925
  audience: "admin",
1333
- lede: "A channel webhook for hand-offs, walls and digests; a bot token if you want the named person DMed.",
926
+ lede: "One button. SLEDS posts hand-offs, walls and digests to a channel of yours and DMs the person a thing is for — with nothing to create, copy or paste.",
1334
927
  steps: [
1335
928
  {
1336
- do: "Create a Slack app in your workspace and turn on Incoming Webhooks",
1337
- where: "api.slack.com/apps › `Create New App` › `Incoming Webhooks`",
1338
- note: "Add one to the channel where the team talks about work in flight; copy its URL."
1339
- },
1340
- {
1341
- do: "Store the webhook",
1342
- where: "Settings › Platform keys › provider `slack_webhook`"
929
+ do: "Add to Slack",
930
+ where: "Settings › Integrations › Slack › `Add to Slack`",
931
+ note: "Slack's own screen opens: choose the channel SLEDS posts to (private channels you belong to are in the list) and allow. You are back here in a few seconds with the team and channel shown on the tile. Nobody at SLEDS is involved and no key touches a clipboard."
1343
932
  },
1344
933
  {
1345
- do: "Optional: the bot, for DMs",
1346
- where: "The same app › `OAuth & Permissions` › bot scopes `chat:write`, `users:read`, `users:read.email` › install › the `xoxb-` token",
1347
- note: "Store it under provider `slack`. With it, a hand-off to a named coworker DMs them; without it, hand-offs post to the channel only."
934
+ do: "If the channel is private, let the bot in",
935
+ where: "In Slack, in that channel: `/invite @SLEDS`",
936
+ note: "Posting to the channel works from the moment you allowed; reading and replying in a private channel needs the bot as a member."
1348
937
  },
1349
938
  {
1350
939
  do: "Send one test post",
1351
940
  where: "Settings › Integrations › Slack › `Send test`",
1352
- note: "One real post to the channel (and a DM when the bot is on). Safe to run any time."
941
+ note: "One real post to the channel and one DM to you. Safe to run any time. If the DM part says Slack has no user for your email, you sign in to Slack with another address — set it under Settings › People › you › `Slack email, if different`."
942
+ },
943
+ {
944
+ do: "Advanced: keys by hand",
945
+ where: "Settings › Platform keys › `slack_webhook` and `slack`",
946
+ note: "Only for a Slack app of your own. Add to Slack writes both of these for you."
1353
947
  }
1354
948
  ],
1355
- check: "The test post arrives in the channel. From then on a hand-off, a wall, and a product decision each post there."
949
+ check: "The test post arrives in the channel and the DM in your Slack. From then on a hand-off, a wall, and a product decision each post there; a hand-off to a named coworker reaches them directly."
1356
950
  },
1357
951
  {
1358
952
  id: "approval",
1359
953
  title: "Turn on product's yes",
1360
954
  audience: "admin",
1361
- lede: "The gate in front of ready: asks wait until someone product-side says yes or defers with a note.",
955
+ lede: "An approval step before engineering can take a row: it waits until someone product-side says yes, or defers it with a note.",
1362
956
  steps: [
1363
957
  {
1364
- do: "Choose which asks wait",
1365
- where: "Policy › Product's yes › `Which asks wait`",
1366
- note: "`Asks from intake wait for product` gates what the business side files; `Every row waits for product` gates engineers' own briefs too. `Never` is the default."
958
+ do: "Choose who needs a yes",
959
+ where: "Policy › Product's yes › `Who needs a yes`",
960
+ note: "`Asks filed from chat` gates what the business side files and leaves tracker issues ready as they are; `Everything` gates engineers' own briefs too. `Nobody` is the default — everything filed is ready to take."
1367
961
  },
1368
962
  {
1369
963
  do: "Name the approvers",
@@ -1371,8 +965,8 @@ var GUIDES = [
1371
965
  note: "Emails, comma-separated. Blank means any owner or admin of the workspace."
1372
966
  },
1373
967
  {
1374
- do: "Approvers decide from the Sled or Jobs",
1375
- where: "Jobs › `Waiting on you`, or the row on the Sled",
968
+ do: "Approvers decide from the board or the Overview",
969
+ where: "Overview › `Waiting on product`, or the row on Sleds",
1376
970
  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?}`."
1377
971
  },
1378
972
  {
@@ -1380,7 +974,7 @@ var GUIDES = [
1380
974
  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."
1381
975
  }
1382
976
  ],
1383
- check: "File an ask from chat (`intake`): its row shows waiting for product on the Sled, the approvers see it under Waiting on you, and `run` on it is refused until someone approves."
977
+ check: "File an ask from chat (`intake`): its row shows waiting for product in the pool on Sleds, the approvers see it under Waiting on product, and `run` on it is refused until someone approves."
1384
978
  }
1385
979
  ];
1386
980
  function findGuides(filter) {
@@ -1436,7 +1030,7 @@ var VERBS = [
1436
1030
  want: "See my overnight jobs and the team's open hand-offs",
1437
1031
  mcp: "jobs · handoffs",
1438
1032
  claudeCode: "/sled jobs · `sleds handoffs`",
1439
- app: "Jobs",
1033
+ app: "Sleds › Moving (the runs on each seat's sled) · Record › Receipts once they land",
1440
1034
  how: "Newest first: id, status, route, cost. Hand-offs meant for you are listed first."
1441
1035
  },
1442
1036
  {
@@ -1444,16 +1038,40 @@ var VERBS = [
1444
1038
  group: "meter",
1445
1039
  want: "Ask where a piece of work is",
1446
1040
  mcp: "status {q?} — a tracker key or a word from the title; nothing for what you filed",
1447
- app: "Sled (the lane strip and the row's stage: waiting for product · deferred · ready · on the sled · landed)",
1041
+ app: "Sleds (the strip and the row's stage: waiting for product · deferred · ready · moving · landed)",
1448
1042
  how: "One paragraph per row: 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."
1449
1043
  },
1044
+ {
1045
+ id: "touches",
1046
+ group: "meter",
1047
+ want: "Know what a change reaches before I ask for it",
1048
+ mcp: "touches {text}",
1049
+ app: "Settings › Integrations › GitHub › Product map",
1050
+ how: "The product map — built from the workspace's repos on every merge, no model — says which areas of the product an ask reaches, which of the product's own documents it names, and what on the board collides with it. Your AI reads it back in plain words; intake reads it on its own while drafting."
1051
+ },
1052
+ {
1053
+ id: "attach",
1054
+ group: "meter",
1055
+ want: "Put the spec, the prototype or a screenshot on the row so the team can open it",
1056
+ mcp: "attach {project, name, content, mime?, kind?} — intake takes the same as `attachments` when filing",
1057
+ app: "Sleds › the row › Assets",
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 row; the person who added it, or an admin, can remove it."
1059
+ },
1060
+ {
1061
+ id: "answer",
1062
+ group: "meter",
1063
+ want: "Answer a question engineering asked about something I filed",
1064
+ mcp: "answer {answer, project?, id?} — nothing to see what is waiting on you",
1065
+ app: "Sleds › the row › Questions",
1066
+ how: "An approver asks on the row; you hear by bell, DM and email. Your reply, here or on the row, goes on the record and the asker hears at once. A worth question answered with a level or a band sets the row's worth."
1067
+ },
1450
1068
  {
1451
1069
  id: "estimate",
1452
1070
  group: "size",
1453
1071
  want: "Size a task before running it",
1454
1072
  mcp: "estimate {spec, source?}",
1455
1073
  claudeCode: "/sled estimate · `sleds spec-event` fires on save of a spec file",
1456
- app: "Sled (rows appear as specs and issues are sized)",
1074
+ app: "Sleds (rows appear in the pool as specs and issues are sized)",
1457
1075
  how: "A cost and time band with confidence and a spend cap — never a point figure. Recorded; the actual calibrates the next one."
1458
1076
  },
1459
1077
  {
@@ -1462,7 +1080,7 @@ var VERBS = [
1462
1080
  want: "Say what the work is worth to the business",
1463
1081
  mcp: "a `Worth:` line in the spec you estimate (`Worth: high · $5k–20k · Acme renewal`)",
1464
1082
  claudeCode: "`SLEDS-Worth:` line in the PR body",
1465
- app: "Sled › … › Worth",
1083
+ app: "Sleds › the row › Worth…",
1466
1084
  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."
1467
1085
  },
1468
1086
  {
@@ -1470,15 +1088,15 @@ var VERBS = [
1470
1088
  group: "send",
1471
1089
  want: "Send an ask to engineering from the conversation I already have open",
1472
1090
  mcp: "intake {text, links?, answers?, confirm?, parent?}",
1473
- app: "Sled (the row lands sized, filed by you, at waiting or ready)",
1474
- 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 — then shows the brief. On yes the row 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."
1091
+ app: "Sleds (the row lands in the pool sized, filed by you, at waiting or ready)",
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 row 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."
1475
1093
  },
1476
1094
  {
1477
1095
  id: "plan",
1478
1096
  group: "send",
1479
1097
  want: "Turn an approved epic into milestones I can send one at a time",
1480
1098
  mcp: "plan {q | project, milestones?, confirm?} — the epic first, then the drafted list",
1481
- app: "Sled (the epic row expands into its milestones; the tree under its band)",
1099
+ app: "Sleds (the epic row expands into its milestones; the tree under its band) · Approve · plan it",
1482
1100
  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."
1483
1101
  },
1484
1102
  {
@@ -1486,7 +1104,7 @@ var VERBS = [
1486
1104
  group: "send",
1487
1105
  want: "Give product's yes on an ask — or say not now, and why",
1488
1106
  mcp: "approve {q, decision: approve | defer, note?}",
1489
- app: "Sled › Approve / Defer on a waiting row · Jobs › Waiting on you · Policy › Product's yes",
1107
+ app: "Sleds › Approve / Defer on a waiting row · Overview › Waiting on product · Policy › Product's yes",
1490
1108
  how: "Off by default. When Policy says asks from intake (or every row) wait for product, a row 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."
1491
1109
  },
1492
1110
  {
@@ -1494,7 +1112,7 @@ var VERBS = [
1494
1112
  group: "send",
1495
1113
  want: "Ask where a piece of work should go, then send it there",
1496
1114
  mcp: "dispatch {q, run?: now | park | overnight, to?} — suggests with no run",
1497
- app: "Sled › Take it (onto your own seat) · Sled › … › Send to… (the park path)",
1115
+ app: "Sleds › Take it (onto your own sled) · Sleds › the row › Send to… · Put it on <seat>'s sled",
1498
1116
  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."
1499
1117
  },
1500
1118
  {
@@ -1503,7 +1121,7 @@ var VERBS = [
1503
1121
  want: "Send work to a teammate or a buddy",
1504
1122
  mcp: "handoff {task, to?, note?}",
1505
1123
  claudeCode: "/sled handoff · the wall popup",
1506
- app: "Sled › … › Send to… · extension › Park hand-off",
1124
+ app: "Sleds › the row › Send to… · extension › Park hand-off",
1507
1125
  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."
1508
1126
  },
1509
1127
  {
@@ -1512,7 +1130,7 @@ var VERBS = [
1512
1130
  want: "Take back a hand-off nobody has picked up",
1513
1131
  mcp: "withdraw {handoffId}",
1514
1132
  claudeCode: "`sleds withdraw <id>`",
1515
- app: "Jobs",
1133
+ app: "Sleds › the row › Withdraw",
1516
1134
  how: "Only the seat that parked it can withdraw it. Unpicked hand-offs are reminded after a day and expire after three."
1517
1135
  },
1518
1136
  {
@@ -1521,7 +1139,7 @@ var VERBS = [
1521
1139
  want: "Pick up work someone sent me",
1522
1140
  mcp: "handoffs → pickup {handoffId}",
1523
1141
  claudeCode: "/sled pickup <id> · `sleds pickup <id>`",
1524
- app: "Jobs › Take it · extension › Take it",
1142
+ app: "Sleds › Take it · extension › Take it",
1525
1143
  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."
1526
1144
  },
1527
1145
  {
@@ -1531,7 +1149,7 @@ var VERBS = [
1531
1149
  mcp: "defer {at?, task?}",
1532
1150
  claudeCode: "`sleds defer` · the wall popup › Hold until reset",
1533
1151
  app: "wall popup",
1534
- how: "The musher on your Mac resumes the session at the binding reset; no key needed. `+2h` or an ISO time overrides the reset."
1152
+ how: "The night shift on your Mac resumes the session at the binding reset; no key needed. `+2h` or an ISO time overrides the reset."
1535
1153
  },
1536
1154
  {
1537
1155
  id: "overnight",
@@ -1539,7 +1157,7 @@ var VERBS = [
1539
1157
  want: "Run this tonight at half price",
1540
1158
  mcp: "overnight {spec, source?}",
1541
1159
  claudeCode: "/sled overnight",
1542
- app: "wall popup › Run overnight · Jobs",
1160
+ app: "wall popup › Run overnight · Sleds › the Night shift seat",
1543
1161
  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."
1544
1162
  },
1545
1163
  {
@@ -1547,8 +1165,8 @@ var VERBS = [
1547
1165
  group: "defer",
1548
1166
  want: "Pull a finished overnight job into my session",
1549
1167
  claudeCode: "/sled resume <id>",
1550
- app: "Jobs",
1551
- how: "The delivered result, with its attestation — which model built it, signed by the workspace key."
1168
+ app: "Sleds › the Night shift seat › the row",
1169
+ how: "The delivered result, with its record of which model built it — which model built it, signed by the workspace key."
1552
1170
  },
1553
1171
  {
1554
1172
  id: "actual",
@@ -1556,7 +1174,7 @@ var VERBS = [
1556
1174
  want: "Record what a piece of work actually took",
1557
1175
  mcp: "actual {source, minutes?, usd?, model?, ref?}",
1558
1176
  claudeCode: "`SLEDS-Actual: 45 min · $7` in the PR body — the ledger posts it on merge",
1559
- app: "Sled › Est → actual (fills in on merge or delivery)",
1177
+ app: "Record › Receipts (fills in on merge or delivery) · Sleds › Landed this week",
1560
1178
  how: "Closes the estimate: est → actual per row, and the calibration for the next band. A delivered job closes its own."
1561
1179
  },
1562
1180
  {
@@ -1564,7 +1182,7 @@ var VERBS = [
1564
1182
  group: "close",
1565
1183
  want: "Check which model built something",
1566
1184
  claudeCode: "`sleds verify <jobId | SLEDS-Attest line>`",
1567
- app: "Jobs › attestation",
1185
+ app: "Record › Receipts › the row's ref",
1568
1186
  how: "Ed25519 over the job, model, outputs and time; verified locally against the workspace's public key."
1569
1187
  },
1570
1188
  {
@@ -1579,7 +1197,7 @@ var VERBS = [
1579
1197
  id: "team",
1580
1198
  group: "people",
1581
1199
  want: "See the team's work — in flight, landed, waiting",
1582
- app: "Overview · Sled · Jobs at the team zoom",
1200
+ app: "Overview · Sleds · Record at the team zoom",
1583
1201
  how: "Same screens, wider scope, summed by role. Aggregates by default; nobody's spend to anyone else without their say-so."
1584
1202
  },
1585
1203
  {
@@ -1603,7 +1221,7 @@ var VERBS = [
1603
1221
  id: "connector",
1604
1222
  group: "trust",
1605
1223
  want: "Use SLEDS from chat, Cowork or Claude Design",
1606
- mcp: "add https://agent.sleds.ai/mcp as a connector (OAuth) — then every verb in this column",
1224
+ mcp: "add https://mcp.sleds.ai/mcp as a connector (OAuth) — then every verb in this column",
1607
1225
  how: "The conversation is your cockpit: the same tools, no terminal. Your seat is the account you signed in with."
1608
1226
  },
1609
1227
  {
@@ -1686,14 +1304,16 @@ var QUESTIONS = {
1686
1304
  done: "What does done look like, in your words?",
1687
1305
  smallest: "What is the smallest version still worth doing?",
1688
1306
  worth: "What is it worth to the business — a customer, a number, or high / medium / low?",
1689
- touches: "What does it touch — which product areas or systems?"
1307
+ touches: "What does it touch — which product areas or systems, and where in the product would it live?",
1308
+ demand: "How many people have asked for this, or how often does it come up?"
1690
1309
  };
1691
1310
  var REQUIRED_TO_FILE = ["why", "done"];
1692
1311
  var HEADINGS = {
1693
1312
  why: /^(why|context|background|problem|motivation|goal|purpose|the ask)\b/i,
1694
1313
  done: /^(done|done when|definition of done|acceptance|acceptance criteria|success|outcome|expected)\b/i,
1695
1314
  smallest: /^(smallest|smallest version|mvp|minimum|minimal|first cut|v1|scope|in scope)\b/i,
1696
- touches: /^(touches|affects|areas?|surfaces?|systems?|components?|files)\b/i
1315
+ touches: /^(touches|affects|areas?|surfaces?|systems?|components?|files|where)\b/i,
1316
+ demand: /^(demand|requests?|requested by|how many|how often|asked for|frequency|volume)\b/i
1697
1317
  };
1698
1318
  var clip = (s, max) => {
1699
1319
  const t = s.replace(/\s+/g, " ").trim();
@@ -1788,13 +1408,14 @@ function draftBrief(input) {
1788
1408
  done: pick("done"),
1789
1409
  smallest: pick("smallest"),
1790
1410
  touches: pick("touches"),
1411
+ demand: pick("demand"),
1791
1412
  ...worth ? { worth } : {},
1792
1413
  sources
1793
1414
  };
1794
1415
  }
1795
1416
  function openQuestions(b) {
1796
1417
  const out = [];
1797
- const keys = ["why", "done", "smallest", "worth", "touches"];
1418
+ const keys = ["why", "done", "smallest", "worth", "touches", "demand"];
1798
1419
  for (const key of keys) {
1799
1420
  const missing = key === "worth" ? !b.worth : !b[key];
1800
1421
  if (missing)
@@ -1805,7 +1426,7 @@ function openQuestions(b) {
1805
1426
  function missingToFile(b) {
1806
1427
  return REQUIRED_TO_FILE.filter((k) => !b[k]);
1807
1428
  }
1808
- var fromWord = (f) => f.from === "filer" ? "from you" : "from what you sent";
1429
+ var fromWord = (f) => f.from === "filer" ? "from you" : f.from === "product" ? "from the product map" : "from what you sent";
1809
1430
  function renderBrief(b) {
1810
1431
  const parts = [b.ask.text];
1811
1432
  if (b.why)
@@ -1817,10 +1438,42 @@ function renderBrief(b) {
1817
1438
  parts.push(`worth: ${b.worth ? worthLabel(b.worth) : "not stated"}`);
1818
1439
  if (b.touches)
1819
1440
  parts.push(`touches (${fromWord(b.touches)}): ${b.touches.text}`);
1441
+ if (b.demand)
1442
+ parts.push(`demand (${fromWord(b.demand)}): ${b.demand.text}`);
1820
1443
  if (b.sources.length > 0)
1821
1444
  parts.push(`sources: ${b.sources.join(", ")}`);
1822
1445
  return `Brief: ${parts.join(" · ")}`;
1823
1446
  }
1447
+ function renderBriefMarkdown(b, open, extra = {}) {
1448
+ const cell = (t) => t.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
1449
+ const rows = ["| | | |", "|---|---|---|"];
1450
+ const field = (label, f) => {
1451
+ if (f)
1452
+ rows.push(`| **${label}** | ${cell(f.text)} | ${fromWord(f)} |`);
1453
+ };
1454
+ rows.push(`| **Ask** | ${cell(b.ask.text)} | ${fromWord(b.ask)} |`);
1455
+ field("Why", b.why);
1456
+ field("Done", b.done);
1457
+ field("Smallest", b.smallest);
1458
+ rows.push(`| **Worth** | ${b.worth ? cell(worthLabel(b.worth)) : "_not stated_"} | ${b.worth ? "stated" : ""} |`);
1459
+ field("Touches", b.touches);
1460
+ field("Demand", b.demand);
1461
+ if (b.due)
1462
+ rows.push(`| **Due** | ${b.due} | from your words |`);
1463
+ const out = ["**Brief — draft**", "", ...rows];
1464
+ const files = [...b.sources, ...extra.files ?? []];
1465
+ if (files.length)
1466
+ out.push("", `Files: ${files.map((f) => `\`${cell(f)}\``).join(" · ")}`);
1467
+ if (extra.map)
1468
+ out.push("", `> ${cell(extra.map)}`);
1469
+ if (open.length) {
1470
+ out.push("", `**Still open** — ${open.length === 1 ? "one question" : `${open.length} questions`}:`, "");
1471
+ open.forEach((q, i) => out.push(`${i + 1}. ${q.question}`));
1472
+ } else
1473
+ out.push("", "Nothing open — say yes to file it.");
1474
+ return out.join(`
1475
+ `);
1476
+ }
1824
1477
  function briefToSpec(b) {
1825
1478
  const lines = [b.ask.text, ""];
1826
1479
  if (b.why)
@@ -1831,6 +1484,8 @@ function briefToSpec(b) {
1831
1484
  lines.push(`Smallest: ${b.smallest.text}`);
1832
1485
  if (b.touches)
1833
1486
  lines.push(`Touches: ${b.touches.text}`);
1487
+ if (b.demand)
1488
+ lines.push(`Demand: ${b.demand.text}`);
1834
1489
  if (b.worth)
1835
1490
  lines.push(`Worth: ${worthLabel(b.worth)}`);
1836
1491
  if (b.sources.length > 0)
@@ -1844,7 +1499,8 @@ function intakeReply(i) {
1844
1499
  const blank = i.blank.length > 0 ? ` Still blank: ${i.blank.join(", ")} — say them any time and the row updates.` : "";
1845
1500
  const under = i.parent ? ` under ${i.parent}` : "";
1846
1501
  const next = i.needsApproval ? "It waits for product's yes — they approve or defer it with a note; status tells you which." : "An engineer takes it from the sled; you will hear when it lands.";
1847
- return `On the sled as ${i.title}${under} — filed in ${i.filedIn}. ${size}, ${worth}.${blank} ${next}`;
1502
+ const name = i.key ? `${i.key} · ${i.title}` : i.title;
1503
+ return `On the sled as ${name}${under} — filed in ${i.filedIn}. ${size}, ${worth}.${blank} ${next}`;
1848
1504
  }
1849
1505
  function parseWorthBand(raw) {
1850
1506
  if (typeof raw !== "object" || raw === null)
@@ -1868,8 +1524,139 @@ function parseWorthBand(raw) {
1868
1524
  ...typeof w.note === "string" && w.note.trim() ? { note: clip(w.note, 140) } : {}
1869
1525
  };
1870
1526
  }
1871
- // ../../packages/shared/src/dispatch/plan.ts
1872
- init_format();
1527
+ // ../../packages/shared/src/intake/related.ts
1528
+ var STOP = new Set([
1529
+ "a",
1530
+ "an",
1531
+ "the",
1532
+ "and",
1533
+ "or",
1534
+ "of",
1535
+ "to",
1536
+ "in",
1537
+ "on",
1538
+ "for",
1539
+ "with",
1540
+ "by",
1541
+ "at",
1542
+ "from",
1543
+ "as",
1544
+ "is",
1545
+ "are",
1546
+ "be",
1547
+ "it",
1548
+ "its",
1549
+ "this",
1550
+ "that",
1551
+ "we",
1552
+ "our",
1553
+ "add",
1554
+ "new",
1555
+ "make",
1556
+ "fix",
1557
+ "update",
1558
+ "support",
1559
+ "when",
1560
+ "into",
1561
+ "up",
1562
+ "via"
1563
+ ]);
1564
+ // ../../packages/shared/src/dispatch/plan.ts
1565
+ init_format();
1566
+
1567
+ // ../../packages/shared/src/intake/due.ts
1568
+ var MONTHS = {
1569
+ jan: 1,
1570
+ january: 1,
1571
+ feb: 2,
1572
+ february: 2,
1573
+ mar: 3,
1574
+ march: 3,
1575
+ apr: 4,
1576
+ april: 4,
1577
+ may: 5,
1578
+ jun: 6,
1579
+ june: 6,
1580
+ jul: 7,
1581
+ july: 7,
1582
+ aug: 8,
1583
+ august: 8,
1584
+ sep: 9,
1585
+ sept: 9,
1586
+ september: 9,
1587
+ oct: 10,
1588
+ october: 10,
1589
+ nov: 11,
1590
+ november: 11,
1591
+ dec: 12,
1592
+ december: 12
1593
+ };
1594
+ var MONTH_RE = "(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)";
1595
+ var LEAD = "(?:by|before|due|until|no later than|target(?:ed)?(?: for)?|deadline(?: is)?|needed(?: by)?|end of|eo)\\s*(?:the\\s+)?";
1596
+ var iso = (y, m, d) => {
1597
+ if (m < 1 || m > 12 || d < 1 || d > 31)
1598
+ return null;
1599
+ const t = Date.UTC(y, m - 1, d);
1600
+ const dt = new Date(t);
1601
+ if (dt.getUTCMonth() !== m - 1 || dt.getUTCDate() !== d)
1602
+ return null;
1603
+ return dt.toISOString().slice(0, 10);
1604
+ };
1605
+ var lastDay = (y, m) => new Date(Date.UTC(y, m, 0)).getUTCDate();
1606
+ function yearFor(m, d, now) {
1607
+ const n = new Date(now);
1608
+ const y = n.getUTCFullYear();
1609
+ const candidate = Date.UTC(y, m - 1, d);
1610
+ return candidate < Date.UTC(y, n.getUTCMonth(), n.getUTCDate()) - 86400000 ? y + 1 : y;
1611
+ }
1612
+ function parseDue(text, now = Date.now()) {
1613
+ if (!text)
1614
+ return null;
1615
+ const t = text.replace(/\s+/g, " ");
1616
+ const isoM = new RegExp(`(?:${LEAD})?(\\d{4})-(\\d{2})-(\\d{2})\\b`, "i").exec(t);
1617
+ if (isoM) {
1618
+ const r = iso(Number(isoM[1]), Number(isoM[2]), Number(isoM[3]));
1619
+ if (r)
1620
+ return r;
1621
+ }
1622
+ const mdy = new RegExp(`${LEAD}${MONTH_RE}\\.?\\s+(\\d{1,2})(?:st|nd|rd|th)?(?:,?\\s+(\\d{4}))?\\b`, "i").exec(t);
1623
+ if (mdy) {
1624
+ const m = MONTHS[(mdy[1] ?? "").toLowerCase()] ?? 0;
1625
+ const d = Number(mdy[2]);
1626
+ const y = mdy[3] ? Number(mdy[3]) : yearFor(m, d, now);
1627
+ const r = iso(y, m, d);
1628
+ if (r)
1629
+ return r;
1630
+ }
1631
+ const dmy = new RegExp(`${LEAD}(\\d{1,2})(?:st|nd|rd|th)?\\s+(?:of\\s+)?${MONTH_RE}\\.?(?:,?\\s+(\\d{4}))?\\b`, "i").exec(t);
1632
+ if (dmy) {
1633
+ const d = Number(dmy[1]);
1634
+ const m = MONTHS[(dmy[2] ?? "").toLowerCase()] ?? 0;
1635
+ const y = dmy[3] ? Number(dmy[3]) : yearFor(m, d, now);
1636
+ const r = iso(y, m, d);
1637
+ if (r)
1638
+ return r;
1639
+ }
1640
+ const eom = new RegExp(`(?:end of|eo)\\s*(?:the\\s+)?(?:${MONTH_RE}|month|m\\b)`, "i").exec(t);
1641
+ if (eom) {
1642
+ const n = new Date(now);
1643
+ const named = eom[1] ? MONTHS[eom[1].toLowerCase()] ?? 0 : 0;
1644
+ const m = named || n.getUTCMonth() + 1;
1645
+ const y = named ? yearFor(m, 1, now) : n.getUTCFullYear();
1646
+ return iso(y, m, lastDay(y, m));
1647
+ }
1648
+ const slash = new RegExp(`${LEAD}(\\d{1,2})/(\\d{1,2})(?:/(\\d{2,4}))?\\b`, "i").exec(t);
1649
+ if (slash) {
1650
+ const m = Number(slash[1]);
1651
+ const d = Number(slash[2]);
1652
+ const yy = slash[3] ? Number(slash[3]) : null;
1653
+ const y = yy === null ? yearFor(m, d, now) : yy < 100 ? 2000 + yy : yy;
1654
+ const r = iso(y, m, d);
1655
+ if (r)
1656
+ return r;
1657
+ }
1658
+ return null;
1659
+ }
1873
1660
 
1874
1661
  // ../../packages/shared/src/mcp/server.ts
1875
1662
  init_format();
@@ -1879,56 +1666,112 @@ init_format();
1879
1666
  var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1880
1667
  var str2 = (v) => typeof v === "string" ? v : undefined;
1881
1668
  var num2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
1882
- function statusName(row2) {
1883
- const source = isObject2(row2.source) ? row2.source : {};
1669
+ function statusName(row) {
1670
+ const source = isObject2(row.source) ? row.source : {};
1884
1671
  const kind = str2(source.kind);
1885
1672
  const doc = str2(source.doc);
1886
- const title = str2(row2.title) ?? str2(row2.project) ?? "this row";
1887
- if ((kind === "linear" || kind === "github") && doc)
1673
+ const title = str2(row.title) ?? str2(row.project) ?? "this row";
1674
+ if ((kind === "linear" || kind === "github" || kind === "jira") && doc)
1888
1675
  return `${doc} · ${title}`;
1889
- return title;
1676
+ const key = str2(row.key);
1677
+ return key ? `${key} · ${title}` : title;
1890
1678
  }
1891
- function stateTail(row2) {
1892
- const retired = str2(row2.retired);
1679
+ function stateTail(row) {
1680
+ const retired = str2(row.retired);
1893
1681
  if (retired)
1894
1682
  return ` Retired: ${retired}.`;
1895
- if (row2.stale === true)
1683
+ if (row.stale === true)
1896
1684
  return " Stale: no movement in 14 days.";
1897
1685
  return "";
1898
1686
  }
1899
- function statusLine(row2, now) {
1900
- return `${statusBody(row2, now)}${stateTail(row2)}`;
1687
+ function statusLine(row, now) {
1688
+ return `${statusBody(row, now)}${trackerTail(row)}${stateTail(row)}${assetsTail(row)}`;
1689
+ }
1690
+ function assetsTail(row) {
1691
+ const assets = Array.isArray(row.assets) ? row.assets.filter(isObject2) : [];
1692
+ if (assets.length === 0)
1693
+ return "";
1694
+ const parts = assets.map((a) => {
1695
+ const name = str2(a.name) ?? "file";
1696
+ const kind = str2(a.kind);
1697
+ const url = str2(a.url);
1698
+ return `${name}${kind && kind !== "other" ? ` (${kind})` : ""}${url ? ` ${url}` : ""}`;
1699
+ });
1700
+ return ` On the row: ${parts.join(" · ")}.`;
1901
1701
  }
1902
- function statusBody(row2, now) {
1903
- const name = statusName(row2);
1904
- const stage = str2(row2.stage) ?? "ready";
1905
- const handoff = isObject2(row2.handoff) ? row2.handoff : null;
1906
- const work = isObject2(row2.work) ? row2.work : null;
1907
- const worth = isObject2(row2.worth) ? row2.worth : null;
1908
- const band = bandLine(row2.band);
1702
+ function trackerTail(row) {
1703
+ const source = isObject2(row.source) ? row.source : {};
1704
+ const kind = str2(source.kind);
1705
+ if (kind !== "github" && kind !== "jira" && kind !== "linear")
1706
+ return "";
1707
+ const bits = [];
1708
+ const openedBy = str2(source.openedBy);
1709
+ const assignee = str2(source.assignee);
1710
+ if (openedBy)
1711
+ bits.push(`opened by ${openedBy}`);
1712
+ if (assignee)
1713
+ bits.push(`assigned to ${assignee}`);
1714
+ else if (openedBy)
1715
+ bits.push("nobody assigned");
1716
+ const issueType = str2(source.issueType);
1717
+ const parentKey = str2(source.parentKey);
1718
+ if (issueType)
1719
+ bits.push(parentKey ? `${issueType.toLowerCase()} under ${parentKey}` : issueType.toLowerCase());
1720
+ const mergedBy = str2(source.mergedBy);
1721
+ if (mergedBy)
1722
+ bits.push(`merged by ${mergedBy}`);
1723
+ const agent = str2(source.agent);
1724
+ if (agent)
1725
+ bits.push(`built with ${agent}`);
1726
+ const diff = isObject2(source.diff) ? source.diff : null;
1727
+ if (diff) {
1728
+ const add = num2(diff.additions) ?? 0;
1729
+ const del = num2(diff.deletions) ?? 0;
1730
+ const files = num2(diff.files) ?? 0;
1731
+ const commits = num2(diff.commits) ?? 0;
1732
+ bits.push(`+${add} −${del}${files ? ` · ${files} ${files === 1 ? "file" : "files"}` : ""}${commits ? ` · ${commits} ${commits === 1 ? "commit" : "commits"}` : ""}`);
1733
+ }
1734
+ const blockedBy = Array.isArray(source.blockedBy) ? source.blockedBy.filter((b) => typeof b === "string") : [];
1735
+ if (blockedBy.length)
1736
+ bits.push(`blocked by ${blockedBy.join(", ")}`);
1737
+ const held = str2(source.held);
1738
+ if (held)
1739
+ bits.push(`held: "${held}"`);
1740
+ if (bits.length === 0)
1741
+ return "";
1742
+ const where = kind === "github" ? "GitHub" : kind === "jira" ? "Jira" : "Linear";
1743
+ return ` In ${where}: ${bits.join(" · ")}.`;
1744
+ }
1745
+ function statusBody(row, now) {
1746
+ const name = statusName(row);
1747
+ const stage = str2(row.stage) ?? "ready";
1748
+ const handoff = isObject2(row.handoff) ? row.handoff : null;
1749
+ const work = isObject2(row.work) ? row.work : null;
1750
+ const worth = isObject2(row.worth) ? row.worth : null;
1751
+ const band = bandLine(row.band);
1909
1752
  const sized = band ? `Sized ${band}` : "Not sized";
1910
1753
  const worthText = worth ? `worth ${worthLabel(worth)}` : "worth not stated";
1911
1754
  const ago = (ts) => num2(ts) !== undefined ? `${shortDuration(Math.max(0, now - num2(ts)))} ago` : "";
1912
1755
  if (stage === "landed") {
1913
- const mins = num2(row2.actualMinutes);
1914
- const dollars2 = num2(row2.actualUsd);
1756
+ const mins = num2(row.actualMinutes);
1757
+ const dollars = num2(row.actualUsd);
1915
1758
  const credits = work ? num2(work.seatCreditsUsd) : undefined;
1916
1759
  const bits = [
1917
1760
  mins !== undefined ? `${Math.round(mins)} min` : null,
1918
- dollars2 !== undefined && dollars2 > 0 ? usd2(dollars2) : null,
1761
+ dollars !== undefined && dollars > 0 ? usd2(dollars) : null,
1919
1762
  credits !== undefined ? `${usd2(credits)} credits` : null,
1920
1763
  work ? str2(work.model) : null
1921
1764
  ].filter((b) => Boolean(b));
1922
1765
  const ref = work ? str2(work.ref) : undefined;
1923
1766
  return `${name} — landed${bits.length ? `: ${bits.join(" · ")}` : ""}${ref ? ` · ${ref}` : ""}. ${sized}, ${worthText}.`;
1924
1767
  }
1925
- const tree = isObject2(row2.tree) ? row2.tree : null;
1768
+ const tree = isObject2(row.tree) ? row.tree : null;
1926
1769
  const treeText = treeLine(tree);
1927
1770
  if (stage === "on_sled" && handoff) {
1928
1771
  const who = str2(handoff.pickedBy) ?? "someone";
1929
1772
  return `${name} — on the sled: ${who} has it (picked ${ago(handoff.at)}). ${sized}, ${worthText}.${treeText} You will hear when it lands.`;
1930
1773
  }
1931
- const approval = isObject2(row2.approval) ? row2.approval : null;
1774
+ const approval = isObject2(row.approval) ? row.approval : null;
1932
1775
  if (stage === "deferred") {
1933
1776
  const by = approval ? str2(approval.by) ?? "product" : "product";
1934
1777
  const note = approval ? str2(approval.note) : undefined;
@@ -2043,12 +1886,139 @@ function planReply(epic, rows, total) {
2043
1886
  `);
2044
1887
  }
2045
1888
 
1889
+ // ../../packages/shared/src/mcp/ui/intakeView.ts
1890
+ var INTAKE_VIEW_URI = "ui://sleds/intake";
1891
+ var MCP_APP_MIME = "text/html;profile=mcp-app";
1892
+ var INTAKE_VIEW_HTML = `<!doctype html>
1893
+ <html lang="en"><head><meta charset="utf-8"><title>SLEDS · intake</title>
1894
+ <style>
1895
+ :root{
1896
+ --bg:var(--color-background-primary,light-dark(#ffffff,#0f1116));
1897
+ --bg2:var(--color-background-secondary,light-dark(#f4f5f2,#14171e));
1898
+ --fg:var(--color-text-primary,light-dark(#171717,#f4f5f2));
1899
+ --fg2:var(--color-text-secondary,light-dark(#555a66,#9aa0ad));
1900
+ --fg3:light-dark(#7a7f8a,#6b6f7b);
1901
+ --line:var(--color-border-primary,light-dark(#e3e4e0,#2a2e38));
1902
+ --line2:light-dark(#c9cbd1,#3a4050);
1903
+ --volt:#d9dd45;--volt-ink:#0b0d11;--volt-dim:rgba(217,221,69,.12);
1904
+ --peri:#8b90ff;--peri-dim:rgba(139,144,255,.14);--warning:#ffb02e;--warning-dim:rgba(255,176,46,.12);--success:#55e08c;--success-dim:rgba(85,224,140,.12);--error:#ff5c47;
1905
+ --sans:var(--font-sans,system-ui,-apple-system,"Segoe UI",sans-serif);--mono:var(--font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
1906
+ color-scheme:light dark;
1907
+ }
1908
+ *{box-sizing:border-box}
1909
+ html,body{margin:0;background:transparent;color:var(--fg);font:14px/1.45 var(--sans)}
1910
+ .card{border:1px solid var(--line);border-radius:10px;background:var(--bg);padding:14px 16px}
1911
+ .h{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px}
1912
+ .l{font:600 11px var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--fg3)}.l b{color:var(--volt);font-weight:600}
1913
+ .title{font-weight:600;font-size:16px;line-height:1.3}.meta{font:11px var(--mono);color:var(--fg3);margin-top:3px}
1914
+ .kv{display:grid;grid-template-columns:88px 1fr;gap:6px 12px;margin:12px 0;font-size:13px}
1915
+ .k{font:11px var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--fg3);padding-top:3px}
1916
+ .from{font:11px var(--mono);color:var(--fg3);margin-left:6px;white-space:nowrap}
1917
+ .v[contenteditable]{border-bottom:1px dashed var(--line2);outline:none;padding-bottom:1px}
1918
+ .v[contenteditable]:focus{border-bottom-color:var(--volt)}
1919
+ .q{border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin:8px 0}
1920
+ .q .t{font-weight:500}
1921
+ .q textarea{width:100%;margin-top:8px;background:var(--bg2);border:1px solid var(--line2);border-radius:6px;color:var(--fg);padding:8px 10px;font:14px var(--sans);resize:vertical;min-height:38px}
1922
+ .q textarea:focus{outline:none;border-color:var(--volt)}
1923
+ .opt{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
1924
+ .opt button{font:12px var(--sans);border:1px solid var(--line2);border-radius:14px;padding:3px 10px;color:var(--fg2);background:transparent;cursor:pointer}
1925
+ .opt button.on{border-color:var(--volt);color:var(--fg);background:var(--volt-dim)}
1926
+ .chips{display:flex;gap:6px;flex-wrap:wrap;margin:6px 0 10px}
1927
+ .chip{font:11px var(--mono);padding:2px 7px;border-radius:4px;border:1px solid var(--line2);color:var(--fg2)}
1928
+ .chip.w{border-color:var(--warning);color:var(--warning);background:var(--warning-dim)}.chip.p{border-color:var(--peri);color:var(--peri);background:var(--peri-dim)}.chip.s{border-color:var(--success);color:var(--success);background:var(--success-dim)}.chip.v{border-color:var(--volt);color:var(--volt);background:var(--volt-dim)}
1929
+ .map{font-size:12px;color:var(--fg2);border-left:2px solid var(--line2);padding:2px 10px;margin:8px 0}
1930
+ .acts{display:flex;gap:8px;align-items:center;border-top:1px solid var(--line);padding-top:12px;margin-top:6px;flex-wrap:wrap}
1931
+ .verb{border:1px solid var(--line2);border-radius:6px;padding:6px 12px;font:500 13px var(--sans);background:transparent;color:var(--fg);cursor:pointer}
1932
+ .verb.pri{background:var(--volt);color:var(--volt-ink);border-color:var(--volt);font-weight:600}
1933
+ .verb:disabled{opacity:.5;cursor:default}
1934
+ .n{font:11px var(--mono);color:var(--fg3);margin-left:auto}
1935
+ .err{color:var(--error);font-size:12px;margin-top:8px}
1936
+ a{color:var(--peri)}
1937
+ </style></head><body>
1938
+ <div id="root" class="card"><div class="l"><b>SLEDS</b> · intake</div><div class="meta">waiting for the brief…</div></div>
1939
+ <script>
1940
+ (function(){
1941
+ var seq=1, pending={}, args={}, state=null, busy=false, err="";
1942
+ var parent=window.parent;
1943
+ function send(method, params){ var id=seq++; return new Promise(function(res,rej){ pending[id]={res:res,rej:rej}; parent.postMessage({jsonrpc:"2.0",id:id,method:method,params:params||{}},"*"); }); }
1944
+ function notify(method, params){ parent.postMessage({jsonrpc:"2.0",method:method,params:params||{}},"*"); }
1945
+ window.addEventListener("message", function(ev){
1946
+ var m=ev.data; if(!m||m.jsonrpc!=="2.0") return;
1947
+ if(m.id!==undefined && (m.result!==undefined||m.error!==undefined) && pending[m.id]){ var p=pending[m.id]; delete pending[m.id]; m.error?p.rej(m.error):p.res(m.result); return; }
1948
+ if(m.method==="ui/notifications/tool-input"){ args=(m.params&&m.params.arguments)||{}; return; }
1949
+ if(m.method==="ui/notifications/tool-result"){ onResult(m.params||{}); return; }
1950
+ if(m.method==="ui/notifications/host-context-changed"){ applyStyles(m.params||{}); return; }
1951
+ });
1952
+ function applyStyles(hc){ var vars=(hc&&hc.styles&&hc.styles.variables)||{}; var root=document.documentElement; Object.keys(vars).forEach(function(k){ root.style.setProperty(k, vars[k]); }); if(hc&&hc.theme) root.style.colorScheme=hc.theme; }
1953
+ function onResult(r){ var s=r.structuredContent; if(s&&s.kind){ state=s; } else { state={kind:"text", text:((r.content||[]).map(function(c){return c.text||"";}).join("\\n"))}; } busy=false; err=r.isError?"":err; render(); }
1954
+ function esc(s){ return String(s==null?"":s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"); }
1955
+ function fit(){ var h=document.documentElement.scrollHeight; notify("ui/notifications/size-changed",{height:h}); }
1956
+ var FIELDS=["ask","why","done","smallest","touches","demand"];
1957
+ var LABEL={ask:"Ask",why:"Why",done:"Done",smallest:"Smallest",touches:"Touches",demand:"Demand",worth:"Worth"};
1958
+ var WORTH=["critical","high","medium","low"];
1959
+ var picked={worth:null};
1960
+ function render(){
1961
+ var root=document.getElementById("root");
1962
+ if(!state){ return; }
1963
+ if(state.kind==="text"){ root.innerHTML='<div class="l"><b>SLEDS</b> · intake</div><div style="white-space:pre-wrap;margin-top:8px">'+esc(state.text)+'</div>'; fit(); return; }
1964
+ if(state.kind==="filed"){
1965
+ root.innerHTML='<div class="h"><span class="l"><b>SLEDS</b> · on the sled</span><span class="chip '+(state.needsApproval?'w':'s')+'">'+(state.needsApproval?"waits for product's yes":"ready")+'</span></div>'
1966
+ +'<div class="title">'+esc(state.title)+'</div><div class="meta">'+esc(state.band||"")+(state.worth?' · worth '+esc(state.worth):' · worth not stated')+' · filed in '+esc(state.filedIn||"SLEDS")+'</div>'
1967
+ +(state.attached&&state.attached.length?'<div class="chips">'+state.attached.map(function(a){return '<span class="chip '+(a.ok?'p':'')+'">'+esc(a.name)+(a.ok?'':' · not stored')+'</span>';}).join("")+'</div>':'')
1968
+ +(state.blank&&state.blank.length?'<div class="map">Still blank: '+esc(state.blank.join(", "))+' — engineering may ask.</div>':'')
1969
+ +'<div class="acts">'+(state.rowUrl?'<button class="verb pri" id="open">Open the row</button>':'')+'<span class="n">'+esc(state.project||"")+'</span></div>';
1970
+ var o=document.getElementById("open"); if(o) o.onclick=function(){ send("ui/open-link",{url:state.rowUrl}); };
1971
+ fit(); return;
1972
+ }
1973
+ var b=state.brief||{}; var open=state.open||[];
1974
+ var html='<div class="h"><span class="l"><b>SLEDS</b> · intake · draft</span>'+(open.length?'<span class="chip w">'+open.length+' question'+(open.length===1?'':'s')+' open</span>':'<span class="chip s">ready to file</span>')+'</div>';
1975
+ html+='<div class="title">'+esc(b.ask&&b.ask.text||"")+'</div><div class="meta">from '+esc(b.ask&&b.ask.from||"the artifact")+(state.needed&&state.needed.length?' · needs '+esc(state.needed.join(" and "))+' to file':' · nothing is filed until you confirm')+'</div>';
1976
+ html+='<div class="kv">';
1977
+ FIELDS.slice(1).forEach(function(k){ var f=b[k]; if(!f) return; html+='<div class="k">'+LABEL[k]+'</div><div><span class="v" contenteditable="true" data-k="'+k+'">'+esc(f.text)+'</span><span class="from">from the '+esc(f.from)+'</span></div>'; });
1978
+ if(b.worth){ html+='<div class="k">Worth</div><div><span class="chip v">'+esc(b.worth.level)+'</span>'+(b.worth.note?'<span class="from">'+esc(b.worth.note)+'</span>':'')+'</div>'; }
1979
+ if(b.due){ html+='<div class="k">Due</div><div>'+esc(b.due)+'<span class="from">from your words</span></div>'; }
1980
+ html+='</div>';
1981
+ if(state.map){ html+='<div class="map">What the product says: '+esc(state.map)+'</div>'; }
1982
+ open.forEach(function(q){
1983
+ html+='<div class="q"><div class="t">'+esc(q.question)+'</div>';
1984
+ if(q.key==="worth"){ html+='<div class="opt">'+WORTH.map(function(w){return '<button type="button" data-w="'+w+'" class="'+(picked.worth===w?'on':'')+'">'+w+'</button>';}).join("")+'</div><textarea data-q="worth" rows="1" placeholder="…or a customer, a number, a renewal"></textarea>'; }
1985
+ else { html+='<textarea data-q="'+esc(q.key)+'" rows="2" placeholder="In your words"></textarea>'; }
1986
+ html+='</div>';
1987
+ });
1988
+ var files=(state.links||[]).map(function(l){return '<span class="chip">'+esc(l)+'</span>';}).concat((state.attachments||[]).map(function(a){return '<span class="chip p">'+esc(a)+'</span>';}));
1989
+ if(files.length) html+='<div class="chips">'+files.join("")+'</div>';
1990
+ html+='<div class="acts"><button class="verb pri" id="confirm"'+(busy?' disabled':'')+'>'+(busy?"Filing…":"Put it on the sled")+'</button><span class="n">'+(state.needsApproval?"waits for product's yes":"")+'</span></div>';
1991
+ if(err) html+='<div class="err">'+esc(err)+'</div>';
1992
+ root.innerHTML=html;
1993
+ root.querySelectorAll("[data-w]").forEach(function(btn){ btn.onclick=function(){ picked.worth=btn.getAttribute("data-w"); root.querySelectorAll("[data-w]").forEach(function(x){x.classList.toggle("on",x===btn);}); }; });
1994
+ root.querySelectorAll("textarea").forEach(function(t){ t.oninput=fit; });
1995
+ document.getElementById("confirm").onclick=confirm;
1996
+ fit();
1997
+ }
1998
+ function collect(){
1999
+ var answers={}; var root=document.getElementById("root");
2000
+ var a=(args&&args.answers)||{}; Object.keys(a).forEach(function(k){ if(a[k]) answers[k]=a[k]; });
2001
+ root.querySelectorAll(".v[contenteditable]").forEach(function(el){ var k=el.getAttribute("data-k"); var t=el.textContent.trim(); var b=(state.brief||{})[k]; if(t && (!b || b.text!==t || b.from==="filer")) answers[k]=t; });
2002
+ root.querySelectorAll("textarea[data-q]").forEach(function(t){ var k=t.getAttribute("data-q"); var v=t.value.trim(); if(k==="worth"){ var w=[picked.worth,v].filter(Boolean).join(" · "); if(w) answers.worth=w; } else if(v) answers[k]=v; });
2003
+ return answers;
2004
+ }
2005
+ function confirm(){
2006
+ if(busy) return; busy=true; err=""; render();
2007
+ var call={ text:args.text||"", confirm:true, answers:collect() };
2008
+ if(args.links) call.links=args.links; if(args.attachments) call.attachments=args.attachments; if(args.added) call.added=args.added; if(args.due) call.due=args.due; if(args.parent) call.parent=args.parent;
2009
+ send("tools/call",{name:"intake",arguments:call}).then(function(r){ onResult(r||{}); if(r&&r.isError){ err=((r.content||[]).map(function(c){return c.text||"";}).join(" ")); busy=false; render(); } })
2010
+ .catch(function(e){ err=(e&&e.message)||"failed"; busy=false; render(); });
2011
+ }
2012
+ send("ui/initialize",{appCapabilities:{availableDisplayModes:["inline"]}}).then(function(r){ applyStyles((r&&r.hostContext)||{}); notify("ui/notifications/initialized",{}); }).catch(function(){ notify("ui/notifications/initialized",{}); });
2013
+ })();
2014
+ </script></body></html>`;
2015
+
2046
2016
  // ../../packages/shared/src/mcp/server.ts
2047
- function fetchClient(o2) {
2048
- const fetchFn = o2.fetchFn ?? fetch;
2049
- const base = o2.proxyUrl.replace(/\/+$/, "");
2050
- const headers = { "content-type": "application/json", authorization: `Bearer ${o2.token}` };
2051
- const timeout = () => AbortSignal.timeout(o2.timeoutMs ?? MCP_TIMEOUT_MS);
2017
+ function fetchClient(o) {
2018
+ const fetchFn = o.fetchFn ?? fetch;
2019
+ const base = o.proxyUrl.replace(/\/+$/, "");
2020
+ const headers = { "content-type": "application/json", authorization: `Bearer ${o.token}` };
2021
+ const timeout = () => AbortSignal.timeout(o.timeoutMs ?? MCP_TIMEOUT_MS);
2052
2022
  return {
2053
2023
  async post(path, body) {
2054
2024
  try {
@@ -2106,15 +2076,15 @@ var SERVER_INFO = {
2106
2076
  description: "The cost and limits of AI agent work — estimate, hand off, and track what a task takes.",
2107
2077
  websiteUrl: "https://sleds.ai",
2108
2078
  icons: [
2109
- { src: "https://agent.sleds.ai/icon.png", mimeType: "image/png", sizes: ["192x192"] },
2110
- { src: "https://agent.sleds.ai/icon.svg", mimeType: "image/svg+xml", sizes: ["any"] }
2079
+ { src: "https://mcp.sleds.ai/icon.png", mimeType: "image/png", sizes: ["192x192"] },
2080
+ { src: "https://mcp.sleds.ai/icon.svg", mimeType: "image/svg+xml", sizes: ["any"] }
2111
2081
  ]
2112
2082
  };
2113
2083
  var SERVER_INSTRUCTIONS = [
2114
- "SLEDS is the ledger of engineering work: every piece is sized before it runs (a cost and time band, never a point figure) and closed with a measured actual after. You are the head; SLEDS holds the record. The person may be a PM or designer describing something they want built, or an engineer about to build — read which from the conversation.",
2115
- 'When someone describes work for engineering (a feature, a fix, a spec, a PRD, "can we…"): offer once to put it on the sled. If they say yes, use `intake` — it drafts the brief, asks only what it cannot answer (who it is for and why now, what done looks like, the smallest version, the worth) two questions at a time, and writes nothing until they confirm. Never guess worth: leave it blank and say so. Afterwards tell them in plain words that it is on the sled, the band, and whether it waits for product\'s yes. They never need to learn a verb.',
2116
- "When you are about to build something that will take more than a few minutes: `status` first if a row may already exist for it; otherwise `estimate` with a stable `source` (kind · project · doc · item) before the first line of work, and say the band. When it lands: `actual` with the same `source`, the state, where the output lives (`ref`), the model, and your session link (`sessionId`). Do not pass `minutes` unless a person stated them — SLEDS measures the time itself. A follow-up to something already landed is a new `item` under the same doc, never an overwrite. An `actual` with no estimate still records — the row is created and shown as landed unsized — but the band you skipped is the point, so estimate first.",
2117
- "If SLEDS says there is no estimate for the source, size it first and send the actual again. If it says unreachable, try once more; if that fails, move on and tell the person the row is still owed. Never invent a number to get past an error."
2084
+ "SLEDS is the ledger of engineering work: every piece is sized before it runs (a cost and time band, never a point figure) and closed with a measured actual after. You are the head; SLEDS holds the record. The person may be a PM or designer describing something they want built, or an engineer about to build — read which.",
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 have 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 row 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.',
2086
+ "When you are about to build something that will take more than a few minutes: `status` first if a row may exist for it; otherwise `estimate` with a stable `source` (kind · project · doc · item) before the first line of work, and say the band. When it lands: `actual` with the same `source`, the state, where the output lives (`ref`), the model, and your session link (`sessionId`). Do not pass `minutes` unless a person stated them — SLEDS measures the time itself. A follow-up to landed work is a new `item` under the same doc, never an overwrite. An `actual` with no estimate still records (landed unsized), but the band you skipped is the point, so estimate first.",
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 row is still owed. Never invent a number to get past an error."
2118
2088
  ].join(`
2119
2089
 
2120
2090
  `);
@@ -2162,7 +2132,8 @@ var TOOLS = [
2162
2132
  },
2163
2133
  {
2164
2134
  name: "intake",
2165
- description: 'Use this when a person describes work they want engineering to do — a feature, a fix, a spec, a PRD, "can we…" — and has said yes to putting it on the sled. Sends the ask to engineering from this conversation (spec 13). Pass what the person sent — a paragraph, a spec, a PRD — plus any links. Without `confirm` nothing is written: you get the brief drafted from it and the questions still open (who it is for and why now, what done looks like, the smallest version, the worth, what it touches) — ask at most two at a time, in your own words, and skip any the person already answered. Put their answers in `answers`, show them the brief, and when they say yes call again with `confirm: true`: the row is filed on the sled, sized, with their name on it. A brief without a why and a done is a draft and will not file. Worth is never guessed.',
2135
+ _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 row is filed on the sled, sized, with their name on it. A brief without a why and a done is a draft and will not file. Worth is never guessed.',
2166
2137
  inputSchema: {
2167
2138
  type: "object",
2168
2139
  properties: {
@@ -2175,6 +2146,28 @@ var TOOLS = [
2175
2146
  items: { type: "string" },
2176
2147
  description: "Links and file names that came with it (a Figma frame, a Loom, a doc)."
2177
2148
  },
2149
+ attachments: {
2150
+ type: "array",
2151
+ description: 'The files themselves, stored on the row so anyone on the board can open them: the spec or PRD as markdown, a prototype you made as a single HTML page, a screenshot. Sent only on `confirm`. Up to 4 MB each; text as is, an image as base64 with `encoding: "base64"`. The same name again replaces.',
2152
+ items: {
2153
+ type: "object",
2154
+ properties: {
2155
+ name: {
2156
+ type: "string",
2157
+ description: "File name with extension: spec.md, prototype.html, before.png."
2158
+ },
2159
+ kind: {
2160
+ type: "string",
2161
+ enum: ["spec", "prototype", "screenshot", "doc", "other"],
2162
+ description: "Read from the name and mime when omitted."
2163
+ },
2164
+ mime: { type: "string", description: "text/markdown, text/html, image/png…" },
2165
+ content: { type: "string", description: "The file: text as is, or base64." },
2166
+ encoding: { type: "string", enum: ["text", "base64"] }
2167
+ },
2168
+ required: ["name", "content"]
2169
+ }
2170
+ },
2178
2171
  answers: {
2179
2172
  type: "object",
2180
2173
  description: "The person's answers, in their words. Each overrides what the artifact said.",
@@ -2187,9 +2180,25 @@ var TOOLS = [
2187
2180
  type: "string",
2188
2181
  description: "A level (high / medium / low), dollars, a customer — e.g. 'high · Acme renewal'."
2189
2182
  },
2190
- touches: { type: "string", description: "Areas or systems it touches." }
2183
+ touches: {
2184
+ type: "string",
2185
+ description: "Areas or systems it touches, and where in the product it would live."
2186
+ },
2187
+ demand: {
2188
+ type: "string",
2189
+ description: "How many have asked for it, or how often it comes up."
2190
+ }
2191
2191
  }
2192
2192
  },
2193
+ added: {
2194
+ type: "array",
2195
+ 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 row as the AI's contribution; an approver reads it before saying yes."
2197
+ },
2198
+ due: {
2199
+ type: "string",
2200
+ description: "A due day the person stated, as YYYY-MM-DD — only when they said one ('by Oct 10', 'end of the month'). Never ask for it. Read from the text on its own when you leave it out."
2201
+ },
2193
2202
  confirm: {
2194
2203
  type: "boolean",
2195
2204
  description: "True only after the person said yes to the brief. Files the row."
@@ -2295,6 +2304,64 @@ var TOOLS = [
2295
2304
  }
2296
2305
  }
2297
2306
  },
2307
+ {
2308
+ 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 rows 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
+ inputSchema: {
2311
+ type: "object",
2312
+ properties: {
2313
+ text: { type: "string", description: "The ask or the question, in the person's words." }
2314
+ },
2315
+ required: ["text"]
2316
+ }
2317
+ },
2318
+ {
2319
+ name: "attach",
2320
+ description: "Put a file on a row already on the sled 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. Pass the row's project key (from status, or what intake said) and the file. Up to 4 MB; the same name again replaces the earlier one. `intake` takes the same files as `attachments` when filing.",
2321
+ inputSchema: {
2322
+ type: "object",
2323
+ properties: {
2324
+ project: {
2325
+ type: "string",
2326
+ description: "The row's project key, e.g. manual:intake/help-and-support or jira:WORK/WORK-7551."
2327
+ },
2328
+ name: {
2329
+ type: "string",
2330
+ description: "File name with extension: spec.md, prototype.html, before.png."
2331
+ },
2332
+ kind: {
2333
+ type: "string",
2334
+ enum: ["spec", "prototype", "screenshot", "doc", "other"],
2335
+ description: "Read from the name and mime when omitted."
2336
+ },
2337
+ mime: { type: "string", description: "text/markdown, text/html, image/png…" },
2338
+ content: {
2339
+ type: "string",
2340
+ description: "The file: text as is, or base64 with encoding base64."
2341
+ },
2342
+ encoding: { type: "string", enum: ["text", "base64"] }
2343
+ },
2344
+ required: ["project", "name", "content"]
2345
+ }
2346
+ },
2347
+ {
2348
+ 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 row); 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
+ inputSchema: {
2351
+ type: "object",
2352
+ properties: {
2353
+ answer: { type: "string", description: "The reply, in the person's words." },
2354
+ project: {
2355
+ type: "string",
2356
+ description: "The row's project key (from status or the question list) when more than one question is open."
2357
+ },
2358
+ id: {
2359
+ type: "string",
2360
+ description: "The question's id from the list, when more than one is open on the same row."
2361
+ }
2362
+ }
2363
+ }
2364
+ },
2298
2365
  {
2299
2366
  name: "handoffs",
2300
2367
  description: "The team's open hand-offs: tasks and walled sessions parked for pickup, the ones meant for this seat first. Each has an id for the pickup tool (or /sled pickup <id> in Claude Code).",
@@ -2484,6 +2551,10 @@ async function usageTool(ctx) {
2484
2551
  if (!ctx.client)
2485
2552
  return NOT_SET_UP;
2486
2553
  const raw = await ctx.client.get("/proxy/meter");
2554
+ if (raw && typeof raw.note === "string" && raw.seat?.filer === true)
2555
+ return {
2556
+ text: `${raw.note}. Intake, status, answer, touches and attach are yours; taking or sizing work is a seat's.`
2557
+ };
2487
2558
  const m = raw ? parseMeter(raw) : null;
2488
2559
  if (!m)
2489
2560
  return { text: "sleds offline (proxy did not answer).", isError: true };
@@ -2538,12 +2609,12 @@ function byModelText(raw) {
2538
2609
  if (!Array.isArray(raw))
2539
2610
  return "";
2540
2611
  const parts = [];
2541
- for (const row2 of raw) {
2542
- if (!isObject4(row2) || !isObject4(row2.standard))
2612
+ for (const row of raw) {
2613
+ if (!isObject4(row) || !isObject4(row.standard))
2543
2614
  continue;
2544
- const model = str4(row2.model);
2545
- const low = num3(row2.standard.lowUsd);
2546
- const high = num3(row2.standard.highUsd);
2615
+ const model = str4(row.model);
2616
+ const low = num3(row.standard.lowUsd);
2617
+ const high = num3(row.standard.highUsd);
2547
2618
  if (!model || low === undefined || high === undefined)
2548
2619
  continue;
2549
2620
  const m = /^claude-([a-z]+)-(\d+)(?:-(\d+))?/.exec(model);
@@ -2587,13 +2658,35 @@ function briefAnswers(raw) {
2587
2658
  if (!isObject4(raw))
2588
2659
  return {};
2589
2660
  const out = {};
2590
- for (const k of ["ask", "why", "done", "smallest", "worth", "touches"]) {
2661
+ for (const k of ["ask", "why", "done", "smallest", "worth", "touches", "demand"]) {
2591
2662
  const v = str4(raw[k])?.trim();
2592
2663
  if (v)
2593
2664
  out[k] = v;
2594
2665
  }
2595
2666
  return out;
2596
2667
  }
2668
+ async function readTouches(ctx, text) {
2669
+ if (!ctx.client || !text.trim())
2670
+ return null;
2671
+ const r = await ctx.client.get(`/proxy/touches?q=${encodeURIComponent(text.slice(0, 2000))}`);
2672
+ if (!r)
2673
+ return null;
2674
+ const mapped = r.mapped === true;
2675
+ const areas = Array.isArray(r.areas) ? r.areas : [];
2676
+ 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 };
2678
+ }
2679
+ async function touchesTool(ctx, args) {
2680
+ if (!ctx.client)
2681
+ return NOT_SET_UP;
2682
+ const text = str4(args.text)?.trim();
2683
+ if (!text)
2684
+ return { text: "Say what the ask or the question is (text).", isError: true };
2685
+ const r = await readTouches(ctx, text);
2686
+ if (!r)
2687
+ return UNREACHABLE;
2688
+ return { text: r.text || "Nothing in the product map matches these words." };
2689
+ }
2597
2690
  async function intakeTool(ctx, args) {
2598
2691
  if (!ctx.client)
2599
2692
  return NOT_SET_UP;
@@ -2607,18 +2700,35 @@ async function intakeTool(ctx, args) {
2607
2700
  const brief = draftBrief({ text, links, answers: briefAnswers(args.answers) });
2608
2701
  if (!brief)
2609
2702
  return { text: "Nothing to file. Pass text: the ask, a spec, or a paragraph.", isError: true };
2703
+ const map = await readTouches(ctx, [brief.ask.text, brief.why?.text, brief.done?.text].filter(Boolean).join(`
2704
+ `));
2705
+ if (map?.touchesLine && !brief.touches)
2706
+ brief.touches = { text: map.touchesLine, from: "product" };
2610
2707
  const open = openQuestions(brief);
2611
2708
  if (args.confirm !== true) {
2612
- const lines = [renderBrief(brief)];
2613
- if (open.length > 0) {
2614
- lines.push("", "Still open (ask at most two at a time, in your own words; skip what they already said):");
2615
- for (const q of open)
2616
- lines.push(`- ${q.key}: ${q.question}`);
2617
- }
2618
- const needed2 = missingToFile(brief);
2619
- lines.push("", 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. Show them the brief; when they say yes, call intake again with the same text, their answers, and confirm: true.");
2709
+ const needed = missingToFile(brief);
2710
+ const attachments = parseAttachments(args.attachments).map((a) => a.name || "file");
2711
+ const structured = {
2712
+ kind: "draft",
2713
+ brief: briefForView(brief),
2714
+ open,
2715
+ needed,
2716
+ map: map?.text ?? null,
2717
+ links,
2718
+ attachments
2719
+ };
2720
+ const lines = [
2721
+ renderBriefMarkdown(brief, open, { map: map?.text ?? null, files: attachments }),
2722
+ "",
2723
+ 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
+ needed.length > 0 ? `Nothing is filed yet. Filing needs: ${needed.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
+ "",
2726
+ "```json sleds-draft",
2727
+ JSON.stringify(structured),
2728
+ "```"
2729
+ ];
2620
2730
  return { text: lines.join(`
2621
- `) };
2731
+ `), structured };
2622
2732
  }
2623
2733
  const needed = missingToFile(brief);
2624
2734
  if (needed.length > 0)
@@ -2627,8 +2737,14 @@ async function intakeTool(ctx, args) {
2627
2737
  isError: true
2628
2738
  };
2629
2739
  const parent = str4(args.parent)?.trim() || undefined;
2740
+ const answers = briefAnswers(args.answers);
2741
+ const asked = Object.keys(QUESTIONS).filter((k) => answers[k]).map((k) => ({ q: QUESTIONS[k], a: answers[k] }));
2742
+ const added = Array.isArray(args.added) ? args.added.filter((l) => typeof l === "string" && l.trim().length > 0).map((l) => l.trim().slice(0, 200)).slice(0, 12) : [];
2743
+ const dueArg = typeof args.due === "string" && /^\d{4}-\d{2}-\d{2}$/.test(args.due) ? args.due : null;
2744
+ const due = dueArg ?? parseDue([text, ...Object.values(answers), ...added].filter(Boolean).join(`
2745
+ `));
2630
2746
  const r = await ctx.client.postFull("/proxy/intake", {
2631
- brief,
2747
+ brief: { ...brief, intake: { asked, added, related: [] }, ...due ? { due } : {} },
2632
2748
  ...parent ? { parent } : {}
2633
2749
  });
2634
2750
  if (!r)
@@ -2641,6 +2757,7 @@ async function intakeTool(ctx, args) {
2641
2757
  const j = r.json ?? {};
2642
2758
  const worth = parseWorthBand(j.worth) ?? brief.worth ?? null;
2643
2759
  const blank = open.filter((q) => q.key !== "worth" || !worth).map((q) => q.key);
2760
+ const attached = str4(j.project) ? await sendAttachments(ctx, str4(j.project), args.attachments) : [];
2644
2761
  return {
2645
2762
  text: intakeReply({
2646
2763
  title: briefToSpec(brief).title,
@@ -2649,8 +2766,132 @@ async function intakeTool(ctx, args) {
2649
2766
  blank,
2650
2767
  filedIn: str4(j.filedIn) ?? "SLEDS (no tracker connected)",
2651
2768
  needsApproval: j.needsApproval === true,
2652
- ...str4(j.parent) ? { parent: str4(j.parent) } : {}
2653
- })
2769
+ ...str4(j.parent) ? { parent: str4(j.parent) } : {},
2770
+ key: str4(j.key) ?? null
2771
+ }) + attachedLine(attached),
2772
+ structured: {
2773
+ kind: "filed",
2774
+ project: str4(j.project) ?? null,
2775
+ key: str4(j.key) ?? null,
2776
+ title: briefToSpec(brief).title,
2777
+ band: bandLine(j.band),
2778
+ worth: worth ? worthLabel(worth) : null,
2779
+ blank,
2780
+ filedIn: str4(j.filedIn) ?? "SLEDS",
2781
+ needsApproval: j.needsApproval === true,
2782
+ rowUrl: str4(j.url) ?? null,
2783
+ attached
2784
+ }
2785
+ };
2786
+ }
2787
+ function briefForView(b) {
2788
+ const f = (x) => x ? { text: x.text, from: x.from } : undefined;
2789
+ return {
2790
+ ask: f(b.ask),
2791
+ why: f(b.why),
2792
+ done: f(b.done),
2793
+ smallest: f(b.smallest),
2794
+ touches: f(b.touches),
2795
+ demand: f(b.demand),
2796
+ worth: b.worth ? { level: b.worth.level, note: b.worth.note ?? null } : undefined,
2797
+ due: b.due ?? null
2798
+ };
2799
+ }
2800
+ function parseAttachments(raw) {
2801
+ if (!Array.isArray(raw))
2802
+ return [];
2803
+ const out = [];
2804
+ for (const a of raw) {
2805
+ if (!isObject4(a))
2806
+ continue;
2807
+ const content = str4(a.content);
2808
+ if (!content)
2809
+ continue;
2810
+ const mime = str4(a.mime)?.trim() || mimeFromName(str4(a.name) ?? "");
2811
+ out.push({
2812
+ name: str4(a.name)?.trim() || "",
2813
+ mime,
2814
+ content,
2815
+ ...str4(a.kind) ? { kind: str4(a.kind) } : {},
2816
+ ...a.encoding === "base64" ? { encoding: "base64" } : {}
2817
+ });
2818
+ }
2819
+ return out.slice(0, 12);
2820
+ }
2821
+ function mimeFromName(name) {
2822
+ const ext = name.toLowerCase().split(".").pop() ?? "";
2823
+ return {
2824
+ html: "text/html",
2825
+ htm: "text/html",
2826
+ md: "text/markdown",
2827
+ markdown: "text/markdown",
2828
+ txt: "text/plain",
2829
+ csv: "text/csv",
2830
+ json: "application/json",
2831
+ pdf: "application/pdf",
2832
+ png: "image/png",
2833
+ jpg: "image/jpeg",
2834
+ jpeg: "image/jpeg",
2835
+ gif: "image/gif",
2836
+ webp: "image/webp",
2837
+ svg: "image/svg+xml"
2838
+ }[ext] ?? "text/plain";
2839
+ }
2840
+ async function sendAttachments(ctx, project, raw) {
2841
+ const files = parseAttachments(raw);
2842
+ const out = [];
2843
+ for (const f of files) {
2844
+ const r = await ctx.client.postFull("/proxy/asset", { project, ...f });
2845
+ if (!r)
2846
+ out.push({ name: f.name || "file", ok: false, why: "unreachable" });
2847
+ else if (r.status !== 200)
2848
+ out.push({
2849
+ name: f.name || "file",
2850
+ ok: false,
2851
+ why: errorMessage(r.json) ?? `HTTP ${r.status}`
2852
+ });
2853
+ else
2854
+ out.push({
2855
+ name: str4(r.json?.name) ?? f.name,
2856
+ ok: true,
2857
+ ...str4(r.json?.url) ? { url: str4(r.json?.url) } : {}
2858
+ });
2859
+ }
2860
+ return out;
2861
+ }
2862
+ function attachedLine(sent) {
2863
+ if (sent.length === 0)
2864
+ return "";
2865
+ const ok = sent.filter((s) => s.ok).map((s) => s.name);
2866
+ const bad = sent.filter((s) => !s.ok);
2867
+ const parts = [];
2868
+ if (ok.length)
2869
+ parts.push(`On the row: ${ok.join(", ")}.`);
2870
+ for (const b of bad)
2871
+ parts.push(`${b.name} was not stored (${b.why ?? "failed"}).`);
2872
+ return ` ${parts.join(" ")}`;
2873
+ }
2874
+ async function attachTool(ctx, args) {
2875
+ if (!ctx.client)
2876
+ return NOT_SET_UP;
2877
+ const project = str4(args.project)?.trim();
2878
+ if (!project)
2879
+ return {
2880
+ text: "Which row? Pass project: the key status shows (or what intake said).",
2881
+ isError: true
2882
+ };
2883
+ const files = parseAttachments([args]);
2884
+ if (files.length === 0)
2885
+ return {
2886
+ text: "Nothing to store. Pass name and content (text as is, or base64 with encoding base64).",
2887
+ isError: true
2888
+ };
2889
+ const sent = await sendAttachments(ctx, project, files);
2890
+ const first = sent[0];
2891
+ if (!first?.ok)
2892
+ return { text: `Not stored: ${first?.why ?? "failed"}.`, isError: true };
2893
+ return {
2894
+ text: `On the row ${project}: ${first.name}${first.url ? ` — ${first.url}` : ""}. Anyone on the board can open it from the row.`
2654
2895
  };
2655
2896
  }
2656
2897
  async function overnightTool(ctx, args) {
@@ -2767,8 +3008,8 @@ async function dispatchTool(ctx, args) {
2767
3008
  }
2768
3009
  return { text: reply };
2769
3010
  }
2770
- const row2 = isObject4(j.row) ? j.row : {};
2771
- const title = str4(row2.title) ?? "the row";
3011
+ const row = isObject4(j.row) ? j.row : {};
3012
+ const title = str4(row.title) ?? "the row";
2772
3013
  if (action.kind === "parked") {
2773
3014
  const dest = str4(action.to);
2774
3015
  return {
@@ -2811,9 +3052,9 @@ async function approveTool(ctx, args) {
2811
3052
  isError: true
2812
3053
  };
2813
3054
  const j = r.json ?? {};
2814
- const row2 = isObject4(j.row) ? j.row : {};
2815
- const title = str4(row2.title) ?? "the row";
2816
- const filedBy = str4(row2.filedBy);
3055
+ const row = isObject4(j.row) ? j.row : {};
3056
+ const title = str4(row.title) ?? "the row";
3057
+ const filedBy = str4(row.filedBy);
2817
3058
  if (decision === "approved")
2818
3059
  return {
2819
3060
  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.`
@@ -2865,6 +3106,50 @@ async function statusTool(ctx, args) {
2865
3106
  const rows = Array.isArray(r.rows) ? r.rows.filter(isObject4) : [];
2866
3107
  return { text: statusText(rows, (ctx.now ?? Date.now)(), q) };
2867
3108
  }
3109
+ function questionsText(open, now) {
3110
+ if (open.length === 0)
3111
+ return "Nothing is waiting on your answer.";
3112
+ const lines = open.map((q) => {
3113
+ 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) ?? "a row"}"${at}: ${str4(q.question) ?? ""} (id ${str4(q.id) ?? "?"})`;
3115
+ });
3116
+ return `${open.length === 1 ? "One question is" : `${open.length} questions are`} waiting on you:
3117
+ ${lines.join(`
3118
+ `)}
3119
+ Answer with the answer tool; your reply goes on the row.`;
3120
+ }
3121
+ async function answerTool(ctx, args) {
3122
+ if (!ctx.client)
3123
+ return NOT_SET_UP;
3124
+ const now = (ctx.now ?? Date.now)();
3125
+ const answer = str4(args.answer)?.trim();
3126
+ if (!answer) {
3127
+ const r = await ctx.client.get("/proxy/questions");
3128
+ if (!r)
3129
+ return UNREACHABLE;
3130
+ const open = Array.isArray(r.open) ? r.open.filter(isObject4) : [];
3131
+ return { text: questionsText(open, now) };
3132
+ }
3133
+ const project = str4(args.project)?.trim() || undefined;
3134
+ const questionId = str4(args.id)?.trim() || undefined;
3135
+ const r = await ctx.client.postFull("/proxy/answer", {
3136
+ answer,
3137
+ ...project ? { project } : {},
3138
+ ...questionId ? { questionId } : {}
3139
+ });
3140
+ if (!r)
3141
+ return { text: `Nothing was answered. ${UNREACHABLE.text}`, isError: true };
3142
+ if (r.status !== 200) {
3143
+ const open = Array.isArray(r.json?.open) ? (r.json?.open).filter(isObject4) : [];
3144
+ const msg = errorMessage(r.json) ?? `Nothing was answered (HTTP ${r.status}).`;
3145
+ return { text: open.length ? `${msg}
3146
+ ${questionsText(open, now)}` : msg, isError: true };
3147
+ }
3148
+ const asker = str4(r.json?.asker) ?? "the person who asked";
3149
+ return {
3150
+ text: `On the row. ${asker} hears now: "${str4(r.json?.question) ?? "the question"}" → ${answer}`
3151
+ };
3152
+ }
2868
3153
  async function handoffsTool(ctx) {
2869
3154
  if (!ctx.client)
2870
3155
  return NOT_SET_UP;
@@ -2875,7 +3160,7 @@ async function handoffsTool(ctx) {
2875
3160
  if (handoffs.length === 0)
2876
3161
  return { text: "No open hand-offs on your team. Park one with the handoff tool." };
2877
3162
  const now = (ctx.now ?? Date.now)();
2878
- const packets = handoffs.map((h) => str4(h.packet)).filter((p2) => Boolean(p2)).join(`
3163
+ const packets = handoffs.map((h) => str4(h.packet)).filter((p) => Boolean(p)).join(`
2879
3164
 
2880
3165
  ---
2881
3166
 
@@ -3033,7 +3318,7 @@ async function actualTool(ctx, args) {
3033
3318
  ...num3(args.tokensIn) !== undefined ? { tokensIn: num3(args.tokensIn) } : {},
3034
3319
  ...num3(args.tokensOut) !== undefined ? { tokensOut: num3(args.tokensOut) } : {},
3035
3320
  ...str4(args.by) ? { by: str4(args.by) } : {},
3036
- ...sessionIdOf(args.sessionId) ? { sessionId: sessionIdOf(args.sessionId) } : {},
3321
+ ...sessionIdOf(args.sessionId) ?? ctx.sessionId?.() ? { sessionId: sessionIdOf(args.sessionId) ?? ctx.sessionId?.() } : {},
3037
3322
  ...str4(args.ref) ? { ref: str4(args.ref) } : {},
3038
3323
  ...phases && Object.keys(phases).length > 0 ? { phases } : {},
3039
3324
  ...num3(args.seatCreditsUsd) !== undefined ? { seatCreditsUsd: num3(args.seatCreditsUsd) } : {}
@@ -3088,10 +3373,10 @@ async function buddyTool(ctx, args) {
3088
3373
  return NOT_SET_UP;
3089
3374
  const email = str4(args.email)?.trim();
3090
3375
  if (!email) {
3091
- const r2 = await ctx.client.get("/proxy/buddy");
3092
- if (!r2)
3376
+ const r = await ctx.client.get("/proxy/buddy");
3377
+ if (!r)
3093
3378
  return UNREACHABLE;
3094
- const list = Array.isArray(r2.buddies) ? r2.buddies : [];
3379
+ const list = Array.isArray(r.buddies) ? r.buddies : [];
3095
3380
  if (list.length === 0)
3096
3381
  return {
3097
3382
  text: "No buddies yet. `buddy {email}` sends someone an invite; once they accept, `handoff {to: email}` reaches them in their own org."
@@ -3102,10 +3387,10 @@ ${lines.join(`
3102
3387
  `)}` };
3103
3388
  }
3104
3389
  if (args.remove === true) {
3105
- const r2 = await ctx.client.postFull("/proxy/buddy", { email, remove: true });
3106
- if (!r2)
3390
+ const r = await ctx.client.postFull("/proxy/buddy", { email, remove: true });
3391
+ if (!r)
3107
3392
  return UNREACHABLE;
3108
- return { text: r2.json?.ok ? `Unpaired from ${email}.` : `${email} was not a buddy.` };
3393
+ return { text: r.json?.ok ? `Unpaired from ${email}.` : `${email} was not a buddy.` };
3109
3394
  }
3110
3395
  const r = await ctx.client.postFull("/proxy/buddy", { email });
3111
3396
  if (!r)
@@ -3133,6 +3418,12 @@ async function callTool(name, args, ctx) {
3133
3418
  return jobsTool(ctx);
3134
3419
  case "status":
3135
3420
  return statusTool(ctx, args);
3421
+ case "touches":
3422
+ return touchesTool(ctx, args);
3423
+ case "attach":
3424
+ return attachTool(ctx, args);
3425
+ case "answer":
3426
+ return answerTool(ctx, args);
3136
3427
  case "dispatch":
3137
3428
  return dispatchTool(ctx, args);
3138
3429
  case "approve":
@@ -3161,6 +3452,15 @@ async function callTool(name, args, ctx) {
3161
3452
  return { text: `Unknown tool: ${name}`, isError: true };
3162
3453
  }
3163
3454
  }
3455
+ var UI_RESOURCES = [
3456
+ {
3457
+ uri: INTAKE_VIEW_URI,
3458
+ name: "sleds_intake",
3459
+ description: "The intake brief as a form: edit, answer what is open, confirm.",
3460
+ mimeType: MCP_APP_MIME,
3461
+ html: INTAKE_VIEW_HTML
3462
+ }
3463
+ ];
3164
3464
  var response = (id, result) => JSON.stringify({ jsonrpc: "2.0", id, result });
3165
3465
  var errorResponse = (id, code, message) => JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
3166
3466
  async function handleLine(line, ctx) {
@@ -3188,7 +3488,7 @@ async function handleLine(line, ctx) {
3188
3488
  const requested = str4(params.protocolVersion);
3189
3489
  return response(id, {
3190
3490
  protocolVersion: requested ?? PROTOCOL_VERSION,
3191
- capabilities: { tools: {} },
3491
+ capabilities: { tools: {}, resources: {} },
3192
3492
  serverInfo: SERVER_INFO,
3193
3493
  instructions: SERVER_INSTRUCTIONS
3194
3494
  });
@@ -3197,12 +3497,33 @@ async function handleLine(line, ctx) {
3197
3497
  return response(id, {});
3198
3498
  case "tools/list":
3199
3499
  return response(id, { tools: TOOLS });
3500
+ case "resources/list":
3501
+ return response(id, { resources: UI_RESOURCES.map(({ html: _html, ...r }) => r) });
3502
+ case "resources/templates/list":
3503
+ return response(id, { resourceTemplates: [] });
3504
+ case "resources/read": {
3505
+ const uri = str4(params.uri) ?? "";
3506
+ const r = UI_RESOURCES.find((x) => x.uri === uri);
3507
+ if (!r)
3508
+ return errorResponse(id, -32002, `resource not found: ${uri}`);
3509
+ return response(id, { contents: [{ uri: r.uri, mimeType: r.mimeType, text: r.html }] });
3510
+ }
3200
3511
  case "tools/call": {
3201
3512
  const name = str4(params.name) ?? "";
3202
3513
  const args = isObject4(params.arguments) ? params.arguments : {};
3514
+ const t0 = (ctx.now ?? Date.now)();
3203
3515
  const r = await callTool(name, args, ctx);
3516
+ try {
3517
+ ctx.observe?.({
3518
+ tool: name,
3519
+ args,
3520
+ result: r,
3521
+ ms: Math.max(0, (ctx.now ?? Date.now)() - t0)
3522
+ });
3523
+ } catch {}
3204
3524
  return response(id, {
3205
3525
  content: [{ type: "text", text: r.text }],
3526
+ ...r.structured ? { structuredContent: r.structured } : {},
3206
3527
  ...r.isError ? { isError: true } : {}
3207
3528
  });
3208
3529
  }
@@ -3213,6 +3534,8 @@ async function handleLine(line, ctx) {
3213
3534
 
3214
3535
  // ../../packages/shared/src/dispatch/plan.ts
3215
3536
  var FRESH_MS = 24 * 3600000;
3537
+ // ../../packages/shared/src/dispatch/recommend.ts
3538
+ var QA_TASK_TYPES = new Set(["tests", "bugfix"]);
3216
3539
  // src/codex.ts
3217
3540
  init_config();
3218
3541
  import { spawnSync } from "node:child_process";
@@ -3253,7 +3576,7 @@ ${out.startsWith(`
3253
3576
  ${providerBlock(proxyUrl)}
3254
3577
  `;
3255
3578
  }
3256
- function removeCodexProvider(toml, o2 = {}) {
3579
+ function removeCodexProvider(toml, o = {}) {
3257
3580
  const lines = toml.split(`
3258
3581
  `);
3259
3582
  const out = [];
@@ -3266,7 +3589,7 @@ function removeCodexProvider(toml, o2 = {}) {
3266
3589
  }
3267
3590
  let text = out.join(`
3268
3591
  `);
3269
- if (!o2.keepPrior) {
3592
+ if (!o.keepPrior) {
3270
3593
  const prior = text.match(/^# sleds-prior-model-provider = "([^"]*)"\s*\n/m);
3271
3594
  if (prior) {
3272
3595
  text = text.replace(prior[0], "");
@@ -3283,22 +3606,22 @@ function topLevel(toml) {
3283
3606
  const i = toml.search(/^\s*\[/m);
3284
3607
  return i === -1 ? toml : toml.slice(0, i);
3285
3608
  }
3286
- function initCodex(o2) {
3609
+ function initCodex(o) {
3287
3610
  const wrote = [];
3288
- const path = codexConfigPath(o2.home);
3611
+ const path = codexConfigPath(o.home);
3289
3612
  const prior = existsSync3(path) ? readFileSync3(path, "utf8") : "";
3290
3613
  mkdirSync3(dirname2(path), { recursive: true });
3291
- writeFileSync3(path, applyCodexProvider(prior, o2.proxyUrl), { mode: 384 });
3292
- wrote.push(`${prior ? "updated" : "created"} ${path}: provider "${PROVIDER_ID}" → ${o2.proxyUrl}/v1 (key from $${TOKEN_ENV}), model_provider = "${PROVIDER_ID}"`);
3293
- const cfg = readConfig(o2.home);
3294
- writeConfig(o2.home, {
3614
+ writeFileSync3(path, applyCodexProvider(prior, o.proxyUrl), { mode: 384 });
3615
+ wrote.push(`${prior ? "updated" : "created"} ${path}: provider "${PROVIDER_ID}" → ${o.proxyUrl}/v1 (key from $${TOKEN_ENV}), model_provider = "${PROVIDER_ID}"`);
3616
+ const cfg = readConfig(o.home);
3617
+ writeConfig(o.home, {
3295
3618
  ...cfg,
3296
- token: o2.token,
3297
- proxyUrl: o2.proxyUrl,
3619
+ token: o.token,
3620
+ proxyUrl: o.proxyUrl,
3298
3621
  agentKind: "codex",
3299
- ...o2.email ? { email: o2.email } : {}
3622
+ ...o.email ? { email: o.email } : {}
3300
3623
  });
3301
- wrote.push(`saved token and proxy URL to ${configPath(o2.home)} (0600)`);
3624
+ wrote.push(`saved token and proxy URL to ${configPath(o.home)} (0600)`);
3302
3625
  return wrote;
3303
3626
  }
3304
3627
  function runCodex(home, args, deps = {}) {
@@ -3344,7 +3667,7 @@ var musherLabel = (env = process.env) => {
3344
3667
  return `${MUSHER_LABEL}.${tag}`;
3345
3668
  };
3346
3669
  var musherPlistPath = (home, env = process.env) => join5(home, "Library", "LaunchAgents", `${musherLabel(env)}.plist`);
3347
- var musherLogPath = (configDir2) => join5(configDir2, "musher.log");
3670
+ var musherLogPath = (configDir) => join5(configDir, "musher.log");
3348
3671
  var xmlEscape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3349
3672
  var musherPath = (home, execPath, currentPath = "") => {
3350
3673
  const parts = [
@@ -3360,11 +3683,11 @@ var musherPath = (home, execPath, currentPath = "") => {
3360
3683
  ];
3361
3684
  return [...new Set(parts)].join(":");
3362
3685
  };
3363
- var buildMusherPlist = (o2) => {
3364
- const label = o2.label ?? MUSHER_LABEL;
3365
- const args = [...o2.sledsArgv, "musher", "tick"].map((a) => ` <string>${xmlEscape(a)}</string>`).join(`
3686
+ var buildMusherPlist = (o) => {
3687
+ const label = o.label ?? MUSHER_LABEL;
3688
+ const args = [...o.sledsArgv, "musher", "tick"].map((a) => ` <string>${xmlEscape(a)}</string>`).join(`
3366
3689
  `);
3367
- const env = Object.entries(o2.env).filter(([, v]) => v !== "").map(([k, v]) => ` <key>${xmlEscape(k)}</key>
3690
+ const env = Object.entries(o.env).filter(([, v]) => v !== "").map(([k, v]) => ` <key>${xmlEscape(k)}</key>
3368
3691
  <string>${xmlEscape(v)}</string>`).join(`
3369
3692
  `);
3370
3693
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -3386,9 +3709,9 @@ ${env}
3386
3709
  <key>RunAtLoad</key>
3387
3710
  <true/>
3388
3711
  <key>StandardOutPath</key>
3389
- <string>${xmlEscape(o2.logPath)}</string>
3712
+ <string>${xmlEscape(o.logPath)}</string>
3390
3713
  <key>StandardErrorPath</key>
3391
- <string>${xmlEscape(o2.logPath)}</string>
3714
+ <string>${xmlEscape(o.logPath)}</string>
3392
3715
  </dict>
3393
3716
  </plist>
3394
3717
  `;
@@ -3405,15 +3728,15 @@ var NO_SESSION_PROMPT = [
3405
3728
  "SLEDS musher: a deferred job started on this seat, but no Claude Code session was recorded",
3406
3729
  "to resume. Do not guess at work. Reply with one line saying so, and stop."
3407
3730
  ].join(" ");
3408
- var claudeArgs = (o2) => [
3731
+ var claudeArgs = (o) => [
3409
3732
  "-p",
3410
- o2.sessionId ? RESUME_PROMPT : NO_SESSION_PROMPT,
3411
- ...o2.sessionId ? ["--resume", o2.sessionId] : [],
3412
- ...o2.model ? ["--model", o2.model] : [],
3733
+ o.sessionId ? RESUME_PROMPT : NO_SESSION_PROMPT,
3734
+ ...o.sessionId ? ["--resume", o.sessionId] : [],
3735
+ ...o.model ? ["--model", o.model] : [],
3413
3736
  "--output-format",
3414
3737
  "json",
3415
3738
  "--permission-mode",
3416
- o2.permissionMode
3739
+ o.permissionMode
3417
3740
  ];
3418
3741
  var parseClaudeOutput = (stdout, exitCode) => {
3419
3742
  const lines = stdout.split(`
@@ -3443,11 +3766,11 @@ var parseClaudeOutput = (stdout, exitCode) => {
3443
3766
  const tail = lines.slice(-5).join(" ").slice(0, SUMMARY_MAX);
3444
3767
  return { ok: false, summary: tail || `claude exited ${exitCode ?? "without a code"}` };
3445
3768
  };
3446
- var defaultExecCapture = (cmd, args, o2) => {
3769
+ var defaultExecCapture = (cmd, args, o) => {
3447
3770
  const r = spawnSync2(cmd, [...args], {
3448
- cwd: o2.cwd,
3771
+ cwd: o.cwd,
3449
3772
  encoding: "utf8",
3450
- timeout: o2.timeoutMs,
3773
+ timeout: o.timeoutMs,
3451
3774
  maxBuffer: 64 * 1024 * 1024,
3452
3775
  stdio: ["ignore", "pipe", "pipe"],
3453
3776
  env: { ...process.env, SLEDS_MUSHER: "1" }
@@ -3459,16 +3782,16 @@ var defaultExecCapture = (cmd, args, o2) => {
3459
3782
  ...r.error ? { error: r.error.message } : {}
3460
3783
  };
3461
3784
  };
3462
- var musherClient = (o2) => {
3463
- const fetchFn = o2.fetchFn ?? fetch;
3464
- const headers = { authorization: `Bearer ${o2.token}`, "content-type": "application/json" };
3785
+ var musherClient = (o) => {
3786
+ const fetchFn = o.fetchFn ?? fetch;
3787
+ const headers = { authorization: `Bearer ${o.token}`, "content-type": "application/json" };
3465
3788
  const call = async (path, body) => {
3466
3789
  try {
3467
- const res = await fetchFn(`${o2.proxyUrl}${path}`, {
3790
+ const res = await fetchFn(`${o.proxyUrl}${path}`, {
3468
3791
  method: body === undefined ? "GET" : "POST",
3469
3792
  headers,
3470
3793
  ...body === undefined ? {} : { body: JSON.stringify(body) },
3471
- signal: AbortSignal.timeout(o2.timeoutMs ?? 1e4)
3794
+ signal: AbortSignal.timeout(o.timeoutMs ?? 1e4)
3472
3795
  });
3473
3796
  if (!res.ok)
3474
3797
  return null;
@@ -3510,33 +3833,33 @@ var collectOutputs = (commits, summary) => {
3510
3833
  out.push(`pr:${m[0]}`);
3511
3834
  return out.slice(0, 50);
3512
3835
  };
3513
- var runJob = async (o2) => {
3514
- const log = o2.log ?? (() => {});
3515
- const sessionId = o2.job.claudeSessionId ?? o2.fallbackSession?.id ?? null;
3516
- const cwd = o2.job.cwd ?? o2.fallbackSession?.cwd ?? undefined;
3517
- const started = await o2.client.start(o2.job.jobId, {
3836
+ var runJob = async (o) => {
3837
+ const log = o.log ?? (() => {});
3838
+ const sessionId = o.job.claudeSessionId ?? o.fallbackSession?.id ?? null;
3839
+ const cwd = o.job.cwd ?? o.fallbackSession?.cwd ?? undefined;
3840
+ const started = await o.client.start(o.job.jobId, {
3518
3841
  ...sessionId ? { claudeSessionId: sessionId } : {},
3519
3842
  ...cwd ? { cwd } : {},
3520
- host: o2.host
3843
+ host: o.host
3521
3844
  });
3522
3845
  if (!started)
3523
3846
  return { kind: "skipped", reason: "control plane unreachable" };
3524
3847
  if (!started.ok)
3525
3848
  return { kind: "skipped", reason: started.reason };
3526
- const model = o2.job.model ?? started.model ?? o2.fallbackSession?.model ?? null;
3527
- const args = claudeArgs({ sessionId, model, permissionMode: o2.permissionMode });
3528
- log(`musher: running job ${o2.job.jobId} (${sessionId ? `resume ${sessionId}` : "no session"}${model ? ` · ${model}` : ""}${cwd ? ` · ${cwd}` : ""})`);
3529
- const exec = o2.exec ?? defaultExecCapture;
3849
+ const model = o.job.model ?? started.model ?? o.fallbackSession?.model ?? null;
3850
+ const args = claudeArgs({ sessionId, model, permissionMode: o.permissionMode });
3851
+ log(`musher: running job ${o.job.jobId} (${sessionId ? `resume ${sessionId}` : "no session"}${model ? ` · ${model}` : ""}${cwd ? ` · ${cwd}` : ""})`);
3852
+ const exec = o.exec ?? defaultExecCapture;
3530
3853
  const headBefore = gitHead(cwd, exec);
3531
3854
  const t0 = Date.now();
3532
- const r = exec(o2.claudePath, args, { cwd, timeoutMs: o2.timeoutMs });
3855
+ const r = exec(o.claudePath, args, { cwd, timeoutMs: o.timeoutMs });
3533
3856
  const durationMs = Date.now() - t0;
3534
- const result = r.error ? { ok: false, summary: `could not run ${o2.claudePath}: ${r.error}` } : parseClaudeOutput(r.stdout, r.status);
3857
+ const result = r.error ? { ok: false, summary: `could not run ${o.claudePath}: ${r.error}` } : parseClaudeOutput(r.stdout, r.status);
3535
3858
  if (!r.error && r.status === null)
3536
- result.summary = `timed out after ${Math.round(o2.timeoutMs / 60000)} min. ${result.summary}`.trim();
3859
+ result.summary = `timed out after ${Math.round(o.timeoutMs / 60000)} min. ${result.summary}`.trim();
3537
3860
  const outputs = collectOutputs(gitNewCommits(cwd, headBefore, gitHead(cwd, exec), exec), result.summary);
3538
- await o2.client.finish({
3539
- jobId: o2.job.jobId,
3861
+ await o.client.finish({
3862
+ jobId: o.job.jobId,
3540
3863
  runId: started.runId,
3541
3864
  ok: result.ok,
3542
3865
  summary: result.summary,
@@ -3545,7 +3868,7 @@ var runJob = async (o2) => {
3545
3868
  durationMs: result.durationMs ?? durationMs,
3546
3869
  ...result.sessionId ?? sessionId ? { claudeSessionId: result.sessionId ?? sessionId } : {}
3547
3870
  });
3548
- log(`musher: job ${o2.job.jobId} ${result.ok ? "delivered" : "failed"} in ${Math.round(durationMs / 1000)}s`);
3871
+ log(`musher: job ${o.job.jobId} ${result.ok ? "delivered" : "failed"} in ${Math.round(durationMs / 1000)}s`);
3549
3872
  return { kind: "ran", ok: result.ok, summary: result.summary, durationMs, outputs };
3550
3873
  };
3551
3874
  var verifyAttestation = (payload, sig, publicKeyB64) => {
@@ -3849,11 +4172,11 @@ var bindingResetAt = (w, now) => {
3849
4172
  return pick.length ? Math.min(...pick) : undefined;
3850
4173
  };
3851
4174
  var readSeatState = (home) => {
3852
- const p2 = seatStatePath(home);
3853
- if (!existsSync5(p2))
4175
+ const p = seatStatePath(home);
4176
+ if (!existsSync5(p))
3854
4177
  return {};
3855
4178
  try {
3856
- return parseSeatState(JSON.parse(readFileSync5(p2, "utf8")));
4179
+ return parseSeatState(JSON.parse(readFileSync5(p, "utf8")));
3857
4180
  } catch {
3858
4181
  return {};
3859
4182
  }
@@ -3978,28 +4301,28 @@ var seatWindowBody = (w) => ({
3978
4301
  ...w.sevenDay ? { sevenDay: w.sevenDay } : {},
3979
4302
  ...w.spendLimit ? { spendLimit: w.spendLimit } : {}
3980
4303
  });
3981
- var postJson = async (o2, path, body) => {
3982
- const fetchFn = o2.fetchFn ?? fetch;
4304
+ var postJson = async (o, path, body) => {
4305
+ const fetchFn = o.fetchFn ?? fetch;
3983
4306
  try {
3984
- const res = await fetchFn(`${o2.proxyUrl}${path}`, {
4307
+ const res = await fetchFn(`${o.proxyUrl}${path}`, {
3985
4308
  method: "POST",
3986
- headers: { authorization: `Bearer ${o2.token}`, "content-type": "application/json" },
4309
+ headers: { authorization: `Bearer ${o.token}`, "content-type": "application/json" },
3987
4310
  body: JSON.stringify(body),
3988
- signal: AbortSignal.timeout(o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS)
4311
+ signal: AbortSignal.timeout(o.timeoutMs ?? SEAT_POST_TIMEOUT_MS)
3989
4312
  });
3990
4313
  return res.ok;
3991
4314
  } catch {
3992
4315
  return false;
3993
4316
  }
3994
4317
  };
3995
- var postSeatWindow = (o2, w) => postJson(o2, "/proxy/seat", seatWindowBody(w));
3996
- var postSeatWall = (o2, input) => postJson(o2, "/proxy/seat-wall", { source: "claude", ...input });
4318
+ var postSeatWindow = (o, w) => postJson(o, "/proxy/seat", seatWindowBody(w));
4319
+ var postSeatWall = (o, input) => postJson(o, "/proxy/seat-wall", { source: "claude", ...input });
3997
4320
  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.";
3998
4321
  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.";
3999
4322
  var seatWallLine = (hasUpstreamKey) => hasUpstreamKey ? SEAT_WALL_LINE : SEAT_WALL_LINE_NO_KEY;
4000
- var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) => {
4323
+ var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve) => {
4001
4324
  if (stdin.isTTY) {
4002
- resolve3("");
4325
+ resolve("");
4003
4326
  return;
4004
4327
  }
4005
4328
  let data = "";
@@ -4009,7 +4332,7 @@ var readStdin = (timeoutMs, stdin = process.stdin) => new Promise((resolve3) =>
4009
4332
  return;
4010
4333
  settled = true;
4011
4334
  clearTimeout(timer);
4012
- resolve3(data);
4335
+ resolve(data);
4013
4336
  };
4014
4337
  const timer = setTimeout(finish, timeoutMs);
4015
4338
  stdin.setEncoding("utf8");
@@ -4127,21 +4450,21 @@ var parseFlags = (argv) => {
4127
4450
  }
4128
4451
  return flags;
4129
4452
  };
4130
- var defer = async (o2) => {
4131
- const log = o2.log ?? console.log;
4132
- const error = o2.error ?? console.error;
4133
- const now = (o2.now ?? Date.now)();
4134
- const flags = parseFlags(o2.argv);
4135
- const client = o2.client ?? (() => {
4136
- const src = meterSource(o2.home);
4137
- return src ? musherClient({ ...src, fetchFn: o2.fetchFn }) : null;
4453
+ var defer = async (o) => {
4454
+ const log = o.log ?? console.log;
4455
+ const error = o.error ?? console.error;
4456
+ const now = (o.now ?? Date.now)();
4457
+ const flags = parseFlags(o.argv);
4458
+ const client = o.client ?? (() => {
4459
+ const src = meterSource(o.home);
4460
+ return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
4138
4461
  })();
4139
4462
  if (!client) {
4140
4463
  error("No seat on this machine. Run `sleds init` first.");
4141
4464
  return 1;
4142
4465
  }
4143
- const state = readSeatState(o2.home);
4144
- const piped = o2.stdin ? seatSessionFromStdin(o2.stdin, now) : null;
4466
+ const state = readSeatState(o.home);
4467
+ const piped = o.stdin ? seatSessionFromStdin(o.stdin, now) : null;
4145
4468
  const remembered = piped ?? state.session;
4146
4469
  const sessionId = flags.session ?? remembered?.id;
4147
4470
  const cwd = flags.cwd ?? remembered?.cwd;
@@ -4162,7 +4485,7 @@ var defer = async (o2) => {
4162
4485
  ...model ? { model } : {},
4163
4486
  ...sessionId ? { claudeSessionId: sessionId } : {},
4164
4487
  ...cwd ? { cwd } : {},
4165
- host: o2.host ?? hostname()
4488
+ host: o.host ?? hostname()
4166
4489
  });
4167
4490
  if (!r) {
4168
4491
  error("Could not reach the SLEDS proxy; nothing was scheduled.");
@@ -4171,8 +4494,8 @@ var defer = async (o2) => {
4171
4494
  const when = r.scheduledAt <= now + 60000 ? "as soon as the musher ticks (within 5 min)" : `${formatLocal(r.scheduledAt)} (in ${formatDuration(r.scheduledAt - now)})`;
4172
4495
  log(`Deferred. This Mac resumes ${sessionId ? `session ${sessionId.slice(0, 8)}…` : "the work"}${model ? ` on ${model}` : ""} ${when}.`);
4173
4496
  log(` job ${r.jobId}${cwd ? ` · ${cwd}` : ""} · runs on your seat, no key · sleds musher --status to watch`);
4174
- const cfg = readConfig(o2.home);
4175
- if (process.platform === "darwin" && !existsSync6(musherPlistPath(o2.home)) && cfg.musher?.agent !== "launchd")
4497
+ const cfg = readConfig(o.home);
4498
+ if (process.platform === "darwin" && !existsSync6(musherPlistPath(o.home)) && cfg.musher?.agent !== "launchd")
4176
4499
  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.`);
4177
4500
  log(" Keep this Mac awake at that time (System Settings → Battery → prevent sleeping on power adapter).");
4178
4501
  return 0;
@@ -4352,7 +4675,7 @@ var renderCockpit = (cockpit, window, events, prior, now = Date.now(), extra = {
4352
4675
  `/sled pickup ${newHandoffs[0]}`,
4353
4676
  ...ws(cockpit)
4354
4677
  ]);
4355
- for (const id of extra.feed.delivered.filter((id2) => !(before?.delivered ?? []).includes(id2)))
4678
+ for (const id of extra.feed.delivered.filter((id) => !(before?.delivered ?? []).includes(id)))
4356
4679
  argv.push([
4357
4680
  "log",
4358
4681
  "--level",
@@ -4362,7 +4685,7 @@ var renderCockpit = (cockpit, window, events, prior, now = Date.now(), extra = {
4362
4685
  `overnight job done · /sled resume ${id}`,
4363
4686
  ...ws(cockpit)
4364
4687
  ]);
4365
- for (const id of extra.feed.failed.filter((id2) => !(before?.failed ?? []).includes(id2)))
4688
+ for (const id of extra.feed.failed.filter((id) => !(before?.failed ?? []).includes(id)))
4366
4689
  argv.push([
4367
4690
  "notify",
4368
4691
  "--title",
@@ -4419,15 +4742,15 @@ var feedFromJobs = (body, me, now) => {
4419
4742
  }
4420
4743
  return f;
4421
4744
  };
4422
- var fetchFeed = async (o2, now) => {
4745
+ var fetchFeed = async (o, now) => {
4423
4746
  try {
4424
- const res = await (o2.fetchFn ?? fetch)(`${o2.proxyUrl}/proxy/jobs`, {
4425
- headers: { authorization: `Bearer ${o2.token}` },
4426
- signal: AbortSignal.timeout(o2.timeoutMs ?? 1500)
4747
+ const res = await (o.fetchFn ?? fetch)(`${o.proxyUrl}/proxy/jobs`, {
4748
+ headers: { authorization: `Bearer ${o.token}` },
4749
+ signal: AbortSignal.timeout(o.timeoutMs ?? 1500)
4427
4750
  });
4428
4751
  if (!res.ok)
4429
4752
  return null;
4430
- return feedFromJobs(await res.json(), o2.email, now);
4753
+ return feedFromJobs(await res.json(), o.email, now);
4431
4754
  } catch {
4432
4755
  return null;
4433
4756
  }
@@ -4463,9 +4786,9 @@ init_meter();
4463
4786
  var defaultWhich = (cmd) => {
4464
4787
  const path = process.env.PATH ?? "";
4465
4788
  for (const dir of path.split(":").filter(Boolean)) {
4466
- const p2 = `${dir}/${cmd}`;
4467
- if (existsSync7(p2))
4468
- return p2;
4789
+ const p = `${dir}/${cmd}`;
4790
+ if (existsSync7(p))
4791
+ return p;
4469
4792
  }
4470
4793
  return null;
4471
4794
  };
@@ -4475,14 +4798,14 @@ var findClaude = (home, configured, which) => {
4475
4798
  const found = which("claude");
4476
4799
  if (found)
4477
4800
  return found;
4478
- for (const p2 of [
4801
+ for (const p of [
4479
4802
  `${home}/.local/bin/claude`,
4480
4803
  "/opt/homebrew/bin/claude",
4481
4804
  "/usr/local/bin/claude",
4482
4805
  `${home}/.npm-global/bin/claude`
4483
4806
  ])
4484
- if (existsSync7(p2))
4485
- return p2;
4807
+ if (existsSync7(p))
4808
+ return p;
4486
4809
  return null;
4487
4810
  };
4488
4811
  var musherAgentState = (home, env = process.env) => {
@@ -4545,10 +4868,10 @@ function defaultWhich2(cmd) {
4545
4868
  return [];
4546
4869
  }
4547
4870
  }
4548
- async function runDoctor(o2) {
4549
- const env = o2.env ?? process.env;
4871
+ async function runDoctor(o) {
4872
+ const env = o.env ?? process.env;
4550
4873
  const checks = [];
4551
- const home = o2.home;
4874
+ const home = o.home;
4552
4875
  const overrides = [
4553
4876
  env.SLEDS_HOME?.trim() ? `SLEDS_HOME=${env.SLEDS_HOME.trim()}` : null,
4554
4877
  env.CLAUDE_CONFIG_DIR?.trim() ? `CLAUDE_CONFIG_DIR=${env.CLAUDE_CONFIG_DIR.trim()}` : null
@@ -4598,7 +4921,7 @@ async function runDoctor(o2) {
4598
4921
  }
4599
4922
  const src = meterSource(home);
4600
4923
  if (src) {
4601
- const health = await checkHealth(src.proxyUrl, o2.fetchFn, o2.timeoutMs);
4924
+ const health = await checkHealth(src.proxyUrl, o.fetchFn, o.timeoutMs);
4602
4925
  checks.push({
4603
4926
  level: health === "ok" ? "ok" : "fail",
4604
4927
  name: "proxy",
@@ -4607,15 +4930,15 @@ async function runDoctor(o2) {
4607
4930
  });
4608
4931
  }
4609
4932
  if (settings) {
4610
- const seat2 = hasSeatHook(settings);
4933
+ const seat = hasSeatHook(settings);
4611
4934
  const spec = hasSpecHook(settings);
4612
4935
  const session = hasSessionHook(settings);
4613
4936
  const stop = hasStopHook(settings);
4614
- const all = seat2 && spec && session && stop;
4937
+ const all = seat && spec && session && stop;
4615
4938
  checks.push({
4616
4939
  level: all ? "ok" : "warn",
4617
4940
  name: "hooks",
4618
- detail: `seat-event ${seat2 ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"} · session-event ${session ? "on" : "missing"} · stop-event ${stop ? "on" : "missing"}`,
4941
+ detail: `seat-event ${seat ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"} · session-event ${session ? "on" : "missing"} · stop-event ${stop ? "on" : "missing"}`,
4619
4942
  fix: all ? undefined : "sleds hooks"
4620
4943
  });
4621
4944
  const want = env.SLEDS_HOME?.trim() ? resolve3(home, env.SLEDS_HOME.trim()) : undefined;
@@ -4646,18 +4969,18 @@ async function runDoctor(o2) {
4646
4969
  name: "cockpit",
4647
4970
  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)"
4648
4971
  });
4649
- const nodeVersion = o2.nodeVersion ?? process.version;
4972
+ const nodeVersion = o.nodeVersion ?? process.version;
4650
4973
  checks.push({
4651
4974
  level: nodeOk(nodeVersion) ? "ok" : "warn",
4652
4975
  name: "node",
4653
4976
  detail: `${nodeVersion}${nodeOk(nodeVersion) ? "" : ` — seat-sync needs ≥ ${NODE_MIN} (node:sqlite)`}`,
4654
4977
  fix: nodeOk(nodeVersion) ? undefined : `brew install node@22 && echo 'export PATH="/opt/homebrew/opt/node@22/bin:$PATH"' >> ~/.zshrc`
4655
4978
  });
4656
- const which = (o2.which ?? defaultWhich2)("sleds");
4657
- const stale = which.filter((p2) => {
4979
+ const which = (o.which ?? defaultWhich2)("sleds");
4980
+ const stale = which.filter((p) => {
4658
4981
  try {
4659
- const target = lstatSync(p2).isSymbolicLink() ? readlinkSync(p2) : p2;
4660
- return /\/src\/index\.ts$/.test(target) || /\.bun\/bin\/sleds$/.test(p2);
4982
+ const target = lstatSync(p).isSymbolicLink() ? readlinkSync(p) : p;
4983
+ return /\/src\/index\.ts$/.test(target) || /\.bun\/bin\/sleds$/.test(p);
4661
4984
  } catch {
4662
4985
  return false;
4663
4986
  }
@@ -4679,7 +5002,7 @@ async function runDoctor(o2) {
4679
5002
  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)}%` : ""}`,
4680
5003
  fix: age === null ? "open claude, say hi, exit — the status line posts the seat's quota" : undefined
4681
5004
  });
4682
- if ((o2.platform ?? process.platform) === "darwin") {
5005
+ if ((o.platform ?? process.platform) === "darwin") {
4683
5006
  const agent = musherAgentState(home, env);
4684
5007
  checks.push(!agent.installed ? {
4685
5008
  level: "warn",
@@ -4696,14 +5019,14 @@ async function runDoctor(o2) {
4696
5019
  name: "musher",
4697
5020
  detail: `launchd agent ${musherLabel(env)}, every 5 min`
4698
5021
  });
4699
- const claude = findClaude(home, readConfig(home).musher?.claudePath, (c) => (o2.which ?? defaultWhich2)(c)[0] ?? null);
5022
+ const claude = findClaude(home, readConfig(home).musher?.claudePath, (c) => (o.which ?? defaultWhich2)(c)[0] ?? null);
4700
5023
  checks.push(claude ? { level: "ok", name: "claude cli", detail: claude } : {
4701
5024
  level: "warn",
4702
5025
  name: "claude cli",
4703
5026
  detail: "`claude` not on PATH — the musher cannot run Claude Code",
4704
5027
  fix: 'sleds musher --claude "$(which claude)"'
4705
5028
  });
4706
- const sleep = (o2.pmset ?? defaultPmset)();
5029
+ const sleep = (o.pmset ?? defaultPmset)();
4707
5030
  if (sleep !== null)
4708
5031
  checks.push(sleep.acSleepMinutes === 0 ? {
4709
5032
  level: "ok",
@@ -4733,9 +5056,9 @@ var defaultPmset = () => {
4733
5056
  return null;
4734
5057
  }
4735
5058
  };
4736
- var doctor = async (o2) => {
4737
- const log = o2.log ?? console.log;
4738
- const checks = await runDoctor(o2);
5059
+ var doctor = async (o) => {
5060
+ const log = o.log ?? console.log;
5061
+ const checks = await runDoctor(o);
4739
5062
  for (const line of formatDoctor(checks))
4740
5063
  log(line);
4741
5064
  return checks.some((c) => c.level === "fail") ? 1 : 0;
@@ -4763,21 +5086,21 @@ function sourceKey(s) {
4763
5086
  function sourceText2(s) {
4764
5087
  return [`${str5(s.kind) ?? "?"} ${str5(s.project) ?? "?"}`, str5(s.doc), str5(s.item)].filter(Boolean).join(" › ");
4765
5088
  }
4766
- var readJson = (p2) => {
4767
- if (!existsSync9(p2))
5089
+ var readJson = (p) => {
5090
+ if (!existsSync9(p))
4768
5091
  return null;
4769
5092
  try {
4770
- return JSON.parse(readFileSync8(p2, "utf8"));
5093
+ return JSON.parse(readFileSync8(p, "utf8"));
4771
5094
  } catch {
4772
5095
  return null;
4773
5096
  }
4774
5097
  };
4775
- var writeJson = (p2, v) => {
4776
- mkdirSync7(dirname4(p2), { recursive: true, mode: 448 });
4777
- const tmp = `${p2}.${process.pid}.tmp`;
5098
+ var writeJson = (p, v) => {
5099
+ mkdirSync7(dirname4(p), { recursive: true, mode: 448 });
5100
+ const tmp = `${p}.${process.pid}.tmp`;
4778
5101
  writeFileSync7(tmp, `${JSON.stringify(v, null, 2)}
4779
5102
  `, { mode: 384 });
4780
- renameSync5(tmp, p2);
5103
+ renameSync5(tmp, p);
4781
5104
  };
4782
5105
  var readOwed = (home, now = Date.now()) => {
4783
5106
  const f = readJson(owedPath(home));
@@ -4792,14 +5115,14 @@ var readSession = (home) => {
4792
5115
  var writeSession = (home, s) => writeJson(sessionPath(home), s);
4793
5116
  function withEstimated(rows, a) {
4794
5117
  const key = sourceKey(a.source);
4795
- const row2 = {
5118
+ const row = {
4796
5119
  key,
4797
5120
  source: a.source,
4798
5121
  ...a.title ? { title: a.title } : {},
4799
5122
  ts: a.now,
4800
5123
  ...a.sessionId ? { sessionId: a.sessionId } : {}
4801
5124
  };
4802
- return [...rows.filter((r) => r.key !== key), row2];
5125
+ return [...rows.filter((r) => r.key !== key), row];
4803
5126
  }
4804
5127
  function withActual(rows, a) {
4805
5128
  if (a.state === "started")
@@ -4891,65 +5214,70 @@ function contextFor(deps) {
4891
5214
  const src = meterSource(deps.home);
4892
5215
  const now = deps.now ?? Date.now;
4893
5216
  const client = src ? trackingClient(fetchClient({ ...src, fetchFn: deps.fetchFn, timeoutMs: deps.timeoutMs }), deps.home, now) : null;
4894
- return { client, now: deps.now, usage: () => usageTool2(deps) };
5217
+ return {
5218
+ client,
5219
+ now: deps.now,
5220
+ usage: () => usageTool2(deps),
5221
+ sessionId: () => readSession(deps.home)?.sessionId
5222
+ };
4895
5223
  }
4896
5224
  var callTool2 = (name, args, deps) => callTool(name, args, contextFor(deps));
4897
5225
  var handleLine2 = (line, deps) => handleLine(line, contextFor(deps));
4898
5226
 
4899
5227
  // src/commands/handoffs.ts
4900
- var handoffsCmd = async (o2) => {
4901
- const r = await callTool2("handoffs", {}, { home: o2.home });
4902
- (r.isError ? o2.error ?? console.error : o2.log ?? console.log)(r.text);
5228
+ var handoffsCmd = async (o) => {
5229
+ const r = await callTool2("handoffs", {}, { home: o.home });
5230
+ (r.isError ? o.error ?? console.error : o.log ?? console.log)(r.text);
4903
5231
  return r.isError ? 1 : 0;
4904
5232
  };
4905
- var pickupCmd = async (o2) => {
4906
- const id = o2.argv[0];
5233
+ var pickupCmd = async (o) => {
5234
+ const id = o.argv[0];
4907
5235
  if (!id) {
4908
- (o2.error ?? console.error)("usage: sleds pickup <handoffId> (ids from `sleds handoffs`)");
5236
+ (o.error ?? console.error)("usage: sleds pickup <handoffId> (ids from `sleds handoffs`)");
4909
5237
  return 2;
4910
5238
  }
4911
- const r = await callTool2("pickup", { handoffId: id }, { home: o2.home });
4912
- (r.isError ? o2.error ?? console.error : o2.log ?? console.log)(r.text);
5239
+ const r = await callTool2("pickup", { handoffId: id }, { home: o.home });
5240
+ (r.isError ? o.error ?? console.error : o.log ?? console.log)(r.text);
4913
5241
  return r.isError ? 1 : 0;
4914
5242
  };
4915
- var withdrawCmd = async (o2) => {
4916
- const id = o2.argv[0];
5243
+ var withdrawCmd = async (o) => {
5244
+ const id = o.argv[0];
4917
5245
  if (!id) {
4918
- (o2.error ?? console.error)("usage: sleds withdraw <handoffId> (one you parked)");
5246
+ (o.error ?? console.error)("usage: sleds withdraw <handoffId> (one you parked)");
4919
5247
  return 2;
4920
5248
  }
4921
- const r = await callTool2("withdraw", { handoffId: id }, { home: o2.home });
4922
- (r.isError ? o2.error ?? console.error : o2.log ?? console.log)(r.text);
5249
+ const r = await callTool2("withdraw", { handoffId: id }, { home: o.home });
5250
+ (r.isError ? o.error ?? console.error : o.log ?? console.log)(r.text);
4923
5251
  return r.isError ? 1 : 0;
4924
5252
  };
4925
5253
 
4926
5254
  // src/commands/hooks.ts
4927
5255
  init_settings();
4928
- var hooks = (o2) => {
4929
- const log = o2.log ?? console.log;
4930
- const error = o2.error ?? console.error;
4931
- if (!hasClaudeCode(o2.home)) {
4932
- error(`Claude Code not found (${claudeDir(o2.home)} does not exist). Install it, then re-run.`);
5256
+ var hooks = (o) => {
5257
+ const log = o.log ?? console.log;
5258
+ const error = o.error ?? console.error;
5259
+ if (!hasClaudeCode(o.home)) {
5260
+ error(`Claude Code not found (${claudeDir(o.home)} does not exist). Install it, then re-run.`);
4933
5261
  return 1;
4934
5262
  }
4935
- const settings = readSettings(o2.home);
5263
+ const settings = readSettings(o.home);
4936
5264
  const had = hasSeatHook(settings);
4937
5265
  const hadSpec = hasSpecHook(settings);
4938
- const withSeat = applySeatHook(settings, o2.seatEventCommand ?? sledsCommand("seat-event", o2.home, o2.env));
5266
+ const withSeat = applySeatHook(settings, o.seatEventCommand ?? sledsCommand("seat-event", o.home, o.env));
4939
5267
  const hadPrompt = hasPromptHook(settings);
4940
5268
  const hadSession = hasSessionHook(settings);
4941
5269
  const hadStop = hasStopHook(settings);
4942
- const withPrompt = applyPromptHook(applySpecHook(withSeat, o2.specEventCommand ?? sledsCommand("spec-event", o2.home, o2.env)), o2.promptEventCommand ?? sledsCommand("prompt-event", o2.home, o2.env));
4943
- const withSpec = applyStopHook(applySessionHook(withPrompt, o2.sessionEventCommand ?? sledsCommand("session-event", o2.home, o2.env)), o2.stopEventCommand ?? sledsCommand("stop-event", o2.home, o2.env));
4944
- const hadLine = o2.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
4945
- writeSettings(o2.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o2.home, o2.env)).settings : withSpec);
4946
- log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o2.home)}`);
5270
+ const withPrompt = applyPromptHook(applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env)), o.promptEventCommand ?? sledsCommand("prompt-event", o.home, o.env));
5271
+ const withSpec = applyStopHook(applySessionHook(withPrompt, o.sessionEventCommand ?? sledsCommand("session-event", o.home, o.env)), o.stopEventCommand ?? sledsCommand("stop-event", o.home, o.env));
5272
+ const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
5273
+ writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
5274
+ log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
4947
5275
  if (hadLine)
4948
- log(`refreshed statusLine in ${settingsPath(o2.home)}`);
4949
- log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o2.home)}`);
4950
- log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o2.home)}`);
4951
- log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(o2.home)}`);
4952
- log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(o2.home)}`);
5276
+ log(`refreshed statusLine in ${settingsPath(o.home)}`);
5277
+ log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
5278
+ log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o.home)}`);
5279
+ log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(o.home)}`);
5280
+ log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(o.home)}`);
4953
5281
  return 0;
4954
5282
  };
4955
5283
 
@@ -5100,43 +5428,172 @@ From Cowork or any MCP client (\`sleds mcp\`): the \`handoffs\` tool lists what
5100
5428
  A buddy is one person outside your org you can hand work to and from. Pair once (MCP \`buddy {email}\` — they click Accept in the mail); then \`handoff {task, to: "<their email>"}\` lands in their Waiting for you, in whatever org their seat is, and \`pickup\` there runs it on their seat, metered to them. Only the packet crosses — never keys, usage or membership. \`buddy\` with no email lists your pairs; \`buddy {email, remove: true}\` unpairs. A teammate is never a buddy: hand off to them directly.
5101
5429
  `;
5102
5430
 
5431
+ // src/commands/device.ts
5432
+ import { spawnSync as spawnSync3 } from "node:child_process";
5433
+ import { hostname as hostname2, platform } from "node:os";
5434
+ var DEVICE_REASONS = {
5435
+ unreachable: "could not reach the proxy.",
5436
+ rate_limited: "too many sign-ins are open right now. Try again in a few minutes.",
5437
+ denied: "that sign-in was declined on the web.",
5438
+ expired: "the code expired before it was approved (they last fifteen minutes). Run `sleds init` again.",
5439
+ bad_answer: "the proxy gave an answer this CLI does not understand — update it: curl -fsSL sleds.ai/install | sh"
5440
+ };
5441
+ function deviceFailureText(reason) {
5442
+ return `Sign-in did not complete: ${DEVICE_REASONS[reason]} Or mint a seat on the dashboard (Settings › Your seat & tools) and run \`sleds init --token …\`.`;
5443
+ }
5444
+ function parseStart(body) {
5445
+ if (typeof body !== "object" || body === null)
5446
+ return null;
5447
+ const b = body;
5448
+ if (typeof b.deviceCode !== "string" || typeof b.userCode !== "string")
5449
+ return null;
5450
+ if (typeof b.verifyUrl !== "string" || !/^https?:\/\//.test(b.verifyUrl))
5451
+ return null;
5452
+ const expiresIn = typeof b.expiresIn === "number" && b.expiresIn > 0 ? Math.min(b.expiresIn, 3600) : 900;
5453
+ const interval = typeof b.interval === "number" && b.interval >= 1 ? Math.min(b.interval, 30) : 3;
5454
+ return {
5455
+ deviceCode: b.deviceCode,
5456
+ userCode: b.userCode,
5457
+ verifyUrl: b.verifyUrl,
5458
+ expiresIn,
5459
+ interval
5460
+ };
5461
+ }
5462
+ function parsePoll(body) {
5463
+ if (typeof body !== "object" || body === null)
5464
+ return null;
5465
+ const b = body;
5466
+ if (b.status === "pending" || b.status === "denied" || b.status === "expired")
5467
+ return { status: b.status };
5468
+ if (b.status !== "approved")
5469
+ return null;
5470
+ const org = b.org;
5471
+ if (typeof b.token !== "string" || typeof b.email !== "string" || !org)
5472
+ return null;
5473
+ if (typeof org.name !== "string" || typeof org.slug !== "string")
5474
+ return null;
5475
+ return {
5476
+ status: "approved",
5477
+ approved: {
5478
+ token: b.token,
5479
+ email: b.email,
5480
+ org: { name: org.name, slug: org.slug, plan: typeof org.plan === "string" ? org.plan : "" },
5481
+ workspaceUrl: typeof b.workspaceUrl === "string" ? b.workspaceUrl : ""
5482
+ }
5483
+ };
5484
+ }
5485
+ function openInBrowser(url) {
5486
+ const p = platform();
5487
+ const cmd = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
5488
+ const args = p === "win32" ? ["/c", "start", "", url] : [url];
5489
+ try {
5490
+ const r = spawnSync3(cmd, args, { stdio: "ignore", timeout: 5000 });
5491
+ return r.error === undefined && r.status === 0;
5492
+ } catch {
5493
+ return false;
5494
+ }
5495
+ }
5496
+ var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
5497
+ async function deviceFlow(o) {
5498
+ const fetchFn = o.fetchFn ?? fetch;
5499
+ const log = o.log ?? console.log;
5500
+ const sleep = o.sleep ?? defaultSleep;
5501
+ const now = o.now ?? Date.now;
5502
+ let started = null;
5503
+ try {
5504
+ const res = await fetchFn(`${o.proxyUrl}/device/start`, {
5505
+ method: "POST",
5506
+ headers: { "content-type": "application/json" },
5507
+ body: JSON.stringify({ agent: o.agent ?? "claude-code", host: o.host ?? hostname2() }),
5508
+ signal: AbortSignal.timeout(20000)
5509
+ });
5510
+ if (res.status === 429)
5511
+ return { ok: false, reason: "rate_limited" };
5512
+ if (!res.ok)
5513
+ return { ok: false, reason: "unreachable" };
5514
+ started = parseStart(await res.json().catch(() => null));
5515
+ } catch {
5516
+ return { ok: false, reason: "unreachable" };
5517
+ }
5518
+ if (!started)
5519
+ return { ok: false, reason: "bad_answer" };
5520
+ const opened = o.noBrowser ? false : (o.openBrowser ?? openInBrowser)(started.verifyUrl);
5521
+ log("");
5522
+ log(opened ? "Opening your browser to sign in to SLEDS." : "Open this in your browser to sign in to SLEDS:");
5523
+ log(` ${started.verifyUrl}`);
5524
+ log("");
5525
+ log(` Your code: ${started.userCode} — the page shows the same one.`);
5526
+ log("");
5527
+ log("Waiting for the approval…");
5528
+ const deadline = now() + started.expiresIn * 1000;
5529
+ let intervalMs = started.interval * 1000;
5530
+ while (now() < deadline) {
5531
+ await sleep(intervalMs);
5532
+ let body;
5533
+ try {
5534
+ const res = await fetchFn(`${o.proxyUrl}/device/poll`, {
5535
+ method: "POST",
5536
+ headers: { "content-type": "application/json" },
5537
+ body: JSON.stringify({ deviceCode: started.deviceCode }),
5538
+ signal: AbortSignal.timeout(20000)
5539
+ });
5540
+ if (res.status === 429) {
5541
+ intervalMs = Math.min(intervalMs * 2, 30000);
5542
+ continue;
5543
+ }
5544
+ body = await res.json().catch(() => null);
5545
+ } catch {
5546
+ continue;
5547
+ }
5548
+ const r = parsePoll(body);
5549
+ if (!r)
5550
+ return { ok: false, reason: "bad_answer" };
5551
+ if (r.status === "pending")
5552
+ continue;
5553
+ if (r.status === "approved")
5554
+ return { ok: true, approved: r.approved };
5555
+ return { ok: false, reason: r.status };
5556
+ }
5557
+ return { ok: false, reason: "expired" };
5558
+ }
5559
+
5103
5560
  // src/commands/hooks.ts
5104
5561
  init_settings();
5105
- var hooks2 = (o2) => {
5106
- const log = o2.log ?? console.log;
5107
- const error = o2.error ?? console.error;
5108
- if (!hasClaudeCode(o2.home)) {
5109
- error(`Claude Code not found (${claudeDir(o2.home)} does not exist). Install it, then re-run.`);
5562
+ var hooks2 = (o) => {
5563
+ const log = o.log ?? console.log;
5564
+ const error = o.error ?? console.error;
5565
+ if (!hasClaudeCode(o.home)) {
5566
+ error(`Claude Code not found (${claudeDir(o.home)} does not exist). Install it, then re-run.`);
5110
5567
  return 1;
5111
5568
  }
5112
- const settings = readSettings(o2.home);
5569
+ const settings = readSettings(o.home);
5113
5570
  const had = hasSeatHook(settings);
5114
5571
  const hadSpec = hasSpecHook(settings);
5115
- const withSeat = applySeatHook(settings, o2.seatEventCommand ?? sledsCommand("seat-event", o2.home, o2.env));
5572
+ const withSeat = applySeatHook(settings, o.seatEventCommand ?? sledsCommand("seat-event", o.home, o.env));
5116
5573
  const hadPrompt = hasPromptHook(settings);
5117
5574
  const hadSession = hasSessionHook(settings);
5118
5575
  const hadStop = hasStopHook(settings);
5119
- const withPrompt = applyPromptHook(applySpecHook(withSeat, o2.specEventCommand ?? sledsCommand("spec-event", o2.home, o2.env)), o2.promptEventCommand ?? sledsCommand("prompt-event", o2.home, o2.env));
5120
- const withSpec = applyStopHook(applySessionHook(withPrompt, o2.sessionEventCommand ?? sledsCommand("session-event", o2.home, o2.env)), o2.stopEventCommand ?? sledsCommand("stop-event", o2.home, o2.env));
5121
- const hadLine = o2.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
5122
- writeSettings(o2.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o2.home, o2.env)).settings : withSpec);
5123
- log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o2.home)}`);
5576
+ const withPrompt = applyPromptHook(applySpecHook(withSeat, o.specEventCommand ?? sledsCommand("spec-event", o.home, o.env)), o.promptEventCommand ?? sledsCommand("prompt-event", o.home, o.env));
5577
+ const withSpec = applyStopHook(applySessionHook(withPrompt, o.sessionEventCommand ?? sledsCommand("session-event", o.home, o.env)), o.stopEventCommand ?? sledsCommand("stop-event", o.home, o.env));
5578
+ const hadLine = o.refreshStatusLine !== false && isSledsStatusLine(withSpec[STATUS_LINE_KEY]);
5579
+ writeSettings(o.home, hadLine ? applyStatusLine(withSpec, sledsCommand("statusline", o.home, o.env)).settings : withSpec);
5580
+ log(`${had ? "refreshed" : "set"} hooks.Notification (${SEAT_EVENT_MATCHER}) in ${settingsPath(o.home)}`);
5124
5581
  if (hadLine)
5125
- log(`refreshed statusLine in ${settingsPath(o2.home)}`);
5126
- log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o2.home)}`);
5127
- log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o2.home)}`);
5128
- log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(o2.home)}`);
5129
- log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(o2.home)}`);
5582
+ log(`refreshed statusLine in ${settingsPath(o.home)}`);
5583
+ log(`${hadSpec ? "refreshed" : "set"} hooks.PostToolUse (${SPEC_EVENT_MATCHER} → estimate spec files) in ${settingsPath(o.home)}`);
5584
+ log(`${hadPrompt ? "refreshed" : "set"} hooks.UserPromptSubmit (the seat speaks up in the session at 90 % and at a wall) in ${settingsPath(o.home)}`);
5585
+ log(`${hadSession ? "refreshed" : "set"} hooks.SessionStart (the loop and the rows this machine owes, before the first turn) in ${settingsPath(o.home)}`);
5586
+ log(`${hadStop ? "refreshed" : "set"} hooks.Stop (a session that estimated and never posted its actual is asked once) in ${settingsPath(o.home)}`);
5130
5587
  return 0;
5131
5588
  };
5132
5589
 
5133
5590
  // src/commands/init.ts
5134
5591
  var DEFAULT_PROXY_URL = "https://proxy.sleds.ai";
5135
- var promptStdin = (question) => new Promise((resolve4) => {
5592
+ var promptStdin = (question) => new Promise((resolve) => {
5136
5593
  const rl = createInterface({ input: process.stdin, output: process.stderr });
5137
5594
  rl.question(question, (answer) => {
5138
5595
  rl.close();
5139
- resolve4(answer.trim());
5596
+ resolve(answer.trim());
5140
5597
  });
5141
5598
  });
5142
5599
  async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch) {
@@ -5233,56 +5690,70 @@ async function probeProxy(url, fetchFn = fetch, opts = {}) {
5233
5690
  }
5234
5691
  return false;
5235
5692
  }
5236
- var resolveToken = async (o2) => {
5237
- const env = o2.env ?? process.env;
5238
- if (o2.token)
5239
- return { token: o2.token.trim(), source: "flag" };
5693
+ var resolveToken = async (o) => {
5694
+ const env = o.env ?? process.env;
5695
+ if (o.token)
5696
+ return { token: o.token.trim(), source: "flag" };
5240
5697
  if (env.SLEDS_TOKEN)
5241
5698
  return { token: env.SLEDS_TOKEN.trim(), source: "env" };
5242
- const fromFile = readTokenFile(o2.home);
5699
+ const fromFile = readTokenFile(o.home);
5243
5700
  if (fromFile)
5244
5701
  return { token: fromFile, source: "file" };
5245
- if (o2.nonInteractive || !o2.prompt)
5702
+ if (o.nonInteractive)
5246
5703
  return null;
5247
- if (!o2.signup) {
5248
- const typed = (await o2.prompt("Paste your SLEDS seat token (sled_…), or press enter to sign up as an individual: ")).trim();
5249
- if (typed)
5250
- return { token: typed, source: "prompt" };
5251
- }
5252
- const url = normalizeProxyUrl(o2.url ?? env.SLEDS_PROXY_URL ?? readConfig(o2.home).proxyUrl ?? DEFAULT_PROXY_URL);
5704
+ const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? readConfig(o.home).proxyUrl ?? DEFAULT_PROXY_URL);
5253
5705
  if (!url)
5254
5706
  return null;
5255
- const log = o2.log ?? console.log;
5256
- const err = o2.error ?? console.error;
5257
- let team = o2.team?.trim() || null;
5258
- if (team === null && !o2.email) {
5259
- const which = (await o2.prompt("Set up SLEDS for (1) just you, or (2) your team? [1/2]: ")).trim();
5707
+ const log = o.log ?? console.log;
5708
+ const err = o.error ?? console.error;
5709
+ if (!o.signup) {
5710
+ const r = await deviceFlow({
5711
+ proxyUrl: url,
5712
+ agent: "claude-code",
5713
+ host: o.host,
5714
+ fetchFn: o.fetchFn,
5715
+ openBrowser: o.openBrowser ?? openInBrowser,
5716
+ noBrowser: o.noBrowser,
5717
+ sleep: o.sleep,
5718
+ log
5719
+ });
5720
+ if (!r.ok) {
5721
+ err(deviceFailureText(r.reason));
5722
+ return null;
5723
+ }
5724
+ return { token: r.approved.token, source: "device", approved: r.approved };
5725
+ }
5726
+ if (!o.prompt)
5727
+ return null;
5728
+ let team = o.team?.trim() || null;
5729
+ if (team === null && !o.email) {
5730
+ const which = (await o.prompt("Set up SLEDS for (1) just you, or (2) your team? [1/2]: ")).trim();
5260
5731
  if (which === "2") {
5261
- team = (await o2.prompt("Team name (e.g. your company): ")).trim();
5732
+ team = (await o.prompt("Team name (e.g. your company): ")).trim();
5262
5733
  if (!team)
5263
5734
  return null;
5264
5735
  }
5265
5736
  }
5266
- const email = (o2.email ?? await o2.prompt(team ? "Your work email: " : "Your email: ")).trim();
5737
+ const email = (o.email ?? await o.prompt(team ? "Your work email: " : "Your email: ")).trim();
5267
5738
  if (!email.includes("@"))
5268
5739
  return null;
5269
5740
  if (team) {
5270
- 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();
5271
- const r2 = await signupTeam(url, email, team, credential2 || null, o2.fetchFn);
5272
- if (!r2.ok) {
5273
- err(signupFailureText(r2));
5741
+ const credential = (await o.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();
5742
+ const r = await signupTeam(url, email, team, credential || null, o.fetchFn);
5743
+ if (!r.ok) {
5744
+ err(signupFailureText(r));
5274
5745
  return null;
5275
5746
  }
5276
- 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."}`);
5277
- if (r2.verifyPending)
5747
+ log(`Created ${team} with you as owner (${email}).${credential ? " 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."}`);
5748
+ if (r.verifyPending)
5278
5749
  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).`);
5279
5750
  log("Invite your team on the dashboard (Members → invite by email, Admin → Mint a seat); each person runs `sleds init --token …`.");
5280
- return { token: r2.token, source: "signup" };
5751
+ return { token: r.token, source: "signup" };
5281
5752
  }
5282
- const credential = (await o2.prompt("Your Anthropic API key (sk-ant-…, stored encrypted, spends only your account): ")).trim();
5753
+ const credential = (await o.prompt("Your Anthropic API key (sk-ant-…, stored encrypted, spends only your account): ")).trim();
5283
5754
  if (!credential)
5284
5755
  return null;
5285
- const r = await signupIndividual(url, email, credential, o2.fetchFn);
5756
+ const r = await signupIndividual(url, email, credential, o.fetchFn);
5286
5757
  if (!r.ok) {
5287
5758
  err(signupFailureText(r));
5288
5759
  return null;
@@ -5290,66 +5761,66 @@ var resolveToken = async (o2) => {
5290
5761
  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.`);
5291
5762
  return { token: r.token, source: "signup" };
5292
5763
  };
5293
- var init = async (o2) => {
5294
- const log = o2.log ?? console.log;
5295
- const error = o2.error ?? console.error;
5296
- const env = o2.env ?? process.env;
5297
- if (!hasClaudeCode(o2.home)) {
5298
- error(`Claude Code not found (${claudeDir(o2.home)} does not exist). Install it, then re-run.`);
5764
+ var init = async (o) => {
5765
+ const log = o.log ?? console.log;
5766
+ const error = o.error ?? console.error;
5767
+ const env = o.env ?? process.env;
5768
+ if (!hasClaudeCode(o.home)) {
5769
+ error(`Claude Code not found (${claudeDir(o.home)} does not exist). Install it, then re-run.`);
5299
5770
  return 1;
5300
5771
  }
5301
- const resolved = await resolveToken(o2);
5772
+ const resolved = await resolveToken(o);
5302
5773
  if (!resolved) {
5303
- error(o2.signup ? "Signup did not complete. Nothing was changed." : "No seat token. Pass --token, set SLEDS_TOKEN, run without --non-interactive, or use --signup.");
5774
+ error(o.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.");
5304
5775
  return 2;
5305
5776
  }
5306
5777
  if (!isSeatToken(resolved.token)) {
5307
5778
  error("Invalid seat token: expected something like sled_xxxxxxxx.");
5308
5779
  return 2;
5309
5780
  }
5310
- const url = normalizeProxyUrl(o2.url ?? env.SLEDS_PROXY_URL ?? readConfig(o2.home).proxyUrl ?? DEFAULT_PROXY_URL);
5781
+ const url = normalizeProxyUrl(o.url ?? env.SLEDS_PROXY_URL ?? readConfig(o.home).proxyUrl ?? DEFAULT_PROXY_URL);
5311
5782
  if (!url) {
5312
- error(`Invalid proxy URL: ${JSON.stringify(o2.url ?? env.SLEDS_PROXY_URL ?? "")}. Expected https://proxy.sleds.ai.`);
5783
+ error(`Invalid proxy URL: ${JSON.stringify(o.url ?? env.SLEDS_PROXY_URL ?? "")}. Expected https://proxy.sleds.ai.`);
5313
5784
  return 2;
5314
5785
  }
5315
- if (!o2.noProbe && !await probeProxy(url, o2.fetchFn)) {
5786
+ if (!o.noProbe && !await probeProxy(url, o.fetchFn)) {
5316
5787
  error(`No SLEDS proxy answered at ${url}/healthz. Check the URL (or pass --no-probe to save it anyway).`);
5317
5788
  return 2;
5318
5789
  }
5319
5790
  const wrote = [];
5320
- const seatInfo = await fetchSeatInfo(url, resolved.token, o2.fetchFn);
5791
+ const seatInfo = await fetchSeatInfo(url, resolved.token, o.fetchFn);
5321
5792
  const org = seatInfo.org;
5322
- const wiring = o2.proxyAlways ? "proxy-always" : o2.seatFirst ? "seat-first" : org?.mode ?? "proxy-always";
5793
+ const wiring = o.proxyAlways ? "proxy-always" : o.seatFirst ? "seat-first" : org?.mode ?? "proxy-always";
5323
5794
  if (wiring === "proxy-always" && org && !org.capabilities.upstreamKey) {
5324
5795
  error(`${org.name} has no platform key for the proxy to forward with; --proxy-always would break every request. Using seat-first.`);
5325
5796
  }
5326
5797
  const finalWiring = wiring === "proxy-always" && org && !org.capabilities.upstreamKey ? "seat-first" : wiring;
5327
- if (backupSettings(o2.home))
5328
- wrote.push(`backed up settings to ${backupPath(o2.home)}`);
5329
- const current = readSettings(o2.home);
5798
+ if (backupSettings(o.home))
5799
+ wrote.push(`backed up settings to ${backupPath(o.home)}`);
5800
+ const current = readSettings(o.home);
5330
5801
  const wired = finalWiring === "proxy-always" ? applyProxy(current, { url, token: resolved.token }) : removeProxy(current);
5331
- const statusLine2 = applyStatusLine(wired, o2.statusLineCommand ?? sledsCommand("statusline", o2.home, o2.env));
5332
- writeSettings(o2.home, statusLine2.settings);
5333
- 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`);
5334
- wrote.push(statusLine2.prior === undefined ? `set statusLine in ${settingsPath(o2.home)}` : `set statusLine in ${settingsPath(o2.home)} (previous one kept for uninstall)`);
5802
+ const statusLine = applyStatusLine(wired, o.statusLineCommand ?? sledsCommand("statusline", o.home, o.env));
5803
+ writeSettings(o.home, statusLine.settings);
5804
+ wrote.push(finalWiring === "proxy-always" ? `set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home)} (everything through the proxy)` : `left Claude Code on your seat (no ANTHROPIC_BASE_URL in ${settingsPath(o.home)}); \`sleds on\` switches to the proxy at a wall`);
5805
+ wrote.push(statusLine.prior === undefined ? `set statusLine in ${settingsPath(o.home)}` : `set statusLine in ${settingsPath(o.home)} (previous one kept for uninstall)`);
5335
5806
  hooks2({
5336
- home: o2.home,
5337
- env: o2.env,
5807
+ home: o.home,
5808
+ env: o.env,
5338
5809
  refreshStatusLine: false,
5339
- seatEventCommand: o2.seatEventCommand,
5340
- specEventCommand: o2.specEventCommand,
5341
- promptEventCommand: o2.promptEventCommand,
5342
- sessionEventCommand: o2.sessionEventCommand,
5343
- stopEventCommand: o2.stopEventCommand,
5810
+ seatEventCommand: o.seatEventCommand,
5811
+ specEventCommand: o.specEventCommand,
5812
+ promptEventCommand: o.promptEventCommand,
5813
+ sessionEventCommand: o.sessionEventCommand,
5814
+ stopEventCommand: o.stopEventCommand,
5344
5815
  log: (line) => wrote.push(line),
5345
5816
  error
5346
5817
  });
5347
5818
  if (resolved.source !== "file") {
5348
- writeTokenFile(o2.home, resolved.token);
5349
- wrote.push(`saved token to ${tokenPath(o2.home)} (0600)`);
5819
+ writeTokenFile(o.home, resolved.token);
5820
+ wrote.push(`saved token to ${tokenPath(o.home)} (0600)`);
5350
5821
  }
5351
- const prior = readConfig(o2.home);
5352
- writeConfig(o2.home, {
5822
+ const prior = readConfig(o.home);
5823
+ writeConfig(o.home, {
5353
5824
  ...prior,
5354
5825
  token: resolved.token,
5355
5826
  proxyUrl: url,
@@ -5357,18 +5828,24 @@ var init = async (o2) => {
5357
5828
  agentKind: "claude-code",
5358
5829
  ...seatInfo.email ? { email: seatInfo.email } : {},
5359
5830
  ...org ? { org } : {},
5360
- ...statusLine2.prior !== undefined ? { priorStatusLine: statusLine2.prior } : {}
5831
+ ...statusLine.prior !== undefined ? { priorStatusLine: statusLine.prior } : {}
5361
5832
  });
5362
- wrote.push(`saved token and proxy URL to ${configPath(o2.home)} (0600)`);
5363
- mkdirSync8(skillDir(o2.home), { recursive: true });
5364
- writeFileSync8(skillPath(o2.home), SKILL_MD);
5365
- wrote.push(`installed skill at ${skillPath(o2.home)}`);
5366
- if (o2.installMusher) {
5367
- const r = o2.installMusher();
5833
+ wrote.push(`saved token and proxy URL to ${configPath(o.home)} (0600)`);
5834
+ mkdirSync8(skillDir(o.home), { recursive: true });
5835
+ writeFileSync8(skillPath(o.home), SKILL_MD);
5836
+ wrote.push(`installed skill at ${skillPath(o.home)}`);
5837
+ if (o.installMusher) {
5838
+ const r = o.installMusher();
5368
5839
  if (r)
5369
5840
  wrote.push(r);
5370
5841
  }
5371
5842
  const orgLine = org ? `${org.name} · ${org.plan}${org.capabilities.upstreamKey ? ` · ${org.capabilities.upstreamSource === "own" ? "platform key" : "shared key"}` : " · no platform key"}` : "org unknown (proxy did not answer /proxy/meter)";
5843
+ if (resolved.approved) {
5844
+ log("");
5845
+ log(`Signed in as ${resolved.approved.email} · ${resolved.approved.org.name}. This machine is now a seat there.`);
5846
+ if (resolved.approved.workspaceUrl)
5847
+ log(`Your workspace on the web: ${resolved.approved.workspaceUrl}`);
5848
+ }
5372
5849
  if (finalWiring === "proxy-always") {
5373
5850
  log(`Claude Code now runs through SLEDS on the platform key. (${orgLine})`);
5374
5851
  log("");
@@ -5395,9 +5872,9 @@ var init = async (o2) => {
5395
5872
  };
5396
5873
 
5397
5874
  // src/commands/mcp.ts
5398
- var mcp = async (o2) => {
5399
- const stdin = o2.stdin ?? process.stdin;
5400
- const write = o2.write ?? ((line) => process.stdout.write(`${line}
5875
+ var mcp = async (o) => {
5876
+ const stdin = o.stdin ?? process.stdin;
5877
+ const write = o.write ?? ((line) => process.stdout.write(`${line}
5401
5878
  `));
5402
5879
  let buffer = "";
5403
5880
  for await (const chunk of stdin) {
@@ -5407,7 +5884,7 @@ var mcp = async (o2) => {
5407
5884
  while (nl !== -1) {
5408
5885
  const line = buffer.slice(0, nl);
5409
5886
  buffer = buffer.slice(nl + 1);
5410
- const out = await handleLine2(line, { home: o2.home });
5887
+ const out = await handleLine2(line, { home: o.home });
5411
5888
  if (out !== null)
5412
5889
  write(out);
5413
5890
  nl = buffer.indexOf(`
@@ -5415,7 +5892,7 @@ var mcp = async (o2) => {
5415
5892
  }
5416
5893
  }
5417
5894
  if (buffer.trim().length > 0) {
5418
- const out = await handleLine2(buffer, { home: o2.home });
5895
+ const out = await handleLine2(buffer, { home: o.home });
5419
5896
  if (out !== null)
5420
5897
  write(out);
5421
5898
  }
@@ -5425,15 +5902,15 @@ var mcp = async (o2) => {
5425
5902
  // src/commands/meter.ts
5426
5903
  init_config();
5427
5904
  init_meter();
5428
- var meter = async (o2) => {
5429
- const log = o2.log ?? console.log;
5430
- const now = (o2.now ?? Date.now)();
5431
- const src = meterSource(o2.home);
5905
+ var meter = async (o) => {
5906
+ const log = o.log ?? console.log;
5907
+ const now = (o.now ?? Date.now)();
5908
+ const src = meterSource(o.home);
5432
5909
  if (!src) {
5433
- log(`SLEDS is not set up (${configPath(o2.home)}). Run \`sleds init\` first.`);
5910
+ log(`SLEDS is not set up (${configPath(o.home)}). Run \`sleds init\` first.`);
5434
5911
  return 1;
5435
5912
  }
5436
- const m = await loadMeter({ home: o2.home, now, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs });
5913
+ const m = await loadMeter({ home: o.home, now, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs });
5437
5914
  if (!m) {
5438
5915
  log(`sleds offline. ${src.proxyUrl} did not answer → \`sleds status\` for the proxy health.`);
5439
5916
  return 2;
@@ -5446,16 +5923,16 @@ var meter = async (o2) => {
5446
5923
  // src/commands/musher.ts
5447
5924
  init_config();
5448
5925
  init_meter();
5449
- import { spawnSync as spawnSync3 } from "node:child_process";
5926
+ import { spawnSync as spawnSync4 } from "node:child_process";
5450
5927
  import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "node:fs";
5451
- import { hostname as hostname2 } from "node:os";
5928
+ import { hostname as hostname3 } from "node:os";
5452
5929
  import { dirname as dirname5 } from "node:path";
5453
5930
  var defaultWhich3 = (cmd) => {
5454
5931
  const path = process.env.PATH ?? "";
5455
5932
  for (const dir of path.split(":").filter(Boolean)) {
5456
- const p2 = `${dir}/${cmd}`;
5457
- if (existsSync10(p2))
5458
- return p2;
5933
+ const p = `${dir}/${cmd}`;
5934
+ if (existsSync10(p))
5935
+ return p;
5459
5936
  }
5460
5937
  return null;
5461
5938
  };
@@ -5465,68 +5942,68 @@ var findClaude2 = (home, configured, which) => {
5465
5942
  const found = which("claude");
5466
5943
  if (found)
5467
5944
  return found;
5468
- for (const p2 of [
5945
+ for (const p of [
5469
5946
  `${home}/.local/bin/claude`,
5470
5947
  "/opt/homebrew/bin/claude",
5471
5948
  "/usr/local/bin/claude",
5472
5949
  `${home}/.npm-global/bin/claude`
5473
5950
  ])
5474
- if (existsSync10(p2))
5475
- return p2;
5951
+ if (existsSync10(p))
5952
+ return p;
5476
5953
  return null;
5477
5954
  };
5478
- var musherInstall = (o2) => {
5479
- const log = o2.log ?? console.log;
5480
- const error = o2.error ?? console.error;
5481
- const platform = o2.platform ?? process.platform;
5955
+ var musherInstall = (o) => {
5956
+ const log = o.log ?? console.log;
5957
+ const error = o.error ?? console.error;
5958
+ const platform = o.platform ?? process.platform;
5482
5959
  if (platform !== "darwin") {
5483
5960
  error(`sleds musher --install needs launchd (macOS). On ${platform}, run \`sleds musher tick\` from cron every 5 minutes.`);
5484
5961
  return 1;
5485
5962
  }
5486
- const env = o2.env ?? process.env;
5487
- const plist = musherPlistPath(o2.home, env);
5963
+ const env = o.env ?? process.env;
5964
+ const plist = musherPlistPath(o.home, env);
5488
5965
  const label = musherLabel(env);
5489
- const dir = configDir(o2.home, env);
5966
+ const dir = configDir(o.home, env);
5490
5967
  mkdirSync9(dirname5(plist), { recursive: true });
5491
5968
  mkdirSync9(dir, { recursive: true, mode: 448 });
5492
- const execPath = o2.sledsArgv[0] ?? process.execPath;
5969
+ const execPath = o.sledsArgv[0] ?? process.execPath;
5493
5970
  const agentEnv = {
5494
- PATH: musherPath(o2.home, execPath, env.PATH ?? ""),
5495
- HOME: o2.home,
5971
+ PATH: musherPath(o.home, execPath, env.PATH ?? ""),
5972
+ HOME: o.home,
5496
5973
  ...env.SLEDS_HOME ? { SLEDS_HOME: env.SLEDS_HOME } : {},
5497
5974
  ...env.CLAUDE_CONFIG_DIR ? { CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR } : {}
5498
5975
  };
5499
- const exec = o2.exec ?? defaultExecSync;
5976
+ const exec = o.exec ?? defaultExecSync;
5500
5977
  if (existsSync10(plist))
5501
5978
  exec("launchctl", ["unload", plist]);
5502
- writeFileSync9(plist, buildMusherPlist({ sledsArgv: o2.sledsArgv, env: agentEnv, logPath: musherLogPath(dir), label }));
5979
+ writeFileSync9(plist, buildMusherPlist({ sledsArgv: o.sledsArgv, env: agentEnv, logPath: musherLogPath(dir), label }));
5503
5980
  const r = exec("launchctl", ["load", plist]);
5504
5981
  if (!r.ok) {
5505
5982
  error(`launchctl load failed: ${r.stderr.trim() || "unknown"}`);
5506
5983
  return 1;
5507
5984
  }
5508
- const cfg = readConfig(o2.home);
5509
- writeConfig(o2.home, { ...cfg, musher: { ...cfg.musher ?? {}, agent: "launchd" } });
5510
- const claude = findClaude2(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich3);
5985
+ const cfg = readConfig(o.home);
5986
+ writeConfig(o.home, { ...cfg, musher: { ...cfg.musher ?? {}, agent: "launchd" } });
5987
+ const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
5511
5988
  log(`musher installed: launchd agent ${label}, every 5 min · log ${musherLogPath(dir)}`);
5512
5989
  if (!claude)
5513
5990
  log("warn: `claude` not found on PATH — set it with `sleds musher --claude /path/to/claude`");
5514
5991
  return 0;
5515
5992
  };
5516
- var musherUninstall = (o2) => {
5517
- const log = o2.log ?? console.log;
5518
- const env = o2.env ?? process.env;
5519
- const plist = musherPlistPath(o2.home, env);
5520
- const exec = o2.exec ?? defaultExecSync;
5993
+ var musherUninstall = (o) => {
5994
+ const log = o.log ?? console.log;
5995
+ const env = o.env ?? process.env;
5996
+ const plist = musherPlistPath(o.home, env);
5997
+ const exec = o.exec ?? defaultExecSync;
5521
5998
  if (existsSync10(plist)) {
5522
5999
  exec("launchctl", ["unload", plist]);
5523
6000
  rmSync2(plist, { force: true });
5524
6001
  log(`musher removed: ${musherLabel(env)}`);
5525
6002
  } else
5526
6003
  log("musher was not installed.");
5527
- const cfg = readConfig(o2.home);
6004
+ const cfg = readConfig(o.home);
5528
6005
  if (cfg.musher)
5529
- writeConfig(o2.home, { ...cfg, musher: { ...cfg.musher, agent: "none" } });
6006
+ writeConfig(o.home, { ...cfg, musher: { ...cfg.musher, agent: "none" } });
5530
6007
  return 0;
5531
6008
  };
5532
6009
  var formatMusherStatus = (list, now) => {
@@ -5541,23 +6018,23 @@ var formatMusherStatus = (list, now) => {
5541
6018
  lines.push("nothing deferred on this seat.");
5542
6019
  return lines;
5543
6020
  };
5544
- var clientFor = (o2) => {
5545
- if (o2.client)
5546
- return o2.client;
5547
- const src = meterSource(o2.home);
5548
- return src ? musherClient({ ...src, fetchFn: o2.fetchFn }) : null;
5549
- };
5550
- var musherStatus = async (o2) => {
5551
- const log = o2.log ?? console.log;
5552
- const now = (o2.now ?? Date.now)();
5553
- const env = o2.env ?? process.env;
5554
- const plist = musherPlistPath(o2.home, env);
5555
- const cfg = readConfig(o2.home);
6021
+ var clientFor = (o) => {
6022
+ if (o.client)
6023
+ return o.client;
6024
+ const src = meterSource(o.home);
6025
+ return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
6026
+ };
6027
+ var musherStatus = async (o) => {
6028
+ const log = o.log ?? console.log;
6029
+ const now = (o.now ?? Date.now)();
6030
+ const env = o.env ?? process.env;
6031
+ const plist = musherPlistPath(o.home, env);
6032
+ const cfg = readConfig(o.home);
5556
6033
  log(existsSync10(plist) ? `agent ${musherLabel(env)} installed (${plist})` : "agent not installed — sleds musher --install");
5557
- const claude = findClaude2(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich3);
6034
+ const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
5558
6035
  log(`claude ${claude ?? "not found — sleds musher --claude /path/to/claude"}`);
5559
6036
  log(`mode ${cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
5560
- const client = clientFor(o2);
6037
+ const client = clientFor(o);
5561
6038
  if (!client) {
5562
6039
  log("seat not initialised (sleds init)");
5563
6040
  return 1;
@@ -5571,9 +6048,9 @@ var musherStatus = async (o2) => {
5571
6048
  log(line);
5572
6049
  return 0;
5573
6050
  };
5574
- var musherTick = async (o2) => {
5575
- const log = o2.log ?? console.log;
5576
- const client = clientFor(o2);
6051
+ var musherTick = async (o) => {
6052
+ const log = o.log ?? console.log;
6053
+ const client = clientFor(o);
5577
6054
  if (!client) {
5578
6055
  log("musher: seat not initialised; nothing to do");
5579
6056
  return 1;
@@ -5585,19 +6062,19 @@ var musherTick = async (o2) => {
5585
6062
  }
5586
6063
  if (list.due.length === 0)
5587
6064
  return 0;
5588
- const cfg = readConfig(o2.home);
5589
- const claude = findClaude2(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich3) ?? "claude";
5590
- const seat = readSeatState(o2.home);
6065
+ const cfg = readConfig(o.home);
6066
+ const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3) ?? "claude";
6067
+ const seat = readSeatState(o.home);
5591
6068
  for (const job of list.due) {
5592
6069
  const out = await runJob({
5593
6070
  job,
5594
6071
  client,
5595
6072
  fallbackSession: seat.session,
5596
- host: o2.host ?? hostname2(),
6073
+ host: o.host ?? hostname3(),
5597
6074
  claudePath: claude,
5598
6075
  permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
5599
6076
  timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
5600
- exec: o2.execCapture,
6077
+ exec: o.execCapture,
5601
6078
  log
5602
6079
  });
5603
6080
  if (out.kind === "skipped")
@@ -5605,9 +6082,9 @@ var musherTick = async (o2) => {
5605
6082
  }
5606
6083
  return 0;
5607
6084
  };
5608
- var musherConfigure = (o2, flags) => {
5609
- const log = o2.log ?? console.log;
5610
- const cfg = readConfig(o2.home);
6085
+ var musherConfigure = (o, flags) => {
6086
+ const log = o.log ?? console.log;
6087
+ const cfg = readConfig(o.home);
5611
6088
  const musher = { ...cfg.musher ?? {} };
5612
6089
  if (flags.claude)
5613
6090
  musher.claudePath = flags.claude;
@@ -5616,23 +6093,23 @@ var musherConfigure = (o2, flags) => {
5616
6093
  if (flags.timeout) {
5617
6094
  const n = Number.parseInt(flags.timeout, 10);
5618
6095
  if (!Number.isFinite(n) || n <= 0) {
5619
- (o2.error ?? console.error)("--timeout takes minutes, a positive number");
6096
+ (o.error ?? console.error)("--timeout takes minutes, a positive number");
5620
6097
  return 1;
5621
6098
  }
5622
6099
  musher.timeoutMinutes = n;
5623
6100
  }
5624
- writeConfig(o2.home, { ...cfg, musher });
6101
+ writeConfig(o.home, { ...cfg, musher });
5625
6102
  log(`musher settings: claude ${musher.claudePath ?? "(PATH)"} · mode ${musher.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${musher.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
5626
6103
  return 0;
5627
6104
  };
5628
6105
  var defaultExecSync = (cmd, args) => {
5629
- const r = spawnSync3(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
6106
+ const r = spawnSync4(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
5630
6107
  if (r.error)
5631
6108
  return { ok: false, stderr: r.error.message };
5632
6109
  return { ok: r.status === 0, stderr: r.stderr ?? "" };
5633
6110
  };
5634
- var musher = async (o2) => {
5635
- const args = [...o2.argv];
6111
+ var musher = async (o) => {
6112
+ const args = [...o.argv];
5636
6113
  const flags = {};
5637
6114
  const positional = [];
5638
6115
  for (let i = 0;i < args.length; i++) {
@@ -5648,16 +6125,16 @@ var musher = async (o2) => {
5648
6125
  positional.push(a);
5649
6126
  }
5650
6127
  if (flags.install)
5651
- return musherInstall(o2);
6128
+ return musherInstall(o);
5652
6129
  if (flags.uninstall)
5653
- return musherUninstall(o2);
6130
+ return musherUninstall(o);
5654
6131
  if (flags.claude || flags.mode || flags.timeout)
5655
- return musherConfigure(o2, flags);
6132
+ return musherConfigure(o, flags);
5656
6133
  if (positional[0] === "tick")
5657
- return musherTick(o2);
6134
+ return musherTick(o);
5658
6135
  if (flags.status || positional.length === 0)
5659
- return musherStatus(o2);
5660
- (o2.error ?? console.error)(`sleds musher: unknown argument ${positional[0]}`);
6136
+ return musherStatus(o);
6137
+ (o.error ?? console.error)(`sleds musher: unknown argument ${positional[0]}`);
5661
6138
  return 1;
5662
6139
  };
5663
6140
 
@@ -5705,12 +6182,12 @@ var systemdCalendar = (deadline) => `${deadline.getFullYear()}-${pad2(deadline.g
5705
6182
  var scheduleRevert = (d, deadlineMs) => {
5706
6183
  const deadline = new Date(deadlineMs);
5707
6184
  if (d.platform === "darwin") {
5708
- const p2 = plistPath(d.home);
6185
+ const p = plistPath(d.home);
5709
6186
  try {
5710
6187
  mkdirSync10(join9(d.home, "Library", "LaunchAgents"), { recursive: true });
5711
- d.exec("launchctl", ["unload", p2]);
5712
- writeFileSync10(p2, buildLaunchdPlist(d.sledsArgv, deadline));
5713
- const r = d.exec("launchctl", ["load", p2]);
6188
+ d.exec("launchctl", ["unload", p]);
6189
+ writeFileSync10(p, buildLaunchdPlist(d.sledsArgv, deadline));
6190
+ const r = d.exec("launchctl", ["load", p]);
5714
6191
  if (r.ok)
5715
6192
  return { kind: "launchd", detail: `launchd agent ${REVERT_LABEL}` };
5716
6193
  return { kind: "manual", detail: `launchctl load failed: ${r.stderr.trim() || "unknown"}` };
@@ -5735,10 +6212,10 @@ var scheduleRevert = (d, deadlineMs) => {
5735
6212
  };
5736
6213
  var cancelRevert = (d, kind) => {
5737
6214
  if (kind === "launchd" || d.platform === "darwin") {
5738
- const p2 = plistPath(d.home);
5739
- if (existsSync11(p2)) {
5740
- d.exec("launchctl", ["unload", p2]);
5741
- rmSync3(p2, { force: true });
6215
+ const p = plistPath(d.home);
6216
+ if (existsSync11(p)) {
6217
+ d.exec("launchctl", ["unload", p]);
6218
+ rmSync3(p, { force: true });
5742
6219
  }
5743
6220
  }
5744
6221
  if (kind === "systemd" || d.platform === "linux") {
@@ -5748,15 +6225,15 @@ var cancelRevert = (d, kind) => {
5748
6225
 
5749
6226
  // src/commands/off.ts
5750
6227
  init_settings();
5751
- var off = (o2) => {
5752
- const log = o2.log ?? console.log;
5753
- const cfg = readConfig(o2.home);
5754
- const settings = readSettings(o2.home);
6228
+ var off = (o) => {
6229
+ const log = o.log ?? console.log;
6230
+ const cfg = readConfig(o.home);
6231
+ const settings = readSettings(o.home);
5755
6232
  const wasOn = cfg.mode === "on" || isPointedAtProxy(settings);
5756
6233
  if (wasOn)
5757
- writeSettings(o2.home, switchOff(settings, cfg.priorEnv ?? {}));
5758
- if (o2.scheduler)
5759
- cancelRevert(o2.scheduler, cfg.schedule);
6234
+ writeSettings(o.home, switchOff(settings, cfg.priorEnv ?? {}));
6235
+ if (o.scheduler)
6236
+ cancelRevert(o.scheduler, cfg.schedule);
5760
6237
  const next = {
5761
6238
  ...cfg,
5762
6239
  mode: "off",
@@ -5766,7 +6243,7 @@ var off = (o2) => {
5766
6243
  priorEnv: undefined,
5767
6244
  schedule: undefined
5768
6245
  };
5769
- writeConfig(o2.home, next);
6246
+ writeConfig(o.home, next);
5770
6247
  if (!wasOn) {
5771
6248
  log("Sleds already off. Claude Code is on your seat.");
5772
6249
  return 0;
@@ -5785,19 +6262,19 @@ init_settings();
5785
6262
  var DEFAULT_PROXY_URL2 = "https://proxy.sleds.ai";
5786
6263
 
5787
6264
  // src/commands/on.ts
5788
- var on = (o2) => {
5789
- const log = o2.log ?? console.log;
5790
- const error = o2.error ?? console.error;
5791
- const env = o2.env ?? process.env;
5792
- const now = (o2.now ?? Date.now)();
5793
- if (!hasClaudeCode(o2.home)) {
5794
- error(`Claude Code not found (${claudeDir(o2.home)} does not exist). Install it, then re-run.`);
6265
+ var on = (o) => {
6266
+ const log = o.log ?? console.log;
6267
+ const error = o.error ?? console.error;
6268
+ const env = o.env ?? process.env;
6269
+ const now = (o.now ?? Date.now)();
6270
+ if (!hasClaudeCode(o.home)) {
6271
+ error(`Claude Code not found (${claudeDir(o.home)} does not exist). Install it, then re-run.`);
5795
6272
  return 1;
5796
6273
  }
5797
- const cfg = readConfig(o2.home);
5798
- const token = cfg.token ?? readTokenFile(o2.home) ?? env.SLEDS_TOKEN?.trim();
6274
+ const cfg = readConfig(o.home);
6275
+ const token = cfg.token ?? readTokenFile(o.home) ?? env.SLEDS_TOKEN?.trim();
5799
6276
  if (!token) {
5800
- error(`No seat token in ${configPath(o2.home)}. Run \`sleds init\` first.`);
6277
+ error(`No seat token in ${configPath(o.home)}. Run \`sleds init\` first.`);
5801
6278
  return 2;
5802
6279
  }
5803
6280
  if (!isSeatToken(token)) {
@@ -5808,28 +6285,28 @@ var on = (o2) => {
5808
6285
  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.`);
5809
6286
  return 2;
5810
6287
  }
5811
- const url = (o2.url ?? cfg.proxyUrl ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL2).replace(/\/+$/, "");
6288
+ const url = (o.url ?? cfg.proxyUrl ?? env.SLEDS_PROXY_URL ?? DEFAULT_PROXY_URL2).replace(/\/+$/, "");
5812
6289
  let untilResetMs;
5813
- if (o2.untilReset !== undefined) {
6290
+ if (o.untilReset !== undefined) {
5814
6291
  try {
5815
- untilResetMs = parseUntilReset(o2.untilReset, now);
6292
+ untilResetMs = parseUntilReset(o.untilReset, now);
5816
6293
  } catch (err) {
5817
6294
  error(err instanceof Error ? err.message : String(err));
5818
6295
  return 2;
5819
6296
  }
5820
6297
  }
5821
- const settings = readSettings(o2.home);
6298
+ const settings = readSettings(o.home);
5822
6299
  const priorEnv = cfg.mode === "on" && cfg.priorEnv ? cfg.priorEnv : capturePriorEnv(settings);
5823
- const backedUp = backupSettings(o2.home);
5824
- writeSettings(o2.home, switchOn(settings, { url, token }));
6300
+ const backedUp = backupSettings(o.home);
6301
+ writeSettings(o.home, switchOn(settings, { url, token }));
5825
6302
  let schedule;
5826
6303
  let scheduleNote;
5827
- if (untilResetMs !== undefined && !o2.noSchedule && o2.scheduler && hasHomeOverride(env)) {
6304
+ if (untilResetMs !== undefined && !o.noSchedule && o.scheduler && hasHomeOverride(env)) {
5828
6305
  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.");
5829
6306
  return 2;
5830
6307
  }
5831
- if (untilResetMs !== undefined && !o2.noSchedule && o2.scheduler) {
5832
- const r = scheduleRevert(o2.scheduler, untilResetMs);
6308
+ if (untilResetMs !== undefined && !o.noSchedule && o.scheduler) {
6309
+ const r = scheduleRevert(o.scheduler, untilResetMs);
5833
6310
  schedule = r.kind;
5834
6311
  scheduleNote = r.kind === "manual" ? r.detail : undefined;
5835
6312
  }
@@ -5845,19 +6322,19 @@ var on = (o2) => {
5845
6322
  next.untilReset = new Date(untilResetMs).toISOString();
5846
6323
  else
5847
6324
  next.untilReset = undefined;
5848
- next.reason = o2.reason?.trim() || undefined;
6325
+ next.reason = o.reason?.trim() || undefined;
5849
6326
  next.schedule = schedule;
5850
- writeConfig(o2.home, next);
6327
+ writeConfig(o.home, next);
5851
6328
  log(`Sleds on. Claude Code now runs on the platform key through ${url}. Restart claude to apply.`);
5852
6329
  if (untilResetMs !== undefined) {
5853
- log(`Reverts at ${formatLocal(untilResetMs, o2.tz)} — run \`sleds off\` earlier if you want.`);
6330
+ log(`Reverts at ${formatLocal(untilResetMs, o.tz)} — run \`sleds off\` earlier if you want.`);
5854
6331
  if (schedule === undefined || schedule === "manual") {
5855
6332
  const why = scheduleNote ? ` (${scheduleNote})` : "";
5856
6333
  log(`Auto-revert not scheduled${why}. Run \`sleds off\` after the reset.`);
5857
6334
  }
5858
6335
  }
5859
6336
  if (backedUp)
5860
- log(`Backed up ${settingsPath(o2.home)} to ${backupPath(o2.home)}.`);
6337
+ log(`Backed up ${settingsPath(o.home)} to ${backupPath(o.home)}.`);
5861
6338
  return 0;
5862
6339
  };
5863
6340
 
@@ -5905,22 +6382,22 @@ var exitsLine = (hasUpstreamKey) => hasUpstreamKey ? "`/sled handoff <what you w
5905
6382
  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.`;
5906
6383
  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.`;
5907
6384
  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.`;
5908
- var promptEvent = async (o2) => {
5909
- const log = o2.log ?? console.log;
5910
- const error = o2.error ?? console.error;
5911
- const now = (o2.now ?? Date.now)();
5912
- if (o2.stdin === undefined)
6385
+ var promptEvent = async (o) => {
6386
+ const log = o.log ?? console.log;
6387
+ const error = o.error ?? console.error;
6388
+ const now = (o.now ?? Date.now)();
6389
+ if (o.stdin === undefined)
5913
6390
  await readStdin(HOOK_STDIN_TIMEOUT_MS);
5914
6391
  let state;
5915
6392
  try {
5916
- state = readSeatState(o2.home);
6393
+ state = readSeatState(o.home);
5917
6394
  } catch {
5918
6395
  return 0;
5919
6396
  }
5920
6397
  const pending = untold(state);
5921
6398
  if (pending.length === 0)
5922
6399
  return 0;
5923
- const hasKey = readConfig(o2.home).org?.capabilities.upstreamKey !== false;
6400
+ const hasKey = readConfig(o.home).org?.capabilities.upstreamKey !== false;
5924
6401
  const told = { ...state.told ?? {} };
5925
6402
  const mark = (bar, stage, at) => {
5926
6403
  told[bar] = { ...told[bar] ?? {}, [stage]: at };
@@ -5940,7 +6417,7 @@ var promptEvent = async (o2) => {
5940
6417
  lines.push(e.stage === "approaching" ? approachingContext(e.bar, pct, e.resetsAt, hasKey, now) : hitContext(e.bar, e.resetsAt, hasKey, now));
5941
6418
  }
5942
6419
  try {
5943
- writeSeatState(o2.home, { ...state, told });
6420
+ writeSeatState(o.home, { ...state, told });
5944
6421
  } catch {}
5945
6422
  if (code === 0 && lines.length > 0)
5946
6423
  log(lines.join(`
@@ -5951,17 +6428,17 @@ var promptEvent = async (o2) => {
5951
6428
  // src/commands/run.ts
5952
6429
  init_config();
5953
6430
  init_meter();
5954
- import { hostname as hostname3 } from "node:os";
5955
- var run = async (o2) => {
5956
- const log = o2.log ?? console.log;
5957
- const error = o2.error ?? console.error;
5958
- if (!o2.jobId) {
6431
+ import { hostname as hostname4 } from "node:os";
6432
+ var run = async (o) => {
6433
+ const log = o.log ?? console.log;
6434
+ const error = o.error ?? console.error;
6435
+ if (!o.jobId) {
5959
6436
  error("usage: sleds run <jobId>");
5960
6437
  return 2;
5961
6438
  }
5962
- const client = o2.client ?? (() => {
5963
- const src = meterSource(o2.home);
5964
- return src ? musherClient({ ...src, fetchFn: o2.fetchFn }) : null;
6439
+ const client = o.client ?? (() => {
6440
+ const src = meterSource(o.home);
6441
+ return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
5965
6442
  })();
5966
6443
  if (!client) {
5967
6444
  error("No seat on this machine. Run `sleds init` first.");
@@ -5972,13 +6449,13 @@ var run = async (o2) => {
5972
6449
  error("Could not reach the SLEDS proxy.");
5973
6450
  return 1;
5974
6451
  }
5975
- const job = [...list.due, ...list.upcoming].find((j) => j.jobId === o2.jobId);
6452
+ const job = [...list.due, ...list.upcoming].find((j) => j.jobId === o.jobId);
5976
6453
  if (!job) {
5977
- error(`Job ${o2.jobId} is not a scheduled local job on this seat (sleds musher --status lists them).`);
6454
+ error(`Job ${o.jobId} is not a scheduled local job on this seat (sleds musher --status lists them).`);
5978
6455
  return 1;
5979
6456
  }
5980
- const cfg = readConfig(o2.home);
5981
- const claude = findClaude(o2.home, cfg.musher?.claudePath, o2.which ?? defaultWhich);
6457
+ const cfg = readConfig(o.home);
6458
+ const claude = findClaude(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich);
5982
6459
  if (!claude) {
5983
6460
  error("`claude` not found. Install Claude Code, or `sleds musher --claude /path/to/claude`.");
5984
6461
  return 1;
@@ -5986,12 +6463,12 @@ var run = async (o2) => {
5986
6463
  const out = await runJob({
5987
6464
  job,
5988
6465
  client,
5989
- fallbackSession: readSeatState(o2.home).session,
5990
- host: o2.host ?? hostname3(),
6466
+ fallbackSession: readSeatState(o.home).session,
6467
+ host: o.host ?? hostname4(),
5991
6468
  claudePath: claude,
5992
6469
  permissionMode: cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE,
5993
6470
  timeoutMs: (cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES) * 60000,
5994
- exec: o2.execCapture,
6471
+ exec: o.execCapture,
5995
6472
  log
5996
6473
  });
5997
6474
  if (out.kind === "skipped") {
@@ -6024,25 +6501,25 @@ var notificationType = (text) => {
6024
6501
  return;
6025
6502
  }
6026
6503
  };
6027
- var seatEvent = async (o2) => {
6028
- const log = o2.log ?? console.log;
6029
- const now = (o2.now ?? Date.now)();
6030
- const input = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS2);
6504
+ var seatEvent = async (o) => {
6505
+ const log = o.log ?? console.log;
6506
+ const now = (o.now ?? Date.now)();
6507
+ const input = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS2);
6031
6508
  const kind = notificationType(input);
6032
6509
  try {
6033
- const state = readSeatState(o2.home);
6510
+ const state = readSeatState(o.home);
6034
6511
  const week = state.window?.sevenDay;
6035
- const src = meterSource(o2.home);
6512
+ const src = meterSource(o.home);
6036
6513
  if (src) {
6037
- await postSeatWall({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
6514
+ await postSeatWall({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
6038
6515
  ...week ? { resetsAt: week.resetsAt, usedPct: week.usedPct } : {},
6039
6516
  ...kind ? { notification: kind } : {}
6040
6517
  });
6041
6518
  }
6042
6519
  if (week && week.resetsAt > now)
6043
- writeSeatState(o2.home, { ...state, lastWallResetsAt: week.resetsAt });
6520
+ writeSeatState(o.home, { ...state, lastWallResetsAt: week.resetsAt });
6044
6521
  } catch {}
6045
- const line = seatWallLine(readConfig(o2.home).org?.capabilities.upstreamKey !== false);
6522
+ const line = seatWallLine(readConfig(o.home).org?.capabilities.upstreamKey !== false);
6046
6523
  log(line);
6047
6524
  return 0;
6048
6525
  };
@@ -6050,7 +6527,7 @@ var seatEvent = async (o2) => {
6050
6527
  // src/commands/seatSync.ts
6051
6528
  import { execFileSync as execFileSync2 } from "node:child_process";
6052
6529
  import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync10, readdirSync as readdirSync2, writeFileSync as writeFileSync11 } from "node:fs";
6053
- import { platform } from "node:os";
6530
+ import { platform as platform2 } from "node:os";
6054
6531
  import { join as join11 } from "node:path";
6055
6532
 
6056
6533
  // src/cookieDb.ts
@@ -6062,11 +6539,11 @@ var isBun = () => typeof globalThis.Bun !== "undefined";
6062
6539
  async function queryRows(dbPath) {
6063
6540
  if (isBun()) {
6064
6541
  const { Database } = await import("bun:sqlite");
6065
- const conn2 = new Database(dbPath, { readonly: true });
6542
+ const conn = new Database(dbPath, { readonly: true });
6066
6543
  try {
6067
- return conn2.query(QUERY).all();
6544
+ return conn.query(QUERY).all();
6068
6545
  } finally {
6069
- conn2.close();
6546
+ conn.close();
6070
6547
  }
6071
6548
  }
6072
6549
  const mod = nodeSqlite();
@@ -6177,9 +6654,9 @@ function resolveProfileDir(localStateJson, profile) {
6177
6654
  if (localStateJson) {
6178
6655
  try {
6179
6656
  const raw = JSON.parse(localStateJson);
6180
- const p2 = isObject11(raw) && isObject11(raw.profile) ? raw.profile : null;
6181
- if (p2 && isObject11(p2.info_cache))
6182
- cache = p2.info_cache;
6657
+ const p = isObject11(raw) && isObject11(raw.profile) ? raw.profile : null;
6658
+ if (p && isObject11(p.info_cache))
6659
+ cache = p.info_cache;
6183
6660
  } catch {}
6184
6661
  }
6185
6662
  if (Object.hasOwn(cache, profile))
@@ -6370,11 +6847,11 @@ function findFile(root, name) {
6370
6847
  continue;
6371
6848
  }
6372
6849
  for (const e of entries) {
6373
- const p2 = join11(dir, e.name);
6850
+ const p = join11(dir, e.name);
6374
6851
  if (e.isDirectory())
6375
- stack.push(p2);
6852
+ stack.push(p);
6376
6853
  else if (e.name === name)
6377
- return p2;
6854
+ return p;
6378
6855
  }
6379
6856
  }
6380
6857
  return null;
@@ -6385,10 +6862,10 @@ function findCookieDb(home, source) {
6385
6862
  if (!existsSync12(root))
6386
6863
  throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
6387
6864
  if (!b.profiled) {
6388
- const db2 = findFile(root, "Cookies");
6389
- if (!db2)
6865
+ const db = findFile(root, "Cookies");
6866
+ if (!db)
6390
6867
  throw new Error(`no cookie store under ${root}`);
6391
- return db2;
6868
+ return db;
6392
6869
  }
6393
6870
  const localStatePath = join11(root, "Local State");
6394
6871
  const localState = existsSync12(localStatePath) ? readFileSync10(localStatePath, "utf8") : null;
@@ -6398,7 +6875,7 @@ function findCookieDb(home, source) {
6398
6875
  throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
6399
6876
  if (!dir)
6400
6877
  throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
6401
- const db = cookieDbCandidates(join11(root, dir)).find((p2) => existsSync12(p2));
6878
+ const db = cookieDbCandidates(join11(root, dir)).find((p) => existsSync12(p));
6402
6879
  if (!db)
6403
6880
  throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
6404
6881
  return db;
@@ -6429,8 +6906,8 @@ var curlUsage = (org, cookie) => {
6429
6906
  return { status: 0, body: "" };
6430
6907
  }
6431
6908
  };
6432
- function fetchSeatUsage(jar, org, now, run2) {
6433
- const { status, body } = run2(org, cookieHeader(jar, org));
6909
+ function fetchSeatUsage(jar, org, now, run) {
6910
+ const { status, body } = run(org, cookieHeader(jar, org));
6434
6911
  if (status !== 200)
6435
6912
  return { usage: null, status };
6436
6913
  try {
@@ -6491,24 +6968,24 @@ function uninstall(home, source, log) {
6491
6968
  log(existsSync12(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
6492
6969
  return 0;
6493
6970
  }
6494
- var seatSync = async (o2) => {
6495
- const log = o2.log ?? console.log;
6496
- const now = (o2.now ?? Date.now)();
6497
- const parsed = parseSeatSyncArgs(o2.argv ?? process.argv.slice(3));
6971
+ var seatSync = async (o) => {
6972
+ const log = o.log ?? console.log;
6973
+ const now = (o.now ?? Date.now)();
6974
+ const parsed = parseSeatSyncArgs(o.argv ?? process.argv.slice(3));
6498
6975
  if ("error" in parsed) {
6499
6976
  log(`seat-sync: ${parsed.error}`);
6500
6977
  return 1;
6501
6978
  }
6502
6979
  const { source } = parsed;
6503
6980
  if (source.uninstall)
6504
- return uninstall(o2.home, source, log);
6981
+ return uninstall(o.home, source, log);
6505
6982
  if (source.install)
6506
- return install(o2.home, [process.execPath, process.argv[1] ?? ""], source, log);
6507
- if (platform() !== "darwin" && !o2.readCookieRows) {
6983
+ return install(o.home, [process.execPath, process.argv[1] ?? ""], source, log);
6984
+ if (platform2() !== "darwin" && !o.readCookieRows) {
6508
6985
  log("seat-sync is macOS-only for now.");
6509
6986
  return 2;
6510
6987
  }
6511
- const saved = meterSource(o2.home);
6988
+ const saved = meterSource(o.home);
6512
6989
  const proxyUrl = saved?.proxyUrl;
6513
6990
  const token = source.token ?? saved?.token;
6514
6991
  if (!proxyUrl || !token) {
@@ -6517,13 +6994,13 @@ var seatSync = async (o2) => {
6517
6994
  }
6518
6995
  let rows;
6519
6996
  try {
6520
- rows = o2.readCookieRows ? o2.readCookieRows(o2.home, source) : await readCookieRowsBun(o2.home, source);
6997
+ rows = o.readCookieRows ? o.readCookieRows(o.home, source) : await readCookieRowsBun(o.home, source);
6521
6998
  } catch (e) {
6522
6999
  log(`seat-sync: ${e instanceof Error ? e.message : String(e)}`);
6523
7000
  return 1;
6524
7001
  }
6525
7002
  const service = BROWSERS[source.browser].keychainService;
6526
- const pw = (o2.safeStorageKey ?? keychainKey)(service);
7003
+ const pw = (o.safeStorageKey ?? keychainKey)(service);
6527
7004
  if (!pw) {
6528
7005
  log(`seat-sync: could not read the "${service}" key from your Keychain.`);
6529
7006
  return 1;
@@ -6543,16 +7020,16 @@ var seatSync = async (o2) => {
6543
7020
  log("seat-sync: no active org in the session cookie (pass --org <uuid>).");
6544
7021
  return 1;
6545
7022
  }
6546
- const { usage, status } = fetchSeatUsage(jar, org, now, o2.runUsage ?? curlUsage);
7023
+ const { usage, status } = fetchSeatUsage(jar, org, now, o.runUsage ?? curlUsage);
6547
7024
  if (!usage) {
6548
7025
  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}.`;
6549
7026
  log(`seat-sync: usage read failed — ${why}`);
6550
7027
  return 2;
6551
7028
  }
6552
7029
  try {
6553
- writeSeatState(o2.home, { ...readSeatState(o2.home), window: usageAsWindow(usage) });
7030
+ writeSeatState(o.home, { ...readSeatState(o.home), window: usageAsWindow(usage) });
6554
7031
  } catch {}
6555
- const posted = await postSeat(proxyUrl, token, seatSyncBody(usage), o2.fetchFn ?? fetch);
7032
+ const posted = await postSeat(proxyUrl, token, seatSyncBody(usage), o.fetchFn ?? fetch);
6556
7033
  const wk = usage.sevenDay ? `wk ${Math.round(usage.sevenDay.usedPct)}%` : "wk —";
6557
7034
  const models = Object.keys(usage.models).length;
6558
7035
  const credits = usage.usedCreditsUsd !== undefined ? ` · $${usage.usedCreditsUsd.toFixed(2)}` : "";
@@ -6604,14 +7081,14 @@ function sessionContext(a) {
6604
7081
  return lines.join(`
6605
7082
  `);
6606
7083
  }
6607
- var sessionEvent = async (o2) => {
6608
- const log = o2.log ?? console.log;
6609
- const now = (o2.now ?? Date.now)();
6610
- const raw = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
7084
+ var sessionEvent = async (o) => {
7085
+ const log = o.log ?? console.log;
7086
+ const now = (o.now ?? Date.now)();
7087
+ const raw = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
6611
7088
  const payload = parseSessionPayload(raw);
6612
7089
  if (payload.session_id) {
6613
7090
  try {
6614
- writeSession(o2.home, {
7091
+ writeSession(o.home, {
6615
7092
  sessionId: payload.session_id,
6616
7093
  startedAt: now,
6617
7094
  ...payload.source ? { source: payload.source } : {}
@@ -6621,13 +7098,13 @@ var sessionEvent = async (o2) => {
6621
7098
  let email;
6622
7099
  let org;
6623
7100
  try {
6624
- const cfg = readConfig(o2.home);
7101
+ const cfg = readConfig(o.home);
6625
7102
  email = cfg.email;
6626
7103
  org = cfg.org?.slug;
6627
7104
  } catch {}
6628
7105
  let owed = [];
6629
7106
  try {
6630
- owed = readOwed(o2.home, now);
7107
+ owed = readOwed(o.home, now);
6631
7108
  } catch {
6632
7109
  owed = [];
6633
7110
  }
@@ -6672,13 +7149,13 @@ function parseHookInput(raw) {
6672
7149
  }
6673
7150
  if (typeof parsed !== "object" || parsed === null)
6674
7151
  return null;
6675
- const p2 = parsed;
6676
- const toolName = typeof p2.tool_name === "string" ? p2.tool_name : "";
6677
- const input = typeof p2.tool_input === "object" && p2.tool_input !== null ? p2.tool_input : {};
7152
+ const p = parsed;
7153
+ const toolName = typeof p.tool_name === "string" ? p.tool_name : "";
7154
+ const input = typeof p.tool_input === "object" && p.tool_input !== null ? p.tool_input : {};
6678
7155
  const filePath = typeof input.file_path === "string" ? input.file_path : "";
6679
7156
  if (!filePath)
6680
7157
  return null;
6681
- const cwd = typeof p2.cwd === "string" ? p2.cwd : "";
7158
+ const cwd = typeof p.cwd === "string" ? p.cwd : "";
6682
7159
  return { toolName, filePath, cwd };
6683
7160
  }
6684
7161
  function projectKey(filePath, cwd) {
@@ -6698,16 +7175,16 @@ function readFileOrNull(path) {
6698
7175
  return null;
6699
7176
  }
6700
7177
  }
6701
- var specEvent = async (o2) => {
6702
- const log = o2.log ?? console.log;
6703
- const raw = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS3);
7178
+ var specEvent = async (o) => {
7179
+ const log = o.log ?? console.log;
7180
+ const raw = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS3);
6704
7181
  const input = raw ? parseHookInput(raw) : null;
6705
7182
  if (!input || !isSpecPath(input.filePath))
6706
7183
  return 0;
6707
- const src = meterSource(o2.home);
7184
+ const src = meterSource(o.home);
6708
7185
  if (!src)
6709
7186
  return 0;
6710
- const body = (o2.readFile ?? readFileOrNull)(input.filePath);
7187
+ const body = (o.readFile ?? readFileOrNull)(input.filePath);
6711
7188
  if (!body || !looksLikeSpec(body))
6712
7189
  return 0;
6713
7190
  const project = projectKey(input.filePath, input.cwd);
@@ -6717,7 +7194,7 @@ var specEvent = async (o2) => {
6717
7194
  repoHint: input.cwd ? basename2(input.cwd) : undefined
6718
7195
  };
6719
7196
  try {
6720
- const res = await (o2.fetchFn ?? fetch)(`${src.proxyUrl}/proxy/estimate`, {
7197
+ const res = await (o.fetchFn ?? fetch)(`${src.proxyUrl}/proxy/estimate`, {
6721
7198
  method: "POST",
6722
7199
  headers: { authorization: `Bearer ${src.token}`, "content-type": "application/json" },
6723
7200
  body: JSON.stringify({ spec, project }),
@@ -6732,15 +7209,15 @@ var specEvent = async (o2) => {
6732
7209
  log(`sleds: ${project} sized at $${b.lowUsd.toFixed(2)}–${b.highUsd.toFixed(2)} · ${min(b.lowMinutes)}–${min(b.highMinutes)} min`);
6733
7210
  if (typeof b.lowMinutes === "number" && typeof b.highMinutes === "number") {
6734
7211
  try {
6735
- writeSeatState(o2.home, {
6736
- ...readSeatState(o2.home),
7212
+ writeSeatState(o.home, {
7213
+ ...readSeatState(o.home),
6737
7214
  estimate: {
6738
7215
  project,
6739
7216
  lowUsd: b.lowUsd,
6740
7217
  highUsd: b.highUsd,
6741
7218
  lowMin: b.lowMinutes,
6742
7219
  highMin: b.highMinutes,
6743
- at: (o2.now ?? Date.now)()
7220
+ at: (o.now ?? Date.now)()
6744
7221
  }
6745
7222
  });
6746
7223
  } catch {}
@@ -6823,16 +7300,16 @@ var formatStatus = (s) => {
6823
7300
  }
6824
7301
  return lines;
6825
7302
  };
6826
- var status = async (o2) => {
6827
- const log = o2.log ?? console.log;
6828
- const nowMs = (o2.now ?? Date.now)();
6829
- const cfg = readConfig(o2.home);
6830
- const settings = existsSync13(settingsPath(o2.home)) ? readSettings(o2.home) : {};
7303
+ var status = async (o) => {
7304
+ const log = o.log ?? console.log;
7305
+ const nowMs = (o.now ?? Date.now)();
7306
+ const cfg = readConfig(o.home);
7307
+ const settings = existsSync13(settingsPath(o.home)) ? readSettings(o.home) : {};
6831
7308
  const inSettings = readProxy(settings);
6832
7309
  const proxyUrl = inSettings?.url ?? cfg.proxyUrl;
6833
7310
  const token = inSettings?.token ?? cfg.token;
6834
7311
  if (!proxyUrl && !token && cfg.mode === undefined) {
6835
- log(`SLEDS is not set up (${configPath(o2.home)} and ${settingsPath(o2.home)}).`);
7312
+ log(`SLEDS is not set up (${configPath(o.home)} and ${settingsPath(o.home)}).`);
6836
7313
  log("Run `sleds init` first.");
6837
7314
  return 1;
6838
7315
  }
@@ -6840,8 +7317,8 @@ var status = async (o2) => {
6840
7317
  mode: cfg.mode ?? "off",
6841
7318
  pointed: inSettings !== null,
6842
7319
  nowMs,
6843
- settingsFile: settingsPath(o2.home),
6844
- configFile: configPath(o2.home)
7320
+ settingsFile: settingsPath(o.home),
7321
+ configFile: configPath(o.home)
6845
7322
  };
6846
7323
  if (proxyUrl)
6847
7324
  info.proxyUrl = proxyUrl;
@@ -6861,8 +7338,8 @@ var status = async (o2) => {
6861
7338
  };
6862
7339
  if (cfg.reason)
6863
7340
  info.reason = cfg.reason;
6864
- if (o2.tz)
6865
- info.tz = o2.tz;
7341
+ if (o.tz)
7342
+ info.tz = o.tz;
6866
7343
  const since = cfg.since ? Date.parse(cfg.since) : Number.NaN;
6867
7344
  if (!Number.isNaN(since))
6868
7345
  info.sinceMs = since;
@@ -6871,8 +7348,8 @@ var status = async (o2) => {
6871
7348
  info.untilResetMs = until;
6872
7349
  if (proxyUrl) {
6873
7350
  const [health, proxy] = await Promise.all([
6874
- checkHealth2(proxyUrl, o2.fetchFn, o2.timeoutMs),
6875
- fetchProxyStatus(proxyUrl, o2.fetchFn, o2.timeoutMs)
7351
+ checkHealth2(proxyUrl, o.fetchFn, o.timeoutMs),
7352
+ fetchProxyStatus(proxyUrl, o.fetchFn, o.timeoutMs)
6876
7353
  ]);
6877
7354
  info.health = health;
6878
7355
  info.proxy = proxy;
@@ -6885,62 +7362,62 @@ var status = async (o2) => {
6885
7362
  // src/commands/statusline.ts
6886
7363
  init_config();
6887
7364
  init_meter();
6888
- var statusline = async (o2) => {
6889
- const log = o2.log ?? console.log;
6890
- const now = (o2.now ?? Date.now)();
6891
- const input = o2.stdin ?? await readStdin(STDIN_TIMEOUT_MS);
7365
+ var statusline = async (o) => {
7366
+ const log = o.log ?? console.log;
7367
+ const now = (o.now ?? Date.now)();
7368
+ const input = o.stdin ?? await readStdin(STDIN_TIMEOUT_MS);
6892
7369
  const window = seatWindowFromStdin(input, now);
6893
7370
  const session = seatSessionFromStdin(input, now);
6894
7371
  let posts = [];
6895
7372
  let events = [];
6896
7373
  try {
6897
7374
  if (window)
6898
- ({ posts, events } = rememberSeat(o2, window, session, now, input));
7375
+ ({ posts, events } = rememberSeat(o, window, session, now, input));
6899
7376
  else {
6900
- const st = readSeatState(o2.home);
6901
- const credits = creditsEvent(o2, st, input, now);
7377
+ const st = readSeatState(o.home);
7378
+ const credits = creditsEvent(o, st, input, now);
6902
7379
  if (session || credits.scan) {
6903
7380
  const next = {
6904
7381
  ...st,
6905
7382
  ...session ? { session } : {},
6906
7383
  ...credits.scan ? { transcriptScan: credits.scan } : {}
6907
7384
  };
6908
- writeSeatState(o2.home, credits.event ? markWarned(next, [credits.event]) : next);
7385
+ writeSeatState(o.home, credits.event ? markWarned(next, [credits.event]) : next);
6909
7386
  }
6910
7387
  if (credits.event) {
6911
7388
  events = [credits.event];
6912
- posts = announce(o2, events);
7389
+ posts = announce(o, events);
6913
7390
  }
6914
7391
  }
6915
7392
  } catch {}
6916
7393
  const seatFragment = formatSeatFragment(window, now);
6917
7394
  let line;
6918
7395
  try {
6919
- const meter2 = await loadMeter({
6920
- home: o2.home,
7396
+ const meter = await loadMeter({
7397
+ home: o.home,
6921
7398
  now,
6922
- fetchFn: o2.fetchFn,
6923
- timeoutMs: o2.timeoutMs
7399
+ fetchFn: o.fetchFn,
7400
+ timeoutMs: o.timeoutMs
6924
7401
  });
6925
- line = formatMeterLine(meter2, now, seatFragment);
7402
+ line = formatMeterLine(meter, now, seatFragment);
6926
7403
  } catch {
6927
7404
  line = formatMeterLine(null, now, seatFragment);
6928
7405
  }
6929
7406
  log(line);
6930
- posts.push(speakToCockpit(o2, window, events, now));
7407
+ posts.push(speakToCockpit(o, window, events, now));
6931
7408
  await Promise.allSettled(posts);
6932
7409
  return 0;
6933
7410
  };
6934
- var speakToCockpit = async (o2, window, events, now) => {
6935
- const cockpit = detectCockpit(o2.env ?? process.env);
7411
+ var speakToCockpit = async (o, window, events, now) => {
7412
+ const cockpit = detectCockpit(o.env ?? process.env);
6936
7413
  if (!cockpit)
6937
7414
  return;
6938
7415
  try {
6939
- const st = readSeatState(o2.home);
7416
+ const st = readSeatState(o.home);
6940
7417
  let feed;
6941
- const src = meterSource(o2.home);
7418
+ const src = meterSource(o.home);
6942
7419
  if (src && now - (st.cockpit?.feed?.at ?? 0) >= FEED_MIN_INTERVAL_MS) {
6943
- const got = await fetchFeed({ ...src, email: readConfig(o2.home).email, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs }, now);
7420
+ const got = await fetchFeed({ ...src, email: readConfig(o.home).email, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs }, now);
6944
7421
  if (got)
6945
7422
  feed = got;
6946
7423
  }
@@ -6951,32 +7428,32 @@ var speakToCockpit = async (o2, window, events, now) => {
6951
7428
  const changed = r.argv.length > 0 || feed !== undefined;
6952
7429
  if (!changed)
6953
7430
  return;
6954
- writeSeatState(o2.home, { ...readSeatState(o2.home), cockpit: r.state });
7431
+ writeSeatState(o.home, { ...readSeatState(o.home), cockpit: r.state });
6955
7432
  if (r.argv.length)
6956
- await runCockpit(cockpit, r.argv, { spawn: o2.spawn });
7433
+ await runCockpit(cockpit, r.argv, { spawn: o.spawn });
6957
7434
  } catch {}
6958
7435
  };
6959
- var rememberSeat = (o2, window, session, now, input) => {
6960
- const prior = readSeatState(o2.home);
7436
+ var rememberSeat = (o, window, session, now, input) => {
7437
+ const prior = readSeatState(o.home);
6961
7438
  let state = { ...prior, window, ...session ? { session } : {} };
6962
- const src = meterSource(o2.home);
7439
+ const src = meterSource(o.home);
6963
7440
  const posts = [];
6964
7441
  if (src && shouldPostSeat(prior, window, now)) {
6965
7442
  state = markPosted(state, window, now);
6966
- posts.push(postSeatWindow({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, window));
7443
+ posts.push(postSeatWindow({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, window));
6967
7444
  }
6968
7445
  const events = seatWallEvents(prior, window);
6969
- const credits = creditsEvent(o2, prior, input, now);
7446
+ const credits = creditsEvent(o, prior, input, now);
6970
7447
  if (credits.scan)
6971
7448
  state = { ...state, transcriptScan: credits.scan };
6972
7449
  if (credits.event)
6973
7450
  events.push(credits.event);
6974
7451
  state = markWarned(state, events);
6975
- posts.push(...announce(o2, events));
6976
- writeSeatState(o2.home, state);
7452
+ posts.push(...announce(o, events));
7453
+ writeSeatState(o.home, state);
6977
7454
  return { posts, events };
6978
7455
  };
6979
- var creditsEvent = (o2, prior, input, now) => {
7456
+ var creditsEvent = (o, prior, input, now) => {
6980
7457
  const path = transcriptPathFromStdin(input);
6981
7458
  if (!path)
6982
7459
  return { event: null, scan: undefined };
@@ -6986,13 +7463,13 @@ var creditsEvent = (o2, prior, input, now) => {
6986
7463
  return { event: null, scan: next };
6987
7464
  return { event: { bar: "credits", stage: "hit", usedPct: 100, resetsAt: now }, scan: next };
6988
7465
  };
6989
- var announce = (o2, events) => {
7466
+ var announce = (o, events) => {
6990
7467
  if (events.length === 0)
6991
7468
  return [];
6992
- const src = meterSource(o2.home);
7469
+ const src = meterSource(o.home);
6993
7470
  if (!src)
6994
7471
  return [];
6995
- return events.map((e) => postSeatWall({ ...src, fetchFn: o2.fetchFn, timeoutMs: o2.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
7472
+ return events.map((e) => postSeatWall({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
6996
7473
  bar: e.bar,
6997
7474
  stage: e.stage,
6998
7475
  usedPct: e.usedPct,
@@ -7002,6 +7479,128 @@ var announce = (o2, events) => {
7002
7479
  };
7003
7480
 
7004
7481
  // src/commands/stopEvent.ts
7482
+ init_meter();
7483
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "node:fs";
7484
+
7485
+ // src/transcript.ts
7486
+ var isObject12 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
7487
+ var n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
7488
+ function sumTranscript(text) {
7489
+ const seen = new Set;
7490
+ const turn = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
7491
+ let sub = 0;
7492
+ let messages = 0;
7493
+ const byModel = new Map;
7494
+ const agents = new Set;
7495
+ let anySidechain = false;
7496
+ for (const line of text.split(`
7497
+ `)) {
7498
+ const t = line.trim();
7499
+ if (!t)
7500
+ continue;
7501
+ let j;
7502
+ try {
7503
+ j = JSON.parse(t);
7504
+ } catch {
7505
+ continue;
7506
+ }
7507
+ if (!isObject12(j) || j.type !== "assistant" || !isObject12(j.message))
7508
+ continue;
7509
+ const m = j.message;
7510
+ const id = typeof m.id === "string" ? m.id : typeof j.uuid === "string" ? j.uuid : undefined;
7511
+ if (id) {
7512
+ if (seen.has(id))
7513
+ continue;
7514
+ seen.add(id);
7515
+ }
7516
+ const u = isObject12(m.usage) ? m.usage : undefined;
7517
+ if (!u)
7518
+ continue;
7519
+ const input = n(u.input_tokens);
7520
+ const output = n(u.output_tokens);
7521
+ const cacheRead = n(u.cache_read_input_tokens);
7522
+ const cacheWrite = n(u.cache_creation_input_tokens);
7523
+ messages++;
7524
+ const model = typeof m.model === "string" ? m.model : undefined;
7525
+ if (model)
7526
+ byModel.set(model, (byModel.get(model) ?? 0) + output);
7527
+ if (j.isSidechain === true) {
7528
+ anySidechain = true;
7529
+ sub += input + output + cacheRead + cacheWrite;
7530
+ if (typeof j.agentId === "string")
7531
+ agents.add(j.agentId);
7532
+ } else {
7533
+ turn.input += input;
7534
+ turn.output += output;
7535
+ turn.cacheRead += cacheRead;
7536
+ turn.cacheWrite += cacheWrite;
7537
+ }
7538
+ }
7539
+ let model;
7540
+ let best = -1;
7541
+ for (const [k, v] of byModel)
7542
+ if (v > best)
7543
+ [model, best] = [k, v];
7544
+ return {
7545
+ turnTokens: turn,
7546
+ subagentTokens: sub,
7547
+ messages,
7548
+ ...model ? { model } : {},
7549
+ subagentRuns: agents.size > 0 ? agents.size : anySidechain ? 1 : 0
7550
+ };
7551
+ }
7552
+ function sessionBody(sessionId, sum, a) {
7553
+ return {
7554
+ capturedAt: a.capturedAt,
7555
+ sessions: [
7556
+ {
7557
+ sessionId,
7558
+ ...a.title ? { title: a.title.slice(0, 200) } : {},
7559
+ updatedAt: a.capturedAt,
7560
+ subagentTokens: sum.subagentTokens,
7561
+ taskCount: sum.subagentRuns,
7562
+ turnTokens: sum.turnTokens,
7563
+ tasks: []
7564
+ }
7565
+ ]
7566
+ };
7567
+ }
7568
+
7569
+ // src/commands/stopEvent.ts
7570
+ var SESSION_POST_TIMEOUT_MS = 2500;
7571
+ async function postTranscript(o, payload, now) {
7572
+ const sessionId = payload.session_id?.trim();
7573
+ const path = payload.transcript_path?.trim();
7574
+ if (!sessionId || !path)
7575
+ return false;
7576
+ const src = meterSource(o.home);
7577
+ if (!src)
7578
+ return false;
7579
+ const read = o.readFile ?? ((p) => existsSync14(p) ? readFileSync12(p, "utf8") : null);
7580
+ let text;
7581
+ try {
7582
+ text = read(path);
7583
+ } catch {
7584
+ return false;
7585
+ }
7586
+ if (!text)
7587
+ return false;
7588
+ const sum = sumTranscript(text);
7589
+ if (sum.messages === 0)
7590
+ return false;
7591
+ const fetchFn = o.fetchFn ?? fetch;
7592
+ try {
7593
+ const res = await fetchFn(`${src.proxyUrl.replace(/\/+$/, "")}/proxy/sessions`, {
7594
+ method: "POST",
7595
+ headers: { authorization: `Bearer ${src.token}`, "content-type": "application/json" },
7596
+ body: JSON.stringify(sessionBody(sessionId, sum, { capturedAt: now })),
7597
+ signal: AbortSignal.timeout(SESSION_POST_TIMEOUT_MS)
7598
+ });
7599
+ return res.ok;
7600
+ } catch {
7601
+ return false;
7602
+ }
7603
+ }
7005
7604
  var parseStopPayload = (raw) => {
7006
7605
  try {
7007
7606
  const j = JSON.parse(raw);
@@ -7026,14 +7625,15 @@ function decideStop(a) {
7026
7625
  rows: markAsked(a.rows, due.map((r) => r.key), a.now)
7027
7626
  };
7028
7627
  }
7029
- var stopEvent = async (o2) => {
7030
- const log = o2.log ?? console.log;
7031
- const now = (o2.now ?? Date.now)();
7032
- const raw = o2.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
7628
+ var stopEvent = async (o) => {
7629
+ const log = o.log ?? console.log;
7630
+ const now = (o.now ?? Date.now)();
7631
+ const raw = o.stdin ?? await readStdin(HOOK_STDIN_TIMEOUT_MS);
7033
7632
  const payload = parseStopPayload(raw);
7633
+ await postTranscript(o, payload, now);
7034
7634
  let rows;
7035
7635
  try {
7036
- rows = readOwed(o2.home, now);
7636
+ rows = readOwed(o.home, now);
7037
7637
  } catch {
7038
7638
  return 0;
7039
7639
  }
@@ -7046,24 +7646,24 @@ var stopEvent = async (o2) => {
7046
7646
  if (!d.block)
7047
7647
  return 0;
7048
7648
  try {
7049
- writeOwed(o2.home, d.rows);
7649
+ writeOwed(o.home, d.rows);
7050
7650
  } catch {}
7051
7651
  log(JSON.stringify({ decision: "block", reason: d.reason }));
7052
7652
  return 0;
7053
7653
  };
7054
7654
 
7055
7655
  // src/commands/uninstall.ts
7056
- import { spawnSync as spawnSync4 } from "node:child_process";
7057
- import { existsSync as existsSync15, rmSync as rmSync5 } from "node:fs";
7656
+ import { spawnSync as spawnSync5 } from "node:child_process";
7657
+ import { existsSync as existsSync16, rmSync as rmSync5 } from "node:fs";
7058
7658
 
7059
7659
  // src/codex.ts
7060
7660
  init_config();
7061
- import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "node:fs";
7661
+ import { existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "node:fs";
7062
7662
  import { dirname as dirname6, join as join12 } from "node:path";
7063
7663
  var PROVIDER_ID2 = "sleds";
7064
7664
  var codexDir2 = (home) => join12(home, ".codex");
7065
7665
  var codexConfigPath2 = (home) => join12(codexDir2(home), "config.toml");
7066
- function removeCodexProvider2(toml, o2 = {}) {
7666
+ function removeCodexProvider2(toml, o = {}) {
7067
7667
  const lines = toml.split(`
7068
7668
  `);
7069
7669
  const out = [];
@@ -7076,7 +7676,7 @@ function removeCodexProvider2(toml, o2 = {}) {
7076
7676
  }
7077
7677
  let text = out.join(`
7078
7678
  `);
7079
- if (!o2.keepPrior) {
7679
+ if (!o.keepPrior) {
7080
7680
  const prior = text.match(/^# sleds-prior-model-provider = "([^"]*)"\s*\n/m);
7081
7681
  if (prior) {
7082
7682
  text = text.replace(prior[0], "");
@@ -7091,9 +7691,9 @@ function removeCodexProvider2(toml, o2 = {}) {
7091
7691
  }
7092
7692
  function uninstallCodex(home) {
7093
7693
  const path = codexConfigPath2(home);
7094
- if (!existsSync14(path))
7694
+ if (!existsSync15(path))
7095
7695
  return null;
7096
- const prior = readFileSync12(path, "utf8");
7696
+ const prior = readFileSync13(path, "utf8");
7097
7697
  const next = removeCodexProvider2(prior);
7098
7698
  if (next === prior)
7099
7699
  return null;
@@ -7106,74 +7706,74 @@ init_config();
7106
7706
  init_meter();
7107
7707
  init_settings();
7108
7708
  var defaultUnload = (plist) => {
7109
- spawnSync4("launchctl", ["unload", plist], { stdio: "ignore" });
7709
+ spawnSync5("launchctl", ["unload", plist], { stdio: "ignore" });
7110
7710
  };
7111
- var uninstall2 = (o2) => {
7112
- const codexLine = uninstallCodex(o2.home);
7711
+ var uninstall2 = (o) => {
7712
+ const codexLine = uninstallCodex(o.home);
7113
7713
  if (codexLine)
7114
- (o2.log ?? console.log)(` ${codexLine}`);
7115
- const log = o2.log ?? console.log;
7714
+ (o.log ?? console.log)(` ${codexLine}`);
7715
+ const log = o.log ?? console.log;
7116
7716
  const removed = [];
7117
- if (existsSync15(settingsPath(o2.home))) {
7118
- let settings = readSettings(o2.home);
7717
+ if (existsSync16(settingsPath(o.home))) {
7718
+ let settings = readSettings(o.home);
7119
7719
  let changed = false;
7120
7720
  if (hasEitherKey(settings)) {
7121
7721
  settings = removeProxy(settings);
7122
7722
  changed = true;
7123
- removed.push(`env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN from ${settingsPath(o2.home)}`);
7723
+ removed.push(`env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN from ${settingsPath(o.home)}`);
7124
7724
  }
7125
7725
  if (isSledsStatusLine(settings[STATUS_LINE_KEY])) {
7126
- const cfg = readConfig(o2.home);
7726
+ const cfg = readConfig(o.home);
7127
7727
  settings = removeStatusLine(settings, cfg.priorStatusLine);
7128
7728
  changed = true;
7129
- removed.push(cfg.priorStatusLine === undefined ? `statusLine from ${settingsPath(o2.home)}` : `statusLine from ${settingsPath(o2.home)} (previous one restored)`);
7729
+ removed.push(cfg.priorStatusLine === undefined ? `statusLine from ${settingsPath(o.home)}` : `statusLine from ${settingsPath(o.home)} (previous one restored)`);
7130
7730
  if (cfg.priorStatusLine !== undefined) {
7131
7731
  const { priorStatusLine: _restored, ...rest } = cfg;
7132
- writeConfig(o2.home, rest);
7732
+ writeConfig(o.home, rest);
7133
7733
  }
7134
7734
  }
7135
7735
  if (hasSeatHook(settings)) {
7136
7736
  settings = removeSeatHook(settings);
7137
7737
  changed = true;
7138
- removed.push(`hooks.Notification seat-event entry from ${settingsPath(o2.home)}`);
7738
+ removed.push(`hooks.Notification seat-event entry from ${settingsPath(o.home)}`);
7139
7739
  }
7140
7740
  if (hasPromptHook(settings)) {
7141
7741
  settings = removePromptHook(settings);
7142
7742
  changed = true;
7143
- removed.push(`hooks.UserPromptSubmit prompt-event entry from ${settingsPath(o2.home)}`);
7743
+ removed.push(`hooks.UserPromptSubmit prompt-event entry from ${settingsPath(o.home)}`);
7144
7744
  }
7145
7745
  if (hasSpecHook(settings)) {
7146
7746
  settings = removeSpecHook(settings);
7147
7747
  changed = true;
7148
- removed.push(`hooks.PostToolUse spec-event entry from ${settingsPath(o2.home)}`);
7748
+ removed.push(`hooks.PostToolUse spec-event entry from ${settingsPath(o.home)}`);
7149
7749
  }
7150
7750
  if (hasSessionHook(settings)) {
7151
7751
  settings = removeSessionHook(settings);
7152
7752
  changed = true;
7153
- removed.push(`hooks.SessionStart session-event entry from ${settingsPath(o2.home)}`);
7753
+ removed.push(`hooks.SessionStart session-event entry from ${settingsPath(o.home)}`);
7154
7754
  }
7155
7755
  if (hasStopHook(settings)) {
7156
7756
  settings = removeStopHook(settings);
7157
7757
  changed = true;
7158
- removed.push(`hooks.Stop stop-event entry from ${settingsPath(o2.home)}`);
7758
+ removed.push(`hooks.Stop stop-event entry from ${settingsPath(o.home)}`);
7159
7759
  }
7160
7760
  if (changed)
7161
- writeSettings(o2.home, settings);
7761
+ writeSettings(o.home, settings);
7162
7762
  }
7163
- for (const file of [meterCachePath(o2.home), seatStatePath(o2.home)]) {
7164
- if (existsSync15(file)) {
7763
+ for (const file of [meterCachePath(o.home), seatStatePath(o.home)]) {
7764
+ if (existsSync16(file)) {
7165
7765
  rmSync5(file, { force: true });
7166
7766
  removed.push(file);
7167
7767
  }
7168
7768
  }
7169
- const skill = skillDir(o2.home);
7170
- if (existsSync15(skill)) {
7769
+ const skill = skillDir(o.home);
7770
+ if (existsSync16(skill)) {
7171
7771
  rmSync5(skill, { recursive: true, force: true });
7172
7772
  removed.push(skill);
7173
7773
  }
7174
- const plist = musherPlistPath(o2.home);
7175
- if (existsSync15(plist)) {
7176
- (o2.unloadAgent ?? defaultUnload)(plist);
7774
+ const plist = musherPlistPath(o.home);
7775
+ if (existsSync16(plist)) {
7776
+ (o.unloadAgent ?? defaultUnload)(plist);
7177
7777
  rmSync5(plist, { force: true });
7178
7778
  removed.push(`launchd agent ${musherLabel()}`);
7179
7779
  }
@@ -7184,7 +7784,7 @@ var uninstall2 = (o2) => {
7184
7784
  for (const r of removed)
7185
7785
  log(` ${r}`);
7186
7786
  }
7187
- log(`Kept ${tokenPath(o2.home)} (delete it yourself if you want the token gone).`);
7787
+ log(`Kept ${tokenPath(o.home)} (delete it yourself if you want the token gone).`);
7188
7788
  return 0;
7189
7789
  };
7190
7790
  var hasEitherKey = (settings) => {
@@ -7203,17 +7803,17 @@ var jobIdFromTarget = (target) => {
7203
7803
  return m[1];
7204
7804
  return /^[A-Za-z0-9_-]{3,64}$/.test(t) ? t : null;
7205
7805
  };
7206
- var verify = async (o2) => {
7207
- const log = o2.log ?? console.log;
7208
- const error = o2.error ?? console.error;
7209
- const jobId = o2.target ? jobIdFromTarget(o2.target) : null;
7806
+ var verify = async (o) => {
7807
+ const log = o.log ?? console.log;
7808
+ const error = o.error ?? console.error;
7809
+ const jobId = o.target ? jobIdFromTarget(o.target) : null;
7210
7810
  if (!jobId) {
7211
7811
  error("usage: sleds verify <jobId> (or paste the SLEDS-Attest line)");
7212
7812
  return 2;
7213
7813
  }
7214
- const client = o2.client ?? (() => {
7215
- const src = meterSource(o2.home);
7216
- return src ? musherClient({ ...src, fetchFn: o2.fetchFn }) : null;
7814
+ const client = o.client ?? (() => {
7815
+ const src = meterSource(o.home);
7816
+ return src ? musherClient({ ...src, fetchFn: o.fetchFn }) : null;
7217
7817
  })();
7218
7818
  if (!client) {
7219
7819
  error("No seat on this machine. Run `sleds init` first.");
@@ -7244,17 +7844,17 @@ var verify = async (o2) => {
7244
7844
 
7245
7845
  // src/config.ts
7246
7846
  init_settings();
7247
- import { chmodSync as chmodSync2, existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync13 } from "node:fs";
7847
+ import { chmodSync as chmodSync2, existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync14, renameSync as renameSync6, writeFileSync as writeFileSync13 } from "node:fs";
7248
7848
  import { join as join13, resolve as resolve4 } from "node:path";
7249
7849
  var configDir2 = (home, env = process.env) => {
7250
- const o2 = env.SLEDS_HOME?.trim();
7251
- return o2 ? resolve4(home, o2) : join13(home, ".config", "sleds");
7850
+ const o = env.SLEDS_HOME?.trim();
7851
+ return o ? resolve4(home, o) : join13(home, ".config", "sleds");
7252
7852
  };
7253
7853
  var configPath2 = (home) => join13(configDir2(home), "config.json");
7254
- var isObject12 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
7854
+ var isObject13 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
7255
7855
  var optString2 = (v) => typeof v === "string" ? v : undefined;
7256
7856
  var parsePriorEnv2 = (v) => {
7257
- if (!isObject12(v))
7857
+ if (!isObject13(v))
7258
7858
  return;
7259
7859
  const out = {};
7260
7860
  const url = optString2(v[BASE_URL_KEY]);
@@ -7269,7 +7869,7 @@ var parsePriorEnv2 = (v) => {
7269
7869
  };
7270
7870
  var parseSchedule2 = (v) => v === "launchd" || v === "systemd" || v === "manual" ? v : undefined;
7271
7871
  var parseConfig2 = (raw) => {
7272
- if (!isObject12(raw))
7872
+ if (!isObject13(raw))
7273
7873
  return {};
7274
7874
  const cfg = {};
7275
7875
  const token = optString2(raw.token);
@@ -7307,13 +7907,13 @@ var parseConfig2 = (raw) => {
7307
7907
  const org = parseOrgInfo2(raw.org);
7308
7908
  if (org)
7309
7909
  cfg.org = org;
7310
- const musher2 = parseMusher2(raw.musher);
7311
- if (musher2)
7312
- cfg.musher = musher2;
7910
+ const musher = parseMusher2(raw.musher);
7911
+ if (musher)
7912
+ cfg.musher = musher;
7313
7913
  return cfg;
7314
7914
  };
7315
7915
  var parseMusher2 = (raw) => {
7316
- if (!isObject12(raw))
7916
+ if (!isObject13(raw))
7317
7917
  return;
7318
7918
  const out = {};
7319
7919
  const pm = optString2(raw.permissionMode);
@@ -7329,19 +7929,19 @@ var parseMusher2 = (raw) => {
7329
7929
  return out;
7330
7930
  };
7331
7931
  var parseOrgInfo2 = (raw) => {
7332
- if (!isObject12(raw) || !isObject12(raw.capabilities))
7932
+ if (!isObject13(raw) || !isObject13(raw.capabilities))
7333
7933
  return;
7334
7934
  const name = optString2(raw.name);
7335
- const slug2 = optString2(raw.slug);
7336
- const plan2 = raw.plan;
7935
+ const slug = optString2(raw.slug);
7936
+ const plan = raw.plan;
7337
7937
  const c = raw.capabilities;
7338
7938
  const src = c.upstreamSource;
7339
- if (name === undefined || slug2 === undefined || plan2 !== "team" && plan2 !== "enterprise" && plan2 !== "api" && plan2 !== "individual" || src !== "own" && src !== "shared" && src !== "none")
7939
+ if (name === undefined || slug === undefined || plan !== "team" && plan !== "enterprise" && plan !== "api" && plan !== "individual" || src !== "own" && src !== "shared" && src !== "none")
7340
7940
  return;
7341
7941
  return {
7342
7942
  name,
7343
- slug: slug2,
7344
- plan: plan2,
7943
+ slug,
7944
+ plan,
7345
7945
  capabilities: {
7346
7946
  upstreamKey: c.upstreamKey === true,
7347
7947
  adminKey: c.adminKey === true,
@@ -7352,16 +7952,16 @@ var parseOrgInfo2 = (raw) => {
7352
7952
  };
7353
7953
  };
7354
7954
  var readConfig2 = (home) => {
7355
- const p2 = configPath2(home);
7356
- if (!existsSync16(p2))
7955
+ const p = configPath2(home);
7956
+ if (!existsSync17(p))
7357
7957
  return {};
7358
- return parseConfig2(JSON.parse(readFileSync13(p2, "utf8")));
7958
+ return parseConfig2(JSON.parse(readFileSync14(p, "utf8")));
7359
7959
  };
7360
7960
 
7361
7961
  // src/schedule.ts
7362
- import { spawnSync as spawnSync5 } from "node:child_process";
7962
+ import { spawnSync as spawnSync6 } from "node:child_process";
7363
7963
  var defaultExec = (cmd, args) => {
7364
- const r = spawnSync5(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
7964
+ const r = spawnSync6(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
7365
7965
  if (r.error)
7366
7966
  return { ok: false, stderr: r.error.message };
7367
7967
  return { ok: r.status === 0, stderr: r.stderr ?? "" };
@@ -7381,12 +7981,12 @@ var withSeatEnv2 = (command, home, env = process.env) => {
7381
7981
  return parts.join(" ");
7382
7982
  };
7383
7983
  var shellWord2 = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
7384
- var isObject13 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
7984
+ var isObject14 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
7385
7985
  var isSledsHookFor2 = (sub) => (entry) => {
7386
- if (!isObject13(entry) || !Array.isArray(entry.hooks))
7986
+ if (!isObject14(entry) || !Array.isArray(entry.hooks))
7387
7987
  return false;
7388
7988
  const re = new RegExp(`\\b${sub}\\b`);
7389
- return entry.hooks.some((h) => isObject13(h) && h.type === "command" && typeof h.command === "string" && re.test(h.command));
7989
+ return entry.hooks.some((h) => isObject14(h) && h.type === "command" && typeof h.command === "string" && re.test(h.command));
7390
7990
  };
7391
7991
  var isSledsSessionHook2 = isSledsHookFor2("session-event");
7392
7992
  var isSledsStopHook2 = isSledsHookFor2("stop-event");
@@ -7395,13 +7995,15 @@ var isSledsStopHook2 = isSledsHookFor2("stop-event");
7395
7995
  var USAGE = `usage: sleds <command> [options]
7396
7996
 
7397
7997
  commands:
7398
- init Point Claude Code at a SLEDS proxy, install the /sled skill and the status line.
7399
- --token <sled_\u2026> seat token (else SLEDS_TOKEN, ~/.sleds-token, or prompt)
7998
+ init Sign in through the browser, make this machine a seat, install the /sled skill
7999
+ and the status line. No flags needed: it opens app.sleds.ai/connect and waits.
8000
+ --token <sled_\u2026> skip the browser: a seat token (else SLEDS_TOKEN, ~/.sleds-token)
7400
8001
  --url <proxy-url> proxy base URL (else SLEDS_PROXY_URL, default ${DEFAULT_PROXY_URL})
8002
+ --no-browser print the sign-in URL and code instead of opening a browser
7401
8003
  --no-probe skip the /healthz check of the proxy URL
7402
8004
  --seat-first keep Claude Code on your seat; the proxy only at a wall (default for seat plans)
7403
8005
  --proxy-always every request through the proxy on the platform key (default for API-plan orgs)
7404
- --non-interactive never prompt; exit 2 if no token is available
8006
+ --non-interactive never open a browser or prompt; exit 2 if no token is available
7405
8007
  --agent codex Codex CLI instead of Claude Code: writes ~/.codex/config.toml
7406
8008
  (provider "sleds" \u2192 the proxy, key from $SLEDS_TOKEN); no hooks/status line
7407
8009
  --signup no org? create one: (1) just you \u2014 your email + your own Anthropic
@@ -7490,8 +8092,8 @@ var stringFlag = (flags, name) => {
7490
8092
  var hookStdin = async () => {
7491
8093
  if (process.stdin.isTTY)
7492
8094
  return;
7493
- const { readStdin: readStdin3 } = await Promise.resolve().then(() => (init_seat(), exports_seat));
7494
- const text = await readStdin3(300);
8095
+ await Promise.resolve().then(() => init_seat());
8096
+ const text = await readStdin2(300);
7495
8097
  return text.trim() ? text : undefined;
7496
8098
  };
7497
8099
  var sledsArgv = () => {
@@ -7509,7 +8111,7 @@ var main = async (argv) => {
7509
8111
  const { cmd, flags } = parseArgs(argv);
7510
8112
  const home = homedir();
7511
8113
  const scheduler = {
7512
- platform: platform2(),
8114
+ platform: platform3(),
7513
8115
  home,
7514
8116
  sledsArgv: sledsArgv(),
7515
8117
  exec: defaultExec
@@ -7557,10 +8159,11 @@ var main = async (argv) => {
7557
8159
  team: typeof flags.team === "string" ? flags.team : undefined,
7558
8160
  email: typeof flags.email === "string" ? flags.email : undefined,
7559
8161
  noProbe: flags["no-probe"] === true,
8162
+ noBrowser: flags["no-browser"] === true,
7560
8163
  proxyAlways: flags["proxy-always"] === true,
7561
8164
  seatFirst: flags["seat-first"] === true,
7562
8165
  installMusher: () => {
7563
- if (platform2() !== "darwin")
8166
+ if (platform3() !== "darwin")
7564
8167
  return null;
7565
8168
  const lines = [];
7566
8169
  const code = musherInstall({
@@ -7643,7 +8246,7 @@ ${renderHelpText(argv[1])}`);
7643
8246
  case "version":
7644
8247
  case "--version":
7645
8248
  case "-v":
7646
- console.log("sleds 0.1.23");
8249
+ console.log("sleds 0.1.25");
7647
8250
  return 0;
7648
8251
  default:
7649
8252
  console.error(`unknown command: ${cmd}