@aigne/afs-cli 1.11.0-beta.12 → 1.11.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/config/afs-loader.cjs +0 -28
  2. package/dist/config/afs-loader.d.cts.map +1 -1
  3. package/dist/config/afs-loader.d.mts.map +1 -1
  4. package/dist/config/afs-loader.mjs +5 -33
  5. package/dist/config/afs-loader.mjs.map +1 -1
  6. package/dist/config/credential-helpers.cjs +14 -2
  7. package/dist/config/credential-helpers.mjs +14 -2
  8. package/dist/config/credential-helpers.mjs.map +1 -1
  9. package/dist/config/program-install.cjs +174 -0
  10. package/dist/config/program-install.mjs +173 -2
  11. package/dist/config/program-install.mjs.map +1 -1
  12. package/dist/core/commands/daemon.cjs +5 -1
  13. package/dist/core/commands/daemon.mjs +5 -1
  14. package/dist/core/commands/daemon.mjs.map +1 -1
  15. package/dist/core/commands/index.d.cts.map +1 -1
  16. package/dist/core/commands/index.d.mts.map +1 -1
  17. package/dist/core/commands/index.mjs.map +1 -1
  18. package/dist/core/commands/install.cjs +49 -1
  19. package/dist/core/commands/install.mjs +51 -3
  20. package/dist/core/commands/install.mjs.map +1 -1
  21. package/dist/core/formatters/install.cjs +19 -0
  22. package/dist/core/formatters/install.mjs +18 -1
  23. package/dist/core/formatters/install.mjs.map +1 -1
  24. package/dist/credential/auth-server.cjs +22 -4
  25. package/dist/credential/auth-server.mjs +22 -4
  26. package/dist/credential/auth-server.mjs.map +1 -1
  27. package/dist/credential/resolver.cjs +7 -4
  28. package/dist/credential/resolver.mjs +7 -4
  29. package/dist/credential/resolver.mjs.map +1 -1
  30. package/dist/program/program-manager.cjs +5 -1
  31. package/dist/program/program-manager.mjs +5 -1
  32. package/dist/program/program-manager.mjs.map +1 -1
  33. package/dist/repl.cjs +5 -1
  34. package/dist/repl.mjs +5 -1
  35. package/dist/repl.mjs.map +1 -1
  36. package/package.json +28 -28
@@ -2,27 +2,11 @@ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
2
  const require_credential_helpers = require('./credential-helpers.cjs');
3
3
  const require_loader = require('./loader.cjs');
4
4
  const require_mount_commands = require('./mount-commands.cjs');
5
- let node_fs_promises = require("node:fs/promises");
6
- let node_os = require("node:os");
7
- let node_path = require("node:path");
8
5
  let _aigne_afs = require("@aigne/afs");
9
6
  let _aigne_afs_utils_uri = require("@aigne/afs/utils/uri");
10
7
 
11
8
  //#region src/config/afs-loader.ts
12
9
  /**
13
- * AFS Loader - Lazy loading with parallel tolerant mount
14
- *
15
- * Provides loadAFS() for on-demand AFS creation with caching.
16
- * createAFS() uses Promise.allSettled for parallel provider creation + mount,
17
- * tolerating individual failures while reporting them to stderr.
18
- *
19
- * Integrates 4-step credential resolution:
20
- * 1. Determine missing fields from provider schema
21
- * 2. Silent resolution (config > env > credential store)
22
- * 3. Interactive collection (provider auth() or default collect())
23
- * 4. Unified persistence (sensitive → credentials.toml, non-sensitive → config options)
24
- */
25
- /**
26
10
  * Register the workspace:// scheme on a ProviderRegistry.
27
11
  *
28
12
  * Extracted so that both createAFS() and verifyMount() can support
@@ -262,18 +246,6 @@ async function createAFS(cwd, options) {
262
246
  for (const f of failures) console.warn(` - ${f.path}: ${f.reason}`);
263
247
  }
264
248
  if (succeeded.length === 0 && config.mounts.length > 0) throw new Error("All providers failed to mount");
265
- {
266
- const dataDir = (0, node_path.join)(process.env[require_loader.AFS_USER_CONFIG_DIR_ENV] ?? (0, node_path.join)((0, node_os.homedir)(), require_loader.CONFIG_DIR_NAME), "data");
267
- try {
268
- await (0, node_fs_promises.mkdir)(dataDir, { recursive: true });
269
- const dataProvider = await registry.createProvider({
270
- uri: `fs://${dataDir}`,
271
- path: "/.data",
272
- access_mode: "readwrite"
273
- });
274
- await afs.mount(dataProvider, "/.data");
275
- } catch {}
276
- }
277
249
  return {
278
250
  afs,
279
251
  failures,
@@ -1 +1 @@
1
- {"version":3,"file":"afs-loader.d.cts","names":[],"sources":["../../src/config/afs-loader.ts"],"mappings":";;;;UA8EiB,kBAAA;EACf,KAAA;EACA,SAAA;EACA,MAAA;AAAA;AAAA,UAiBe,gBAAA;EACf,UAAA,IAAc,KAAA,EAAO,kBAAA;;EAErB,WAAA,GAAc,WAAA;;EAEd,eAAA,GAAkB,eAAA;AAAA"}
1
+ {"version":3,"file":"afs-loader.d.cts","names":[],"sources":["../../src/config/afs-loader.ts"],"mappings":";;;;UA2EiB,kBAAA;EACf,KAAA;EACA,SAAA;EACA,MAAA;AAAA;AAAA,UAiBe,gBAAA;EACf,UAAA,IAAc,KAAA,EAAO,kBAAA;;EAErB,WAAA,GAAc,WAAA;;EAEd,eAAA,GAAkB,eAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"afs-loader.d.mts","names":[],"sources":["../../src/config/afs-loader.ts"],"mappings":";;;;;UA8EiB,kBAAA;EACf,KAAA;EACA,SAAA;EACA,MAAA;AAAA;AAAA,UAiBe,gBAAA;EACf,UAAA,IAAc,KAAA,EAAO,kBAAA;;EAErB,WAAA,GAAc,WAAA;;EAEd,eAAA,GAAkB,eAAA;AAAA"}
1
+ {"version":3,"file":"afs-loader.d.mts","names":[],"sources":["../../src/config/afs-loader.ts"],"mappings":";;;;;UA2EiB,kBAAA;EACf,KAAA;EACA,SAAA;EACA,MAAA;AAAA;AAAA,UAiBe,gBAAA;EACf,UAAA,IAAc,KAAA,EAAO,kBAAA;;EAErB,WAAA,GAAc,WAAA;;EAEd,eAAA,GAAkB,eAAA;AAAA"}
@@ -1,28 +1,12 @@
1
1
  import { __require } from "../_virtual/rolldown_runtime.mjs";
2
2
  import { extractEnvFromURI, mergeStoredCredentials, persistCredentialResult, resolveAndMergeCredentials, resolveCredentialsForMount } from "./credential-helpers.mjs";
3
- import { AFS_USER_CONFIG_DIR_ENV, CONFIG_DIR_NAME, ConfigLoader } from "./loader.mjs";
3
+ import { ConfigLoader } from "./loader.mjs";
4
4
  import { persistMount, unpersistMount, updateMountOptions } from "./mount-commands.mjs";
5
- import { mkdir } from "node:fs/promises";
6
- import { homedir } from "node:os";
7
- import { join } from "node:path";
8
5
  import { AFS, ProviderRegistry } from "@aigne/afs";
9
6
  import { parseURI } from "@aigne/afs/utils/uri";
10
7
 
11
8
  //#region src/config/afs-loader.ts
12
9
  /**
13
- * AFS Loader - Lazy loading with parallel tolerant mount
14
- *
15
- * Provides loadAFS() for on-demand AFS creation with caching.
16
- * createAFS() uses Promise.allSettled for parallel provider creation + mount,
17
- * tolerating individual failures while reporting them to stderr.
18
- *
19
- * Integrates 4-step credential resolution:
20
- * 1. Determine missing fields from provider schema
21
- * 2. Silent resolution (config > env > credential store)
22
- * 3. Interactive collection (provider auth() or default collect())
23
- * 4. Unified persistence (sensitive → credentials.toml, non-sensitive → config options)
24
- */
25
- /**
26
10
  * Register the workspace:// scheme on a ProviderRegistry.
27
11
  *
28
12
  * Extracted so that both createAFS() and verifyMount() can support
@@ -46,8 +30,8 @@ function registerWorkspaceFactory(registry) {
46
30
  }
47
31
  const cacheMap = /* @__PURE__ */ new Map();
48
32
  function cacheKey(cwd) {
49
- const { resolve: resolve$1 } = __require("node:path");
50
- return resolve$1(cwd);
33
+ const { resolve } = __require("node:path");
34
+ return resolve(cwd);
51
35
  }
