@opennous/cli 0.13.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/cli",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
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
@@ -97,6 +97,9 @@ function runCommand({ cmd, args, input }, cwd) {
97
97
  });
98
98
  }
99
99
 
100
+ /** Nothing to read in this window. Pure. */
101
+ export const emptyEstimate = (counts) => !Object.values(counts ?? {}).some((n) => n > 0);
102
+
100
103
  /** One line on how far the job is, or null when there is nothing to say yet. Pure. */
101
104
  export function progressLine(reading, answered) {
102
105
  if (reading?.finished) return "Wrapping up: last summaries for your accounts.";
package/src/index.js CHANGED
@@ -110,9 +110,9 @@ function writeConfig(cfg) {
110
110
  /** A key is there to use: from the environment, or saved by a sign-in. */
111
111
  const hasKey = () => !!(process.env.NOUS_API_KEY || readConfig().apiKey);
112
112
 
113
- function apiClient() {
113
+ function apiClient(key) {
114
114
  const cfg = readConfig();
115
- const apiKey = process.env.NOUS_API_KEY || cfg.apiKey;
115
+ const apiKey = key || process.env.NOUS_API_KEY || cfg.apiKey;
116
116
  const apiUrl = process.env.NOUS_API_URL || cfg.apiUrl || "https://api.opennous.cloud";
117
117
 
118
118
  if (!apiKey) {
@@ -288,14 +288,18 @@ async function browserLogin(url, opts = {}) {
288
288
  } catch { continue; }
289
289
 
290
290
  if (j.status === "approved" && j.api_key) {
291
- const cfg = readConfig();
292
- cfg.apiKey = j.api_key;
293
- cfg.apiUrl = apiUrl;
294
- writeConfig(cfg);
291
+ // `save: false` is one command's key: the sign-in on this machine stays what it was.
292
+ if (opts.save !== false) {
293
+ const cfg = readConfig();
294
+ cfg.apiKey = j.api_key;
295
+ cfg.apiUrl = apiUrl;
296
+ writeConfig(cfg);
297
+ }
295
298
  const who = await fetchWhoami(apiUrl, j.api_key);
296
299
  console.log(`\n\n ${green("✓")} ${bold("Signed in")}${who ? ` as ${identityLine(who)}` : ""}`);
297
- console.log(` ${dim("Key saved to ~/.nous/config.json")}\n`);
298
- return true;
300
+ if (opts.save !== false) console.log(` ${dim("Key saved to ~/.nous/config.json")}`);
301
+ console.log("");
302
+ return j.api_key;
299
303
  }
300
304
  if (j.status === "denied") { console.log("\nAuthorization was denied."); process.exit(1); }
301
305
  if (j.status === "expired") { console.log("\nThis sign-in expired. Run it again."); process.exit(1); }
@@ -546,10 +550,11 @@ program
546
550
  .description("Read your email and meeting history on your own Claude or ChatGPT plan")
547
551
  .option("--agent <agent>", "claude or codex (default: whichever you're signed in to)")
548
552
  .option("--days <days>", "how many days back to read, 1 to 730", "180")
553
+ .option("--workspace <id>", "the workspace to read; the app's command fills this in")
549
554
  .option("--yes", "start without asking")
550
555
  .option("--url <url>", "API base URL (default: https://api.opennous.cloud)")
551
556
  .action(async (opts) => {
552
- const { detectAgents, runBackfill, liveRun, RERUN, NO_AGENT } = await import("./backfill.js");
557
+ const { detectAgents, runBackfill, liveRun, emptyEstimate, RERUN, NO_AGENT } = await import("./backfill.js");
553
558
  const days = Number(opts.days);
554
559
  if (!Number.isInteger(days) || days < 1 || days > 730) {
555
560
  console.error("--days must be a whole number from 1 to 730.");
@@ -563,9 +568,30 @@ program
563
568
  }
564
569
  // Asked after the check above, so nobody signs in to be told they can't run this.
565
570
  if (!hasKey()) await browserLogin(opts.url);
566
- const api = apiClient();
571
+ // A key saved here may be for another workspace, so say which one this is, and never read the
572
+ // wrong one: the app's command names its workspace, and a key for any other is not used.
573
+ const cfg = readConfig();
574
+ const whoamiUrl = opts.url || process.env.NOUS_API_URL || cfg.apiUrl || "https://api.opennous.cloud";
575
+ let key = process.env.NOUS_API_KEY || cfg.apiKey;
576
+ let who = await fetchWhoami(whoamiUrl, key);
577
+ if (opts.workspace && who?.workspace?.id !== opts.workspace) {
578
+ console.log(`This machine is signed in to ${who?.workspace?.name ?? "another workspace"}. Sign in to the workspace this backfill is for.`);
579
+ key = await browserLogin(opts.url, { save: false });
580
+ who = await fetchWhoami(whoamiUrl, key);
581
+ if (who?.workspace?.id !== opts.workspace) {
582
+ console.error(`That sign-in is for ${who?.workspace?.name ?? "another workspace"}. Open the right workspace in the app, then run this again.`);
583
+ process.exit(1);
584
+ }
585
+ }
586
+ const workspace = who?.workspace?.name ?? "your workspace";
587
+ const api = apiClient(key);
567
588
  const est = await api.post("/v2/backfill/own/estimate", { days });
568
- console.log(`\nLast ${days} days: about ${est.counts.email_thread} email threads, ${est.counts.meeting} meetings, ${est.counts.call} calls.`);
589
+ if (emptyEstimate(est.counts)) {
590
+ console.log(`\nNothing to read in ${workspace} for the last ${days} days. If your sync is still running, try again when it's done.`);
591
+ return;
592
+ }
593
+ console.log(`\nWorkspace: ${workspace}`);
594
+ console.log(`Last ${days} days: about ${est.counts.email_thread} email threads, ${est.counts.meeting} meetings, ${est.counts.call} calls.`);
569
595
  console.log(`They're filtered first, then read on your ${agent} plan. None of it uses your OpenNous credits.`);
570
596
  console.log("You can stop at any time and run this again: it carries on where it stopped.\n");
571
597
  if (!opts.yes) {