@fluid-app/fluid-cli 0.1.21 → 0.1.23

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.
@@ -538,6 +538,72 @@ async function importPlugin(specifier) {
538
538
  return mod["default"] ?? mod;
539
539
  }
540
540
  //#endregion
541
+ //#region src/plugins/format-load-line.ts
542
+ /**
543
+ * Format one-line diagnostics naming a CLI plugin build and where it was
544
+ * resolved from. The resolved path is the load-bearing detail: a workspace
545
+ * build and a published copy share the same name and version, so the path is
546
+ * the only thing that distinguishes them. See CURRENT-3218.
547
+ */
548
+ function modeLabel(preferInstallation) {
549
+ return preferInstallation ? "installation-preferred" : "project-first";
550
+ }
551
+ /**
552
+ * Render a specifier for display: `file://` URLs become absolute filesystem
553
+ * paths; anything else is shown verbatim. Never throws — a malformed `file://`
554
+ * specifier falls back to its raw form so a diagnostic can never turn a
555
+ * successful load into a reported failure.
556
+ */
557
+ function displaySource(importSpecifier) {
558
+ if (!importSpecifier.startsWith("file://")) return importSpecifier;
559
+ try {
560
+ return fileURLToPath(importSpecifier);
561
+ } catch {
562
+ return importSpecifier;
563
+ }
564
+ }
565
+ function formatPluginLoadLine(info) {
566
+ return `Loaded plugin ${info.name}@${info.version} from ${displaySource(info.importSpecifier)} [${modeLabel(info.preferInstallation)}]`;
567
+ }
568
+ function formatPluginLoadFailure(info) {
569
+ return `Failed to load plugin ${info.name} from ${displaySource(info.importSpecifier)} [${modeLabel(info.preferInstallation)}]: ${info.message}`;
570
+ }
571
+ //#endregion
572
+ //#region src/plugins/resolve-load-path.ts
573
+ const defaultResolver = (specifier) => import.meta.resolve(specifier);
574
+ /**
575
+ * Return a `file://` URL for `importSpecifier`. `file://` specifiers (already
576
+ * resolved by discovery) pass through untouched; bare specifiers are resolved
577
+ * via `resolve`. If resolution throws, the bare specifier is returned so a
578
+ * diagnostic degrades gracefully rather than failing the load.
579
+ */
580
+ function resolveLoadPath(importSpecifier, resolve = defaultResolver) {
581
+ if (importSpecifier.startsWith("file://")) return importSpecifier;
582
+ try {
583
+ return resolve(importSpecifier);
584
+ } catch {
585
+ return importSpecifier;
586
+ }
587
+ }
588
+ //#endregion
589
+ //#region src/plugins/should-log-plugin-loads.ts
590
+ /**
591
+ * Decide whether to print the per-plugin "Loaded plugin …" diagnostics.
592
+ *
593
+ * A fluid-mono checkout resolves ~a dozen plugin packages, so emitting a line
594
+ * for each on every command (including `--help` and tab-completion, and into
595
+ * Mist's captured stderr dialog) is noise. The success diagnostics are gated
596
+ * behind an explicit opt-in; load *failures* are always surfaced by the loader
597
+ * regardless of this flag. See CURRENT-3218.
598
+ */
599
+ function isTruthyFlag(value) {
600
+ return value === "1" || value === "true";
601
+ }
602
+ function shouldLogPluginLoads(argv = process.argv, env = process.env) {
603
+ if (argv.includes("--verbose")) return true;
604
+ return isTruthyFlag(env["FLUID_CLI_DEBUG"] ?? env["FLUID_CLI_VERBOSE"]);
605
+ }
606
+ //#endregion
541
607
  //#region src/plugins/loader.ts