52
36
  /**
53
37
  * Load AFS with per-cwd caching — same cwd returns same instance,
@@ -84,8 +68,8 @@ async function createAFS(cwd, options) {
84
68
  const mod = await import("@aigne/afs-workspace");
85
69
  const AFSWorkspace = mod.AFSWorkspace ?? mod.default;
86
70
  if (!AFSWorkspace) throw new Error("workspace:// scheme requires @aigne/afs-workspace package. Install it with: pnpm add @aigne/afs-workspace");
87
- const { resolve: resolve$1 } = await import("node:path");
88
- resolve$1(parsed.body);
71
+ const { resolve } = await import("node:path");
72
+ resolve(parsed.body);
89
73
  return new AFSWorkspace({
90
74
  workspacePath: parsed.body,
91
75
  registry,
@@ -262,18 +246,6 @@ async function createAFS(cwd, options) {
262
246
  for (const f of failures) console.warn(` - ${f.path}: ${f.reason}`);
263
247
  }
264
248
  if (succeeded.length === 0 && config.mounts.length > 0) throw new Error("All providers failed to mount");
265
- {
266
- const dataDir = join(process.env[AFS_USER_CONFIG_DIR_ENV] ?? join(homedir(), CONFIG_DIR_NAME), "data");
267
- try {
268
- await mkdir(dataDir, { recursive: true });
269
- const dataProvider = await registry.createProvider({
270
- uri: `fs://${dataDir}`,
271
- path: "/.data",
272
- access_mode: "readwrite"
273
- });
274
- await afs.mount(dataProvider, "/.data");
275
- } catch {}
276
- }
277
249
  return {
278
250
  afs,
279
251
  failures,
@@ -1 +1 @@
1
- {"version":3,"file":"afs-loader.mjs","names":["resolve","options","registry","AFS"],"sources":["../../src/config/afs-loader.ts"],"sourcesContent":["/**\n * AFS Loader - Lazy loading with parallel tolerant mount\n *\n * Provides loadAFS() for on-demand AFS creation with caching.\n * createAFS() uses Promise.allSettled for parallel provider creation + mount,\n * tolerating individual failures while reporting them to stderr.\n *\n * Integrates 4-step credential resolution:\n * 1. Determine missing fields from provider schema\n * 2. Silent resolution (config > env > credential store)\n * 3. Interactive collection (provider auth() or default collect())\n * 4. Unified persistence (sensitive → credentials.toml, non-sensitive → config options)\n */\n\nimport { mkdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AFSModule, AuthContext, MountConfig } from \"@aigne/afs\";\nimport { AFS, ProviderRegistry } from \"@aigne/afs\";\nimport { parseURI } from \"@aigne/afs/utils/uri\";\nimport type { CredentialStore } from \"../credential/store.js\";\nimport {\n extractEnvFromURI,\n mergeStoredCredentials,\n persistCredentialResult,\n type ResolveCredentialsForMountOptions,\n type ResolveCredentialsForMountResult,\n resolveAndMergeCredentials,\n resolveCredentialsForMount,\n} from \"./credential-helpers.js\";\nimport { AFS_USER_CONFIG_DIR_ENV, CONFIG_DIR_NAME, ConfigLoader } from \"./loader.js\";\nimport {\n type ConfigMountEntry,\n type PersistScope,\n persistMount,\n unpersistMount,\n updateMountOptions,\n} from \"./mount-commands.js\";\n\n// Re-export credential helpers for backward compatibility\nexport {\n extractEnvFromURI,\n resolveCredentialsForMount,\n type ResolveCredentialsForMountOptions,\n type ResolveCredentialsForMountResult,\n};\n\n// ─── Workspace Factory Helper ─────────────────────────────────────────────\n\n/**\n * Register the workspace:// scheme on a ProviderRegistry.\n *\n * Extracted so that both createAFS() and verifyMount() can support\n * workspace URIs. createAFS() overrides this with a richer variant\n * that includes credential resolution.\n */\nfunction registerWorkspaceFactory(registry: import(\"@aigne/afs\").ProviderRegistry): void {\n registry.register(\"workspace\", async (mount, parsed) => {\n const mod = await import(\"@aigne/afs-workspace\" as string);\n const AFSWorkspace = mod.AFSWorkspace ?? mod.default;\n if (!AFSWorkspace) {\n throw new Error(\n \"workspace:// scheme requires @aigne/afs-workspace package. Install it with: pnpm add @aigne/afs-workspace\",\n );\n }\n return new AFSWorkspace({\n workspacePath: parsed.body,\n registry,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"workspace\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n });\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface MountProgressEvent {\n total: number;\n completed: number;\n failed: number;\n}\n\nexport interface MountFailure {\n path: string;\n reason: string;\n}\n\nexport interface CreateAFSResult {\n afs: AFS;\n failures: MountFailure[];\n /** Mount paths that were successfully loaded from config.toml (excludes code-managed mounts). */\n configMountPaths: string[];\n /** Provider registry with all registered factories (workspace, etc.). */\n registry: import(\"@aigne/afs\").ProviderRegistry;\n}\n\nexport interface CreateAFSOptions {\n onProgress?: (event: MountProgressEvent) => void;\n /** Auth context for interactive credential collection (CLI or MCP) */\n authContext?: AuthContext;\n /** Credential store for reading/writing credentials */\n credentialStore?: CredentialStore;\n}\n\n// ─── Cache ────────────────────────────────────────────────────────────────\n\nconst cacheMap = new Map<string, AFS>();\n\nfunction cacheKey(cwd: string): string {\n const { resolve } = require(\"node:path\") as typeof import(\"node:path\");\n return resolve(cwd);\n}\n\n/**\n * Reset all cached AFS instances (for testing)\n */\nexport function resetAFSCache(): void {\n cacheMap.clear();\n}\n\n/**\n * Load AFS with per-cwd caching — same cwd returns same instance,\n * different cwd creates a separate instance.\n */\nexport async function loadAFS(cwd: string, options?: CreateAFSOptions): Promise<CreateAFSResult> {\n const key = cacheKey(cwd);\n const cached = cacheMap.get(key);\n if (cached)\n return { afs: cached, failures: [], configMountPaths: [], registry: new ProviderRegistry() };\n const result = await createAFS(cwd, options);\n cacheMap.set(key, result.afs);\n return result;\n}\n\n/**\n * Create AFS instance from config with parallel tolerant mount\n *\n * - All providers are created and mounted in parallel via Promise.allSettled\n * - Individual failures are logged to stderr and skipped (unless onProgress is provided)\n * - If ALL providers fail, throws an error\n * - If no mounts configured, returns empty AFS (no error)\n */\nexport async function createAFS(cwd: string, options?: CreateAFSOptions): Promise<CreateAFSResult> {\n const loader = new ConfigLoader();\n const { config, mountSources } = await loader.loadWithSources(cwd);\n const afs = new AFS();\n const authContext = options?.authContext;\n const credentialStore = options?.credentialStore;\n\n // Create registry — auto-loads built-in providers via manifest-driven resolution.\n // Only workspace needs explicit registration (to break circular deps + inject credential callback).\n const registry = new ProviderRegistry();\n registry.register(\"workspace\", async (mount, parsed) => {\n const mod = await import(\"@aigne/afs-workspace\" as string);\n const AFSWorkspace = mod.AFSWorkspace ?? mod.default;\n if (!AFSWorkspace) {\n throw new Error(\n \"workspace:// scheme requires @aigne/afs-workspace package. Install it with: pnpm add @aigne/afs-workspace\",\n );\n }\n const { resolve } = await import(\"node:path\");\n const _workspacePath = resolve(parsed.body);\n return new AFSWorkspace({\n workspacePath: parsed.body,\n registry,\n createProvider: async (subMount: MountConfig) => {\n const credResult = await resolveAndMergeCredentials(\n subMount,\n authContext,\n credentialStore,\n registry,\n );\n // Create provider with resolved credentials in mount.options\n const provider = await registry.createProvider(subMount);\n // Persist sensitive credentials to credential store\n if (credResult) {\n await persistCredentialResult(subMount, credResult);\n // Strip sensitive values from mount.options (keep only non-sensitive for config)\n const opts = subMount.options ?? {};\n for (const key of Object.keys(credResult.sensitive)) {\n delete opts[key];\n }\n subMount.options = Object.keys(opts).length > 0 ? opts : undefined;\n // Merge non-sensitive resolved values back\n if (Object.keys(credResult.nonSensitive).length > 0) {\n subMount.options = { ...(subMount.options ?? {}), ...credResult.nonSensitive };\n }\n }\n return provider;\n },\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"workspace\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n });\n\n // ── Inject provider factory for program mount fallback ──\n // Handles credential resolution + registry creation for program mounts\n // when shared mount URI isn't found in host AFS.\n afs.createProviderFromMount = async (mount) => {\n await resolveAndMergeCredentials(mount, authContext, credentialStore, registry);\n return registry.createProvider(mount);\n };\n\n // ── Inject loadProvider ──\n // Allows agents to mount new providers at runtime via /.actions/mount\n afs.loadProvider = async (uri: string, mountPath: string, options?: Record<string, unknown>) => {\n // Validate URI format before passing to factory\n parseURI(uri);\n\n // Extract env query params from MCP URIs for secure credential storage\n const { cleanUri: loadConfigUri, envRecord: loadEnvRecord } = extractEnvFromURI(uri);\n const hasLoadEnv = Object.keys(loadEnvRecord).length > 0;\n\n // Extract known mount-level fields; pass remaining as provider-specific options\n const { accessMode, auth, description, scope, ...providerOptions } = options ?? {};\n\n // Inject extracted env values into provider options\n if (hasLoadEnv) {\n const existingEnv = (providerOptions.env as Record<string, string>) ?? {};\n providerOptions.env = { ...existingEnv, ...loadEnvRecord };\n }\n\n const mount: MountConfig = {\n uri,\n path: mountPath,\n access_mode: (accessMode as \"readonly\" | \"readwrite\") ?? undefined,\n auth: (auth as string) ?? undefined,\n description: (description as string) ?? undefined,\n options: Object.keys(providerOptions).length > 0 ? providerOptions : undefined,\n };\n\n const persistScope = (scope as PersistScope) || \"cwd\";\n let credResult = await resolveAndMergeCredentials(\n mount,\n authContext,\n credentialStore,\n registry,\n );\n\n try {\n const provider = await registry.createProvider(mount);\n await afs.mount(provider, mountPath);\n } catch (mountError) {\n // Health check failed with silently resolved credentials →\n // retry once with forced interactive collection so user can fix values\n if (credResult === null && authContext) {\n // credResult === null means all fields resolved silently (no interactive collection)\n // Rebuild mount config from scratch for retry\n const retryProviderOptions = { ...providerOptions };\n const retryMount: MountConfig = {\n uri,\n path: mountPath,\n access_mode: (accessMode as \"readonly\" | \"readwrite\") ?? undefined,\n auth: (auth as string) ?? undefined,\n description: (description as string) ?? undefined,\n options: Object.keys(retryProviderOptions).length > 0 ? retryProviderOptions : undefined,\n };\n credResult = await resolveAndMergeCredentials(\n retryMount,\n authContext,\n credentialStore,\n registry,\n { forceCollect: true },\n );\n const retryProvider = await registry.createProvider(retryMount);\n await afs.mount(retryProvider, mountPath);\n // Update mount reference for persistence below\n Object.assign(mount, retryMount);\n } else {\n throw mountError;\n }\n }\n\n // Persist credentials if any were collected\n if (credResult) {\n await persistCredentialResult(mount, credResult);\n }\n\n // Persist extracted MCP env values to credentials.toml\n if (hasLoadEnv && credentialStore) {\n try {\n const envCreds: Record<string, string> = {};\n for (const [k, v] of Object.entries(loadEnvRecord)) {\n envCreds[`env:${k}`] = v;\n }\n // Merge with any existing sensitive values from credential resolution\n const existing = credResult ? { ...credResult.sensitive } : {};\n await credentialStore.set(loadConfigUri, { ...existing, ...envCreds });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount] env credential persistence failed: ${msg}`);\n }\n }\n\n // Always persist to config after successful mount\n try {\n // Use configUri (env stripped) for config.toml — env values are in credentials.toml\n const entry: ConfigMountEntry = {\n path: mountPath,\n uri: hasLoadEnv ? loadConfigUri : mount.uri,\n };\n if (description) entry.description = description as string;\n if (accessMode) entry.access_mode = accessMode as \"readonly\" | \"readwrite\";\n if (auth) entry.auth = auth as string;\n const mergedOptions = { ...providerOptions, ...credResult?.nonSensitive };\n // Strip env from config options — it's in credentials.toml\n if (hasLoadEnv) delete mergedOptions.env;\n if (Object.keys(mergedOptions).length > 0) entry.options = mergedOptions;\n await persistMount(cwd, entry, persistScope);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount] config persistence failed: ${msg}`);\n }\n };\n\n // ── Inject unloadProvider ──\n // Removes mount config when provider is unmounted via /.actions/unmount\n afs.unloadProvider = async (mountPath: string, options?: Record<string, unknown>) => {\n try {\n await unpersistMount(cwd, mountPath, (options?.scope as PersistScope) || undefined);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[unmount] config removal failed: ${msg}`);\n }\n };\n\n // ── Inject updateProviderConfig ──\n // Allows providers to persist option changes back to their config file\n afs.updateProviderConfig = async (mountPath: string, optionUpdates: Record<string, unknown>) => {\n try {\n const key = `:${mountPath}`;\n const configDir = mountSources.get(key);\n if (!configDir) return;\n await updateMountOptions(configDir, mountPath, optionUpdates);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[updateProviderConfig] failed: ${msg}`);\n }\n };\n\n // ── Auto-mount Registry (tolerant — silent on failure) ──\n // Scans locally installed @aigne/afs-* packages at runtime.\n if (config.registry?.enabled !== false) {\n try {\n const { AFSRegistry } = await import(\"@aigne/afs-registry\");\n const registry = new AFSRegistry(\n config.registry?.providers?.length\n ? { providers: config.registry.providers as any[] }\n : undefined,\n );\n await afs.mount(registry, \"/registry\");\n } catch {\n // Silent degradation — registry is optional\n }\n }\n\n if (config.mounts.length === 0) {\n return { afs, failures: [], configMountPaths: [], registry };\n }\n\n const total = config.mounts.length;\n let completed = 0;\n let failedCount = 0;\n\n options?.onProgress?.({ total, completed: 0, failed: 0 });\n\n // Load stored credentials keyed by URI\n if (credentialStore && mountSources.size > 0) {\n await mergeStoredCredentials(config.mounts, mountSources, credentialStore);\n }\n\n const results = await Promise.allSettled(\n config.mounts.map(async (mount) => {\n const provider = await registry.createProvider(mount);\n await afs.mount(provider, mount.path, { namespace: mount.namespace ?? null });\n completed++;\n options?.onProgress?.({ total, completed, failed: failedCount });\n return mount.path;\n }),\n );\n\n const failures: MountFailure[] = [];\n const succeeded: string[] = [];\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]!;\n if (result.status === \"fulfilled\") {\n succeeded.push(result.value);\n } else {\n const reason = result.reason;\n const msg = reason instanceof Error ? reason.message : String(reason);\n failures.push({ path: config.mounts[i]!.path, reason: msg });\n failedCount++;\n completed++;\n options?.onProgress?.({ total, completed, failed: failedCount });\n }\n }\n\n // When onProgress is provided, the caller handles display; otherwise log to stderr\n if (!options?.onProgress && failures.length > 0) {\n console.warn(`[mount] ${succeeded.length} succeeded, ${failures.length} failed:`);\n for (const f of failures) {\n console.warn(` - ${f.path}: ${f.reason}`);\n }\n }\n\n if (succeeded.length === 0 && config.mounts.length > 0) {\n throw new Error(\"All providers failed to mount\");\n }\n\n // Auto-mount program data directory from user's home ~/.afs-config/data/\n // Always use user-level config dir so program data persists across projects\n {\n const userConfigDir = process.env[AFS_USER_CONFIG_DIR_ENV] ?? join(homedir(), CONFIG_DIR_NAME);\n const dataDir = join(userConfigDir, \"data\");\n try {\n await mkdir(dataDir, { recursive: true });\n const dataProvider = await registry.createProvider({\n uri: `fs://${dataDir}`,\n path: \"/.data\",\n access_mode: \"readwrite\",\n });\n await afs.mount(dataProvider, \"/.data\");\n } catch {\n // Non-critical: program data mount is optional\n }\n }\n\n return { afs, failures, configMountPaths: succeeded, registry };\n}\n\n// ─── Mount Verification ───────────────────────────────────────────────────────\n\n/**\n * Verify that a mount configuration produces a working provider.\n *\n * Creates the provider and mounts it on a temporary AFS instance,\n * which triggers the built-in checkProviderOnMount (stat + data validation + list).\n * Throws if the mount check fails.\n *\n * @param uri - Provider URI\n * @param mountPath - Mount path\n * @param options - Merged options (non-sensitive + sensitive) for provider creation\n */\nexport async function verifyMount(\n uri: string,\n mountPath: string,\n options?: Record<string, unknown>,\n): Promise<void> {\n const mount: MountConfig = {\n uri,\n path: mountPath,\n options: options && Object.keys(options).length > 0 ? options : undefined,\n };\n\n const registry = new ProviderRegistry();\n registerWorkspaceFactory(registry);\n let provider: AFSModule;\n try {\n provider = await registry.createProvider(mount);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Mount verification failed (provider creation): ${msg}`);\n }\n\n // Mount on a temporary AFS to trigger the real checkProviderOnMount\n try {\n const { AFS } = await import(\"@aigne/afs\");\n const tempAFS = new AFS();\n await tempAFS.mount(provider, mountPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Mount verification failed: could not reach provider at ${uri}. ` +\n `Error: ${msg}. Check your URI and credentials.`,\n );\n } finally {\n // Clean up provider resources (e.g., MCP process)\n try {\n await provider.close?.();\n } catch {\n // ignore cleanup errors\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"afs-loader.mjs","names":["options","registry","AFS"],"sources":["../../src/config/afs-loader.ts"],"sourcesContent":["/**\n * AFS Loader - Lazy loading with parallel tolerant mount\n *\n * Provides loadAFS() for on-demand AFS creation with caching.\n * createAFS() uses Promise.allSettled for parallel provider creation + mount,\n * tolerating individual failures while reporting them to stderr.\n *\n * Integrates 4-step credential resolution:\n * 1. Determine missing fields from provider schema\n * 2. Silent resolution (config > env > credential store)\n * 3. Interactive collection (provider auth() or default collect())\n * 4. Unified persistence (sensitive → credentials.toml, non-sensitive → config options)\n */\n\nimport type { AFSModule, AuthContext, MountConfig } from \"@aigne/afs\";\nimport { AFS, ProviderRegistry } from \"@aigne/afs\";\nimport { parseURI } from \"@aigne/afs/utils/uri\";\nimport type { CredentialStore } from \"../credential/store.js\";\nimport {\n extractEnvFromURI,\n mergeStoredCredentials,\n persistCredentialResult,\n type ResolveCredentialsForMountOptions,\n type ResolveCredentialsForMountResult,\n resolveAndMergeCredentials,\n resolveCredentialsForMount,\n} from \"./credential-helpers.js\";\nimport { ConfigLoader } from \"./loader.js\";\nimport {\n type ConfigMountEntry,\n type PersistScope,\n persistMount,\n unpersistMount,\n updateMountOptions,\n} from \"./mount-commands.js\";\n\n// Re-export credential helpers for backward compatibility\nexport {\n extractEnvFromURI,\n resolveCredentialsForMount,\n type ResolveCredentialsForMountOptions,\n type ResolveCredentialsForMountResult,\n};\n\n// ─── Workspace Factory Helper ─────────────────────────────────────────────\n\n/**\n * Register the workspace:// scheme on a ProviderRegistry.\n *\n * Extracted so that both createAFS() and verifyMount() can support\n * workspace URIs. createAFS() overrides this with a richer variant\n * that includes credential resolution.\n */\nfunction registerWorkspaceFactory(registry: import(\"@aigne/afs\").ProviderRegistry): void {\n registry.register(\"workspace\", async (mount, parsed) => {\n const mod = await import(\"@aigne/afs-workspace\" as string);\n const AFSWorkspace = mod.AFSWorkspace ?? mod.default;\n if (!AFSWorkspace) {\n throw new Error(\n \"workspace:// scheme requires @aigne/afs-workspace package. Install it with: pnpm add @aigne/afs-workspace\",\n );\n }\n return new AFSWorkspace({\n workspacePath: parsed.body,\n registry,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"workspace\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n });\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport interface MountProgressEvent {\n total: number;\n completed: number;\n failed: number;\n}\n\nexport interface MountFailure {\n path: string;\n reason: string;\n}\n\nexport interface CreateAFSResult {\n afs: AFS;\n failures: MountFailure[];\n /** Mount paths that were successfully loaded from config.toml (excludes code-managed mounts). */\n configMountPaths: string[];\n /** Provider registry with all registered factories (workspace, etc.). */\n registry: import(\"@aigne/afs\").ProviderRegistry;\n}\n\nexport interface CreateAFSOptions {\n onProgress?: (event: MountProgressEvent) => void;\n /** Auth context for interactive credential collection (CLI or MCP) */\n authContext?: AuthContext;\n /** Credential store for reading/writing credentials */\n credentialStore?: CredentialStore;\n}\n\n// ─── Cache ────────────────────────────────────────────────────────────────\n\nconst cacheMap = new Map<string, AFS>();\n\nfunction cacheKey(cwd: string): string {\n const { resolve } = require(\"node:path\") as typeof import(\"node:path\");\n return resolve(cwd);\n}\n\n/**\n * Reset all cached AFS instances (for testing)\n */\nexport function resetAFSCache(): void {\n cacheMap.clear();\n}\n\n/**\n * Load AFS with per-cwd caching — same cwd returns same instance,\n * different cwd creates a separate instance.\n */\nexport async function loadAFS(cwd: string, options?: CreateAFSOptions): Promise<CreateAFSResult> {\n const key = cacheKey(cwd);\n const cached = cacheMap.get(key);\n if (cached)\n return { afs: cached, failures: [], configMountPaths: [], registry: new ProviderRegistry() };\n const result = await createAFS(cwd, options);\n cacheMap.set(key, result.afs);\n return result;\n}\n\n/**\n * Create AFS instance from config with parallel tolerant mount\n *\n * - All providers are created and mounted in parallel via Promise.allSettled\n * - Individual failures are logged to stderr and skipped (unless onProgress is provided)\n * - If ALL providers fail, throws an error\n * - If no mounts configured, returns empty AFS (no error)\n */\nexport async function createAFS(cwd: string, options?: CreateAFSOptions): Promise<CreateAFSResult> {\n const loader = new ConfigLoader();\n const { config, mountSources } = await loader.loadWithSources(cwd);\n const afs = new AFS();\n const authContext = options?.authContext;\n const credentialStore = options?.credentialStore;\n\n // Create registry — auto-loads built-in providers via manifest-driven resolution.\n // Only workspace needs explicit registration (to break circular deps + inject credential callback).\n const registry = new ProviderRegistry();\n registry.register(\"workspace\", async (mount, parsed) => {\n const mod = await import(\"@aigne/afs-workspace\" as string);\n const AFSWorkspace = mod.AFSWorkspace ?? mod.default;\n if (!AFSWorkspace) {\n throw new Error(\n \"workspace:// scheme requires @aigne/afs-workspace package. Install it with: pnpm add @aigne/afs-workspace\",\n );\n }\n const { resolve } = await import(\"node:path\");\n const _workspacePath = resolve(parsed.body);\n return new AFSWorkspace({\n workspacePath: parsed.body,\n registry,\n createProvider: async (subMount: MountConfig) => {\n const credResult = await resolveAndMergeCredentials(\n subMount,\n authContext,\n credentialStore,\n registry,\n );\n // Create provider with resolved credentials in mount.options\n const provider = await registry.createProvider(subMount);\n // Persist sensitive credentials to credential store\n if (credResult) {\n await persistCredentialResult(subMount, credResult);\n // Strip sensitive values from mount.options (keep only non-sensitive for config)\n const opts = subMount.options ?? {};\n for (const key of Object.keys(credResult.sensitive)) {\n delete opts[key];\n }\n subMount.options = Object.keys(opts).length > 0 ? opts : undefined;\n // Merge non-sensitive resolved values back\n if (Object.keys(credResult.nonSensitive).length > 0) {\n subMount.options = { ...(subMount.options ?? {}), ...credResult.nonSensitive };\n }\n }\n return provider;\n },\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"workspace\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n });\n\n // ── Inject provider factory for program mount fallback ──\n // Handles credential resolution + registry creation for program mounts\n // when shared mount URI isn't found in host AFS.\n afs.createProviderFromMount = async (mount) => {\n await resolveAndMergeCredentials(mount, authContext, credentialStore, registry);\n return registry.createProvider(mount);\n };\n\n // ── Inject loadProvider ──\n // Allows agents to mount new providers at runtime via /.actions/mount\n afs.loadProvider = async (uri: string, mountPath: string, options?: Record<string, unknown>) => {\n // Validate URI format before passing to factory\n parseURI(uri);\n\n // Extract env query params from MCP URIs for secure credential storage\n const { cleanUri: loadConfigUri, envRecord: loadEnvRecord } = extractEnvFromURI(uri);\n const hasLoadEnv = Object.keys(loadEnvRecord).length > 0;\n\n // Extract known mount-level fields; pass remaining as provider-specific options\n const { accessMode, auth, description, scope, ...providerOptions } = options ?? {};\n\n // Inject extracted env values into provider options\n if (hasLoadEnv) {\n const existingEnv = (providerOptions.env as Record<string, string>) ?? {};\n providerOptions.env = { ...existingEnv, ...loadEnvRecord };\n }\n\n const mount: MountConfig = {\n uri,\n path: mountPath,\n access_mode: (accessMode as \"readonly\" | \"readwrite\") ?? undefined,\n auth: (auth as string) ?? undefined,\n description: (description as string) ?? undefined,\n options: Object.keys(providerOptions).length > 0 ? providerOptions : undefined,\n };\n\n const persistScope = (scope as PersistScope) || \"cwd\";\n let credResult = await resolveAndMergeCredentials(\n mount,\n authContext,\n credentialStore,\n registry,\n );\n\n try {\n const provider = await registry.createProvider(mount);\n await afs.mount(provider, mountPath);\n } catch (mountError) {\n // Health check failed with silently resolved credentials →\n // retry once with forced interactive collection so user can fix values\n if (credResult === null && authContext) {\n // credResult === null means all fields resolved silently (no interactive collection)\n // Rebuild mount config from scratch for retry\n const retryProviderOptions = { ...providerOptions };\n const retryMount: MountConfig = {\n uri,\n path: mountPath,\n access_mode: (accessMode as \"readonly\" | \"readwrite\") ?? undefined,\n auth: (auth as string) ?? undefined,\n description: (description as string) ?? undefined,\n options: Object.keys(retryProviderOptions).length > 0 ? retryProviderOptions : undefined,\n };\n credResult = await resolveAndMergeCredentials(\n retryMount,\n authContext,\n credentialStore,\n registry,\n { forceCollect: true },\n );\n const retryProvider = await registry.createProvider(retryMount);\n await afs.mount(retryProvider, mountPath);\n // Update mount reference for persistence below\n Object.assign(mount, retryMount);\n } else {\n throw mountError;\n }\n }\n\n // Persist credentials if any were collected\n if (credResult) {\n await persistCredentialResult(mount, credResult);\n }\n\n // Persist extracted MCP env values to credentials.toml\n if (hasLoadEnv && credentialStore) {\n try {\n const envCreds: Record<string, string> = {};\n for (const [k, v] of Object.entries(loadEnvRecord)) {\n envCreds[`env:${k}`] = v;\n }\n // Merge with any existing sensitive values from credential resolution\n const existing = credResult ? { ...credResult.sensitive } : {};\n await credentialStore.set(loadConfigUri, { ...existing, ...envCreds });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount] env credential persistence failed: ${msg}`);\n }\n }\n\n // Always persist to config after successful mount\n try {\n // Use configUri (env stripped) for config.toml — env values are in credentials.toml\n const entry: ConfigMountEntry = {\n path: mountPath,\n uri: hasLoadEnv ? loadConfigUri : mount.uri,\n };\n if (description) entry.description = description as string;\n if (accessMode) entry.access_mode = accessMode as \"readonly\" | \"readwrite\";\n if (auth) entry.auth = auth as string;\n const mergedOptions = { ...providerOptions, ...credResult?.nonSensitive };\n // Strip env from config options — it's in credentials.toml\n if (hasLoadEnv) delete mergedOptions.env;\n if (Object.keys(mergedOptions).length > 0) entry.options = mergedOptions;\n await persistMount(cwd, entry, persistScope);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount] config persistence failed: ${msg}`);\n }\n };\n\n // ── Inject unloadProvider ──\n // Removes mount config when provider is unmounted via /.actions/unmount\n afs.unloadProvider = async (mountPath: string, options?: Record<string, unknown>) => {\n try {\n await unpersistMount(cwd, mountPath, (options?.scope as PersistScope) || undefined);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[unmount] config removal failed: ${msg}`);\n }\n };\n\n // ── Inject updateProviderConfig ──\n // Allows providers to persist option changes back to their config file\n afs.updateProviderConfig = async (mountPath: string, optionUpdates: Record<string, unknown>) => {\n try {\n const key = `:${mountPath}`;\n const configDir = mountSources.get(key);\n if (!configDir) return;\n await updateMountOptions(configDir, mountPath, optionUpdates);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[updateProviderConfig] failed: ${msg}`);\n }\n };\n\n // ── Auto-mount Registry (tolerant — silent on failure) ──\n // Scans locally installed @aigne/afs-* packages at runtime.\n if (config.registry?.enabled !== false) {\n try {\n const { AFSRegistry } = await import(\"@aigne/afs-registry\");\n const registry = new AFSRegistry(\n config.registry?.providers?.length\n ? { providers: config.registry.providers as any[] }\n : undefined,\n );\n await afs.mount(registry, \"/registry\");\n } catch {\n // Silent degradation — registry is optional\n }\n }\n\n if (config.mounts.length === 0) {\n return { afs, failures: [], configMountPaths: [], registry };\n }\n\n const total = config.mounts.length;\n let completed = 0;\n let failedCount = 0;\n\n options?.onProgress?.({ total, completed: 0, failed: 0 });\n\n // Load stored credentials keyed by URI\n if (credentialStore && mountSources.size > 0) {\n await mergeStoredCredentials(config.mounts, mountSources, credentialStore);\n }\n\n const results = await Promise.allSettled(\n config.mounts.map(async (mount) => {\n const provider = await registry.createProvider(mount);\n await afs.mount(provider, mount.path, { namespace: mount.namespace ?? null });\n completed++;\n options?.onProgress?.({ total, completed, failed: failedCount });\n return mount.path;\n }),\n );\n\n const failures: MountFailure[] = [];\n const succeeded: string[] = [];\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]!;\n if (result.status === \"fulfilled\") {\n succeeded.push(result.value);\n } else {\n const reason = result.reason;\n const msg = reason instanceof Error ? reason.message : String(reason);\n failures.push({ path: config.mounts[i]!.path, reason: msg });\n failedCount++;\n completed++;\n options?.onProgress?.({ total, completed, failed: failedCount });\n }\n }\n\n // When onProgress is provided, the caller handles display; otherwise log to stderr\n if (!options?.onProgress && failures.length > 0) {\n console.warn(`[mount] ${succeeded.length} succeeded, ${failures.length} failed:`);\n for (const f of failures) {\n console.warn(` - ${f.path}: ${f.reason}`);\n }\n }\n\n if (succeeded.length === 0 && config.mounts.length > 0) {\n throw new Error(\"All providers failed to mount\");\n }\n\n return { afs, failures, configMountPaths: succeeded, registry };\n}\n\n// ─── Mount Verification ───────────────────────────────────────────────────────\n\n/**\n * Verify that a mount configuration produces a working provider.\n *\n * Creates the provider and mounts it on a temporary AFS instance,\n * which triggers the built-in checkProviderOnMount (stat + data validation + list).\n * Throws if the mount check fails.\n *\n * @param uri - Provider URI\n * @param mountPath - Mount path\n * @param options - Merged options (non-sensitive + sensitive) for provider creation\n */\nexport async function verifyMount(\n uri: string,\n mountPath: string,\n options?: Record<string, unknown>,\n): Promise<void> {\n const mount: MountConfig = {\n uri,\n path: mountPath,\n options: options && Object.keys(options).length > 0 ? options : undefined,\n };\n\n const registry = new ProviderRegistry();\n registerWorkspaceFactory(registry);\n let provider: AFSModule;\n try {\n provider = await registry.createProvider(mount);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Mount verification failed (provider creation): ${msg}`);\n }\n\n // Mount on a temporary AFS to trigger the real checkProviderOnMount\n try {\n const { AFS } = await import(\"@aigne/afs\");\n const tempAFS = new AFS();\n await tempAFS.mount(provider, mountPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Mount verification failed: could not reach provider at ${uri}. ` +\n `Error: ${msg}. Check your URI and credentials.`,\n );\n } finally {\n // Clean up provider resources (e.g., MCP process)\n try {\n await provider.close?.();\n } catch {\n // ignore cleanup errors\n }\n }\n}\n"],"mappings":"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"}
@@ -254,6 +254,17 @@ async function resolveCredentialsForMount(options) {
254
254
  const fieldNames = Object.keys(properties);
255
255
  throw new Error(`Missing credentials: ${fieldNames.join(", ")}. Retry with them as args, e.g. { "uri": "${uri}", "path": "${mountPath}", ${fieldNames.map((f) => `"${f}": "..."`).join(", ")} }`);
256
256
  }
