@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 +637 -577
- package/package.json +1 -1
- package/skills/build/SKILL.md +19 -5
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
|
|
804
|
+
let out2 = "";
|
|
805
805
|
for (const p of paragraphs(text2).slice(0, most)) {
|
|
806
|
-
const room = max -
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
1286
|
+
out2.assumes = follow("This assumes", joined);
|
|
1287
1287
|
}
|
|
1288
|
-
return
|
|
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
|
|
1605
|
+
const out2 = [];
|
|
1606
1606
|
const walk = (list) => {
|
|
1607
1607
|
for (const n of list) {
|
|
1608
|
-
|
|
1608
|
+
out2.push(n);
|
|
1609
1609
|
walk(n.children);
|
|
1610
1610
|
}
|
|
1611
1611
|
};
|
|
1612
1612
|
walk(roots);
|
|
1613
|
-
return
|
|
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
|
|
1633
|
-
if (difficulty)
|
|
1634
|
-
if (precise)
|
|
1635
|
-
return `${
|
|
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
|
|
1647
|
-
if (whose(
|
|
1646
|
+
for (const run2 of active) {
|
|
1647
|
+
if (whose(run2) !== persona) continue;
|
|
1648
1648
|
const name = persona ? ctx.name(persona) : "this persona";
|
|
1649
|
-
if (
|
|
1650
|
-
if (wanted.scope === "map") return `NanoPM is digging into ${
|
|
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(
|
|
1653
|
-
return `NanoPM is already digging into ${
|
|
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
|
|
2038
|
-
slots.forEach((id, i) => id &&
|
|
2039
|
-
return
|
|
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
|
|
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
|
-
|
|
2091
|
+
out2.push(p);
|
|
2092
2092
|
used += p.length + 1;
|
|
2093
2093
|
}
|
|
2094
|
-
return
|
|
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
|
|
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)
|
|
2157
|
-
return
|
|
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
|
|
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
|
-
|
|
2171
|
+
out2.set(id, { horizon, place: last[horizon] });
|
|
2172
2172
|
}
|
|
2173
|
-
return
|
|
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
|
|
2217
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
2218
2218
|
for (const p of params) {
|
|
2219
|
-
const
|
|
2220
|
-
for (const leaf of
|
|
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
|
|
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
|
|
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)
|
|
2948
|
+
if (kept.length) out2[lever] = kept;
|
|
2949
2949
|
}
|
|
2950
|
-
return
|
|
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
|
|
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)
|
|
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
|
-
|
|
4289
|
+
out2.push(c);
|
|
4288
4290
|
i += 1;
|
|
4289
4291
|
}
|
|
4290
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4768
|
+
out2 += decoder.decode(value, { stream: true });
|
|
4767
4769
|
}
|
|
4768
|
-
|
|
4769
|
-
return { body:
|
|
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
|
|
4904
|
+
const out2 = {};
|
|
4903
4905
|
columns2.forEach((column, i) => {
|
|
4904
|
-
|
|
4906
|
+
out2[column] = Array.isArray(row) ? row[i] : void 0;
|
|
4905
4907
|
});
|
|
4906
|
-
return
|
|
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
|
|
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
|
-
|
|
5185
|
+
out2.push(JSON.parse(data[1]));
|
|
5184
5186
|
} catch {
|
|
5185
5187
|
}
|
|
5186
5188
|
}
|
|
5187
|
-
return
|
|
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
|
|
5677
|
-
const url = reviewUrlFrom(
|
|
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
|
|
5695
|
-
const stamps =
|
|
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
|
|
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)))
|
|
5834
|
+
if (!manifest.capabilities.some((c) => NEEDS_THE_REQUESTERS_CLONE.includes(c))) out2.push(name);
|
|
5831
5835
|
} catch {
|
|
5832
5836
|
}
|
|
5833
5837
|
}
|
|
5834
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
|
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
|
-
|
|
8047
|
+
out2.push(r);
|
|
8044
8048
|
grew = true;
|
|
8045
8049
|
}
|
|
8046
8050
|
}
|
|
8047
8051
|
}
|
|
8048
|
-
return
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
8510
|
-
const n = (p) =>
|
|
8511
|
-
await logStep(ctx, "opportunities.prioritized", again ? "the revision (the order)" : "ordering", `The prioritizer ordered ${
|
|
8512
|
-
return
|
|
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
|
|
8632
|
-
if (!said.length || !rows2.length) return
|
|
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 =
|
|
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
|
-
|
|
8656
|
+
out2.set(m.key, list);
|
|
8653
8657
|
}
|
|
8654
|
-
const quotes = [...
|
|
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 ${
|
|
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
|
|
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
|
|
8868
|
-
for (let p = parentOf2(r); p && !
|
|
8869
|
-
memo.set(r.id,
|
|
8870
|
-
return
|
|
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
|
|
8956
|
-
depthNew.set(k,
|
|
8957
|
-
return
|
|
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
|
|
8968
|
-
for (const f of rows2.filter((r) => isLive(r) && isFounders(r))) for (const a of tree.ancestors(f))
|
|
8969
|
-
return [...
|
|
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
|
|
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))
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
9455
|
+
for (const leaf of w.leaves) if (!out2.some((o) => o.leaf === leaf)) out2.push({ leaf, failed: why });
|
|
9452
9456
|
}
|
|
9453
|
-
const failed2 =
|
|
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
|
|
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
|
|
9573
|
-
const fresh = uniqueKeys([...
|
|
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 = [...
|
|
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
|
|
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 (!
|
|
9641
|
+
else if (!out2.some((s) => s.kind === source.kind && s.ref === source.ref)) out2.push(source);
|
|
9638
9642
|
}
|
|
9639
|
-
return
|
|
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((
|
|
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
|
|
10266
|
+
const out2 = [];
|
|
10263
10267
|
for (const line of raw) {
|
|
10264
|
-
if (!line.trim() ||
|
|
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
|
-
|
|
10273
|
+
out2.push(line.length > 200 ? `${line.slice(0, 199)}\u2026` : line);
|
|
10270
10274
|
}
|
|
10271
|
-
return
|
|
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
|
-
|
|
11273
|
-
await
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
const
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
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
|
|
12396
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
12337
12397
|
for (const d of [...baseDomains, ...confirmedDomains]) {
|
|
12338
|
-
if (d && !refuseHost(d))
|
|
12398
|
+
if (d && !refuseHost(d)) out2.add(d.toLowerCase());
|
|
12339
12399
|
}
|
|
12340
|
-
return [...
|
|
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
|
|
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
|
|
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
|
-
|
|
12965
|
+
out2.push(done);
|
|
12906
12966
|
if (done.target) at = done.target;
|
|
12907
12967
|
}
|
|
12908
|
-
return json(
|
|
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
|
|
14007
|
+
import { execFile as execFile8 } from "child_process";
|
|
13948
14008
|
import { basename as basename2 } from "path";
|
|
13949
|
-
import { promisify as
|
|
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 =
|
|
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(
|
|
14165
|
-
return { settle:
|
|
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(
|
|
14513
|
+
function answersFromTerminal(out2) {
|
|
14454
14514
|
return async (q, signal) => {
|
|
14455
14515
|
if (!process.stdin.isTTY) {
|
|
14456
|
-
|
|
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(
|
|
14478
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
14502
|
-
const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(
|
|
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
|
|
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
|
-
|
|
14689
|
+
out2(`
|
|
14630
14690
|
${project2.name}`);
|
|
14631
|
-
const code = await askOne(auth, project2.id, question, daemonId, opts,
|
|
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,
|
|
14697
|
+
return askOne(auth, project.id, question, daemonId, opts, out2);
|
|
14638
14698
|
}
|
|
14639
|
-
async function askOne(auth, projectId, question, daemonId, opts,
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
14838
|
+
out2("Claude Code answers.");
|
|
14779
14839
|
const web = await adapter.probeWebSearch();
|
|
14780
14840
|
await rememberWebSearch(web.ok);
|
|
14781
|
-
|
|
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
|
-
|
|
14854
|
+
out2(`Installed ${LAUNCHD_LABEL}; the daemon starts now and at every login. Logs: ${daemonLogPath()}`);
|
|
14795
14855
|
const shared = opts.shared ?? await localShared();
|
|
14796
|
-
|
|
14797
|
-
if (opts.next !== false)
|
|
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(
|
|
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
|
-
|
|
14872
|
+
out2(`Removed ${LAUNCHD_LABEL}.`);
|
|
14813
14873
|
}
|
|
14814
|
-
async function daemonStatus(
|
|
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
|
-
|
|
14877
|
+
out2(`launchd: ${installed ? "installed" : "not installed"}`);
|
|
14818
14878
|
if (!id) {
|
|
14819
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14830
|
-
|
|
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
|
-
|
|
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 ?? [])
|
|
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
|
|
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
|
-
|
|
14896
|
-
if (job.note)
|
|
14897
|
-
for (const row of jobRows)
|
|
14898
|
-
|
|
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
|
-
|
|
14904
|
-
for (const row of loose)
|
|
14963
|
+
out2.push(paint(ANSI.bold, "Outside any job"));
|
|
14964
|
+
for (const row of loose) out2.push(renderRow(row, paint));
|
|
14905
14965
|
}
|
|
14906
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
15038
|
-
|
|
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
|
-
|
|
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(
|
|
15132
|
+
await installDaemon(out2, { next: false });
|
|
15073
15133
|
} catch (err) {
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
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
|
-
|
|
15081
|
-
|
|
15140
|
+
out2("");
|
|
15141
|
+
out2(`Your machine is linked. Continue at ${web}/new`);
|
|
15082
15142
|
}
|
|
15083
|
-
async function logout(
|
|
15143
|
+
async function logout(out2 = (l) => console.log(l)) {
|
|
15084
15144
|
const stored = await loadCredentials();
|
|
15085
15145
|
if (!stored) {
|
|
15086
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
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
|
-
|
|
15173
|
+
out2(`Already up to date (${VERSION}).`);
|
|
15114
15174
|
return;
|
|
15115
15175
|
}
|
|
15116
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
15200
|
-
|
|
15201
|
-
if (opts.previous)
|
|
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
|
-
|
|
15204
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
15220
|
-
if (r.learn) for (const l of wrap(`To learn more: ${r.learn}`, width - pad.length))
|
|
15221
|
-
if (r.founder_rank !== null && r.now_rank)
|
|
15222
|
-
if (r.reconsider) for (const l of wrap(`Worth reconsidering: ${r.reconsider}`, width - pad.length))
|
|
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
|
-
|
|
15229
|
-
for (const r of aside)
|
|
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
|
-
|
|
15232
|
-
return
|
|
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
|
|
15300
|
+
const out2 = [];
|
|
15241
15301
|
const counts = needCounts(tree);
|
|
15242
|
-
|
|
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
|
-
|
|
15245
|
-
return
|
|
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
|
-
|
|
15255
|
-
for (const more of body.slice(1))
|
|
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
|
-
|
|
15319
|
+
out2.push("");
|
|
15260
15320
|
line(need, 0);
|
|
15261
15321
|
}
|
|
15262
|
-
|
|
15263
|
-
if (opts.others?.length)
|
|
15264
|
-
return
|
|
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
|
|
15465
|
-
if (plan.problem.children.length) for (const l of plan.leaves)
|
|
15466
|
-
if (plan.busy)
|
|
15467
|
-
|
|
15468
|
-
return
|
|
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
|
|
15485
|
-
if (problem)
|
|
15486
|
-
|
|
15487
|
-
for (const line of wrap(item.reasoning, Math.max(40, columns() - 4)))
|
|
15488
|
-
|
|
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)
|
|
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
|
-
|
|
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)
|
|
15504
|
-
return
|
|
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
|
|
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
|
-
|
|
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))
|
|
15597
|
+
for (const line of wrap(row.body, 72)) out2.push(paint(ANSI.dim, ` ${line}`));
|
|
15538
15598
|
}
|
|
15539
15599
|
}
|
|
15540
|
-
return
|
|
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
|
|
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
|
-
|
|
15566
|
-
if (!board.cards.some((c) => c.pick.placed_by === "founder"))
|
|
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
|
-
|
|
15573
|
-
if (!column.length)
|
|
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
|
-
|
|
15577
|
-
|
|
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)
|
|
15639
|
+
if (depth) out2.push(`${gutter}${paint(ANSI.blue, "\u25CF")} ${paint(ANSI.dim, WORK_WORD[depth])}`);
|
|
15580
15640
|
}
|
|
15581
15641
|
}
|
|
15582
|
-
if (board.earlier)
|
|
15583
|
-
|
|
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
|
|
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
|
|
15595
|
-
if (theme)
|
|
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
|
-
|
|
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) =>
|
|
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)
|
|
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))
|
|
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
|
-
|
|
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)
|
|
15616
|
-
for (const line of wrap(p, width - 6))
|
|
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))
|
|
15623
|
-
for (const point of changelog.points) wrap(point, width - 8).forEach((line, i) =>
|
|
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) =>
|
|
15630
|
-
if (step.checks)
|
|
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
|
-
|
|
15636
|
-
if (s.status === "rejected")
|
|
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
|
-
|
|
15641
|
-
} else
|
|
15642
|
-
if (others.length)
|
|
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
|
-
|
|
15705
|
+
out2.push(paint(ANSI.dim, ` ${move}nanopm reject ${s.ref} ["why"]`));
|
|
15646
15706
|
}
|
|
15647
|
-
return
|
|
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
|
|
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
|
-
|
|
15685
|
-
if (problem.mapping)
|
|
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
|
-
|
|
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
|
-
|
|
15693
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15764
|
+
out2.push(paint(ANSI.amber, `? ${why}`));
|
|
15705
15765
|
}
|
|
15706
|
-
|
|
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
|
-
|
|
15710
|
-
|
|
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
|
-
|
|
15718
|
-
|
|
15719
|
-
return
|
|
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
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
for (const line of wrap(problem.statement, width))
|
|
15729
|
-
|
|
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
|
-
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
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
|
-
|
|
15810
|
+
out2.push(`${paint(ANSI.dim, label.padEnd(9))}${clip(value, width - 9)}`);
|
|
15751
15811
|
}
|
|
15752
15812
|
if (evidence2.length) {
|
|
15753
|
-
|
|
15754
|
-
|
|
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))
|
|
15759
|
-
|
|
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
|
-
|
|
15763
|
-
|
|
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
|
-
|
|
15767
|
-
|
|
15826
|
+
out2.push("");
|
|
15827
|
+
out2.push(paint(ANSI.dim, "addressed by"));
|
|
15768
15828
|
for (const item of addressedBy) {
|
|
15769
|
-
|
|
15829
|
+
out2.push(` ${paint(ANSI.dim, item.ref)} ${clip(item.title, width - 12)} ${paint(ANSI.dim, `(${item.status})`)}`);
|
|
15770
15830
|
}
|
|
15771
15831
|
}
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
return
|
|
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
|
|
15856
|
+
const out2 = [];
|
|
15797
15857
|
const width = Math.max(40, columns());
|
|
15798
15858
|
if (strategy) {
|
|
15799
|
-
|
|
15800
|
-
for (const line of wrap(strategy, width - 4))
|
|
15801
|
-
|
|
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
|
-
|
|
15808
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15884
|
+
out2.push(paint(ANSI.dim, ` ${" ".repeat(refWidth + 2)} no solution yet`));
|
|
15825
15885
|
continue;
|
|
15826
15886
|
}
|
|
15827
|
-
for (const s of under)
|
|
15887
|
+
for (const s of under) out2.push(...solutionLines(s, refWidth, width, paint));
|
|
15828
15888
|
}
|
|
15829
|
-
|
|
15889
|
+
out2.push("");
|
|
15830
15890
|
}
|
|
15831
15891
|
const waiting = shown.filter((s) => s.status === "proposed").length;
|
|
15832
15892
|
if (waiting) {
|
|
15833
|
-
|
|
15834
|
-
|
|
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
|
-
|
|
15896
|
+
out2.push(paint(ANSI.dim, "Nothing waiting on you."));
|
|
15837
15897
|
}
|
|
15838
|
-
return
|
|
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
|
|
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
|
-
|
|
15877
|
-
if (!rows2.length)
|
|
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
|
-
|
|
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
|
-
|
|
15944
|
+
out2.push(paint(ANSI.dim, ` ${" ".repeat(refWidth + 2)} ${under}`));
|
|
15885
15945
|
}
|
|
15886
|
-
|
|
15946
|
+
out2.push("");
|
|
15887
15947
|
}
|
|
15888
|
-
return
|
|
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 =
|
|
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
|
-
|
|
16080
|
-
const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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)
|
|
16208
|
+
if (value) out2[match[1]] = value;
|
|
16149
16209
|
}
|
|
16150
|
-
return
|
|
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
|
|
16232
|
+
const out2 = [];
|
|
16173
16233
|
for (const file of ENV_FILES) {
|
|
16174
16234
|
try {
|
|
16175
|
-
|
|
16235
|
+
out2.push({ file, env: parseEnv(await readFile15(join17(repoRoot, file), "utf8")) });
|
|
16176
16236
|
} catch {
|
|
16177
16237
|
continue;
|
|
16178
16238
|
}
|
|
16179
16239
|
}
|
|
16180
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
16348
|
+
out2(`queued start as job ${job.id}`);
|
|
16289
16349
|
const link = newProjectUrl(job.id);
|
|
16290
|
-
|
|
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(
|
|
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"))
|
|
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(
|
|
16357
|
+
async function askSentence(out2) {
|
|
16298
16358
|
if (!process.stdin.isTTY) return null;
|
|
16299
|
-
|
|
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,
|
|
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
|
-
|
|
16320
|
-
const sentence2 = opts.sentence ?? await askSentence(
|
|
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
|
-
|
|
16389
|
+
out2(`created project ${project.name} (${project.id})`);
|
|
16330
16390
|
} else {
|
|
16331
|
-
|
|
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
|
-
|
|
16415
|
+
out2(`queued onboard as job ${job.id}`);
|
|
16356
16416
|
const link = newProjectUrl(job.id);
|
|
16357
16417
|
if (!opts.scanOnly) {
|
|
16358
|
-
|
|
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(
|
|
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))
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
16436
|
-
const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(
|
|
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
|
|
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
|
-
|
|
16561
|
+
out2.push(whereItGoes(color));
|
|
16502
16562
|
if (databases.length) {
|
|
16503
16563
|
const first = databases[0];
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
process.stdout.write(`${
|
|
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
|
-
|
|
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(`${
|
|
16576
|
+
process.stdout.write(`${out2.join("\n")}
|
|
16517
16577
|
`);
|
|
16518
|
-
|
|
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"))
|
|
16524
|
-
if (kinds.has("amplitude"))
|
|
16525
|
-
|
|
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
|
-
|
|
16552
|
-
|
|
16553
|
-
|
|
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
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
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
|
-
|
|
16562
|
-
return
|
|
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
|
|
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
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
if (s.status === "failed" && 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
|
-
|
|
16704
|
-
|
|
16705
|
-
return
|
|
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
|
|
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
|
-
|
|
16758
|
-
|
|
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
|
-
|
|
16762
|
-
|
|
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([...
|
|
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
|
-
|
|
16781
|
-
|
|
16782
|
-
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
17023
|
+
const out2 = [];
|
|
16964
17024
|
for (const source of rows2) {
|
|
16965
17025
|
const list = watchesOf(source);
|
|
16966
|
-
|
|
17026
|
+
out2.push(paint(ANSI.bold, source.label));
|
|
16967
17027
|
if (!list.length) {
|
|
16968
|
-
|
|
16969
|
-
|
|
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
|
-
|
|
16979
|
-
|
|
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
|
-
|
|
17041
|
+
out2.push("");
|
|
16982
17042
|
}
|
|
16983
|
-
|
|
16984
|
-
return
|
|
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
|
|
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
|
-
|
|
17114
|
-
|
|
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
|
-
|
|
17117
|
-
|
|
17118
|
-
return
|
|
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
|
|
17218
|
+
const out2 = [];
|
|
17159
17219
|
const width = columns();
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
for (const line of wrap(review2.summary, width))
|
|
17163
|
-
|
|
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
|
-
|
|
17166
|
-
|
|
17225
|
+
out2.push(renderMetrics(metrics2, opts).split("\n\n")[0]);
|
|
17226
|
+
out2.push("");
|
|
17167
17227
|
}
|
|
17168
17228
|
for (const item of review2.confirms) {
|
|
17169
|
-
|
|
17229
|
+
out2.push(`${paint(ANSI.green, "\u2713")} ${wrap(item, width - 2).join("\n ")}`);
|
|
17170
17230
|
}
|
|
17171
17231
|
for (const item of review2.contradicts) {
|
|
17172
|
-
|
|
17232
|
+
out2.push(`${paint(ANSI.amber, "\u2717")} ${wrap(item, width - 2).join("\n ")}`);
|
|
17173
17233
|
}
|
|
17174
|
-
if (review2.confirms.length || review2.contradicts.length)
|
|
17175
|
-
|
|
17176
|
-
return
|
|
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
|
|
17309
|
+
const out2 = [];
|
|
17250
17310
|
for (const project2 of await memberProjects(auth.client, auth.userId, opts.cwd)) {
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
17459
|
-
|
|
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
|
-
|
|
17467
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17475
|
-
|
|
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
|
-
|
|
17537
|
+
out2.push("");
|
|
17478
17538
|
}
|
|
17479
17539
|
const unconfirmed = competitors.filter((c) => !c.confirmed).length;
|
|
17480
17540
|
if (unconfirmed) {
|
|
17481
|
-
|
|
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
|
-
|
|
17489
|
-
|
|
17490
|
-
return
|
|
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
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
for (const line of wrap(row.statement, width))
|
|
17499
|
-
|
|
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)
|
|
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
|
-
|
|
17510
|
-
|
|
17511
|
-
for (const s of pages)
|
|
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
|
-
|
|
17515
|
-
|
|
17574
|
+
out2.push("");
|
|
17575
|
+
out2.push(paint(ANSI.dim, `Also about ${row.competitor}`));
|
|
17516
17576
|
for (const r of related) {
|
|
17517
|
-
|
|
17518
|
-
for (const line of wrap(r.statement, width - 4))
|
|
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
|
|
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
|
|
17606
|
+
const out2 = [describeSearch(row, local), ""];
|
|
17547
17607
|
if (isClaudeMode(row)) {
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
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
|
-
|
|
17553
|
-
|
|
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
|
-
|
|
17556
|
-
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
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
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
17779
|
-
|
|
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)
|
|
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
|
-
|
|
17851
|
+
out2.push(` - ${n.title ? `${n.title}: ` : ""}${n.content}`);
|
|
17792
17852
|
}
|
|
17793
|
-
|
|
17853
|
+
out2.push("");
|
|
17794
17854
|
}
|
|
17795
17855
|
const loose = needs2.filter((n) => !taken.has(n.key));
|
|
17796
17856
|
if (loose.length) {
|
|
17797
|
-
|
|
17798
|
-
for (const n of loose)
|
|
17799
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
17831
|
-
|
|
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
|
-
|
|
17894
|
+
out2.push(` proposed: ${next.content}${because ? ` \xB7 ${because}` : ""}`);
|
|
17835
17895
|
}
|
|
17836
17896
|
}
|
|
17837
|
-
|
|
17838
|
-
|
|
17839
|
-
if (counts)
|
|
17840
|
-
return
|
|
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
|
|
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
|
-
|
|
17877
|
-
|
|
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
|
-
|
|
17940
|
+
out2.push(` proposed: ${next.content}${because ? ` \xB7 ${because}` : ""}`);
|
|
17881
17941
|
}
|
|
17882
17942
|
}
|
|
17883
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17962
|
-
if (slugRow?.slug)
|
|
17963
|
-
|
|
17964
|
-
return await ensureName(auth.client, auth.userId, opts,
|
|
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
|
|
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
|
-
|
|
17981
|
-
return
|
|
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
|
|
18110
|
+
const out2 = [];
|
|
18051
18111
|
if (!daemon) {
|
|
18052
|
-
|
|
18053
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18062
|
-
|
|
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
|
-
|
|
18126
|
+
out2.push(paint(ANSI.dim, ` ${opts.activeProjects} of ${projects.length} projects active in the last 30 days`));
|
|
18067
18127
|
}
|
|
18068
|
-
|
|
18128
|
+
out2.push("");
|
|
18069
18129
|
for (const project of projects) {
|
|
18070
|
-
|
|
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
|
-
|
|
18132
|
+
out2.push(` ${paint(ANSI.dim, "reviews")} ${describeCadence(project.schedule)} \xB7 next ${describeNextRun(project.schedule, now)}`);
|
|
18073
18133
|
} else {
|
|
18074
|
-
|
|
18134
|
+
out2.push(` ${paint(ANSI.dim, "reviews")} not scheduled \xB7 ${paint(ANSI.dim, "nanopm schedule weekly monday")}`);
|
|
18075
18135
|
}
|
|
18076
|
-
|
|
18077
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18167
|
+
out2.push("");
|
|
18108
18168
|
}
|
|
18109
18169
|
if (projects.some((p) => p.measures)) {
|
|
18110
|
-
for (const line of MEASURE_DEFINITIONS)
|
|
18111
|
-
|
|
18170
|
+
for (const line of MEASURE_DEFINITIONS) out2.push(paint(ANSI.dim, ` ${line}`));
|
|
18171
|
+
out2.push("");
|
|
18112
18172
|
}
|
|
18113
|
-
|
|
18114
|
-
return
|
|
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
|