@fluid-app/fluid-cli 0.1.64 → 0.1.65

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.
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
2
  import { randomBytes } from "node:crypto";
3
+ import { dirname, join } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  //#region src/catalog/annotations.ts
6
6
  const ANNOTATION = Symbol.for("@fluid-app/fluid-cli.command-annotation");
@@ -192,7 +192,11 @@ const FLUID_API_ERROR = {
192
192
  },
193
193
  CODE_EXPIRED: {
194
194
  code: "CODE_EXPIRED",
195
- message: "Verification code has expired — please request a new one"
195
+ message: "Verification code has expired, was already used, or is locked after too many attempts — please request a new one"
196
+ },
197
+ RATE_LIMITED: {
198
+ code: "RATE_LIMITED",
199
+ message: "Too many sign-in requests — wait a minute and try again"
196
200
  },
197
201
  INVALID_CODE: {
198
202
  code: "INVALID_CODE",
@@ -242,15 +246,23 @@ async function validateToken(token) {
242
246
  }
243
247
  /**
244
248
  * Send a multi-factor authentication code to the given email address.
245
- * Always returns 201 from the API (anti-enumeration).
249
+ * The API answers 201 whether or not the address can sign in
250
+ * (anti-enumeration).
251
+ *
252
+ * With `fluidShop`, the server ties the code to the person's membership in
253
+ * that company, so redeeming it signs in to that company alone and, for an
254
+ * admin's emailed code, confirms a new signup.
246
255
  */
247
- async function sendMfa(email) {
256
+ async function sendMfa(email, options = {}) {
248
257
  const { signal, cleanup } = makeSignal();
249
258
  try {
250
259
  const response = await fetch(`${getFluidApiBase()}/api/authentication/send_mfa`, {
251
260
  method: "POST",
252
261
  headers: { "Content-Type": "application/json" },
253
- body: JSON.stringify({ email }),
262
+ body: JSON.stringify(options.fluidShop ? {
263
+ email,
264
+ fluid_shop: options.fluidShop
265
+ } : { email }),
254
266
  signal
255
267
  });
256
268
  if (response.status === 201) {
@@ -264,6 +276,7 @@ async function sendMfa(email) {
264
276
  });
265
277
  }
266
278
  const body = await response.text();
279
+ if (response.status === 429) return failure(createApiError(FLUID_API_ERROR.RATE_LIMITED, `HTTP 429: ${body}`));
267
280
  return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, `HTTP ${response.status}: ${body}`));
268
281
  } catch (err) {
269
282
  const message = err instanceof Error ? err.message : String(err);
@@ -274,16 +287,21 @@ async function sendMfa(email) {
274
287
  }
275
288
  /**
276
289
  * Confirm a multi-factor authentication code and retrieve company JWTs.
290
+ *
291
+ * `baseUrl` is the API the code was sent from; a sign-in finished in a
292
+ * later process passes the one it saved, not the current environment's.
277
293
  */
278
- async function confirmMfa(uuid, code) {
294
+ async function confirmMfa(uuid, code, options = {}) {
279
295
  const { signal, cleanup } = makeSignal();
296
+ const apiBase = options.baseUrl ?? getFluidApiBase();
280
297
  try {
281
- const response = await fetch(`${getFluidApiBase()}/api/authentication/confirm_mfa`, {
298
+ const response = await fetch(`${apiBase}/api/authentication/confirm_mfa`, {
282
299
  method: "POST",
283
300
  headers: { "Content-Type": "application/json" },
284
301
  body: JSON.stringify({
285
302
  uuid,
286
- multi_factor_authentication: { verification_code: code }
303
+ multi_factor_authentication: { verification_code: code },
304
+ ...options.fluidShop ? { fluid_shop: options.fluidShop } : {}
287
305
  }),
288
306
  signal
289
307
  });
@@ -297,7 +315,8 @@ async function confirmMfa(uuid, code) {
297
315
  id: c.id,
298
316
  name: c.name,
299
317
  shopName: c.shop_name,
300
- jwt: c.jwt
318
+ jwt: c.jwt,
319
+ ...c.auth_type ? { authType: c.auth_type } : {}
301
320
  }))
302
321
  });
303
322
  }
@@ -305,6 +324,7 @@ async function confirmMfa(uuid, code) {
305
324
  if (response.status === 410) return failure(createApiError(FLUID_API_ERROR.CODE_EXPIRED));
306
325
  if (response.status === 422) return failure(createApiError(FLUID_API_ERROR.INVALID_CODE));
307
326
  const body = await response.text();
327
+ if (response.status === 429) return failure(createApiError(FLUID_API_ERROR.RATE_LIMITED, `HTTP 429: ${body}`));
308
328
  return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, `HTTP ${response.status}: ${body}`));
309
329
  } catch (err) {
310
330
  const message = err instanceof Error ? err.message : String(err);
@@ -382,16 +402,6 @@ async function switchCompany(token, companyId, baseUrl) {
382
402
  }
383
403
  }
384
404
  //#endregion
385
- //#region src/config/types.ts
386
- function createDefaultConfig() {
387
- return {
388
- activeProfile: null,
389
- profiles: {},
390
- plugins: {},
391
- enabledPlugins: null
392
- };
393
- }
394
- //#endregion
395
405
  //#region src/config/paths.ts
