@sleds/cli 0.1.36 → 0.1.38

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 +250 -16
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -6121,10 +6121,15 @@ var init_pack = __esm(() => {
6121
6121
  init_sentry();
6122
6122
  init_setup();
6123
6123
  });
6124
+ // ../../packages/shared/src/pack/prHeader.ts
6125
+ var PR_HEADER_OPEN = "<!-- sleds:header -->", PR_HEADER_CLOSE = "<!-- /sleds:header -->";
6126
+ var init_prHeader = () => {};
6127
+
6124
6128
  // ../../packages/shared/src/pack/index.ts
6125
6129
  var init_pack2 = __esm(() => {
6126
6130
  init_setup();
6127
6131
  init_pack();
6132
+ init_prHeader();
6128
6133
  });
6129
6134
 
6130
6135
  // ../../packages/shared/src/newrelic/newrelic.ts
@@ -6523,6 +6528,9 @@ function nodePrompt(o) {
6523
6528
  "",
6524
6529
  `- Work on branch \`${o.branch}\` from the latest \`origin/${base}\` (create it if this checkout is not on it). One branch, one PR.`,
6525
6530
  `- Open the PR against \`${base}\` with \`gh pr create${visual ? " --draft" : ""}\`${o.prTitle ? ` and title it exactly \`${o.prTitle}\` (the repo's CI checks it)` : ""}. Its body carries \`SLEDS-Node: ${claim.node}\` on the line under \`SLEDS-Source:\` when the pack names a source.`,
6531
+ ...claim.pack.includes(PR_HEADER_OPEN) ? [
6532
+ `- The PR body starts with the pack's PR header block (\`${PR_HEADER_OPEN}\` … \`${PR_HEADER_CLOSE}\`, in its part 5), above everything else and as given; when you edit the body later, keep it on top.`
6533
+ ] : [],
6526
6534
  "- Run the repo's own gate before you push. Never merge: a person (or the musher, when the attachment says so) does.",
6527
6535
  "- Stay inside this node's scope. Work another node needs goes in the PR description, not in this branch.",
6528
6536
  `- End every comment and reply you post on the PR with the line \`${ENGINE_MARK}\`, so the engine knows it is yours and not a reviewer's.`,
@@ -6847,6 +6855,7 @@ function eventPrompt(o) {
6847
6855
  }
6848
6856
  var DEFAULT_SEAT = "\x00default", MAX_SEAT_AT_ONCE = 16, HOLD_DEFAULTS, DRAFT_NODE = "draft", BRANCH_FIELDS, PR_TITLE_FIELDS, ENGINE_MCP_SERVER = "sleds", RUN_TOKEN_ENV = "SLEDS_RUN_TOKEN", NO_RUN_TOKEN = "no run token from SLEDS: this session acts as the seat", SANDBOX_DOMAIN, SANDBOX_DEFAULT_ALLOW, toolName = (name, engineMcp) => engineMcp ? `mcp__${ENGINE_MCP_SERVER}__${name}` : name, ENGINE_MARK = "<!-- sleds:engine -->", VISUAL_PATH, APPROACH_SHAPE, AUTONOMY_SAYS, IRREVERSIBLE = "deleting or migrating data in place; deploying outside the playbook's window; anything that leaves the org (a message to a customer, a publish, a release, an email); spend past the band or the allowance; force-pushing or deleting a branch someone else holds; granting or removing access; changing the brief. When in doubt, it is irreversible.", oneLine = (s, max) => s.replace(/\s+/g, " ").trim().slice(0, max), VERDICT_SHAPE, DECIDER_RULES, codes = (xs) => xs.map((x) => `\`${x}\``).join(", "), RESOLVE_MAX_FILES = 8;
