@nanopm/cli 0.1.19 → 0.1.21
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 +633 -576
- package/package.json +1 -1
- package/skills/build/SKILL.md +17 -4
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;
|
|
@@ -5675,8 +5677,8 @@ async function pullRequestState(cwd, number) {
|
|
|
5675
5677
|
}
|
|
5676
5678
|
async function openPullRequest(cwd, pr) {
|
|
5677
5679
|
try {
|
|
5678
|
-
const
|
|
5679
|
-
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) ?? "";
|
|
5680
5682
|
return { ok: true, url, number: Number(/\/pull\/(\d+)/.exec(url)?.[1] ?? 0) };
|
|
5681
5683
|
} catch (err) {
|
|
5682
5684
|
return { ok: false, reason: errorMessage(err).split("\n").filter(Boolean).slice(-2).join(" ") || "gh pr create failed" };
|
|
@@ -5693,8 +5695,8 @@ async function git2(cwd, args) {
|
|
|
5693
5695
|
}
|
|
5694
5696
|
async function commitsSince(cwd, since) {
|
|
5695
5697
|
const branch = await defaultBranch(cwd);
|
|
5696
|
-
const
|
|
5697
|
-
const stamps =
|
|
5698
|
+
const out2 = await git2(cwd, ["log", branch, `--since=${since.toISOString()}`, "--format=%cI"]);
|
|
5699
|
+
const stamps = out2.split("\n").filter(Boolean);
|
|
5698
5700
|
return { count: stamps.length, newestAt: stamps[0] ? new Date(stamps[0]) : null };
|
|
5699
5701
|
}
|
|
5700
5702
|
async function reviewDeploys(client, daemonId, now = /* @__PURE__ */ new Date()) {
|
|
@@ -5824,16 +5826,16 @@ var NEEDS_THE_REQUESTERS_CLONE = ["write_repo", "run_commands"];
|
|
|
5824
5826
|
var NEEDS_THE_REQUESTERS_IDENTITY = /* @__PURE__ */ new Set(["want", "start"]);
|
|
5825
5827
|
async function clonelessSkills(skillsDir = findSkillsDir()) {
|
|
5826
5828
|
const names = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && existsSync2(join7(skillsDir, d.name, "skill.json"))).map((d) => d.name);
|
|
5827
|
-
const
|
|
5829
|
+
const out2 = [];
|
|
5828
5830
|
for (const name of names) {
|
|
5829
5831
|
try {
|
|
5830
5832
|
const { manifest } = await loadSkill(name, skillsDir);
|
|
5831
5833
|
if (NEEDS_THE_REQUESTERS_IDENTITY.has(name)) continue;
|
|
5832
|
-
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);
|
|
5833
5835
|
} catch {
|
|
5834
5836
|
}
|
|
5835
5837
|
}
|
|
5836
|
-
return
|
|
5838
|
+
return out2.sort();
|
|
5837
5839
|
}
|
|
5838
5840
|
|
|
5839
5841
|
// ../daemon/src/problem-mapping-harness.ts
|
|
@@ -6356,15 +6358,15 @@ async function readDocs(cwd, opts = {}) {
|
|
|
6356
6358
|
};
|
|
6357
6359
|
await walk(cwd, 0);
|
|
6358
6360
|
found.sort((a, b) => Number(WRITTEN.test(b)) - Number(WRITTEN.test(a)) || a.length - b.length);
|
|
6359
|
-
const
|
|
6361
|
+
const out2 = [];
|
|
6360
6362
|
for (const path of found.slice(0, DOCS_MAX)) {
|
|
6361
6363
|
try {
|
|
6362
6364
|
if ((await stat(path)).size > 4e5) continue;
|
|
6363
|
-
|
|
6365
|
+
out2.push({ path: relative(cwd, path), text: (await readFile5(path, "utf8")).slice(0, DOC_CHARS) });
|
|
6364
6366
|
} catch {
|
|
6365
6367
|
}
|
|
6366
6368
|
}
|
|
6367
|
-
return
|
|
6369
|
+
return out2;
|
|
6368
6370
|
}
|
|
6369
6371
|
|
|
6370
6372
|
// ../daemon/src/persona-job.ts
|
|
@@ -7962,18 +7964,18 @@ function droppedAt(rows2, parent, byId, current, target) {
|
|
|
7962
7964
|
});
|
|
7963
7965
|
}
|
|
7964
7966
|
function sourcesOf(ids, heard, read6, today) {
|
|
7965
|
-
const
|
|
7967
|
+
const out2 = [];
|
|
7966
7968
|
for (const id of ids) {
|
|
7967
7969
|
const h = heard.find((q) => q.id === id);
|
|
7968
7970
|
if (h) {
|
|
7969
|
-
|
|
7971
|
+
out2.push({ kind: h.kind, ref: h.ref, quote: clip2(h.quote, NEED_QUOTE_MAX), read_at: today });
|
|
7970
7972
|
continue;
|
|
7971
7973
|
}
|
|
7972
7974
|
const w = read6.find((f) => f.id === id && f.read);
|
|
7973
|
-
if (w)
|
|
7975
|
+
if (w) out2.push({ kind: "web", ref: w.page, quote: clip2(w.quote, NEED_QUOTE_MAX), read_at: w.published ?? today });
|
|
7974
7976
|
}
|
|
7975
7977
|
const seen = /* @__PURE__ */ new Set();
|
|
7976
|
-
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);
|
|
7977
7979
|
}
|
|
7978
7980
|
function checkHeard(quotes, material) {
|
|
7979
7981
|
const kept = [];
|
|
@@ -8033,7 +8035,7 @@ function descendants(rows2, root2) {
|
|
|
8033
8035
|
for (let hops = 0; n && n.status === "replaced" && n.replaced_by && hops < 50; hops += 1) n = byId.get(n.replaced_by);
|
|
8034
8036
|
return n && n.status !== "replaced" ? n : null;
|
|
8035
8037
|
};
|
|
8036
|
-
const
|
|
8038
|
+
const out2 = [root2];
|
|
8037
8039
|
const ids = /* @__PURE__ */ new Set([root2.id]);
|
|
8038
8040
|
for (let grew = true; grew; ) {
|
|
8039
8041
|
grew = false;
|
|
@@ -8042,12 +8044,12 @@ function descendants(rows2, root2) {
|
|
|
8042
8044
|
const p = current(r.parent_id);
|
|
8043
8045
|
if (p && ids.has(p.id)) {
|
|
8044
8046
|
ids.add(r.id);
|
|
8045
|
-
|
|
8047
|
+
out2.push(r);
|
|
8046
8048
|
grew = true;
|
|
8047
8049
|
}
|
|
8048
8050
|
}
|
|
8049
8051
|
}
|
|
8050
|
-
return
|
|
8052
|
+
return out2;
|
|
8051
8053
|
}
|
|
8052
8054
|
function pathOf2(rows2, node) {
|
|
8053
8055
|
const found = needTree(rows2).flatMap(function walk(n) {
|
|
@@ -8347,18 +8349,18 @@ function snapshot(input) {
|
|
|
8347
8349
|
for (const n of input.nodes) byPersona.set(n.persona, [...byPersona.get(n.persona) ?? [], n]);
|
|
8348
8350
|
let lines = 0;
|
|
8349
8351
|
for (const [persona, rows2] of byPersona) {
|
|
8350
|
-
const
|
|
8352
|
+
const out2 = [];
|
|
8351
8353
|
const walk = (list, under) => {
|
|
8352
8354
|
for (const n of list) {
|
|
8353
8355
|
if (n.status === "dropped" || n.outside_mission || lines >= NEEDS_LINES_MAX) continue;
|
|
8354
8356
|
lines += 1;
|
|
8355
8357
|
cites.set(n.ref.toUpperCase(), { kind: "needs", ref: n.ref, note: clip3(n.label, 140) });
|
|
8356
|
-
|
|
8358
|
+
out2.push({ ref: n.ref, level: n.level, line: n.label, statement: n.statement, under });
|
|
8357
8359
|
walk(n.children, n.ref);
|
|
8358
8360
|
}
|
|
8359
8361
|
};
|
|
8360
8362
|
walk(needTree(rows2), null);
|
|
8361
|
-
if (
|
|
8363
|
+
if (out2.length) needsMaps.push({ persona, lines: out2 });
|
|
8362
8364
|
}
|
|
8363
8365
|
if (ask7.note) cites.set("NOTE", { kind: "founder", ref: "note", note: clip3(ask7.note, 140) });
|
|
8364
8366
|
const tree = flattenTree(liveTree(input.mine));
|
|
@@ -8496,22 +8498,22 @@ async function deepRun(ctx, mine) {
|
|
|
8496
8498
|
const full = { ...m, explorers };
|
|
8497
8499
|
const synthesize = async () => {
|
|
8498
8500
|
const cost = await role6({ ...ctx, role: "synthesizer", tools: "none", say: "Making one map of what was found\u2026", prompt: JSON.stringify(full) });
|
|
8499
|
-
const
|
|
8501
|
+
const out2 = SynthesizedMap.parse(JSON.parse(cost.text));
|
|
8500
8502
|
await logStep(
|
|
8501
8503
|
ctx,
|
|
8502
8504
|
"opportunities.synthesized",
|
|
8503
8505
|
"making one map",
|
|
8504
|
-
`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` : ""}.`,
|
|
8505
8507
|
cost
|
|
8506
8508
|
);
|
|
8507
|
-
return
|
|
8509
|
+
return out2;
|
|
8508
8510
|
};
|
|
8509
8511
|
const prioritize = async (synth2, again) => {
|
|
8510
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) }) });
|
|
8511
|
-
const
|
|
8512
|
-
const n = (p) =>
|
|
8513
|
-
await logStep(ctx, "opportunities.prioritized", again ? "the revision (the order)" : "ordering", `The prioritizer ordered ${
|
|
8514
|
-
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;
|
|
8515
8517
|
};
|
|
8516
8518
|
let synth = await synthesize();
|
|
8517
8519
|
const reading = beside(readEvidence(ctx, synth.opportunities));
|
|
@@ -8630,8 +8632,8 @@ function revisionLine(m) {
|
|
|
8630
8632
|
}
|
|
8631
8633
|
async function readEvidence(ctx, rows2) {
|
|
8632
8634
|
const said = ctx.known.material.users_said.filter((s) => ctx.known.cites.get(s.id.toUpperCase())?.kind === "user");
|
|
8633
|
-
const
|
|
8634
|
-
if (!said.length || !rows2.length) return
|
|
8635
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
8636
|
+
if (!said.length || !rows2.length) return out2;
|
|
8635
8637
|
const cost = await role6({
|
|
8636
8638
|
...ctx,
|
|
8637
8639
|
role: "evidence",
|
|
@@ -8649,19 +8651,19 @@ async function readEvidence(ctx, rows2) {
|
|
|
8649
8651
|
dropped += 1;
|
|
8650
8652
|
continue;
|
|
8651
8653
|
}
|
|
8652
|
-
const list =
|
|
8654
|
+
const list = out2.get(m.key) ?? [];
|
|
8653
8655
|
if (!list.some((x) => x.id === text2.id && x.quote === m.quote)) list.push({ id: text2.id, quote: m.quote.trim() });
|
|
8654
|
-
|
|
8656
|
+
out2.set(m.key, list);
|
|
8655
8657
|
}
|
|
8656
|
-
const quotes = [...
|
|
8658
|
+
const quotes = [...out2.values()].reduce((n, l) => n + l.length, 0);
|
|
8657
8659
|
await logStep(
|
|
8658
8660
|
ctx,
|
|
8659
8661
|
"opportunities.evidenced",
|
|
8660
8662
|
"reading the evidence (alongside)",
|
|
8661
|
-
`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)}` : ""}`,
|
|
8662
8664
|
cost
|
|
8663
8665
|
);
|
|
8664
|
-
return
|
|
8666
|
+
return out2;
|
|
8665
8667
|
}
|
|
8666
8668
|
function withEvidence(rows2, evidence2) {
|
|
8667
8669
|
return rows2.map((r) => {
|
|
@@ -8866,10 +8868,10 @@ function existingTree(rows2) {
|
|
|
8866
8868
|
const ancestors = (r) => {
|
|
8867
8869
|
const known = memo.get(r.id);
|
|
8868
8870
|
if (known) return known;
|
|
8869
|
-
const
|
|
8870
|
-
for (let p = parentOf2(r); p && !
|
|
8871
|
-
memo.set(r.id,
|
|
8872
|
-
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;
|
|
8873
8875
|
};
|
|
8874
8876
|
return { parentOf: parentOf2, ancestors };
|
|
8875
8877
|
}
|
|
@@ -8954,9 +8956,9 @@ function structure(pool, gone, drawn, tree, liveByRef, asideByRef) {
|
|
|
8954
8956
|
if (!at || seen.has(k)) return -1;
|
|
8955
8957
|
seen.add(k);
|
|
8956
8958
|
const d = "key" in at ? measure(at.key, seen) : 0;
|
|
8957
|
-
const
|
|
8958
|
-
depthNew.set(k,
|
|
8959
|
-
return
|
|
8959
|
+
const out2 = d < 0 ? -1 : d + 1;
|
|
8960
|
+
depthNew.set(k, out2);
|
|
8961
|
+
return out2;
|
|
8960
8962
|
};
|
|
8961
8963
|
for (const r of pool) {
|
|
8962
8964
|
if (late.some((l) => l.key === r.key)) continue;
|
|
@@ -8966,9 +8968,9 @@ function structure(pool, gone, drawn, tree, liveByRef, asideByRef) {
|
|
|
8966
8968
|
}
|
|
8967
8969
|
function retireable(rows2) {
|
|
8968
8970
|
const tree = existingTree(rows2);
|
|
8969
|
-
const
|
|
8970
|
-
for (const f of rows2.filter((r) => isLive(r) && isFounders(r))) for (const a of tree.ancestors(f))
|
|
8971
|
-
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];
|
|
8972
8974
|
}
|
|
8973
8975
|
function critiqueWording(o, v, reworked) {
|
|
8974
8976
|
if (v?.verdict !== "pass" || reworked.has(o.key)) return o;
|
|
@@ -9416,7 +9418,7 @@ async function flashBranch(ctx, w, n, places) {
|
|
|
9416
9418
|
const name = clip3(themeName(w.branch), 60);
|
|
9417
9419
|
const material = flashMaterial(ctx.known, w);
|
|
9418
9420
|
const call2 = { role: "flash", parse: FlashAnswer, dir: `flash-${n + 1}` };
|
|
9419
|
-
const
|
|
9421
|
+
const out2 = [];
|
|
9420
9422
|
try {
|
|
9421
9423
|
const first = await ask2(ctx, { ...call2, prompt: material, say: `Finding solutions to \u201C${name}\u201D\u2026` });
|
|
9422
9424
|
const gated = w.leaves.map((leaf) => {
|
|
@@ -9429,7 +9431,7 @@ async function flashBranch(ctx, w, n, places) {
|
|
|
9429
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)}` : ""}`,
|
|
9430
9432
|
first.cost
|
|
9431
9433
|
);
|
|
9432
|
-
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)));
|
|
9433
9435
|
if (again.length) {
|
|
9434
9436
|
const refused = again.map((g) => ({ ref: g.leaf.ref, why: g.refused, you_wrote: g.written }));
|
|
9435
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` });
|
|
@@ -9437,11 +9439,11 @@ async function flashBranch(ctx, w, n, places) {
|
|
|
9437
9439
|
for (const g of again) {
|
|
9438
9440
|
const re = gate(ctx, answersFor(second.answer.leaves, g.leaf), g.leaf);
|
|
9439
9441
|
if (!re.kept.length) {
|
|
9440
|
-
|
|
9442
|
+
out2.push({ leaf: g.leaf, failed: re.refused.join("; ") });
|
|
9441
9443
|
continue;
|
|
9442
9444
|
}
|
|
9443
9445
|
for (const why of re.refused) await ctx.journal("solutions.refused", `${g.leaf.ref}: ${why}; not recorded.`);
|
|
9444
|
-
|
|
9446
|
+
out2.push(await publish4(ctx, g.leaf, re.kept, places.get(g.leaf.id)));
|
|
9445
9447
|
}
|
|
9446
9448
|
}
|
|
9447
9449
|
} catch (error) {
|
|
@@ -9450,13 +9452,13 @@ async function flashBranch(ctx, w, n, places) {
|
|
|
9450
9452
|
throw error;
|
|
9451
9453
|
}
|
|
9452
9454
|
const why = clip3(error instanceof Error ? error.message : String(error), 300);
|
|
9453
|
-
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 });
|
|
9454
9456
|
}
|
|
9455
|
-
const failed2 =
|
|
9457
|
+
const failed2 = out2.filter((o) => "failed" in o);
|
|
9456
9458
|
if (failed2.length) {
|
|
9457
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.`);
|
|
9458
9460
|
}
|
|
9459
|
-
return
|
|
9461
|
+
return out2;
|
|
9460
9462
|
}
|
|
9461
9463
|
function answersFor(answered2, leaf) {
|
|
9462
9464
|
return answered2.find((a) => a.ref.trim().toUpperCase() === leaf.ref.toUpperCase())?.solutions ?? [];
|
|
@@ -9571,10 +9573,10 @@ function uniqueKeys(list) {
|
|
|
9571
9573
|
}
|
|
9572
9574
|
function revised2(first, r, cut) {
|
|
9573
9575
|
const rework = new Map(r.rework.filter((x) => !cut.has(x.key)).map((x) => [x.key, x]));
|
|
9574
|
-
const
|
|
9575
|
-
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);
|
|
9576
9578
|
const dropped = new Set(r.drop.map((d) => d.key));
|
|
9577
|
-
const kept = [...
|
|
9579
|
+
const kept = [...out2, ...fresh].filter((s) => !dropped.has(s.key));
|
|
9578
9580
|
const at = (key) => r.order.includes(key) ? r.order.indexOf(key) : Infinity;
|
|
9579
9581
|
return r.order.length ? [...kept].sort((a, b) => at(a.key) - at(b.key)) : kept;
|
|
9580
9582
|
}
|
|
@@ -9631,14 +9633,14 @@ function draftFields(w) {
|
|
|
9631
9633
|
};
|
|
9632
9634
|
}
|
|
9633
9635
|
function resolve3(ctx, ids) {
|
|
9634
|
-
const
|
|
9636
|
+
const out2 = [];
|
|
9635
9637
|
for (const raw of ids) {
|
|
9636
9638
|
const id = raw.trim().toUpperCase();
|
|
9637
9639
|
const source = ctx.known.cites.get(id);
|
|
9638
9640
|
if (!source) ctx.known.unresolved.add(raw.trim());
|
|
9639
|
-
else if (!
|
|
9641
|
+
else if (!out2.some((s) => s.kind === source.kind && s.ref === source.ref)) out2.push(source);
|
|
9640
9642
|
}
|
|
9641
|
-
return
|
|
9643
|
+
return out2.length ? out2.slice(0, OPPORTUNITY_SOURCES_MAX) : [{ kind: "inference", ref: "nanopm", note: null }];
|
|
9642
9644
|
}
|
|
9643
9645
|
async function publish4(ctx, leaf, drafts, place) {
|
|
9644
9646
|
ctx.spec.signal.throwIfAborted();
|
|
@@ -9829,7 +9831,7 @@ async function git3(cwd, args) {
|
|
|
9829
9831
|
return stdout.trim();
|
|
9830
9832
|
}
|
|
9831
9833
|
async function listTrackedFiles(cwd) {
|
|
9832
|
-
const lines = (args) => git3(cwd, args).then((
|
|
9834
|
+
const lines = (args) => git3(cwd, args).then((out2) => out2 ? out2.split("\n") : [], () => []);
|
|
9833
9835
|
const [files, wiki] = await Promise.all([lines(["ls-files"]), lines(["ls-files", "--others", "--ignored", "--exclude-standard", "--", WIKI_DIR])]);
|
|
9834
9836
|
return { files, wiki };
|
|
9835
9837
|
}
|
|
@@ -10261,16 +10263,16 @@ async function countingLines(cwd) {
|
|
|
10261
10263
|
}
|
|
10262
10264
|
function pickLines(raw) {
|
|
10263
10265
|
const perFile = /* @__PURE__ */ new Map();
|
|
10264
|
-
const
|
|
10266
|
+
const out2 = [];
|
|
10265
10267
|
for (const line of raw) {
|
|
10266
|
-
if (!line.trim() ||
|
|
10268
|
+
if (!line.trim() || out2.length >= COUNTING_LINES_MAX) continue;
|
|
10267
10269
|
const file = line.split(":", 1)[0];
|
|
10268
10270
|
const n = perFile.get(file) ?? 0;
|
|
10269
10271
|
if (n >= PER_FILE) continue;
|
|
10270
10272
|
perFile.set(file, n + 1);
|
|
10271
|
-
|
|
10273
|
+
out2.push(line.length > 200 ? `${line.slice(0, 199)}\u2026` : line);
|
|
10272
10274
|
}
|
|
10273
|
-
return
|
|
10275
|
+
return out2;
|
|
10274
10276
|
}
|
|
10275
10277
|
async function killRecorded(runDir) {
|
|
10276
10278
|
try {
|
|
@@ -11205,6 +11207,56 @@ function usageTokens(usage) {
|
|
|
11205
11207
|
return usage.inputTokens + (usage.cacheWriteTokens ?? 0) + usage.outputTokens;
|
|
11206
11208
|
}
|
|
11207
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
|
+
|
|
11208
11260
|
// ../daemon/src/build-job.ts
|
|
11209
11261
|
var PLAN_TOOL = "mcp__nanopm__build_plan";
|
|
11210
11262
|
var ASK_TOOL = "mcp__nanopm__ask_user";
|
|
@@ -11271,31 +11323,36 @@ async function runBuildJob(input) {
|
|
|
11271
11323
|
};
|
|
11272
11324
|
const session2 = async (prompt, resume3) => {
|
|
11273
11325
|
let end = { stop: failed("the agent produced no result") };
|
|
11274
|
-
|
|
11275
|
-
await
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
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 } } };
|
|
11288
11351
|
}
|
|
11289
|
-
} else if (event.type === "finished") {
|
|
11290
|
-
usage = sumUsage(usage, event.usage);
|
|
11291
|
-
said = event.result;
|
|
11292
|
-
end = null;
|
|
11293
|
-
} else if (event.type === "deferred" && event.tool === ASK_TOOL) {
|
|
11294
|
-
usage = sumUsage(usage, event.usage);
|
|
11295
|
-
end = { park: { ask: askOf(event.input) } };
|
|
11296
|
-
} else if (event.type === "failed") {
|
|
11297
|
-
end = event.code === "rate_limit" ? { park: { resetsAt: event.resetsAt } } : { stop: { status: "failed", error: { code: event.code, message: event.message } } };
|
|
11298
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`);
|
|
11299
11356
|
}
|
|
11300
11357
|
return end;
|
|
11301
11358
|
};
|
|
@@ -12336,11 +12393,11 @@ function domainAllowed(host, allowlist) {
|
|
|
12336
12393
|
return false;
|
|
12337
12394
|
}
|
|
12338
12395
|
function allowlistFor(baseDomains, confirmedDomains) {
|
|
12339
|
-
const
|
|
12396
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
12340
12397
|
for (const d of [...baseDomains, ...confirmedDomains]) {
|
|
12341
|
-
if (d && !refuseHost(d))
|
|
12398
|
+
if (d && !refuseHost(d)) out2.add(d.toLowerCase());
|
|
12342
12399
|
}
|
|
12343
|
-
return [...
|
|
12400
|
+
return [...out2];
|
|
12344
12401
|
}
|
|
12345
12402
|
function refuseFetch(url, allowlist, open = false) {
|
|
12346
12403
|
const host = hostOf(url);
|
|
@@ -12901,14 +12958,14 @@ ${renderMap()}`,
|
|
|
12901
12958
|
const act = (change, asked, why) => talkAct(ctx, round2 ??= talkRound(ctx), change, asked, why);
|
|
12902
12959
|
const actInTurn = async (ref, changes, asked, why) => {
|
|
12903
12960
|
if (!changes.length) return failure("Say what changes.");
|
|
12904
|
-
const
|
|
12961
|
+
const out2 = [];
|
|
12905
12962
|
let at = ref;
|
|
12906
12963
|
for (const change of changes) {
|
|
12907
12964
|
const done = await act({ ...change, ref: at }, asked, why);
|
|
12908
|
-
|
|
12965
|
+
out2.push(done);
|
|
12909
12966
|
if (done.target) at = done.target;
|
|
12910
12967
|
}
|
|
12911
|
-
return json(
|
|
12968
|
+
return json(out2);
|
|
12912
12969
|
};
|
|
12913
12970
|
const F2 = TalkField;
|
|
12914
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.');
|
|
@@ -13947,9 +14004,9 @@ async function startMemoryServer(env = process.env) {
|
|
|
13947
14004
|
import { readFile as readFile12 } from "fs/promises";
|
|
13948
14005
|
|
|
13949
14006
|
// src/project.ts
|
|
13950
|
-
import { execFile as
|
|
14007
|
+
import { execFile as execFile8 } from "child_process";
|
|
13951
14008
|
import { basename as basename2 } from "path";
|
|
13952
|
-
import { promisify as
|
|
14009
|
+
import { promisify as promisify8 } from "util";
|
|
13953
14010
|
|
|
13954
14011
|
// src/local.ts
|
|
13955
14012
|
import { readFile as readFile11 } from "fs/promises";
|
|
@@ -13967,7 +14024,7 @@ function useColor() {
|
|
|
13967
14024
|
}
|
|
13968
14025
|
|
|
13969
14026
|
// src/project.ts
|
|
13970
|
-
var execFileAsync7 =
|
|
14027
|
+
var execFileAsync7 = promisify8(execFile8);
|
|
13971
14028
|
async function detectRepo(cwd) {
|
|
13972
14029
|
let root2;
|
|
13973
14030
|
try {
|
|
@@ -14164,8 +14221,8 @@ function nanoFrame(mood, tick, paint) {
|
|
|
14164
14221
|
const dot = mood === "working" ? DOTS[Math.floor(tick / 3) % DOTS.length] : mood === "happy" ? "\u02D9" : " ";
|
|
14165
14222
|
return `${paint(ANSI.red, face)}${paint(ANSI.red, dot)}`;
|
|
14166
14223
|
}
|
|
14167
|
-
function plainRenderer(
|
|
14168
|
-
return { settle:
|
|
14224
|
+
function plainRenderer(out2) {
|
|
14225
|
+
return { settle: out2, live: () => void 0, pause: () => void 0, resume: () => void 0, stop: () => void 0 };
|
|
14169
14226
|
}
|
|
14170
14227
|
function liveRenderer(stream, opts = {}) {
|
|
14171
14228
|
const paint = painter(opts.color ?? Boolean(stream.isTTY && !process.env.NO_COLOR));
|
|
@@ -14453,10 +14510,10 @@ function scriptedAnswerer(entries) {
|
|
|
14453
14510
|
return entry.answer;
|
|
14454
14511
|
};
|
|
14455
14512
|
}
|
|
14456
|
-
function answersFromTerminal(
|
|
14513
|
+
function answersFromTerminal(out2) {
|
|
14457
14514
|
return async (q, signal) => {
|
|
14458
14515
|
if (!process.stdin.isTTY) {
|
|
14459
|
-
|
|
14516
|
+
out2("(no terminal to answer from; skipping)");
|
|
14460
14517
|
return "skip";
|
|
14461
14518
|
}
|
|
14462
14519
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
@@ -14477,17 +14534,17 @@ function verboseWanted(flag, interactive = Boolean(process.stdout.isTTY)) {
|
|
|
14477
14534
|
if (process.env.NANOPM_VERBOSE === "1") return true;
|
|
14478
14535
|
return !interactive;
|
|
14479
14536
|
}
|
|
14480
|
-
function rendererFor(
|
|
14481
|
-
if (!
|
|
14537
|
+
function rendererFor(out2) {
|
|
14538
|
+
if (!out2 && process.stdout.isTTY) {
|
|
14482
14539
|
const renderer = liveRenderer(process.stdout);
|
|
14483
14540
|
return { renderer, out: (l) => renderer.settle(l) };
|
|
14484
14541
|
}
|
|
14485
|
-
const plain =
|
|
14542
|
+
const plain = out2 ?? ((l) => console.log(l));
|
|
14486
14543
|
return { renderer: plainRenderer(plain), out: plain };
|
|
14487
14544
|
}
|
|
14488
14545
|
var TERMINAL = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
|
|
14489
14546
|
async function runSkill(skill, opts) {
|
|
14490
|
-
const { renderer, out } = rendererFor(opts.out);
|
|
14547
|
+
const { renderer, out: out2 } = rendererFor(opts.out);
|
|
14491
14548
|
const err = opts.err ?? ((l) => console.error(l));
|
|
14492
14549
|
const pollMs = opts.pollMs ?? 1e3;
|
|
14493
14550
|
const auth = await authenticate();
|
|
@@ -14501,10 +14558,10 @@ async function runSkill(skill, opts) {
|
|
|
14501
14558
|
target_daemon_id: daemonId
|
|
14502
14559
|
}).select("id").single();
|
|
14503
14560
|
if (error || !job) throw new Error(`could not queue the job: ${error?.message}`);
|
|
14504
|
-
|
|
14505
|
-
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);
|
|
14506
14563
|
const link = opts.slug ? conversationUrl(opts.slug, job.id) : void 0;
|
|
14507
|
-
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) });
|
|
14508
14565
|
}
|
|
14509
14566
|
async function streamJob(client, jobId, opts) {
|
|
14510
14567
|
const renderer = opts.renderer ?? plainRenderer(opts.out);
|
|
@@ -14620,7 +14677,7 @@ async function answeredElsewhere(client, jobId, seq, signal, pollMs) {
|
|
|
14620
14677
|
|
|
14621
14678
|
// src/commands/ask.ts
|
|
14622
14679
|
async function ask4(question, opts) {
|
|
14623
|
-
const
|
|
14680
|
+
const out2 = opts.out ?? ((l) => console.log(l));
|
|
14624
14681
|
if (!question.trim()) throw new Error("ask needs a question");
|
|
14625
14682
|
if (opts.all && opts.projectId) throw new Error("--all asks every project; drop --project, or drop --all");
|
|
14626
14683
|
const auth = await authenticate();
|
|
@@ -14629,17 +14686,17 @@ async function ask4(question, opts) {
|
|
|
14629
14686
|
let worst = 0;
|
|
14630
14687
|
for (const project2 of await memberProjects(auth.client, auth.userId, opts.cwd)) {
|
|
14631
14688
|
if (opts.cancelSignal?.aborted) break;
|
|
14632
|
-
|
|
14689
|
+
out2(`
|
|
14633
14690
|
${project2.name}`);
|
|
14634
|
-
const code = await askOne(auth, project2.id, question, daemonId, opts,
|
|
14691
|
+
const code = await askOne(auth, project2.id, question, daemonId, opts, out2);
|
|
14635
14692
|
worst = Math.max(worst, code);
|
|
14636
14693
|
}
|
|
14637
14694
|
return worst;
|
|
14638
14695
|
}
|
|
14639
14696
|
const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
|
|
14640
|
-
return askOne(auth, project.id, question, daemonId, opts,
|
|
14697
|
+
return askOne(auth, project.id, question, daemonId, opts, out2);
|
|
14641
14698
|
}
|
|
14642
|
-
async function askOne(auth, projectId, question, daemonId, opts,
|
|
14699
|
+
async function askOne(auth, projectId, question, daemonId, opts, out2) {
|
|
14643
14700
|
const { data: job, error } = await auth.client.from("jobs").insert({
|
|
14644
14701
|
project_id: projectId,
|
|
14645
14702
|
skill: "ask",
|
|
@@ -14652,7 +14709,7 @@ async function askOne(auth, projectId, question, daemonId, opts, out) {
|
|
|
14652
14709
|
pollMs: 700,
|
|
14653
14710
|
out: (line) => {
|
|
14654
14711
|
if (line.startsWith("\u203A") || line.startsWith("\u25CF") || line.startsWith("\u2713") || line.startsWith(" \u21BA")) return;
|
|
14655
|
-
|
|
14712
|
+
out2(line);
|
|
14656
14713
|
},
|
|
14657
14714
|
cancelSignal: opts.cancelSignal,
|
|
14658
14715
|
verbose: false
|
|
@@ -14660,17 +14717,17 @@ async function askOne(auth, projectId, question, daemonId, opts, out) {
|
|
|
14660
14717
|
}
|
|
14661
14718
|
|
|
14662
14719
|
// src/commands/daemon-service.ts
|
|
14663
|
-
import { execFile as
|
|
14720
|
+
import { execFile as execFile9 } from "child_process";
|
|
14664
14721
|
import { existsSync as existsSync6 } from "fs";
|
|
14665
14722
|
import { mkdir as mkdir7, readFile as readFile14, rm as rm6, writeFile as writeFile7 } from "fs/promises";
|
|
14666
14723
|
import { homedir as homedir5 } from "os";
|
|
14667
14724
|
import { join as join16 } from "path";
|
|
14668
|
-
import { promisify as
|
|
14725
|
+
import { promisify as promisify9 } from "util";
|
|
14669
14726
|
|
|
14670
14727
|
// package.json
|
|
14671
14728
|
var package_default = {
|
|
14672
14729
|
name: "@nanopm/cli",
|
|
14673
|
-
version: "0.1.
|
|
14730
|
+
version: "0.1.21",
|
|
14674
14731
|
description: "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
|
|
14675
14732
|
type: "module",
|
|
14676
14733
|
license: "MIT",
|
|
@@ -14719,7 +14776,7 @@ var package_default = {
|
|
|
14719
14776
|
var VERSION = package_default.version;
|
|
14720
14777
|
|
|
14721
14778
|
// src/commands/daemon-service.ts
|
|
14722
|
-
var execFileAsync8 =
|
|
14779
|
+
var execFileAsync8 = promisify9(execFile9);
|
|
14723
14780
|
var LAUNCHD_LABEL = "pm.nano.daemon";
|
|
14724
14781
|
function plistPath() {
|
|
14725
14782
|
return join16(homedir5(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
@@ -14761,7 +14818,7 @@ function requireMac() {
|
|
|
14761
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");
|
|
14762
14819
|
}
|
|
14763
14820
|
}
|
|
14764
|
-
async function installDaemon(
|
|
14821
|
+
async function installDaemon(out2 = (l) => console.log(l), opts = {}) {
|
|
14765
14822
|
requireMac();
|
|
14766
14823
|
if (!await loadCredentials()) throw new Error("not logged in; run `nanopm login` first");
|
|
14767
14824
|
const auth = await authenticate();
|
|
@@ -14772,16 +14829,16 @@ async function installDaemon(out = (l) => console.log(l), opts = {}) {
|
|
|
14772
14829
|
await found(detection);
|
|
14773
14830
|
throw new Error("Claude Code is not installed on this machine (`claude` not found on PATH)");
|
|
14774
14831
|
}
|
|
14775
|
-
|
|
14832
|
+
out2(`Claude Code ${detection.version} found; checking its login\u2026`);
|
|
14776
14833
|
const ping = await adapter.ping();
|
|
14777
14834
|
if (!ping.ok) {
|
|
14778
14835
|
await found({ ...detection, authenticated: false });
|
|
14779
14836
|
throw new Error(`Claude Code is installed but not usable headless: ${ping.message}`);
|
|
14780
14837
|
}
|
|
14781
|
-
|
|
14838
|
+
out2("Claude Code answers.");
|
|
14782
14839
|
const web = await adapter.probeWebSearch();
|
|
14783
14840
|
await rememberWebSearch(web.ok);
|
|
14784
|
-
|
|
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.`);
|
|
14785
14842
|
const bin = process.argv[1];
|
|
14786
14843
|
if (!bin) throw new Error("cannot locate the nanopm binary");
|
|
14787
14844
|
const home = homedir5();
|
|
@@ -14794,10 +14851,10 @@ async function installDaemon(out = (l) => console.log(l), opts = {}) {
|
|
|
14794
14851
|
const target = guiTarget();
|
|
14795
14852
|
await execFileAsync8("launchctl", ["bootout", target, plistPath()]).catch(() => void 0);
|
|
14796
14853
|
await execFileAsync8("launchctl", ["bootstrap", target, plistPath()]);
|
|
14797
|
-
|
|
14854
|
+
out2(`Installed ${LAUNCHD_LABEL}; the daemon starts now and at every login. Logs: ${daemonLogPath()}`);
|
|
14798
14855
|
const shared = opts.shared ?? await localShared();
|
|
14799
|
-
|
|
14800
|
-
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`);
|
|
14801
14858
|
}
|
|
14802
14859
|
function guiTarget() {
|
|
14803
14860
|
return `gui/${process.getuid?.() ?? 501}`;
|
|
@@ -14807,34 +14864,34 @@ async function restartDaemon() {
|
|
|
14807
14864
|
await execFileAsync8("launchctl", ["kickstart", "-k", `${guiTarget()}/${LAUNCHD_LABEL}`]);
|
|
14808
14865
|
return true;
|
|
14809
14866
|
}
|
|
14810
|
-
async function uninstallDaemon(
|
|
14867
|
+
async function uninstallDaemon(out2 = (l) => console.log(l)) {
|
|
14811
14868
|
requireMac();
|
|
14812
14869
|
const target = guiTarget();
|
|
14813
14870
|
await execFileAsync8("launchctl", ["bootout", target, plistPath()]).catch(() => void 0);
|
|
14814
14871
|
await rm6(plistPath(), { force: true });
|
|
14815
|
-
|
|
14872
|
+
out2(`Removed ${LAUNCHD_LABEL}.`);
|
|
14816
14873
|
}
|
|
14817
|
-
async function daemonStatus(
|
|
14874
|
+
async function daemonStatus(out2 = (l) => console.log(l)) {
|
|
14818
14875
|
const id = await localDaemonId();
|
|
14819
14876
|
const installed = process.platform === "darwin" && existsSync6(plistPath());
|
|
14820
|
-
|
|
14877
|
+
out2(`launchd: ${installed ? "installed" : "not installed"}`);
|
|
14821
14878
|
if (!id) {
|
|
14822
|
-
|
|
14879
|
+
out2("daemon: never registered on this machine");
|
|
14823
14880
|
return;
|
|
14824
14881
|
}
|
|
14825
14882
|
const auth = await authenticate();
|
|
14826
14883
|
const { data } = await auth.client.from("daemons").select("hostname, status, last_seen_at, version, harnesses, shared").eq("id", id).maybeSingle();
|
|
14827
14884
|
if (!data) {
|
|
14828
|
-
|
|
14885
|
+
out2(`daemon: ${id} is not in the account any more`);
|
|
14829
14886
|
return;
|
|
14830
14887
|
}
|
|
14831
14888
|
const seen = data.last_seen_at ? `${Math.round((Date.now() - new Date(data.last_seen_at).getTime()) / 1e3)}s ago` : "never";
|
|
14832
|
-
|
|
14833
|
-
|
|
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");
|
|
14834
14891
|
const { count: count2 } = await auth.client.from("jobs").select("id", { count: "exact", head: true }).eq("status", "queued");
|
|
14835
|
-
|
|
14892
|
+
out2(`queued jobs: ${count2 ?? 0}`);
|
|
14836
14893
|
const { data: running } = await auth.client.from("jobs").select("id, skill").eq("assigned_daemon_id", id).in("status", ["running", "waiting"]);
|
|
14837
|
-
for (const j of running ?? [])
|
|
14894
|
+
for (const j of running ?? []) out2(`running: ${j.skill} ${j.id}`);
|
|
14838
14895
|
}
|
|
14839
14896
|
async function daemonLogs(lines = 50) {
|
|
14840
14897
|
try {
|
|
@@ -14891,22 +14948,22 @@ function renderActivityLog(jobs, rows2, opts) {
|
|
|
14891
14948
|
const key = row.job_id ?? "";
|
|
14892
14949
|
byJob.set(key, [...byJob.get(key) ?? [], row]);
|
|
14893
14950
|
}
|
|
14894
|
-
const
|
|
14951
|
+
const out2 = [];
|
|
14895
14952
|
const ordered = [...jobs].sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
14896
14953
|
for (const job of ordered) {
|
|
14897
14954
|
const jobRows = byJob.get(job.id) ?? [];
|
|
14898
|
-
|
|
14899
|
-
if (job.note)
|
|
14900
|
-
for (const row of jobRows)
|
|
14901
|
-
|
|
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("");
|
|
14902
14959
|
byJob.delete(job.id);
|
|
14903
14960
|
}
|
|
14904
14961
|
const loose = [...byJob.values()].flat();
|
|
14905
14962
|
if (loose.length) {
|
|
14906
|
-
|
|
14907
|
-
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));
|
|
14908
14965
|
}
|
|
14909
|
-
return
|
|
14966
|
+
return out2.join("\n").trimEnd();
|
|
14910
14967
|
}
|
|
14911
14968
|
function jobHeader(job) {
|
|
14912
14969
|
const parts = [job.skill];
|
|
@@ -15008,17 +15065,17 @@ async function correctionNote(client, jobId) {
|
|
|
15008
15065
|
import { setTimeout as sleep4 } from "timers/promises";
|
|
15009
15066
|
|
|
15010
15067
|
// src/browser.ts
|
|
15011
|
-
import { execFile as
|
|
15068
|
+
import { execFile as execFile10 } from "child_process";
|
|
15012
15069
|
function openBrowser(url, interactive = Boolean(process.stdout.isTTY)) {
|
|
15013
15070
|
if (!interactive) return false;
|
|
15014
15071
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
15015
|
-
|
|
15072
|
+
execFile10(cmd, [url], () => void 0);
|
|
15016
15073
|
return true;
|
|
15017
15074
|
}
|
|
15018
15075
|
|
|
15019
15076
|
// src/commands/login.ts
|
|
15020
15077
|
async function login(opts = {}) {
|
|
15021
|
-
const
|
|
15078
|
+
const out2 = opts.out ?? ((l) => console.log(l));
|
|
15022
15079
|
const web = (opts.web ?? webUrl()).replace(/\/+$/, "");
|
|
15023
15080
|
const config = supabaseConfigFromEnv();
|
|
15024
15081
|
let code = generateDeviceCode();
|
|
@@ -15037,8 +15094,8 @@ async function login(opts = {}) {
|
|
|
15037
15094
|
throw new Error(`nano.pm refused to start a login (${res.status}): ${await res.text()}`);
|
|
15038
15095
|
}
|
|
15039
15096
|
const url = `${web}/device?code=${code}`;
|
|
15040
|
-
|
|
15041
|
-
|
|
15097
|
+
out2(`Your code: ${formatDeviceCode(code)}`);
|
|
15098
|
+
out2(`Confirm it at ${url}`);
|
|
15042
15099
|
if (!opts.noBrowser) openBrowser(url);
|
|
15043
15100
|
const deadline = Date.now() + DEVICE_CODE_TTL_MS;
|
|
15044
15101
|
const pollMs = opts.pollMs ?? 2e3;
|
|
@@ -15069,58 +15126,58 @@ async function login(opts = {}) {
|
|
|
15069
15126
|
expiresAt: data.session.expires_at ?? session2.expires_at
|
|
15070
15127
|
});
|
|
15071
15128
|
const { data: profile } = await client.from("users").select("display_name, github_login").eq("id", data.session.user.id).maybeSingle();
|
|
15072
|
-
|
|
15129
|
+
out2(`Logged in as ${profile?.github_login ?? profile?.display_name ?? data.session.user.email ?? data.session.user.id}.`);
|
|
15073
15130
|
if (!opts.noDaemon) {
|
|
15074
15131
|
try {
|
|
15075
|
-
await installDaemon(
|
|
15132
|
+
await installDaemon(out2, { next: false });
|
|
15076
15133
|
} catch (err) {
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
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.");
|
|
15080
15137
|
return;
|
|
15081
15138
|
}
|
|
15082
15139
|
}
|
|
15083
|
-
|
|
15084
|
-
|
|
15140
|
+
out2("");
|
|
15141
|
+
out2(`Your machine is linked. Continue at ${web}/new`);
|
|
15085
15142
|
}
|
|
15086
|
-
async function logout(
|
|
15143
|
+
async function logout(out2 = (l) => console.log(l)) {
|
|
15087
15144
|
const stored = await loadCredentials();
|
|
15088
15145
|
if (!stored) {
|
|
15089
|
-
|
|
15146
|
+
out2("Not logged in.");
|
|
15090
15147
|
return;
|
|
15091
15148
|
}
|
|
15092
15149
|
const client = createNanopmClient({ url: stored.supabaseUrl, anonKey: stored.anonKey });
|
|
15093
15150
|
const { error } = await client.auth.setSession({ access_token: stored.accessToken, refresh_token: stored.refreshToken });
|
|
15094
15151
|
if (!error) await client.auth.signOut({ scope: "local" });
|
|
15095
15152
|
await deleteCredentials();
|
|
15096
|
-
|
|
15153
|
+
out2("Logged out. The daemon on this machine will stop taking jobs at its next start.");
|
|
15097
15154
|
}
|
|
15098
15155
|
|
|
15099
15156
|
// src/commands/update.ts
|
|
15100
|
-
async function update(opts,
|
|
15157
|
+
async function update(opts, out2 = (l) => console.log(l)) {
|
|
15101
15158
|
if (opts.auto !== void 0) {
|
|
15102
15159
|
if (opts.auto !== "on" && opts.auto !== "off") throw new Error("--auto takes on or off");
|
|
15103
15160
|
await setAutoUpdate(opts.auto === "on");
|
|
15104
|
-
|
|
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.");
|
|
15105
15162
|
return;
|
|
15106
15163
|
}
|
|
15107
15164
|
if (installKind() !== "installer") {
|
|
15108
|
-
|
|
15109
|
-
|
|
15110
|
-
|
|
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");
|
|
15111
15168
|
return;
|
|
15112
15169
|
}
|
|
15113
15170
|
const latest = await latestVersion();
|
|
15114
15171
|
if (!latest) throw new Error("could not reach the npm registry; try again in a moment");
|
|
15115
15172
|
if (!newer(latest, VERSION)) {
|
|
15116
|
-
|
|
15173
|
+
out2(`Already up to date (${VERSION}).`);
|
|
15117
15174
|
return;
|
|
15118
15175
|
}
|
|
15119
|
-
|
|
15176
|
+
out2(`Updating ${VERSION} \u2192 ${latest}\u2026`);
|
|
15120
15177
|
const done = await installVersion(latest);
|
|
15121
15178
|
if (!done.ok) throw new Error(`${done.why}
|
|
15122
15179
|
The install line does the same: curl -fsSL https://nanopm.app/install | sh`);
|
|
15123
|
-
|
|
15180
|
+
out2(await restartDaemon() ? `Updated ${VERSION} \u2192 ${latest}; the daemon restarted on it.` : `Updated ${VERSION} \u2192 ${latest}.`);
|
|
15124
15181
|
}
|
|
15125
15182
|
|
|
15126
15183
|
// src/commands/autonomy.ts
|
|
@@ -15188,51 +15245,51 @@ async function verifyCommand(command, opts) {
|
|
|
15188
15245
|
}
|
|
15189
15246
|
|
|
15190
15247
|
// src/commands/build.ts
|
|
15191
|
-
import { execFile as
|
|
15248
|
+
import { execFile as execFile11 } from "child_process";
|
|
15192
15249
|
import { existsSync as existsSync7 } from "fs";
|
|
15193
15250
|
import { homedir as homedir6 } from "os";
|
|
15194
|
-
import { promisify as
|
|
15251
|
+
import { promisify as promisify10 } from "util";
|
|
15195
15252
|
|
|
15196
15253
|
// src/format/opportunities.ts
|
|
15197
15254
|
function renderOpportunities(rows2, lens, opts) {
|
|
15198
15255
|
const paint = painter(opts.color);
|
|
15199
15256
|
const width = Math.max(40, columns());
|
|
15200
|
-
const
|
|
15257
|
+
const out2 = [];
|
|
15201
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`.";
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
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."));
|
|
15205
15262
|
if (!rows2.length) {
|
|
15206
|
-
|
|
15207
|
-
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");
|
|
15208
15265
|
}
|
|
15209
15266
|
const tree = liveTree(rows2);
|
|
15210
15267
|
const live = rows2.filter(isLive);
|
|
15211
15268
|
const evidenced = live.filter((r) => r.evidence_status === "evidence_backed").length;
|
|
15212
|
-
|
|
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.`));
|
|
15213
15270
|
const who = (r) => r.personas.map((p) => opts.names?.get(p) ?? p).join(", ");
|
|
15214
15271
|
const one = (r, place) => {
|
|
15215
15272
|
const indent = " ".repeat(r.depth - 1);
|
|
15216
15273
|
const yours = r.status === "confirmed" ? paint(ANSI.green, " yours") : "";
|
|
15217
|
-
|
|
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)}`);
|
|
15218
15275
|
const pad = `${indent} `;
|
|
15219
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 ");
|
|
15220
|
-
|
|
15277
|
+
out2.push(`${pad}${paint(ANSI.dim, facts)}`);
|
|
15221
15278
|
const gist = r.description ? paragraphs(r.description)[0] : null;
|
|
15222
|
-
if (gist) for (const l of wrap(gist, width - pad.length))
|
|
15223
|
-
if (r.learn) for (const l of wrap(`To learn more: ${r.learn}`, width - pad.length))
|
|
15224
|
-
if (r.founder_rank !== null && r.now_rank)
|
|
15225
|
-
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)}`);
|
|
15226
15283
|
for (const c of r.children) one(c, `${place}.${c.rank}`);
|
|
15227
15284
|
};
|
|
15228
15285
|
for (const r of tree) one(r, String(r.rank));
|
|
15229
15286
|
const aside = rows2.filter((r) => r.status === "set_aside");
|
|
15230
15287
|
if (aside.length) {
|
|
15231
|
-
|
|
15232
|
-
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}`) : ""}`);
|
|
15233
15290
|
}
|
|
15234
|
-
|
|
15235
|
-
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");
|
|
15236
15293
|
}
|
|
15237
15294
|
|
|
15238
15295
|
// src/format/needs.ts
|
|
@@ -15240,12 +15297,12 @@ var MARK = { observed: "\u25CF", stated: "\u2013", inferred: "\u25CB" };
|
|
|
15240
15297
|
function renderNeeds(persona, tree, opts) {
|
|
15241
15298
|
const paint = painter(opts.color);
|
|
15242
15299
|
const width = Math.max(40, columns());
|
|
15243
|
-
const
|
|
15300
|
+
const out2 = [];
|
|
15244
15301
|
const counts = needCounts(tree);
|
|
15245
|
-
|
|
15302
|
+
out2.push(`${paint(ANSI.bold, `${persona.name}\u2019s needs`)} ${paint(ANSI.dim, `${counts.needs} needs \xB7 ${counts.difficulties + counts.precise} difficulties`)}`);
|
|
15246
15303
|
if (!tree.length) {
|
|
15247
|
-
|
|
15248
|
-
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");
|
|
15249
15306
|
}
|
|
15250
15307
|
const line = (n, depth) => {
|
|
15251
15308
|
const indent = " ".repeat(depth);
|
|
@@ -15254,17 +15311,17 @@ function renderNeeds(persona, tree, opts) {
|
|
|
15254
15311
|
const state = n.status === "dropped" ? paint(ANSI.dim, " dropped") : n.status === "confirmed" || n.author_kind === "user" ? "" : paint(ANSI.amber, " to review");
|
|
15255
15312
|
const label = n.status === "dropped" ? paint(ANSI.dim, n.label) : n.level === 1 ? paint(ANSI.bold, n.label) : n.label;
|
|
15256
15313
|
const body = wrap(`${label}${where}${state}${n.proposal ? paint(ANSI.amber, " \xB7 NanoPM suggests a new wording") : ""}`, width - indent.length - 12);
|
|
15257
|
-
|
|
15258
|
-
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}`);
|
|
15259
15316
|
for (const c of n.children) line(c, depth + 1);
|
|
15260
15317
|
};
|
|
15261
15318
|
for (const need of tree) {
|
|
15262
|
-
|
|
15319
|
+
out2.push("");
|
|
15263
15320
|
line(need, 0);
|
|
15264
15321
|
}
|
|
15265
|
-
|
|
15266
|
-
if (opts.others?.length)
|
|
15267
|
-
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");
|
|
15268
15325
|
}
|
|
15269
15326
|
|
|
15270
15327
|
// src/commands/needs.ts
|
|
@@ -15464,11 +15521,11 @@ async function brainstorm(ref, opts) {
|
|
|
15464
15521
|
const minutes = deep ? DEEP_MINUTES * n : FLASH_MINUTES;
|
|
15465
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`;
|
|
15466
15523
|
const how = deep ? `${DEPTH_WORD.deep}${n > 1 ? ", one after the other" : ""}` : `${DEPTH_WORD.quick}, a first pass`;
|
|
15467
|
-
const
|
|
15468
|
-
if (plan.problem.children.length) for (const l of plan.leaves)
|
|
15469
|
-
if (plan.busy)
|
|
15470
|
-
|
|
15471
|
-
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");
|
|
15472
15529
|
}
|
|
15473
15530
|
|
|
15474
15531
|
// src/format/roadmap.ts
|
|
@@ -15484,11 +15541,11 @@ var WORK_KINDS = /* @__PURE__ */ new Set(["branch", "git", "url", "job"]);
|
|
|
15484
15541
|
function renderRoadmapItem(item, opts, problem, builtBy) {
|
|
15485
15542
|
const paint = painter(opts.color);
|
|
15486
15543
|
const shape2 = SHAPE[item.status] ?? { mark: "\xB7", colour: "", label: item.status };
|
|
15487
|
-
const
|
|
15488
|
-
if (problem)
|
|
15489
|
-
|
|
15490
|
-
for (const line of wrap(item.reasoning, Math.max(40, columns() - 4)))
|
|
15491
|
-
|
|
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("");
|
|
15492
15549
|
const facts = [
|
|
15493
15550
|
["status", shape2.label + (item.outcome ? ` \xB7 ${item.outcome}${item.outcome_note ? `: ${item.outcome_note}` : ""}` : "")],
|
|
15494
15551
|
["rank", String(item.rank)],
|
|
@@ -15496,15 +15553,15 @@ function renderRoadmapItem(item, opts, problem, builtBy) {
|
|
|
15496
15553
|
["bets on", item.assumption ?? "\u2014"],
|
|
15497
15554
|
["we'll know", item.eval ?? "\u2014"]
|
|
15498
15555
|
];
|
|
15499
|
-
for (const [k, v] of facts)
|
|
15556
|
+
for (const [k, v] of facts) out2.push(paint(ANSI.dim, ` ${k.padEnd(9)} ${v}`));
|
|
15500
15557
|
const sources2 = item.sources ?? [];
|
|
15501
15558
|
const restsOn = sources2.filter((s) => !WORK_KINDS.has(s.kind));
|
|
15502
15559
|
if (restsOn.length) {
|
|
15503
|
-
|
|
15560
|
+
out2.push(paint(ANSI.dim, ` ${"rests on".padEnd(9)} ${restsOn.map((s) => `${s.kind}:${s.ref}`).join(", ")}`));
|
|
15504
15561
|
}
|
|
15505
15562
|
const where = renderWhere(sources2);
|
|
15506
|
-
if (where)
|
|
15507
|
-
return
|
|
15563
|
+
if (where) out2.push(paint(ANSI.dim, ` ${"where".padEnd(9)} ${where}${builtBy ? ` \xB7 built by ${builtBy}` : ""}`));
|
|
15564
|
+
return out2.join("\n");
|
|
15508
15565
|
}
|
|
15509
15566
|
function renderWhere(sources2) {
|
|
15510
15567
|
const branch = sources2.find((s) => s.kind === "branch")?.ref;
|
|
@@ -15529,18 +15586,18 @@ var VERB = {
|
|
|
15529
15586
|
function renderDecisions(rows2, opts) {
|
|
15530
15587
|
const paint = painter(opts.color);
|
|
15531
15588
|
if (!rows2.length) return "No decisions yet.";
|
|
15532
|
-
const
|
|
15589
|
+
const out2 = [];
|
|
15533
15590
|
for (const row of rows2) {
|
|
15534
15591
|
const when = row.created_at.slice(0, 10);
|
|
15535
15592
|
const verb = VERB[row.kind] ?? { text: row.kind, colour: ANSI.blue };
|
|
15536
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() : "";
|
|
15537
15594
|
const by = row.by ? paint(ANSI.dim, ` \xB7 by ${row.by}`) : "";
|
|
15538
|
-
|
|
15595
|
+
out2.push(`${paint(ANSI.dim, when)} ${paint(verb.colour, verb.text)} ${about}${by}`.trimEnd());
|
|
15539
15596
|
if (row.body) {
|
|
15540
|
-
for (const line of wrap(row.body, 72))
|
|
15597
|
+
for (const line of wrap(row.body, 72)) out2.push(paint(ANSI.dim, ` ${line}`));
|
|
15541
15598
|
}
|
|
15542
15599
|
}
|
|
15543
|
-
return
|
|
15600
|
+
return out2.join("\n");
|
|
15544
15601
|
}
|
|
15545
15602
|
var HUE = ["\x1B[34m", "\x1B[35m", "\x1B[36m", "\x1B[32m", "\x1B[95m"];
|
|
15546
15603
|
function themesOf(tree) {
|
|
@@ -15553,7 +15610,7 @@ function renderSolutionsBoard(board, opts) {
|
|
|
15553
15610
|
const paint = painter(opts.color);
|
|
15554
15611
|
const width = Math.max(40, columns());
|
|
15555
15612
|
const themes = themesOf(board.tree);
|
|
15556
|
-
const
|
|
15613
|
+
const out2 = [`${paint(ANSI.bold, "Roadmap")}${board.objective ? ` ${paint(ANSI.dim, `${board.objective.lever} \u2014 ${board.objective.sentence}`)}` : ""}`];
|
|
15557
15614
|
const chips = board.tree.flatMap((b) => {
|
|
15558
15615
|
const n = board.cards.filter((c) => c.branch.id === b.id).length;
|
|
15559
15616
|
const t = themes.get(b.id);
|
|
@@ -15565,89 +15622,89 @@ function renderSolutionsBoard(board, opts) {
|
|
|
15565
15622
|
else lines.push(chip);
|
|
15566
15623
|
return lines;
|
|
15567
15624
|
}, []);
|
|
15568
|
-
|
|
15569
|
-
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."));
|
|
15570
15627
|
const refWidth = Math.max(5, ...board.cards.map((c) => c.pick.ref.length));
|
|
15571
15628
|
const gutter = " ".repeat(refWidth + 4);
|
|
15572
15629
|
const titleWidth = Math.min(Math.max(...board.cards.map((c) => c.pick.title.length)), width - gutter.length - 8);
|
|
15573
15630
|
for (const h of HORIZONS) {
|
|
15574
15631
|
const column = board.cards.filter((c) => c.horizon === h);
|
|
15575
|
-
|
|
15576
|
-
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}`));
|
|
15577
15634
|
for (const c of column) {
|
|
15578
15635
|
const t = themes.get(c.branch.id);
|
|
15579
|
-
|
|
15580
|
-
|
|
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());
|
|
15581
15638
|
const depth = board.working.get(c.leaf.id);
|
|
15582
|
-
if (depth)
|
|
15639
|
+
if (depth) out2.push(`${gutter}${paint(ANSI.blue, "\u25CF")} ${paint(ANSI.dim, WORK_WORD[depth])}`);
|
|
15583
15640
|
}
|
|
15584
15641
|
}
|
|
15585
|
-
if (board.earlier)
|
|
15586
|
-
|
|
15642
|
+
if (board.earlier) out2.push("", paint(ANSI.bold, "On the problem list"), "", board.earlier);
|
|
15643
|
+
out2.push(
|
|
15587
15644
|
"",
|
|
15588
15645
|
paint(ANSI.dim, 'nanopm roadmap <ref> \xB7 nanopm roadmap move <ref> now|next|later [position] \xB7 nanopm reject <ref> ["why"]'),
|
|
15589
15646
|
paint(ANSI.dim, "nanopm opportunities brainstorm <ref> [--deep]: solutions to another problem, or deeper ones")
|
|
15590
15647
|
);
|
|
15591
|
-
return
|
|
15648
|
+
return out2.join("\n");
|
|
15592
15649
|
}
|
|
15593
15650
|
function renderSolutionSheet(s, at, opts) {
|
|
15594
15651
|
const paint = painter(opts.color);
|
|
15595
15652
|
const width = Math.max(40, columns());
|
|
15596
15653
|
const theme = at.branch ? themesOf(at.tree).get(at.branch.id) : void 0;
|
|
15597
|
-
const
|
|
15598
|
-
if (theme)
|
|
15654
|
+
const out2 = [];
|
|
15655
|
+
if (theme) out2.push(`${paint(theme.colour, "\u25CF")} ${paint(ANSI.dim, theme.name)}`);
|
|
15599
15656
|
const size = sizeWord(s.size, paint);
|
|
15600
|
-
|
|
15657
|
+
out2.push(`${paint(ANSI.dim, s.ref)} ${paint(ANSI.bold, s.title)}${size ? ` ${size}` : ""}`);
|
|
15601
15658
|
const pad = " ".repeat(12);
|
|
15602
|
-
const heading = (text2) =>
|
|
15659
|
+
const heading = (text2) => out2.push("", ` ${paint(ANSI.bold, text2)}`);
|
|
15603
15660
|
heading("Problem we are solving");
|
|
15604
15661
|
if (at.leaf) {
|
|
15605
15662
|
const solves = wrap(at.leaf.problem, width - 6 - at.leaf.ref.length - 2);
|
|
15606
15663
|
solves[solves.length - 1] += ` ${paint(ANSI.dim, at.leaf.ref)}`;
|
|
15607
|
-
for (const line of solves)
|
|
15664
|
+
for (const line of solves) out2.push(` ${line}`);
|
|
15608
15665
|
if (at.branch && at.branch.id !== at.leaf.id) {
|
|
15609
|
-
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)}`);
|
|
15610
15667
|
}
|
|
15611
15668
|
} else {
|
|
15612
|
-
|
|
15669
|
+
out2.push(paint(ANSI.dim, " The problem it answered is no longer on the map."));
|
|
15613
15670
|
}
|
|
15614
15671
|
const described = paragraphs(s.description);
|
|
15615
15672
|
if (described.length) {
|
|
15616
15673
|
heading("Proposed solution");
|
|
15617
15674
|
described.forEach((p, i) => {
|
|
15618
|
-
if (i)
|
|
15619
|
-
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}`);
|
|
15620
15677
|
});
|
|
15621
15678
|
}
|
|
15622
15679
|
const changelog = parseChangelog(s.changelog);
|
|
15623
15680
|
if (changelog) {
|
|
15624
15681
|
heading("Future changelog");
|
|
15625
|
-
for (const line of wrap(changelog.headline, width - 6))
|
|
15626
|
-
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}`));
|
|
15627
15684
|
}
|
|
15628
15685
|
if (s.steps.length) {
|
|
15629
15686
|
heading("Plan");
|
|
15630
15687
|
s.steps.forEach((step, i) => {
|
|
15631
15688
|
const who = STEP_WHO_WORD[step.who] ?? step.who;
|
|
15632
|
-
wrap(step.do, width - pad.length - 8).forEach((line, j) =>
|
|
15633
|
-
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"));
|
|
15634
15691
|
});
|
|
15635
15692
|
}
|
|
15636
15693
|
const card = at.leaf ? at.cards.find((c) => c.leaf.id === at.leaf.id) : void 0;
|
|
15637
15694
|
const others = card ? card.others.filter((o) => o.id !== s.id) : [];
|
|
15638
|
-
|
|
15639
|
-
if (s.status === "rejected")
|
|
15695
|
+
out2.push("");
|
|
15696
|
+
if (s.status === "rejected") out2.push(paint(ANSI.dim, " Thrown out."));
|
|
15640
15697
|
else if (card?.pick.id === s.id) {
|
|
15641
15698
|
const column = at.cards.filter((c) => c.horizon === card.horizon);
|
|
15642
15699
|
const yours = s.placed_by === "founder" ? ", where you put it" : "";
|
|
15643
|
-
|
|
15644
|
-
} else
|
|
15645
|
-
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(", ")}.`));
|
|
15646
15703
|
if (s.status === "proposed") {
|
|
15647
15704
|
const move = card?.pick.id === s.id ? `nanopm roadmap move ${s.ref} now|next|later [position] \xB7 ` : "";
|
|
15648
|
-
|
|
15705
|
+
out2.push(paint(ANSI.dim, ` ${move}nanopm reject ${s.ref} ["why"]`));
|
|
15649
15706
|
}
|
|
15650
|
-
return
|
|
15707
|
+
return out2.join("\n");
|
|
15651
15708
|
}
|
|
15652
15709
|
|
|
15653
15710
|
// src/format/problems.ts
|
|
@@ -15677,23 +15734,23 @@ function renderProblems(problems2, duplicates, opts) {
|
|
|
15677
15734
|
].join("\n");
|
|
15678
15735
|
}
|
|
15679
15736
|
const refWidth = Math.max(...problems2.map((p) => p.ref.length));
|
|
15680
|
-
const
|
|
15737
|
+
const out2 = [];
|
|
15681
15738
|
const map = groupProblemMap(problems2.map((p) => ({ ...p, parentRef: p.parent_ref })));
|
|
15682
15739
|
const ordered = [...map.branches.flatMap((b) => [b.parent, ...b.children]), ...map.unplaced];
|
|
15683
15740
|
for (const problem of ordered) {
|
|
15684
15741
|
const shape2 = SHAPE2[problem.status] ?? { mark: "\xB7", colour: ANSI.dim, label: problem.status };
|
|
15685
15742
|
const score = renderImportance(problem, opts);
|
|
15686
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))}`;
|
|
15687
|
-
|
|
15688
|
-
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"}`));
|
|
15689
15746
|
const indent = ` ${" ".repeat(refWidth)} `;
|
|
15690
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";
|
|
15691
|
-
|
|
15748
|
+
out2.push(paint(ANSI.dim, indent + clip(tail, Math.max(40, columns() - indent.length))));
|
|
15692
15749
|
}
|
|
15693
15750
|
const unproven = problems2.filter((p) => p.proofs === 0).length;
|
|
15694
15751
|
if (unproven) {
|
|
15695
|
-
|
|
15696
|
-
|
|
15752
|
+
out2.push("");
|
|
15753
|
+
out2.push(
|
|
15697
15754
|
paint(
|
|
15698
15755
|
ANSI.dim,
|
|
15699
15756
|
`${unproven} of ${problems2.length} rest on judgement, not proof. A tilde marks those.`
|
|
@@ -15701,45 +15758,45 @@ function renderProblems(problems2, duplicates, opts) {
|
|
|
15701
15758
|
);
|
|
15702
15759
|
}
|
|
15703
15760
|
if (duplicates.length) {
|
|
15704
|
-
|
|
15761
|
+
out2.push("");
|
|
15705
15762
|
for (const pair of duplicates.slice(0, 3)) {
|
|
15706
15763
|
const why = pair.flagged ? `NanoPM is not sure ${pair.a} and ${pair.b} are different problems` : `${pair.a} and ${pair.b} read alike`;
|
|
15707
|
-
|
|
15764
|
+
out2.push(paint(ANSI.amber, `? ${why}`));
|
|
15708
15765
|
}
|
|
15709
|
-
|
|
15766
|
+
out2.push(paint(ANSI.dim, ` nanopm decisions why \xB7 nanopm problems merge ${duplicates[0].b} ${duplicates[0].a}`));
|
|
15710
15767
|
}
|
|
15711
15768
|
if (opts.manualMerges !== void 0) {
|
|
15712
|
-
|
|
15713
|
-
|
|
15769
|
+
out2.push("");
|
|
15770
|
+
out2.push(
|
|
15714
15771
|
paint(
|
|
15715
15772
|
ANSI.dim,
|
|
15716
15773
|
`${opts.manualMerges} merge${opts.manualMerges === 1 ? "" : "s"} you have had to make by hand.`
|
|
15717
15774
|
)
|
|
15718
15775
|
);
|
|
15719
15776
|
}
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
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");
|
|
15723
15780
|
}
|
|
15724
15781
|
function renderProblem(problem, evidence2, addressedBy, opts) {
|
|
15725
15782
|
const paint = painter(opts.color);
|
|
15726
15783
|
const shape2 = SHAPE2[problem.status] ?? { mark: "\xB7", colour: ANSI.dim, label: problem.status };
|
|
15727
15784
|
const width = columns();
|
|
15728
|
-
const
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
for (const line of wrap(problem.statement, width))
|
|
15732
|
-
|
|
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("");
|
|
15733
15790
|
const facts = [];
|
|
15734
15791
|
const m = problem.mapping ?? null;
|
|
15735
15792
|
if (m) {
|
|
15736
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 ?? "" });
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
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"));
|
|
15743
15800
|
facts.push(["kind", `${m.level === 1 ? "a need" : "a difficulty"} \xB7 ${m.knowledge} \xB7 ${m.origin === "expert_inference" ? "expert inference" : "source analysis"}`]);
|
|
15744
15801
|
if (m.parent_reason) facts.push(["grouping", m.parent_reason]);
|
|
15745
15802
|
}
|
|
@@ -15750,31 +15807,31 @@ function renderProblem(problem, evidence2, addressedBy, opts) {
|
|
|
15750
15807
|
]);
|
|
15751
15808
|
if (problem.parent_ref) facts.push(["part of", problem.parent_ref]);
|
|
15752
15809
|
for (const [label, value] of facts) {
|
|
15753
|
-
|
|
15810
|
+
out2.push(`${paint(ANSI.dim, label.padEnd(9))}${clip(value, width - 9)}`);
|
|
15754
15811
|
}
|
|
15755
15812
|
if (evidence2.length) {
|
|
15756
|
-
|
|
15757
|
-
|
|
15813
|
+
out2.push("");
|
|
15814
|
+
out2.push(paint(ANSI.dim, "sources"));
|
|
15758
15815
|
for (const item of evidence2) {
|
|
15759
15816
|
const when = (item.occurred_at ?? item.created_at).slice(0, 10);
|
|
15760
15817
|
const where = item.metric_key ?? item.source_ref ?? item.kind;
|
|
15761
|
-
for (const line of wrap(item.body, width - 2))
|
|
15762
|
-
|
|
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}`));
|
|
15763
15820
|
}
|
|
15764
15821
|
} else {
|
|
15765
|
-
|
|
15766
|
-
|
|
15822
|
+
out2.push("");
|
|
15823
|
+
out2.push(paint(ANSI.dim, "No proof yet. This ranking is a judgement, and the next review may settle it."));
|
|
15767
15824
|
}
|
|
15768
15825
|
if (addressedBy.length) {
|
|
15769
|
-
|
|
15770
|
-
|
|
15826
|
+
out2.push("");
|
|
15827
|
+
out2.push(paint(ANSI.dim, "addressed by"));
|
|
15771
15828
|
for (const item of addressedBy) {
|
|
15772
|
-
|
|
15829
|
+
out2.push(` ${paint(ANSI.dim, item.ref)} ${clip(item.title, width - 12)} ${paint(ANSI.dim, `(${item.status})`)}`);
|
|
15773
15830
|
}
|
|
15774
15831
|
}
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
return
|
|
15832
|
+
out2.push("");
|
|
15833
|
+
out2.push(paint(ANSI.dim, " nanopm problems \xB7 nanopm roadmap"));
|
|
15834
|
+
return out2.join("\n");
|
|
15778
15835
|
}
|
|
15779
15836
|
|
|
15780
15837
|
// src/format/tree.ts
|
|
@@ -15796,49 +15853,49 @@ var NO_AREA = "no area yet";
|
|
|
15796
15853
|
var SHOWN = /* @__PURE__ */ new Set(["proposed", "accepted", "building", "done"]);
|
|
15797
15854
|
function renderTree(problems2, solutions, strategy, opts) {
|
|
15798
15855
|
const paint = painter(opts.color);
|
|
15799
|
-
const
|
|
15856
|
+
const out2 = [];
|
|
15800
15857
|
const width = Math.max(40, columns());
|
|
15801
15858
|
if (strategy) {
|
|
15802
|
-
|
|
15803
|
-
for (const line of wrap(strategy, width - 4))
|
|
15804
|
-
|
|
15859
|
+
out2.push(paint(ANSI.bold, "Strategy"));
|
|
15860
|
+
for (const line of wrap(strategy, width - 4)) out2.push(` ${line}`);
|
|
15861
|
+
out2.push("");
|
|
15805
15862
|
}
|
|
15806
15863
|
const shown = solutions.filter((s) => SHOWN.has(s.status));
|
|
15807
15864
|
const open = problems2.filter((p) => p.status !== "dropped" && (!opts.alongside || shown.some((s) => s.problem_id === p.id)));
|
|
15808
15865
|
if (opts.alongside && !open.length) return "";
|
|
15809
15866
|
if (!open.length && !shown.length) {
|
|
15810
|
-
|
|
15811
|
-
return
|
|
15867
|
+
out2.push("No problems yet. `nanopm opportunities` shows what to work on.");
|
|
15868
|
+
return out2.join("\n");
|
|
15812
15869
|
}
|
|
15813
15870
|
const byProblem = /* @__PURE__ */ new Map();
|
|
15814
15871
|
for (const s of shown) byProblem.set(s.problem_id, [...byProblem.get(s.problem_id) ?? [], s]);
|
|
15815
15872
|
const refWidth = Math.max(...[...open.map((p) => p.ref), ...shown.map((s) => s.ref)].map((r) => r.length), 5);
|
|
15816
15873
|
for (const [area, rows2] of groupByArea(open)) {
|
|
15817
|
-
|
|
15874
|
+
out2.push(paint(ANSI.bold, area));
|
|
15818
15875
|
for (const p of rows2) {
|
|
15819
15876
|
const shape2 = PROBLEM_SHAPE[p.status] ?? { mark: "\xB7", colour: "" };
|
|
15820
15877
|
const kind = p.kind === "founder" ? paint(ANSI.dim, " (founder)") : "";
|
|
15821
15878
|
const proof = p.proofs ? `${p.proofs} proof${p.proofs === 1 ? "" : "s"}` : "no proof";
|
|
15822
15879
|
const tail = `${renderImportance(p, opts)} ${paint(ANSI.dim, proof)}`;
|
|
15823
15880
|
const head = ` ${paint(shape2.colour, shape2.mark)} ${paint(ANSI.dim, p.ref.padEnd(refWidth))} `;
|
|
15824
|
-
|
|
15881
|
+
out2.push(head + clip(p.statement, Math.max(30, width - visibleLength(head) - visibleLength(tail) - 2)) + kind + " " + tail);
|
|
15825
15882
|
const under = byProblem.get(p.id) ?? [];
|
|
15826
15883
|
if (!under.length) {
|
|
15827
|
-
|
|
15884
|
+
out2.push(paint(ANSI.dim, ` ${" ".repeat(refWidth + 2)} no solution yet`));
|
|
15828
15885
|
continue;
|
|
15829
15886
|
}
|
|
15830
|
-
for (const s of under)
|
|
15887
|
+
for (const s of under) out2.push(...solutionLines(s, refWidth, width, paint));
|
|
15831
15888
|
}
|
|
15832
|
-
|
|
15889
|
+
out2.push("");
|
|
15833
15890
|
}
|
|
15834
15891
|
const waiting = shown.filter((s) => s.status === "proposed").length;
|
|
15835
15892
|
if (waiting) {
|
|
15836
|
-
|
|
15837
|
-
|
|
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'));
|
|
15838
15895
|
} else if (!opts.alongside) {
|
|
15839
|
-
|
|
15896
|
+
out2.push(paint(ANSI.dim, "Nothing waiting on you."));
|
|
15840
15897
|
}
|
|
15841
|
-
return
|
|
15898
|
+
return out2.join("\n").trimEnd();
|
|
15842
15899
|
}
|
|
15843
15900
|
function solutionLines(s, refWidth, width, paint, indent = 6) {
|
|
15844
15901
|
const shape2 = SOLUTION_SHAPE[s.status] ?? { mark: "\xB7", colour: "", label: s.status };
|
|
@@ -15870,25 +15927,25 @@ var BOARD_COLUMNS = [
|
|
|
15870
15927
|
];
|
|
15871
15928
|
function renderBoard(problems2, solutions, opts) {
|
|
15872
15929
|
const paint = painter(opts.color);
|
|
15873
|
-
const
|
|
15930
|
+
const out2 = [];
|
|
15874
15931
|
const byId = new Map(problems2.map((p) => [p.id, p]));
|
|
15875
15932
|
const width = Math.max(40, columns());
|
|
15876
15933
|
const refWidth = Math.max(...solutions.map((s) => s.ref.length), 5);
|
|
15877
15934
|
for (const col of BOARD_COLUMNS) {
|
|
15878
15935
|
const rows2 = solutions.filter((s) => s.status === col.status);
|
|
15879
|
-
|
|
15880
|
-
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"));
|
|
15881
15938
|
for (const s of rows2) {
|
|
15882
15939
|
const shape2 = SOLUTION_SHAPE[s.status];
|
|
15883
15940
|
const problem = byId.get(s.problem_id);
|
|
15884
15941
|
const head = ` ${paint(shape2.colour, shape2.mark)} ${paint(ANSI.dim, s.ref.padEnd(refWidth))} `;
|
|
15885
|
-
|
|
15942
|
+
out2.push(head + clip(s.title, Math.max(30, width - visibleLength(head))));
|
|
15886
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 ");
|
|
15887
|
-
|
|
15944
|
+
out2.push(paint(ANSI.dim, ` ${" ".repeat(refWidth + 2)} ${under}`));
|
|
15888
15945
|
}
|
|
15889
|
-
|
|
15946
|
+
out2.push("");
|
|
15890
15947
|
}
|
|
15891
|
-
return
|
|
15948
|
+
return out2.join("\n").trimEnd();
|
|
15892
15949
|
}
|
|
15893
15950
|
|
|
15894
15951
|
// src/commands/roadmap.ts
|
|
@@ -16067,9 +16124,9 @@ function normalizeRef(input, items) {
|
|
|
16067
16124
|
}
|
|
16068
16125
|
|
|
16069
16126
|
// src/commands/build.ts
|
|
16070
|
-
var execFileAsync9 =
|
|
16127
|
+
var execFileAsync9 = promisify10(execFile11);
|
|
16071
16128
|
async function build(ref, opts) {
|
|
16072
|
-
const { renderer, out } = rendererFor(opts.out);
|
|
16129
|
+
const { renderer, out: out2 } = rendererFor(opts.out);
|
|
16073
16130
|
const auth = await authenticate();
|
|
16074
16131
|
const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
|
|
16075
16132
|
const repo = await detectRepo(opts.cwd);
|
|
@@ -16079,9 +16136,9 @@ async function build(ref, opts) {
|
|
|
16079
16136
|
const solution = (solutions ?? []).find((s) => s.ref === wanted);
|
|
16080
16137
|
if (!solution) throw new Error(`No solution ${wanted} on the Roadmap.`);
|
|
16081
16138
|
const stream = async (jobId2) => {
|
|
16082
|
-
|
|
16083
|
-
const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(
|
|
16084
|
-
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() });
|
|
16085
16142
|
};
|
|
16086
16143
|
const live = await buildOf(auth.client, project.id, [...ACTIVE_STATUSES]);
|
|
16087
16144
|
if (live) {
|
|
@@ -16091,7 +16148,7 @@ async function build(ref, opts) {
|
|
|
16091
16148
|
}
|
|
16092
16149
|
const stopped = opts.continue || opts.throwAway ? await buildOf(auth.client, project.id, ["failed", "cancelled"], solution.id) : null;
|
|
16093
16150
|
if ((opts.continue || opts.throwAway) && !stopped) throw new Error(`${solution.ref} has no stopped build to ${opts.continue ? "continue" : "throw away"}.`);
|
|
16094
|
-
if (opts.throwAway) return throwAway(auth.client, stopped, repo.root,
|
|
16151
|
+
if (opts.throwAway) return throwAway(auth.client, stopped, repo.root, out2);
|
|
16095
16152
|
const [daemon, local] = await Promise.all([awakeDaemon(auth.client, auth.userId), localDaemonId()]);
|
|
16096
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.");
|
|
16097
16154
|
await rememberClone(auth.client, project.id, repo.root);
|
|
@@ -16113,13 +16170,13 @@ async function buildOf(client, projectId, statuses, solutionId) {
|
|
|
16113
16170
|
const params = data?.params ?? {};
|
|
16114
16171
|
return data ? { id: data.id, solutionId: params.solution_id ?? "", was: wasOf(data.params), origin: params.origin ?? data.id } : null;
|
|
16115
16172
|
}
|
|
16116
|
-
async function throwAway(client, build2, clone,
|
|
16173
|
+
async function throwAway(client, build2, clone, out2) {
|
|
16117
16174
|
const { place } = await loadBuildHistory(client, [build2.origin], { home: homedir6(), jobId: build2.origin });
|
|
16118
16175
|
if (!place) throw new Error("that build left nothing on this machine.");
|
|
16119
16176
|
if (existsSync7(place.dir)) await execFileAsync9("git", ["-C", clone, "worktree", "remove", "--force", place.dir]);
|
|
16120
16177
|
await execFileAsync9("git", ["-C", clone, "branch", "-D", place.branch]).catch(() => void 0);
|
|
16121
16178
|
await appendJobEvent(client, build2.id, "thrown", {}, "user");
|
|
16122
|
-
|
|
16179
|
+
out2(`Thrown away: the copy of the code and the local branch ${place.branch}.`);
|
|
16123
16180
|
return 0;
|
|
16124
16181
|
}
|
|
16125
16182
|
|
|
@@ -16138,7 +16195,7 @@ var POSTGRES_KEYS = [
|
|
|
16138
16195
|
var POSTHOG_KEYS = ["POSTHOG_API_KEY", "POSTHOG_PERSONAL_API_KEY", "NEXT_PUBLIC_POSTHOG_KEY"];
|
|
16139
16196
|
var AMPLITUDE_KEYS = ["AMPLITUDE_API_KEY", "AMPLITUDE_SECRET_KEY", "NEXT_PUBLIC_AMPLITUDE_KEY"];
|
|
16140
16197
|
function parseEnv(text2) {
|
|
16141
|
-
const
|
|
16198
|
+
const out2 = {};
|
|
16142
16199
|
for (const raw of text2.split("\n")) {
|
|
16143
16200
|
const line = raw.trim();
|
|
16144
16201
|
if (!line || line.startsWith("#")) continue;
|
|
@@ -16148,9 +16205,9 @@ function parseEnv(text2) {
|
|
|
16148
16205
|
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
16149
16206
|
value = value.slice(1, -1);
|
|
16150
16207
|
}
|
|
16151
|
-
if (value)
|
|
16208
|
+
if (value) out2[match[1]] = value;
|
|
16152
16209
|
}
|
|
16153
|
-
return
|
|
16210
|
+
return out2;
|
|
16154
16211
|
}
|
|
16155
16212
|
var PLACEHOLDER = /<|your-|xxxx/i;
|
|
16156
16213
|
function detectInEnv(env, file) {
|
|
@@ -16172,15 +16229,15 @@ function detectInEnv(env, file) {
|
|
|
16172
16229
|
return found;
|
|
16173
16230
|
}
|
|
16174
16231
|
async function envFiles(repoRoot) {
|
|
16175
|
-
const
|
|
16232
|
+
const out2 = [];
|
|
16176
16233
|
for (const file of ENV_FILES) {
|
|
16177
16234
|
try {
|
|
16178
|
-
|
|
16235
|
+
out2.push({ file, env: parseEnv(await readFile15(join17(repoRoot, file), "utf8")) });
|
|
16179
16236
|
} catch {
|
|
16180
16237
|
continue;
|
|
16181
16238
|
}
|
|
16182
16239
|
}
|
|
16183
|
-
return
|
|
16240
|
+
return out2;
|
|
16184
16241
|
}
|
|
16185
16242
|
async function detectSources(repoRoot) {
|
|
16186
16243
|
const found = [];
|
|
@@ -16266,7 +16323,7 @@ async function start(sentence2, opts) {
|
|
|
16266
16323
|
return startProject(text2, { ...opts, repo });
|
|
16267
16324
|
}
|
|
16268
16325
|
async function startProject(text2, opts) {
|
|
16269
|
-
const { renderer, out } = rendererFor(opts.out);
|
|
16326
|
+
const { renderer, out: out2 } = rendererFor(opts.out);
|
|
16270
16327
|
const auth = await authenticate();
|
|
16271
16328
|
const daemonId = await localDaemonId();
|
|
16272
16329
|
if (!daemonId) throw new Error("no daemon on this machine yet; `nanopm login` starts one (or `nanopm daemon install`)");
|
|
@@ -16282,24 +16339,24 @@ async function startProject(text2, opts) {
|
|
|
16282
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();
|
|
16283
16340
|
if (error2 || !data) throw new Error(`could not create the project: ${error2?.message}`);
|
|
16284
16341
|
project = data;
|
|
16285
|
-
|
|
16342
|
+
out2(`created project ${project.name} (${project.id})${opts.repo ? "" : ", with no repository yet"}`);
|
|
16286
16343
|
}
|
|
16287
16344
|
await auth.client.from("project_members").update({ daemon_id: daemonId }).eq("project_id", project.id).eq("user_id", auth.userId);
|
|
16288
16345
|
if (opts.repo) await rememberClone(auth.client, project.id, opts.repo.root);
|
|
16289
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();
|
|
16290
16347
|
if (error || !job) throw new Error(`could not queue the conversation: ${error?.message}`);
|
|
16291
|
-
|
|
16348
|
+
out2(`queued start as job ${job.id}`);
|
|
16292
16349
|
const link = newProjectUrl(job.id);
|
|
16293
|
-
|
|
16350
|
+
out2(`Follow along and answer at ${link}`);
|
|
16294
16351
|
if (!opts.noBrowser) openBrowser(link);
|
|
16295
|
-
const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(
|
|
16296
|
-
const exit = await streamJob(auth.client, job.id, { pollMs: 1e3, out, renderer, cancelSignal: opts.cancelSignal, answerer, link, verbose: verboseWanted() });
|
|
16297
|
-
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);
|
|
16298
16355
|
return exit;
|
|
16299
16356
|
}
|
|
16300
|
-
async function askSentence(
|
|
16357
|
+
async function askSentence(out2) {
|
|
16301
16358
|
if (!process.stdin.isTTY) return null;
|
|
16302
|
-
|
|
16359
|
+
out2("Tell me about it in a sentence: what are you building, and for whom?");
|
|
16303
16360
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
16304
16361
|
try {
|
|
16305
16362
|
return (await rl.question("> ")).trim() || null;
|
|
@@ -16310,17 +16367,17 @@ async function askSentence(out) {
|
|
|
16310
16367
|
|
|
16311
16368
|
// src/commands/onboard.ts
|
|
16312
16369
|
async function onboard(opts) {
|
|
16313
|
-
const { renderer, out } = rendererFor(opts.out);
|
|
16370
|
+
const { renderer, out: out2 } = rendererFor(opts.out);
|
|
16314
16371
|
const repo = await detectRepo(opts.cwd);
|
|
16315
16372
|
if (!repo) throw new Error('run this inside a git repository, or nanopm start "<sentence>" for a project with no code yet');
|
|
16316
16373
|
const auth = await authenticate();
|
|
16317
16374
|
const daemonId = await localDaemonId();
|
|
16318
16375
|
if (!daemonId) throw new Error("no daemon on this machine yet; `nanopm login` starts one (or `nanopm daemon install`)");
|
|
16319
|
-
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;
|
|
16320
16377
|
if (repo.empty) {
|
|
16321
16378
|
if (opts.scanOnly) throw new Error('nothing to read here yet; nanopm start "<sentence>" holds the conversation instead');
|
|
16322
|
-
|
|
16323
|
-
const sentence2 = opts.sentence ?? await askSentence(
|
|
16379
|
+
out2("Nothing to read here yet.");
|
|
16380
|
+
const sentence2 = opts.sentence ?? await askSentence(out2);
|
|
16324
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');
|
|
16325
16382
|
return startProject(startSentence(sentence2), { ...opts, repo, existing: project ?? void 0 });
|
|
16326
16383
|
}
|
|
@@ -16329,9 +16386,9 @@ async function onboard(opts) {
|
|
|
16329
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();
|
|
16330
16387
|
if (error2 || !data) throw new Error(`could not create the project: ${error2?.message}`);
|
|
16331
16388
|
project = data;
|
|
16332
|
-
|
|
16389
|
+
out2(`created project ${project.name} (${project.id})`);
|
|
16333
16390
|
} else {
|
|
16334
|
-
|
|
16391
|
+
out2(`project ${project.name} (${project.id}) exists; refreshing its memory`);
|
|
16335
16392
|
}
|
|
16336
16393
|
await auth.client.from("project_members").update({ daemon_id: daemonId }).eq("project_id", project.id).eq("user_id", auth.userId);
|
|
16337
16394
|
await rememberClone(auth.client, project.id, repo.root);
|
|
@@ -16355,18 +16412,18 @@ async function onboard(opts) {
|
|
|
16355
16412
|
target_daemon_id: daemonId
|
|
16356
16413
|
}).select("id").single();
|
|
16357
16414
|
if (error || !job) throw new Error(`could not queue onboarding: ${error?.message}`);
|
|
16358
|
-
|
|
16415
|
+
out2(`queued onboard as job ${job.id}`);
|
|
16359
16416
|
const link = newProjectUrl(job.id);
|
|
16360
16417
|
if (!opts.scanOnly) {
|
|
16361
|
-
|
|
16418
|
+
out2(`Follow along, then confirm what I read at ${link}`);
|
|
16362
16419
|
if (!opts.noBrowser) openBrowser(link);
|
|
16363
16420
|
}
|
|
16364
|
-
const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(
|
|
16365
|
-
const exit = await streamJob(auth.client, job.id, { pollMs: 1e3, out, renderer, cancelSignal: opts.cancelSignal, answerer, link, verbose: verboseWanted() });
|
|
16366
|
-
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);
|
|
16367
16424
|
return exit;
|
|
16368
16425
|
}
|
|
16369
|
-
async function attach(client, projectId, repo,
|
|
16426
|
+
async function attach(client, projectId, repo, out2) {
|
|
16370
16427
|
const { data: project } = await client.from("projects").select("id, name, slug, repo_remote").eq("id", projectId).maybeSingle();
|
|
16371
16428
|
if (!project) throw new Error(`project ${projectId} not found (or you are not a member)`);
|
|
16372
16429
|
if (repo.remote && project.repo_remote && project.repo_remote !== repo.remote) {
|
|
@@ -16377,7 +16434,7 @@ async function attach(client, projectId, repo, out) {
|
|
|
16377
16434
|
if (taken) throw new Error(`${repo.remote} is already the repository of ${taken.name}`);
|
|
16378
16435
|
const { data: updated } = await client.from("projects").update({ repo_remote: repo.remote }).eq("id", project.id).select("id").maybeSingle();
|
|
16379
16436
|
if (!updated) throw new Error(`only the owner of ${project.name} can attach its repository`);
|
|
16380
|
-
|
|
16437
|
+
out2(`${project.name} now lives at ${repo.remote}`);
|
|
16381
16438
|
}
|
|
16382
16439
|
return { id: project.id, name: project.name, slug: project.slug };
|
|
16383
16440
|
}
|
|
@@ -16417,7 +16474,7 @@ async function want(sentence2, opts) {
|
|
|
16417
16474
|
|
|
16418
16475
|
// src/commands/resume.ts
|
|
16419
16476
|
async function resume(jobId, opts) {
|
|
16420
|
-
const { renderer, out } = rendererFor(opts.out);
|
|
16477
|
+
const { renderer, out: out2 } = rendererFor(opts.out);
|
|
16421
16478
|
const auth = await authenticate();
|
|
16422
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();
|
|
16423
16480
|
if (!original) throw new Error(`job ${jobId} not found`);
|
|
@@ -16435,9 +16492,9 @@ async function resume(jobId, opts) {
|
|
|
16435
16492
|
}).select("id").single();
|
|
16436
16493
|
if (error || !job) throw new Error(`could not queue the resume: ${error?.message}`);
|
|
16437
16494
|
const code = original.error?.code ?? "failed";
|
|
16438
|
-
|
|
16439
|
-
const answerer = opts.answersFile ? await answersFromFile(opts.answersFile) : answersFromTerminal(
|
|
16440
|
-
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() });
|
|
16441
16498
|
}
|
|
16442
16499
|
|
|
16443
16500
|
// src/commands/connect.ts
|
|
@@ -16488,7 +16545,7 @@ async function connect2(opts) {
|
|
|
16488
16545
|
const paint = painter(color);
|
|
16489
16546
|
const auth = await authenticate();
|
|
16490
16547
|
const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
|
|
16491
|
-
const
|
|
16548
|
+
const out2 = [];
|
|
16492
16549
|
const index = { i: 0 };
|
|
16493
16550
|
let url = opts.url;
|
|
16494
16551
|
let label = opts.label;
|
|
@@ -16501,31 +16558,31 @@ async function connect2(opts) {
|
|
|
16501
16558
|
if (!url) {
|
|
16502
16559
|
const databases = found.filter((f) => f.kind === "postgres");
|
|
16503
16560
|
const analytics = found.filter((f) => f.kind !== "postgres");
|
|
16504
|
-
|
|
16561
|
+
out2.push(whereItGoes(color));
|
|
16505
16562
|
if (databases.length) {
|
|
16506
16563
|
const first = databases[0];
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
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")}
|
|
16514
16571
|
`);
|
|
16515
|
-
|
|
16572
|
+
out2.length = 0;
|
|
16516
16573
|
const answer = await ask5("Connection string (enter to use the one above): ", opts.answers, index);
|
|
16517
16574
|
url = answer || first.value;
|
|
16518
16575
|
} else {
|
|
16519
|
-
process.stdout.write(`${
|
|
16576
|
+
process.stdout.write(`${out2.join("\n")}
|
|
16520
16577
|
`);
|
|
16521
|
-
|
|
16578
|
+
out2.length = 0;
|
|
16522
16579
|
url = await ask5("Read-only Postgres connection string: ", opts.answers, index);
|
|
16523
16580
|
}
|
|
16524
16581
|
if (analytics.length) {
|
|
16525
16582
|
const kinds = new Set(analytics.map((a) => a.kind));
|
|
16526
|
-
if (kinds.has("posthog"))
|
|
16527
|
-
if (kinds.has("amplitude"))
|
|
16528
|
-
|
|
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("");
|
|
16529
16586
|
}
|
|
16530
16587
|
}
|
|
16531
16588
|
if (!url) throw new Error("nothing to connect. Run it again with a read-only connection string.");
|
|
@@ -16551,18 +16608,18 @@ ${refusalForWritableRole(url, color)}`);
|
|
|
16551
16608
|
});
|
|
16552
16609
|
if (error) throw new Error(`could not store the connection: ${pgMessage(error)}`);
|
|
16553
16610
|
await auth.client.rpc("set_connection_status", { p_connection_id: connectionId, p_status: "ok" });
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
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("");
|
|
16557
16614
|
if (test.blindness) {
|
|
16558
16615
|
const role7 = parseConnectionUrl(url)?.user.split(".")[0] ?? "the role";
|
|
16559
|
-
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
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("");
|
|
16563
16620
|
}
|
|
16564
|
-
|
|
16565
|
-
return
|
|
16621
|
+
out2.push(paint(ANSI.dim, " nanopm review \xB7 nanopm connect --list"));
|
|
16622
|
+
return out2.join("\n");
|
|
16566
16623
|
}
|
|
16567
16624
|
function soleKind(found) {
|
|
16568
16625
|
const kinds = [...new Set(found.map((f) => f.kind))];
|
|
@@ -16694,18 +16751,18 @@ async function sources(opts) {
|
|
|
16694
16751
|
ok: { sign: "\u25CF", colour: ANSI.green },
|
|
16695
16752
|
failed: { sign: "\u2298", colour: ANSI.red }
|
|
16696
16753
|
};
|
|
16697
|
-
const
|
|
16754
|
+
const out2 = [];
|
|
16698
16755
|
for (const s of rows2) {
|
|
16699
16756
|
const m = mark2[s.status] ?? { sign: "\xB7", colour: ANSI.dim };
|
|
16700
16757
|
const watches2 = watchesOf(s);
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
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}`));
|
|
16705
16762
|
}
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
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");
|
|
16709
16766
|
}
|
|
16710
16767
|
function describeStats(stats) {
|
|
16711
16768
|
const s = stats ?? {};
|
|
@@ -16740,7 +16797,7 @@ async function addSource(kindArg, opts) {
|
|
|
16740
16797
|
const index = { i: 0 };
|
|
16741
16798
|
const connection = await pickConnection(auth.client, project.id, spec2.connection, opts, index);
|
|
16742
16799
|
const label = opts.label || `${spec2.label} watches`;
|
|
16743
|
-
const
|
|
16800
|
+
const out2 = [];
|
|
16744
16801
|
let watches2 = [];
|
|
16745
16802
|
if (opts.fromReview) {
|
|
16746
16803
|
const rows2 = await lastReviewMetrics(auth.client, project.id);
|
|
@@ -16757,15 +16814,15 @@ async function addSource(kindArg, opts) {
|
|
|
16757
16814
|
const tried = await seedWatches(seeded, read6);
|
|
16758
16815
|
watches2 = tried.kept;
|
|
16759
16816
|
for (const skip of tried.skipped) {
|
|
16760
|
-
|
|
16761
|
-
|
|
16817
|
+
out2.push(paint(ANSI.amber, `skipped ${skip.watch.id}: ${skip.why}`));
|
|
16818
|
+
out2.push(paint(ANSI.dim, ` ${clipQuery(skip.watch.query)}`));
|
|
16762
16819
|
}
|
|
16763
16820
|
if (tried.skipped.length) {
|
|
16764
|
-
|
|
16765
|
-
|
|
16821
|
+
out2.push(paint(ANSI.dim, " A watch reads one row with one number. Name it: `select count(*) \u2026 as value`."));
|
|
16822
|
+
out2.push("");
|
|
16766
16823
|
}
|
|
16767
16824
|
if (!watches2.length) {
|
|
16768
|
-
throw new Error([...
|
|
16825
|
+
throw new Error([...out2, "None of those queries returns one number, so there is nothing to watch yet."].join("\n"));
|
|
16769
16826
|
}
|
|
16770
16827
|
}
|
|
16771
16828
|
const { error } = await auth.client.rpc("put_source", {
|
|
@@ -16780,28 +16837,28 @@ async function addSource(kindArg, opts) {
|
|
|
16780
16837
|
p_timezone: timezone
|
|
16781
16838
|
});
|
|
16782
16839
|
if (error) throw new Error(`could not store the source: ${pgMessage(error)}`);
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
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");
|
|
16791
16848
|
}
|
|
16792
16849
|
async function runSource(labelOrId, opts) {
|
|
16793
|
-
const { renderer, out } = rendererFor(opts.out);
|
|
16850
|
+
const { renderer, out: out2 } = rendererFor(opts.out);
|
|
16794
16851
|
const auth = await authenticate();
|
|
16795
16852
|
const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
|
|
16796
16853
|
const source = findSource(await liveSources(auth.client, project.id), labelOrId);
|
|
16797
16854
|
const { data: jobId, error } = await auth.client.rpc("enqueue_source_run", { p_source_id: source.id, p_trigger: "user" });
|
|
16798
16855
|
if (error) throw new Error(`could not queue the run: ${pgMessage(error)}`);
|
|
16799
16856
|
if (!jobId) {
|
|
16800
|
-
|
|
16857
|
+
out2(`the map is being worked out, so ${source.label} was not read. It stays due; it runs when the map lands.`);
|
|
16801
16858
|
return 0;
|
|
16802
16859
|
}
|
|
16803
|
-
|
|
16804
|
-
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() });
|
|
16805
16862
|
}
|
|
16806
16863
|
async function removeSource(labelOrId, opts) {
|
|
16807
16864
|
const auth = await authenticate();
|
|
@@ -16963,13 +17020,13 @@ async function watches(labelOrId, opts) {
|
|
|
16963
17020
|
paint(ANSI.dim, " nanopm source add postgres --from-review")
|
|
16964
17021
|
].join("\n");
|
|
16965
17022
|
}
|
|
16966
|
-
const
|
|
17023
|
+
const out2 = [];
|
|
16967
17024
|
for (const source of rows2) {
|
|
16968
17025
|
const list = watchesOf(source);
|
|
16969
|
-
|
|
17026
|
+
out2.push(paint(ANSI.bold, source.label));
|
|
16970
17027
|
if (!list.length) {
|
|
16971
|
-
|
|
16972
|
-
|
|
17028
|
+
out2.push(paint(ANSI.dim, ` nothing watched yet \xB7 nanopm watch add ${source.label} <id> --query "\u2026"`));
|
|
17029
|
+
out2.push("");
|
|
16973
17030
|
continue;
|
|
16974
17031
|
}
|
|
16975
17032
|
const width = Math.max(...list.map((w) => w.id.length));
|
|
@@ -16978,13 +17035,13 @@ async function watches(labelOrId, opts) {
|
|
|
16978
17035
|
const last = state.observations?.[state.observations.length - 1];
|
|
16979
17036
|
const open = state.open?.since ? paint(ANSI.amber, ` open since ${ago(state.open.since)}`) : "";
|
|
16980
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)})`) : ""}`;
|
|
16981
|
-
|
|
16982
|
-
|
|
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}`);
|
|
16983
17040
|
}
|
|
16984
|
-
|
|
17041
|
+
out2.push("");
|
|
16985
17042
|
}
|
|
16986
|
-
|
|
16987
|
-
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");
|
|
16988
17045
|
}
|
|
16989
17046
|
function watchInput(id, query, opts) {
|
|
16990
17047
|
const text2 = (query ?? "").trim();
|
|
@@ -17109,16 +17166,16 @@ async function evidence(opts) {
|
|
|
17109
17166
|
paint(ANSI.dim, sources2.length ? " nanopm source \xB7 nanopm watch" : " nanopm source add postgres --from-review")
|
|
17110
17167
|
].join("\n");
|
|
17111
17168
|
}
|
|
17112
|
-
const
|
|
17169
|
+
const out2 = [];
|
|
17113
17170
|
for (const row of rows2) {
|
|
17114
17171
|
const when = (row.occurred_at ?? row.created_at).slice(0, 10);
|
|
17115
17172
|
const where = row.source_id ? labelById.get(row.source_id) ?? "a removed feed" : "";
|
|
17116
|
-
|
|
17117
|
-
|
|
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}` : ""}`));
|
|
17118
17175
|
}
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
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");
|
|
17122
17179
|
}
|
|
17123
17180
|
function bars(strength, color) {
|
|
17124
17181
|
const paint = painter(color);
|
|
@@ -17158,25 +17215,25 @@ function renderReview(review2, metrics2, opts) {
|
|
|
17158
17215
|
if (!review2) {
|
|
17159
17216
|
return ["No review yet.", "", paint(ANSI.dim, " nanopm connect \xB7 nanopm review")].join("\n");
|
|
17160
17217
|
}
|
|
17161
|
-
const
|
|
17218
|
+
const out2 = [];
|
|
17162
17219
|
const width = columns();
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
for (const line of wrap(review2.summary, width))
|
|
17166
|
-
|
|
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("");
|
|
17167
17224
|
if (metrics2.length) {
|
|
17168
|
-
|
|
17169
|
-
|
|
17225
|
+
out2.push(renderMetrics(metrics2, opts).split("\n\n")[0]);
|
|
17226
|
+
out2.push("");
|
|
17170
17227
|
}
|
|
17171
17228
|
for (const item of review2.confirms) {
|
|
17172
|
-
|
|
17229
|
+
out2.push(`${paint(ANSI.green, "\u2713")} ${wrap(item, width - 2).join("\n ")}`);
|
|
17173
17230
|
}
|
|
17174
17231
|
for (const item of review2.contradicts) {
|
|
17175
|
-
|
|
17232
|
+
out2.push(`${paint(ANSI.amber, "\u2717")} ${wrap(item, width - 2).join("\n ")}`);
|
|
17176
17233
|
}
|
|
17177
|
-
if (review2.confirms.length || review2.contradicts.length)
|
|
17178
|
-
|
|
17179
|
-
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");
|
|
17180
17237
|
}
|
|
17181
17238
|
|
|
17182
17239
|
// src/commands/review.ts
|
|
@@ -17249,13 +17306,13 @@ async function problems(ref, opts) {
|
|
|
17249
17306
|
const auth = await authenticate();
|
|
17250
17307
|
const color = opts.color ?? useColor();
|
|
17251
17308
|
if (opts.all) {
|
|
17252
|
-
const
|
|
17309
|
+
const out2 = [];
|
|
17253
17310
|
for (const project2 of await memberProjects(auth.client, auth.userId, opts.cwd)) {
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17311
|
+
out2.push(painter(color)(ANSI.bold, project2.name));
|
|
17312
|
+
out2.push(await problemList(auth.client, project2.id, color));
|
|
17313
|
+
out2.push("");
|
|
17257
17314
|
}
|
|
17258
|
-
return
|
|
17315
|
+
return out2.join("\n").trimEnd();
|
|
17259
17316
|
}
|
|
17260
17317
|
const project = await resolveProject(auth.client, { projectId: opts.projectId, cwd: opts.cwd });
|
|
17261
17318
|
if (!ref) return problemList(auth.client, project.id, color);
|
|
@@ -17452,76 +17509,76 @@ function renderMarket(rows2, opts) {
|
|
|
17452
17509
|
const rest = rows2.filter((r) => r.kind !== "competitor");
|
|
17453
17510
|
const refWidth = Math.max(...rows2.map((r) => r.ref.length));
|
|
17454
17511
|
const width = columns();
|
|
17455
|
-
const
|
|
17512
|
+
const out2 = [];
|
|
17456
17513
|
for (const c of competitors) {
|
|
17457
17514
|
const mark2 = c.confirmed ? paint(ANSI.green, "\u25CF") : paint(ANSI.amber, "\u25CB");
|
|
17458
17515
|
const name = c.competitor ?? c.domain ?? c.ref;
|
|
17459
|
-
|
|
17516
|
+
out2.push(`${mark2} ${paint(ANSI.dim, c.ref.padEnd(refWidth))} ${paint(ANSI.bold, name)}${c.domain ? paint(ANSI.dim, ` ${c.domain}`) : ""}`);
|
|
17460
17517
|
const indent = ` ${" ".repeat(refWidth)} `;
|
|
17461
|
-
|
|
17462
|
-
|
|
17518
|
+
out2.push(`${indent}${clip(firstSentence(c.statement), Math.max(40, width - indent.length))}`);
|
|
17519
|
+
out2.push(
|
|
17463
17520
|
paint(
|
|
17464
17521
|
ANSI.dim,
|
|
17465
17522
|
indent + (c.confirmed ? `confirmed${c.provenance === "stated" ? " \xB7 you added them" : ""}` : `not confirmed \xB7 nanopm market confirm ${c.ref}`)
|
|
17466
17523
|
)
|
|
17467
17524
|
);
|
|
17468
17525
|
for (const r of rest.filter((r2) => sameCompetitor(r2, c))) {
|
|
17469
|
-
|
|
17470
|
-
|
|
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))}`);
|
|
17471
17528
|
}
|
|
17472
|
-
|
|
17529
|
+
out2.push("");
|
|
17473
17530
|
}
|
|
17474
17531
|
const unmatched = rest.filter((r) => !competitors.some((c) => sameCompetitor(r, c)));
|
|
17475
17532
|
if (unmatched.length) {
|
|
17476
17533
|
for (const r of unmatched) {
|
|
17477
|
-
|
|
17478
|
-
|
|
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))}`);
|
|
17479
17536
|
}
|
|
17480
|
-
|
|
17537
|
+
out2.push("");
|
|
17481
17538
|
}
|
|
17482
17539
|
const unconfirmed = competitors.filter((c) => !c.confirmed).length;
|
|
17483
17540
|
if (unconfirmed) {
|
|
17484
|
-
|
|
17541
|
+
out2.push(
|
|
17485
17542
|
paint(
|
|
17486
17543
|
ANSI.dim,
|
|
17487
17544
|
`${unconfirmed} of ${competitors.length} competitor${competitors.length === 1 ? "" : "s"} not confirmed.`
|
|
17488
17545
|
)
|
|
17489
17546
|
);
|
|
17490
17547
|
}
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
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");
|
|
17494
17551
|
}
|
|
17495
17552
|
function renderMarketEntry(row, related, opts) {
|
|
17496
17553
|
const paint = painter(opts.color);
|
|
17497
17554
|
const width = columns();
|
|
17498
|
-
const
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
for (const line of wrap(row.statement, width))
|
|
17502
|
-
|
|
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("");
|
|
17503
17560
|
const facts = [];
|
|
17504
17561
|
if (row.domain) facts.push(["site", row.domain]);
|
|
17505
17562
|
facts.push(["from", row.provenance === "stated" ? "you" : "the web"]);
|
|
17506
17563
|
if (row.kind === "competitor") {
|
|
17507
17564
|
facts.push(["confirmed", row.confirmed ? "yes" : `no \xB7 nanopm market confirm ${row.ref}`]);
|
|
17508
17565
|
}
|
|
17509
|
-
for (const [k, v] of facts)
|
|
17566
|
+
for (const [k, v] of facts) out2.push(`${paint(ANSI.dim, k.padEnd(10))} ${v}`);
|
|
17510
17567
|
const pages = row.sources.filter((s) => s.kind === "web");
|
|
17511
17568
|
if (pages.length) {
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
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}`) : ""}`);
|
|
17515
17572
|
}
|
|
17516
17573
|
if (related.length) {
|
|
17517
|
-
|
|
17518
|
-
|
|
17574
|
+
out2.push("");
|
|
17575
|
+
out2.push(paint(ANSI.dim, `Also about ${row.competitor}`));
|
|
17519
17576
|
for (const r of related) {
|
|
17520
|
-
|
|
17521
|
-
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}`);
|
|
17522
17579
|
}
|
|
17523
17580
|
}
|
|
17524
|
-
return
|
|
17581
|
+
return out2.join("\n");
|
|
17525
17582
|
}
|
|
17526
17583
|
|
|
17527
17584
|
// src/commands/search.ts
|
|
@@ -17546,27 +17603,27 @@ async function showSearch(opts) {
|
|
|
17546
17603
|
const paint = painter(opts.color ?? useColor());
|
|
17547
17604
|
const row = await searchConnection(auth.client, project.id);
|
|
17548
17605
|
const local = await localWebSearch();
|
|
17549
|
-
const
|
|
17606
|
+
const out2 = [describeSearch(row, local), ""];
|
|
17550
17607
|
if (isClaudeMode(row)) {
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
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"));
|
|
17554
17611
|
} else if (row) {
|
|
17555
|
-
|
|
17556
|
-
|
|
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"));
|
|
17557
17614
|
} else if (local) {
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
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"));
|
|
17563
17620
|
} else {
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
17567
|
-
|
|
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"));
|
|
17568
17625
|
}
|
|
17569
|
-
return
|
|
17626
|
+
return out2.join("\n");
|
|
17570
17627
|
}
|
|
17571
17628
|
async function setClaudeSearch(opts) {
|
|
17572
17629
|
const auth = await authenticate();
|
|
@@ -17774,34 +17831,34 @@ function render(rows2) {
|
|
|
17774
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.";
|
|
17775
17832
|
const needs2 = rows2.filter((r) => r.topic === NEEDS_TOPIC);
|
|
17776
17833
|
const taken = /* @__PURE__ */ new Set();
|
|
17777
|
-
const
|
|
17834
|
+
const out2 = [];
|
|
17778
17835
|
for (const row of people2) {
|
|
17779
17836
|
const p = parsePersona(row.key, row.content);
|
|
17780
17837
|
const mark2 = row.provenance === "stated" ? "" : ` \xB7 to review${row.confidence != null ? ` (${row.confidence}% sure)` : ""}`;
|
|
17781
|
-
|
|
17782
|
-
|
|
17838
|
+
out2.push(`${row.title ?? p.slug}${mark2}`);
|
|
17839
|
+
out2.push(` ${p.who}`);
|
|
17783
17840
|
for (const [label, value] of [
|
|
17784
17841
|
["comes for", p.comesFor],
|
|
17785
17842
|
["today instead", p.todayInstead],
|
|
17786
17843
|
["context", p.context],
|
|
17787
17844
|
["weight", p.weight]
|
|
17788
17845
|
]) {
|
|
17789
|
-
if (value)
|
|
17846
|
+
if (value) out2.push(` ${label}: ${value}`);
|
|
17790
17847
|
}
|
|
17791
17848
|
const mine = needs2.filter((n) => needOwner(n) === p.slug);
|
|
17792
17849
|
for (const n of mine) {
|
|
17793
17850
|
taken.add(n.key);
|
|
17794
|
-
|
|
17851
|
+
out2.push(` - ${n.title ? `${n.title}: ` : ""}${n.content}`);
|
|
17795
17852
|
}
|
|
17796
|
-
|
|
17853
|
+
out2.push("");
|
|
17797
17854
|
}
|
|
17798
17855
|
const loose = needs2.filter((n) => !taken.has(n.key));
|
|
17799
17856
|
if (loose.length) {
|
|
17800
|
-
|
|
17801
|
-
for (const n of loose)
|
|
17802
|
-
|
|
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("");
|
|
17803
17860
|
}
|
|
17804
|
-
return
|
|
17861
|
+
return out2.join("\n").trimEnd();
|
|
17805
17862
|
}
|
|
17806
17863
|
|
|
17807
17864
|
// src/commands/direction.ts
|
|
@@ -17826,21 +17883,21 @@ function renderDirection(rows2) {
|
|
|
17826
17883
|
if (!rows2.some((r) => DIRECTION_ROWS.some((line) => r.key === directionKey(line))) && !objective) {
|
|
17827
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.";
|
|
17828
17885
|
}
|
|
17829
|
-
const
|
|
17886
|
+
const out2 = [];
|
|
17830
17887
|
for (const line of DIRECTION_ROWS) {
|
|
17831
17888
|
const row = rows2.find((r) => r.key === directionKey(line));
|
|
17832
17889
|
const next = rows2.find((r) => r.key === directionKey(line, true));
|
|
17833
|
-
|
|
17834
|
-
|
|
17890
|
+
out2.push(`${DIRECTION_ROW_TITLE[line]}${mark(row)}`);
|
|
17891
|
+
out2.push(` ${row?.content ?? "\u2014"}`);
|
|
17835
17892
|
if (next) {
|
|
17836
17893
|
const because = reasonOf2(next.sources);
|
|
17837
|
-
|
|
17894
|
+
out2.push(` proposed: ${next.content}${because ? ` \xB7 ${because}` : ""}`);
|
|
17838
17895
|
}
|
|
17839
17896
|
}
|
|
17840
|
-
|
|
17841
|
-
|
|
17842
|
-
if (counts)
|
|
17843
|
-
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");
|
|
17844
17901
|
}
|
|
17845
17902
|
function mark(row) {
|
|
17846
17903
|
return row && row.provenance !== "stated" ? ` \xB7 to review${row.confidence != null ? ` (${row.confidence}% sure)` : ""}` : "";
|
|
@@ -17871,19 +17928,19 @@ function render2(rows2) {
|
|
|
17871
17928
|
if (!rows2.some((r) => PITCH_LINE_KEYS.some((l) => r.key === pitchKey(l)))) {
|
|
17872
17929
|
return "No pitch yet. An onboarding writes the first one; `nanopm pitch --refresh` has the crew write one from what the card holds.";
|
|
17873
17930
|
}
|
|
17874
|
-
const
|
|
17931
|
+
const out2 = [];
|
|
17875
17932
|
for (const line of PITCH_LINE_KEYS) {
|
|
17876
17933
|
const row = rows2.find((r) => r.key === pitchKey(line));
|
|
17877
17934
|
const next = rows2.find((r) => r.key === pitchKey(line, true));
|
|
17878
17935
|
const mark2 = row && row.provenance !== "stated" ? ` \xB7 to review${row.confidence != null ? ` (${row.confidence}% sure)` : ""}` : "";
|
|
17879
|
-
|
|
17880
|
-
|
|
17936
|
+
out2.push(`${PITCH_LINE_TITLE[line]}${mark2}`);
|
|
17937
|
+
out2.push(` ${row?.content ?? "\u2014"}`);
|
|
17881
17938
|
if (next) {
|
|
17882
17939
|
const because = reasonOf3(next.sources);
|
|
17883
|
-
|
|
17940
|
+
out2.push(` proposed: ${next.content}${because ? ` \xB7 ${because}` : ""}`);
|
|
17884
17941
|
}
|
|
17885
17942
|
}
|
|
17886
|
-
return
|
|
17943
|
+
return out2.join("\n");
|
|
17887
17944
|
}
|
|
17888
17945
|
function reasonOf3(sources2) {
|
|
17889
17946
|
if (!Array.isArray(sources2)) return null;
|
|
@@ -17955,16 +18012,16 @@ async function join18(code, opts) {
|
|
|
17955
18012
|
const joined = row ? { id: row.joined_id, name: row.joined_name, repo_remote: row.joined_remote, role: row.joined_role } : void 0;
|
|
17956
18013
|
if (!joined) throw new Error("nothing was joined");
|
|
17957
18014
|
const paint = painter(opts.color ?? useColor());
|
|
17958
|
-
const
|
|
18015
|
+
const out2 = [paint(ANSI.green, `\u2713 you are on ${joined.name}, as ${joined.role}`)];
|
|
17959
18016
|
if (joined.repo_remote && joined.repo_remote !== repo.remote) {
|
|
17960
|
-
|
|
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.`));
|
|
17961
18018
|
}
|
|
17962
|
-
|
|
18019
|
+
out2.push(paint(ANSI.dim, ` Clone: ${repo.root}${daemonId ? "" : " \xB7 no daemon on this machine yet: nanopm daemon install"}`));
|
|
17963
18020
|
const { data: slugRow } = await auth.client.from("projects").select("slug").eq("id", joined.id).maybeSingle();
|
|
17964
|
-
|
|
17965
|
-
if (slugRow?.slug)
|
|
17966
|
-
|
|
17967
|
-
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"));
|
|
17968
18025
|
}
|
|
17969
18026
|
async function members(opts) {
|
|
17970
18027
|
const auth = await authenticate();
|
|
@@ -17974,14 +18031,14 @@ async function members(opts) {
|
|
|
17974
18031
|
if (error) throw new Error(pgMessage(error));
|
|
17975
18032
|
const rows2 = data ?? [];
|
|
17976
18033
|
const width = Math.max(...rows2.map((r) => r.name.length));
|
|
17977
|
-
const
|
|
18034
|
+
const out2 = rows2.map((r) => {
|
|
17978
18035
|
const you = r.user_id === auth.userId ? paint(ANSI.dim, " (you)") : "";
|
|
17979
18036
|
const seen = r.daemon_last_seen_at ? Date.now() - new Date(r.daemon_last_seen_at).getTime() : Infinity;
|
|
17980
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") : "");
|
|
17981
18038
|
return `${paint(ANSI.bold, r.name.padEnd(width))}${you} ${paint(ANSI.dim, r.role.padEnd(6))} ${daemon}`;
|
|
17982
18039
|
});
|
|
17983
|
-
|
|
17984
|
-
return
|
|
18040
|
+
out2.push("", paint(ANSI.dim, " nanopm invite \xB7 nanopm name <what partners see>"));
|
|
18041
|
+
return out2.join("\n");
|
|
17985
18042
|
}
|
|
17986
18043
|
async function setName(name) {
|
|
17987
18044
|
const trimmed = name.trim();
|
|
@@ -18050,36 +18107,36 @@ var MEASURE_DEFINITIONS = [
|
|
|
18050
18107
|
function renderStatus(projects, daemon, opts) {
|
|
18051
18108
|
const paint = painter(opts.color);
|
|
18052
18109
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
18053
|
-
const
|
|
18110
|
+
const out2 = [];
|
|
18054
18111
|
if (!daemon) {
|
|
18055
|
-
|
|
18056
|
-
|
|
18112
|
+
out2.push(paint(ANSI.amber, "\u25CB no daemon on this machine"));
|
|
18113
|
+
out2.push(paint(ANSI.dim, " nanopm daemon install"));
|
|
18057
18114
|
} else {
|
|
18058
18115
|
const seenMs = daemon.lastSeenAt ? now.getTime() - new Date(daemon.lastSeenAt).getTime() : Infinity;
|
|
18059
18116
|
const alive = daemon.status === "online" && seenMs < 5 * 6e4;
|
|
18060
18117
|
const stale = seenMs > 24 * 36e5;
|
|
18061
18118
|
const mark2 = alive ? paint(ANSI.green, "\u25CF") : stale ? paint(ANSI.red, "\u2298") : paint(ANSI.amber, "\u25CB");
|
|
18062
|
-
|
|
18119
|
+
out2.push(`${mark2} daemon on ${daemon.hostname} \xB7 ${alive ? "online" : `last seen ${ago(daemon.lastSeenAt, now)}`} \xB7 nanopm ${daemon.version}`);
|
|
18063
18120
|
if (stale) {
|
|
18064
|
-
|
|
18065
|
-
|
|
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"));
|
|
18066
18123
|
}
|
|
18067
18124
|
}
|
|
18068
18125
|
if (opts.activeProjects !== void 0 && projects.length > 1) {
|
|
18069
|
-
|
|
18126
|
+
out2.push(paint(ANSI.dim, ` ${opts.activeProjects} of ${projects.length} projects active in the last 30 days`));
|
|
18070
18127
|
}
|
|
18071
|
-
|
|
18128
|
+
out2.push("");
|
|
18072
18129
|
for (const project of projects) {
|
|
18073
|
-
|
|
18130
|
+
out2.push(paint(ANSI.bold, project.name) + (project.onThisMachine === false ? paint(ANSI.dim, " \xB7 clone not on this machine") : ""));
|
|
18074
18131
|
if (project.schedule) {
|
|
18075
|
-
|
|
18132
|
+
out2.push(` ${paint(ANSI.dim, "reviews")} ${describeCadence(project.schedule)} \xB7 next ${describeNextRun(project.schedule, now)}`);
|
|
18076
18133
|
} else {
|
|
18077
|
-
|
|
18134
|
+
out2.push(` ${paint(ANSI.dim, "reviews")} not scheduled \xB7 ${paint(ANSI.dim, "nanopm schedule weekly monday")}`);
|
|
18078
18135
|
}
|
|
18079
|
-
|
|
18080
|
-
|
|
18136
|
+
out2.push(` ${paint(ANSI.dim, "last")} ${ago(project.lastReviewAt, now)}`);
|
|
18137
|
+
out2.push(` ${paint(ANSI.dim, "search")} ${project.search}`);
|
|
18081
18138
|
if (project.stalledDays !== null) {
|
|
18082
|
-
|
|
18139
|
+
out2.push(
|
|
18083
18140
|
paint(
|
|
18084
18141
|
ANSI.amber,
|
|
18085
18142
|
` ? a review was queued ${project.stalledDays} day${project.stalledDays === 1 ? "" : "s"} ago and has not run \u2014 is the daemon awake?`
|
|
@@ -18089,7 +18146,7 @@ function renderStatus(projects, daemon, opts) {
|
|
|
18089
18146
|
const parts = [];
|
|
18090
18147
|
if (project.runningJobs) parts.push(`${project.runningJobs} running`);
|
|
18091
18148
|
if (project.queuedJobs) parts.push(`${project.queuedJobs} queued`);
|
|
18092
|
-
|
|
18149
|
+
out2.push(` ${paint(ANSI.dim, "jobs")} ${parts.join(", ")}`);
|
|
18093
18150
|
}
|
|
18094
18151
|
const waiting = [];
|
|
18095
18152
|
if (project.waitingDecisions) {
|
|
@@ -18098,23 +18155,23 @@ function renderStatus(projects, daemon, opts) {
|
|
|
18098
18155
|
if (project.unprovenProblems) {
|
|
18099
18156
|
waiting.push(`${project.unprovenProblems} of ${project.openProblems} problems unproven`);
|
|
18100
18157
|
}
|
|
18101
|
-
|
|
18158
|
+
out2.push(
|
|
18102
18159
|
` ${paint(ANSI.dim, "waiting")} ${waiting.length ? waiting.join(" \xB7 ") : paint(ANSI.dim, "nothing needs you")}`
|
|
18103
18160
|
);
|
|
18104
18161
|
if (project.measures) {
|
|
18105
18162
|
const m = project.measures;
|
|
18106
|
-
|
|
18163
|
+
out2.push(
|
|
18107
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`
|
|
18108
18165
|
);
|
|
18109
18166
|
}
|
|
18110
|
-
|
|
18167
|
+
out2.push("");
|
|
18111
18168
|
}
|
|
18112
18169
|
if (projects.some((p) => p.measures)) {
|
|
18113
|
-
for (const line of MEASURE_DEFINITIONS)
|
|
18114
|
-
|
|
18170
|
+
for (const line of MEASURE_DEFINITIONS) out2.push(paint(ANSI.dim, ` ${line}`));
|
|
18171
|
+
out2.push("");
|
|
18115
18172
|
}
|
|
18116
|
-
|
|
18117
|
-
return
|
|
18173
|
+
out2.push(paint(ANSI.dim, " nanopm roadmap \xB7 nanopm problems \xB7 nanopm review --last"));
|
|
18174
|
+
return out2.join("\n");
|
|
18118
18175
|
}
|
|
18119
18176
|
|
|
18120
18177
|
// src/commands/schedule.ts
|