@opennous/cli 0.14.0 → 0.14.1

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,13 @@
1
1
  # @opennous/cli changelog
2
2
 
3
+ ## 0.14.1
4
+
5
+ ### Fixed: carrying on with `backfill --workspace`
6
+
7
+ The carry-on line now repeats `--workspace`, so a rerun stays in the same workspace. A workspace you
8
+ signed in to for a backfill keeps its key, so carrying on doesn't ask you to sign in again. `logout`
9
+ removes these keys too (GET-1087).
10
+
3
11
  ## 0.13.0
4
12
 
5
13
  ### Added: `backfill`, in one command
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/cli",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "Nous CLI — set up Nous, connect it to your coding agent, and read the Context API from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
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;
@@ -101,9 +103,9 @@ function runCommand({ cmd, args, input }, cwd) {
101
103
  export const emptyEstimate = (counts) => !Object.values(counts ?? {}).some((n) => n > 0);
102
104
 
103
105
  /** One line on how far the job is, or null when there is nothing to say yet. Pure. */
104
- export function progressLine(reading, answered) {
106
+ export function progressLine(reading, answered, rerun = RERUN) {
105
107
  if (reading?.finished) return "Wrapping up: last summaries for your accounts.";
106
- const stop = `Stop any time with Ctrl-C; run \`${RERUN}\` again to carry on.`;
108
+ const stop = `Stop any time with Ctrl-C; run \`${rerun}\` again to carry on.`;
107
109
  if (reading?.total) return `Read ${reading.done} of ${reading.total} ${reading.kind === "meetings" ? "meetings" : "email threads"}. ${stop}`;
108
110
  if (answered) return `${answered} reads done on your plan. ${stop}`;
109
111
  return `Getting ready to read. This can take a few minutes. ${stop}`;
@@ -114,7 +116,7 @@ export function progressLine(reading, answered) {
114
116
  * `run` (one command → stdout), `sleep`, `log`. Stopping loses nothing: every read that finished
115
117
  * is saved, and unanswered calls come back to the next run.
116
118
  */
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 }) {
119
+ 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
120
  let waitUntil = 0, answered = 0, lastLine = null, lastAt = -Infinity;
119
121
  const say = (line) => {
120
122
  if (!line || (line === lastLine && now() - lastAt < progressMs) || now() - lastAt < Math.min(progressMs, 1000)) return;
@@ -144,7 +146,7 @@ export async function runBackfill({ api, agent, jobId, run, sleep, log, now = Da
144
146
  if (page.status === "failed") throw new Error(page.error || "The backfill failed on our side.");
145
147
  // The server gave up waiting for us (a long sleep, say). We're back, so pick it up.
146
148
  if (page.status === "paused_own_model") { await api.resume?.(); await sleep(pollMs); continue; }
147
- say(progressLine(page.reading, answered));
149
+ say(progressLine(page.reading, answered, rerun));
148
150
  if (!page.calls.length) { await sleep(pollMs); continue; }
149
151
  await Promise.all(page.calls.map((c) => answerOne(c, page.models)));
150
152
  }
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, RERUN, NO_AGENT } = await import("./backfill.js");
558
+ const { detectAgents, runBackfill, liveRun, emptyEstimate, 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)) {
@@ -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 \`${RERUN}\` again to carry on.`);
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 \`${RERUN}\` to carry on.`);
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 \`${RERUN}\` again to retry them.`);
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
  });