@opennous/cli 0.14.1 → 0.16.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @opennous/cli changelog
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Added: your Intelligence report after `backfill`
6
+
7
+ When the read is done, `backfill` writes your Intelligence report: private to you, built from what it
8
+ read. Its executive summary, recommended next steps and objection responses are written by your own model, from the report's data only,
9
+ with no tools. The command prints the link. If that step fails, the report is still there (GET-1199).
10
+
11
+ ## 0.15.0
12
+
13
+ ### Added: Slack in `backfill`
14
+
15
+ On a workspace that syncs its history, your Slack messages (DMs and group DMs with customers) wait
16
+ for a backfill like email does. `backfill` counts them before it asks, and reads them on your plan
17
+ (GET-1087).
18
+
3
19
  ## 0.14.1
4
20
 
5
21
  ### Fixed: carrying on with `backfill --workspace`
package/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # @opennous/cli
2
2
 
3
- Nous CLI — set Nous up, connect it to your coding agent, and read the Context API from your terminal.
3
+ Ownable CLI — set Ownable up, connect it to your coding agent, and read the Context API from your terminal.
4
4
 
5
5
  ## Get started
6
6
 
7
- One command. It signs you in (creating your account if you don't have one), registers the Nous MCP server with the agent on your machine, and hands off. The same command on macOS, Linux, and Windows (needs [Node 18+](https://nodejs.org)):
7
+ One command. It signs you in (creating your account if you don't have one), registers the Ownable MCP server with the agent on your machine, and hands off. The same command on macOS, Linux, and Windows (needs [Node 18+](https://nodejs.org)):
8
8
 
9
9
  ```bash
10
10
  npx @opennous/cli@latest init
11
11
  ```
12
12
 
13
- Then tell your agent *"set up my Nous workspace"* — it reads your project, finds your ICP (or drafts one), and syncs it. On self-host, add `--url https://api.yourdomain.com`.
13
+ Then tell your agent *"set up my Ownable workspace"* — it reads your project, finds your ICP (or drafts one), and syncs it. On self-host, add `--url https://api.yourdomain.com`.
14
14
 
15
15
  ## Event tracking
16
16
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@opennous/cli",
3
- "version": "0.14.1",
4
- "description": "Nous CLI — set up Nous, connect it to your coding agent, and read the Context API from your terminal",
3
+ "version": "0.16.0",
4
+ "description": "Ownable CLI — set up Ownable, connect it to your coding agent, and read the Context API from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "nous": "src/index.js"
package/src/backfill.js CHANGED
@@ -99,14 +99,50 @@ function runCommand({ cmd, args, input }, cwd) {
99
99
  });
100
100
  }
101
101
 
102
+ /** "Last 180 days: about 40 email threads, 3 meetings, 0 calls, 12 Slack conversations." Slack only when there is some. Pure. */
103
+ export function estimateLine(days, counts) {
104
+ const slack = counts?.slack ? `, ${counts.slack} Slack conversations` : "";
105
+ return `Last ${days} days: about ${counts?.email_thread ?? 0} email threads, ${counts?.meeting ?? 0} meetings, ${counts?.call ?? 0} calls${slack}.`;
106
+ }
107
+
108
+ /**
109
+ * "5 to review: https://…/review (12 not on file)", or null when there is nothing to decide. Pure.
110
+ * To review is Probably mentions plus open groups (GET-1152); a mention nothing on file could be is
111
+ * not on file and never counted. An older server sends only `mentions`.
112
+ */
113
+ export function reviewLine(review) {
114
+ const n = Number(review?.to_review ?? review?.mentions ?? 0);
115
+ if (!Number.isFinite(n) || n <= 0) return null;
116
+ const quiet = Number(review?.not_on_file ?? 0);
117
+ return `${n} to review${review?.url ? `: ${review.url}` : ""}${quiet > 0 ? ` (${quiet} not on file)` : ""}`;
118
+ }
119
+
120
+ /**
121
+ * Who each person is, before anything is read (GET-1159): the people worth a look, as a short table,
122
+ * with where to confirm them. Null when there is nothing to show. Pure.
123
+ */
124
+ export function proposalsLines(rel) {
125
+ if (!rel || !(rel.judged > 0)) return null;
126
+ const pct = (c) => `${Math.round(Number(c ?? 0) * 100)}%`;
127
+ const lines = [`Who's who: ${rel.judged} people judged, ${rel.to_confirm ?? 0} worth a look.`];
128
+ const top = (rel.top ?? []).slice(0, 15);
129
+ const w = Math.min(28, Math.max(4, ...top.map((p) => String(p.name ?? "Unknown").length)));
130
+ for (const p of top) lines.push(` ${String(p.name ?? "Unknown").slice(0, w).padEnd(w)} ${String(p.group ?? "").padEnd(14)} ${pct(p.confidence).padStart(4)} ${p.reason ?? ""}`.trimEnd());
131
+ if (rel.url) lines.push(`Confirm or fix them: ${rel.url}`);
132
+ lines.push(rel.waiting ? "Waiting for you to confirm before reading (or Ctrl-C and run with --yes)." : rel.mode === "yes" ? "Accepted as proposed." : "Reading now; fixing someone later re-reads their calls.");
133
+ return lines;
134
+ }
135
+
102
136
  /** Nothing to read in this window. Pure. */
