archal 0.9.7 → 0.9.9

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 (39) hide show
  1. package/README.md +163 -93
  2. package/bin/archal.cjs +3 -3
  3. package/dist/index.cjs +82301 -0
  4. package/dist/index.d.cts +1 -0
  5. package/dist/seed/dynamic-generator.cjs +45640 -0
  6. package/dist/seed/dynamic-generator.d.cts +67 -0
  7. package/dist/vitest/chunk-RKYS44AS.js +2216 -0
  8. package/dist/vitest/chunk-YJICENME.js +1230 -0
  9. package/dist/vitest/chunk-YV6BH6DO.js +45974 -0
  10. package/dist/vitest/index.cjs +51963 -0
  11. package/dist/vitest/index.d.ts +398 -0
  12. package/dist/vitest/index.js +2669 -0
  13. package/dist/vitest/runtime/hosted-session-reaper.cjs +29349 -0
  14. package/dist/vitest/runtime/hosted-session-reaper.d.ts +2 -0
  15. package/dist/vitest/runtime/hosted-session-reaper.js +58 -0
  16. package/dist/vitest/runtime/setup-files.d.ts +2 -0
  17. package/dist/vitest/runtime/setup-files.js +27 -0
  18. package/dist/vitest/src-JGHX6UKK.js +94 -0
  19. package/package.json +19 -22
  20. package/twin-assets/discord/fidelity.json +113 -0
  21. package/twin-assets/discord/tools.json +1953 -0
  22. package/twin-assets/github/fidelity.json +13 -0
  23. package/twin-assets/github/tools.json +21818 -0
  24. package/twin-assets/google-workspace/fidelity.json +19 -0
  25. package/twin-assets/google-workspace/tools.json +10191 -0
  26. package/twin-assets/jira/fidelity.json +40 -0
  27. package/twin-assets/jira/tools.json +17387 -0
  28. package/twin-assets/linear/fidelity.json +18 -0
  29. package/twin-assets/linear/tools.json +6496 -0
  30. package/twin-assets/ramp/fidelity.json +22 -0
  31. package/twin-assets/ramp/tools.json +2610 -0
  32. package/twin-assets/slack/fidelity.json +20 -0
  33. package/twin-assets/slack/tools.json +7301 -0
  34. package/twin-assets/stripe/fidelity.json +22 -0
  35. package/twin-assets/stripe/tools.json +15284 -0
  36. package/twin-assets/supabase/fidelity.json +13 -0
  37. package/twin-assets/supabase/tools.json +2973 -0
  38. package/dist/vitest.d.ts +0 -1
  39. package/dist/vitest.js +0 -23