6849
6857
  var init_engine = __esm(() => {
6858
+ init_prHeader();
6850
6859
  init_route();
6851
6860
  HOLD_DEFAULTS = {
6852
6861
  holdCpu: 85,
@@ -7943,7 +7952,8 @@ function parseRun(v) {
7943
7952
  ...typeof v.pr.conflict === "string" ? { conflict: v.pr.conflict } : {},
7944
7953
  ...typeof v.pr.resolving === "string" ? { resolving: v.pr.resolving } : {},
7945
7954
  ...conflicted ? { conflicted } : {},
7946
- ...typeof v.pr.reviewed === "string" ? { reviewed: v.pr.reviewed } : {}
7955
+ ...typeof v.pr.reviewed === "string" ? { reviewed: v.pr.reviewed } : {},
7956
+ ...typeof v.pr.look === "string" ? { look: v.pr.look } : {}
7947
7957
  }
7948
7958
  } : {}
7949
7959
  };
@@ -8014,6 +8024,7 @@ function parseLock(text) {
8014
8024
  runner: typeof v.runner === "string" ? v.runner : "",
8015
8025
  atOnce: n(v.atOnce, 1),
8016
8026
  interval: n(v.interval, 0),
8027
+ ...typeof v.cliVersion === "string" && v.cliVersion ? { cliVersion: v.cliVersion } : {},
8017
8028
  ...typeof v.checkedAt === "number" ? { checkedAt: v.checkedAt } : {},
8018
8029
  ...typeof v.nextCheckAt === "number" ? { nextCheckAt: v.nextCheckAt } : {},
8019
8030
  ...machine ? { machine } : {},
@@ -10329,15 +10340,28 @@ import { join as join15, resolve as resolve7 } from "node:path";
10329
10340
  // src/engine/beat.ts
10330
10341
  var BEAT_EVERY_MS = 60000;
10331
10342
  var BEAT_ITEMS = 8;
10332
- function beatBody(state, flags, host = {}) {
10343
+ function beatBody(state, flags, host = {}, cap = {}) {
10333
10344
  const live = state.runs.filter((r) => !r.pr && !r.parked);
10345
+ const m = cap.machine;
10334
10346
  return {
10335
10347
  name: flags.name,
10336
10348
  os: host.os ?? process.platform,
10337
10349
  runner: flags.runner,
10338
- version: host.version ?? "0.1.36" ?? "dev",
10350
+ version: host.version ?? "0.1.38" ?? "dev",
10339
10351
  running: live.length,
10340
- items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`)
10352
+ items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
10353
+ ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
10354
+ ...m ? {
10355
+ machine: {
10356
+ cpuPct: m.cpuPct,
10357
+ load1: m.load1,
10358
+ cores: m.cores,
10359
+ loadPerCore: m.loadPerCore,
10360
+ pressure: m.pressure
10361
+ },
10362
+ level: cap.hold?.level ?? "calm",
10363
+ ...cap.hold ? { allowed: cap.hold.allowed } : {}
10364
+ } : {}
10341
10365
  };
10342
10366
  }
10343
10367
  async function postBeat(client, body) {
@@ -10635,6 +10659,15 @@ function prForBranch(exec, repoPath, branch) {
10635
10659
  return { error: e instanceof Error ? e.message : String(e) };
10636
10660
  }
10637
10661
  }
10662
+ var URL_RE = /https:\/\/[^\s)>\]"']+/;
10663
+ var ARTIFACT_RE = /https:\/\/claude\.ai\/[^\s)>\]"']*artifact\/[^\s)>\]"']+/;
10664
+ function mockOf(body) {
10665
+ const header = /<!--\s*sleds:header\s*-->([\s\S]*?)<!--\s*\/sleds:header\s*-->/.exec(body)?.[1];
10666
+ const line = header?.split(`
10667
+ `).find((l) => /\*\*Mocks\*\*\s*:/i.test(l));
10668
+ const fromHeader = line ? URL_RE.exec(line)?.[0] : undefined;
10669
+ return fromHeader ?? ARTIFACT_RE.exec(body)?.[0];
10670
+ }
10638
10671
  var FAILED = new Set([
10639
10672
  "FAILURE",
10640
10673
  "ERROR",
@@ -10680,6 +10713,7 @@ function parsePrView(stdout) {
10680
10713
  pending++;
10681
10714
  }
10682
10715
  const mergeCommit = typeof o.mergeCommit === "object" && o.mergeCommit !== null ? o.mergeCommit.oid : undefined;
10716
+ const mock = typeof o.body === "string" ? mockOf(o.body) : undefined;
10683
10717
  return {
10684
10718
  state: o.state,
10685
10719
  author: loginOf(o.author),
@@ -10690,6 +10724,8 @@ function parsePrView(stdout) {
10690
10724
  reviewDecision: typeof o.reviewDecision === "string" ? o.reviewDecision : "",
10691
10725
  feedback: 0,
10692
10726
  authorFeedback: 0,
10727
+ draft: o.isDraft === true,
10728
+ ...mock ? { mock } : {},
10693
10729
  ...typeof o.headRefOid === "string" && o.headRefOid ? { head: o.headRefOid } : {},
10694
10730
  ...typeof mergeCommit === "string" && mergeCommit ? { mergeCommit } : {}
10695
10731
  };
@@ -10744,7 +10780,7 @@ function prView(exec, repoPath, url) {
10744
10780
  "view",
10745
10781
  url,
10746
10782
  "--json",
10747
- "state,author,statusCheckRollup,reviewDecision,comments,reviews,headRefOid,mergeable,baseRefName,mergeCommit"
10783
+ "state,author,statusCheckRollup,reviewDecision,comments,reviews,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body"
10748
10784
  ], { cwd: repoPath });
10749
10785
  const view = r.status === 0 ? parsePrView(r.stdout) : null;
10750
10786
  if (!view || view.state !== "OPEN")
@@ -11210,6 +11246,10 @@ init_machine();
11210
11246
 
11211
11247
  // src/engine/sledsSource.ts
11212
11248
  init_src();
11249
+ function looksOf(s) {
11250
+ const l = s;
11251
+ return l && typeof l.openLook === "function" && typeof l.clearLook === "function" ? s : null;
11252
+ }
11213
11253
  var FAIL_LOG_MAX = 2000;
11214
11254
  var UNREACHABLE2 = "SLEDS unreachable";
11215
11255
  function keysIn(states, state) {
@@ -11351,20 +11391,34 @@ var PROFILE_TEXT = [
11351
11391
  "reposDir",
11352
11392
  "engine"
11353
11393
  ];
11394
+ var SHELL_TEXT = ["agent", "launcher"];
11354
11395
  function profileOf(r) {
11355
11396
  if (!r || r.ok === false || !isObject8(r.profile))
11356
11397
  return null;
11357
11398
  const p = r.profile;
11399
+ const member = isObject8(r.member) ? r.member : {};
11358
11400
  const out = {
11359
11401
  pickup: p.pickup === "auto" ? "auto" : "approve",
11360
11402
  ...HOLD_DEFAULTS2,
11361
11403
  holdOff: p.holdOff === true
11362
11404
  };
11363
11405
  for (const k of PROFILE_TEXT) {
11406
+ if (SHELL_TEXT.includes(k))
11407
+ continue;
11364
11408
  const v = str6(p[k])?.trim();
11365
11409
  if (v)
11366
11410
  out[k] = v;
11367
11411
  }
11412
+ const notYours = [];
11413
+ for (const k of SHELL_TEXT) {
11414
+ const mine = str6(member[k])?.trim();
11415
+ if (mine)
11416
+ out[k] = mine;
11417
+ else if (str6(p[k])?.trim())
11418
+ notYours.push(k);
11419
+ }
11420
+ if (notYours.length)
11421
+ out.notYours = notYours;
11368
11422
  const atOnce = num5(p.atOnce);
11369
11423
  if (atOnce !== undefined && atOnce >= 1)
11370
11424
  out.atOnce = Math.round(atOnce);
@@ -11724,6 +11778,36 @@ function sledsSource(client, o) {
11724
11778
  throw new Error(serverMessage(r));
11725
11779
  return eventOpenOf(r.json);
11726
11780
  },
11781
+ async openLook(runId, look, engine) {
11782
+ const r = await client.postFull("/proxy/run", {
11783
+ runId,
11784
+ engine,
11785
+ event: {
11786
+ kind: "look",
11787
+ md: look.md,
11788
+ about: { prs: [look.pr], ...look.mock ? { mock: look.mock } : {} }
11789
+ }
11790
+ });
11791
+ if (!r)
11792
+ throw new Error(`${UNREACHABLE2} — the look not raised`);
11793
+ if (r.status >= 300 || !r.json || r.json.ok === false)
11794
+ throw new Error(serverMessage(r));
11795
+ const decisionId = str6(r.json.decisionId);
11796
+ const state = str6(r.json.state);
11797
+ return {
11798
+ opened: r.json.opened === true,
11799
+ ...decisionId ? { decisionId } : {},
11800
+ ...state ? { state } : {}
11801
+ };
11802
+ },
11803
+ async clearLook(runId, clear, engine) {
11804
+ const r = await client.postFull("/proxy/run", { runId, engine, clearLook: clear });
11805
+ if (!r)
11806
+ throw new Error(`${UNREACHABLE2} — the look not cleared`);
11807
+ if (r.status >= 300 || !r.json || r.json.ok === false)
11808
+ throw new Error(serverMessage(r));
11809
+ return r.json.cleared === true;
11810
+ },
11727
11811
  async decisionOf(item, decisionId) {
11728
11812
  const route = await routeOf(item);
11729
11813
  const decisions = Array.isArray(route?.decisions) ? route.decisions.filter(isObject8) : [];
@@ -12185,9 +12269,13 @@ async function tick(deps) {
12185
12269
  deps.providerHold = d.providerHold;
12186
12270
  deps.atOnce = d.atOnce;
12187
12271
  }
12272
+ removeEndedMcpConfigs(deps, made);
12188
12273
  }
12274
+ return report;
12275
+ }
12276
+ function removeEndedMcpConfigs(deps, made) {
12189
12277
  if (!deps.removeMcpConfig || made.size === 0)
12190
- return report;
12278
+ return;
12191
12279
  const state = deps.readState();
12192
12280
  const live = liveMcpConfigs(state);
12193
12281
  for (const p of made) {
@@ -12205,7 +12293,6 @@ async function tick(deps) {
12205
12293
  r.mcpConfig = undefined;
12206
12294
  deps.writeState(state);
12207
12295
  }
12208
- return report;
12209
12296
  }
12210
12297
  async function tickOnce(d) {
12211
12298
  d.tickProfile = profileOnce(d.readProfile);
@@ -13491,11 +13578,47 @@ var SELF_NUDGES = 2;
13491
13578
  function reviewNudge(url, count) {
13492
13579
  return `New review feedback on your PR (${count} ${count === 1 ? "comment" : "comments"} from people so far): ${url}. Read all of it (gh pr view ${url} --comments, and the inline threads), answer each comment on its own thread with the fix or a reasoned reply, push, and leave the threads for the reviewer to resolve. End every comment and reply you post on the PR with the line ${ENGINE_MARK} so the engine knows it is yours and not the reviewer's.`;
13493
13580
  }
13581
+ function lookQuestion(url, mock) {
13582
+ return `A draft PR waits for your look: ${url}.${mock ? ` The mock: ${mock}.` : " Its body names no mock; the PR is the place to look."} Looks good marks it ready for review.`;
13583
+ }
13584
+ async function judgeLook(d, run, view) {
13585
+ const looks = looksOf(d.source);
13586
+ const pr = run.pr;
13587
+ if (!looks || !run.runId)
13588
+ return;
13589
+ const draft = view.state === "OPEN" && view.draft;
13590
+ if (draft && !pr.look) {
13591
+ try {
13592
+ const r = await looks.openLook(run.runId, {
13593
+ md: lookQuestion(pr.url, view.mock),
13594
+ pr: pr.url,
13595
+ ...view.mock ? { mock: view.mock } : {}
13596
+ }, d.engine);
13597
+ if (!r.decisionId)
13598
+ return;
13599
+ pr.look = r.decisionId;
13600
+ if (r.opened)
13601
+ d.log(`${ref(run)}: ${pr.url} is a draft — a look waits for a person`);
13602
+ } catch (e) {
13603
+ d.log(`${ref(run)}: could not raise the look on ${pr.url}: ${message(e)}`);
13604
+ }
13605
+ } else if (!draft && pr.look) {
13606
+ const why = view.state === "MERGED" ? "the PR merged" : view.state === "CLOSED" ? "the PR closed without a merge" : "the PR left draft";
13607
+ try {
13608
+ if (await looks.clearLook(run.runId, { pr: pr.url, why }, d.engine))
13609
+ d.log(`${ref(run)}: the look on ${pr.url} is cleared — ${why}`);
13610
+ pr.look = undefined;
13611
+ } catch (e) {
13612
+ d.log(`${ref(run)}: could not clear the look on ${pr.url}: ${message(e)}`);
13613
+ }
13614
+ }
13615
+ }
13494
13616
  async function follow(d, state, run, claim, runner, finished, attention, changes, decided) {
13495
13617
  const pr = run.pr;
13496
13618
  const view = d.prView(d.exec, run.repoPath, pr.url);
13497
13619
  if (!view)
13498
13620
  return true;
13621
+ await judgeLook(d, run, view);
13499
13622
  if (view.state === "MERGED") {
13500
13623
  const landed = await ripple(d, state, run, claim, runner, pr.url, finished, decided, view.head, view.mergeCommit);
13501
13624
  return !landed;
@@ -15407,6 +15530,8 @@ function flagsFromProfile(flags, given, profile, home) {
15407
15530
  const out = { ...flags };
15408
15531
  const used = [];
15409
15532
  const skipped = [];
15533
+ for (const k of profile.notYours ?? [])
15534
+ skipped.push(`${k} (the workspace default — the engine runs only one you set yourself)`);
15410
15535
  if (profile.atOnce !== undefined && !has("at-once")) {
15411
15536
  out.atOnce = profile.atOnce;
15412
15537
  used.push(`at-once ${profile.atOnce}`);
@@ -15797,7 +15922,8 @@ async function driveEngine(o, flags, deps, until = {}) {
15797
15922
  startedAt: (o.now ?? Date.now)(),
15798
15923
  runner: deps.runner.name,
15799
15924
  atOnce: flags.atOnce,
15800
- interval: flags.interval
15925
+ interval: flags.interval,
15926
+ cliVersion: "0.1.38"
15801
15927
  };
15802
15928
  const beat = (checkedAt) => {
15803
15929
  const m = deps.machine;
@@ -17399,6 +17525,8 @@ function flagsFromProfile2(flags, given, profile, home) {
17399
17525
  const out = { ...flags };
17400
17526
  const used = [];
17401
17527
  const skipped = [];
17528
+ for (const k of profile.notYours ?? [])
17529
+ skipped.push(`${k} (the workspace default — the engine runs only one you set yourself)`);
17402
17530
  if (profile.atOnce !== undefined && !has("at-once")) {
17403
17531
  out.atOnce = profile.atOnce;
17404
17532
  used.push(`at-once ${profile.atOnce}`);
@@ -17827,7 +17955,8 @@ async function driveEngine2(o, flags, deps, until = {}) {
17827
17955
  startedAt: (o.now ?? Date.now)(),
17828
17956
  runner: deps.runner.name,
17829
17957
  atOnce: flags.atOnce,
17830
- interval: flags.interval
17958
+ interval: flags.interval,
17959
+ cliVersion: "0.1.38"
17831
17960
  };
17832
17961
  const beat = (checkedAt) => {
17833
17962
  const m = deps.machine;
@@ -18119,7 +18248,12 @@ var engineCmd = async (o) => {
18119
18248
  const who = { name: flags.name, runner: runner.name };
18120
18249
  afterTick = () => {
18121
18250
  try {
18122
- send(beatBody(deps.readState(), who));
18251
+ const m = deps.machine;
18252
+ send(beatBody(deps.readState(), who, {}, {
18253
+ atOnce: deps.atOnce,
18254
+ ...m?.last ? { machine: m.last } : {},
18255
+ ...m?.hold && !m.off ? { hold: m.hold } : {}
18256
+ }));
18123
18257
  } catch {}
18124
18258
  };
18125
18259
  }
@@ -18577,6 +18711,31 @@ function ago(at, now) {
18577
18711
  // src/commands/engineList.ts
18578
18712
  var ENGINE_LIST_USAGE = "usage: sleds engine list [--json]";
18579
18713
  var str11 = (x) => typeof x === "string" ? x : "";
18714
+ var numOrNull = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
18715
+ function machineOf(x) {
18716
+ if (!x || typeof x !== "object")
18717
+ return null;
18718
+ const m = x;
18719
+ const out = { pressure: str11(m.pressure) || "unknown" };
18720
+ for (const k of ["cpuPct", "load1", "cores", "loadPerCore"]) {
18721
+ const n = numOrNull(m[k]);
18722
+ if (n !== null)
18723
+ out[k] = n;
18724
+ }
18725
+ return out;
18726
+ }
18727
+ var LEVELS = ["calm", "strained", "busy"];
18728
+ function workOf(x) {
18729
+ if (!Array.isArray(x))
18730
+ return [];
18731
+ return x.flatMap((w) => {
18732
+ if (!w || typeof w !== "object")
18733
+ return [];
18734
+ const o = w;
18735
+ const item = str11(o.item);
18736
+ return item ? [{ item, node: str11(o.node), title: typeof o.title === "string" ? o.title : null }] : [];
18737
+ });
18738
+ }
18580
18739
  function engineRows(raw) {
18581
18740
  const rows = Array.isArray(raw.engines) ? raw.engines : [];
18582
18741
  return rows.flatMap((r) => {
@@ -18596,8 +18755,13 @@ function engineRows(raw) {
18596
18755
  running: typeof e.running === "number" ? e.running : 0,
18597
18756
  items: Array.isArray(e.items) ? e.items.filter((i) => typeof i === "string") : [],
18598
18757
  seatKind: typeof e.seatKind === "string" ? e.seatKind : null,
18758
+ atOnce: numOrNull(e.atOnce),
18759
+ machine: machineOf(e.machine),
18760
+ level: LEVELS.find((l) => l === e.level) ?? null,
18761
+ allowed: numOrNull(e.allowed),
18599
18762
  lastBeatAt: typeof e.lastBeatAt === "number" ? e.lastBeatAt : 0,
18600
- state: e.state === "up" ? "up" : "down"
18763
+ state: e.state === "up" ? "up" : "down",
18764
+ work: workOf(e.work)
18601
18765
  }
18602
18766
  ];
18603
18767
  });
@@ -18608,7 +18772,10 @@ function engineListLines(rows, now) {
18608
18772
  "no engines have beaten in this org — start one with `sleds engine` or `sleds engine install`"
18609
18773
  ];
18610
18774
  return rows.map((r) => {
18611
- const parts = [r.name.replace(/^engine:/, ""), r.os || "?", r.state, `${r.running} running`];
18775
+ const slots = r.atOnce ? `${r.running} of ${r.atOnce} running` : `${r.running} running`;
18776
+ const parts = [r.name.replace(/^engine:/, ""), r.os || "?", r.state, slots];
18777
+ if (r.state === "up" && r.level && r.level !== "calm")
18778
+ parts.push(r.level);
18612
18779
  if (r.state === "down" && r.lastBeatAt)
18613
18780
  parts.push(`last beat ${ago(r.lastBeatAt, now)}`);
18614
18781
  return clean(parts.join(" · "));
@@ -18767,6 +18934,7 @@ function engineStatus(i) {
18767
18934
  ...opt("runner", lock.runner || undefined),
18768
18935
  atOnce: lock.atOnce,
18769
18936
  interval: lock.interval,
18937
+ cliVersion: lock.cliVersion ?? "",
18770
18938
  ...opt("checkedAt", iso2(lock.checkedAt)),
18771
18939
  ...opt("nextCheckAt", alive ? iso2(lock.nextCheckAt) : undefined),
18772
18940
  ...opt("machine", lock.machine ? { ...lock.machine, at: new Date(lock.machine.at).toISOString() } : undefined),
@@ -20704,8 +20872,9 @@ var mcp = async (o) => {
20704
20872
  (o.warn ?? ((line) => process.stderr.write(`${line}
20705
20873
  `)))("sleds mcp: on this run's token (SLEDS_RUN_TOKEN), not the seat's");
20706
20874
  let buffer = "";
20875
+ const decoder = new TextDecoder;
20707
20876
  for await (const chunk of stdin) {
20708
- buffer += typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
20877
+ buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
20709
20878
  let nl = buffer.indexOf(`
20710
20879
  `);
20711
20880
  while (nl !== -1) {
@@ -20718,6 +20887,7 @@ var mcp = async (o) => {
20718
20887
  `);
20719
20888
  }
20720
20889
  }
20890
+ buffer += decoder.decode();
20721
20891
  if (buffer.trim().length > 0) {
20722
20892
  const out = await handleLine2(buffer, { home: o.home, env });
20723
20893
  if (out !== null)
@@ -21321,13 +21491,13 @@ init_src();
21321
21491
  import { readFileSync as readFileSync24 } from "node:fs";
21322
21492
  import { hostname as hostname12 } from "node:os";
21323
21493
  var ARM_USAGE = "usage: sleds route arm <key> [--off] [--json] [--name <engine>] [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--mode <permission-mode>] [--model <id>] [--effort <e>] [--branch '<template>'] [--pr-title '<template>'] [--repos-dir <dir>]";
21324
- var USAGE = "usage: sleds route <key> [--long | --dot | --json] | sleds route arm <key> [--off] [--json] | sleds route answer <key>/<node> --stdin | sleds route propose <key> --file <nodes.json> | sleds route approve <key> | sleds route redraft <key> | sleds route single <key> | sleds route decisions [<key>] [--flagged] | sleds route decision <key> <id> --seen | --answer --stdin [--route accept|keep|redraft] | --keep-route | --apply [--answer --stdin] | --overrule --stdin | sleds route run <key> [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--interval <s>] [--lease <min>] [--idle <min>] [--repo <path>] [--repos-dir <dir>] [--mode <permission-mode>] [--model <id>] [--branch '<template>'] [--pr-title '<template>'] [--once]";
21494
+ var USAGE = "usage: sleds route <key> [--long | --dot | --json] | sleds route arm <key> [--off] [--json] | sleds route answer <key>/<node> --stdin | sleds route propose <key> --file <nodes.json> | sleds route approve <key> | sleds route redraft <key> | sleds route single <key> | sleds route decisions [<key>] [--flagged] | sleds route decision <key> <id> --seen | --answer --stdin [--route accept|keep|redraft] | --keep-route | --apply [--answer --stdin] | --overrule --stdin | --ready | sleds route run <key> [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--interval <s>] [--lease <min>] [--idle <min>] [--repo <path>] [--repos-dir <dir>] [--mode <permission-mode>] [--model <id>] [--branch '<template>'] [--pr-title '<template>'] [--once]";
21325
21495
  var ANSWER_USAGE = "usage: sleds route answer <key>/<node> <answer…> | sleds route answer <key>/<node> --stdin [--json]";
21326
21496
  var PROPOSE_USAGE = "usage: sleds route propose <key> (--file <nodes.json> | --stdin) [--name <engine>] [--json]";
21327
21497
  var APPROVE_USAGE = "usage: sleds route approve <key> [--file <nodes.json>] [--json]";
21328
21498
  var REDRAFT_USAGE = "usage: sleds route redraft <key> [--json]";
21329
21499
  var SINGLE_USAGE = "usage: sleds route single <key> [--json]";
21330
- var DECISION_USAGE = "usage: sleds route decision <key> <id> --seen | sleds route decision <key> <id> (--answer | --overrule) (<answer…> | --stdin) | sleds route decision <key> <id> [--answer (<answer…> | --stdin)] --route accept|keep|redraft | sleds route decision <key> <id> --keep-route | sleds route decision <key> <id> --apply [--answer (<answer…> | --stdin)] [--json]";
21500
+ var DECISION_USAGE = "usage: sleds route decision <key> <id> --seen | sleds route decision <key> <id> (--answer | --overrule) (<answer…> | --stdin) | sleds route decision <key> <id> [--answer (<answer…> | --stdin)] --route accept|keep|redraft | sleds route decision <key> <id> --keep-route | sleds route decision <key> <id> --apply [--answer (<answer…> | --stdin)] | sleds route decision <key> <id> --ready [--json]";
21331
21501
  var ROUTE_VERDICTS = ["accept", "keep", "redraft"];
21332
21502
  function nodesFromJson(text) {
21333
21503
  let parsed;
@@ -21656,6 +21826,8 @@ function appliedLine(key, applied) {
21656
21826
  return a.changed === false ? `${key}: ${what} was already set — answered` : `${key}: set ${what} — answered`;
21657
21827
  }
21658
21828
  async function decisionCmd(o, argv) {
21829
+ if (argv.includes("--ready"))
21830
+ return readyCmd(o, argv);
21659
21831
  const log = o.log ?? console.log;
21660
21832
  const error = o.error ?? console.error;
21661
21833
  const json = argv.includes("--json");
@@ -21709,6 +21881,68 @@ async function decisionCmd(o, argv) {
21709
21881
  }
21710
21882
  return said(o, key, json, r, () => log(seen ? `${key}: marked seen` : overrule ? `${key}: overruled — your answer stands in the log` : apply ? appliedLine(key, r.json?.applied) : answeredLine(key, r.json?.route ?? route, r.json?.held)));
21711
21883
  }
21884
+ var LOOK_READY_ANSWER = "Looks good — marked ready for review.";
21885
+ function lookToReady(route, id) {
21886
+ const rows = Array.isArray(route?.decisions) ? route.decisions : [];
21887
+ const d = rows.find((r) => r.id === id);
21888
+ if (!d)
21889
+ return { error: `no decision ${id} on this item` };
21890
+ if (d.kind !== "look")
21891
+ return { error: `decision ${id} is not a look — --ready marks a look` };
21892
+ if (d.state !== "escalated")
21893
+ return { error: `decision ${id} is ${String(d.state)} — not a look you owe` };
21894
+ const about = d.about && typeof d.about === "object" ? d.about : {};
21895
+ const pr = Array.isArray(about.prs) ? about.prs.find((p) => typeof p === "string" && p) : undefined;
21896
+ return typeof pr === "string" ? { pr } : { error: `decision ${id} names no PR` };
21897
+ }
21898
+ async function readyCmd(o, argv) {
21899
+ const log = o.log ?? console.log;
21900
+ const error = o.error ?? console.error;
21901
+ const json = argv.includes("--json");
21902
+ const words = argv.filter((a) => a !== "--ready" && a !== "--json");
21903
+ const [key, id] = words;
21904
+ if (!key || !id || words.length !== 2 || words.some((w) => w.startsWith("--"))) {
21905
+ error(DECISION_USAGE);
21906
+ return 2;
21907
+ }
21908
+ const client = engineClient(o);
21909
+ if ("error" in client) {
21910
+ error(client.error);
21911
+ return 1;
21912
+ }
21913
+ const reader = routeReader(o);
21914
+ if ("error" in reader) {
21915
+ error(reader.error);
21916
+ return 1;
21917
+ }
21918
+ const route = await reader.read(key).catch((e) => {
21919
+ error(e instanceof Error ? e.message : String(e));
21920
+ return;
21921
+ });
21922
+ if (route === undefined)
21923
+ return 1;
21924
+ const look = lookToReady(route, id);
21925
+ if ("error" in look) {
21926
+ error(`${key}: ${look.error}`);
21927
+ return 1;
21928
+ }
21929
+ const gh = (o.exec ?? defaultExec)("gh", ["pr", "ready", look.pr]);
21930
+ if (gh.status !== 0) {
21931
+ error(gh.stderr.trim() || `gh pr ready ${look.pr} exited ${gh.status}`);
21932
+ error(`${key}: the look is not answered`);
21933
+ return 1;
21934
+ }
21935
+ const r = await client.postFull("/proxy/route/decision", {
21936
+ project: key,
21937
+ id,
21938
+ answer: LOOK_READY_ANSWER
21939
+ });
21940
+ if (!r) {
21941
+ error(`${key}: ${look.pr} is ready for review, but SLEDS is unreachable — the look is not answered`);
21942
+ return 1;
21943
+ }
21944
+ return said(o, key, json, r, () => log(`${key}: ${look.pr} marked ready for review — the look is answered`));
21945
+ }
21712
21946
  var DECISIONS_USAGE = "usage: sleds route decisions [<key>] [--flagged] [--json]";
21713
21947
  function decisionsLines(raw) {
21714
21948
  const j = clean(raw);
@@ -24155,7 +24389,7 @@ ${renderHelpText(argv[1])}`);
24155
24389
  case "version":
24156
24390
  case "--version":
24157
24391
  case "-v":
24158
- console.log("sleds 0.1.36");
24392
+ console.log("sleds 0.1.38");
24159
24393
  return 0;
24160
24394
  default:
24161
24395
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "SLEDS CLI — the cost and limits of AI agent work, decided at the wall. Point Claude Code at a SLEDS proxy; estimate before you run; hold, switch model, or run overnight when you hit a limit.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",