@opennous/cli 0.15.0 → 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 +8 -0
- package/README.md +3 -3
- package/package.json +2 -2
- package/src/backfill.js +54 -2
- package/src/certHint.js +1 -1
- package/src/hooks/icp-sync-reminder.mjs +5 -5
- package/src/index.js +47 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
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
|
+
|
|
3
11
|
## 0.15.0
|
|
4
12
|
|
|
5
13
|
### Added: Slack in `backfill`
|
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# @opennous/cli
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
@@ -105,12 +105,41 @@ export function estimateLine(days, counts) {
|
|
|
105
105
|
return `Last ${days} days: about ${counts?.email_thread ?? 0} email threads, ${counts?.meeting ?? 0} meetings, ${counts?.call ?? 0} calls${slack}.`;
|
|
106
106
|
}
|
|
107
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
|
+
|
|
108
136
|
/** Nothing to read in this window. Pure. */
|
|
109
137
|
export const emptyEstimate = (counts) => !Object.values(counts ?? {}).some((n) => n > 0);
|
|
110
138
|
|
|
111
139
|
/** One line on how far the job is, or null when there is nothing to say yet. Pure. */
|
|
112
140
|
export function progressLine(reading, answered, rerun = RERUN) {
|
|
113
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.`}`;
|
|
114
143
|
const stop = `Stop any time with Ctrl-C; run \`${rerun}\` again to carry on.`;
|
|
115
144
|
const what = { meetings: "meetings", slack: "Slack conversations" }[reading?.kind] ?? "email threads";
|
|
116
145
|
if (reading?.total) return `Read ${reading.done} of ${reading.total} ${what}. ${stop}`;
|
|
@@ -124,7 +153,7 @@ export function progressLine(reading, answered, rerun = RERUN) {
|
|
|
124
153
|
* is saved, and unanswered calls come back to the next run.
|
|
125
154
|
*/
|
|
126
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 }) {
|
|
127
|
-
let waitUntil = 0, answered = 0, lastLine = null, lastAt = -Infinity;
|
|
156
|
+
let waitUntil = 0, answered = 0, lastLine = null, lastAt = -Infinity, shownProposals = false;
|
|
128
157
|
const say = (line) => {
|
|
129
158
|
if (!line || (line === lastLine && now() - lastAt < progressMs) || now() - lastAt < Math.min(progressMs, 1000)) return;
|
|
130
159
|
lastLine = line; lastAt = now(); log(line);
|
|
@@ -149,10 +178,15 @@ export async function runBackfill({ api, agent, jobId, run, sleep, log, now = Da
|
|
|
149
178
|
};
|
|
150
179
|
for (;;) {
|
|
151
180
|
const page = await api.calls(jobId, workers);
|
|
152
|
-
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 } : {}) };
|
|
153
182
|
if (page.status === "failed") throw new Error(page.error || "The backfill failed on our side.");
|
|
154
183
|
// The server gave up waiting for us (a long sleep, say). We're back, so pick it up.
|
|
155
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
|
+
}
|
|
156
190
|
say(progressLine(page.reading, answered, rerun));
|
|
157
191
|
if (!page.calls.length) { await sleep(pollMs); continue; }
|
|
158
192
|
await Promise.all(page.calls.map((c) => answerOne(c, page.models)));
|
|
@@ -164,3 +198,21 @@ export function liveRun() {
|
|
|
164
198
|
const cwd = mkdtempSync(join(tmpdir(), "nous-backfill-"));
|
|
165
199
|
return (command) => runCommand(command, cwd);
|
|
166
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
|
|
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
|
-
//
|
|
2
|
+
// Ownable ICP auto-sync reminder (installed by `npx @opennous/cli init`).
|
|
3
3
|
//
|
|
4
|
-
// A Claude Code PostToolUse hook. After the agent edits
|
|
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
|
|
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
|
|
29
|
-
: `You edited
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
464
|
-
console.log(" Open Claude Code and
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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.
|
|
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, estimateLine, 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.");
|
|
@@ -602,7 +607,7 @@ program
|
|
|
602
607
|
}
|
|
603
608
|
console.log(`\nWorkspace: ${workspace}`);
|
|
604
609
|
console.log(estimateLine(days, est.counts));
|
|
605
|
-
console.log(`They're filtered first, then read on your ${agent} plan. None of it uses your
|
|
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
|
-
|
|
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
|
|
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
|