@nanopm/cli 0.1.5 → 0.1.6
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 +245 -124
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3856,10 +3856,10 @@ function parsePrintResult(stdout) {
|
|
|
3856
3856
|
}
|
|
3857
3857
|
|
|
3858
3858
|
// ../daemon/src/daemon.ts
|
|
3859
|
-
import { mkdir as mkdir5, readdir as readdir2, readFile as readFile7, rm as
|
|
3859
|
+
import { mkdir as mkdir5, readdir as readdir2, readFile as readFile7, rm as rm4, writeFile as writeFile4 } from "fs/promises";
|
|
3860
3860
|
import { openSync, closeSync, unlinkSync, writeFileSync, readFileSync as readFileSyncNode, mkdirSync } from "fs";
|
|
3861
3861
|
import { hostname as hostname2 } from "os";
|
|
3862
|
-
import { join as
|
|
3862
|
+
import { join as join11 } from "path";
|
|
3863
3863
|
import { setTimeout as sleep } from "timers/promises";
|
|
3864
3864
|
|
|
3865
3865
|
// ../daemon/src/awake.ts
|
|
@@ -3878,6 +3878,71 @@ function stayAwake(platform = process.platform) {
|
|
|
3878
3878
|
}
|
|
3879
3879
|
}
|
|
3880
3880
|
|
|
3881
|
+
// ../daemon/src/update.ts
|
|
3882
|
+
import { execFile as execFile2 } from "child_process";
|
|
3883
|
+
import { existsSync, realpathSync } from "fs";
|
|
3884
|
+
import { rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
3885
|
+
import { homedir as homedir2 } from "os";
|
|
3886
|
+
import { join as join4, sep } from "path";
|
|
3887
|
+
import { promisify as promisify2 } from "util";
|
|
3888
|
+
var PACKAGE = "@nanopm/cli";
|
|
3889
|
+
var LATEST_URL = "https://registry.npmjs.org/@nanopm%2fcli/latest";
|
|
3890
|
+
var RELEASE = /^\d+\.\d+\.\d+$/;
|
|
3891
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
3892
|
+
var root = (home = homedir2()) => join4(home, ".nanopm");
|
|
3893
|
+
var appDir = (home) => join4(root(home), "app");
|
|
3894
|
+
var offFlag = (home) => join4(root(home), "no-auto-update");
|
|
3895
|
+
function installKind(bin = process.argv[1], home = homedir2()) {
|
|
3896
|
+
if (!bin) return "other";
|
|
3897
|
+
let real = bin;
|
|
3898
|
+
try {
|
|
3899
|
+
real = realpathSync(bin);
|
|
3900
|
+
} catch {
|
|
3901
|
+
}
|
|
3902
|
+
return real.startsWith(appDir(home) + sep) ? "installer" : "other";
|
|
3903
|
+
}
|
|
3904
|
+
function newer(latest, current) {
|
|
3905
|
+
if (!latest || !RELEASE.test(latest) || !RELEASE.test(current)) return false;
|
|
3906
|
+
const a = latest.split(".").map(Number);
|
|
3907
|
+
const b = current.split(".").map(Number);
|
|
3908
|
+
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
|
|
3909
|
+
return false;
|
|
3910
|
+
}
|
|
3911
|
+
async function latestVersion(fetchImpl = fetch) {
|
|
3912
|
+
try {
|
|
3913
|
+
const res = await fetchImpl(LATEST_URL, { signal: AbortSignal.timeout(5e3) });
|
|
3914
|
+
if (!res.ok) return null;
|
|
3915
|
+
const { version } = await res.json();
|
|
3916
|
+
return typeof version === "string" && RELEASE.test(version) ? version : null;
|
|
3917
|
+
} catch {
|
|
3918
|
+
return null;
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
async function installVersion(version, home = homedir2()) {
|
|
3922
|
+
if (!RELEASE.test(version)) return { ok: false, why: `not a version: ${version}` };
|
|
3923
|
+
const runtime = join4(root(home), "runtime", "bin");
|
|
3924
|
+
try {
|
|
3925
|
+
await execFileAsync2(join4(runtime, "npm"), ["install", "-g", "--prefix", appDir(home), `${PACKAGE}@${version}`, "--no-fund", "--no-audit", "--loglevel=error"], {
|
|
3926
|
+
timeout: 5 * 6e4,
|
|
3927
|
+
env: { ...process.env, PATH: `${runtime}:${process.env.PATH ?? ""}`, NPM_CONFIG_UPDATE_NOTIFIER: "false" }
|
|
3928
|
+
});
|
|
3929
|
+
return { ok: true };
|
|
3930
|
+
} catch (err) {
|
|
3931
|
+
const failed = err;
|
|
3932
|
+
return { ok: false, why: (failed.stderr?.trim() || failed.message || String(err)).split("\n").slice(-3).join(" ") };
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
function autoUpdateOff(home = homedir2()) {
|
|
3936
|
+
return existsSync(offFlag(home));
|
|
3937
|
+
}
|
|
3938
|
+
async function setAutoUpdate(on, home = homedir2()) {
|
|
3939
|
+
if (on) await rm2(offFlag(home), { force: true });
|
|
3940
|
+
else await writeFile3(offFlag(home), "Automatic updates are off: `nanopm update --auto on` turns them back on.\n");
|
|
3941
|
+
}
|
|
3942
|
+
function shouldUpdate(s) {
|
|
3943
|
+
return s.kind === "installer" && !s.off && !s.quickBusy && newer(s.latest, s.current);
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3881
3946
|
// ../daemon/src/mcp.ts
|
|
3882
3947
|
function memoryMcpCommand(env = process.env) {
|
|
3883
3948
|
const override = env.NANOPM_MCP_COMMAND;
|
|
@@ -4865,7 +4930,7 @@ async function appStoreSearch(term, country, limit, opts = {}) {
|
|
|
4865
4930
|
|
|
4866
4931
|
// ../connectors/src/stores/play.ts
|
|
4867
4932
|
var SEARCH2 = "https://play.google.com/store/search";
|
|
4868
|
-
var
|
|
4933
|
+
var PACKAGE2 = /^[a-z][a-z0-9_]*(\.[a-z0-9_]+){2,}$/i;
|
|
4869
4934
|
function playSearchUrl(term, country) {
|
|
4870
4935
|
const url = new URL(SEARCH2);
|
|
4871
4936
|
url.searchParams.set("q", term);
|
|
@@ -4889,7 +4954,7 @@ function blobs(html) {
|
|
|
4889
4954
|
function cards(node, found = []) {
|
|
4890
4955
|
if (!Array.isArray(node)) return found;
|
|
4891
4956
|
const head = node[0];
|
|
4892
|
-
if (Array.isArray(head) && head.length === 2 && typeof head[0] === "string" &&
|
|
4957
|
+
if (Array.isArray(head) && head.length === 2 && typeof head[0] === "string" && PACKAGE2.test(head[0]) && head[1] === 7) {
|
|
4893
4958
|
found.push(node);
|
|
4894
4959
|
return found;
|
|
4895
4960
|
}
|
|
@@ -5022,13 +5087,13 @@ async function sendDigestFor(client, accessToken, jobId, fetchImpl = fetch) {
|
|
|
5022
5087
|
}
|
|
5023
5088
|
|
|
5024
5089
|
// ../daemon/src/deploys.ts
|
|
5025
|
-
import { execFile as
|
|
5026
|
-
import { promisify as
|
|
5027
|
-
var
|
|
5090
|
+
import { execFile as execFile3 } from "child_process";
|
|
5091
|
+
import { promisify as promisify3 } from "util";
|
|
5092
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
5028
5093
|
var SETTLE_HOURS = 3;
|
|
5029
5094
|
var MIN_COMMITS = 3;
|
|
5030
5095
|
async function git(cwd, args) {
|
|
5031
|
-
const { stdout } = await
|
|
5096
|
+
const { stdout } = await execFileAsync3("git", ["-C", cwd, ...args], { timeout: 2e4, maxBuffer: 1024 * 1024 });
|
|
5032
5097
|
return stdout.trim();
|
|
5033
5098
|
}
|
|
5034
5099
|
async function defaultBranch(cwd) {
|
|
@@ -5124,17 +5189,17 @@ async function reviewWithoutData(client, job) {
|
|
|
5124
5189
|
}
|
|
5125
5190
|
|
|
5126
5191
|
// ../daemon/src/skills.ts
|
|
5127
|
-
import { existsSync, readdirSync } from "fs";
|
|
5192
|
+
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
5128
5193
|
import { readFile as readFile2 } from "fs/promises";
|
|
5129
|
-
import { dirname as dirname2, join as
|
|
5194
|
+
import { dirname as dirname2, join as join5, resolve as resolve2 } from "path";
|
|
5130
5195
|
function findSkillsDir(env = process.env) {
|
|
5131
5196
|
if (env.NANOPM_SKILLS_DIR) return env.NANOPM_SKILLS_DIR;
|
|
5132
5197
|
const candidates = [
|
|
5133
|
-
|
|
5198
|
+
join5(process.cwd(), "skills"),
|
|
5134
5199
|
...process.argv[1] ? [resolve2(dirname2(process.argv[1]), "..", "skills")] : []
|
|
5135
5200
|
];
|
|
5136
5201
|
for (const dir of candidates) {
|
|
5137
|
-
if (
|
|
5202
|
+
if (existsSync2(join5(dir, "onboard", "skill.json"))) return dir;
|
|
5138
5203
|
}
|
|
5139
5204
|
throw new Error(`Cannot find the skills directory (tried ${candidates.join(", ")}). Set NANOPM_SKILLS_DIR.`);
|
|
5140
5205
|
}
|
|
@@ -5144,9 +5209,9 @@ function skillDirs(skillsDir, env = process.env) {
|
|
|
5144
5209
|
}
|
|
5145
5210
|
async function loadSkill(name, skillsDir = findSkillsDir()) {
|
|
5146
5211
|
if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`Invalid skill name "${name}"`);
|
|
5147
|
-
const dir = skillDirs(skillsDir).map((d) =>
|
|
5212
|
+
const dir = skillDirs(skillsDir).map((d) => join5(d, name)).find((d) => existsSync2(join5(d, "skill.json")));
|
|
5148
5213
|
if (!dir) throw new Error(`Unknown skill "${name}"`);
|
|
5149
|
-
const raw = JSON.parse(await readFile2(
|
|
5214
|
+
const raw = JSON.parse(await readFile2(join5(dir, "skill.json"), "utf8"));
|
|
5150
5215
|
let manifest;
|
|
5151
5216
|
try {
|
|
5152
5217
|
manifest = parseSkillManifest(raw);
|
|
@@ -5155,7 +5220,7 @@ async function loadSkill(name, skillsDir = findSkillsDir()) {
|
|
|
5155
5220
|
}
|
|
5156
5221
|
const tainted = taintedByTheWeb(manifest);
|
|
5157
5222
|
if (tainted) throw new Error(`the "${name}" skill cannot be loaded: ${tainted}`);
|
|
5158
|
-
const body = await readFile2(
|
|
5223
|
+
const body = await readFile2(join5(dir, "SKILL.md"), "utf8");
|
|
5159
5224
|
return { manifest, body, dir };
|
|
5160
5225
|
}
|
|
5161
5226
|
var NOT_WITH_READ_WEB = ["write_memory", "problems", "propose", "review", "write_repo", "run_commands"];
|
|
@@ -5190,7 +5255,7 @@ function renderPrompt(template, params) {
|
|
|
5190
5255
|
var NEEDS_THE_REQUESTERS_CLONE = ["write_repo", "run_commands"];
|
|
5191
5256
|
var NEEDS_THE_REQUESTERS_IDENTITY = /* @__PURE__ */ new Set(["want", "start"]);
|
|
5192
5257
|
async function clonelessSkills(skillsDir = findSkillsDir()) {
|
|
5193
|
-
const names = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory() &&
|
|
5258
|
+
const names = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && existsSync2(join5(skillsDir, d.name, "skill.json"))).map((d) => d.name);
|
|
5194
5259
|
const out = [];
|
|
5195
5260
|
for (const name of names) {
|
|
5196
5261
|
try {
|
|
@@ -5402,9 +5467,9 @@ async function runMappingJob({ client, job, adapter, spec, mcpServers, onEvent,
|
|
|
5402
5467
|
};
|
|
5403
5468
|
const { data: rootId, error: rootError } = await client.rpc("mapping_run_root", { p_job_id: job.id });
|
|
5404
5469
|
if (rootError) throw new Error(rootError.message);
|
|
5405
|
-
const { data:
|
|
5470
|
+
const { data: root2, error: readError } = await client.from("jobs").select("result").eq("id", rootId).single();
|
|
5406
5471
|
if (readError) throw new Error(readError.message);
|
|
5407
|
-
const saved =
|
|
5472
|
+
const saved = root2.result;
|
|
5408
5473
|
if (saved?.mapping_receipt) return { status: "succeeded", result: saved.mapping_audit?.checked.summary ?? `Problem map published: ${saved.mapping_receipt.refs.join(", ")}` };
|
|
5409
5474
|
const previousUsage = mappingUsage(saved?.mapping_checkpoints ?? []);
|
|
5410
5475
|
const result = await runProblemMapping({
|
|
@@ -5520,7 +5585,7 @@ var UNANSWERED = /structured[ _]output|returned no result/i;
|
|
|
5520
5585
|
|
|
5521
5586
|
// ../daemon/src/site.ts
|
|
5522
5587
|
import { readFile as readFile3 } from "fs/promises";
|
|
5523
|
-
import { join as
|
|
5588
|
+
import { join as join6 } from "path";
|
|
5524
5589
|
var SITE_PATHS = ["/", "/about", "/pricing"];
|
|
5525
5590
|
var PAGE_CHARS = 6e3;
|
|
5526
5591
|
var SITE_TIMEOUT_MS = 2e4;
|
|
@@ -5643,7 +5708,7 @@ function siteFromSearch(name, hits) {
|
|
|
5643
5708
|
}
|
|
5644
5709
|
async function read(repoRoot, name) {
|
|
5645
5710
|
try {
|
|
5646
|
-
const text2 = await readFile3(
|
|
5711
|
+
const text2 = await readFile3(join6(repoRoot, name), "utf8");
|
|
5647
5712
|
return text2.length > 2e5 ? null : text2;
|
|
5648
5713
|
} catch {
|
|
5649
5714
|
return null;
|
|
@@ -5698,7 +5763,7 @@ async function fromReadme(repoRoot) {
|
|
|
5698
5763
|
|
|
5699
5764
|
// ../daemon/src/repo-docs.ts
|
|
5700
5765
|
import { readFile as readFile4, readdir, stat } from "fs/promises";
|
|
5701
|
-
import { join as
|
|
5766
|
+
import { join as join7, relative } from "path";
|
|
5702
5767
|
var WRITTEN = /(persona|audience|customer|user-research|research\/|icp|segment)/i;
|
|
5703
5768
|
var ABOUT_PEOPLE = /(persona|audience|customer|user-research|research\/|interview|feedback|survey|verbatim|voice|icp|segment)/i;
|
|
5704
5769
|
var DOC = /\.(md|mdx|txt)$/i;
|
|
@@ -5716,7 +5781,7 @@ async function readDocs(cwd, opts = {}) {
|
|
|
5716
5781
|
return;
|
|
5717
5782
|
}
|
|
5718
5783
|
for (const entry of entries) {
|
|
5719
|
-
const full =
|
|
5784
|
+
const full = join7(dir, entry.name);
|
|
5720
5785
|
if (SKIP.test(`/${relative(cwd, full)}`)) continue;
|
|
5721
5786
|
if (entry.isDirectory()) await walk(full, depth + 1);
|
|
5722
5787
|
else if (DOC.test(entry.name) && (!opts.onlyAboutPeople || ABOUT_PEOPLE.test(relative(cwd, full)))) found.push(full);
|
|
@@ -7389,15 +7454,15 @@ async function mapRows(client, projectId, persona) {
|
|
|
7389
7454
|
function scopeNodes(rows2, scope) {
|
|
7390
7455
|
return scope ? descendants(rows2, scope) : [...rows2];
|
|
7391
7456
|
}
|
|
7392
|
-
function descendants(rows2,
|
|
7457
|
+
function descendants(rows2, root2) {
|
|
7393
7458
|
const byId = new Map(rows2.map((r) => [r.id, r]));
|
|
7394
7459
|
const current = (id) => {
|
|
7395
7460
|
let n = id ? byId.get(id) : void 0;
|
|
7396
7461
|
for (let hops = 0; n && n.status === "replaced" && n.replaced_by && hops < 50; hops += 1) n = byId.get(n.replaced_by);
|
|
7397
7462
|
return n && n.status !== "replaced" ? n : null;
|
|
7398
7463
|
};
|
|
7399
|
-
const out = [
|
|
7400
|
-
const ids = /* @__PURE__ */ new Set([
|
|
7464
|
+
const out = [root2];
|
|
7465
|
+
const ids = /* @__PURE__ */ new Set([root2.id]);
|
|
7401
7466
|
for (let grew = true; grew; ) {
|
|
7402
7467
|
grew = false;
|
|
7403
7468
|
for (const r of rows2) {
|
|
@@ -7706,7 +7771,7 @@ function opportunitiesOutputSchema(which) {
|
|
|
7706
7771
|
// ../daemon/src/crew.ts
|
|
7707
7772
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
7708
7773
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
7709
|
-
import { join as
|
|
7774
|
+
import { join as join8 } from "path";
|
|
7710
7775
|
var READ_ONLY = {
|
|
7711
7776
|
read_repo: /^(Read|Glob|Grep)$/,
|
|
7712
7777
|
read_data: /(^|__)(list_connections|describe_source|query)$/
|
|
@@ -7721,7 +7786,7 @@ async function attempt6({ adapter, spec, onEvent, skill: skillName, name, schema
|
|
|
7721
7786
|
if (skill.manifest.capabilities.some((c) => !holds.includes(c))) throw new Error(`The ${name} may not hold tools.`);
|
|
7722
7787
|
const refused = grant.find((c) => !READ_ONLY[c] || !skill.manifest.capabilities.includes(c));
|
|
7723
7788
|
if (refused) throw new Error(`The ${name} may not hold ${refused}.`);
|
|
7724
|
-
const runDir = dir ?
|
|
7789
|
+
const runDir = dir ? join8(spec.runDir, dir) : spec.runDir;
|
|
7725
7790
|
if (dir) await mkdir2(runDir, { recursive: true, mode: 448 });
|
|
7726
7791
|
let result;
|
|
7727
7792
|
let usage = null;
|
|
@@ -9398,21 +9463,21 @@ var told = (r) => ({
|
|
|
9398
9463
|
});
|
|
9399
9464
|
|
|
9400
9465
|
// ../daemon/src/objective-definitions.ts
|
|
9401
|
-
import { execFile as
|
|
9466
|
+
import { execFile as execFile5 } from "child_process";
|
|
9402
9467
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
9403
|
-
import { existsSync as
|
|
9468
|
+
import { existsSync as existsSync3 } from "fs";
|
|
9404
9469
|
import { mkdir as mkdir3, readFile as readFile5 } from "fs/promises";
|
|
9405
|
-
import { join as
|
|
9406
|
-
import { promisify as
|
|
9470
|
+
import { join as join9 } from "path";
|
|
9471
|
+
import { promisify as promisify5 } from "util";
|
|
9407
9472
|
|
|
9408
9473
|
// ../daemon/src/git.ts
|
|
9409
|
-
import { execFile as
|
|
9410
|
-
import { promisify as
|
|
9411
|
-
var
|
|
9474
|
+
import { execFile as execFile4 } from "child_process";
|
|
9475
|
+
import { promisify as promisify4 } from "util";
|
|
9476
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
9412
9477
|
var MAX_BUFFER = 4 * 1024 * 1024;
|
|
9413
9478
|
var WIKI_DIR = ".nanopm/wiki/docs";
|
|
9414
9479
|
async function git2(cwd, args) {
|
|
9415
|
-
const { stdout } = await
|
|
9480
|
+
const { stdout } = await execFileAsync4("git", ["-C", cwd, ...args], { maxBuffer: MAX_BUFFER, timeout: 2e4 });
|
|
9416
9481
|
return stdout.trim();
|
|
9417
9482
|
}
|
|
9418
9483
|
async function listTrackedFiles(cwd) {
|
|
@@ -9511,7 +9576,7 @@ function renderTrackedFiles(files, max = TRACKED_FILES_MAX, wiki = []) {
|
|
|
9511
9576
|
}
|
|
9512
9577
|
|
|
9513
9578
|
// ../daemon/src/objective-definitions.ts
|
|
9514
|
-
var
|
|
9579
|
+
var execFileAsync5 = promisify5(execFile5);
|
|
9515
9580
|
var DEFINITIONS_TIMEOUT_MS = 9e4;
|
|
9516
9581
|
var DEFINITIONS_THINKING_TOKENS = 2e3;
|
|
9517
9582
|
var READ_TOOLS3 = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "StructuredOutput"]);
|
|
@@ -9520,7 +9585,7 @@ var COUNTING_LINES_MAX = 80;
|
|
|
9520
9585
|
var PER_FILE = 4;
|
|
9521
9586
|
async function countingLines(cwd) {
|
|
9522
9587
|
try {
|
|
9523
|
-
const { stdout } = await
|
|
9588
|
+
const { stdout } = await execFileAsync5("git", ["grep", "-n", "-I", "-i", "-E", COUNTING, "--", ".", ":!*lock*", ":!*.min.*", ":!*.map", ":!*.svg", ":!*.snap"], {
|
|
9524
9589
|
cwd,
|
|
9525
9590
|
maxBuffer: 16 * 1024 * 1024,
|
|
9526
9591
|
timeout: 5e3
|
|
@@ -9545,7 +9610,7 @@ function pickLines(raw) {
|
|
|
9545
9610
|
}
|
|
9546
9611
|
async function killRecorded(runDir) {
|
|
9547
9612
|
try {
|
|
9548
|
-
const pid = Number((await readFile5(
|
|
9613
|
+
const pid = Number((await readFile5(join9(runDir, "harness.pid"), "utf8")).trim());
|
|
9549
9614
|
if (pid > 1) process.kill(pid, "SIGKILL");
|
|
9550
9615
|
} catch {
|
|
9551
9616
|
}
|
|
@@ -9565,7 +9630,7 @@ async function readDefinitions({ adapter, spec, publish: publish5, journal, hasR
|
|
|
9565
9630
|
if (!hasRepo) return give({}, "No repository to read: the card offers the common definitions.", "ok");
|
|
9566
9631
|
try {
|
|
9567
9632
|
const skill = await loadSkill("define-objective");
|
|
9568
|
-
const runDir =
|
|
9633
|
+
const runDir = join9(spec.runDir, "definitions");
|
|
9569
9634
|
await mkdir3(runDir, { recursive: true, mode: 448 });
|
|
9570
9635
|
const schema = definitionsOutputSchema();
|
|
9571
9636
|
const [lines, listed] = await Promise.all([countingLines(spec.cwd), listTrackedFiles(spec.cwd)]);
|
|
@@ -9627,7 +9692,7 @@ ${renderTrackedFiles(listed.files, void 0, listed.wiki)}
|
|
|
9627
9692
|
if (!result) throw new Error("no answer");
|
|
9628
9693
|
const parsed = DefinitionsAnswer.safeParse(JSON.parse(result));
|
|
9629
9694
|
if (!parsed.success) throw new Error("an answer in the wrong shape");
|
|
9630
|
-
const checked = checkProposals(parsed.data, (file) =>
|
|
9695
|
+
const checked = checkProposals(parsed.data, (file) => existsSync3(join9(spec.cwd, file)));
|
|
9631
9696
|
const definitions2 = lever ? checked[lever] ? { [lever]: checked[lever] } : {} : checked;
|
|
9632
9697
|
const kept = Object.values(definitions2).reduce((n, list) => n + (list?.length ?? 0), 0);
|
|
9633
9698
|
const read6 = Object.values(definitions2).flat().filter((d) => d?.found).length;
|
|
@@ -9900,10 +9965,10 @@ function localDate(timezone, at) {
|
|
|
9900
9965
|
|
|
9901
9966
|
// ../daemon/src/run-job.ts
|
|
9902
9967
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
9903
|
-
import { existsSync as
|
|
9904
|
-
import { mkdir as mkdir4, readFile as readFile6, rm as
|
|
9905
|
-
import { homedir as
|
|
9906
|
-
import { join as
|
|
9968
|
+
import { existsSync as existsSync4 } from "fs";
|
|
9969
|
+
import { mkdir as mkdir4, readFile as readFile6, rm as rm3 } from "fs/promises";
|
|
9970
|
+
import { homedir as homedir3, tmpdir } from "os";
|
|
9971
|
+
import { join as join10, resolve as resolve4 } from "path";
|
|
9907
9972
|
|
|
9908
9973
|
// ../daemon/src/events.ts
|
|
9909
9974
|
var JobEventWriter = class {
|
|
@@ -9984,13 +10049,13 @@ function lighten(value) {
|
|
|
9984
10049
|
}
|
|
9985
10050
|
|
|
9986
10051
|
// ../daemon/src/repo.ts
|
|
9987
|
-
import { execFile as
|
|
9988
|
-
import { promisify as
|
|
9989
|
-
var
|
|
10052
|
+
import { execFile as execFile6 } from "child_process";
|
|
10053
|
+
import { promisify as promisify6 } from "util";
|
|
10054
|
+
var execFileAsync6 = promisify6(execFile6);
|
|
9990
10055
|
var MAX_BUFFER2 = 4 * 1024 * 1024;
|
|
9991
10056
|
var TIMEOUT_MS = 6e4;
|
|
9992
10057
|
async function git3(cwd, args, timeoutMs = TIMEOUT_MS) {
|
|
9993
|
-
const { stdout } = await
|
|
10058
|
+
const { stdout } = await execFileAsync6("git", ["-C", cwd, ...args], { maxBuffer: MAX_BUFFER2, timeout: timeoutMs });
|
|
9994
10059
|
return stdout.trim();
|
|
9995
10060
|
}
|
|
9996
10061
|
var RepoError = class extends Error {
|
|
@@ -10061,7 +10126,7 @@ async function pushBranch(cwd, name) {
|
|
|
10061
10126
|
const state = await readState(cwd);
|
|
10062
10127
|
if (!state.hasRemote) return { pushed: false, reason: "this repository has no origin remote" };
|
|
10063
10128
|
try {
|
|
10064
|
-
const { stderr } = await
|
|
10129
|
+
const { stderr } = await execFileAsync6("git", ["-C", cwd, "push", "--set-upstream", "origin", name], {
|
|
10065
10130
|
maxBuffer: MAX_BUFFER2,
|
|
10066
10131
|
timeout: 12e4
|
|
10067
10132
|
});
|
|
@@ -10341,7 +10406,7 @@ async function runJob(job, ctx) {
|
|
|
10341
10406
|
return outcome;
|
|
10342
10407
|
}
|
|
10343
10408
|
async function execute(job, skill, ctx, events) {
|
|
10344
|
-
const runDir =
|
|
10409
|
+
const runDir = join10(nanopmHome(), "run", job.id);
|
|
10345
10410
|
await mkdir4(runDir, { recursive: true, mode: 448 });
|
|
10346
10411
|
const resumeSessionId = job.resume_of ? await sessionOf(ctx.client, job.resume_of) : void 0;
|
|
10347
10412
|
const sessionId = resumeSessionId ?? randomUUID12();
|
|
@@ -10386,7 +10451,7 @@ async function execute(job, skill, ctx, events) {
|
|
|
10386
10451
|
clearTimeout(timer);
|
|
10387
10452
|
clearInterval(heartbeat2);
|
|
10388
10453
|
clearInterval(cancelPoll);
|
|
10389
|
-
await
|
|
10454
|
+
await rm3(runDir, { recursive: true, force: true });
|
|
10390
10455
|
}
|
|
10391
10456
|
}
|
|
10392
10457
|
const quickRun2 = QUICK_RUNS[job.skill];
|
|
@@ -10413,8 +10478,8 @@ async function execute(job, skill, ctx, events) {
|
|
|
10413
10478
|
clearTimeout(timer);
|
|
10414
10479
|
clearInterval(heartbeat2);
|
|
10415
10480
|
clearInterval(cancelPoll);
|
|
10416
|
-
await
|
|
10417
|
-
await
|
|
10481
|
+
await rm3(runDir, { recursive: true, force: true });
|
|
10482
|
+
await rm3(cwd2, { recursive: true, force: true });
|
|
10418
10483
|
}
|
|
10419
10484
|
}
|
|
10420
10485
|
const capabilities = capCapabilities(skill.manifest.capabilities, autonomy2);
|
|
@@ -10622,7 +10687,7 @@ async function execute(job, skill, ctx, events) {
|
|
|
10622
10687
|
clearInterval(heartbeat2);
|
|
10623
10688
|
clearInterval(cancelPoll);
|
|
10624
10689
|
await definitionsRead;
|
|
10625
|
-
await
|
|
10690
|
+
await rm3(runDir, { recursive: true, force: true });
|
|
10626
10691
|
}
|
|
10627
10692
|
if (outcome?.status === "succeeded" && verified && verified.ran && !verified.passed) {
|
|
10628
10693
|
outcome = {
|
|
@@ -10704,7 +10769,7 @@ function harnessWebFor(searchProvider, machineCanSearch) {
|
|
|
10704
10769
|
}
|
|
10705
10770
|
async function machineWebSearch() {
|
|
10706
10771
|
try {
|
|
10707
|
-
const parsed = JSON.parse(await readFile6(
|
|
10772
|
+
const parsed = JSON.parse(await readFile6(join10(nanopmHome(), "daemon.json"), "utf8"));
|
|
10708
10773
|
return parsed.webSearch === true;
|
|
10709
10774
|
} catch {
|
|
10710
10775
|
return false;
|
|
@@ -10746,7 +10811,7 @@ function buildHeader(job, project, cwd, harnessWeb = false, memory = false) {
|
|
|
10746
10811
|
return lines.join("\n");
|
|
10747
10812
|
}
|
|
10748
10813
|
function scratchDir(jobId) {
|
|
10749
|
-
return
|
|
10814
|
+
return join10(tmpdir(), `nanopm-run-${jobId}`);
|
|
10750
10815
|
}
|
|
10751
10816
|
async function resolveCwd(ctx, job, params, fallback, mustBeTheClone = false) {
|
|
10752
10817
|
const refuse2 = (why) => {
|
|
@@ -10771,7 +10836,7 @@ async function resolveCwd(ctx, job, params, fallback, mustBeTheClone = false) {
|
|
|
10771
10836
|
await mkdir4(fallback, { recursive: true, mode: 448 });
|
|
10772
10837
|
return fallback;
|
|
10773
10838
|
}
|
|
10774
|
-
if (
|
|
10839
|
+
if (existsSync4(allowed)) return resolve4(allowed);
|
|
10775
10840
|
if (mustBeTheClone) refuse2(`${allowed} is not on this machine any more`);
|
|
10776
10841
|
await mkdir4(fallback, { recursive: true, mode: 448 });
|
|
10777
10842
|
return fallback;
|
|
@@ -10782,10 +10847,10 @@ ${body}
|
|
|
10782
10847
|
</${label}>`;
|
|
10783
10848
|
}
|
|
10784
10849
|
function harnessEnv(timeoutMs, source = process.env) {
|
|
10785
|
-
const home = source.HOME ??
|
|
10850
|
+
const home = source.HOME ?? homedir3();
|
|
10786
10851
|
const env = {
|
|
10787
10852
|
HOME: home,
|
|
10788
|
-
PATH: [source.PATH ?? "/usr/local/bin:/usr/bin:/bin",
|
|
10853
|
+
PATH: [source.PATH ?? "/usr/local/bin:/usr/bin:/bin", join10(home, ".local", "bin")].join(":"),
|
|
10789
10854
|
MCP_TOOL_TIMEOUT: String(timeoutMs)
|
|
10790
10855
|
};
|
|
10791
10856
|
for (const key of ["USER", "LANG", "TMPDIR", "SHELL", "ANTHROPIC_API_KEY", "CLAUDE_CONFIG_DIR"]) {
|
|
@@ -10865,6 +10930,10 @@ async function runDaemon(options = {}) {
|
|
|
10865
10930
|
const startedAt = Date.now();
|
|
10866
10931
|
let lastDeployCheck = 0;
|
|
10867
10932
|
const DEPLOY_CHECK_MS = 30 * 6e4;
|
|
10933
|
+
const UPDATE_CHECK_MS = 60 * 6e4;
|
|
10934
|
+
const updatable = !options.once && installKind() === "installer";
|
|
10935
|
+
let lastUpdateCheck = Date.now() - UPDATE_CHECK_MS + 5 * 6e4;
|
|
10936
|
+
let restart = false;
|
|
10868
10937
|
const cloneless = await clonelessSkills().catch(() => []);
|
|
10869
10938
|
const staleAfter = process.env.NANOPM_STALE_DAEMON_AFTER ?? "2 minutes";
|
|
10870
10939
|
let testingConnections = false;
|
|
@@ -10924,6 +10993,21 @@ async function runDaemon(options = {}) {
|
|
|
10924
10993
|
if (check.queued) log(`deploy on ${check.projectName}: review queued (${check.reason})`);
|
|
10925
10994
|
}
|
|
10926
10995
|
}
|
|
10996
|
+
if (updatable && Date.now() - lastUpdateCheck > UPDATE_CHECK_MS) {
|
|
10997
|
+
lastUpdateCheck = Date.now();
|
|
10998
|
+
const current = options.version ?? "dev";
|
|
10999
|
+
const off = autoUpdateOff();
|
|
11000
|
+
const latest = off ? null : await latestVersion();
|
|
11001
|
+
if (shouldUpdate({ kind: "installer", off, quickBusy: quick?.busy() ?? false, latest, current })) {
|
|
11002
|
+
const done = await installVersion(latest);
|
|
11003
|
+
if (done.ok) {
|
|
11004
|
+
log(`updated to ${latest}; restarting on it`);
|
|
11005
|
+
restart = true;
|
|
11006
|
+
break;
|
|
11007
|
+
}
|
|
11008
|
+
log(`update to ${latest} failed: ${done.why}; staying on ${current}`);
|
|
11009
|
+
}
|
|
11010
|
+
}
|
|
10927
11011
|
await heartbeat(auth.client, daemonId);
|
|
10928
11012
|
await sleep(pollIntervalMs, void 0, { signal: options.signal }).catch(() => void 0);
|
|
10929
11013
|
}
|
|
@@ -10934,7 +11018,7 @@ async function runDaemon(options = {}) {
|
|
|
10934
11018
|
lock.release();
|
|
10935
11019
|
log(`daemon ${daemonId} offline`);
|
|
10936
11020
|
}
|
|
10937
|
-
return { daemonId, processed };
|
|
11021
|
+
return { daemonId, processed, restart };
|
|
10938
11022
|
}
|
|
10939
11023
|
async function chainAfter(auth, job, outcome, log) {
|
|
10940
11024
|
if (NOT_CHAINED_BY_DAEMON.includes(job.skill)) return;
|
|
@@ -10986,7 +11070,7 @@ function quickLane(ctx, daemonId, log, everyMs = QUICK_POLL_MS) {
|
|
|
10986
11070
|
});
|
|
10987
11071
|
}, everyMs);
|
|
10988
11072
|
timer.unref?.();
|
|
10989
|
-
return { stop: () => clearInterval(timer) };
|
|
11073
|
+
return { stop: () => clearInterval(timer), busy: () => busy };
|
|
10990
11074
|
}
|
|
10991
11075
|
async function parentOf(auth, id) {
|
|
10992
11076
|
const { data } = await auth.client.from("jobs").select("skill, params").eq("id", id).maybeSingle();
|
|
@@ -11031,7 +11115,7 @@ var heldInThisProcess = false;
|
|
|
11031
11115
|
function acquireLock() {
|
|
11032
11116
|
if (heldInThisProcess) throw new Error("a nanopm daemon is already running in this process");
|
|
11033
11117
|
mkdirSync(nanopmHome(), { recursive: true, mode: 448 });
|
|
11034
|
-
const path =
|
|
11118
|
+
const path = join11(nanopmHome(), "daemon.pid");
|
|
11035
11119
|
for (; ; ) {
|
|
11036
11120
|
try {
|
|
11037
11121
|
const fd = openSync(path, "wx");
|
|
@@ -11118,13 +11202,13 @@ async function reclaimOwnJobs(client, daemonId) {
|
|
|
11118
11202
|
async function harnessPids(runDir) {
|
|
11119
11203
|
const dirs = [runDir];
|
|
11120
11204
|
try {
|
|
11121
|
-
for (const d of await readdir2(runDir, { withFileTypes: true })) if (d.isDirectory()) dirs.push(
|
|
11205
|
+
for (const d of await readdir2(runDir, { withFileTypes: true })) if (d.isDirectory()) dirs.push(join11(runDir, d.name));
|
|
11122
11206
|
} catch {
|
|
11123
11207
|
}
|
|
11124
11208
|
const pids = [];
|
|
11125
11209
|
for (const dir of dirs) {
|
|
11126
11210
|
try {
|
|
11127
|
-
const pid = Number((await readFile7(
|
|
11211
|
+
const pid = Number((await readFile7(join11(dir, "harness.pid"), "utf8")).trim());
|
|
11128
11212
|
if (pid > 1) pids.push(pid);
|
|
11129
11213
|
} catch {
|
|
11130
11214
|
}
|
|
@@ -11132,17 +11216,17 @@ async function harnessPids(runDir) {
|
|
|
11132
11216
|
return pids;
|
|
11133
11217
|
}
|
|
11134
11218
|
async function killOrphanedHarness(jobId) {
|
|
11135
|
-
const runDir =
|
|
11219
|
+
const runDir = join11(nanopmHome(), "run", jobId);
|
|
11136
11220
|
for (const pid of await harnessPids(runDir)) {
|
|
11137
11221
|
try {
|
|
11138
11222
|
process.kill(pid, "SIGTERM");
|
|
11139
11223
|
} catch {
|
|
11140
11224
|
}
|
|
11141
11225
|
}
|
|
11142
|
-
await
|
|
11226
|
+
await rm4(runDir, { recursive: true, force: true });
|
|
11143
11227
|
}
|
|
11144
11228
|
async function registerDaemon(auth, version, opts = {}) {
|
|
11145
|
-
const path =
|
|
11229
|
+
const path = join11(nanopmHome(), "daemon.json");
|
|
11146
11230
|
let identity = null;
|
|
11147
11231
|
try {
|
|
11148
11232
|
identity = JSON.parse(await readFile7(path, "utf8"));
|
|
@@ -11184,7 +11268,7 @@ async function registerDaemon(auth, version, opts = {}) {
|
|
|
11184
11268
|
}
|
|
11185
11269
|
async function rememberDaemon(id, shared = false) {
|
|
11186
11270
|
await mkdir5(nanopmHome(), { recursive: true, mode: 448 });
|
|
11187
|
-
await
|
|
11271
|
+
await writeFile4(join11(nanopmHome(), "daemon.json"), JSON.stringify({ id, shared }, null, 2) + "\n", { mode: 384 });
|
|
11188
11272
|
}
|
|
11189
11273
|
async function heartbeat(client, daemonId) {
|
|
11190
11274
|
await client.from("daemons").update({ status: "online", last_seen_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", daemonId);
|
|
@@ -11389,11 +11473,11 @@ function checkAgainstDecisions(item, prior, sourceDates) {
|
|
|
11389
11473
|
}
|
|
11390
11474
|
if (match.status === "rejected") {
|
|
11391
11475
|
if (!match.decidedAt) return `"${item.title}" was rejected; it comes back only with evidence written since`;
|
|
11392
|
-
const
|
|
11476
|
+
const newer2 = item.sources.some((s) => {
|
|
11393
11477
|
const at = sourceDates(s.ref);
|
|
11394
11478
|
return at !== void 0 && at > match.decidedAt;
|
|
11395
11479
|
});
|
|
11396
|
-
if (!
|
|
11480
|
+
if (!newer2) {
|
|
11397
11481
|
return `"${item.title}" was rejected on ${match.decidedAt.slice(0, 10)}. It comes back only citing something learned since, and saying what changed`;
|
|
11398
11482
|
}
|
|
11399
11483
|
}
|
|
@@ -11537,14 +11621,14 @@ function refuseFetch(url, allowlist, open = false) {
|
|
|
11537
11621
|
|
|
11538
11622
|
// ../memory-mcp/src/repo-rules.ts
|
|
11539
11623
|
import { lstat, unlink } from "fs/promises";
|
|
11540
|
-
import { relative as relative3, resolve as resolve5, sep } from "path";
|
|
11624
|
+
import { relative as relative3, resolve as resolve5, sep as sep2 } from "path";
|
|
11541
11625
|
var REMOVE_MAX = 50;
|
|
11542
11626
|
function refuseRemove(workspace, requested) {
|
|
11543
|
-
const
|
|
11544
|
-
const target = resolve5(
|
|
11545
|
-
const rel = relative3(
|
|
11546
|
-
if (!rel || rel.startsWith("..") || rel.startsWith(`..${
|
|
11547
|
-
if (rel === ".git" || rel.startsWith(`.git${
|
|
11627
|
+
const root2 = resolve5(workspace);
|
|
11628
|
+
const target = resolve5(root2, requested);
|
|
11629
|
+
const rel = relative3(root2, target);
|
|
11630
|
+
if (!rel || rel.startsWith("..") || rel.startsWith(`..${sep2}`)) return "outside the workspace";
|
|
11631
|
+
if (rel === ".git" || rel.startsWith(`.git${sep2}`)) return "inside .git";
|
|
11548
11632
|
if (looksLikeASecret(rel)) return "looks like a secret, which the agent may neither read nor remove";
|
|
11549
11633
|
return null;
|
|
11550
11634
|
}
|
|
@@ -13163,16 +13247,16 @@ async function startMemoryServer(env = process.env) {
|
|
|
13163
13247
|
import { readFile as readFile9 } from "fs/promises";
|
|
13164
13248
|
|
|
13165
13249
|
// src/project.ts
|
|
13166
|
-
import { execFile as
|
|
13250
|
+
import { execFile as execFile7 } from "child_process";
|
|
13167
13251
|
import { basename } from "path";
|
|
13168
|
-
import { promisify as
|
|
13252
|
+
import { promisify as promisify7 } from "util";
|
|
13169
13253
|
|
|
13170
13254
|
// src/local.ts
|
|
13171
13255
|
import { readFile as readFile8 } from "fs/promises";
|
|
13172
|
-
import { join as
|
|
13256
|
+
import { join as join12 } from "path";
|
|
13173
13257
|
async function localDaemonId() {
|
|
13174
13258
|
try {
|
|
13175
|
-
const parsed = JSON.parse(await readFile8(
|
|
13259
|
+
const parsed = JSON.parse(await readFile8(join12(nanopmHome(), "daemon.json"), "utf8"));
|
|
13176
13260
|
return parsed.id ?? null;
|
|
13177
13261
|
} catch {
|
|
13178
13262
|
return null;
|
|
@@ -13183,30 +13267,30 @@ function useColor() {
|
|
|
13183
13267
|
}
|
|
13184
13268
|
|
|
13185
13269
|
// src/project.ts
|
|
13186
|
-
var
|
|
13270
|
+
var execFileAsync7 = promisify7(execFile7);
|
|
13187
13271
|
async function detectRepo(cwd) {
|
|
13188
|
-
let
|
|
13272
|
+
let root2;
|
|
13189
13273
|
try {
|
|
13190
|
-
|
|
13274
|
+
root2 = (await execFileAsync7("git", ["-C", cwd, "rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13191
13275
|
} catch {
|
|
13192
13276
|
return null;
|
|
13193
13277
|
}
|
|
13194
13278
|
let remote = null;
|
|
13195
13279
|
try {
|
|
13196
|
-
remote = canonicalRemote((await
|
|
13280
|
+
remote = canonicalRemote((await execFileAsync7("git", ["-C", root2, "remote", "get-url", "origin"])).stdout.trim());
|
|
13197
13281
|
} catch {
|
|
13198
13282
|
remote = null;
|
|
13199
13283
|
}
|
|
13200
|
-
const name = remote ? remote.split("/").pop() : basename(
|
|
13201
|
-
return { root, remote, name, empty: await repoIsEmpty(
|
|
13284
|
+
const name = remote ? remote.split("/").pop() : basename(root2);
|
|
13285
|
+
return { root: root2, remote, name, empty: await repoIsEmpty(root2) };
|
|
13202
13286
|
}
|
|
13203
|
-
async function repoIsEmpty(
|
|
13287
|
+
async function repoIsEmpty(root2) {
|
|
13204
13288
|
try {
|
|
13205
|
-
await
|
|
13289
|
+
await execFileAsync7("git", ["-C", root2, "rev-parse", "--verify", "HEAD"]);
|
|
13206
13290
|
} catch {
|
|
13207
13291
|
return true;
|
|
13208
13292
|
}
|
|
13209
|
-
const files = (await
|
|
13293
|
+
const files = (await execFileAsync7("git", ["-C", root2, "ls-files"])).stdout.split("\n").filter(Boolean);
|
|
13210
13294
|
return files.every((f) => {
|
|
13211
13295
|
const base = basename(f);
|
|
13212
13296
|
return base.startsWith(".") || /^(readme|licen[cs]e)(\.|$)/i.test(base);
|
|
@@ -13875,17 +13959,17 @@ async function askOne(auth, projectId, question, daemonId, opts, out) {
|
|
|
13875
13959
|
}
|
|
13876
13960
|
|
|
13877
13961
|
// src/commands/daemon-service.ts
|
|
13878
|
-
import { execFile as
|
|
13879
|
-
import { existsSync as
|
|
13880
|
-
import { mkdir as mkdir6, readFile as readFile11, rm as
|
|
13881
|
-
import { homedir as
|
|
13882
|
-
import { join as
|
|
13883
|
-
import { promisify as
|
|
13962
|
+
import { execFile as execFile8 } from "child_process";
|
|
13963
|
+
import { existsSync as existsSync5 } from "fs";
|
|
13964
|
+
import { mkdir as mkdir6, readFile as readFile11, rm as rm5, writeFile as writeFile5 } from "fs/promises";
|
|
13965
|
+
import { homedir as homedir4 } from "os";
|
|
13966
|
+
import { join as join13 } from "path";
|
|
13967
|
+
import { promisify as promisify8 } from "util";
|
|
13884
13968
|
|
|
13885
13969
|
// package.json
|
|
13886
13970
|
var package_default = {
|
|
13887
13971
|
name: "@nanopm/cli",
|
|
13888
|
-
version: "0.1.
|
|
13972
|
+
version: "0.1.6",
|
|
13889
13973
|
description: "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
|
|
13890
13974
|
type: "module",
|
|
13891
13975
|
license: "MIT",
|
|
@@ -13934,13 +14018,13 @@ var package_default = {
|
|
|
13934
14018
|
var VERSION = package_default.version;
|
|
13935
14019
|
|
|
13936
14020
|
// src/commands/daemon-service.ts
|
|
13937
|
-
var
|
|
14021
|
+
var execFileAsync8 = promisify8(execFile8);
|
|
13938
14022
|
var LAUNCHD_LABEL = "pm.nano.daemon";
|
|
13939
14023
|
function plistPath() {
|
|
13940
|
-
return
|
|
14024
|
+
return join13(homedir4(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
13941
14025
|
}
|
|
13942
14026
|
function daemonLogPath() {
|
|
13943
|
-
return
|
|
14027
|
+
return join13(nanopmHome(), "daemon.log");
|
|
13944
14028
|
}
|
|
13945
14029
|
function renderPlist(opts) {
|
|
13946
14030
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -13999,31 +14083,39 @@ async function installDaemon(out = (l) => console.log(l), opts = {}) {
|
|
|
13999
14083
|
out(web.ok ? "Claude's own search answers; searches go through your subscription." : `Claude's search is not available on this machine (${web.message}); searches use DuckDuckGo.`);
|
|
14000
14084
|
const bin = process.argv[1];
|
|
14001
14085
|
if (!bin) throw new Error("cannot locate the nanopm binary");
|
|
14002
|
-
const home =
|
|
14003
|
-
const path = [process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
|
|
14004
|
-
await mkdir6(
|
|
14086
|
+
const home = homedir4();
|
|
14087
|
+
const path = [process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin", join13(home, ".local", "bin")].join(":");
|
|
14088
|
+
await mkdir6(join13(home, "Library", "LaunchAgents"), { recursive: true });
|
|
14005
14089
|
await mkdir6(nanopmHome(), { recursive: true, mode: 448 });
|
|
14006
14090
|
if (opts.shared !== void 0) await rememberShared(opts.shared);
|
|
14007
14091
|
await found({ ...detection, authenticated: true });
|
|
14008
|
-
await
|
|
14009
|
-
const target =
|
|
14010
|
-
await
|
|
14011
|
-
await
|
|
14092
|
+
await writeFile5(plistPath(), renderPlist({ node: process.execPath, bin, home, path, logPath: daemonLogPath(), nanopmHome: nanopmHome() }), { mode: 420 });
|
|
14093
|
+
const target = guiTarget();
|
|
14094
|
+
await execFileAsync8("launchctl", ["bootout", target, plistPath()]).catch(() => void 0);
|
|
14095
|
+
await execFileAsync8("launchctl", ["bootstrap", target, plistPath()]);
|
|
14012
14096
|
out(`Installed ${LAUNCHD_LABEL}; the daemon starts now and at every login. Logs: ${daemonLogPath()}`);
|
|
14013
14097
|
const shared = opts.shared ?? await localShared();
|
|
14014
14098
|
out(shared ? "This machine is shared with your projects: it also runs scheduled reviews and partners' jobs." : "This machine runs your jobs only. `nanopm daemon install --share` to offer it to your projects.");
|
|
14015
14099
|
if (opts.next !== false) out(`Next, in a repo: nanopm onboard \xB7 or continue at ${webUrl()}/new`);
|
|
14016
14100
|
}
|
|
14101
|
+
function guiTarget() {
|
|
14102
|
+
return `gui/${process.getuid?.() ?? 501}`;
|
|
14103
|
+
}
|
|
14104
|
+
async function restartDaemon() {
|
|
14105
|
+
if (!existsSync5(plistPath())) return false;
|
|
14106
|
+
await execFileAsync8("launchctl", ["kickstart", "-k", `${guiTarget()}/${LAUNCHD_LABEL}`]);
|
|
14107
|
+
return true;
|
|
14108
|
+
}
|
|
14017
14109
|
async function uninstallDaemon(out = (l) => console.log(l)) {
|
|
14018
14110
|
requireMac();
|
|
14019
|
-
const target =
|
|
14020
|
-
await
|
|
14021
|
-
await
|
|
14111
|
+
const target = guiTarget();
|
|
14112
|
+
await execFileAsync8("launchctl", ["bootout", target, plistPath()]).catch(() => void 0);
|
|
14113
|
+
await rm5(plistPath(), { force: true });
|
|
14022
14114
|
out(`Removed ${LAUNCHD_LABEL}.`);
|
|
14023
14115
|
}
|
|
14024
14116
|
async function daemonStatus(out = (l) => console.log(l)) {
|
|
14025
14117
|
const id = await localDaemonId();
|
|
14026
|
-
const installed = process.platform === "darwin" &&
|
|
14118
|
+
const installed = process.platform === "darwin" && existsSync5(plistPath());
|
|
14027
14119
|
out(`launchd: ${installed ? "installed" : "not installed"}`);
|
|
14028
14120
|
if (!id) {
|
|
14029
14121
|
out("daemon: never registered on this machine");
|
|
@@ -14053,14 +14145,14 @@ async function daemonLogs(lines = 50) {
|
|
|
14053
14145
|
}
|
|
14054
14146
|
async function localWebSearch() {
|
|
14055
14147
|
try {
|
|
14056
|
-
const parsed = JSON.parse(await readFile11(
|
|
14148
|
+
const parsed = JSON.parse(await readFile11(join13(nanopmHome(), "daemon.json"), "utf8"));
|
|
14057
14149
|
return parsed.webSearch === true;
|
|
14058
14150
|
} catch {
|
|
14059
14151
|
return false;
|
|
14060
14152
|
}
|
|
14061
14153
|
}
|
|
14062
14154
|
async function rememberWebSearch(webSearch2) {
|
|
14063
|
-
const path =
|
|
14155
|
+
const path = join13(nanopmHome(), "daemon.json");
|
|
14064
14156
|
let parsed = {};
|
|
14065
14157
|
try {
|
|
14066
14158
|
parsed = JSON.parse(await readFile11(path, "utf8"));
|
|
@@ -14068,18 +14160,18 @@ async function rememberWebSearch(webSearch2) {
|
|
|
14068
14160
|
parsed = {};
|
|
14069
14161
|
}
|
|
14070
14162
|
await mkdir6(nanopmHome(), { recursive: true, mode: 448 });
|
|
14071
|
-
await
|
|
14163
|
+
await writeFile5(path, JSON.stringify({ ...parsed, webSearch: webSearch2 }, null, 2) + "\n", { mode: 384 });
|
|
14072
14164
|
}
|
|
14073
14165
|
async function localShared() {
|
|
14074
14166
|
try {
|
|
14075
|
-
const parsed = JSON.parse(await readFile11(
|
|
14167
|
+
const parsed = JSON.parse(await readFile11(join13(nanopmHome(), "daemon.json"), "utf8"));
|
|
14076
14168
|
return Boolean(parsed.shared);
|
|
14077
14169
|
} catch {
|
|
14078
14170
|
return false;
|
|
14079
14171
|
}
|
|
14080
14172
|
}
|
|
14081
14173
|
async function rememberShared(shared) {
|
|
14082
|
-
const path =
|
|
14174
|
+
const path = join13(nanopmHome(), "daemon.json");
|
|
14083
14175
|
let parsed = {};
|
|
14084
14176
|
try {
|
|
14085
14177
|
parsed = JSON.parse(await readFile11(path, "utf8"));
|
|
@@ -14087,7 +14179,7 @@ async function rememberShared(shared) {
|
|
|
14087
14179
|
parsed = {};
|
|
14088
14180
|
}
|
|
14089
14181
|
await mkdir6(nanopmHome(), { recursive: true, mode: 448 });
|
|
14090
|
-
await
|
|
14182
|
+
await writeFile5(path, JSON.stringify({ ...parsed, shared }, null, 2) + "\n", { mode: 384 });
|
|
14091
14183
|
}
|
|
14092
14184
|
|
|
14093
14185
|
// src/format/log.ts
|
|
@@ -14215,11 +14307,11 @@ async function correctionNote(client, jobId) {
|
|
|
14215
14307
|
import { setTimeout as sleep4 } from "timers/promises";
|
|
14216
14308
|
|
|
14217
14309
|
// src/browser.ts
|
|
14218
|
-
import { execFile as
|
|
14310
|
+
import { execFile as execFile9 } from "child_process";
|
|
14219
14311
|
function openBrowser(url, interactive = Boolean(process.stdout.isTTY)) {
|
|
14220
14312
|
if (!interactive) return false;
|
|
14221
14313
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
14222
|
-
|
|
14314
|
+
execFile9(cmd, [url], () => void 0);
|
|
14223
14315
|
return true;
|
|
14224
14316
|
}
|
|
14225
14317
|
|
|
@@ -14303,6 +14395,33 @@ async function logout(out = (l) => console.log(l)) {
|
|
|
14303
14395
|
out("Logged out. The daemon on this machine will stop taking jobs at its next start.");
|
|
14304
14396
|
}
|
|
14305
14397
|
|
|
14398
|
+
// src/commands/update.ts
|
|
14399
|
+
async function update(opts, out = (l) => console.log(l)) {
|
|
14400
|
+
if (opts.auto !== void 0) {
|
|
14401
|
+
if (opts.auto !== "on" && opts.auto !== "off") throw new Error("--auto takes on or off");
|
|
14402
|
+
await setAutoUpdate(opts.auto === "on");
|
|
14403
|
+
out(opts.auto === "on" ? "Automatic updates are on: NanoPM updates itself when it is idle." : "Automatic updates are off. `nanopm update` still updates when you ask.");
|
|
14404
|
+
return;
|
|
14405
|
+
}
|
|
14406
|
+
if (installKind() !== "installer") {
|
|
14407
|
+
out(`This NanoPM (${VERSION}) was not installed with the install line, so it does not update itself.`);
|
|
14408
|
+
out("From a clone of nanopm2: git pull && pnpm build && nanopm daemon install");
|
|
14409
|
+
out("Installed with npm: npm i -g @nanopm/cli@latest && nanopm daemon install");
|
|
14410
|
+
return;
|
|
14411
|
+
}
|
|
14412
|
+
const latest = await latestVersion();
|
|
14413
|
+
if (!latest) throw new Error("could not reach the npm registry; try again in a moment");
|
|
14414
|
+
if (!newer(latest, VERSION)) {
|
|
14415
|
+
out(`Already up to date (${VERSION}).`);
|
|
14416
|
+
return;
|
|
14417
|
+
}
|
|
14418
|
+
out(`Updating ${VERSION} \u2192 ${latest}\u2026`);
|
|
14419
|
+
const done = await installVersion(latest);
|
|
14420
|
+
if (!done.ok) throw new Error(`${done.why}
|
|
14421
|
+
The install line does the same: curl -fsSL https://nanopm.app/install | sh`);
|
|
14422
|
+
out(await restartDaemon() ? `Updated ${VERSION} \u2192 ${latest}; the daemon restarted on it.` : `Updated ${VERSION} \u2192 ${latest}.`);
|
|
14423
|
+
}
|
|
14424
|
+
|
|
14306
14425
|
// src/format/roadmap.ts
|
|
14307
14426
|
var SHAPE = {
|
|
14308
14427
|
proposed: { mark: "\u25CB", colour: ANSI.amber, label: "proposed" },
|
|
@@ -15292,7 +15411,7 @@ async function verifyCommand(command, opts) {
|
|
|
15292
15411
|
|
|
15293
15412
|
// src/detect-sources.ts
|
|
15294
15413
|
import { readFile as readFile12 } from "fs/promises";
|
|
15295
|
-
import { join as
|
|
15414
|
+
import { join as join14 } from "path";
|
|
15296
15415
|
var ENV_FILES = [".env", ".env.local", ".env.development.local", ".env.development", ".env.example"];
|
|
15297
15416
|
var POSTGRES_KEYS = [
|
|
15298
15417
|
"DATABASE_URL",
|
|
@@ -15342,7 +15461,7 @@ async function envFiles(repoRoot) {
|
|
|
15342
15461
|
const out = [];
|
|
15343
15462
|
for (const file of ENV_FILES) {
|
|
15344
15463
|
try {
|
|
15345
|
-
out.push({ file, env: parseEnv(await readFile12(
|
|
15464
|
+
out.push({ file, env: parseEnv(await readFile12(join14(repoRoot, file), "utf8")) });
|
|
15346
15465
|
} catch {
|
|
15347
15466
|
continue;
|
|
15348
15467
|
}
|
|
@@ -17083,7 +17202,7 @@ async function invite(email, opts) {
|
|
|
17083
17202
|
const [, ...rest] = inviteLines({ name: project.name, code, email: email.trim().toLowerCase(), web: webUrl() });
|
|
17084
17203
|
return [`Invite to ${paint(ANSI.bold, project.name)}: ${paint(ANSI.bold, code)}`, ...rest.map((l) => l ? paint(ANSI.dim, l) : l)].join("\n");
|
|
17085
17204
|
}
|
|
17086
|
-
async function
|
|
17205
|
+
async function join15(code, opts) {
|
|
17087
17206
|
const normalized2 = code.trim().toUpperCase();
|
|
17088
17207
|
if (!INVITE_CODE.test(normalized2)) throw new Error(`that does not look like an invite code: ten letters and digits, like ${"K7PQ2XM9RT"}`);
|
|
17089
17208
|
const repo = await detectRepo(opts.cwd);
|
|
@@ -17368,7 +17487,7 @@ async function showSchedule(opts) {
|
|
|
17368
17487
|
}
|
|
17369
17488
|
|
|
17370
17489
|
// src/commands/status.ts
|
|
17371
|
-
import { existsSync as
|
|
17490
|
+
import { existsSync as existsSync6 } from "fs";
|
|
17372
17491
|
async function status(opts) {
|
|
17373
17492
|
const auth = await authenticate();
|
|
17374
17493
|
const color = opts.color ?? useColor();
|
|
@@ -17421,7 +17540,7 @@ async function projectStatus(client, project) {
|
|
|
17421
17540
|
openProblems: (problems2.data ?? []).length,
|
|
17422
17541
|
unprovenProblems: (problems2.data ?? []).filter((p) => p.importance_basis === "judgment").length,
|
|
17423
17542
|
search: describeSearch(search, await localWebSearch()),
|
|
17424
|
-
onThisMachine: Boolean(project.repoPath &&
|
|
17543
|
+
onThisMachine: Boolean(project.repoPath && existsSync6(project.repoPath)),
|
|
17425
17544
|
measures: measures({
|
|
17426
17545
|
solutionStatuses: (roadmap2.data ?? []).map((r) => r.status),
|
|
17427
17546
|
decisions: decisions2.data ?? [],
|
|
@@ -17460,6 +17579,7 @@ function buildProgram() {
|
|
|
17460
17579
|
process.once("SIGINT", stop2);
|
|
17461
17580
|
process.once("SIGTERM", stop2);
|
|
17462
17581
|
const result = await runDaemon({ once: opts.once, version: VERSION, signal: abort.signal });
|
|
17582
|
+
if (result.restart) process.exit(0);
|
|
17463
17583
|
if (opts.once && result.processed === 0) {
|
|
17464
17584
|
console.error("nanopm: no queued job arrived in time");
|
|
17465
17585
|
process.exitCode = 2;
|
|
@@ -17469,6 +17589,7 @@ function buildProgram() {
|
|
|
17469
17589
|
daemon.command("uninstall").description("Remove the background daemon").action(async () => uninstallDaemon());
|
|
17470
17590
|
daemon.command("status").description("Is the daemon online, and what is queued").action(async () => daemonStatus());
|
|
17471
17591
|
daemon.command("logs").description("The daemon log").option("-n <lines>", "lines", (v) => Number(v), 50).action(async (opts) => console.log(await daemonLogs(opts.n)));
|
|
17592
|
+
program.command("update").description("Update NanoPM to the latest release now; it also updates itself when idle").option("--auto <on|off>", "turn the automatic update on or off").action(async (opts) => update(opts));
|
|
17472
17593
|
program.command("login").description("Connect this computer to your nano.pm account, and start the daemon on it").option("--no-browser", "print the link instead of opening it").option("--no-daemon", "link only; do not install the background daemon").option("--web <url>", "nano.pm base URL (development)").action(async (opts) => {
|
|
17473
17594
|
await login({ noBrowser: !opts.browser, noDaemon: !opts.daemon, web: opts.web });
|
|
17474
17595
|
});
|
|
@@ -17779,7 +17900,7 @@ function buildProgram() {
|
|
|
17779
17900
|
console.log(await invite(email, { cwd: process.cwd(), projectId: opts.project, color: colorOf(opts) }));
|
|
17780
17901
|
});
|
|
17781
17902
|
program.command("join <code>").description("Join a project you were invited to, from inside your clone of its repository").option("--no-color", "plain output").action(async (code, opts) => {
|
|
17782
|
-
console.log(await
|
|
17903
|
+
console.log(await join15(code, { cwd: process.cwd(), color: colorOf(opts) }));
|
|
17783
17904
|
});
|
|
17784
17905
|
program.command("members").description("Who is on this project, and whether their daemon is awake").option("--project <id>", "project id (default: the project of the current repo)").option("--no-color", "plain output").action(async (opts) => {
|
|
17785
17906
|
console.log(await members({ cwd: process.cwd(), projectId: opts.project, color: colorOf(opts) }));
|
package/package.json
CHANGED