396
406
  /**
397
407
  * XDG-compliant config directory resolution
@@ -416,6 +426,16 @@ function getConfigFilePath() {
416
426
  return join(getConfigDir(), "config.json");
417
427
  }
418
428
  //#endregion
429
+ //#region src/config/types.ts
430
+ function createDefaultConfig() {
431
+ return {
432
+ activeProfile: null,
433
+ profiles: {},
434
+ plugins: {},
435
+ enabledPlugins: null
436
+ };
437
+ }
438
+ //#endregion
419
439
  //#region src/config/config.ts
420
440
  /**
421
441
  * Read/write config.json with atomic writes and safe defaults
@@ -599,6 +619,6 @@ function listProfileNames() {
599
619
  return Object.keys(config.profiles);
600
620
  }
601
621
  //#endregion
602
- export { unwrapOr as A, isSuccess as C, tryCatch as D, success as E, walkCommands as F, describeCommand as I, rootCommand as M, visibleOptions as N, tryCatchAsync as O, visibleSubcommands as P, isNodeError as S, mapResult as T, validateToken as _, readConfig as a, isError as b, getConfigDir as c, FLUID_API_ERROR as d, confirmMfa as f, switchCompany as g, sendMfa as h, findProjectConfig as i, filterCatalog as j, unwrap as k, getConfigFilePath as l, getFluidApiBase as m, getAuthToken as n, updateConfig as o, fetchUserCompanies as p, listProfileNames as r, writeConfig as s, getActiveProfile as t, createDefaultConfig as u, failure as v, mapError as w, isFailure as x, getErrorMessage as y };
622
+ export { unwrapOr as A, isSuccess as C, tryCatch as D, success as E, walkCommands as F, describeCommand as I, rootCommand as M, visibleOptions as N, tryCatchAsync as O, visibleSubcommands as P, isNodeError as S, mapResult as T, validateToken as _, readConfig as a, isError as b, createDefaultConfig as c, FLUID_API_ERROR as d, confirmMfa as f, switchCompany as g, sendMfa as h, findProjectConfig as i, filterCatalog as j, unwrap as k, getConfigDir as l, getFluidApiBase as m, getAuthToken as n, updateConfig as o, fetchUserCompanies as p, listProfileNames as r, writeConfig as s, getActiveProfile as t, getConfigFilePath as u, failure as v, mapError as w, isFailure as x, getErrorMessage as y };
603
623
 
604
- //# sourceMappingURL=token-6SEVoGMw.mjs.map
624
+ //# sourceMappingURL=token-DnFYT9pe.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-DnFYT9pe.mjs","names":[],"sources":["../src/catalog/annotations.ts","../src/catalog/walk.ts","../src/utils/result.ts","../src/auth/fluid-api.ts","../src/config/paths.ts","../src/config/types.ts","../src/config/config.ts","../src/config/project-config.ts","../src/auth/token.ts"],"sourcesContent":["/**\n * What a command does to the API, for the command catalog.\n *\n * Commander has nowhere to keep this, so it rides on the command object\n * under a registered symbol. A plugin installed with its own copy of\n * @fluid-app/fluid-cli still writes to the same key the running CLI reads,\n * which a module-scoped WeakMap would not.\n */\n\nimport type { Command } from \"commander\";\n\nexport interface CommandEndpoint {\n readonly method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /** Path as the API documents it, e.g. `/api/domains/{id}`. */\n readonly path: string;\n}\n\nexport interface CommandAnnotation {\n /** The endpoints the command calls. */\n readonly endpoints?: readonly CommandEndpoint[];\n /** Whether the command changes data. */\n readonly writes?: boolean;\n}\n\nconst ANNOTATION = Symbol.for(\"@fluid-app/fluid-cli.command-annotation\");\n\ntype Annotated = Command & { [ANNOTATION]?: CommandAnnotation };\n\n/**\n * Record the endpoints a command calls and whether it writes, so\n * `fluid commands --json` can list them. Returns the command for chaining.\n */\nexport function describeCommand<T extends Command>(\n command: T,\n annotation: CommandAnnotation,\n): T {\n const target = command as T & Annotated;\n target[ANNOTATION] = { ...target[ANNOTATION], ...annotation };\n return command;\n}\n\nexport function readAnnotation(command: Command): CommandAnnotation | null {\n return (command as Annotated)[ANNOTATION] ?? null;\n}\n","/**\n * Walk the loaded command tree into a plain catalog: every command, its\n * arguments and flags, and what it does to the API. `fluid commands` prints\n * it and shell completion reads the same tree, so neither can drift from\n * the commands a user actually has installed.\n */\n\nimport type { Argument, Command, Option } from \"commander\";\nimport { readAnnotation, type CommandEndpoint } from \"./annotations.js\";\n\nexport interface CatalogOption {\n readonly flags: string;\n readonly long: string | null;\n readonly short: string | null;\n readonly description: string;\n /** `<value>` must follow the flag. */\n readonly takesValue: boolean;\n /** `[value]` may follow the flag. */\n readonly optionalValue: boolean;\n /** The command refuses to run without it. */\n readonly required: boolean;\n readonly defaultValue: unknown;\n}\n\nexport interface CatalogArgument {\n readonly name: string;\n readonly description: string;\n readonly required: boolean;\n readonly variadic: boolean;\n}\n\nexport interface CatalogEntry {\n /** Full invocation, e.g. `fluid domains add`. */\n readonly command: string;\n readonly path: readonly string[];\n readonly description: string;\n readonly aliases: readonly string[];\n readonly arguments: readonly CatalogArgument[];\n readonly options: readonly CatalogOption[];\n /** Ancestors whose flags this command also accepts, e.g. `fluid admin`'s `--token`. */\n readonly inheritsOptionsFrom: readonly string[];\n readonly subcommands: readonly string[];\n /** False for a group that only holds subcommands. */\n readonly runnable: boolean;\n /** True or false when the plugin says so or the command takes `--yes`; null when unknown. */\n readonly writes: boolean | null;\n /** The endpoints the plugin declared; null when it declared none, which means unknown, not none. */\n readonly endpoints: readonly CommandEndpoint[] | null;\n}\n\n// Commander keeps these two private; they have been stable since v7.\ntype CommandInternals = { _hidden?: boolean; _actionHandler?: unknown };\n\nexport function isHidden(command: Command): boolean {\n return (command as unknown as CommandInternals)._hidden === true;\n}\n\nfunction isRunnable(command: Command): boolean {\n return (command as unknown as CommandInternals)._actionHandler != null;\n}\n\nexport function visibleSubcommands(command: Command): Command[] {\n return command.commands.filter((sub) => !isHidden(sub));\n}\n\nexport function visibleOptions(command: Command): Option[] {\n return command.options.filter((option) => !option.hidden);\n}\n\nfunction describeOption(option: Option): CatalogOption {\n return {\n flags: option.flags,\n long: option.long ?? null,\n short: option.short ?? null,\n description: option.description,\n takesValue: option.required,\n optionalValue: option.optional,\n required: option.mandatory,\n defaultValue: option.defaultValue,\n };\n}\n\nfunction describeArgument(argument: Argument): CatalogArgument {\n return {\n name: argument.name(),\n description: argument.description,\n required: argument.required,\n variadic: argument.variadic,\n };\n}\n\nfunction inferWrites(command: Command, declared: boolean | undefined) {\n if (declared !== undefined) return declared;\n // Commands that change live data confirm with --yes across the plugins.\n return command.options.some((option) => option.long === \"--yes\")\n ? true\n : null;\n}\n\nexport function walkCommands(root: Command): CatalogEntry[] {\n const entries: CatalogEntry[] = [];\n\n function visit(command: Command, path: string[], ancestors: Command[]) {\n const annotation = readAnnotation(command);\n const subcommands = visibleSubcommands(command);\n entries.push({\n command: path.join(\" \"),\n path,\n description: command.description(),\n aliases: command.aliases(),\n arguments: command.registeredArguments.map(describeArgument),\n options: visibleOptions(command).map(describeOption),\n inheritsOptionsFrom: ancestors\n .filter((ancestor) =>\n // The program's --version is not a flag a subcommand takes.\n visibleOptions(ancestor).some(\n (option) => option.long !== \"--version\",\n ),\n )\n .map((ancestor) => commandPath(ancestor).join(\" \")),\n subcommands: subcommands.map((sub) => sub.name()),\n runnable: isRunnable(command),\n writes: inferWrites(command, annotation?.writes),\n endpoints: annotation?.endpoints ?? null,\n });\n for (const sub of subcommands) {\n visit(sub, [...path, sub.name()], [...ancestors, command]);\n }\n }\n\n visit(root, [root.name()], []);\n return entries;\n}\n\nexport function rootCommand(command: Command): Command {\n let root = command;\n while (root.parent) root = root.parent;\n return root;\n}\n\nexport function commandPath(command: Command): string[] {\n const path: string[] = [];\n for (let c: Command | null = command; c; c = c.parent) {\n path.unshift(c.name());\n }\n return path;\n}\n\n/** Keep the entries at or under `prefix`, matching names or aliases. */\nexport function filterCatalog(\n entries: readonly CatalogEntry[],\n root: Command,\n prefix: readonly string[],\n): CatalogEntry[] {\n let target = root;\n for (const word of prefix) {\n const next = visibleSubcommands(target).find(\n (sub) => sub.name() === word || sub.aliases().includes(word),\n );\n if (!next) return [];\n target = next;\n }\n const base = commandPath(target);\n return entries.filter((entry) =>\n base.every((word, index) => entry.path[index] === word),\n );\n}\n","/**\n * Result type utilities for type-safe error handling\n *\n * The Result<T, E> pattern provides a discriminated union for fallible operations,\n * enabling exhaustive handling without try/catch blocks.\n */\n\n// Result type - discriminated union for success/failure\n\nexport interface Success<T> {\n readonly success: true;\n readonly value: T;\n}\n\nexport interface Failure<E> {\n readonly success: false;\n readonly error: E;\n}\n\nexport type Result<T, E = Error> = Success<T> | Failure<E>;\n\n// Constructor functions\n\nexport function success<T>(value: T): Success<T> {\n return { success: true, value };\n}\n\nexport function failure<E>(error: E): Failure<E> {\n return { success: false, error };\n}\n\n// Type guards\n\nexport function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {\n return result.success === true;\n}\n\nexport function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {\n return result.success === false;\n}\n\n// Utility functions\n\nexport function tryCatch<T>(fn: () => T): Result<T, Error> {\n try {\n return success(fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport async function tryCatchAsync<T>(\n fn: () => Promise<T>,\n): Promise<Result<T, Error>> {\n try {\n return success(await fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (isSuccess(result)) return result.value;\n // Always throw an Error instance so V8 attaches a stack trace and\n // `instanceof Error` checks in catch blocks work as expected.\n if (result.error instanceof Error) throw result.error;\n throw new Error(\n typeof result.error === \"object\" && result.error !== null\n ? JSON.stringify(result.error)\n : String(result.error),\n );\n}\n\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (isSuccess(result)) return result.value;\n return defaultValue;\n}\n\nexport function mapResult<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => U,\n): Result<U, E> {\n if (isSuccess(result)) return success(fn(result.value));\n return result;\n}\n\nexport function mapError<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => F,\n): Result<T, F> {\n if (isFailure(result)) return failure(fn(result.error));\n return result;\n}\n\n// Error narrowing utilities\n\nexport function isError(value: unknown): value is Error {\n return value instanceof Error;\n}\n\nexport function isNodeError(value: unknown): value is NodeJS.ErrnoException {\n return value instanceof Error && \"code\" in value;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === \"string\") return error;\n return String(error);\n}\n","/**\n * Fluid API client for authentication operations\n */\n\nimport type { CliError } from \"../utils/errors.js\";\nimport { type Result, success, failure } from \"../utils/result.js\";\n\nconst API_TIMEOUT_MS = 10_000;\n\nexport function getFluidApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nfunction makeSignal(): { signal: AbortSignal; cleanup: () => void } {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);\n return { signal: controller.signal, cleanup: () => clearTimeout(timer) };\n}\n\nexport interface FluidApiError extends CliError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\nexport const FLUID_API_ERROR = {\n INVALID_TOKEN: {\n code: \"INVALID_TOKEN\",\n message: \"Token is invalid or expired\",\n },\n API_UNREACHABLE: {\n code: \"API_UNREACHABLE\",\n message: \"Could not reach the Fluid API\",\n },\n UUID_NOT_FOUND: {\n code: \"UUID_NOT_FOUND\",\n message: \"Verification session not found\",\n },\n // The server answers 410 for an expired code, a used one, and one locked\n // after too many wrong attempts, and the body does not say which.\n CODE_EXPIRED: {\n code: \"CODE_EXPIRED\",\n message:\n \"Verification code has expired, was already used, or is locked after too many attempts — please request a new one\",\n },\n RATE_LIMITED: {\n code: \"RATE_LIMITED\",\n message: \"Too many sign-in requests — wait a minute and try again\",\n },\n INVALID_CODE: {\n code: \"INVALID_CODE\",\n message: \"Invalid verification code\",\n },\n COMPANY_NOT_FOUND: {\n code: \"COMPANY_NOT_FOUND\",\n message: \"Company not found\",\n },\n SWITCH_FAILED: {\n code: \"SWITCH_FAILED\",\n message: \"Failed to switch company\",\n },\n} as const;\n\nfunction createApiError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): FluidApiError {\n return { code: template.code, message: template.message, details };\n}\n\nexport interface CompanyInfo {\n readonly name: string;\n}\n\nexport interface UserCompany {\n readonly id: number;\n readonly name: string;\n}\n\nexport interface SwitchCompanyResult {\n readonly companyId: number;\n readonly companyName: string;\n readonly fluidShop: string;\n readonly jwt: string;\n}\n\nexport interface MfaResponse {\n readonly uuid: string;\n readonly expiresAt: string;\n}\n\nexport interface CompanyChoice {\n readonly id: number;\n readonly name: string;\n readonly shopName: string;\n readonly jwt: string;\n /** \"rep\" for an admin or rep session; \"member\" or \"customer\" for a shopper's. */\n readonly authType?: string;\n}\n\nexport interface ConfirmMfaResponse {\n readonly authType: string;\n readonly companies: CompanyChoice[];\n}\n\n/**\n * Validate a token against the Fluid API and return company info\n */\nexport async function validateToken(\n token: string,\n): Promise<Result<CompanyInfo, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/company/v1/companies/me`,\n {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n data?: { company?: { name?: string } };\n };\n const name = data?.data?.company?.name;\n if (!name) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /companies/me\",\n ),\n );\n }\n return success({ name });\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}: Check that your token is valid and non-expired.`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Send a multi-factor authentication code to the given email address.\n * The API answers 201 whether or not the address can sign in\n * (anti-enumeration).\n *\n * With `fluidShop`, the server ties the code to the person's membership in\n * that company, so redeeming it signs in to that company alone and, for an\n * admin's emailed code, confirms a new signup.\n */\nexport async function sendMfa(\n email: string,\n options: { readonly fluidShop?: string } = {},\n): Promise<Result<MfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/send_mfa`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(\n options.fluidShop\n ? { email, fluid_shop: options.fluidShop }\n : { email },\n ),\n signal,\n },\n );\n\n if (response.status === 201) {\n const data = (await response.json()) as {\n multi_factor_authentication?: {\n uuid?: string;\n verification_code_expires_at?: string;\n };\n };\n const uuid = data.multi_factor_authentication?.uuid;\n const expiresAt =\n data.multi_factor_authentication?.verification_code_expires_at;\n if (!uuid || !expiresAt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /send_mfa\",\n ),\n );\n }\n return success({ uuid, expiresAt });\n }\n\n const body = await response.text();\n if (response.status === 429) {\n return failure(\n createApiError(FLUID_API_ERROR.RATE_LIMITED, `HTTP 429: ${body}`),\n );\n }\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Confirm a multi-factor authentication code and retrieve company JWTs.\n *\n * `baseUrl` is the API the code was sent from; a sign-in finished in a\n * later process passes the one it saved, not the current environment's.\n */\nexport async function confirmMfa(\n uuid: string,\n code: string,\n options: { readonly fluidShop?: string; readonly baseUrl?: string } = {},\n): Promise<Result<ConfirmMfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = options.baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(`${apiBase}/api/authentication/confirm_mfa`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n uuid,\n multi_factor_authentication: { verification_code: code },\n ...(options.fluidShop ? { fluid_shop: options.fluidShop } : {}),\n }),\n signal,\n });\n\n if (response.ok) {\n const data = (await response.json()) as {\n authenticated?: boolean;\n auth_type?: string;\n companies?:\n | {\n id: number;\n name: string;\n shop_name: string;\n jwt: string;\n auth_type?: string | null;\n }[]\n | null;\n };\n if (!data.authenticated) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_CODE,\n \"Authentication was not confirmed by the server\",\n ),\n );\n }\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success({\n authType: data.auth_type ?? \"\",\n companies: companies.map((c) => ({\n id: c.id,\n name: c.name,\n shopName: c.shop_name,\n jwt: c.jwt,\n ...(c.auth_type ? { authType: c.auth_type } : {}),\n })),\n });\n }\n\n if (response.status === 404) {\n return failure(createApiError(FLUID_API_ERROR.UUID_NOT_FOUND));\n }\n if (response.status === 410) {\n return failure(createApiError(FLUID_API_ERROR.CODE_EXPIRED));\n }\n if (response.status === 422) {\n return failure(createApiError(FLUID_API_ERROR.INVALID_CODE));\n }\n\n const body = await response.text();\n if (response.status === 429) {\n return failure(\n createApiError(FLUID_API_ERROR.RATE_LIMITED, `HTTP 429: ${body}`),\n );\n }\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Fetch all companies the authenticated user has access to.\n */\nexport async function fetchUserCompanies(\n token: string,\n baseUrl?: string,\n): Promise<Result<UserCompany[], FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(`${apiBase}/api/me`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${token}` },\n signal,\n });\n\n if (response.ok) {\n const data = (await response.json()) as {\n companies?: { id: number; name: string }[];\n };\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success(companies.map((c) => ({ id: c.id, name: c.name })));\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Switch to a different company and receive a new JWT.\n *\n * Response shape (from @fluid-app/auth SwitchCompanyResponse):\n * { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }\n */\nexport async function switchCompany(\n token: string,\n companyId: number,\n baseUrl?: string,\n): Promise<Result<SwitchCompanyResult, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(\n `${apiBase}/api/authentication/company/${companyId}/switch`,\n {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n company?: {\n id: number;\n name: string;\n fluid_shop: string;\n jwt: string;\n };\n };\n const company = data.company;\n if (!company?.jwt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n \"Unexpected response shape from /switch\",\n ),\n );\n }\n return success({\n companyId: company.id,\n companyName: company.name,\n fluidShop: company.fluid_shop,\n jwt: company.jwt,\n });\n }\n\n if (response.status === 404) {\n return failure(\n createApiError(\n FLUID_API_ERROR.COMPANY_NOT_FOUND,\n `Company ${companyId} not found`,\n ),\n );\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n","/**\n * XDG-compliant config directory resolution\n *\n * Priority:\n * 1. FLUID_CONFIG_DIR env var (explicit override)\n * 2. ~/.fluid/ on macOS (convention for CLI tools)\n * 3. %APPDATA%/fluid/ on Windows\n * 4. $XDG_CONFIG_HOME/fluid/ on Linux (XDG spec)\n * 5. ~/.config/fluid/ fallback on Linux\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function getConfigDir(): string {\n const envOverride = process.env[\"FLUID_CONFIG_DIR\"];\n if (envOverride) return envOverride;\n\n if (process.platform === \"darwin\") {\n return join(homedir(), \".fluid\");\n }\n\n if (process.platform === \"win32\") {\n const appData =\n process.env[\"APPDATA\"] ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"fluid\");\n }\n\n const xdgConfig = process.env[\"XDG_CONFIG_HOME\"];\n if (xdgConfig) return join(xdgConfig, \"fluid\");\n\n return join(homedir(), \".config\", \"fluid\");\n}\n\nexport function getConfigFilePath(): string {\n return join(getConfigDir(), \"config.json\");\n}\n","/**\n * Configuration types for the Fluid CLI\n */\n\nexport interface FluidProfile {\n readonly name: string;\n readonly token: string;\n readonly companyName: string;\n readonly storedAt: string; // ISO-8601\n readonly baseUrl?: string;\n /** Stable storefront identity written by Mist and newer auth endpoints. */\n readonly fluidShop?: string;\n /**\n * Cached commit identity for GitSync subjects, so a publish doesn't\n * pay for `/api/me` to learn a name that changes approximately never.\n * Scoped to the profile (token + company), and dropped whenever login\n * or a company switch rewrites it. See git-sync/actor-cache.ts.\n */\n readonly gitSyncActor?: {\n readonly name: string;\n readonly publicId: string | null;\n /** ISO-8601. */\n readonly cachedAt: string;\n };\n}\n\nexport interface FluidConfig {\n activeProfile: string | null;\n profiles: Record<string, FluidProfile>;\n plugins: Record<string, unknown>;\n /**\n * Allow-list of plugin package names.\n *\n * - `null` (default) — auto-discover and load all `@fluid-app/fluid-cli-*`\n * and `*-cli-commands` packages found in `node_modules` or the pnpm\n * workspace. Only `@fluid-app`-scoped packages matching the naming\n * convention are eligible; no third-party code is loaded.\n * - `string[]` — only load plugins whose names appear in the array.\n * Set to `[]` to disable all plugins.\n */\n enabledPlugins: string[] | null;\n /** Historical local workspace slugs, keyed by stable storefront slug.\n * Optional because older CLI versions and hand-written test configs do not\n * know about Mist's local workspace compatibility metadata. */\n workspaceAliasesByFluidShop?: Record<string, string[]>;\n}\n\nexport function createDefaultConfig(): FluidConfig {\n return {\n activeProfile: null,\n profiles: {},\n plugins: {},\n enabledPlugins: null, // auto-discover all plugins\n };\n}\n","/**\n * Read/write config.json with atomic writes and safe defaults\n */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport { type FluidConfig, createDefaultConfig } from \"./types.js\";\nimport { getConfigFilePath } from \"./paths.js\";\n\nexport function readConfig(): FluidConfig {\n const configPath = getConfigFilePath();\n\n if (!existsSync(configPath)) {\n return createDefaultConfig();\n }\n\n const raw = readFileSync(configPath, \"utf8\");\n\n let parsed: Partial<FluidConfig>;\n try {\n parsed = JSON.parse(raw) as Partial<FluidConfig>;\n } catch {\n // Corrupted config — fall back to defaults rather than crashing\n return createDefaultConfig();\n }\n\n // Merge with defaults to handle schema evolution\n return withDurableWorkspaceAliases({\n ...createDefaultConfig(),\n ...parsed,\n });\n}\n\nexport function writeConfig(config: FluidConfig): void {\n const configPath = getConfigFilePath();\n const dir = dirname(configPath);\n\n // 0o700: owner-only access so other users cannot enumerate the config dir\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // Atomic write: write to a temp file in the same directory, then rename.\n // rename() is a POSIX atomic operation — a crash mid-write will never leave\n // a partially-written config.json. The temp file must be on the same\n // filesystem as the destination for rename() to succeed.\n const suffix = randomBytes(6).toString(\"hex\");\n const tmpPath = join(dir, `.fluid-config-${suffix}.tmp`);\n\n try {\n writeFileSync(\n tmpPath,\n JSON.stringify(withDurableWorkspaceAliases(config), null, 2) + \"\\n\",\n {\n encoding: \"utf8\",\n // NOTE: mode: 0o600 restricts to owner read/write on POSIX (macOS, Linux).\n // On Windows, Node.js ignores this option — NTFS permissions are not\n // set via the mode parameter. Windows file protection would require\n // icacls or Windows security descriptor APIs.\n mode: 0o600,\n },\n );\n renameSync(tmpPath, configPath);\n } catch (err) {\n // Clean up the temp file if something went wrong before the rename\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore cleanup errors — the temp file may not exist yet\n }\n throw err;\n }\n}\n\nfunction workspaceSlug(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction storefrontSlug(value: string): string {\n const withoutScheme = value.trim().replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, \"\");\n const host = withoutScheme.split(/[/?#]/, 1)[0]?.replace(/:\\d+$/, \"\") ?? \"\";\n return workspaceSlug(host.replace(/\\.fluid\\.app$/i, \"\"));\n}\n\n/** Preserve rename history even when `fluid logout` clears the profiles that\n * originally carried it. Derivation on read migrates existing config files;\n * normalization on every write makes the migration durable before profiles\n * are removed. */\nfunction withDurableWorkspaceAliases(config: FluidConfig): FluidConfig {\n const aliases: Record<string, string[]> = {};\n const add = (storefront: string, candidates: readonly string[]): void => {\n const key = storefrontSlug(storefront);\n if (!key) return;\n const values = new Set(aliases[key] ?? []);\n for (const candidate of candidates) {\n const slug = workspaceSlug(candidate);\n if (slug) values.add(slug);\n }\n if (values.size > 0) aliases[key] = [...values];\n };\n\n for (const [storefront, candidates] of Object.entries(\n config.workspaceAliasesByFluidShop ?? {},\n )) {\n if (!Array.isArray(candidates)) continue;\n add(\n storefront,\n candidates.filter(\n (candidate): candidate is string => typeof candidate === \"string\",\n ),\n );\n }\n for (const profile of Object.values(config.profiles)) {\n if (profile.fluidShop) {\n add(profile.fluidShop, [profile.name, profile.companyName]);\n }\n }\n\n return Object.keys(aliases).length > 0 ||\n config.workspaceAliasesByFluidShop !== undefined\n ? { ...config, workspaceAliasesByFluidShop: aliases }\n : config;\n}\n\nexport function updateConfig(\n updater: (config: FluidConfig) => FluidConfig,\n): FluidConfig {\n const config = readConfig();\n const updated = updater(config);\n writeConfig(updated);\n return updated;\n}\n","/**\n * Project-level config (.fluidrc) reader.\n *\n * Walks upward from a starting directory to find a `.fluidrc` file.\n * The file is JSON with a `profile` field that maps to a profile name\n * in `~/.fluid/config.json`.\n *\n * This allows portal repos to pin themselves to a specific CLI profile\n * so that `fluid portal pull` / `push` always use the correct company\n * credentials, regardless of which profile is globally active.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport interface ProjectRcConfig {\n readonly profile: string;\n}\n\nconst RC_FILENAME = \".fluidrc\";\n\n/** Cached result keyed by the starting directory. */\nlet cachedResult: { dir: string; config: ProjectRcConfig | null } | undefined;\n\n/**\n * Walk upward from `startDir` looking for a `.fluidrc` file.\n *\n * Returns the parsed config when found, or `null` if no valid `.fluidrc`\n * exists in any ancestor directory.\n *\n * The result is cached per starting directory for the lifetime of the\n * process — CLI commands are short-lived so stale-cache is not a concern.\n */\nexport function findProjectConfig(startDir: string): ProjectRcConfig | null {\n if (cachedResult && cachedResult.dir === startDir) {\n return cachedResult.config;\n }\n\n let dir = startDir;\n while (true) {\n const rcPath = join(dir, RC_FILENAME);\n if (existsSync(rcPath)) {\n try {\n const raw = readFileSync(rcPath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<ProjectRcConfig>;\n if (typeof parsed.profile === \"string\" && parsed.profile.length > 0) {\n const config: ProjectRcConfig = { profile: parsed.profile };\n cachedResult = { dir: startDir, config };\n return config;\n }\n } catch {\n // Malformed .fluidrc — treat as absent\n }\n\n // Found a .fluidrc but it was invalid or empty — stop searching\n // upward to avoid confusing inheritance from a parent directory.\n cachedResult = { dir: startDir, config: null };\n return null;\n }\n\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n cachedResult = { dir: startDir, config: null };\n return null;\n}\n\n/** Reset the cache. Exposed for tests only. */\nexport function _resetProjectConfigCache(): void {\n cachedResult = undefined;\n}\n","/**\n * Token storage and retrieval from config\n */\n\nimport { readConfig } from \"../config/config.js\";\nimport { findProjectConfig } from \"../config/project-config.js\";\nimport type { FluidProfile } from \"../config/types.js\";\n\n/**\n * Get the auth token for a named profile, or the active profile when omitted.\n *\n * Resolution order (when `profileName` is omitted — mirrors `resolveToken`\n * in domain/context.ts):\n * 1. `FLUID_TOKEN` / `FLUID_API_TOKEN` environment variables\n * 2. Project-level `.fluidrc` profile (walks upward from cwd)\n * 3. Global active profile from `~/.fluid/config.json`\n *\n * When `profileName` is provided explicitly (e.g. via `--profile` flag),\n * it takes precedence over the environment variables, `.fluidrc`, and the\n * active profile.\n */\nexport function getAuthToken(profileName?: string): string | null {\n const config = readConfig();\n\n // Explicit profile name takes precedence\n if (profileName) {\n const profile = config.profiles[profileName];\n return profile?.token ?? null;\n }\n\n // Environment variable override (before any stored profile)\n const envToken = process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"];\n if (envToken) return envToken;\n\n // Project-level .fluidrc profile\n const projectConfig = findProjectConfig(process.cwd());\n if (projectConfig?.profile) {\n const profile = config.profiles[projectConfig.profile];\n if (profile) return profile.token;\n }\n\n // Fall back to global active profile\n if (!config.activeProfile) return null;\n const profile = config.profiles[config.activeProfile];\n return profile?.token ?? null;\n}\n\n/**\n * Get the active profile, or null if not logged in\n */\nexport function getActiveProfile(): FluidProfile | null {\n const config = readConfig();\n if (!config.activeProfile) return null;\n\n return config.profiles[config.activeProfile] ?? null;\n}\n\n/**\n * List all stored profile names\n */\nexport function listProfileNames(): string[] {\n const config = readConfig();\n return Object.keys(config.profiles);\n}\n"],"mappings":";;;;;AAwBA,MAAM,aAAa,OAAO,IAAI,0CAA0C;;;;;AAQxE,SAAgB,gBACd,SACA,YACG;CACH,MAAM,SAAS;AACf,QAAO,cAAc;EAAE,GAAG,OAAO;EAAa,GAAG;EAAY;AAC7D,QAAO;;AAGT,SAAgB,eAAe,SAA4C;AACzE,QAAQ,QAAsB,eAAe;;;;ACW/C,SAAgB,SAAS,SAA2B;AAClD,QAAQ,QAAwC,YAAY;;AAG9D,SAAS,WAAW,SAA2B;AAC7C,QAAQ,QAAwC,kBAAkB;;AAGpE,SAAgB,mBAAmB,SAA6B;AAC9D,QAAO,QAAQ,SAAS,QAAQ,QAAQ,CAAC,SAAS,IAAI,CAAC;;AAGzD,SAAgB,eAAe,SAA4B;AACzD,QAAO,QAAQ,QAAQ,QAAQ,WAAW,CAAC,OAAO,OAAO;;AAG3D,SAAS,eAAe,QAA+B;AACrD,QAAO;EACL,OAAO,OAAO;EACd,MAAM,OAAO,QAAQ;EACrB,OAAO,OAAO,SAAS;EACvB,aAAa,OAAO;EACpB,YAAY,OAAO;EACnB,eAAe,OAAO;EACtB,UAAU,OAAO;EACjB,cAAc,OAAO;EACtB;;AAGH,SAAS,iBAAiB,UAAqC;AAC7D,QAAO;EACL,MAAM,SAAS,MAAM;EACrB,aAAa,SAAS;EACtB,UAAU,SAAS;EACnB,UAAU,SAAS;EACpB;;AAGH,SAAS,YAAY,SAAkB,UAA+B;AACpE,KAAI,aAAa,KAAA,EAAW,QAAO;AAEnC,QAAO,QAAQ,QAAQ,MAAM,WAAW,OAAO,SAAS,QAAQ,GAC5D,OACA;;AAGN,SAAgB,aAAa,MAA+B;CAC1D,MAAM,UAA0B,EAAE;CAElC,SAAS,MAAM,SAAkB,MAAgB,WAAsB;EACrE,MAAM,aAAa,eAAe,QAAQ;EAC1C,MAAM,cAAc,mBAAmB,QAAQ;AAC/C,UAAQ,KAAK;GACX,SAAS,KAAK,KAAK,IAAI;GACvB;GACA,aAAa,QAAQ,aAAa;GAClC,SAAS,QAAQ,SAAS;GAC1B,WAAW,QAAQ,oBAAoB,IAAI,iBAAiB;GAC5D,SAAS,eAAe,QAAQ,CAAC,IAAI,eAAe;GACpD,qBAAqB,UAClB,QAAQ,aAEP,eAAe,SAAS,CAAC,MACtB,WAAW,OAAO,SAAS,YAC7B,CACF,CACA,KAAK,aAAa,YAAY,SAAS,CAAC,KAAK,IAAI,CAAC;GACrD,aAAa,YAAY,KAAK,QAAQ,IAAI,MAAM,CAAC;GACjD,UAAU,WAAW,QAAQ;GAC7B,QAAQ,YAAY,SAAS,YAAY,OAAO;GAChD,WAAW,YAAY,aAAa;GACrC,CAAC;AACF,OAAK,MAAM,OAAO,YAChB,OAAM,KAAK,CAAC,GAAG,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC,GAAG,WAAW,QAAQ,CAAC;;AAI9D,OAAM,MAAM,CAAC,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;AAC9B,QAAO;;AAGT,SAAgB,YAAY,SAA2B;CACrD,IAAI,OAAO;AACX,QAAO,KAAK,OAAQ,QAAO,KAAK;AAChC,QAAO;;AAGT,SAAgB,YAAY,SAA4B;CACtD,MAAM,OAAiB,EAAE;AACzB,MAAK,IAAI,IAAoB,SAAS,GAAG,IAAI,EAAE,OAC7C,MAAK,QAAQ,EAAE,MAAM,CAAC;AAExB,QAAO;;;AAIT,SAAgB,cACd,SACA,MACA,QACgB;CAChB,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,QAAQ;EACzB,MAAM,OAAO,mBAAmB,OAAO,CAAC,MACrC,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,SAAS,KAAK,CAC7D;AACD,MAAI,CAAC,KAAM,QAAO,EAAE;AACpB,WAAS;;CAEX,MAAM,OAAO,YAAY,OAAO;AAChC,QAAO,QAAQ,QAAQ,UACrB,KAAK,OAAO,MAAM,UAAU,MAAM,KAAK,WAAW,KAAK,CACxD;;;;AC9IH,SAAgB,QAAW,OAAsB;AAC/C,QAAO;EAAE,SAAS;EAAM;EAAO;;AAGjC,SAAgB,QAAW,OAAsB;AAC/C,QAAO;EAAE,SAAS;EAAO;EAAO;;AAKlC,SAAgB,UAAgB,QAA4C;AAC1E,QAAO,OAAO,YAAY;;AAG5B,SAAgB,UAAgB,QAA4C;AAC1E,QAAO,OAAO,YAAY;;AAK5B,SAAgB,SAAY,IAA+B;AACzD,KAAI;AACF,SAAO,QAAQ,IAAI,CAAC;UACb,OAAO;AACd,SAAO,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;;;AAI7E,eAAsB,cACpB,IAC2B;AAC3B,KAAI;AACF,SAAO,QAAQ,MAAM,IAAI,CAAC;UACnB,OAAO;AACd,SAAO,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;;;AAI7E,SAAgB,OAAa,QAAyB;AACpD,KAAI,UAAU,OAAO,CAAE,QAAO,OAAO;AAGrC,KAAI,OAAO,iBAAiB,MAAO,OAAM,OAAO;AAChD,OAAM,IAAI,MACR,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,OACjD,KAAK,UAAU,OAAO,MAAM,GAC5B,OAAO,OAAO,MAAM,CACzB;;AAGH,SAAgB,SAAe,QAAsB,cAAoB;AACvE,KAAI,UAAU,OAAO,CAAE,QAAO,OAAO;AACrC,QAAO;;AAGT,SAAgB,UACd,QACA,IACc;AACd,KAAI,UAAU,OAAO,CAAE,QAAO,QAAQ,GAAG,OAAO,MAAM,CAAC;AACvD,QAAO;;AAGT,SAAgB,SACd,QACA,IACc;AACd,KAAI,UAAU,OAAO,CAAE,QAAO,QAAQ,GAAG,OAAO,MAAM,CAAC;AACvD,QAAO;;AAKT,SAAgB,QAAQ,OAAgC;AACtD,QAAO,iBAAiB;;AAG1B,SAAgB,YAAY,OAAgD;AAC1E,QAAO,iBAAiB,SAAS,UAAU;;AAG7C,SAAgB,gBAAgB,OAAwB;AACtD,KAAI,iBAAiB,MAAO,QAAO,MAAM;AACzC,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAO,OAAO,MAAM;;;;ACpGtB,MAAM,iBAAiB;AAEvB,SAAgB,kBAA0B;AACxC,QAAO,QAAQ,IAAI,qBAAqB;;AAG1C,SAAS,aAA2D;CAClE,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,QAAQ,iBAAiB,WAAW,OAAO,EAAE,eAAe;AAClE,QAAO;EAAE,QAAQ,WAAW;EAAQ,eAAe,aAAa,MAAM;EAAE;;AAS1E,MAAa,kBAAkB;CAC7B,eAAe;EACb,MAAM;EACN,SAAS;EACV;CACD,iBAAiB;EACf,MAAM;EACN,SAAS;EACV;CACD,gBAAgB;EACd,MAAM;EACN,SAAS;EACV;CAGD,cAAc;EACZ,MAAM;EACN,SACE;EACH;CACD,cAAc;EACZ,MAAM;EACN,SAAS;EACV;CACD,cAAc;EACZ,MAAM;EACN,SAAS;EACV;CACD,mBAAmB;EACjB,MAAM;EACN,SAAS;EACV;CACD,eAAe;EACb,MAAM;EACN,SAAS;EACV;CACF;AAED,SAAS,eACP,UACA,SACe;AACf,QAAO;EAAE,MAAM,SAAS;EAAM,SAAS,SAAS;EAAS;EAAS;;;;;AAyCpE,eAAsB,cACpB,OAC6C;CAC7C,MAAM,EAAE,QAAQ,YAAY,YAAY;AACxC,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,GAAG,iBAAiB,CAAC,+BACrB;GACE,QAAQ;GACR,SAAS,EACP,eAAe,UAAU,SAC1B;GACD;GACD,CACF;AAED,MAAI,SAAS,IAAI;GAIf,MAAM,QAHQ,MAAM,SAAS,MAAM,GAGhB,MAAM,SAAS;AAClC,OAAI,CAAC,KACH,QAAO,QACL,eACE,gBAAgB,iBAChB,+CACD,CACF;AAEH,UAAO,QAAQ,EAAE,MAAM,CAAC;;AAG1B,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IACjD,QAAO,QACL,eACE,gBAAgB,eAChB,QAAQ,SAAS,OAAO,mDACzB,CACF;EAGH,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;;;;;;;AAab,eAAsB,QACpB,OACA,UAA2C,EAAE,EACA;CAC7C,MAAM,EAAE,QAAQ,YAAY,YAAY;AACxC,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,GAAG,iBAAiB,CAAC,+BACrB;GACE,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UACT,QAAQ,YACJ;IAAE;IAAO,YAAY,QAAQ;IAAW,GACxC,EAAE,OAAO,CACd;GACD;GACD,CACF;AAED,MAAI,SAAS,WAAW,KAAK;GAC3B,MAAM,OAAQ,MAAM,SAAS,MAAM;GAMnC,MAAM,OAAO,KAAK,6BAA6B;GAC/C,MAAM,YACJ,KAAK,6BAA6B;AACpC,OAAI,CAAC,QAAQ,CAAC,UACZ,QAAO,QACL,eACE,gBAAgB,iBAChB,2CACD,CACF;AAEH,UAAO,QAAQ;IAAE;IAAM;IAAW,CAAC;;EAGrC,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,MAAI,SAAS,WAAW,IACtB,QAAO,QACL,eAAe,gBAAgB,cAAc,aAAa,OAAO,CAClE;AAEH,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;;;;AAUb,eAAsB,WACpB,MACA,MACA,UAAsE,EAAE,EACpB;CACpD,MAAM,EAAE,QAAQ,YAAY,YAAY;CACxC,MAAM,UAAU,QAAQ,WAAW,iBAAiB;AACpD,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,kCAAkC;GACxE,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU;IACnB;IACA,6BAA6B,EAAE,mBAAmB,MAAM;IACxD,GAAI,QAAQ,YAAY,EAAE,YAAY,QAAQ,WAAW,GAAG,EAAE;IAC/D,CAAC;GACF;GACD,CAAC;AAEF,MAAI,SAAS,IAAI;GACf,MAAM,OAAQ,MAAM,SAAS,MAAM;AAanC,OAAI,CAAC,KAAK,cACR,QAAO,QACL,eACE,gBAAgB,cAChB,iDACD,CACF;GAEH,MAAM,YAAY,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,YAAY,EAAE;AACrE,UAAO,QAAQ;IACb,UAAU,KAAK,aAAa;IAC5B,WAAW,UAAU,KAAK,OAAO;KAC/B,IAAI,EAAE;KACN,MAAM,EAAE;KACR,UAAU,EAAE;KACZ,KAAK,EAAE;KACP,GAAI,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,GAAG,EAAE;KACjD,EAAE;IACJ,CAAC;;AAGJ,MAAI,SAAS,WAAW,IACtB,QAAO,QAAQ,eAAe,gBAAgB,eAAe,CAAC;AAEhE,MAAI,SAAS,WAAW,IACtB,QAAO,QAAQ,eAAe,gBAAgB,aAAa,CAAC;AAE9D,MAAI,SAAS,WAAW,IACtB,QAAO,QAAQ,eAAe,gBAAgB,aAAa,CAAC;EAG9D,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,MAAI,SAAS,WAAW,IACtB,QAAO,QACL,eAAe,gBAAgB,cAAc,aAAa,OAAO,CAClE;AAEH,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;AAOb,eAAsB,mBACpB,OACA,SAC+C;CAC/C,MAAM,EAAE,QAAQ,YAAY,YAAY;CACxC,MAAM,UAAU,WAAW,iBAAiB;AAC5C,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,UAAU;GAChD,QAAQ;GACR,SAAS,EAAE,eAAe,UAAU,SAAS;GAC7C;GACD,CAAC;AAEF,MAAI,SAAS,IAAI;GACf,MAAM,OAAQ,MAAM,SAAS,MAAM;AAInC,UAAO,SADW,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,YAAY,EAAE,EAC5C,KAAK,OAAO;IAAE,IAAI,EAAE;IAAI,MAAM,EAAE;IAAM,EAAE,CAAC;;AAGpE,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IACjD,QAAO,QACL,eACE,gBAAgB,eAChB,QAAQ,SAAS,SAClB,CACF;EAGH,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;;;;AAUb,eAAsB,cACpB,OACA,WACA,SACqD;CACrD,MAAM,EAAE,QAAQ,YAAY,YAAY;CACxC,MAAM,UAAU,WAAW,iBAAiB;AAC5C,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,GAAG,QAAQ,8BAA8B,UAAU,UACnD;GACE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;IACjB;GACD;GACD,CACF;AAED,MAAI,SAAS,IAAI;GASf,MAAM,WARQ,MAAM,SAAS,MAAM,EAQd;AACrB,OAAI,CAAC,SAAS,IACZ,QAAO,QACL,eACE,gBAAgB,eAChB,yCACD,CACF;AAEH,UAAO,QAAQ;IACb,WAAW,QAAQ;IACnB,aAAa,QAAQ;IACrB,WAAW,QAAQ;IACnB,KAAK,QAAQ;IACd,CAAC;;AAGJ,MAAI,SAAS,WAAW,IACtB,QAAO,QACL,eACE,gBAAgB,mBAChB,WAAW,UAAU,YACtB,CACF;AAGH,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IACjD,QAAO,QACL,eACE,gBAAgB,eAChB,QAAQ,SAAS,SAClB,CACF;EAGH,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAO,QACL,eACE,gBAAgB,eAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;;;;;;;;;;ACvbb,SAAgB,eAAuB;CACrC,MAAM,cAAc,QAAQ,IAAI;AAChC,KAAI,YAAa,QAAO;AAExB,KAAI,QAAQ,aAAa,SACvB,QAAO,KAAK,SAAS,EAAE,SAAS;AAGlC,KAAI,QAAQ,aAAa,QAGvB,QAAO,KADL,QAAQ,IAAI,cAAc,KAAK,SAAS,EAAE,WAAW,UAAU,EAC5C,QAAQ;CAG/B,MAAM,YAAY,QAAQ,IAAI;AAC9B,KAAI,UAAW,QAAO,KAAK,WAAW,QAAQ;AAE9C,QAAO,KAAK,SAAS,EAAE,WAAW,QAAQ;;AAG5C,SAAgB,oBAA4B;AAC1C,QAAO,KAAK,cAAc,EAAE,cAAc;;;;ACY5C,SAAgB,sBAAmC;AACjD,QAAO;EACL,eAAe;EACf,UAAU,EAAE;EACZ,SAAS,EAAE;EACX,gBAAgB;EACjB;;;;;;;ACpCH,SAAgB,aAA0B;CACxC,MAAM,aAAa,mBAAmB;AAEtC,KAAI,CAAC,WAAW,WAAW,CACzB,QAAO,qBAAqB;CAG9B,MAAM,MAAM,aAAa,YAAY,OAAO;CAE5C,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AAEN,SAAO,qBAAqB;;AAI9B,QAAO,4BAA4B;EACjC,GAAG,qBAAqB;EACxB,GAAG;EACJ,CAAC;;AAGJ,SAAgB,YAAY,QAA2B;CACrD,MAAM,aAAa,mBAAmB;CACtC,MAAM,MAAM,QAAQ,WAAW;AAG/B,WAAU,KAAK;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;CAOhD,MAAM,UAAU,KAAK,KAAK,iBADX,YAAY,EAAE,CAAC,SAAS,MAAM,CACK,MAAM;AAExD,KAAI;AACF,gBACE,SACA,KAAK,UAAU,4BAA4B,OAAO,EAAE,MAAM,EAAE,GAAG,MAC/D;GACE,UAAU;GAKV,MAAM;GACP,CACF;AACD,aAAW,SAAS,WAAW;UACxB,KAAK;AAEZ,MAAI;AACF,cAAW,QAAQ;UACb;AAGR,QAAM;;;AAIV,SAAS,cAAc,OAAuB;AAC5C,QAAO,MACJ,aAAa,CACb,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,QAAQ,UAAU,GAAG;;AAG1B,SAAS,eAAe,OAAuB;AAG7C,QAAO,eAFe,MAAM,MAAM,CAAC,QAAQ,4BAA4B,GAAG,CAC/C,MAAM,SAAS,EAAE,CAAC,IAAI,QAAQ,SAAS,GAAG,IAAI,IAC/C,QAAQ,kBAAkB,GAAG,CAAC;;;;;;AAO1D,SAAS,4BAA4B,QAAkC;CACrE,MAAM,UAAoC,EAAE;CAC5C,MAAM,OAAO,YAAoB,eAAwC;EACvE,MAAM,MAAM,eAAe,WAAW;AACtC,MAAI,CAAC,IAAK;EACV,MAAM,SAAS,IAAI,IAAI,QAAQ,QAAQ,EAAE,CAAC;AAC1C,OAAK,MAAM,aAAa,YAAY;GAClC,MAAM,OAAO,cAAc,UAAU;AACrC,OAAI,KAAM,QAAO,IAAI,KAAK;;AAE5B,MAAI,OAAO,OAAO,EAAG,SAAQ,OAAO,CAAC,GAAG,OAAO;;AAGjD,MAAK,MAAM,CAAC,YAAY,eAAe,OAAO,QAC5C,OAAO,+BAA+B,EAAE,CACzC,EAAE;AACD,MAAI,CAAC,MAAM,QAAQ,WAAW,CAAE;AAChC,MACE,YACA,WAAW,QACR,cAAmC,OAAO,cAAc,SAC1D,CACF;;AAEH,MAAK,MAAM,WAAW,OAAO,OAAO,OAAO,SAAS,CAClD,KAAI,QAAQ,UACV,KAAI,QAAQ,WAAW,CAAC,QAAQ,MAAM,QAAQ,YAAY,CAAC;AAI/D,QAAO,OAAO,KAAK,QAAQ,CAAC,SAAS,KACnC,OAAO,gCAAgC,KAAA,IACrC;EAAE,GAAG;EAAQ,6BAA6B;EAAS,GACnD;;AAGN,SAAgB,aACd,SACa;CAEb,MAAM,UAAU,QADD,YAAY,CACI;AAC/B,aAAY,QAAQ;AACpB,QAAO;;;;;;;;;;;;;;;ACzHT,MAAM,cAAc;;AAGpB,IAAI;;;;;;;;;;AAWJ,SAAgB,kBAAkB,UAA0C;AAC1E,KAAI,gBAAgB,aAAa,QAAQ,SACvC,QAAO,aAAa;CAGtB,IAAI,MAAM;AACV,QAAO,MAAM;EACX,MAAM,SAAS,KAAK,KAAK,YAAY;AACrC,MAAI,WAAW,OAAO,EAAE;AACtB,OAAI;IACF,MAAM,MAAM,aAAa,QAAQ,OAAO;IACxC,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAI,OAAO,OAAO,YAAY,YAAY,OAAO,QAAQ,SAAS,GAAG;KACnE,MAAM,SAA0B,EAAE,SAAS,OAAO,SAAS;AAC3D,oBAAe;MAAE,KAAK;MAAU;MAAQ;AACxC,YAAO;;WAEH;AAMR,kBAAe;IAAE,KAAK;IAAU,QAAQ;IAAM;AAC9C,UAAO;;EAGT,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAGR,gBAAe;EAAE,KAAK;EAAU,QAAQ;EAAM;AAC9C,QAAO;;;;;;;;;;;;;;;;;;;;AC7CT,SAAgB,aAAa,aAAqC;CAChE,MAAM,SAAS,YAAY;AAG3B,KAAI,YAEF,QADgB,OAAO,SAAS,cAChB,SAAS;CAI3B,MAAM,WAAW,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;AAC3D,KAAI,SAAU,QAAO;CAGrB,MAAM,gBAAgB,kBAAkB,QAAQ,KAAK,CAAC;AACtD,KAAI,eAAe,SAAS;EAC1B,MAAM,UAAU,OAAO,SAAS,cAAc;AAC9C,MAAI,QAAS,QAAO,QAAQ;;AAI9B,KAAI,CAAC,OAAO,cAAe,QAAO;AAElC,QADgB,OAAO,SAAS,OAAO,gBACvB,SAAS;;;;;AAM3B,SAAgB,mBAAwC;CACtD,MAAM,SAAS,YAAY;AAC3B,KAAI,CAAC,OAAO,cAAe,QAAO;AAElC,QAAO,OAAO,SAAS,OAAO,kBAAkB;;;;;AAMlD,SAAgB,mBAA6B;CAC3C,MAAM,SAAS,YAAY;AAC3B,QAAO,OAAO,KAAK,OAAO,SAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-app/fluid-cli",
3
- "version": "0.1.64",
3
+ "version": "0.1.65",
4
4
  "description": "Core CLI for Fluid Commerce — auth, config, and plugin system",
