@sleds/cli 0.1.52 → 0.1.53

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 +122 -57
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -8601,6 +8601,7 @@ function parseRun(v) {
8601
8601
  options: Array.isArray(v.permission.options) ? v.permission.options.filter((x) => typeof x === "string") : [],
8602
8602
  at: v.permission.at,
8603
8603
  ...v.permission.onScreen === true ? { onScreen: true } : {},
8604
+ ...typeof v.permission.dialog === "string" ? { dialog: v.permission.dialog } : {},
8604
8605
  ...typeof v.permission.pressedAt === "number" ? { pressedAt: v.permission.pressedAt } : {},
8605
8606
  ...typeof v.permission.presses === "number" ? { presses: v.permission.presses } : {},
8606
8607
  ...v.permission.gaveUp === true ? { gaveUp: true } : {}
@@ -8615,12 +8616,21 @@ function parseRun(v) {
8615
8616
  feedback: typeof v.pr.feedback === "number" ? v.pr.feedback : 0,
8616
8617
  ...typeof v.pr.authorFeedback === "number" ? { authorFeedback: v.pr.authorFeedback } : {},
8617
8618
  ...typeof v.pr.selfNudges === "number" ? { selfNudges: v.pr.selfNudges } : {},
8619
+ ...isObject8(v.pr.feedbackRead) && typeof v.pr.feedbackRead.updatedAt === "string" && typeof v.pr.feedbackRead.all === "number" && typeof v.pr.feedbackRead.author === "number" && typeof v.pr.feedbackRead.at === "number" ? {
8620
+ feedbackRead: {
8621
+ updatedAt: v.pr.feedbackRead.updatedAt,
8622
+ all: v.pr.feedbackRead.all,
8623
+ author: v.pr.feedbackRead.author,
8624
+ at: v.pr.feedbackRead.at
8625
+ }
8626
+ } : {},
8618
8627
  ...v.pr.clear === true ? { clear: true } : {},
8619
8628
  ...typeof v.pr.ci === "string" ? { ci: v.pr.ci } : {},
8620
8629
  ...typeof v.pr.review === "string" ? { review: v.pr.review } : {},
8621
8630
  ...typeof v.pr.posted === "string" ? { posted: v.pr.posted } : {},
8622
8631
  ...Array.isArray(v.pr.failedHeads) ? { failedHeads: v.pr.failedHeads.filter((x) => typeof x === "string") } : {},
8623
8632
  ...typeof v.pr.conflict === "string" ? { conflict: v.pr.conflict } : {},
8633
+ ...isObject8(v.pr.conflictCheck) && typeof v.pr.conflictCheck.head === "string" && typeof v.pr.conflictCheck.at === "number" ? { conflictCheck: { head: v.pr.conflictCheck.head, at: v.pr.conflictCheck.at } } : {},
8624
8634
  ...typeof v.pr.resolving === "string" ? { resolving: v.pr.resolving } : {},
8625
8635
  ...conflicted ? { conflicted } : {},
8626
8636
  ...typeof v.pr.reviewed === "string" ? { reviewed: v.pr.reviewed } : {},
@@ -10759,15 +10769,36 @@ var claudeAddDirFlag = (mainCheckout) => mainCheckout ? ` --add-dir=${shq(mainCh
10759
10769
  var shq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
10760
10770
 
10761
10771
  // src/runners/exec.ts
10772
+ var EXEC_TIMEOUT_MS = 2 * 60000;
10773
+ var EXEC_TIMEOUT_STATUS = 124;
10774
+ var timedOut = (r) => r.error?.code === "ETIMEDOUT";
10775
+ var logTimeouts = (exec, log) => (cmd, args, o) => {
10776
+ const r = exec(cmd, args, o);
10777
+ if (r.status === EXEC_TIMEOUT_STATUS && isTimeout(r.stderr))
10778
+ log(`exec timed out: ${[cmd, ...args].join(" ")} — ${r.stderr.split(`
10779
+ `)[0]}`);
10780
+ return r;
10781
+ };
10762
10782
  var defaultExec = (cmd, args, o) => {
10783
+ const timeoutMs = o?.timeoutMs ?? EXEC_TIMEOUT_MS;
10763
10784
  const r = spawnSync3(cmd, [...args], {
10764
10785
  cwd: o?.cwd,
10765
10786
  input: o?.input,
10766
10787
  env: o?.env ? { ...process.env, ...o.env } : undefined,
10767
10788
  encoding: "utf8",
10768
10789
  shell: false,
10769
- maxBuffer: 16 * 1024 * 1024
10790
+ maxBuffer: 16 * 1024 * 1024,
10791
+ timeout: timeoutMs,
10792
+ killSignal: "SIGKILL"
10770
10793
  });
10794
+ if (timedOut(r)) {
10795
+ return {
10796
+ status: EXEC_TIMEOUT_STATUS,
10797
+ stdout: r.stdout ?? "",
10798
+ stderr: `${cmd}: timed out after ${timeoutMs / 1000}s and was killed${r.stderr ? `
10799
+ ${r.stderr}` : ""}`
10800
+ };
10801
+ }
10771
10802
  if (r.error) {
10772
10803
  return { status: 127, stdout: r.stdout ?? "", stderr: `${cmd}: ${r.error.message}` };
10773
10804
  }
@@ -11436,14 +11467,7 @@ ${r.stdout}`) };
11436
11467
  sendKey(h, key) {
11437
11468
  if (!isRunnerKey(key))
11438
11469
  throw new Error(`orca terminal send ${h.id}: no key ${JSON.stringify(key)}`);
11439
- const what = `orca terminal send ${h.id} (key ${key})`;
11440
- const send = (args) => orca(["terminal", "send", "--terminal", h.id, ...args, "--json"], what);
11441
- if (key === "esc") {
11442
- send(["--text", ESC]);
11443
- return;
11444
- }
11445
- send(["--text", key]);
11446
- send(["--enter"]);
11470
+ orca(["terminal", "send", "--terminal", h.id, "--text", key === "esc" ? ESC : key, "--json"], `orca terminal send ${h.id} (key ${key})`);
11447
11471
  },
