@sleds/cli 0.1.43 → 0.1.45

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 +1086 -444
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -21,6 +21,9 @@ var p = (input, output, cacheWrite, cacheWrite1h, cacheRead) => ({ input, output
21
21
  }), OPENAI_PRICES, byLength = (t) => Object.keys(t).sort((a, b) => b.length - a.length), ANTHROPIC_KEYS, OPENAI_KEYS;
22
22
  var init_pricing = __esm(() => {
23
23
  ANTHROPIC_PRICES = {
24
+ "claude-opus-5-5": p(4, 20, 5, 8, 0.2),
25
+ "claude-sonnet-5-5": p(2, 10, 2.5, 4, 0.1),
26
+ "claude-haiku-5-5": p(0.1, 0.5, 0.125, 0.2, 0.01),
24
27
  "claude-fable-5-1": p(10, 50, 12.5, 20, 0.25),
25
28
  "claude-mythos-5-1": p(10, 50, 12.5, 20, 0.25),
26
29
  "claude-fable-5": p(10, 50, 12.5, 20, 1),
@@ -794,10 +797,17 @@ var init_verbs = __esm(() => {
794
797
  {
795
798
  id: "update",
796
799
  group: "meter",
797
- want: "Change an item — its title, why, done, description, due day, what it sits under, or what it touches",
798
- mcp: "update {project, ask?, why?, done?, smallest?, touches?, demand?, description?, due?, parent?, repos?, sensitivity?}",
800
+ want: "Change an item — its title, why, done, worth, description, due day, what it sits under, or what it touches",
801
+ mcp: "update {project, ask?, why?, done?, smallest?, touches?, demand?, worth?, carry?, description?, due?, parent?, repos?, sensitivity?}",
799
802
  app: "Work › the item › click a field",
800
- how: "The filer, an approver or an admin changes an item from chat or by clicking the field in the app; every change says who and through what (via Claude · Cowork, via ChatGPT, in the app) and the record keeps before and after. A change to the ask, done or smallest marks the band stale with a Re-size at hand — never re-sized silently. A mirrored ticket is the tracker's word and is not changed here."
803
+ how: "The filer, an approver or an admin changes an item from chat or by clicking the field in the app; every change says who and through what (via Claude · Cowork, via ChatGPT, in the app) and the record keeps before and after. A change to the ask, done or smallest marks the band stale with a Re-size at hand — never re-sized silently. Worth is the person's word in their words ('high · Acme renewal', '$5k–20k'; null clears it); on an epic, `carry: true` puts it on each child that has none, each child's record signed the same way. A mirrored ticket is the tracker's word and is not changed here."
804
+ },
805
+ {
806
+ id: "reopen",
807
+ group: "meter",
808
+ want: "Put back an item that landed by mistake",
809
+ mcp: "reopen {project, reason}",
810
+ how: "SL-1819. For an item a skipped node or a wrong actual landed. It goes back on the sled it was on — or to ready on no sled, when that sled is gone from the org — the false actual is cleared and the band stands. A reopen always says why; the why goes on the item's record. The filer, an approver or an admin. Only a landed item reopens."
801
811
  },
802
812
  {
803
813
  id: "archive",
@@ -1085,7 +1095,19 @@ var init_verbs = __esm(() => {
1085
1095
 
1086
1096
  // ../../packages/shared/src/intake/forms.ts
1087
1097
  function norm(label) {
1088
- return label.replace(/[*_`~]/g, "").replace(/\(s\)/gi, "s").replace(/[:?]+\s*$/, "").replace(/\s+/g, " ").trim().toLowerCase();
1098
+ return label.replace(/[*_`~]/g, "").replace(/\(s\)/gi, "s").replace(/[:?]+\s*$/, "").replace(/,\s*including\b.*$/i, "").replace(/\s+/g, " ").trim().toLowerCase();
1099
+ }
1100
+ function plain(s) {
1101
+ return s.replace(/[*_`~]+/g, "").replace(/\s+/g, " ").trim();
1102
+ }
1103
+ function titleOf(s, max = 80) {
1104
+ const t = plain(s);
1105
+ const first = /^(.+?[.!?])(\s|$)/.exec(t)?.[1] ?? t;
1106
+ if (first.length <= max)
1107
+ return first;
1108
+ const cut = first.slice(0, max);
1109
+ const sp = cut.lastIndexOf(" ");
1110
+ return `${(sp > max / 2 ? cut.slice(0, sp) : cut).replace(/[,;:\s]+$/, "")}…`;
1089
1111
  }
1090
1112
  function readForm(text) {
1091
1113
  const lines = text.split(/\r?\n/);
@@ -1097,67 +1119,84 @@ function readForm(text) {
1097
1119
  const line = raw.trim();
1098
1120
  if (!line)
1099
1121
  continue;
1122
+ const bold = /^\*[^*]{2,80}\*:?$/.test(line);
1123
+ const whole = line.length <= 80 ? slotOf(def, line) : null;
1100
1124
  const inline = /^\**([^:*]{2,60}?)\**\s*:\s*(.+)$/.exec(line);
1101
- const whole = norm(line);
1102
- const slotWhole = def.labels[whole];
1103
- const slotInline = inline ? def.labels[norm(inline[1] ?? "")] : undefined;
1104
- if (slotWhole) {
1105
- cur = { label: line.replace(/[*_]/g, "").replace(/:\s*$/, ""), slot: slotWhole, value: [] };
1125
+ const slotInline = inline ? slotOf(def, inline[1] ?? "") : null;
1126
+ if (whole && (bold || !inline)) {
1127
+ cur = { label: plain(norm(line)), slot: whole, value: [] };
1106
1128
  found.push(cur);
1107
1129
  known++;
1108
1130
  } else if (slotInline && inline) {
1109
- cur = { label: (inline[1] ?? "").trim(), slot: slotInline, value: [inline[2] ?? ""] };
1131
+ cur = { label: plain(norm(inline[1] ?? "")), slot: slotInline, value: [inline[2] ?? ""] };
1110
1132
  found.push(cur);
1111
1133
  known++;
1112
- } else if (/^\*[^*]{2,60}\*:?$/.test(line) && cur) {
1113
- cur = { label: line.replace(/[*:]/g, "").trim(), slot: null, value: [] };
1134
+ } else if (bold && cur) {
1135
+ cur = { label: plain(line.replace(/:$/, "")), slot: null, value: [] };
1114
1136
  found.push(cur);
1115
1137
  } else if (cur)
1116
1138
  cur.value.push(line);
1117
1139
  }
1118
1140
  if (known < def.min)
1119
1141
  continue;
1120
- const val = (f) => f.value.join(" ").trim();
1121
- const of = (slot) => found.filter((f) => f.slot === slot && val(f)).map((f) => ({ label: f.label, v: val(f) }));
1122
- const ask = of("ask")[0]?.v;
1123
- const who = of("for").map((x) => x.v);
1124
- const extra = found.filter((f) => f.slot === null && val(f)).map((f) => `${f.label}: ${val(f)}`);
1125
- const why = [who.length > 0 ? `For ${who.join(", ")}.` : "", ask ?? "", ...extra].filter(Boolean).join(" ");
1126
- const touches = of("touches").map((x) => `${x.label} ${x.v}`).join(" · ");
1127
- const n = of("demand")[0]?.v;
1128
- const demand = n ? /^\d+$/.test(n) ? `${n} clients affected` : n : undefined;
1129
- const rev = of("revenue")[0]?.v;
1142
+ const val = (f) => plain(f.value.join(" "));
1143
+ const one = (slot) => found.find((f) => f.slot === slot && val(f));
1144
+ const ask = one("ask");
1145
+ const users = one("users");
1146
+ const sid = one("sid");
1147
+ const loc = one("location");
1148
+ const clients = one("clients");
1149
+ const rev = one("revenue");
1150
+ const n = clients ? val(clients) : "";
1151
+ const clientsText = n ? /^\d+$/.test(n) ? `${n} client${n === "1" ? "" : "s"} affected` : `clients affected: ${n}` : "";
1152
+ const r = rev ? val(rev) : "";
1153
+ const revenue = YES.test(r) ? true : NO.test(r) ? false : undefined;
1154
+ const revText = revenue === true ? "Impacting revenue" : revenue === false ? "No revenue impact" : "";
1155
+ const unknown = found.filter((f) => f.slot === null && val(f)).map((f) => `${f.label}: ${val(f)}`);
1156
+ const who = [
1157
+ users ? `Users: ${val(users)}` : "",
1158
+ sid ? `SID: ${val(sid)}` : "",
1159
+ loc ? `Location ${val(loc)}` : "",
1160
+ ...unknown
1161
+ ].filter(Boolean);
1162
+ const touches = who.join(" · ");
1163
+ const demand = [clientsText, revText].filter(Boolean).join(" · ");
1164
+ const details = [...who, clientsText, revText].filter(Boolean).join(" · ");
1130
1165
  return {
1131
1166
  form: def.name,
1132
- ...ask ? { ask } : {},
1133
- ...why ? { why } : {},
1167
+ ...ask ? { ask: val(ask) } : {},
1168
+ ...details ? { details } : {},
1134
1169
  ...touches ? { touches } : {},
1135
1170
  ...demand ? { demand } : {},
1136
- ...rev && YES.test(rev) ? { worth: "high · impacting revenue" } : {}
1171
+ ...revenue !== undefined ? { revenue } : {}
1137
1172
  };
1138
1173
  }
1139
1174
  return null;
1140
1175
  }
1141
- var FORMS, YES;
1176
+ var FORMS, YES, NO, slotOf = (def, label) => {
1177
+ const n = norm(label);
1178
+ for (const [re, slot] of def.labels)
1179
+ if (re.test(n))
1180
+ return slot;
1181
+ return null;
1182
+ };
1142
1183
  var init_forms = __esm(() => {
1143
1184
  FORMS = [
1144
1185
  {
1145
1186
  name: "Create Tech Support Ticket",
1146
- labels: {
1147
- "users experiencing the issue": "for",
1148
- "user experiencing the issue": "for",
1149
- sids: "touches",
1150
- sid: "touches",
1151
- "location ids": "touches",
1152
- "location id": "touches",
1153
- "issue description": "ask",
1154
- "number of clients affected": "demand",
1155
- "is the issue currently impacting revenue": "revenue"
1156
- },
1187
+ labels: [
1188
+ [/^users? experiencing\b/, "users"],
1189
+ [/^sids?\b/, "sid"],
1190
+ [/^location ids?\b/, "location"],
1191
+ [/^issue description\b/, "ask"],
1192
+ [/^(?:number of )?clients? affected\b|^number of clients\b/, "clients"],
1193
+ [/\bimpacting revenue\b|\brevenue impact\b/, "revenue"]
1194
+ ],
1157
1195
  min: 3
1158
1196
  }
1159
1197
  ];
1160
1198
  YES = /^(yes|y|yep|true|definitely|it is)\b/i;
1199
+ NO = /^(no|n|nope|false|not)\b/i;
1161
1200
  });
1162
1201
 
1163
1202
  // ../../packages/shared/src/intake/brief.ts
@@ -1235,13 +1274,14 @@ function draftBrief(input) {
1235
1274
  const form = readForm(text);
1236
1275
  const parsed = sections(text);
1237
1276
  const preamble = parsed.preamble;
1238
- const byKey = form ? { ...parsed.byKey, why: form.why, touches: form.touches, demand: form.demand } : parsed.byKey;
1239
- const askText = a.ask?.trim() || (form?.ask ? askFrom(form.ask, form.ask) : askFrom(text, preamble));
1277
+ const byKey = form ? { touches: form.touches, demand: form.demand } : parsed.byKey;
1278
+ const fromText = form?.ask ? form.ask : askFrom(text, preamble);
1279
+ const askText = a.ask?.trim() || (fromText ? titleOf(fromText) : undefined);
1240
1280
  if (!askText)
1241
1281
  return null;
1242
1282
  const ask = a.ask?.trim() ? { text: clip(a.ask, MAX_ASK), from: "filer" } : { text: clip(askText, MAX_ASK), from: "artifact" };
1243
1283
  const pick = (k) => field(a[k], "filer") ?? field(byKey[k], "artifact");
1244
- const worth = worthFromAnswer(a.worth) ?? parseWorth(text) ?? worthFromAnswer(form?.worth) ?? undefined;
1284
+ const worth = worthFromAnswer(a.worth) ?? parseWorth(text) ?? undefined;
1245
1285
  const sources = [...new Set((input.links ?? []).map((l) => l.trim()).filter(Boolean))].slice(0, MAX_SOURCES);
1246
1286
  return {
1247
1287
  ask,
@@ -1370,11 +1410,11 @@ var init_brief = __esm(() => {
1370
1410
  init_worth();
1371
1411
  init_forms();
1372
1412
  QUESTIONS = {
1373
- why: "Who is it for, and why now?",
1374
- done: "What does done look like, in your words?",
1375
- smallest: "What is the smallest version still worth doing?",
1376
- worth: "What is it worth to the business — a customer, a number, or high / medium / low?",
1377
- touches: "What does it touch — which product areas or systems, and where in the product would it live?",
1413
+ why: "Who is this for, and why does it matter now?",
1414
+ done: "How will we know it's done?",
1415
+ smallest: "What's the smallest version that would still help?",
1416
+ worth: "How important is this: high, medium, or low? If a specific customer is waiting on it, tell me who.",
1417
+ touches: "Which part of the product is this about?",
1378
1418
  demand: "How many people have asked for this, or how often does it come up?"
1379
1419
  };
1380
1420
  REQUIRED_TO_FILE = ["why", "done"];
@@ -1635,7 +1675,8 @@ function intakePlan(g) {
1635
1675
  card: g.card ? [...g.card] : base.card,
1636
1676
  limits: [...g.limits ?? []],
1637
1677
  followed: [...g.followed ?? []],
1638
- text: g.text ?? ""
1678
+ text: g.text ?? "",
1679
+ ...g.references ? { references: g.references } : {}
1639
1680
  };
1640
1681
  }
1641
1682
  function planOpen(b, plan, extra = {}) {
@@ -1667,11 +1708,14 @@ var init_intake = __esm(() => {
1667
1708
  ORDER = ["why", "done", "smallest", "worth", "touches", "demand"];
1668
1709
  DEFAULT_CARD = CARD_FIELDS.filter((f) => f !== "product map details");
1669
1710
  FIELD_WORDS = [
1670
- ["why", /\bwho is it for\b|\bwhy now\b|\bwho (?:hits|needs|asked)\b|\bwhy\b/i],
1671
- ["done", /\bdone\b|\blook like\b|\bsuccess\b|\bacceptance\b/i],
1711
+ ["why", /\bwho is (?:it|this) for\b|\bwhy now\b|\bwho (?:hits|needs|asked)\b|\bwhy\b/i],
1712
+ ["done", /\bdone\b|\blook like\b|\bsuccess\b|\bacceptance\b|\bis fixed\b|\bit'?s fixed\b/i],
1672
1713
  ["smallest", /\bsmallest\b|\bminimum\b|\bmvp\b|\bsmaller version\b/i],
1673
- ["worth", /\bworth\b|\bvalue to\b|\bthe business\b|\brevenue\b/i],
1674
- ["touches", /\btouch(?:es)?\b|\bwhich (?:product|system|area)s?\b|\bwhere in the product\b/i],
1714
+ ["worth", /\bworth\b|\bvalue to\b|\bthe business\b|\brevenue\b|\bhow important\b/i],
1715
+ [
1716
+ "touches",
1717
+ /\btouch(?:es)?\b|\bwhich (?:product|system|area)s?\b|\bwhere in the product\b|\bwhich part of the product\b/i
1718
+ ],
1675
1719
  ["demand", /\bhow many\b|\bhow often\b|\bcome up\b|\basked for this\b/i]
1676
1720
  ];
1677
1721
  });
@@ -2042,7 +2086,7 @@ ${state}`)}, fillcolor="${fill}", color="${border}"${style}];`);
2042
2086
  `)}
2043
2087
  `;
2044
2088
  }
2045
- var ROUTE_APPROACH_MAX = 2000, PLAN_APPROACH_MAX = 1200, PLAN_LIST_MAX = 8, PLAN_LINE_MAX = 240, CONTROL, MAX_NODES = 24, DEPTH_WARN = 4, norm2 = (s) => s.trim().toUpperCase(), AREA_ROOTS, NOT_AREAS, sameRepo = (a, b) => a.length === 0 || b.length === 0 || a.some((r) => b.includes(r)), DOT_COLORS, ROUTE_DRAFT_RULE = "Draft the route: PR-sized nodes, file-disjoint where they run in parallel, each with a title, the files or areas it touches, the repo, and the nodes it waits on (by key: A, B, C…). Prefer wide waves over chains deeper than four. Make the tough calls once, so each node's session follows them: an `approach` paragraph for the whole route, and on each node a `plan` — its approach, what it `provides` that later nodes rely on (names, shapes, files), what it `expects` from the nodes it waits on, and the `doneChecks` that say it landed. Show the graph and the plans as text and ask for a yes; file nothing before it. Then `route {q, approach, nodes: [{key, title, touches, repos, blockedBy, plan: {approach, provides, expects, doneChecks}}], confirm: true}`.";
2089
+ var ROUTE_APPROACH_MAX = 2000, PLAN_APPROACH_MAX = 1200, PLAN_LIST_MAX = 8, PLAN_LINE_MAX = 240, CONTROL, MAX_NODES = 24, DEPTH_WARN = 4, norm2 = (s) => s.trim().toUpperCase(), AREA_ROOTS, NOT_AREAS, sameRepo = (a, b) => a.length === 0 || b.length === 0 || a.some((r) => b.includes(r)), DOT_COLORS, ROUTE_DRAFT_RULE = "Draft the route: PR-sized nodes, file-disjoint where they run in parallel, each with a title, the files or areas it touches, the repo, and the nodes it waits on (by key: A, B, C…). Prefer wide waves over chains deeper than four. Make the tough calls once, so each node's session follows them: an `approach` paragraph for the whole route, and on each node a `plan` — its approach, what it `provides` that later nodes rely on (names, shapes, files), what it `expects` from the nodes it waits on, and the `doneChecks` that say it landed. Show the graph and the plans as text and ask for a yes; file nothing before it. An item with children takes no nodes: its children are the work, and their order lives in their blockedBy. Then `route {q, approach, nodes: [{key, title, touches, repos, blockedBy, plan: {approach, provides, expects, doneChecks}}], confirm: true}`.";
2046
2090
  var init_route = __esm(() => {
2047
2091
  CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
2048
2092
  AREA_ROOTS = new Set(["apps", "packages", "services", "libs", "crates", "modules"]);
@@ -2114,6 +2158,7 @@ var init_annotations = __esm(() => {
2114
2158
  finish: add("Land the work"),
2115
2159
  buddy: add("Pair with a buddy"),
2116
2160
  pickup: add("Pick up a hand-off"),
2161
+ reopen: add("Reopen an item", true),
2117
2162
  update: change("Change an item", true),
2118
2163
  archive: change("Archive an item", true),
2119
2164
  withdraw: change("Withdraw a hand-off", true)
@@ -3154,6 +3199,13 @@ async function updateTool(ctx, args) {
3154
3199
  patch.repos = args.repos.split(/[,\s]+/).filter(Boolean);
3155
3200
  if (Array.isArray(args.relations))
3156
3201
  patch.relations = args.relations.filter(isObject4).map((r) => ({ key: str5(r.key)?.trim() ?? "", how: str5(r.how) ?? "relates" })).filter((r) => r.key && /^(blockedBy|blocks|relates|duplicates)$/.test(r.how));
3202
+ if (args.worth === null)
3203
+ patch.worth = null;
3204
+ else if (typeof args.worth === "string" && args.worth.trim()) {
3205
+ patch.worth = args.worth.trim();
3206
+ if (args.carry === true)
3207
+ patch.carry = true;
3208
+ }
3157
3209
  if (args.sensitivity === null)
3158
3210
  patch.sensitivity = null;
3159
3211
  else if (typeof args.sensitivity === "string" && /^(public|internal|restricted)$/.test(args.sensitivity)) {
@@ -3163,7 +3215,7 @@ async function updateTool(ctx, args) {
3163
3215
  }
3164
3216
  if (Object.keys(patch).length === 0)
3165
3217
  return {
3166
- text: "Nothing to change. Pass the fields that change: ask, why, done, smallest, touches, demand, description, due, parent, repos, relations.",
3218
+ text: "Nothing to change. Pass the fields that change: ask, why, done, smallest, touches, demand, worth, description, due, parent, repos, relations.",
3167
3219
  isError: true
3168
3220
  };
3169
3221
  const res = await postSaying(ctx, "/proxy/item/update", { project, ...patch }, "Could not update the item.");
@@ -3171,13 +3223,48 @@ async function updateTool(ctx, args) {
3171
3223
  return res.fail;
3172
3224
  const changed = Array.isArray(res.json.changed) ? res.json.changed.filter((x) => typeof x === "string") : [];
3173
3225
  const key = str5(res.json.key) ?? project;
3226
+ const carried = carriedLine(res.json.carried);
3174
3227
  if (changed.length === 0)
3175
- return { text: `${key}: nothing differed from what is on the item.` };
3228
+ return { text: `${key}: nothing differed from what is on the item.${carried}` };
3176
3229
  const stale = res.json.sizeStale === true;
3177
3230
  return {
3178
- text: `${key} updated: ${changed.join(", ")}.${stale ? " The band was sized before this change — say so, and offer to re-size (estimate with the same source)." : ""}`
3231
+ text: `${key} updated: ${changed.join(", ")}.${carried}${stale ? " The band was sized before this change — say so, and offer to re-size (estimate with the same source)." : ""}`
3232
+ };
3233
+ }
3234
+ async function reopenTool(ctx, args) {
3235
+ if (!ctx.client)
3236
+ return NOT_SET_UP;
3237
+ const project = str5(args.project)?.trim();
3238
+ if (!project)
3239
+ return { text: "Which item? Pass project: its key (QW-142).", isError: true };
3240
+ const reason = str5(args.reason)?.trim();
3241
+ if (!reason)
3242
+ return {
3243
+ text: "Say why it is reopened — pass reason, one line. It goes on the item's record.",
3244
+ isError: true
3245
+ };
3246
+ const res = await postSaying(ctx, "/proxy/reopen", { project, reason }, "Could not reopen the item.");
3247
+ if ("fail" in res)
3248
+ return res.fail;
3249
+ const key = str5(res.json.key) ?? project;
3250
+ const sled = str5(res.json.sled);
3251
+ return {
3252
+ text: `Reopened ${key} — ${sled ? `back on ${sled}'s sled` : "back to ready, on no sled"}. The false actual is cleared; the band stands.`
3179
3253
  };
3180
3254
  }
3255
+ function carriedLine(c) {
3256
+ if (!isObject4(c))
3257
+ return "";
3258
+ const set = Array.isArray(c.set) ? c.set.filter((x) => typeof x === "string") : [];
3259
+ const valued = num3(c.valued) ?? 0;
3260
+ const skipped = num3(c.skipped) ?? 0;
3261
+ const parts = [
3262
+ set.length ? `worth set on ${set.length} ${set.length === 1 ? "child" : "children"} (${set.join(", ")})` : "no child took it",
3263
+ ...valued ? [`${valued} already had a worth and kept it`] : [],
3264
+ ...skipped ? [`${skipped} you may not change (a mirror, or not yours) — left as they were`] : []
3265
+ ];
3266
+ return ` ${parts.join("; ").replace(/^./, (ch) => ch.toUpperCase())}.`;
3267
+ }
3181
3268
  async function archiveTool(ctx, args) {
3182
3269
  if (!ctx.client)
3183
3270
  return NOT_SET_UP;
@@ -3473,9 +3560,21 @@ async function itemTool(ctx, args) {
3473
3560
  text: `Nothing on the sled answers to "${q}". Try the key (QW-142) or a word from the title with status.`,
3474
3561
  isError: true
3475
3562
  };
3476
- const md = str5(r.markdown) ?? "";
3477
3563
  if (asFile)
3478
- return { text: md, structured: { key: str5(r.key) ?? q, export: true } };
3564
+ return { text: str5(r.markdown) ?? "", structured: { key: str5(r.key) ?? q, export: true } };
3565
+ const envs = isObject4(r.whereLive) && Array.isArray(r.whereLive.envs) ? r.whereLive.envs : [];
3566
+ const live = envs.filter(isObject4).filter((e) => {
3567
+ const s = isObject4(e.state) ? e.state : null;
3568
+ return !(s && (s.kind === "no_code" || s.state === "no_code"));
3569
+ }).map((e) => `- ${str5(e.name) ?? str5(e.slug) ?? "?"}: ${str5(e.words) ?? ""}`);
3570
+ const body = str5(r.markdown) ?? "";
3571
+ const section = live.length ? `## Where it's live
3572
+ ${live.join(`
3573
+ `)}
3574
+
3575
+ ` : "";
3576
+ const title = body.match(/^# [^\n]*\n+/)?.[0] ?? "";
3577
+ const md = `${title}${section}${body.slice(title.length)}`;
3479
3578
  const readiness = isObject4(r.readiness) ? r.readiness : null;
3480
3579
  const line = readiness ? str5(readiness.line) : undefined;
3481
3580
  const state = readiness ? str5(readiness.state) : undefined;
@@ -4216,6 +4315,8 @@ async function callTool(name, args, ctx) {
4216
4315
  return attachTool(ctx, args);
4217
4316
  case "update":
4218
4317
  return updateTool(ctx, args);
4318
+ case "reopen":
4319
+ return reopenTool(ctx, args);
4219
4320
  case "archive":
4220
4321
  return archiveTool(ctx, args);
4221
4322
  case "comment":
@@ -4621,7 +4722,7 @@ var init_server = __esm(() => {
4621
4722
  },
4622
4723
  {
4623
4724
  name: "route",
4624
- description: "The route — an item's internal order as PR-sized nodes with blockers (spec 26). With only `q`: the route as filed (each node's state, what it waits on, the ready set, where each run can be watched), or, when none is filed, the pack and the drafting rule so you draft one. Drafting: PR-sized nodes, file-disjoint where they run in parallel, each with a title, the files or areas it touches, the repo, and the nodes it waits on by key (A, B, C…); prefer wide waves over chains deeper than four. Make the tough calls once: an `approach` for the route and a `plan` on each node (its approach, what it provides to later nodes, what it expects from the nodes it waits on, its done-checks), so each node's session follows them instead of re-planning. Show the graph and the plans as text and ask for a yes; file nothing before it. Then call again with `nodes` and `confirm: true`. Re-drafting shows what changed; a node with a run or a PR is never removed. Ready is computed from landed blockers, never set. With nobody to ask (a drafting session an engine started, spec 26 §5.1): call with `nodes` and `propose: true` instead — the route is held on the item for a person's yes and nothing is filed; then stop. A read of an item with a proposal shows it as drafted, not filed.",
4725
+ description: "The route — an item's internal order as PR-sized nodes with blockers (spec 26). With only `q`: the route as filed (each node's state, what it waits on, the ready set, where each run can be watched), or, when none is filed, the pack and the drafting rule so you draft one. Drafting: PR-sized nodes, file-disjoint where they run in parallel, each with a title, the files or areas it touches, the repo, and the nodes it waits on by key (A, B, C…); prefer wide waves over chains deeper than four. Make the tough calls once: an `approach` for the route and a `plan` on each node (its approach, what it provides to later nodes, what it expects from the nodes it waits on, its done-checks), so each node's session follows them instead of re-planning. Show the graph and the plans as text and ask for a yes; file nothing before it. Then call again with `nodes` and `confirm: true`. An item with children is refused nodes — its children are the work: set their order with blockedBy on them. The item lands only when every child has landed. Re-drafting shows what changed; a node with a run or a PR is never removed. Ready is computed from landed blockers, never set. With nobody to ask (a drafting session an engine started, spec 26 §5.1): call with `nodes` and `propose: true` instead — the route is held on the item for a person's yes and nothing is filed; then stop. A read of an item with a proposal shows it as drafted, not filed.",
4625
4726
  inputSchema: {
4626
4727
  type: "object",
4627
4728
  properties: {
@@ -4854,7 +4955,7 @@ var init_server = __esm(() => {
4854
4955
  },
4855
4956
  {
4856
4957
  name: "update",
4857
- description: "Change an item on the sled — its title (`ask`), why, done, smallest, touches, demand, its description (markdown: the prose the brief does not hold), its due day, which item it sits under, the repo(s) it lands in, or its sensitivity (public · internal · restricted). Pass the item's key and only the fields that change. The filer, an approver or an admin may; a mirrored ticket cannot be changed here. A change to the ask, done or smallest marks the band stale — say so, and offer to re-size. Everything you write is signed as coming through you.",
4958
+ description: "Change an item on the sled — its title (`ask`), why, done, smallest, touches, demand, its worth, its description (markdown: the prose the brief does not hold), its due day, which item it sits under, the repo(s) it lands in, or its sensitivity (public · internal · restricted). Pass the item's key and only the fields that change. Worth is the person's word — never guess it; on an epic, `carry: true` puts it on the children that have none. The filer, an approver or an admin may; a mirrored ticket cannot be changed here. A change to the ask, done or smallest marks the band stale — say so, and offer to re-size. Everything you write is signed as coming through you.",
4858
4959
  inputSchema: {
4859
4960
  type: "object",
4860
4961
  properties: {
@@ -4865,6 +4966,14 @@ var init_server = __esm(() => {
4865
4966
  smallest: { type: "string" },
4866
4967
  touches: { type: "string" },
4867
4968
  demand: { type: "string" },
4969
+ worth: {
4970
+ type: ["string", "null"],
4971
+ description: "What the person said it is worth: a level (low · medium · high · critical), dollars, a customer — e.g. 'high · Acme renewal', '$5k–20k'. Null clears it."
4972
+ },
4973
+ carry: {
4974
+ type: "boolean",
4975
+ description: "With worth: put it on this item's children that have no worth yet, each on its own record. A child with a worth keeps it."
4976
+ },
4868
4977
  description: {
4869
4978
  type: "string",
4870
4979
  description: "Markdown. Links and images inline. Empty clears it."
@@ -4901,6 +5010,21 @@ var init_server = __esm(() => {
4901
5010
  required: ["project"]
4902
5011
  }
4903
5012
  },
5013
+ {
5014
+ name: "reopen",
5015
+ description: "Put back an item that landed by mistake — a skipped node closed its route, or an actual was sent against the wrong item. It goes back on the sled it was on (or to ready, when that sled is gone), the false actual is cleared and the band stands. Pass the item's key and why: a reopen always says why, and the why goes on the item's record. The filer, an approver or an admin may. Only a landed item can be reopened.",
5016
+ inputSchema: {
5017
+ type: "object",
5018
+ properties: {
5019
+ project: { type: "string", description: "The item's key — QW-142 — or its project key." },
5020
+ reason: {
5021
+ type: "string",
5022
+ description: "Why it is reopened, one line — e.g. 'a skipped node landed it; six children are open'."
5023
+ }
5024
+ },
5025
+ required: ["project", "reason"]
5026
+ }
5027
+ },
4904
5028
  {
4905
5029
  name: "item",
4906
5030
  description: "Read one item whole, as one markdown document — the pack: everything an engineer or an agent needs to finish the work from this document alone. Eight parts: the ask, where the code is, how to run it (the repo's setup profile), how to know it is done (the gate), how to ship it (the playbook), what the org expects, what is known already (the Field Book — notes from earlier runs on this item, its feature and what landed beside it, the questions, the thread), and the rules of the road. Carries its readiness and whether it is packable. Pass the item's key (QW-142) or its title. Call this before building from an item, and before `check`.",
@@ -5323,9 +5447,33 @@ var init_outcome = __esm(() => {
5323
5447
  });
5324
5448
 
5325
5449
  // ../../packages/shared/src/slack/mention.ts
5450
+ var MENTION_HELP;
5326
5451
  var init_mention = __esm(() => {
5327
5452
  init_intake();
5328
5453
  init_brief();
5454
+ MENTION_HELP = [
5455
+ "Here's what I can do:",
5456
+ "• *@SLEDS file this*: turn the message above into a request for engineering",
5457
+ "• *@SLEDS status*: see where something stands",
5458
+ "You can also ask me to *watch* or *mute* a thread, or *run QW-123*."
5459
+ ].join(`
5460
+ `);
5461
+ });
5462
+
5463
+ // ../../packages/shared/src/slack/copy.ts
5464
+ var init_copy = __esm(() => {
5465
+ init_intake();
5466
+ init_forms();
5467
+ init_mention();
5468
+ init_mention();
5469
+ });
5470
+
5471
+ // ../../packages/shared/src/slack/agent.ts
5472
+ var init_agent = __esm(() => {
5473
+ init_intake();
5474
+ init_brief();
5475
+ init_copy();
5476
+ init_mention();
5329
5477
  });
5330
5478
 
5331
5479
  // ../../packages/shared/src/item/productHits.ts
@@ -5627,6 +5775,24 @@ var init_fieldMap = __esm(() => {
5627
5775
  var SEPT_30 = "/whats-new/2026-09-30", WHATS_NEW;
5628
5776
  var init_whatsNew = __esm(() => {
5629
5777
  WHATS_NEW = [
5778
+ {
5779
+ date: "2026-10-22",
5780
+ title: "Policy is an org setting",
5781
+ lede: "There is one policy for the whole org, so it now lives under Settings beside Platform keys and AI Guidance, instead of on each workspace's rail where it read as a workspace's own. Nothing in it changed; old links redirect.",
5782
+ sections: [
5783
+ {
5784
+ title: "Where it went",
5785
+ where: "Settings › Policy",
5786
+ body: "Open your profile menu › Settings, then Policy. The page is the same: every rule as one sentence, saved as a new version. The rail's Plan group keeps Seats."
5787
+ }
5788
+ ],
5789
+ also: [
5790
+ {
5791
+ label: "Next",
5792
+ text: "Each workspace's Jira projects get their own pages for fields and statuses, and the org's Jira page keeps only the connection, the webhook and what SLEDS may write."
5793
+ }
5794
+ ]
5795
+ },
5630
5796
  {
5631
5797
  date: "2026-10-21",
5632
5798
  title: "The Slack thread is the item's discussion",
@@ -5650,18 +5816,28 @@ var init_whatsNew = __esm(() => {
5650
5816
  {
5651
5817
  title: "Forms are read as fields",
5652
5818
  where: "Slack, filing from a workflow post",
5653
- body: "A support ticket form posted by a Slack workflow is read field by field: the issue description is the ask, who is affected and why go to the why, the IDs to what it touches, the count of clients to demand, and a yes on revenue to a high worth. The draft then asks only what the form did not say."
5819
+ body: "A support ticket form posted by a Slack workflow is read field by field: the issue description is the title, the users and IDs go on one details line, and the count of clients and the revenue answer to demand. SLEDS then asks only what the form did not say."
5820
+ },
5821
+ {
5822
+ title: "Replies you can read",
5823
+ where: "Slack, filing from a thread",
5824
+ body: `One summary message per thread, kept up to date as you answer, with the title in plain words and the form's other fields on one line. Answer several questions at once, one per line, or say which one you're answering: "done means the sync works", "high priority". Say "send it to Core" to change the team before filing. A yes with something missing asks for just that one thing.`
5825
+ },
5826
+ {
5827
+ title: "A model can write the replies",
5828
+ where: "Policy › Intake",
5829
+ body: "Turn on Slack intake replies written by a model, Claude Haiku 5.5 by default, and it answers in the thread the way the channel's AI Guidance says: what it understood, a workaround when there is one, and the questions that matter for this kind of issue. It spends your own Anthropic key from Platform keys, up to a daily cap you set. Filing still waits for a yes, and it never sets the priority."
5654
5830
  },
5655
5831
  {
5656
5832
  title: "Which workspace, picked",
5657
5833
  where: "Slack, filing",
5658
- body: "A channel tied to a workspace files there. With several workspaces and none tied to the channel, SLEDS asks which, as a numbered pick: reply with the number. The Jira key is never typed — the Filed as reply shows it."
5834
+ body: "A channel tied to a workspace files there. With several workspaces and none tied to the channel, SLEDS asks which team: reply with the number or the name. The Jira key is never typed — the Filed as reply shows it."
5659
5835
  }
5660
5836
  ],
5661
5837
  also: [
5662
5838
  {
5663
5839
  label: "Fixed",
5664
- text: "An ask filed from Slack now creates its Jira ticket in the workspace it was filed on, and the reply names the key or says why none was made."
5840
+ text: "An ask filed from Slack now creates its Jira ticket in the workspace it was filed on, and a stated priority sets the ticket's priority. A form posted by a Slack workflow is now read field by field."
5665
5841
  }
5666
5842
  ]
5667
5843
  },
@@ -6297,6 +6473,56 @@ var init_pack2 = __esm(() => {
6297
6473
  init_prHeader();
6298
6474
  });
6299
6475
 
6476
+ // ../../packages/shared/src/deploys/deploys.ts
6477
+ function parseDeployBody(json) {
6478
+ if (!json || typeof json !== "object" || Array.isArray(json))
6479
+ return bad("body must be a JSON object");
6480
+ const j = json;
6481
+ const repo = typeof j.repo === "string" ? j.repo.trim().toLowerCase() : "";
6482
+ if (!REPO_RE.test(repo))
6483
+ return bad("repo must be owner/name");
6484
+ const sha = typeof j.sha === "string" ? j.sha.trim().toLowerCase() : "";
6485
+ if (!SHA_RE.test(sha))
6486
+ return bad("sha must be 7–40 hex characters");
6487
+ const environment = typeof j.environment === "string" ? j.environment.trim() : "";
6488
+ if (!ENV_SLUG_RE.test(environment))
6489
+ return bad("environment must be a slug: lower-case letters, digits, - and _");
6490
+ const body = { repo, sha, environment };
6491
+ if (j.service !== undefined && j.service !== null) {
6492
+ const service = typeof j.service === "string" ? j.service.trim() : "";
6493
+ if (!SERVICE_RE.test(service))
6494
+ return bad("service must be a short name");
6495
+ body.service = service;
6496
+ }
6497
+ if (j.at !== undefined && j.at !== null) {
6498
+ const at = typeof j.at === "string" ? Date.parse(j.at) : typeof j.at === "number" ? j.at : Number.NaN;
6499
+ if (!Number.isFinite(at))
6500
+ return bad("at must be an ISO time");
6501
+ body.at = at;
6502
+ }
6503
+ if (j.url !== undefined && j.url !== null) {
6504
+ let ok = false;
6505
+ if (typeof j.url === "string") {
6506
+ try {
6507
+ ok = new URL(j.url).protocol === "https:";
6508
+ } catch {
6509
+ ok = false;
6510
+ }
6511
+ }
6512
+ if (!ok)
6513
+ return bad("url must be an https link");
6514
+ body.url = j.url;
6515
+ }
6516
+ return { ok: true, body };
6517
+ }
6518
+ var REPO_RE, SHA_RE, ENV_SLUG_RE, SERVICE_RE, bad = (error) => ({ ok: false, error });
6519
+ var init_deploys = __esm(() => {
6520
+ REPO_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+$/;
6521
+ SHA_RE = /^[0-9a-f]{7,40}$/;
6522
+ ENV_SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,39}$/;
6523
+ SERVICE_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/;
6524
+ });
6525
+
6300
6526
  // ../../packages/shared/src/newrelic/newrelic.ts
6301
6527
  var init_newrelic = () => {};
6302
6528
 
@@ -6696,7 +6922,7 @@ function nodePrompt(o) {
6696
6922
  ...claim.pack.includes(PR_HEADER_OPEN) ? [
6697
6923
  `- The PR body starts with the pack's PR header block (\`${PR_HEADER_OPEN}\` … \`${PR_HEADER_CLOSE}\`, in its part 5), above everything else. The pack wrote it before your work existed: when you make a mock (a screenshot or an artifact) or write a doc, put its link on that part's line in place of \`None\` — not only lower in the body (SL-1746). When you edit the body later, keep it on top.`
6698
6924
  ] : [],
6699
- "- Run the repo's own gate before you push. Never merge: a person (or the musher, when the attachment says so) does.",
6925
+ o.gate ? `- Run the repo's gate before you push: exactly \`${o.gate}\`, as given. Never wrap it — no \`$?\`, \`$(...)\`, \`bash -c\` or \`; echo\` around it — or it stops at a permission dialog; read its result from the exit and the output. Never merge: a person (or the musher, when the attachment says so) does.` : "- Run the repo's own gate before you push. Never merge: a person (or the musher, when the attachment says so) does.",
6700
6926
  "- Stay inside this node's scope. Work another node needs goes in the PR description, not in this branch.",
6701
6927
  `- 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.`,
6702
6928
  "- The approved brief and this route are the go-ahead: a person said yes to them before this session started. Build, commit, push and open the PR; a park is for a question the record does not settle, not for a go-ahead to build or push.",
@@ -7138,6 +7364,9 @@ var init_effort = __esm(() => {
7138
7364
  EFFORTS = ["low", "medium", "high", "xhigh", "max"];
7139
7365
  });
7140
7366
 
7367
+ // ../../packages/shared/src/workspaceLook.ts
7368
+ var init_workspaceLook = () => {};
7369
+
7141
7370
  // src/settings.ts
7142
7371
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
7143
7372
  import { dirname, join, resolve as resolve2 } from "node:path";
@@ -7682,6 +7911,8 @@ var init_src = __esm(() => {
7682
7911
  init_profile2();
7683
7912
  init_outcome();
7684
7913
  init_mention();
7914
+ init_agent();
7915
+ init_copy();
7685
7916
  init_productHits();
7686
7917
  init_model();
7687
7918
  init_parse();
@@ -7696,6 +7927,7 @@ var init_src = __esm(() => {
7696
7927
  init_cheatSheet();
7697
7928
  init_pack2();
7698
7929
  init_flags();
7930
+ init_deploys();
7699
7931
  init_sentry();
7700
7932
  init_newrelic();
7701
7933
  init_musher();
@@ -7706,6 +7938,7 @@ var init_src = __esm(() => {
7706
7938
  init_otlp();
7707
7939
  init_session();
7708
7940
  init_effort();
7941
+ init_workspaceLook();
7709
7942
  });
7710
7943
 
7711
7944
  // src/engine/json.ts
@@ -8383,6 +8616,8 @@ init_due();
8383
8616
  init_profile2();
8384
8617
  init_outcome();
8385
8618
  init_mention();
8619
+ init_agent();
8620
+ init_copy();
8386
8621
  init_productHits();
8387
8622
  init_model();
8388
8623
  init_parse();
@@ -8397,6 +8632,7 @@ init_whatsNew();
8397
8632
  init_cheatSheet();
8398
8633
  init_pack2();
8399
8634
  init_flags();
8635
+ init_deploys();
8400
8636
  init_sentry();
8401
8637
  init_newrelic();
8402
8638
  init_musher();
@@ -8407,6 +8643,7 @@ init_engine();
8407
8643
  init_otlp();
8408
8644
  init_session();
8409
8645
  init_effort();
8646
+ init_workspaceLook();
8410
8647
 
8411
8648
  // src/agentMcp.ts
8412
8649
  import { execFileSync } from "node:child_process";
@@ -9561,24 +9798,142 @@ var defer = async (o) => {
9561
9798
  return 0;
9562
9799
  };
9563
9800
 
9801
+ // src/commands/deploy.ts
9802
+ init_src();
9803
+ import { execFileSync as execFileSync2 } from "node:child_process";
9804
+ var DEPLOYS_URL = "https://hooks.sleds.ai/deploys";
9805
+ var USAGE = "usage: sleds deploy --env <slug> [--service <name>] [--sha <sha>] [--repo <owner/name>]";
9806
+ var FLAGS = ["env", "service", "sha", "repo"];
9807
+ var set = (x) => x?.trim() ? x.trim() : undefined;
9808
+ function repoFromRemote(remote) {
9809
+ const m = /^(?:[a-z+]+:\/\/)?(?:[^@/]+@)?[^/:]+[:/](.+?)(?:\.git)?\/?$/i.exec(remote.trim());
9810
+ const parts = m?.[1]?.split("/").filter(Boolean) ?? [];
9811
+ if (parts.length < 2)
9812
+ return null;
9813
+ return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`.toLowerCase();
9814
+ }
9815
+ function resolveDeploy(argv, env, git) {
9816
+ const flags = {};
9817
+ for (let i = 0;i < argv.length; i++) {
9818
+ const a = argv[i] ?? "";
9819
+ const m = /^--([a-z]+)(?:=(.*))?$/.exec(a);
9820
+ const name = m?.[1];
9821
+ if (!m || !name || !FLAGS.includes(name))
9822
+ return { ok: false, error: `unknown argument ${a}` };
9823
+ const value = m[2] ?? argv[++i];
9824
+ if (!set(value))
9825
+ return { ok: false, error: `--${name} needs a value` };
9826
+ flags[name] = value?.trim();
9827
+ }
9828
+ if (!flags.env)
9829
+ return { ok: false, error: "--env <slug> is required" };
9830
+ const vercelRepo = set(env.VERCEL_GIT_REPO_OWNER) && set(env.VERCEL_GIT_REPO_SLUG) ? `${set(env.VERCEL_GIT_REPO_OWNER)}/${set(env.VERCEL_GIT_REPO_SLUG)}` : undefined;
9831
+ const remote = () => {
9832
+ const url = git(["remote", "get-url", "origin"]);
9833
+ return url && repoFromRemote(url) || undefined;
9834
+ };
9835
+ const repo = flags.repo ?? set(env.GITHUB_REPOSITORY) ?? vercelRepo ?? remote();
9836
+ if (!repo)
9837
+ return { ok: false, error: "no repo: pass --repo owner/name (no origin remote)" };
9838
+ const sha = flags.sha ?? set(env.SOURCE_VERSION) ?? set(env.HEROKU_SLUG_COMMIT) ?? set(env.VERCEL_GIT_COMMIT_SHA) ?? set(env.GITHUB_SHA) ?? set(git(["rev-parse", "HEAD"]) ?? undefined);
9839
+ if (!sha)
9840
+ return { ok: false, error: "no sha: pass --sha (not a git checkout)" };
9841
+ return parseDeployBody({
9842
+ repo,
9843
+ sha,
9844
+ environment: flags.env,
9845
+ ...flags.service ? { service: flags.service } : {}
9846
+ });
9847
+ }
9848
+ var defaultGit = (args) => {
9849
+ try {
9850
+ return execFileSync2("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
9851
+ } catch {
9852
+ return null;
9853
+ }
9854
+ };
9855
+ var defaultPost = async (url, token, body) => {
9856
+ try {
9857
+ const res = await fetch(url, {
9858
+ method: "POST",
9859
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
9860
+ body: JSON.stringify(body),
9861
+ signal: AbortSignal.timeout(15000)
9862
+ });
9863
+ let json = null;
9864
+ try {
9865
+ json = await res.json();
9866
+ } catch {}
9867
+ return { status: res.status, json };
9868
+ } catch (e) {
9869
+ return { error: e instanceof Error ? e.message : String(e) };
9870
+ }
9871
+ };
9872
+ function deployLine(body, json) {
9873
+ const where = `${body.repo}${body.service ? ` (${body.service})` : ""} → ${body.environment}`;
9874
+ if (typeof json.ignored === "string")
9875
+ return `ignored: ${json.ignored}`;
9876
+ if (json.duplicate)
9877
+ return `${where}: already at ${body.sha.slice(0, 7)}`;
9878
+ const prs = Array.isArray(json.newlyDeployed) ? json.newlyDeployed : [];
9879
+ const list = prs.map((p) => `#${p.number}`).join(", ");
9880
+ if (json.rollback)
9881
+ return `${where}: rollback to ${body.sha.slice(0, 7)}${list ? ` (carries ${list} again)` : ""}`;
9882
+ return prs.length ? `${where}: newly deployed ${list}` : `${where}: ${body.sha.slice(0, 7)} recorded, no newly deployed PRs`;
9883
+ }
9884
+ var deployCmd = async (o) => {
9885
+ const log = o.log ?? console.log;
9886
+ const error = o.error ?? console.error;
9887
+ const env = o.env ?? process.env;
9888
+ const token = set(env.SLEDS_DEPLOY_TOKEN);
9889
+ if (!token) {
9890
+ error("SLEDS_DEPLOY_TOKEN is not set — mint one in Settings › Deploys");
9891
+ return 2;
9892
+ }
9893
+ const resolved = resolveDeploy(o.argv, env, o.git ?? defaultGit);
9894
+ if (!resolved.ok) {
9895
+ error(resolved.error);
9896
+ error(USAGE);
9897
+ return 2;
9898
+ }
9899
+ const body = resolved.body;
9900
+ const url = set(env.SLEDS_DEPLOYS_URL) ?? DEPLOYS_URL;
9901
+ const r = await (o.post ?? defaultPost)(url, token, body);
9902
+ if ("error" in r) {
9903
+ error(`warning: SLEDS unreachable, deploy not recorded — ${r.error}`);
9904
+ return 0;
9905
+ }
9906
+ if (r.status === 200 && r.json) {
9907
+ log(deployLine(body, r.json));
9908
+ return 0;
9909
+ }
9910
+ const why = typeof r.json?.error === "string" ? r.json.error : `HTTP ${r.status}`;
9911
+ if (r.status >= 400 && r.status < 500) {
9912
+ error(r.status === 401 ? `SLEDS refused the deploy token (${why})` : why);
9913
+ return 1;
9914
+ }
9915
+ error(`warning: SLEDS answered ${why}, deploy not recorded`);
9916
+ return 0;
9917
+ };
9918
+
9564
9919
  // src/commands/doctor.ts
9565
9920
  init_src();
9566
- import { execFileSync as execFileSync3, spawnSync as spawnSync5 } from "node:child_process";
9921
+ import { execFileSync as execFileSync4, spawnSync as spawnSync5 } from "node:child_process";
9567
9922
  import { randomBytes } from "node:crypto";
9568
9923
  import {
9569
- existsSync as existsSync16,
9570
- lstatSync,
9924
+ existsSync as existsSync17,
9925
+ lstatSync as lstatSync2,
9571
9926
  mkdtempSync,
9572
9927
  readFileSync as readFileSync17,
9573
- readlinkSync,
9928
+ readlinkSync as readlinkSync2,
9574
9929
  rmSync as rmSync8,
9575
9930
  writeFileSync as writeFileSync17
9576
9931
  } from "node:fs";
9577
9932
  import { tmpdir } from "node:os";
9578
- import { join as join19, resolve as resolve8 } from "node:path";
9933
+ import { join as join20, resolve as resolve9 } from "node:path";
9579
9934
 
9580
9935
  // src/agentMcp.ts
9581
- import { execFileSync as execFileSync2 } from "node:child_process";
9936
+ import { execFileSync as execFileSync3 } from "node:child_process";
9582
9937
  import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "node:fs";
9583
9938
  import { dirname as dirname8, join as join10, resolve as resolve6 } from "node:path";
9584
9939
  init_settings();
@@ -9646,7 +10001,7 @@ function applyCodexMcp2(toml, home, env = process.env) {
9646
10001
  `;
9647
10002
  }
9648
10003
  var defaultRunClaude2 = (args) => {
9649
- execFileSync2("claude", args, { stdio: ["ignore", "ignore", "pipe"], timeout: 20000 });
10004
+ execFileSync3("claude", args, { stdio: ["ignore", "ignore", "pipe"], timeout: 20000 });
9650
10005
  };
9651
10006
  async function offerMcp2(o) {
9652
10007
  const env = o.env ?? process.env;
@@ -10101,6 +10456,7 @@ var agentShellCommand = (o) => {
10101
10456
  const effort = o.effort ? ` --effort ${shq2(o.effort)}` : "";
10102
10457
  return `${sandboxedAgent("claude", o.sandbox, o.mainCheckout)}${hookSettingsFlag(o)}${mcpConfigFlag(o)} --permission-mode ${shq2(o.permissionMode)}${model}${effort} ${prompt}`;
10103
10458
  };
10459
+ var execLine = (cmd) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(cmd) ? `exec env ${cmd}` : `exec ${cmd}`;
10104
10460
  var sandboxedAgent = (agent, sandbox, mainCheckout) => {
10105
10461
  const kind = sandboxAgentKind(agent);
10106
10462
  const line = kind === "claude" ? `${agent}${claudeAddDirFlag(mainCheckout)}` : agent;
@@ -10537,6 +10893,215 @@ function cmuxRunner(exec, deps = {}) {
10537
10893
  };
10538
10894
  }
10539
10895
 
10896
+ // src/runners/orca.ts
10897
+ import { existsSync as existsSync12, lstatSync, readlinkSync, writeFileSync as writeFileSync11 } from "node:fs";
10898
+ import { join as join14, resolve as resolve7 } from "node:path";
10899
+ var obj = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
10900
+ var str8 = (v) => typeof v === "string" && v.length > 0 ? v : null;
10901
+ var parseOrcaJson = (stdout) => {
10902
+ try {
10903
+ return obj(JSON.parse(stdout));
10904
+ } catch {
10905
+ return null;
10906
+ }
10907
+ };
10908
+ var orcaError = (env) => {
10909
+ if (!env || env.ok !== false)
10910
+ return null;
10911
+ const e = obj(env.error);
10912
+ const message = str8(e?.message) ?? "Orca refused the call";
10913
+ const code = str8(e?.code);
10914
+ return code ? `${message} (${code})` : message;
10915
+ };
10916
+ var parseOrcaWorktree = (env) => {
10917
+ const result = obj(env.result) ?? {};
10918
+ return str8(obj(result.worktree)?.path) ?? str8(result.worktreePath) ?? str8(result.path);
10919
+ };
10920
+ var parseOrcaBranch = (env) => {
10921
+ const wt = obj(obj(env.result)?.worktree);
10922
+ const branch = str8(wt?.branch)?.replace(/^refs\/heads\//, "");
10923
+ const head = str8(wt?.head);
10924
+ return branch && head ? { branch, head } : null;
10925
+ };
10926
+ var parseOrcaTerminal = (env) => {
10927
+ const result = obj(env.result) ?? {};
10928
+ return str8(obj(result.terminal)?.handle) ?? str8(result.handle) ?? str8(result.terminalHandle) ?? null;
10929
+ };
10930
+ var orcaLauncher = (o) => `#!/bin/sh
10931
+ # sleds engine: ${o.name}
10932
+ ${execLine(agentShellCommand(o))}
10933
+ `;
10934
+ var orcaCommand = (launcher) => `sh ${shq2(launcher)}; exit`;
10935
+ var GONE = /exit|clos|gone|dead|stopp|kill/i;
10936
+ var orcaTerminalLive = (status, stdout) => {
10937
+ const env = parseOrcaJson(stdout);
10938
+ if (str8(obj(env?.error)?.code) === "runtime_unavailable")
10939
+ return true;
10940
+ if (status !== 0 || !env || env.ok === false)
10941
+ return false;
10942
+ const t = obj(obj(env.result)?.terminal) ?? obj(env.result) ?? {};
10943
+ if (typeof t.status === "string" && GONE.test(t.status))
10944
+ return false;
10945
+ if (t.exited === true || t.alive === false)
10946
+ return false;
10947
+ if (typeof t.exitCode === "number")
10948
+ return false;
10949
+ if (obj(t.exitCause))
10950
+ return false;
10951
+ if (t.orphaned === true && t.writable === false)
10952
+ return false;
10953
+ return true;
10954
+ };
10955
+ var realFs = { lstat: lstatSync, readlink: readlinkSync, exists: existsSync12 };
10956
+ var ORCA_LINK_FIX = "reinstall Orca's shell command from Orca (it links `orca` onto your PATH)";
10957
+ function danglingOrca(path, fs) {
10958
+ for (const dir of path.split(":").filter(Boolean)) {
10959
+ const link = join14(dir, "orca");
10960
+ try {
10961
+ if (!fs.lstat(link).isSymbolicLink())
10962
+ return null;
10963
+ const target = resolve7(dir, fs.readlink(link));
10964
+ if (!fs.exists(target))
10965
+ return { link, target };
10966
+ return null;
10967
+ } catch {}
10968
+ }
10969
+ return null;
10970
+ }
10971
+ function orcaPreflight(exec, path, fs = realFs) {
10972
+ let r;
10973
+ try {
10974
+ r = exec("orca", ["--version"], { env: { PATH: path } });
10975
+ } catch (e) {
10976
+ r = { status: 127, stdout: "", stderr: e instanceof Error ? e.message : String(e) };
10977
+ }
10978
+ if (r.status === 0) {
10979
+ return { ok: true, version: r.stdout.trim().split(`
10980
+ `)[0]?.trim() || "orca" };
10981
+ }
10982
+ const dangling = danglingOrca(path, fs);
10983
+ if (dangling) {
10984
+ return {
10985
+ ok: false,
10986
+ why: `${dangling.link} links to ${dangling.target}, which is gone`,
10987
+ fix: ORCA_LINK_FIX
10988
+ };
10989
+ }
10990
+ if (!path.split(":").includes("/usr/bin")) {
10991
+ return { ok: false, why: "the engine's PATH has no /usr/bin", fix: "sleds engine install" };
10992
+ }
10993
+ if (r.status === 127) {
10994
+ return { ok: false, why: "not installed (no `orca` on the PATH)", fix: ORCA_LINK_FIX };
10995
+ }
10996
+ const said = `${r.stderr}
10997
+ ${r.stdout}`.split(`
10998
+ `).map((l) => l.trim()).find(Boolean);
10999
+ return {
11000
+ ok: false,
11001
+ why: `\`orca --version\` failed: ${said ?? `exit ${r.status}`}`,
11002
+ fix: "reinstall Orca, or its shell command from Orca"
11003
+ };
11004
+ }
11005
+ var launcherPath2 = (promptFile) => `${promptFile.replace(/\.md$/, "")}.orca.sh`;
11006
+ function orcaRunner(exec, deps = {}) {
11007
+ const writeFile = deps.writeFile ?? ((p, t) => writeFileSync11(p, t, { mode: 384 }));
11008
+ const orca = (args, what) => {
11009
+ const r = exec("orca", args);
11010
+ const env = parseOrcaJson(r.stdout);
11011
+ const refused = orcaError(env);
11012
+ if (refused)
11013
+ throw new Error(`${what}: ${refused}`);
11014
+ if (r.status !== 0 || !env) {
11015
+ throw new Error(`${what} failed: ${r.stderr.trim() || r.stdout.trim() || `exit ${r.status}`}`);
11016
+ }
11017
+ return env;
11018
+ };
11019
+ return {
11020
+ name: "orca",
11021
+ makesWorktree: true,
11022
+ readsSession: true,
11023
+ start(o) {
11024
+ const created = orca([
11025
+ "worktree",
11026
+ "create",
11027
+ "--repo",
11028
+ `path:${o.repoPath}`,
11029
+ "--name",
11030
+ o.name,
11031
+ "--base-branch",
11032
+ `origin/${o.base ?? "main"}`,
11033
+ "--no-parent",
11034
+ "--json"
11035
+ ], `orca worktree create ${o.name}`);
11036
+ const cwd = parseOrcaWorktree(created);
11037
+ if (!cwd) {
11038
+ throw new Error(`orca worktree create ${o.name} returned no worktree path; the agent was not started`);
11039
+ }
11040
+ const launcher = launcherPath2(o.promptFile);
11041
+ writeFile(launcher, orcaLauncher(o));
11042
+ const shown = orca([
11043
+ "terminal",
11044
+ "create",
11045
+ "--worktree",
11046
+ `path:${cwd}`,
11047
+ "--title",
11048
+ o.name,
11049
+ "--command",
11050
+ orcaCommand(launcher),
11051
+ "--json"
11052
+ ], `orca terminal create ${o.name} (worktree ${cwd})`);
11053
+ const terminal = parseOrcaTerminal(shown);
11054
+ if (!terminal) {
11055
+ throw new Error(`orca terminal create ${o.name} started in ${cwd} but returned no terminal handle`);
11056
+ }
11057
+ const own = parseOrcaBranch(created);
11058
+ const h = { runner: "orca", id: terminal, cwd, ...own ? { own } : {} };
11059
+ return h;
11060
+ },
11061
+ status(h) {
11062
+ const r = exec("orca", ["terminal", "show", "--terminal", h.id, "--json"]);
11063
+ return orcaTerminalLive(r.status, r.stdout) ? "running" : "exited";
11064
+ },
11065
+ read(h) {
11066
+ const r = exec("orca", ["terminal", "read", "--terminal", h.id, "--limit", "200"]);
11067
+ return r.status === 0 ? r.stdout : "";
11068
+ },
11069
+ send(h, text) {
11070
+ const r = exec("orca", ["terminal", "send", "--terminal", h.id, "--text", text, "--enter"]);
11071
+ if (r.status !== 0) {
11072
+ throw new Error(`orca terminal send ${h.id} failed: ${r.stderr.trim() || `exit ${r.status}`}`);
11073
+ }
11074
+ },
11075
+ stop(h) {
11076
+ exec("orca", ["terminal", "close", "--terminal", h.id]);
11077
+ },
11078
+ watchHint(h) {
11079
+ return `orca terminal show --terminal ${h.id}`;
11080
+ },
11081
+ probe() {
11082
+ try {
11083
+ const p = orcaPreflight(exec, process.env.PATH ?? "", deps.fs);
11084
+ return p.ok ? null : `orca: ${p.why} — ${p.fix}`;
11085
+ } catch {
11086
+ return "orca: not installed";
11087
+ }
11088
+ },
11089
+ removeWorktree(h, repoPath) {
11090
+ orca(["worktree", "rm", "--worktree", `path:${h.cwd}`, "--json"], `orca worktree rm ${h.cwd}`);
11091
+ if (!h.own)
11092
+ return;
11093
+ const ref = `refs/heads/${h.own.branch}`;
11094
+ const r = exec("git", ["-C", repoPath, "update-ref", "-d", ref, h.own.head]);
11095
+ if (r.status === 0)
11096
+ return;
11097
+ const still = exec("git", ["-C", repoPath, "rev-parse", "--verify", "--quiet", ref]);
11098
+ if (still.status !== 0)
11099
+ return;
11100
+ return `kept Orca's branch ${h.own.branch}: ${r.stderr.trim() || `exit ${r.status}`}`;
11101
+ }
11102
+ };
11103
+ }
11104
+
10540
11105
  // src/commands/doctor.ts
10541
11106
  init_settings();
10542
11107
 
@@ -10554,7 +11119,7 @@ import {
10554
11119
  writeFileSync as writeFileSync13
10555
11120
  } from "node:fs";
10556
11121
  import { hostname as hostname2 } from "node:os";
10557
- import { join as join15, resolve as resolve7 } from "node:path";
11122
+ import { join as join16, resolve as resolve8 } from "node:path";
10558
11123
 
10559
11124
  // src/engine/beat.ts
10560
11125
  var BEAT_EVERY_MS = 60000;
@@ -10566,7 +11131,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
10566
11131
  name: flags.name,
10567
11132
  os: host.os ?? process.platform,
10568
11133
  runner: flags.runner,
10569
- version: host.version ?? "0.1.43" ?? "dev",
11134
+ version: host.version ?? "0.1.45" ?? "dev",
10570
11135
  running: live.length,
10571
11136
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
10572
11137
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -11359,7 +11924,7 @@ function jiraFromEnv(epic, gate, env = process.env) {
11359
11924
  init_src();
11360
11925
 
11361
11926
  // src/commands/permissionEvent.ts
11362
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync10, readFileSync as readFileSync10, renameSync as renameSync6, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
11927
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync10, readFileSync as readFileSync10, renameSync as renameSync6, rmSync as rmSync3, writeFileSync as writeFileSync12 } from "node:fs";
11363
11928
 
11364
11929
  // src/commands/seatEvent.ts
11365
11930
  init_config();
@@ -11559,7 +12124,7 @@ function keysIn(states, state) {
11559
12124
  }
11560
12125
  function statesLanded(states) {
11561
12126
  const all = Object.values(states);
11562
- return all.length > 0 && all.every((s) => s === "landed" || s === "skipped");
12127
+ return all.length > 0 && all.every((s) => s === "landed" || s === "skipped") && all.some((s) => s === "landed");
11563
12128
  }
11564
12129
  function armedTerms(armed) {
11565
12130
  if (!isObject8(armed))
@@ -11838,11 +12403,12 @@ function sledsSource(client, o) {
11838
12403
  async function draft(row, item, why) {
11839
12404
  const project = str6(row.project) ?? item;
11840
12405
  const pending = isObject8(row.route) && isObject8(row.route.redraft) ? row.route.redraft : null;
12406
+ const runner = typeof o.runner === "function" ? o.runner() : o.runner;
11841
12407
  const r = await client.postFull("/proxy/route", {
11842
12408
  project,
11843
12409
  draft: "take",
11844
12410
  engine: o.engine,
11845
- ...o.runner ? { runner: o.runner } : {},
12411
+ ...runner ? { runner } : {},
11846
12412
  ...o.leaseMin !== undefined ? { leaseMin: o.leaseMin } : {}
11847
12413
  });
11848
12414
  if (!r || r.status >= 300 || !r.json || r.json.ok === false) {
@@ -12370,10 +12936,10 @@ function providerFailure(screen) {
12370
12936
 
12371
12937
  // src/engine/worktree.ts
12372
12938
  init_src();
12373
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync11 } from "node:fs";
12374
- import { dirname as dirname11, isAbsolute, join as join14 } from "node:path";
12939
+ import { appendFileSync, existsSync as existsSync13, mkdirSync as mkdirSync11 } from "node:fs";
12940
+ import { dirname as dirname11, isAbsolute, join as join15 } from "node:path";
12375
12941
  function worktreePath(repoPath, name) {
12376
- return join14(repoPath, ".worktrees", name);
12942
+ return join15(repoPath, ".worktrees", name);
12377
12943
  }
12378
12944
  var fail = (what, r) => new Error(`${what} failed: ${(r.stderr || r.stdout).trim() || `exit ${r.status}`}`);
12379
12945
  function defaultBranch(exec, repoPath) {
@@ -12394,7 +12960,7 @@ function defaultBranch(exec, repoPath) {
12394
12960
  return "main";
12395
12961
  }
12396
12962
  var realWorktreeFs = {
12397
- exists: existsSync12,
12963
+ exists: existsSync13,
12398
12964
  append: (p, text) => {
12399
12965
  mkdirSync11(dirname11(p), { recursive: true });
12400
12966
  appendFileSync(p, text);
@@ -12408,7 +12974,7 @@ function ensureIgnored(exec, repoPath, fs = realWorktreeFs) {
12408
12974
  if (common.status !== 0)
12409
12975
  return;
12410
12976
  const dir = common.stdout.trim();
12411
- const exclude = join14(isAbsolute(dir) ? dir : join14(repoPath, dir), "info", "exclude");
12977
+ const exclude = join15(isAbsolute(dir) ? dir : join15(repoPath, dir), "info", "exclude");
12412
12978
  fs.append(exclude, `
12413
12979
  # sleds engine: one worktree per route node
12414
12980
  .worktrees/
@@ -12420,7 +12986,7 @@ function ensureWorktree(exec, repoPath, branch, path, base = "main", fs = realWo
12420
12986
  return path;
12421
12987
  }
12422
12988
  function makeWorktree(exec, repoPath, branch, path, base, fs, log) {
12423
- if (fs.exists(join14(path, ".git"))) {
12989
+ if (fs.exists(join15(path, ".git"))) {
12424
12990
  fastForwardUntouched(exec, path, base, log);
12425
12991
  return path;
12426
12992
  }
@@ -12493,13 +13059,13 @@ function installArgv(command) {
12493
13059
  return argv;
12494
13060
  }
12495
13061
  function lockfileInstall(path, exists) {
12496
- if (exists(join14(path, "pnpm-lock.yaml")))
13062
+ if (exists(join15(path, "pnpm-lock.yaml")))
12497
13063
  return ["pnpm", "install", "--frozen-lockfile"];
12498
- if (exists(join14(path, "bun.lock")) || exists(join14(path, "bun.lockb")))
13064
+ if (exists(join15(path, "bun.lock")) || exists(join15(path, "bun.lockb")))
12499
13065
  return ["bun", "install", "--frozen-lockfile"];
12500
- if (exists(join14(path, "yarn.lock")))
13066
+ if (exists(join15(path, "yarn.lock")))
12501
13067
  return ["yarn", "install", "--frozen-lockfile"];
12502
- if (exists(join14(path, "package-lock.json")))
13068
+ if (exists(join15(path, "package-lock.json")))
12503
13069
  return ["npm", "ci"];
12504
13070
  return null;
12505
13071
  }
@@ -12523,8 +13089,39 @@ function packInstallOf(pack, repo) {
12523
13089
  }
12524
13090
  return first;
12525
13091
  }
13092
+ function packGateOf(pack, repo) {
13093
+ let inDone = false;
13094
+ let first;
13095
+ const want = repo?.trim().toLowerCase();
13096
+ for (const line of pack.split(`
13097
+ `)) {
13098
+ if (/^##\s/.test(line)) {
13099
+ inDone = /^##\s+4\./.test(line);
13100
+ continue;
13101
+ }
13102
+ if (!inDone)
13103
+ continue;
13104
+ const m = line.match(/^\s*-\s*([^:`]+?):\s*`(.+)`\s*$/);
13105
+ if (!m?.[1] || !m[2])
13106
+ continue;
13107
+ const name = m[1].trim().toLowerCase();
13108
+ if (name === "done means" || name.startsWith("try it"))
13109
+ continue;
13110
+ if (want && name === want)
13111
+ return m[2];
13112
+ first ??= m[2];
13113
+ }
13114
+ return first;
13115
+ }
13116
+ function gateAllowRules(gate) {
13117
+ const whole = gate?.trim();
13118
+ if (!whole)
13119
+ return [];
13120
+ const parts = whole.split(/&&|\|\||;/).map((p) => p.trim()).filter(Boolean);
13121
+ return [...new Set([whole, ...parts])].map((c) => `Bash(${c})`);
13122
+ }
12526
13123
  function ensureInstalled(exec, path, fs, o = {}) {
12527
- if (!fs.exists(join14(path, "package.json")) || fs.exists(join14(path, "node_modules")))
13124
+ if (!fs.exists(join15(path, "package.json")) || fs.exists(join15(path, "node_modules")))
12528
13125
  return null;
12529
13126
  const argv = installArgv(o.command) ?? lockfileInstall(path, fs.exists);
12530
13127
  if (!argv)
@@ -12539,7 +13136,7 @@ function ensureInstalled(exec, path, fs, o = {}) {
12539
13136
  return { argv, ok };
12540
13137
  }
12541
13138
  function ensureDetachedWorktree(exec, repoPath, path, base = "main", fs = realWorktreeFs) {
12542
- if (fs.exists(join14(path, ".git")))
13139
+ if (fs.exists(join15(path, ".git")))
12543
13140
  return path;
12544
13141
  ensureIgnored(exec, repoPath, fs);
12545
13142
  const f = exec("git", ["-C", repoPath, "fetch", "origin", base]);
@@ -12615,17 +13212,17 @@ function fingerprint(text) {
12615
13212
  }
12616
13213
  return (h >>> 0).toString(16);
12617
13214
  }
12618
- function sandboxFor(d, name, cwd) {
13215
+ function sandboxFor(d, name, cwd, allow) {
12619
13216
  if (!d.sandbox)
12620
13217
  return {};
12621
13218
  if (!d.writeSandboxSettings)
12622
13219
  throw new Error("sessions are sandboxed, but nothing writes their settings — not started");
12623
- return { sandbox: d.writeSandboxSettings(name, cwd) };
13220
+ return { sandbox: d.writeSandboxSettings(name, cwd, allow) };
12624
13221
  }
12625
- function hooksFor(d, name) {
13222
+ function hooksFor(d, name, allow) {
12626
13223
  if (d.sandbox || !d.writeHookSettings)
12627
13224
  return {};
12628
- return { settingsFile: d.writeHookSettings(name) };
13225
+ return { settingsFile: d.writeHookSettings(name, allow) };
12629
13226
  }
12630
13227
  function mainCheckoutFor(d, cwd) {
12631
13228
  const main = d.mainCheckoutOf?.(cwd);
@@ -12686,6 +13283,8 @@ async function tick(deps) {
12686
13283
  deps.startBackoff = d.startBackoff;
12687
13284
  deps.providerHold = d.providerHold;
12688
13285
  deps.atOnce = d.atOnce;
13286
+ deps.runner = d.runner;
13287
+ deps.runnerRefused = d.runnerRefused;
12689
13288
  }
12690
13289
  removeEndedMcpConfigs(deps, made);
12691
13290
  }
@@ -12728,6 +13327,7 @@ async function tickOnce(d) {
12728
13327
  const changes = [];
12729
13328
  const decided = [];
12730
13329
  const save = () => d.writeState(state);
13330
+ await rereadRunner(d);
12731
13331
  probeRunner(d);
12732
13332
  if (d.startBackoff && --d.startBackoff.checks <= 0)
12733
13333
  d.startBackoff = undefined;
@@ -13080,6 +13680,8 @@ async function tickOnce(d) {
13080
13680
  const worktree = d.runner.makesWorktree ? repoPath : d.ensureWorktree(d.exec, repoPath, branch, worktreePath(repoPath, name), base, packInstallOf(claim.pack, claim.repos[0])) ?? worktreePath(repoPath, name);
13081
13681
  const prTitle = d.prTitleOf?.(claim);
13082
13682
  mcpConfig = engineMcpConfigFor(d, name, claim.runToken, at, worktree);
13683
+ const gate = packGateOf(claim.pack, claim.repos[0]);
13684
+ const allow = gateAllowRules(gate);
13083
13685
  const prompt = nodePrompt({
13084
13686
  claim,
13085
13687
  branch,
@@ -13088,6 +13690,7 @@ async function tickOnce(d) {
13088
13690
  sledsTools: d.source.name === "sleds",
13089
13691
  engineMcp: Boolean(mcpConfig),
13090
13692
  ...prTitle ? { prTitle } : {},
13693
+ ...gate ? { gate } : {},
13091
13694
  ...nodePlanContext(await briefsOf(claim.item), claim.node)
13092
13695
  });
13093
13696
  const promptFile = d.writePrompt(name, prompt);
@@ -13104,8 +13707,8 @@ async function tickOnce(d) {
13104
13707
  ...launch.effort && !launch.agent ? { effort: launch.effort } : {},
13105
13708
  ...launch.agent ? { agent: launch.agent } : {},
13106
13709
  ...mcpConfig ? { mcpConfig, ...d.strictMcp ? { strictMcp: true } : {} } : {},
13107
- ...sandboxFor(d, name, worktree),
13108
- ...hooksFor(d, name),
13710
+ ...sandboxFor(d, name, worktree, allow),
13711
+ ...hooksFor(d, name, allow),
13109
13712
  ...mainCheckoutFor(d, worktree),
13110
13713
  ...claim.group ? { group: claim.group } : {}
13111
13714
  };
@@ -13363,6 +13966,24 @@ function pressAnswer(d, runner, run, options, answer) {
13363
13966
  return false;
13364
13967
  }
13365
13968
  }
13969
+ function readFollowed(d, runner, run) {
13970
+ if (!runner.readsSession)
13971
+ return "";
13972
+ let shown = "";
13973
+ try {
13974
+ shown = runner.read(run.handle);
13975
+ } catch {
13976
+ shown = "";
13977
+ }
13978
+ const print = fingerprint(shown);
13979
+ if (run.seen !== print) {
13980
+ run.seen = print;
13981
+ run.seenAt = d.now();
13982
+ run.seenTicks = 0;
13983
+ } else
13984
+ run.seenTicks = (run.seenTicks ?? 0) + 1;
13985
+ return shown;
13986
+ }
13366
13987
  async function dropPermission(d, run, why) {
13367
13988
  const perms = permissionsOf(d.source);
13368
13989
  if (!run.permission || !perms || !run.runId)
@@ -13513,6 +14134,29 @@ async function rereadAtOnce(d) {
13513
14134
  d.atOnce = next;
13514
14135
  d.log(`at once now ${next}, from your engine profile`);
13515
14136
  }
14137
+ async function rereadRunner(d) {
14138
+ if (!d.rereadRunner)
14139
+ return;
14140
+ let next = null;
14141
+ try {
14142
+ next = await d.rereadRunner(profileOf2(d));
14143
+ } catch {}
14144
+ if (next === null)
14145
+ return;
14146
+ if (typeof next === "string") {
14147
+ if (d.runnerRefused !== next)
14148
+ d.log(next);
14149
+ d.runnerRefused = next;
14150
+ return;
14151
+ }
14152
+ d.runnerRefused = undefined;
14153
+ if (next === d.runner)
14154
+ return;
14155
+ const was = d.runner.name;
14156
+ d.runner = next;
14157
+ d.runnerHold = undefined;
14158
+ d.log(`runner now ${next.name}, from your engine profile — new sessions start on it; running ones stay on ${was}`);
14159
+ }
13516
14160
  async function rereadMachine(w, d) {
13517
14161
  if (!w.reread)
13518
14162
  return;
@@ -14164,11 +14808,13 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
14164
14808
  return true;
14165
14809
  await judgeLook(d, run, view);
14166
14810
  if (view.state === "MERGED") {
14811
+ await dropPermission(d, run, "the PR merged");
14167
14812
  const landed = await ripple(d, state, run, claim, runner, pr.url, finished, decided, view.head, view.mergeCommit);
14168
14813
  return !landed;
14169
14814
  }
14170
14815
  if (view.state === "CLOSED") {
14171
14816
  d.log(`${ref(run)}: ${pr.url} was closed without a merge — let go, its session stopped`);
14817
+ await dropPermission(d, run, "the PR closed without a merge");
14172
14818
  stopOpen(d, run, runner);
14173
14819
  return false;
14174
14820
  }
@@ -14188,6 +14834,8 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
14188
14834
  kind: "review",
14189
14835
  what: `${view.feedback - pr.feedback} new comment${view.feedback - pr.feedback === 1 ? "" : "s"}`
14190
14836
  });
14837
+ if (await settlePermission(d, runner, run, readFollowed(d, runner, run)))
14838
+ return true;
14191
14839
  const tell = (text, what) => {
14192
14840
  let alive = false;
14193
14841
  try {
@@ -15057,8 +15705,8 @@ function commandRunner(template, deps = {}) {
15057
15705
 
15058
15706
  // src/runners/conductor.ts
15059
15707
  import { readFileSync as readFileSync11 } from "node:fs";
15060
- var obj = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
15061
- var str8 = (v) => typeof v === "string" && v.length > 0 ? v : null;
15708
+ var obj2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
15709
+ var str9 = (v) => typeof v === "string" && v.length > 0 ? v : null;
15062
15710
  var CONDUCTOR_API = "https://api.conductor.build";
15063
15711
  var CLAUDE_MODELS = new Set([
15064
15712
  "fable-5-1",
@@ -15115,7 +15763,7 @@ var splitCurl = (stdout) => {
15115
15763
  const code = Number(stdout.slice(cut + 1).trim()) || 0;
15116
15764
  let body = null;
15117
15765
  try {
15118
- body = obj(JSON.parse(text));
15766
+ body = obj2(JSON.parse(text));
15119
15767
  } catch {
15120
15768
  body = null;
15121
15769
  }
@@ -15130,7 +15778,7 @@ var messageText = (content, depth = 0) => {
15130
15778
  return content.map((c) => messageText(c, depth + 1)).filter(Boolean).join(`
15131
15779
  `);
15132
15780
  }
15133
- const o = obj(content);
15781
+ const o = obj2(content);
15134
15782
  if (!o)
15135
15783
  return String(content);
15136
15784
  for (const k of ["text", "content", "message", "result"]) {
@@ -15138,12 +15786,12 @@ var messageText = (content, depth = 0) => {
15138
15786
  if (t)
15139
15787
  return t;
15140
15788
  }
15141
- return str8(o.type) ? `(${o.type}${str8(o.name) ? ` ${o.name}` : ""})` : "";
15789
+ return str9(o.type) ? `(${o.type}${str9(o.name) ? ` ${o.name}` : ""})` : "";
15142
15790
  };
15143
15791
  var renderTranscript = (messages, keep = 40, clip = 2000) => messages.slice(-keep).map((m) => {
15144
15792
  const t = messageText(m.content).trim();
15145
15793
  const body = t.length > clip ? `${t.slice(0, clip)}…` : t;
15146
- return body ? `[${str8(m.type) ?? "message"}] ${body}` : "";
15794
+ return body ? `[${str9(m.type) ?? "message"}] ${body}` : "";
15147
15795
  }).filter(Boolean).join(`
15148
15796
 
15149
15797
  `);
@@ -15173,7 +15821,7 @@ function conductorRunner(exec, deps = {}) {
15173
15821
  }
15174
15822
  const res = splitCurl(r.stdout);
15175
15823
  if (res.code < 200 || res.code >= 300 || !res.body) {
15176
- const why = str8(res.body?.userMessage) ?? str8(res.body?.message) ?? (res.text.trim().slice(0, 300) || "no body");
15824
+ const why = str9(res.body?.userMessage) ?? str9(res.body?.message) ?? (res.text.trim().slice(0, 300) || "no body");
15177
15825
  throw new Error(`conductor ${method} ${path}: HTTP ${res.code}: ${why}`);
15178
15826
  }
15179
15827
  return res.body;
@@ -15203,15 +15851,15 @@ function conductorRunner(exec, deps = {}) {
15203
15851
  ...effort ? { effort } : {},
15204
15852
  message: prompt
15205
15853
  });
15206
- const workspace = str8(created.workspaceId);
15207
- const session = str8(created.sessionId);
15854
+ const workspace = str9(created.workspaceId);
15855
+ const session = str9(created.sessionId);
15208
15856
  if (!workspace || !session) {
15209
15857
  throw new Error(`conductor: workspace create for ${o.name} returned no workspace or session id`);
15210
15858
  }
15211
- if (!obj(created.initialMessage)) {
15859
+ if (!obj2(created.initialMessage)) {
15212
15860
  api("POST", `/v0/sessions/${session}/messages`, { message: prompt });
15213
15861
  }
15214
- const link = str8(created.deepLink);
15862
+ const link = str9(created.deepLink);
15215
15863
  return {
15216
15864
  runner: "conductor",
15217
15865
  id: `${workspace}/${session}`,
@@ -15233,8 +15881,8 @@ function conductorRunner(exec, deps = {}) {
15233
15881
  let line = "";
15234
15882
  try {
15235
15883
  const s = api("GET", `/v0/sessions/${session}/status`);
15236
- state = str8(s.status) ?? "unknown";
15237
- const err = str8(s.lastError) ?? str8(s.errorMessage);
15884
+ state = str9(s.status) ?? "unknown";
15885
+ const err = str9(s.lastError) ?? str9(s.errorMessage);
15238
15886
  line = state === "working" ? `conductor session: working (checked ${new Date(now()).toISOString()})` : `conductor session: ${state}${err ? ` — ${err}` : ""}`;
15239
15887
  } catch (e) {
15240
15888
  line = `conductor session: status unread (${e instanceof Error ? e.message : String(e)})`;
@@ -15282,7 +15930,7 @@ var headlessScript = (o, log) => {
15282
15930
  ...o.model ? [`--model ${shq2(o.model)}`] : [],
15283
15931
  ...o.effort ? [`--effort ${shq2(o.effort)}`] : []
15284
15932
  ].join(" ");
15285
- return `exec ${cmd} > ${shq2(log)} 2>&1`;
15933
+ return `${execLine(cmd)} > ${shq2(log)} 2>&1`;
15286
15934
  };
15287
15935
  var transcriptTail = (log) => {
15288
15936
  const lines = log.trimEnd().split(`
@@ -15346,8 +15994,8 @@ function headlessRunner(deps = {}) {
15346
15994
  // src/runners/managed.ts
15347
15995
  init_src();
15348
15996
  import { readFileSync as readFileSync12 } from "node:fs";
15349
- var obj2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
15350
- var str9 = (v) => typeof v === "string" && v.length > 0 ? v : null;
15997
+ var obj3 = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
15998
+ var str10 = (v) => typeof v === "string" && v.length > 0 ? v : null;
15351
15999
  var ANTHROPIC_API = "https://api.anthropic.com";
15352
16000
  var MANAGED_BETA = "managed-agents-2026-04-01";
15353
16001
  var DEFAULT_BUDGET_USD = 25;
@@ -15422,7 +16070,7 @@ var lastIdle = (events) => {
15422
16070
  return null;
15423
16071
  };
15424
16072
  var pendingAsks = (events, idle) => {
15425
- const ids = obj2(idle?.stop_reason)?.event_ids;
16073
+ const ids = obj3(idle?.stop_reason)?.event_ids;
15426
16074
  if (!Array.isArray(ids))
15427
16075
  return [];
15428
16076
  const want = new Set(ids.filter((x) => typeof x === "string"));
@@ -15443,7 +16091,7 @@ var eventMessages = (events) => {
15443
16091
  case "agent.custom_tool_use":
15444
16092
  out.push({
15445
16093
  type: "tool",
15446
- content: `(${str9(e.name) ?? "tool"}${e.evaluated_permission === "ask" ? " — waiting on a yes" : ""})`
16094
+ content: `(${str10(e.name) ?? "tool"}${e.evaluated_permission === "ask" ? " — waiting on a yes" : ""})`
15447
16095
  });
15448
16096
  break;
15449
16097
  case "session.error":
@@ -15454,9 +16102,9 @@ var eventMessages = (events) => {
15454
16102
  return out;
15455
16103
  };
15456
16104
  var statusLine2 = (session, events, now) => {
15457
- const status = str9(session.status) ?? "unknown";
15458
- const spent = usd4(obj2(obj2(session.usage)?.list_cost)?.amount);
15459
- const cap = usd4(obj2(obj2(session.budget)?.max_list_cost)?.amount);
16105
+ const status = str10(session.status) ?? "unknown";
16106
+ const spent = usd4(obj3(obj3(session.usage)?.list_cost)?.amount);
16107
+ const cap = usd4(obj3(obj3(session.budget)?.max_list_cost)?.amount);
15460
16108
  const money = spent ? ` · ${spent}${cap ? ` of ${cap}` : ""}` : "";
15461
16109
  if (status === "running" || status === "rescheduling") {
15462
16110
  return `managed session: ${status} (checked ${new Date(now).toISOString()})${money}`;
@@ -15464,10 +16112,10 @@ var statusLine2 = (session, events, now) => {
15464
16112
  if (status !== "idle")
15465
16113
  return `managed session: ${status}${money}`;
15466
16114
  const idle = lastIdle(events);
15467
- const why = str9(obj2(idle?.stop_reason)?.type);
16115
+ const why = str10(obj3(idle?.stop_reason)?.type);
15468
16116
  switch (why) {
15469
16117
  case "requires_action": {
15470
- const tools = pendingAsks(events, idle).map((e) => str9(e.name) ?? "a tool").join(", ");
16118
+ const tools = pendingAsks(events, idle).map((e) => str10(e.name) ?? "a tool").join(", ");
15471
16119
  return `managed session: idle — waiting on a yes for ${tools || "a tool call"} (answer yes to allow, anything else denies)${money}`;
15472
16120
  }
15473
16121
  case "budget_reached":
@@ -15501,7 +16149,7 @@ function managedRunner(exec, deps = {}) {
15501
16149
  }
15502
16150
  const res = splitCurl(r.stdout);
15503
16151
  if (res.code < 200 || res.code >= 300 || !res.body) {
15504
- const why = str9(obj2(res.body?.error)?.message) ?? str9(res.body?.message) ?? (res.text.trim().slice(0, 300) || "no body");
16152
+ const why = str10(obj3(res.body?.error)?.message) ?? str10(res.body?.message) ?? (res.text.trim().slice(0, 300) || "no body");
15505
16153
  throw new Error(`managed ${method} ${path}: HTTP ${res.code}: ${why}`);
15506
16154
  }
15507
16155
  return res.body;
@@ -15513,7 +16161,7 @@ function managedRunner(exec, deps = {}) {
15513
16161
  const got = api("GET", `/v1/sessions/${id}/events?limit=1000${page ? `&page=${encodeURIComponent(page)}` : ""}`);
15514
16162
  const data = Array.isArray(got.data) ? got.data : [];
15515
16163
  out.push(...data);
15516
- page = str9(got.next_page);
16164
+ page = str10(got.next_page);
15517
16165
  if (!page || data.length === 0)
15518
16166
  break;
15519
16167
  }
@@ -15576,7 +16224,7 @@ function managedRunner(exec, deps = {}) {
15576
16224
  }
15577
16225
  ]
15578
16226
  });
15579
- const id = str9(created.id);
16227
+ const id = str10(created.id);
15580
16228
  if (!id)
15581
16229
  throw new Error(`managed: session create for ${o.name} returned no id`);
15582
16230
  return { runner: "managed", id, cwd: o.repoPath, log: consoleUrl(id) };
@@ -15601,7 +16249,7 @@ ${renderTranscript(eventMessages(all))}`;
15601
16249
  const session = api("GET", `/v1/sessions/${h.id}`);
15602
16250
  const all = session.status === "idle" ? events(h.id) : [];
15603
16251
  const idle = lastIdle(all);
15604
- const asks = str9(obj2(idle?.stop_reason)?.type) === "requires_action" ? pendingAsks(all, idle) : [];
16252
+ const asks = str10(obj3(idle?.stop_reason)?.type) === "requires_action" ? pendingAsks(all, idle) : [];
15605
16253
  if (asks.length) {
15606
16254
  const allow = YES2.test(text);
15607
16255
  api("POST", `/v1/sessions/${h.id}/events`, {
@@ -15650,156 +16298,6 @@ ${renderTranscript(eventMessages(all))}`;
15650
16298
  };
15651
16299
  }
15652
16300
 
15653
- // src/runners/orca.ts
15654
- import { writeFileSync as writeFileSync12 } from "node:fs";
15655
- var obj3 = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
15656
- var str10 = (v) => typeof v === "string" && v.length > 0 ? v : null;
15657
- var parseOrcaJson = (stdout) => {
15658
- try {
15659
- return obj3(JSON.parse(stdout));
15660
- } catch {
15661
- return null;
15662
- }
15663
- };
15664
- var orcaError = (env) => {
15665
- if (!env || env.ok !== false)
15666
- return null;
15667
- const e = obj3(env.error);
15668
- const message = str10(e?.message) ?? "Orca refused the call";
15669
- const code = str10(e?.code);
15670
- return code ? `${message} (${code})` : message;
15671
- };
15672
- var parseOrcaWorktree = (env) => {
15673
- const result = obj3(env.result) ?? {};
15674
- return str10(obj3(result.worktree)?.path) ?? str10(result.worktreePath) ?? str10(result.path);
15675
- };
15676
- var parseOrcaBranch = (env) => {
15677
- const wt = obj3(obj3(env.result)?.worktree);
15678
- const branch = str10(wt?.branch)?.replace(/^refs\/heads\//, "");
15679
- const head = str10(wt?.head);
15680
- return branch && head ? { branch, head } : null;
15681
- };
15682
- var parseOrcaTerminal = (env) => {
15683
- const result = obj3(env.result) ?? {};
15684
- return str10(obj3(result.terminal)?.handle) ?? str10(result.handle) ?? str10(result.terminalHandle) ?? null;
15685
- };
15686
- var orcaLauncher = (o) => `#!/bin/sh
15687
- # sleds engine: ${o.name}
15688
- exec ${agentShellCommand(o)}
15689
- `;
15690
- var orcaCommand = (launcher) => `sh ${shq2(launcher)}; exit`;
15691
- var GONE = /exit|clos|gone|dead|stopp|kill/i;
15692
- var orcaTerminalLive = (status, stdout) => {
15693
- const env = parseOrcaJson(stdout);
15694
- if (str10(obj3(env?.error)?.code) === "runtime_unavailable")
15695
- return true;
15696
- if (status !== 0 || !env || env.ok === false)
15697
- return false;
15698
- const t = obj3(obj3(env.result)?.terminal) ?? obj3(env.result) ?? {};
15699
- if (typeof t.status === "string" && GONE.test(t.status))
15700
- return false;
15701
- if (t.exited === true || t.alive === false)
15702
- return false;
15703
- if (typeof t.exitCode === "number")
15704
- return false;
15705
- if (obj3(t.exitCause))
15706
- return false;
15707
- if (t.orphaned === true && t.writable === false)
15708
- return false;
15709
- return true;
15710
- };
15711
- var launcherPath2 = (promptFile) => `${promptFile.replace(/\.md$/, "")}.orca.sh`;
15712
- function orcaRunner(exec, deps = {}) {
15713
- const writeFile = deps.writeFile ?? ((p, t) => writeFileSync12(p, t, { mode: 384 }));
15714
- const orca = (args, what) => {
15715
- const r = exec("orca", args);
15716
- const env = parseOrcaJson(r.stdout);
15717
- const refused = orcaError(env);
15718
- if (refused)
15719
- throw new Error(`${what}: ${refused}`);
15720
- if (r.status !== 0 || !env) {
15721
- throw new Error(`${what} failed: ${r.stderr.trim() || r.stdout.trim() || `exit ${r.status}`}`);
15722
- }
15723
- return env;
15724
- };
15725
- return {
15726
- name: "orca",
15727
- makesWorktree: true,
15728
- readsSession: true,
15729
- start(o) {
15730
- const created = orca([
15731
- "worktree",
15732
- "create",
15733
- "--repo",
15734
- `path:${o.repoPath}`,
15735
- "--name",
15736
- o.name,
15737
- "--base-branch",
15738
- `origin/${o.base ?? "main"}`,
15739
- "--no-parent",
15740
- "--json"
15741
- ], `orca worktree create ${o.name}`);
15742
- const cwd = parseOrcaWorktree(created);
15743
- if (!cwd) {
15744
- throw new Error(`orca worktree create ${o.name} returned no worktree path; the agent was not started`);
15745
- }
15746
- const launcher = launcherPath2(o.promptFile);
15747
- writeFile(launcher, orcaLauncher(o));
15748
- const shown = orca([
15749
- "terminal",
15750
- "create",
15751
- "--worktree",
15752
- `path:${cwd}`,
15753
- "--title",
15754
- o.name,
15755
- "--command",
15756
- orcaCommand(launcher),
15757
- "--json"
15758
- ], `orca terminal create ${o.name} (worktree ${cwd})`);
15759
- const terminal = parseOrcaTerminal(shown);
15760
- if (!terminal) {
15761
- throw new Error(`orca terminal create ${o.name} started in ${cwd} but returned no terminal handle`);
15762
- }
15763
- const own = parseOrcaBranch(created);
15764
- const h = { runner: "orca", id: terminal, cwd, ...own ? { own } : {} };
15765
- return h;
15766
- },
15767
- status(h) {
15768
- const r = exec("orca", ["terminal", "show", "--terminal", h.id, "--json"]);
15769
- return orcaTerminalLive(r.status, r.stdout) ? "running" : "exited";
15770
- },
15771
- read(h) {
15772
- const r = exec("orca", ["terminal", "read", "--terminal", h.id, "--limit", "200"]);
15773
- return r.status === 0 ? r.stdout : "";
15774
- },
15775
- send(h, text) {
15776
- const r = exec("orca", ["terminal", "send", "--terminal", h.id, "--text", text, "--enter"]);
15777
- if (r.status !== 0) {
15778
- throw new Error(`orca terminal send ${h.id} failed: ${r.stderr.trim() || `exit ${r.status}`}`);
15779
- }
15780
- },
15781
- stop(h) {
15782
- exec("orca", ["terminal", "close", "--terminal", h.id]);
15783
- },
15784
- watchHint(h) {
15785
- return `orca terminal show --terminal ${h.id}`;
15786
- },
15787
- removeWorktree(h, repoPath) {
15788
- orca(["worktree", "rm", "--worktree", `path:${h.cwd}`, "--json"], `orca worktree rm ${h.cwd}`);
15789
- if (!h.own)
15790
- return;
15791
- const ref = `refs/heads/${h.own.branch}`;
15792
- const r = exec("git", ["-C", repoPath, "update-ref", "-d", ref, h.own.head]);
15793
- if (r.status === 0)
15794
- return;
15795
- const still = exec("git", ["-C", repoPath, "rev-parse", "--verify", "--quiet", ref]);
15796
- if (still.status !== 0)
15797
- return;
15798
- return `kept Orca's branch ${h.own.branch}: ${r.stderr.trim() || `exit ${r.status}`}`;
15799
- }
15800
- };
15801
- }
15802
-
15803
16301
  // src/runners/tmux.ts
15804
16302
  var tmuxSession = (name) => `=${name}`;
15805
16303
  var tmuxPane = (name) => `=${name}:`;
@@ -16160,7 +16658,7 @@ function flagsFromProfile(flags, given, profile, home) {
16160
16658
  }
16161
16659
  }
16162
16660
  if (profile.reposDir && !has("repos-dir") && !has("repo")) {
16163
- out.reposDir = splitReposDirs(profile.reposDir).map((d) => home && /^~(\/|$)/.test(d) ? join15(home, d.slice(1)) : d).join(":");
16661
+ out.reposDir = splitReposDirs(profile.reposDir).map((d) => home && /^~(\/|$)/.test(d) ? join16(home, d.slice(1)) : d).join(":");
16164
16662
  used.push(`repos ${profile.reposDir}`);
16165
16663
  }
16166
16664
  if (profile.mcpStrict !== undefined && !has("strict-mcp") && !has("no-strict-mcp")) {
@@ -16290,12 +16788,12 @@ function memberMcpConfigs(o, cwd) {
16290
16788
  });
16291
16789
  const claudeDir = (o.env ?? process.env).CLAUDE_CONFIG_DIR;
16292
16790
  return [
16293
- read(claudeDir ? join15(claudeDir, ".claude.json") : join15(o.home, ".claude.json")),
16294
- ...cwd ? [read(join15(cwd, ".mcp.json"))] : []
16791
+ read(claudeDir ? join16(claudeDir, ".claude.json") : join16(o.home, ".claude.json")),
16792
+ ...cwd ? [read(join16(cwd, ".mcp.json"))] : []
16295
16793
  ];
16296
16794
  }
16297
16795
  function engineStateDir(o, org, seat) {
16298
- return seatStateDir(orgStateDir(o.stateDir ?? join15(configDir(o.home, o.env ?? process.env), "engine"), org), seat);
16796
+ return seatStateDir(orgStateDir(o.stateDir ?? join16(configDir(o.home, o.env ?? process.env), "engine"), org), seat);
16299
16797
  }
16300
16798
  function engineSeat(o, org) {
16301
16799
  try {
@@ -16322,11 +16820,11 @@ function engineDeps(o, flags, source, runner, stateName) {
16322
16820
  const stateDir = engineStateDir(o, flags.org, flags.seat);
16323
16821
  const file = statePath(stateDir, stateName);
16324
16822
  const mcpOrg = flags.org && engineOrg(o, flags.org).seated ? flags.org : undefined;
16325
- const repo = flags.repo ? resolve7(cwd, flags.repo) : undefined;
16326
- const reposDirs = splitReposDirs(flags.reposDir).map((d) => resolve7(cwd, d));
16823
+ const repo = flags.repo ? resolve8(cwd, flags.repo) : undefined;
16824
+ const reposDirs = splitReposDirs(flags.reposDir).map((d) => resolve8(cwd, d));
16327
16825
  const { branch, prTitle } = flags;
16328
- const hooksDir = join15(stateDir, "hooks");
16329
- const permissionMark = (name) => join15(hooksDir, `${name}.permission.json`);
16826
+ const hooksDir = join16(stateDir, "hooks");
16827
+ const permissionMark = (name) => join16(hooksDir, `${name}.permission.json`);
16330
16828
  const withHook = (name, settings) => applyPermissionHook(settings, [...o.sledsArgv ?? ["sleds"], "permission-event", "--mark", permissionMark(name)].map(shq2).join(" "));
16331
16829
  return {
16332
16830
  source,
@@ -16349,15 +16847,18 @@ function engineDeps(o, flags, source, runner, stateName) {
16349
16847
  ...flags.strictMcp ? { strictMcp: true } : {},
16350
16848
  ...flags.sandbox ? {
16351
16849
  sandbox: true,
16352
- writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd) => {
16850
+ writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd, allow) => {
16353
16851
  const gitCommonDir = gitCommonDirOf(exec, cwd);
16354
- const dir = join15(stateDir, "sandbox");
16852
+ const dir = join16(stateDir, "sandbox");
16355
16853
  mkdirSync12(dir, { recursive: true, mode: 448 });
16356
- const p = join15(dir, `${name}.json`);
16854
+ const p = join16(dir, `${name}.json`);
16357
16855
  const walls = engineSandboxSettings(o.home, o.env ?? process.env, gitCommonDir, [
16358
16856
  ...SANDBOX_DEFAULT_ALLOW,
16359
16857
  ...flags.sandboxAllow ?? []
16360
16858
  ]);
16859
+ walls.permissions.allow = [
16860
+ ...new Set([...walls.permissions.allow, ...allow ?? []])
16861
+ ];
16361
16862
  const settings = withHook(name, { ...walls });
16362
16863
  writeFileSync13(p, `${JSON.stringify(settings, null, 2)}
16363
16864
  `, { mode: 384 });
@@ -16365,10 +16866,11 @@ function engineDeps(o, flags, source, runner, stateName) {
16365
16866
  return { settingsFile: p, gitCommonDir };
16366
16867
  })
16367
16868
  } : {},
16368
- writeHookSettings: (name) => {
16869
+ writeHookSettings: (name, allow) => {
16369
16870
  mkdirSync12(hooksDir, { recursive: true, mode: 448 });
16370
- const p = join15(hooksDir, `${name}.json`);
16371
- writeFileSync13(p, `${JSON.stringify(withHook(name, {}), null, 2)}
16871
+ const p = join16(hooksDir, `${name}.json`);
16872
+ const base = allow?.length ? { permissions: { allow: [...allow] } } : {};
16873
+ writeFileSync13(p, `${JSON.stringify(withHook(name, base), null, 2)}
16372
16874
  `, { mode: 384 });
16373
16875
  chmodSync4(p, 384);
16374
16876
  return p;
@@ -16394,9 +16896,9 @@ function engineDeps(o, flags, source, runner, stateName) {
16394
16896
  exists
16395
16897
  }),
16396
16898
  writeMcpConfig: o.writeMcpConfig ?? ((name, runToken, worktree) => {
16397
- const dir = join15(stateDir, "mcp");
16899
+ const dir = join16(stateDir, "mcp");
16398
16900
  mkdirSync12(dir, { recursive: true, mode: 448 });
16399
- const p = join15(dir, `${name}.json`);
16901
+ const p = join16(dir, `${name}.json`);
16400
16902
  let others = {};
16401
16903
  if (flags.strictMcp && flags.mcpAllow?.length) {
16402
16904
  const r = mcpPassThrough(flags.mcpAllow, memberMcpConfigs(o, worktree));
@@ -16412,9 +16914,9 @@ function engineDeps(o, flags, source, runner, stateName) {
16412
16914
  }),
16413
16915
  removeMcpConfig: o.removeMcpConfig ?? ((p) => rmSync4(p, { force: true })),
16414
16916
  writePrompt: o.writePrompt ?? ((name, text) => {
16415
- const dir = join15(stateDir, "prompts");
16917
+ const dir = join16(stateDir, "prompts");
16416
16918
  mkdirSync12(dir, { recursive: true, mode: 448 });
16417
- const p = join15(dir, `${name}.md`);
16919
+ const p = join16(dir, `${name}.md`);
16418
16920
  writeFileSync13(p, text, { mode: 384 });
16419
16921
  return p;
16420
16922
  }),
@@ -16441,8 +16943,8 @@ function engineDeps(o, flags, source, runner, stateName) {
16441
16943
  }
16442
16944
  function sandboxDenyRead(home, env) {
16443
16945
  return {
16444
- dirs: [...new Set([configDir(home, env), join15(home, ".config", "sleds")])],
16445
- files: [...new Set([tokenPath(home, env), join15(home, ".sleds-token")])]
16946
+ dirs: [...new Set([configDir(home, env), join16(home, ".config", "sleds")])],
16947
+ files: [...new Set([tokenPath(home, env), join16(home, ".sleds-token")])]
16446
16948
  };
16447
16949
  }
16448
16950
  function engineSandboxSettings(home, env, gitCommonDir, allow) {
@@ -16457,7 +16959,7 @@ function gitCommonDirOf(exec, cwd) {
16457
16959
  const dir = r.status === 0 ? r.stdout.trim() : "";
16458
16960
  if (!dir)
16459
16961
  throw new Error(`git has no common dir for ${cwd}, so the sandbox has nowhere to commit`);
16460
- return resolve7(cwd, dir);
16962
+ return resolve8(cwd, dir);
16461
16963
  }
16462
16964
  function pinOrg2(configJson, org) {
16463
16965
  if (!org)
@@ -16499,13 +17001,14 @@ async function driveEngine(o, flags, deps, until = {}) {
16499
17001
  runner: deps.runner.name,
16500
17002
  atOnce: flags.atOnce,
16501
17003
  interval: flags.interval,
16502
- cliVersion: "0.1.43"
17004
+ cliVersion: "0.1.45"
16503
17005
  };
16504
17006
  const beat = (checkedAt) => {
16505
17007
  const m = deps.machine;
16506
17008
  try {
16507
17009
  writeLock(lockFile, checkedAt === undefined ? lock : {
16508
17010
  ...lock,
17011
+ runner: deps.runner.name,
16509
17012
  atOnce: deps.atOnce,
16510
17013
  checkedAt,
16511
17014
  nextCheckAt: checkedAt + flags.interval * 1000,
@@ -16811,7 +17314,7 @@ async function enginePauseCmd(o) {
16811
17314
  // src/commands/engineInstall.ts
16812
17315
  init_config();
16813
17316
  import {
16814
- existsSync as existsSync14,
17317
+ existsSync as existsSync15,
16815
17318
  mkdirSync as mkdirSync14,
16816
17319
  readFileSync as readFileSync14,
16817
17320
  readdirSync as readdirSync2,
@@ -16820,13 +17323,13 @@ import {
16820
17323
  writeFileSync as writeFileSync15
16821
17324
  } from "node:fs";
16822
17325
  import { hostname as hostname3 } from "node:os";
16823
- import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute2, join as join18 } from "node:path";
17326
+ import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute2, join as join19 } from "node:path";
16824
17327
  init_state();
16825
17328
 
16826
17329
  // src/schedule.ts
16827
17330
  import { spawnSync as spawnSync4 } from "node:child_process";
16828
- import { existsSync as existsSync13, mkdirSync as mkdirSync13, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
16829
- import { join as join16 } from "node:path";
17331
+ import { existsSync as existsSync14, mkdirSync as mkdirSync13, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
17332
+ import { join as join17 } from "node:path";
16830
17333
  var REVERT_LABEL = "ai.sleds.revert";
16831
17334
  var defaultExec2 = (cmd, args) => {
16832
17335
  const r = spawnSync4(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
@@ -16834,7 +17337,7 @@ var defaultExec2 = (cmd, args) => {
16834
17337
  return { ok: false, stderr: r.error.message };
16835
17338
  return { ok: r.status === 0, stderr: r.stderr ?? "" };
16836
17339
  };
16837
- var plistPath = (home) => join16(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
17340
+ var plistPath = (home) => join17(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
16838
17341
  var xmlEscape2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
16839
17342
  var buildLaunchdPlist = (sledsArgv, deadline) => {
16840
17343
  const args = [...sledsArgv, "off"].map((a) => ` <string>${xmlEscape2(a)}</string>`).join(`
@@ -16873,7 +17376,7 @@ var scheduleRevert = (d, deadlineMs) => {
16873
17376
  if (d.platform === "darwin") {
16874
17377
  const p = plistPath(d.home);
16875
17378
  try {
16876
- mkdirSync13(join16(d.home, "Library", "LaunchAgents"), { recursive: true });
17379
+ mkdirSync13(join17(d.home, "Library", "LaunchAgents"), { recursive: true });
16877
17380
  d.exec("launchctl", ["unload", p]);
16878
17381
  writeFileSync14(p, buildLaunchdPlist(d.sledsArgv, deadline));
16879
17382
  const r = d.exec("launchctl", ["load", p]);
@@ -16902,7 +17405,7 @@ var scheduleRevert = (d, deadlineMs) => {
16902
17405
  var cancelRevert = (d, kind) => {
16903
17406
  if (kind === "launchd" || d.platform === "darwin") {
16904
17407
  const p = plistPath(d.home);
16905
- if (existsSync13(p)) {
17408
+ if (existsSync14(p)) {
16906
17409
  d.exec("launchctl", ["unload", p]);
16907
17410
  rmSync5(p, { force: true });
16908
17411
  }
@@ -16913,16 +17416,16 @@ var cancelRevert = (d, kind) => {
16913
17416
  };
16914
17417
 
16915
17418
  // src/commands/engineSystemd.ts
16916
- import { join as join17 } from "node:path";
17419
+ import { join as join18 } from "node:path";
16917
17420
  var BOX_ENV_UNIT_PATH = "%h/.config/sleds/box.env";
16918
17421
  function boxEnvPath(home) {
16919
- return join17(home, ".config", "sleds", "box.env");
17422
+ return join18(home, ".config", "sleds", "box.env");
16920
17423
  }
16921
17424
  function engineUnitDir(home) {
16922
- return join17(home, ".config", "systemd", "user");
17425
+ return join18(home, ".config", "systemd", "user");
16923
17426
  }
16924
17427
  function engineUnitPath(home, label) {
16925
- return join17(engineUnitDir(home), `${label}.service`);
17428
+ return join18(engineUnitDir(home), `${label}.service`);
16926
17429
  }
16927
17430
  function systemdQuote(s) {
16928
17431
  const body = s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/%/g, "%%").replace(/\$/g, "$$$$");
@@ -16980,13 +17483,13 @@ function engineLabel(name, org, seat) {
16980
17483
  return [ENGINE_LABEL, org, seat?.trim(), name].filter((p) => Boolean(p)).map((p, i) => i === 0 ? p : engineSlug(p)).join(".");
16981
17484
  }
16982
17485
  function enginePlistPath(home, name, org, seat) {
16983
- return join18(home, "Library", "LaunchAgents", `${engineLabel(name, org, seat)}.plist`);
17486
+ return join19(home, "Library", "LaunchAgents", `${engineLabel(name, org, seat)}.plist`);
16984
17487
  }
16985
17488
  function engineLogPath(stateDir, name) {
16986
- return join18(stateDir, `${engineSlug(name)}.log`);
17489
+ return join19(stateDir, `${engineSlug(name)}.log`);
16987
17490
  }
16988
17491
  function engineEventsPath(stateDir, name) {
16989
- return join18(stateDir, `${engineSlug(name)}.events.jsonl`);
17492
+ return join19(stateDir, `${engineSlug(name)}.events.jsonl`);
16990
17493
  }
16991
17494
  var xmlEscape3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
16992
17495
  function buildEnginePlist(o) {
@@ -17062,7 +17565,13 @@ function parseEnginePlist(text) {
17062
17565
  if (!label || array === undefined)
17063
17566
  return null;
17064
17567
  const programArgs = [...array.matchAll(/<string>([^<]*)<\/string>/g)].map((m) => xmlUnescape(m[1] ?? ""));
17065
- return { label: xmlUnescape(label), programArgs };
17568
+ const envDict = /<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/.exec(text)?.[1];
17569
+ const path = envDict && /<key>PATH<\/key>\s*<string>([^<]*)<\/string>/.exec(envDict)?.[1];
17570
+ return {
17571
+ label: xmlUnescape(label),
17572
+ programArgs,
17573
+ ...typeof path === "string" ? { path: xmlUnescape(path) } : {}
17574
+ };
17066
17575
  }
17067
17576
  function engineArg(programArgs, flag) {
17068
17577
  const at = programArgs.indexOf(`--${flag}`);
@@ -17077,7 +17586,7 @@ function readEngines(dir, suffix, manager, parse) {
17077
17586
  }
17078
17587
  const out = [];
17079
17588
  for (const name of names.filter((n) => n.startsWith(`${ENGINE_LABEL}.`) && n.endsWith(suffix))) {
17080
- const path = join18(dir, name);
17589
+ const path = join19(dir, name);
17081
17590
  let parsed;
17082
17591
  try {
17083
17592
  parsed = parse(readFileSync14(path, "utf8"), name);
@@ -17097,7 +17606,7 @@ function readEngines(dir, suffix, manager, parse) {
17097
17606
  }
17098
17607
  function installedEngines(home) {
17099
17608
  return [
17100
- ...readEngines(join18(home, "Library", "LaunchAgents"), ".plist", "launchd", parseEnginePlist),
17609
+ ...readEngines(join19(home, "Library", "LaunchAgents"), ".plist", "launchd", parseEnginePlist),
17101
17610
  ...readEngines(engineUnitDir(home), ".service", "systemd", (text, file) => {
17102
17611
  const parsed = parseEngineUnit(text);
17103
17612
  return parsed && { label: basename4(file, ".service"), ...parsed };
@@ -17148,7 +17657,7 @@ function engineInstall(o) {
17148
17657
  return installUnit({ o, parsed, label, sledsArgv, env: agentEnv, logPath, exec });
17149
17658
  const plist = enginePlistPath(o.home, parsed.name, parsed.org, seat);
17150
17659
  mkdirSync14(dirname12(plist), { recursive: true });
17151
- if (existsSync14(plist))
17660
+ if (existsSync15(plist))
17152
17661
  exec("launchctl", ["unload", plist]);
17153
17662
  for (const other of installedEngines(o.home)) {
17154
17663
  if (other.manager !== "launchd" || other.plist === plist || other.org !== parsed.org)
@@ -17160,7 +17669,7 @@ function engineInstall(o) {
17160
17669
  log(`replaced ${other.label}, this engine as installed before`);
17161
17670
  }
17162
17671
  const legacy = enginePlistPath(o.home, parsed.name);
17163
- if (parsed.org && existsSync14(legacy)) {
17672
+ if (parsed.org && existsSync15(legacy)) {
17164
17673
  const cfg = readConfig(o.home);
17165
17674
  if (org.sole || parsed.org === (cfg.activeOrg ?? cfg.org?.slug)) {
17166
17675
  exec("launchctl", ["unload", legacy]);
@@ -17190,7 +17699,7 @@ function installUnit(a) {
17190
17699
  const unitName = basename4(unit);
17191
17700
  const systemctl = (...args) => exec("systemctl", ["--user", ...args]);
17192
17701
  mkdirSync14(dirname12(unit), { recursive: true });
17193
- if (existsSync14(unit))
17702
+ if (existsSync15(unit))
17194
17703
  systemctl("stop", unitName);
17195
17704
  for (const other of installedEngines(o.home)) {
17196
17705
  if (other.manager !== "systemd" || other.plist === unit || other.org !== parsed.org)
@@ -17248,7 +17757,7 @@ function engineUninstall(o) {
17248
17757
  ...installed.filter((e) => e.manager === manager).map((e) => e.plist),
17249
17758
  ...args.org ? [] : [pathOf(manager)]
17250
17759
  ]);
17251
- const file = candidates.find((p) => existsSync14(p));
17760
+ const file = candidates.find((p) => existsSync15(p));
17252
17761
  if (!file) {
17253
17762
  log(`engine:${name} was not installed (no ${candidates[0]}).`);
17254
17763
  return 0;
@@ -17267,20 +17776,20 @@ function engineUninstall(o) {
17267
17776
  }
17268
17777
 
17269
17778
  // src/commands/musher.ts
17270
- import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "node:fs";
17779
+ import { existsSync as existsSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "node:fs";
17271
17780
  init_config();
17272
17781
  init_meter();
17273
17782
  var defaultWhich = (cmd) => {
17274
17783
  const path = process.env.PATH ?? "";
17275
17784
  for (const dir of path.split(":").filter(Boolean)) {
17276
17785
  const p = `${dir}/${cmd}`;
17277
- if (existsSync15(p))
17786
+ if (existsSync16(p))
17278
17787
  return p;
17279
17788
  }
17280
17789
  return null;
17281
17790
  };
17282
17791
  var findClaude = (home, configured, which) => {
17283
- if (configured && existsSync15(configured))
17792
+ if (configured && existsSync16(configured))
17284
17793
  return configured;
17285
17794
  const found = which("claude");
17286
17795
  if (found)
@@ -17291,13 +17800,13 @@ var findClaude = (home, configured, which) => {
17291
17800
  "/usr/local/bin/claude",
17292
17801
  `${home}/.npm-global/bin/claude`
17293
17802
  ])
17294
- if (existsSync15(p))
17803
+ if (existsSync16(p))
17295
17804
  return p;
17296
17805
  return null;
17297
17806
  };
17298
17807
  var musherAgentState = (home, env = process.env) => {
17299
17808
  const plist = musherPlistPath(home, env);
17300
- if (!existsSync15(plist))
17809
+ if (!existsSync16(plist))
17301
17810
  return { installed: false, thisSeat: false };
17302
17811
  try {
17303
17812
  const text = readFileSync15(plist, "utf8");
@@ -17394,7 +17903,7 @@ function formatDoctor(checks) {
17394
17903
  }
17395
17904
  function defaultWhich2(cmd) {
17396
17905
  try {
17397
- return execFileSync3("which", ["-a", cmd], {
17906
+ return execFileSync4("which", ["-a", cmd], {
17398
17907
  encoding: "utf8",
17399
17908
  stdio: ["ignore", "pipe", "ignore"]
17400
17909
  }).split(`
@@ -17413,7 +17922,7 @@ async function runDoctor(o) {
17413
17922
  ].filter(Boolean);
17414
17923
  const cfgDir = configDir(home, env);
17415
17924
  const cfg = readConfig(home);
17416
- const cfgExists = existsSync16(configPath(home));
17925
+ const cfgExists = existsSync17(configPath(home));
17417
17926
  if (!cfgExists) {
17418
17927
  checks.push({
17419
17928
  level: "fail",
@@ -17429,7 +17938,7 @@ async function runDoctor(o) {
17429
17938
  });
17430
17939
  }
17431
17940
  const sPath = settingsPath(home);
17432
- const settings = existsSync16(sPath) ? readSettings(home) : null;
17941
+ const settings = existsSync17(sPath) ? readSettings(home) : null;
17433
17942
  const pointed = settings ? readProxy(settings) : null;
17434
17943
  if (!settings) {
17435
17944
  checks.push({
@@ -17476,7 +17985,7 @@ async function runDoctor(o) {
17476
17985
  detail: `seat-event ${seat ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"} · session-event ${session ? "on" : "missing"} · stop-event ${stop ? "on" : "missing"}`,
17477
17986
  fix: all ? undefined : "sleds hooks"
17478
17987
  });
17479
- const want = env.SLEDS_HOME?.trim() ? resolve8(home, env.SLEDS_HOME.trim()) : undefined;
17988
+ const want = env.SLEDS_HOME?.trim() ? resolve9(home, env.SLEDS_HOME.trim()) : undefined;
17480
17989
  const cmds = sledsCommandsIn(settings);
17481
17990
  const wrong = cmds.filter((c) => {
17482
17991
  const got = bakedHome(c);
@@ -17514,7 +18023,7 @@ async function runDoctor(o) {
17514
18023
  fix: other.length + unpinned.length === 0 ? undefined : `${[...overrides, `SLEDS_ORG=${wantOrg}`].join(" ")} sleds hooks (pins the status line + hooks to ${wantOrg}; use the org this Claude config is for)`
17515
18024
  });
17516
18025
  }
17517
- const skill = existsSync16(skillPath(home));
18026
+ const skill = existsSync17(skillPath(home));
17518
18027
  checks.push({
17519
18028
  level: skill ? "ok" : "warn",
17520
18029
  name: "skill",
@@ -17548,7 +18057,7 @@ async function runDoctor(o) {
17548
18057
  const which = (o.which ?? defaultWhich2)("sleds");
17549
18058
  const stale = which.filter((p) => {
17550
18059
  try {
17551
- const target = lstatSync(p).isSymbolicLink() ? readlinkSync(p) : p;
18060
+ const target = lstatSync2(p).isSymbolicLink() ? readlinkSync2(p) : p;
17552
18061
  return /\/src\/index\.ts$/.test(target) || /\.bun\/bin\/sleds$/.test(p);
17553
18062
  } catch {
17554
18063
  return false;
@@ -17683,7 +18192,7 @@ function sandboxChecks(o, env) {
17683
18192
  fix: "npm install -g @anthropic-ai/claude-code"
17684
18193
  }
17685
18194
  ];
17686
- if (!existsSync16(dir))
18195
+ if (!existsSync17(dir))
17687
18196
  return [
17688
18197
  {
17689
18198
  level: "warn",
@@ -17693,12 +18202,12 @@ function sandboxChecks(o, env) {
17693
18202
  }
17694
18203
  ];
17695
18204
  const nonce = o.nonce ?? `sleds-probe-${randomBytes(16).toString("hex")}`;
17696
- const probe = join19(dir, SANDBOX_PROBE);
17697
- const work = mkdtempSync(join19(tmpdir(), "sleds-sandbox-"));
18205
+ const probe = join20(dir, SANDBOX_PROBE);
18206
+ const work = mkdtempSync(join20(tmpdir(), "sleds-sandbox-"));
17698
18207
  try {
17699
18208
  writeFileSync17(probe, `${nonce}
17700
18209
  `, { mode: 384 });
17701
- const settingsFile = join19(work, "settings.json");
18210
+ const settingsFile = join20(work, "settings.json");
17702
18211
  const settings = sandboxSettings({
17703
18212
  denyRead: [dir],
17704
18213
  gitCommonDir: work,
@@ -17734,7 +18243,7 @@ function engineChecks(o, env) {
17734
18243
  const checks = [];
17735
18244
  const which = (c) => (o.which ?? defaultWhich2)(c)[0] ?? null;
17736
18245
  const exec = o.exec ?? defaultExec3;
17737
- const exists = o.exists ?? existsSync16;
18246
+ const exists = o.exists ?? existsSync17;
17738
18247
  const platform = o.platform ?? process.platform;
17739
18248
  const mac = platform === "darwin";
17740
18249
  const linux = platform === "linux";
@@ -17776,6 +18285,11 @@ function engineChecks(o, env) {
17776
18285
  fix: mac ? "brew install tmux" : "install tmux with your package manager"
17777
18286
  });
17778
18287
  }
18288
+ if (runner === "orca") {
18289
+ const path = engines.find((e) => engineArg(e.programArgs, "runner") === "orca")?.path ?? env.PATH ?? "";
18290
+ const p = orcaPreflight(exec, path, o.orcaFs);
18291
+ checks.push(p.ok ? { level: "ok", name: "orca", detail: `${p.version} · the engine's runner` } : { level: need, name: "orca", detail: p.why, fix: p.fix });
18292
+ }
17779
18293
  if (linux)
17780
18294
  checks.push(...boxChecks(o, env, which, need));
17781
18295
  if (!manager)
@@ -17865,7 +18379,7 @@ function boxChecks(o, env, which, need) {
17865
18379
  }
17866
18380
  function cmuxCheck(o, env) {
17867
18381
  const exec = o.exec ?? defaultExec3;
17868
- const bin = resolveCmuxBin(o.exists ?? existsSync16, env.PATH ?? "");
18382
+ const bin = resolveCmuxBin(o.exists ?? existsSync17, env.PATH ?? "");
17869
18383
  const pw = readCmuxPassword(exec);
17870
18384
  const ping = pw ? exec(bin, ["ping"], { env: { CMUX_SOCKET_PASSWORD: pw } }) : exec(bin, ["ping"]);
17871
18385
  if (ping.status === 127)
@@ -17926,7 +18440,7 @@ function parsePmset(text) {
17926
18440
  }
17927
18441
  var defaultPmset = () => {
17928
18442
  try {
17929
- return parsePmset(execFileSync3("pmset", ["-g", "custom"], { encoding: "utf8", timeout: 2000 }));
18443
+ return parsePmset(execFileSync4("pmset", ["-g", "custom"], { encoding: "utf8", timeout: 2000 }));
17930
18444
  } catch {
17931
18445
  return null;
17932
18446
  }
@@ -17956,7 +18470,7 @@ import {
17956
18470
  writeFileSync as writeFileSync18
17957
18471
  } from "node:fs";
17958
18472
  import { hostname as hostname4 } from "node:os";
17959
- import { join as join20, resolve as resolve9 } from "node:path";
18473
+ import { join as join21, resolve as resolve10 } from "node:path";
17960
18474
  init_machine();
17961
18475
  init_state();
17962
18476
  init_meter();
@@ -18211,7 +18725,7 @@ function flagsFromProfile2(flags, given, profile, home) {
18211
18725
  }
18212
18726
  }
18213
18727
  if (profile.reposDir && !has("repos-dir") && !has("repo")) {
18214
- out.reposDir = splitReposDirs(profile.reposDir).map((d) => home && /^~(\/|$)/.test(d) ? join20(home, d.slice(1)) : d).join(":");
18728
+ out.reposDir = splitReposDirs(profile.reposDir).map((d) => home && /^~(\/|$)/.test(d) ? join21(home, d.slice(1)) : d).join(":");
18215
18729
  used.push(`repos ${profile.reposDir}`);
18216
18730
  }
18217
18731
  if (profile.mcpStrict !== undefined && !has("strict-mcp") && !has("no-strict-mcp")) {
@@ -18301,6 +18815,29 @@ async function rereadAtOnce2(read, flags, given) {
18301
18815
  const profile = await read();
18302
18816
  return profile ? profile.atOnce ?? 1 : null;
18303
18817
  }
18818
+ async function rereadRunner2(read, flags, given, home) {
18819
+ if (given.includes("runner"))
18820
+ return null;
18821
+ const profile = await read();
18822
+ if (!profile)
18823
+ return null;
18824
+ const base = {
18825
+ ...flags,
18826
+ runner: "tmux",
18827
+ runnerCmd: given.includes("runner-cmd") ? flags.runnerCmd : undefined
18828
+ };
18829
+ const r = flagsFromProfile2(base, given, profile, home);
18830
+ const skipped = r.skipped.find((s) => s.startsWith("runner"));
18831
+ if (skipped)
18832
+ return { refused: `skipped from your engine profile: ${skipped} — fix it in Settings` };
18833
+ const refusal = sandboxRefusal({ sandbox: flags.sandbox, runner: r.flags.runner });
18834
+ if (refusal)
18835
+ return { refused: `runner ${r.flags.runner} not taken — ${refusal}` };
18836
+ return {
18837
+ runner: r.flags.runner,
18838
+ ...r.flags.runner === "command" && r.flags.runnerCmd ? { runnerCmd: r.flags.runnerCmd } : {}
18839
+ };
18840
+ }
18304
18841
  var PROFILE_RETRY_MS2 = [2000, 5000];
18305
18842
  async function withProfile2(client, flags, given, home, retry) {
18306
18843
  let profile = await readProfile(client);
@@ -18379,12 +18916,12 @@ function memberMcpConfigs2(o, cwd) {
18379
18916
  });
18380
18917
  const claudeDir = (o.env ?? process.env).CLAUDE_CONFIG_DIR;
18381
18918
  return [
18382
- read(claudeDir ? join20(claudeDir, ".claude.json") : join20(o.home, ".claude.json")),
18383
- ...cwd ? [read(join20(cwd, ".mcp.json"))] : []
18919
+ read(claudeDir ? join21(claudeDir, ".claude.json") : join21(o.home, ".claude.json")),
18920
+ ...cwd ? [read(join21(cwd, ".mcp.json"))] : []
18384
18921
  ];
18385
18922
  }
18386
18923
  function engineStateDir2(o, org, seat) {
18387
- return seatStateDir(orgStateDir(o.stateDir ?? join20(configDir(o.home, o.env ?? process.env), "engine"), org), seat);
18924
+ return seatStateDir(orgStateDir(o.stateDir ?? join21(configDir(o.home, o.env ?? process.env), "engine"), org), seat);
18388
18925
  }
18389
18926
  function engineSeat2(o, org) {
18390
18927
  try {
@@ -18411,11 +18948,11 @@ function engineDeps2(o, flags, source, runner, stateName) {
18411
18948
  const stateDir = engineStateDir2(o, flags.org, flags.seat);
18412
18949
  const file = statePath(stateDir, stateName);
18413
18950
  const mcpOrg = flags.org && engineOrg2(o, flags.org).seated ? flags.org : undefined;
18414
- const repo = flags.repo ? resolve9(cwd, flags.repo) : undefined;
18415
- const reposDirs = splitReposDirs(flags.reposDir).map((d) => resolve9(cwd, d));
18951
+ const repo = flags.repo ? resolve10(cwd, flags.repo) : undefined;
18952
+ const reposDirs = splitReposDirs(flags.reposDir).map((d) => resolve10(cwd, d));
18416
18953
  const { branch, prTitle } = flags;
18417
- const hooksDir = join20(stateDir, "hooks");
18418
- const permissionMark = (name) => join20(hooksDir, `${name}.permission.json`);
18954
+ const hooksDir = join21(stateDir, "hooks");
18955
+ const permissionMark = (name) => join21(hooksDir, `${name}.permission.json`);
18419
18956
  const withHook = (name, settings) => applyPermissionHook(settings, [...o.sledsArgv ?? ["sleds"], "permission-event", "--mark", permissionMark(name)].map(shq2).join(" "));
18420
18957
  return {
18421
18958
  source,
@@ -18438,15 +18975,18 @@ function engineDeps2(o, flags, source, runner, stateName) {
18438
18975
  ...flags.strictMcp ? { strictMcp: true } : {},
18439
18976
  ...flags.sandbox ? {
18440
18977
  sandbox: true,
18441
- writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd) => {
18978
+ writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd, allow) => {
18442
18979
  const gitCommonDir = gitCommonDirOf2(exec, cwd);
18443
- const dir = join20(stateDir, "sandbox");
18980
+ const dir = join21(stateDir, "sandbox");
18444
18981
  mkdirSync16(dir, { recursive: true, mode: 448 });
18445
- const p = join20(dir, `${name}.json`);
18982
+ const p = join21(dir, `${name}.json`);
18446
18983
  const walls = engineSandboxSettings2(o.home, o.env ?? process.env, gitCommonDir, [
18447
18984
  ...SANDBOX_DEFAULT_ALLOW,
18448
18985
  ...flags.sandboxAllow ?? []
18449
18986
  ]);
18987
+ walls.permissions.allow = [
18988
+ ...new Set([...walls.permissions.allow, ...allow ?? []])
18989
+ ];
18450
18990
  const settings = withHook(name, { ...walls });
18451
18991
  writeFileSync18(p, `${JSON.stringify(settings, null, 2)}
18452
18992
  `, { mode: 384 });
@@ -18454,10 +18994,11 @@ function engineDeps2(o, flags, source, runner, stateName) {
18454
18994
  return { settingsFile: p, gitCommonDir };
18455
18995
  })
18456
18996
  } : {},
18457
- writeHookSettings: (name) => {
18997
+ writeHookSettings: (name, allow) => {
18458
18998
  mkdirSync16(hooksDir, { recursive: true, mode: 448 });
18459
- const p = join20(hooksDir, `${name}.json`);
18460
- writeFileSync18(p, `${JSON.stringify(withHook(name, {}), null, 2)}
18999
+ const p = join21(hooksDir, `${name}.json`);
19000
+ const base = allow?.length ? { permissions: { allow: [...allow] } } : {};
19001
+ writeFileSync18(p, `${JSON.stringify(withHook(name, base), null, 2)}
18461
19002
  `, { mode: 384 });
18462
19003
  chmodSync5(p, 384);
18463
19004
  return p;
@@ -18483,9 +19024,9 @@ function engineDeps2(o, flags, source, runner, stateName) {
18483
19024
  exists
18484
19025
  }),
18485
19026
  writeMcpConfig: o.writeMcpConfig ?? ((name, runToken, worktree) => {
18486
- const dir = join20(stateDir, "mcp");
19027
+ const dir = join21(stateDir, "mcp");
18487
19028
  mkdirSync16(dir, { recursive: true, mode: 448 });
18488
- const p = join20(dir, `${name}.json`);
19029
+ const p = join21(dir, `${name}.json`);
18489
19030
  let others = {};
18490
19031
  if (flags.strictMcp && flags.mcpAllow?.length) {
18491
19032
  const r = mcpPassThrough(flags.mcpAllow, memberMcpConfigs2(o, worktree));
@@ -18501,9 +19042,9 @@ function engineDeps2(o, flags, source, runner, stateName) {
18501
19042
  }),
18502
19043
  removeMcpConfig: o.removeMcpConfig ?? ((p) => rmSync9(p, { force: true })),
18503
19044
  writePrompt: o.writePrompt ?? ((name, text) => {
18504
- const dir = join20(stateDir, "prompts");
19045
+ const dir = join21(stateDir, "prompts");
18505
19046
  mkdirSync16(dir, { recursive: true, mode: 448 });
18506
- const p = join20(dir, `${name}.md`);
19047
+ const p = join21(dir, `${name}.md`);
18507
19048
  writeFileSync18(p, text, { mode: 384 });
18508
19049
  return p;
18509
19050
  }),
@@ -18530,8 +19071,8 @@ function engineDeps2(o, flags, source, runner, stateName) {
18530
19071
  }
18531
19072
  function sandboxDenyRead2(home, env) {
18532
19073
  return {
18533
- dirs: [...new Set([configDir(home, env), join20(home, ".config", "sleds")])],
18534
- files: [...new Set([tokenPath(home, env), join20(home, ".sleds-token")])]
19074
+ dirs: [...new Set([configDir(home, env), join21(home, ".config", "sleds")])],
19075
+ files: [...new Set([tokenPath(home, env), join21(home, ".sleds-token")])]
18535
19076
  };
18536
19077
  }
18537
19078
  function engineSandboxSettings2(home, env, gitCommonDir, allow) {
@@ -18546,7 +19087,7 @@ function gitCommonDirOf2(exec, cwd) {
18546
19087
  const dir = r.status === 0 ? r.stdout.trim() : "";
18547
19088
  if (!dir)
18548
19089
  throw new Error(`git has no common dir for ${cwd}, so the sandbox has nowhere to commit`);
18549
- return resolve9(cwd, dir);
19090
+ return resolve10(cwd, dir);
18550
19091
  }
18551
19092
  function sandboxRefusal(flags) {
18552
19093
  if (!flags.sandbox)
@@ -18597,13 +19138,14 @@ async function driveEngine2(o, flags, deps, until = {}) {
18597
19138
  runner: deps.runner.name,
18598
19139
  atOnce: flags.atOnce,
18599
19140
  interval: flags.interval,
18600
- cliVersion: "0.1.43"
19141
+ cliVersion: "0.1.45"
18601
19142
  };
18602
19143
  const beat = (checkedAt) => {
18603
19144
  const m = deps.machine;
18604
19145
  try {
18605
19146
  writeLock(lockFile, checkedAt === undefined ? lock : {
18606
19147
  ...lock,
19148
+ runner: deps.runner.name,
18607
19149
  atOnce: deps.atOnce,
18608
19150
  checkedAt,
18609
19151
  nextCheckAt: checkedAt + flags.interval * 1000,
@@ -18876,7 +19418,7 @@ var engineCmd = async (o) => {
18876
19418
  armedOnly: true,
18877
19419
  engine: `engine:${flags.name}`,
18878
19420
  ...parsed.given.includes("at-once") ? { atOnce: flags.atOnce } : {},
18879
- runner: runner.name,
19421
+ runner: () => deps.runner.name,
18880
19422
  leaseMin: flags.lease,
18881
19423
  log
18882
19424
  });
@@ -18894,6 +19436,21 @@ var engineCmd = async (o) => {
18894
19436
  const started = flags;
18895
19437
  deps.rereadRoles = (read) => rereadRoles(read, started, parsed.given);
18896
19438
  deps.rereadAtOnce = (read) => rereadAtOnce2(read, started, parsed.given);
19439
+ let current = { runner, cmd: flags.runner === "command" ? flags.runnerCmd : undefined };
19440
+ deps.rereadRunner = async (read) => {
19441
+ const next = await rereadRunner2(read, started, parsed.given, o.home);
19442
+ if (!next)
19443
+ return null;
19444
+ if ("refused" in next)
19445
+ return next.refused;
19446
+ if (next.runner === current.runner.name && next.runnerCmd === current.cmd)
19447
+ return current.runner;
19448
+ const made = engineRunner2(o, { ...started, ...next });
19449
+ if (!made)
19450
+ return `runner ${next.runner} not taken — this CLI cannot start it`;
19451
+ current = { runner: made, cmd: next.runnerCmd };
19452
+ return made;
19453
+ };
18897
19454
  }
18898
19455
  log(engineHeader2(terminalOf2(o).c, deps.engine, (source.name === "jira" ? `Jira epic ${flags.jira}` : pickup === "auto" ? "your sled, Pickup: Auto (it arms the next ready item)" : "the routes armed on your sled") + (flags.org ? ` · org ${flags.org}` : "") + (flags.seat ? ` · as ${flags.seat}` : ""), runner.name, flags));
18899
19456
  if (flags.strictMcp && flags.agent)
@@ -18903,7 +19460,6 @@ var engineCmd = async (o) => {
18903
19460
  let afterTick;
18904
19461
  if (client) {
18905
19462
  const send = beater(client, o.now ?? Date.now);
18906
- const who = { name: flags.name, runner: runner.name };
18907
19463
  let refused;
18908
19464
  const sayRefused = (r) => {
18909
19465
  if (!r || r.ok || !r.refused || r.refused === refused)
@@ -18914,7 +19470,7 @@ var engineCmd = async (o) => {
18914
19470
  afterTick = () => {
18915
19471
  try {
18916
19472
  const m = deps.machine;
18917
- send(beatBody(deps.readState(), who, {}, {
19473
+ send(beatBody(deps.readState(), { name: flags.name, runner: deps.runner.name }, {}, {
18918
19474
  atOnce: deps.atOnce,
18919
19475
  ...m?.last ? { machine: m.last } : {},
18920
19476
  ...m?.hold && !m.off ? { hold: m.hold } : {}
@@ -19047,7 +19603,7 @@ async function enginePauseCmd2(o) {
19047
19603
  // src/commands/engineInstall.ts
19048
19604
  init_config();
19049
19605
  import {
19050
- existsSync as existsSync17,
19606
+ existsSync as existsSync18,
19051
19607
  mkdirSync as mkdirSync17,
19052
19608
  readFileSync as readFileSync19,
19053
19609
  readdirSync as readdirSync4,
@@ -19056,7 +19612,7 @@ import {
19056
19612
  writeFileSync as writeFileSync19
19057
19613
  } from "node:fs";
19058
19614
  import { hostname as hostname5 } from "node:os";
19059
- import { basename as basename5, dirname as dirname13, isAbsolute as isAbsolute3, join as join21 } from "node:path";
19615
+ import { basename as basename5, dirname as dirname13, isAbsolute as isAbsolute3, join as join22 } from "node:path";
19060
19616
  init_state();
19061
19617
  var ENGINE_LABEL2 = "ai.sleds.engine";
19062
19618
  var ENGINE_INSTALL_USAGE2 = `usage: sleds engine install [engine flags] | sleds engine uninstall [--name <n>] [--org <slug>]
@@ -19076,13 +19632,13 @@ function engineLabel2(name, org, seat) {
19076
19632
  return [ENGINE_LABEL2, org, seat?.trim(), name].filter((p) => Boolean(p)).map((p, i) => i === 0 ? p : engineSlug(p)).join(".");
19077
19633
  }
19078
19634
  function enginePlistPath2(home, name, org, seat) {
19079
- return join21(home, "Library", "LaunchAgents", `${engineLabel2(name, org, seat)}.plist`);
19635
+ return join22(home, "Library", "LaunchAgents", `${engineLabel2(name, org, seat)}.plist`);
19080
19636
  }
19081
19637
  function engineLogPath2(stateDir, name) {
19082
- return join21(stateDir, `${engineSlug(name)}.log`);
19638
+ return join22(stateDir, `${engineSlug(name)}.log`);
19083
19639
  }
19084
19640
  function engineEventsPath2(stateDir, name) {
19085
- return join21(stateDir, `${engineSlug(name)}.events.jsonl`);
19641
+ return join22(stateDir, `${engineSlug(name)}.events.jsonl`);
19086
19642
  }
19087
19643
  var xmlEscape4 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
19088
19644
  function buildEnginePlist2(o) {
@@ -19158,7 +19714,13 @@ function parseEnginePlist2(text) {
19158
19714
  if (!label || array === undefined)
19159
19715
  return null;
19160
19716
  const programArgs = [...array.matchAll(/<string>([^<]*)<\/string>/g)].map((m) => xmlUnescape2(m[1] ?? ""));
19161
- return { label: xmlUnescape2(label), programArgs };
19717
+ const envDict = /<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/.exec(text)?.[1];
19718
+ const path = envDict && /<key>PATH<\/key>\s*<string>([^<]*)<\/string>/.exec(envDict)?.[1];
19719
+ return {
19720
+ label: xmlUnescape2(label),
19721
+ programArgs,
19722
+ ...typeof path === "string" ? { path: xmlUnescape2(path) } : {}
19723
+ };
19162
19724
  }
19163
19725
  function engineArg2(programArgs, flag) {
19164
19726
  const at = programArgs.indexOf(`--${flag}`);
@@ -19173,7 +19735,7 @@ function readEngines2(dir, suffix, manager, parse) {
19173
19735
  }
19174
19736
  const out = [];
19175
19737
  for (const name of names.filter((n) => n.startsWith(`${ENGINE_LABEL2}.`) && n.endsWith(suffix))) {
19176
- const path = join21(dir, name);
19738
+ const path = join22(dir, name);
19177
19739
  let parsed;
19178
19740
  try {
19179
19741
  parsed = parse(readFileSync19(path, "utf8"), name);
@@ -19193,7 +19755,7 @@ function readEngines2(dir, suffix, manager, parse) {
19193
19755
  }
19194
19756
  function installedEngines2(home) {
19195
19757
  return [
19196
- ...readEngines2(join21(home, "Library", "LaunchAgents"), ".plist", "launchd", parseEnginePlist2),
19758
+ ...readEngines2(join22(home, "Library", "LaunchAgents"), ".plist", "launchd", parseEnginePlist2),
19197
19759
  ...readEngines2(engineUnitDir(home), ".service", "systemd", (text, file) => {
19198
19760
  const parsed = parseEngineUnit(text);
19199
19761
  return parsed && { label: basename5(file, ".service"), ...parsed };
@@ -19230,8 +19792,8 @@ async function ensureReposDir(o) {
19230
19792
  error(REPOS_DIR_NEEDED);
19231
19793
  return false;
19232
19794
  }
19233
- const exists = o.exists ?? existsSync17;
19234
- const missing = dirs.filter((d) => !exists(/^~(\/|$)/.test(d) ? join21(o.home, d.slice(1)) : d));
19795
+ const exists = o.exists ?? existsSync18;
19796
+ const missing = dirs.filter((d) => !exists(/^~(\/|$)/.test(d) ? join22(o.home, d.slice(1)) : d));
19235
19797
  if (missing.length) {
19236
19798
  error(`Not a folder on this machine: ${missing.join(", ")}. ${REPOS_DIR_NEEDED}`);
19237
19799
  return false;
@@ -19288,7 +19850,7 @@ function engineInstall2(o) {
19288
19850
  return installUnit2({ o, parsed, label, sledsArgv, env: agentEnv, logPath, exec });
19289
19851
  const plist = enginePlistPath2(o.home, parsed.name, parsed.org, seat);
19290
19852
  mkdirSync17(dirname13(plist), { recursive: true });
19291
- if (existsSync17(plist))
19853
+ if (existsSync18(plist))
19292
19854
  exec("launchctl", ["unload", plist]);
19293
19855
  for (const other of installedEngines2(o.home)) {
19294
19856
  if (other.manager !== "launchd" || other.plist === plist || other.org !== parsed.org)
@@ -19300,7 +19862,7 @@ function engineInstall2(o) {
19300
19862
  log(`replaced ${other.label}, this engine as installed before`);
19301
19863
  }
19302
19864
  const legacy = enginePlistPath2(o.home, parsed.name);
19303
- if (parsed.org && existsSync17(legacy)) {
19865
+ if (parsed.org && existsSync18(legacy)) {
19304
19866
  const cfg = readConfig(o.home);
19305
19867
  if (org.sole || parsed.org === (cfg.activeOrg ?? cfg.org?.slug)) {
19306
19868
  exec("launchctl", ["unload", legacy]);
@@ -19330,7 +19892,7 @@ function installUnit2(a) {
19330
19892
  const unitName = basename5(unit);
19331
19893
  const systemctl = (...args) => exec("systemctl", ["--user", ...args]);
19332
19894
  mkdirSync17(dirname13(unit), { recursive: true });
19333
- if (existsSync17(unit))
19895
+ if (existsSync18(unit))
19334
19896
  systemctl("stop", unitName);
19335
19897
  for (const other of installedEngines2(o.home)) {
19336
19898
  if (other.manager !== "systemd" || other.plist === unit || other.org !== parsed.org)
@@ -19388,7 +19950,7 @@ function engineUninstall2(o) {
19388
19950
  ...installed.filter((e) => e.manager === manager).map((e) => e.plist),
19389
19951
  ...args.org ? [] : [pathOf(manager)]
19390
19952
  ]);
19391
- const file = candidates.find((p) => existsSync17(p));
19953
+ const file = candidates.find((p) => existsSync18(p));
19392
19954
  if (!file) {
19393
19955
  log(`engine:${name} was not installed (no ${candidates[0]}).`);
19394
19956
  return 0;
@@ -19530,7 +20092,7 @@ import { closeSync as closeSync3, fstatSync as fstatSync2, openSync as openSync3
19530
20092
  import { hostname as hostname6 } from "node:os";
19531
20093
 
19532
20094
  // src/commands/engineStatus.ts
19533
- import { existsSync as existsSync18, readFileSync as readFileSync20 } from "node:fs";
20095
+ import { existsSync as existsSync19, readFileSync as readFileSync20 } from "node:fs";
19534
20096
  init_state();
19535
20097
 
19536
20098
  // src/engine/status.ts
@@ -19713,7 +20275,7 @@ function statusDir(o, engine, org) {
19713
20275
  const env = o.env ?? process.env;
19714
20276
  const which = engineOrg({ home: o.home, env }, org);
19715
20277
  const slug = which.seated || org ? which.slug : null;
19716
- const has = (d) => existsSync18(statePath(d, engine)) || existsSync18(lockPath(d, engine));
20278
+ const has = (d) => existsSync19(statePath(d, engine)) || existsSync19(lockPath(d, engine));
19717
20279
  const seat = engineSeat({ home: o.home, env }, slug ?? undefined);
19718
20280
  const dir = engineStateDir({ ...o, env }, slug);
19719
20281
  const seatDir = engineStateDir({ ...o, env }, slug, seat);
@@ -19820,7 +20382,7 @@ var engineLogCmd = (o) => {
19820
20382
 
19821
20383
  // src/commands/engineProfile.ts
19822
20384
  init_src();
19823
- import { existsSync as existsSync19, readFileSync as readFileSync21 } from "node:fs";
20385
+ import { existsSync as existsSync20, readFileSync as readFileSync21 } from "node:fs";
19824
20386
  import { hostname as hostname7 } from "node:os";
19825
20387
  var PROFILE_USAGE = "usage: sleds engine profile [--json] | sleds engine profile set <field>=<value>… [--workspace] [--json] | sleds engine profile set --stdin [--workspace] [--json] | sleds engine profile --strict-mcp [--mcp-allow <a,b>] | --no-strict-mcp [--workspace] [--json] | sleds engine profile --sandbox [--sandbox-allow <a,b>] | --no-sandbox [--workspace] [--json] | sleds engine profile set --hold on|off [--workspace] [--json] | sleds engine profile set cmuxPassword=<pw> | --cmux-password <pw> [--json]";
19826
20388
  var PROFILE_FIELDS = [
@@ -20048,7 +20610,7 @@ function localOf(o) {
20048
20610
  return {
20049
20611
  engine: `engine:${name}`,
20050
20612
  canInstall,
20051
- installed: canInstall && existsSync19(enginePlistPath(o.home, name))
20613
+ installed: canInstall && existsSync20(enginePlistPath(o.home, name))
20052
20614
  };
20053
20615
  }
20054
20616
  async function engineProfileCmd(o) {
@@ -20170,7 +20732,7 @@ var keychainLine = (k) => k === "set" ? "cmuxPassword: set (Keychain)" : k === "
20170
20732
 
20171
20733
  // src/commands/engineStatus.ts
20172
20734
  init_state();
20173
- import { existsSync as existsSync20, readFileSync as readFileSync22 } from "node:fs";
20735
+ import { existsSync as existsSync21, readFileSync as readFileSync22 } from "node:fs";
20174
20736
  import { hostname as hostname8 } from "node:os";
20175
20737
  var ENGINE_STATUS_USAGE = "usage: sleds engine status [--name <n>] [--org <slug>] [--json]";
20176
20738
  function parseStatusArgs(argv, host) {
@@ -20199,7 +20761,7 @@ function statusDir2(o, engine, org) {
20199
20761
  const env = o.env ?? process.env;
20200
20762
  const which = engineOrg({ home: o.home, env }, org);
20201
20763
  const slug = which.seated || org ? which.slug : null;
20202
- const has = (d) => existsSync20(statePath(d, engine)) || existsSync20(lockPath(d, engine));
20764
+ const has = (d) => existsSync21(statePath(d, engine)) || existsSync21(lockPath(d, engine));
20203
20765
  const seat = engineSeat({ home: o.home, env }, slug ?? undefined);
20204
20766
  const dir = engineStateDir({ ...o, env }, slug);
20205
20767
  const seatDir = engineStateDir({ ...o, env }, slug, seat);
@@ -20212,7 +20774,7 @@ function statusDir2(o, engine, org) {
20212
20774
  }
20213
20775
  function readEngineStatus(stateDir, engine, host, pidAlive, seat) {
20214
20776
  const file = statePath(stateDir, engine);
20215
- const text = existsSync20(file) ? safeRead(file) : "";
20777
+ const text = existsSync21(file) ? safeRead(file) : "";
20216
20778
  return engineStatus({
20217
20779
  engine,
20218
20780
  state: text ? parseState(text) : { runs: [] },
@@ -20347,10 +20909,10 @@ init_meter();
20347
20909
 
20348
20910
  // src/owed.ts
20349
20911
  init_config();
20350
- import { existsSync as existsSync21, mkdirSync as mkdirSync18, readFileSync as readFileSync23, renameSync as renameSync7, writeFileSync as writeFileSync20 } from "node:fs";
20351
- import { dirname as dirname14, join as join22 } from "node:path";
20352
- var owedPath = (home) => join22(configDir(home), "owed.json");
20353
- var sessionPath = (home) => join22(configDir(home), "session.json");
20912
+ import { existsSync as existsSync22, mkdirSync as mkdirSync18, readFileSync as readFileSync23, renameSync as renameSync7, writeFileSync as writeFileSync20 } from "node:fs";
20913
+ import { dirname as dirname14, join as join23 } from "node:path";
20914
+ var owedPath = (home) => join23(configDir(home), "owed.json");
20915
+ var sessionPath = (home) => join23(configDir(home), "session.json");
20354
20916
  var OWED_TTL_MS = 3 * 24 * 60 * 60000;
20355
20917
  var str13 = (v) => typeof v === "string" ? v : undefined;
20356
20918
  function sourceKey(s) {
@@ -20365,7 +20927,7 @@ function sourceText2(s) {
20365
20927
  return [`${str13(s.kind) ?? "?"} ${str13(s.project) ?? "?"}`, str13(s.doc), str13(s.item)].filter(Boolean).join(" › ");
20366
20928
  }
20367
20929
  var readJson = (p) => {
20368
- if (!existsSync21(p))
20930
+ if (!existsSync22(p))
20369
20931
  return null;
20370
20932
  try {
20371
20933
  return JSON.parse(readFileSync23(p, "utf8"));
@@ -21395,7 +21957,7 @@ var login = async (o) => {
21395
21957
  // src/commands/logout.ts
21396
21958
  init_config();
21397
21959
  init_state();
21398
- import { existsSync as existsSync22, readFileSync as readFileSync24, rmSync as rmSync11 } from "node:fs";
21960
+ import { existsSync as existsSync23, readFileSync as readFileSync24, rmSync as rmSync11 } from "node:fs";
21399
21961
  import { hostname as hostname10 } from "node:os";
21400
21962
  init_settings();
21401
21963
  var LOGOUT_USAGE = "usage: sleds logout [--org <slug>] [--json]";
@@ -21552,7 +22114,7 @@ async function logout(o) {
21552
22114
  };
21553
22115
  writeConfig(o.home, next);
21554
22116
  const tp = tokenPath(o.home, env);
21555
- if (existsSync22(tp) && readFileSync24(tp, "utf8").trim() === seat.token)
22117
+ if (existsSync23(tp) && readFileSync24(tp, "utf8").trim() === seat.token)
21556
22118
  rmSync11(tp, { force: true });
21557
22119
  const active = next.activeOrg ?? next.org?.slug ?? null;
21558
22120
  const result = { ok: true, email, org: slug, paused, active };
@@ -21630,20 +22192,20 @@ var meter = async (o) => {
21630
22192
  init_config();
21631
22193
  init_meter();
21632
22194
  import { spawnSync as spawnSync7 } from "node:child_process";
21633
- import { existsSync as existsSync23, mkdirSync as mkdirSync20, readFileSync as readFileSync25, rmSync as rmSync12, writeFileSync as writeFileSync22 } from "node:fs";
22195
+ import { existsSync as existsSync24, mkdirSync as mkdirSync20, readFileSync as readFileSync25, rmSync as rmSync12, writeFileSync as writeFileSync22 } from "node:fs";
21634
22196
  import { hostname as hostname11 } from "node:os";
21635
22197
  import { dirname as dirname15 } from "node:path";
21636
22198
  var defaultWhich3 = (cmd) => {
21637
22199
  const path = process.env.PATH ?? "";
21638
22200
  for (const dir of path.split(":").filter(Boolean)) {
21639
22201
  const p = `${dir}/${cmd}`;
21640
- if (existsSync23(p))
22202
+ if (existsSync24(p))
21641
22203
  return p;
21642
22204
  }
21643
22205
  return null;
21644
22206
  };
21645
22207
  var findClaude2 = (home, configured, which) => {
21646
- if (configured && existsSync23(configured))
22208
+ if (configured && existsSync24(configured))
21647
22209
  return configured;
21648
22210
  const found = which("claude");
21649
22211
  if (found)
@@ -21654,7 +22216,7 @@ var findClaude2 = (home, configured, which) => {
21654
22216
  "/usr/local/bin/claude",
21655
22217
  `${home}/.npm-global/bin/claude`
21656
22218
  ])
21657
- if (existsSync23(p))
22219
+ if (existsSync24(p))
21658
22220
  return p;
21659
22221
  return null;
21660
22222
  };
@@ -21680,7 +22242,7 @@ var musherInstall = (o) => {
21680
22242
  ...env.CLAUDE_CONFIG_DIR ? { CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR } : {}
21681
22243
  };
21682
22244
  const exec = o.exec ?? defaultExecSync;
21683
- if (existsSync23(plist))
22245
+ if (existsSync24(plist))
21684
22246
  exec("launchctl", ["unload", plist]);
21685
22247
  writeFileSync22(plist, buildMusherPlist({ sledsArgv: o.sledsArgv, env: agentEnv, logPath: musherLogPath(dir), label }));
21686
22248
  const r = exec("launchctl", ["load", plist]);
@@ -21701,7 +22263,7 @@ var musherUninstall = (o) => {
21701
22263
  const env = o.env ?? process.env;
21702
22264
  const plist = musherPlistPath(o.home, env);
21703
22265
  const exec = o.exec ?? defaultExecSync;
21704
- if (existsSync23(plist)) {
22266
+ if (existsSync24(plist)) {
21705
22267
  exec("launchctl", ["unload", plist]);
21706
22268
  rmSync12(plist, { force: true });
21707
22269
  log(`musher removed: ${musherLabel(env)}`);
@@ -21736,7 +22298,7 @@ var musherStatus = async (o) => {
21736
22298
  const env = o.env ?? process.env;
21737
22299
  const plist = musherPlistPath(o.home, env);
21738
22300
  const cfg = readConfig(o.home);
21739
- log(existsSync23(plist) ? `agent ${musherLabel(env)} installed (${plist})` : "agent not installed — sleds musher --install");
22301
+ log(existsSync24(plist) ? `agent ${musherLabel(env)} installed (${plist})` : "agent not installed — sleds musher --install");
21740
22302
  const claude = findClaude2(o.home, cfg.musher?.claudePath, o.which ?? defaultWhich3);
21741
22303
  log(`claude ${claude ?? "not found — sleds musher --claude /path/to/claude"}`);
21742
22304
  log(`mode ${cfg.musher?.permissionMode ?? DEFAULT_PERMISSION_MODE} · timeout ${cfg.musher?.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES} min`);
@@ -21966,7 +22528,7 @@ var on = (o) => {
21966
22528
  init_config();
21967
22529
  init_meter();
21968
22530
  init_settings();
21969
- import { existsSync as existsSync24, rmSync as rmSync13 } from "node:fs";
22531
+ import { existsSync as existsSync25, rmSync as rmSync13 } from "node:fs";
21970
22532
  var ORG_USAGE = "usage: sleds org list [--json] | sleds org use <slug>";
21971
22533
  var orgRows = (cfg, activeSlug) => {
21972
22534
  const seats = { ...cfg.orgs ?? {} };
@@ -22033,7 +22595,7 @@ var orgUse = (o) => {
22033
22595
  }
22034
22596
  writeConfig(o.home, withActiveOrg(cfg, slug, seat));
22035
22597
  writeTokenFile(o.home, seat.token);
22036
- if (existsSync24(settingsPath(o.home)) && cfg.mode !== "on") {
22598
+ if (existsSync25(settingsPath(o.home)) && cfg.mode !== "on") {
22037
22599
  const settings = readSettings(o.home);
22038
22600
  const pointed = readProxy(settings);
22039
22601
  const url = pointed?.url ?? cfg.proxyUrl;
@@ -22070,7 +22632,7 @@ var orgCmd = (o) => {
22070
22632
  init_src();
22071
22633
  import { spawnSync as spawnSync8 } from "node:child_process";
22072
22634
  import { writeFileSync as writeFileSync23 } from "node:fs";
22073
- import { join as join23 } from "node:path";
22635
+ import { join as join24 } from "node:path";
22074
22636
  function packFileName(key) {
22075
22637
  return `pack-${slugify(key, 80) || "item"}.md`;
22076
22638
  }
@@ -22098,7 +22660,7 @@ var packCmd = async (o) => {
22098
22660
  log(r.text);
22099
22661
  return 0;
22100
22662
  }
22101
- const path = join23(o.cwd ?? process.cwd(), packFileName(key));
22663
+ const path = join24(o.cwd ?? process.cwd(), packFileName(key));
22102
22664
  (o.write ?? ((p, t) => writeFileSync23(p, t.endsWith(`
22103
22665
  `) ? t : `${t}
22104
22666
  `)))(path, r.text);
@@ -22224,12 +22786,81 @@ var promptEvent = async (o) => {
22224
22786
  return code;
22225
22787
  };
22226
22788
 
22789
+ // src/commands/reopen.ts
22790
+ var REOPEN_USAGE = 'usage: sleds reopen <key> --reason "<why it is reopened>" [--json]';
22791
+ function parseReopenArgs(argv) {
22792
+ let key;
22793
+ let reason = "";
22794
+ let json = false;
22795
+ for (let i = 0;i < argv.length; i++) {
22796
+ const a = argv[i];
22797
+ if (a === "--json")
22798
+ json = true;
22799
+ else if (a === "--reason") {
22800
+ const value = argv[i + 1];
22801
+ if (value === undefined || value.startsWith("--"))
22802
+ return { error: "--reason needs a value" };
22803
+ reason = value;
22804
+ i++;
22805
+ } else if (a.startsWith("--"))
22806
+ return { error: `unknown flag ${a}` };
22807
+ else if (key === undefined)
22808
+ key = a;
22809
+ else
22810
+ return { error: `one item at a time — "${a}" is extra` };
22811
+ }
22812
+ if (!key)
22813
+ return { error: "which item?" };
22814
+ return { key, reason: reason.trim(), json };
22815
+ }
22816
+ function reopenedLine(key, j) {
22817
+ const shown = typeof j.key === "string" ? j.key : key;
22818
+ return typeof j.sled === "string" && j.sled ? `${shown} reopened — back on ${j.sled}'s sled` : `${shown} reopened — back to ready, on no sled`;
22819
+ }
22820
+ async function reopenCmd(o) {
22821
+ const log = o.log ?? console.log;
22822
+ const error = o.error ?? console.error;
22823
+ const a = parseReopenArgs(o.argv);
22824
+ if ("error" in a) {
22825
+ error(a.error);
22826
+ error(REOPEN_USAGE);
22827
+ return 2;
22828
+ }
22829
+ if (!a.reason) {
22830
+ error(`${a.key}: say why it is reopened — it goes on the item's thread`);
22831
+ error(REOPEN_USAGE);
22832
+ return 1;
22833
+ }
22834
+ const client = engineClient({ home: o.home, ...o.client ? { client: o.client } : {} });
22835
+ if ("error" in client) {
22836
+ error(client.error);
22837
+ return 1;
22838
+ }
22839
+ const r = await client.postFull("/proxy/reopen", { project: a.key, reason: a.reason });
22840
+ if (!r) {
22841
+ error(`${a.key}: SLEDS unreachable — nothing reopened`);
22842
+ return 1;
22843
+ }
22844
+ const ok = r.status < 300 && r.json?.ok !== false;
22845
+ if (a.json)
22846
+ log(JSON.stringify(r.json ?? { ok: false, error: `HTTP ${r.status}` }));
22847
+ if (!ok) {
22848
+ const why = typeof r.json?.error === "string" ? r.json.error : `HTTP ${r.status}`;
22849
+ if (!a.json)
22850
+ error(r.status === 404 && !r.json ? `${a.key}: SLEDS does not know this seat — run \`sleds init\`` : `${a.key}: ${why}`);
22851
+ return 1;
22852
+ }
22853
+ if (!a.json)
22854
+ log(reopenedLine(a.key, r.json ?? {}));
22855
+ return 0;
22856
+ }
22857
+
22227
22858
  // src/commands/route.ts
22228
22859
  init_src();
22229
22860
  import { readFileSync as readFileSync27 } from "node:fs";
22230
22861
  import { hostname as hostname12 } from "node:os";
22231
22862
  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>]";
22232
- 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]";
22863
+ var USAGE2 = "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]";
22233
22864
  var ANSWER_USAGE = "usage: sleds route answer <key>/<node> <answer…> | sleds route answer <key>/<node> --stdin [--json]";
22234
22865
  var PROPOSE_USAGE = "usage: sleds route propose <key> (--file <nodes.json> | --stdin) [--name <engine>] [--json]";
22235
22866
  var APPROVE_USAGE = "usage: sleds route approve <key> [--file <nodes.json>] [--json]";
@@ -22784,14 +23415,14 @@ var routeCmd = async (o) => {
22784
23415
  const parsed = parseEngineArgs(o.argv.filter((a) => a !== "--dot" && a !== "--long" && a !== "--json"), o.host ?? hostname12());
22785
23416
  if ("error" in parsed) {
22786
23417
  error(parsed.error);
22787
- error(USAGE);
23418
+ error(USAGE2);
22788
23419
  return 2;
22789
23420
  }
22790
23421
  const { positional } = parsed;
22791
23422
  const isRun = positional[0] === "run";
22792
23423
  const key = isRun ? positional[1] : positional[0];
22793
23424
  if (!key) {
22794
- error(USAGE);
23425
+ error(USAGE2);
22795
23426
  return 2;
22796
23427
  }
22797
23428
  if (asJson) {
@@ -23047,15 +23678,15 @@ var seatEvent = async (o) => {
23047
23678
  };
23048
23679
 
23049
23680
  // src/commands/seatSync.ts
23050
- import { execFileSync as execFileSync4 } from "node:child_process";
23051
- import { existsSync as existsSync25, mkdirSync as mkdirSync22, readFileSync as readFileSync28, readdirSync as readdirSync5, rmSync as rmSync16, writeFileSync as writeFileSync25 } from "node:fs";
23681
+ import { execFileSync as execFileSync5 } from "node:child_process";
23682
+ import { existsSync as existsSync26, mkdirSync as mkdirSync22, readFileSync as readFileSync28, readdirSync as readdirSync5, rmSync as rmSync16, writeFileSync as writeFileSync25 } from "node:fs";
23052
23683
  import { platform as platform2 } from "node:os";
23053
- import { dirname as dirname17, join as join25 } from "node:path";
23684
+ import { dirname as dirname17, join as join26 } from "node:path";
23054
23685
 
23055
23686
  // src/cookieDb.ts
23056
23687
  import { copyFileSync, rmSync as rmSync15 } from "node:fs";
23057
23688
  import { tmpdir as tmpdir2 } from "node:os";
23058
- import { join as join24 } from "node:path";
23689
+ import { join as join25 } from "node:path";
23059
23690
  var QUERY = "SELECT name, hex(encrypted_value) AS ev FROM cookies WHERE host_key LIKE '%claude.ai%'";
23060
23691
  var isBun = () => typeof globalThis.Bun !== "undefined";
23061
23692
  async function queryRows(dbPath) {
@@ -23084,7 +23715,7 @@ function nodeSqlite() {
23084
23715
  return mod;
23085
23716
  }
23086
23717
  async function readCookieRows(dbPath) {
23087
- const copy = join24(tmpdir2(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
23718
+ const copy = join25(tmpdir2(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
23088
23719
  copyFileSync(dbPath, copy);
23089
23720
  try {
23090
23721
  const rows = await queryRows(copy);
@@ -23368,7 +23999,7 @@ function findFile(root, name) {
23368
23999
  const stack = [root];
23369
24000
  while (stack.length > 0) {
23370
24001
  const dir = stack.pop();
23371
- if (!dir || !existsSync25(dir))
24002
+ if (!dir || !existsSync26(dir))
23372
24003
  continue;
23373
24004
  let entries;
23374
24005
  try {
@@ -23377,7 +24008,7 @@ function findFile(root, name) {
23377
24008
  continue;
23378
24009
  }
23379
24010
  for (const e of entries) {
23380
- const p = join25(dir, e.name);
24011
+ const p = join26(dir, e.name);
23381
24012
  if (e.isDirectory())
23382
24013
  stack.push(p);
23383
24014
  else if (e.name === name)
@@ -23388,8 +24019,8 @@ function findFile(root, name) {
23388
24019
  }
23389
24020
  function findCookieDb(home, source) {
23390
24021
  const b = BROWSERS[source.browser];
23391
- const root = join25(home, APP_SUPPORT, b.appDir);
23392
- if (!existsSync25(root))
24022
+ const root = join26(home, APP_SUPPORT, b.appDir);
24023
+ if (!existsSync26(root))
23393
24024
  throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
23394
24025
  if (!b.profiled) {
23395
24026
  const db = findFile(root, "Cookies");
@@ -23397,15 +24028,15 @@ function findCookieDb(home, source) {
23397
24028
  throw new Error(`no cookie store under ${root}`);
23398
24029
  return db;
23399
24030
  }
23400
- const localStatePath = join25(root, "Local State");
23401
- const localState = existsSync25(localStatePath) ? readFileSync28(localStatePath, "utf8") : null;
24031
+ const localStatePath = join26(root, "Local State");
24032
+ const localState = existsSync26(localStatePath) ? readFileSync28(localStatePath, "utf8") : null;
23402
24033
  const wanted = source.profile ?? "Default";
23403
24034
  const { dir, ambiguous } = resolveProfileDir(localState, wanted);
23404
24035
  if (ambiguous)
23405
24036
  throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
23406
24037
  if (!dir)
23407
24038
  throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
23408
- const db = cookieDbCandidates(join25(root, dir)).find((p) => existsSync25(p));
24039
+ const db = cookieDbCandidates(join26(root, dir)).find((p) => existsSync26(p));
23409
24040
  if (!db)
23410
24041
  throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
23411
24042
  return db;
@@ -23415,7 +24046,7 @@ async function readCookieRowsBun(home, source) {
23415
24046
  }
23416
24047
  function keychainKey(service) {
23417
24048
  try {
23418
- return execFileSync4("security", ["find-generic-password", "-w", "-s", service], {
24049
+ return execFileSync5("security", ["find-generic-password", "-w", "-s", service], {
23419
24050
  encoding: "utf8"
23420
24051
  }).trim();
23421
24052
  } catch {
@@ -23427,7 +24058,7 @@ function decryptJar(rows, safeStoragePw) {
23427
24058
  }
23428
24059
  var curlUsage = (org, cookie) => {
23429
24060
  try {
23430
- const out = execFileSync4("curl", usageCurlArgs(), {
24061
+ const out = execFileSync5("curl", usageCurlArgs(), {
23431
24062
  input: usageCurlConfig(org, cookie),
23432
24063
  encoding: "utf8",
23433
24064
  maxBuffer: 8000000
@@ -23449,7 +24080,7 @@ function fetchSeatUsage(jar, org, now, run) {
23449
24080
  }
23450
24081
  function launchAgentPlist(bun, script, home, source) {
23451
24082
  const label = launchLabel(source);
23452
- const log = join25(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
24083
+ const log = join26(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
23453
24084
  const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
23454
24085
  const args = [bun, script, "seat-sync", ...sourceArgs(source)].map((a) => ` <string>${esc(a)}</string>`).join(`
23455
24086
  `);
@@ -23471,7 +24102,7 @@ ${args}
23471
24102
  `;
23472
24103
  }
23473
24104
  function plistPath2(home, source) {
23474
- return join25(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
24105
+ return join26(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
23475
24106
  }
23476
24107
  function install(home, argv, source, log) {
23477
24108
  const bun = argv[0] ?? process.execPath;
@@ -23480,7 +24111,7 @@ function install(home, argv, source, log) {
23480
24111
  log("cannot resolve the sleds script path to install the timer");
23481
24112
  return 1;
23482
24113
  }
23483
- const dir = join25(home, "Library", "LaunchAgents");
24114
+ const dir = join26(home, "Library", "LaunchAgents");
23484
24115
  mkdirSync22(dir, { recursive: true });
23485
24116
  const path = plistPath2(home, source);
23486
24117
  const tokenPath = seatTokenPath(home, source);
@@ -23492,19 +24123,19 @@ function install(home, argv, source, log) {
23492
24123
  }
23493
24124
  writeFileSync25(path, launchAgentPlist(bun, script, home, source), { mode: 384 });
23494
24125
  try {
23495
- execFileSync4("launchctl", ["unload", path], { stdio: "ignore" });
24126
+ execFileSync5("launchctl", ["unload", path], { stdio: "ignore" });
23496
24127
  } catch {}
23497
- execFileSync4("launchctl", ["load", "-w", path]);
24128
+ execFileSync5("launchctl", ["load", "-w", path]);
23498
24129
  log(`seat-sync timer installed (every ${SYNC_INTERVAL_S / 60} min) → ${path}`);
23499
24130
  return 0;
23500
24131
  }
23501
24132
  function uninstall(home, source, log) {
23502
24133
  const path = plistPath2(home, source);
23503
24134
  try {
23504
- execFileSync4("launchctl", ["unload", "-w", path], { stdio: "ignore" });
24135
+ execFileSync5("launchctl", ["unload", "-w", path], { stdio: "ignore" });
23505
24136
  } catch {}
23506
24137
  rmSync16(seatTokenPath(home, source), { force: true });
23507
- log(existsSync25(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
24138
+ log(existsSync26(path) ? `seat-sync timer removed (${path} can be deleted)` : "no seat-sync timer");
23508
24139
  return 0;
23509
24140
  }
23510
24141
  var seatSync = async (o) => {
@@ -23776,7 +24407,7 @@ var specEvent = async (o) => {
23776
24407
  };
23777
24408
 
23778
24409
  // src/commands/status.ts
23779
- import { existsSync as existsSync26 } from "node:fs";
24410
+ import { existsSync as existsSync27 } from "node:fs";
23780
24411
  init_config();
23781
24412
  init_settings();
23782
24413
  var HEALTH_TIMEOUT_MS2 = 4000;
@@ -23864,7 +24495,7 @@ var status3 = async (o) => {
23864
24495
  const log = o.log ?? console.log;
23865
24496
  const nowMs = (o.now ?? Date.now)();
23866
24497
  const cfg = readConfig(o.home);
23867
- const settings = existsSync26(settingsPath(o.home)) ? readSettings(o.home) : {};
24498
+ const settings = existsSync27(settingsPath(o.home)) ? readSettings(o.home) : {};
23868
24499
  const inSettings = readProxy(settings);
23869
24500
  const picked = selectOrg(cfg, undefined, o.env ?? process.env);
23870
24501
  const viaEnv = picked.from === "env";
@@ -24057,7 +24688,7 @@ var announce = (o, events) => {
24057
24688
 
24058
24689
  // src/commands/stopEvent.ts
24059
24690
  init_meter();
24060
- import { existsSync as existsSync27, readFileSync as readFileSync30 } from "node:fs";
24691
+ import { existsSync as existsSync28, readFileSync as readFileSync30 } from "node:fs";
24061
24692
 
24062
24693
  // src/transcript.ts
24063
24694
  var isObject16 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -24153,7 +24784,7 @@ async function postTranscript(o, payload, now) {
24153
24784
  const src = meterSource(o.home);
24154
24785
  if (!src)
24155
24786
  return false;
24156
- const read = o.readFile ?? ((p) => existsSync27(p) ? readFileSync30(p, "utf8") : null);
24787
+ const read = o.readFile ?? ((p) => existsSync28(p) ? readFileSync30(p, "utf8") : null);
24157
24788
  let text;
24158
24789
  try {
24159
24790
  text = read(path);
@@ -24231,7 +24862,7 @@ var stopEvent = async (o) => {
24231
24862
 
24232
24863
  // src/commands/uninstall.ts
24233
24864
  import { spawnSync as spawnSync9 } from "node:child_process";
24234
- import { existsSync as existsSync28, rmSync as rmSync17 } from "node:fs";
24865
+ import { existsSync as existsSync29, rmSync as rmSync17 } from "node:fs";
24235
24866
  init_config();
24236
24867
  init_meter();
24237
24868
  init_settings();
@@ -24244,7 +24875,7 @@ var uninstall2 = (o) => {
24244
24875
  (o.log ?? console.log)(` ${codexLine}`);
24245
24876
  const log = o.log ?? console.log;
24246
24877
  const removed = [];
24247
- if (existsSync28(settingsPath(o.home))) {
24878
+ if (existsSync29(settingsPath(o.home))) {
24248
24879
  let settings = readSettings(o.home);
24249
24880
  let changed = false;
24250
24881
  if (hasEitherKey(settings)) {
@@ -24291,18 +24922,18 @@ var uninstall2 = (o) => {
24291
24922
  writeSettings(o.home, settings);
24292
24923
  }
24293
24924
  for (const file of [meterCachePath(o.home), ...allSeatStatePaths(o.home)]) {
24294
- if (existsSync28(file)) {
24925
+ if (existsSync29(file)) {
24295
24926
  rmSync17(file, { force: true });
24296
24927
  removed.push(file);
24297
24928
  }
24298
24929
  }
24299
24930
  const skill = skillDir(o.home);
24300
- if (existsSync28(skill)) {
24931
+ if (existsSync29(skill)) {
24301
24932
  rmSync17(skill, { recursive: true, force: true });
24302
24933
  removed.push(skill);
24303
24934
  }
24304
24935
  const plist = musherPlistPath(o.home);
24305
- if (existsSync28(plist)) {
24936
+ if (existsSync29(plist)) {
24306
24937
  (o.unloadAgent ?? defaultUnload)(plist);
24307
24938
  rmSync17(plist, { force: true });
24308
24939
  removed.push(`launchd agent ${musherLabel()}`);
@@ -24373,7 +25004,7 @@ var verify = async (o) => {
24373
25004
  };
24374
25005
 
24375
25006
  // src/commands/webhooks.ts
24376
- var USAGE2 = "usage: sleds webhooks [--json] | sleds webhooks deliveries <id or name> [--json]";
25007
+ var USAGE3 = "usage: sleds webhooks [--json] | sleds webhooks deliveries <id or name> [--json]";
24377
25008
  var str14 = (x) => typeof x === "string" ? x : "";
24378
25009
  function ago3(at, now) {
24379
25010
  const m = Math.max(0, Math.floor((now - at) / 60000));
@@ -24424,7 +25055,7 @@ var webhooksCmd = async (o) => {
24424
25055
  const now = (o.now ?? Date.now)();
24425
25056
  const sub = words[0];
24426
25057
  if (sub && (sub !== "deliveries" || words.length !== 2)) {
24427
- error(USAGE2);
25058
+ error(USAGE3);
24428
25059
  return 2;
24429
25060
  }
24430
25061
  const path = sub === "deliveries" ? `/proxy/webhooks/deliveries?hook=${encodeURIComponent(words[1] ?? "")}` : "/proxy/webhooks";
@@ -24457,13 +25088,13 @@ var webhooksCmd = async (o) => {
24457
25088
 
24458
25089
  // src/config.ts
24459
25090
  init_settings();
24460
- import { chmodSync as chmodSync7, existsSync as existsSync29, mkdirSync as mkdirSync23, readFileSync as readFileSync31, renameSync as renameSync9, writeFileSync as writeFileSync26 } from "node:fs";
24461
- import { join as join26, resolve as resolve10 } from "node:path";
25091
+ import { chmodSync as chmodSync7, existsSync as existsSync30, mkdirSync as mkdirSync23, readFileSync as readFileSync31, renameSync as renameSync9, writeFileSync as writeFileSync26 } from "node:fs";
25092
+ import { join as join27, resolve as resolve11 } from "node:path";
24462
25093
  var configDir2 = (home, env = process.env) => {
24463
25094
  const o = env.SLEDS_HOME?.trim();
24464
- return o ? resolve10(home, o) : join26(home, ".config", "sleds");
25095
+ return o ? resolve11(home, o) : join27(home, ".config", "sleds");
24465
25096
  };
24466
- var configPath2 = (home) => join26(configDir2(home), "config.json");
25097
+ var configPath2 = (home) => join27(configDir2(home), "config.json");
24467
25098
  var isObject17 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
24468
25099
  var optString2 = (v) => typeof v === "string" ? v : undefined;
24469
25100
  var parsePriorEnv2 = (v) => {
@@ -24595,7 +25226,7 @@ var parseOrgInfo2 = (raw) => {
24595
25226
  };
24596
25227
  var readConfig2 = (home) => {
24597
25228
  const p = configPath2(home);
24598
- if (!existsSync29(p))
25229
+ if (!existsSync30(p))
24599
25230
  return {};
24600
25231
  return parseConfig2(JSON.parse(readFileSync31(p, "utf8")));
24601
25232
  };
@@ -24649,15 +25280,15 @@ var defaultExec5 = (cmd, args) => {
24649
25280
  };
24650
25281
 
24651
25282
  // src/settings.ts
24652
- import { dirname as dirname18, join as join27, resolve as resolve11 } from "node:path";
25283
+ import { dirname as dirname18, join as join28, resolve as resolve12 } from "node:path";
24653
25284
  var withSeatEnv2 = (command, home, env = process.env, org) => {
24654
25285
  const parts = [];
24655
25286
  const sh = env.SLEDS_HOME?.trim();
24656
25287
  const cc = env.CLAUDE_CONFIG_DIR?.trim();
24657
25288
  if (sh)
24658
- parts.push(`SLEDS_HOME=${shellWord2(resolve11(home, sh))}`);
25289
+ parts.push(`SLEDS_HOME=${shellWord2(resolve12(home, sh))}`);
24659
25290
  if (cc)
24660
- parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(resolve11(home, cc))}`);
25291
+ parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(resolve12(home, cc))}`);
24661
25292
  parts.push(command);
24662
25293
  return pinOrg4(parts.join(" "), org);
24663
25294
  };
@@ -24684,7 +25315,7 @@ var isSledsStopHook2 = isSledsHookFor2("stop-event");
24684
25315
  var isSledsPermissionHook2 = isSledsHookFor2("permission-event");
24685
25316
 
24686
25317
  // src/index.ts
24687
- var USAGE3 = `usage: sleds <command> [options]
25318
+ var USAGE4 = `usage: sleds <command> [options]
24688
25319
 
24689
25320
  commands:
24690
25321
  init Sign in through the browser, make this machine a seat, install the /sled skill
@@ -24739,6 +25370,10 @@ commands:
24739
25370
  --run start Claude Code with the pack as the first prompt
24740
25371
  webhooks The org's outbound webhooks (admins); deliveries <id or name> lists one hook's
24741
25372
  last hundred deliveries. Registering and Redeliver stay in the app.
25373
+ deploy A release step's one line: tell SLEDS this commit is now running in an environment.
25374
+ --env <slug> the environment (required); --service <name> for a monorepo
25375
+ --sha / --repo else the CI's own variables, then git
25376
+ Token from SLEDS_DEPLOY_TOKEN (Settings \u203A Deploys). SLEDS down never fails a release.
24742
25377
  sled --json What is on your sled (mine with each route, proposed, sent), for a front end.
24743
25378
  sleds sled top KEY put KEY first on your sled; the engine picks it next
24744
25379
  route <key> Print an item's route: its nodes, what each waits on, what is ready, where each
@@ -24766,6 +25401,9 @@ commands:
24766
25401
  --mode <permission-mode> Claude Code's (default auto; acceptEdits stops an unattended
24767
25402
  session at git push; an account without auto passes acceptEdits)
24768
25403
  --model <id> Claude Code's model
25404
+ reopen <key> Put back an item that landed by mistake (a skipped node closed its route): its
25405
+ landing is cleared and it goes back on the sled it came off, or to ready.
25406
+ --reason "<text>" why \u2014 required; it goes on the item's thread (--json)
24769
25407
  engine The long-running loop over the routes Run armed on your sled: takes each ready
24770
25408
  node, starts a session on its own branch, follows its PR to the merge (CI failures
24771
25409
  and review feedback go back to the session; a merge lands the node, closes the
@@ -24887,11 +25525,11 @@ var wantsHelp = (argv) => {
24887
25525
  return false;
24888
25526
  };
24889
25527
  var commandUsage = (cmd) => {
24890
- const lines = USAGE3.split(`
25528
+ const lines = USAGE4.split(`
24891
25529
  `);
24892
25530
  const start = lines.findIndex((l) => l === ` ${cmd}` || l.startsWith(` ${cmd} `));
24893
25531
  if (start === -1)
24894
- return USAGE3;
25532
+ return USAGE4;
24895
25533
  const section = [lines[start]];
24896
25534
  for (const l of lines.slice(start + 1)) {
24897
25535
  if (!/^ {3,}\S/.test(l))
@@ -24947,8 +25585,12 @@ var main = async (argv) => {
24947
25585
  return packCmd({ home, argv: argv.slice(1) });
24948
25586
  case "webhooks":
24949
25587
  return webhooksCmd({ home, argv: argv.slice(1) });
25588
+ case "deploy":
25589
+ return deployCmd({ argv: argv.slice(1) });
24950
25590
  case "sled":
24951
25591
  return sledCmd({ home, argv: argv.slice(1) });
25592
+ case "reopen":
25593
+ return reopenCmd({ home, argv: argv.slice(1) });
24952
25594
  case "engine":
24953
25595
  case "route": {
24954
25596
  if (cmd === "engine" && argv[1] === "status")
@@ -25141,19 +25783,19 @@ var main = async (argv) => {
25141
25783
  case "help":
25142
25784
  case "--help":
25143
25785
  case "-h":
25144
- console.log(USAGE3);
25786
+ console.log(USAGE4);
25145
25787
  console.log(`
25146
25788
  ${renderHelpText(argv[1])}`);
25147
25789
  return 0;
25148
25790
  case "version":
25149
25791
  case "--version":
25150
25792
  case "-v":
25151
- console.log("sleds 0.1.43");
25793
+ console.log("sleds 0.1.45");
25152
25794
  return 0;
25153
25795
  default:
25154
25796
  console.error(`unknown command: ${cmd}
25155
25797
  `);
25156
- console.error(USAGE3);
25798
+ console.error(USAGE4);
25157
25799
  return 1;
25158
25800
  }
25159
25801
  };