5
5
  "bin": {
6
6
  "fluid": "./dist/bin/fluid.mjs"
@@ -1 +0,0 @@
1
- {"version":3,"file":"token-6SEVoGMw.mjs","names":[],"sources":["../src/catalog/annotations.ts","../src/catalog/walk.ts","../src/utils/result.ts","../src/auth/fluid-api.ts","../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/config/project-config.ts","../src/auth/token.ts"],"sourcesContent":["/**\n * What a command does to the API, for the command catalog.\n *\n * Commander has nowhere to keep this, so it rides on the command object\n * under a registered symbol. A plugin installed with its own copy of\n * @fluid-app/fluid-cli still writes to the same key the running CLI reads,\n * which a module-scoped WeakMap would not.\n */\n\nimport type { Command } from \"commander\";\n\nexport interface CommandEndpoint {\n readonly method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /** Path as the API documents it, e.g. `/api/domains/{id}`. */\n readonly path: string;\n}\n\nexport interface CommandAnnotation {\n /** The endpoints the command calls. */\n readonly endpoints?: readonly CommandEndpoint[];\n /** Whether the command changes data. */\n readonly writes?: boolean;\n}\n\nconst ANNOTATION = Symbol.for(\"@fluid-app/fluid-cli.command-annotation\");\n\ntype Annotated = Command & { [ANNOTATION]?: CommandAnnotation };\n\n/**\n * Record the endpoints a command calls and whether it writes, so\n * `fluid commands --json` can list them. Returns the command for chaining.\n */\nexport function describeCommand<T extends Command>(\n command: T,\n annotation: CommandAnnotation,\n): T {\n const target = command as T & Annotated;\n target[ANNOTATION] = { ...target[ANNOTATION], ...annotation };\n return command;\n}\n\nexport function readAnnotation(command: Command): CommandAnnotation | null {\n return (command as Annotated)[ANNOTATION] ?? null;\n}\n","/**\n * Walk the loaded command tree into a plain catalog: every command, its\n * arguments and flags, and what it does to the API. `fluid commands` prints\n * it and shell completion reads the same tree, so neither can drift from\n * the commands a user actually has installed.\n */\n\nimport type { Argument, Command, Option } from \"commander\";\nimport { readAnnotation, type CommandEndpoint } from \"./annotations.js\";\n\nexport interface CatalogOption {\n readonly flags: string;\n readonly long: string | null;\n readonly short: string | null;\n readonly description: string;\n /** `<value>` must follow the flag. */\n readonly takesValue: boolean;\n /** `[value]` may follow the flag. */\n readonly optionalValue: boolean;\n /** The command refuses to run without it. */\n readonly required: boolean;\n readonly defaultValue: unknown;\n}\n\nexport interface CatalogArgument {\n readonly name: string;\n readonly description: string;\n readonly required: boolean;\n readonly variadic: boolean;\n}\n\nexport interface CatalogEntry {\n /** Full invocation, e.g. `fluid domains add`. */\n readonly command: string;\n readonly path: readonly string[];\n readonly description: string;\n readonly aliases: readonly string[];\n readonly arguments: readonly CatalogArgument[];\n readonly options: readonly CatalogOption[];\n /** Ancestors whose flags this command also accepts, e.g. `fluid admin`'s `--token`. */\n readonly inheritsOptionsFrom: readonly string[];\n readonly subcommands: readonly string[];\n /** False for a group that only holds subcommands. */\n readonly runnable: boolean;\n /** True or false when the plugin says so or the command takes `--yes`; null when unknown. */\n readonly writes: boolean | null;\n /** The endpoints the plugin declared; null when it declared none, which means unknown, not none. */\n readonly endpoints: readonly CommandEndpoint[] | null;\n}\n\n// Commander keeps these two private; they have been stable since v7.\ntype CommandInternals = { _hidden?: boolean; _actionHandler?: unknown };\n\nexport function isHidden(command: Command): boolean {\n return (command as unknown as CommandInternals)._hidden === true;\n}\n\nfunction isRunnable(command: Command): boolean {\n return (command as unknown as CommandInternals)._actionHandler != null;\n}\n\nexport function visibleSubcommands(command: Command): Command[] {\n return command.commands.filter((sub) => !isHidden(sub));\n}\n\nexport function visibleOptions(command: Command): Option[] {\n return command.options.filter((option) => !option.hidden);\n}\n\nfunction describeOption(option: Option): CatalogOption {\n return {\n flags: option.flags,\n long: option.long ?? null,\n short: option.short ?? null,\n description: option.description,\n takesValue: option.required,\n optionalValue: option.optional,\n required: option.mandatory,\n defaultValue: option.defaultValue,\n };\n}\n\nfunction describeArgument(argument: Argument): CatalogArgument {\n return {\n name: argument.name(),\n description: argument.description,\n required: argument.required,\n variadic: argument.variadic,\n };\n}\n\nfunction inferWrites(command: Command, declared: boolean | undefined) {\n if (declared !== undefined) return declared;\n // Commands that change live data confirm with --yes across the plugins.\n return command.options.some((option) => option.long === \"--yes\")\n ? true\n : null;\n}\n\nexport function walkCommands(root: Command): CatalogEntry[] {\n const entries: CatalogEntry[] = [];\n\n function visit(command: Command, path: string[], ancestors: Command[]) {\n const annotation = readAnnotation(command);\n const subcommands = visibleSubcommands(command);\n entries.push({\n command: path.join(\" \"),\n path,\n description: command.description(),\n aliases: command.aliases(),\n arguments: command.registeredArguments.map(describeArgument),\n options: visibleOptions(command).map(describeOption),\n inheritsOptionsFrom: ancestors\n .filter((ancestor) =>\n // The program's --version is not a flag a subcommand takes.\n visibleOptions(ancestor).some(\n (option) => option.long !== \"--version\",\n ),\n )\n .map((ancestor) => commandPath(ancestor).join(\" \")),\n subcommands: subcommands.map((sub) => sub.name()),\n runnable: isRunnable(command),\n writes: inferWrites(command, annotation?.writes),\n endpoints: annotation?.endpoints ?? null,\n });\n for (const sub of subcommands) {\n visit(sub, [...path, sub.name()], [...ancestors, command]);\n }\n }\n\n visit(root, [root.name()], []);\n return entries;\n}\n\nexport function rootCommand(command: Command): Command {\n let root = command;\n while (root.parent) root = root.parent;\n return root;\n}\n\nexport function commandPath(command: Command): string[] {\n const path: string[] = [];\n for (let c: Command | null = command; c; c = c.parent) {\n path.unshift(c.name());\n }\n return path;\n}\n\n/** Keep the entries at or under `prefix`, matching names or aliases. */\nexport function filterCatalog(\n entries: readonly CatalogEntry[],\n root: Command,\n prefix: readonly string[],\n): CatalogEntry[] {\n let target = root;\n for (const word of prefix) {\n const next = visibleSubcommands(target).find(\n (sub) => sub.name() === word || sub.aliases().includes(word),\n );\n if (!next) return [];\n target = next;\n }\n const base = commandPath(target);\n return entries.filter((entry) =>\n base.every((word, index) => entry.path[index] === word),\n );\n}\n","/**\n * Result type utilities for type-safe error handling\n *\n * The Result<T, E> pattern provides a discriminated union for fallible operations,\n * enabling exhaustive handling without try/catch blocks.\n */\n\n// Result type - discriminated union for success/failure\n\nexport interface Success<T> {\n readonly success: true;\n readonly value: T;\n}\n\nexport interface Failure<E> {\n readonly success: false;\n readonly error: E;\n}\n\nexport type Result<T, E = Error> = Success<T> | Failure<E>;\n\n// Constructor functions\n\nexport function success<T>(value: T): Success<T> {\n return { success: true, value };\n}\n\nexport function failure<E>(error: E): Failure<E> {\n return { success: false, error };\n}\n\n// Type guards\n\nexport function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {\n return result.success === true;\n}\n\nexport function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {\n return result.success === false;\n}\n\n// Utility functions\n\nexport function tryCatch<T>(fn: () => T): Result<T, Error> {\n try {\n return success(fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport async function tryCatchAsync<T>(\n fn: () => Promise<T>,\n): Promise<Result<T, Error>> {\n try {\n return success(await fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (isSuccess(result)) return result.value;\n // Always throw an Error instance so V8 attaches a stack trace and\n // `instanceof Error` checks in catch blocks work as expected.\n if (result.error instanceof Error) throw result.error;\n throw new Error(\n typeof result.error === \"object\" && result.error !== null\n ? JSON.stringify(result.error)\n : String(result.error),\n );\n}\n\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (isSuccess(result)) return result.value;\n return defaultValue;\n}\n\nexport function mapResult<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => U,\n): Result<U, E> {\n if (isSuccess(result)) return success(fn(result.value));\n return result;\n}\n\nexport function mapError<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => F,\n): Result<T, F> {\n if (isFailure(result)) return failure(fn(result.error));\n return result;\n}\n\n// Error narrowing utilities\n\nexport function isError(value: unknown): value is Error {\n return value instanceof Error;\n}\n\nexport function isNodeError(value: unknown): value is NodeJS.ErrnoException {\n return value instanceof Error && \"code\" in value;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === \"string\") return error;\n return String(error);\n}\n","/**\n * Fluid API client for authentication operations\n */\n\nimport type { CliError } from \"../utils/errors.js\";\nimport { type Result, success, failure } from \"../utils/result.js\";\n\nconst API_TIMEOUT_MS = 10_000;\n\nexport function getFluidApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nfunction makeSignal(): { signal: AbortSignal; cleanup: () => void } {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);\n return { signal: controller.signal, cleanup: () => clearTimeout(timer) };\n}\n\nexport interface FluidApiError extends CliError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\nexport const FLUID_API_ERROR = {\n INVALID_TOKEN: {\n code: \"INVALID_TOKEN\",\n message: \"Token is invalid or expired\",\n },\n API_UNREACHABLE: {\n code: \"API_UNREACHABLE\",\n message: \"Could not reach the Fluid API\",\n },\n UUID_NOT_FOUND: {\n code: \"UUID_NOT_FOUND\",\n message: \"Verification session not found\",\n },\n CODE_EXPIRED: {\n code: \"CODE_EXPIRED\",\n message: \"Verification code has expired — please request a new one\",\n },\n INVALID_CODE: {\n code: \"INVALID_CODE\",\n message: \"Invalid verification code\",\n },\n COMPANY_NOT_FOUND: {\n code: \"COMPANY_NOT_FOUND\",\n message: \"Company not found\",\n },\n SWITCH_FAILED: {\n code: \"SWITCH_FAILED\",\n message: \"Failed to switch company\",\n },\n} as const;\n\nfunction createApiError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): FluidApiError {\n return { code: template.code, message: template.message, details };\n}\n\nexport interface CompanyInfo {\n readonly name: string;\n}\n\nexport interface UserCompany {\n readonly id: number;\n readonly name: string;\n}\n\nexport interface SwitchCompanyResult {\n readonly companyId: number;\n readonly companyName: string;\n readonly fluidShop: string;\n readonly jwt: string;\n}\n\nexport interface MfaResponse {\n readonly uuid: string;\n readonly expiresAt: string;\n}\n\nexport interface CompanyChoice {\n readonly id: number;\n readonly name: string;\n readonly shopName: string;\n readonly jwt: string;\n}\n\nexport interface ConfirmMfaResponse {\n readonly authType: string;\n readonly companies: CompanyChoice[];\n}\n\n/**\n * Validate a token against the Fluid API and return company info\n */\nexport async function validateToken(\n token: string,\n): Promise<Result<CompanyInfo, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/company/v1/companies/me`,\n {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${token}`,\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n data?: { company?: { name?: string } };\n };\n const name = data?.data?.company?.name;\n if (!name) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /companies/me\",\n ),\n );\n }\n return success({ name });\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}: Check that your token is valid and non-expired.`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Send a multi-factor authentication code to the given email address.\n * Always returns 201 from the API (anti-enumeration).\n */\nexport async function sendMfa(\n email: string,\n): Promise<Result<MfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/send_mfa`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email }),\n signal,\n },\n );\n\n if (response.status === 201) {\n const data = (await response.json()) as {\n multi_factor_authentication?: {\n uuid?: string;\n verification_code_expires_at?: string;\n };\n };\n const uuid = data.multi_factor_authentication?.uuid;\n const expiresAt =\n data.multi_factor_authentication?.verification_code_expires_at;\n if (!uuid || !expiresAt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /send_mfa\",\n ),\n );\n }\n return success({ uuid, expiresAt });\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Confirm a multi-factor authentication code and retrieve company JWTs.\n */\nexport async function confirmMfa(\n uuid: string,\n code: string,\n): Promise<Result<ConfirmMfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/confirm_mfa`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n uuid,\n multi_factor_authentication: { verification_code: code },\n }),\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n authenticated?: boolean;\n auth_type?: string;\n companies?:\n | {\n id: number;\n name: string;\n shop_name: string;\n jwt: string;\n }[]\n | null;\n };\n if (!data.authenticated) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_CODE,\n \"Authentication was not confirmed by the server\",\n ),\n );\n }\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success({\n authType: data.auth_type ?? \"\",\n companies: companies.map((c) => ({\n id: c.id,\n name: c.name,\n shopName: c.shop_name,\n jwt: c.jwt,\n })),\n });\n }\n\n if (response.status === 404) {\n return failure(createApiError(FLUID_API_ERROR.UUID_NOT_FOUND));\n }\n if (response.status === 410) {\n return failure(createApiError(FLUID_API_ERROR.CODE_EXPIRED));\n }\n if (response.status === 422) {\n return failure(createApiError(FLUID_API_ERROR.INVALID_CODE));\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Fetch all companies the authenticated user has access to.\n */\nexport async function fetchUserCompanies(\n token: string,\n baseUrl?: string,\n): Promise<Result<UserCompany[], FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(`${apiBase}/api/me`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${token}` },\n signal,\n });\n\n if (response.ok) {\n const data = (await response.json()) as {\n companies?: { id: number; name: string }[];\n };\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success(companies.map((c) => ({ id: c.id, name: c.name })));\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Switch to a different company and receive a new JWT.\n *\n * Response shape (from @fluid-app/auth SwitchCompanyResponse):\n * { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }\n */\nexport async function switchCompany(\n token: string,\n companyId: number,\n baseUrl?: string,\n): Promise<Result<SwitchCompanyResult, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(\n `${apiBase}/api/authentication/company/${companyId}/switch`,\n {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n company?: {\n id: number;\n name: string;\n fluid_shop: string;\n jwt: string;\n };\n };\n const company = data.company;\n if (!company?.jwt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n \"Unexpected response shape from /switch\",\n ),\n );\n }\n return success({\n companyId: company.id,\n companyName: company.name,\n fluidShop: company.fluid_shop,\n jwt: company.jwt,\n });\n }\n\n if (response.status === 404) {\n return failure(\n createApiError(\n FLUID_API_ERROR.COMPANY_NOT_FOUND,\n `Company ${companyId} not found`,\n ),\n );\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n","/**\n * Configuration types for the Fluid CLI\n */\n\nexport interface FluidProfile {\n readonly name: string;\n readonly token: string;\n readonly companyName: string;\n readonly storedAt: string; // ISO-8601\n readonly baseUrl?: string;\n /** Stable storefront identity written by Mist and newer auth endpoints. */\n readonly fluidShop?: string;\n /**\n * Cached commit identity for GitSync subjects, so a publish doesn't\n * pay for `/api/me` to learn a name that changes approximately never.\n * Scoped to the profile (token + company), and dropped whenever login\n * or a company switch rewrites it. See git-sync/actor-cache.ts.\n */\n readonly gitSyncActor?: {\n readonly name: string;\n readonly publicId: string | null;\n /** ISO-8601. */\n readonly cachedAt: string;\n };\n}\n\nexport interface FluidConfig {\n activeProfile: string | null;\n profiles: Record<string, FluidProfile>;\n plugins: Record<string, unknown>;\n /**\n * Allow-list of plugin package names.\n *\n * - `null` (default) — auto-discover and load all `@fluid-app/fluid-cli-*`\n * and `*-cli-commands` packages found in `node_modules` or the pnpm\n * workspace. Only `@fluid-app`-scoped packages matching the naming\n * convention are eligible; no third-party code is loaded.\n * - `string[]` — only load plugins whose names appear in the array.\n * Set to `[]` to disable all plugins.\n */\n enabledPlugins: string[] | null;\n /** Historical local workspace slugs, keyed by stable storefront slug.\n * Optional because older CLI versions and hand-written test configs do not\n * know about Mist's local workspace compatibility metadata. */\n workspaceAliasesByFluidShop?: Record<string, string[]>;\n}\n\nexport function createDefaultConfig(): FluidConfig {\n return {\n activeProfile: null,\n profiles: {},\n plugins: {},\n enabledPlugins: null, // auto-discover all plugins\n };\n}\n","/**\n * XDG-compliant config directory resolution\n *\n * Priority:\n * 1. FLUID_CONFIG_DIR env var (explicit override)\n * 2. ~/.fluid/ on macOS (convention for CLI tools)\n * 3. %APPDATA%/fluid/ on Windows\n * 4. $XDG_CONFIG_HOME/fluid/ on Linux (XDG spec)\n * 5. ~/.config/fluid/ fallback on Linux\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function getConfigDir(): string {\n const envOverride = process.env[\"FLUID_CONFIG_DIR\"];\n if (envOverride) return envOverride;\n\n if (process.platform === \"darwin\") {\n return join(homedir(), \".fluid\");\n }\n\n if (process.platform === \"win32\") {\n const appData =\n process.env[\"APPDATA\"] ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"fluid\");\n }\n\n const xdgConfig = process.env[\"XDG_CONFIG_HOME\"];\n if (xdgConfig) return join(xdgConfig, \"fluid\");\n\n return join(homedir(), \".config\", \"fluid\");\n}\n\nexport function getConfigFilePath(): string {\n return join(getConfigDir(), \"config.json\");\n}\n","/**\n * Read/write config.json with atomic writes and safe defaults\n */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport { type FluidConfig, createDefaultConfig } from \"./types.js\";\nimport { getConfigFilePath } from \"./paths.js\";\n\nexport function readConfig(): FluidConfig {\n const configPath = getConfigFilePath();\n\n if (!existsSync(configPath)) {\n return createDefaultConfig();\n }\n\n const raw = readFileSync(configPath, \"utf8\");\n\n let parsed: Partial<FluidConfig>;\n try {\n parsed = JSON.parse(raw) as Partial<FluidConfig>;\n } catch {\n // Corrupted config — fall back to defaults rather than crashing\n return createDefaultConfig();\n }\n\n // Merge with defaults to handle schema evolution\n return withDurableWorkspaceAliases({\n ...createDefaultConfig(),\n ...parsed,\n });\n}\n\nexport function writeConfig(config: FluidConfig): void {\n const configPath = getConfigFilePath();\n const dir = dirname(configPath);\n\n // 0o700: owner-only access so other users cannot enumerate the config dir\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // Atomic write: write to a temp file in the same directory, then rename.\n // rename() is a POSIX atomic operation — a crash mid-write will never leave\n // a partially-written config.json. The temp file must be on the same\n // filesystem as the destination for rename() to succeed.\n const suffix = randomBytes(6).toString(\"hex\");\n const tmpPath = join(dir, `.fluid-config-${suffix}.tmp`);\n\n try {\n writeFileSync(\n tmpPath,\n JSON.stringify(withDurableWorkspaceAliases(config), null, 2) + \"\\n\",\n {\n encoding: \"utf8\",\n // NOTE: mode: 0o600 restricts to owner read/write on POSIX (macOS, Linux).\n // On Windows, Node.js ignores this option — NTFS permissions are not\n // set via the mode parameter. Windows file protection would require\n // icacls or Windows security descriptor APIs.\n mode: 0o600,\n },\n );\n renameSync(tmpPath, configPath);\n } catch (err) {\n // Clean up the temp file if something went wrong before the rename\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore cleanup errors — the temp file may not exist yet\n }\n throw err;\n }\n}\n\nfunction workspaceSlug(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction storefrontSlug(value: string): string {\n const withoutScheme = value.trim().replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, \"\");\n const host = withoutScheme.split(/[/?#]/, 1)[0]?.replace(/:\\d+$/, \"\") ?? \"\";\n return workspaceSlug(host.replace(/\\.fluid\\.app$/i, \"\"));\n}\n\n/** Preserve rename history even when `fluid logout` clears the profiles that\n * originally carried it. Derivation on read migrates existing config files;\n * normalization on every write makes the migration durable before profiles\n * are removed. */\nfunction withDurableWorkspaceAliases(config: FluidConfig): FluidConfig {\n const aliases: Record<string, string[]> = {};\n const add = (storefront: string, candidates: readonly string[]): void => {\n const key = storefrontSlug(storefront);\n if (!key) return;\n const values = new Set(aliases[key] ?? []);\n for (const candidate of candidates) {\n const slug = workspaceSlug(candidate);\n if (slug) values.add(slug);\n }\n if (values.size > 0) aliases[key] = [...values];\n };\n\n for (const [storefront, candidates] of Object.entries(\n config.workspaceAliasesByFluidShop ?? {},\n )) {\n if (!Array.isArray(candidates)) continue;\n add(\n storefront,\n candidates.filter(\n (candidate): candidate is string => typeof candidate === \"string\",\n ),\n );\n }\n for (const profile of Object.values(config.profiles)) {\n if (profile.fluidShop) {\n add(profile.fluidShop, [profile.name, profile.companyName]);\n }\n }\n\n return Object.keys(aliases).length > 0 ||\n config.workspaceAliasesByFluidShop !== undefined\n ? { ...config, workspaceAliasesByFluidShop: aliases }\n : config;\n}\n\nexport function updateConfig(\n updater: (config: FluidConfig) => FluidConfig,\n): FluidConfig {\n const config = readConfig();\n const updated = updater(config);\n writeConfig(updated);\n return updated;\n}\n","/**\n * Project-level config (.fluidrc) reader.\n *\n * Walks upward from a starting directory to find a `.fluidrc` file.\n * The file is JSON with a `profile` field that maps to a profile name\n * in `~/.fluid/config.json`.\n *\n * This allows portal repos to pin themselves to a specific CLI profile\n * so that `fluid portal pull` / `push` always use the correct company\n * credentials, regardless of which profile is globally active.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport interface ProjectRcConfig {\n readonly profile: string;\n}\n\nconst RC_FILENAME = \".fluidrc\";\n\n/** Cached result keyed by the starting directory. */\nlet cachedResult: { dir: string; config: ProjectRcConfig | null } | undefined;\n\n/**\n * Walk upward from `startDir` looking for a `.fluidrc` file.\n *\n * Returns the parsed config when found, or `null` if no valid `.fluidrc`\n * exists in any ancestor directory.\n *\n * The result is cached per starting directory for the lifetime of the\n * process — CLI commands are short-lived so stale-cache is not a concern.\n */\nexport function findProjectConfig(startDir: string): ProjectRcConfig | null {\n if (cachedResult && cachedResult.dir === startDir) {\n return cachedResult.config;\n }\n\n let dir = startDir;\n while (true) {\n const rcPath = join(dir, RC_FILENAME);\n if (existsSync(rcPath)) {\n try {\n const raw = readFileSync(rcPath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<ProjectRcConfig>;\n if (typeof parsed.profile === \"string\" && parsed.profile.length > 0) {\n const config: ProjectRcConfig = { profile: parsed.profile };\n cachedResult = { dir: startDir, config };\n return config;\n }\n } catch {\n // Malformed .fluidrc — treat as absent\n }\n\n // Found a .fluidrc but it was invalid or empty — stop searching\n // upward to avoid confusing inheritance from a parent directory.\n cachedResult = { dir: startDir, config: null };\n return null;\n }\n\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n cachedResult = { dir: startDir, config: null };\n return null;\n}\n\n/** Reset the cache. Exposed for tests only. */\nexport function _resetProjectConfigCache(): void {\n cachedResult = undefined;\n}\n","/**\n * Token storage and retrieval from config\n */\n\nimport { readConfig } from \"../config/config.js\";\nimport { findProjectConfig } from \"../config/project-config.js\";\nimport type { FluidProfile } from \"../config/types.js\";\n\n/**\n * Get the auth token for a named profile, or the active profile when omitted.\n *\n * Resolution order (when `profileName` is omitted — mirrors `resolveToken`\n * in domain/context.ts):\n * 1. `FLUID_TOKEN` / `FLUID_API_TOKEN` environment variables\n * 2. Project-level `.fluidrc` profile (walks upward from cwd)\n * 3. Global active profile from `~/.fluid/config.json`\n *\n * When `profileName` is provided explicitly (e.g. via `--profile` flag),\n * it takes precedence over the environment variables, `.fluidrc`, and the\n * active profile.\n */\nexport function getAuthToken(profileName?: string): string | null {\n const config = readConfig();\n\n // Explicit profile name takes precedence\n if (profileName) {\n const profile = config.profiles[profileName];\n return profile?.token ?? null;\n }\n\n // Environment variable override (before any stored profile)\n const envToken = process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"];\n if (envToken) return envToken;\n\n // Project-level .fluidrc profile\n const projectConfig = findProjectConfig(process.cwd());\n if (projectConfig?.profile) {\n const profile = config.profiles[projectConfig.profile];\n if (profile) return profile.token;\n }\n\n // Fall back to global active profile\n if (!config.activeProfile) return null;\n const profile = config.profiles[config.activeProfile];\n return profile?.token ?? null;\n}\n\n/**\n * Get the active profile, or null if not logged in\n */\nexport function getActiveProfile(): FluidProfile | null {\n const config = readConfig();\n if (!config.activeProfile) return null;\n\n return config.profiles[config.activeProfile] ?? null;\n}\n\n/**\n * List all stored profile names\n */\nexport function listProfileNames(): string[] {\n const config = readConfig();\n return Object.keys(config.profiles);\n}\n"],"mappings":"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"}