257
+ if (Object.keys(result.values).length > 0) {
258
+ const opts = mount.options ?? {};
259
+ for (const [key, value] of Object.entries(result.values)) {
260
+ if (key === "token" || key === "auth") continue;
261
+ if (result.collected || opts[key] === void 0) opts[key] = value;
262
+ }
263
+ if (Object.keys(opts).length > 0) mount.options = opts;
264
+ }
265
+ rebuildURIFromTemplate(mount, info?.manifest);
266
+ const resolvedUri = mount.uri !== uri ? mount.uri : void 0;
267
+ const storeKey = resolvedUri ?? configUri;
257
268
  const sensitiveFieldSet = new Set(sensitiveFieldsInSchema);
258
269
  const flatSensitive = {};
259
270
  for (const field of sensitiveFieldsInSchema) {
@@ -267,7 +278,7 @@ async function resolveCredentialsForMount(options) {
267
278
  const toStore = { ...flatSensitive };
268
279
  for (const [key, val] of Object.entries(envRecord)) toStore[`env:${key}`] = val;
269
280
  if (Object.keys(toStore).length > 0 && credentialStore) try {
270
- await credentialStore.set(configUri, toStore);
281
+ await credentialStore.set(storeKey, toStore);
271
282
  } catch (err) {
272
283
  const msg = err instanceof Error ? err.message : String(err);
273
284
  console.warn(`[mount add] credential persistence failed: ${msg}`);
@@ -279,7 +290,8 @@ async function resolveCredentialsForMount(options) {
279
290
  allValues: result.values,
280
291
  persistCredentials,
281
292
  sensitiveFields: sensitiveFieldsInSchema,
282
- configUri: hasExtractedEnv ? configUri : void 0
293
+ configUri: hasExtractedEnv ? configUri : void 0,
294
+ resolvedUri
283
295
  };
284
296
  }
285
297
 
@@ -254,6 +254,17 @@ async function resolveCredentialsForMount(options) {
254
254
  const fieldNames = Object.keys(properties);
255
255
  throw new Error(`Missing credentials: ${fieldNames.join(", ")}. Retry with them as args, e.g. { "uri": "${uri}", "path": "${mountPath}", ${fieldNames.map((f) => `"${f}": "..."`).join(", ")} }`);
256
256
  }
257
+ if (Object.keys(result.values).length > 0) {
258
+ const opts = mount.options ?? {};
259
+ for (const [key, value] of Object.entries(result.values)) {
260
+ if (key === "token" || key === "auth") continue;
261
+ if (result.collected || opts[key] === void 0) opts[key] = value;
262
+ }
263
+ if (Object.keys(opts).length > 0) mount.options = opts;
264
+ }
265
+ rebuildURIFromTemplate(mount, info?.manifest);
266
+ const resolvedUri = mount.uri !== uri ? mount.uri : void 0;
267
+ const storeKey = resolvedUri ?? configUri;
257
268
  const sensitiveFieldSet = new Set(sensitiveFieldsInSchema);
258
269
  const flatSensitive = {};
259
270
  for (const field of sensitiveFieldsInSchema) {
@@ -267,7 +278,7 @@ async function resolveCredentialsForMount(options) {
267
278
  const toStore = { ...flatSensitive };
268
279
  for (const [key, val] of Object.entries(envRecord)) toStore[`env:${key}`] = val;
269
280
  if (Object.keys(toStore).length > 0 && credentialStore) try {
270
- await credentialStore.set(configUri, toStore);
281
+ await credentialStore.set(storeKey, toStore);
271
282
  } catch (err) {
272
283
  const msg = err instanceof Error ? err.message : String(err);
273
284
  console.warn(`[mount add] credential persistence failed: ${msg}`);
@@ -279,7 +290,8 @@ async function resolveCredentialsForMount(options) {
279
290
  allValues: result.values,
280
291
  persistCredentials,
281
292
  sensitiveFields: sensitiveFieldsInSchema,
282
- configUri: hasExtractedEnv ? configUri : void 0
293
+ configUri: hasExtractedEnv ? configUri : void 0,
294
+ resolvedUri
283
295
  };
284
296
  }
285
297
 
@@ -1 +1 @@
1
- {"version":3,"file":"credential-helpers.mjs","names":[],"sources":["../../src/config/credential-helpers.ts"],"sourcesContent":["/**\n * Credential Resolution Helpers\n *\n * Extracted from afs-loader.ts to separate credential resolution concerns\n * from AFS lifecycle management. All credential-related logic lives here:\n * - URI env extraction (MCP schemes)\n * - URI template variable merging\n * - Credential resolution + merge into mount config\n * - Credential persistence\n * - CLI mount add credential resolution\n */\n\nimport type { AuthContext, MountConfig } from \"@aigne/afs\";\nimport { ProviderRegistry } from \"@aigne/afs\";\nimport { parseURI } from \"@aigne/afs/utils/uri\";\nimport type { CredentialStore } from \"../credential/store.js\";\n\n// ─── URI & Template Manipulation ─────────────────────────────────────────\n\n/**\n * Extract env query params from MCP URIs for secure credential storage.\n *\n * MCP servers receive secrets via env vars (e.g., `mcp+stdio://npx?env=API_KEY=sk-xxx`).\n * This function extracts env params from the URI so they can be stored in credentials.toml\n * instead of being persisted in plaintext in config.toml.\n *\n * Only applies to MCP schemes (mcp://, mcp+stdio://, mcp+sse://).\n * Non-MCP URIs are returned unchanged.\n */\nexport function extractEnvFromURI(uri: string): {\n cleanUri: string;\n envRecord: Record<string, string>;\n} {\n const parsed = parseURI(uri);\n const envRecord: Record<string, string> = {};\n\n // Only extract env for MCP schemes\n if (!parsed.scheme.startsWith(\"mcp\")) {\n return { cleanUri: uri, envRecord };\n }\n\n const envValues = parsed.query.env;\n if (!envValues) return { cleanUri: uri, envRecord };\n\n // Parse env=KEY=VALUE format (split on first = only)\n const envList = Array.isArray(envValues) ? envValues : [envValues];\n for (const entry of envList) {\n const eqIdx = entry.indexOf(\"=\");\n if (eqIdx > 0) {\n envRecord[entry.slice(0, eqIdx)] = entry.slice(eqIdx + 1);\n }\n }\n\n // Rebuild URI without env params, preserving other query params\n const queryIndex = uri.indexOf(\"?\");\n if (queryIndex < 0) return { cleanUri: uri, envRecord };\n\n const rawQuery = uri.slice(queryIndex + 1);\n const params = new URLSearchParams(rawQuery);\n params.delete(\"env\");\n const newQuery = params.toString();\n const base = uri.slice(0, queryIndex);\n const cleanUri = newQuery ? `${base}?${newQuery}` : base;\n\n return { cleanUri, envRecord };\n}\n\n/**\n * Extract template variables from mount.uri using the manifest's uriTemplate\n * and merge them into mount.options so the credential resolver sees them as \"known\".\n */\nexport function mergeTemplateVarsIntoMount(\n mount: MountConfig,\n manifest: import(\"@aigne/afs\").ProviderManifest | null | undefined,\n): void {\n if (!manifest?.uriTemplate) return;\n const { parseTemplate } =\n require(\"@aigne/afs/utils/uri-template\") as typeof import(\"@aigne/afs/utils/uri-template\");\n const parsed = parseURI(mount.uri);\n let templateVars: Record<string, string | undefined>;\n try {\n templateVars = parseTemplate(manifest.uriTemplate, parsed.body);\n } catch {\n return; // Body doesn't match template yet — nothing to merge\n }\n for (const [key, value] of Object.entries(templateVars)) {\n if (value !== undefined) {\n if (!mount.options) mount.options = {};\n if (mount.options[key] === undefined) {\n mount.options[key] = value;\n }\n }\n }\n}\n\n/**\n * After credential resolution, rebuild mount.uri from the template if the\n * current URI body is empty/incomplete and resolved options can fill template vars.\n */\nexport function rebuildURIFromTemplate(\n mount: MountConfig,\n manifest: import(\"@aigne/afs\").ProviderManifest | null | undefined,\n): void {\n if (!manifest?.uriTemplate) return;\n const { buildURI, getTemplateVariableNames } =\n require(\"@aigne/afs/utils/uri-template\") as typeof import(\"@aigne/afs/utils/uri-template\");\n const varNames = getTemplateVariableNames(manifest.uriTemplate);\n if (varNames.length === 0) return;\n\n const parsed = parseURI(mount.uri);\n const { parseTemplate } =\n require(\"@aigne/afs/utils/uri-template\") as typeof import(\"@aigne/afs/utils/uri-template\");\n let existingVars: Record<string, string | undefined>;\n try {\n existingVars = parseTemplate(manifest.uriTemplate, parsed.body);\n } catch {\n existingVars = {};\n }\n\n const allVars: Record<string, string | undefined> = { ...existingVars };\n for (const name of varNames) {\n if (!allVars[name] && mount.options?.[name] != null) {\n allVars[name] = String(mount.options[name]);\n }\n }\n\n try {\n const newURI = buildURI(manifest.uriTemplate, allVars);\n if (newURI !== mount.uri) {\n mount.uri = newURI;\n }\n } catch {\n // Still can't build a complete URI — that's OK, createProvider will handle it\n }\n}\n\n// ─── Credential Resolution Core ─────────────────────────────────────────\n\n/**\n * Attempt credential resolution for a mount, merging resolved values into mount.options.\n *\n * Returns the credential result if any fields were collected interactively,\n * or null if no interactive collection was needed (or no schema/authContext).\n *\n * This function mutates mount.options and mount.auth/mount.token when credentials\n * are resolved, so the subsequent registry.createProvider(mount) receives complete values.\n */\nexport async function resolveAndMergeCredentials(\n mount: MountConfig,\n authContext: AuthContext | undefined,\n credentialStore: CredentialStore | undefined,\n registry: ProviderRegistry,\n opts?: { forceCollect?: boolean },\n): Promise<import(\"../credential/resolver.js\").ResolveCredentialsResult | null> {\n const info = await registry.getProviderInfo(mount.uri);\n const schema = info?.schema ?? null;\n const providerAuth = info?.auth;\n\n if (!schema) return null;\n\n mergeTemplateVarsIntoMount(mount, info?.manifest);\n\n const { getSensitiveFields } = await import(\"@aigne/afs/utils/schema\");\n const sensitiveFieldsInSchema = getSensitiveFields(schema);\n const schemaProps = (schema as any).properties ?? {};\n const hasEnvFields = Object.values(schemaProps).some((p: any) => Array.isArray(p?.env));\n if (sensitiveFieldsInSchema.length === 0 && !hasEnvFields && !providerAuth) return null;\n\n const { resolveCredentials } = await import(\"../credential/resolver.js\");\n\n const result = await resolveCredentials({\n mount,\n schema,\n authContext,\n credentialStore,\n providerAuth,\n forceCollect: opts?.forceCollect,\n });\n\n if (!result) {\n const fieldNames = Object.keys((schema as any).properties ?? {});\n const known = new Set<string>();\n if (mount.auth !== undefined) known.add(\"auth\");\n if (mount.token !== undefined) known.add(\"token\");\n if (mount.options) {\n for (const k of Object.keys(mount.options)) known.add(k);\n }\n const missing = fieldNames.filter((f) => !known.has(f));\n const fieldList = missing.length > 0 ? missing.join(\", \") : fieldNames.join(\", \");\n throw new Error(\n `Missing credentials: ${fieldList}. ` +\n `Retry with them as args, e.g. { \"uri\": \"${mount.uri}\", \"path\": \"${mount.path}\", ${missing.map((f) => `\"${f}\": \"...\"`).join(\", \")} }`,\n );\n }\n\n if (Object.keys(result.values).length > 0) {\n if (result.values.token !== undefined && mount.token === undefined) {\n mount.token = String(result.values.token);\n }\n if (result.values.auth !== undefined && mount.auth === undefined) {\n mount.auth = String(result.values.auth);\n }\n\n const mergedOpts = mount.options ?? {};\n for (const [key, value] of Object.entries(result.values)) {\n if (key !== \"token\" && key !== \"auth\" && mergedOpts[key] === undefined) {\n mergedOpts[key] = value;\n }\n }\n if (Object.keys(mergedOpts).length > 0) {\n mount.options = mergedOpts;\n }\n }\n\n rebuildURIFromTemplate(mount, info?.manifest);\n\n return result.collected ? result : null;\n}\n\n/**\n * Persist credential resolution result (sensitive → credentials.toml).\n */\nexport async function persistCredentialResult(\n mount: MountConfig,\n result: import(\"../credential/resolver.js\").ResolveCredentialsResult,\n): Promise<void> {\n if (Object.keys(result.sensitive).length > 0) {\n try {\n const { createCredentialStore } = await import(\"../credential/store.js\");\n const store = createCredentialStore();\n await store.set(mount.uri, result.sensitive);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount] credential persistence failed: ${msg}`);\n }\n }\n}\n\n/**\n * Load stored credentials and merge into mount options during startup.\n */\nexport async function mergeStoredCredentials(\n mounts: MountConfig[],\n _mountSources: Map<string, string>,\n store: CredentialStore,\n): Promise<void> {\n for (const mount of mounts) {\n try {\n const stored = await store.get(mount.uri);\n if (stored) {\n const opts = mount.options ?? {};\n for (const [key, value] of Object.entries(stored)) {\n if (key.startsWith(\"env:\")) {\n if (!opts.env) opts.env = {};\n (opts.env as Record<string, string>)[key.slice(4)] = value;\n } else if (opts[key] === undefined) {\n opts[key] = value;\n }\n }\n if (Object.keys(opts).length > 0) {\n mount.options = opts;\n }\n }\n } catch {\n // Credential lookup failure is non-fatal\n }\n }\n}\n\n// ─── Exported Credential Resolution for CLI mount add ───────────────────────\n\nexport interface ResolveCredentialsForMountOptions {\n cwd: string;\n uri: string;\n mountPath: string;\n authContext?: AuthContext;\n credentialStore?: CredentialStore;\n extraOptions?: Record<string, unknown>;\n sensitiveArgs?: string[];\n registry?: ProviderRegistry;\n forceCollect?: boolean;\n}\n\nexport interface ResolveCredentialsForMountResult {\n collected: boolean;\n nonSensitive: Record<string, unknown>;\n allValues: Record<string, unknown>;\n persistCredentials: () => Promise<void>;\n sensitiveFields: string[];\n configUri?: string;\n}\n\n/**\n * Resolve and persist credentials for a mount configuration.\n *\n * Used by `mount add` CLI command to trigger credential collection\n * at add-time rather than deferring to AFS creation.\n */\nexport async function resolveCredentialsForMount(\n options: ResolveCredentialsForMountOptions,\n): Promise<ResolveCredentialsForMountResult | null> {\n const { uri, mountPath, authContext, credentialStore, extraOptions, sensitiveArgs } = options;\n\n const { cleanUri: configUri, envRecord } = extractEnvFromURI(uri);\n const hasExtractedEnv = Object.keys(envRecord).length > 0;\n\n const registry = options.registry ?? new ProviderRegistry();\n const info = await registry.getProviderInfo(uri);\n let schema = info?.schema ?? null;\n const providerAuth = info?.auth;\n\n if (!schema && extraOptions && Object.keys(extraOptions).length > 0) {\n const { buildAdHocSchema } = await import(\"@aigne/afs/utils/schema\");\n schema = buildAdHocSchema(extraOptions, sensitiveArgs ?? []);\n }\n\n const envOnlyResult = (): ResolveCredentialsForMountResult | null => {\n if (!hasExtractedEnv) return null;\n return {\n collected: false,\n nonSensitive: {},\n allValues: {},\n persistCredentials: async () => {\n const toStore: Record<string, string> = {};\n for (const [key, val] of Object.entries(envRecord)) {\n toStore[`env:${key}`] = val;\n }\n if (credentialStore) {\n try {\n await credentialStore.set(configUri, toStore);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount add] credential persistence failed: ${msg}`);\n }\n }\n },\n sensitiveFields: [],\n configUri,\n };\n };\n\n if (!schema) return envOnlyResult();\n\n const properties = (schema as any).properties;\n if (!properties || Object.keys(properties).length === 0) return envOnlyResult();\n\n if (sensitiveArgs && sensitiveArgs.length > 0) {\n const mergedProps = { ...properties };\n let changed = false;\n for (const field of sensitiveArgs) {\n if (mergedProps[field]) {\n mergedProps[field] = { ...mergedProps[field], sensitive: true };\n changed = true;\n } else if (extraOptions?.[field] !== undefined) {\n const value = extraOptions[field];\n mergedProps[field] = {\n type:\n typeof value === \"number\"\n ? \"number\"\n : typeof value === \"boolean\"\n ? \"boolean\"\n : \"string\",\n sensitive: true,\n };\n changed = true;\n }\n }\n if (changed) {\n schema = { ...schema, properties: mergedProps } as typeof schema;\n }\n }\n\n const { getSensitiveFields } = await import(\"@aigne/afs/utils/schema\");\n const sensitiveFieldsInSchema = getSensitiveFields(schema);\n const schemaProps = (schema as any).properties;\n const hasEnvFields = Object.values(schemaProps).some((p: any) => Array.isArray(p?.env));\n if (sensitiveFieldsInSchema.length === 0 && !hasEnvFields && !extraOptions)\n return envOnlyResult();\n\n const mount: MountConfig = { uri, path: mountPath };\n\n if (extraOptions && Object.keys(extraOptions).length > 0) {\n mount.options = { ...(mount.options ?? {}), ...extraOptions };\n }\n\n mergeTemplateVarsIntoMount(mount, info?.manifest);\n\n const { resolveCredentials } = await import(\"../credential/resolver.js\");\n\n const result = await resolveCredentials({\n mount,\n schema,\n authContext,\n credentialStore,\n providerAuth,\n forceCollect: options.forceCollect,\n });\n\n if (!result) {\n const fieldNames = Object.keys(properties);\n throw new Error(\n `Missing credentials: ${fieldNames.join(\", \")}. ` +\n `Retry with them as args, e.g. { \"uri\": \"${uri}\", \"path\": \"${mountPath}\", ${fieldNames.map((f) => `\"${f}\": \"...\"`).join(\", \")} }`,\n );\n }\n\n const sensitiveFieldSet = new Set(sensitiveFieldsInSchema);\n\n const flatSensitive: Record<string, string> = {};\n for (const field of sensitiveFieldsInSchema) {\n const val = result.values[field];\n if (val === undefined) continue;\n if (field === \"env\" && typeof val === \"object\" && val !== null) {\n for (const [envKey, envVal] of Object.entries(val as Record<string, string>)) {\n flatSensitive[`env:${envKey}`] = String(envVal);\n }\n } else {\n flatSensitive[field] = String(val);\n }\n }\n\n const nonSensitive: Record<string, unknown> = result.collected\n ? result.nonSensitive\n : extraOptions\n ? Object.fromEntries(Object.entries(extraOptions).filter(([k]) => !sensitiveFieldSet.has(k)))\n : {};\n\n const persistCredentials = async () => {\n const toStore = { ...flatSensitive };\n for (const [key, val] of Object.entries(envRecord)) {\n toStore[`env:${key}`] = val;\n }\n if (Object.keys(toStore).length > 0 && credentialStore) {\n try {\n await credentialStore.set(configUri, toStore);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount add] credential persistence failed: ${msg}`);\n }\n }\n };\n\n return {\n collected: result.collected,\n nonSensitive,\n allValues: result.values,\n persistCredentials,\n sensitiveFields: sensitiveFieldsInSchema,\n configUri: hasExtractedEnv ? configUri : undefined,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"credential-helpers.mjs","names":[],"sources":["../../src/config/credential-helpers.ts"],"sourcesContent":["/**\n * Credential Resolution Helpers\n *\n * Extracted from afs-loader.ts to separate credential resolution concerns\n * from AFS lifecycle management. All credential-related logic lives here:\n * - URI env extraction (MCP schemes)\n * - URI template variable merging\n * - Credential resolution + merge into mount config\n * - Credential persistence\n * - CLI mount add credential resolution\n */\n\nimport type { AuthContext, MountConfig } from \"@aigne/afs\";\nimport { ProviderRegistry } from \"@aigne/afs\";\nimport { parseURI } from \"@aigne/afs/utils/uri\";\nimport type { CredentialStore } from \"../credential/store.js\";\n\n// ─── URI & Template Manipulation ─────────────────────────────────────────\n\n/**\n * Extract env query params from MCP URIs for secure credential storage.\n *\n * MCP servers receive secrets via env vars (e.g., `mcp+stdio://npx?env=API_KEY=sk-xxx`).\n * This function extracts env params from the URI so they can be stored in credentials.toml\n * instead of being persisted in plaintext in config.toml.\n *\n * Only applies to MCP schemes (mcp://, mcp+stdio://, mcp+sse://).\n * Non-MCP URIs are returned unchanged.\n */\nexport function extractEnvFromURI(uri: string): {\n cleanUri: string;\n envRecord: Record<string, string>;\n} {\n const parsed = parseURI(uri);\n const envRecord: Record<string, string> = {};\n\n // Only extract env for MCP schemes\n if (!parsed.scheme.startsWith(\"mcp\")) {\n return { cleanUri: uri, envRecord };\n }\n\n const envValues = parsed.query.env;\n if (!envValues) return { cleanUri: uri, envRecord };\n\n // Parse env=KEY=VALUE format (split on first = only)\n const envList = Array.isArray(envValues) ? envValues : [envValues];\n for (const entry of envList) {\n const eqIdx = entry.indexOf(\"=\");\n if (eqIdx > 0) {\n envRecord[entry.slice(0, eqIdx)] = entry.slice(eqIdx + 1);\n }\n }\n\n // Rebuild URI without env params, preserving other query params\n const queryIndex = uri.indexOf(\"?\");\n if (queryIndex < 0) return { cleanUri: uri, envRecord };\n\n const rawQuery = uri.slice(queryIndex + 1);\n const params = new URLSearchParams(rawQuery);\n params.delete(\"env\");\n const newQuery = params.toString();\n const base = uri.slice(0, queryIndex);\n const cleanUri = newQuery ? `${base}?${newQuery}` : base;\n\n return { cleanUri, envRecord };\n}\n\n/**\n * Extract template variables from mount.uri using the manifest's uriTemplate\n * and merge them into mount.options so the credential resolver sees them as \"known\".\n */\nexport function mergeTemplateVarsIntoMount(\n mount: MountConfig,\n manifest: import(\"@aigne/afs\").ProviderManifest | null | undefined,\n): void {\n if (!manifest?.uriTemplate) return;\n const { parseTemplate } =\n require(\"@aigne/afs/utils/uri-template\") as typeof import(\"@aigne/afs/utils/uri-template\");\n const parsed = parseURI(mount.uri);\n let templateVars: Record<string, string | undefined>;\n try {\n templateVars = parseTemplate(manifest.uriTemplate, parsed.body);\n } catch {\n return; // Body doesn't match template yet — nothing to merge\n }\n for (const [key, value] of Object.entries(templateVars)) {\n if (value !== undefined) {\n if (!mount.options) mount.options = {};\n if (mount.options[key] === undefined) {\n mount.options[key] = value;\n }\n }\n }\n}\n\n/**\n * After credential resolution, rebuild mount.uri from the template if the\n * current URI body is empty/incomplete and resolved options can fill template vars.\n */\nexport function rebuildURIFromTemplate(\n mount: MountConfig,\n manifest: import(\"@aigne/afs\").ProviderManifest | null | undefined,\n): void {\n if (!manifest?.uriTemplate) return;\n const { buildURI, getTemplateVariableNames } =\n require(\"@aigne/afs/utils/uri-template\") as typeof import(\"@aigne/afs/utils/uri-template\");\n const varNames = getTemplateVariableNames(manifest.uriTemplate);\n if (varNames.length === 0) return;\n\n const parsed = parseURI(mount.uri);\n const { parseTemplate } =\n require(\"@aigne/afs/utils/uri-template\") as typeof import(\"@aigne/afs/utils/uri-template\");\n let existingVars: Record<string, string | undefined>;\n try {\n existingVars = parseTemplate(manifest.uriTemplate, parsed.body);\n } catch {\n existingVars = {};\n }\n\n const allVars: Record<string, string | undefined> = { ...existingVars };\n for (const name of varNames) {\n if (!allVars[name] && mount.options?.[name] != null) {\n allVars[name] = String(mount.options[name]);\n }\n }\n\n try {\n const newURI = buildURI(manifest.uriTemplate, allVars);\n if (newURI !== mount.uri) {\n mount.uri = newURI;\n }\n } catch {\n // Still can't build a complete URI — that's OK, createProvider will handle it\n }\n}\n\n// ─── Credential Resolution Core ─────────────────────────────────────────\n\n/**\n * Attempt credential resolution for a mount, merging resolved values into mount.options.\n *\n * Returns the credential result if any fields were collected interactively,\n * or null if no interactive collection was needed (or no schema/authContext).\n *\n * This function mutates mount.options and mount.auth/mount.token when credentials\n * are resolved, so the subsequent registry.createProvider(mount) receives complete values.\n */\nexport async function resolveAndMergeCredentials(\n mount: MountConfig,\n authContext: AuthContext | undefined,\n credentialStore: CredentialStore | undefined,\n registry: ProviderRegistry,\n opts?: { forceCollect?: boolean },\n): Promise<import(\"../credential/resolver.js\").ResolveCredentialsResult | null> {\n const info = await registry.getProviderInfo(mount.uri);\n const schema = info?.schema ?? null;\n const providerAuth = info?.auth;\n\n if (!schema) return null;\n\n mergeTemplateVarsIntoMount(mount, info?.manifest);\n\n const { getSensitiveFields } = await import(\"@aigne/afs/utils/schema\");\n const sensitiveFieldsInSchema = getSensitiveFields(schema);\n const schemaProps = (schema as any).properties ?? {};\n const hasEnvFields = Object.values(schemaProps).some((p: any) => Array.isArray(p?.env));\n if (sensitiveFieldsInSchema.length === 0 && !hasEnvFields && !providerAuth) return null;\n\n const { resolveCredentials } = await import(\"../credential/resolver.js\");\n\n const result = await resolveCredentials({\n mount,\n schema,\n authContext,\n credentialStore,\n providerAuth,\n forceCollect: opts?.forceCollect,\n });\n\n if (!result) {\n const fieldNames = Object.keys((schema as any).properties ?? {});\n const known = new Set<string>();\n if (mount.auth !== undefined) known.add(\"auth\");\n if (mount.token !== undefined) known.add(\"token\");\n if (mount.options) {\n for (const k of Object.keys(mount.options)) known.add(k);\n }\n const missing = fieldNames.filter((f) => !known.has(f));\n const fieldList = missing.length > 0 ? missing.join(\", \") : fieldNames.join(\", \");\n throw new Error(\n `Missing credentials: ${fieldList}. ` +\n `Retry with them as args, e.g. { \"uri\": \"${mount.uri}\", \"path\": \"${mount.path}\", ${missing.map((f) => `\"${f}\": \"...\"`).join(\", \")} }`,\n );\n }\n\n if (Object.keys(result.values).length > 0) {\n if (result.values.token !== undefined && mount.token === undefined) {\n mount.token = String(result.values.token);\n }\n if (result.values.auth !== undefined && mount.auth === undefined) {\n mount.auth = String(result.values.auth);\n }\n\n const mergedOpts = mount.options ?? {};\n for (const [key, value] of Object.entries(result.values)) {\n if (key !== \"token\" && key !== \"auth\" && mergedOpts[key] === undefined) {\n mergedOpts[key] = value;\n }\n }\n if (Object.keys(mergedOpts).length > 0) {\n mount.options = mergedOpts;\n }\n }\n\n rebuildURIFromTemplate(mount, info?.manifest);\n\n return result.collected ? result : null;\n}\n\n/**\n * Persist credential resolution result (sensitive → credentials.toml).\n */\nexport async function persistCredentialResult(\n mount: MountConfig,\n result: import(\"../credential/resolver.js\").ResolveCredentialsResult,\n): Promise<void> {\n if (Object.keys(result.sensitive).length > 0) {\n try {\n const { createCredentialStore } = await import(\"../credential/store.js\");\n const store = createCredentialStore();\n await store.set(mount.uri, result.sensitive);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount] credential persistence failed: ${msg}`);\n }\n }\n}\n\n/**\n * Load stored credentials and merge into mount options during startup.\n */\nexport async function mergeStoredCredentials(\n mounts: MountConfig[],\n _mountSources: Map<string, string>,\n store: CredentialStore,\n): Promise<void> {\n for (const mount of mounts) {\n try {\n const stored = await store.get(mount.uri);\n if (stored) {\n const opts = mount.options ?? {};\n for (const [key, value] of Object.entries(stored)) {\n if (key.startsWith(\"env:\")) {\n if (!opts.env) opts.env = {};\n (opts.env as Record<string, string>)[key.slice(4)] = value;\n } else if (opts[key] === undefined) {\n opts[key] = value;\n }\n }\n if (Object.keys(opts).length > 0) {\n mount.options = opts;\n }\n }\n } catch {\n // Credential lookup failure is non-fatal\n }\n }\n}\n\n// ─── Exported Credential Resolution for CLI mount add ───────────────────────\n\nexport interface ResolveCredentialsForMountOptions {\n cwd: string;\n uri: string;\n mountPath: string;\n authContext?: AuthContext;\n credentialStore?: CredentialStore;\n extraOptions?: Record<string, unknown>;\n sensitiveArgs?: string[];\n registry?: ProviderRegistry;\n forceCollect?: boolean;\n}\n\nexport interface ResolveCredentialsForMountResult {\n collected: boolean;\n nonSensitive: Record<string, unknown>;\n allValues: Record<string, unknown>;\n persistCredentials: () => Promise<void>;\n sensitiveFields: string[];\n configUri?: string;\n /** URI after template rebuild with resolved values (may differ from input URI) */\n resolvedUri?: string;\n}\n\n/**\n * Resolve and persist credentials for a mount configuration.\n *\n * Used by `mount add` CLI command to trigger credential collection\n * at add-time rather than deferring to AFS creation.\n */\nexport async function resolveCredentialsForMount(\n options: ResolveCredentialsForMountOptions,\n): Promise<ResolveCredentialsForMountResult | null> {\n const { uri, mountPath, authContext, credentialStore, extraOptions, sensitiveArgs } = options;\n\n const { cleanUri: configUri, envRecord } = extractEnvFromURI(uri);\n const hasExtractedEnv = Object.keys(envRecord).length > 0;\n\n const registry = options.registry ?? new ProviderRegistry();\n const info = await registry.getProviderInfo(uri);\n let schema = info?.schema ?? null;\n const providerAuth = info?.auth;\n\n if (!schema && extraOptions && Object.keys(extraOptions).length > 0) {\n const { buildAdHocSchema } = await import(\"@aigne/afs/utils/schema\");\n schema = buildAdHocSchema(extraOptions, sensitiveArgs ?? []);\n }\n\n const envOnlyResult = (): ResolveCredentialsForMountResult | null => {\n if (!hasExtractedEnv) return null;\n return {\n collected: false,\n nonSensitive: {},\n allValues: {},\n persistCredentials: async () => {\n const toStore: Record<string, string> = {};\n for (const [key, val] of Object.entries(envRecord)) {\n toStore[`env:${key}`] = val;\n }\n if (credentialStore) {\n try {\n await credentialStore.set(configUri, toStore);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount add] credential persistence failed: ${msg}`);\n }\n }\n },\n sensitiveFields: [],\n configUri,\n };\n };\n\n if (!schema) return envOnlyResult();\n\n const properties = (schema as any).properties;\n if (!properties || Object.keys(properties).length === 0) return envOnlyResult();\n\n if (sensitiveArgs && sensitiveArgs.length > 0) {\n const mergedProps = { ...properties };\n let changed = false;\n for (const field of sensitiveArgs) {\n if (mergedProps[field]) {\n mergedProps[field] = { ...mergedProps[field], sensitive: true };\n changed = true;\n } else if (extraOptions?.[field] !== undefined) {\n const value = extraOptions[field];\n mergedProps[field] = {\n type:\n typeof value === \"number\"\n ? \"number\"\n : typeof value === \"boolean\"\n ? \"boolean\"\n : \"string\",\n sensitive: true,\n };\n changed = true;\n }\n }\n if (changed) {\n schema = { ...schema, properties: mergedProps } as typeof schema;\n }\n }\n\n const { getSensitiveFields } = await import(\"@aigne/afs/utils/schema\");\n const sensitiveFieldsInSchema = getSensitiveFields(schema);\n const schemaProps = (schema as any).properties;\n const hasEnvFields = Object.values(schemaProps).some((p: any) => Array.isArray(p?.env));\n if (sensitiveFieldsInSchema.length === 0 && !hasEnvFields && !extraOptions)\n return envOnlyResult();\n\n const mount: MountConfig = { uri, path: mountPath };\n\n if (extraOptions && Object.keys(extraOptions).length > 0) {\n mount.options = { ...(mount.options ?? {}), ...extraOptions };\n }\n\n mergeTemplateVarsIntoMount(mount, info?.manifest);\n\n const { resolveCredentials } = await import(\"../credential/resolver.js\");\n\n const result = await resolveCredentials({\n mount,\n schema,\n authContext,\n credentialStore,\n providerAuth,\n forceCollect: options.forceCollect,\n });\n\n if (!result) {\n const fieldNames = Object.keys(properties);\n throw new Error(\n `Missing credentials: ${fieldNames.join(\", \")}. ` +\n `Retry with them as args, e.g. { \"uri\": \"${uri}\", \"path\": \"${mountPath}\", ${fieldNames.map((f) => `\"${f}\": \"...\"`).join(\", \")} }`,\n );\n }\n\n // Merge resolved values into mount.options so rebuildURIFromTemplate can\n // fill template variables (e.g. bot name collected via form → URI body).\n if (Object.keys(result.values).length > 0) {\n const opts = mount.options ?? {};\n for (const [key, value] of Object.entries(result.values)) {\n if (key === \"token\" || key === \"auth\") continue;\n // When user collected new values (forceCollect), allow overwriting existing\n if (result.collected || opts[key] === undefined) {\n opts[key] = value;\n }\n }\n if (Object.keys(opts).length > 0) {\n mount.options = opts;\n }\n }\n\n // Rebuild URI from template with all resolved values (may change mount.uri)\n rebuildURIFromTemplate(mount, info?.manifest);\n const resolvedUri = mount.uri !== uri ? mount.uri : undefined;\n\n // Credential store key: use rebuilt URI if available, otherwise cleaned original\n const storeKey = resolvedUri ?? configUri;\n\n const sensitiveFieldSet = new Set(sensitiveFieldsInSchema);\n\n const flatSensitive: Record<string, string> = {};\n for (const field of sensitiveFieldsInSchema) {\n const val = result.values[field];\n if (val === undefined) continue;\n if (field === \"env\" && typeof val === \"object\" && val !== null) {\n for (const [envKey, envVal] of Object.entries(val as Record<string, string>)) {\n flatSensitive[`env:${envKey}`] = String(envVal);\n }\n } else {\n flatSensitive[field] = String(val);\n }\n }\n\n const nonSensitive: Record<string, unknown> = result.collected\n ? result.nonSensitive\n : extraOptions\n ? Object.fromEntries(Object.entries(extraOptions).filter(([k]) => !sensitiveFieldSet.has(k)))\n : {};\n\n const persistCredentials = async () => {\n const toStore = { ...flatSensitive };\n for (const [key, val] of Object.entries(envRecord)) {\n toStore[`env:${key}`] = val;\n }\n if (Object.keys(toStore).length > 0 && credentialStore) {\n try {\n await credentialStore.set(storeKey, toStore);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[mount add] credential persistence failed: ${msg}`);\n }\n }\n };\n\n return {\n collected: result.collected,\n nonSensitive,\n allValues: result.values,\n persistCredentials,\n sensitiveFields: sensitiveFieldsInSchema,\n configUri: hasExtractedEnv ? configUri : undefined,\n resolvedUri,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,SAAgB,kBAAkB,KAGhC;CACA,MAAM,SAAS,SAAS,IAAI;CAC5B,MAAM,YAAoC,EAAE;AAG5C,KAAI,CAAC,OAAO,OAAO,WAAW,MAAM,CAClC,QAAO;EAAE,UAAU;EAAK;EAAW;CAGrC,MAAM,YAAY,OAAO,MAAM;AAC/B,KAAI,CAAC,UAAW,QAAO;EAAE,UAAU;EAAK;EAAW;CAGnD,MAAM,UAAU,MAAM,QAAQ,UAAU,GAAG,YAAY,CAAC,UAAU;AAClE,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,MAAI,QAAQ,EACV,WAAU,MAAM,MAAM,GAAG,MAAM,IAAI,MAAM,MAAM,QAAQ,EAAE;;CAK7D,MAAM,aAAa,IAAI,QAAQ,IAAI;AACnC,KAAI,aAAa,EAAG,QAAO;EAAE,UAAU;EAAK;EAAW;CAEvD,MAAM,WAAW,IAAI,MAAM,aAAa,EAAE;CAC1C,MAAM,SAAS,IAAI,gBAAgB,SAAS;AAC5C,QAAO,OAAO,MAAM;CACpB,MAAM,WAAW,OAAO,UAAU;CAClC,MAAM,OAAO,IAAI,MAAM,GAAG,WAAW;AAGrC,QAAO;EAAE,UAFQ,WAAW,GAAG,KAAK,GAAG,aAAa;EAEjC;EAAW;;;;;;AAOhC,SAAgB,2BACd,OACA,UACM;AACN,KAAI,CAAC,UAAU,YAAa;CAC5B,MAAM,EAAE,4BACE,gCAAgC;CAC1C,MAAM,SAAS,SAAS,MAAM,IAAI;CAClC,IAAI;AACJ,KAAI;AACF,iBAAe,cAAc,SAAS,aAAa,OAAO,KAAK;SACzD;AACN;;AAEF,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,aAAa,CACrD,KAAI,UAAU,QAAW;AACvB,MAAI,CAAC,MAAM,QAAS,OAAM,UAAU,EAAE;AACtC,MAAI,MAAM,QAAQ,SAAS,OACzB,OAAM,QAAQ,OAAO;;;;;;;AAU7B,SAAgB,uBACd,OACA,UACM;AACN,KAAI,CAAC,UAAU,YAAa;CAC5B,MAAM,EAAE,UAAU,uCACR,gCAAgC;CAC1C,MAAM,WAAW,yBAAyB,SAAS,YAAY;AAC/D,KAAI,SAAS,WAAW,EAAG;CAE3B,MAAM,SAAS,SAAS,MAAM,IAAI;CAClC,MAAM,EAAE,4BACE,gCAAgC;CAC1C,IAAI;AACJ,KAAI;AACF,iBAAe,cAAc,SAAS,aAAa,OAAO,KAAK;SACzD;AACN,iBAAe,EAAE;;CAGnB,MAAM,UAA8C,EAAE,GAAG,cAAc;AACvE,MAAK,MAAM,QAAQ,SACjB,KAAI,CAAC,QAAQ,SAAS,MAAM,UAAU,SAAS,KAC7C,SAAQ,QAAQ,OAAO,MAAM,QAAQ,MAAM;AAI/C,KAAI;EACF,MAAM,SAAS,SAAS,SAAS,aAAa,QAAQ;AACtD,MAAI,WAAW,MAAM,IACnB,OAAM,MAAM;SAER;;;;;;;;;;;AAgBV,eAAsB,2BACpB,OACA,aACA,iBACA,UACA,MAC8E;CAC9E,MAAM,OAAO,MAAM,SAAS,gBAAgB,MAAM,IAAI;CACtD,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,eAAe,MAAM;AAE3B,KAAI,CAAC,OAAQ,QAAO;AAEpB,4BAA2B,OAAO,MAAM,SAAS;CAEjD,MAAM,EAAE,uBAAuB,MAAM,OAAO;CAC5C,MAAM,0BAA0B,mBAAmB,OAAO;CAC1D,MAAM,cAAe,OAAe,cAAc,EAAE;CACpD,MAAM,eAAe,OAAO,OAAO,YAAY,CAAC,MAAM,MAAW,MAAM,QAAQ,GAAG,IAAI,CAAC;AACvF,KAAI,wBAAwB,WAAW,KAAK,CAAC,gBAAgB,CAAC,aAAc,QAAO;CAEnF,MAAM,EAAE,uBAAuB,MAAM,OAAO;CAE5C,MAAM,SAAS,MAAM,mBAAmB;EACtC;EACA;EACA;EACA;EACA;EACA,cAAc,MAAM;EACrB,CAAC;AAEF,KAAI,CAAC,QAAQ;EACX,MAAM,aAAa,OAAO,KAAM,OAAe,cAAc,EAAE,CAAC;EAChE,MAAM,wBAAQ,IAAI,KAAa;AAC/B,MAAI,MAAM,SAAS,OAAW,OAAM,IAAI,OAAO;AAC/C,MAAI,MAAM,UAAU,OAAW,OAAM,IAAI,QAAQ;AACjD,MAAI,MAAM,QACR,MAAK,MAAM,KAAK,OAAO,KAAK,MAAM,QAAQ,CAAE,OAAM,IAAI,EAAE;EAE1D,MAAM,UAAU,WAAW,QAAQ,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;EACvD,MAAM,YAAY,QAAQ,SAAS,IAAI,QAAQ,KAAK,KAAK,GAAG,WAAW,KAAK,KAAK;AACjF,QAAM,IAAI,MACR,wBAAwB,UAAU,4CACW,MAAM,IAAI,cAAc,MAAM,KAAK,KAAK,QAAQ,KAAK,MAAM,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,CAAC,IACrI;;AAGH,KAAI,OAAO,KAAK,OAAO,OAAO,CAAC,SAAS,GAAG;AACzC,MAAI,OAAO,OAAO,UAAU,UAAa,MAAM,UAAU,OACvD,OAAM,QAAQ,OAAO,OAAO,OAAO,MAAM;AAE3C,MAAI,OAAO,OAAO,SAAS,UAAa,MAAM,SAAS,OACrD,OAAM,OAAO,OAAO,OAAO,OAAO,KAAK;EAGzC,MAAM,aAAa,MAAM,WAAW,EAAE;AACtC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,OAAO,CACtD,KAAI,QAAQ,WAAW,QAAQ,UAAU,WAAW,SAAS,OAC3D,YAAW,OAAO;AAGtB,MAAI,OAAO,KAAK,WAAW,CAAC,SAAS,EACnC,OAAM,UAAU;;AAIpB,wBAAuB,OAAO,MAAM,SAAS;AAE7C,QAAO,OAAO,YAAY,SAAS;;;;;AAMrC,eAAsB,wBACpB,OACA,QACe;AACf,KAAI,OAAO,KAAK,OAAO,UAAU,CAAC,SAAS,EACzC,KAAI;EACF,MAAM,EAAE,0BAA0B,MAAM,OAAO;AAE/C,QADc,uBAAuB,CACzB,IAAI,MAAM,KAAK,OAAO,UAAU;UACrC,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,UAAQ,KAAK,0CAA0C,MAAM;;;;;;AAQnE,eAAsB,uBACpB,QACA,eACA,OACe;AACf,MAAK,MAAM,SAAS,OAClB,KAAI;EACF,MAAM,SAAS,MAAM,MAAM,IAAI,MAAM,IAAI;AACzC,MAAI,QAAQ;GACV,MAAM,OAAO,MAAM,WAAW,EAAE;AAChC,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,KAAI,IAAI,WAAW,OAAO,EAAE;AAC1B,QAAI,CAAC,KAAK,IAAK,MAAK,MAAM,EAAE;AAC5B,IAAC,KAAK,IAA+B,IAAI,MAAM,EAAE,IAAI;cAC5C,KAAK,SAAS,OACvB,MAAK,OAAO;AAGhB,OAAI,OAAO,KAAK,KAAK,CAAC,SAAS,EAC7B,OAAM,UAAU;;SAGd;;;;;;;;AAqCZ,eAAsB,2BACpB,SACkD;CAClD,MAAM,EAAE,KAAK,WAAW,aAAa,iBAAiB,cAAc,kBAAkB;CAEtF,MAAM,EAAE,UAAU,WAAW,cAAc,kBAAkB,IAAI;CACjE,MAAM,kBAAkB,OAAO,KAAK,UAAU,CAAC,SAAS;CAGxD,MAAM,OAAO,OADI,QAAQ,YAAY,IAAI,kBAAkB,EAC/B,gBAAgB,IAAI;CAChD,IAAI,SAAS,MAAM,UAAU;CAC7B,MAAM,eAAe,MAAM;AAE3B,KAAI,CAAC,UAAU,gBAAgB,OAAO,KAAK,aAAa,CAAC,SAAS,GAAG;EACnE,MAAM,EAAE,qBAAqB,MAAM,OAAO;AAC1C,WAAS,iBAAiB,cAAc,iBAAiB,EAAE,CAAC;;CAG9D,MAAM,sBAA+D;AACnE,MAAI,CAAC,gBAAiB,QAAO;AAC7B,SAAO;GACL,WAAW;GACX,cAAc,EAAE;GAChB,WAAW,EAAE;GACb,oBAAoB,YAAY;IAC9B,MAAM,UAAkC,EAAE;AAC1C,SAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,UAAU,CAChD,SAAQ,OAAO,SAAS;AAE1B,QAAI,gBACF,KAAI;AACF,WAAM,gBAAgB,IAAI,WAAW,QAAQ;aACtC,KAAK;KACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,aAAQ,KAAK,8CAA8C,MAAM;;;GAIvE,iBAAiB,EAAE;GACnB;GACD;;AAGH,KAAI,CAAC,OAAQ,QAAO,eAAe;CAEnC,MAAM,aAAc,OAAe;AACnC,KAAI,CAAC,cAAc,OAAO,KAAK,WAAW,CAAC,WAAW,EAAG,QAAO,eAAe;AAE/E,KAAI,iBAAiB,cAAc,SAAS,GAAG;EAC7C,MAAM,cAAc,EAAE,GAAG,YAAY;EACrC,IAAI,UAAU;AACd,OAAK,MAAM,SAAS,cAClB,KAAI,YAAY,QAAQ;AACtB,eAAY,SAAS;IAAE,GAAG,YAAY;IAAQ,WAAW;IAAM;AAC/D,aAAU;aACD,eAAe,WAAW,QAAW;GAC9C,MAAM,QAAQ,aAAa;AAC3B,eAAY,SAAS;IACnB,MACE,OAAO,UAAU,WACb,WACA,OAAO,UAAU,YACf,YACA;IACR,WAAW;IACZ;AACD,aAAU;;AAGd,MAAI,QACF,UAAS;GAAE,GAAG;GAAQ,YAAY;GAAa;;CAInD,MAAM,EAAE,uBAAuB,MAAM,OAAO;CAC5C,MAAM,0BAA0B,mBAAmB,OAAO;CAC1D,MAAM,cAAe,OAAe;CACpC,MAAM,eAAe,OAAO,OAAO,YAAY,CAAC,MAAM,MAAW,MAAM,QAAQ,GAAG,IAAI,CAAC;AACvF,KAAI,wBAAwB,WAAW,KAAK,CAAC,gBAAgB,CAAC,aAC5D,QAAO,eAAe;CAExB,MAAM,QAAqB;EAAE;EAAK,MAAM;EAAW;AAEnD,KAAI,gBAAgB,OAAO,KAAK,aAAa,CAAC,SAAS,EACrD,OAAM,UAAU;EAAE,GAAI,MAAM,WAAW,EAAE;EAAG,GAAG;EAAc;AAG/D,4BAA2B,OAAO,MAAM,SAAS;CAEjD,MAAM,EAAE,uBAAuB,MAAM,OAAO;CAE5C,MAAM,SAAS,MAAM,mBAAmB;EACtC;EACA;EACA;EACA;EACA;EACA,cAAc,QAAQ;EACvB,CAAC;AAEF,KAAI,CAAC,QAAQ;EACX,MAAM,aAAa,OAAO,KAAK,WAAW;AAC1C,QAAM,IAAI,MACR,wBAAwB,WAAW,KAAK,KAAK,CAAC,4CACD,IAAI,cAAc,UAAU,KAAK,WAAW,KAAK,MAAM,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,CAAC,IACjI;;AAKH,KAAI,OAAO,KAAK,OAAO,OAAO,CAAC,SAAS,GAAG;EACzC,MAAM,OAAO,MAAM,WAAW,EAAE;AAChC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,OAAO,EAAE;AACxD,OAAI,QAAQ,WAAW,QAAQ,OAAQ;AAEvC,OAAI,OAAO,aAAa,KAAK,SAAS,OACpC,MAAK,OAAO;;AAGhB,MAAI,OAAO,KAAK,KAAK,CAAC,SAAS,EAC7B,OAAM,UAAU;;AAKpB,wBAAuB,OAAO,MAAM,SAAS;CAC7C,MAAM,cAAc,MAAM,QAAQ,MAAM,MAAM,MAAM;CAGpD,MAAM,WAAW,eAAe;CAEhC,MAAM,oBAAoB,IAAI,IAAI,wBAAwB;CAE1D,MAAM,gBAAwC,EAAE;AAChD,MAAK,MAAM,SAAS,yBAAyB;EAC3C,MAAM,MAAM,OAAO,OAAO;AAC1B,MAAI,QAAQ,OAAW;AACvB,MAAI,UAAU,SAAS,OAAO,QAAQ,YAAY,QAAQ,KACxD,MAAK,MAAM,CAAC,QAAQ,WAAW,OAAO,QAAQ,IAA8B,CAC1E,eAAc,OAAO,YAAY,OAAO,OAAO;MAGjD,eAAc,SAAS,OAAO,IAAI;;CAItC,MAAM,eAAwC,OAAO,YACjD,OAAO,eACP,eACE,OAAO,YAAY,OAAO,QAAQ,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,GAC3F,EAAE;CAER,MAAM,qBAAqB,YAAY;EACrC,MAAM,UAAU,EAAE,GAAG,eAAe;AACpC,OAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,UAAU,CAChD,SAAQ,OAAO,SAAS;AAE1B,MAAI,OAAO,KAAK,QAAQ,CAAC,SAAS,KAAK,gBACrC,KAAI;AACF,SAAM,gBAAgB,IAAI,UAAU,QAAQ;WACrC,KAAK;GACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,WAAQ,KAAK,8CAA8C,MAAM;;;AAKvE,QAAO;EACL,WAAW,OAAO;EAClB;EACA,WAAW,OAAO;EAClB;EACA,iBAAiB;EACjB,WAAW,kBAAkB,YAAY;EACzC;EACD"}
@@ -268,9 +268,183 @@ async function removeProgram(programId, options) {
268
268
  purgedData
269
269
  };
270
270
  }
271
+ /**
272
+ * Read per-program mount overrides from mounts.toml.
273
+ *
274
+ * Location: ~/.afs-config/data/{programId}/mounts.toml
275
+ *
276
+ * Returns [] if the file is missing or invalid.
277
+ */
278
+ async function readProgramMountOverrides(programId, options) {
279
+ const mountsPath = (0, node_path.join)(getUserConfigDir(options?.userConfigDir), "data", programId, "mounts.toml");
280
+ let content;
281
+ try {
282
+ content = await (0, node_fs_promises.readFile)(mountsPath, "utf-8");
283
+ } catch {
284
+ return [];
285
+ }
286
+ try {
287
+ const { parse } = await import("smol-toml");
288
+ const mounts = parse(content).mounts;
289
+ if (!Array.isArray(mounts)) return [];
290
+ const result = [];
291
+ for (const entry of mounts) {
292
+ if (typeof entry !== "object" || entry === null) continue;
293
+ const { path, uri, options: options$1 } = entry;
294
+ if (typeof path !== "string" || typeof uri !== "string") continue;
295
+ const override = {
296
+ target: path,
297
+ uri
298
+ };
299
+ if (options$1 && typeof options$1 === "object" && !Array.isArray(options$1)) {
300
+ const opts = options$1;
301
+ if (Object.keys(opts).length > 0) override.options = opts;
302
+ }
303
+ result.push(override);
304
+ }
305
+ return result;
306
+ } catch {
307
+ return [];
308
+ }
309
+ }
310
+ /**
311
+ * Interactive configure flow for a program's mount dependencies.
312
+ *
313
+ * Uses the standard credential resolution flow (browser form / OAuth) for each
314
+ * mount. Mounts with already-stored credentials are skipped silently.
315
+ * After resolution, URI templates are rebuilt with collected values and
316
+ * credentials are persisted under the final URI.
317
+ */
318
+ async function configureProgramMounts(programId, options) {
319
+ const userConfigDir = getUserConfigDir(options?.userConfigDir);
320
+ const cwd = options?.cwd ?? process.cwd();
321
+ const manifest = await validateProgramDir((0, node_path.join)(userConfigDir, "programs", programId));
322
+ const existingOverrides = await readProgramMountOverrides(programId, { userConfigDir });
323
+ const existingByTarget = new Map(existingOverrides.map((o) => [o.target, o]));
324
+ const { ProviderRegistry } = await import("@aigne/afs");
325
+ const { resolveCredentialsForMount } = await Promise.resolve().then(() => require("./credential-helpers.cjs"));
326
+ const { createCLIAuthContext } = await Promise.resolve().then(() => require("../credential/cli-auth-context.cjs"));
327
+ const { createCredentialStore } = await Promise.resolve().then(() => require("../credential/store.cjs"));
328
+ const registry = new ProviderRegistry();
329
+ const authContext = createCLIAuthContext();
330
+ const credentialStore = createCredentialStore();
331
+ const configuredMounts = [];
332
+ const { getTemplateVariableNames } = await import("@aigne/afs/utils/uri-template");
333
+ for (const mountDecl of manifest.mounts) {
334
+ const existing = existingByTarget.get(mountDecl.target);
335
+ const startUri = existing?.uri || mountDecl.uri;
336
+ try {
337
+ const forceThis = options?.force === true || options?.force === "" || options?.force === mountDecl.target;
338
+ const result = await resolveCredentialsForMount({
339
+ cwd,
340
+ uri: startUri,
341
+ mountPath: mountDecl.target,
342
+ authContext,
343
+ credentialStore,
344
+ registry,
345
+ forceCollect: forceThis || void 0,
346
+ extraOptions: existing?.options
347
+ });
348
+ if (result) {
349
+ await result.persistCredentials();
350
+ const finalUri = result.resolvedUri || result.configUri || startUri;
351
+ const info = await registry.getProviderInfo(startUri);
352
+ const templateVars = new Set(info?.manifest?.uriTemplate ? getTemplateVariableNames(info.manifest.uriTemplate) : []);
353
+ const filteredOptions = {};
354
+ if (existing?.options) {
355
+ for (const [k, v] of Object.entries(existing.options)) if (!templateVars.has(k)) filteredOptions[k] = v;
356
+ }
357
+ if (result.collected) {
358
+ for (const [k, v] of Object.entries(result.nonSensitive)) if (!templateVars.has(k)) filteredOptions[k] = v;
359
+ }
360
+ const options$1 = Object.keys(filteredOptions).length > 0 ? filteredOptions : void 0;
361
+ configuredMounts.push({
362
+ target: mountDecl.target,
363
+ uri: finalUri,
364
+ options: options$1
365
+ });
366
+ } else configuredMounts.push({
367
+ target: mountDecl.target,
368
+ uri: startUri,
369
+ options: existing?.options
370
+ });
371
+ } catch (err) {
372
+ const msg = err instanceof Error ? err.message : String(err);
373
+ console.warn(` Skipping ${mountDecl.target}: ${msg}`);
374
+ configuredMounts.push({
375
+ target: mountDecl.target,
376
+ uri: startUri,
377
+ options: existing?.options
378
+ });
379
+ }
380
+ }
381
+ const changedMounts = configuredMounts.filter((m) => {
382
+ const decl = manifest.mounts.find((d) => d.target === m.target);
383
+ return decl && (m.uri !== decl.uri || m.options && Object.keys(m.options).length > 0);
384
+ });
385
+ const dataDir = (0, node_path.join)(userConfigDir, "data", programId);
386
+ const mountsTomlPath = (0, node_path.join)(dataDir, "mounts.toml");
387
+ if (changedMounts.length > 0) {
388
+ await (0, node_fs_promises.mkdir)(dataDir, { recursive: true });
389
+ const { stringify } = await import("smol-toml");
390
+ await (0, node_fs_promises.writeFile)(mountsTomlPath, stringify({ mounts: changedMounts.map((m) => ({
391
+ path: m.target,
392
+ uri: m.uri,
393
+ ...m.options && Object.keys(m.options).length > 0 ? { options: m.options } : {}
394
+ })) }), "utf-8");
395
+ } else try {
396
+ await (0, node_fs_promises.rm)(mountsTomlPath);
397
+ } catch {}
398
+ try {
399
+ const { notifyDaemonReload } = await Promise.resolve().then(() => require("../program/daemon-integration.cjs"));
400
+ const { getDaemonStatus } = await Promise.resolve().then(() => require("../daemon/manager.cjs"));
401
+ await notifyDaemonReload({ getDaemonStatus });
402
+ } catch {}
403
+ return {
404
+ success: true,
405
+ programId,
406
+ configuredMounts: changedMounts
407
+ };
408
+ }
409
+ /**
410
+ * List configurable mounts and their current status for a program.
411
+ */
412
+ async function listProgramMountStatus(programId, options) {
413
+ const userConfigDir = getUserConfigDir(options?.userConfigDir);
414
+ const manifest = await validateProgramDir((0, node_path.join)(userConfigDir, "programs", programId));
415
+ const overrides = await readProgramMountOverrides(programId, { userConfigDir });
416
+ const overrideByTarget = new Map(overrides.map((o) => [o.target, o]));
417
+ const { createCredentialStore } = await Promise.resolve().then(() => require("../credential/store.cjs"));
418
+ const credentialStore = createCredentialStore();
419
+ const mounts = [];
420
+ for (const mountDecl of manifest.mounts) {
421
+ const override = overrideByTarget.get(mountDecl.target);
422
+ const effectiveUri = override?.uri || mountDecl.uri;
423
+ let hasCredentials = false;
424
+ try {
425
+ const stored = await credentialStore.get(effectiveUri);
426
+ hasCredentials = !!stored && Object.keys(stored).length > 0;
427
+ } catch {}
428
+ mounts.push({
429
+ path: mountDecl.target,
430
+ uri: mountDecl.uri,
431
+ configuredUri: override?.uri !== mountDecl.uri ? override?.uri : void 0,
432
+ required: mountDecl.required,
433
+ hasCredentials,
434
+ hasOptions: !!(override?.options && Object.keys(override.options).length > 0)
435
+ });
436
+ }
437
+ return {
438
+ programId,
439
+ mounts
440
+ };
441
+ }
271
442
 
272
443
  //#endregion
444
+ exports.configureProgramMounts = configureProgramMounts;
273
445
  exports.getUserConfigDir = getUserConfigDir;
274
446
  exports.installProgram = installProgram;
275
447
  exports.listInstalledPrograms = listInstalledPrograms;
448
+ exports.listProgramMountStatus = listProgramMountStatus;
449
+ exports.readProgramMountOverrides = readProgramMountOverrides;
276
450
  exports.removeProgram = removeProgram;