rscot-agent 1.2.2 → 1.7.47

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/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import kleur2 from "kleur";
8
8
  // src/install.ts
9
9
  import prompts from "prompts";
10
10
  import ora from "ora";
11
+ import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
11
12
 
12
13
  // src/ui.ts
13
14
  import kleur from "kleur";
@@ -116,7 +117,7 @@ function exampleGrid(examples) {
116
117
  // src/checks.ts
117
118
  import { execFile } from "node:child_process";
118
119
  import { promisify } from "node:util";
119
- import { existsSync, mkdirSync, accessSync, constants } from "node:fs";
120
+ import { existsSync, mkdirSync, accessSync, constants, readdirSync, statSync } from "node:fs";
120
121
  import { homedir } from "node:os";
121
122
  import { join } from "node:path";
122
123
  var pexec = promisify(execFile);
@@ -126,8 +127,8 @@ async function tryRun(cmd, args) {
126
127
  let resolvedPath;
127
128
  try {
128
129
  const which = process.platform === "win32" ? "where" : "which";
129
- const w = await pexec(which, [cmd], { timeout: 2e3, windowsHide: true });
130
- resolvedPath = w.stdout.split(/\r?\n/)[0]?.trim();
130
+ const w2 = await pexec(which, [cmd], { timeout: 2e3, windowsHide: true });
131
+ resolvedPath = w2.stdout.split(/\r?\n/)[0]?.trim();
131
132
  } catch {
132
133
  }
133
134
  return { out: stdout.trim(), path: resolvedPath };
@@ -228,18 +229,41 @@ async function checkProviderKeys() {
228
229
  "DEEPSEEK_API_KEY",
229
230
  "OPENROUTER_API_KEY"
230
231
  ];
231
- const found = envKeys.filter((k) => (process.env[k] ?? "").length > 8);
232
- if (found.length > 0) {
232
+ const envFound = envKeys.filter((k) => (process.env[k] ?? "").length > 8);
233
+ const keysDir2 = join(homedir(), ".rscot", "keys");
234
+ const fileFound = [];
235
+ try {
236
+ if (existsSync(keysDir2)) {
237
+ for (const f of readdirSync(keysDir2)) {
238
+ if (!f.endsWith(".key"))
239
+ continue;
240
+ try {
241
+ if (statSync(join(keysDir2, f)).size > 8) {
242
+ fileFound.push(f.replace(/\.key$/, ""));
243
+ }
244
+ } catch {
245
+ }
246
+ }
247
+ }
248
+ } catch {
249
+ }
250
+ const total = envFound.length + fileFound.length;
251
+ if (total > 0) {
252
+ const parts = [];
253
+ if (envFound.length)
254
+ parts.push("env: " + envFound.join(", "));
255
+ if (fileFound.length)
256
+ parts.push("~/.rscot/keys/: " + fileFound.join(", "));
233
257
  return {
234
258
  name: "LLM provider key",
235
259
  ok: true,
236
- detail: "found " + found.join(", ")
260
+ detail: parts.join(" \xB7 ")
237
261
  };
238
262
  }
239
263
  return {
240
264
  name: "LLM provider key",
241
265
  ok: false,
242
- detail: "no ANTHROPIC_API_KEY \xB7 OPENAI_API_KEY \xB7 DEEPSEEK_API_KEY \xB7 OPENROUTER_API_KEY in env"
266
+ detail: "no ANTHROPIC_API_KEY \xB7 OPENAI_API_KEY \xB7 DEEPSEEK_API_KEY \xB7 OPENROUTER_API_KEY in env, no key file under ~/.rscot/keys/"
243
267
  };
244
268
  }
245
269
  function ensureConfigDir() {
@@ -358,6 +382,22 @@ var providers = [
358
382
  keyMinLen: 24,
359
383
  envVar: "OPENROUTER_API_KEY"
360
384
  },
385
+ {
386
+ id: "kimi",
387
+ display: "Kimi (Moonshot)",
388
+ blurb: "Moonshot Kimi K2 \xB7 long-context CN flagship \xB7 OpenAI-compatible",
389
+ needsKey: true,
390
+ baseUrl: "https://api.moonshot.cn/v1",
391
+ defaultModel: "kimi-k2-turbo",
392
+ models: [
393
+ { id: "kimi-k2-turbo", label: "kimi-k2-turbo", note: "default \xB7 fast \xB7 256K ctx", priceIn: 0.6, priceOut: 2.4 },
394
+ { id: "kimi-k2", label: "kimi-k2", note: "flagship \xB7 reasoning", priceIn: 1.2, priceOut: 4.8 }
395
+ ],
396
+ keyHelpUrl: "https://platform.moonshot.cn/console/api-keys",
397
+ keyPrefix: "sk-",
398
+ keyMinLen: 24,
399
+ envVar: "MOONSHOT_API_KEY"
400
+ },
361
401
  {
362
402
  id: "groq",
363
403
  display: "Groq",
@@ -445,7 +485,7 @@ var providers = [
445
485
  {
446
486
  id: "custom",
447
487
  display: "OpenAI-compatible (custom)",
448
- blurb: "vLLM / Cerebras / Fireworks / self-hosted endpoint",
488
+ blurb: "OpenToken / vLLM / Cerebras / Fireworks / self-hosted endpoint",
449
489
  needsKey: false,
450
490
  baseUrl: "",
451
491
  defaultModel: "",
@@ -519,10 +559,284 @@ function saveConfig(opts) {
519
559
  }
520
560
  return { configPath: path, keyPath: kp };
521
561
  }
562
+ function loadConfig() {
563
+ const p = configPath();
564
+ if (!existsSync2(p))
565
+ return null;
566
+ try {
567
+ return JSON.parse(readFileSync(p, "utf8"));
568
+ } catch {
569
+ return null;
570
+ }
571
+ }
522
572
 
523
- // src/install.ts
524
- var VERSION = "1.0.0";
573
+ // src/repl.ts
574
+ import readline from "node:readline";
575
+ import { homedir as homedir4 } from "node:os";
576
+
577
+ // src/agentRuntime.ts
578
+ import { spawn } from "node:child_process";
579
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
580
+ import { homedir as homedir3 } from "node:os";
581
+ import { dirname as dirname2, join as join3, resolve } from "node:path";
582
+ import { fileURLToPath } from "node:url";
583
+ import { createRequire } from "node:module";
584
+ var __filename = fileURLToPath(import.meta.url);
585
+ var __dirname = dirname2(__filename);
586
+ var req = createRequire(import.meta.url);
587
+ function findAgentRuntime() {
588
+ const envPath = process.env.RSCOT_AGENT_RUNTIME;
589
+ if (envPath && existsSync3(envPath))
590
+ return envPath;
591
+ const bundled = join3(__dirname, "agent-runtime.cjs");
592
+ if (existsSync3(bundled))
593
+ return bundled;
594
+ const monoDev = resolve(__dirname, "..", "..", "vscode-extension", "dist", "cli.js");
595
+ if (existsSync3(monoDev))
596
+ return monoDev;
597
+ try {
598
+ return req.resolve("code-workspace-agent/dist/cli.js");
599
+ } catch {
600
+ }
601
+ throw new Error(
602
+ "Rscot agent runtime (cli.js) not found. Set RSCOT_AGENT_RUNTIME to the cli.js path or install the desktop app which ships it."
603
+ );
604
+ }
605
+ function readApiKey(cfg) {
606
+ const kp = keyPath(cfg.provider);
607
+ if (!existsSync3(kp))
608
+ return "";
609
+ try {
610
+ return readFileSync2(kp, "utf8").trim();
611
+ } catch {
612
+ return "";
613
+ }
614
+ }
615
+ function buildAgentArgs(cfg, opts) {
616
+ const args = [
617
+ "--workspace",
618
+ opts.workspace,
619
+ "--backend-url",
620
+ opts.backendUrl,
621
+ "--provider",
622
+ cfg.provider,
623
+ "--stream"
624
+ ];
625
+ if (cfg.model)
626
+ args.push("--model", cfg.model);
627
+ if (opts.resume)
628
+ args.push("--resume");
629
+ if (opts.quiet)
630
+ args.push("--quiet");
631
+ if (opts.extraArgs)
632
+ args.push(...opts.extraArgs);
633
+ if (opts.prompt !== void 0)
634
+ args.push(opts.prompt);
635
+ return args;
636
+ }
637
+ async function preflightBackend(backendUrl2) {
638
+ try {
639
+ const res = await fetch(backendUrl2 + "/health", {
640
+ signal: AbortSignal.timeout(1500)
641
+ });
642
+ if (res.ok)
643
+ return { ok: true };
644
+ return { ok: false, detail: "HTTP " + res.status };
645
+ } catch (err) {
646
+ return { ok: false, detail: err instanceof Error ? err.message : String(err) };
647
+ }
648
+ }
649
+ function readCwaAdminToken() {
650
+ const path = join3(homedir3(), ".rscot", "cwa", "token");
651
+ if (!existsSync3(path))
652
+ return null;
653
+ try {
654
+ const t = readFileSync2(path, "utf8").trim();
655
+ return t.length > 0 ? t : null;
656
+ } catch {
657
+ return null;
658
+ }
659
+ }
660
+ function runAgent(opts) {
661
+ const cfg = loadConfig();
662
+ if (!cfg)
663
+ return Promise.resolve(1);
664
+ const runtime = findAgentRuntime();
665
+ const args = buildAgentArgs(cfg, opts);
666
+ const env = { ...process.env };
667
+ const apiKey = readApiKey(cfg);
668
+ if (apiKey)
669
+ env.CWA_API_KEY = apiKey;
670
+ const cwaToken = readCwaAdminToken();
671
+ if (cwaToken)
672
+ env.CWA_AUTH_BEARER = cwaToken;
673
+ return new Promise((resolve2) => {
674
+ const child = spawn(process.execPath, [runtime, ...args], {
675
+ stdio: "inherit",
676
+ env
677
+ });
678
+ const onSigint = () => {
679
+ try {
680
+ child.kill("SIGINT");
681
+ } catch {
682
+ }
683
+ };
684
+ process.on("SIGINT", onSigint);
685
+ child.on("exit", (code) => {
686
+ process.off("SIGINT", onSigint);
687
+ resolve2(code ?? 0);
688
+ });
689
+ child.on("error", (err) => {
690
+ process.off("SIGINT", onSigint);
691
+ process.stderr.write("\nrscot: agent runtime failed to start \u2014 " + err.message + "\n");
692
+ resolve2(1);
693
+ });
694
+ });
695
+ }
696
+
697
+ // src/repl.ts
698
+ var VERSION = "1.3.0";
525
699
  var BUILD = "a7f31c2";
700
+ function homeShort(p) {
701
+ const h = homedir4();
702
+ return p.startsWith(h) ? "~" + p.slice(h.length) : p;
703
+ }
704
+ function w(s) {
705
+ process.stdout.write(s);
706
+ }
707
+ function printReadyBanner(cfg, workspace, backendUrl2) {
708
+ w("\n" + brandHeader(VERSION, BUILD) + "\n\n");
709
+ w(
710
+ " " + palette.success("\u25CF") + " " + palette.dim("ready ") + palette.cyan(cfg.provider + "/" + cfg.model) + " " + palette.dim("\xB7 ") + palette.cmd(homeShort(workspace)) + "\n"
711
+ );
712
+ w(
713
+ " " + palette.dim(" backend ") + palette.cyan(backendUrl2) + "\n"
714
+ );
715
+ }
716
+ function printReplHelp() {
717
+ w("\n");
718
+ w(" " + palette.bold("REPL commands") + "\n");
719
+ w(" " + palette.cyan("<any text>") + palette.dim(" send as prompt to the agent") + "\n");
720
+ w(" " + palette.cyan("/help") + palette.dim(" show this help") + "\n");
721
+ w(" " + palette.cyan("/reset") + palette.dim(" start a fresh conversation (drop --resume)") + "\n");
722
+ w(" " + palette.cyan("/cd <dir>") + palette.dim(" change working directory") + "\n");
723
+ w(" " + palette.cyan("exit") + palette.dim(" | ") + palette.cyan("quit") + palette.dim(" | Ctrl+D leave REPL") + "\n");
724
+ w(" " + palette.dim("(during a turn, Ctrl+C forwards to agent and cancels it)") + "\n\n");
725
+ }
726
+ function promptLine(promptStr) {
727
+ return new Promise((resolve2) => {
728
+ const rl = readline.createInterface({
729
+ input: process.stdin,
730
+ output: process.stdout,
731
+ terminal: true,
732
+ historySize: 500
733
+ });
734
+ rl.setPrompt(promptStr);
735
+ rl.prompt();
736
+ let settled = false;
737
+ const done = (v) => {
738
+ if (settled)
739
+ return;
740
+ settled = true;
741
+ try {
742
+ rl.close();
743
+ } catch {
744
+ }
745
+ resolve2(v);
746
+ };
747
+ rl.once("line", (line) => done(line));
748
+ rl.once("close", () => done(null));
749
+ rl.on("SIGINT", () => done(null));
750
+ });
751
+ }
752
+ async function runOneShot(prompt, workspace, backendUrl2) {
753
+ const cfg = loadConfig();
754
+ if (!cfg) {
755
+ process.stderr.write(palette.error("rscot: not installed \u2014 run `rscot` first\n"));
756
+ return 1;
757
+ }
758
+ const pf = await preflightBackend(backendUrl2);
759
+ if (!pf.ok) {
760
+ process.stderr.write(
761
+ palette.error("rscot: backend unreachable at " + backendUrl2) + palette.dim(" (" + (pf.detail ?? "no detail") + ")\n") + palette.dim(" start it with `docker compose up -d` or set RSCOT_BACKEND_URL\n")
762
+ );
763
+ return 1;
764
+ }
765
+ return runAgent({ workspace, backendUrl: backendUrl2, prompt });
766
+ }
767
+ async function runRepl(workspace, backendUrl2) {
768
+ const cfg = loadConfig();
769
+ if (!cfg) {
770
+ process.stderr.write(palette.error("rscot: not installed \u2014 run `rscot` first\n"));
771
+ return 1;
772
+ }
773
+ const pf = await preflightBackend(backendUrl2);
774
+ printReadyBanner(cfg, workspace, backendUrl2);
775
+ if (!pf.ok) {
776
+ w(
777
+ " " + palette.error("\u2717") + palette.dim(" backend ") + palette.error("unreachable") + palette.dim(" (" + (pf.detail ?? "no detail") + ")\n")
778
+ );
779
+ w(palette.dim(" start it with `docker compose up -d` or set RSCOT_BACKEND_URL\n\n"));
780
+ } else {
781
+ w("\n");
782
+ }
783
+ w(" " + palette.dim("hint type a prompt \xB7 /help for commands \xB7 empty line or Ctrl+D quits\n\n"));
784
+ let currentWorkspace = workspace;
785
+ let turnCount = 0;
786
+ let resumeNext = false;
787
+ while (true) {
788
+ const cwdShort = homeShort(currentWorkspace);
789
+ const promptStr = palette.dim(cwdShort + " ") + palette.prompt("\u258C ");
790
+ const raw = await promptLine(promptStr);
791
+ if (raw === null)
792
+ break;
793
+ const line = raw.trim();
794
+ if (line === "" || line === "exit" || line === "quit")
795
+ break;
796
+ if (line === "/help") {
797
+ printReplHelp();
798
+ continue;
799
+ }
800
+ if (line === "/reset") {
801
+ resumeNext = false;
802
+ turnCount = 0;
803
+ w(palette.dim(" \xB7 conversation reset \xB7 next turn starts a fresh session\n\n"));
804
+ continue;
805
+ }
806
+ if (line.startsWith("/cd ")) {
807
+ const target = line.slice(4).trim();
808
+ try {
809
+ process.chdir(target);
810
+ currentWorkspace = process.cwd();
811
+ w(palette.dim(" \xB7 cwd = ") + palette.cyan(homeShort(currentWorkspace)) + "\n\n");
812
+ } catch (err) {
813
+ w(palette.error(" \xB7 cd failed: " + (err instanceof Error ? err.message : String(err)) + "\n\n"));
814
+ }
815
+ continue;
816
+ }
817
+ const rc = await runAgent({
818
+ workspace: currentWorkspace,
819
+ backendUrl: backendUrl2,
820
+ prompt: line,
821
+ resume: resumeNext
822
+ });
823
+ turnCount++;
824
+ resumeNext = true;
825
+ if (rc !== 0) {
826
+ w("\n" + palette.warn("!") + " agent exited with code " + rc + "\n");
827
+ }
828
+ w("\n");
829
+ }
830
+ w("\n" + palette.dim(" bye \xB7 ") + palette.dim(turnCount + " turn" + (turnCount === 1 ? "" : "s") + " this session\n\n"));
831
+ return 0;
832
+ }
833
+
834
+ // src/install.ts
835
+ var VERSION2 = "1.3.0";
836
+ var BUILD2 = "a7f31c2";
837
+ function backendUrl() {
838
+ return process.env.RSCOT_BACKEND_URL || "http://localhost:8010";
839
+ }
526
840
  function nl(n = 1) {
527
841
  process.stdout.write("\n".repeat(n));
528
842
  }
@@ -535,7 +849,7 @@ function sleep(ms) {
535
849
  async function runEnvironmentProbe() {
536
850
  println(palette.prompt("$") + " " + palette.cmd("npx rscot"));
537
851
  nl();
538
- println(brandHeader(VERSION, BUILD));
852
+ println(brandHeader(VERSION2, BUILD2));
539
853
  nl();
540
854
  println(sectionTitle("Checking your environment\u2026"));
541
855
  nl();
@@ -681,7 +995,7 @@ async function runInstallPhases(provider, apiKey) {
681
995
  );
682
996
  }
683
997
  nl();
684
- println(sectionTitle("Installing Rscot Agent v" + VERSION));
998
+ println(sectionTitle("Installing Rscot Agent v" + VERSION2));
685
999
  println(rule());
686
1000
  nl();
687
1001
  const phases = [
@@ -714,7 +1028,7 @@ async function runInstallPhases(provider, apiKey) {
714
1028
  model: provider.defaultModel,
715
1029
  apiKey: provider.needsKey ? apiKey : void 0,
716
1030
  apiKeyEnvVar: provider.envVar,
717
- version: VERSION
1031
+ version: VERSION2
718
1032
  });
719
1033
  const elapsedMs = Date.now() - startedAt;
720
1034
  const ss = Math.round(elapsedMs / 1e3);
@@ -731,7 +1045,7 @@ async function runInstallPhases(provider, apiKey) {
731
1045
  function printSuccess(provider) {
732
1046
  println(
733
1047
  successBanner([
734
- "Installed Rscot Agent v" + VERSION,
1048
+ "Installed Rscot Agent v" + VERSION2,
735
1049
  "Provider: " + provider.display + (provider.needsKey ? " (key stored)" : "")
736
1050
  ])
737
1051
  );
@@ -764,8 +1078,32 @@ function printSuccess(provider) {
764
1078
  );
765
1079
  nl();
766
1080
  }
1081
+ function existingKeyForConfig(cfg) {
1082
+ try {
1083
+ const kp = keyPath(cfg.provider);
1084
+ if (existsSync4(kp) && statSync2(kp).size > 8)
1085
+ return true;
1086
+ } catch {
1087
+ }
1088
+ const envVar = cfg.apiKeyEnvVar;
1089
+ if (envVar && (process.env[envVar] ?? "").length > 8)
1090
+ return true;
1091
+ return false;
1092
+ }
1093
+ async function runAlreadyInstalled(cfg, positional) {
1094
+ const workspace = process.cwd();
1095
+ const url = backendUrl();
1096
+ if (positional)
1097
+ return runOneShot(positional, workspace, url);
1098
+ return runRepl(workspace, url);
1099
+ }
767
1100
  async function runInstall(opts = {}) {
768
1101
  try {
1102
+ const existing = loadConfig();
1103
+ if (existing && existingKeyForConfig(existing)) {
1104
+ const positional2 = process.argv.slice(2).find((a) => !a.startsWith("-") && !["login", "logout", "whoami", "providers", "models", "keys", "config"].includes(a));
1105
+ return runAlreadyInstalled(existing, positional2 ?? null);
1106
+ }
769
1107
  const results = await runEnvironmentProbe();
770
1108
  printEnvironmentReport(results);
771
1109
  if (opts.noninteractive) {
@@ -803,7 +1141,12 @@ async function runInstall(opts = {}) {
803
1141
  }
804
1142
  await runInstallPhases(provider, apiKey);
805
1143
  printSuccess(provider);
806
- return 0;
1144
+ const positional = process.argv.slice(2).find((a) => !a.startsWith("-") && !["login", "logout", "whoami", "providers", "models", "keys", "config"].includes(a));
1145
+ const workspace = process.cwd();
1146
+ const url = backendUrl();
1147
+ if (positional)
1148
+ return runOneShot(positional, workspace, url);
1149
+ return runRepl(workspace, url);
807
1150
  } catch (err) {
808
1151
  println(palette.error("\n install failed: " + (err instanceof Error ? err.message : String(err))));
809
1152
  return 1;
@@ -811,22 +1154,22 @@ async function runInstall(opts = {}) {
811
1154
  }
812
1155
 
813
1156
  // src/commands/catalog.ts
814
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
815
- import { join as join3 } from "node:path";
1157
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
1158
+ import { join as join4 } from "node:path";
816
1159
  var CACHE_FILE = "providers.cache.json";
817
1160
  var TTL_HOURS = 24;
818
1161
  function cachePath() {
819
1162
  const dir = configRoot();
820
- if (!existsSync3(dir))
1163
+ if (!existsSync5(dir))
821
1164
  mkdirSync3(dir, { recursive: true, mode: 448 });
822
- return join3(dir, CACHE_FILE);
1165
+ return join4(dir, CACHE_FILE);
823
1166
  }
824
1167
  function readCache() {
825
1168
  try {
826
1169
  const p = cachePath();
827
- if (!existsSync3(p))
1170
+ if (!existsSync5(p))
828
1171
  return null;
829
- return JSON.parse(readFileSync2(p, "utf8"));
1172
+ return JSON.parse(readFileSync3(p, "utf8"));
830
1173
  } catch {
831
1174
  return null;
832
1175
  }
@@ -839,9 +1182,9 @@ function isFresh(c) {
839
1182
  return false;
840
1183
  return Date.now() - t < TTL_HOURS * 3600 * 1e3;
841
1184
  }
842
- async function fetchBackend(backendUrl) {
1185
+ async function fetchBackend(backendUrl2) {
843
1186
  try {
844
- const res = await fetch(`${backendUrl.replace(/\/+$/, "")}/api/providers`);
1187
+ const res = await fetch(`${backendUrl2.replace(/\/+$/, "")}/api/providers`);
845
1188
  if (!res.ok)
846
1189
  return null;
847
1190
  return await res.json();
@@ -871,13 +1214,13 @@ function fromBackend(p) {
871
1214
  };
872
1215
  }
873
1216
  async function loadCatalog(opts = {}) {
874
- const backendUrl = opts.backendUrl || process.env.RSCOT_BACKEND_URL || "https://learningfind.com/api";
1217
+ const backendUrl2 = opts.backendUrl || process.env.RSCOT_BACKEND_URL || "https://learningfind.com/api";
875
1218
  if (!opts.force) {
876
1219
  const cached2 = readCache();
877
1220
  if (isFresh(cached2))
878
1221
  return cached2.payload.providers.map(fromBackend);
879
1222
  }
880
- const payload = await fetchBackend(backendUrl);
1223
+ const payload = await fetchBackend(backendUrl2);
881
1224
  if (payload) {
882
1225
  try {
883
1226
  writeFileSync2(
@@ -896,16 +1239,16 @@ async function loadCatalog(opts = {}) {
896
1239
  }
897
1240
 
898
1241
  // src/commands/keys.ts
899
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, rmSync, readdirSync, chmodSync as chmodSync2 } from "node:fs";
900
- import { join as join4 } from "node:path";
1242
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, rmSync, readdirSync as readdirSync2, chmodSync as chmodSync2 } from "node:fs";
1243
+ import { join as join5 } from "node:path";
901
1244
  function keysDir() {
902
- const d = join4(configRoot(), "keys");
903
- if (!existsSync4(d))
1245
+ const d = join5(configRoot(), "keys");
1246
+ if (!existsSync6(d))
904
1247
  mkdirSync4(d, { recursive: true, mode: 448 });
905
1248
  return d;
906
1249
  }
907
1250
  function keyFile(providerId) {
908
- return join4(keysDir(), `${providerId}.key`);
1251
+ return join5(keysDir(), `${providerId}.key`);
909
1252
  }
910
1253
  function setKey(providerId, value) {
911
1254
  const path = keyFile(providerId);
@@ -918,17 +1261,17 @@ function setKey(providerId, value) {
918
1261
  }
919
1262
  function getKey(providerId) {
920
1263
  const path = keyFile(providerId);
921
- if (!existsSync4(path))
1264
+ if (!existsSync6(path))
922
1265
  return "";
923
1266
  try {
924
- return readFileSync3(path, "utf8").trim();
1267
+ return readFileSync4(path, "utf8").trim();
925
1268
  } catch {
926
1269
  return "";
927
1270
  }
928
1271
  }
929
1272
  function removeKey(providerId) {
930
1273
  const path = keyFile(providerId);
931
- if (!existsSync4(path))
1274
+ if (!existsSync6(path))
932
1275
  return false;
933
1276
  rmSync(path);
934
1277
  return true;
@@ -936,7 +1279,7 @@ function removeKey(providerId) {
936
1279
  function listKeys() {
937
1280
  const d = keysDir();
938
1281
  try {
939
- return readdirSync(d).filter((f) => f.endsWith(".key")).map((f) => f.replace(/\.key$/, "")).sort();
1282
+ return readdirSync2(d).filter((f) => f.endsWith(".key")).map((f) => f.replace(/\.key$/, "")).sort();
940
1283
  } catch {
941
1284
  return [];
942
1285
  }
@@ -953,12 +1296,12 @@ function maskKey(raw) {
953
1296
  return "***" + raw.slice(-2);
954
1297
  return raw.slice(0, 4) + "\u2022\u2022\u2022\u2022\u2022" + raw.slice(-4);
955
1298
  }
956
- function pad(s, w) {
957
- return s.length >= w ? s : s + " ".repeat(w - s.length);
1299
+ function pad(s, w2) {
1300
+ return s.length >= w2 ? s : s + " ".repeat(w2 - s.length);
958
1301
  }
959
1302
  async function readStdinLine(promptText) {
960
1303
  process.stdout.write(palette.prompt(promptText));
961
- return new Promise((resolve) => {
1304
+ return new Promise((resolve2) => {
962
1305
  const chunks = [];
963
1306
  const onData = (b) => {
964
1307
  chunks.push(b);
@@ -970,7 +1313,7 @@ async function readStdinLine(promptText) {
970
1313
  process.stdin.pause();
971
1314
  } catch {
972
1315
  }
973
- resolve(buf.slice(0, nl2).replace(/\r$/, ""));
1316
+ resolve2(buf.slice(0, nl2).replace(/\r$/, ""));
974
1317
  }
975
1318
  };
976
1319
  process.stdin.on("data", onData);
@@ -1177,11 +1520,11 @@ async function cmdKeysRemove(providerId) {
1177
1520
  }
1178
1521
 
1179
1522
  // src/auth.ts
1180
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, rmSync as rmSync2, chmodSync as chmodSync3 } from "node:fs";
1181
- import { join as join5 } from "node:path";
1523
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4, rmSync as rmSync2, chmodSync as chmodSync3 } from "node:fs";
1524
+ import { join as join6 } from "node:path";
1182
1525
  import * as crypto from "node:crypto";
1183
1526
  import * as http from "node:http";
1184
- import { spawn } from "node:child_process";
1527
+ import { spawn as spawn2 } from "node:child_process";
1185
1528
  var AUTH_BASE = "https://auth.learningfind.com";
1186
1529
  var PROFILE_URL = "https://learningfind.com/api/me/profile";
1187
1530
  var PORT_LO = 49152;
@@ -1191,27 +1534,27 @@ var LOOPBACK_TIMEOUT_MS = 18e4;
1191
1534
  var ACCESS_LEEWAY_S = 30;
1192
1535
  function authDir() {
1193
1536
  const d = configRoot();
1194
- if (!existsSync5(d))
1537
+ if (!existsSync7(d))
1195
1538
  mkdirSync5(d, { recursive: true, mode: 448 });
1196
1539
  return d;
1197
1540
  }
1198
1541
  function pathAccess() {
1199
- return join5(authDir(), "access");
1542
+ return join6(authDir(), "access");
1200
1543
  }
1201
1544
  function pathRefresh() {
1202
- return join5(authDir(), "refresh");
1545
+ return join6(authDir(), "refresh");
1203
1546
  }
1204
1547
  function pathAccessExp() {
1205
- return join5(authDir(), "access.exp");
1548
+ return join6(authDir(), "access.exp");
1206
1549
  }
1207
1550
  function pathDevice() {
1208
- return join5(authDir(), "device_id");
1551
+ return join6(authDir(), "device_id");
1209
1552
  }
1210
1553
  function pathProfile() {
1211
- return join5(authDir(), "profile.json");
1554
+ return join6(authDir(), "profile.json");
1212
1555
  }
1213
1556
  function pathLocalMode() {
1214
- return join5(authDir(), "local-mode");
1557
+ return join6(authDir(), "local-mode");
1215
1558
  }
1216
1559
  function writeSecret(path, value) {
1217
1560
  writeFileSync4(path, value, { mode: 384 });
@@ -1221,16 +1564,16 @@ function writeSecret(path, value) {
1221
1564
  }
1222
1565
  }
1223
1566
  function readMaybe(path) {
1224
- if (!existsSync5(path))
1567
+ if (!existsSync7(path))
1225
1568
  return null;
1226
1569
  try {
1227
- return readFileSync4(path, "utf8").trim();
1570
+ return readFileSync5(path, "utf8").trim();
1228
1571
  } catch {
1229
1572
  return null;
1230
1573
  }
1231
1574
  }
1232
1575
  function rmIf(path) {
1233
- if (existsSync5(path))
1576
+ if (existsSync7(path))
1234
1577
  try {
1235
1578
  rmSync2(path);
1236
1579
  } catch {
@@ -1269,7 +1612,7 @@ function getOrCreateDeviceId() {
1269
1612
  return id;
1270
1613
  }
1271
1614
  function isLocalMode() {
1272
- return existsSync5(pathLocalMode());
1615
+ return existsSync7(pathLocalMode());
1273
1616
  }
1274
1617
  function setLocalMode(on) {
1275
1618
  if (on)
@@ -1340,7 +1683,7 @@ async function getAccessToken() {
1340
1683
  }
1341
1684
  }
1342
1685
  function bindLoopback() {
1343
- return new Promise((resolve, reject) => {
1686
+ return new Promise((resolve2, reject) => {
1344
1687
  let attempts = 0;
1345
1688
  const tryBind = () => {
1346
1689
  if (attempts++ >= BIND_ATTEMPTS) {
@@ -1359,7 +1702,7 @@ function bindLoopback() {
1359
1702
  server.listen(port, "127.0.0.1", () => {
1360
1703
  const addr = server.address();
1361
1704
  server.removeAllListeners("error");
1362
- resolve({ server, port: addr.port });
1705
+ resolve2({ server, port: addr.port });
1363
1706
  });
1364
1707
  };
1365
1708
  tryBind();
@@ -1367,7 +1710,7 @@ function bindLoopback() {
1367
1710
  }
1368
1711
  var SUCCESS_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>Rscot Agent</title></head><body style="font-family:system-ui,sans-serif;padding:40px;color:#222"><h2>Sign-in complete</h2><p>You can close this window and return to your terminal.</p><script>setTimeout(function(){window.close();},250);</script></body></html>`;
1369
1712
  function waitForCallback(server, expectedState) {
1370
- return new Promise((resolve, reject) => {
1713
+ return new Promise((resolve2, reject) => {
1371
1714
  const timer = setTimeout(() => {
1372
1715
  try {
1373
1716
  server.close();
@@ -1375,8 +1718,8 @@ function waitForCallback(server, expectedState) {
1375
1718
  }
1376
1719
  reject(new Error("Sign-in timed out after 3 minutes."));
1377
1720
  }, LOOPBACK_TIMEOUT_MS);
1378
- server.on("request", (req, res) => {
1379
- const url = new URL(req.url || "/", "http://127.0.0.1");
1721
+ server.on("request", (req2, res) => {
1722
+ const url = new URL(req2.url || "/", "http://127.0.0.1");
1380
1723
  if (url.pathname !== "/cb") {
1381
1724
  res.statusCode = 404;
1382
1725
  res.end("not found");
@@ -1399,7 +1742,7 @@ function waitForCallback(server, expectedState) {
1399
1742
  return reject(new Error("Auth callback missing code/state."));
1400
1743
  if (state !== expectedState)
1401
1744
  return reject(new Error("Auth callback state mismatch."));
1402
- resolve(code);
1745
+ resolve2(code);
1403
1746
  });
1404
1747
  });
1405
1748
  }
@@ -1435,7 +1778,7 @@ function openBrowser(url) {
1435
1778
  args = [url];
1436
1779
  }
1437
1780
  try {
1438
- const child = spawn(cmd, args, { detached: true, stdio: "ignore", windowsHide: true });
1781
+ const child = spawn2(cmd, args, { detached: true, stdio: "ignore", windowsHide: true });
1439
1782
  child.unref();
1440
1783
  } catch {
1441
1784
  }
@@ -1455,13 +1798,17 @@ async function signIn() {
1455
1798
  client: "cli"
1456
1799
  });
1457
1800
  const authUrl = `${AUTH_BASE}/auth/desktop/start?${params.toString()}`;
1458
- process.stdout.write(`
1459
- Opening browser to: ${authUrl}
1801
+ const verbose = process.env.RSCOT_VERBOSE === "1";
1802
+ process.stdout.write("\nOpened your browser to sign in.\n");
1803
+ process.stdout.write("Complete the flow there, then return to this terminal.\n");
1804
+ process.stdout.write("Waiting (up to 3 minutes)\u2026\n\n");
1805
+ if (verbose) {
1806
+ process.stdout.write(`[verbose] auth url: ${authUrl}
1460
1807
  `);
1461
- process.stdout.write("If the browser does not open, paste that URL manually.\n");
1462
- process.stdout.write(`Waiting for callback on http://127.0.0.1:${port}/cb (up to 3 minutes)\u2026
1808
+ process.stdout.write(`[verbose] loopback: 127.0.0.1:${port}
1463
1809
 
1464
1810
  `);
1811
+ }
1465
1812
  openBrowser(authUrl);
1466
1813
  const code = await waitForCallback(server, state);
1467
1814
  const bundle = await exchangeCode(code, verifier, deviceId);
@@ -1514,7 +1861,7 @@ async function refreshProfile() {
1514
1861
  if (process.stdout.isTTY || process.env.FORCE_COLOR) {
1515
1862
  kleur2.enabled = true;
1516
1863
  }
1517
- var VERSION2 = "1.2.0";
1864
+ var VERSION3 = "1.7.47";
1518
1865
  var AUTH_FREE_COMMANDS = /* @__PURE__ */ new Set([
1519
1866
  "login",
1520
1867
  "logout",
@@ -1718,7 +2065,7 @@ async function dispatch(argv) {
1718
2065
  async function main() {
1719
2066
  const argv = process.argv.slice(2);
1720
2067
  if (argv.includes("--version") || argv.includes("-v")) {
1721
- process.stdout.write(VERSION2 + "\n");
2068
+ process.stdout.write(VERSION3 + "\n");
1722
2069
  return;
1723
2070
  }
1724
2071
  if (argv.includes("--help") || argv.includes("-h")) {