11448
11472
  stop(h) {
11449
11473
  exec("orca", ["terminal", "close", "--terminal", h.id]);
@@ -11511,7 +11535,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11511
11535
  name: flags.name,
11512
11536
  os: host.os ?? process.platform,
11513
11537
  runner: flags.runner,
11514
- version: host.version ?? "0.1.52" ?? "dev",
11538
+ version: host.version ?? "0.1.53" ?? "dev",
11515
11539
  running: live.length,
11516
11540
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11517
11541
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -11911,6 +11935,7 @@ function parsePrView(stdout) {
11911
11935
  feedback: 0,
11912
11936
  authorFeedback: 0,
11913
11937
  draft: o.isDraft === true,
11938
+ updatedAt: typeof o.updatedAt === "string" ? o.updatedAt : "",
11914
11939
  ...mock ? { mock } : {},
11915
11940
  ...typeof o.headRefOid === "string" && o.headRefOid ? { head: o.headRefOid } : {},
11916
11941
  ...typeof mergeCommit === "string" && mergeCommit ? { mergeCommit } : {}
@@ -11959,20 +11984,24 @@ function prFeedback(exec, repoPath, url, author) {
11959
11984
  }
11960
11985
  return total;
11961
11986
  }
11962
- function prView(exec, repoPath, url) {
11987
+ function prView(exec, repoPath, url, last) {
11963
11988
  try {
11964
11989
  const r = exec("gh", [
11965
11990
  "pr",
11966
11991
  "view",
11967
11992
  url,
11968
11993
  "--json",
11969
- "state,author,statusCheckRollup,reviewDecision,comments,reviews,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body"
11994
+ "state,author,statusCheckRollup,reviewDecision,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body,updatedAt"
11970
11995
  ], { cwd: repoPath });
11971
11996
  const view = r.status === 0 ? parsePrView(r.stdout) : null;
11972
11997
  if (!view || view.state !== "OPEN")
11973
11998
  return view;
11974
- const feedback = prFeedback(exec, repoPath, url, view.author);
11975
- return feedback === null ? view : { ...view, feedback: feedback.all, authorFeedback: feedback.author };
11999
+ let read = view.updatedAt && last?.updatedAt === view.updatedAt ? last : undefined;
12000
+ if (!read) {
12001
+ const f = prFeedback(exec, repoPath, url, view.author);
12002
+ read = f && { ...f, updatedAt: view.updatedAt };
12003
+ }
12004
+ return read ? { ...view, feedback: read.all, authorFeedback: read.author, read } : view;
11976
12005
  } catch {
11977
12006
  return null;
11978
12007
  }
@@ -13011,26 +13040,28 @@ function sledsSource(client, o) {
13011
13040
  });
13012
13041
  if (r?.status !== 409)
13013
13042
  return "running";
13043
+ let route;
13044
+ try {
13045
+ route = await routeOf(claim.item);
13046
+ } catch {
13047
+ route = null;
13048
+ }
13049
+ if (!isObject8(route))
13050
+ return "running";
13014
13051
  if (claim.kind === "draft") {
13015
- try {
13016
- const route = await routeOf(claim.item);
13017
- const filed = Array.isArray(route?.nodes) && route.nodes.length > 0;
13018
- if (filed || isObject8(route?.proposed))
13019
- return "done";
13020
- if (isObject8(route?.draftParked))
13021
- return "parked";
13022
- } catch {}
13052
+ const filed = Array.isArray(route.nodes) && route.nodes.length > 0;
13053
+ if (filed || isObject8(route.proposed))
13054
+ return "done";
13055
+ if (isObject8(route.draftParked))
13056
+ return "parked";
13023
13057
  return "lost";
13024
13058
  }
13025
- try {
13026
- const route = await routeOf(claim.item);
13027
- const nodes = Array.isArray(route?.nodes) ? route.nodes.filter(isObject8) : [];
13028
- const state = nodes.find((n) => n.key === claim.node)?.state;
13029
- if (state === "parked")
13030
- return "parked";
13031
- if (state === "pr" || state === "landed" || state === "skipped")
13032
- return "done";
13033
- } catch {}
13059
+ const nodes = Array.isArray(route.nodes) ? route.nodes.filter(isObject8) : [];
13060
+ const state = nodes.find((n) => n.key === claim.node)?.state;
13061
+ if (state === "parked")
13062
+ return "parked";
13063
+ if (state === "pr" || state === "landed" || state === "skipped")
13064
+ return "done";
13034
13065
  return "lost";
13035
13066
  },
13036
13067
  async report(claim, outcome, by) {
@@ -13526,6 +13557,7 @@ function fastForwardUntouched(exec, path, base, log) {
13526
13557
  log?.(`${path}: fast-forwarded to origin/${base}`);
13527
13558
  return true;
13528
13559
  }
13560
+ var INSTALL_TIMEOUT_MS = 15 * 60000;
13529
13561
  var MANAGERS = new Set(["pnpm", "npm", "yarn", "bun"]);
13530
13562
  var INSTALL_VERBS = new Set(["install", "i", "ci"]);
13531
13563
  function installArgv(command) {
@@ -13617,7 +13649,7 @@ function ensureInstalled(exec, path, fs, o = {}) {
13617
13649
  return null;
13618
13650
  const [cmd, ...args] = argv;
13619
13651
  o.log?.(`${path}: no node_modules — running ${argv.join(" ")}`);
13620
- const r = exec(cmd, args, { cwd: path });
13652
+ const r = exec(cmd, args, { cwd: path, timeoutMs: INSTALL_TIMEOUT_MS });
13621
13653
  const ok = r.status === 0;
13622
13654
  if (!ok)
13623
13655
  o.log?.(`${path}: ${argv.join(" ")} failed (exit ${r.status}) — the session starts anyway: ${(r.stderr || r.stdout).trim().split(`
@@ -14364,6 +14396,10 @@ function permissionKey(answer) {
14364
14396
  const m = /^\s*(\d|esc)\s*·/i.exec(answer);
14365
14397
  return m ? m[1].toLowerCase() : null;
14366
14398
  }
14399
+ function dialogPrint(dialog) {
14400
+ return [dialog.text, ...dialog.options.map((o) => `${o.key} · ${o.label}`)].join(`
14401
+ `);
14402
+ }
14367
14403
  async function settlePermission(d, runner, run, shown) {
14368
14404
  const perms = permissionsOf(d.source);
14369
14405
  if (!perms || !run.runId)
@@ -14373,19 +14409,20 @@ async function settlePermission(d, runner, run, shown) {
14373
14409
  const dialog = runner.readsSession ? permissionDialog(shown) : null;
14374
14410
  const p = run.permission;
14375
14411
  if (p) {
14376
- const gone = runner.readsSession && (p.onScreen ? !dialog : (run.seenAt ?? 0) > (p.pressedAt ?? p.at));
14412
+ const gone = runner.readsSession && (p.onScreen ? !dialog || p.dialog !== undefined && dialogPrint(dialog) !== p.dialog : (run.seenAt ?? 0) > (p.pressedAt ?? p.at));
14377
14413
  const answered = () => {
14378
14414
  run.permission = undefined;
14379
14415
  run.answeredAt = d.now();
14380
14416
  if (markFile)
14381
14417
  clearPermissionMark(markFile);
14382
14418
  };
14419
+ const next = (held) => dialog ? settlePermission(d, runner, run, shown) : held;
14383
14420
  if (p.gaveUp) {
14384
14421
  if (!gone)
14385
14422
  return true;
14386
14423
  d.log(`${at}: the permission dialog was answered in the terminal`);
14387
14424
  answered();
14388
- return false;
14425
+ return next(false);
14389
14426
  }
14390
14427
  let now;
14391
14428
  try {
@@ -14408,13 +14445,13 @@ async function settlePermission(d, runner, run, shown) {
14408
14445
  }
14409
14446
  }
14410
14447
  answered();
14411
- return true;
14448
+ return gone ? next(true) : true;
14412
14449
  }
14413
14450
  const key = permissionKey(now.answer) ?? now.answer;
14414
14451
  if (gone) {
14415
14452
  d.log(`${at}: the permission is answered — pressed ${key}; the session goes on`);
14416
14453
  answered();
14417
- return false;
14454
+ return next(false);
14418
14455
  }
14419
14456
  const presses = p.presses ?? 1;
14420
14457
  if (presses >= PRESS_TRIES) {
@@ -14447,7 +14484,7 @@ async function settlePermission(d, runner, run, shown) {
14447
14484
  }
14448
14485
  d.log(`${at}: the permission dialog closed before an answer — cleared`);
14449
14486
  run.permission = undefined;
14450
- return false;
14487
+ return next(false);
14451
14488
  }
14452
14489
  let mark = markFile ? readPermissionMark(markFile) : null;
14453
14490
  if (mark && markFile && (!isToolPermission(mark.message) || runner.readsSession && choicePicker(shown))) {
@@ -14473,7 +14510,7 @@ async function settlePermission(d, runner, run, shown) {
14473
14510
  decisionId: opened.decisionId,
14474
14511
  options,
14475
14512
  at: d.now(),
14476
- ...dialog ? { onScreen: true } : {}
14513
+ ...dialog ? { onScreen: true, dialog: dialogPrint(dialog) } : {}
14477
14514
  };
14478
14515
  if (markFile)
14479
14516
  clearPermissionMark(markFile);
@@ -15361,6 +15398,8 @@ function ciNudge(url, failing) {
15361
15398
  return `CI failed on your PR (${failing.join(", ")}): ${url}. Read the failing logs (gh pr checks ${url}), fix the cause, run the repo's gate, and push to the same branch.`;
15362
15399
  }
15363
15400
  var SELF_NUDGES = 2;
15401
+ var FEEDBACK_REREAD_MS = 10 * 60000;
15402
+ var CONFLICT_RECHECK_MS = 10 * 60000;
15364
15403
  function reviewNudge(url, count) {
15365
15404
  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.`;
15366
15405
  }
@@ -15401,9 +15440,12 @@ async function judgeLook(d, run, view) {
15401
15440
  }
15402
15441
  async function follow(d, state, run, claim, runner, finished, attention, changes, decided) {
15403
15442
  const pr = run.pr;
15404
- const view = d.prView(d.exec, run.repoPath, pr.url);
15443
+ const last = pr.feedbackRead && d.now() - pr.feedbackRead.at < FEEDBACK_REREAD_MS ? pr.feedbackRead : undefined;
15444
+ const view = d.prView(d.exec, run.repoPath, pr.url, last);
15405
15445
  if (!view)
15406
15446
  return true;
15447
+ if (view.read && view.read !== last)
15448
+ pr.feedbackRead = { ...view.read, at: d.now() };
15407
15449
  await judgeLook(d, run, view);
15408
15450
  if (view.state === "MERGED") {
15409
15451
  await dropPermission(d, run, "the PR merged");
@@ -15457,6 +15499,7 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
15457
15499
  else if (view.mergeable === "MERGEABLE") {
15458
15500
  pr.resolving = undefined;
15459
15501
  pr.conflicted = undefined;
15502
+ pr.conflictCheck = undefined;
15460
15503
  }
15461
15504
  if (view.feedback > pr.feedback) {
15462
15505
  const what = `${view.feedback - pr.feedback} new review comment(s)`;
@@ -15696,10 +15739,15 @@ async function judgeConflict(d, state, run, view, attention, decided) {
15696
15739
  const pr = run.pr;
15697
15740
  if (!d.mergeTree)
15698
15741
  return;
15742
+ const check = pr.conflictCheck;
15743
+ if (view.head && check?.head === view.head && !pr.resolving && d.now() - check.at < CONFLICT_RECHECK_MS)
15744
+ return;
15699
15745
  const base = view.base || d.defaultBranch(d.exec, run.repoPath);
15700
15746
  const mt = d.mergeTree(d.exec, run.repoPath, base, run.branch);
15701
15747
  if (!mt)
15702
15748
  return;
15749
+ if (view.head)
15750
+ pr.conflictCheck = { head: view.head, at: d.now() };
15703
15751
  if (pr.resolving === mt.sha && mt.files.length > 0 && !sessionRuns(d, run.handle)) {
15704
15752
  pr.resolving = undefined;
15705
15753
  const what = `${pr.url} still conflicts with ${base} on ${mt.files.join(", ")} after its session had the brief`;
@@ -17362,7 +17410,7 @@ async function fetchVersion(o) {
17362
17410
  const platform = o.platform ?? process.platform;
17363
17411
  const arch = o.arch ?? process.arch;
17364
17412
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
17365
- const running = o.running ?? "0.1.52";
17413
+ const running = o.running ?? "0.1.53";
17366
17414
  if (!publicKey)
17367
17415
  return { ok: false, why: "no release key yet" };
17368
17416
  if (!VERSION_RE2.test(o.version))
@@ -18946,7 +18994,7 @@ function engineClient(o, org) {
18946
18994
  return fetchClient({
18947
18995
  ...src,
18948
18996
  timeoutMs: ENGINE_TIMEOUT_MS,
18949
- version: "0.1.52"
18997
+ version: "0.1.53"
18950
18998
  });
18951
18999
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
18952
19000
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -19011,10 +19059,10 @@ function engineToken(o, org) {
19011
19059
  }
19012
19060
  var withToken = (token, now) => token ? { token, tokenNow: now } : {};
19013
19061
  function engineDeps(o, flags, source, runner, stateName) {
19014
- const exec = guardGit(o.exec ?? defaultExec);
19062
+ const log = o.log ?? console.log;
19063
+ const exec = logTimeouts(guardGit(o.exec ?? defaultExec), log);
19015
19064
  const cwd = o.cwd ?? process.cwd();
19016
19065
  const exists = o.exists ?? realExists;
19017
- const log = o.log ?? console.log;
19018
19066
  const stateDir = engineStateDir(o, flags.org, flags.seat);
19019
19067
  const file = statePath(stateDir, stateName);
19020
19068
  const mcpOrg = flags.org && engineOrg(o, flags.org).seated ? flags.org : undefined;
@@ -19237,7 +19285,7 @@ async function driveEngine(o, flags, deps, until = {}) {
19237
19285
  runner: deps.runner.name,
19238
19286
  atOnce: flags.atOnce,
19239
19287
  interval: flags.interval,
19240
- cliVersion: "0.1.52"
19288
+ cliVersion: "0.1.53"
19241
19289
  };
19242
19290
  const beat = (checkedAt) => {
19243
19291
  const m = deps.machine;
@@ -19429,7 +19477,7 @@ async function drive(o, flags, deps, until, beat) {
19429
19477
  var SEAT_CHANGED_EXIT = 3;
19430
19478
  function engineRunner(o, flags) {
19431
19479
  const runnerFor = o.runnerFor ?? runnerFor2;
19432
- return runnerFor(flags.runner, guardGit(o.exec ?? defaultExec), {
19480
+ return runnerFor(flags.runner, logTimeouts(guardGit(o.exec ?? defaultExec), o.log ?? console.log), {
19433
19481
  ...flags.runnerCmd ? { command: flags.runnerCmd } : {}
19434
19482
  });
19435
19483
  }
@@ -21094,7 +21142,7 @@ function engineClient2(o, org) {
21094
21142
  return fetchClient({
21095
21143
  ...src,
21096
21144
  timeoutMs: ENGINE_TIMEOUT_MS2,
21097
- version: "0.1.52"
21145
+ version: "0.1.53"
21098
21146
  });
21099
21147
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
21100
21148
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -21181,10 +21229,10 @@ function engineCredential(o) {
21181
21229
  })?.kind;
21182
21230
  }
21183
21231
  function engineDeps2(o, flags, source, runner, stateName) {
21184
- const exec = guardGit(o.exec ?? defaultExec);
21232
+ const log = o.log ?? console.log;
21233
+ const exec = logTimeouts(guardGit(o.exec ?? defaultExec), log);
21185
21234
  const cwd = o.cwd ?? process.cwd();
21186
21235
  const exists = o.exists ?? realExists2;
21187
- const log = o.log ?? console.log;
21188
21236
  const stateDir = engineStateDir2(o, flags.org, flags.seat);
21189
21237
  const file = statePath(stateDir, stateName);
21190
21238
  const mcpOrg = flags.org && engineOrg2(o, flags.org).seated ? flags.org : undefined;
@@ -21420,7 +21468,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
21420
21468
  runner: deps.runner.name,
21421
21469
  atOnce: flags.atOnce,
21422
21470
  interval: flags.interval,
21423
- cliVersion: "0.1.52"
21471
+ cliVersion: "0.1.53"
21424
21472
  };
21425
21473
  const beat = (checkedAt) => {
21426
21474
  const m = deps.machine;
@@ -21612,7 +21660,7 @@ async function drive2(o, flags, deps, until, beat) {
21612
21660
  var SEAT_CHANGED_EXIT2 = 3;
21613
21661
  function engineRunner2(o, flags) {
21614
21662
  const runnerFor = o.runnerFor ?? runnerFor2;
21615
- return runnerFor(flags.runner, guardGit(o.exec ?? defaultExec), {
21663
+ return runnerFor(flags.runner, logTimeouts(guardGit(o.exec ?? defaultExec), o.log ?? console.log), {
21616
21664
  ...flags.runnerCmd ? { command: flags.runnerCmd } : {}
21617
21665
  });
21618
21666
  }
@@ -23508,7 +23556,7 @@ function contextFor(deps) {
23508
23556
  ...src,
23509
23557
  fetchFn: deps.fetchFn,
23510
23558
  timeoutMs: deps.timeoutMs,
23511
- version: "0.1.52"
23559
+ version: "0.1.53"
23512
23560
  }), deps.home, now) : null;
23513
23561
  return {
23514
23562
  channel: "code",
@@ -26095,14 +26143,17 @@ async function sledOff(client, key, json, log, error) {
26095
26143
  return 0;
26096
26144
  }
26097
26145
  const k = typeof r.json?.key === "string" ? r.json.key : key;
26146
+ const why = typeof r.json?.why === "string" && r.json.why.trim() ? r.json.why : null;
26098
26147
  if (r.json?.changed === false) {
26099
- log(`${k} was not on a sled`);
26100
- return 0;
26148
+ error(why ?? `${k} was not on a sled`);
26149
+ return 1;
26101
26150
  }
26102
26151
  log(`${k} is off your sled — back in the pool`);
26103
26152
  const running = typeof r.json?.running === "number" ? r.json.running : 0;
26104
26153
  if (running > 0)
26105
26154
  log(`${running} running node${running === 1 ? "" : "s"} stopped`);
26155
+ if (why)
26156
+ log(why);
26106
26157
  return 0;
26107
26158
  }
26108
26159
 
@@ -27623,7 +27674,7 @@ var realPidAlive4 = (pid) => {
27623
27674
  };
27624
27675
  function machineClient(home, env) {
27625
27676
  const src = meterSource(home, undefined, { env });
27626
- return src ? fetchClient({ ...src, version: "0.1.52" }) : null;
27677
+ return src ? fetchClient({ ...src, version: "0.1.53" }) : null;
27627
27678
  }
27628
27679
  async function noteAnswer2(home, a, now) {
27629
27680
  try {
@@ -28093,15 +28144,29 @@ var readConfig2 = (home) => {
28093
28144
 
28094
28145
  // src/runners/exec.ts
28095
28146
  import { spawn as spawn3, spawnSync as spawnSync10 } from "node:child_process";
28147
+ var EXEC_TIMEOUT_MS2 = 2 * 60000;
28148
+ var EXEC_TIMEOUT_STATUS2 = 124;
28149
+ var timedOut2 = (r) => r.error?.code === "ETIMEDOUT";
28096
28150
  var defaultExec4 = (cmd, args, o) => {
28151
+ const timeoutMs = o?.timeoutMs ?? EXEC_TIMEOUT_MS2;
28097
28152
  const r = spawnSync10(cmd, [...args], {
28098
28153
  cwd: o?.cwd,
28099
28154
  input: o?.input,
28100
28155
  env: o?.env ? { ...process.env, ...o.env } : undefined,
28101
28156
  encoding: "utf8",
28102
28157
  shell: false,
28103
- maxBuffer: 16 * 1024 * 1024
28158
+ maxBuffer: 16 * 1024 * 1024,
28159
+ timeout: timeoutMs,
28160
+ killSignal: "SIGKILL"
28104
28161
  });
28162
+ if (timedOut2(r)) {
28163
+ return {
28164
+ status: EXEC_TIMEOUT_STATUS2,
28165
+ stdout: r.stdout ?? "",
28166
+ stderr: `${cmd}: timed out after ${timeoutMs / 1000}s and was killed${r.stderr ? `
28167
+ ${r.stderr}` : ""}`
28168
+ };
28169
+ }
28105
28170
  if (r.error) {
28106
28171
  return { status: 127, stdout: r.stdout ?? "", stderr: `${cmd}: ${r.error.message}` };
28107
28172
  }
@@ -28469,7 +28534,7 @@ var main = async (argv) => {
28469
28534
  now: Date.now(),
28470
28535
  stderr: (line) => console.error(line),
28471
28536
  isTTY: !!process.stderr.isTTY,
28472
- version: "0.1.52"
28537
+ version: "0.1.53"
28473
28538
  });
28474
28539
  switch (cmd) {
28475
28540
  case "update":
@@ -28702,7 +28767,7 @@ ${renderHelpText(argv[1])}`);
28702
28767
  case "version":
28703
28768
  case "--version":
28704
28769
  case "-v":
28705
- console.log("sleds 0.1.52");
28770
+ console.log("sleds 0.1.53");
28706
28771
  return 0;
28707
28772
  default:
28708
28773
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
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",