camstack 1.2.190 → 1.2.191

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
@@ -37,7 +37,7 @@ async function runServe(args) {
37
37
  ...typeof values.data === "string" ? { data: values.data } : {}
38
38
  };
39
39
  Object.assign(process.env, buildServeEnv(opts));
40
- await import("./launcher-ILGFB3AQ.js");
40
+ await import("./launcher-AZI5I6X5.js");
41
41
  }
42
42
 
43
43
  // src/commands/agent.ts
@@ -82,7 +82,7 @@ async function runAgent(args) {
82
82
  ...typeof values.port === "string" ? { port: values.port } : {}
83
83
  };
84
84
  Object.assign(process.env, buildAgentEnv(opts));
85
- await import("./launcher-ILGFB3AQ.js");
85
+ await import("./launcher-AZI5I6X5.js");
86
86
  }
87
87
 
88
88
  // src/commands/setup.ts
@@ -14968,6 +14968,7 @@ var WELL_KNOWN_TABS = [
14968
14968
  ];
14969
14969
  var WELL_KNOWN_TAB_MAP = Object.fromEntries(WELL_KNOWN_TABS.map((t) => [t.id, t]));
14970
14970
  var SECRET_RETRY_MAX_MS = 5 * 6e4;
14971
+ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
14971
14972
  var PEER_BYTES_CHUNK_BYTES = 256 * 1024;
