@nanopm/cli 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -801,16 +801,16 @@ function paragraphs(text2) {
801
801
  return (text2 ?? "").split(/\n\s*\n/).map((p) => p.trim().replace(/\s+/g, " ")).filter(Boolean);
802
802
  }
803
803
  function wholeParagraphs(text2, max, most = Infinity) {
804
- let out = "";
804
+ let out2 = "";
805
805
  for (const p of paragraphs(text2).slice(0, most)) {
806
- const room = max - out.length - (out ? 2 : 0);
806
+ const room = max - out2.length - (out2 ? 2 : 0);
807
807
  const kept = p.length <= room ? p : wholeSentences(p, room);
808
- if (/[.!?]$/.test(kept) || kept === p) out = out ? `${out}
808
+ if (/[.!?]$/.test(kept) || kept === p) out2 = out2 ? `${out2}
809
809
 
810
810
  ${kept}` : kept;
811
811
  if (kept !== p) break;
812
812
  }
813
- return out || null;
813
+ return out2 || null;
814
814
  }
815
815
  var CODE_PATTERNS = [
816
816
  [/\b\d+\s?px\b/, "a size in pixels"],
@@ -1279,13 +1279,13 @@ function doubtLine(m, f) {
1279
1279
  }
1280
1280
  function problemStory(m, f) {
1281
1281
  const story = [m.situation, m.progress, m.obstacle, m.rationale].map(sentence).filter(Boolean).join(" ");
1282
- const out = { story, doubt: doubtLine(m, f), test: testLine(m) };
1282
+ const out2 = { story, doubt: doubtLine(m, f), test: testLine(m) };
1283
1283
  if (m.knowledge === "hypothesis" && m.assumptions.length) {
1284
1284
  const parts = m.assumptions.map((a) => lower(a).replace(/[.]$/, ""));
1285
1285
  const joined = parts.length === 1 ? parts[0] : `${parts.slice(0, -1).join("; ")}, and ${parts[parts.length - 1]}`;
1286
- out.assumes = follow("This assumes", joined);
1286
+ out2.assumes = follow("This assumes", joined);
1287
1287
  }
1288
- return out;
1288
+ return out2;
1289
1289
  }
1290
1290
 
1291
1291
  // ../shared/src/signals.ts
@@ -1602,15 +1602,15 @@ function stripTree(n) {
1602
1602
  return row;
1603
1603
  }
1604
1604
  function flattenNeedTree(roots) {
1605
- const out = [];
1605
+ const out2 = [];
1606
1606
  const walk = (list) => {
1607
1607
  for (const n of list) {
1608
- out.push(n);
1608
+ out2.push(n);
1609
1609
  walk(n.children);
1610
1610
  }
1611
1611
  };
1612
1612
  walk(roots);
1613
- return out;
1613
+ return out2;
1614
1614
  }
1615
1615
  function needCounts(roots) {
1616
1616
  const live = flattenNeedTree(roots).filter((n) => n.status !== "dropped");
@@ -1629,10 +1629,10 @@ function personaName(title, slug) {
1629
1629
  function threadSentence(who, path) {
1630
1630
  const [need, difficulty, precise] = path.map((p) => lowerFirst(p.label.trim().replace(/[.!?;:,]+$/, "")));
1631
1631
  if (!need) return "";
1632
- let out = `${who.trim()} wants to ${need}`;
1633
- if (difficulty) out += `, but ${difficulty}`;
1634
- if (precise) out += `: ${precise}`;
1635
- return `${out}.`;
1632
+ let out2 = `${who.trim()} wants to ${need}`;
1633
+ if (difficulty) out2 += `, but ${difficulty}`;
1634
+ if (precise) out2 += `: ${precise}`;
1635
+ return `${out2}.`;
1636
1636
  }
1637
1637
  function lowerFirst(text2) {
1638
1638
  if (!text2) return text2;
@@ -1643,14 +1643,14 @@ function lowerFirst(text2) {
1643
1643
  function needsRunConflict(active, wanted, ctx) {
1644
1644
  const whose = (r) => r.persona ?? ctx.primary;
1645
1645
  const persona = whose(wanted);
1646
- for (const run of active) {
1647
- if (whose(run) !== persona) continue;
1646
+ for (const run2 of active) {
1647
+ if (whose(run2) !== persona) continue;
1648
1648
  const name = persona ? ctx.name(persona) : "this persona";
1649
- if (run.scope === "map") return `${name}'s whole map is already being drawn; this waits for it.`;
1650
- if (wanted.scope === "map") return `NanoPM is digging into ${run.scope} on ${name}'s map; map it again once that is done.`;
1649
+ if (run2.scope === "map") return `${name}'s whole map is already being drawn; this waits for it.`;
1650
+ if (wanted.scope === "map") return `NanoPM is digging into ${run2.scope} on ${name}'s map; map it again once that is done.`;
1651
1651
  const up = (ref) => ctx.lineage(ref).map((r) => r.toUpperCase());
1652
- if (up(wanted.scope).includes(run.scope.toUpperCase()) || up(run.scope).includes(wanted.scope.toUpperCase())) {
1653
- return `NanoPM is already digging into ${run.scope}, which covers this; it waits for that run.`;
1652
+ if (up(wanted.scope).includes(run2.scope.toUpperCase()) || up(run2.scope).includes(wanted.scope.toUpperCase())) {
1653
+ return `NanoPM is already digging into ${run2.scope}, which covers this; it waits for that run.`;
1654
1654
  }
1655
1655
  }
1656
1656
  return null;
@@ -2034,9 +2034,9 @@ function placeRanks(crew, pinned) {
2034
2034
  if (slots[i] !== null) continue;
2035
2035
  slots[i] = loose[next++] ?? null;
2036
2036
  }
2037
- const out = /* @__PURE__ */ new Map();
2038
- slots.forEach((id, i) => id && out.set(id, i + 1));
2039
- return out;
2037
+ const out2 = /* @__PURE__ */ new Map();
2038
+ slots.forEach((id, i) => id && out2.set(id, i + 1));
2039
+ return out2;
2040
2040
  }
2041
2041
  function settlePriorities(inOrder) {
2042
2042
  const p = inOrder.map((r) => r.priority);
@@ -2084,14 +2084,14 @@ function changelogText(c) {
2084
2084
  return [line(c.headline), ...c.points.map(line)].filter(Boolean).join("\n");
2085
2085
  }
2086
2086
  function pointsWithin(points, room) {
2087
- const out = [];
2087
+ const out2 = [];
2088
2088
  let used = 0;
2089
2089
  for (const p of points.map((x) => x.trim().replace(/\s+/g, " ")).filter(Boolean)) {
2090
2090
  if (used + p.length + 1 > room) break;
2091
- out.push(p);
2091
+ out2.push(p);
2092
2092
  used += p.length + 1;
2093
2093
  }
2094
- return out;
2094
+ return out2;
2095
2095
  }
2096
2096
  var descriptionText = (text2) => wholeParagraphs(text2, DESCRIPTION_MAX2, DESCRIPTION_PARAGRAPHS2);
2097
2097
  function parseChangelog(text2) {
@@ -2151,10 +2151,10 @@ var HORIZONS = ["now", "next", "later"];
2151
2151
  var HORIZON_WORD = { now: "Now", next: "Next", later: "Later" };
2152
2152
  var BOARD_FILL = { now: 3, next: 4 };
2153
2153
  function boardOrder(branches) {
2154
- const out = [];
2154
+ const out2 = [];
2155
2155
  const longest = Math.max(0, ...branches.map((b) => b.length));
2156
- for (let i = 0; i < longest; i += 1) for (const b of branches) if (i < b.length) out.push(b[i]);
2157
- return out;
2156
+ for (let i = 0; i < longest; i += 1) for (const b of branches) if (i < b.length) out2.push(b[i]);
2157
+ return out2;
2158
2158
  }
2159
2159
  function placeNewCards(existing, fresh) {
2160
2160
  const count2 = { now: 0, next: 0, later: 0 };
@@ -2163,14 +2163,14 @@ function placeNewCards(existing, fresh) {
2163
2163
  count2[c.horizon] += 1;
2164
2164
  last[c.horizon] = Math.max(last[c.horizon], c.place);
2165
2165
  }
2166
- const out = /* @__PURE__ */ new Map();
2166
+ const out2 = /* @__PURE__ */ new Map();
2167
2167
  for (const id of fresh) {
2168
2168
  const horizon = count2.now < BOARD_FILL.now ? "now" : count2.next < BOARD_FILL.next ? "next" : "later";
2169
2169
  count2[horizon] += 1;
2170
2170
  last[horizon] += 1;
2171
- out.set(id, { horizon, place: last[horizon] });
2171
+ out2.set(id, { horizon, place: last[horizon] });
2172
2172
  }
2173
- return out;
2173
+ return out2;
2174
2174
  }
2175
2175
  var SOLUTION_COLUMNS = "id, ref, opportunity_id, problem_id, title, description, changelog, steps, size, assumption, reasoning, sources, rank, status, horizon, place, placed_by, superseded_by, job_id, created_at, specced_at";
2176
2176
  function boardOf(all, solutions) {
@@ -2214,12 +2214,12 @@ function solutionsRunOf(params) {
2214
2214
  return { depth: p.depth === "deep" ? "deep" : "quick", leaves };
2215
2215
  }
2216
2216
  function leavesAtWork(params) {
2217
- const out = /* @__PURE__ */ new Map();
2217
+ const out2 = /* @__PURE__ */ new Map();
2218
2218
  for (const p of params) {
2219
- const run = solutionsRunOf(p);
2220
- for (const leaf of run?.leaves ?? []) if (out.get(leaf) !== "deep") out.set(leaf, run.depth);
2219
+ const run2 = solutionsRunOf(p);
2220
+ for (const leaf of run2?.leaves ?? []) if (out2.get(leaf) !== "deep") out2.set(leaf, run2.depth);
2221
2221
  }
2222
- return out;
2222
+ return out2;
2223
2223
  }
2224
2224
  function brainstormPlan(map, ref, deep, busy = /* @__PURE__ */ new Set()) {
2225
2225
  const wanted = ref.trim().toUpperCase();
@@ -2929,7 +2929,7 @@ function whoFirst(sentence2) {
2929
2929
  }
2930
2930
  var safePath = (file) => !file.startsWith("/") && !file.split(/[\\/]/).includes("..");
2931
2931
  function checkProposals(answer, fileExists) {
2932
- const out = {};
2932
+ const out2 = {};
2933
2933
  for (const lever of OUTCOME_LEVERS) {
2934
2934
  const kept = [];
2935
2935
  const seen = /* @__PURE__ */ new Set();
@@ -2945,9 +2945,9 @@ function checkProposals(answer, fileExists) {
2945
2945
  const found = p.found && safePath(p.found.file) && fileExists(p.found.file) ? { file: p.found.file, line: p.found.line, note: p.found.note } : null;
2946
2946
  kept.push({ sentence: sentence2, found });
2947
2947
  }
2948
- if (kept.length) out[lever] = kept;
2948
+ if (kept.length) out2[lever] = kept;
2949
2949
  }
2950
- return out;
2950
+ return out2;
2951
2951
  }
2952
2952
 
2953
2953
  // ../shared/src/reword.ts
@@ -3696,6 +3696,8 @@ function sandboxSettingsFor(home, grantedTools, network = [], answerFile) {
3696
3696
  enabled: true,
3697
3697
  allowUnsandboxedCommands: false,
3698
3698
  failIfUnavailable: true,
3699
+ // No local ports (spec 77 §6): Claude Code's allowLocalBinding binds every interface and opens
3700
+ // every localhost port to the shell — the founder's databases, proxies, debug ports.
3699
3701
  network: { allowedDomains: network.filter((h) => !/(^|\.)github(usercontent)?\.com$/.test(h)) }
3700
3702
  };
3701
3703
  }
@@ -4218,11 +4220,11 @@ import postgres from "postgres";
4218
4220
 
4219
4221
  // ../connectors/src/sql-text.ts
4220
4222
  function stripLiterals(sql) {
4221
- const out = [];
4223
+ const out2 = [];
4222
4224
  let i = 0;
4223
4225
  const n = sql.length;
4224
4226
  const blank = (from, to) => {
4225
- for (let k = from; k < to; k += 1) out.push(sql[k] === "\n" ? "\n" : " ");
4227
+ for (let k = from; k < to; k += 1) out2.push(sql[k] === "\n" ? "\n" : " ");
4226
4228
  };
4227
4229
  while (i < n) {
4228
4230
  const c = sql[i];
@@ -4284,10 +4286,10 @@ function stripLiterals(sql) {
4284
4286
  i = k;
4285
4287
  continue;
4286
4288
  }
4287
- out.push(c);
4289
+ out2.push(c);
4288
4290
  i += 1;
4289
4291
  }
4290
- return out.join("");
4292
+ return out2.join("");
4291
4293
  }
4292
4294
  function refuseUnlessSingleStatement(sql) {
4293
4295
  const trimmed = sql.trim();
@@ -4749,7 +4751,7 @@ async function readCapped(response, maxBytes) {
4749
4751
  const reader = response.body?.getReader();
4750
4752
  if (!reader) return { body: await response.text(), truncated: false };
4751
4753
  const decoder = new TextDecoder("utf-8", { fatal: false });
4752
- let out = "";
4754
+ let out2 = "";
4753
4755
  let bytes = 0;
4754
4756
  let truncated = false;
4755
4757
  for (; ; ) {
@@ -4757,16 +4759,16 @@ async function readCapped(response, maxBytes) {
4757
4759
  if (done) break;
4758
4760
  bytes += value.byteLength;
4759
4761
  if (bytes > maxBytes) {
4760
- out += decoder.decode(value.subarray(0, Math.max(0, value.byteLength - (bytes - maxBytes))), { stream: true });
4762
+ out2 += decoder.decode(value.subarray(0, Math.max(0, value.byteLength - (bytes - maxBytes))), { stream: true });
4761
4763
  truncated = true;
4762
4764
  await reader.cancel().catch(() => {
4763
4765
  });
4764
4766
  break;
4765
4767
  }
4766
- out += decoder.decode(value, { stream: true });
4768
+ out2 += decoder.decode(value, { stream: true });
4767
4769
  }
4768
- out += decoder.decode();
4769
- return { body: out, truncated };
4770
+ out2 += decoder.decode();
4771
+ return { body: out2, truncated };
4770
4772
  }
4771
4773
  async function defaultLookup(host) {
4772
4774
  const records = await dnsLookup(host, { all: true });
@@ -4899,11 +4901,11 @@ async function queryPosthog(key, host, projectId, hogql, opts = {}) {
4899
4901
  const results = data.results ?? [];
4900
4902
  const kept = results.slice(0, maxRows);
4901
4903
  const rows2 = kept.map((row) => {
4902
- const out = {};
4904
+ const out2 = {};
4903
4905
  columns2.forEach((column, i) => {
4904
- out[column] = Array.isArray(row) ? row[i] : void 0;
4906
+ out2[column] = Array.isArray(row) ? row[i] : void 0;
4905
4907
  });
4906
- return out;
4908
+ return out2;
4907
4909
  });
4908
4910
  return {
4909
4911
  rows: rows2,
@@ -5175,16 +5177,16 @@ function playSearchUrl(term, country) {
5175
5177
  return url.toString();
5176
5178
  }
5177
5179
  function blobs(html) {
5178
- const out = [];
5180
+ const out2 = [];
5179
5181
  for (const match of html.matchAll(/AF_initDataCallback\((\{[\s\S]*?\})\);<\/script>/g)) {
5180
5182
  const data = /data:\s*(\[[\s\S]*\])\s*,\s*sideChannel/.exec(match[1]);
5181
5183
  if (!data) continue;
5182
5184
  try {
5183
- out.push(JSON.parse(data[1]));
5185
+ out2.push(JSON.parse(data[1]));
5184
5186
  } catch {
5185
5187
  }
5186
5188
  }
5187
- return out;
5189
+ return out2;
5188
5190
  }
5189
5191
  function cards(node, found = []) {
5190
5192
  if (!Array.isArray(node)) return found;
@@ -5335,6 +5337,8 @@ var BUILD_SKILL = "build";
5335
5337
  var BUILD_STEPS_MIN = 1;
5336
5338
  var BUILD_STEPS_MAX = 8;
5337
5339
  var BUILD_STEP_MAX = 120;
5340
+ var BUILD_UNDERSTOOD_MIN = 40;
5341
+ var BUILD_UNDERSTOOD_MAX = 600;
5338
5342
  var BUILD_RESUMES_MAX = 3;
5339
5343
  var BUILD_TRAILER = "Co-Authored-By: NanoPM <noreply@nanopm.app>";
5340
5344
  var BUILD_COLUMNS = "id, ref, title, status, actor, agent_spec, spec";
@@ -5673,8 +5677,8 @@ async function pullRequestState(cwd, number) {
5673
5677
  }
5674
5678
  async function openPullRequest(cwd, pr) {
5675
5679
  try {
5676
- const out = await gh(cwd, ["pr", "create", "--draft", "--head", pr.branch, "--base", pr.base, "--title", pr.title, "--body", pr.body]);
5677
- const url = reviewUrlFrom(out) ?? out.split("\n").at(-1) ?? "";
5680
+ const out2 = await gh(cwd, ["pr", "create", "--draft", "--head", pr.branch, "--base", pr.base, "--title", pr.title, "--body", pr.body]);
5681
+ const url = reviewUrlFrom(out2) ?? out2.split("\n").at(-1) ?? "";
5678
5682
  return { ok: true, url, number: Number(/\/pull\/(\d+)/.exec(url)?.[1] ?? 0) };
5679
5683
  } catch (err) {
5680
5684
  return { ok: false, reason: errorMessage(err).split("\n").filter(Boolean).slice(-2).join(" ") || "gh pr create failed" };
@@ -5691,8 +5695,8 @@ async function git2(cwd, args) {
5691
5695
  }
5692
5696
  async function commitsSince(cwd, since) {
5693
5697
  const branch = await defaultBranch(cwd);
5694
- const out = await git2(cwd, ["log", branch, `--since=${since.toISOString()}`, "--format=%cI"]);
5695
- const stamps = out.split("\n").filter(Boolean);
5698
+ const out2 = await git2(cwd, ["log", branch, `--since=${since.toISOString()}`, "--format=%cI"]);
5699
+ const stamps = out2.split("\n").filter(Boolean);
5696
5700
  return { count: stamps.length, newestAt: stamps[0] ? new Date(stamps[0]) : null };
5697
5701
  }
5698
5702
  async function reviewDeploys(client, daemonId, now = /* @__PURE__ */ new Date()) {
@@ -5822,16 +5826,16 @@ var NEEDS_THE_REQUESTERS_CLONE = ["write_repo", "run_commands"];
5822
5826
  var NEEDS_THE_REQUESTERS_IDENTITY = /* @__PURE__ */ new Set(["want", "start"]);
5823
5827
  async function clonelessSkills(skillsDir = findSkillsDir()) {
5824
5828
  const names = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && existsSync2(join7(skillsDir, d.name, "skill.json"))).map((d) => d.name);
5825
- const out = [];
5829
+ const out2 = [];
5826
5830
  for (const name of names) {
5827
5831
  try {
5828
5832
  const { manifest } = await loadSkill(name, skillsDir);
5829
5833
  if (NEEDS_THE_REQUESTERS_IDENTITY.has(name)) continue;
5830
- if (!manifest.capabilities.some((c) => NEEDS_THE_REQUESTERS_CLONE.includes(c))) out.push(name);
5834
+ if (!manifest.capabilities.some((c) => NEEDS_THE_REQUESTERS_CLONE.includes(c))) out2.push(name);
5831
5835
  } catch {
5832
5836
  }
5833
5837
  }
5834
- return out.sort();
5838
+ return out2.sort();
5835
5839
  }
5836
5840
 
5837
5841
  // ../daemon/src/problem-mapping-harness.ts
@@ -6354,15 +6358,15 @@ async function readDocs(cwd, opts = {}) {
6354
6358
  };
6355
6359
  await walk(cwd, 0);
6356
6360
  found.sort((a, b) => Number(WRITTEN.test(b)) - Number(WRITTEN.test(a)) || a.length - b.length);
6357
- const out = [];
6361
+ const out2 = [];
6358
6362
  for (const path of found.slice(0, DOCS_MAX)) {
6359
6363
  try {
6360
6364
  if ((await stat(path)).size > 4e5) continue;
6361
- out.push({ path: relative(cwd, path), text: (await readFile5(path, "utf8")).slice(0, DOC_CHARS) });
6365
+ out2.push({ path: relative(cwd, path), text: (await readFile5(path, "utf8")).slice(0, DOC_CHARS) });
6362
6366
  } catch {
6363
6367
  }
6364
6368
  }
6365
- return out;
6369
+ return out2;
6366
6370
  }
6367
6371
 
6368
6372
  // ../daemon/src/persona-job.ts
@@ -7960,18 +7964,18 @@ function droppedAt(rows2, parent, byId, current, target) {
7960
7964
  });
7961
7965
  }
7962
7966
  function sourcesOf(ids, heard, read6, today) {
7963
- const out = [];
7967
+ const out2 = [];
7964
7968
  for (const id of ids) {
7965
7969
  const h = heard.find((q) => q.id === id);
7966
7970
  if (h) {
7967
- out.push({ kind: h.kind, ref: h.ref, quote: clip2(h.quote, NEED_QUOTE_MAX), read_at: today });
7971
+ out2.push({ kind: h.kind, ref: h.ref, quote: clip2(h.quote, NEED_QUOTE_MAX), read_at: today });
7968
7972
  continue;
7969
7973
  }
7970
7974
  const w = read6.find((f) => f.id === id && f.read);
7971
- if (w) out.push({ kind: "web", ref: w.page, quote: clip2(w.quote, NEED_QUOTE_MAX), read_at: w.published ?? today });
7975
+ if (w) out2.push({ kind: "web", ref: w.page, quote: clip2(w.quote, NEED_QUOTE_MAX), read_at: w.published ?? today });
7972
7976
  }
7973
7977
  const seen = /* @__PURE__ */ new Set();
7974
- return out.filter((s) => seen.has(`${s.ref}|${s.quote}`) ? false : (seen.add(`${s.ref}|${s.quote}`), true)).slice(0, NEED_SOURCES_MAX);
7978
+ return out2.filter((s) => seen.has(`${s.ref}|${s.quote}`) ? false : (seen.add(`${s.ref}|${s.quote}`), true)).slice(0, NEED_SOURCES_MAX);
7975
7979
  }
7976
7980
  function checkHeard(quotes, material) {
7977
7981
  const kept = [];
@@ -8031,7 +8035,7 @@ function descendants(rows2, root2) {
8031
8035
  for (let hops = 0; n && n.status === "replaced" && n.replaced_by && hops < 50; hops += 1) n = byId.get(n.replaced_by);
8032
8036
  return n && n.status !== "replaced" ? n : null;
8033
8037
  };
8034
- const out = [root2];
8038
+ const out2 = [root2];
8035
8039
  const ids = /* @__PURE__ */ new Set([root2.id]);
8036
8040
  for (let grew = true; grew; ) {
8037
8041
  grew = false;
@@ -8040,12 +8044,12 @@ function descendants(rows2, root2) {
8040
8044
  const p = current(r.parent_id);
8041
8045
  if (p && ids.has(p.id)) {
8042
8046
  ids.add(r.id);
8043
- out.push(r);
8047
+ out2.push(r);
8044
8048
  grew = true;
8045
8049
  }
8046
8050
  }
8047
8051
  }
8048
- return out;
8052
+ return out2;
8049
8053
  }
8050
8054
  function pathOf2(rows2, node) {
8051
8055
  const found = needTree(rows2).flatMap(function walk(n) {
@@ -8345,18 +8349,18 @@ function snapshot(input) {
8345
8349
  for (const n of input.nodes) byPersona.set(n.persona, [...byPersona.get(n.persona) ?? [], n]);
8346
8350
  let lines = 0;
8347
8351
  for (const [persona, rows2] of byPersona) {
8348
- const out = [];
8352
+ const out2 = [];
8349
8353
  const walk = (list, under) => {
8350
8354
  for (const n of list) {
8351
8355
  if (n.status === "dropped" || n.outside_mission || lines >= NEEDS_LINES_MAX) continue;
8352
8356
  lines += 1;
8353
8357
  cites.set(n.ref.toUpperCase(), { kind: "needs", ref: n.ref, note: clip3(n.label, 140) });
8354
- out.push({ ref: n.ref, level: n.level, line: n.label, statement: n.statement, under });
8358
+ out2.push({ ref: n.ref, level: n.level, line: n.label, statement: n.statement, under });
8355
8359
  walk(n.children, n.ref);
8356
8360
  }
8357
8361
  };
8358
8362
  walk(needTree(rows2), null);
8359
- if (out.length) needsMaps.push({ persona, lines: out });
8363
+ if (out2.length) needsMaps.push({ persona, lines: out2 });
8360
8364
  }
8361
8365
  if (ask7.note) cites.set("NOTE", { kind: "founder", ref: "note", note: clip3(ask7.note, 140) });
8362
8366
  const tree = flattenTree(liveTree(input.mine));
@@ -8494,22 +8498,22 @@ async function deepRun(ctx, mine) {
8494
8498
  const full = { ...m, explorers };
8495
8499
  const synthesize = async () => {
8496
8500
  const cost = await role6({ ...ctx, role: "synthesizer", tools: "none", say: "Making one map of what was found\u2026", prompt: JSON.stringify(full) });
8497
- const out = SynthesizedMap.parse(JSON.parse(cost.text));
8501
+ const out2 = SynthesizedMap.parse(JSON.parse(cost.text));
8498
8502
  await logStep(
8499
8503
  ctx,
8500
8504
  "opportunities.synthesized",
8501
8505
  "making one map",
8502
- `The synthesizer made one map: ${mapLine(out.opportunities)}; kept ${out.keep.length} as they were, removed ${out.removed.length}${out.reconsider.length ? `, ${out.reconsider.length} of yours worth reconsidering` : ""}.`,
8506
+ `The synthesizer made one map: ${mapLine(out2.opportunities)}; kept ${out2.keep.length} as they were, removed ${out2.removed.length}${out2.reconsider.length ? `, ${out2.reconsider.length} of yours worth reconsidering` : ""}.`,
8503
8507
  cost
8504
8508
  );
8505
- return out;
8509
+ return out2;
8506
8510
  };
8507
8511
  const prioritize = async (synth2, again) => {
8508
8512
  const cost = await role6({ ...ctx, role: "prioritizer", tools: "none", say: "Ordering the map\u2026", prompt: JSON.stringify({ objective: m.objective, business: m.business, personas: m.personas, note: m.note, map: mapForPrioritizer(synth2, mine) }) });
8509
- const out = PrioritizedMap.parse(JSON.parse(cost.text));
8510
- const n = (p) => out.rows.filter((r) => r.priority === p).length;
8511
- await logStep(ctx, "opportunities.prioritized", again ? "the revision (the order)" : "ordering", `The prioritizer ordered ${out.rows.length}: ${n("high")} high, ${n("medium")} medium, ${n("low")} low.`, cost);
8512
- return out;
8513
+ const out2 = PrioritizedMap.parse(JSON.parse(cost.text));
8514
+ const n = (p) => out2.rows.filter((r) => r.priority === p).length;
8515
+ await logStep(ctx, "opportunities.prioritized", again ? "the revision (the order)" : "ordering", `The prioritizer ordered ${out2.rows.length}: ${n("high")} high, ${n("medium")} medium, ${n("low")} low.`, cost);
8516
+ return out2;
8513
8517
  };
8514
8518
  let synth = await synthesize();
8515
8519
  const reading = beside(readEvidence(ctx, synth.opportunities));
@@ -8628,8 +8632,8 @@ function revisionLine(m) {
8628
8632
  }
8629
8633
  async function readEvidence(ctx, rows2) {
8630
8634
  const said = ctx.known.material.users_said.filter((s) => ctx.known.cites.get(s.id.toUpperCase())?.kind === "user");
8631
- const out = /* @__PURE__ */ new Map();
8632
- if (!said.length || !rows2.length) return out;
8635
+ const out2 = /* @__PURE__ */ new Map();
8636
+ if (!said.length || !rows2.length) return out2;
8633
8637
  const cost = await role6({
8634
8638
  ...ctx,
8635
8639
  role: "evidence",
@@ -8647,19 +8651,19 @@ async function readEvidence(ctx, rows2) {
8647
8651
  dropped += 1;
8648
8652
  continue;
8649
8653
  }
8650
- const list = out.get(m.key) ?? [];
8654
+ const list = out2.get(m.key) ?? [];
8651
8655
  if (!list.some((x) => x.id === text2.id && x.quote === m.quote)) list.push({ id: text2.id, quote: m.quote.trim() });
8652
- out.set(m.key, list);
8656
+ out2.set(m.key, list);
8653
8657
  }
8654
- const quotes = [...out.values()].reduce((n, l) => n + l.length, 0);
8658
+ const quotes = [...out2.values()].reduce((n, l) => n + l.length, 0);
8655
8659
  await logStep(
8656
8660
  ctx,
8657
8661
  "opportunities.evidenced",
8658
8662
  "reading the evidence (alongside)",
8659
- `The evidence reader matched what users said to ${out.size} problem${out.size === 1 ? "" : "s"}, ${quotes} quote${quotes === 1 ? "" : "s"} checked word for word${dropped ? `; ${dropped} not found in the words, left out` : ""}.${read6.notes ? ` ${wholeSentences(read6.notes, 400)}` : ""}`,
8663
+ `The evidence reader matched what users said to ${out2.size} problem${out2.size === 1 ? "" : "s"}, ${quotes} quote${quotes === 1 ? "" : "s"} checked word for word${dropped ? `; ${dropped} not found in the words, left out` : ""}.${read6.notes ? ` ${wholeSentences(read6.notes, 400)}` : ""}`,
8660
8664
  cost
8661
8665
  );
8662
- return out;
8666
+ return out2;
8663
8667
  }
8664
8668
  function withEvidence(rows2, evidence2) {
8665
8669
  return rows2.map((r) => {
@@ -8864,10 +8868,10 @@ function existingTree(rows2) {
8864
8868
  const ancestors = (r) => {
8865
8869
  const known = memo.get(r.id);
8866
8870
  if (known) return known;
8867
- const out = [];
8868
- for (let p = parentOf2(r); p && !out.includes(p) && out.length < 50; p = parentOf2(p)) out.push(p);
8869
- memo.set(r.id, out);
8870
- return out;
8871
+ const out2 = [];
8872
+ for (let p = parentOf2(r); p && !out2.includes(p) && out2.length < 50; p = parentOf2(p)) out2.push(p);
8873
+ memo.set(r.id, out2);
8874
+ return out2;
8871
8875
  };
8872
8876
  return { parentOf: parentOf2, ancestors };
8873
8877
  }
@@ -8952,9 +8956,9 @@ function structure(pool, gone, drawn, tree, liveByRef, asideByRef) {
8952
8956
  if (!at || seen.has(k)) return -1;
8953
8957
  seen.add(k);
8954
8958
  const d = "key" in at ? measure(at.key, seen) : 0;
8955
- const out = d < 0 ? -1 : d + 1;
8956
- depthNew.set(k, out);
8957
- return out;
8959
+ const out2 = d < 0 ? -1 : d + 1;
8960
+ depthNew.set(k, out2);
8961
+ return out2;
8958
8962
  };
8959
8963
  for (const r of pool) {
8960
8964
  if (late.some((l) => l.key === r.key)) continue;
@@ -8964,9 +8968,9 @@ function structure(pool, gone, drawn, tree, liveByRef, asideByRef) {
8964
8968
  }
8965
8969
  function retireable(rows2) {
8966
8970
  const tree = existingTree(rows2);
8967
- const out = new Set(rows2.filter((r) => isLive(r) && r.author_kind === "nanopm" && !isFounders(r)).map((r) => r.id));
8968
- for (const f of rows2.filter((r) => isLive(r) && isFounders(r))) for (const a of tree.ancestors(f)) out.delete(a.id);
8969
- return [...out];
8971
+ const out2 = new Set(rows2.filter((r) => isLive(r) && r.author_kind === "nanopm" && !isFounders(r)).map((r) => r.id));
8972
+ for (const f of rows2.filter((r) => isLive(r) && isFounders(r))) for (const a of tree.ancestors(f)) out2.delete(a.id);
8973
+ return [...out2];
8970
8974
  }
8971
8975
  function critiqueWording(o, v, reworked) {
8972
8976
  if (v?.verdict !== "pass" || reworked.has(o.key)) return o;
@@ -9414,7 +9418,7 @@ async function flashBranch(ctx, w, n, places) {
9414
9418
  const name = clip3(themeName(w.branch), 60);
9415
9419
  const material = flashMaterial(ctx.known, w);
9416
9420
  const call2 = { role: "flash", parse: FlashAnswer, dir: `flash-${n + 1}` };
9417
- const out = [];
9421
+ const out2 = [];
9418
9422
  try {
9419
9423
  const first = await ask2(ctx, { ...call2, prompt: material, say: `Finding solutions to \u201C${name}\u201D\u2026` });
9420
9424
  const gated = w.leaves.map((leaf) => {
@@ -9427,7 +9431,7 @@ async function flashBranch(ctx, w, n, places) {
9427
9431
  `\u201C${name}\u201D: ${gated.reduce((t, g) => t + g.written.length, 0)} solutions for ${w.leaves.length} problem${w.leaves.length === 1 ? "" : "s"}${again.length ? `; ${again.length} to write again` : ""}.${first.answer.notes ? ` ${wholeSentences(first.answer.notes, 400)}` : ""}`,
9428
9432
  first.cost
9429
9433
  );
9430
- for (const g of gated.filter((x) => !x.refused.length)) out.push(await publish4(ctx, g.leaf, g.kept, places.get(g.leaf.id)));
9434
+ for (const g of gated.filter((x) => !x.refused.length)) out2.push(await publish4(ctx, g.leaf, g.kept, places.get(g.leaf.id)));
9431
9435
  if (again.length) {
9432
9436
  const refused = again.map((g) => ({ ref: g.leaf.ref, why: g.refused, you_wrote: g.written }));
9433
9437
  const second = await ask2(ctx, { ...call2, prompt: { ...material, answer: again.map((g) => g.leaf.ref), refused }, say: `Writing again what would be refused on \u201C${name}\u201D\u2026` });
@@ -9435,11 +9439,11 @@ async function flashBranch(ctx, w, n, places) {
9435
9439
  for (const g of again) {
9436
9440
  const re = gate(ctx, answersFor(second.answer.leaves, g.leaf), g.leaf);
9437
9441
  if (!re.kept.length) {
9438
- out.push({ leaf: g.leaf, failed: re.refused.join("; ") });
9442
+ out2.push({ leaf: g.leaf, failed: re.refused.join("; ") });
9439
9443
  continue;
9440
9444
  }
9441
9445
  for (const why of re.refused) await ctx.journal("solutions.refused", `${g.leaf.ref}: ${why}; not recorded.`);
9442
- out.push(await publish4(ctx, g.leaf, re.kept, places.get(g.leaf.id)));
9446
+ out2.push(await publish4(ctx, g.leaf, re.kept, places.get(g.leaf.id)));
9443
9447
  }
9444
9448
  }
9445
9449
  } catch (error) {
@@ -9448,13 +9452,13 @@ async function flashBranch(ctx, w, n, places) {
9448
9452
  throw error;
9449
9453
  }
9450
9454
  const why = clip3(error instanceof Error ? error.message : String(error), 300);
9451
- for (const leaf of w.leaves) if (!out.some((o) => o.leaf === leaf)) out.push({ leaf, failed: why });
9455
+ for (const leaf of w.leaves) if (!out2.some((o) => o.leaf === leaf)) out2.push({ leaf, failed: why });
9452
9456
  }
9453
- const failed2 = out.filter((o) => "failed" in o);
9457
+ const failed2 = out2.filter((o) => "failed" in o);
9454
9458
  if (failed2.length) {
9455
9459
  await ctx.journal("solutions.failed", `\u201C${name}\u201D could not finish: ${failed2.map((f) => `${f.leaf.ref} \u2014 ${f.failed}`).join("; ")}. The other branches were not held.`);
9456
9460
  }
9457
- return out;
9461
+ return out2;
9458
9462
  }
9459
9463
  function answersFor(answered2, leaf) {
9460
9464
  return answered2.find((a) => a.ref.trim().toUpperCase() === leaf.ref.toUpperCase())?.solutions ?? [];
@@ -9569,10 +9573,10 @@ function uniqueKeys(list) {
9569
9573
  }
9570
9574
  function revised2(first, r, cut) {
9571
9575
  const rework = new Map(r.rework.filter((x) => !cut.has(x.key)).map((x) => [x.key, x]));
9572
- const out = first.map((s) => rework.get(s.key) ?? s);
9573
- const fresh = uniqueKeys([...out, ...r.add, ...r.rework.filter((x) => !first.some((s) => s.key === x.key))]).slice(out.length);
9576
+ const out2 = first.map((s) => rework.get(s.key) ?? s);
9577
+ const fresh = uniqueKeys([...out2, ...r.add, ...r.rework.filter((x) => !first.some((s) => s.key === x.key))]).slice(out2.length);
9574
9578
  const dropped = new Set(r.drop.map((d) => d.key));
9575
- const kept = [...out, ...fresh].filter((s) => !dropped.has(s.key));
9579
+ const kept = [...out2, ...fresh].filter((s) => !dropped.has(s.key));
9576
9580
  const at = (key) => r.order.includes(key) ? r.order.indexOf(key) : Infinity;
9577
9581
  return r.order.length ? [...kept].sort((a, b) => at(a.key) - at(b.key)) : kept;
9578
9582
  }
@@ -9629,14 +9633,14 @@ function draftFields(w) {
9629
9633
  };
9630
9634
  }
9631
9635
  function resolve3(ctx, ids) {
9632
- const out = [];
9636
+ const out2 = [];
9633
9637
  for (const raw of ids) {
9634
9638
  const id = raw.trim().toUpperCase();
9635
9639
  const source = ctx.known.cites.get(id);
9636
9640
  if (!source) ctx.known.unresolved.add(raw.trim());
9637
- else if (!out.some((s) => s.kind === source.kind && s.ref === source.ref)) out.push(source);
9641
+ else if (!out2.some((s) => s.kind === source.kind && s.ref === source.ref)) out2.push(source);
9638
9642
  }
9639
- return out.length ? out.slice(0, OPPORTUNITY_SOURCES_MAX) : [{ kind: "inference", ref: "nanopm", note: null }];
9643
+ return out2.length ? out2.slice(0, OPPORTUNITY_SOURCES_MAX) : [{ kind: "inference", ref: "nanopm", note: null }];
9640
9644
  }
9641
9645
  async function publish4(ctx, leaf, drafts, place) {
9642
9646
  ctx.spec.signal.throwIfAborted();
@@ -9827,7 +9831,7 @@ async function git3(cwd, args) {
9827
9831
  return stdout.trim();
9828
9832
  }
9829
9833
  async function listTrackedFiles(cwd) {
9830
- const lines = (args) => git3(cwd, args).then((out) => out ? out.split("\n") : [], () => []);
9834
+ const lines = (args) => git3(cwd, args).then((out2) => out2 ? out2.split("\n") : [], () => []);
9831
9835
  const [files, wiki] = await Promise.all([lines(["ls-files"]), lines(["ls-files", "--others", "--ignored", "--exclude-standard", "--", WIKI_DIR])]);
9832
9836
  return { files, wiki };
9833
9837
  }
@@ -10259,16 +10263,16 @@ async function countingLines(cwd) {
10259
10263
  }
10260
10264
  function pickLines(raw) {
10261
10265
  const perFile = /* @__PURE__ */ new Map();
10262
- const out = [];
10266
+ const out2 = [];
10263
10267
  for (const line of raw) {
10264
- if (!line.trim() || out.length >= COUNTING_LINES_MAX) continue;
10268
+ if (!line.trim() || out2.length >= COUNTING_LINES_MAX) continue;
10265
10269
  const file = line.split(":", 1)[0];
10266
10270
  const n = perFile.get(file) ?? 0;
10267
10271
  if (n >= PER_FILE) continue;
10268
10272
  perFile.set(file, n + 1);
10269
- out.push(line.length > 200 ? `${line.slice(0, 199)}\u2026` : line);
10273
+ out2.push(line.length > 200 ? `${line.slice(0, 199)}\u2026` : line);
10270
10274
  }
10271
- return out;
10275
+ return out2;
10272
10276
  }
10273
10277
  async function killRecorded(runDir) {
10274
10278
  try {
@@ -11203,6 +11207,56 @@ function usageTokens(usage) {
11203
11207
  return usage.inputTokens + (usage.cacheWriteTokens ?? 0) + usage.outputTokens;
11204
11208
  }
11205
11209
 
11210
+ // ../daemon/src/strays.ts
11211
+ import { execFile as execFile7 } from "child_process";
11212
+ import { realpathSync as realpathSync2 } from "fs";
11213
+ import { promisify as promisify7 } from "util";
11214
+ var run = promisify7(execFile7);
11215
+ function strayPids(lsof, dir, orphans = null, self = process.pid) {
11216
+ const root2 = dir.replace(/\/+$/, "");
11217
+ const pids = [];
11218
+ let pid = 0;
11219
+ for (const line of lsof.split("\n")) {
11220
+ if (line.startsWith("p")) pid = Number(line.slice(1));
11221
+ else if (line.startsWith("n") && pid && pid !== self && (!orphans || orphans.has(pid))) {
11222
+ const at = line.slice(1);
11223
+ if (at === root2 || at.startsWith(`${root2}/`)) pids.push(pid);
11224
+ }
11225
+ }
11226
+ return [...new Set(pids)];
11227
+ }
11228
+ function orphansOf(ps) {
11229
+ return new Set(
11230
+ ps.split("\n").map((l) => l.trim().split(/\s+/).map(Number)).filter(([pid, ppid]) => pid && ppid === 1).map(([pid]) => pid)
11231
+ );
11232
+ }
11233
+ var out = (e) => ({ stdout: String(e.stdout ?? "") });
11234
+ async function stopStrays(dir) {
11235
+ let root2 = dir;
11236
+ try {
11237
+ root2 = realpathSync2(dir);
11238
+ } catch {
11239
+ return [];
11240
+ }
11241
+ const [{ stdout: lsof }, { stdout: ps }] = await Promise.all([
11242
+ run("lsof", ["-w", "-a", "-d", "cwd", "-Fpn"], { maxBuffer: 16 * 1024 * 1024, timeout: 1e4 }).catch(out),
11243
+ run("ps", ["-A", "-o", "pid=,ppid="]).catch(out)
11244
+ ]);
11245
+ const pids = strayPids(lsof, root2, orphansOf(ps));
11246
+ const signal = (s) => pids.forEach((pid) => {
11247
+ try {
11248
+ process.kill(pid, s);
11249
+ } catch {
11250
+ }
11251
+ });
11252
+ signal("SIGTERM");
11253
+ if (pids.length) {
11254
+ await new Promise((r) => setTimeout(r, 2e3));
11255
+ signal("SIGKILL");
11256
+ }
11257
+ return pids;
11258
+ }
11259
+
11206
11260
  // ../daemon/src/build-job.ts
11207
11261
  var PLAN_TOOL = "mcp__nanopm__build_plan";
11208
11262
  var ASK_TOOL = "mcp__nanopm__ask_user";
@@ -11269,30 +11323,36 @@ async function runBuildJob(input) {
11269
11323
  };
11270
11324
  const session2 = async (prompt, resume3) => {
11271
11325
  let end = { stop: failed("the agent produced no result") };
11272
- for await (const event of adapter.run({ ...spec2, prompt, resumeSessionId: resume3 ? spec2.sessionId : void 0 })) {
11273
- await input.onEvent(event);
11274
- if (event.type === "tool_use" && event.tool === PLAN_TOOL && !plan) {
11275
- plan = stepsOf(event.input);
11276
- if (plan) await events.write("plan", { steps: plan });
11277
- } else if (event.type === "tool_result" && event.tool === STEP_TOOL && !event.isError) {
11278
- const { step, summary } = event.input ?? {};
11279
- const index = Number(step) - 1;
11280
- if (Number.isInteger(index) && index >= 0 && !done.has(index)) {
11281
- const says = plan?.[index] ?? `step ${index + 1}`;
11282
- const commit = await commitAll(worktree, `${solution.ref}: ${says}`, summary);
11283
- done.add(index);
11284
- await events.write("step", { step: index + 1, says, commit: commit?.sha ?? null, files: commit?.files ?? 0 });
11326
+ try {
11327
+ for await (const event of adapter.run({ ...spec2, prompt, resumeSessionId: resume3 ? spec2.sessionId : void 0 })) {
11328
+ await input.onEvent(event);
11329
+ if (event.type === "tool_result" && event.tool === PLAN_TOOL && !event.isError && !plan) {
11330
+ plan = stepsOf(event.input);
11331
+ const understood = event.input?.understood;
11332
+ if (plan) await events.write("plan", { steps: plan, ...typeof understood === "string" && understood.trim() ? { understood: understood.trim().slice(0, BUILD_UNDERSTOOD_MAX) } : {} });
11333
+ } else if (event.type === "tool_result" && event.tool === STEP_TOOL && !event.isError) {
11334
+ const { step, summary } = event.input ?? {};
11335
+ const index = Number(step) - 1;
11336
+ if (Number.isInteger(index) && index >= 0 && !done.has(index)) {
11337
+ const says = plan?.[index] ?? `step ${index + 1}`;
11338
+ const commit = await commitAll(worktree, `${solution.ref}: ${says}`, summary);
11339
+ done.add(index);
11340
+ await events.write("step", { step: index + 1, says, commit: commit?.sha ?? null, files: commit?.files ?? 0 });
11341
+ }
11342
+ } else if (event.type === "finished") {
11343
+ usage = sumUsage(usage, event.usage);
11344
+ said = event.result;
11345
+ end = null;
11346
+ } else if (event.type === "deferred" && event.tool === ASK_TOOL) {
11347
+ usage = sumUsage(usage, event.usage);
11348
+ end = { park: { ask: askOf(event.input) } };
11349
+ } else if (event.type === "failed") {
11350
+ end = event.code === "rate_limit" ? { park: { resetsAt: event.resetsAt } } : { stop: { status: "failed", error: { code: event.code, message: event.message } } };
11285
11351
  }
11286
- } else if (event.type === "finished") {
11287
- usage = sumUsage(usage, event.usage);
11288
- said = event.result;
11289
- end = null;
11290
- } else if (event.type === "deferred" && event.tool === ASK_TOOL) {
11291
- usage = sumUsage(usage, event.usage);
11292
- end = { park: { ask: askOf(event.input) } };
11293
- } else if (event.type === "failed") {
11294
- end = event.code === "rate_limit" ? { park: { resetsAt: event.resetsAt } } : { stop: { status: "failed", error: { code: event.code, message: event.message } } };
11295
11352
  }
11353
+ } finally {
11354
+ const stopped = await stopStrays(worktree.dir);
11355
+ if (stopped.length) await log("build.strays", `stopped ${stopped.length} process${stopped.length === 1 ? "" : "es"} the build left running`);
11296
11356
  }
11297
11357
  return end;
11298
11358
  };
@@ -12333,11 +12393,11 @@ function domainAllowed(host, allowlist) {
12333
12393
  return false;
12334
12394
  }
12335
12395
  function allowlistFor(baseDomains, confirmedDomains) {
12336
- const out = /* @__PURE__ */ new Set();
12396
+ const out2 = /* @__PURE__ */ new Set();
12337
12397
  for (const d of [...baseDomains, ...confirmedDomains]) {
12338
- if (d && !refuseHost(d)) out.add(d.toLowerCase());
12398
+ if (d && !refuseHost(d)) out2.add(d.toLowerCase());
12339
12399
  }
12340
- return [...out];
12400
+ return [...out2];
12341
12401
  }
12342
12402
  function refuseFetch(url, allowlist, open = false) {
12343
12403
  const host = hostOf(url);
@@ -12827,8 +12887,8 @@ ${renderMap()}`,
12827
12887
  server.registerTool(
12828
12888
  "build_plan",
12829
12889
  {
12830
- description: `Say the plan before you change anything: ${BUILD_STEPS_MIN} to ${BUILD_STEPS_MAX} steps, in order, each one change the founder can recognise, in their words (at most ${BUILD_STEP_MAX} characters). The last step is your own check of the whole change. Call it once; the founder sees it as a checklist.`,
12831
- inputSchema: { steps: z21.array(z21.string().min(3).max(BUILD_STEP_MAX)).min(BUILD_STEPS_MIN).max(BUILD_STEPS_MAX) }
12890
+ description: `Say what you understood and your plan before you change anything. \`understood\`: in your own words and plain language, the problem, what you will change, and what you will not touch \u2014 a short paragraph the founder reads first, so a misunderstanding shows before any code (at most ${BUILD_UNDERSTOOD_MAX} characters). \`steps\`: ${BUILD_STEPS_MIN} to ${BUILD_STEPS_MAX}, in order, each one change the founder can recognise, in their words (at most ${BUILD_STEP_MAX} characters); the last is your own check of the whole change. Call it once; the founder sees the paragraph, then the steps as a checklist.`,
12891
+ inputSchema: { understood: z21.string().min(BUILD_UNDERSTOOD_MIN).max(BUILD_UNDERSTOOD_MAX), steps: z21.array(z21.string().min(3).max(BUILD_STEP_MAX)).min(BUILD_STEPS_MIN).max(BUILD_STEPS_MAX) }
12832
12892
  },
12833
12893
  async ({ steps }) => ctx.workspace ? json({ recorded: steps.length, note: "Recorded. Build step 1 now; call build_step when it is done." }) : failure("this job builds nothing")
12834
12894
  );
@@ -12898,14 +12958,14 @@ ${renderMap()}`,
12898
12958
  const act = (change, asked, why) => talkAct(ctx, round2 ??= talkRound(ctx), change, asked, why);
12899
12959
  const actInTurn = async (ref, changes, asked, why) => {
12900
12960
  if (!changes.length) return failure("Say what changes.");
12901
- const out = [];
12961
+ const out2 = [];
12902
12962
  let at = ref;
12903
12963
  for (const change of changes) {
12904
12964
  const done = await act({ ...change, ref: at }, asked, why);
12905
- out.push(done);
12965
+ out2.push(done);
12906
12966
  if (done.target) at = done.target;
12907
12967
  }
12908
- return json(out);
12968
+ return json(out2);
12909
12969
  };
12910
12970
  const F2 = TalkField;
12911
12971
  const ASKED = z21.string().max(300).nullable().describe('The founder\u2019s own words that ask for this change, copied exactly from their message (a few words are enough: "lower it"). Null when it is your own idea: then it is only suggested, and waits for their yes.');
@@ -13944,9 +14004,9 @@ async function startMemoryServer(env = process.env) {
13944
14004
  import { readFile as readFile12 } from "fs/promises";
13945
14005
 
13946
14006
  // src/project.ts
13947
- import { execFile as execFile7 } from "child_process";
14007
+ import { execFile as execFile8 } from "child_process";
13948
14008
  import { basename as basename2 } from "path";
13949
- import { promisify as promisify7 } from "util";
14009
+ import { promisify as promisify8 } from "util";
13950
14010
 
13951
14011
  // src/local.ts
13952
14012
  import { readFile as readFile11 } from "fs/promises";
@@ -13964,7 +14024,7 @@ function useColor() {
13964
14024
  }
13965
14025
 
13966
14026
  // src/project.ts
13967
- var execFileAsync7 = promisify7(execFile7);
14027
+ var execFileAsync7 = promisify8(execFile8);
13968
14028
  async function detectRepo(cwd) {
13969
14029
  let root2;
13970
14030
  try {
@@ -14161,8 +14221,8 @@ function nanoFrame(mood, tick, paint) {
14161
14221
  const dot = mood === "working" ? DOTS[Math.floor(tick / 3) % DOTS.length] : mood === "happy" ? "\u02D9" : " ";
14162
14222
  return `${paint(ANSI.red, face)}${paint(ANSI.red, dot)}`;
14163
14223
  }
14164
- function plainRenderer(out) {
14165
- return { settle: out, live: () => void 0, pause: () => void 0, resume: () => void 0, stop: () => void 0 };
14224
+ function plainRenderer(out2) {
14225
+ return { settle: out2, live: () => void 0, pause: () => void 0, resume: () => void 0, stop: () => void 0 };
14166
14226
  }
14167
14227
  function liveRenderer(stream, opts = {}) {
14168
14228
  const paint = painter(opts.color ?? Boolean(stream.isTTY && !process.env.NO_COLOR));
@@ -14450,10 +14510,10 @@ function scriptedAnswerer(entries) {
14450
14510
  return entry.answer;
14451
14511
  };
14452
14512
  }
14453
- function answersFromTerminal(out) {
14513
+ function answersFromTerminal(out2) {
14454
14514
  return async (q, signal) => {
14455
14515
  if (!process.stdin.isTTY) {
14456
- out("(no terminal to answer from; skipping)");
14516
+ out2("(no terminal to answer from; skipping)");
14457
14517
  return "skip";
14458
14518
  }
14459
14519
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
@@ -14474,17 +14534,17 @@ function verboseWanted(flag, interactive = Boolean(process.stdout.isTTY)) {
14474
14534
  if (process.env.NANOPM_VERBOSE === "1") return true;
14475
14535
  return !interactive;
14476
14536
  }
14477
- function rendererFor(out) {
14478
- if (!out && process.stdout.isTTY) {
14537
+ function rendererFor(out2) {
14538
+ if (!out2 && process.stdout.isTTY) {
14479
14539
  const renderer = liveRenderer(process.stdout);
14480
14540
  return { renderer, out: (l) => renderer.settle(l) };
14481
14541
  }
14482
- const plain = out ?? ((l) => console.log(l));
14542
+ const plain = out2 ?? ((l) => console.log(l));
14483
14543
  return { renderer: plainRenderer(plain), out: plain };
14484
14544
  }
14485
14545
  var TERMINAL = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
14486
14546
  async function runSkill(skill, opts) {
14487
- const { renderer, out } = rendererFor(opts.out);
14547
+ const { renderer, out: out2 } = rendererFor(opts.out);
14488
14548
  const err = opts.err ?? ((l) => console.error(l));
14489
14549
  const pollMs = opts.pollMs ?? 1e3;
14490
14550
  const auth = await authenticate();
@@ -14498,10 +14558,10 @@ async function runSkill(skill, opts) {
14498
14558
  target_daemon_id: daemonId
14499
14559
  }).select("id").single();
14500
14560
  if (error || !job) throw new Error(`could not queue the job: ${error?.message}`);
14501
- out(`queued ${skill} as job ${job.id}`);
14502
- const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out);
14561
+ out2(`queued ${skill} as job ${job.id}`);
14562
+ const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out2);
14503
14563
  const link = opts.slug ? conversationUrl(opts.slug, job.id) : void 0;
14504
- return streamJob(auth.client, job.id, { pollMs, out, renderer, cancelSignal: opts.cancelSignal, answerer, link, verbose: verboseWanted(opts.verbose) });
14564
+ return streamJob(auth.client, job.id, { pollMs, out: out2, renderer, cancelSignal: opts.cancelSignal, answerer, link, verbose: verboseWanted(opts.verbose) });
14505
14565
  }
14506
14566
  async function streamJob(client, jobId, opts) {
14507
14567
  const renderer = opts.renderer ?? plainRenderer(opts.out);
@@ -14617,7 +14677,7 @@ async function answeredElsewhere(client, jobId, seq, signal, pollMs) {
14617
14677
 
14618
14678
  // src/commands/ask.ts
14619
14679
  async function ask4(question, opts) {
14620
- const out = opts.out ?? ((l) => console.log(l));
14680
+ const out2 = opts.out ?? ((l) => console.log(l));
14621
14681
  if (!question.trim()) throw new Error("ask needs a question");
14622
14682
  if (opts.all && opts.projectId) throw new Error("--all asks every project; drop --project, or drop --all");
14623
14683
  const auth = await authenticate();
@@ -14626,17 +14686,17 @@ async function ask4(question, opts) {
14626
14686
  let worst = 0;
14627
14687
  for (const project2 of await memberProjects(auth.client, auth.userId, opts.cwd)) {
14628
14688
  if (opts.cancelSignal?.aborted) break;
14629
- out(`
14689
+ out2(`
14630
14690
  ${project2.name}`);
14631
- const code = await askOne(auth, project2.id, question, daemonId, opts, out);
14691
+ const code = await askOne(auth, project2.id, question, daemonId, opts, out2);
14632
14692
  worst = Math.max(worst, code);
14633
14693
  }
14634
14694
  return worst;
14635
14695
  }
14636
14696
  const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
14637
- return askOne(auth, project.id, question, daemonId, opts, out);
14697
+ return askOne(auth, project.id, question, daemonId, opts, out2);
14638
14698
  }
14639
- async function askOne(auth, projectId, question, daemonId, opts, out) {
14699
+ async function askOne(auth, projectId, question, daemonId, opts, out2) {
14640
14700
  const { data: job, error } = await auth.client.from("jobs").insert({
14641
14701
  project_id: projectId,
14642
14702
  skill: "ask",
@@ -14649,7 +14709,7 @@ async function askOne(auth, projectId, question, daemonId, opts, out) {
14649
14709
  pollMs: 700,
14650
14710
  out: (line) => {
14651
14711
  if (line.startsWith("\u203A") || line.startsWith("\u25CF") || line.startsWith("\u2713") || line.startsWith(" \u21BA")) return;
14652
- out(line);
14712
+ out2(line);
14653
14713
  },
14654
14714
  cancelSignal: opts.cancelSignal,
14655
14715
  verbose: false
@@ -14657,17 +14717,17 @@ async function askOne(auth, projectId, question, daemonId, opts, out) {
14657
14717
  }
14658
14718
 
14659
14719
  // src/commands/daemon-service.ts
14660
- import { execFile as execFile8 } from "child_process";
14720
+ import { execFile as execFile9 } from "child_process";
14661
14721
  import { existsSync as existsSync6 } from "fs";
14662
14722
  import { mkdir as mkdir7, readFile as readFile14, rm as rm6, writeFile as writeFile7 } from "fs/promises";
14663
14723
  import { homedir as homedir5 } from "os";
14664
14724
  import { join as join16 } from "path";
14665
- import { promisify as promisify8 } from "util";
14725
+ import { promisify as promisify9 } from "util";
14666
14726
 
14667
14727
  // package.json
14668
14728
  var package_default = {
14669
14729
  name: "@nanopm/cli",
14670
- version: "0.1.18",
14730
+ version: "0.1.20",
14671
14731
  description: "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
14672
14732
  type: "module",
14673
14733
  license: "MIT",
@@ -14716,7 +14776,7 @@ var package_default = {
14716
14776
  var VERSION = package_default.version;
14717
14777
 
14718
14778
  // src/commands/daemon-service.ts
14719
- var execFileAsync8 = promisify8(execFile8);
14779
+ var execFileAsync8 = promisify9(execFile9);
14720
14780
  var LAUNCHD_LABEL = "pm.nano.daemon";
14721
14781
  function plistPath() {
14722
14782
  return join16(homedir5(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
@@ -14758,7 +14818,7 @@ function requireMac() {
14758
14818
  throw new Error("`nanopm daemon install` supports macOS in block 1; on other systems run `nanopm daemon run` in a terminal or a systemd user unit");
14759
14819
  }
14760
14820
  }
14761
- async function installDaemon(out = (l) => console.log(l), opts = {}) {
14821
+ async function installDaemon(out2 = (l) => console.log(l), opts = {}) {
14762
14822
  requireMac();
14763
14823
  if (!await loadCredentials()) throw new Error("not logged in; run `nanopm login` first");
14764
14824
  const auth = await authenticate();
@@ -14769,16 +14829,16 @@ async function installDaemon(out = (l) => console.log(l), opts = {}) {
14769
14829
  await found(detection);
14770
14830
  throw new Error("Claude Code is not installed on this machine (`claude` not found on PATH)");
14771
14831
  }
14772
- out(`Claude Code ${detection.version} found; checking its login\u2026`);
14832
+ out2(`Claude Code ${detection.version} found; checking its login\u2026`);
14773
14833
  const ping = await adapter.ping();
14774
14834
  if (!ping.ok) {
14775
14835
  await found({ ...detection, authenticated: false });
14776
14836
  throw new Error(`Claude Code is installed but not usable headless: ${ping.message}`);
14777
14837
  }
14778
- out("Claude Code answers.");
14838
+ out2("Claude Code answers.");
14779
14839
  const web = await adapter.probeWebSearch();
14780
14840
  await rememberWebSearch(web.ok);
14781
- out(web.ok ? "Claude's own search answers; searches go through your subscription." : `Claude's search is not available on this machine (${web.message}); searches use DuckDuckGo.`);
14841
+ out2(web.ok ? "Claude's own search answers; searches go through your subscription." : `Claude's search is not available on this machine (${web.message}); searches use DuckDuckGo.`);
14782
14842
  const bin = process.argv[1];
14783
14843
  if (!bin) throw new Error("cannot locate the nanopm binary");
14784
14844
  const home = homedir5();
@@ -14791,10 +14851,10 @@ async function installDaemon(out = (l) => console.log(l), opts = {}) {
14791
14851
  const target = guiTarget();
14792
14852
  await execFileAsync8("launchctl", ["bootout", target, plistPath()]).catch(() => void 0);
14793
14853
  await execFileAsync8("launchctl", ["bootstrap", target, plistPath()]);
14794
- out(`Installed ${LAUNCHD_LABEL}; the daemon starts now and at every login. Logs: ${daemonLogPath()}`);
14854
+ out2(`Installed ${LAUNCHD_LABEL}; the daemon starts now and at every login. Logs: ${daemonLogPath()}`);
14795
14855
  const shared = opts.shared ?? await localShared();
14796
- out(shared ? "This machine is shared with your projects: it also runs scheduled reviews and partners' jobs." : "This machine runs your jobs only. `nanopm daemon install --share` to offer it to your projects.");
14797
- if (opts.next !== false) out(`Next, in a repo: nanopm onboard \xB7 or continue at ${webUrl()}/new`);
14856
+ out2(shared ? "This machine is shared with your projects: it also runs scheduled reviews and partners' jobs." : "This machine runs your jobs only. `nanopm daemon install --share` to offer it to your projects.");
14857
+ if (opts.next !== false) out2(`Next, in a repo: nanopm onboard \xB7 or continue at ${webUrl()}/new`);
14798
14858
  }
14799
14859
  function guiTarget() {
14800
14860
  return `gui/${process.getuid?.() ?? 501}`;
@@ -14804,34 +14864,34 @@ async function restartDaemon() {
14804
14864
  await execFileAsync8("launchctl", ["kickstart", "-k", `${guiTarget()}/${LAUNCHD_LABEL}`]);
14805
14865
  return true;
14806
14866
  }
14807
- async function uninstallDaemon(out = (l) => console.log(l)) {
14867
+ async function uninstallDaemon(out2 = (l) => console.log(l)) {
14808
14868
  requireMac();
14809
14869
  const target = guiTarget();
14810
14870
  await execFileAsync8("launchctl", ["bootout", target, plistPath()]).catch(() => void 0);
14811
14871
  await rm6(plistPath(), { force: true });
14812
- out(`Removed ${LAUNCHD_LABEL}.`);
14872
+ out2(`Removed ${LAUNCHD_LABEL}.`);
14813
14873
  }
14814
- async function daemonStatus(out = (l) => console.log(l)) {
14874
+ async function daemonStatus(out2 = (l) => console.log(l)) {
14815
14875
  const id = await localDaemonId();
14816
14876
  const installed = process.platform === "darwin" && existsSync6(plistPath());
14817
- out(`launchd: ${installed ? "installed" : "not installed"}`);
14877
+ out2(`launchd: ${installed ? "installed" : "not installed"}`);
14818
14878
  if (!id) {
14819
- out("daemon: never registered on this machine");
14879
+ out2("daemon: never registered on this machine");
14820
14880
  return;
14821
14881
  }
14822
14882
  const auth = await authenticate();
14823
14883
  const { data } = await auth.client.from("daemons").select("hostname, status, last_seen_at, version, harnesses, shared").eq("id", id).maybeSingle();
14824
14884
  if (!data) {
14825
- out(`daemon: ${id} is not in the account any more`);
14885
+ out2(`daemon: ${id} is not in the account any more`);
14826
14886
  return;
14827
14887
  }
14828
14888
  const seen = data.last_seen_at ? `${Math.round((Date.now() - new Date(data.last_seen_at).getTime()) / 1e3)}s ago` : "never";
14829
- out(`daemon: ${data.hostname} \xB7 ${data.status} \xB7 last seen ${seen} \xB7 nanopm ${data.version}`);
14830
- out(data.shared ? "shared with your projects: runs scheduled reviews and partners' jobs too" : "runs your jobs only \xB7 `nanopm daemon install --share` to offer it to your projects");
14889
+ out2(`daemon: ${data.hostname} \xB7 ${data.status} \xB7 last seen ${seen} \xB7 nanopm ${data.version}`);
14890
+ out2(data.shared ? "shared with your projects: runs scheduled reviews and partners' jobs too" : "runs your jobs only \xB7 `nanopm daemon install --share` to offer it to your projects");
14831
14891
  const { count: count2 } = await auth.client.from("jobs").select("id", { count: "exact", head: true }).eq("status", "queued");
14832
- out(`queued jobs: ${count2 ?? 0}`);
14892
+ out2(`queued jobs: ${count2 ?? 0}`);
14833
14893
  const { data: running } = await auth.client.from("jobs").select("id, skill").eq("assigned_daemon_id", id).in("status", ["running", "waiting"]);
14834
- for (const j of running ?? []) out(`running: ${j.skill} ${j.id}`);
14894
+ for (const j of running ?? []) out2(`running: ${j.skill} ${j.id}`);
14835
14895
  }
14836
14896
  async function daemonLogs(lines = 50) {
14837
14897
  try {
@@ -14888,22 +14948,22 @@ function renderActivityLog(jobs, rows2, opts) {
14888
14948
  const key = row.job_id ?? "";
14889
14949
  byJob.set(key, [...byJob.get(key) ?? [], row]);
14890
14950
  }
14891
- const out = [];
14951
+ const out2 = [];
14892
14952
  const ordered = [...jobs].sort((a, b) => a.created_at.localeCompare(b.created_at));
14893
14953
  for (const job of ordered) {
14894
14954
  const jobRows = byJob.get(job.id) ?? [];
14895
- out.push(paint(ANSI.bold, jobHeader(job)));
14896
- if (job.note) out.push(` ${job.note}`);
14897
- for (const row of jobRows) out.push(renderRow(row, paint));
14898
- out.push("");
14955
+ out2.push(paint(ANSI.bold, jobHeader(job)));
14956
+ if (job.note) out2.push(` ${job.note}`);
14957
+ for (const row of jobRows) out2.push(renderRow(row, paint));
14958
+ out2.push("");
14899
14959
  byJob.delete(job.id);
14900
14960
  }
14901
14961
  const loose = [...byJob.values()].flat();
14902
14962
  if (loose.length) {
14903
- out.push(paint(ANSI.bold, "Outside any job"));
14904
- for (const row of loose) out.push(renderRow(row, paint));
14963
+ out2.push(paint(ANSI.bold, "Outside any job"));
14964
+ for (const row of loose) out2.push(renderRow(row, paint));
14905
14965
  }
14906
- return out.join("\n").trimEnd();
14966
+ return out2.join("\n").trimEnd();
14907
14967
  }
14908
14968
  function jobHeader(job) {
14909
14969
  const parts = [job.skill];
@@ -15005,17 +15065,17 @@ async function correctionNote(client, jobId) {
15005
15065
  import { setTimeout as sleep4 } from "timers/promises";
15006
15066
 
15007
15067
  // src/browser.ts
15008
- import { execFile as execFile9 } from "child_process";
15068
+ import { execFile as execFile10 } from "child_process";
15009
15069
  function openBrowser(url, interactive = Boolean(process.stdout.isTTY)) {
15010
15070
  if (!interactive) return false;
15011
15071
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
15012
- execFile9(cmd, [url], () => void 0);
15072
+ execFile10(cmd, [url], () => void 0);
15013
15073
  return true;
15014
15074
  }
15015
15075
 
15016
15076
  // src/commands/login.ts
15017
15077
  async function login(opts = {}) {
15018
- const out = opts.out ?? ((l) => console.log(l));
15078
+ const out2 = opts.out ?? ((l) => console.log(l));
15019
15079
  const web = (opts.web ?? webUrl()).replace(/\/+$/, "");
15020
15080
  const config = supabaseConfigFromEnv();
15021
15081
  let code = generateDeviceCode();
@@ -15034,8 +15094,8 @@ async function login(opts = {}) {
15034
15094
  throw new Error(`nano.pm refused to start a login (${res.status}): ${await res.text()}`);
15035
15095
  }
15036
15096
  const url = `${web}/device?code=${code}`;
15037
- out(`Your code: ${formatDeviceCode(code)}`);
15038
- out(`Confirm it at ${url}`);
15097
+ out2(`Your code: ${formatDeviceCode(code)}`);
15098
+ out2(`Confirm it at ${url}`);
15039
15099
  if (!opts.noBrowser) openBrowser(url);
15040
15100
  const deadline = Date.now() + DEVICE_CODE_TTL_MS;
15041
15101
  const pollMs = opts.pollMs ?? 2e3;
@@ -15066,58 +15126,58 @@ async function login(opts = {}) {
15066
15126
  expiresAt: data.session.expires_at ?? session2.expires_at
15067
15127
  });
15068
15128
  const { data: profile } = await client.from("users").select("display_name, github_login").eq("id", data.session.user.id).maybeSingle();
15069
- out(`Logged in as ${profile?.github_login ?? profile?.display_name ?? data.session.user.email ?? data.session.user.id}.`);
15129
+ out2(`Logged in as ${profile?.github_login ?? profile?.display_name ?? data.session.user.email ?? data.session.user.id}.`);
15070
15130
  if (!opts.noDaemon) {
15071
15131
  try {
15072
- await installDaemon(out, { next: false });
15132
+ await installDaemon(out2, { next: false });
15073
15133
  } catch (err) {
15074
- out(`could not start me in the background: ${err instanceof Error ? err.message : String(err)}`);
15075
- out("");
15076
- out("Your account is linked, but I cannot work on this machine yet. Fix the line above, then run: nanopm daemon install. The page in your browser moves on by itself.");
15134
+ out2(`could not start me in the background: ${err instanceof Error ? err.message : String(err)}`);
15135
+ out2("");
15136
+ out2("Your account is linked, but I cannot work on this machine yet. Fix the line above, then run: nanopm daemon install. The page in your browser moves on by itself.");
15077
15137
  return;
15078
15138
  }
15079
15139
  }
15080
- out("");
15081
- out(`Your machine is linked. Continue at ${web}/new`);
15140
+ out2("");
15141
+ out2(`Your machine is linked. Continue at ${web}/new`);
15082
15142
  }
15083
- async function logout(out = (l) => console.log(l)) {
15143
+ async function logout(out2 = (l) => console.log(l)) {
15084
15144
  const stored = await loadCredentials();
15085
15145
  if (!stored) {
15086
- out("Not logged in.");
15146
+ out2("Not logged in.");
15087
15147
  return;
15088
15148
  }
15089
15149
  const client = createNanopmClient({ url: stored.supabaseUrl, anonKey: stored.anonKey });
15090
15150
  const { error } = await client.auth.setSession({ access_token: stored.accessToken, refresh_token: stored.refreshToken });
15091
15151
  if (!error) await client.auth.signOut({ scope: "local" });
15092
15152
  await deleteCredentials();
15093
- out("Logged out. The daemon on this machine will stop taking jobs at its next start.");
15153
+ out2("Logged out. The daemon on this machine will stop taking jobs at its next start.");
15094
15154
  }
15095
15155
 
15096
15156
  // src/commands/update.ts
15097
- async function update(opts, out = (l) => console.log(l)) {
15157
+ async function update(opts, out2 = (l) => console.log(l)) {
15098
15158
  if (opts.auto !== void 0) {
15099
15159
  if (opts.auto !== "on" && opts.auto !== "off") throw new Error("--auto takes on or off");
15100
15160
  await setAutoUpdate(opts.auto === "on");
15101
- out(opts.auto === "on" ? "Automatic updates are on: NanoPM updates itself when it is idle." : "Automatic updates are off. `nanopm update` still updates when you ask.");
15161
+ out2(opts.auto === "on" ? "Automatic updates are on: NanoPM updates itself when it is idle." : "Automatic updates are off. `nanopm update` still updates when you ask.");
15102
15162
  return;
15103
15163
  }
15104
15164
  if (installKind() !== "installer") {
15105
- out(`This NanoPM (${VERSION}) was not installed with the install line, so it does not update itself.`);
15106
- out("From a clone of nanopm2: git pull && pnpm build && nanopm daemon install");
15107
- out("Installed with npm: npm i -g @nanopm/cli@latest && nanopm daemon install");
15165
+ out2(`This NanoPM (${VERSION}) was not installed with the install line, so it does not update itself.`);
15166
+ out2("From a clone of nanopm2: git pull && pnpm build && nanopm daemon install");
15167
+ out2("Installed with npm: npm i -g @nanopm/cli@latest && nanopm daemon install");
15108
15168
  return;
15109
15169
  }
15110
15170
  const latest = await latestVersion();
15111
15171
  if (!latest) throw new Error("could not reach the npm registry; try again in a moment");
15112
15172
  if (!newer(latest, VERSION)) {
15113
- out(`Already up to date (${VERSION}).`);
15173
+ out2(`Already up to date (${VERSION}).`);
15114
15174
  return;
15115
15175
  }
15116
- out(`Updating ${VERSION} \u2192 ${latest}\u2026`);
15176
+ out2(`Updating ${VERSION} \u2192 ${latest}\u2026`);
15117
15177
  const done = await installVersion(latest);
15118
15178
  if (!done.ok) throw new Error(`${done.why}
15119
15179
  The install line does the same: curl -fsSL https://nanopm.app/install | sh`);
15120
- out(await restartDaemon() ? `Updated ${VERSION} \u2192 ${latest}; the daemon restarted on it.` : `Updated ${VERSION} \u2192 ${latest}.`);
15180
+ out2(await restartDaemon() ? `Updated ${VERSION} \u2192 ${latest}; the daemon restarted on it.` : `Updated ${VERSION} \u2192 ${latest}.`);
15121
15181
  }
15122
15182
 
15123
15183
  // src/commands/autonomy.ts
@@ -15185,51 +15245,51 @@ async function verifyCommand(command, opts) {
15185
15245
  }
15186
15246
 
15187
15247
  // src/commands/build.ts
15188
- import { execFile as execFile10 } from "child_process";
15248
+ import { execFile as execFile11 } from "child_process";
15189
15249
  import { existsSync as existsSync7 } from "fs";
15190
15250
  import { homedir as homedir6 } from "os";
15191
- import { promisify as promisify9 } from "util";
15251
+ import { promisify as promisify10 } from "util";
15192
15252
 
15193
15253
  // src/format/opportunities.ts
15194
15254
  function renderOpportunities(rows2, lens, opts) {
15195
15255
  const paint = painter(opts.color);
15196
15256
  const width = Math.max(40, columns());
15197
- const out = [];
15257
+ const out2 = [];
15198
15258
  if (!lens) return "No objective yet: the map is drawn for it. Say what matters most right now on Your business, then `nanopm opportunities run`.";
15199
- out.push(`${paint(ANSI.bold, "Opportunities")} ${paint(ANSI.dim, `${lens.lever} \u2014 ${lens.sentence}`)}`);
15200
- out.push(paint(ANSI.dim, lens.definition ? `Counts: ${lens.definition}` : "What it counts is not defined yet: drawn against the lever and your sentence."));
15201
- if (opts.previous) out.push(paint(ANSI.amber, "Drawn for your previous objective. `nanopm opportunities run` draws it for this one."));
15259
+ out2.push(`${paint(ANSI.bold, "Opportunities")} ${paint(ANSI.dim, `${lens.lever} \u2014 ${lens.sentence}`)}`);
15260
+ out2.push(paint(ANSI.dim, lens.definition ? `Counts: ${lens.definition}` : "What it counts is not defined yet: drawn against the lever and your sentence."));
15261
+ if (opts.previous) out2.push(paint(ANSI.amber, "Drawn for your previous objective. `nanopm opportunities run` draws it for this one."));
15202
15262
  if (!rows2.length) {
15203
- out.push("", "No map yet. `nanopm opportunities run` draws it from what NanoPM knows; add --deep to check it against your users, code and data.");
15204
- return out.join("\n");
15263
+ out2.push("", "No map yet. `nanopm opportunities run` draws it from what NanoPM knows; add --deep to check it against your users, code and data.");
15264
+ return out2.join("\n");
15205
15265
  }
15206
15266
  const tree = liveTree(rows2);
15207
15267
  const live = rows2.filter(isLive);
15208
15268
  const evidenced = live.filter((r) => r.evidence_status === "evidence_backed").length;
15209
- out.push(paint(ANSI.dim, `${live.length} on the map: ${evidenced} evidence-backed, ${live.length - evidenced} hypotheses \u2014 what users or their behaviour have not shown yet.`));
15269
+ out2.push(paint(ANSI.dim, `${live.length} on the map: ${evidenced} evidence-backed, ${live.length - evidenced} hypotheses \u2014 what users or their behaviour have not shown yet.`));
15210
15270
  const who = (r) => r.personas.map((p) => opts.names?.get(p) ?? p).join(", ");
15211
15271
  const one = (r, place) => {
15212
15272
  const indent = " ".repeat(r.depth - 1);
15213
15273
  const yours = r.status === "confirmed" ? paint(ANSI.green, " yours") : "";
15214
- out.push("", `${indent}${paint(ANSI.bold, place.padStart(2))} ${r.depth === 1 ? paint(ANSI.bold, r.problem) : r.problem}${yours} ${paint(ANSI.dim, r.ref)}`);
15274
+ out2.push("", `${indent}${paint(ANSI.bold, place.padStart(2))} ${r.depth === 1 ? paint(ANSI.bold, r.problem) : r.problem}${yours} ${paint(ANSI.dim, r.ref)}`);
15215
15275
  const pad = `${indent} `;
15216
15276
  const facts = [PRIORITY_WORD[r.priority], EVIDENCE_STATUS_WORD[r.evidence_status], NEXT_ACTION_WORD[r.next_action], who(r)].filter(Boolean).join(" \xB7 ");
15217
- out.push(`${pad}${paint(ANSI.dim, facts)}`);
15277
+ out2.push(`${pad}${paint(ANSI.dim, facts)}`);
15218
15278
  const gist = r.description ? paragraphs(r.description)[0] : null;
15219
- if (gist) for (const l of wrap(gist, width - pad.length)) out.push(`${pad}${l}`);
15220
- if (r.learn) for (const l of wrap(`To learn more: ${r.learn}`, width - pad.length)) out.push(`${pad}${paint(ANSI.dim, l)}`);
15221
- if (r.founder_rank !== null && r.now_rank) out.push(`${pad}${paint(ANSI.dim, `Its place is yours; NanoPM would now put it at #${r.now_rank}.`)}`);
15222
- if (r.reconsider) for (const l of wrap(`Worth reconsidering: ${r.reconsider}`, width - pad.length)) out.push(`${pad}${paint(ANSI.amber, l)}`);
15279
+ if (gist) for (const l of wrap(gist, width - pad.length)) out2.push(`${pad}${l}`);
15280
+ if (r.learn) for (const l of wrap(`To learn more: ${r.learn}`, width - pad.length)) out2.push(`${pad}${paint(ANSI.dim, l)}`);
15281
+ if (r.founder_rank !== null && r.now_rank) out2.push(`${pad}${paint(ANSI.dim, `Its place is yours; NanoPM would now put it at #${r.now_rank}.`)}`);
15282
+ if (r.reconsider) for (const l of wrap(`Worth reconsidering: ${r.reconsider}`, width - pad.length)) out2.push(`${pad}${paint(ANSI.amber, l)}`);
15223
15283
  for (const c of r.children) one(c, `${place}.${c.rank}`);
15224
15284
  };
15225
15285
  for (const r of tree) one(r, String(r.rank));
15226
15286
  const aside = rows2.filter((r) => r.status === "set_aside");
15227
15287
  if (aside.length) {
15228
- out.push("", paint(ANSI.bold, `Set aside (${aside.length})`));
15229
- for (const r of aside) out.push(` ${paint(ANSI.dim, r.ref.padEnd(8))} ${r.problem}${r.set_aside_reason ? paint(ANSI.dim, ` \u2014 ${r.set_aside_reason}`) : ""}`);
15288
+ out2.push("", paint(ANSI.bold, `Set aside (${aside.length})`));
15289
+ for (const r of aside) out2.push(` ${paint(ANSI.dim, r.ref.padEnd(8))} ${r.problem}${r.set_aside_reason ? paint(ANSI.dim, ` \u2014 ${r.set_aside_reason}`) : ""}`);
15230
15290
  }
15231
- out.push("", paint(ANSI.dim, '`nanopm opportunities confirm <ref>`, `set-aside <ref> "why"`, `move <ref> <place>`, `reword <ref> "\u2026"`, `brainstorm <ref> [--deep]`, `run [--deep] [--note "\u2026"] [--use signals,data,repo,market] [--skip \u2026]`.'));
15232
- return out.join("\n");
15291
+ out2.push("", paint(ANSI.dim, '`nanopm opportunities confirm <ref>`, `set-aside <ref> "why"`, `move <ref> <place>`, `reword <ref> "\u2026"`, `brainstorm <ref> [--deep]`, `run [--deep] [--note "\u2026"] [--use signals,data,repo,market] [--skip \u2026]`.'));
15292
+ return out2.join("\n");
15233
15293
  }
15234
15294
 
15235
15295
  // src/format/needs.ts
@@ -15237,12 +15297,12 @@ var MARK = { observed: "\u25CF", stated: "\u2013", inferred: "\u25CB" };
15237
15297
  function renderNeeds(persona, tree, opts) {
15238
15298
  const paint = painter(opts.color);
15239
15299
  const width = Math.max(40, columns());
15240
- const out = [];
15300
+ const out2 = [];
15241
15301
  const counts = needCounts(tree);
15242
- out.push(`${paint(ANSI.bold, `${persona.name}\u2019s needs`)} ${paint(ANSI.dim, `${counts.needs} needs \xB7 ${counts.difficulties + counts.precise} difficulties`)}`);
15302
+ out2.push(`${paint(ANSI.bold, `${persona.name}\u2019s needs`)} ${paint(ANSI.dim, `${counts.needs} needs \xB7 ${counts.difficulties + counts.precise} difficulties`)}`);
15243
15303
  if (!tree.length) {
15244
- out.push("", `Nothing mapped yet. \`nanopm needs --refresh ${persona.slug}\` maps it in depth; add --short for a quick pass.`);
15245
- return out.join("\n");
15304
+ out2.push("", `Nothing mapped yet. \`nanopm needs --refresh ${persona.slug}\` maps it in depth; add --short for a quick pass.`);
15305
+ return out2.join("\n");
15246
15306
  }
15247
15307
  const line = (n, depth) => {
15248
15308
  const indent = " ".repeat(depth);
@@ -15251,17 +15311,17 @@ function renderNeeds(persona, tree, opts) {
15251
15311
  const state = n.status === "dropped" ? paint(ANSI.dim, " dropped") : n.status === "confirmed" || n.author_kind === "user" ? "" : paint(ANSI.amber, " to review");
15252
15312
  const label = n.status === "dropped" ? paint(ANSI.dim, n.label) : n.level === 1 ? paint(ANSI.bold, n.label) : n.label;
15253
15313
  const body = wrap(`${label}${where}${state}${n.proposal ? paint(ANSI.amber, " \xB7 NanoPM suggests a new wording") : ""}`, width - indent.length - 12);
15254
- out.push(`${indent}${lead}${paint(ANSI.dim, n.ref.padEnd(8))} ${body[0] ?? ""}`);
15255
- for (const more of body.slice(1)) out.push(`${indent} ${more}`);
15314
+ out2.push(`${indent}${lead}${paint(ANSI.dim, n.ref.padEnd(8))} ${body[0] ?? ""}`);
15315
+ for (const more of body.slice(1)) out2.push(`${indent} ${more}`);
15256
15316
  for (const c of n.children) line(c, depth + 1);
15257
15317
  };
15258
15318
  for (const need of tree) {
15259
- out.push("");
15319
+ out2.push("");
15260
15320
  line(need, 0);
15261
15321
  }
15262
- out.push("", paint(ANSI.dim, `\u25CF observed \u2013 stated by you \u25CB NanoPM\u2019s guess. \`nanopm needs confirm <ref>\`, \`nanopm needs drop <ref> "why"\`, \`nanopm needs dig <ref>\`.`));
15263
- if (opts.others?.length) out.push(paint(ANSI.dim, `Other personas: ${opts.others.join(", ")}.`));
15264
- return out.join("\n");
15322
+ out2.push("", paint(ANSI.dim, `\u25CF observed \u2013 stated by you \u25CB NanoPM\u2019s guess. \`nanopm needs confirm <ref>\`, \`nanopm needs drop <ref> "why"\`, \`nanopm needs dig <ref>\`.`));
15323
+ if (opts.others?.length) out2.push(paint(ANSI.dim, `Other personas: ${opts.others.join(", ")}.`));
15324
+ return out2.join("\n");
15265
15325
  }
15266
15326
 
15267
15327
  // src/commands/needs.ts
@@ -15461,11 +15521,11 @@ async function brainstorm(ref, opts) {
15461
15521
  const minutes = deep ? DEEP_MINUTES * n : FLASH_MINUTES;
15462
15522
  const what = plan.problem.children.length ? `the ${n} narrower problem${n === 1 ? "" : "s"} under ${plan.problem.ref}` : `${plan.problem.ref} \u201C${clip(plan.problem.problem, 60)}\u201D`;
15463
15523
  const how = deep ? `${DEPTH_WORD.deep}${n > 1 ? ", one after the other" : ""}` : `${DEPTH_WORD.quick}, a first pass`;
15464
- const out = [`${deep ? "Going deeper on" : "Brainstorming"} ${what}: ${how}, about ${minutes} minutes on ${daemon.hostname}.`];
15465
- if (plan.problem.children.length) for (const l of plan.leaves) out.push(` ${l.ref.padEnd(8)} ${clip(l.problem, Math.max(30, columns() - 11))}`);
15466
- if (plan.busy) out.push(`${plan.busy} left out: Nano is already finding solutions to ${plan.busy === 1 ? "it" : "them"}.`);
15467
- out.push("The solutions land on `nanopm roadmap`.");
15468
- return out.join("\n");
15524
+ const out2 = [`${deep ? "Going deeper on" : "Brainstorming"} ${what}: ${how}, about ${minutes} minutes on ${daemon.hostname}.`];
15525
+ if (plan.problem.children.length) for (const l of plan.leaves) out2.push(` ${l.ref.padEnd(8)} ${clip(l.problem, Math.max(30, columns() - 11))}`);
15526
+ if (plan.busy) out2.push(`${plan.busy} left out: Nano is already finding solutions to ${plan.busy === 1 ? "it" : "them"}.`);
15527
+ out2.push("The solutions land on `nanopm roadmap`.");
15528
+ return out2.join("\n");
15469
15529
  }
15470
15530
 
15471
15531
  // src/format/roadmap.ts
@@ -15481,11 +15541,11 @@ var WORK_KINDS = /* @__PURE__ */ new Set(["branch", "git", "url", "job"]);
15481
15541
  function renderRoadmapItem(item, opts, problem, builtBy) {
15482
15542
  const paint = painter(opts.color);
15483
15543
  const shape2 = SHAPE[item.status] ?? { mark: "\xB7", colour: "", label: item.status };
15484
- const out = [`${paint(shape2.colour, shape2.mark)} ${paint(ANSI.bold, item.ref)} ${item.title}`];
15485
- if (problem) out.push(paint(ANSI.dim, ` answers ${problem.ref} ${clip(problem.statement, Math.max(30, columns() - 20))}`));
15486
- out.push("");
15487
- for (const line of wrap(item.reasoning, Math.max(40, columns() - 4))) out.push(` ${line}`);
15488
- out.push("");
15544
+ const out2 = [`${paint(shape2.colour, shape2.mark)} ${paint(ANSI.bold, item.ref)} ${item.title}`];
15545
+ if (problem) out2.push(paint(ANSI.dim, ` answers ${problem.ref} ${clip(problem.statement, Math.max(30, columns() - 20))}`));
15546
+ out2.push("");
15547
+ for (const line of wrap(item.reasoning, Math.max(40, columns() - 4))) out2.push(` ${line}`);
15548
+ out2.push("");
15489
15549
  const facts = [
15490
15550
  ["status", shape2.label + (item.outcome ? ` \xB7 ${item.outcome}${item.outcome_note ? `: ${item.outcome_note}` : ""}` : "")],
15491
15551
  ["rank", String(item.rank)],
@@ -15493,15 +15553,15 @@ function renderRoadmapItem(item, opts, problem, builtBy) {
15493
15553
  ["bets on", item.assumption ?? "\u2014"],
15494
15554
  ["we'll know", item.eval ?? "\u2014"]
15495
15555
  ];
15496
- for (const [k, v] of facts) out.push(paint(ANSI.dim, ` ${k.padEnd(9)} ${v}`));
15556
+ for (const [k, v] of facts) out2.push(paint(ANSI.dim, ` ${k.padEnd(9)} ${v}`));
15497
15557
  const sources2 = item.sources ?? [];
15498
15558
  const restsOn = sources2.filter((s) => !WORK_KINDS.has(s.kind));
15499
15559
  if (restsOn.length) {
15500
- out.push(paint(ANSI.dim, ` ${"rests on".padEnd(9)} ${restsOn.map((s) => `${s.kind}:${s.ref}`).join(", ")}`));
15560
+ out2.push(paint(ANSI.dim, ` ${"rests on".padEnd(9)} ${restsOn.map((s) => `${s.kind}:${s.ref}`).join(", ")}`));
15501
15561
  }
15502
15562
  const where = renderWhere(sources2);
15503
- if (where) out.push(paint(ANSI.dim, ` ${"where".padEnd(9)} ${where}${builtBy ? ` \xB7 built by ${builtBy}` : ""}`));
15504
- return out.join("\n");
15563
+ if (where) out2.push(paint(ANSI.dim, ` ${"where".padEnd(9)} ${where}${builtBy ? ` \xB7 built by ${builtBy}` : ""}`));
15564
+ return out2.join("\n");
15505
15565
  }
15506
15566
  function renderWhere(sources2) {
15507
15567
  const branch = sources2.find((s) => s.kind === "branch")?.ref;
@@ -15526,18 +15586,18 @@ var VERB = {
15526
15586
  function renderDecisions(rows2, opts) {
15527
15587
  const paint = painter(opts.color);
15528
15588
  if (!rows2.length) return "No decisions yet.";
15529
- const out = [];
15589
+ const out2 = [];
15530
15590
  for (const row of rows2) {
15531
15591
  const when = row.created_at.slice(0, 10);
15532
15592
  const verb = VERB[row.kind] ?? { text: row.kind, colour: ANSI.blue };
15533
15593
  const about = row.solutions ? `${paint(ANSI.dim, row.solutions.ref)} ${row.solutions.title}` : row.problems ? paint(ANSI.dim, row.problems.ref) : row.market_entries ? `${paint(ANSI.dim, row.market_entries.ref)} ${row.market_entries.competitor ?? ""}`.trimEnd() : "";
15534
15594
  const by = row.by ? paint(ANSI.dim, ` \xB7 by ${row.by}`) : "";
15535
- out.push(`${paint(ANSI.dim, when)} ${paint(verb.colour, verb.text)} ${about}${by}`.trimEnd());
15595
+ out2.push(`${paint(ANSI.dim, when)} ${paint(verb.colour, verb.text)} ${about}${by}`.trimEnd());
15536
15596
  if (row.body) {
15537
- for (const line of wrap(row.body, 72)) out.push(paint(ANSI.dim, ` ${line}`));
15597
+ for (const line of wrap(row.body, 72)) out2.push(paint(ANSI.dim, ` ${line}`));
15538
15598
  }
15539
15599
  }
15540
- return out.join("\n");
15600
+ return out2.join("\n");
15541
15601
  }
15542
15602
  var HUE = ["\x1B[34m", "\x1B[35m", "\x1B[36m", "\x1B[32m", "\x1B[95m"];
15543
15603
  function themesOf(tree) {
@@ -15550,7 +15610,7 @@ function renderSolutionsBoard(board, opts) {
15550
15610
  const paint = painter(opts.color);
15551
15611
  const width = Math.max(40, columns());
15552
15612
  const themes = themesOf(board.tree);
15553
- const out = [`${paint(ANSI.bold, "Roadmap")}${board.objective ? ` ${paint(ANSI.dim, `${board.objective.lever} \u2014 ${board.objective.sentence}`)}` : ""}`];
15613
+ const out2 = [`${paint(ANSI.bold, "Roadmap")}${board.objective ? ` ${paint(ANSI.dim, `${board.objective.lever} \u2014 ${board.objective.sentence}`)}` : ""}`];
15554
15614
  const chips = board.tree.flatMap((b) => {
15555
15615
  const n = board.cards.filter((c) => c.branch.id === b.id).length;
15556
15616
  const t = themes.get(b.id);
@@ -15562,89 +15622,89 @@ function renderSolutionsBoard(board, opts) {
15562
15622
  else lines.push(chip);
15563
15623
  return lines;
15564
15624
  }, []);
15565
- out.push("", ...legend);
15566
- if (!board.cards.some((c) => c.pick.placed_by === "founder")) out.push(paint(ANSI.dim, "Nano placed these in the order of your opportunity map. Move them where you want them."));
15625
+ out2.push("", ...legend);
15626
+ if (!board.cards.some((c) => c.pick.placed_by === "founder")) out2.push(paint(ANSI.dim, "Nano placed these in the order of your opportunity map. Move them where you want them."));
15567
15627
  const refWidth = Math.max(5, ...board.cards.map((c) => c.pick.ref.length));
15568
15628
  const gutter = " ".repeat(refWidth + 4);
15569
15629
  const titleWidth = Math.min(Math.max(...board.cards.map((c) => c.pick.title.length)), width - gutter.length - 8);
15570
15630
  for (const h of HORIZONS) {
15571
15631
  const column = board.cards.filter((c) => c.horizon === h);
15572
- out.push("", `${paint(ANSI.bold, HORIZON_WORD[h])} ${paint(ANSI.dim, String(column.length))}`);
15573
- if (!column.length) out.push(paint(ANSI.dim, ` Move a card here: nanopm roadmap move <ref> ${h}`));
15632
+ out2.push("", `${paint(ANSI.bold, HORIZON_WORD[h])} ${paint(ANSI.dim, String(column.length))}`);
15633
+ if (!column.length) out2.push(paint(ANSI.dim, ` Move a card here: nanopm roadmap move <ref> ${h}`));
15574
15634
  for (const c of column) {
15575
15635
  const t = themes.get(c.branch.id);
15576
- out.push(` ${paint(ANSI.dim, c.pick.ref.padEnd(refWidth))} ${paint(t.colour, "\u25CF")} ${paint(ANSI.dim, t.name)}`);
15577
- out.push(`${gutter}${clip(c.pick.title, titleWidth).padEnd(titleWidth)} ${sizeWord(c.pick.size, paint)}`.trimEnd());
15636
+ out2.push(` ${paint(ANSI.dim, c.pick.ref.padEnd(refWidth))} ${paint(t.colour, "\u25CF")} ${paint(ANSI.dim, t.name)}`);
15637
+ out2.push(`${gutter}${clip(c.pick.title, titleWidth).padEnd(titleWidth)} ${sizeWord(c.pick.size, paint)}`.trimEnd());
15578
15638
  const depth = board.working.get(c.leaf.id);
15579
- if (depth) out.push(`${gutter}${paint(ANSI.blue, "\u25CF")} ${paint(ANSI.dim, WORK_WORD[depth])}`);
15639
+ if (depth) out2.push(`${gutter}${paint(ANSI.blue, "\u25CF")} ${paint(ANSI.dim, WORK_WORD[depth])}`);
15580
15640
  }
15581
15641
  }
15582
- if (board.earlier) out.push("", paint(ANSI.bold, "On the problem list"), "", board.earlier);
15583
- out.push(
15642
+ if (board.earlier) out2.push("", paint(ANSI.bold, "On the problem list"), "", board.earlier);
15643
+ out2.push(
15584
15644
  "",
15585
15645
  paint(ANSI.dim, 'nanopm roadmap <ref> \xB7 nanopm roadmap move <ref> now|next|later [position] \xB7 nanopm reject <ref> ["why"]'),
15586
15646
  paint(ANSI.dim, "nanopm opportunities brainstorm <ref> [--deep]: solutions to another problem, or deeper ones")
15587
15647
  );
15588
- return out.join("\n");
15648
+ return out2.join("\n");
15589
15649
  }
15590
15650
  function renderSolutionSheet(s, at, opts) {
15591
15651
  const paint = painter(opts.color);
15592
15652
  const width = Math.max(40, columns());
15593
15653
  const theme = at.branch ? themesOf(at.tree).get(at.branch.id) : void 0;
15594
- const out = [];
15595
- if (theme) out.push(`${paint(theme.colour, "\u25CF")} ${paint(ANSI.dim, theme.name)}`);
15654
+ const out2 = [];
15655
+ if (theme) out2.push(`${paint(theme.colour, "\u25CF")} ${paint(ANSI.dim, theme.name)}`);
15596
15656
  const size = sizeWord(s.size, paint);
15597
- out.push(`${paint(ANSI.dim, s.ref)} ${paint(ANSI.bold, s.title)}${size ? ` ${size}` : ""}`);
15657
+ out2.push(`${paint(ANSI.dim, s.ref)} ${paint(ANSI.bold, s.title)}${size ? ` ${size}` : ""}`);
15598
15658
  const pad = " ".repeat(12);
15599
- const heading = (text2) => out.push("", ` ${paint(ANSI.bold, text2)}`);
15659
+ const heading = (text2) => out2.push("", ` ${paint(ANSI.bold, text2)}`);
15600
15660
  heading("Problem we are solving");
15601
15661
  if (at.leaf) {
15602
15662
  const solves = wrap(at.leaf.problem, width - 6 - at.leaf.ref.length - 2);
15603
15663
  solves[solves.length - 1] += ` ${paint(ANSI.dim, at.leaf.ref)}`;
15604
- for (const line of solves) out.push(` ${line}`);
15664
+ for (const line of solves) out2.push(` ${line}`);
15605
15665
  if (at.branch && at.branch.id !== at.leaf.id) {
15606
- for (const line of wrap(`Part of: ${at.branch.problem} ${at.branch.ref}`, width - 6)) out.push(` ${paint(ANSI.dim, line)}`);
15666
+ for (const line of wrap(`Part of: ${at.branch.problem} ${at.branch.ref}`, width - 6)) out2.push(` ${paint(ANSI.dim, line)}`);
15607
15667
  }
15608
15668
  } else {
15609
- out.push(paint(ANSI.dim, " The problem it answered is no longer on the map."));
15669
+ out2.push(paint(ANSI.dim, " The problem it answered is no longer on the map."));
15610
15670
  }
15611
15671
  const described = paragraphs(s.description);
15612
15672
  if (described.length) {
15613
15673
  heading("Proposed solution");
15614
15674
  described.forEach((p, i) => {
15615
- if (i) out.push("");
15616
- for (const line of wrap(p, width - 6)) out.push(` ${line}`);
15675
+ if (i) out2.push("");
15676
+ for (const line of wrap(p, width - 6)) out2.push(` ${line}`);
15617
15677
  });
15618
15678
  }
15619
15679
  const changelog = parseChangelog(s.changelog);
15620
15680
  if (changelog) {
15621
15681
  heading("Future changelog");
15622
- for (const line of wrap(changelog.headline, width - 6)) out.push(` ${paint(ANSI.bold, line)}`);
15623
- for (const point of changelog.points) wrap(point, width - 8).forEach((line, i) => out.push(` ${i ? " " : "\u2022"} ${line}`));
15682
+ for (const line of wrap(changelog.headline, width - 6)) out2.push(` ${paint(ANSI.bold, line)}`);
15683
+ for (const point of changelog.points) wrap(point, width - 8).forEach((line, i) => out2.push(` ${i ? " " : "\u2022"} ${line}`));
15624
15684
  }
15625
15685
  if (s.steps.length) {
15626
15686
  heading("Plan");
15627
15687
  s.steps.forEach((step, i) => {
15628
15688
  const who = STEP_WHO_WORD[step.who] ?? step.who;
15629
- wrap(step.do, width - pad.length - 8).forEach((line, j) => out.push(`${j ? pad : ` \u2610 ${who.padEnd(6)}`}${line}`));
15630
- if (step.checks) out.push(pad + paint(ANSI.amber, i === 0 ? "checks the problem first" : "checks the problem"));
15689
+ wrap(step.do, width - pad.length - 8).forEach((line, j) => out2.push(`${j ? pad : ` \u2610 ${who.padEnd(6)}`}${line}`));
15690
+ if (step.checks) out2.push(pad + paint(ANSI.amber, i === 0 ? "checks the problem first" : "checks the problem"));
15631
15691
  });
15632
15692
  }
15633
15693
  const card = at.leaf ? at.cards.find((c) => c.leaf.id === at.leaf.id) : void 0;
15634
15694
  const others = card ? card.others.filter((o) => o.id !== s.id) : [];
15635
- out.push("");
15636
- if (s.status === "rejected") out.push(paint(ANSI.dim, " Thrown out."));
15695
+ out2.push("");
15696
+ if (s.status === "rejected") out2.push(paint(ANSI.dim, " Thrown out."));
15637
15697
  else if (card?.pick.id === s.id) {
15638
15698
  const column = at.cards.filter((c) => c.horizon === card.horizon);
15639
15699
  const yours = s.placed_by === "founder" ? ", where you put it" : "";
15640
- out.push(paint(ANSI.dim, ` On the board: ${HORIZON_WORD[card.horizon]}, ${column.indexOf(card) + 1} of ${column.length}${yours}.`));
15641
- } else out.push(paint(ANSI.dim, card ? ` Not on the board: ${card.pick.ref} is this problem's card.` : " Not on the board."));
15642
- if (others.length) out.push(paint(ANSI.dim, ` Other solutions to this problem: ${others.map((o) => o.ref).join(", ")}.`));
15700
+ out2.push(paint(ANSI.dim, ` On the board: ${HORIZON_WORD[card.horizon]}, ${column.indexOf(card) + 1} of ${column.length}${yours}.`));
15701
+ } else out2.push(paint(ANSI.dim, card ? ` Not on the board: ${card.pick.ref} is this problem's card.` : " Not on the board."));
15702
+ if (others.length) out2.push(paint(ANSI.dim, ` Other solutions to this problem: ${others.map((o) => o.ref).join(", ")}.`));
15643
15703
  if (s.status === "proposed") {
15644
15704
  const move = card?.pick.id === s.id ? `nanopm roadmap move ${s.ref} now|next|later [position] \xB7 ` : "";
15645
- out.push(paint(ANSI.dim, ` ${move}nanopm reject ${s.ref} ["why"]`));
15705
+ out2.push(paint(ANSI.dim, ` ${move}nanopm reject ${s.ref} ["why"]`));
15646
15706
  }
15647
- return out.join("\n");
15707
+ return out2.join("\n");
15648
15708
  }
15649
15709
 
15650
15710
  // src/format/problems.ts
@@ -15674,23 +15734,23 @@ function renderProblems(problems2, duplicates, opts) {
15674
15734
  ].join("\n");
15675
15735
  }
15676
15736
  const refWidth = Math.max(...problems2.map((p) => p.ref.length));
15677
- const out = [];
15737
+ const out2 = [];
15678
15738
  const map = groupProblemMap(problems2.map((p) => ({ ...p, parentRef: p.parent_ref })));
15679
15739
  const ordered = [...map.branches.flatMap((b) => [b.parent, ...b.children]), ...map.unplaced];
15680
15740
  for (const problem of ordered) {
15681
15741
  const shape2 = SHAPE2[problem.status] ?? { mark: "\xB7", colour: ANSI.dim, label: problem.status };
15682
15742
  const score = renderImportance(problem, opts);
15683
15743
  const head = `${paint(shape2.colour, shape2.mark)} ${paint(ANSI.dim, problem.ref.padEnd(refWidth))} ${score} ${problem.parent_ref ? paint(ANSI.dim, "\u21B3 ") : ""}${clip(firstSentence(problem.statement), Math.max(40, columns() - refWidth - 14))}`;
15684
- out.push(head);
15685
- if (problem.mapping) out.push(paint(ANSI.dim, ` ${problem.mapping.level === 1 ? "Need" : "Difficulty"} \xB7 ${problem.mapping.knowledge} \xB7 ${problem.mapping.origin === "expert_inference" ? "expert inference" : "source analysis"}`));
15744
+ out2.push(head);
15745
+ if (problem.mapping) out2.push(paint(ANSI.dim, ` ${problem.mapping.level === 1 ? "Need" : "Difficulty"} \xB7 ${problem.mapping.knowledge} \xB7 ${problem.mapping.origin === "expert_inference" ? "expert inference" : "source analysis"}`));
15686
15746
  const indent = ` ${" ".repeat(refWidth)} `;
15687
15747
  const tail = problem.proofs > 0 ? `${problem.proofs} proof${problem.proofs === 1 ? "" : "s"}${problem.proof_kinds?.length ? ` \xB7 ${problem.proof_kinds.join(", ")}` : ""}` : problem.importance_note ? `no proof yet \xB7 ${problem.importance_note}` : "no proof yet";
15688
- out.push(paint(ANSI.dim, indent + clip(tail, Math.max(40, columns() - indent.length))));
15748
+ out2.push(paint(ANSI.dim, indent + clip(tail, Math.max(40, columns() - indent.length))));
15689
15749
  }
15690
15750
  const unproven = problems2.filter((p) => p.proofs === 0).length;
15691
15751
  if (unproven) {
15692
- out.push("");
15693
- out.push(
15752
+ out2.push("");
15753
+ out2.push(
15694
15754
  paint(
15695
15755
  ANSI.dim,
15696
15756
  `${unproven} of ${problems2.length} rest on judgement, not proof. A tilde marks those.`
@@ -15698,45 +15758,45 @@ function renderProblems(problems2, duplicates, opts) {
15698
15758
  );
15699
15759
  }
15700
15760
  if (duplicates.length) {
15701
- out.push("");
15761
+ out2.push("");
15702
15762
  for (const pair of duplicates.slice(0, 3)) {
15703
15763
  const why = pair.flagged ? `NanoPM is not sure ${pair.a} and ${pair.b} are different problems` : `${pair.a} and ${pair.b} read alike`;
15704
- out.push(paint(ANSI.amber, `? ${why}`));
15764
+ out2.push(paint(ANSI.amber, `? ${why}`));
15705
15765
  }
15706
- out.push(paint(ANSI.dim, ` nanopm decisions why \xB7 nanopm problems merge ${duplicates[0].b} ${duplicates[0].a}`));
15766
+ out2.push(paint(ANSI.dim, ` nanopm decisions why \xB7 nanopm problems merge ${duplicates[0].b} ${duplicates[0].a}`));
15707
15767
  }
15708
15768
  if (opts.manualMerges !== void 0) {
15709
- out.push("");
15710
- out.push(
15769
+ out2.push("");
15770
+ out2.push(
15711
15771
  paint(
15712
15772
  ANSI.dim,
15713
15773
  `${opts.manualMerges} merge${opts.manualMerges === 1 ? "" : "s"} you have had to make by hand.`
15714
15774
  )
15715
15775
  );
15716
15776
  }
15717
- out.push("");
15718
- out.push(paint(ANSI.dim, " nanopm problems <ref> \xB7 nanopm problems merge <from> <into> \xB7 nanopm problems weight <ref> <0-100>"));
15719
- return out.join("\n");
15777
+ out2.push("");
15778
+ out2.push(paint(ANSI.dim, " nanopm problems <ref> \xB7 nanopm problems merge <from> <into> \xB7 nanopm problems weight <ref> <0-100>"));
15779
+ return out2.join("\n");
15720
15780
  }
15721
15781
  function renderProblem(problem, evidence2, addressedBy, opts) {
15722
15782
  const paint = painter(opts.color);
15723
15783
  const shape2 = SHAPE2[problem.status] ?? { mark: "\xB7", colour: ANSI.dim, label: problem.status };
15724
15784
  const width = columns();
15725
- const out = [];
15726
- out.push(`${paint(shape2.colour, shape2.mark)} ${paint(ANSI.dim, problem.ref)} ${paint(ANSI.bold, shape2.label)}`);
15727
- out.push("");
15728
- for (const line of wrap(problem.statement, width)) out.push(line);
15729
- out.push("");
15785
+ const out2 = [];
15786
+ out2.push(`${paint(shape2.colour, shape2.mark)} ${paint(ANSI.dim, problem.ref)} ${paint(ANSI.bold, shape2.label)}`);
15787
+ out2.push("");
15788
+ for (const line of wrap(problem.statement, width)) out2.push(line);
15789
+ out2.push("");
15730
15790
  const facts = [];
15731
15791
  const m = problem.mapping ?? null;
15732
15792
  if (m) {
15733
15793
  const story = problemStory(m, { proofs: problem.proofs, kinds: problem.proof_kinds ?? [], provenance: problem.provenance ?? "inferred", authorKind: problem.author_kind ?? "nanopm", createdAt: problem.created_at ?? "" });
15734
- out.push(...wrap(story.story, width));
15735
- out.push("");
15736
- out.push(...wrap(story.doubt, width).map((l) => paint(ANSI.dim, l)));
15737
- out.push(...wrap(story.assumes ? `${story.test} ${story.assumes}` : story.test, width).map((l) => paint(ANSI.dim, l)));
15738
- out.push("");
15739
- out.push(paint(ANSI.dim, "how I got here"));
15794
+ out2.push(...wrap(story.story, width));
15795
+ out2.push("");
15796
+ out2.push(...wrap(story.doubt, width).map((l) => paint(ANSI.dim, l)));
15797
+ out2.push(...wrap(story.assumes ? `${story.test} ${story.assumes}` : story.test, width).map((l) => paint(ANSI.dim, l)));
15798
+ out2.push("");
15799
+ out2.push(paint(ANSI.dim, "how I got here"));
15740
15800
  facts.push(["kind", `${m.level === 1 ? "a need" : "a difficulty"} \xB7 ${m.knowledge} \xB7 ${m.origin === "expert_inference" ? "expert inference" : "source analysis"}`]);
15741
15801
  if (m.parent_reason) facts.push(["grouping", m.parent_reason]);
15742
15802
  }
@@ -15747,31 +15807,31 @@ function renderProblem(problem, evidence2, addressedBy, opts) {
15747
15807
  ]);
15748
15808
  if (problem.parent_ref) facts.push(["part of", problem.parent_ref]);
15749
15809
  for (const [label, value] of facts) {
15750
- out.push(`${paint(ANSI.dim, label.padEnd(9))}${clip(value, width - 9)}`);
15810
+ out2.push(`${paint(ANSI.dim, label.padEnd(9))}${clip(value, width - 9)}`);
15751
15811
  }
15752
15812
  if (evidence2.length) {
15753
- out.push("");
15754
- out.push(paint(ANSI.dim, "sources"));
15813
+ out2.push("");
15814
+ out2.push(paint(ANSI.dim, "sources"));
15755
15815
  for (const item of evidence2) {
15756
15816
  const when = (item.occurred_at ?? item.created_at).slice(0, 10);
15757
15817
  const where = item.metric_key ?? item.source_ref ?? item.kind;
15758
- for (const line of wrap(item.body, width - 2)) out.push(` ${line}`);
15759
- out.push(paint(ANSI.dim, ` \u2014 ${item.relation ?? "supports"} \xB7 ${item.kind} \xB7 ${where} \xB7 ${when}`));
15818
+ for (const line of wrap(item.body, width - 2)) out2.push(` ${line}`);
15819
+ out2.push(paint(ANSI.dim, ` \u2014 ${item.relation ?? "supports"} \xB7 ${item.kind} \xB7 ${where} \xB7 ${when}`));
15760
15820
  }
15761
15821
  } else {
15762
- out.push("");
15763
- out.push(paint(ANSI.dim, "No proof yet. This ranking is a judgement, and the next review may settle it."));
15822
+ out2.push("");
15823
+ out2.push(paint(ANSI.dim, "No proof yet. This ranking is a judgement, and the next review may settle it."));
15764
15824
  }
15765
15825
  if (addressedBy.length) {
15766
- out.push("");
15767
- out.push(paint(ANSI.dim, "addressed by"));
15826
+ out2.push("");
15827
+ out2.push(paint(ANSI.dim, "addressed by"));
15768
15828
  for (const item of addressedBy) {
15769
- out.push(` ${paint(ANSI.dim, item.ref)} ${clip(item.title, width - 12)} ${paint(ANSI.dim, `(${item.status})`)}`);
15829
+ out2.push(` ${paint(ANSI.dim, item.ref)} ${clip(item.title, width - 12)} ${paint(ANSI.dim, `(${item.status})`)}`);
15770
15830
  }
15771
15831
  }
15772
- out.push("");
15773
- out.push(paint(ANSI.dim, " nanopm problems \xB7 nanopm roadmap"));
15774
- return out.join("\n");
15832
+ out2.push("");
15833
+ out2.push(paint(ANSI.dim, " nanopm problems \xB7 nanopm roadmap"));
15834
+ return out2.join("\n");
15775
15835
  }
15776
15836
 
15777
15837
  // src/format/tree.ts
@@ -15793,49 +15853,49 @@ var NO_AREA = "no area yet";
15793
15853
  var SHOWN = /* @__PURE__ */ new Set(["proposed", "accepted", "building", "done"]);
15794
15854
  function renderTree(problems2, solutions, strategy, opts) {
15795
15855
  const paint = painter(opts.color);
15796
- const out = [];
15856
+ const out2 = [];
15797
15857
  const width = Math.max(40, columns());
15798
15858
  if (strategy) {
15799
- out.push(paint(ANSI.bold, "Strategy"));
15800
- for (const line of wrap(strategy, width - 4)) out.push(` ${line}`);
15801
- out.push("");
15859
+ out2.push(paint(ANSI.bold, "Strategy"));
15860
+ for (const line of wrap(strategy, width - 4)) out2.push(` ${line}`);
15861
+ out2.push("");
15802
15862
  }
15803
15863
  const shown = solutions.filter((s) => SHOWN.has(s.status));
15804
15864
  const open = problems2.filter((p) => p.status !== "dropped" && (!opts.alongside || shown.some((s) => s.problem_id === p.id)));
15805
15865
  if (opts.alongside && !open.length) return "";
15806
15866
  if (!open.length && !shown.length) {
15807
- out.push("No problems yet. `nanopm opportunities` shows what to work on.");
15808
- return out.join("\n");
15867
+ out2.push("No problems yet. `nanopm opportunities` shows what to work on.");
15868
+ return out2.join("\n");
15809
15869
  }
15810
15870
  const byProblem = /* @__PURE__ */ new Map();
15811
15871
  for (const s of shown) byProblem.set(s.problem_id, [...byProblem.get(s.problem_id) ?? [], s]);
15812
15872
  const refWidth = Math.max(...[...open.map((p) => p.ref), ...shown.map((s) => s.ref)].map((r) => r.length), 5);
15813
15873
  for (const [area, rows2] of groupByArea(open)) {
15814
- out.push(paint(ANSI.bold, area));
15874
+ out2.push(paint(ANSI.bold, area));
15815
15875
  for (const p of rows2) {
15816
15876
  const shape2 = PROBLEM_SHAPE[p.status] ?? { mark: "\xB7", colour: "" };
15817
15877
  const kind = p.kind === "founder" ? paint(ANSI.dim, " (founder)") : "";
15818
15878
  const proof = p.proofs ? `${p.proofs} proof${p.proofs === 1 ? "" : "s"}` : "no proof";
15819
15879
  const tail = `${renderImportance(p, opts)} ${paint(ANSI.dim, proof)}`;
15820
15880
  const head = ` ${paint(shape2.colour, shape2.mark)} ${paint(ANSI.dim, p.ref.padEnd(refWidth))} `;
15821
- out.push(head + clip(p.statement, Math.max(30, width - visibleLength(head) - visibleLength(tail) - 2)) + kind + " " + tail);
15881
+ out2.push(head + clip(p.statement, Math.max(30, width - visibleLength(head) - visibleLength(tail) - 2)) + kind + " " + tail);
15822
15882
  const under = byProblem.get(p.id) ?? [];
15823
15883
  if (!under.length) {
15824
- out.push(paint(ANSI.dim, ` ${" ".repeat(refWidth + 2)} no solution yet`));
15884
+ out2.push(paint(ANSI.dim, ` ${" ".repeat(refWidth + 2)} no solution yet`));
15825
15885
  continue;
15826
15886
  }
15827
- for (const s of under) out.push(...solutionLines(s, refWidth, width, paint));
15887
+ for (const s of under) out2.push(...solutionLines(s, refWidth, width, paint));
15828
15888
  }
15829
- out.push("");
15889
+ out2.push("");
15830
15890
  }
15831
15891
  const waiting = shown.filter((s) => s.status === "proposed").length;
15832
15892
  if (waiting) {
15833
- out.push(`${waiting} waiting on you.`);
15834
- out.push(paint(ANSI.dim, ' nanopm accept <ref> \xB7 nanopm reject <ref> "why" \xB7 nanopm roadmap <ref> \xB7 nanopm roadmap --board'));
15893
+ out2.push(`${waiting} waiting on you.`);
15894
+ out2.push(paint(ANSI.dim, ' nanopm accept <ref> \xB7 nanopm reject <ref> "why" \xB7 nanopm roadmap <ref> \xB7 nanopm roadmap --board'));
15835
15895
  } else if (!opts.alongside) {
15836
- out.push(paint(ANSI.dim, "Nothing waiting on you."));
15896
+ out2.push(paint(ANSI.dim, "Nothing waiting on you."));
15837
15897
  }
15838
- return out.join("\n").trimEnd();
15898
+ return out2.join("\n").trimEnd();
15839
15899
  }
15840
15900
  function solutionLines(s, refWidth, width, paint, indent = 6) {
15841
15901
  const shape2 = SOLUTION_SHAPE[s.status] ?? { mark: "\xB7", colour: "", label: s.status };
@@ -15867,25 +15927,25 @@ var BOARD_COLUMNS = [
15867
15927
  ];
15868
15928
  function renderBoard(problems2, solutions, opts) {
15869
15929
  const paint = painter(opts.color);
15870
- const out = [];
15930
+ const out2 = [];
15871
15931
  const byId = new Map(problems2.map((p) => [p.id, p]));
15872
15932
  const width = Math.max(40, columns());
15873
15933
  const refWidth = Math.max(...solutions.map((s) => s.ref.length), 5);
15874
15934
  for (const col of BOARD_COLUMNS) {
15875
15935
  const rows2 = solutions.filter((s) => s.status === col.status);
15876
- out.push(`${paint(ANSI.bold, col.label)} ${paint(ANSI.dim, String(rows2.length))}`);
15877
- if (!rows2.length) out.push(paint(ANSI.dim, " \u2014"));
15936
+ out2.push(`${paint(ANSI.bold, col.label)} ${paint(ANSI.dim, String(rows2.length))}`);
15937
+ if (!rows2.length) out2.push(paint(ANSI.dim, " \u2014"));
15878
15938
  for (const s of rows2) {
15879
15939
  const shape2 = SOLUTION_SHAPE[s.status];
15880
15940
  const problem = byId.get(s.problem_id);
15881
15941
  const head = ` ${paint(shape2.colour, shape2.mark)} ${paint(ANSI.dim, s.ref.padEnd(refWidth))} `;
15882
- out.push(head + clip(s.title, Math.max(30, width - visibleLength(head))));
15942
+ out2.push(head + clip(s.title, Math.max(30, width - visibleLength(head))));
15883
15943
  const under = [problem ? `${problem.ref} ${clip(problem.statement, 50)}` : "its problem was dropped", s.assumption ? clip(s.assumption, 40) : null].filter(Boolean).join(" \xB7 ");
15884
- out.push(paint(ANSI.dim, ` ${" ".repeat(refWidth + 2)} ${under}`));
15944
+ out2.push(paint(ANSI.dim, ` ${" ".repeat(refWidth + 2)} ${under}`));
15885
15945
  }
15886
- out.push("");
15946
+ out2.push("");
15887
15947
  }
15888
- return out.join("\n").trimEnd();
15948
+ return out2.join("\n").trimEnd();
15889
15949
  }
15890
15950
 
15891
15951
  // src/commands/roadmap.ts
@@ -16064,9 +16124,9 @@ function normalizeRef(input, items) {
16064
16124
  }
16065
16125
 
16066
16126
  // src/commands/build.ts
16067
- var execFileAsync9 = promisify9(execFile10);
16127
+ var execFileAsync9 = promisify10(execFile11);
16068
16128
  async function build(ref, opts) {
16069
- const { renderer, out } = rendererFor(opts.out);
16129
+ const { renderer, out: out2 } = rendererFor(opts.out);
16070
16130
  const auth = await authenticate();
16071
16131
  const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
16072
16132
  const repo = await detectRepo(opts.cwd);
@@ -16076,9 +16136,9 @@ async function build(ref, opts) {
16076
16136
  const solution = (solutions ?? []).find((s) => s.ref === wanted);
16077
16137
  if (!solution) throw new Error(`No solution ${wanted} on the Roadmap.`);
16078
16138
  const stream = async (jobId2) => {
16079
- out(`${solution.ref} ${solution.title}`);
16080
- const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out);
16081
- return streamJob(auth.client, jobId2, { pollMs: 700, out, renderer, cancelSignal: opts.cancelSignal, answerer, verbose: verboseWanted() });
16139
+ out2(`${solution.ref} ${solution.title}`);
16140
+ const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out2);
16141
+ return streamJob(auth.client, jobId2, { pollMs: 700, out: out2, renderer, cancelSignal: opts.cancelSignal, answerer, verbose: verboseWanted() });
16082
16142
  };
16083
16143
  const live = await buildOf(auth.client, project.id, [...ACTIVE_STATUSES]);
16084
16144
  if (live) {
@@ -16088,7 +16148,7 @@ async function build(ref, opts) {
16088
16148
  }
16089
16149
  const stopped = opts.continue || opts.throwAway ? await buildOf(auth.client, project.id, ["failed", "cancelled"], solution.id) : null;
16090
16150
  if ((opts.continue || opts.throwAway) && !stopped) throw new Error(`${solution.ref} has no stopped build to ${opts.continue ? "continue" : "throw away"}.`);
16091
- if (opts.throwAway) return throwAway(auth.client, stopped, repo.root, out);
16151
+ if (opts.throwAway) return throwAway(auth.client, stopped, repo.root, out2);
16092
16152
  const [daemon, local] = await Promise.all([awakeDaemon(auth.client, auth.userId), localDaemonId()]);
16093
16153
  if (!local || daemon?.id !== local) throw new Error("This machine\u2019s daemon is asleep, so nothing was queued. `nanopm daemon install` starts it; then build again.");
16094
16154
  await rememberClone(auth.client, project.id, repo.root);
@@ -16110,13 +16170,13 @@ async function buildOf(client, projectId, statuses, solutionId) {
16110
16170
  const params = data?.params ?? {};
16111
16171
  return data ? { id: data.id, solutionId: params.solution_id ?? "", was: wasOf(data.params), origin: params.origin ?? data.id } : null;
16112
16172
  }
16113
- async function throwAway(client, build2, clone, out) {
16173
+ async function throwAway(client, build2, clone, out2) {
16114
16174
  const { place } = await loadBuildHistory(client, [build2.origin], { home: homedir6(), jobId: build2.origin });
16115
16175
  if (!place) throw new Error("that build left nothing on this machine.");
16116
16176
  if (existsSync7(place.dir)) await execFileAsync9("git", ["-C", clone, "worktree", "remove", "--force", place.dir]);
16117
16177
  await execFileAsync9("git", ["-C", clone, "branch", "-D", place.branch]).catch(() => void 0);
16118
16178
  await appendJobEvent(client, build2.id, "thrown", {}, "user");
16119
- out(`Thrown away: the copy of the code and the local branch ${place.branch}.`);
16179
+ out2(`Thrown away: the copy of the code and the local branch ${place.branch}.`);
16120
16180
  return 0;
16121
16181
  }
16122
16182
 
@@ -16135,7 +16195,7 @@ var POSTGRES_KEYS = [
16135
16195
  var POSTHOG_KEYS = ["POSTHOG_API_KEY", "POSTHOG_PERSONAL_API_KEY", "NEXT_PUBLIC_POSTHOG_KEY"];
16136
16196
  var AMPLITUDE_KEYS = ["AMPLITUDE_API_KEY", "AMPLITUDE_SECRET_KEY", "NEXT_PUBLIC_AMPLITUDE_KEY"];
16137
16197
  function parseEnv(text2) {
16138
- const out = {};
16198
+ const out2 = {};
16139
16199
  for (const raw of text2.split("\n")) {
16140
16200
  const line = raw.trim();
16141
16201
  if (!line || line.startsWith("#")) continue;
@@ -16145,9 +16205,9 @@ function parseEnv(text2) {
16145
16205
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
16146
16206
  value = value.slice(1, -1);
16147
16207
  }
16148
- if (value) out[match[1]] = value;
16208
+ if (value) out2[match[1]] = value;
16149
16209
  }
16150
- return out;
16210
+ return out2;
16151
16211
  }
16152
16212
  var PLACEHOLDER = /<|your-|xxxx/i;
16153
16213
  function detectInEnv(env, file) {
@@ -16169,15 +16229,15 @@ function detectInEnv(env, file) {
16169
16229
  return found;
16170
16230
  }
16171
16231
  async function envFiles(repoRoot) {
16172
- const out = [];
16232
+ const out2 = [];
16173
16233
  for (const file of ENV_FILES) {
16174
16234
  try {
16175
- out.push({ file, env: parseEnv(await readFile15(join17(repoRoot, file), "utf8")) });
16235
+ out2.push({ file, env: parseEnv(await readFile15(join17(repoRoot, file), "utf8")) });
16176
16236
  } catch {
16177
16237
  continue;
16178
16238
  }
16179
16239
  }
16180
- return out;
16240
+ return out2;
16181
16241
  }
16182
16242
  async function detectSources(repoRoot) {
16183
16243
  const found = [];
@@ -16263,7 +16323,7 @@ async function start(sentence2, opts) {
16263
16323
  return startProject(text2, { ...opts, repo });
16264
16324
  }
16265
16325
  async function startProject(text2, opts) {
16266
- const { renderer, out } = rendererFor(opts.out);
16326
+ const { renderer, out: out2 } = rendererFor(opts.out);
16267
16327
  const auth = await authenticate();
16268
16328
  const daemonId = await localDaemonId();
16269
16329
  if (!daemonId) throw new Error("no daemon on this machine yet; `nanopm login` starts one (or `nanopm daemon install`)");
@@ -16279,24 +16339,24 @@ async function startProject(text2, opts) {
16279
16339
  const { data, error: error2 } = await auth.client.from("projects").insert({ name, slug: uniqueSlug(name, (mine ?? []).map((p) => p.slug)), repo_remote: opts.repo?.remote ?? null, created_by: auth.userId }).select("id, name, slug").single();
16280
16340
  if (error2 || !data) throw new Error(`could not create the project: ${error2?.message}`);
16281
16341
  project = data;
16282
- out(`created project ${project.name} (${project.id})${opts.repo ? "" : ", with no repository yet"}`);
16342
+ out2(`created project ${project.name} (${project.id})${opts.repo ? "" : ", with no repository yet"}`);
16283
16343
  }
16284
16344
  await auth.client.from("project_members").update({ daemon_id: daemonId }).eq("project_id", project.id).eq("user_id", auth.userId);
16285
16345
  if (opts.repo) await rememberClone(auth.client, project.id, opts.repo.root);
16286
16346
  const { data: job, error } = await auth.client.from("jobs").insert({ project_id: project.id, skill: "start", params: { sentence: text2 }, requested_by: auth.userId, target_daemon_id: daemonId }).select("id").single();
16287
16347
  if (error || !job) throw new Error(`could not queue the conversation: ${error?.message}`);
16288
- out(`queued start as job ${job.id}`);
16348
+ out2(`queued start as job ${job.id}`);
16289
16349
  const link = newProjectUrl(job.id);
16290
- out(`Follow along and answer at ${link}`);
16350
+ out2(`Follow along and answer at ${link}`);
16291
16351
  if (!opts.noBrowser) openBrowser(link);
16292
- const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out);
16293
- const exit = await streamJob(auth.client, job.id, { pollMs: 1e3, out, renderer, cancelSignal: opts.cancelSignal, answerer, link, verbose: verboseWanted() });
16294
- for (const line of closingLines(project, exit, process.env, opts.repo ? "onboard" : "start")) out(line);
16352
+ const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out2);
16353
+ const exit = await streamJob(auth.client, job.id, { pollMs: 1e3, out: out2, renderer, cancelSignal: opts.cancelSignal, answerer, link, verbose: verboseWanted() });
16354
+ for (const line of closingLines(project, exit, process.env, opts.repo ? "onboard" : "start")) out2(line);
16295
16355
  return exit;
16296
16356
  }
16297
- async function askSentence(out) {
16357
+ async function askSentence(out2) {
16298
16358
  if (!process.stdin.isTTY) return null;
16299
- out("Tell me about it in a sentence: what are you building, and for whom?");
16359
+ out2("Tell me about it in a sentence: what are you building, and for whom?");
16300
16360
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
16301
16361
  try {
16302
16362
  return (await rl.question("> ")).trim() || null;
@@ -16307,17 +16367,17 @@ async function askSentence(out) {
16307
16367
 
16308
16368
  // src/commands/onboard.ts
16309
16369
  async function onboard(opts) {
16310
- const { renderer, out } = rendererFor(opts.out);
16370
+ const { renderer, out: out2 } = rendererFor(opts.out);
16311
16371
  const repo = await detectRepo(opts.cwd);
16312
16372
  if (!repo) throw new Error('run this inside a git repository, or nanopm start "<sentence>" for a project with no code yet');
16313
16373
  const auth = await authenticate();
16314
16374
  const daemonId = await localDaemonId();
16315
16375
  if (!daemonId) throw new Error("no daemon on this machine yet; `nanopm login` starts one (or `nanopm daemon install`)");
16316
- let project = opts.projectId ? await attach(auth.client, opts.projectId, repo, out) : repo.remote ? (await auth.client.from("projects").select("id, name, slug").eq("repo_remote", repo.remote).limit(1).maybeSingle()).data : null;
16376
+ let project = opts.projectId ? await attach(auth.client, opts.projectId, repo, out2) : repo.remote ? (await auth.client.from("projects").select("id, name, slug").eq("repo_remote", repo.remote).limit(1).maybeSingle()).data : null;
16317
16377
  if (repo.empty) {
16318
16378
  if (opts.scanOnly) throw new Error('nothing to read here yet; nanopm start "<sentence>" holds the conversation instead');
16319
- out("Nothing to read here yet.");
16320
- const sentence2 = opts.sentence ?? await askSentence(out);
16379
+ out2("Nothing to read here yet.");
16380
+ const sentence2 = opts.sentence ?? await askSentence(out2);
16321
16381
  if (!sentence2) throw new Error('nothing to read here yet: say what you are building with nanopm start "<sentence>", in this folder, so the repository is recorded');
16322
16382
  return startProject(startSentence(sentence2), { ...opts, repo, existing: project ?? void 0 });
16323
16383
  }
@@ -16326,9 +16386,9 @@ async function onboard(opts) {
16326
16386
  const { data, error: error2 } = await auth.client.from("projects").insert({ name, slug: slugify(name), repo_remote: repo.remote, created_by: auth.userId }).select("id, name, slug").single();
16327
16387
  if (error2 || !data) throw new Error(`could not create the project: ${error2?.message}`);
16328
16388
  project = data;
16329
- out(`created project ${project.name} (${project.id})`);
16389
+ out2(`created project ${project.name} (${project.id})`);
16330
16390
  } else {
16331
- out(`project ${project.name} (${project.id}) exists; refreshing its memory`);
16391
+ out2(`project ${project.name} (${project.id}) exists; refreshing its memory`);
16332
16392
  }
16333
16393
  await auth.client.from("project_members").update({ daemon_id: daemonId }).eq("project_id", project.id).eq("user_id", auth.userId);
16334
16394
  await rememberClone(auth.client, project.id, repo.root);
@@ -16352,18 +16412,18 @@ async function onboard(opts) {
16352
16412
  target_daemon_id: daemonId
16353
16413
  }).select("id").single();
16354
16414
  if (error || !job) throw new Error(`could not queue onboarding: ${error?.message}`);
16355
- out(`queued onboard as job ${job.id}`);
16415
+ out2(`queued onboard as job ${job.id}`);
16356
16416
  const link = newProjectUrl(job.id);
16357
16417
  if (!opts.scanOnly) {
16358
- out(`Follow along, then confirm what I read at ${link}`);
16418
+ out2(`Follow along, then confirm what I read at ${link}`);
16359
16419
  if (!opts.noBrowser) openBrowser(link);
16360
16420
  }
16361
- const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out);
16362
- const exit = await streamJob(auth.client, job.id, { pollMs: 1e3, out, renderer, cancelSignal: opts.cancelSignal, answerer, link, verbose: verboseWanted() });
16363
- if (!opts.scanOnly) for (const line of closingLines(project, exit)) out(line);
16421
+ const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out2);
16422
+ const exit = await streamJob(auth.client, job.id, { pollMs: 1e3, out: out2, renderer, cancelSignal: opts.cancelSignal, answerer, link, verbose: verboseWanted() });
16423
+ if (!opts.scanOnly) for (const line of closingLines(project, exit)) out2(line);
16364
16424
  return exit;
16365
16425
  }
16366
- async function attach(client, projectId, repo, out) {
16426
+ async function attach(client, projectId, repo, out2) {
16367
16427
  const { data: project } = await client.from("projects").select("id, name, slug, repo_remote").eq("id", projectId).maybeSingle();
16368
16428
  if (!project) throw new Error(`project ${projectId} not found (or you are not a member)`);
16369
16429
  if (repo.remote && project.repo_remote && project.repo_remote !== repo.remote) {
@@ -16374,7 +16434,7 @@ async function attach(client, projectId, repo, out) {
16374
16434
  if (taken) throw new Error(`${repo.remote} is already the repository of ${taken.name}`);
16375
16435
  const { data: updated } = await client.from("projects").update({ repo_remote: repo.remote }).eq("id", project.id).select("id").maybeSingle();
16376
16436
  if (!updated) throw new Error(`only the owner of ${project.name} can attach its repository`);
16377
- out(`${project.name} now lives at ${repo.remote}`);
16437
+ out2(`${project.name} now lives at ${repo.remote}`);
16378
16438
  }
16379
16439
  return { id: project.id, name: project.name, slug: project.slug };
16380
16440
  }
@@ -16414,7 +16474,7 @@ async function want(sentence2, opts) {
16414
16474
 
16415
16475
  // src/commands/resume.ts
16416
16476
  async function resume(jobId, opts) {
16417
- const { renderer, out } = rendererFor(opts.out);
16477
+ const { renderer, out: out2 } = rendererFor(opts.out);
16418
16478
  const auth = await authenticate();
16419
16479
  const { data: original } = await auth.client.from("jobs").select("id, project_id, skill, params, status, attempt, harness_session_id, error").eq("id", jobId).maybeSingle();
16420
16480
  if (!original) throw new Error(`job ${jobId} not found`);
@@ -16432,9 +16492,9 @@ async function resume(jobId, opts) {
16432
16492
  }).select("id").single();
16433
16493
  if (error || !job) throw new Error(`could not queue the resume: ${error?.message}`);
16434
16494
  const code = original.error?.code ?? "failed";
16435
- out(`resuming ${original.skill} (${code}) as job ${job.id}, attempt ${original.attempt + 1}`);
16436
- const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out);
16437
- return streamJob(auth.client, job.id, { pollMs: 1e3, out, renderer, cancelSignal: opts.cancelSignal, answerer, verbose: verboseWanted() });
16495
+ out2(`resuming ${original.skill} (${code}) as job ${job.id}, attempt ${original.attempt + 1}`);
16496
+ const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(out2);
16497
+ return streamJob(auth.client, job.id, { pollMs: 1e3, out: out2, renderer, cancelSignal: opts.cancelSignal, answerer, verbose: verboseWanted() });
16438
16498
  }
16439
16499
 
16440
16500
  // src/commands/connect.ts
@@ -16485,7 +16545,7 @@ async function connect2(opts) {
16485
16545
  const paint = painter(color);
16486
16546
  const auth = await authenticate();
16487
16547
  const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
16488
- const out = [];
16548
+ const out2 = [];
16489
16549
  const index = { i: 0 };
16490
16550
  let url = opts.url;
16491
16551
  let label = opts.label;
@@ -16498,31 +16558,31 @@ async function connect2(opts) {
16498
16558
  if (!url) {
16499
16559
  const databases = found.filter((f) => f.kind === "postgres");
16500
16560
  const analytics = found.filter((f) => f.kind !== "postgres");
16501
- out.push(whereItGoes(color));
16561
+ out2.push(whereItGoes(color));
16502
16562
  if (databases.length) {
16503
16563
  const first = databases[0];
16504
- out.push(`Found ${paint(ANSI.bold, first.from)} in ${first.file}:`);
16505
- out.push(` ${mask(first.value)}`);
16506
- out.push("");
16507
- out.push(paint(ANSI.dim, "That is probably the owner role, which this command will refuse. Paste the"));
16508
- out.push(paint(ANSI.dim, "read-only one instead, or press enter to try this one and see."));
16509
- out.push("");
16510
- process.stdout.write(`${out.join("\n")}
16564
+ out2.push(`Found ${paint(ANSI.bold, first.from)} in ${first.file}:`);
16565
+ out2.push(` ${mask(first.value)}`);
16566
+ out2.push("");
16567
+ out2.push(paint(ANSI.dim, "That is probably the owner role, which this command will refuse. Paste the"));
16568
+ out2.push(paint(ANSI.dim, "read-only one instead, or press enter to try this one and see."));
16569
+ out2.push("");
16570
+ process.stdout.write(`${out2.join("\n")}
16511
16571
  `);
16512
- out.length = 0;
16572
+ out2.length = 0;
16513
16573
  const answer = await ask5("Connection string (enter to use the one above): ", opts.answers, index);
16514
16574
  url = answer || first.value;
16515
16575
  } else {
16516
- process.stdout.write(`${out.join("\n")}
16576
+ process.stdout.write(`${out2.join("\n")}
16517
16577
  `);
16518
- out.length = 0;
16578
+ out2.length = 0;
16519
16579
  url = await ask5("Read-only Postgres connection string: ", opts.answers, index);
16520
16580
  }
16521
16581
  if (analytics.length) {
16522
16582
  const kinds = new Set(analytics.map((a) => a.kind));
16523
- if (kinds.has("posthog")) out.push(paint(ANSI.dim, "Also saw posthog in your env files. `nanopm connect --kind posthog` reads it."));
16524
- if (kinds.has("amplitude")) out.push(paint(ANSI.dim, "Also saw amplitude in your env files. NanoPM cannot read that yet."));
16525
- out.push("");
16583
+ if (kinds.has("posthog")) out2.push(paint(ANSI.dim, "Also saw posthog in your env files. `nanopm connect --kind posthog` reads it."));
16584
+ if (kinds.has("amplitude")) out2.push(paint(ANSI.dim, "Also saw amplitude in your env files. NanoPM cannot read that yet."));
16585
+ out2.push("");
16526
16586
  }
16527
16587
  }
16528
16588
  if (!url) throw new Error("nothing to connect. Run it again with a read-only connection string.");
@@ -16548,18 +16608,18 @@ ${refusalForWritableRole(url, color)}`);
16548
16608
  });
16549
16609
  if (error) throw new Error(`could not store the connection: ${pgMessage(error)}`);
16550
16610
  await auth.client.rpc("set_connection_status", { p_connection_id: connectionId, p_status: "ok" });
16551
- out.push(paint(ANSI.green, `\u2713 connected ${label}`));
16552
- out.push(paint(ANSI.dim, ` ${test.serverVersion ?? "postgres"} \xB7 read-only \xB7 ${describeUrl(url)}`));
16553
- out.push("");
16611
+ out2.push(paint(ANSI.green, `\u2713 connected ${label}`));
16612
+ out2.push(paint(ANSI.dim, ` ${test.serverVersion ?? "postgres"} \xB7 read-only \xB7 ${describeUrl(url)}`));
16613
+ out2.push("");
16554
16614
  if (test.blindness) {
16555
16615
  const role7 = parseConnectionUrl(url)?.user.split(".")[0] ?? "the role";
16556
- out.push(paint(ANSI.amber, test.blindness.replace("${the role}", role7)));
16557
- out.push("");
16558
- out.push(paint(ANSI.dim, " nanopm review will run, and will have almost nothing to read."));
16559
- out.push("");
16616
+ out2.push(paint(ANSI.amber, test.blindness.replace("${the role}", role7)));
16617
+ out2.push("");
16618
+ out2.push(paint(ANSI.dim, " nanopm review will run, and will have almost nothing to read."));
16619
+ out2.push("");
16560
16620
  }
16561
- out.push(paint(ANSI.dim, " nanopm review \xB7 nanopm connect --list"));
16562
- return out.join("\n");
16621
+ out2.push(paint(ANSI.dim, " nanopm review \xB7 nanopm connect --list"));
16622
+ return out2.join("\n");
16563
16623
  }
16564
16624
  function soleKind(found) {
16565
16625
  const kinds = [...new Set(found.map((f) => f.kind))];
@@ -16691,18 +16751,18 @@ async function sources(opts) {
16691
16751
  ok: { sign: "\u25CF", colour: ANSI.green },
16692
16752
  failed: { sign: "\u2298", colour: ANSI.red }
16693
16753
  };
16694
- const out = [];
16754
+ const out2 = [];
16695
16755
  for (const s of rows2) {
16696
16756
  const m = mark2[s.status] ?? { sign: "\xB7", colour: ANSI.dim };
16697
16757
  const watches2 = watchesOf(s);
16698
- out.push(`${paint(m.colour, m.sign)} ${paint(ANSI.bold, s.label)} ${paint(ANSI.dim, `${s.kind.replace("_watch", "")} \xB7 ${watches2.length} watch${watches2.length === 1 ? "" : "es"}`)}`);
16699
- out.push(paint(ANSI.dim, ` ${describeCadence(s)} ${s.timezone} \xB7 next ${describeNextRun(s, /* @__PURE__ */ new Date(), { timezone: false })}`));
16700
- out.push(paint(ANSI.dim, ` last run ${ago(s.last_run_at)}${s.last_run_at ? ` \xB7 ${describeStats(s.last_run_stats)}` : ""}`));
16701
- if (s.status === "failed" && s.status_detail) out.push(paint(ANSI.red, ` ${s.status_detail}`));
16758
+ out2.push(`${paint(m.colour, m.sign)} ${paint(ANSI.bold, s.label)} ${paint(ANSI.dim, `${s.kind.replace("_watch", "")} \xB7 ${watches2.length} watch${watches2.length === 1 ? "" : "es"}`)}`);
16759
+ out2.push(paint(ANSI.dim, ` ${describeCadence(s)} ${s.timezone} \xB7 next ${describeNextRun(s, /* @__PURE__ */ new Date(), { timezone: false })}`));
16760
+ out2.push(paint(ANSI.dim, ` last run ${ago(s.last_run_at)}${s.last_run_at ? ` \xB7 ${describeStats(s.last_run_stats)}` : ""}`));
16761
+ if (s.status === "failed" && s.status_detail) out2.push(paint(ANSI.red, ` ${s.status_detail}`));
16702
16762
  }
16703
- out.push("");
16704
- out.push(paint(ANSI.dim, " nanopm watch \xB7 nanopm source run <label> \xB7 nanopm evidence"));
16705
- return out.join("\n");
16763
+ out2.push("");
16764
+ out2.push(paint(ANSI.dim, " nanopm watch \xB7 nanopm source run <label> \xB7 nanopm evidence"));
16765
+ return out2.join("\n");
16706
16766
  }
16707
16767
  function describeStats(stats) {
16708
16768
  const s = stats ?? {};
@@ -16737,7 +16797,7 @@ async function addSource(kindArg, opts) {
16737
16797
  const index = { i: 0 };
16738
16798
  const connection = await pickConnection(auth.client, project.id, spec2.connection, opts, index);
16739
16799
  const label = opts.label || `${spec2.label} watches`;
16740
- const out = [];
16800
+ const out2 = [];
16741
16801
  let watches2 = [];
16742
16802
  if (opts.fromReview) {
16743
16803
  const rows2 = await lastReviewMetrics(auth.client, project.id);
@@ -16754,15 +16814,15 @@ async function addSource(kindArg, opts) {
16754
16814
  const tried = await seedWatches(seeded, read6);
16755
16815
  watches2 = tried.kept;
16756
16816
  for (const skip of tried.skipped) {
16757
- out.push(paint(ANSI.amber, `skipped ${skip.watch.id}: ${skip.why}`));
16758
- out.push(paint(ANSI.dim, ` ${clipQuery(skip.watch.query)}`));
16817
+ out2.push(paint(ANSI.amber, `skipped ${skip.watch.id}: ${skip.why}`));
16818
+ out2.push(paint(ANSI.dim, ` ${clipQuery(skip.watch.query)}`));
16759
16819
  }
16760
16820
  if (tried.skipped.length) {
16761
- out.push(paint(ANSI.dim, " A watch reads one row with one number. Name it: `select count(*) \u2026 as value`."));
16762
- out.push("");
16821
+ out2.push(paint(ANSI.dim, " A watch reads one row with one number. Name it: `select count(*) \u2026 as value`."));
16822
+ out2.push("");
16763
16823
  }
16764
16824
  if (!watches2.length) {
16765
- throw new Error([...out, "None of those queries returns one number, so there is nothing to watch yet."].join("\n"));
16825
+ throw new Error([...out2, "None of those queries returns one number, so there is nothing to watch yet."].join("\n"));
16766
16826
  }
16767
16827
  }
16768
16828
  const { error } = await auth.client.rpc("put_source", {
@@ -16777,28 +16837,28 @@ async function addSource(kindArg, opts) {
16777
16837
  p_timezone: timezone
16778
16838
  });
16779
16839
  if (error) throw new Error(`could not store the source: ${pgMessage(error)}`);
16780
- out.push(paint(ANSI.green, `\u2713 watching ${label}`));
16781
- out.push(paint(ANSI.dim, ` ${connection.label} \xB7 ${watches2.length} watch${watches2.length === 1 ? "" : "es"} \xB7 ${describeCadence({ cadence, day_of_week: day2 ?? null, hour, timezone, next_due_at: (/* @__PURE__ */ new Date()).toISOString() })} ${timezone}`));
16782
- out.push("");
16783
- out.push("The first run writes down where every number stands and raises nothing. It takes a");
16784
- out.push("second reading for a rule to have something to compare against.");
16785
- out.push("");
16786
- out.push(paint(ANSI.dim, ` nanopm source run ${label} \xB7 nanopm watch ${label} \xB7 nanopm watch add ${label} <id> --query "\u2026"`));
16787
- return out.join("\n");
16840
+ out2.push(paint(ANSI.green, `\u2713 watching ${label}`));
16841
+ out2.push(paint(ANSI.dim, ` ${connection.label} \xB7 ${watches2.length} watch${watches2.length === 1 ? "" : "es"} \xB7 ${describeCadence({ cadence, day_of_week: day2 ?? null, hour, timezone, next_due_at: (/* @__PURE__ */ new Date()).toISOString() })} ${timezone}`));
16842
+ out2.push("");
16843
+ out2.push("The first run writes down where every number stands and raises nothing. It takes a");
16844
+ out2.push("second reading for a rule to have something to compare against.");
16845
+ out2.push("");
16846
+ out2.push(paint(ANSI.dim, ` nanopm source run ${label} \xB7 nanopm watch ${label} \xB7 nanopm watch add ${label} <id> --query "\u2026"`));
16847
+ return out2.join("\n");
16788
16848
  }
16789
16849
  async function runSource(labelOrId, opts) {
16790
- const { renderer, out } = rendererFor(opts.out);
16850
+ const { renderer, out: out2 } = rendererFor(opts.out);
16791
16851
  const auth = await authenticate();
16792
16852
  const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
16793
16853
  const source = findSource(await liveSources(auth.client, project.id), labelOrId);
16794
16854
  const { data: jobId, error } = await auth.client.rpc("enqueue_source_run", { p_source_id: source.id, p_trigger: "user" });
16795
16855
  if (error) throw new Error(`could not queue the run: ${pgMessage(error)}`);
16796
16856
  if (!jobId) {
16797
- out(`the map is being worked out, so ${source.label} was not read. It stays due; it runs when the map lands.`);
16857
+ out2(`the map is being worked out, so ${source.label} was not read. It stays due; it runs when the map lands.`);
16798
16858
  return 0;
16799
16859
  }
16800
- out(`reading ${source.label} as job ${jobId}`);
16801
- return streamJob(auth.client, jobId, { pollMs: 1e3, out, renderer, cancelSignal: opts.cancelSignal, verbose: verboseWanted() });
16860
+ out2(`reading ${source.label} as job ${jobId}`);
16861
+ return streamJob(auth.client, jobId, { pollMs: 1e3, out: out2, renderer, cancelSignal: opts.cancelSignal, verbose: verboseWanted() });
16802
16862
  }
16803
16863
  async function removeSource(labelOrId, opts) {
16804
16864
  const auth = await authenticate();
@@ -16960,13 +17020,13 @@ async function watches(labelOrId, opts) {
16960
17020
  paint(ANSI.dim, " nanopm source add postgres --from-review")
16961
17021
  ].join("\n");
16962
17022
  }
16963
- const out = [];
17023
+ const out2 = [];
16964
17024
  for (const source of rows2) {
16965
17025
  const list = watchesOf(source);
16966
- out.push(paint(ANSI.bold, source.label));
17026
+ out2.push(paint(ANSI.bold, source.label));
16967
17027
  if (!list.length) {
16968
- out.push(paint(ANSI.dim, ` nothing watched yet \xB7 nanopm watch add ${source.label} <id> --query "\u2026"`));
16969
- out.push("");
17028
+ out2.push(paint(ANSI.dim, ` nothing watched yet \xB7 nanopm watch add ${source.label} <id> --query "\u2026"`));
17029
+ out2.push("");
16970
17030
  continue;
16971
17031
  }
16972
17032
  const width = Math.max(...list.map((w) => w.id.length));
@@ -16975,13 +17035,13 @@ async function watches(labelOrId, opts) {
16975
17035
  const last = state.observations?.[state.observations.length - 1];
16976
17036
  const open = state.open?.since ? paint(ANSI.amber, ` open since ${ago(state.open.since)}`) : "";
16977
17037
  const reading = last?.value === void 0 ? paint(ANSI.dim, "not read yet") : `${last.value}${watch.unit ? ` ${watch.unit}` : ""}${last.at ? paint(ANSI.dim, ` (${ago(last.at)})`) : ""}`;
16978
- out.push(` ${paint(ANSI.dim, watch.id.padEnd(width))} ${watch.label}`);
16979
- out.push(` ${" ".repeat(width)} ${paint(ANSI.dim, describeRule(watch.rule))} \xB7 ${reading}${open}`);
17038
+ out2.push(` ${paint(ANSI.dim, watch.id.padEnd(width))} ${watch.label}`);
17039
+ out2.push(` ${" ".repeat(width)} ${paint(ANSI.dim, describeRule(watch.rule))} \xB7 ${reading}${open}`);
16980
17040
  }
16981
- out.push("");
17041
+ out2.push("");
16982
17042
  }
16983
- out.push(paint(ANSI.dim, ' nanopm watch add <source> <id> --query "\u2026" \xB7 nanopm watch remove <source> <id>'));
16984
- return out.join("\n");
17043
+ out2.push(paint(ANSI.dim, ' nanopm watch add <source> <id> --query "\u2026" \xB7 nanopm watch remove <source> <id>'));
17044
+ return out2.join("\n");
16985
17045
  }
16986
17046
  function watchInput(id, query, opts) {
16987
17047
  const text2 = (query ?? "").trim();
@@ -17106,16 +17166,16 @@ async function evidence(opts) {
17106
17166
  paint(ANSI.dim, sources2.length ? " nanopm source \xB7 nanopm watch" : " nanopm source add postgres --from-review")
17107
17167
  ].join("\n");
17108
17168
  }
17109
- const out = [];
17169
+ const out2 = [];
17110
17170
  for (const row of rows2) {
17111
17171
  const when = (row.occurred_at ?? row.created_at).slice(0, 10);
17112
17172
  const where = row.source_id ? labelById.get(row.source_id) ?? "a removed feed" : "";
17113
- out.push(`${paint(ANSI.dim, when)} ${bars(row.strength, color)} ${clip(row.body, 72)}`);
17114
- out.push(paint(ANSI.dim, ` ${row.kind}${where ? ` \xB7 ${where}` : ""}${row.source_ref ? ` \xB7 ${row.source_ref}` : ""}`));
17173
+ out2.push(`${paint(ANSI.dim, when)} ${bars(row.strength, color)} ${clip(row.body, 72)}`);
17174
+ out2.push(paint(ANSI.dim, ` ${row.kind}${where ? ` \xB7 ${where}` : ""}${row.source_ref ? ` \xB7 ${row.source_ref}` : ""}`));
17115
17175
  }
17116
- out.push("");
17117
- out.push(paint(ANSI.dim, ` ${rows2.length} of the newest first \xB7 nanopm evidence --unclaimed \xB7 nanopm problems`));
17118
- return out.join("\n");
17176
+ out2.push("");
17177
+ out2.push(paint(ANSI.dim, ` ${rows2.length} of the newest first \xB7 nanopm evidence --unclaimed \xB7 nanopm problems`));
17178
+ return out2.join("\n");
17119
17179
  }
17120
17180
  function bars(strength, color) {
17121
17181
  const paint = painter(color);
@@ -17155,25 +17215,25 @@ function renderReview(review2, metrics2, opts) {
17155
17215
  if (!review2) {
17156
17216
  return ["No review yet.", "", paint(ANSI.dim, " nanopm connect \xB7 nanopm review")].join("\n");
17157
17217
  }
17158
- const out = [];
17218
+ const out2 = [];
17159
17219
  const width = columns();
17160
- out.push(paint(ANSI.dim, `Reviewed ${review2.created_at.slice(0, 10)}`));
17161
- out.push("");
17162
- for (const line of wrap(review2.summary, width)) out.push(line);
17163
- out.push("");
17220
+ out2.push(paint(ANSI.dim, `Reviewed ${review2.created_at.slice(0, 10)}`));
17221
+ out2.push("");
17222
+ for (const line of wrap(review2.summary, width)) out2.push(line);
17223
+ out2.push("");
17164
17224
  if (metrics2.length) {
17165
- out.push(renderMetrics(metrics2, opts).split("\n\n")[0]);
17166
- out.push("");
17225
+ out2.push(renderMetrics(metrics2, opts).split("\n\n")[0]);
17226
+ out2.push("");
17167
17227
  }
17168
17228
  for (const item of review2.confirms) {
17169
- out.push(`${paint(ANSI.green, "\u2713")} ${wrap(item, width - 2).join("\n ")}`);
17229
+ out2.push(`${paint(ANSI.green, "\u2713")} ${wrap(item, width - 2).join("\n ")}`);
17170
17230
  }
17171
17231
  for (const item of review2.contradicts) {
17172
- out.push(`${paint(ANSI.amber, "\u2717")} ${wrap(item, width - 2).join("\n ")}`);
17232
+ out2.push(`${paint(ANSI.amber, "\u2717")} ${wrap(item, width - 2).join("\n ")}`);
17173
17233
  }
17174
- if (review2.confirms.length || review2.contradicts.length) out.push("");
17175
- out.push(paint(ANSI.dim, " nanopm roadmap \xB7 nanopm metrics \xB7 nanopm log"));
17176
- return out.join("\n");
17234
+ if (review2.confirms.length || review2.contradicts.length) out2.push("");
17235
+ out2.push(paint(ANSI.dim, " nanopm roadmap \xB7 nanopm metrics \xB7 nanopm log"));
17236
+ return out2.join("\n");
17177
17237
  }
17178
17238
 
17179
17239
  // src/commands/review.ts
@@ -17246,13 +17306,13 @@ async function problems(ref, opts) {
17246
17306
  const auth = await authenticate();
17247
17307
  const color = opts.color ?? useColor();
17248
17308
  if (opts.all) {
17249
- const out = [];
17309
+ const out2 = [];
17250
17310
  for (const project2 of await memberProjects(auth.client, auth.userId, opts.cwd)) {
17251
- out.push(painter(color)(ANSI.bold, project2.name));
17252
- out.push(await problemList(auth.client, project2.id, color));
17253
- out.push("");
17311
+ out2.push(painter(color)(ANSI.bold, project2.name));
17312
+ out2.push(await problemList(auth.client, project2.id, color));
17313
+ out2.push("");
17254
17314
  }
17255
- return out.join("\n").trimEnd();
17315
+ return out2.join("\n").trimEnd();
17256
17316
  }
17257
17317
  const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
17258
17318
  if (!ref) return problemList(auth.client, project.id, color);
@@ -17449,76 +17509,76 @@ function renderMarket(rows2, opts) {
17449
17509
  const rest = rows2.filter((r) => r.kind !== "competitor");
17450
17510
  const refWidth = Math.max(...rows2.map((r) => r.ref.length));
17451
17511
  const width = columns();
17452
- const out = [];
17512
+ const out2 = [];
17453
17513
  for (const c of competitors) {
17454
17514
  const mark2 = c.confirmed ? paint(ANSI.green, "\u25CF") : paint(ANSI.amber, "\u25CB");
17455
17515
  const name = c.competitor ?? c.domain ?? c.ref;
17456
- out.push(`${mark2} ${paint(ANSI.dim, c.ref.padEnd(refWidth))} ${paint(ANSI.bold, name)}${c.domain ? paint(ANSI.dim, ` ${c.domain}`) : ""}`);
17516
+ out2.push(`${mark2} ${paint(ANSI.dim, c.ref.padEnd(refWidth))} ${paint(ANSI.bold, name)}${c.domain ? paint(ANSI.dim, ` ${c.domain}`) : ""}`);
17457
17517
  const indent = ` ${" ".repeat(refWidth)} `;
17458
- out.push(`${indent}${clip(firstSentence(c.statement), Math.max(40, width - indent.length))}`);
17459
- out.push(
17518
+ out2.push(`${indent}${clip(firstSentence(c.statement), Math.max(40, width - indent.length))}`);
17519
+ out2.push(
17460
17520
  paint(
17461
17521
  ANSI.dim,
17462
17522
  indent + (c.confirmed ? `confirmed${c.provenance === "stated" ? " \xB7 you added them" : ""}` : `not confirmed \xB7 nanopm market confirm ${c.ref}`)
17463
17523
  )
17464
17524
  );
17465
17525
  for (const r of rest.filter((r2) => sameCompetitor(r2, c))) {
17466
- out.push(paint(ANSI.dim, `${indent}${r.ref.padEnd(refWidth)} ${KIND_LABEL[r.kind] ?? r.kind} \xB7 ${dateOf(r)}`));
17467
- out.push(`${indent}${" ".repeat(refWidth + 1)}${clip(firstSentence(r.statement), Math.max(40, width - indent.length - refWidth - 1))}`);
17526
+ out2.push(paint(ANSI.dim, `${indent}${r.ref.padEnd(refWidth)} ${KIND_LABEL[r.kind] ?? r.kind} \xB7 ${dateOf(r)}`));
17527
+ out2.push(`${indent}${" ".repeat(refWidth + 1)}${clip(firstSentence(r.statement), Math.max(40, width - indent.length - refWidth - 1))}`);
17468
17528
  }
17469
- out.push("");
17529
+ out2.push("");
17470
17530
  }
17471
17531
  const unmatched = rest.filter((r) => !competitors.some((c) => sameCompetitor(r, c)));
17472
17532
  if (unmatched.length) {
17473
17533
  for (const r of unmatched) {
17474
- out.push(`${paint(ANSI.dim, "\xB7")} ${paint(ANSI.dim, r.ref.padEnd(refWidth))} ${paint(ANSI.dim, `${KIND_LABEL[r.kind] ?? r.kind}${r.competitor ? ` \xB7 ${r.competitor}` : ""} \xB7 ${dateOf(r)}`)}`);
17475
- out.push(` ${" ".repeat(refWidth)} ${clip(firstSentence(r.statement), Math.max(40, width - refWidth - 4))}`);
17534
+ out2.push(`${paint(ANSI.dim, "\xB7")} ${paint(ANSI.dim, r.ref.padEnd(refWidth))} ${paint(ANSI.dim, `${KIND_LABEL[r.kind] ?? r.kind}${r.competitor ? ` \xB7 ${r.competitor}` : ""} \xB7 ${dateOf(r)}`)}`);
17535
+ out2.push(` ${" ".repeat(refWidth)} ${clip(firstSentence(r.statement), Math.max(40, width - refWidth - 4))}`);
17476
17536
  }
17477
- out.push("");
17537
+ out2.push("");
17478
17538
  }
17479
17539
  const unconfirmed = competitors.filter((c) => !c.confirmed).length;
17480
17540
  if (unconfirmed) {
17481
- out.push(
17541
+ out2.push(
17482
17542
  paint(
17483
17543
  ANSI.dim,
17484
17544
  `${unconfirmed} of ${competitors.length} competitor${competitors.length === 1 ? "" : "s"} not confirmed.`
17485
17545
  )
17486
17546
  );
17487
17547
  }
17488
- out.push("");
17489
- out.push(paint(ANSI.dim, " nanopm market <ref> \xB7 nanopm market confirm <ref> \xB7 nanopm market add <name> <domain> \xB7 nanopm market --refresh"));
17490
- return out.join("\n");
17548
+ out2.push("");
17549
+ out2.push(paint(ANSI.dim, " nanopm market <ref> \xB7 nanopm market confirm <ref> \xB7 nanopm market add <name> <domain> \xB7 nanopm market --refresh"));
17550
+ return out2.join("\n");
17491
17551
  }
17492
17552
  function renderMarketEntry(row, related, opts) {
17493
17553
  const paint = painter(opts.color);
17494
17554
  const width = columns();
17495
- const out = [];
17496
- out.push(`${paint(ANSI.dim, row.ref)} ${paint(ANSI.bold, KIND_LABEL[row.kind] ?? row.kind)}${row.competitor ? ` \xB7 ${row.competitor}` : ""}`);
17497
- out.push("");
17498
- for (const line of wrap(row.statement, width)) out.push(line);
17499
- out.push("");
17555
+ const out2 = [];
17556
+ out2.push(`${paint(ANSI.dim, row.ref)} ${paint(ANSI.bold, KIND_LABEL[row.kind] ?? row.kind)}${row.competitor ? ` \xB7 ${row.competitor}` : ""}`);
17557
+ out2.push("");
17558
+ for (const line of wrap(row.statement, width)) out2.push(line);
17559
+ out2.push("");
17500
17560
  const facts = [];
17501
17561
  if (row.domain) facts.push(["site", row.domain]);
17502
17562
  facts.push(["from", row.provenance === "stated" ? "you" : "the web"]);
17503
17563
  if (row.kind === "competitor") {
17504
17564
  facts.push(["confirmed", row.confirmed ? "yes" : `no \xB7 nanopm market confirm ${row.ref}`]);
17505
17565
  }
17506
- for (const [k, v] of facts) out.push(`${paint(ANSI.dim, k.padEnd(10))} ${v}`);
17566
+ for (const [k, v] of facts) out2.push(`${paint(ANSI.dim, k.padEnd(10))} ${v}`);
17507
17567
  const pages = row.sources.filter((s) => s.kind === "web");
17508
17568
  if (pages.length) {
17509
- out.push("");
17510
- out.push(paint(ANSI.dim, pages.length === 1 ? "Page" : "Pages"));
17511
- for (const s of pages) out.push(` ${s.ref}${s.note ? paint(ANSI.dim, ` ${s.note}`) : ""}`);
17569
+ out2.push("");
17570
+ out2.push(paint(ANSI.dim, pages.length === 1 ? "Page" : "Pages"));
17571
+ for (const s of pages) out2.push(` ${s.ref}${s.note ? paint(ANSI.dim, ` ${s.note}`) : ""}`);
17512
17572
  }
17513
17573
  if (related.length) {
17514
- out.push("");
17515
- out.push(paint(ANSI.dim, `Also about ${row.competitor}`));
17574
+ out2.push("");
17575
+ out2.push(paint(ANSI.dim, `Also about ${row.competitor}`));
17516
17576
  for (const r of related) {
17517
- out.push(` ${paint(ANSI.dim, r.ref)} ${paint(ANSI.dim, `${KIND_LABEL[r.kind] ?? r.kind} \xB7 ${dateOf(r)}`)}`);
17518
- for (const line of wrap(r.statement, width - 4)) out.push(` ${line}`);
17577
+ out2.push(` ${paint(ANSI.dim, r.ref)} ${paint(ANSI.dim, `${KIND_LABEL[r.kind] ?? r.kind} \xB7 ${dateOf(r)}`)}`);
17578
+ for (const line of wrap(r.statement, width - 4)) out2.push(` ${line}`);
17519
17579
  }
17520
17580
  }
17521
- return out.join("\n");
17581
+ return out2.join("\n");
17522
17582
  }
17523
17583
 
17524
17584
  // src/commands/search.ts
@@ -17543,27 +17603,27 @@ async function showSearch(opts) {
17543
17603
  const paint = painter(opts.color ?? useColor());
17544
17604
  const row = await searchConnection(auth.client, project.id);
17545
17605
  const local = await localWebSearch();
17546
- const out = [describeSearch(row, local), ""];
17606
+ const out2 = [describeSearch(row, local), ""];
17547
17607
  if (isClaudeMode(row)) {
17548
- out.push(paint(ANSI.dim, " Claude Code's own search and fetch, on your subscription. The allowlist and the hourly"));
17549
- out.push(paint(ANSI.dim, " cap do not apply; every search and fetch is journalled."));
17550
- out.push(paint(ANSI.dim, " nanopm search default go back to DuckDuckGo"));
17608
+ out2.push(paint(ANSI.dim, " Claude Code's own search and fetch, on your subscription. The allowlist and the hourly"));
17609
+ out2.push(paint(ANSI.dim, " cap do not apply; every search and fetch is journalled."));
17610
+ out2.push(paint(ANSI.dim, " nanopm search default go back to DuckDuckGo"));
17551
17611
  } else if (row) {
17552
- out.push(paint(ANSI.dim, " nanopm search default go back to DuckDuckGo; the key is deleted"));
17553
- out.push(paint(ANSI.dim, " nanopm search claude Claude Code's own search instead, no key"));
17612
+ out2.push(paint(ANSI.dim, " nanopm search default go back to DuckDuckGo; the key is deleted"));
17613
+ out2.push(paint(ANSI.dim, " nanopm search claude Claude Code's own search instead, no key"));
17554
17614
  } else if (local) {
17555
- out.push(paint(ANSI.dim, " No key, no account: Claude Code's own search, on the subscription you already have."));
17556
- out.push(paint(ANSI.dim, " The allowlist and the hourly cap do not apply; every search and fetch is journalled."));
17557
- out.push(paint(ANSI.dim, " A run claimed by a machine without it falls back to DuckDuckGo."));
17558
- out.push(paint(ANSI.dim, ` nanopm search ${SEARCH_PROVIDERS.join("|")} <key> bring your own key instead`));
17559
- out.push(paint(ANSI.dim, " nanopm search duckduckgo use DuckDuckGo instead"));
17615
+ out2.push(paint(ANSI.dim, " No key, no account: Claude Code's own search, on the subscription you already have."));
17616
+ out2.push(paint(ANSI.dim, " The allowlist and the hourly cap do not apply; every search and fetch is journalled."));
17617
+ out2.push(paint(ANSI.dim, " A run claimed by a machine without it falls back to DuckDuckGo."));
17618
+ out2.push(paint(ANSI.dim, ` nanopm search ${SEARCH_PROVIDERS.join("|")} <key> bring your own key instead`));
17619
+ out2.push(paint(ANSI.dim, " nanopm search duckduckgo use DuckDuckGo instead"));
17560
17620
  } else {
17561
- out.push(paint(ANSI.dim, " No key, no account. Queries leave this machine directly, capped and journalled."));
17562
- out.push(paint(ANSI.dim, " Claude's own search would be the default, but it did not answer on this machine."));
17563
- out.push(paint(ANSI.dim, ` nanopm search ${SEARCH_PROVIDERS.join("|")} <key> bring your own key for a better search`));
17564
- out.push(paint(ANSI.dim, " nanopm search claude Claude Code's own search and fetch, on your subscription"));
17621
+ out2.push(paint(ANSI.dim, " No key, no account. Queries leave this machine directly, capped and journalled."));
17622
+ out2.push(paint(ANSI.dim, " Claude's own search would be the default, but it did not answer on this machine."));
17623
+ out2.push(paint(ANSI.dim, ` nanopm search ${SEARCH_PROVIDERS.join("|")} <key> bring your own key for a better search`));
17624
+ out2.push(paint(ANSI.dim, " nanopm search claude Claude Code's own search and fetch, on your subscription"));
17565
17625
  }
17566
- return out.join("\n");
17626
+ return out2.join("\n");
17567
17627
  }
17568
17628
  async function setClaudeSearch(opts) {
17569
17629
  const auth = await authenticate();
@@ -17771,34 +17831,34 @@ function render(rows2) {
17771
17831
  if (people2.length === 0) return "Nobody yet. `nanopm personas --refresh` works out who this is for from your docs, your pricing and what the product says about itself.";
17772
17832
  const needs2 = rows2.filter((r) => r.topic === NEEDS_TOPIC);
17773
17833
  const taken = /* @__PURE__ */ new Set();
17774
- const out = [];
17834
+ const out2 = [];
17775
17835
  for (const row of people2) {
17776
17836
  const p = parsePersona(row.key, row.content);
17777
17837
  const mark2 = row.provenance === "stated" ? "" : ` \xB7 to review${row.confidence != null ? ` (${row.confidence}% sure)` : ""}`;
17778
- out.push(`${row.title ?? p.slug}${mark2}`);
17779
- out.push(` ${p.who}`);
17838
+ out2.push(`${row.title ?? p.slug}${mark2}`);
17839
+ out2.push(` ${p.who}`);
17780
17840
  for (const [label, value] of [
17781
17841
  ["comes for", p.comesFor],
17782
17842
  ["today instead", p.todayInstead],
17783
17843
  ["context", p.context],
17784
17844
  ["weight", p.weight]
17785
17845
  ]) {
17786
- if (value) out.push(` ${label}: ${value}`);
17846
+ if (value) out2.push(` ${label}: ${value}`);
17787
17847
  }
17788
17848
  const mine = needs2.filter((n) => needOwner(n) === p.slug);
17789
17849
  for (const n of mine) {
17790
17850
  taken.add(n.key);
17791
- out.push(` - ${n.title ? `${n.title}: ` : ""}${n.content}`);
17851
+ out2.push(` - ${n.title ? `${n.title}: ` : ""}${n.content}`);
17792
17852
  }
17793
- out.push("");
17853
+ out2.push("");
17794
17854
  }
17795
17855
  const loose = needs2.filter((n) => !taken.has(n.key));
17796
17856
  if (loose.length) {
17797
- out.push("Not attached to anyone yet");
17798
- for (const n of loose) out.push(` - ${n.title ? `${n.title}: ` : ""}${n.content}`);
17799
- out.push("");
17857
+ out2.push("Not attached to anyone yet");
17858
+ for (const n of loose) out2.push(` - ${n.title ? `${n.title}: ` : ""}${n.content}`);
17859
+ out2.push("");
17800
17860
  }
17801
- return out.join("\n").trimEnd();
17861
+ return out2.join("\n").trimEnd();
17802
17862
  }
17803
17863
 
17804
17864
  // src/commands/direction.ts
@@ -17823,21 +17883,21 @@ function renderDirection(rows2) {
17823
17883
  if (!rows2.some((r) => DIRECTION_ROWS.some((line) => r.key === directionKey(line))) && !objective) {
17824
17884
  return "No direction yet. `nanopm direction --refresh` has the crew write a mission and a vision from what the card holds; the objective is yours, and the onboarding asks for it.";
17825
17885
  }
17826
- const out = [];
17886
+ const out2 = [];
17827
17887
  for (const line of DIRECTION_ROWS) {
17828
17888
  const row = rows2.find((r) => r.key === directionKey(line));
17829
17889
  const next = rows2.find((r) => r.key === directionKey(line, true));
17830
- out.push(`${DIRECTION_ROW_TITLE[line]}${mark(row)}`);
17831
- out.push(` ${row?.content ?? "\u2014"}`);
17890
+ out2.push(`${DIRECTION_ROW_TITLE[line]}${mark(row)}`);
17891
+ out2.push(` ${row?.content ?? "\u2014"}`);
17832
17892
  if (next) {
17833
17893
  const because = reasonOf2(next.sources);
17834
- out.push(` proposed: ${next.content}${because ? ` \xB7 ${because}` : ""}`);
17894
+ out2.push(` proposed: ${next.content}${because ? ` \xB7 ${because}` : ""}`);
17835
17895
  }
17836
17896
  }
17837
- out.push("Objective");
17838
- out.push(` ${objective ? objective.content.replace(/^Lever: [a-z]+\.\s*/, "") : "\u2014"}`);
17839
- if (counts) out.push(` ${counts.content.replace(/^Lever: [a-z]+\.\s*/, "")}`);
17840
- return out.join("\n");
17897
+ out2.push("Objective");
17898
+ out2.push(` ${objective ? objective.content.replace(/^Lever: [a-z]+\.\s*/, "") : "\u2014"}`);
17899
+ if (counts) out2.push(` ${counts.content.replace(/^Lever: [a-z]+\.\s*/, "")}`);
17900
+ return out2.join("\n");
17841
17901
  }
17842
17902
  function mark(row) {
17843
17903
  return row && row.provenance !== "stated" ? ` \xB7 to review${row.confidence != null ? ` (${row.confidence}% sure)` : ""}` : "";
@@ -17868,19 +17928,19 @@ function render2(rows2) {
17868
17928
  if (!rows2.some((r) => PITCH_LINE_KEYS.some((l) => r.key === pitchKey(l)))) {
17869
17929
  return "No pitch yet. An onboarding writes the first one; `nanopm pitch --refresh` has the crew write one from what the card holds.";
17870
17930
  }
17871
- const out = [];
17931
+ const out2 = [];
17872
17932
  for (const line of PITCH_LINE_KEYS) {
17873
17933
  const row = rows2.find((r) => r.key === pitchKey(line));
17874
17934
  const next = rows2.find((r) => r.key === pitchKey(line, true));
17875
17935
  const mark2 = row && row.provenance !== "stated" ? ` \xB7 to review${row.confidence != null ? ` (${row.confidence}% sure)` : ""}` : "";
17876
- out.push(`${PITCH_LINE_TITLE[line]}${mark2}`);
17877
- out.push(` ${row?.content ?? "\u2014"}`);
17936
+ out2.push(`${PITCH_LINE_TITLE[line]}${mark2}`);
17937
+ out2.push(` ${row?.content ?? "\u2014"}`);
17878
17938
  if (next) {
17879
17939
  const because = reasonOf3(next.sources);
17880
- out.push(` proposed: ${next.content}${because ? ` \xB7 ${because}` : ""}`);
17940
+ out2.push(` proposed: ${next.content}${because ? ` \xB7 ${because}` : ""}`);
17881
17941
  }
17882
17942
  }
17883
- return out.join("\n");
17943
+ return out2.join("\n");
17884
17944
  }
17885
17945
  function reasonOf3(sources2) {
17886
17946
  if (!Array.isArray(sources2)) return null;
@@ -17952,16 +18012,16 @@ async function join18(code, opts) {
17952
18012
  const joined = row ? { id: row.joined_id, name: row.joined_name, repo_remote: row.joined_remote, role: row.joined_role } : void 0;
17953
18013
  if (!joined) throw new Error("nothing was joined");
17954
18014
  const paint = painter(opts.color ?? useColor());
17955
- const out = [paint(ANSI.green, `\u2713 you are on ${joined.name}, as ${joined.role}`)];
18015
+ const out2 = [paint(ANSI.green, `\u2713 you are on ${joined.name}, as ${joined.role}`)];
17956
18016
  if (joined.repo_remote && joined.repo_remote !== repo.remote) {
17957
- out.push(paint(ANSI.amber, ` This clone's remote is ${repo.remote}; the project's is ${joined.repo_remote}. A branch NanoPM makes here lands on yours.`));
18017
+ out2.push(paint(ANSI.amber, ` This clone's remote is ${repo.remote}; the project's is ${joined.repo_remote}. A branch NanoPM makes here lands on yours.`));
17958
18018
  }
17959
- out.push(paint(ANSI.dim, ` Clone: ${repo.root}${daemonId ? "" : " \xB7 no daemon on this machine yet: nanopm daemon install"}`));
18019
+ out2.push(paint(ANSI.dim, ` Clone: ${repo.root}${daemonId ? "" : " \xB7 no daemon on this machine yet: nanopm daemon install"}`));
17960
18020
  const { data: slugRow } = await auth.client.from("projects").select("slug").eq("id", joined.id).maybeSingle();
17961
- out.push("");
17962
- if (slugRow?.slug) out.push(` Your project: ${webUrl()}/p/${slugRow.slug}`);
17963
- out.push(paint(ANSI.dim, " nanopm roadmap \xB7 nanopm problems \xB7 nanopm status"));
17964
- return await ensureName(auth.client, auth.userId, opts, out.join("\n"));
18021
+ out2.push("");
18022
+ if (slugRow?.slug) out2.push(` Your project: ${webUrl()}/p/${slugRow.slug}`);
18023
+ out2.push(paint(ANSI.dim, " nanopm roadmap \xB7 nanopm problems \xB7 nanopm status"));
18024
+ return await ensureName(auth.client, auth.userId, opts, out2.join("\n"));
17965
18025
  }
17966
18026
  async function members(opts) {
17967
18027
  const auth = await authenticate();
@@ -17971,14 +18031,14 @@ async function members(opts) {
17971
18031
  if (error) throw new Error(pgMessage(error));
17972
18032
  const rows2 = data ?? [];
17973
18033
  const width = Math.max(...rows2.map((r) => r.name.length));
17974
- const out = rows2.map((r) => {
18034
+ const out2 = rows2.map((r) => {
17975
18035
  const you = r.user_id === auth.userId ? paint(ANSI.dim, " (you)") : "";
17976
18036
  const seen = r.daemon_last_seen_at ? Date.now() - new Date(r.daemon_last_seen_at).getTime() : Infinity;
17977
18037
  const daemon = !r.daemon_id ? paint(ANSI.dim, "no daemon") : seen < 12e4 ? paint(ANSI.green, "\u25CF daemon online") + (r.daemon_shared ? paint(ANSI.dim, " \xB7 shared") : "") : paint(ANSI.amber, `\u25CB daemon last seen ${Math.round(seen / 6e4)} min ago`) + (r.daemon_shared ? paint(ANSI.dim, " \xB7 shared") : "");
17978
18038
  return `${paint(ANSI.bold, r.name.padEnd(width))}${you} ${paint(ANSI.dim, r.role.padEnd(6))} ${daemon}`;
17979
18039
  });
17980
- out.push("", paint(ANSI.dim, " nanopm invite \xB7 nanopm name <what partners see>"));
17981
- return out.join("\n");
18040
+ out2.push("", paint(ANSI.dim, " nanopm invite \xB7 nanopm name <what partners see>"));
18041
+ return out2.join("\n");
17982
18042
  }
17983
18043
  async function setName(name) {
17984
18044
  const trimmed = name.trim();
@@ -18047,36 +18107,36 @@ var MEASURE_DEFINITIONS = [
18047
18107
  function renderStatus(projects, daemon, opts) {
18048
18108
  const paint = painter(opts.color);
18049
18109
  const now = opts.now ?? /* @__PURE__ */ new Date();
18050
- const out = [];
18110
+ const out2 = [];
18051
18111
  if (!daemon) {
18052
- out.push(paint(ANSI.amber, "\u25CB no daemon on this machine"));
18053
- out.push(paint(ANSI.dim, " nanopm daemon install"));
18112
+ out2.push(paint(ANSI.amber, "\u25CB no daemon on this machine"));
18113
+ out2.push(paint(ANSI.dim, " nanopm daemon install"));
18054
18114
  } else {
18055
18115
  const seenMs = daemon.lastSeenAt ? now.getTime() - new Date(daemon.lastSeenAt).getTime() : Infinity;
18056
18116
  const alive = daemon.status === "online" && seenMs < 5 * 6e4;
18057
18117
  const stale = seenMs > 24 * 36e5;
18058
18118
  const mark2 = alive ? paint(ANSI.green, "\u25CF") : stale ? paint(ANSI.red, "\u2298") : paint(ANSI.amber, "\u25CB");
18059
- out.push(`${mark2} daemon on ${daemon.hostname} \xB7 ${alive ? "online" : `last seen ${ago(daemon.lastSeenAt, now)}`} \xB7 nanopm ${daemon.version}`);
18119
+ out2.push(`${mark2} daemon on ${daemon.hostname} \xB7 ${alive ? "online" : `last seen ${ago(daemon.lastSeenAt, now)}`} \xB7 nanopm ${daemon.version}`);
18060
18120
  if (stale) {
18061
- out.push(paint(ANSI.dim, " It has not checked in for a day. Nothing scheduled will run until it does."));
18062
- out.push(paint(ANSI.dim, " nanopm daemon install"));
18121
+ out2.push(paint(ANSI.dim, " It has not checked in for a day. Nothing scheduled will run until it does."));
18122
+ out2.push(paint(ANSI.dim, " nanopm daemon install"));
18063
18123
  }
18064
18124
  }
18065
18125
  if (opts.activeProjects !== void 0 && projects.length > 1) {
18066
- out.push(paint(ANSI.dim, ` ${opts.activeProjects} of ${projects.length} projects active in the last 30 days`));
18126
+ out2.push(paint(ANSI.dim, ` ${opts.activeProjects} of ${projects.length} projects active in the last 30 days`));
18067
18127
  }
18068
- out.push("");
18128
+ out2.push("");
18069
18129
  for (const project of projects) {
18070
- out.push(paint(ANSI.bold, project.name) + (project.onThisMachine === false ? paint(ANSI.dim, " \xB7 clone not on this machine") : ""));
18130
+ out2.push(paint(ANSI.bold, project.name) + (project.onThisMachine === false ? paint(ANSI.dim, " \xB7 clone not on this machine") : ""));
18071
18131
  if (project.schedule) {
18072
- out.push(` ${paint(ANSI.dim, "reviews")} ${describeCadence(project.schedule)} \xB7 next ${describeNextRun(project.schedule, now)}`);
18132
+ out2.push(` ${paint(ANSI.dim, "reviews")} ${describeCadence(project.schedule)} \xB7 next ${describeNextRun(project.schedule, now)}`);
18073
18133
  } else {
18074
- out.push(` ${paint(ANSI.dim, "reviews")} not scheduled \xB7 ${paint(ANSI.dim, "nanopm schedule weekly monday")}`);
18134
+ out2.push(` ${paint(ANSI.dim, "reviews")} not scheduled \xB7 ${paint(ANSI.dim, "nanopm schedule weekly monday")}`);
18075
18135
  }
18076
- out.push(` ${paint(ANSI.dim, "last")} ${ago(project.lastReviewAt, now)}`);
18077
- out.push(` ${paint(ANSI.dim, "search")} ${project.search}`);
18136
+ out2.push(` ${paint(ANSI.dim, "last")} ${ago(project.lastReviewAt, now)}`);
18137
+ out2.push(` ${paint(ANSI.dim, "search")} ${project.search}`);
18078
18138
  if (project.stalledDays !== null) {
18079
- out.push(
18139
+ out2.push(
18080
18140
  paint(
18081
18141
  ANSI.amber,
18082
18142
  ` ? a review was queued ${project.stalledDays} day${project.stalledDays === 1 ? "" : "s"} ago and has not run \u2014 is the daemon awake?`
@@ -18086,7 +18146,7 @@ function renderStatus(projects, daemon, opts) {
18086
18146
  const parts = [];
18087
18147
  if (project.runningJobs) parts.push(`${project.runningJobs} running`);
18088
18148
  if (project.queuedJobs) parts.push(`${project.queuedJobs} queued`);
18089
- out.push(` ${paint(ANSI.dim, "jobs")} ${parts.join(", ")}`);
18149
+ out2.push(` ${paint(ANSI.dim, "jobs")} ${parts.join(", ")}`);
18090
18150
  }
18091
18151
  const waiting = [];
18092
18152
  if (project.waitingDecisions) {
@@ -18095,23 +18155,23 @@ function renderStatus(projects, daemon, opts) {
18095
18155
  if (project.unprovenProblems) {
18096
18156
  waiting.push(`${project.unprovenProblems} of ${project.openProblems} problems unproven`);
18097
18157
  }
18098
- out.push(
18158
+ out2.push(
18099
18159
  ` ${paint(ANSI.dim, "waiting")} ${waiting.length ? waiting.join(" \xB7 ") : paint(ANSI.dim, "nothing needs you")}`
18100
18160
  );
18101
18161
  if (project.measures) {
18102
18162
  const m = project.measures;
18103
- out.push(
18163
+ out2.push(
18104
18164
  ` ${paint(ANSI.dim, "measures")} autonomy ${ratio(m.autonomy.done, m.autonomy.accepted)} \xB7 decisions ${ratio(m.decisions.asWritten, m.decisions.decided)} \xB7 reviews ${m.reviews.weeks} of ${m.reviews.of} weeks`
18105
18165
  );
18106
18166
  }
18107
- out.push("");
18167
+ out2.push("");
18108
18168
  }
18109
18169
  if (projects.some((p) => p.measures)) {
18110
- for (const line of MEASURE_DEFINITIONS) out.push(paint(ANSI.dim, ` ${line}`));
18111
- out.push("");
18170
+ for (const line of MEASURE_DEFINITIONS) out2.push(paint(ANSI.dim, ` ${line}`));
18171
+ out2.push("");
18112
18172
  }
18113
- out.push(paint(ANSI.dim, " nanopm roadmap \xB7 nanopm problems \xB7 nanopm review --last"));
18114
- return out.join("\n");
18173
+ out2.push(paint(ANSI.dim, " nanopm roadmap \xB7 nanopm problems \xB7 nanopm review --last"));
18174
+ return out2.join("\n");
18115
18175
  }
18116
18176
 
18117
18177
  // src/commands/schedule.ts