camstack 0.7.0 → 0.7.2

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.
Files changed (2) hide show
  1. package/dist/cli.js +102 -15
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -376,11 +376,23 @@ function resolveSessionFile(serverUrl) {
376
376
  if (files.length === 0) return null;
377
377
  return files.map((f) => ({ f, mtime: fs3.statSync(f).mtimeMs })).sort((a, b) => b.mtime - a.mtime)[0].f;
378
378
  }
379
+ function isSessionFile(value) {
380
+ if (value === null || typeof value !== "object") return false;
381
+ const v = value;
382
+ if (typeof v.server !== "string") return false;
383
+ if (typeof v.username !== "string") return false;
384
+ if (typeof v.token !== "string") return false;
385
+ if (typeof v.tokenId !== "string") return false;
386
+ if (typeof v.createdAt !== "number") return false;
387
+ if (!Array.isArray(v.scopes)) return false;
388
+ return true;
389
+ }
379
390
  function loadSession(serverUrl) {
380
391
  const file = resolveSessionFile(serverUrl);
381
392
  if (!file) return null;
382
393
  try {
383
- return JSON.parse(fs3.readFileSync(file, "utf8"));
394
+ const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
395
+ return isSessionFile(parsed) ? parsed : null;
384
396
  } catch {
385
397
  return null;
386
398
  }
@@ -526,20 +538,55 @@ async function resolveServerInteractive(presetNamespace) {
526
538
  probeSpinner.stop(`Reachable at https://${probed.address}:${probed.port}`);
527
539
  return `https://${probed.address}:${probed.port}`;
528
540
  }
541
+ var BACK_SENTINEL = "__back__";
542
+ async function askWithBack(label, defaultValue) {
543
+ const v = await askText(`${label} (or type :back)`, defaultValue);
544
+ return v.trim() === ":back" || v.trim() === ":b" ? BACK_SENTINEL : v;
545
+ }
546
+ async function askPasswordWithBack() {
547
+ const v = await askPassword("Password (or type :back to change username)");
548
+ return v.trim() === ":back" || v.trim() === ":b" ? BACK_SENTINEL : v;
549
+ }
529
550
  async function loginCommand(opts) {
530
551
  clack.intro("camstack login");
531
- let server = opts.server;
532
- if (!server) {
533
- server = await resolveServerInteractive(opts.namespace);
534
- } else {
535
- clack.log.info(`Server: ${server}`);
552
+ let step = opts.server ? "username" : "server";
553
+ let server = opts.server ?? "";
554
+ let username = opts.username ?? "";
555
+ let password2 = opts.password ?? "";
556
+ if (opts.server) clack.log.info(`Server: ${opts.server}`);
557
+ while (step !== "password" || !password2) {
558
+ if (step === "server") {
559
+ server = await resolveServerInteractive(opts.namespace);
560
+ step = "username";
561
+ continue;
562
+ }
563
+ if (step === "username") {
564
+ const result2 = opts.username && !username ? opts.username : await askWithBack("Username", username || "admin");
565
+ if (result2 === BACK_SENTINEL) {
566
+ if (opts.server) {
567
+ clack.log.warn("Server was passed via --server; nothing to go back to. Re-enter username.");
568
+ continue;
569
+ }
570
+ step = "server";
571
+ continue;
572
+ }
573
+ username = result2;
574
+ step = "password";
575
+ continue;
576
+ }
577
+ const result = opts.password && !password2 ? opts.password : await askPasswordWithBack();
578
+ if (result === BACK_SENTINEL) {
579
+ step = "username";
580
+ continue;
581
+ }
582
+ password2 = result;
583
+ break;
536
584
  }
537
- const username = opts.username ?? await askText("Username", "admin");
538
585
  const MAX_ATTEMPTS = 3;
539
- let password2 = opts.password ?? await askPassword("Password");
540
586
  let attempt = 1;
541
587
  let jwt;
542
588
  let displayName;
589
+ let userId;
543
590
  while (true) {
544
591
  const authSpinner = clack.spinner();
545
592
  authSpinner.start(`Authenticating as ${username}${attempt > 1 ? ` (attempt ${attempt}/${MAX_ATTEMPTS})` : ""}`);
@@ -552,6 +599,7 @@ async function loginCommand(opts) {
552
599
  );
553
600
  jwt = login.token;
554
601
  displayName = login.user.username;
602
+ userId = login.user.id;
555
603
  authSpinner.stop(`Authenticated as ${displayName}`);
556
604
  break;
557
605
  } catch (err) {
@@ -578,13 +626,18 @@ async function loginCommand(opts) {
578
626
  }
579
627
  }
580
628
  const mintSpinner = clack.spinner();
581
- mintSpinner.start("Creating upload-only scoped token");
629
+ mintSpinner.start("Creating scoped token (upload + log streaming)");
582
630
  const tokenName = opts.tokenName ?? `camstack-cli@${os2.hostname()}`;
583
- const scopes = [{ type: "route-prefix", target: "/api/addons/upload" }];
631
+ const scopes = [
632
+ { type: "route-prefix", target: "/api/addons/upload" },
633
+ { type: "route-prefix", target: "/trpc/addons.onAddonLogs" },
634
+ { type: "route-prefix", target: "/trpc/addons.getLogs" },
635
+ { type: "route-prefix", target: "/trpc/addons.list" }
636
+ ];
584
637
  const created = await callTrpcMutation(
585
638
  `${server}/trpc/userManagement.createScopedToken?batch=1`,
586
639
  `Bearer ${jwt}`,
587
- { name: tokenName, scopes },
640
+ { userId, name: tokenName, scopes },
588
641
  isCreateScopedTokenPayload
589
642
  );
590
643
  const scopedToken = created.token;
@@ -626,18 +679,52 @@ async function logoutCommand(opts) {
626
679
  clearSession(session.server);
627
680
  clack.outro(`\u2713 Logged out of ${session.server}`);
628
681
  }
629
- function whoamiCommand(opts) {
682
+ async function whoamiCommand(opts) {
630
683
  const session = loadSession(opts.server);
631
684
  if (!session) {
632
685
  console.log(`[camstack] Not logged in${opts.server ? ` to ${opts.server}` : ""}.`);
633
686
  process.exit(1);
634
687
  }
635
- console.log(`[camstack] Active session:`);
688
+ console.log(`[camstack] Active session (local cache):`);
636
689
  console.log(` server: ${session.server}`);
637
690
  console.log(` username: ${session.username}`);
638
691
  console.log(` token: ${session.token.slice(0, 12)}\u2026 (id: ${session.tokenId})`);
639
692
  console.log(` scopes: ${session.scopes.map((s) => `${s.type}:${s.target}`).join(", ")}`);
640
693
  console.log(` createdAt: ${new Date(session.createdAt).toISOString()}`);
694
+ console.log("");
695
+ console.log(`[camstack] Pinging ${session.server}/api/auth/whoami\u2026`);
696
+ try {
697
+ const controller = new AbortController();
698
+ const timer = setTimeout(() => controller.abort(), 3e3);
699
+ const res = await fetch(`${session.server}/api/auth/whoami`, {
700
+ headers: { Authorization: `Bearer ${session.token}` },
701
+ signal: controller.signal
702
+ });
703
+ clearTimeout(timer);
704
+ if (res.status === 401) {
705
+ console.error(`[camstack] \u2717 Token rejected by server (revoked / expired). Run \`camstack login\` to refresh.`);
706
+ process.exit(2);
707
+ }
708
+ if (!res.ok) {
709
+ const body = await res.text().catch(() => "");
710
+ console.error(`[camstack] \u2717 Server returned ${res.status}: ${body.slice(0, 160)}`);
711
+ process.exit(2);
712
+ }
713
+ const json = await res.json();
714
+ console.log(`[camstack] \u2713 Token valid on server.`);
715
+ console.log(` kind: ${json.kind ?? "unknown"}`);
716
+ if (json.userId) console.log(` userId: ${json.userId}`);
717
+ if (json.username) console.log(` username: ${json.username}`);
718
+ if (Array.isArray(json.scopes)) {
719
+ const scopeList = json.scopes.map((s) => `${s.type}:${s.target}`).join(", ");
720
+ console.log(` scopes: ${scopeList}`);
721
+ }
722
+ } catch (err) {
723
+ const msg = err instanceof Error ? err.message : String(err);
724
+ console.error(`[camstack] \u26A0 Could not reach server: ${msg}`);
725
+ console.error(`[camstack] The local cache may be stale. Try \`camstack login\` once the server is reachable.`);
726
+ process.exit(2);
727
+ }
641
728
  }
642
729
 
643
730
  // src/commands/update.ts
@@ -1234,7 +1321,7 @@ async function runLogout(args) {
1234
1321
  const server = stringOpt(parsed.values, "server") ?? process.env.CAMSTACK_SERVER ?? "https://localhost:4443";
1235
1322
  await logoutCommand({ server });
1236
1323
  }
1237
- function runWhoami(args) {
1324
+ async function runWhoami(args) {
1238
1325
  const parsed = parseSubcommandArgs(args, {
1239
1326
  server: { type: "string", short: "s" }
1240
1327
  }, false);
@@ -1243,7 +1330,7 @@ function runWhoami(args) {
1243
1330
  return;
1244
1331
  }
1245
1332
  const server = stringOpt(parsed.values, "server");
1246
- whoamiCommand(server ? { server } : {});
1333
+ await whoamiCommand(server ? { server } : {});
1247
1334
  }
1248
1335
  async function runDeploy(args) {
1249
1336
  const parsed = parseSubcommandArgs(args, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",