@opennous/cli 0.14.0 → 0.15.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 +16 -0
- package/package.json +1 -1
- package/src/backfill.js +14 -5
- package/src/index.js +19 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @opennous/cli changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Added: Slack in `backfill`
|
|
6
|
+
|
|
7
|
+
On a workspace that syncs its history, your Slack messages (DMs and group DMs with customers) wait
|
|
8
|
+
for a backfill like email does. `backfill` counts them before it asks, and reads them on your plan
|
|
9
|
+
(GET-1087).
|
|
10
|
+
|
|
11
|
+
## 0.14.1
|
|
12
|
+
|
|
13
|
+
### Fixed: carrying on with `backfill --workspace`
|
|
14
|
+
|
|
15
|
+
The carry-on line now repeats `--workspace`, so a rerun stays in the same workspace. A workspace you
|
|
16
|
+
signed in to for a backfill keeps its key, so carrying on doesn't ask you to sign in again. `logout`
|
|
17
|
+
removes these keys too (GET-1087).
|
|
18
|
+
|
|
3
19
|
## 0.13.0
|
|
4
20
|
|
|
5
21
|
### Added: `backfill`, in one command
|
package/package.json
CHANGED
package/src/backfill.js
CHANGED
|
@@ -13,6 +13,8 @@ import { join } from "node:path";
|
|
|
13
13
|
|
|
14
14
|
/** What to type to carry on. `npx` because most people never installed `nous` (GET-1087). */
|
|
15
15
|
export const RERUN = "npx @opennous/cli@latest backfill";
|
|
16
|
+
/** The same, for the workspace this run read: without it a rerun goes to the saved key's. Pure. */
|
|
17
|
+
export const rerunCommand = (workspaceId) => (workspaceId ? `${RERUN} --workspace ${workspaceId}` : RERUN);
|
|
16
18
|
export const NO_AGENT = "Sign in to Claude Code (`claude`) or Codex (`codex login`) first: this runs on your own plan, so it needs one of them. Then run this again.";
|
|
17
19
|
|
|
18
20
|
export const POLL_MS = 2000;
|
|
@@ -97,14 +99,21 @@ function runCommand({ cmd, args, input }, cwd) {
|
|
|
97
99
|
});
|
|
98
100
|
}
|
|
99
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
|
+
|
|
100
108
|
/** Nothing to read in this window. Pure. */
|
|
101
109
|
export const emptyEstimate = (counts) => !Object.values(counts ?? {}).some((n) => n > 0);
|
|
102
110
|
|
|
103
111
|
/** One line on how far the job is, or null when there is nothing to say yet. Pure. */
|
|
104
|
-
export function progressLine(reading, answered) {
|
|
112
|
+
export function progressLine(reading, answered, rerun = RERUN) {
|
|
105
113
|
if (reading?.finished) return "Wrapping up: last summaries for your accounts.";
|
|
106
|
-
const stop = `Stop any time with Ctrl-C; run \`${
|
|
107
|
-
|
|
114
|
+
const stop = `Stop any time with Ctrl-C; run \`${rerun}\` again to carry on.`;
|
|
115
|
+
const what = { meetings: "meetings", slack: "Slack conversations" }[reading?.kind] ?? "email threads";
|
|
116
|
+
if (reading?.total) return `Read ${reading.done} of ${reading.total} ${what}. ${stop}`;
|
|
108
117
|
if (answered) return `${answered} reads done on your plan. ${stop}`;
|
|
109
118
|
return `Getting ready to read. This can take a few minutes. ${stop}`;
|
|
110
119
|
}
|
|
@@ -114,7 +123,7 @@ export function progressLine(reading, answered) {
|
|
|
114
123
|
* `run` (one command → stdout), `sleep`, `log`. Stopping loses nothing: every read that finished
|
|
115
124
|
* is saved, and unanswered calls come back to the next run.
|
|
116
125
|
*/
|
|
117
|
-
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 }) {
|
|
126
|
+
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 }) {
|
|
118
127
|
let waitUntil = 0, answered = 0, lastLine = null, lastAt = -Infinity;
|
|
119
128
|
const say = (line) => {
|
|
120
129
|
if (!line || (line === lastLine && now() - lastAt < progressMs) || now() - lastAt < Math.min(progressMs, 1000)) return;
|
|
@@ -144,7 +153,7 @@ export async function runBackfill({ api, agent, jobId, run, sleep, log, now = Da
|
|
|
144
153
|
if (page.status === "failed") throw new Error(page.error || "The backfill failed on our side.");
|
|
145
154
|
// The server gave up waiting for us (a long sleep, say). We're back, so pick it up.
|
|
146
155
|
if (page.status === "paused_own_model") { await api.resume?.(); await sleep(pollMs); continue; }
|
|
147
|
-
say(progressLine(page.reading, answered));
|
|
156
|
+
say(progressLine(page.reading, answered, rerun));
|
|
148
157
|
if (!page.calls.length) { await sleep(pollMs); continue; }
|
|
149
158
|
await Promise.all(page.calls.map((c) => answerOne(c, page.models)));
|
|
150
159
|
}
|
package/src/index.js
CHANGED
|
@@ -192,8 +192,9 @@ program
|
|
|
192
192
|
.description("Sign out — remove the saved API key")
|
|
193
193
|
.action(() => {
|
|
194
194
|
const cfg = readConfig();
|
|
195
|
-
if (!cfg.apiKey) { console.log("Not signed in."); return; }
|
|
195
|
+
if (!cfg.apiKey && !cfg.workspaceKeys) { console.log("Not signed in."); return; }
|
|
196
196
|
delete cfg.apiKey;
|
|
197
|
+
delete cfg.workspaceKeys;
|
|
197
198
|
writeConfig(cfg);
|
|
198
199
|
console.log("✓ Signed out — removed the saved API key from ~/.nous/config.json");
|
|
199
200
|
})
|
|
@@ -554,7 +555,7 @@ program
|
|
|
554
555
|
.option("--yes", "start without asking")
|
|
555
556
|
.option("--url <url>", "API base URL (default: https://api.opennous.cloud)")
|
|
556
557
|
.action(async (opts) => {
|
|
557
|
-
const { detectAgents, runBackfill, liveRun, emptyEstimate,
|
|
558
|
+
const { detectAgents, runBackfill, liveRun, emptyEstimate, estimateLine, rerunCommand, NO_AGENT } = await import("./backfill.js");
|
|
558
559
|
const days = Number(opts.days);
|
|
559
560
|
if (!Number.isInteger(days) || days < 1 || days > 730) {
|
|
560
561
|
console.error("--days must be a whole number from 1 to 730.");
|
|
@@ -574,6 +575,12 @@ program
|
|
|
574
575
|
const whoamiUrl = opts.url || process.env.NOUS_API_URL || cfg.apiUrl || "https://api.opennous.cloud";
|
|
575
576
|
let key = process.env.NOUS_API_KEY || cfg.apiKey;
|
|
576
577
|
let who = await fetchWhoami(whoamiUrl, key);
|
|
578
|
+
// A workspace signed in to here before keeps its key, so carrying on doesn't mean signing in again.
|
|
579
|
+
const kept = opts.workspace && who?.workspace?.id !== opts.workspace ? cfg.workspaceKeys?.[opts.workspace] : null;
|
|
580
|
+
if (kept) {
|
|
581
|
+
const keptWho = await fetchWhoami(whoamiUrl, kept);
|
|
582
|
+
if (keptWho?.workspace?.id === opts.workspace) { key = kept; who = keptWho; }
|
|
583
|
+
}
|
|
577
584
|
if (opts.workspace && who?.workspace?.id !== opts.workspace) {
|
|
578
585
|
console.log(`This machine is signed in to ${who?.workspace?.name ?? "another workspace"}. Sign in to the workspace this backfill is for.`);
|
|
579
586
|
key = await browserLogin(opts.url, { save: false });
|
|
@@ -582,8 +589,11 @@ program
|
|
|
582
589
|
console.error(`That sign-in is for ${who?.workspace?.name ?? "another workspace"}. Open the right workspace in the app, then run this again.`);
|
|
583
590
|
process.exit(1);
|
|
584
591
|
}
|
|
592
|
+
const saved = readConfig();
|
|
593
|
+
writeConfig({ ...saved, workspaceKeys: { ...saved.workspaceKeys, [opts.workspace]: key } });
|
|
585
594
|
}
|
|
586
595
|
const workspace = who?.workspace?.name ?? "your workspace";
|
|
596
|
+
const rerun = rerunCommand(opts.workspace);
|
|
587
597
|
const api = apiClient(key);
|
|
588
598
|
const est = await api.post("/v2/backfill/own/estimate", { days });
|
|
589
599
|
if (emptyEstimate(est.counts)) {
|
|
@@ -591,7 +601,7 @@ program
|
|
|
591
601
|
return;
|
|
592
602
|
}
|
|
593
603
|
console.log(`\nWorkspace: ${workspace}`);
|
|
594
|
-
console.log(
|
|
604
|
+
console.log(estimateLine(days, est.counts));
|
|
595
605
|
console.log(`They're filtered first, then read on your ${agent} plan. None of it uses your OpenNous credits.`);
|
|
596
606
|
console.log("You can stop at any time and run this again: it carries on where it stopped.\n");
|
|
597
607
|
if (!opts.yes) {
|
|
@@ -602,13 +612,13 @@ program
|
|
|
602
612
|
if (!ok) return;
|
|
603
613
|
}
|
|
604
614
|
const job = await api.post("/v2/backfill/own", { days });
|
|
605
|
-
console.log(job.resumed ? "Picking up where you left off. Stop any time with Ctrl-C." : `Started. Stop any time with Ctrl-C; run \`${
|
|
615
|
+
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.`);
|
|
606
616
|
process.once("SIGINT", () => {
|
|
607
|
-
console.log(`\nStopped. Everything read so far is saved. Run \`${
|
|
617
|
+
console.log(`\nStopped. Everything read so far is saved. Run \`${rerun}\` to carry on.`);
|
|
608
618
|
process.exit(130);
|
|
609
619
|
});
|
|
610
620
|
const result = await runBackfill({
|
|
611
|
-
agent, jobId: job.job_id, run: liveRun(), log: console.log,
|
|
621
|
+
agent, jobId: job.job_id, run: liveRun(), log: console.log, rerun,
|
|
612
622
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
613
623
|
api: {
|
|
614
624
|
calls: (id, limit) => api.get(`/v2/backfill/own/${id}/calls`, { limit }),
|
|
@@ -617,7 +627,7 @@ program
|
|
|
617
627
|
},
|
|
618
628
|
});
|
|
619
629
|
console.log(`Done. This run: ${result.read} conversations read, ${result.answered} model calls on your ${agent} plan.`);
|
|
620
|
-
if (result.failed) console.log(`${result.failed} conversations couldn't be read. Run \`${
|
|
630
|
+
if (result.failed) console.log(`${result.failed} conversations couldn't be read. Run \`${rerun}\` again to retry them.`);
|
|
621
631
|
});
|
|
622
632
|
|
|
623
633
|
program
|
|
@@ -625,8 +635,9 @@ program
|
|
|
625
635
|
.description("Sign out — remove the saved API key")
|
|
626
636
|
.action(() => {
|
|
627
637
|
const cfg = readConfig();
|
|
628
|
-
if (!cfg.apiKey) { console.log("Not signed in."); return; }
|
|
638
|
+
if (!cfg.apiKey && !cfg.workspaceKeys) { console.log("Not signed in."); return; }
|
|
629
639
|
delete cfg.apiKey;
|
|
640
|
+
delete cfg.workspaceKeys;
|
|
630
641
|
writeConfig(cfg);
|
|
631
642
|
console.log("✓ Signed out — removed the saved API key from ~/.nous/config.json");
|
|
632
643
|
});
|