camstack 1.2.180 → 1.2.181

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/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  FFMPEG_PATH_ENV,
4
4
  runDiscover
5
- } from "./chunk-5IZ6XS4J.js";
5
+ } from "./chunk-4MQR56VQ.js";
6
6
  import "./chunk-LMMQX4CK.js";
7
7
 
8
8
  // src/cli.ts
@@ -39,7 +39,7 @@ async function runServe(args) {
39
39
  ...typeof values.data === "string" ? { data: values.data } : {}
40
40
  };
41
41
  Object.assign(process.env, buildServeEnv(opts));
42
- await import("./launcher-QBPVDG54.js");
42
+ await import("./launcher-TUOJ3PCS.js");
43
43
  }
44
44
 
45
45
  // src/commands/agent.ts
@@ -84,7 +84,7 @@ async function runAgent(args) {
84
84
  ...typeof values.port === "string" ? { port: values.port } : {}
85
85
  };
86
86
  Object.assign(process.env, buildAgentEnv(opts));
87
- await import("./launcher-QBPVDG54.js");
87
+ await import("./launcher-TUOJ3PCS.js");
88
88
  }
89
89
 
90
90
  // src/commands/setup.ts
@@ -136,7 +136,7 @@ function runSetup(args) {
136
136
  // src/commands/deploy.ts
137
137
  import { execSync } from "child_process";
138
138
  import { randomBytes } from "crypto";
139
- import * as fs2 from "fs";
139
+ import * as fs3 from "fs";
140
140
  import * as path2 from "path";
141
141
 
142
142
  // src/http/hub-http.ts
@@ -187,6 +187,95 @@ function hubRequest(url, opts = {}) {
187
187
  });
188
188
  }
189
189
 
190
+ // src/commands/credentials.ts
191
+ import * as fs2 from "fs";
192
+ var MissingCredentialsError = class extends Error {
193
+ constructor(server) {
194
+ super(
195
+ [
196
+ `Not logged in to ${server ?? "any hub"}.`,
197
+ " Run `camstack login`: it caches a token in ~/.camstack/<host>.json that every",
198
+ " command reuses automatically. For scripts/CI, pass --token-file <path> with a",
199
+ " token issued on Identity \u2192 Tokens (or a copied ~/.camstack session file)."
200
+ ].join("\n")
201
+ );
202
+ this.name = "MissingCredentialsError";
203
+ }
204
+ };
205
+ var GROUP_OR_OTHER_BITS = 63;
206
+ function readTokenFile(filePath) {
207
+ let raw;
208
+ let mode;
209
+ try {
210
+ raw = fs2.readFileSync(filePath, "utf8");
211
+ mode = fs2.statSync(filePath).mode;
212
+ } catch (err) {
213
+ const reason = err instanceof Error ? err.message : String(err);
214
+ throw new Error(`Cannot read token file ${filePath}: ${reason}`, { cause: err });
215
+ }
216
+ const warning = process.platform !== "win32" && (mode & GROUP_OR_OTHER_BITS) !== 0 ? `Token file ${filePath} is readable by other users \u2014 run \`chmod 600 ${filePath}\`.` : void 0;
217
+ const parsed = parseTokenFile(raw.trim(), filePath);
218
+ return warning ? { ...parsed, warning } : parsed;
219
+ }
220
+ function parseTokenFile(content, filePath) {
221
+ if (content.length === 0) throw new Error(`Token file ${filePath} is empty.`);
222
+ if (content.startsWith("{")) return parseSessionJson(content, filePath);
223
+ if (/\s/.test(content)) {
224
+ throw new Error(`Token file ${filePath} must hold exactly one token (found several lines).`);
225
+ }
226
+ return { token: content };
227
+ }
228
+ function parseSessionJson(content, filePath) {
229
+ let json;
230
+ try {
231
+ json = JSON.parse(content);
232
+ } catch {
233
+ throw new Error(`Token file ${filePath} looks like JSON but does not parse.`);
234
+ }
235
+ if (json === null || typeof json !== "object") {
236
+ throw new Error(`Token file ${filePath}: expected a JSON object with a "token" field.`);
237
+ }
238
+ const token = Reflect.get(json, "token");
239
+ const server = Reflect.get(json, "server");
240
+ if (typeof token !== "string" || token.length === 0) {
241
+ throw new Error(`Token file ${filePath}: the JSON has no string "token" field.`);
242
+ }
243
+ return typeof server === "string" && server.length > 0 ? { token, server } : { token };
244
+ }
245
+ function resolveCredentials(inputs) {
246
+ const env = inputs.env ?? process.env;
247
+ const file = inputs.tokenFile ? readTokenFile(inputs.tokenFile) : null;
248
+ const targeted = inputs.server ?? env["CAMSTACK_SERVER"] ?? file?.server;
249
+ const session = inputs.loadSession(targeted);
250
+ const server = targeted ?? session?.server;
251
+ const picked = pickToken(inputs.token, file, env["CAMSTACK_TOKEN"], session);
252
+ if (!picked) throw new MissingCredentialsError(server);
253
+ const warning = file?.warning;
254
+ return warning ? { server, ...picked, warning } : { server, ...picked };
255
+ }
256
+ function pickToken(flag, file, envToken, session) {
257
+ if (flag) return { token: flag, source: "flag" };
258
+ if (file) return { token: file.token, source: "token-file" };
259
+ if (envToken) return { token: envToken, source: "env" };
260
+ if (session) return { token: session.token, source: "session" };
261
+ return null;
262
+ }
263
+ var REJECTION_ADVICE = {
264
+ session: "The hub rejected the cached session token (revoked or expired). Run `camstack login` again.",
265
+ "token-file": "The hub rejected the token from the token file (revoked, expired or lacking scope) \u2014 ask the operator who issued it for a new one.",
266
+ env: "The hub rejected $CAMSTACK_TOKEN (revoked, expired or lacking scope).",
267
+ flag: "The hub rejected the --token value (revoked, expired or lacking scope)."
268
+ };
269
+ function authRejectedHint(status, source) {
270
+ if (status !== 401 && status !== 403) return null;
271
+ return REJECTION_ADVICE[source];
272
+ }
273
+ function withAuthHint(message, status, source) {
274
+ const hint = source ? authRejectedHint(status, source) : null;
275
+ return hint ? `${message}
276
+ ${hint}` : message;
277
+ }
278
+
190
279
  // src/commands/deploy.ts
