@fluid-app/fluid-cli 0.1.3 → 0.1.4

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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as updateConfig, d as sendMfa, f as validateToken, i as readConfig, n as getAuthToken, o as writeConfig, s as getConfigDir, t as getActiveProfile, u as confirmMfa } from "../token-DCpSVmEk.mjs";
2
+ import { a as updateConfig, d as confirmMfa, f as fetchUserCompanies, h as validateToken, i as readConfig, m as switchCompany, n as getAuthToken, o as writeConfig, p as sendMfa, s as getConfigDir, t as getActiveProfile, u as FLUID_API_ERROR } from "../token-B1HZnEAi.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { Command } from "commander";
5
5
  import chalk from "chalk";
@@ -135,13 +135,14 @@ async function loginWithEmail(initialEmail, profileName) {
135
135
  selected = first;
136
136
  } else {
137
137
  const idx = (await prompts({
138
- type: "select",
138
+ type: "autocomplete",
139
139
  name: "companyIndex",
140
- message: "Select a company",
140
+ message: "Select a company (type to search)",
141
141
  choices: companies.map((c, i) => ({
142
142
  title: `${c.name} (${c.shopName})`,
143
143
  value: i
144
- }))
144
+ })),
145
+ suggest: (input, choices) => Promise.resolve(input ? choices.filter((c) => c.title.toLowerCase().includes(input.toLowerCase())) : choices)
145
146
  }))["companyIndex"];