542
608
  function isFluidPlugin(value) {
543
609
  if (typeof value !== "object" || value === null) return false;
@@ -548,7 +614,8 @@ function isFluidPlugin(value) {
548
614
  * Discover, import, and register all installed plugins
549
615
  */
550
616
  async function loadPlugins(program, basePath) {
551
- const discovered = discoverPlugins(basePath, void 0, { preferInstallation: process.env["FLUID_CLI_PREFER_INSTALLATION_PLUGINS"] === "1" });
617
+ const preferInstallation = process.env["FLUID_CLI_PREFER_INSTALLATION_PLUGINS"] === "1";
618
+ const discovered = discoverPlugins(basePath, void 0, { preferInstallation });
552
619
  const { enabledPlugins } = readConfig();
553
620
  const allowedSet = enabledPlugins !== null ? new Set(enabledPlugins) : null;
554
621
  const plugins = allowedSet ? discovered.filter((p) => allowedSet.has(p.name)) : discovered;
@@ -558,16 +625,31 @@ async function loadPlugins(program, basePath) {
558
625
  getAuthToken,
559
626
  configDir: getConfigDir()
560
627
  };
561
- for (const { name, importSpecifier } of plugins) try {
562
- const exported = await importPlugin(importSpecifier);
563
- if (!isFluidPlugin(exported)) {
564
- console.warn(chalk.yellow(`Warning: ${name} does not export a valid FluidPlugin`));
565
- continue;
628
+ const verbose = shouldLogPluginLoads();
629
+ for (const { name, importSpecifier } of plugins) {
630
+ const source = resolveLoadPath(importSpecifier);
631
+ try {
632
+ const exported = await importPlugin(importSpecifier);
633
+ if (!isFluidPlugin(exported)) {
634
+ console.warn(chalk.yellow(`Warning: ${name} does not export a valid FluidPlugin`));
635
+ continue;
636
+ }
637
+ await exported.register(ctx);
638
+ if (verbose) console.error(chalk.dim(formatPluginLoadLine({
639
+ name: exported.name,
640
+ version: exported.version,
641
+ importSpecifier: source,
642
+ preferInstallation
643
+ })));
644
+ } catch (err) {
645
+ const message = err instanceof Error ? err.message : String(err);
646
+ console.warn(chalk.yellow(`Warning: ${formatPluginLoadFailure({
647
+ name,
648
+ importSpecifier: source,
649
+ preferInstallation,
650
+ message
651
+ })}`));
566
652
  }
567
- await exported.register(ctx);
568
- } catch (err) {
569
- const message = err instanceof Error ? err.message : String(err);
570
- console.warn(chalk.yellow(`Warning: Failed to load plugin ${name}: ${message}`));
571
653
  }
572
654
  }
573
655
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"fluid.mjs","names":[],"sources":["../../src/auth/profiles.ts","../../src/commands/login.ts","../../src/commands/logout.ts","../../src/commands/whoami.ts","../../src/commands/switch.ts","../../src/plugins/discovery.ts","../../src/plugins/loader.ts","../../src/bin/fluid.ts"],"sourcesContent":["/**\n * Helpers for constructing stored auth profiles.\n */\n\nimport type { FluidProfile } from \"../config/types.js\";\n\ninterface CreateFluidProfileInput {\n readonly name: string;\n readonly token: string;\n readonly companyName: string;\n readonly baseUrl: string;\n readonly storedAt?: string;\n}\n\nexport function createFluidProfile({\n name,\n token,\n companyName,\n baseUrl,\n storedAt,\n}: CreateFluidProfileInput): FluidProfile {\n return {\n name,\n token,\n companyName,\n storedAt: storedAt ?? new Date().toISOString(),\n baseUrl,\n };\n}\n","/**\n * fluid login — authenticate via email MFA or API token\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport {\n validateToken,\n sendMfa,\n confirmMfa,\n getFluidApiBase,\n type CompanyChoice,\n} from \"../auth/fluid-api.js\";\nimport { createFluidProfile } from \"../auth/profiles.js\";\nimport { updateConfig, readConfig } from \"../config/config.js\";\n\nasync function confirmOverwrite(profileName: string): Promise<boolean> {\n const existing = readConfig().profiles[profileName];\n if (!existing) return true;\n\n const response = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Profile \"${profileName}\" already exists (${existing.companyName}). Overwrite?`,\n initial: false,\n });\n return Boolean(response[\"overwrite\"]);\n}\n\nfunction storeProfile(\n profileName: string,\n token: string,\n companyName: string,\n): void {\n updateConfig((config) => ({\n ...config,\n activeProfile: profileName,\n profiles: {\n ...config.profiles,\n [profileName]: createFluidProfile({\n name: profileName,\n token,\n companyName,\n baseUrl: getFluidApiBase(),\n }),\n },\n }));\n}\n\nasync function loginWithToken(\n token: string,\n profileName?: string,\n): Promise<void> {\n const spinner = ora(\"Validating token...\").start();\n const result = await validateToken(token);\n\n if (!result.success) {\n spinner.fail(chalk.red(result.error.message));\n if (result.error.details) {\n console.log(chalk.dim(result.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n const name = profileName ?? result.value.name;\n\n // Stop spinner before potential interactive prompt to avoid terminal corruption\n spinner.succeed(\n chalk.green(`Token valid — ${chalk.bold(result.value.name)}`),\n );\n\n if (!(await confirmOverwrite(name))) {\n console.log(\n chalk.yellow(\"Login cancelled — existing profile not overwritten.\"),\n );\n return;\n }\n\n storeProfile(name, token, result.value.name);\n console.log(\n chalk.green(\n `Logged in as ${chalk.bold(result.value.name)} (profile: ${chalk.bold(name)})`,\n ),\n );\n}\n\nasync function loginWithEmail(\n initialEmail?: string,\n profileName?: string,\n): Promise<void> {\n // 1. Prompt for email\n let email = initialEmail;\n if (!email) {\n const response = await prompts({\n type: \"text\",\n name: \"email\",\n message: \"Enter your email\",\n });\n email = response[\"email\"] as string | undefined;\n if (!email) {\n console.log(chalk.red(\"No email provided. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n }\n\n // 2. Send MFA code\n const sendSpinner = ora(\"Sending verification code...\").start();\n const sendResult = await sendMfa(email);\n\n if (!sendResult.success) {\n sendSpinner.fail(chalk.red(sendResult.error.message));\n if (sendResult.error.details) {\n console.log(chalk.dim(sendResult.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n const expiresAt = new Date(sendResult.value.expiresAt);\n const expiresInMs = expiresAt.getTime() - Date.now();\n const expiresInMin = Math.round(expiresInMs / 1000 / 60);\n const expiryNote =\n expiresInMin > 0\n ? `expires in ~${expiresInMin} min`\n : \"code may expire soon — check your email quickly\";\n sendSpinner.succeed(\n `Verification code sent — check your email (${expiryNote})`,\n );\n\n // 3. Prompt for verification code (retry up to 3 times on invalid code)\n const MAX_CODE_ATTEMPTS = 3;\n let confirmResult;\n for (let attempt = 1; attempt <= MAX_CODE_ATTEMPTS; attempt++) {\n const codeResponse = await prompts({\n type: \"text\",\n name: \"code\",\n message: \"Enter the 6-digit code\",\n });\n const code = codeResponse[\"code\"] as string | undefined;\n if (!code) {\n console.log(chalk.red(\"No code provided. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n if (!/^\\d{6}$/.test(code)) {\n console.log(chalk.red(\"Code must be exactly 6 digits.\"));\n if (attempt < MAX_CODE_ATTEMPTS) continue;\n console.log(chalk.red(\"Too many invalid attempts. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n\n const confirmSpinner = ora(\"Verifying code...\").start();\n confirmResult = await confirmMfa(sendResult.value.uuid, code);\n\n if (confirmResult.success) {\n confirmSpinner.succeed(\"Verified\");\n break;\n }\n\n // Retryable: wrong code, still have attempts left\n if (\n confirmResult.error.code === \"INVALID_CODE\" &&\n attempt < MAX_CODE_ATTEMPTS\n ) {\n confirmSpinner.fail(\n chalk.red(\n `${confirmResult.error.message} (attempt ${attempt}/${MAX_CODE_ATTEMPTS})`,\n ),\n );\n continue;\n }\n\n // Non-retryable (expired, unreachable, etc.) or final attempt\n confirmSpinner.fail(chalk.red(confirmResult.error.message));\n if (confirmResult.error.details) {\n console.log(chalk.dim(confirmResult.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n if (!confirmResult?.success) return;\n\n const { companies } = confirmResult.value;\n\n if (companies.length === 0) {\n console.log(chalk.red(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n\n // 4. Select company (auto-select if only one)\n let selected: CompanyChoice;\n if (companies.length === 1) {\n const first = companies[0];\n if (!first) {\n console.log(chalk.red(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n selected = first;\n } else {\n // Key on `c.id`, not the array index, and resolve via `.find` —\n // `prompts` autocomplete returns the raw typed string on Enter when\n // the user hasn't arrow-key-highlighted a filtered choice, so an\n // array-index lookup ends up asking for `companies[\"Fluid\"]` and\n // failing with \"Invalid selection\". Same shape as `switch.ts`.\n const companyChoices = companies.map((c) => ({\n title: `${c.name} (${c.shopName})`,\n value: c.id,\n }));\n\n const selectResponse = await prompts({\n type: \"autocomplete\",\n name: \"companyId\",\n message: \"Select a company (type to search)\",\n choices: companyChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.title.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n\n const companyId = selectResponse[\"companyId\"] as number | undefined;\n\n if (companyId === undefined) {\n console.log(chalk.red(\"No company selected. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n const choice = companies.find((c) => c.id === companyId);\n if (!choice) {\n console.log(chalk.red(\"Invalid selection. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n selected = choice;\n }\n\n // 5. Store profile\n const name = profileName ?? selected.name;\n\n if (!(await confirmOverwrite(name))) {\n console.log(\n chalk.yellow(\"Login cancelled — existing profile not overwritten.\"),\n );\n return;\n }\n\n storeProfile(name, selected.jwt, selected.name);\n console.log(\n chalk.green(\n `Logged in as ${chalk.bold(selected.name)} (profile: ${chalk.bold(name)})`,\n ),\n );\n}\n\nexport const loginCommand = new Command(\"login\")\n .description(\"Authenticate with the Fluid API\")\n .option(\n \"-t, --token <token>\",\n \"API token (skips email flow). Prefer FLUID_TOKEN env var to avoid shell history exposure\",\n )\n .option(\"-e, --email <email>\", \"Email address for MFA login\")\n .option(\"-n, --name <name>\", \"Profile name (defaults to company name)\")\n .action(async (opts: { token?: string; email?: string; name?: string }) => {\n // Accept token from env var to avoid shell history / process listing exposure\n const token = opts.token ?? process.env[\"FLUID_TOKEN\"];\n if (token) {\n if (opts.token) {\n console.log(\n chalk.yellow(\n \"Warning: token passed via --token flag is visible in shell history and process listings. \" +\n \"Prefer the FLUID_TOKEN environment variable.\",\n ),\n );\n }\n await loginWithToken(token, opts.name);\n } else {\n await loginWithEmail(opts.email, opts.name);\n }\n });\n","/**\n * fluid logout — remove stored auth profile(s)\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { readConfig, writeConfig } from \"../config/config.js\";\n\nexport const logoutCommand = new Command(\"logout\")\n .description(\"Remove stored authentication\")\n .option(\"-a, --all\", \"Remove all profiles\")\n .action((opts: { all?: boolean }) => {\n const config = readConfig();\n\n if (opts.all) {\n writeConfig({\n ...config,\n activeProfile: null,\n profiles: {},\n });\n console.log(chalk.green(\"All profiles removed.\"));\n return;\n }\n\n if (!config.activeProfile) {\n console.log(chalk.yellow(\"Not currently logged in.\"));\n return;\n }\n\n const profileName = config.activeProfile;\n const { [profileName]: _, ...remainingProfiles } = config.profiles;\n\n // Pick the first remaining profile as active, or null\n const nextActive = Object.keys(remainingProfiles)[0] ?? null;\n\n writeConfig({\n ...config,\n activeProfile: nextActive,\n profiles: remainingProfiles,\n });\n\n console.log(\n chalk.green(`Logged out of profile ${chalk.bold(profileName)}.`),\n );\n if (nextActive) {\n console.log(chalk.dim(`Switched to profile ${nextActive}.`));\n }\n });\n","/**\n * fluid whoami — show current auth profile and validate against API\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { getActiveProfile } from \"../auth/token.js\";\nimport { validateToken } from \"../auth/fluid-api.js\";\n\nexport const whoamiCommand = new Command(\"whoami\")\n .description(\"Show the current authenticated profile\")\n .action(async () => {\n const profile = getActiveProfile();\n\n if (!profile) {\n console.log(\n chalk.yellow(\"Not logged in. Run `fluid login` to authenticate.\"),\n );\n process.exitCode = 1;\n return;\n }\n\n const spinner = ora(\"Verifying token...\").start();\n const result = await validateToken(profile.token);\n\n if (!result.success) {\n spinner.fail(chalk.red(\"Token is no longer valid.\"));\n console.log(chalk.dim(`Profile: ${profile.name}`));\n console.log(chalk.dim(\"Run `fluid login` to re-authenticate.\"));\n process.exitCode = 1;\n return;\n }\n\n spinner.succeed(chalk.green(\"Authenticated\"));\n console.log(` Profile: ${chalk.bold(profile.name)}`);\n console.log(` Company: ${chalk.bold(result.value.name)}`);\n console.log(` Stored: ${chalk.dim(profile.storedAt)}`);\n });\n","/**\n * fluid switch — switch between companies (fetched from API, with local fallback)\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport {\n fetchUserCompanies,\n switchCompany,\n FLUID_API_ERROR,\n getFluidApiBase,\n type UserCompany,\n} from \"../auth/fluid-api.js\";\nimport { createFluidProfile } from \"../auth/profiles.js\";\nimport { getActiveProfile } from \"../auth/token.js\";\nimport { readConfig, updateConfig } from \"../config/config.js\";\n\nasync function localSwitch(profileArg?: string): Promise<void> {\n const config = readConfig();\n const profileNames = Object.keys(config.profiles);\n\n if (profileNames.length === 0) {\n console.log(chalk.yellow(\"No profiles stored. Run `fluid login` first.\"));\n process.exitCode = 1;\n return;\n }\n\n let targetProfile = profileArg;\n\n if (!targetProfile) {\n const profileChoices = profileNames.map((name) => ({\n title: name === config.activeProfile ? `${name} (active)` : name,\n value: name,\n }));\n\n const response = await prompts({\n type: \"autocomplete\",\n name: \"profile\",\n message: \"Select a profile (type to search)\",\n choices: profileChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.value.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n targetProfile = response[\"profile\"] as string | undefined;\n\n if (!targetProfile) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n }\n\n if (!config.profiles[targetProfile]) {\n console.log(chalk.red(`Profile \"${targetProfile}\" not found.`));\n console.log(chalk.dim(`Available: ${profileNames.join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n\n updateConfig((c) => ({ ...c, activeProfile: targetProfile! }));\n console.log(chalk.green(`Switched to profile ${chalk.bold(targetProfile)}.`));\n}\n\nasync function selectCompany(\n companies: UserCompany[],\n activeCompanyName: string | undefined,\n): Promise<UserCompany | undefined> {\n const companyChoices = companies.map((c) => ({\n title:\n c.name === activeCompanyName\n ? `${c.name} ${chalk.dim(\"(active)\")}`\n : c.name,\n value: c.id,\n }));\n\n const response = await prompts({\n type: \"autocomplete\",\n name: \"companyId\",\n message: \"Select a company (type to search)\",\n choices: companyChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.title.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n\n const companyId = response[\"companyId\"] as number | undefined;\n if (companyId === undefined) return undefined;\n return companies.find((c) => c.id === companyId);\n}\n\nasync function performSwitch(\n token: string,\n company: UserCompany,\n baseUrl?: string,\n): Promise<void> {\n const spinner = ora(`Switching to ${chalk.bold(company.name)}...`).start();\n const result = await switchCompany(token, company.id, baseUrl);\n\n if (!result.success) {\n if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {\n spinner.fail(\n chalk.red(\n \"Your session has expired. Please run `fluid login` to re-authenticate.\",\n ),\n );\n } else {\n spinner.fail(chalk.red(result.error.message));\n if (result.error.details) {\n console.log(chalk.dim(result.error.details));\n }\n }\n process.exitCode = 1;\n return;\n }\n\n const { companyName, jwt } = result.value;\n\n updateConfig((config) => ({\n ...config,\n activeProfile: companyName,\n profiles: {\n ...config.profiles,\n [companyName]: createFluidProfile({\n name: companyName,\n token: jwt,\n companyName,\n baseUrl: baseUrl ?? getFluidApiBase(),\n }),\n },\n }));\n\n spinner.succeed(\n chalk.green(\n `Switched to ${chalk.bold(companyName)} (profile: ${chalk.bold(companyName)})`,\n ),\n );\n}\n\nexport const switchCommand = new Command(\"switch\")\n .description(\"Switch between companies\")\n .argument(\"[profile]\", \"Company or profile name to switch to\")\n .action(async (profileArg?: string) => {\n const activeProfile = getActiveProfile();\n\n if (!activeProfile) {\n console.log(chalk.yellow(\"Not logged in. Run `fluid login` first.\"));\n process.exitCode = 1;\n return;\n }\n\n const token = activeProfile.token;\n const activeCompanyName = activeProfile.companyName;\n const activeBaseUrl = activeProfile.baseUrl ?? getFluidApiBase();\n\n // Fetch companies from API\n const spinner = ora(\"Fetching companies...\").start();\n const result = await fetchUserCompanies(token, activeBaseUrl);\n\n if (!result.success) {\n if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {\n spinner.fail(\n chalk.red(\n \"Your session has expired. Please run `fluid login` to re-authenticate.\",\n ),\n );\n process.exitCode = 1;\n return;\n }\n spinner.warn(\n chalk.yellow(\n `Could not fetch companies from API: ${result.error.message}`,\n ),\n );\n console.log(chalk.dim(\"Falling back to locally stored profiles.\"));\n await localSwitch(profileArg);\n return;\n }\n\n const companies = result.value;\n spinner.succeed(\n `Found ${companies.length} company${companies.length === 1 ? \"\" : \"ies\"}`,\n );\n\n if (companies.length === 0) {\n console.log(chalk.yellow(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n\n // If a profile argument was passed, match against API companies first\n if (profileArg) {\n const match = companies.find(\n (c) => c.name.toLowerCase() === profileArg.toLowerCase(),\n );\n\n if (match) {\n await performSwitch(token, match, activeBaseUrl);\n return;\n }\n\n // Fall back to local profile matching\n const config = readConfig();\n if (config.profiles[profileArg]) {\n updateConfig((c) => ({ ...c, activeProfile: profileArg! }));\n console.log(\n chalk.green(`Switched to profile ${chalk.bold(profileArg)}.`),\n );\n return;\n }\n\n console.log(chalk.red(`Company or profile \"${profileArg}\" not found.`));\n process.exitCode = 1;\n return;\n }\n\n // Interactive selection\n const selected = await selectCompany(companies, activeCompanyName);\n if (!selected) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n await performSwitch(token, selected, activeBaseUrl);\n });\n","/**\n * Auto-discover @fluid-app CLI plugins.\n *\n * Three discovery strategies run in order:\n *\n * 1a. **node_modules scan (cwd)** — look in `<cwd>/node_modules/@fluid-app/`\n * for directories whose names match the plugin naming convention.\n * This is the primary mechanism for project-local plugin installs.\n *\n * 1b. **node_modules scan (CLI install location)** — look in the\n * `node_modules/@fluid-app/` directory containing the CLI core package\n * itself. This covers global installs where plugins are sibling packages\n * under the same global `node_modules/`.\n *\n * 2. **Workspace scan** — walk upward from the CLI core package root to the\n * monorepo workspace root, then scan `packages/` for sibling plugin\n * packages. This covers the pnpm-workspace development case where\n * plugins are not symlinked into `node_modules`.\n *\n * Only first-party @fluid-app scoped packages are loaded.\n */\n\nimport { existsSync, readFileSync, readdirSync, realpathSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nconst PLUGIN_SCOPE = \"@fluid-app\";\n\n/**\n * A plugin package name must match one of these patterns:\n * - @fluid-app/fluid-cli-* (standard)\n * - @fluid-app/*-cli-commands (v2025-06 style)\n */\nfunction isPluginName(packageName: string): boolean {\n if (!packageName.startsWith(`${PLUGIN_SCOPE}/`)) return false;\n const bare = packageName.slice(`${PLUGIN_SCOPE}/`.length);\n return bare.startsWith(\"fluid-cli-\") || bare.endsWith(\"-cli-commands\");\n}\n\n// ---------------------------------------------------------------------------\n// Strategy 1: node_modules scan (production / published installs)\n// ---------------------------------------------------------------------------\n\nfunction discoverFromNodeModules(basePath: string): DiscoveredPlugin[] {\n const scopeDir = join(basePath, \"node_modules\", PLUGIN_SCOPE);\n\n if (!existsSync(scopeDir)) return [];\n\n return readdirSync(scopeDir, { withFileTypes: true })\n .filter(\n (entry) =>\n (entry.isDirectory() || entry.isSymbolicLink()) &&\n isPluginName(`${PLUGIN_SCOPE}/${entry.name}`),\n )\n .map((entry) => {\n const name = `${PLUGIN_SCOPE}/${entry.name}`;\n const entryDir = join(scopeDir, entry.name);\n // Resolve symlinks (pnpm workspace links) to file:// URLs so the\n // dynamic import works regardless of pnpm's strict module resolution.\n try {\n const realDir = realpathSync(entryDir);\n const distEntry = join(realDir, \"dist\", \"index.mjs\");\n if (existsSync(distEntry)) {\n return { name, importSpecifier: pathToFileURL(distEntry).href };\n }\n } catch {\n // Broken symlink or other fs error — fall through to bare specifier\n }\n // Fallback to bare specifier for non-workspace (published) installs\n return { name, importSpecifier: name };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Strategy 2: workspace scan (pnpm monorepo development)\n// ---------------------------------------------------------------------------\n\nfunction findWorkspaceRoot(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n if (\n existsSync(join(dir, \"pnpm-workspace.yaml\")) ||\n existsSync(join(dir, \"pnpm-workspace.yml\"))\n ) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nfunction readPackageName(dir: string): string | null {\n const pkgPath = join(dir, \"package.json\");\n if (!existsSync(pkgPath)) return null;\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n name?: string;\n };\n return pkg.name ?? null;\n } catch {\n return null;\n }\n}\n\nexport interface DiscoveredPlugin {\n name: string;\n importSpecifier: string;\n}\n\nexport interface PluginDiscoveryOptions {\n /**\n * Prefer plugins installed beside this CLI over same-named packages in the\n * target project's node_modules. Desktop callers use this so an older\n * project-local plugin cannot override the command contract bundled with\n * the app. Normal project-local CLI invocations keep the existing order.\n */\n preferInstallation?: boolean;\n /** Test seam for the directory containing the CLI installation. */\n installationBasePath?: string;\n}\n\nfunction discoverFromWorkspace(startDir: string): DiscoveredPlugin[] {\n const workspaceRoot = findWorkspaceRoot(startDir);\n if (!workspaceRoot) return [];\n\n const results: DiscoveredPlugin[] = [];\n const packagesDir = join(workspaceRoot, \"packages\");\n if (!existsSync(packagesDir)) return [];\n\n // Scan exactly two levels deep under packages/ (e.g. packages/cli/themes,\n // packages/cli/v2025-06). This matches the monorepo convention where all\n // packages live at packages/<domain>/<package>/.\n // If a plugin is added at a different depth, update this scan accordingly.\n let domainDirs: string[];\n try {\n domainDirs = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(packagesDir, e.name));\n } catch {\n return [];\n }\n\n for (const domainDir of domainDirs) {\n let subDirs: string[];\n try {\n subDirs = readdirSync(domainDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(domainDir, e.name));\n } catch {\n continue;\n }\n\n for (const subDir of subDirs) {\n const name = readPackageName(subDir);\n if (!name || !name.startsWith(`${PLUGIN_SCOPE}/`) || !isPluginName(name))\n continue;\n\n const distEntry = join(subDir, \"dist\", \"index.mjs\");\n if (!existsSync(distEntry)) {\n continue;\n }\n\n results.push({\n name,\n importSpecifier: pathToFileURL(distEntry).href,\n });\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Exported API\n// ---------------------------------------------------------------------------\n\n/** Resolved once — the CLI core package root (`packages/cli/core/`). */\nexport const corePackageDir = dirname(\n dirname(dirname(fileURLToPath(import.meta.url))),\n);\n\n/**\n * Discover installed plugin packages. Returns a de-duplicated, sorted list\n * of `{ name, importSpecifier }` objects.\n *\n * @param basePath - Directory to scan for `node_modules/@fluid-app/`\n * @param searchFrom - Starting directory for workspace root detection\n * (walked upward via `findWorkspaceRoot`). Pass `null` to skip workspace\n * scanning (useful in tests). Defaults to `corePackageDir`.\n */\nexport function discoverPlugins(\n basePath: string,\n searchFrom?: string | null,\n options: PluginDiscoveryOptions = {},\n): DiscoveredPlugin[] {\n const seen = new Set<string>();\n const results: DiscoveredPlugin[] = [];\n\n const addPlugins = (plugins: DiscoveredPlugin[]) => {\n for (const plugin of plugins) {\n if (seen.has(plugin.name)) continue;\n seen.add(plugin.name);\n results.push(plugin);\n }\n };\n\n const cliParent =\n options.installationBasePath ?? dirname(dirname(dirname(corePackageDir)));\n const projectPlugins = () => discoverFromNodeModules(basePath);\n const installationPlugins = () =>\n cliParent === basePath ? [] : discoverFromNodeModules(cliParent);\n const workspacePlugins = () => {\n if (searchFrom === null) return [];\n return discoverFromWorkspace(searchFrom ?? corePackageDir);\n };\n\n if (options.preferInstallation) {\n addPlugins(installationPlugins());\n // In a monorepo development build, resolveFluidBinary() deliberately\n // realpaths the CLI into packages/cli/core. Its sibling plugins are then\n // discovered from the workspace rather than an installation-level\n // node_modules, so they are part of the same trusted CLI installation.\n addPlugins(workspacePlugins());\n addPlugins(projectPlugins());\n } else {\n addPlugins(projectPlugins());\n addPlugins(installationPlugins());\n addPlugins(workspacePlugins());\n }\n\n return results.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Dynamically import a plugin module and return its default export.\n *\n * @internal Not intended for external callers. Only called with specifiers\n * produced by {@link discoverPlugins}:\n * - bare package names (e.g. `@fluid-app/fluid-cli-portal`) from the\n * node_modules scan, validated by {@link isPluginName}.\n * - `file://` URLs from {@link discoverFromWorkspace}, which constructs\n * them internally from validated workspace paths (`packages/` tree,\n * `dist/index.mjs`). These are trusted internal specifiers.\n */\nexport async function importPlugin(specifier: string): Promise<unknown> {\n // file:// URLs are trusted — they originate from discoverFromWorkspace\n // which builds them from validated workspace paths under packages/.\n // For bare package names, verify they match the plugin naming convention.\n if (!specifier.startsWith(\"file://\") && !isPluginName(specifier)) {\n throw new Error(`Refusing to import non-plugin package: ${specifier}`);\n }\n const mod = (await import(specifier)) as Record<string, unknown>;\n return mod[\"default\"] ?? mod;\n}\n","/**\n * Plugin loader — orchestrates discovery and registration\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { getConfigDir } from \"../config/paths.js\";\nimport { getAuthToken } from \"../auth/token.js\";\nimport { readConfig } from \"../config/config.js\";\nimport type { FluidPlugin, PluginContext } from \"./types.js\";\nimport { discoverPlugins, importPlugin } from \"./discovery.js\";\n\nfunction isFluidPlugin(value: unknown): value is FluidPlugin {\n if (typeof value !== \"object\" || value === null) return false;\n const obj = value as Record<string, unknown>;\n return (\n typeof obj[\"name\"] === \"string\" &&\n typeof obj[\"version\"] === \"string\" &&\n typeof obj[\"register\"] === \"function\"\n );\n}\n\n/**\n * Discover, import, and register all installed plugins\n */\nexport async function loadPlugins(\n program: Command,\n basePath: string,\n): Promise<void> {\n const discovered = discoverPlugins(basePath, undefined, {\n preferInstallation:\n process.env[\"FLUID_CLI_PREFER_INSTALLATION_PLUGINS\"] === \"1\",\n });\n const { enabledPlugins } = readConfig();\n\n // If enabledPlugins is set, only load explicitly allowed plugins.\n // This acts as an allow-list to prevent accidental supply-chain execution.\n const allowedSet = enabledPlugins !== null ? new Set(enabledPlugins) : null;\n const plugins = allowedSet\n ? discovered.filter((p) => allowedSet.has(p.name))\n : discovered;\n\n if (plugins.length === 0) return;\n\n const ctx: PluginContext = {\n program,\n getAuthToken,\n configDir: getConfigDir(),\n };\n\n for (const { name, importSpecifier } of plugins) {\n try {\n const exported = await importPlugin(importSpecifier);\n\n if (!isFluidPlugin(exported)) {\n console.warn(\n chalk.yellow(`Warning: ${name} does not export a valid FluidPlugin`),\n );\n continue;\n }\n\n await exported.register(ctx);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(\n chalk.yellow(`Warning: Failed to load plugin ${name}: ${message}`),\n );\n }\n }\n}\n","#!/usr/bin/env node\n\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { loginCommand } from \"../commands/login.js\";\nimport { logoutCommand } from \"../commands/logout.js\";\nimport { whoamiCommand } from \"../commands/whoami.js\";\nimport { switchCommand } from \"../commands/switch.js\";\nimport { loadPlugins } from \"../plugins/loader.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../../package.json\") as { version: string };\n\nconst packageRoot = process.cwd();\n\nconst program = new Command();\n\nprogram.name(\"fluid\").description(\"Fluid Commerce CLI\").version(version);\n\n// Built-in auth commands\nprogram.addCommand(loginCommand);\nprogram.addCommand(logoutCommand);\nprogram.addCommand(whoamiCommand);\nprogram.addCommand(switchCommand);\n\n// Discover and load all plugins (auto-discovered from node_modules and workspace)\nawait loadPlugins(program, packageRoot);\n\nprogram.parse(process.argv, { from: \"node\" });\n"],"mappings":"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"}
1
+ {"version":3,"file":"fluid.mjs","names":[],"sources":["../../src/auth/profiles.ts","../../src/commands/login.ts","../../src/commands/logout.ts","../../src/commands/whoami.ts","../../src/commands/switch.ts","../../src/plugins/discovery.ts","../../src/plugins/format-load-line.ts","../../src/plugins/resolve-load-path.ts","../../src/plugins/should-log-plugin-loads.ts","../../src/plugins/loader.ts","../../src/bin/fluid.ts"],"sourcesContent":["/**\n * Helpers for constructing stored auth profiles.\n */\n\nimport type { FluidProfile } from \"../config/types.js\";\n\ninterface CreateFluidProfileInput {\n readonly name: string;\n readonly token: string;\n readonly companyName: string;\n readonly baseUrl: string;\n readonly storedAt?: string;\n}\n\nexport function createFluidProfile({\n name,\n token,\n companyName,\n baseUrl,\n storedAt,\n}: CreateFluidProfileInput): FluidProfile {\n return {\n name,\n token,\n companyName,\n storedAt: storedAt ?? new Date().toISOString(),\n baseUrl,\n };\n}\n","/**\n * fluid login — authenticate via email MFA or API token\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport {\n validateToken,\n sendMfa,\n confirmMfa,\n getFluidApiBase,\n type CompanyChoice,\n} from \"../auth/fluid-api.js\";\nimport { createFluidProfile } from \"../auth/profiles.js\";\nimport { updateConfig, readConfig } from \"../config/config.js\";\n\nasync function confirmOverwrite(profileName: string): Promise<boolean> {\n const existing = readConfig().profiles[profileName];\n if (!existing) return true;\n\n const response = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Profile \"${profileName}\" already exists (${existing.companyName}). Overwrite?`,\n initial: false,\n });\n return Boolean(response[\"overwrite\"]);\n}\n\nfunction storeProfile(\n profileName: string,\n token: string,\n companyName: string,\n): void {\n updateConfig((config) => ({\n ...config,\n activeProfile: profileName,\n profiles: {\n ...config.profiles,\n [profileName]: createFluidProfile({\n name: profileName,\n token,\n companyName,\n baseUrl: getFluidApiBase(),\n }),\n },\n }));\n}\n\nasync function loginWithToken(\n token: string,\n profileName?: string,\n): Promise<void> {\n const spinner = ora(\"Validating token...\").start();\n const result = await validateToken(token);\n\n if (!result.success) {\n spinner.fail(chalk.red(result.error.message));\n if (result.error.details) {\n console.log(chalk.dim(result.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n const name = profileName ?? result.value.name;\n\n // Stop spinner before potential interactive prompt to avoid terminal corruption\n spinner.succeed(\n chalk.green(`Token valid — ${chalk.bold(result.value.name)}`),\n );\n\n if (!(await confirmOverwrite(name))) {\n console.log(\n chalk.yellow(\"Login cancelled — existing profile not overwritten.\"),\n );\n return;\n }\n\n storeProfile(name, token, result.value.name);\n console.log(\n chalk.green(\n `Logged in as ${chalk.bold(result.value.name)} (profile: ${chalk.bold(name)})`,\n ),\n );\n}\n\nasync function loginWithEmail(\n initialEmail?: string,\n profileName?: string,\n): Promise<void> {\n // 1. Prompt for email\n let email = initialEmail;\n if (!email) {\n const response = await prompts({\n type: \"text\",\n name: \"email\",\n message: \"Enter your email\",\n });\n email = response[\"email\"] as string | undefined;\n if (!email) {\n console.log(chalk.red(\"No email provided. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n }\n\n // 2. Send MFA code\n const sendSpinner = ora(\"Sending verification code...\").start();\n const sendResult = await sendMfa(email);\n\n if (!sendResult.success) {\n sendSpinner.fail(chalk.red(sendResult.error.message));\n if (sendResult.error.details) {\n console.log(chalk.dim(sendResult.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n const expiresAt = new Date(sendResult.value.expiresAt);\n const expiresInMs = expiresAt.getTime() - Date.now();\n const expiresInMin = Math.round(expiresInMs / 1000 / 60);\n const expiryNote =\n expiresInMin > 0\n ? `expires in ~${expiresInMin} min`\n : \"code may expire soon — check your email quickly\";\n sendSpinner.succeed(\n `Verification code sent — check your email (${expiryNote})`,\n );\n\n // 3. Prompt for verification code (retry up to 3 times on invalid code)\n const MAX_CODE_ATTEMPTS = 3;\n let confirmResult;\n for (let attempt = 1; attempt <= MAX_CODE_ATTEMPTS; attempt++) {\n const codeResponse = await prompts({\n type: \"text\",\n name: \"code\",\n message: \"Enter the 6-digit code\",\n });\n const code = codeResponse[\"code\"] as string | undefined;\n if (!code) {\n console.log(chalk.red(\"No code provided. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n if (!/^\\d{6}$/.test(code)) {\n console.log(chalk.red(\"Code must be exactly 6 digits.\"));\n if (attempt < MAX_CODE_ATTEMPTS) continue;\n console.log(chalk.red(\"Too many invalid attempts. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n\n const confirmSpinner = ora(\"Verifying code...\").start();\n confirmResult = await confirmMfa(sendResult.value.uuid, code);\n\n if (confirmResult.success) {\n confirmSpinner.succeed(\"Verified\");\n break;\n }\n\n // Retryable: wrong code, still have attempts left\n if (\n confirmResult.error.code === \"INVALID_CODE\" &&\n attempt < MAX_CODE_ATTEMPTS\n ) {\n confirmSpinner.fail(\n chalk.red(\n `${confirmResult.error.message} (attempt ${attempt}/${MAX_CODE_ATTEMPTS})`,\n ),\n );\n continue;\n }\n\n // Non-retryable (expired, unreachable, etc.) or final attempt\n confirmSpinner.fail(chalk.red(confirmResult.error.message));\n if (confirmResult.error.details) {\n console.log(chalk.dim(confirmResult.error.details));\n }\n process.exitCode = 1;\n return;\n }\n\n if (!confirmResult?.success) return;\n\n const { companies } = confirmResult.value;\n\n if (companies.length === 0) {\n console.log(chalk.red(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n\n // 4. Select company (auto-select if only one)\n let selected: CompanyChoice;\n if (companies.length === 1) {\n const first = companies[0];\n if (!first) {\n console.log(chalk.red(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n selected = first;\n } else {\n // Key on `c.id`, not the array index, and resolve via `.find` —\n // `prompts` autocomplete returns the raw typed string on Enter when\n // the user hasn't arrow-key-highlighted a filtered choice, so an\n // array-index lookup ends up asking for `companies[\"Fluid\"]` and\n // failing with \"Invalid selection\". Same shape as `switch.ts`.\n const companyChoices = companies.map((c) => ({\n title: `${c.name} (${c.shopName})`,\n value: c.id,\n }));\n\n const selectResponse = await prompts({\n type: \"autocomplete\",\n name: \"companyId\",\n message: \"Select a company (type to search)\",\n choices: companyChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.title.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n\n const companyId = selectResponse[\"companyId\"] as number | undefined;\n\n if (companyId === undefined) {\n console.log(chalk.red(\"No company selected. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n const choice = companies.find((c) => c.id === companyId);\n if (!choice) {\n console.log(chalk.red(\"Invalid selection. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n selected = choice;\n }\n\n // 5. Store profile\n const name = profileName ?? selected.name;\n\n if (!(await confirmOverwrite(name))) {\n console.log(\n chalk.yellow(\"Login cancelled — existing profile not overwritten.\"),\n );\n return;\n }\n\n storeProfile(name, selected.jwt, selected.name);\n console.log(\n chalk.green(\n `Logged in as ${chalk.bold(selected.name)} (profile: ${chalk.bold(name)})`,\n ),\n );\n}\n\nexport const loginCommand = new Command(\"login\")\n .description(\"Authenticate with the Fluid API\")\n .option(\n \"-t, --token <token>\",\n \"API token (skips email flow). Prefer FLUID_TOKEN env var to avoid shell history exposure\",\n )\n .option(\"-e, --email <email>\", \"Email address for MFA login\")\n .option(\"-n, --name <name>\", \"Profile name (defaults to company name)\")\n .action(async (opts: { token?: string; email?: string; name?: string }) => {\n // Accept token from env var to avoid shell history / process listing exposure\n const token = opts.token ?? process.env[\"FLUID_TOKEN\"];\n if (token) {\n if (opts.token) {\n console.log(\n chalk.yellow(\n \"Warning: token passed via --token flag is visible in shell history and process listings. \" +\n \"Prefer the FLUID_TOKEN environment variable.\",\n ),\n );\n }\n await loginWithToken(token, opts.name);\n } else {\n await loginWithEmail(opts.email, opts.name);\n }\n });\n","/**\n * fluid logout — remove stored auth profile(s)\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { readConfig, writeConfig } from \"../config/config.js\";\n\nexport const logoutCommand = new Command(\"logout\")\n .description(\"Remove stored authentication\")\n .option(\"-a, --all\", \"Remove all profiles\")\n .action((opts: { all?: boolean }) => {\n const config = readConfig();\n\n if (opts.all) {\n writeConfig({\n ...config,\n activeProfile: null,\n profiles: {},\n });\n console.log(chalk.green(\"All profiles removed.\"));\n return;\n }\n\n if (!config.activeProfile) {\n console.log(chalk.yellow(\"Not currently logged in.\"));\n return;\n }\n\n const profileName = config.activeProfile;\n const { [profileName]: _, ...remainingProfiles } = config.profiles;\n\n // Pick the first remaining profile as active, or null\n const nextActive = Object.keys(remainingProfiles)[0] ?? null;\n\n writeConfig({\n ...config,\n activeProfile: nextActive,\n profiles: remainingProfiles,\n });\n\n console.log(\n chalk.green(`Logged out of profile ${chalk.bold(profileName)}.`),\n );\n if (nextActive) {\n console.log(chalk.dim(`Switched to profile ${nextActive}.`));\n }\n });\n","/**\n * fluid whoami — show current auth profile and validate against API\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { getActiveProfile } from \"../auth/token.js\";\nimport { validateToken } from \"../auth/fluid-api.js\";\n\nexport const whoamiCommand = new Command(\"whoami\")\n .description(\"Show the current authenticated profile\")\n .action(async () => {\n const profile = getActiveProfile();\n\n if (!profile) {\n console.log(\n chalk.yellow(\"Not logged in. Run `fluid login` to authenticate.\"),\n );\n process.exitCode = 1;\n return;\n }\n\n const spinner = ora(\"Verifying token...\").start();\n const result = await validateToken(profile.token);\n\n if (!result.success) {\n spinner.fail(chalk.red(\"Token is no longer valid.\"));\n console.log(chalk.dim(`Profile: ${profile.name}`));\n console.log(chalk.dim(\"Run `fluid login` to re-authenticate.\"));\n process.exitCode = 1;\n return;\n }\n\n spinner.succeed(chalk.green(\"Authenticated\"));\n console.log(` Profile: ${chalk.bold(profile.name)}`);\n console.log(` Company: ${chalk.bold(result.value.name)}`);\n console.log(` Stored: ${chalk.dim(profile.storedAt)}`);\n });\n","/**\n * fluid switch — switch between companies (fetched from API, with local fallback)\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport {\n fetchUserCompanies,\n switchCompany,\n FLUID_API_ERROR,\n getFluidApiBase,\n type UserCompany,\n} from \"../auth/fluid-api.js\";\nimport { createFluidProfile } from \"../auth/profiles.js\";\nimport { getActiveProfile } from \"../auth/token.js\";\nimport { readConfig, updateConfig } from \"../config/config.js\";\n\nasync function localSwitch(profileArg?: string): Promise<void> {\n const config = readConfig();\n const profileNames = Object.keys(config.profiles);\n\n if (profileNames.length === 0) {\n console.log(chalk.yellow(\"No profiles stored. Run `fluid login` first.\"));\n process.exitCode = 1;\n return;\n }\n\n let targetProfile = profileArg;\n\n if (!targetProfile) {\n const profileChoices = profileNames.map((name) => ({\n title: name === config.activeProfile ? `${name} (active)` : name,\n value: name,\n }));\n\n const response = await prompts({\n type: \"autocomplete\",\n name: \"profile\",\n message: \"Select a profile (type to search)\",\n choices: profileChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.value.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n targetProfile = response[\"profile\"] as string | undefined;\n\n if (!targetProfile) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n }\n\n if (!config.profiles[targetProfile]) {\n console.log(chalk.red(`Profile \"${targetProfile}\" not found.`));\n console.log(chalk.dim(`Available: ${profileNames.join(\", \")}`));\n process.exitCode = 1;\n return;\n }\n\n updateConfig((c) => ({ ...c, activeProfile: targetProfile! }));\n console.log(chalk.green(`Switched to profile ${chalk.bold(targetProfile)}.`));\n}\n\nasync function selectCompany(\n companies: UserCompany[],\n activeCompanyName: string | undefined,\n): Promise<UserCompany | undefined> {\n const companyChoices = companies.map((c) => ({\n title:\n c.name === activeCompanyName\n ? `${c.name} ${chalk.dim(\"(active)\")}`\n : c.name,\n value: c.id,\n }));\n\n const response = await prompts({\n type: \"autocomplete\",\n name: \"companyId\",\n message: \"Select a company (type to search)\",\n choices: companyChoices,\n suggest: (input, choices) =>\n Promise.resolve(\n input\n ? choices.filter((c) =>\n c.title.toLowerCase().includes(input.toLowerCase()),\n )\n : choices,\n ),\n });\n\n const companyId = response[\"companyId\"] as number | undefined;\n if (companyId === undefined) return undefined;\n return companies.find((c) => c.id === companyId);\n}\n\nasync function performSwitch(\n token: string,\n company: UserCompany,\n baseUrl?: string,\n): Promise<void> {\n const spinner = ora(`Switching to ${chalk.bold(company.name)}...`).start();\n const result = await switchCompany(token, company.id, baseUrl);\n\n if (!result.success) {\n if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {\n spinner.fail(\n chalk.red(\n \"Your session has expired. Please run `fluid login` to re-authenticate.\",\n ),\n );\n } else {\n spinner.fail(chalk.red(result.error.message));\n if (result.error.details) {\n console.log(chalk.dim(result.error.details));\n }\n }\n process.exitCode = 1;\n return;\n }\n\n const { companyName, jwt } = result.value;\n\n updateConfig((config) => ({\n ...config,\n activeProfile: companyName,\n profiles: {\n ...config.profiles,\n [companyName]: createFluidProfile({\n name: companyName,\n token: jwt,\n companyName,\n baseUrl: baseUrl ?? getFluidApiBase(),\n }),\n },\n }));\n\n spinner.succeed(\n chalk.green(\n `Switched to ${chalk.bold(companyName)} (profile: ${chalk.bold(companyName)})`,\n ),\n );\n}\n\nexport const switchCommand = new Command(\"switch\")\n .description(\"Switch between companies\")\n .argument(\"[profile]\", \"Company or profile name to switch to\")\n .action(async (profileArg?: string) => {\n const activeProfile = getActiveProfile();\n\n if (!activeProfile) {\n console.log(chalk.yellow(\"Not logged in. Run `fluid login` first.\"));\n process.exitCode = 1;\n return;\n }\n\n const token = activeProfile.token;\n const activeCompanyName = activeProfile.companyName;\n const activeBaseUrl = activeProfile.baseUrl ?? getFluidApiBase();\n\n // Fetch companies from API\n const spinner = ora(\"Fetching companies...\").start();\n const result = await fetchUserCompanies(token, activeBaseUrl);\n\n if (!result.success) {\n if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {\n spinner.fail(\n chalk.red(\n \"Your session has expired. Please run `fluid login` to re-authenticate.\",\n ),\n );\n process.exitCode = 1;\n return;\n }\n spinner.warn(\n chalk.yellow(\n `Could not fetch companies from API: ${result.error.message}`,\n ),\n );\n console.log(chalk.dim(\"Falling back to locally stored profiles.\"));\n await localSwitch(profileArg);\n return;\n }\n\n const companies = result.value;\n spinner.succeed(\n `Found ${companies.length} company${companies.length === 1 ? \"\" : \"ies\"}`,\n );\n\n if (companies.length === 0) {\n console.log(chalk.yellow(\"No companies found for this account.\"));\n process.exitCode = 1;\n return;\n }\n\n // If a profile argument was passed, match against API companies first\n if (profileArg) {\n const match = companies.find(\n (c) => c.name.toLowerCase() === profileArg.toLowerCase(),\n );\n\n if (match) {\n await performSwitch(token, match, activeBaseUrl);\n return;\n }\n\n // Fall back to local profile matching\n const config = readConfig();\n if (config.profiles[profileArg]) {\n updateConfig((c) => ({ ...c, activeProfile: profileArg! }));\n console.log(\n chalk.green(`Switched to profile ${chalk.bold(profileArg)}.`),\n );\n return;\n }\n\n console.log(chalk.red(`Company or profile \"${profileArg}\" not found.`));\n process.exitCode = 1;\n return;\n }\n\n // Interactive selection\n const selected = await selectCompany(companies, activeCompanyName);\n if (!selected) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n await performSwitch(token, selected, activeBaseUrl);\n });\n","/**\n * Auto-discover @fluid-app CLI plugins.\n *\n * Three discovery strategies run in order:\n *\n * 1a. **node_modules scan (cwd)** — look in `<cwd>/node_modules/@fluid-app/`\n * for directories whose names match the plugin naming convention.\n * This is the primary mechanism for project-local plugin installs.\n *\n * 1b. **node_modules scan (CLI install location)** — look in the\n * `node_modules/@fluid-app/` directory containing the CLI core package\n * itself. This covers global installs where plugins are sibling packages\n * under the same global `node_modules/`.\n *\n * 2. **Workspace scan** — walk upward from the CLI core package root to the\n * monorepo workspace root, then scan `packages/` for sibling plugin\n * packages. This covers the pnpm-workspace development case where\n * plugins are not symlinked into `node_modules`.\n *\n * Only first-party @fluid-app scoped packages are loaded.\n */\n\nimport { existsSync, readFileSync, readdirSync, realpathSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\nconst PLUGIN_SCOPE = \"@fluid-app\";\n\n/**\n * A plugin package name must match one of these patterns:\n * - @fluid-app/fluid-cli-* (standard)\n * - @fluid-app/*-cli-commands (v2025-06 style)\n */\nfunction isPluginName(packageName: string): boolean {\n if (!packageName.startsWith(`${PLUGIN_SCOPE}/`)) return false;\n const bare = packageName.slice(`${PLUGIN_SCOPE}/`.length);\n return bare.startsWith(\"fluid-cli-\") || bare.endsWith(\"-cli-commands\");\n}\n\n// ---------------------------------------------------------------------------\n// Strategy 1: node_modules scan (production / published installs)\n// ---------------------------------------------------------------------------\n\nfunction discoverFromNodeModules(basePath: string): DiscoveredPlugin[] {\n const scopeDir = join(basePath, \"node_modules\", PLUGIN_SCOPE);\n\n if (!existsSync(scopeDir)) return [];\n\n return readdirSync(scopeDir, { withFileTypes: true })\n .filter(\n (entry) =>\n (entry.isDirectory() || entry.isSymbolicLink()) &&\n isPluginName(`${PLUGIN_SCOPE}/${entry.name}`),\n )\n .map((entry) => {\n const name = `${PLUGIN_SCOPE}/${entry.name}`;\n const entryDir = join(scopeDir, entry.name);\n // Resolve symlinks (pnpm workspace links) to file:// URLs so the\n // dynamic import works regardless of pnpm's strict module resolution.\n try {\n const realDir = realpathSync(entryDir);\n const distEntry = join(realDir, \"dist\", \"index.mjs\");\n if (existsSync(distEntry)) {\n return { name, importSpecifier: pathToFileURL(distEntry).href };\n }\n } catch {\n // Broken symlink or other fs error — fall through to bare specifier\n }\n // Fallback to bare specifier for non-workspace (published) installs\n return { name, importSpecifier: name };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Strategy 2: workspace scan (pnpm monorepo development)\n// ---------------------------------------------------------------------------\n\nfunction findWorkspaceRoot(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n if (\n existsSync(join(dir, \"pnpm-workspace.yaml\")) ||\n existsSync(join(dir, \"pnpm-workspace.yml\"))\n ) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nfunction readPackageName(dir: string): string | null {\n const pkgPath = join(dir, \"package.json\");\n if (!existsSync(pkgPath)) return null;\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n name?: string;\n };\n return pkg.name ?? null;\n } catch {\n return null;\n }\n}\n\nexport interface DiscoveredPlugin {\n name: string;\n importSpecifier: string;\n}\n\nexport interface PluginDiscoveryOptions {\n /**\n * Prefer plugins installed beside this CLI over same-named packages in the\n * target project's node_modules. Desktop callers use this so an older\n * project-local plugin cannot override the command contract bundled with\n * the app. Normal project-local CLI invocations keep the existing order.\n */\n preferInstallation?: boolean;\n /** Test seam for the directory containing the CLI installation. */\n installationBasePath?: string;\n}\n\nfunction discoverFromWorkspace(startDir: string): DiscoveredPlugin[] {\n const workspaceRoot = findWorkspaceRoot(startDir);\n if (!workspaceRoot) return [];\n\n const results: DiscoveredPlugin[] = [];\n const packagesDir = join(workspaceRoot, \"packages\");\n if (!existsSync(packagesDir)) return [];\n\n // Scan exactly two levels deep under packages/ (e.g. packages/cli/themes,\n // packages/cli/v2025-06). This matches the monorepo convention where all\n // packages live at packages/<domain>/<package>/.\n // If a plugin is added at a different depth, update this scan accordingly.\n let domainDirs: string[];\n try {\n domainDirs = readdirSync(packagesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(packagesDir, e.name));\n } catch {\n return [];\n }\n\n for (const domainDir of domainDirs) {\n let subDirs: string[];\n try {\n subDirs = readdirSync(domainDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(domainDir, e.name));\n } catch {\n continue;\n }\n\n for (const subDir of subDirs) {\n const name = readPackageName(subDir);\n if (!name || !name.startsWith(`${PLUGIN_SCOPE}/`) || !isPluginName(name))\n continue;\n\n const distEntry = join(subDir, \"dist\", \"index.mjs\");\n if (!existsSync(distEntry)) {\n continue;\n }\n\n results.push({\n name,\n importSpecifier: pathToFileURL(distEntry).href,\n });\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Exported API\n// ---------------------------------------------------------------------------\n\n/** Resolved once — the CLI core package root (`packages/cli/core/`). */\nexport const corePackageDir = dirname(\n dirname(dirname(fileURLToPath(import.meta.url))),\n);\n\n/**\n * Discover installed plugin packages. Returns a de-duplicated, sorted list\n * of `{ name, importSpecifier }` objects.\n *\n * @param basePath - Directory to scan for `node_modules/@fluid-app/`\n * @param searchFrom - Starting directory for workspace root detection\n * (walked upward via `findWorkspaceRoot`). Pass `null` to skip workspace\n * scanning (useful in tests). Defaults to `corePackageDir`.\n */\nexport function discoverPlugins(\n basePath: string,\n searchFrom?: string | null,\n options: PluginDiscoveryOptions = {},\n): DiscoveredPlugin[] {\n const seen = new Set<string>();\n const results: DiscoveredPlugin[] = [];\n\n const addPlugins = (plugins: DiscoveredPlugin[]) => {\n for (const plugin of plugins) {\n if (seen.has(plugin.name)) continue;\n seen.add(plugin.name);\n results.push(plugin);\n }\n };\n\n const cliParent =\n options.installationBasePath ?? dirname(dirname(dirname(corePackageDir)));\n const projectPlugins = () => discoverFromNodeModules(basePath);\n const installationPlugins = () =>\n cliParent === basePath ? [] : discoverFromNodeModules(cliParent);\n const workspacePlugins = () => {\n if (searchFrom === null) return [];\n return discoverFromWorkspace(searchFrom ?? corePackageDir);\n };\n\n if (options.preferInstallation) {\n addPlugins(installationPlugins());\n // In a monorepo development build, resolveFluidBinary() deliberately\n // realpaths the CLI into packages/cli/core. Its sibling plugins are then\n // discovered from the workspace rather than an installation-level\n // node_modules, so they are part of the same trusted CLI installation.\n addPlugins(workspacePlugins());\n addPlugins(projectPlugins());\n } else {\n addPlugins(projectPlugins());\n addPlugins(installationPlugins());\n addPlugins(workspacePlugins());\n }\n\n return results.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Dynamically import a plugin module and return its default export.\n *\n * @internal Not intended for external callers. Only called with specifiers\n * produced by {@link discoverPlugins}:\n * - bare package names (e.g. `@fluid-app/fluid-cli-portal`) from the\n * node_modules scan, validated by {@link isPluginName}.\n * - `file://` URLs from {@link discoverFromWorkspace}, which constructs\n * them internally from validated workspace paths (`packages/` tree,\n * `dist/index.mjs`). These are trusted internal specifiers.\n */\nexport async function importPlugin(specifier: string): Promise<unknown> {\n // file:// URLs are trusted — they originate from discoverFromWorkspace\n // which builds them from validated workspace paths under packages/.\n // For bare package names, verify they match the plugin naming convention.\n if (!specifier.startsWith(\"file://\") && !isPluginName(specifier)) {\n throw new Error(`Refusing to import non-plugin package: ${specifier}`);\n }\n const mod = (await import(specifier)) as Record<string, unknown>;\n return mod[\"default\"] ?? mod;\n}\n","/**\n * Format one-line diagnostics naming a CLI plugin build and where it was\n * resolved from. The resolved path is the load-bearing detail: a workspace\n * build and a published copy share the same name and version, so the path is\n * the only thing that distinguishes them. See CURRENT-3218.\n */\n\nimport { fileURLToPath } from \"node:url\";\n\nexport interface PluginLoadInfo {\n readonly name: string;\n readonly version: string;\n /**\n * Where the plugin was resolved from — an absolute `file://` URL (workspace\n * or published install, resolved upstream) or, only when resolution failed,\n * a bare package name.\n */\n readonly importSpecifier: string;\n /**\n * Whether installation-bundled plugins were preferred over project-local\n * ones (`FLUID_CLI_PREFER_INSTALLATION_PLUGINS=1`, set by Mist).\n */\n readonly preferInstallation: boolean;\n}\n\nexport interface PluginLoadFailureInfo {\n readonly name: string;\n readonly importSpecifier: string;\n readonly preferInstallation: boolean;\n readonly message: string;\n}\n\nexport function modeLabel(preferInstallation: boolean): string {\n return preferInstallation ? \"installation-preferred\" : \"project-first\";\n}\n\n/**\n * Render a specifier for display: `file://` URLs become absolute filesystem\n * paths; anything else is shown verbatim. Never throws — a malformed `file://`\n * specifier falls back to its raw form so a diagnostic can never turn a\n * successful load into a reported failure.\n */\nexport function displaySource(importSpecifier: string): string {\n if (!importSpecifier.startsWith(\"file://\")) return importSpecifier;\n try {\n return fileURLToPath(importSpecifier);\n } catch {\n return importSpecifier;\n }\n}\n\nexport function formatPluginLoadLine(info: PluginLoadInfo): string {\n return `Loaded plugin ${info.name}@${info.version} from ${displaySource(\n info.importSpecifier,\n )} [${modeLabel(info.preferInstallation)}]`;\n}\n\nexport function formatPluginLoadFailure(info: PluginLoadFailureInfo): string {\n return `Failed to load plugin ${info.name} from ${displaySource(\n info.importSpecifier,\n )} [${modeLabel(info.preferInstallation)}]: ${info.message}`;\n}\n","/**\n * Resolve a plugin import specifier to a concrete path for diagnostics.\n *\n * The node_modules discovery strategy falls back to a bare package name when it\n * cannot realpath a workspace symlink — precisely the published-install case\n * where the path is the only thing that would reveal a stale plugin winning.\n * Resolving the bare specifier recovers that path. See CURRENT-3218.\n */\n\nexport type Resolver = (specifier: string) => string;\n\nconst defaultResolver: Resolver = (specifier) => import.meta.resolve(specifier);\n\n/**\n * Return a `file://` URL for `importSpecifier`. `file://` specifiers (already\n * resolved by discovery) pass through untouched; bare specifiers are resolved\n * via `resolve`. If resolution throws, the bare specifier is returned so a\n * diagnostic degrades gracefully rather than failing the load.\n */\nexport function resolveLoadPath(\n importSpecifier: string,\n resolve: Resolver = defaultResolver,\n): string {\n if (importSpecifier.startsWith(\"file://\")) return importSpecifier;\n try {\n return resolve(importSpecifier);\n } catch {\n return importSpecifier;\n }\n}\n","/**\n * Decide whether to print the per-plugin \"Loaded plugin …\" diagnostics.\n *\n * A fluid-mono checkout resolves ~a dozen plugin packages, so emitting a line\n * for each on every command (including `--help` and tab-completion, and into\n * Mist's captured stderr dialog) is noise. The success diagnostics are gated\n * behind an explicit opt-in; load *failures* are always surfaced by the loader\n * regardless of this flag. See CURRENT-3218.\n */\n\nfunction isTruthyFlag(value: string | undefined): boolean {\n return value === \"1\" || value === \"true\";\n}\n\nexport function shouldLogPluginLoads(\n argv: readonly string[] = process.argv,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n if (argv.includes(\"--verbose\")) return true;\n return isTruthyFlag(env[\"FLUID_CLI_DEBUG\"] ?? env[\"FLUID_CLI_VERBOSE\"]);\n}\n","/**\n * Plugin loader — orchestrates discovery and registration\n */\n\nimport type { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { getConfigDir } from \"../config/paths.js\";\nimport { getAuthToken } from \"../auth/token.js\";\nimport { readConfig } from \"../config/config.js\";\nimport type { FluidPlugin, PluginContext } from \"./types.js\";\nimport { discoverPlugins, importPlugin } from \"./discovery.js\";\nimport {\n formatPluginLoadLine,\n formatPluginLoadFailure,\n} from \"./format-load-line.js\";\nimport { resolveLoadPath } from \"./resolve-load-path.js\";\nimport { shouldLogPluginLoads } from \"./should-log-plugin-loads.js\";\n\nfunction isFluidPlugin(value: unknown): value is FluidPlugin {\n if (typeof value !== \"object\" || value === null) return false;\n const obj = value as Record<string, unknown>;\n return (\n typeof obj[\"name\"] === \"string\" &&\n typeof obj[\"version\"] === \"string\" &&\n typeof obj[\"register\"] === \"function\"\n );\n}\n\n/**\n * Discover, import, and register all installed plugins\n */\nexport async function loadPlugins(\n program: Command,\n basePath: string,\n): Promise<void> {\n const preferInstallation =\n process.env[\"FLUID_CLI_PREFER_INSTALLATION_PLUGINS\"] === \"1\";\n const discovered = discoverPlugins(basePath, undefined, {\n preferInstallation,\n });\n const { enabledPlugins } = readConfig();\n\n // If enabledPlugins is set, only load explicitly allowed plugins.\n // This acts as an allow-list to prevent accidental supply-chain execution.\n const allowedSet = enabledPlugins !== null ? new Set(enabledPlugins) : null;\n const plugins = allowedSet\n ? discovered.filter((p) => allowedSet.has(p.name))\n : discovered;\n\n if (plugins.length === 0) return;\n\n const ctx: PluginContext = {\n program,\n getAuthToken,\n configDir: getConfigDir(),\n };\n\n // Success diagnostics are opt-in (a checkout resolves ~a dozen plugins);\n // load failures below are always surfaced. See CURRENT-3218.\n const verbose = shouldLogPluginLoads();\n\n for (const { name, importSpecifier } of plugins) {\n // Resolve to a concrete path so a published install is distinguishable\n // from a workspace build at a glance.\n const source = resolveLoadPath(importSpecifier);\n try {\n const exported = await importPlugin(importSpecifier);\n\n if (!isFluidPlugin(exported)) {\n console.warn(\n chalk.yellow(`Warning: ${name} does not export a valid FluidPlugin`),\n );\n continue;\n }\n\n await exported.register(ctx);\n\n if (verbose) {\n console.error(\n chalk.dim(\n formatPluginLoadLine({\n name: exported.name,\n version: exported.version,\n importSpecifier: source,\n preferInstallation,\n }),\n ),\n );\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n // Always name what failed to load and from where — this is exactly the\n // invisible-artifact failure the diagnostic exists to catch.\n console.warn(\n chalk.yellow(\n `Warning: ${formatPluginLoadFailure({\n name,\n importSpecifier: source,\n preferInstallation,\n message,\n })}`,\n ),\n );\n }\n }\n}\n","#!/usr/bin/env node\n\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { loginCommand } from \"../commands/login.js\";\nimport { logoutCommand } from \"../commands/logout.js\";\nimport { whoamiCommand } from \"../commands/whoami.js\";\nimport { switchCommand } from \"../commands/switch.js\";\nimport { loadPlugins } from \"../plugins/loader.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../../package.json\") as { version: string };\n\nconst packageRoot = process.cwd();\n\nconst program = new Command();\n\nprogram.name(\"fluid\").description(\"Fluid Commerce CLI\").version(version);\n\n// Built-in auth commands\nprogram.addCommand(loginCommand);\nprogram.addCommand(logoutCommand);\nprogram.addCommand(whoamiCommand);\nprogram.addCommand(switchCommand);\n\n// Discover and load all plugins (auto-discovered from node_modules and workspace)\nawait loadPlugins(program, packageRoot);\n\nprogram.parse(process.argv, { from: \"node\" });\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -12,6 +12,17 @@ interface FluidProfile {
12
12
  readonly baseUrl?: string;
13
13
  /** Stable storefront identity written by Mist and newer auth endpoints. */
14
14
  readonly fluidShop?: string;
15
+ /**
16
+ * Cached commit identity for GitSync subjects, so a publish doesn't
17
+ * pay for `/api/me` to learn a name that changes approximately never.
18
+ * Scoped to the profile (token + company), and dropped whenever login
19
+ * or a company switch rewrites it. See git-sync/actor-cache.ts.
20
+ */
21
+ readonly gitSyncActor?: {
22
+ readonly name: string;
23
+ readonly publicId: string | null; /** ISO-8601. */
24
+ readonly cachedAt: string;
25
+ };
15
26
  }
16
27
  interface FluidConfig {
17
28
  activeProfile: string | null;
@@ -272,5 +283,231 @@ declare function createCommandContext(opts: {
272
283
  //#region src/domain/output.d.ts
273
284
  declare function formatOutput(data: unknown, options: OutputOptions): string;
274
285
  //#endregion
275
- export { type CliError, type CommandContext, type CompanyChoice, type CompanyInfo, type ConfirmMfaResponse, type Failure, type FetchClient, type FluidApiError, type FluidConfig, type FluidPlugin, type FluidProfile, type MfaResponse, type OutputOptions, type PluginContext, type ProjectRcConfig, type Result, type Success, type SwitchCompanyResult, type UserCompany, confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, fetchUserCompanies, findProjectConfig, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, getFluidApiBase, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, switchCompany, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
286
+ //#region src/git-sync/commit-subject.d.ts
287
+ /**
288
+ * The canonical GitSync commit-subject format, shared by every kind of
289
+ * Fluid-managed repository — Mist apps, Themes, Portals and Widgets.
290
+ *
291
+ * Each kind used to invent its own wording, so `git log` across a
292
+ * company's repos read as four unrelated systems and only some commits
293
+ * said who made them. One shape now covers all of them:
294
+ *
295
+ * <Actor Name>: <Action> · <timestamp> (<public_id>)
296
+ * Mike Tingey: Publish portal Acme Portal · 2026-08-06 12:00:00 (0182d21e-…)
297
+ *
298
+ * Name and action come first so `git log --oneline` stays scannable in a
299
+ * narrow terminal; the long UUID sits at the end where truncation eats
300
+ * it instead of the prose. The timestamp is redundant with git's own
301
+ * committer date but is kept inline because it survives cherry-picks,
302
+ * rebases and the hosting providers' deployment feeds, which surface the
303
+ * subject and nothing else.
304
+ *
305
+ * System-authored commits (auto-provisioning, sweeper retries, webhook
306
+ * fan-out, an unreachable `/api/me`) use the literal actor name `Fluid`
307
+ * and carry no UUID, so they parse with the same grammar rather than
308
+ * needing a second one:
309
+ *
310
+ * Fluid: Publish theme Aurora · 2026-08-06 12:00:00
311
+ *
312
+ * The Ruby side mirrors this in `GitSync::CommitSubject`
313
+ * (fluid/app/models/git_sync/commit_subject.rb) and Mist Desktop mirrors
314
+ * the parser in `apps/mist-desktop/src/shared/git-commit-subject.ts`.
315
+ * Change all three together.
316
+ */
317
+ /** The person a commit is attributed to. */
318
+ interface GitSyncActor {
319
+ /** Display name: full_name → first+last → email → `user-<id>`. */
320
+ readonly name: string;
321
+ /** The user's stable public UUID, rendered as a ` (uuid)` suffix. */
322
+ readonly publicId: string | null;
323
+ }
324
+ /** Actor name used when no human triggered the commit. */
325
+ declare const SYSTEM_ACTOR_NAME = "Fluid";
326
+ /**
327
+ * A subject broken back into its parts. `publicId` is null for
328
+ * system-authored commits and for actors whose public id was unknown at
329
+ * commit time.
330
+ */
331
+ interface ParsedGitSyncSubject {
332
+ readonly actorName: string;
333
+ readonly action: string;
334
+ /** The inline `YYYY-MM-DD HH:MM:SS` stamp, in UTC. */
335
+ readonly timestamp: string;
336
+ readonly publicId: string | null;
337
+ }
338
+ /** `2026-08-06T12:00:00.000Z` → `2026-08-06 12:00:00`. */
339
+ declare function formatCommitTimestamp(when: Date): string;
340
+ /**
341
+ * Neutralize the two sequences the grammar splits on, so no value can
342
+ * shift the actor / action / uuid boundaries:
343
+ *
344
+ * `": "` separates the actor from the action
345
+ * `"·"` separates the action from the timestamp
346
+ *
347
+ * Only those exact sequences are replaced, not every colon — `12:30`
348
+ * and `fix:handle-nulls` survive intact, because neither can be
349
+ * mistaken for a field boundary. A trailing colon is likewise safe: the
350
+ * parser backtracks past it.
351
+ *
352
+ * {@link gitSyncCommitSubject} applies this to every action, so callers
353
+ * don't have to remember. It's idempotent, so calling it yourself on an
354
+ * interpolated resource name (as the CLIs do) costs nothing.
355
+ */
356
+ declare function subjectFragment(value: string): string;
357
+ /**
358
+ * Build a canonical commit subject.
359
+ *
360
+ * @param action - What happened, e.g. `Publish theme Aurora`. Run any
361
+ * interpolated resource name through {@link subjectFragment} first.
362
+ * @param actor - The signed-in user, or null for system-authored
363
+ * commits (which are attributed to `Fluid`).
364
+ * @param when - Injectable clock, so callers and tests get a stable
365
+ * stamp.
366
+ */
367
+ declare function gitSyncCommitSubject(action: string, actor: GitSyncActor | null, when?: Date): string;
368
+ /**
369
+ * Split a canonical subject back into its parts, or null when the
370
+ * subject predates the format (legacy CLI wording such as `push @ <iso>`
371
+ * or a hand-written commit). Callers fall back to rendering the raw
372
+ * subject.
373
+ */
374
+ declare function parseGitSyncSubject(subject: string | null | undefined): ParsedGitSyncSubject | null;
375
+ /**
376
+ * The person a canonical subject is attributed to, or null when the
377
+ * subject isn't canonical or was system-authored. Use this for "who
378
+ * shipped this" UI — a `Fluid:` commit has no human to name.
379
+ */
380
+ declare function gitSyncSubjectAuthor(subject: string | null | undefined): string | null;
381
+ /** The `/api/me` fields the actor is derived from. */
382
+ interface MeUser {
383
+ readonly id?: unknown;
384
+ readonly public_id?: unknown;
385
+ readonly full_name?: unknown;
386
+ readonly first_name?: unknown;
387
+ readonly last_name?: unknown;
388
+ readonly email?: unknown;
389
+ }
390
+ /** Some `/api/me` deployments nest the user, others return it flat. */
391
+ interface MeResponse extends MeUser {
392
+ readonly user?: MeUser;
393
+ }
394
+ /**
395
+ * Resolve an `/api/me` body to a commit actor, with the name fallback
396
+ * chain `full_name → first+last → email → user-<id>`. Returns null when
397
+ * the body carries no usable identity at all, which callers treat the
398
+ * same as an unreachable `/api/me`: a system-authored commit.
399
+ */
400
+ declare function gitSyncActorFromMe(raw: MeResponse): GitSyncActor | null;
401
+ //#endregion
402
+ //#region src/git-sync/change-summary.d.ts
403
+ /**
404
+ * Turn a set of changed paths into the `<Action>` half of a canonical
405
+ * GitSync commit subject (see commit-subject.ts).
406
+ *
407
+ * Publishes used to be stamped with a constant — "Publish from Mist
408
+ * Desktop", "Publish theme Aurora" — so `git log` told you a publish
409
+ * happened but never what it carried. Every entry in the history looked
410
+ * identical, which made the log useless for finding the commit that
411
+ * introduced something.
412
+ *
413
+ * This derives a short, honest phrase from the staged diff:
414
+ *
415
+ * Update templates/product.liquid
416
+ * Update 3 files in templates
417
+ * Add 2 files, update 3, delete 1
418
+ *
419
+ * It is deliberately mechanical. A caller that knows the INTENT behind a
420
+ * change (Mist Desktop, or a human passing `-m`) should pass that
421
+ * instead — an intent describes why, and no diff can recover that. This
422
+ * is the fallback for when nobody supplied one.
423
+ *
424
+ * Pure: callers collect the diff (each CLI has its own git layer) and
425
+ * hand the entries here, so the wording can't drift between kinds.
426
+ */
427
+ /** Git's porcelain status letters, narrowed to what we act on. */
428
+ type ChangeStatus = "added" | "modified" | "deleted" | "renamed";
429
+ /**
430
+ * What an auto-commit helper accepts as its subject: either a literal
431
+ * (the caller already knows the intent — a `-m` message), or a resolver
432
+ * handed the staged changes so it can describe them.
433
+ *
434
+ * The resolver form exists because staging is decided INSIDE the commit
435
+ * helpers — they `git add -A` and then unstage private paths — so only
436
+ * they know the final file set. A summary built by the caller beforehand
437
+ * would describe a diff the commit doesn't carry.
438
+ *
439
+ * It may return a promise so a caller can start slow work (an `/api/me`
440
+ * identity lookup, say) BEFORE the git reconcile and await it here, at
441
+ * the last possible moment — overlapping it with the fetch/merge instead
442
+ * of paying for it serially up front. Anything awaited here is on the
443
+ * critical path of the commit, so it must be bounded and best-effort:
444
+ * a publish must never hang waiting to describe itself.
445
+ */
446
+ type CommitMessage = string | ((changes: readonly ChangeEntry[]) => string | Promise<string>);
447
+ interface ChangeEntry {
448
+ readonly status: ChangeStatus;
449
+ /** Repo-relative path, forward-slashed. */
450
+ readonly path: string;
451
+ }
452
+ /** Map a `git diff --name-status` letter to a status. `R`/`C` carry a
453
+ * similarity score (`R100`), so only the leading letter is read.
454
+ * Unknown letters (`U` unmerged, `T` typechange) count as modified —
455
+ * the file did change, and mislabeling the verb is better than
456
+ * dropping it from the count. */
457
+ declare function changeStatusFromCode(code: string): ChangeStatus;
458
+ /**
459
+ * Parse `git diff --cached --name-status -z` output.
460
+ *
461
+ * NUL-delimited on purpose: paths containing spaces, quotes or newlines
462
+ * are legal in git and common in themes (`assets/hero image.png`), and
463
+ * the newline-delimited form quotes and escapes them, which would corrupt
464
+ * both the count and the path. With `-z`, records are
465
+ * `status\0path\0` — except renames and copies, which are
466
+ * `status\0from\0to\0` and consume an extra field.
467
+ */
468
+ declare function parseNameStatusZ(stdout: string): ChangeEntry[];
469
+ /**
470
+ * Build the action phrase for a set of changes, or null when there is
471
+ * nothing to describe — the caller then falls back to its own constant
472
+ * (`Publish theme Aurora`), so an empty diff never produces a subject
473
+ * like "Update 0 files".
474
+ *
475
+ * @param entries - The staged changes.
476
+ * @param maxLength - Clip the result to keep `git log --oneline`
477
+ * readable. The subject also carries a name, timestamp and UUID, so
478
+ * the action gets a modest budget.
479
+ */
480
+ declare function summarizeChanges(entries: readonly ChangeEntry[], maxLength?: number): string | null;
481
+ //#endregion
482
+ //#region src/git-sync/actor-cache.d.ts
483
+ /**
484
+ * How long a cached identity is trusted. Long, because a display name
485
+ * changing is rare — but not forever, so a rename does eventually reach
486
+ * the commit log without the user having to re-login.
487
+ */
488
+ declare const ACTOR_CACHE_TTL_MS: number;
489
+ /** Cached identity as persisted on a profile. */
490
+ interface CachedGitSyncActor {
491
+ readonly name: string;
492
+ readonly publicId: string | null;
493
+ /** ISO-8601. */
494
+ readonly cachedAt: string;
495
+ }
496
+ /** The cached identity for the active profile, or null. */
497
+ declare function readCachedActor(now?: number): GitSyncActor | null;
498
+ /** Persist an identity against the active profile. Best-effort. */
499
+ declare function writeCachedActor(actor: GitSyncActor, now?: Date): void;
500
+ /**
501
+ * The commit identity, from cache when possible.
502
+ *
503
+ * @param fetchActor - Network lookup, used only on a cache miss. Each
504
+ * CLI has its own client, so the caller supplies it. It must already
505
+ * be bounded — this function adds no timeout of its own.
506
+ *
507
+ * Returns null when there's no cache and the lookup fails or yields no
508
+ * usable identity; callers then write a `Fluid:` system commit.
509
+ */
510
+ declare function resolveGitSyncActor(fetchActor: () => Promise<GitSyncActor | null>): Promise<GitSyncActor | null>;
511
+ //#endregion
512
+ export { ACTOR_CACHE_TTL_MS, type CachedGitSyncActor, type ChangeEntry, type ChangeStatus, type CliError, type CommandContext, type CommitMessage, type CompanyChoice, type CompanyInfo, type ConfirmMfaResponse, type Failure, type FetchClient, type FluidApiError, type FluidConfig, type FluidPlugin, type FluidProfile, type GitSyncActor, type MfaResponse, type OutputOptions, type ParsedGitSyncSubject, type PluginContext, type ProjectRcConfig, type Result, SYSTEM_ACTOR_NAME, type Success, type SwitchCompanyResult, type UserCompany, changeStatusFromCode, confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, fetchUserCompanies, findProjectConfig, formatCommitTimestamp, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, getFluidApiBase, gitSyncActorFromMe, gitSyncCommitSubject, gitSyncSubjectAuthor, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, parseGitSyncSubject, parseNameStatusZ, readCachedActor, readConfig, resolveGitSyncActor, sendMfa, subjectFragment, success, summarizeChanges, switchCompany, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeCachedActor, writeConfig };
276
513
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/config/project-config.ts","../src/utils/errors.ts","../src/utils/result.ts","../src/auth/fluid-api.ts","../src/auth/token.ts","../src/plugins/types.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/domain/types.ts","../src/domain/command.ts","../src/domain/context.ts","../src/domain/output.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/config/project-config.ts","../src/utils/errors.ts","../src/utils/result.ts","../src/auth/fluid-api.ts","../src/auth/token.ts","../src/plugins/types.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/domain/types.ts","../src/domain/command.ts","../src/domain/context.ts","../src/domain/output.ts","../src/git-sync/commit-subject.ts","../src/git-sync/change-summary.ts","../src/git-sync/actor-cache.ts"],"mappings":"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"}