@@ -0,0 +1,1230 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error('Dynamic require of "' + x + '" is not supported');
12
+ });
13
+ var __commonJS = (cb, mod) => function __require2() {
14
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
15
+ };
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
20
+ var __copyProps = (to, from, except, desc) => {
21
+ if (from && typeof from === "object" || typeof from === "function") {
22
+ for (let key of __getOwnPropNames(from))
23
+ if (!__hasOwnProp.call(to, key) && key !== except)
24
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
25
+ }
26
+ return to;
27
+ };
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
29
+ // If the importer is in node compatibility mode or this is not an ESM
30
+ // file that has been converted to a CommonJS file using a Babel-
31
+ // compatible transform (i.e. "__esModule" has not been set), then set
32
+ // "default" to the CommonJS "module.exports" for node compatibility.
33
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
34
+ mod
35
+ ));
36
+
37
+ // ../node-auth/src/constants.ts
38
+ var CREDENTIALS_FILE = "credentials.json";
39
+ var CREDENTIALS_KEY_FILE = "credentials.key";
40
+ var AUTH_TOKEN_ENV_VAR = "ARCHAL_TOKEN";
41
+ var TOKEN_ENCRYPTION_PREFIX = "enc-v1";
42
+ var CREDENTIALS_MASTER_KEY_ENV_VAR = "ARCHAL_CREDENTIALS_MASTER_KEY";
43
+ var KEYCHAIN_SERVICE = "archal-cli";
44
+ var KEYCHAIN_ACCOUNT = "credentials-master-key";
45
+ var HOSTED_DEFAULT_AUTH_BASE_URL = "https://www.archal.ai";
46
+ var HOSTED_DEFAULT_API_BASE_URL = "https://api.archal.ai";
47
+ var HOSTED_DEFAULT_RUNTIME_BASE_URL = "https://api.archal.ai";
48
+ var STRICT_ENDPOINTS_ENV_VAR = "ARCHAL_STRICT_ENDPOINTS";
49
+ var REQUEST_TIMEOUT_MS = 8e3;
50
+ var ENV_TOKEN_FALLBACK_TTL_SECONDS = 24 * 60 * 60;
51
+ var AUTH_RETRY_OPTIONS = {
52
+ maxRetries: 2,
53
+ timeoutMs: REQUEST_TIMEOUT_MS,
54
+ retryableStatusCodes: /* @__PURE__ */ new Set([429, 502, 503, 504]),
55
+ backoffMs: [500, 1500],
56
+ label: "auth"
57
+ };
58
+
59
+ // ../node-auth/src/types.ts
60
+ function isPlan(value) {
61
+ return value === "free" || value === "pro" || value === "enterprise";
62
+ }
63
+
64
+ // ../node-auth/src/errors.ts
65
+ var ExpiredEnvTokenError = class extends Error {
66
+ constructor(envVar = AUTH_TOKEN_ENV_VAR) {
67
+ super(`${envVar} is expired. Remove it or replace it with a valid token.`);
68
+ this.name = "ExpiredEnvTokenError";
69
+ Object.setPrototypeOf(this, new.target.prototype);
70
+ }
71
+ };
72
+ function errorMessage(error) {
73
+ return error instanceof Error ? error.message : String(error);
74
+ }
75
+
76
+ // ../node-auth/src/credential-store.ts
77
+ import { spawnSync } from "child_process";
78
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto";
79
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
80
+ import { homedir, tmpdir } from "os";
81
+ import { dirname, join } from "path";
82
+ var KEYCHAIN_COMMAND_TIMEOUT_MS = 1500;
83
+ var ARCHAL_DIR_NAME = ".archal";
84
+ var TEST_SANDBOX_ID_ENV_VAR = "ARCHAL_TEST_SANDBOX_ID";
85
+ function debug(message) {
86
+ if (process.env["ARCHAL_DEBUG"] !== "1") {
87
+ return;
88
+ }
89
+ process.stderr.write(`[archal-node-auth] ${message}
90
+ `);
91
+ }
92
+ function getWarn(options) {
93
+ return options?.warn ?? console.warn;
94
+ }
95
+ function isExpired(expiresAt) {
96
+ return expiresAt <= Math.floor(Date.now() / 1e3);
97
+ }
98
+ function isRunningUnderTests() {
99
+ return process.env["VITEST"] === "true" || process.env["VITEST_WORKER_ID"] !== void 0 || process.env["JEST_WORKER_ID"] !== void 0 || process.env["NODE_TEST_CONTEXT"] !== void 0;
100
+ }
101
+ function getRealArchalDir() {
102
+ return join(homedir(), ARCHAL_DIR_NAME);
103
+ }
104
+ function normalizeSandboxId(raw) {
105
+ const trimmed = raw.trim();
106
+ if (/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(trimmed)) {
107
+ return trimmed;
108
+ }
109
+ return createHash("sha256").update(trimmed).digest("hex").slice(0, 16);
110
+ }
111
+ function getTestSandboxDir() {
112
+ const sharedSandboxId = process.env[TEST_SANDBOX_ID_ENV_VAR]?.trim();
113
+ if (sharedSandboxId) {
114
+ return join(tmpdir(), `archal-test-sandbox-${normalizeSandboxId(sharedSandboxId)}`);
115
+ }
116
+ const workerId = process.env["VITEST_WORKER_ID"] ?? process.env["JEST_WORKER_ID"] ?? String(process.pid);
117
+ return join(tmpdir(), `archal-test-sandbox-${workerId}-${process.pid}`);
118
+ }
119
+ var seededSandboxes = /* @__PURE__ */ new Set();
120
+ function seedSandboxFromRealHomeOnce(sandboxDir) {
121
+ try {
122
+ if (seededSandboxes.has(sandboxDir)) {
123
+ const credsIntact = existsSync(join(sandboxDir, CREDENTIALS_FILE));
124
+ if (credsIntact) return;
125
+ seededSandboxes.delete(sandboxDir);
126
+ }
127
+ const realDir = getRealArchalDir();
128
+ if (!existsSync(realDir)) {
129
+ seededSandboxes.add(sandboxDir);
130
+ return;
131
+ }
132
+ if (!existsSync(sandboxDir)) {
133
+ mkdirSync(sandboxDir, { recursive: true, mode: 448 });
134
+ }
135
+ for (const filename of [CREDENTIALS_FILE, CREDENTIALS_KEY_FILE]) {
136
+ const realPath = join(realDir, filename);
137
+ const sandboxPath = join(sandboxDir, filename);
138
+ if (!existsSync(realPath) || existsSync(sandboxPath)) continue;
139
+ const contents = readFileSync(realPath);
140
+ writeFileSync(sandboxPath, contents, { mode: 384 });
141
+ try {
142
+ chmodSync(sandboxPath, 384);
143
+ } catch {
144
+ }
145
+ }
146
+ seededSandboxes.add(sandboxDir);
147
+ } catch (err) {
148
+ debug(`Sandbox seed skipped: ${errorMessage(err)}`);
149
+ }
150
+ }
151
+ function getArchalDir() {
152
+ const explicit = process.env["ARCHAL_HOME"];
153
+ if (explicit) {
154
+ return explicit;
155
+ }
156
+ if (isRunningUnderTests()) {
157
+ const sandbox = getTestSandboxDir();
158
+ seedSandboxFromRealHomeOnce(sandbox);
159
+ return sandbox;
160
+ }
161
+ return getRealArchalDir();
162
+ }
163
+ function ensureArchalDir() {
164
+ const dir = getArchalDir();
165
+ if (!existsSync(dir)) {
166
+ mkdirSync(dir, { recursive: true });
167
+ debug(`Created archal directory at ${dir}`);
168
+ }
169
+ return dir;
170
+ }
171
+ function getCredentialsPath() {
172
+ return join(ensureArchalDir(), CREDENTIALS_FILE);
173
+ }
174
+ function ensureDir(dir) {
175
+ if (!existsSync(dir)) {
176
+ mkdirSync(dir, { recursive: true });
177
+ debug(`Created archal directory at ${dir}`);
178
+ }
179
+ return dir;
180
+ }
181
+ function getCredentialsPathForDir(dir) {
182
+ return join(dir, CREDENTIALS_FILE);
183
+ }
184
+ function getCredentialsKeyPathForDir(dir) {
185
+ return join(dir, CREDENTIALS_KEY_FILE);
186
+ }
187
+ function readCredentialsKeyFromEnv() {
188
+ const raw = process.env[CREDENTIALS_MASTER_KEY_ENV_VAR]?.trim();
189
+ if (!raw) {
190
+ return null;
191
+ }
192
+ if (/^[a-fA-F0-9]{64}$/.test(raw)) {
193
+ return Buffer.from(raw, "hex");
194
+ }
195
+ try {
196
+ const decoded = Buffer.from(raw, "base64");
197
+ if (decoded.length === 32) {
198
+ return decoded;
199
+ }
200
+ } catch (err) {
201
+ debug(`Base64 master key decode failed: ${errorMessage(err)}`);
202
+ }
203
+ return createHash("sha256").update(raw, "utf8").digest();
204
+ }
205
+ function readCredentialsKeyFromMacKeychain() {
206
+ if (process.platform !== "darwin") return null;
207
+ try {
208
+ const result = spawnSync(
209
+ "security",
210
+ ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", KEYCHAIN_ACCOUNT, "-w"],
211
+ { encoding: "utf-8", timeout: KEYCHAIN_COMMAND_TIMEOUT_MS }
212
+ );
213
+ if (!result || result.error || result.status !== 0 || typeof result.stdout !== "string" || result.stdout.length === 0) {
214
+ if (result?.error) {
215
+ debug(`Keychain lookup failed: ${result.error.message}`);
216
+ }
217
+ return null;
218
+ }
219
+ const raw = result.stdout.trim();
220
+ if (!/^[a-fA-F0-9]{64}$/.test(raw)) {
221
+ return null;
222
+ }
223
+ return Buffer.from(raw, "hex");
224
+ } catch (err) {
225
+ debug(`Keychain lookup threw: ${errorMessage(err)}`);
226
+ return null;
227
+ }
228
+ }
229
+ function readCredentialsKeyFromFile(dir = getArchalDir()) {
230
+ const keyPath = getCredentialsKeyPathForDir(dir);
231
+ if (!existsSync(keyPath)) {
232
+ return null;
233
+ }
234
+ try {
235
+ const raw = readFileSync(keyPath, "utf-8").trim();
236
+ const key = Buffer.from(raw, "hex");
237
+ if (key.length === 32) {
238
+ return key;
239
+ }
240
+ } catch (err) {
241
+ debug(`Failed to read credentials key file: ${errorMessage(err)}`);
242
+ }
243
+ return null;
244
+ }
245
+ function collectCredentialKeysForDecrypt(dir = getArchalDir()) {
246
+ const keys = [];
247
+ for (const candidate of [
248
+ readCredentialsKeyFromEnv(),
249
+ readCredentialsKeyFromMacKeychain(),
250
+ readCredentialsKeyFromFile(dir)
251
+ ]) {
252
+ if (!candidate) continue;
253
+ if (!keys.some((entry) => entry.equals(candidate))) {
254
+ keys.push(candidate);
255
+ }
256
+ }
257
+ return keys;
258
+ }
259
+ function writeCredentialsKeyToMacKeychain(key) {
260
+ if (process.platform !== "darwin") return false;
261
+ try {
262
+ const result = spawnSync(
263
+ "security",
264
+ [
265
+ "add-generic-password",
266
+ "-U",
267
+ "-s",
268
+ KEYCHAIN_SERVICE,
269
+ "-a",
270
+ KEYCHAIN_ACCOUNT,
271
+ "-w",
272
+ key.toString("hex")
273
+ ],
274
+ { encoding: "utf-8", timeout: KEYCHAIN_COMMAND_TIMEOUT_MS }
275
+ );
276
+ if (result?.error) {
277
+ debug(`Keychain write failed: ${result.error.message}`);
278
+ }
279
+ return !!result && !result.error && result.status === 0;
280
+ } catch (err) {
281
+ debug(`Keychain write threw: ${errorMessage(err)}`);
282
+ return false;
283
+ }
284
+ }
285
+ function getOrCreateCredentialsKey(dir = getArchalDir()) {
286
+ const envKey = readCredentialsKeyFromEnv();
287
+ if (envKey) {
288
+ return envKey;
289
+ }
290
+ const keychainKey = readCredentialsKeyFromMacKeychain();
291
+ if (keychainKey) {
292
+ return keychainKey;
293
+ }
294
+ const fileKey = readCredentialsKeyFromFile(dir);
295
+ if (fileKey) {
296
+ if (writeCredentialsKeyToMacKeychain(fileKey)) {
297
+ try {
298
+ unlinkSync(getCredentialsKeyPathForDir(dir));
299
+ } catch {
300
+ }
301
+ }
302
+ return fileKey;
303
+ }
304
+ const generated = randomBytes(32);
305
+ if (!writeCredentialsKeyToMacKeychain(generated)) {
306
+ ensureDir(dir);
307
+ writeFileSync(getCredentialsKeyPathForDir(dir), `${generated.toString("hex")}
308
+ `, {
309
+ encoding: "utf-8",
310
+ mode: 384
311
+ });
312
+ }
313
+ return generated;
314
+ }
315
+ function encryptToken(token, dir = getArchalDir()) {
316
+ const key = getOrCreateCredentialsKey(dir);
317
+ const iv = randomBytes(12);
318
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
319
+ const encrypted = Buffer.concat([cipher.update(token, "utf-8"), cipher.final()]);
320
+ return `${TOKEN_ENCRYPTION_PREFIX}:${iv.toString("hex")}:${cipher.getAuthTag().toString("hex")}:${encrypted.toString("hex")}`;
321
+ }
322
+ function decryptToken(ciphertext, dir = getArchalDir()) {
323
+ const parts = ciphertext.split(":");
324
+ if (parts.length !== 4 || parts[0] !== TOKEN_ENCRYPTION_PREFIX) {
325
+ return null;
326
+ }
327
+ const [, ivHex, tagHex, dataHex] = parts;
328
+ if (!ivHex || !tagHex || !dataHex) {
329
+ return null;
330
+ }
331
+ const iv = Buffer.from(ivHex, "hex");
332
+ const tag = Buffer.from(tagHex, "hex");
333
+ const data = Buffer.from(dataHex, "hex");
334
+ const keys = collectCredentialKeysForDecrypt(dir);
335
+ if (keys.length === 0) {
336
+ keys.push(getOrCreateCredentialsKey(dir));
337
+ }
338
+ for (const key of keys) {
339
+ try {
340
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
341
+ decipher.setAuthTag(tag);
342
+ return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf-8");
343
+ } catch {
344
+ }
345
+ }
346
+ debug("Token decryption failed with all available credential keys.");
347
+ return null;
348
+ }
349
+ function decodeJwtPayload(token) {
350
+ try {
351
+ const payloadPart = token.split(".")[1];
352
+ if (!payloadPart) return null;
353
+ const normalized = payloadPart.replace(/-/g, "+").replace(/_/g, "/");
354
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
355
+ return JSON.parse(Buffer.from(padded, "base64").toString("utf-8"));
356
+ } catch (err) {
357
+ debug(`JWT payload decode failed (expected for non-JWT tokens): ${errorMessage(err)}`);
358
+ return null;
359
+ }
360
+ }
361
+ function hasValidSelectedTwins(value) {
362
+ return value === void 0 || Array.isArray(value) && value.every((twin) => typeof twin === "string");
363
+ }
364
+ function resolveStoredToken(parsed, dir = getArchalDir()) {
365
+ if (typeof parsed.token === "string") {
366
+ const token = parsed.token.trim();
367
+ return { token: token.length > 0 ? token : null, source: "legacy" };
368
+ }
369
+ if (typeof parsed.accessToken === "string") {
370
+ const token = parsed.accessToken.trim();
371
+ return { token: token.length > 0 ? token : null, source: "legacy" };
372
+ }
373
+ if (typeof parsed.tokenEncrypted === "string") {
374
+ const token = decryptToken(parsed.tokenEncrypted, dir)?.trim() || null;
375
+ return { token: token?.length ? token : null, source: "encrypted" };
376
+ }
377
+ return { token: null, source: "legacy" };
378
+ }
379
+ function resolveStoredRefreshToken(parsed, dir = getArchalDir()) {
380
+ if (typeof parsed.refreshTokenEncrypted === "string") {
381
+ const refreshToken = decryptToken(parsed.refreshTokenEncrypted, dir)?.trim() || null;
382
+ if (refreshToken !== null) {
383
+ return { refreshToken, source: "encrypted" };
384
+ }
385
+ if (typeof parsed.refreshToken === "string") {
386
+ return { refreshToken: parsed.refreshToken.trim(), source: "legacy" };
387
+ }
388
+ return { refreshToken: null, source: "encrypted" };
389
+ }
390
+ if (typeof parsed.refreshToken === "string") {
391
+ return { refreshToken: parsed.refreshToken.trim(), source: "legacy" };
392
+ }
393
+ return { refreshToken: "", source: "none" };
394
+ }
395
+ function buildStoredCredentials(parsed, path, warn) {
396
+ const dir = dirname(path);
397
+ const { token, source: tokenSource } = resolveStoredToken(parsed, dir);
398
+ const { refreshToken, source: refreshTokenSource } = resolveStoredRefreshToken(parsed, dir);
399
+ if (token === null || refreshToken === null || parsed.refreshToken !== void 0 && typeof parsed.refreshToken !== "string" || parsed.refreshTokenEncrypted !== void 0 && typeof parsed.refreshTokenEncrypted !== "string" || !hasValidSelectedTwins(parsed.selectedTwins)) {
400
+ warn(
401
+ `Credentials file at ${path} has missing or invalid fields. Run \`archal login\` to re-authenticate.`
402
+ );
403
+ return null;
404
+ }
405
+ const { email, plan, expiresAt } = parsed;
406
+ if (typeof email !== "string" || !isPlan(plan) || typeof expiresAt !== "number") {
407
+ warn(
408
+ `Credentials file at ${path} has missing or invalid fields. Run \`archal login\` to re-authenticate.`
409
+ );
410
+ return null;
411
+ }
412
+ const creds = {
413
+ token,
414
+ refreshToken,
415
+ email,
416
+ plan,
417
+ selectedTwins: parsed.selectedTwins ?? [],
418
+ expiresAt
419
+ };
420
+ if (tokenSource === "legacy" || refreshTokenSource === "legacy") {
421
+ try {
422
+ writeCredentialsAtPath(path, creds);
423
+ } catch (err) {
424
+ debug(`Credential migration failed: ${errorMessage(err)}`);
425
+ }
426
+ }
427
+ return creds;
428
+ }
429
+ function readCredentialsFileAtPath(path, options) {
430
+ if (!existsSync(path)) {
431
+ return null;
432
+ }
433
+ try {
434
+ const warn = getWarn(options);
435
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
436
+ return buildStoredCredentials(parsed, path, warn);
437
+ } catch {
438
+ const warn = getWarn(options);
439
+ warn(
440
+ `Credentials file at ${path} exists but could not be parsed. Delete it and run \`archal login\` to re-authenticate.`
441
+ );
442
+ return null;
443
+ }
444
+ }
445
+ function readCredentialsFile(options) {
446
+ return readCredentialsFileAtPath(getCredentialsPath(), options);
447
+ }
448
+ function readCredentialsFromEnv(options) {
449
+ const raw = process.env[AUTH_TOKEN_ENV_VAR];
450
+ if (typeof raw !== "string") {
451
+ return null;
452
+ }
453
+ const token = raw.trim();
454
+ if (token.length === 0) {
455
+ return null;
456
+ }
457
+ const claims = decodeJwtPayload(token);
458
+ const nowSeconds = Math.floor(Date.now() / 1e3);
459
+ const email = claims !== null && typeof claims["email"] === "string" ? claims["email"] : "(from ARCHAL_TOKEN)";
460
+ let plan = claims !== null && isPlan(claims["plan"]) ? claims["plan"] : void 0;
461
+ if (!plan) {
462
+ if (claims !== null) {
463
+ getWarn(options)("JWT does not contain a valid plan claim - defaulting to free");
464
+ }
465
+ plan = "free";
466
+ }
467
+ const expiresAt = claims !== null && typeof claims["exp"] === "number" ? claims["exp"] : nowSeconds + ENV_TOKEN_FALLBACK_TTL_SECONDS;
468
+ if (expiresAt <= nowSeconds) {
469
+ if (options?.onExpiredEnvToken === "throw") {
470
+ throw new ExpiredEnvTokenError();
471
+ }
472
+ debug("Ignoring expired ARCHAL_TOKEN from environment.");
473
+ return null;
474
+ }
475
+ return {
476
+ token,
477
+ refreshToken: "",
478
+ email,
479
+ plan,
480
+ selectedTwins: [],
481
+ expiresAt
482
+ };
483
+ }
484
+ function getCredentials(options) {
485
+ const creds = readCredentialsFromEnv(options) ?? getStoredCredentials(options);
486
+ if (!creds) {
487
+ return null;
488
+ }
489
+ return isExpired(creds.expiresAt) ? null : creds;
490
+ }
491
+ function getStoredCredentials(options) {
492
+ const creds = readCredentialsFile(options);
493
+ if (!creds) {
494
+ return null;
495
+ }
496
+ if (options?.includeExpired) {
497
+ return creds;
498
+ }
499
+ return isExpired(creds.expiresAt) ? null : creds;
500
+ }
501
+ function getRealHomeStoredCredentials(options) {
502
+ const creds = readCredentialsFileAtPath(getCredentialsPathForDir(getRealArchalDir()), options);
503
+ if (!creds) {
504
+ return null;
505
+ }
506
+ if (options?.includeExpired) {
507
+ return creds;
508
+ }
509
+ return isExpired(creds.expiresAt) ? null : creds;
510
+ }
511
+ function writeCredentialsAtPath(path, creds) {
512
+ const dir = dirname(path);
513
+ ensureDir(dir);
514
+ const payload = {
515
+ email: creds.email,
516
+ plan: creds.plan,
517
+ selectedTwins: creds.selectedTwins,
518
+ expiresAt: creds.expiresAt,
519
+ tokenEncrypted: encryptToken(creds.token.trim(), dir),
520
+ refreshTokenEncrypted: creds.refreshToken.trim().length > 0 ? encryptToken(creds.refreshToken.trim(), dir) : void 0
521
+ };
522
+ const tmpPath = `${path}.${randomBytes(4).toString("hex")}.tmp`;
523
+ writeFileSync(tmpPath, `${JSON.stringify(payload, null, 2)}
524
+ `, {
525
+ encoding: "utf-8",
526
+ mode: 384
527
+ });
528
+ renameSync(tmpPath, path);
529
+ }
530
+ function saveCredentials(creds) {
531
+ writeCredentialsAtPath(getCredentialsPath(), creds);
532
+ }
533
+ function saveRealHomeCredentials(creds) {
534
+ writeCredentialsAtPath(getCredentialsPathForDir(getRealArchalDir()), creds);
535
+ }
536
+ function deleteCredentials() {
537
+ const path = getCredentialsPath();
538
+ if (!existsSync(path)) {
539
+ return false;
540
+ }
541
+ unlinkSync(path);
542
+ return true;
543
+ }
544
+
545
+ // ../node-auth/src/loopback.ts
546
+ import { isIP } from "net";
547
+ var CLI_LOOPBACK_CALLBACK_HOST = "127.0.0.1";
548
+ function normalizeHostname(hostname) {
549
+ const trimmed = hostname.trim().toLowerCase();
550
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
551
+ return trimmed.slice(1, -1);
552
+ }
553
+ return trimmed;
554
+ }
555
+ function isLoopbackHostname(hostname) {
556
+ const normalized = normalizeHostname(hostname);
557
+ if (normalized === "localhost" || normalized === "::1") {
558
+ return true;
559
+ }
560
+ if (isIP(normalized) === 4) {
561
+ return normalized.startsWith("127.");
562
+ }
563
+ return false;
564
+ }
565
+ function isLoopbackHttpUrl(rawUrl) {
566
+ try {
567
+ const parsed = new URL(rawUrl);
568
+ return parsed.protocol === "http:" && isLoopbackHostname(parsed.hostname);
569
+ } catch {
570
+ return false;
571
+ }
572
+ }
573
+ function isLoopbackRedirectUri(rawUrl) {
574
+ return isLoopbackHttpUrl(rawUrl);
575
+ }
576
+ function buildLoopbackCallbackUrl(port) {
577
+ return `http://${CLI_LOOPBACK_CALLBACK_HOST}:${port}/callback`;
578
+ }
579
+
580
+ // ../node-auth/src/url-resolver.ts
581
+ function getWarn2(options) {
582
+ return options?.warn ?? console.warn;
583
+ }
584
+ function stripUrlCredentials(url) {
585
+ try {
586
+ const parsed = new URL(url);
587
+ parsed.username = "";
588
+ parsed.password = "";
589
+ return parsed.toString();
590
+ } catch {
591
+ const atIndex = url.indexOf("@");
592
+ return atIndex >= 0 ? url.slice(atIndex + 1) : url;
593
+ }
594
+ }
595
+ function normalizeAuthUrl(value) {
596
+ if (typeof value !== "string") {
597
+ return null;
598
+ }
599
+ const trimmed = value.trim().replace(/\/+$/, "");
600
+ if (trimmed.length === 0) {
601
+ return null;
602
+ }
603
+ const normalized = trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed;
604
+ if (normalized.length === 0) {
605
+ return null;
606
+ }
607
+ const parsed = new URL(normalized);
608
+ if (parsed.protocol === "https:") {
609
+ return normalized;
610
+ }
611
+ const isLocalHttp = isLoopbackHttpUrl(normalized);
612
+ if (!isLocalHttp) {
613
+ throw new Error(
614
+ `Auth URL must use HTTPS (got ${stripUrlCredentials(normalized)}). HTTP is only allowed for loopback hosts.`
615
+ );
616
+ }
617
+ return normalized;
618
+ }
619
+ function parseBooleanEnvFlag(value) {
620
+ if (typeof value !== "string") {
621
+ return false;
622
+ }
623
+ switch (value.trim().toLowerCase()) {
624
+ case "1":
625
+ case "true":
626
+ case "yes":
627
+ case "on":
628
+ return true;
629
+ default:
630
+ return false;
631
+ }
632
+ }
633
+ function isStrictEndpointModeEnabled() {
634
+ return parseBooleanEnvFlag(process.env[STRICT_ENDPOINTS_ENV_VAR]);
635
+ }
636
+ function readConfiguredUrl(options, ...envKeys) {
637
+ for (const key of envKeys) {
638
+ try {
639
+ const value = normalizeAuthUrl(process.env[key]);
640
+ if (value) {
641
+ return value;
642
+ }
643
+ } catch (err) {
644
+ getWarn2(options)(
645
+ `Ignoring invalid ${key}: ${err instanceof Error ? err.message : String(err)}`
646
+ );
647
+ }
648
+ }
649
+ return null;
650
+ }
651
+ function getConfiguredAuthBaseUrl(options) {
652
+ const explicit = readConfiguredUrl(options, "ARCHAL_AUTH_URL", "ARCHAL_AUTH_BASE_URL");
653
+ if (explicit) {
654
+ return explicit;
655
+ }
656
+ return isStrictEndpointModeEnabled() ? null : HOSTED_DEFAULT_AUTH_BASE_URL;
657
+ }
658
+ function getConfiguredApiBaseUrl(options) {
659
+ const explicit = readConfiguredUrl(options, "ARCHAL_API_URL", "ARCHAL_API_BASE_URL");
660
+ if (explicit) {
661
+ return explicit;
662
+ }
663
+ return isStrictEndpointModeEnabled() ? null : HOSTED_DEFAULT_API_BASE_URL;
664
+ }
665
+ function getConfiguredRuntimeBaseUrl(options) {
666
+ const explicit = readConfiguredUrl(options, "ARCHAL_RUNTIME_URL");
667
+ if (explicit) {
668
+ return explicit;
669
+ }
670
+ return isStrictEndpointModeEnabled() ? null : HOSTED_DEFAULT_RUNTIME_BASE_URL;
671
+ }
672
+
673
+ // ../node-auth/src/request-metadata.ts
674
+ function buildAuthRequestHeaders(metadata, includeContentType = false) {
675
+ const headers = {};
676
+ if (includeContentType) {
677
+ headers["content-type"] = "application/json";
678
+ }
679
+ if (metadata?.userAgent) {
680
+ headers["user-agent"] = metadata.userAgent;
681
+ }
682
+ if (metadata?.cliVersion) {
683
+ headers["x-archal-cli-version"] = metadata.cliVersion;
684
+ }
685
+ return headers;
686
+ }
687
+
688
+ // ../billing-constants/src/index.ts
689
+ var PLAN_MAX_SESSION_TTL_SECONDS = {
690
+ free: 30 * 60,
691
+ // 30 minutes
692
+ pro: 60 * 60,
693
+ // 60 minutes
694
+ enterprise: 90 * 60
695
+ // 90 minutes
696
+ };
697
+ var MAX_ABSOLUTE_SESSION_LIFETIME_SECONDS = 4 * 60 * 60;
698
+ var FREE_PLAN_BASE_TWINS = [
699
+ "discord",
700
+ "github",
701
+ "google-workspace",
702
+ "jira",
703
+ "linear",
704
+ "ramp",
705
+ "slack",
706
+ "stripe",
707
+ "supabase"
708
+ ];
709
+ function normalizeTwinIds(twinIds) {
710
+ if (!twinIds) {
711
+ return [];
712
+ }
713
+ const normalized = /* @__PURE__ */ new Set();
714
+ for (const twinId of twinIds) {
715
+ const trimmed = twinId.trim();
716
+ if (trimmed.length > 0) {
717
+ normalized.add(trimmed);
718
+ }
719
+ }
720
+ return [...normalized];
721
+ }
722
+ function resolveKnownTwinSet(knownTwinIds) {
723
+ return new Set(normalizeTwinIds(knownTwinIds ?? FREE_PLAN_BASE_TWINS));
724
+ }
725
+ function getFreePlanEntitledTwins(selectedTwinIds, knownTwinIds) {
726
+ const knownTwinSet = resolveKnownTwinSet(knownTwinIds);
727
+ const selected = normalizeTwinIds(selectedTwinIds).filter((twinId) => knownTwinSet.has(twinId));
728
+ if (selected.length > 0) {
729
+ return new Set(selected);
730
+ }
731
+ const entitled = /* @__PURE__ */ new Set();
732
+ for (const twinId of FREE_PLAN_BASE_TWINS) {
733
+ if (knownTwinSet.has(twinId)) {
734
+ entitled.add(twinId);
735
+ }
736
+ }
737
+ return entitled;
738
+ }
739
+ function isTwinEntitled(plan, twinId, selectedTwinIds, knownTwinIds) {
740
+ if (plan !== "free") {
741
+ return true;
742
+ }
743
+ return getFreePlanEntitledTwins(selectedTwinIds, knownTwinIds).has(twinId);
744
+ }
745
+ var SCENARIO_USAGE_WINDOW_DAYS = 7;
746
+ var SCENARIO_USAGE_WINDOW_MS = SCENARIO_USAGE_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
747
+ var SCENARIO_USAGE_WINDOW_SECONDS = SCENARIO_USAGE_WINDOW_DAYS * 24 * 60 * 60;
748
+
749
+ // ../node-auth/src/entitlements.ts
750
+ function isTokenDerivedIdentity(email) {
751
+ return email === "(from ARCHAL_TOKEN)" || email === "(from token)";
752
+ }
753
+ function getWarn3(metadata) {
754
+ return metadata?.warn ?? console.warn;
755
+ }
756
+ function getAuthBaseUrl(metadata) {
757
+ const configured = metadata?.authBaseUrl?.trim();
758
+ if (configured) {
759
+ return configured.replace(/\/+$/, "");
760
+ }
761
+ return getConfiguredAuthBaseUrl();
762
+ }
763
+ function logRefreshFailure(creds, reason, authBaseUrl, metadata) {
764
+ const endpoint = `${authBaseUrl}/auth/me`;
765
+ const warn = getWarn3(metadata);
766
+ if (isTokenDerivedIdentity(creds.email)) {
767
+ warn(
768
+ `Could not verify token with ${endpoint} (${reason}). Using token without refreshed account metadata.`
769
+ );
770
+ return;
771
+ }
772
+ warn(
773
+ `Could not refresh account metadata from ${endpoint} (${reason}). Using cached credentials.`
774
+ );
775
+ }
776
+ async function readFailureDetail(response) {
777
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
778
+ if (!contentType.includes("application/json")) {
779
+ return null;
780
+ }
781
+ try {
782
+ const payload = await response.json();
783
+ const error = typeof payload.error === "string" ? payload.error.trim() : "";
784
+ const message = typeof payload.message === "string" ? payload.message.trim() : "";
785
+ if (error && message) return `${error}: ${message}`;
786
+ if (message) return message;
787
+ if (error) return error;
788
+ } catch {
789
+ return null;
790
+ }
791
+ return null;
792
+ }
793
+ async function validateTokenWithServer(creds, metadata) {
794
+ const authBaseUrl = getAuthBaseUrl(metadata);
795
+ if (!authBaseUrl) {
796
+ return {
797
+ ok: false,
798
+ code: "no_auth_url",
799
+ status: null,
800
+ reason: "no auth URL configured"
801
+ };
802
+ }
803
+ try {
804
+ const response = await fetch(`${authBaseUrl}/auth/me`, {
805
+ method: "GET",
806
+ headers: {
807
+ authorization: `Bearer ${creds.token}`,
808
+ ...buildAuthRequestHeaders(metadata, false)
809
+ },
810
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
811
+ });
812
+ if (!response.ok) {
813
+ const detail = await readFailureDetail(response);
814
+ return {
815
+ ok: false,
816
+ code: response.status === 401 ? "auth_rejected" : "http_error",
817
+ status: response.status,
818
+ reason: detail ? `HTTP ${response.status} ${detail}` : `HTTP ${response.status}`
819
+ };
820
+ }
821
+ let data;
822
+ try {
823
+ data = await response.json();
824
+ } catch {
825
+ return {
826
+ ok: false,
827
+ code: "invalid_payload",
828
+ status: null,
829
+ reason: "invalid response payload"
830
+ };
831
+ }
832
+ if (typeof data.email !== "string" || !isPlan(data.plan)) {
833
+ return {
834
+ ok: false,
835
+ code: "invalid_payload",
836
+ status: null,
837
+ reason: "invalid response payload"
838
+ };
839
+ }
840
+ const selectedTwins = Array.isArray(data.selectedTwinIds) ? data.selectedTwinIds.filter((id) => typeof id === "string") : creds.selectedTwins;
841
+ const updated = {
842
+ ...creds,
843
+ email: data.email,
844
+ plan: data.plan,
845
+ selectedTwins
846
+ };
847
+ if ((updated.email !== creds.email || updated.plan !== creds.plan || JSON.stringify(updated.selectedTwins) !== JSON.stringify(creds.selectedTwins)) && !process.env[AUTH_TOKEN_ENV_VAR]) {
848
+ saveCredentials(updated);
849
+ }
850
+ return { ok: true, credentials: updated };
851
+ } catch (error) {
852
+ return {
853
+ ok: false,
854
+ code: "network_error",
855
+ status: null,
856
+ reason: errorMessage(error)
857
+ };
858
+ }
859
+ }
860
+ async function refreshAuthFromServer(creds, metadata) {
861
+ const authBaseUrl = getAuthBaseUrl(metadata);
862
+ if (!authBaseUrl) return creds;
863
+ const result = await refreshAuthFromServerWithValidation(creds, metadata);
864
+ if (!result.validation.ok) {
865
+ logRefreshFailure(creds, result.validation.reason, authBaseUrl, metadata);
866
+ if (result.validation.code === "auth_rejected") {
867
+ throw new Error("Authentication failed. Your token may be expired or invalid. Run: archal login");
868
+ }
869
+ }
870
+ return result.credentials;
871
+ }
872
+ async function refreshAuthFromServerWithValidation(creds, metadata) {
873
+ const result = await validateTokenWithServer(creds, metadata);
874
+ if (result.ok) {
875
+ return {
876
+ credentials: result.credentials,
877
+ validation: result
878
+ };
879
+ }
880
+ return {
881
+ credentials: creds,
882
+ validation: result
883
+ };
884
+ }
885
+ async function resolveWhoamiAuthState(creds, metadata) {
886
+ if (!creds) {
887
+ return { loggedIn: false };
888
+ }
889
+ const refreshed = await refreshAuthFromServerWithValidation(creds, metadata);
890
+ return {
891
+ loggedIn: true,
892
+ credentials: refreshed.credentials,
893
+ validation: refreshed.validation
894
+ };
895
+ }
896
+ function isEntitled(creds, twinName) {
897
+ return isTwinEntitled(creds.plan, twinName, creds.selectedTwins);
898
+ }
899
+ function getJwtExpiry(token) {
900
+ const claims = decodeJwtPayload(token);
901
+ if (claims === null) return null;
902
+ return typeof claims["exp"] === "number" ? claims["exp"] : null;
903
+ }
904
+
905
+ // ../node-auth/src/fetch-with-retry.ts
906
+ function sleep(ms, signal) {
907
+ if (!signal) {
908
+ return new Promise((resolve) => {
909
+ setTimeout(resolve, ms);
910
+ });
911
+ }
912
+ const abortSignal = signal;
913
+ if (abortSignal.aborted) {
914
+ return Promise.reject(abortSignal.reason instanceof Error ? abortSignal.reason : new DOMException("The operation was aborted.", "AbortError"));
915
+ }
916
+ return new Promise((resolve, reject) => {
917
+ const timeout = setTimeout(() => {
918
+ abortSignal.removeEventListener("abort", onAbort);
919
+ resolve();
920
+ }, ms);
921
+ function onAbort() {
922
+ clearTimeout(timeout);
923
+ reject(abortSignal.reason instanceof Error ? abortSignal.reason : new DOMException("The operation was aborted.", "AbortError"));
924
+ }
925
+ abortSignal.addEventListener("abort", onAbort, { once: true });
926
+ });
927
+ }
928
+ function resolveBackoffDelay(backoffMs, attempt, fallbackMs) {
929
+ const indexed = backoffMs[attempt];
930
+ if (typeof indexed === "number" && Number.isFinite(indexed) && indexed >= 0) {
931
+ return indexed;
932
+ }
933
+ const last = backoffMs.length > 0 ? backoffMs[backoffMs.length - 1] : void 0;
934
+ if (typeof last === "number" && Number.isFinite(last) && last >= 0) {
935
+ return last;
936
+ }
937
+ return fallbackMs;
938
+ }
939
+ async function fetchWithRetry(url, init, options) {
940
+ const maxRetries = options?.maxRetries ?? AUTH_RETRY_OPTIONS.maxRetries;
941
+ const timeoutMs = options?.timeoutMs ?? AUTH_RETRY_OPTIONS.timeoutMs;
942
+ const retryableStatusCodes = options?.retryableStatusCodes instanceof Set ? options.retryableStatusCodes : new Set(options?.retryableStatusCodes ?? AUTH_RETRY_OPTIONS.retryableStatusCodes);
943
+ const backoffMs = options?.backoffMs ?? AUTH_RETRY_OPTIONS.backoffMs;
944
+ const fetchImpl = options?.fetchImpl ?? fetch;
945
+ const externalSignal = options?.signal;
946
+ let lastError;
947
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
948
+ if (externalSignal?.aborted) {
949
+ throw externalSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
950
+ }
951
+ try {
952
+ const requestSignal = externalSignal ? AbortSignal.any([externalSignal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs);
953
+ const response = await fetchImpl(url, {
954
+ ...init,
955
+ signal: requestSignal
956
+ });
957
+ if (response.ok || !retryableStatusCodes.has(response.status) || attempt >= maxRetries) {
958
+ return response;
959
+ }
960
+ lastError = new Error(`HTTP ${response.status}`);
961
+ } catch (error) {
962
+ lastError = error;
963
+ if (attempt >= maxRetries) {
964
+ break;
965
+ }
966
+ }
967
+ if (attempt < maxRetries) {
968
+ const delay = resolveBackoffDelay(backoffMs, attempt, 3e3);
969
+ await sleep(delay, externalSignal);
970
+ }
971
+ }
972
+ throw lastError instanceof Error ? lastError : new Error(errorMessage(lastError));
973
+ }
974
+
975
+ // ../node-auth/src/token-exchange.ts
976
+ function isCliTokenExchangeResponse(value) {
977
+ if (!value || typeof value !== "object") return false;
978
+ const data = value;
979
+ return typeof data["accessToken"] === "string" && typeof data["refreshToken"] === "string" && typeof data["email"] === "string" && isPlan(data["plan"]) && typeof data["expiresAt"] === "number";
980
+ }
981
+ function isCliRefreshResponse(value) {
982
+ if (!value || typeof value !== "object") return false;
983
+ const data = value;
984
+ return typeof data["accessToken"] === "string" && typeof data["refreshToken"] === "string" && typeof data["expiresAt"] === "number";
985
+ }
986
+ function isCliDeviceAuthStartPayload(value) {
987
+ if (!value || typeof value !== "object") return false;
988
+ const data = value;
989
+ return typeof data["device_code"] === "string" && typeof data["user_code"] === "string" && typeof data["verification_uri"] === "string" && typeof data["verification_uri_complete"] === "string" && typeof data["expires_in"] === "number" && typeof data["interval"] === "number";
990
+ }
991
+ function isCliDeviceAuthApprovedPayload(value) {
992
+ if (!value || typeof value !== "object") return false;
993
+ const data = value;
994
+ return typeof data["access_token"] === "string" && typeof data["token_type"] === "string" && typeof data["expires_in"] === "number";
995
+ }
996
+ function parseCliDeviceAuthErrorPayload(value) {
997
+ if (!value || typeof value !== "object") return null;
998
+ const data = value;
999
+ const error = typeof data.error === "string" ? data.error.trim() : "";
1000
+ if (!error) return null;
1001
+ const interval = typeof data.interval === "number" ? data.interval : void 0;
1002
+ return { error, interval };
1003
+ }
1004
+ function debug2(message) {
1005
+ if (process.env["ARCHAL_DEBUG"] !== "1") {
1006
+ return;
1007
+ }
1008
+ process.stderr.write(`[archal-node-auth] ${message}
1009
+ `);
1010
+ }
1011
+ function getAuthBaseUrl2(metadata) {
1012
+ const configured = metadata?.authBaseUrl?.trim();
1013
+ if (configured) {
1014
+ return configured.replace(/\/+$/, "");
1015
+ }
1016
+ return getConfiguredAuthBaseUrl();
1017
+ }
1018
+ async function readJsonPayload(response) {
1019
+ try {
1020
+ return await response.json();
1021
+ } catch {
1022
+ return null;
1023
+ }
1024
+ }
1025
+ async function exchangeCliAuthCode(input, metadata) {
1026
+ const authBaseUrl = getAuthBaseUrl2(metadata);
1027
+ if (!authBaseUrl) {
1028
+ throw new Error(
1029
+ "ARCHAL_AUTH_BASE_URL (or ARCHAL_AUTH_URL) is required for browser login when ARCHAL_STRICT_ENDPOINTS=1. Set ARCHAL_AUTH_BASE_URL and run `archal login` again."
1030
+ );
1031
+ }
1032
+ const response = await fetchWithRetry(
1033
+ `${authBaseUrl}/auth/cli/token`,
1034
+ {
1035
+ method: "POST",
1036
+ headers: buildAuthRequestHeaders(metadata, true),
1037
+ body: JSON.stringify(input)
1038
+ },
1039
+ AUTH_RETRY_OPTIONS
1040
+ );
1041
+ if (!response.ok) {
1042
+ throw new Error(`Login failed during code exchange (${response.status})`);
1043
+ }
1044
+ const payload = await response.json();
1045
+ if (!isCliTokenExchangeResponse(payload)) {
1046
+ throw new Error("Login failed: invalid token exchange response");
1047
+ }
1048
+ const selectedTwins = Array.isArray(payload.selectedTwinIds) ? payload.selectedTwinIds.filter((id) => typeof id === "string") : [];
1049
+ return {
1050
+ token: payload.accessToken,
1051
+ refreshToken: payload.refreshToken,
1052
+ email: payload.email,
1053
+ plan: payload.plan,
1054
+ selectedTwins,
1055
+ expiresAt: payload.expiresAt
1056
+ };
1057
+ }
1058
+ async function startCliDeviceAuth(metadata) {
1059
+ const authBaseUrl = getAuthBaseUrl2(metadata);
1060
+ if (!authBaseUrl) {
1061
+ throw new Error(
1062
+ "ARCHAL_AUTH_BASE_URL (or ARCHAL_AUTH_URL) is required for device login when ARCHAL_STRICT_ENDPOINTS=1. Set ARCHAL_AUTH_BASE_URL and run `archal login --device` again."
1063
+ );
1064
+ }
1065
+ const response = await fetchWithRetry(
1066
+ `${authBaseUrl}/auth/device`,
1067
+ {
1068
+ method: "POST",
1069
+ headers: buildAuthRequestHeaders(metadata, false)
1070
+ },
1071
+ AUTH_RETRY_OPTIONS
1072
+ );
1073
+ const payload = await readJsonPayload(response);
1074
+ if (!response.ok) {
1075
+ const error = parseCliDeviceAuthErrorPayload(payload);
1076
+ const detail = error ? `: ${error.error}` : "";
1077
+ throw new Error(`Device login failed to start (${response.status})${detail}`);
1078
+ }
1079
+ if (!isCliDeviceAuthStartPayload(payload)) {
1080
+ throw new Error("Device login failed: invalid device auth response");
1081
+ }
1082
+ return {
1083
+ deviceCode: payload.device_code,
1084
+ userCode: payload.user_code,
1085
+ verificationUri: payload.verification_uri,
1086
+ verificationUriComplete: payload.verification_uri_complete,
1087
+ expiresIn: payload.expires_in,
1088
+ interval: payload.interval
1089
+ };
1090
+ }
1091
+ async function pollCliDeviceAuth(deviceCode, metadata) {
1092
+ const authBaseUrl = getAuthBaseUrl2(metadata);
1093
+ if (!authBaseUrl) {
1094
+ throw new Error(
1095
+ "ARCHAL_AUTH_BASE_URL (or ARCHAL_AUTH_URL) is required for device login when ARCHAL_STRICT_ENDPOINTS=1. Set ARCHAL_AUTH_BASE_URL and run `archal login --device` again."
1096
+ );
1097
+ }
1098
+ const response = await fetchWithRetry(
1099
+ `${authBaseUrl}/auth/device/token`,
1100
+ {
1101
+ method: "POST",
1102
+ headers: buildAuthRequestHeaders(metadata, true),
1103
+ body: JSON.stringify({ device_code: deviceCode })
1104
+ },
1105
+ AUTH_RETRY_OPTIONS
1106
+ );
1107
+ const payload = await readJsonPayload(response);
1108
+ if (response.ok) {
1109
+ if (!isCliDeviceAuthApprovedPayload(payload)) {
1110
+ throw new Error("Device login failed: invalid poll response");
1111
+ }
1112
+ return {
1113
+ status: "approved",
1114
+ accessToken: payload.access_token,
1115
+ tokenType: payload.token_type,
1116
+ expiresIn: payload.expires_in
1117
+ };
1118
+ }
1119
+ const error = parseCliDeviceAuthErrorPayload(payload);
1120
+ if (error?.error === "slow_down") {
1121
+ return {
1122
+ status: "slow_down",
1123
+ interval: error.interval ?? 0
1124
+ };
1125
+ }
1126
+ if (error?.error === "authorization_pending" || error?.error === "expired_token" || error?.error === "invalid_device_code") {
1127
+ return { status: error.error };
1128
+ }
1129
+ const detail = error ? `: ${error.error}` : "";
1130
+ throw new Error(`Device login poll failed (${response.status})${detail}`);
1131
+ }
1132
+ async function refreshCliSession(creds, metadata) {
1133
+ if (!creds.refreshToken) {
1134
+ return null;
1135
+ }
1136
+ const authBaseUrl = getAuthBaseUrl2(metadata);
1137
+ if (!authBaseUrl) {
1138
+ return null;
1139
+ }
1140
+ const response = await fetchWithRetry(
1141
+ `${authBaseUrl}/auth/cli/refresh`,
1142
+ {
1143
+ method: "POST",
1144
+ headers: buildAuthRequestHeaders(metadata, true),
1145
+ body: JSON.stringify({ refreshToken: creds.refreshToken })
1146
+ },
1147
+ AUTH_RETRY_OPTIONS
1148
+ );
1149
+ if (!response.ok) {
1150
+ return null;
1151
+ }
1152
+ const payload = await response.json();
1153
+ if (!isCliRefreshResponse(payload)) {
1154
+ return null;
1155
+ }
1156
+ return {
1157
+ ...creds,
1158
+ token: payload.accessToken,
1159
+ refreshToken: payload.refreshToken,
1160
+ expiresAt: payload.expiresAt
1161
+ };
1162
+ }
1163
+ async function revokeCliSession(refreshToken, metadata) {
1164
+ const authBaseUrl = getAuthBaseUrl2(metadata);
1165
+ if (!authBaseUrl) {
1166
+ return;
1167
+ }
1168
+ try {
1169
+ await fetch(`${authBaseUrl}/auth/cli/revoke`, {
1170
+ method: "POST",
1171
+ headers: buildAuthRequestHeaders(metadata, true),
1172
+ body: JSON.stringify({ refreshToken }),
1173
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
1174
+ });
1175
+ } catch (error) {
1176
+ debug2(`Session revoke failed (best-effort): ${errorMessage(error)}`);
1177
+ }
1178
+ }
1179
+
1180
+ export {
1181
+ __require,
1182
+ __commonJS,
1183
+ __export,
1184
+ __toESM,
1185
+ CREDENTIALS_FILE,
1186
+ CREDENTIALS_KEY_FILE,
1187
+ AUTH_TOKEN_ENV_VAR,
1188
+ TOKEN_ENCRYPTION_PREFIX,
1189
+ CREDENTIALS_MASTER_KEY_ENV_VAR,
1190
+ KEYCHAIN_SERVICE,
1191
+ KEYCHAIN_ACCOUNT,
1192
+ HOSTED_DEFAULT_AUTH_BASE_URL,
1193
+ HOSTED_DEFAULT_API_BASE_URL,
1194
+ HOSTED_DEFAULT_RUNTIME_BASE_URL,
1195
+ STRICT_ENDPOINTS_ENV_VAR,
1196
+ REQUEST_TIMEOUT_MS,
1197
+ ENV_TOKEN_FALLBACK_TTL_SECONDS,
1198
+ AUTH_RETRY_OPTIONS,
1199
+ isPlan,
1200
+ ExpiredEnvTokenError,
1201
+ errorMessage,
1202
+ getArchalDir,
1203
+ decodeJwtPayload,
1204
+ getCredentials,
1205
+ getStoredCredentials,
1206
+ getRealHomeStoredCredentials,
1207
+ saveCredentials,
1208
+ saveRealHomeCredentials,
1209
+ deleteCredentials,
1210
+ CLI_LOOPBACK_CALLBACK_HOST,
1211
+ isLoopbackHostname,
1212
+ isLoopbackHttpUrl,
1213
+ isLoopbackRedirectUri,
1214
+ buildLoopbackCallbackUrl,
1215
+ getConfiguredAuthBaseUrl,
1216
+ getConfiguredApiBaseUrl,
1217
+ getConfiguredRuntimeBaseUrl,
1218
+ buildAuthRequestHeaders,
1219
+ validateTokenWithServer,
1220
+ refreshAuthFromServer,
1221
+ refreshAuthFromServerWithValidation,
1222
+ resolveWhoamiAuthState,
1223
+ isEntitled,
1224
+ getJwtExpiry,
1225
+ exchangeCliAuthCode,
1226
+ startCliDeviceAuth,
1227
+ pollCliDeviceAuth,
1228
+ refreshCliSession,
1229
+ revokeCliSession
1230
+ };