191
280
  function isRecord(value) {
192
281
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -283,12 +372,12 @@ function toPackFilenames(raw) {
283
372
  }
284
373
  function resolveAddonPath(arg) {
285
374
  const absolute = path2.resolve(arg);
286
- if (fs2.existsSync(absolute)) return absolute;
375
+ if (fs3.existsSync(absolute)) return absolute;
287
376
  for (const candidate of [
288
377
  path2.resolve("packages", `addon-${arg}`),
289
378
  path2.resolve("packages", arg)
290
379
  ]) {
291
- if (fs2.existsSync(candidate) && fs2.existsSync(path2.join(candidate, "package.json"))) {
380
+ if (fs3.existsSync(candidate) && fs3.existsSync(path2.join(candidate, "package.json"))) {
292
381
  return candidate;
293
382
  }
294
383
  }
@@ -305,7 +394,7 @@ async function resolveAddonPathInteractive(arg) {
305
394
  return resolved;
306
395
  }
307
396
  const cwdAbs = path2.resolve(".");
308
- if (fs2.existsSync(path2.join(cwdAbs, "package.json"))) {
397
+ if (fs3.existsSync(path2.join(cwdAbs, "package.json"))) {
309
398
  const { discoverAddonsInCwd: discoverAddonsInCwd2 } = await import("./discover-addons-3N32LTH7.js");
310
399
  const direct = discoverAddonsInCwd2(cwdAbs, 0);
311
400
  if (direct.length > 0 && direct[0].path === cwdAbs) {
@@ -343,9 +432,9 @@ async function resolveAddonPathInteractive(arg) {
343
432
  }
344
433
  function readBuildScript(addonDir, scriptName) {
345
434
  const pkgJsonPath = path2.join(addonDir, "package.json");
346
- if (!fs2.existsSync(pkgJsonPath)) return null;
435
+ if (!fs3.existsSync(pkgJsonPath)) return null;
347
436
  try {
348
- const parsed = JSON.parse(fs2.readFileSync(pkgJsonPath, "utf8"));
437
+ const parsed = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf8"));
349
438
  if (!parsed || typeof parsed !== "object") return null;
350
439
  const scripts = parsed.scripts;
351
440
  if (!scripts || typeof scripts !== "object") return null;
@@ -370,7 +459,7 @@ function buildAddon(addonDir, scriptName) {
370
459
  }
371
460
  function packAddon(addonDir) {
372
461
  const pkgJsonPath = path2.join(addonDir, "package.json");
373
- if (!fs2.existsSync(pkgJsonPath)) {
462
+ if (!fs3.existsSync(pkgJsonPath)) {
374
463
  throw new Error(`No package.json found in: ${addonDir}`);
375
464
  }
376
465
  console.log(`[camstack] Packing addon from ${addonDir}...`);
@@ -389,7 +478,7 @@ function packAddon(addonDir) {
389
478
  }
390
479
  if (!tgzFilename) throw new Error("npm pack did not produce a .tgz file");
391
480
  const tgzPath = path2.join(addonDir, tgzFilename);
392
- if (!fs2.existsSync(tgzPath)) {
481
+ if (!fs3.existsSync(tgzPath)) {
393
482
  throw new Error(`Expected tgz file not found: ${tgzPath}`);
394
483
  }
395
484
  console.log(`[camstack] Packed: ${tgzFilename}`);
@@ -397,7 +486,7 @@ function packAddon(addonDir) {
397
486
  tgzPath,
398
487
  cleanup: () => {
399
488
  try {
400
- if (fs2.existsSync(tgzPath)) fs2.unlinkSync(tgzPath);
489
+ if (fs3.existsSync(tgzPath)) fs3.unlinkSync(tgzPath);
401
490
  } catch {
402
491
  }
403
492
  }
@@ -427,7 +516,7 @@ async function fetchOnlineAgents(serverUrl, token) {
427
516
  }
428
517
  async function uploadToTarget(args) {
429
518
  const { serverUrl, token, tgzPath, nodeId, addonId } = args;
430
- const fileBuffer = fs2.readFileSync(tgzPath);
519
+ const fileBuffer = fs3.readFileSync(tgzPath);
431
520
  const boundary = `----camstackcli${randomBytes(16).toString("hex")}`;
432
521
  const CRLF = "\r\n";
433
522
  const parts = [];
@@ -464,17 +553,21 @@ async function uploadToTarget(args) {
464
553
  if (!response.ok) {
465
554
  return {
466
555
  success: false,
467
- error: result.error ?? `Server returned ${response.status} ${response.statusText}`
556
+ error: withAuthHint(
557
+ result.error ?? `Server returned ${response.status} ${response.statusText}`,
558
+ response.status,
559
+ args.authSource
560
+ )
468
561
  };
469
562
  }
470
563
  return result;
471
564
  }
472
565
  async function deployAddon(addonPath, opts) {
473
- const resolvedPath = path2.isAbsolute(addonPath) && fs2.existsSync(addonPath) ? addonPath : await resolveAddonPathInteractive(addonPath);
566
+ const resolvedPath = path2.isAbsolute(addonPath) && fs3.existsSync(addonPath) ? addonPath : await resolveAddonPathInteractive(addonPath);
474
567
  let tgzPath;
475
568
  let cleanup = null;
476
569
  if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
477
- if (!fs2.existsSync(resolvedPath)) throw new Error(`File not found: ${resolvedPath}`);
570
+ if (!fs3.existsSync(resolvedPath)) throw new Error(`File not found: ${resolvedPath}`);
478
571
  tgzPath = resolvedPath;
479
572
  } else {
480
573
  const buildMode = opts.buildMode ?? "auto";
@@ -531,7 +624,8 @@ async function deployAddon(addonPath, opts) {
531
624
  serverUrl: opts.serverUrl,
532
625
  token: opts.token,
533
626
  tgzPath,
534
- ...t.nodeId ? { nodeId: t.nodeId } : {}
627
+ ...t.nodeId ? { nodeId: t.nodeId } : {},
628
+ ...opts.authSource ? { authSource: opts.authSource } : {}
535
629
  }),
536
630
  {
537
631
  attempts: 5,
@@ -591,12 +685,12 @@ async function deployAddon(addonPath, opts) {
591
685
  // src/commands/deploy-server.ts
592
686
  import { execSync as execSync2 } from "child_process";
593
687
  import { randomBytes as randomBytes2 } from "crypto";
594
- import * as fs4 from "fs";
688
+ import * as fs5 from "fs";
595
689
  import * as os2 from "os";
596
690
  import * as path4 from "path";
597
691
 
598
692
  // src/commands/server-closure.ts
599
- import * as fs3 from "fs";
693
+ import * as fs4 from "fs";
600
694
  import * as path3 from "path";
601
695
  var CAMSTACK_SCOPE_PREFIX = "@camstack/";
602
696
  function computeDevVersion(base, epochSeconds) {
@@ -605,7 +699,7 @@ function computeDevVersion(base, epochSeconds) {
605
699
  function readPackageManifest(dir) {
606
700
  const pkgJsonPath = path3.join(dir, "package.json");
607
701
  try {
608
- const parsed = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
702
+ const parsed = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
609
703
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
610
704
  const raw = parsed;
611
705
  if (typeof raw["name"] !== "string") return null;
@@ -631,7 +725,7 @@ function loadWorkspacePackages(workspaceRoot) {
631
725
  const base = path3.join(workspaceRoot, pattern.slice(0, -2));
632
726
  let entries;
633
727
  try {
634
- entries = fs3.readdirSync(base, { withFileTypes: true });
728
+ entries = fs4.readdirSync(base, { withFileTypes: true });
635
729
  } catch {
636
730
  continue;
637
731
  }
@@ -705,7 +799,7 @@ function discoverServerClosure(workspaceRoot) {
705
799
  }
706
800
  function withDevVersionPackageJson(pkgDir, devVersion, intraSetNames, fn) {
707
801
  const pkgJsonPath = path3.join(pkgDir, "package.json");
708
- const originalBytes = fs3.readFileSync(pkgJsonPath);
802
+ const originalBytes = fs4.readFileSync(pkgJsonPath);
709
803
  const parsed = JSON.parse(originalBytes.toString("utf-8"));
710
804
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
711
805
  throw new Error(`Malformed package.json: ${pkgJsonPath}`);
@@ -723,12 +817,12 @@ function withDevVersionPackageJson(pkgDir, devVersion, intraSetNames, fn) {
723
817
  }
724
818
  manifest[field] = rewritten;
725
819
  }
726
- fs3.writeFileSync(pkgJsonPath, `${JSON.stringify(manifest, null, 2)}
820
+ fs4.writeFileSync(pkgJsonPath, `${JSON.stringify(manifest, null, 2)}
727
821
  `, "utf-8");
728
822
  try {
729
823
  return fn();
730
824
  } finally {
731
- fs3.writeFileSync(pkgJsonPath, originalBytes);
825
+ fs4.writeFileSync(pkgJsonPath, originalBytes);
732
826
  }
733
827
  }
734
828
 
@@ -751,7 +845,7 @@ function hasBuildScript(pkgDir) {
751
845
  }
752
846
  function readJson(filePath) {
753
847
  try {
754
- return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
848
+ return JSON.parse(fs5.readFileSync(filePath, "utf-8"));
755
849
  } catch {
756
850
  return null;
757
851
  }
@@ -799,7 +893,7 @@ function packPackage(pkg, devVersion, intraSetNames, outDir) {
799
893
  throw new Error(`npm pack produced no tarball for ${pkg.name}`);
800
894
  }
801
895
  const tgzPath = path4.join(outDir, path4.basename(filename));
802
- if (!fs4.existsSync(tgzPath)) {
896
+ if (!fs5.existsSync(tgzPath)) {
803
897
  throw new Error(`Expected tarball not found after pack: ${tgzPath}`);
804
898
  }
805
899
  return { name: pkg.name, tgzPath };
@@ -817,7 +911,7 @@ async function uploadClosure(args) {
817
911
  Buffer.from(
818
912
  `--${boundary}${CRLF}Content-Disposition: form-data; name="file"; filename="${path4.basename(tarball.tgzPath)}"${CRLF}Content-Type: application/gzip${CRLF}${CRLF}`
819
913
  ),
820
- fs4.readFileSync(tarball.tgzPath),
914
+ fs5.readFileSync(tarball.tgzPath),
821
915
  Buffer.from(CRLF)
822
916
  );
823
917
  }
@@ -836,7 +930,9 @@ async function uploadClosure(args) {
836
930
  const payload = await response.json().catch(() => ({}));
837
931
  if (!response.ok || !isRecord2(payload) || payload["success"] !== true) {
838
932
  const detail = isRecord2(payload) && typeof payload["error"] === "string" ? payload["error"] : `${response.status} ${response.statusText}`;
839
- throw new Error(`Server upload rejected: ${detail}`);
933
+ throw new Error(
934
+ withAuthHint(`Server upload rejected: ${detail}`, response.status, args.authSource)
935
+ );
840
936
  }
841
937
  }
842
938
  function toApplyResult(value) {
@@ -872,7 +968,13 @@ async function applyServerUpdate(args) {
872
968
  );
873
969
  const body = await res.json().catch(() => null);
874
970
  if (!res.ok) {
875
- throw new Error(`applyServerUpdate returned ${res.status} ${res.statusText}`);
971
+ throw new Error(
972
+ withAuthHint(
973
+ `applyServerUpdate returned ${res.status} ${res.statusText}`,
974
+ res.status,
975
+ args.authSource
976
+ )
977
+ );
876
978
  }
877
979
  const result = toApplyResult(unwrapTrpcJson(body));
878
980
  if (result === null) {
@@ -950,7 +1052,7 @@ async function deployServer(opts) {
950
1052
  console.log(`[camstack] Building ${closure.buildSet.length} packages (dependency order)...`);
951
1053
  for (const pkg of closure.buildSet) buildPackage(pkg);
952
1054
  const intraSetNames = new Set(closure.packSet.map((p) => p.name));
953
- const outDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "camstack-deploy-server-"));
1055
+ const outDir = fs5.mkdtempSync(path4.join(os2.tmpdir(), "camstack-deploy-server-"));
954
1056
  try {
955
1057
  console.log(`[camstack] Packing ${closure.packSet.length} packages at ${devVersion}...`);
956
1058
  const tarballs = [];
@@ -964,14 +1066,16 @@ async function deployServer(opts) {
964
1066
  serverUrl: opts.serverUrl,
965
1067
  token: opts.token,
966
1068
  version: devVersion,
967
- tarballs
1069
+ tarballs,
1070
+ ...opts.authSource ? { authSource: opts.authSource } : {}
968
1071
  });
969
1072
  console.log("[camstack] \u2713 Upload accepted");
970
1073
  console.log(`[camstack] Applying server update to ${devVersion}...`);
971
1074
  const apply = await applyServerUpdate({
972
1075
  serverUrl: opts.serverUrl,
973
1076
  token: opts.token,
974
- version: devVersion
1077
+ version: devVersion,
1078
+ ...opts.authSource ? { authSource: opts.authSource } : {}
975
1079
  });
976
1080
  if (!apply.accepted) {
977
1081
  console.error(`[camstack] \u2717 applyServerUpdate refused: ${apply.message}`);
@@ -986,12 +1090,12 @@ async function deployServer(opts) {
986
1090
  await pollUntilPromoted({ serverUrl: opts.serverUrl, token: opts.token, version: devVersion });
987
1091
  console.log(`[camstack] \u2713 ${devVersion} promoted \u2014 the hub is running the dev closure`);
988
1092
  } finally {
989
- fs4.rmSync(outDir, { recursive: true, force: true });
1093
+ fs5.rmSync(outDir, { recursive: true, force: true });
990
1094
  }
991
1095
  }
992
1096
 
993
1097
  // src/commands/auth.ts
994
- import * as fs5 from "fs";
1098
+ import * as fs6 from "fs";
995
1099
  import * as path5 from "path";
996
1100
  import * as os3 from "os";
997
1101
  import * as clack from "@clack/prompts";
@@ -1003,12 +1107,12 @@ function sessionFileForServer(serverUrl) {
1003
1107
  function resolveSessionFile(serverUrl) {
1004
1108
  if (serverUrl) {
1005
1109
  const p = sessionFileForServer(serverUrl);
1006
- return fs5.existsSync(p) ? p : null;
1110
+ return fs6.existsSync(p) ? p : null;
1007
1111
  }
1008
- if (!fs5.existsSync(SESSION_DIR)) return null;
1009
- const files = fs5.readdirSync(SESSION_DIR).filter((f) => f.endsWith(".json")).map((f) => path5.join(SESSION_DIR, f));
1112
+ if (!fs6.existsSync(SESSION_DIR)) return null;
1113
+ const files = fs6.readdirSync(SESSION_DIR).filter((f) => f.endsWith(".json")).map((f) => path5.join(SESSION_DIR, f));
1010
1114
  if (files.length === 0) return null;
1011
- return files.map((f) => ({ f, mtime: fs5.statSync(f).mtimeMs })).toSorted((a, b) => b.mtime - a.mtime)[0].f;
1115
+ return files.map((f) => ({ f, mtime: fs6.statSync(f).mtimeMs })).toSorted((a, b) => b.mtime - a.mtime)[0].f;
1012
1116
  }
1013
1117
  function isSessionFile(value) {
1014
1118
  if (value === null || typeof value !== "object") return false;
@@ -1025,24 +1129,24 @@ function loadSession(serverUrl) {
1025
1129
  const file = resolveSessionFile(serverUrl);
1026
1130
  if (!file) return null;
1027
1131
  try {
1028
- const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
1132
+ const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
1029
1133
  return isSessionFile(parsed) ? parsed : null;
1030
1134
  } catch {
1031
1135
  return null;
1032
1136
  }
1033
1137
  }
1034
1138
  function saveSession(session) {
1035
- if (!fs5.existsSync(SESSION_DIR)) {
1036
- fs5.mkdirSync(SESSION_DIR, { recursive: true, mode: 448 });
1139
+ if (!fs6.existsSync(SESSION_DIR)) {
1140
+ fs6.mkdirSync(SESSION_DIR, { recursive: true, mode: 448 });
1037
1141
  }
1038
1142
  const file = sessionFileForServer(session.server);
1039
- fs5.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
1143
+ fs6.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
1040
1144
  return file;
1041
1145
  }
1042
1146
  function clearSession(serverUrl) {
1043
1147
  const file = sessionFileForServer(serverUrl);
1044
- if (fs5.existsSync(file)) {
1045
- fs5.unlinkSync(file);
1148
+ if (fs6.existsSync(file)) {
1149
+ fs6.unlinkSync(file);
1046
1150
  return true;
1047
1151
  }
1048
1152
  return false;
@@ -1132,7 +1236,7 @@ function isUnknown(_value) {
1132
1236
  return true;
1133
1237
  }
1134
1238
  async function resolveServerInteractive(presetNamespace) {
1135
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-2RRBNIQP.js");
1239
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-LW74552N.js");
1136
1240
  if (presetNamespace) {
1137
1241
  const spinner4 = clack.spinner();
1138
1242
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -1381,7 +1485,7 @@ import { spawn } from "child_process";
1381
1485
  import * as clack2 from "@clack/prompts";
1382
1486
 
1383
1487
  // src/update-notifier.ts
1384
- import * as fs6 from "fs";
1488
+ import * as fs7 from "fs";
1385
1489
  import * as path6 from "path";
1386
1490
  import * as os4 from "os";
1387
1491
  var CACHE_DIR = path6.join(os4.homedir(), ".camstack");
@@ -1391,8 +1495,8 @@ var REGISTRY_BASE = "https://registry.npmjs.org";
1391
1495
  var FETCH_TIMEOUT_MS = 2500;
1392
1496
  function readCache() {
1393
1497
  try {
1394
- if (!fs6.existsSync(CACHE_FILE)) return null;
1395
- const parsed = JSON.parse(fs6.readFileSync(CACHE_FILE, "utf8"));
1498
+ if (!fs7.existsSync(CACHE_FILE)) return null;
1499
+ const parsed = JSON.parse(fs7.readFileSync(CACHE_FILE, "utf8"));
1396
1500
  if (!parsed || typeof parsed !== "object") return null;
1397
1501
  const c = parsed;
1398
1502
  if (typeof c.lastCheckAt !== "number") return null;
@@ -1407,8 +1511,8 @@ function readCache() {
1407
1511
  }
1408
1512
  function writeCache(c) {
1409
1513
  try {
1410
- if (!fs6.existsSync(CACHE_DIR)) fs6.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
1411
- fs6.writeFileSync(CACHE_FILE, JSON.stringify(c, null, 2));
1514
+ if (!fs7.existsSync(CACHE_DIR)) fs7.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
1515
+ fs7.writeFileSync(CACHE_FILE, JSON.stringify(c, null, 2));
1412
1516
  } catch {
1413
1517
  }
1414
1518
  }
@@ -1567,7 +1671,7 @@ async function runUpdate(args) {
1567
1671
  }
1568
1672
 
1569
1673
  // src/commands/dev.ts
1570
- import * as fs7 from "fs";
1674
+ import * as fs8 from "fs";
1571
1675
  import * as path7 from "path";
1572
1676
  import { parseArgs as parseArgs5 } from "util";
1573
1677
  import * as clack3 from "@clack/prompts";
@@ -1578,14 +1682,14 @@ function resolveAddonDir(arg) {
1578
1682
  throw new Error("`dev` requires a source directory, not a tarball (nothing to watch).");
1579
1683
  }
1580
1684
  const absolute = path7.resolve(arg);
1581
- if (fs7.existsSync(absolute) && fs7.statSync(absolute).isDirectory()) {
1685
+ if (fs8.existsSync(absolute) && fs8.statSync(absolute).isDirectory()) {
1582
1686
  return absolute;
1583
1687
  }
1584
1688
  for (const candidate of [
1585
1689
  path7.resolve("packages", `addon-${arg}`),
1586
1690
  path7.resolve("packages", arg)
1587
1691
  ]) {
1588
- if (fs7.existsSync(candidate) && fs7.statSync(candidate).isDirectory()) {
1692
+ if (fs8.existsSync(candidate) && fs8.statSync(candidate).isDirectory()) {
1589
1693
  return candidate;
1590
1694
  }
1591
1695
  }
@@ -1595,10 +1699,10 @@ function resolveAddonDir(arg) {
1595
1699
  }
1596
1700
  function watchRecursive(target, onEvent) {
1597
1701
  try {
1598
- const w = fs7.watch(target, { recursive: true }, onEvent);
1702
+ const w = fs8.watch(target, { recursive: true }, onEvent);
1599
1703
  return { close: () => w.close(), recursive: true };
1600
1704
  } catch {
1601
- const w = fs7.watch(target, onEvent);
1705
+ const w = fs8.watch(target, onEvent);
1602
1706
  return { close: () => w.close(), recursive: false };
1603
1707
  }
1604
1708
  }
@@ -1622,6 +1726,7 @@ async function runDev(args) {
1622
1726
  options: {
1623
1727
  server: { type: "string", short: "s" },
1624
1728
  token: { type: "string", short: "t" },
1729
+ "token-file": { type: "string" },
1625
1730
  node: { type: "string", short: "n" },
1626
1731
  cluster: { type: "boolean", short: "c" },
1627
1732
  "watch-path": { type: "string", short: "w" },
@@ -1635,27 +1740,32 @@ async function runDev(args) {
1635
1740
  });
1636
1741
  const addonArg = positionals[0] ?? ".";
1637
1742
  const addonDir = resolveAddonDir(addonArg);
1638
- const explicitServer = typeof values.server === "string" ? values.server : void 0;
1639
- const explicitToken = typeof values.token === "string" ? values.token : void 0;
1640
- const session = loadSession(explicitServer);
1641
- const serverUrl = explicitServer ?? process.env.CAMSTACK_SERVER ?? session?.server;
1642
- const token = explicitToken ?? process.env.CAMSTACK_TOKEN ?? session?.token;
1643
- if (!serverUrl || !token) {
1644
- throw new Error("Missing server/token. Run `camstack login` first, or pass --server + --token.");
1743
+ const creds = resolveCredentials({
1744
+ ...typeof values.server === "string" ? { server: values.server } : {},
1745
+ ...typeof values.token === "string" ? { token: values.token } : {},
1746
+ ...typeof values["token-file"] === "string" ? { tokenFile: values["token-file"] } : {},
1747
+ loadSession
1748
+ });
1749
+ if (creds.warning) console.error(`[camstack dev] Warning: ${creds.warning}`);
1750
+ const serverUrl = creds.server;
1751
+ const token = creds.token;
1752
+ if (!serverUrl) {
1753
+ throw new Error("No hub server known. Pass --server, or run `camstack login` first.");
1645
1754
  }
1646
1755
  const nodeId = typeof values.node === "string" ? values.node : void 0;
1647
1756
  const cluster = values.cluster === true;
1648
1757
  const deployOpts = {
1649
1758
  serverUrl,
1650
1759
  token,
1760
+ authSource: creds.source,
1651
1761
  ...nodeId ? { nodeId } : {},
1652
1762
  ...cluster ? { cluster: true } : {},
1653
1763
  buildMode: values["no-build"] === true ? "skip" : "auto",
1654
1764
  ...typeof values["build-script"] === "string" ? { buildScript: values["build-script"] } : {}
1655
1765
  };
1656
1766
  const watchOverride = typeof values["watch-path"] === "string" ? values["watch-path"] : void 0;
1657
- const watchTarget = watchOverride ? path7.resolve(addonDir, watchOverride) : fs7.existsSync(path7.join(addonDir, DEFAULT_WATCH_SUBDIR)) ? path7.join(addonDir, DEFAULT_WATCH_SUBDIR) : addonDir;
1658
- if (!fs7.existsSync(watchTarget)) {
1767
+ const watchTarget = watchOverride ? path7.resolve(addonDir, watchOverride) : fs8.existsSync(path7.join(addonDir, DEFAULT_WATCH_SUBDIR)) ? path7.join(addonDir, DEFAULT_WATCH_SUBDIR) : addonDir;
1768
+ if (!fs8.existsSync(watchTarget)) {
1659
1769
  throw new Error(`Watch path does not exist: ${watchTarget}`);
1660
1770
  }
1661
1771
  const debounceMs = typeof values.debounce === "string" ? parseInt(values.debounce, 10) : DEFAULT_DEBOUNCE_MS;
@@ -1743,13 +1853,15 @@ function printLine(e, addonId) {
1743
1853
  `
1744
1854
  );
1745
1855
  }
1746
- async function fetchLogs(server, token, addonId, limit) {
1856
+ async function fetchLogs(server, token, addonId, limit, authSource) {
1747
1857
  const input = encodeURIComponent(JSON.stringify({ json: { addonId, limit } }));
1748
1858
  const url = `${server}/trpc/addons.getLogs?input=${input}`;
1749
1859
  const res = await hubRequest(url, { headers: { Authorization: `Bearer ${token}` } });
1750
1860
  if (!res.ok) {
1751
1861
  const text2 = await res.text().catch(() => "");
1752
- throw new Error(`getLogs HTTP ${res.status}: ${text2.slice(0, 200)}`);
1862
+ throw new Error(
1863
+ withAuthHint(`getLogs HTTP ${res.status}: ${text2.slice(0, 200)}`, res.status, authSource)
1864
+ );
1753
1865
  }
1754
1866
  const body = await res.json();
1755
1867
  return body.result?.data?.json ?? [];
@@ -1760,6 +1872,7 @@ async function runWatch(args) {
1760
1872
  options: {
1761
1873
  server: { type: "string", short: "s" },
1762
1874
  token: { type: "string", short: "t" },
1875
+ "token-file": { type: "string" },
1763
1876
  node: { type: "string", short: "n" },
1764
1877
  cluster: { type: "boolean", short: "c" },
1765
1878
  "no-build": { type: "boolean" },
@@ -1771,17 +1884,24 @@ async function runWatch(args) {
1771
1884
  allowPositionals: true
1772
1885
  });
1773
1886
  const addonArg = positionals[0] ?? ".";
1774
- const session = loadSession(typeof values.server === "string" ? values.server : void 0);
1775
- const serverUrl = (typeof values.server === "string" ? values.server : void 0) ?? process.env.CAMSTACK_SERVER ?? session?.server;
1776
- const token = (typeof values.token === "string" ? values.token : void 0) ?? process.env.CAMSTACK_TOKEN ?? session?.token;
1777
- if (!serverUrl || !token) {
1778
- throw new Error("Missing server/token. Run `camstack login` first, or pass --server + --token.");
1887
+ const creds = resolveCredentials({
1888
+ ...typeof values.server === "string" ? { server: values.server } : {},
1889
+ ...typeof values.token === "string" ? { token: values.token } : {},
1890
+ ...typeof values["token-file"] === "string" ? { tokenFile: values["token-file"] } : {},
1891
+ loadSession
1892
+ });
1893
+ if (creds.warning) console.error(`[camstack watch] Warning: ${creds.warning}`);
1894
+ const serverUrl = creds.server;
1895
+ const token = creds.token;
1896
+ if (!serverUrl) {
1897
+ throw new Error("No hub server known. Pass --server, or run `camstack login` first.");
1779
1898
  }
1780
1899
  const nodeId = typeof values.node === "string" ? values.node : void 0;
1781
1900
  const cluster = values.cluster === true;
1782
1901
  const deployOpts = {
1783
1902
  serverUrl,
1784
1903
  token,
1904
+ authSource: creds.source,
1785
1905
  ...nodeId ? { nodeId } : {},
1786
1906
  ...cluster ? { cluster: true } : {},
1787
1907
  buildMode: values["no-build"] === true ? "skip" : "auto",
@@ -1807,7 +1927,7 @@ async function runWatch(args) {
1807
1927
  process.on("SIGTERM", shutdown);
1808
1928
  while (!stop) {
1809
1929
  try {
1810
- const entries = await fetchLogs(serverUrl, token, addonId, LOG_BATCH_LIMIT);
1930
+ const entries = await fetchLogs(serverUrl, token, addonId, LOG_BATCH_LIMIT, creds.source);
1811
1931
  const sorted = [...entries].toSorted((a, b) => {
1812
1932
  const ta = typeof a.timestamp === "number" ? a.timestamp : new Date(a.timestamp).getTime();
1813
1933
  const tb = typeof b.timestamp === "number" ? b.timestamp : new Date(b.timestamp).getTime();
@@ -2210,6 +2330,10 @@ function printGlobalHelp(commands) {
2210
2330
  lines.push(" -h, --help Show this help, or per-command help with `camstack <cmd> --help`");
2211
2331
  console.log(lines.join("\n"));
2212
2332
  }
2333
+ var TOKEN_RESOLUTION_HELP = [
2334
+ "Token resolution (first hit wins): --token, --token-file, $CAMSTACK_TOKEN,",
2335
+ " then the session cached by `camstack login` for the target server."
2336
+ ].join("\n");
2213
2337
  function buildCommands() {
2214
2338
  return [
2215
2339
  {
@@ -2331,10 +2455,13 @@ function buildCommands() {
2331
2455
  " --no-verify Skip the post-deploy hub /health gate",
2332
2456
  "",
2333
2457
  "Target options:",
2334
- " -s, --server <url> Server URL (default: cached session from `camstack login`)",
2335
- " -t, --token <token> Auth token override ($CAMSTACK_TOKEN, then cached scoped token)",
2458
+ " -s, --server <url> Server URL (default: $CAMSTACK_SERVER, then the cached session)",
2459
+ " -t, --token <token> Auth token override",
2460
+ " --token-file <path> Read the token from a file (bare token or a ~/.camstack session file)",
2336
2461
  " -n, --node <id> Push to a specific node. Mutually exclusive with --cluster",
2337
- " -c, --cluster Push to hub + every online agent (requires admin token)"
2462
+ " -c, --cluster Push to hub + every online agent (requires admin token)",
2463
+ "",
2464
+ TOKEN_RESOLUTION_HELP
2338
2465
  ].join("\n")
2339
2466
  },
2340
2467
  {
@@ -2352,9 +2479,12 @@ function buildCommands() {
2352
2479
  " and waits for the probation boot to promote.",
2353
2480
  "",
2354
2481
  "Options:",
2355
- " -s, --server <url> Server URL (default: cached session from `camstack login`)",
2356
- " -t, --token <token> Auth token override ($CAMSTACK_TOKEN, then cached scoped token)",
2357
- " --no-verify Do not wait for promotion after applyServerUpdate is accepted"
2482
+ " -s, --server <url> Server URL (default: $CAMSTACK_SERVER, then the cached session)",
2483
+ " -t, --token <token> Auth token override",
2484
+ " --token-file <path> Read the token from a file (bare token or a ~/.camstack session file)",
2485
+ " --no-verify Do not wait for promotion after applyServerUpdate is accepted",
2486
+ "",
2487
+ TOKEN_RESOLUTION_HELP
2358
2488
  ].join("\n")
2359
2489
  },
2360
2490
  {
@@ -2370,6 +2500,7 @@ function buildCommands() {
2370
2500
  "Options:",
2371
2501
  " -s, --server <url> Server URL (default: cached session)",
2372
2502
  " -t, --token <token> Auth token override",
2503
+ " --token-file <path> Read the token from a file",
2373
2504
  " -n, --node <id> Push to a specific node",
2374
2505
  " -c, --cluster Push to hub + every online agent",
2375
2506
  " --no-build Skip `npm run build`",
@@ -2378,7 +2509,9 @@ function buildCommands() {
2378
2509
  " --no-initial-deploy Skip initial deploy \u2014 only tail existing addon",
2379
2510
  "",
2380
2511
  "Each round polls the last 100 entries; new entries (by timestamp+content)",
2381
- "are printed. Ctrl-C to stop."
2512
+ "are printed. Ctrl-C to stop.",
2513
+ "",
2514
+ TOKEN_RESOLUTION_HELP
2382
2515
  ].join("\n")
2383
2516
  },
2384
2517
  {
@@ -2403,11 +2536,14 @@ function buildCommands() {
2403
2536
  "Target options:",
2404
2537
  " -s, --server <url> Server URL (default: cached session)",
2405
2538
  " -t, --token <token> Auth token override",
2539
+ " --token-file <path> Read the token from a file",
2406
2540
  " -n, --node <id> Push to a specific node",
2407
2541
  " -c, --cluster Push to hub + every online agent",
2408
2542
  "",
2409
2543
  "Stop with Ctrl-C. Each cycle is sequential \u2014 concurrent changes during",
2410
- "a push are coalesced into the next cycle."
2544
+ "a push are coalesced into the next cycle.",
2545
+ "",
2546
+ TOKEN_RESOLUTION_HELP
2411
2547
  ].join("\n")
2412
2548
  },
2413
2549
  {
@@ -2528,6 +2664,7 @@ async function runDeploy(args) {
2528
2664
  {
2529
2665
  server: { type: "string", short: "s" },
2530
2666
  token: { type: "string", short: "t" },
2667
+ "token-file": { type: "string" },
2531
2668
  node: { type: "string", short: "n" },
2532
2669
  cluster: { type: "boolean", short: "c" },
2533
2670
  build: { type: "string", short: "b" },
@@ -2542,15 +2679,9 @@ async function runDeploy(args) {
2542
2679
  return;
2543
2680
  }
2544
2681
  const addonPath = parsed.positionals[0] ?? ".";
2545
- const session = loadSession(stringOpt(parsed.values, "server"));
2546
- const server = stringOpt(parsed.values, "server") ?? process.env.CAMSTACK_SERVER ?? session?.server ?? "https://localhost:4443";
2547
- const token = stringOpt(parsed.values, "token") ?? process.env.CAMSTACK_TOKEN ?? session?.token;
2548
- if (!token) {
2549
- console.error(
2550
- "[camstack] Error: No token. Run `camstack login` first, pass --token, or set CAMSTACK_TOKEN."
2551
- );
2552
- process.exit(1);
2553
- }
2682
+ const creds = resolveCommandCredentials(parsed.values);
2683
+ const server = creds.server ?? DEFAULT_SERVER_URL;
2684
+ const token = creds.token;
2554
2685
  const nodeId = stringOpt(parsed.values, "node");
2555
2686
  const cluster = boolOpt(parsed.values, "cluster");
2556
2687
  if (nodeId && cluster) {
@@ -2572,6 +2703,7 @@ async function runDeploy(args) {
2572
2703
  await deployAddon(addonPath, {
2573
2704
  serverUrl: server,
2574
2705
  token,
2706
+ authSource: creds.source,
2575
2707
  ...nodeId ? { nodeId } : {},
2576
2708
  ...cluster ? { cluster: true } : {},
2577
2709
  buildMode,
@@ -2585,6 +2717,7 @@ async function runDeployServer(args) {
2585
2717
  {
2586
2718
  server: { type: "string", short: "s" },
2587
2719
  token: { type: "string", short: "t" },
2720
+ "token-file": { type: "string" },
2588
2721
  "no-verify": { type: "boolean" }
2589
2722
  },
2590
2723
  false
@@ -2593,21 +2726,36 @@ async function runDeployServer(args) {
2593
2726
  console.log(commandHelp("deploy-server"));
2594
2727
  return;
2595
2728
  }
2596
- const session = loadSession(stringOpt(parsed.values, "server"));
2597
- const server = stringOpt(parsed.values, "server") ?? process.env.CAMSTACK_SERVER ?? session?.server ?? "https://localhost:4443";
2598
- const token = stringOpt(parsed.values, "token") ?? process.env.CAMSTACK_TOKEN ?? session?.token;
2599
- if (!token) {
2600
- console.error(
2601
- "[camstack] Error: No token. Run `camstack login` first, pass --token, or set CAMSTACK_TOKEN."
2602
- );
2603
- process.exit(1);
2604
- }
2729
+ const creds = resolveCommandCredentials(parsed.values);
2605
2730
  await deployServer({
2606
- serverUrl: server,
2607
- token,
2731
+ serverUrl: creds.server ?? DEFAULT_SERVER_URL,
2732
+ token: creds.token,
2733
+ authSource: creds.source,
2608
2734
  verify: !boolOpt(parsed.values, "no-verify")
2609
2735
  });
2610
2736
  }
2737
+ var DEFAULT_SERVER_URL = "https://localhost:4443";
2738
+ function resolveCommandCredentials(values) {
2739
+ const server = stringOpt(values, "server");
2740
+ const token = stringOpt(values, "token");
2741
+ const tokenFile = stringOpt(values, "token-file");
2742
+ try {
2743
+ const creds = resolveCredentials({
2744
+ ...server ? { server } : {},
2745
+ ...token ? { token } : {},
2746
+ ...tokenFile ? { tokenFile } : {},
2747
+ loadSession
2748
+ });
2749
+ if (creds.warning) console.error(`[camstack] Warning: ${creds.warning}`);
2750
+ return creds;
2751
+ } catch (err) {
2752
+ if (err instanceof MissingCredentialsError) {
2753
+ console.error(`[camstack] ${err.message}`);
2754
+ process.exit(1);
2755
+ }
2756
+ throw err;
2757
+ }
2758
+ }
2611
2759
  function commandHelp(name) {
2612
2760
  const cmd = buildCommands().find((c) => c.name === name);
2613
2761
  return cmd ? cmd.help() : "";