14972
14973
  var PeerBytesTicketSchema = external_exports.object({
14973
14974
  /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
@@ -18452,6 +18453,150 @@ var ConvertResultSchema = external_exports.object({
18452
18453
  entry: ModelCatalogEntrySchema,
18453
18454
  artifacts: external_exports.array(ConvertArtifactSchema).readonly()
18454
18455
  });
18456
+ var MethodAccessSchema = external_exports.enum([
18457
+ "view",
18458
+ "create",
18459
+ "delete"
18460
+ ]);
18461
+ var AllowedProviderSchema = external_exports.union([external_exports.literal("*"), external_exports.array(external_exports.string())]);
18462
+ var AllowedDevicesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.literal("*"), external_exports.array(external_exports.string())]));
18463
+ var CapScopeSchema = external_exports.enum(["device", "system"]);
18464
+ var DeviceSelectorSchema = external_exports.discriminatedUnion("kind", [
18465
+ external_exports.object({ kind: external_exports.literal("all") }),
18466
+ external_exports.object({
18467
+ kind: external_exports.literal("ids"),
18468
+ ids: external_exports.array(external_exports.number().int()).min(1)
18469
+ }),
18470
+ external_exports.object({
18471
+ kind: external_exports.literal("types"),
18472
+ types: external_exports.array(external_exports.enum(DeviceType)).min(1)
18473
+ }),
18474
+ external_exports.object({
18475
+ kind: external_exports.literal("locations"),
18476
+ locations: external_exports.array(external_exports.string().min(1)).min(1)
18477
+ })
18478
+ ]);
18479
+ var DeviceTokenScopeSchema = external_exports.object({
18480
+ type: external_exports.literal("device"),
18481
+ /** The device SET this grant covers — resolved against the live fleet. */
18482
+ selector: DeviceSelectorSchema,
18483
+ access: external_exports.array(MethodAccessSchema).min(1),
18484
+ /**
18485
+ * Whether a grant on a PARENT device transparently covers its accessory
18486
+ * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
18487
+ * Direction is parent → children ONLY.
18488
+ *
18489
+ * Absent → the matcher DERIVES it from the access flavour: `view`
18490
+ * inherits (a camera viewer sees the camera's accessories), `create` /
18491
+ * `delete` do NOT (actuating/removing a child is an explicit act the
18492
+ * operator must grant on the child, not inherit from the parent). Set it
18493
+ * explicitly to override that default per grant.
18494
+ */
18495
+ includeLinked: external_exports.boolean().optional()
18496
+ });
18497
+ function migrateLegacyTokenScope(raw) {
18498
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
18499
+ if (Reflect.get(raw, "type") !== "device") return raw;
18500
+ if (Reflect.get(raw, "selector") !== void 0) return raw;
18501
+ const targets = Reflect.get(raw, "targets");
18502
+ if (!Array.isArray(targets)) return raw;
18503
+ return {
18504
+ type: "device",
18505
+ selector: {
18506
+ kind: "ids",
18507
+ ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
18508
+ },
18509
+ access: Reflect.get(raw, "access")
18510
+ };
18511
+ }
18512
+ var TokenScopeSchema = external_exports.preprocess(migrateLegacyTokenScope, external_exports.discriminatedUnion("type", [
18513
+ external_exports.object({
18514
+ type: external_exports.literal("category"),
18515
+ target: CapScopeSchema,
18516
+ access: external_exports.array(MethodAccessSchema).min(1)
18517
+ }),
18518
+ external_exports.object({
18519
+ type: external_exports.literal("capability"),
18520
+ target: external_exports.string(),
18521
+ access: external_exports.array(MethodAccessSchema).min(1)
18522
+ }),
18523
+ external_exports.object({
18524
+ type: external_exports.literal("addon"),
18525
+ target: external_exports.string(),
18526
+ access: external_exports.array(MethodAccessSchema).min(1)
18527
+ }),
18528
+ DeviceTokenScopeSchema
18529
+ ]));
18530
+ var UserRecordSchema = external_exports.object({
18531
+ id: external_exports.string(),
18532
+ username: external_exports.string(),
18533
+ passwordHash: external_exports.string(),
18534
+ /**
18535
+ * Admin bypass. When true, the middleware skips the scope-access
18536
+ * check entirely. There is no other axis of privilege; the legacy
18537
+ * role enum collapsed onto this boolean in v2.
18538
+ */
18539
+ isAdmin: external_exports.boolean().default(false),
18540
+ allowedProviders: AllowedProviderSchema,
18541
+ allowedDevices: AllowedDevicesSchema,
18542
+ /**
18543
+ * Scopes granted to this user. Admins bypass; their `scopes` is
18544
+ * ignored. Non-admins without scopes are locked out of every
18545
+ * protected call.
18546
+ */
18547
+ scopes: external_exports.array(TokenScopeSchema).default([]),
18548
+ createdAt: external_exports.number(),
18549
+ updatedAt: external_exports.number()
18550
+ });
18551
+ var ApiKeyRecordSchema = external_exports.object({
18552
+ id: external_exports.string(),
18553
+ label: external_exports.string(),
18554
+ isAdmin: external_exports.boolean().default(false),
18555
+ allowedProviders: AllowedProviderSchema,
18556
+ allowedDevices: AllowedDevicesSchema,
18557
+ tokenHash: external_exports.string(),
18558
+ tokenPrefix: external_exports.string(),
18559
+ createdAt: external_exports.number(),
18560
+ lastUsedAt: external_exports.number().optional()
18561
+ });
18562
+ var ScopedTokenSchema = external_exports.object({
18563
+ id: external_exports.string(),
18564
+ userId: external_exports.string(),
18565
+ name: external_exports.string(),
18566
+ tokenHash: external_exports.string(),
18567
+ tokenPrefix: external_exports.string(),
18568
+ scopes: external_exports.array(TokenScopeSchema),
18569
+ expiresAt: external_exports.number().nullish(),
18570
+ lastUsedAt: external_exports.number().nullish(),
18571
+ createdAt: external_exports.number()
18572
+ });
18573
+ var ShareViewGrantSchema = external_exports.object({
18574
+ kind: external_exports.enum(SHARE_VIEW_KINDS),
18575
+ deviceIds: external_exports.array(external_exports.number().int().nonnegative()).min(1).max(64),
18576
+ /** Talk-back, opt-in at mint time; absent means off. */
18577
+ talk: external_exports.boolean().optional()
18578
+ });
18579
+ var ShareViewScopeSchema = external_exports.object({
18580
+ type: external_exports.literal("share-view"),
18581
+ grant: ShareViewGrantSchema
18582
+ });
18583
+ var AddonRoutesScopeSchema = external_exports.object({
18584
+ type: external_exports.literal("addon-routes"),
18585
+ access: external_exports.array(MethodAccessSchema).min(1)
18586
+ });
18587
+ var CredentialScopeSchema = external_exports.union([
18588
+ external_exports.literal("*"),
18589
+ ShareViewScopeSchema,
18590
+ AddonRoutesScopeSchema,
18591
+ TokenScopeSchema
18592
+ ]);
18593
+ var CREDENTIAL_TOKEN_KIND = "credential";
18594
+ var CredentialTokenClaimsSchema = external_exports.object({
18595
+ kind: external_exports.literal(CREDENTIAL_TOKEN_KIND),
18596
+ sub: external_exports.string().min(1),
18597
+ jti: external_exports.string().min(16),
18598
+ scopes: external_exports.array(CredentialScopeSchema).min(1)
18599
+ });
18455
18600
  var AppFlowPurposeSchema = external_exports.enum(["new-session", "step-up"]);
18456
18601
  var AppPageLangSchema = external_exports.enum(["en", "it"]);
18457
18602
  var APP_FLOW_REFUSAL_MESSAGES = {
@@ -50264,123 +50409,6 @@ var notificationRulesCapability = {
50264
50409
  getAlarmSensors: method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" })
50265
50410
  }
50266
50411
  };