103
137
  export const emptyEstimate = (counts) => !Object.values(counts ?? {}).some((n) => n > 0);
104
138
 
105
139
  /** One line on how far the job is, or null when there is nothing to say yet. Pure. */
106
140
  export function progressLine(reading, answered, rerun = RERUN) {
107
141
  if (reading?.finished) return "Wrapping up: last summaries for your accounts.";
142
+ if (reading?.kind === "relationships") return `Working out who each person is, before reading. ${`Stop any time with Ctrl-C; run \`${rerun}\` again to carry on.`}`;
108
143
  const stop = `Stop any time with Ctrl-C; run \`${rerun}\` again to carry on.`;
109
- if (reading?.total) return `Read ${reading.done} of ${reading.total} ${reading.kind === "meetings" ? "meetings" : "email threads"}. ${stop}`;
144
+ const what = { meetings: "meetings", slack: "Slack conversations" }[reading?.kind] ?? "email threads";
145
+ if (reading?.total) return `Read ${reading.done} of ${reading.total} ${what}. ${stop}`;
110
146
  if (answered) return `${answered} reads done on your plan. ${stop}`;
111
147
  return `Getting ready to read. This can take a few minutes. ${stop}`;
112
148
  }
@@ -117,7 +153,7 @@ export function progressLine(reading, answered, rerun = RERUN) {
117
153
  * is saved, and unanswered calls come back to the next run.
118
154
  */
119
155
  export async function runBackfill({ api, agent, jobId, run, sleep, log, now = Date.now, workers = WORKERS, limitWaitMs = LIMIT_WAIT_MS, pollMs = POLL_MS, progressMs = PROGRESS_MS, rerun = RERUN }) {
120
- let waitUntil = 0, answered = 0, lastLine = null, lastAt = -Infinity;
156
+ let waitUntil = 0, answered = 0, lastLine = null, lastAt = -Infinity, shownProposals = false;
121
157
  const say = (line) => {
122
158
  if (!line || (line === lastLine && now() - lastAt < progressMs) || now() - lastAt < Math.min(progressMs, 1000)) return;
123
159
  lastLine = line; lastAt = now(); log(line);
@@ -142,10 +178,15 @@ export async function runBackfill({ api, agent, jobId, run, sleep, log, now = Da
142
178
  };
143
179
  for (;;) {
144
180
  const page = await api.calls(jobId, workers);
145
- if (page.status === "done") return { status: "done", answered, read: page.reading?.read ?? 0, failed: page.reading?.failed ?? 0 };
181
+ if (page.status === "done") return { status: "done", answered, read: page.reading?.read ?? 0, failed: page.reading?.failed ?? 0, ...(page.memory_review ? { memoryReview: page.memory_review } : {}) };
146
182
  if (page.status === "failed") throw new Error(page.error || "The backfill failed on our side.");
147
183
  // The server gave up waiting for us (a long sleep, say). We're back, so pick it up.
148
184
  if (page.status === "paused_own_model") { await api.resume?.(); await sleep(pollMs); continue; }
185
+ // Phase 1's proposals (GET-1159), once, as soon as they are in.
186
+ if (!shownProposals && page.relationships?.judged > 0) {
187
+ shownProposals = true;
188
+ for (const l of proposalsLines(page.relationships) ?? []) log(l);
189
+ }
149
190
  say(progressLine(page.reading, answered, rerun));
150
191
  if (!page.calls.length) { await sleep(pollMs); continue; }
151
192
  await Promise.all(page.calls.map((c) => answerOne(c, page.models)));
@@ -157,3 +198,21 @@ export function liveRun() {
157
198
  const cwd = mkdtempSync(join(tmpdir(), "nous-backfill-"));
158
199
  return (command) => runCommand(command, cwd);
159
200
  }
201
+
202
+ /**
203
+ * The Intelligence report (GET-1199), once the read is done. The server builds it from what was
204
+ * filed; its Summary and Recommended actions are then written by your own model, from the report's
205
+ * data only, with no tools. If that fails the report keeps the ones code wrote. Returns its link,
206
+ * or null. Injected like runBackfill: `api` ({ report, writing }), `run`.
207
+ */
208
+ export async function writeReport({ api, agent, jobId, run }) {
209
+ const r = await api.report(jobId);
210
+ const models = r?.models?.[agent];
211
+ if (r?.writing && models) {
212
+ try {
213
+ const { text } = parseAnswer(agent, await run(commandFor(agent, models, r.writing)));
214
+ await api.writing(jobId, { answer: text, model: models.model });
215
+ } catch { /* the report stands as code wrote it */ }
216
+ }
217
+ return r?.url ?? null;
218
+ }
package/src/certHint.js CHANGED
@@ -1,4 +1,4 @@
1
- // A self-hosted Nous on a laptop serves https://api.localhost with Caddy's own local CA, which
1
+ // A self-hosted Ownable on a laptop serves https://api.localhost with Caddy's own local CA, which
2
2
  // Node does not trust. fetch then rejects with a bare "fetch failed" whose cause carries one of
3
3
  // these codes. Name the fix in one line instead of printing a stack trace (GET-1011).
4
4
  const UNTRUSTED_CERT_CODES = new Set([
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- // Nous ICP auto-sync reminder (installed by `npx @opennous/cli init`).
2
+ // Ownable ICP auto-sync reminder (installed by `npx @opennous/cli init`).
3
3
  //
4
- // A Claude Code PostToolUse hook. After the agent edits a Nous context file, it
4
+ // A Claude Code PostToolUse hook. After the agent edits an Ownable context file, it
5
5
  // reminds the agent that the file is INERT until synced: the app, the ICP tab, and
6
- // the ICP scoring model read the Nous GRAPH, not the repo file, so an edit does
6
+ // the ICP scoring model read the Ownable GRAPH, not the repo file, so an edit does
7
7
  // nothing until mcp__nous__sync_icp runs. Standalone Node (no bash/jq) so it behaves
8
8
  // identically on Windows, macOS, and Linux.
9
9
 
@@ -25,8 +25,8 @@ process.stdin.on("end", () => {
25
25
  const base = norm.split("/").pop().toLowerCase();
26
26
 
27
27
  const ctx = base === "icp.md"
28
- ? `You just edited ${rel}, your Nous ICP file. It is INERT until synced: the app, the ICP tab, and the ICP scoring model read the Nous graph, not this file. Call the mcp__nous__sync_icp tool THIS TURN with section "ICP" and source_path "${rel}", passing the file body as the section content, so the graph and the scoring model rebuild from your edit. Do not end the turn with the ICP file edited but unsynced.`
29
- : `You edited a Nous context file (${rel}). It stays inert until synced into the Nous graph. If it is an ICP, positioning, pricing, competitors, or market file, call mcp__nous__sync_icp this turn with the matching section and its source_path so the graph updates.`;
28
+ ? `You just edited ${rel}, your Ownable ICP file. It is INERT until synced: the app, the ICP tab, and the ICP scoring model read the Ownable graph, not this file. Call the mcp__nous__sync_icp tool THIS TURN with section "ICP" and source_path "${rel}", passing the file body as the section content, so the graph and the scoring model rebuild from your edit. Do not end the turn with the ICP file edited but unsynced.`
29
+ : `You edited an Ownable context file (${rel}). It stays inert until synced into the Ownable graph. If it is an ICP, positioning, pricing, competitors, or market file, call mcp__nous__sync_icp this turn with the matching section and its source_path so the graph updates.`;
30
30
 
31
31
  process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: ctx } }));
32
32
  } catch { /* never block a tool call */ }
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // OpenNous CLI — the setup + auth utility. NOT a data surface.
3
+ // Ownable CLI — the setup + auth utility. NOT a data surface.
4
4
  //
5
5
  // It signs you in (browser device flow → key in ~/.nous/config.json) and installs the
6
6
  // opennous plugin into your agent (`init`). All data work happens in the agent through
@@ -120,7 +120,8 @@ function apiClient(key) {
120
120
  process.exit(1);
121
121
  }
122
122
 
123
- async function request(method, path, { body, query } = {}) {
123
+ // soft: a failure throws instead of ending the process, for a step that may fail on its own.
124
+ async function request(method, path, { body, query, soft = false } = {}) {
124
125
  const url = new URL(path, apiUrl);
125
126
  if (query) {
126
127
  for (const [k, v] of Object.entries(query)) {
@@ -135,11 +136,13 @@ function apiClient(key) {
135
136
  body: body ? JSON.stringify(body) : undefined,
136
137
  });
137
138
  } catch (e) {
139
+ if (soft) throw e;
138
140
  console.error(certHint(e) || `Couldn't reach ${apiUrl}: ${e.message}`);
139
141
  process.exit(1);
140
142
  }
141
143
  if (!res.ok) {
142
144
  const err = await res.json().catch(() => ({ message: res.statusText }));
145
+ if (soft) throw new Error(err.message || err.error || `HTTP ${res.status}`);
143
146
  console.error(`Error: ${err.message || err.error}`);
144
147
  process.exit(1);
145
148
  }
@@ -150,6 +153,7 @@ function apiClient(key) {
150
153
  return {
151
154
  get: (path, query) => request("GET", path, { query }),
152
155
  post: (path, body) => request("POST", path, { body }),
156
+ softPost: (path, body) => request("POST", path, { body, soft: true }),
153
157
  };
154
158
  }
155
159
 
@@ -163,7 +167,7 @@ program
163
167
  .addCommand(
164
168
  new Command("login")
165
169
  .description("Save your API key")
166
- .requiredOption("--key <key>", "Your Nous API key")
170
+ .requiredOption("--key <key>", "Your Ownable API key")
167
171
  .option("--url <url>", "API base URL (default: https://api.opennous.cloud)")
168
172
  .action(({ key, url }) => {
169
173
  const cfg = readConfig();
@@ -205,7 +209,7 @@ program
205
209
  // ---------------------------------------------------------------------------
206
210
  // You're already signed in (the saved key identifies you), so there's no device-code
207
211
  // dance — we start a Composio consent, open it in your browser, and poll until the
208
- // account goes ACTIVE. Composio holds the OAuth token; Nous never sees it.
212
+ // account goes ACTIVE. Composio holds the OAuth token; Ownable never sees it.
209
213
  const CONNECT_ALIASES = {
210
214
  gmail: "gmail_oauth",
211
215
  gmail_oauth: "gmail_oauth",
@@ -329,11 +333,11 @@ program
329
333
  });
330
334
 
331
335
  // ---------------------------------------------------------------------------
332
- // Connect OpenNous to whatever agent is on this machine — host-aware.
336
+ // Connect Ownable to whatever agent is on this machine — host-aware.
333
337
  //
334
338
  // The MCP server (7 primitives) is the universal capability: any MCP host runs `npx @opennous/mcp`.
335
339
  // But the SKILLS + routing are the product surface, and those are delivered per host:
336
- // • Claude Code → the OpenNous PLUGIN (skills + routing hooks + the trimmed MCP, one install).
340
+ // • Claude Code → the Ownable PLUGIN (skills + routing hooks + the trimmed MCP, one install).
337
341
  // • Any other host (Cursor, Codex, Claude Desktop, …) → the trimmed MCP via a `.mcp.json`;
338
342
  // each host's skills pack ships as we light it up.
339
343
  // So `init` installs the plugin on Claude Code, and writes the MCP config everywhere else — one
@@ -388,7 +392,7 @@ function writeJsonMcp(path, apiUrl) {
388
392
  const PLUGIN_MARKETPLACE = "NousC/revenue-plugin";
389
393
  const PLUGIN_ID = "opennous@opennous";
390
394
 
391
- // Claude Code → install the OpenNous PLUGIN (skills + routing hooks + the trimmed MCP, in one).
395
+ // Claude Code → install the Ownable PLUGIN (skills + routing hooks + the trimmed MCP, in one).
392
396
  // We try the plugin CLI; if this Claude build has no `plugin` subcommand it returns null and the
393
397
  // caller prints the two slash-commands instead. We deliberately do NOT fall back to a bare
394
398
  // `claude mcp add` — a standalone MCP server would duplicate the plugin's and drop the skills.
@@ -404,11 +408,11 @@ function installClaudePlugin() {
404
408
  function registerMcpServer(apiUrl) {
405
409
  const path = join(process.cwd(), ".mcp.json");
406
410
  writeJsonMcp(path, apiUrl);
407
- return `Wrote ${path} — your agent will pick up OpenNous (7 primitives) from it.`;
411
+ return `Wrote ${path} — your agent will pick up Ownable (7 primitives) from it.`;
408
412
  }
409
413
 
410
414
  // Install the ICP auto-sync hook into the project's Claude Code config. Once in place,
411
- // editing a Nous context file (context/**/icp.md, etc.) reminds the agent to call
415
+ // editing an Ownable context file (context/**/icp.md, etc.) reminds the agent to call
412
416
  // sync_icp that turn — because an edited ICP file is INERT until synced into the graph.
413
417
  // Copies our packaged hook script into .claude/hooks/ and merges the PostToolUse entry
414
418
  // into .claude/settings.json WITHOUT clobbering any existing hooks or settings.
@@ -447,7 +451,7 @@ function installIcpSyncHook() {
447
451
  // This is the command the install script and the marketing site point to.
448
452
  program
449
453
  .command("init")
450
- .description("Get started — sign in and connect Nous to your agent")
454
+ .description("Get started — sign in and connect Ownable to your agent")
451
455
  .option("--url <url>", "API base URL (default: https://api.opennous.cloud)")
452
456
  .option("--no-register", "Skip registering the MCP server (just sign in)")
453
457
  .action(async ({ url, register }) => {
@@ -460,41 +464,41 @@ program
460
464
  let installed = false;
461
465
  try { installed = installClaudePlugin(); } catch { /* fall through to instructions */ }
462
466
  if (installed) {
463
- console.log("\n✓ Installed the OpenNous plugin into Claude Code — skills, routing, and the 8 primitives.");
464
- console.log(" Open Claude Code and OpenNous starts onboarding automatically.");
467
+ console.log("\n✓ Installed the Ownable plugin into Claude Code — skills, routing, and the 8 primitives.");
468
+ console.log(" Open Claude Code and Ownable starts onboarding automatically.");
465
469
  console.log(dim(" (Already have it open? Start a new session so the skills load.)"));
466
470
  } else {
467
- console.log("\nDetected Claude Code. Install the OpenNous plugin (skills + hooks + tools):");
471
+ console.log("\nDetected Claude Code. Install the Ownable plugin (skills + hooks + tools):");
468
472
  console.log(" /plugin marketplace add NousC/revenue-plugin");
469
473
  console.log(" /plugin install opennous@opennous");
470
- console.log(dim(" Then open Claude Code — OpenNous starts onboarding on its own."));
474
+ console.log(dim(" Then open Claude Code — Ownable starts onboarding on its own."));
471
475
  }
472
476
  } else {
473
477
  try {
474
478
  console.log(`\n${registerMcpServer(apiUrl)}`);
475
479
  } catch {
476
- console.log("\nCouldn't write .mcp.json automatically. Add OpenNous to your host's MCP config:");
480
+ console.log("\nCouldn't write .mcp.json automatically. Add Ownable to your host's MCP config:");
477
481
  console.log(` command: ${MCP_CMD} args: ${MCP_ARGS.join(" ")} env: NOUS_SURFACE=plugin`);
478
482
  }
479
483
  }
480
484
 
481
485
  // Auto-sync the ICP: a Claude Code hook so editing your ICP file reminds the
482
- // agent to sync it into Nous (the file is inert until synced). Best-effort —
486
+ // agent to sync it into Ownable (the file is inert until synced). Best-effort —
483
487
  // a convenience, never a reason to fail setup.
484
488
  try {
485
489
  const already = installIcpSyncHook();
486
- if (!already) console.log("Installed the ICP auto-sync hook — edits to your ICP file now sync to Nous automatically.");
490
+ if (!already) console.log("Installed the ICP auto-sync hook — edits to your ICP file now sync to Ownable automatically.");
487
491
  } catch { /* non-fatal */ }
488
492
  }
489
493
 
490
- console.log("\n✅ Signed in. Open Claude Code — OpenNous will walk you through onboarding on its own.");
494
+ console.log("\n✅ Signed in. Open Claude Code — Ownable will walk you through onboarding on its own.");
491
495
  console.log("\n" + dim("Docs: https://docs.opennous.cloud/mcp/introduction"));
492
496
  });
493
497
 
494
498
  // nous pull — write the Vault into ./context/*.md and hand authorship to the repo.
495
499
  //
496
500
  // The bridge for the Google road. A user who set up in the browser has a Vault whose author
497
- // is Nous (source 'nous', no file). The day they open a repo and connect an agent, that
501
+ // is Ownable (source 'nous', no file). The day they open a repo and connect an agent, that
498
502
  // arrangement is backwards: they now live in files, and the Vault should mirror the files,
499
503
  // not the other way round. `pull` flips it — it writes each context doc to disk and re-syncs
500
504
  // it WITH its path, which sets source='claude_code'. From then on their repo is the author,
@@ -506,7 +510,7 @@ const PULL_KINDS = { icp: "icp.md" };
506
510
 
507
511
  program
508
512
  .command("pull")
509
- .description("Write your Nous context docs into ./context and let your repo own them")
513
+ .description("Write your Ownable context docs into ./context and let your repo own them")
510
514
  .option("--dir <path>", "Where to write the context/ folder (default: cwd)", process.cwd())
511
515
  .option("--force", "Overwrite files that already exist", false)
512
516
  .action(async ({ dir, force }) => {
@@ -542,7 +546,7 @@ program
542
546
 
543
547
  console.log(`\n✓ ${written} written${skipped ? `, ${skipped} skipped` : ""}. Your repo now owns your context.`);
544
548
  if (synced < written) console.log(dim(" (some didn't sync back — re-run `nous pull` to finish)"));
545
- console.log(dim("Edit these files and your agent syncs them with sync_foundation. Nous mirrors them."));
549
+ console.log(dim("Edit these files and your agent syncs them with sync_foundation. Ownable mirrors them."));
546
550
  });
547
551
 
548
552
  // nous logout — top-level alias of `auth logout`. Removes the saved API key.
@@ -552,10 +556,11 @@ program
552
556
  .option("--agent <agent>", "claude or codex (default: whichever you're signed in to)")
553
557
  .option("--days <days>", "how many days back to read, 1 to 730", "180")
554
558
  .option("--workspace <id>", "the workspace to read; the app's command fills this in")
555
- .option("--yes", "start without asking")
559
+ .option("--yes", "start without asking, and accept who each person is as proposed")
560
+ .option("--confirm", "wait until you confirm who each person is in Routing before reading")
556
561
  .option("--url <url>", "API base URL (default: https://api.opennous.cloud)")
557
562
  .action(async (opts) => {
558
- const { detectAgents, runBackfill, liveRun, emptyEstimate, rerunCommand, NO_AGENT } = await import("./backfill.js");
563
+ const { detectAgents, runBackfill, liveRun, emptyEstimate, estimateLine, rerunCommand, reviewLine, writeReport, NO_AGENT } = await import("./backfill.js");
559
564
  const days = Number(opts.days);
560
565
  if (!Number.isInteger(days) || days < 1 || days > 730) {
561
566
  console.error("--days must be a whole number from 1 to 730.");
@@ -601,8 +606,8 @@ program
601
606
  return;
602
607
  }
603
608
  console.log(`\nWorkspace: ${workspace}`);
604
- console.log(`Last ${days} days: about ${est.counts.email_thread} email threads, ${est.counts.meeting} meetings, ${est.counts.call} calls.`);
605
- console.log(`They're filtered first, then read on your ${agent} plan. None of it uses your OpenNous credits.`);
609
+ console.log(estimateLine(days, est.counts));
610
+ console.log(`They're filtered first, then read on your ${agent} plan. None of it uses your Ownable credits.`);
606
611
  console.log("You can stop at any time and run this again: it carries on where it stopped.\n");
607
612
  if (!opts.yes) {
608
613
  const { createInterface } = await import("node:readline/promises");
@@ -611,23 +616,38 @@ program
611
616
  rl.close();
612
617
  if (!ok) return;
613
618
  }
614
- const job = await api.post("/v2/backfill/own", { days });
619
+ // Who each person is (GET-1159): read with the proposals by default; --confirm waits for you, --yes accepts them.
620
+ const confirm = opts.confirm ? "wait" : opts.yes ? "yes" : "proceed";
621
+ const job = await api.post("/v2/backfill/own", { days, confirm });
615
622
  console.log(job.resumed ? "Picking up where you left off. Stop any time with Ctrl-C." : `Started. Stop any time with Ctrl-C; run \`${rerun}\` again to carry on.`);
616
623
  process.once("SIGINT", () => {
617
624
  console.log(`\nStopped. Everything read so far is saved. Run \`${rerun}\` to carry on.`);
618
625
  process.exit(130);
619
626
  });
627
+ const run = liveRun();
620
628
  const result = await runBackfill({
621
- agent, jobId: job.job_id, run: liveRun(), log: console.log, rerun,
629
+ agent, jobId: job.job_id, run, log: console.log, rerun,
622
630
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
623
631
  api: {
624
632
  calls: (id, limit) => api.get(`/v2/backfill/own/${id}/calls`, { limit }),
625
633
  answer: (id, body) => api.post(`/v2/backfill/own/calls/${id}`, body),
626
- resume: () => api.post("/v2/backfill/own", { days }),
634
+ resume: () => api.post("/v2/backfill/own", { days, confirm }),
627
635
  },
628
636
  });
629
637
  console.log(`Done. This run: ${result.read} conversations read, ${result.answered} model calls on your ${agent} plan.`);
630
638
  if (result.failed) console.log(`${result.failed} conversations couldn't be read. Run \`${rerun}\` again to retry them.`);
639
+ const review = reviewLine(result.memoryReview);
640
+ if (review) console.log(review);
641
+ // The Intelligence report (GET-1199): private to you, its summary written on your plan.
642
+ console.log("Writing your Intelligence report…");
643
+ const reportUrl = await writeReport({
644
+ agent, jobId: job.job_id, run,
645
+ api: {
646
+ report: (id) => api.softPost(`/v2/backfill/own/${id}/report`, {}),
647
+ writing: (id, body) => api.softPost(`/v2/backfill/own/${id}/report/writing`, body),
648
+ },
649
+ }).catch(() => null);
650
+ if (reportUrl) console.log(`Your Intelligence report is ready: ${reportUrl}`);
631
651
  });
632
652
 
633
653
  program