@nanopm/cli 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { Command } from "commander";
5
5
 
6
6
  // ../daemon/src/adapters/claude-code/index.ts
7
7
  import { execFile, spawn } from "child_process";
8
+ import { randomUUID } from "crypto";
8
9
  import { createInterface } from "readline";
9
10
  import { writeFile as writeFile2 } from "fs/promises";
10
11
  import { join as join3 } from "path";
@@ -862,22 +863,14 @@ var CHAINED = {
862
863
  // (`mapping_completion_hook`): the opportunity map follows the market read below, and
863
864
  // the founder confirms what the scan proposed on the card while it is drawn.
864
865
  //
865
- // **`direction` is queued here rather than after the market, and still reads it.** The
866
- // daemon runs one job at a time and `claim_job` takes them `order by created_at`, so a
867
- // direction queued the moment the onboarding ends is not *started* until the market —
868
- // queued a hair earlier, in this array's own order — has finished and written the
869
- // competitors its judge needs for the substitution test. What it skips is the queue behind
870
- // `pitch`, which it never needed and which cost it eighty seconds: on dogo-3 (2026-09-24)
871
- // the direction landed at 441s against a founder who reaches the card at about 90, and the
872
- // Vision row said *nothing yet* twenty-five seconds before the crew wrote it. Now ~358s.
873
- //
874
- // It does not close the gap — nothing chained can, while the read itself takes 161s — which
875
- // is why the mission is asked for on the way in and the vision is drawn as *coming*
876
- // (docs/nanopm-56-the-required-set.md). This makes that wait honest rather than shorter
877
- // than it looks.
878
- // Since 2026-09-30 the direction is queued only when the onboarding left the mission or the
879
- // vision empty (`WRITTEN_BY_PARENT`): with both written, it had nothing to fill.
880
- onboard: ["market", "direction"],
866
+ // **The direction crew does not follow an onboarding** (Nicolas, 2026-10-01, docs/ACTIVATION.md
867
+ // row 27). It was queued here, in the background, whenever the read left the mission or the
868
+ // vision empty, and it landed 9 to 24 minutes later — after the market, the map and the
869
+ // solutions — while the card's Direction step, right after the pitch, promised *a few
870
+ // minutes*. Direction now works like the card's other sections: the read writes what the
871
+ // repository states, the card asks the founder for a mission it did not find (`missionAsk`),
872
+ // and the crew runs when the founder asks for it from the business card.
873
+ onboard: ["market"],
881
874
  // A problem list is what solutions hang under, so a fresh list is followed by fresh
882
875
  // solutions. **The problem crew itself is retired** (docs/nanopm-59-opportunity-map.md,
883
876
  // Guillaume, 2026-09-25): the opportunity map replaced it, and nothing queues it any more —
@@ -890,10 +883,8 @@ var CHAINED = {
890
883
  // proposes beside a confirmed pitch and never over it (docs/nanopm-52-pitch-crew.md).
891
884
  //
892
885
  // The direction crew **was** chained here too, for the same reason — a vision rests on who
893
- // else exists — and it moved up to `onboard` on 2026-09-24. It still reads the market: the
894
- // daemon is serial and claims `order by created_at`, so a direction queued at the end of
895
- // the onboarding starts only once the market, queued a hair before it, has finished. All
896
- // it gave up was waiting behind the pitch, which it never read. See the note on `onboard`.
886
+ // else exists — then after `onboard`, and since 2026-10-01 after nothing: see the note on
887
+ // `onboard`.
897
888
  //
898
889
  // Neither follows a personas run the founder started: they pressed one button, for one
899
890
  // crew, with a note meant for it (Guillaume, 2026-09-22). A founder who wants both runs asks
@@ -947,16 +938,6 @@ var ONLY_AFTER = {
947
938
  var NEVER_AFTER = {
948
939
  market: { pitch: ["onboard"] }
949
940
  };
950
- var WRITTEN_BY_PARENT = {
951
- onboard: { direction: ["mission", "vision"] }
952
- };
953
- function stillOwed(skill, next2, written) {
954
- const needs2 = WRITTEN_BY_PARENT[skill] ?? {};
955
- return next2.filter((n) => !needs2[n] || needs2[n].some((topic) => !written.has(topic)));
956
- }
957
- var BACKGROUND_EDGES = {
958
- onboard: ["direction"]
959
- };
960
941
  var AFTER_DEEP = {
961
942
  needs: ["opportunities"]
962
943
  };
@@ -3792,7 +3773,26 @@ var ClaudeCodeAdapter = class {
3792
3773
  else args.push("--session-id", spec.sessionId);
3793
3774
  return args;
3794
3775
  }
3776
+ /**
3777
+ * One session, and one more when the model asked for is newer than this Claude Code: a
3778
+ * skill names `claude-opus-5-5`, a Claude Code older than 2.1.280 refuses it, and the run
3779
+ * used to end there with no roadmap (activation row 36). The second session asks for the
3780
+ * family alias under a fresh id and says so, so the run works on the Opus the machine has.
3781
+ * Any other failure stays a failure.
3782
+ */
3795
3783
  async *run(spec) {
3784
+ for await (const event of this.runOnce(spec)) {
3785
+ const alias = event.type === "failed" ? olderAlias(spec.model, event.message) : null;
3786
+ if (!alias) {
3787
+ yield event;
3788
+ continue;
3789
+ }
3790
+ yield { type: "text", text: `This Claude Code does not know ${spec.model} yet; using ${alias}. \`claude update\` brings the newer model.` };
3791
+ yield* this.runOnce({ ...spec, model: alias, sessionId: randomUUID(), resumeSessionId: void 0 });
3792
+ return;
3793
+ }
3794
+ }
3795
+ async *runOnce(spec) {
3796
3796
  const mcpConfigPath = join3(spec.runDir, "mcp.json");
3797
3797
  const mcpServers = Object.fromEntries(
3798
3798
  spec.mcp.map((s) => [s.name, { command: s.command, args: s.args, env: s.env }])
@@ -3839,6 +3839,10 @@ var ClaudeCodeAdapter = class {
3839
3839
  }
3840
3840
  }
3841
3841
  };
3842
+ function olderAlias(model, message) {
3843
+ if (!model || !/does not support this model/i.test(message)) return null;
3844
+ return ["opus", "sonnet", "haiku"].find((family) => model.includes(family) && model !== family) ?? null;
3845
+ }
3842
3846
  function appendExit(message, code) {
3843
3847
  return code === null || code === 0 ? message : `${message} (exit ${code})`;
3844
3848
  }
@@ -5200,7 +5204,7 @@ async function clonelessSkills(skillsDir = findSkillsDir()) {
5200
5204
  }
5201
5205
 
5202
5206
  // ../daemon/src/problem-mapping-harness.ts
5203
- import { randomUUID } from "crypto";
5207
+ import { randomUUID as randomUUID2 } from "crypto";
5204
5208
  function mappingHarnessRunner(options) {
5205
5209
  return async (input) => {
5206
5210
  const skill = await loadSkill(`problem-${input.role}`);
@@ -5209,7 +5213,7 @@ function mappingHarnessRunner(options) {
5209
5213
  for await (const event of options.adapter.run({
5210
5214
  cwd: options.runDir,
5211
5215
  runDir: options.runDir,
5212
- sessionId: randomUUID(),
5216
+ sessionId: randomUUID2(),
5213
5217
  systemPrompt: `${skill.body}
5214
5218
 
5215
5219
  Output schema:
@@ -5500,7 +5504,7 @@ function mappingUsage(checkpoints) {
5500
5504
  }
5501
5505
 
5502
5506
  // ../daemon/src/persona-job.ts
5503
- import { randomUUID as randomUUID2 } from "crypto";
5507
+ import { randomUUID as randomUUID3 } from "crypto";
5504
5508
 
5505
5509
  // ../daemon/src/crew-retry.ts
5506
5510
  async function askAgainIfUnanswered(attempt7, say, role7) {
@@ -5813,7 +5817,7 @@ async function attempt({
5813
5817
  await onEvent({ type: "text", text: say });
5814
5818
  for await (const event of adapter.run({
5815
5819
  ...spec,
5816
- sessionId: randomUUID2(),
5820
+ sessionId: randomUUID3(),
5817
5821
  resumeSessionId: void 0,
5818
5822
  capabilities: [],
5819
5823
  mcp: [],
@@ -6034,7 +6038,7 @@ async function collect2(client, projectId, cwd, replace) {
6034
6038
  }
6035
6039
 
6036
6040
  // ../daemon/src/direction-job.ts
6037
- import { randomUUID as randomUUID3 } from "crypto";
6041
+ import { randomUUID as randomUUID4 } from "crypto";
6038
6042
 
6039
6043
  // ../daemon/src/crew-no-downgrade.ts
6040
6044
  function keepsTheLine(job, live, confidence) {
@@ -6229,7 +6233,7 @@ ${renderTests()}` : skill.body;
6229
6233
  await onEvent({ type: "text", text: say });
6230
6234
  for await (const event of adapter.run({
6231
6235
  ...spec,
6232
- sessionId: randomUUID3(),
6236
+ sessionId: randomUUID4(),
6233
6237
  resumeSessionId: void 0,
6234
6238
  capabilities: [],
6235
6239
  mcp: [],
@@ -6358,7 +6362,7 @@ async function collect3(client, projectId) {
6358
6362
  }
6359
6363
 
6360
6364
  // ../daemon/src/pitch-job.ts
6361
- import { randomUUID as randomUUID4 } from "crypto";
6365
+ import { randomUUID as randomUUID5 } from "crypto";
6362
6366
  async function runPitchJob({ client, job, adapter, spec, onEvent }) {
6363
6367
  if (!job.project_id) throw new Error("A pitch needs a project.");
6364
6368
  const projectId = job.project_id;
@@ -6463,7 +6467,7 @@ async function attempt3({
6463
6467
  await onEvent({ type: "text", text: say });
6464
6468
  for await (const event of adapter.run({
6465
6469
  ...spec,
6466
- sessionId: randomUUID4(),
6470
+ sessionId: randomUUID5(),
6467
6471
  resumeSessionId: void 0,
6468
6472
  capabilities: [],
6469
6473
  mcp: [],
@@ -6575,7 +6579,7 @@ async function collect4(client, projectId) {
6575
6579
  }
6576
6580
 
6577
6581
  // ../daemon/src/market-job.ts
6578
- import { randomUUID as randomUUID5 } from "crypto";
6582
+ import { randomUUID as randomUUID6 } from "crypto";
6579
6583
 
6580
6584
  // ../daemon/src/web-journal.ts
6581
6585
  var HARNESS_WEB_TOOLS = ["WebSearch", "WebFetch"];
@@ -6816,7 +6820,7 @@ async function attempt4({ adapter, spec, onEvent, role: which, prompt, say }) {
6816
6820
  await onEvent({ type: "text", text: say });
6817
6821
  for await (const event of adapter.run({
6818
6822
  ...spec,
6819
- sessionId: randomUUID5(),
6823
+ sessionId: randomUUID6(),
6820
6824
  resumeSessionId: void 0,
6821
6825
  capabilities,
6822
6826
  mcp: which === "scout" ? spec.mcp : [],
@@ -6899,7 +6903,7 @@ async function collect5(client, projectId, ask6) {
6899
6903
  }
6900
6904
 
6901
6905
  // ../daemon/src/needs-job.ts
6902
- import { randomUUID as randomUUID6 } from "crypto";
6906
+ import { randomUUID as randomUUID7 } from "crypto";
6903
6907
  var SIGNALS_MAX = 40;
6904
6908
  var SIGNAL_CHARS = 3e3;
6905
6909
  async function runNeedsJob({ client, job, adapter, spec, onEvent }) {
@@ -7478,7 +7482,7 @@ async function attempt5({ adapter, spec, onEvent, role: which, schema: shape2, t
7478
7482
  await onEvent({ type: "text", text: say });
7479
7483
  for await (const event of adapter.run({
7480
7484
  ...spec,
7481
- sessionId: randomUUID6(),
7485
+ sessionId: randomUUID7(),
7482
7486
  resumeSessionId: void 0,
7483
7487
  capabilities: granted,
7484
7488
  mcp: granted.length ? spec.mcp : [],
@@ -7700,7 +7704,7 @@ function opportunitiesOutputSchema(which) {
7700
7704
  }
7701
7705
 
7702
7706
  // ../daemon/src/crew.ts
7703
- import { randomUUID as randomUUID7 } from "crypto";
7707
+ import { randomUUID as randomUUID8 } from "crypto";
7704
7708
  import { mkdir as mkdir2 } from "fs/promises";
7705
7709
  import { join as join7 } from "path";
7706
7710
  var READ_ONLY = {
@@ -7727,7 +7731,7 @@ async function attempt6({ adapter, spec, onEvent, skill: skillName, name, schema
7727
7731
  // A role that may not read the code does not stand in it (docs/nanopm-71-deep-dive-lenses.md).
7728
7732
  cwd: grant.includes("read_repo") ? spec.cwd : runDir,
7729
7733
  runDir,
7730
- sessionId: randomUUID7(),
7734
+ sessionId: randomUUID8(),
7731
7735
  resumeSessionId: void 0,
7732
7736
  capabilities: [...grant],
7733
7737
  mcp: grant.includes("read_data") ? spec.mcp : [],
@@ -9229,7 +9233,7 @@ async function readRoadmap(client, projectId) {
9229
9233
  }
9230
9234
 
9231
9235
  // ../daemon/src/reword-job.ts
9232
- import { randomUUID as randomUUID8 } from "crypto";
9236
+ import { randomUUID as randomUUID9 } from "crypto";
9233
9237
  async function runRewordJob({ client, job, adapter, spec, onEvent }) {
9234
9238
  if (!job.project_id) throw new Error("A rewrite needs a project.");
9235
9239
  const asked = RewordParams.safeParse(job.params);
@@ -9291,7 +9295,7 @@ async function session({ adapter, spec, onEvent, prompt }) {
9291
9295
  let usage;
9292
9296
  for await (const event of adapter.run({
9293
9297
  ...spec,
9294
- sessionId: randomUUID8(),
9298
+ sessionId: randomUUID9(),
9295
9299
  resumeSessionId: void 0,
9296
9300
  capabilities: [],
9297
9301
  mcp: [],
@@ -9326,7 +9330,7 @@ ${body}
9326
9330
  }
9327
9331
 
9328
9332
  // ../daemon/src/talk-job.ts
9329
- import { randomUUID as randomUUID9 } from "crypto";
9333
+ import { randomUUID as randomUUID10 } from "crypto";
9330
9334
  var ALLOWED = /* @__PURE__ */ new Set([...TALK_TOOLS.map((t) => `mcp__nanopm__${t}`), "StructuredOutput"]);
9331
9335
  async function runTalkJob({ client, job, adapter, spec, onEvent }) {
9332
9336
  if (!job.project_id) throw new Error("A conversation needs a project.");
@@ -9358,7 +9362,7 @@ async function runTalkJob({ client, job, adapter, spec, onEvent }) {
9358
9362
  let usage;
9359
9363
  for await (const event of adapter.run({
9360
9364
  ...spec,
9361
- sessionId: randomUUID9(),
9365
+ sessionId: randomUUID10(),
9362
9366
  resumeSessionId: void 0,
9363
9367
  capabilities: skill.manifest.capabilities,
9364
9368
  harnessWeb: false,
@@ -9395,7 +9399,7 @@ var told = (r) => ({
9395
9399
 
9396
9400
  // ../daemon/src/objective-definitions.ts
9397
9401
  import { execFile as execFile4 } from "child_process";
9398
- import { randomUUID as randomUUID10 } from "crypto";
9402
+ import { randomUUID as randomUUID11 } from "crypto";
9399
9403
  import { existsSync as existsSync2 } from "fs";
9400
9404
  import { mkdir as mkdir3, readFile as readFile5 } from "fs/promises";
9401
9405
  import { join as join8 } from "path";
@@ -9571,7 +9575,7 @@ async function readDefinitions({ adapter, spec, publish: publish5, journal, hasR
9571
9575
  for await (const event of adapter.run({
9572
9576
  ...spec,
9573
9577
  runDir,
9574
- sessionId: randomUUID10(),
9578
+ sessionId: randomUUID11(),
9575
9579
  resumeSessionId: void 0,
9576
9580
  capabilities: ["read_repo"],
9577
9581
  // No Glob: it read the folder at 15 s a search (Guillaume's suggestions took 75 s,
@@ -9895,7 +9899,7 @@ function localDate(timezone, at) {
9895
9899
  }
9896
9900
 
9897
9901
  // ../daemon/src/run-job.ts
9898
- import { randomUUID as randomUUID11 } from "crypto";
9902
+ import { randomUUID as randomUUID12 } from "crypto";
9899
9903
  import { existsSync as existsSync3 } from "fs";
9900
9904
  import { mkdir as mkdir4, readFile as readFile6, rm as rm2 } from "fs/promises";
9901
9905
  import { homedir as homedir2, tmpdir } from "os";
@@ -10340,7 +10344,7 @@ async function execute(job, skill, ctx, events) {
10340
10344
  const runDir = join9(nanopmHome(), "run", job.id);
10341
10345
  await mkdir4(runDir, { recursive: true, mode: 448 });
10342
10346
  const resumeSessionId = job.resume_of ? await sessionOf(ctx.client, job.resume_of) : void 0;
10343
- const sessionId = resumeSessionId ?? randomUUID11();
10347
+ const sessionId = resumeSessionId ?? randomUUID12();
10344
10348
  await ctx.client.from("jobs").update({ harness_session_id: sessionId }).eq("id", job.id);
10345
10349
  const adapter = ctx.adapter ?? new ClaudeCodeAdapter();
10346
10350
  const [project, searchProvider] = job.project_id ? await Promise.all([loadProject(ctx.client, job.project_id), loadSearchProvider(ctx.client, job.project_id)]) : [null, null];
@@ -10932,12 +10936,6 @@ async function runDaemon(options = {}) {
10932
10936
  }
10933
10937
  return { daemonId, processed };
10934
10938
  }
10935
- async function topicsWritten(auth, projectId, skill) {
10936
- const topics = [...new Set(Object.values(WRITTEN_BY_PARENT[skill] ?? {}).flat())];
10937
- if (!topics.length) return /* @__PURE__ */ new Set();
10938
- const { data } = await auth.client.from("memory").select("topic").eq("project_id", projectId).in("topic", topics).is("superseded_by", null);
10939
- return new Set((data ?? []).map((r) => r.topic));
10940
- }
10941
10939
  async function chainAfter(auth, job, outcome, log) {
10942
10940
  if (NOT_CHAINED_BY_DAEMON.includes(job.skill)) return;
10943
10941
  if (job.params?.chain === false) return;
@@ -10945,14 +10943,13 @@ async function chainAfter(auth, job, outcome, log) {
10945
10943
  const parent = job.parent_job_id ? await parentOf(auth, job.parent_job_id) : null;
10946
10944
  const own = job.params ?? {};
10947
10945
  const deep = own.depth === "deep" && job.trigger === "hook";
10948
- const next2 = stillOwed(job.skill, daemonQueues(job.skill, 0, parent?.skill ?? null, deep), await topicsWritten(auth, job.project_id, job.skill));
10946
+ const next2 = daemonQueues(job.skill, 0, parent?.skill ?? null, deep);
10949
10947
  if (!next2.length) return;
10950
10948
  if (await chainDepth(auth, job) >= CHAIN_DEPTH_MAX) return;
10951
10949
  const from = deep ? own.repo_path : parent?.params?.repo_path;
10952
10950
  const repo = typeof from === "string" ? { repo_path: from } : null;
10953
10951
  for (const skill of next2) {
10954
- const background = deep || (BACKGROUND_EDGES[job.skill] ?? []).includes(skill);
10955
- const params = { ...repo ?? {}, ...deep ? { depth: "deep" } : {}, ...background ? { background: true } : {} };
10952
+ const params = { ...repo ?? {}, ...deep ? { depth: "deep", background: true } : {} };
10956
10953
  const { data, error } = await auth.client.from("jobs").insert({
10957
10954
  project_id: job.project_id,
10958
10955
  skill,
@@ -11247,7 +11244,7 @@ function contextFromEnv(env = process.env) {
11247
11244
 
11248
11245
  // ../memory-mcp/src/server.ts
11249
11246
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11250
- import { randomUUID as randomUUID12 } from "crypto";
11247
+ import { randomUUID as randomUUID13 } from "crypto";
11251
11248
  import { setTimeout as sleep2 } from "timers/promises";
11252
11249
  import { z as z20 } from "zod";
11253
11250
 
@@ -13090,7 +13087,7 @@ async function talkAct(ctx, roundOf, proposed, asked, why) {
13090
13087
  const [round2, world] = await Promise.all([roundOf, talkWorld(ctx.client, ctx.projectId)]);
13091
13088
  const ref = change.kind === "add" ? change.parent : change.kind === "add_solution" ? change.leaf : change.ref;
13092
13089
  const target = ref ? currentRow(world, ref)?.ref ?? currentSolution(world, ref)?.ref ?? ref : void 0;
13093
- const base = { id: randomUUID12(), change, ...movesDown(change, world) ? { down: true } : {}, ...change.kind === "brainstorm" ? { cost: runCost(change.depth) } : {} };
13090
+ const base = { id: randomUUID13(), change, ...movesDown(change, world) ? { down: true } : {}, ...change.kind === "brainstorm" ? { cost: runCost(change.depth) } : {} };
13094
13091
  let action;
13095
13092
  if (!askedIn(asked, round2.words)) {
13096
13093
  action = { ...base, state: "suggested", line: talkLine(change, world, false), ...target ? { target } : {} };
@@ -13885,7 +13882,7 @@ import { promisify as promisify7 } from "util";
13885
13882
  // package.json
13886
13883
  var package_default = {
13887
13884
  name: "@nanopm/cli",
13888
- version: "0.1.1",
13885
+ version: "0.1.3",
13889
13886
  description: "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
13890
13887
  type: "module",
13891
13888
  license: "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanopm/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -171,8 +171,8 @@ product is.
171
171
  code does is the pitch with a wider adjective, and it is exactly the row this rule exists
172
172
  to stop (docs/nanopm-53-direction.md §3). If the product's own words do not say where the
173
173
  company is going, write nothing under these two topics and say so in your final log: the
174
- direction crew writes them later, once the personas and the market are in, which is when
175
- there is something to write them from.
174
+ card asks the founder for the mission in their own words, and the business card drafts a
175
+ mission and a vision with them when they ask. Do not write a gap for it: the card asks.
176
176
  Only when the repo states a stance in words (a README that says "maintenance mode",
177
177
  a roadmap that says "growth"), call set_preferences with `stance_provenance:
178
178
  "inferred"`; otherwise do not call it at all. The stance is `grow` until someone says