146
147
  if (idx === void 0) {
147
148
  console.log(chalk.red("No company selected. Aborting."));
@@ -231,9 +232,9 @@ const whoamiCommand = new Command("whoami").description("Show the current authen
231
232
  //#endregion
232
233
  //#region src/commands/switch.ts
233
234
  /**
234
- * fluid switch — switch between stored auth profiles
235
+ * fluid switch — switch between companies (fetched from API, with local fallback)
235
236
  */
236
- const switchCommand = new Command("switch").description("Switch between stored auth profiles").argument("[profile]", "Profile name to switch to").action(async (profileArg) => {
237
+ async function localSwitch(profileArg) {
237
238
  const config = readConfig();
238
239
  const profileNames = Object.keys(config.profiles);
239
240
  if (profileNames.length === 0) {
@@ -244,13 +245,14 @@ const switchCommand = new Command("switch").description("Switch between stored a
244
245
  let targetProfile = profileArg;
245
246
  if (!targetProfile) {
246
247
  targetProfile = (await prompts({
247
- type: "select",
248
+ type: "autocomplete",
248
249
  name: "profile",
249
- message: "Select a profile",
250
+ message: "Select a profile (type to search)",
250
251
  choices: profileNames.map((name) => ({
251
252
  title: name === config.activeProfile ? `${name} (active)` : name,
252
253
  value: name
253
- }))
254
+ })),
255
+ suggest: (input, choices) => Promise.resolve(input ? choices.filter((c) => c.value.toLowerCase().includes(input.toLowerCase())) : choices)
254
256
  }))["profile"];
255
257
  if (!targetProfile) {
256
258
  console.log(chalk.dim("Cancelled."));
@@ -263,11 +265,107 @@ const switchCommand = new Command("switch").description("Switch between stored a
263
265
  process.exitCode = 1;
264
266
  return;
265
267
  }
266
- writeConfig({
267
- ...config,
268
+ updateConfig((c) => ({
269
+ ...c,
268
270
  activeProfile: targetProfile
269
- });
271
+ }));
270
272
  console.log(chalk.green(`Switched to profile ${chalk.bold(targetProfile)}.`));
273
+ }
274
+ async function selectCompany(companies, activeCompanyName) {
275
+ const companyId = (await prompts({
276
+ type: "autocomplete",
277
+ name: "companyId",
278
+ message: "Select a company (type to search)",
279
+ choices: companies.map((c) => ({
280
+ title: c.name === activeCompanyName ? `${c.name} ${chalk.dim("(active)")}` : c.name,
281
+ value: c.id
282
+ })),
283
+ suggest: (input, choices) => Promise.resolve(input ? choices.filter((c) => c.title.toLowerCase().includes(input.toLowerCase())) : choices)
284
+ }))["companyId"];
285
+ if (companyId === void 0) return void 0;
286
+ return companies.find((c) => c.id === companyId);
287
+ }
288
+ async function performSwitch(token, company) {
289
+ const spinner = ora(`Switching to ${chalk.bold(company.name)}...`).start();
290
+ const result = await switchCompany(token, company.id);
291
+ if (!result.success) {
292
+ if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) spinner.fail(chalk.red("Your session has expired. Please run `fluid login` to re-authenticate."));
293
+ else {
294
+ spinner.fail(chalk.red(result.error.message));
295
+ if (result.error.details) console.log(chalk.dim(result.error.details));
296
+ }
297
+ process.exitCode = 1;
298
+ return;
299
+ }
300
+ const { companyName, jwt } = result.value;
301
+ updateConfig((config) => ({
302
+ ...config,
303
+ activeProfile: companyName,
304
+ profiles: {
305
+ ...config.profiles,
306
+ [companyName]: {
307
+ name: companyName,
308
+ token: jwt,
309
+ companyName,
310
+ storedAt: (/* @__PURE__ */ new Date()).toISOString()
311
+ }
312
+ }
313
+ }));
314
+ spinner.succeed(chalk.green(`Switched to ${chalk.bold(companyName)} (profile: ${chalk.bold(companyName)})`));
315
+ }
316
+ const switchCommand = new Command("switch").description("Switch between companies").argument("[profile]", "Company or profile name to switch to").action(async (profileArg) => {
317
+ const activeProfile = getActiveProfile();
318
+ if (!activeProfile) {
319
+ console.log(chalk.yellow("Not logged in. Run `fluid login` first."));
320
+ process.exitCode = 1;
321
+ return;
322
+ }
323
+ const token = activeProfile.token;
324
+ const activeCompanyName = activeProfile.companyName;
325
+ const spinner = ora("Fetching companies...").start();
326
+ const result = await fetchUserCompanies(token);
327
+ if (!result.success) {
328
+ if (result.error.code === FLUID_API_ERROR.INVALID_TOKEN.code) {
329
+ spinner.fail(chalk.red("Your session has expired. Please run `fluid login` to re-authenticate."));
330
+ process.exitCode = 1;
331
+ return;
332
+ }
333
+ spinner.warn(chalk.yellow(`Could not fetch companies from API: ${result.error.message}`));
334
+ console.log(chalk.dim("Falling back to locally stored profiles."));
335
+ await localSwitch(profileArg);
336
+ return;
337
+ }
338
+ const companies = result.value;
339
+ spinner.succeed(`Found ${companies.length} company${companies.length === 1 ? "" : "ies"}`);
340
+ if (companies.length === 0) {
341
+ console.log(chalk.yellow("No companies found for this account."));
342
+ process.exitCode = 1;
343
+ return;
344
+ }
345
+ if (profileArg) {
346
+ const match = companies.find((c) => c.name.toLowerCase() === profileArg.toLowerCase());
347
+ if (match) {
348
+ await performSwitch(token, match);
349
+ return;
350
+ }
351
+ if (readConfig().profiles[profileArg]) {
352
+ updateConfig((c) => ({
353
+ ...c,
354
+ activeProfile: profileArg
355
+ }));
356
+ console.log(chalk.green(`Switched to profile ${chalk.bold(profileArg)}.`));
357
+ return;
358
+ }
359
+ console.log(chalk.red(`Company or profile "${profileArg}" not found.`));
360
+ process.exitCode = 1;
361
+ return;
362
+ }
363
+ const selected = await selectCompany(companies, activeCompanyName);
364
+ if (!selected) {
365
+ console.log(chalk.dim("Cancelled."));
366
+ return;
367
+ }
368
+ await performSwitch(token, selected);
271
369
  });
272
370
  //#endregion
273
371
  //#region src/plugins/discovery.ts
@@ -1 +1 @@
1
- {"version":3,"file":"fluid.mjs","names":[],"sources":["../../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 * 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 type CompanyChoice,\n} from \"../auth/fluid-api.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]: {\n name: profileName,\n token,\n companyName,\n storedAt: new Date().toISOString(),\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 const selectResponse = await prompts({\n type: \"select\",\n name: \"companyIndex\",\n message: \"Select a company\",\n choices: companies.map((c, i) => ({\n title: `${c.name} (${c.shopName})`,\n value: i,\n })),\n });\n const idx = selectResponse[\"companyIndex\"] as number | undefined;\n if (idx === undefined) {\n console.log(chalk.red(\"No company selected. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n const choice = companies[idx];\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 stored auth profiles\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport { readConfig, writeConfig } from \"../config/config.js\";\n\nexport const switchCommand = new Command(\"switch\")\n .description(\"Switch between stored auth profiles\")\n .argument(\"[profile]\", \"Profile name to switch to\")\n .action(async (profileArg?: string) => {\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 response = await prompts({\n type: \"select\",\n name: \"profile\",\n message: \"Select a profile\",\n choices: profileNames.map((name) => ({\n title: name === config.activeProfile ? `${name} (active)` : name,\n value: name,\n })),\n });\n\n targetProfile = response[\"profile\"] as string | undefined;\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 writeConfig({ ...config, activeProfile: targetProfile });\n console.log(\n chalk.green(`Switched to profile ${chalk.bold(targetProfile)}.`),\n );\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 } 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): string[] {\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) => `${PLUGIN_SCOPE}/${entry.name}`);\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\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): DiscoveredPlugin[] {\n const seen = new Set<string>();\n const results: DiscoveredPlugin[] = [];\n\n // Strategy 1a: node_modules relative to cwd (project-local plugins)\n for (const name of discoverFromNodeModules(basePath)) {\n if (!seen.has(name)) {\n seen.add(name);\n results.push({ name, importSpecifier: name });\n }\n }\n\n // Strategy 1b: node_modules relative to the CLI's own install location\n // (for global installs where plugins are siblings under the same\n // node_modules/@fluid-app/ directory)\n const cliParent = dirname(dirname(dirname(corePackageDir)));\n if (cliParent !== basePath) {\n for (const name of discoverFromNodeModules(cliParent)) {\n if (!seen.has(name)) {\n seen.add(name);\n results.push({ name, importSpecifier: name });\n }\n }\n }\n\n // Strategy 2: workspace siblings\n if (searchFrom !== null) {\n const wsDir = searchFrom ?? corePackageDir;\n for (const wp of discoverFromWorkspace(wsDir)) {\n if (!seen.has(wp.name)) {\n seen.add(wp.name);\n results.push({ name: wp.name, importSpecifier: wp.importSpecifier });\n }\n }\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);\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();\n"],"mappings":"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"}
1
+ {"version":3,"file":"fluid.mjs","names":[],"sources":["../../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 * 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 type CompanyChoice,\n} from \"../auth/fluid-api.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]: {\n name: profileName,\n token,\n companyName,\n storedAt: new Date().toISOString(),\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 const companyChoices = companies.map((c, i) => ({\n title: `${c.name} (${c.shopName})`,\n value: i,\n }));\n\n const selectResponse = await prompts({\n type: \"autocomplete\",\n name: \"companyIndex\",\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 idx = selectResponse[\"companyIndex\"] as number | undefined;\n\n if (idx === undefined) {\n console.log(chalk.red(\"No company selected. Aborting.\"));\n process.exitCode = 1;\n return;\n }\n const choice = companies[idx];\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 type UserCompany,\n} from \"../auth/fluid-api.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): Promise<void> {\n const spinner = ora(`Switching to ${chalk.bold(company.name)}...`).start();\n const result = await switchCompany(token, company.id);\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]: {\n name: companyName,\n token: jwt,\n companyName,\n storedAt: new Date().toISOString(),\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\n // Fetch companies from API\n const spinner = ora(\"Fetching companies...\").start();\n const result = await fetchUserCompanies(token);\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);\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);\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 } 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): string[] {\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) => `${PLUGIN_SCOPE}/${entry.name}`);\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\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): DiscoveredPlugin[] {\n const seen = new Set<string>();\n const results: DiscoveredPlugin[] = [];\n\n // Strategy 1a: node_modules relative to cwd (project-local plugins)\n for (const name of discoverFromNodeModules(basePath)) {\n if (!seen.has(name)) {\n seen.add(name);\n results.push({ name, importSpecifier: name });\n }\n }\n\n // Strategy 1b: node_modules relative to the CLI's own install location\n // (for global installs where plugins are siblings under the same\n // node_modules/@fluid-app/ directory)\n const cliParent = dirname(dirname(dirname(corePackageDir)));\n if (cliParent !== basePath) {\n for (const name of discoverFromNodeModules(cliParent)) {\n if (!seen.has(name)) {\n seen.add(name);\n results.push({ name, importSpecifier: name });\n }\n }\n }\n\n // Strategy 2: workspace siblings\n if (searchFrom !== null) {\n const wsDir = searchFrom ?? corePackageDir;\n for (const wp of discoverFromWorkspace(wsDir)) {\n if (!seen.has(wp.name)) {\n seen.add(wp.name);\n results.push({ name: wp.name, importSpecifier: wp.importSpecifier });\n }\n }\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);\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();\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -97,6 +97,16 @@ interface FluidApiError extends CliError {
97
97
  interface CompanyInfo {
98
98
  readonly name: string;
99
99
  }
100
+ interface UserCompany {
101
+ readonly id: number;
102
+ readonly name: string;
103
+ }
104
+ interface SwitchCompanyResult {
105
+ readonly companyId: number;
106
+ readonly companyName: string;
107
+ readonly fluidShop: string;
108
+ readonly jwt: string;
109
+ }
100
110
  interface MfaResponse {
101
111
  readonly uuid: string;
102
112
  readonly expiresAt: string;
@@ -124,6 +134,17 @@ declare function sendMfa(email: string): Promise<Result<MfaResponse, FluidApiErr
124
134
  * Confirm a multi-factor authentication code and retrieve company JWTs.
125
135
  */
126
136
  declare function confirmMfa(uuid: string, code: string): Promise<Result<ConfirmMfaResponse, FluidApiError>>;
137
+ /**
138
+ * Fetch all companies the authenticated user has access to.
139
+ */
140
+ declare function fetchUserCompanies(token: string): Promise<Result<UserCompany[], FluidApiError>>;
141
+ /**
142
+ * Switch to a different company and receive a new JWT.
143
+ *
144
+ * Response shape (from @fluid-app/auth SwitchCompanyResponse):
145
+ * { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }
146
+ */
147
+ declare function switchCompany(token: string, companyId: number): Promise<Result<SwitchCompanyResult, FluidApiError>>;
127
148
  //#endregion
128
149
  //#region src/auth/token.d.ts
129
150
  /**
@@ -206,5 +227,5 @@ declare function createCommandContext(opts: {
206
227
  //#region src/domain/output.d.ts
207
228
  declare function formatOutput(data: unknown, options: OutputOptions): string;
208
229
  //#endregion
209
- 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 Result, type Success, confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
230
+ 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 Result, type Success, type SwitchCompanyResult, type UserCompany, confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, fetchUserCompanies, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, switchCompany, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
210
231
  //# 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/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/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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"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as tryCatchAsync, S as tryCatch, T as unwrapOr, _ as isNodeError, a as updateConfig, b as mapResult, c as getConfigFilePath, d as sendMfa, f as validateToken, g as isFailure, h as isError, i as readConfig, l as createDefaultConfig, m as getErrorMessage, n as getAuthToken, o as writeConfig, p as failure, r as listProfileNames, s as getConfigDir, t as getActiveProfile, u as confirmMfa, v as isSuccess, w as unwrap, x as success, y as mapError } from "./token-DCpSVmEk.mjs";
1
+ import { C as mapResult, D as unwrap, E as tryCatchAsync, O as unwrapOr, S as mapError, T as tryCatch, _ as getErrorMessage, a as updateConfig, b as isNodeError, c as getConfigFilePath, d as confirmMfa, f as fetchUserCompanies, g as failure, h as validateToken, i as readConfig, l as createDefaultConfig, m as switchCompany, n as getAuthToken, o as writeConfig, p as sendMfa, r as listProfileNames, s as getConfigDir, t as getActiveProfile, v as isError, w as success, x as isSuccess, y as isFailure } from "./token-B1HZnEAi.mjs";
2
2
  import { Command } from "commander";
3
3
  //#region ../../platform/api-client-core/src/fetch-client.ts
4
4
  /**
@@ -296,6 +296,6 @@ function createDomainCommand(name, description, register) {
296
296
  return cmd;
297
297
  }
298
298
  //#endregion
299
- export { confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
299
+ export { confirmMfa, createCommandContext, createDefaultConfig, createDomainCommand, failure, fetchUserCompanies, formatOutput, getActiveProfile, getAuthToken, getConfigDir, getConfigFilePath, getErrorMessage, isError, isFailure, isNodeError, isSuccess, listProfileNames, mapError, mapResult, readConfig, sendMfa, success, switchCompany, tryCatch, tryCatchAsync, unwrap, unwrapOr, updateConfig, validateToken, writeConfig };
300
300
 
301
301
  //# sourceMappingURL=index.mjs.map
@@ -97,6 +97,14 @@ const FLUID_API_ERROR = {
97
97
  INVALID_CODE: {
98
98
  code: "INVALID_CODE",
99
99
  message: "Invalid verification code"
100
+ },
101
+ COMPANY_NOT_FOUND: {
102
+ code: "COMPANY_NOT_FOUND",
103
+ message: "Company not found"
104
+ },
105
+ SWITCH_FAILED: {
106
+ code: "SWITCH_FAILED",
107
+ message: "Failed to switch company"
100
108
  }
101
109
  };
102
110
  function createApiError(template, details) {
@@ -205,6 +213,72 @@ async function confirmMfa(uuid, code) {
205
213
  cleanup();
206
214
  }
207
215
  }
216
+ /**
217
+ * Fetch all companies the authenticated user has access to.
218
+ */
219
+ async function fetchUserCompanies(token) {
220
+ const { signal, cleanup } = makeSignal();
221
+ try {
222
+ const response = await fetch(`${getFluidApiBase()}/api/me`, {
223
+ method: "GET",
224
+ headers: { Authorization: `Bearer ${token}` },
225
+ signal
226
+ });
227
+ if (response.ok) {
228
+ const data = await response.json();
229
+ return success((Array.isArray(data.companies) ? data.companies : []).map((c) => ({
230
+ id: c.id,
231
+ name: c.name
232
+ })));
233
+ }
234
+ if (response.status === 401 || response.status === 403) return failure(createApiError(FLUID_API_ERROR.INVALID_TOKEN, `HTTP ${response.status}`));
235
+ const body = await response.text();
236
+ return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, `HTTP ${response.status}: ${body}`));
237
+ } catch (err) {
238
+ const message = err instanceof Error ? err.message : String(err);
239
+ return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));
240
+ } finally {
241
+ cleanup();
242
+ }
243
+ }
244
+ /**
245
+ * Switch to a different company and receive a new JWT.
246
+ *
247
+ * Response shape (from @fluid-app/auth SwitchCompanyResponse):
248
+ * { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }
249
+ */
250
+ async function switchCompany(token, companyId) {
251
+ const { signal, cleanup } = makeSignal();
252
+ try {
253
+ const response = await fetch(`${getFluidApiBase()}/api/authentication/company/${companyId}/switch`, {
254
+ method: "PUT",
255
+ headers: {
256
+ Authorization: `Bearer ${token}`,
257
+ "Content-Type": "application/json"
258
+ },
259
+ signal
260
+ });
261
+ if (response.ok) {
262
+ const company = (await response.json()).company;
263
+ if (!company?.jwt) return failure(createApiError(FLUID_API_ERROR.SWITCH_FAILED, "Unexpected response shape from /switch"));
264
+ return success({
265
+ companyId: company.id,
266
+ companyName: company.name,
267
+ fluidShop: company.fluid_shop,
268
+ jwt: company.jwt
269
+ });
270
+ }
271
+ if (response.status === 404) return failure(createApiError(FLUID_API_ERROR.COMPANY_NOT_FOUND, `Company ${companyId} not found`));
272
+ if (response.status === 401 || response.status === 403) return failure(createApiError(FLUID_API_ERROR.INVALID_TOKEN, `HTTP ${response.status}`));
273
+ const body = await response.text();
274
+ return failure(createApiError(FLUID_API_ERROR.SWITCH_FAILED, `HTTP ${response.status}: ${body}`));
275
+ } catch (err) {
276
+ const message = err instanceof Error ? err.message : String(err);
277
+ return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));
278
+ } finally {
279
+ cleanup();
280
+ }
281
+ }
208
282
  //#endregion
209
283
  //#region src/config/types.ts
210
284
  function createDefaultConfig() {
@@ -317,6 +391,6 @@ function listProfileNames() {
317
391
  return Object.keys(config.profiles);
318
392
  }
319
393
  //#endregion
320
- export { tryCatchAsync as C, tryCatch as S, unwrapOr as T, isNodeError as _, updateConfig as a, mapResult as b, getConfigFilePath as c, sendMfa as d, validateToken as f, isFailure as g, isError as h, readConfig as i, createDefaultConfig as l, getErrorMessage as m, getAuthToken as n, writeConfig as o, failure as p, listProfileNames as r, getConfigDir as s, getActiveProfile as t, confirmMfa as u, isSuccess as v, unwrap as w, success as x, mapError as y };
394
+ export { mapResult as C, unwrap as D, tryCatchAsync as E, unwrapOr as O, mapError as S, tryCatch as T, getErrorMessage as _, updateConfig as a, isNodeError as b, getConfigFilePath as c, confirmMfa as d, fetchUserCompanies as f, failure as g, validateToken as h, readConfig as i, createDefaultConfig as l, switchCompany as m, getAuthToken as n, writeConfig as o, sendMfa as p, listProfileNames as r, getConfigDir as s, getActiveProfile as t, FLUID_API_ERROR as u, isError as v, success as w, isSuccess as x, isFailure as y };
321
395
 
322
- //# sourceMappingURL=token-DCpSVmEk.mjs.map
396
+ //# sourceMappingURL=token-B1HZnEAi.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-B1HZnEAi.mjs","names":[],"sources":["../src/utils/result.ts","../src/auth/fluid-api.ts","../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/auth/token.ts"],"sourcesContent":["/**\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// ─────────────────────────────────────────────────────────────────────────────\n// Result type - discriminated union for success/failure\n// ─────────────────────────────────────────────────────────────────────────────\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// ─────────────────────────────────────────────────────────────────────────────\n// Constructor functions\n// ─────────────────────────────────────────────────────────────────────────────\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// ─────────────────────────────────────────────────────────────────────────────\n// Type guards\n// ─────────────────────────────────────────────────────────────────────────────\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// ─────────────────────────────────────────────────────────────────────────────\n// Utility functions\n// ─────────────────────────────────────────────────────────────────────────────\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// ─────────────────────────────────────────────────────────────────────────────\n// Error narrowing utilities\n// ─────────────────────────────────────────────────────────────────────────────\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\nfunction 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): Promise<Result<UserCompany[], FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(`${getFluidApiBase()}/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): Promise<Result<SwitchCompanyResult, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/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}\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}\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, \"utf-8\");\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 {\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(tmpPath, JSON.stringify(config, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\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 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\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 * Token storage and retrieval from config\n */\n\nimport { readConfig } from \"../config/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 */\nexport function getAuthToken(profileName?: string): string | null {\n const config = readConfig();\n const resolvedProfile = profileName ?? config.activeProfile;\n if (!resolvedProfile) return null;\n\n const profile = config.profiles[resolvedProfile];\n if (!profile) return null;\n\n return profile.token;\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-app/fluid-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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-DCpSVmEk.mjs","names":[],"sources":["../src/utils/result.ts","../src/auth/fluid-api.ts","../src/config/types.ts","../src/config/paths.ts","../src/config/config.ts","../src/auth/token.ts"],"sourcesContent":["/**\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// ─────────────────────────────────────────────────────────────────────────────\n// Result type - discriminated union for success/failure\n// ─────────────────────────────────────────────────────────────────────────────\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// ─────────────────────────────────────────────────────────────────────────────\n// Constructor functions\n// ─────────────────────────────────────────────────────────────────────────────\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// ─────────────────────────────────────────────────────────────────────────────\n// Type guards\n// ─────────────────────────────────────────────────────────────────────────────\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// ─────────────────────────────────────────────────────────────────────────────\n// Utility functions\n// ─────────────────────────────────────────────────────────────────────────────\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// ─────────────────────────────────────────────────────────────────────────────\n// Error narrowing utilities\n// ─────────────────────────────────────────────────────────────────────────────\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\nfunction 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} 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 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 * 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}\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}\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, \"utf-8\");\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 {\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(tmpPath, JSON.stringify(config, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\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 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\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 * Token storage and retrieval from config\n */\n\nimport { readConfig } from \"../config/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 */\nexport function getAuthToken(profileName?: string): string | null {\n const config = readConfig();\n const resolvedProfile = profileName ?? config.activeProfile;\n if (!resolvedProfile) return null;\n\n const profile = config.profiles[resolvedProfile];\n if (!profile) return null;\n\n return profile.token;\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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"}