50267
- var MethodAccessSchema = external_exports.enum([
50268
- "view",
50269
- "create",
50270
- "delete"
50271
- ]);
50272
- var AllowedProviderSchema = external_exports.union([external_exports.literal("*"), external_exports.array(external_exports.string())]);
50273
- var AllowedDevicesSchema = external_exports.record(external_exports.string(), external_exports.union([external_exports.literal("*"), external_exports.array(external_exports.string())]));
50274
- var CapScopeSchema = external_exports.enum(["device", "system"]);
50275
- var DeviceSelectorSchema = external_exports.discriminatedUnion("kind", [
50276
- external_exports.object({ kind: external_exports.literal("all") }),
50277
- external_exports.object({
50278
- kind: external_exports.literal("ids"),
50279
- ids: external_exports.array(external_exports.number().int()).min(1)
50280
- }),
50281
- external_exports.object({
50282
- kind: external_exports.literal("types"),
50283
- types: external_exports.array(external_exports.enum(DeviceType)).min(1)
50284
- }),
50285
- external_exports.object({
50286
- kind: external_exports.literal("locations"),
50287
- locations: external_exports.array(external_exports.string().min(1)).min(1)
50288
- })
50289
- ]);
50290
- var DeviceTokenScopeSchema = external_exports.object({
50291
- type: external_exports.literal("device"),
50292
- /** The device SET this grant covers — resolved against the live fleet. */
50293
- selector: DeviceSelectorSchema,
50294
- access: external_exports.array(MethodAccessSchema).min(1),
50295
- /**
50296
- * Whether a grant on a PARENT device transparently covers its accessory
50297
- * CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
50298
- * Direction is parent → children ONLY.
50299
- *
50300
- * Absent → the matcher DERIVES it from the access flavour: `view`
50301
- * inherits (a camera viewer sees the camera's accessories), `create` /
50302
- * `delete` do NOT (actuating/removing a child is an explicit act the
50303
- * operator must grant on the child, not inherit from the parent). Set it
50304
- * explicitly to override that default per grant.
50305
- */
50306
- includeLinked: external_exports.boolean().optional()
50307
- });
50308
- function migrateLegacyTokenScope(raw) {
50309
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
50310
- if (Reflect.get(raw, "type") !== "device") return raw;
50311
- if (Reflect.get(raw, "selector") !== void 0) return raw;
50312
- const targets = Reflect.get(raw, "targets");
50313
- if (!Array.isArray(targets)) return raw;
50314
- return {
50315
- type: "device",
50316
- selector: {
50317
- kind: "ids",
50318
- ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
50319
- },
50320
- access: Reflect.get(raw, "access")
50321
- };
50322
- }
50323
- var TokenScopeSchema = external_exports.preprocess(migrateLegacyTokenScope, external_exports.discriminatedUnion("type", [
50324
- external_exports.object({
50325
- type: external_exports.literal("category"),
50326
- target: CapScopeSchema,
50327
- access: external_exports.array(MethodAccessSchema).min(1)
50328
- }),
50329
- external_exports.object({
50330
- type: external_exports.literal("capability"),
50331
- target: external_exports.string(),
50332
- access: external_exports.array(MethodAccessSchema).min(1)
50333
- }),
50334
- external_exports.object({
50335
- type: external_exports.literal("addon"),
50336
- target: external_exports.string(),
50337
- access: external_exports.array(MethodAccessSchema).min(1)
50338
- }),
50339
- DeviceTokenScopeSchema
50340
- ]));
50341
- var UserRecordSchema = external_exports.object({
50342
- id: external_exports.string(),
50343
- username: external_exports.string(),
50344
- passwordHash: external_exports.string(),
50345
- /**
50346
- * Admin bypass. When true, the middleware skips the scope-access
50347
- * check entirely. There is no other axis of privilege; the legacy
50348
- * role enum collapsed onto this boolean in v2.
50349
- */
50350
- isAdmin: external_exports.boolean().default(false),
50351
- allowedProviders: AllowedProviderSchema,
50352
- allowedDevices: AllowedDevicesSchema,
50353
- /**
50354
- * Scopes granted to this user. Admins bypass; their `scopes` is
50355
- * ignored. Non-admins without scopes are locked out of every
50356
- * protected call.
50357
- */
50358
- scopes: external_exports.array(TokenScopeSchema).default([]),
50359
- createdAt: external_exports.number(),
50360
- updatedAt: external_exports.number()
50361
- });
50362
- var ApiKeyRecordSchema = external_exports.object({
50363
- id: external_exports.string(),
50364
- label: external_exports.string(),
50365
- isAdmin: external_exports.boolean().default(false),
50366
- allowedProviders: AllowedProviderSchema,
50367
- allowedDevices: AllowedDevicesSchema,
50368
- tokenHash: external_exports.string(),
50369
- tokenPrefix: external_exports.string(),
50370
- createdAt: external_exports.number(),
50371
- lastUsedAt: external_exports.number().optional()
50372
- });
50373
- var ScopedTokenSchema = external_exports.object({
50374
- id: external_exports.string(),
50375
- userId: external_exports.string(),
50376
- name: external_exports.string(),
50377
- tokenHash: external_exports.string(),
50378
- tokenPrefix: external_exports.string(),
50379
- scopes: external_exports.array(TokenScopeSchema),
50380
- expiresAt: external_exports.number().nullish(),
50381
- lastUsedAt: external_exports.number().nullish(),
50382
- createdAt: external_exports.number()
50383
- });
50384
50412
  var OauthIntegrationDescriptorSchema = external_exports.object({
50385
50413
  /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
50386
50414
  integrationId: external_exports.string(),
@@ -85953,6 +85981,7 @@ import * as clack from "@clack/prompts";
85953
85981
  import {
85954
85982
  clearSession,
85955
85983
  defaultSession,
85984
+ jwtClaims,
85956
85985
  listSessions,
85957
85986
  loadSession,
85958
85987
  saveSession
@@ -85968,20 +85997,11 @@ import {
85968
85997
  validateHubAddress
85969
85998
  } from "@camstack/develop";
85970
85999
  import {
85971
- discoverHubs,
85972
- normalizeHubUrl
86000
+ discoverHubs
85973
86001
  } from "@camstack/develop/hub-discovery";
85974
- var ADMIN_TOKEN_SUFFIX = ".admin-token";
85975
86002
  function validateManualHubUrl(value) {
85976
86003
  return validateHubAddress(value);
85977
86004
  }
85978
- function hubUrlFromAdminTokenFile(fileName) {
85979
- if (!fileName.endsWith(ADMIN_TOKEN_SUFFIX)) return null;
85980
- const slug = fileName.slice(0, -ADMIN_TOKEN_SUFFIX.length);
85981
- const match = /^(.+?)_(\d{1,5})$/.exec(slug);
85982
- const candidate = match ? `https://${match[1]}:${match[2]}` : `https://${slug}`;
85983
- return normalizeHubUrl(candidate)?.url ?? null;
85984
- }
85985
86005
  function knownHubCandidates(deps) {
85986
86006
  const dir = deps.sessionDir ?? defaultSessionDir();
85987
86007
  const env = deps.env ?? process.env;
@@ -86004,9 +86024,6 @@ function knownHubCandidates(deps) {
86004
86024
  }
86005
86025
  } catch {
86006
86026
  }
86007
- } else {
86008
- const url2 = hubUrlFromAdminTokenFile(file2);
86009
- if (url2 !== null) out.push({ url: url2, source: "saved admin token" });
86010
86027
  }
86011
86028
  }
86012
86029
  const fromEnv = resolveHubUrl({ env, session: null });
@@ -86134,30 +86151,32 @@ function isLoginPayload(value) {
86134
86151
  const u = v.user;
86135
86152
  return typeof u.id === "string" && typeof u.username === "string";
86136
86153
  }
86137
- function isLogoutPayload(value) {
86138
- if (value === null || typeof value !== "object") return false;
86139
- return Reflect.get(value, "success") === true && typeof Reflect.get(value, "revoked") === "number";
86140
- }
86141
- var SCOPED_TOKEN_PREFIX = "cst_";
86142
- var ADMIN_TOKEN_PREFIX = "csa_";
86143
- async function revokePriorSession(server, prior, authJwt) {
86144
- if (prior.token.startsWith(SCOPED_TOKEN_PREFIX) && prior.tokenId) {
86145
- await callTrpcMutation(
86146
- `${server}/trpc/userManagement.revokeScopedToken?batch=1`,
86147
- `Bearer ${authJwt}`,
86148
- { id: prior.tokenId },
86149
- isUnknown
86150
- );
86151
- return;
86154
+ function revocationFor(session) {
86155
+ if (session.tokenId) {
86156
+ return {
86157
+ procedure: "userManagement.revokeScopedToken",
86158
+ input: { id: session.tokenId },
86159
+ bearer: "fresh"
86160
+ };
86152
86161
  }
86153
- if (!prior.token.startsWith(SCOPED_TOKEN_PREFIX) && !prior.token.startsWith(ADMIN_TOKEN_PREFIX)) {
86154
- await callTrpcMutation(
86155
- `${server}/trpc/auth.logout?batch=1`,
86156
- `Bearer ${prior.token}`,
86157
- {},
86158
- isLogoutPayload
86159
- );
86162
+ if (jwtClaims(session.token) !== null) {
86163
+ return { procedure: "auth.logout", input: {}, bearer: "self" };
86160
86164
  }
86165
+ return null;
86166
+ }
86167
+ async function performRevocation(server, session, freshJwt) {
86168
+ const r = revocationFor(session);
86169
+ if (!r) return;
86170
+ const bearer = r.bearer === "fresh" && freshJwt ? freshJwt : session.token;
86171
+ await callTrpcMutation(
86172
+ `${server}/trpc/${r.procedure}?batch=1`,
86173
+ `Bearer ${bearer}`,
86174
+ r.input,
86175
+ isUnknown
86176
+ );
86177
+ }
86178
+ async function revokePriorSession(server, prior, authJwt) {
86179
+ await performRevocation(server, prior, authJwt);
86161
86180
  }
86162
86181
  async function completeSecondFactor(server, login) {
86163
86182
  const factors = login.secondFactors ?? [];
@@ -86174,7 +86193,7 @@ async function completeSecondFactor(server, login) {
86174
86193
  return await callTrpcMutation(
86175
86194
  `${server}/trpc/auth.loginVerifyTotp?batch=1`,
86176
86195
  void 0,
86177
- { challengeToken: login.token, code },
86196
+ { challengeToken: login.token, code, refreshable: true },
86178
86197
  isLoginPayload
86179
86198
  );
86180
86199
  } catch (err) {
@@ -86267,6 +86286,8 @@ async function loginCommand(opts) {
86267
86286
  let displayName;
86268
86287
  let userId;
86269
86288
  let isAdmin = false;
86289
+ let refreshToken;
86290
+ let expiresAt;
86270
86291
  while (true) {
86271
86292
  const authSpinner = clack.spinner();
86272
86293
  authSpinner.start(
@@ -86276,7 +86297,7 @@ async function loginCommand(opts) {
86276
86297
  const first = await callTrpcMutation(
86277
86298
  `${server}/trpc/auth.login?batch=1`,
86278
86299
  void 0,
86279
- { username, password: password2 },
86300
+ { username, password: password2, refreshable: true },
86280
86301
  isLoginPayload
86281
86302
  );
86282
86303
  const owesSecondFactor = (first.secondFactors ?? []).length > 0;
@@ -86286,6 +86307,8 @@ async function loginCommand(opts) {
86286
86307
  displayName = login.user.username;
86287
86308
  userId = login.user.id;
86288
86309
  isAdmin = login.user.isAdmin === true;
86310
+ refreshToken = login.refreshToken;
86311
+ expiresAt = login.expiresAt;
86289
86312
  if (!owesSecondFactor) authSpinner.stop(`Authenticated as ${displayName}`);
86290
86313
  break;
86291
86314
  } catch (err) {
@@ -86317,14 +86340,14 @@ async function loginCommand(opts) {
86317
86340
  token: jwt2,
86318
86341
  scopes: [],
86319
86342
  createdAt: Date.now(),
86320
- ...isAdmin ? { isAdmin: true } : {}
86343
+ ...isAdmin ? { isAdmin: true } : {},
86344
+ ...refreshToken ? { refreshToken } : {},
86345
+ ...expiresAt !== void 0 ? { expiresAt } : {}
86321
86346
  };
86322
86347
  const file2 = saveSession(session);
86323
86348
  const role = isAdmin ? "administrator" : "user";
86324
- clack.outro(
86325
- `\u2713 Logged in as ${displayName} (${role}) \u2192 ${server}
86326
- Cached at ${file2}`
86327
- );
86349
+ clack.outro(`\u2713 Logged in as ${displayName} (${role}) \u2192 ${server}
86350
+ Cached at ${file2}`);
86328
86351
  }
86329
86352
  async function logoutCommand(opts) {
86330
86353
  const session = loadSession(opts.server);
@@ -86336,21 +86359,7 @@ async function logoutCommand(opts) {
86336
86359
  const spinner3 = clack.spinner();
86337
86360
  spinner3.start(`Revoking token on ${session.server}`);
86338
86361
  try {
86339
- if (session.token.startsWith(SCOPED_TOKEN_PREFIX) && session.tokenId) {
86340
- await callTrpcMutation(
86341
- `${session.server}/trpc/userManagement.revokeScopedToken?batch=1`,
86342
- `Bearer ${session.token}`,
86343
- { id: session.tokenId },
86344
- isUnknown
86345
- );
86346
- } else if (!session.token.startsWith(SCOPED_TOKEN_PREFIX) && !session.token.startsWith(ADMIN_TOKEN_PREFIX)) {
86347
- await callTrpcMutation(
86348
- `${session.server}/trpc/auth.logout?batch=1`,
86349
- `Bearer ${session.token}`,
86350
- {},
86351
- isLogoutPayload
86352
- );
86353
- }
86362
+ await performRevocation(session.server, session, void 0);
86354
86363
  spinner3.stop("Session revoked server-side.");
86355
86364
  } catch (err) {
86356
86365
  spinner3.stop(
@@ -86396,7 +86405,15 @@ async function whoamiCommand(opts) {
86396
86405
  if (session.tokenId) console.log(` tokenId: ${session.tokenId}`);
86397
86406
  const scopeLine = session.scopes.length > 0 ? session.scopes.map((s) => `${s.type}:${s.target}`).join(", ") : "(login JWT)";
86398
86407
  console.log(` scopes: ${scopeLine}`);
86399
- console.log(` admin: ${session.isAdmin === true ? "yes" : "no"}`);
86408
+ const claims = jwtClaims(session.token);
86409
+ const admin = session.isAdmin === true || claims?.["isAdmin"] === true;
86410
+ console.log(` admin: ${admin ? "yes" : "no"}`);
86411
+ const expiresAt = session.expiresAt ?? (typeof claims?.["exp"] === "number" ? claims["exp"] * 1e3 : void 0);
86412
+ if (expiresAt !== void 0) {
86413
+ console.log(
86414
+ ` expires: ${new Date(expiresAt).toISOString()}${session.refreshToken ? " (renewed automatically)" : ""}`
86415
+ );
86416
+ }
86400
86417
  console.log(` createdAt: ${new Date(session.createdAt).toISOString()}`);
86401
86418
  console.log("");
86402
86419
  console.log(`[camstack] Pinging ${session.server}/api/auth/whoami\u2026`);
@@ -86441,7 +86458,7 @@ import {
86441
86458
  MissingCredentialsError,
86442
86459
  normalizeHubAddress as normalizeHubAddress3,
86443
86460
  touchSession as touchSession3,
86444
- resolveCredentials as resolveCredentials3
86461
+ resolveFreshCredentials as resolveFreshCredentials3
86445
86462
  } from "@camstack/develop";
86446
86463
 
86447
86464
  // src/commands/discover.ts
@@ -86688,7 +86705,20 @@ import * as fs7 from "fs";
86688
86705
  import * as path7 from "path";
86689
86706
  import { parseArgs as parseArgs6 } from "util";
86690
86707
  import * as clack3 from "@clack/prompts";
86691
- import { loadSession as loadSession2, resolveCredentials, touchSession } from "@camstack/develop";
86708
+ import { loadSession as loadSession2, resolveFreshCredentials, touchSession } from "@camstack/develop";
86709
+
86710
+ // src/http/hub-transport.ts
86711
+ var cliHubTransport = async (req) => {
86712
+ const res = await hubRequest(req.url, {
86713
+ method: req.method,
86714
+ headers: req.headers,
86715
+ ...req.body !== void 0 ? { body: req.body } : {},
86716
+ timeoutMs: req.timeoutMs
86717
+ });
86718
+ return { status: res.status, text: await res.text() };
86719
+ };
86720
+
86721
+ // src/commands/dev.ts
86692
86722
  var DEFAULT_DEBOUNCE_MS = 500;
86693
86723
  var DEFAULT_WATCH_SUBDIR = "src";
86694
86724
  function resolveAddonDir(arg) {
@@ -86720,12 +86750,12 @@ function watchRecursive(target, onEvent) {
86720
86750
  return { close: () => w.close(), recursive: false };
86721
86751
  }
86722
86752
  }
86723
- async function pushOnce(addonPath, opts, label) {
86753
+ async function pushOnce(addonPath, getOpts, label) {
86724
86754
  const start = Date.now();
86725
86755
  console.log(`
86726
86756
  [camstack dev] \u2192 ${label}\u2026`);
86727
86757
  try {
86728
- await deployAddon(addonPath, opts);
86758
+ await deployAddon(addonPath, await getOpts());
86729
86759
  const took = ((Date.now() - start) / 1e3).toFixed(1);
86730
86760
  console.log(`[camstack dev] \u2713 Synced in ${took}s`);
86731
86761
  } catch (err) {
@@ -86754,12 +86784,14 @@ async function runDev(args) {
86754
86784
  });
86755
86785
  const addonArg = positionals[0] ?? ".";
86756
86786
  const addonDir = resolveAddonDir(addonArg);
86757
- const creds = resolveCredentials({
86787
+ const credentialInputs = {
86758
86788
  ...typeof values.server === "string" ? { server: values.server } : {},
86759
86789
  ...typeof values.token === "string" ? { token: values.token } : {},
86760
86790
  ...typeof values["token-file"] === "string" ? { tokenFile: values["token-file"] } : {},
86761
- loadSession: loadSession2
86762
- });
86791
+ loadSession: loadSession2,
86792
+ transport: cliHubTransport
86793
+ };
86794
+ const creds = await resolveFreshCredentials(credentialInputs);
86763
86795
  if (creds.warning) console.error(`[camstack dev] Warning: ${creds.warning}`);
86764
86796
  if (creds.source === "session" && creds.server) touchSession(creds.server);
86765
86797
  const serverUrl = creds.server;
@@ -86778,6 +86810,10 @@ async function runDev(args) {
86778
86810
  buildMode: values["no-build"] === true ? "skip" : "auto",
86779
86811
  ...typeof values["build-script"] === "string" ? { buildScript: values["build-script"] } : {}
86780
86812
  };
86813
+ const freshOpts = async () => ({
86814
+ ...deployOpts,
86815
+ token: (await resolveFreshCredentials(credentialInputs)).token
86816
+ });
86781
86817
  const watchOverride = typeof values["watch-path"] === "string" ? values["watch-path"] : void 0;
86782
86818
  const watchTarget = watchOverride ? path7.resolve(addonDir, watchOverride) : fs7.existsSync(path7.join(addonDir, DEFAULT_WATCH_SUBDIR)) ? path7.join(addonDir, DEFAULT_WATCH_SUBDIR) : addonDir;
86783
86819
  if (!fs7.existsSync(watchTarget)) {
@@ -86791,7 +86827,7 @@ async function runDev(args) {
86791
86827
  );
86792
86828
  clack3.log.info(`Debounce: ${debounceMs}ms`);
86793
86829
  if (values["no-initial-push"] !== true) {
86794
- await pushOnce(addonDir, deployOpts, "Initial sync");
86830
+ await pushOnce(addonDir, freshOpts, "Initial sync");
86795
86831
  } else {
86796
86832
  console.log("[camstack dev] Skipping initial push (--no-initial-push). Waiting for changes\u2026");
86797
86833
  }
@@ -86807,7 +86843,7 @@ async function runDev(args) {
86807
86843
  }
86808
86844
  pushing = true;
86809
86845
  try {
86810
- await pushOnce(addonDir, deployOpts, "File change detected \u2014 rebuilding");
86846
+ await pushOnce(addonDir, freshOpts, "File change detected \u2014 rebuilding");
86811
86847
  } finally {
86812
86848
  pushing = false;
86813
86849
  if (pending) {
@@ -86843,7 +86879,7 @@ import * as clack4 from "@clack/prompts";
86843
86879
  import {
86844
86880
  loadSession as loadSession3,
86845
86881
  touchSession as touchSession2,
86846
- resolveCredentials as resolveCredentials2,
86882
+ resolveFreshCredentials as resolveFreshCredentials2,
86847
86883
  withAuthHint as withAuthHint3
86848
86884
  } from "@camstack/develop";
86849
86885
  var POLL_INTERVAL_MS2 = 2e3;
@@ -86907,12 +86943,14 @@ async function runWatch(args) {
86907
86943
  allowPositionals: true
86908
86944
  });
86909
86945
  const addonArg = positionals[0] ?? ".";
86910
- const creds = resolveCredentials2({
86946
+ const credentialInputs = {
86911
86947
  ...typeof values.server === "string" ? { server: values.server } : {},
86912
86948
  ...typeof values.token === "string" ? { token: values.token } : {},
86913
86949
  ...typeof values["token-file"] === "string" ? { tokenFile: values["token-file"] } : {},
86914
- loadSession: loadSession3
86915
- });
86950
+ loadSession: loadSession3,
86951
+ transport: cliHubTransport
86952
+ };
86953
+ const creds = await resolveFreshCredentials2(credentialInputs);
86916
86954
  if (creds.warning) console.error(`[camstack watch] Warning: ${creds.warning}`);
86917
86955
  if (creds.source === "session" && creds.server) touchSession2(creds.server);
86918
86956
  const serverUrl = creds.server;
@@ -86951,7 +86989,14 @@ async function runWatch(args) {
86951
86989
  process.on("SIGTERM", shutdown);
86952
86990
  while (!stop) {
86953
86991
  try {
86954
- const entries2 = await fetchLogs(serverUrl, token, addonId, LOG_BATCH_LIMIT, creds.source);
86992
+ const fresh = await resolveFreshCredentials2(credentialInputs);
86993
+ const entries2 = await fetchLogs(
86994
+ serverUrl,
86995
+ fresh.token,
86996
+ addonId,
86997
+ LOG_BATCH_LIMIT,
86998
+ creds.source
86999
+ );
86955
87000
  const sorted = [...entries2].toSorted((a, b) => {
86956
87001
  const ta = typeof a.timestamp === "number" ? a.timestamp : new Date(a.timestamp).getTime();
86957
87002
  const tb = typeof b.timestamp === "number" ? b.timestamp : new Date(b.timestamp).getTime();
@@ -87423,7 +87468,7 @@ function buildCommands() {
87423
87468
  " -p, --password <pwd> Password (skips prompt \u2014 prefer letting the CLI prompt)",
87424
87469
  ` --token-name <name> Ignored (kept for scripts); login uses your hub session JWT`,
87425
87470
  " --admin Ignored (kept for muscle memory); admin follows your account role",
87426
- " --admin-days <n> Ignored (kept for muscle memory); no separate admin-token mint"
87471
+ " --admin-days <n> Ignored (kept for muscle memory); one login, one token format"
87427
87472
  ].join("\n")
87428
87473
  },
87429
87474
  {
@@ -87434,7 +87479,7 @@ function buildCommands() {
87434
87479
  "Usage: camstack discover [options]",
87435
87480
  "",
87436
87481
  "Lists the hubs `camstack login` would offer: those announcing _camstack._tcp over",
87437
- " mDNS plus saved sessions, admin-token files, $CAMSTACK_HUB_URL and scratchpad/.hub-url,",
87482
+ " mDNS plus saved sessions, $CAMSTACK_HUB_URL and scratchpad/.hub-url,",
87438
87483
  " reachable ones only, each with its source. Agents never announce this service.",
87439
87484
  "",
87440
87485
  "Options:",
@@ -87711,7 +87756,7 @@ async function runWhoami(args) {
87711
87756
  const server = stringOpt(parsed.values, "server");
87712
87757
  await whoamiCommand(server ? { server: normalizeHubAddress3(server) } : {});
87713
87758
  }
87714
- function resolveClosureDeployOpts(values) {
87759
+ async function resolveClosureDeployOpts(values) {
87715
87760
  const nodeId = stringOpt(values, "node");
87716
87761
  const cluster = boolOpt(values, "cluster") || boolOpt(values, "all");
87717
87762
  if (cluster && nodeId) {
@@ -87719,7 +87764,7 @@ function resolveClosureDeployOpts(values) {
87719
87764
  process.exit(1);
87720
87765
  }
87721
87766
  const version2 = stringOpt(values, "version");
87722
- const creds = resolveCommandCredentials(values);
87767
+ const creds = await resolveCommandCredentials(values);
87723
87768
  return {
87724
87769
  serverUrl: creds.server ?? DEFAULT_SERVER_URL,
87725
87770
  token: creds.token,
@@ -87757,7 +87802,7 @@ async function runDeploy(args) {
87757
87802
  console.error("[camstack] Error: --closure does not take addon --build / --no-build flags.");
87758
87803
  process.exit(1);
87759
87804
  }
87760
- await deployServer(resolveClosureDeployOpts(parsed.values));
87805
+ await deployServer(await resolveClosureDeployOpts(parsed.values));
87761
87806
  return;
87762
87807
  }
87763
87808
  if (stringOpt(parsed.values, "version")) {
@@ -87765,7 +87810,7 @@ async function runDeploy(args) {
87765
87810
  process.exit(1);
87766
87811
  }
87767
87812
  const addonPath = parsed.positionals[0] ?? ".";
87768
- const creds = resolveCommandCredentials(parsed.values);
87813
+ const creds = await resolveCommandCredentials(parsed.values);
87769
87814
  const server = creds.server ?? DEFAULT_SERVER_URL;
87770
87815
  const token = creds.token;
87771
87816
  const nodeId = stringOpt(parsed.values, "node");
@@ -87817,19 +87862,20 @@ async function runDeployServer(args) {
87817
87862
  return;
87818
87863
  }
87819
87864
  console.log("[camstack] note: `deploy-server` is an alias of `deploy --closure`");
87820
- await deployServer(resolveClosureDeployOpts(parsed.values));
87865
+ await deployServer(await resolveClosureDeployOpts(parsed.values));
87821
87866
  }
87822
87867
  var DEFAULT_SERVER_URL = "https://localhost:4443";
87823
- function resolveCommandCredentials(values) {
87868
+ async function resolveCommandCredentials(values) {
87824
87869
  const server = stringOpt(values, "server");
87825
87870
  const token = stringOpt(values, "token");
87826
87871
  const tokenFile = stringOpt(values, "token-file");
87827
87872
  try {
87828
- const creds = resolveCredentials3({
87873
+ const creds = await resolveFreshCredentials3({
87829
87874
  ...server ? { server } : {},
87830
87875
  ...token ? { token } : {},
87831
87876
  ...tokenFile ? { tokenFile } : {},
87832
- loadSession: loadSession4
87877
+ loadSession: loadSession4,
87878
+ transport: cliHubTransport
87833
87879
  });
87834
87880
  if (creds.warning) console.error(`[camstack] Warning: ${creds.warning}`);
87835
87881
  if (creds.source === "session" && creds.server) touchSession3(creds.server);