@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
@@ -1,7 +1,7 @@
1
1
  import { AFS_USER_CONFIG_DIR_ENV, CONFIG_DIR_NAME } from "./loader.mjs";
2
2
  import { persistMount, unpersistMount } from "./mount-commands.mjs";
3
3
  import { execSync } from "node:child_process";
4
- import { cp, mkdir, readFile, readdir, rename, rm } from "node:fs/promises";
4
+ import { cp, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
5
5
  import { homedir, tmpdir } from "node:os";
6
6
  import { isAbsolute, join, resolve } from "node:path";
7
7
  import { parseProgramManifest } from "@aigne/afs";
@@ -267,7 +267,178 @@ async function removeProgram(programId, options) {
267
267
  purgedData
268
268
  };
269
269
  }
270
+ /**
271
+ * Read per-program mount overrides from mounts.toml.
272
+ *
273
+ * Location: ~/.afs-config/data/{programId}/mounts.toml
274
+ *
275
+ * Returns [] if the file is missing or invalid.
276
+ */
277
+ async function readProgramMountOverrides(programId, options) {
278
+ const mountsPath = join(getUserConfigDir(options?.userConfigDir), "data", programId, "mounts.toml");
279
+ let content;
280
+ try {
281
+ content = await readFile(mountsPath, "utf-8");
282
+ } catch {
283
+ return [];
284
+ }
285
+ try {
286
+ const { parse } = await import("smol-toml");
287
+ const mounts = parse(content).mounts;
288
+ if (!Array.isArray(mounts)) return [];
289
+ const result = [];
290
+ for (const entry of mounts) {
291
+ if (typeof entry !== "object" || entry === null) continue;
292
+ const { path, uri, options: options$1 } = entry;
293
+ if (typeof path !== "string" || typeof uri !== "string") continue;
294
+ const override = {
295
+ target: path,
296
+ uri
297
+ };
298
+ if (options$1 && typeof options$1 === "object" && !Array.isArray(options$1)) {
299
+ const opts = options$1;
300
+ if (Object.keys(opts).length > 0) override.options = opts;
301
+ }
302
+ result.push(override);
303
+ }
304
+ return result;
305
+ } catch {
306
+ return [];
307
+ }
308
+ }
309
+ /**
310
+ * Interactive configure flow for a program's mount dependencies.
311
+ *
312
+ * Uses the standard credential resolution flow (browser form / OAuth) for each
313
+ * mount. Mounts with already-stored credentials are skipped silently.
314
+ * After resolution, URI templates are rebuilt with collected values and
315
+ * credentials are persisted under the final URI.
316
+ */
317
+ async function configureProgramMounts(programId, options) {
318
+ const userConfigDir = getUserConfigDir(options?.userConfigDir);
319
+ const cwd = options?.cwd ?? process.cwd();
320
+ const manifest = await validateProgramDir(join(userConfigDir, "programs", programId));
321
+ const existingOverrides = await readProgramMountOverrides(programId, { userConfigDir });
322
+ const existingByTarget = new Map(existingOverrides.map((o) => [o.target, o]));
323
+ const { ProviderRegistry: ProviderRegistry$1 } = await import("@aigne/afs");
324
+ const { resolveCredentialsForMount } = await import("./credential-helpers.mjs");
325
+ const { createCLIAuthContext } = await import("../credential/cli-auth-context.mjs");
326
+ const { createCredentialStore } = await import("../credential/store.mjs");
327
+ const registry = new ProviderRegistry$1();
328
+ const authContext = createCLIAuthContext();
329
+ const credentialStore = createCredentialStore();
330
+ const configuredMounts = [];
331
+ const { getTemplateVariableNames } = await import("@aigne/afs/utils/uri-template");
332
+ for (const mountDecl of manifest.mounts) {
333
+ const existing = existingByTarget.get(mountDecl.target);
334
+ const startUri = existing?.uri || mountDecl.uri;
335
+ try {
336
+ const forceThis = options?.force === true || options?.force === "" || options?.force === mountDecl.target;
337
+ const result = await resolveCredentialsForMount({
338
+ cwd,
339
+ uri: startUri,
340
+ mountPath: mountDecl.target,
341
+ authContext,
342
+ credentialStore,
343
+ registry,
344
+ forceCollect: forceThis || void 0,
345
+ extraOptions: existing?.options
346
+ });
347
+ if (result) {
348
+ await result.persistCredentials();
349
+ const finalUri = result.resolvedUri || result.configUri || startUri;
350
+ const info = await registry.getProviderInfo(startUri);
351
+ const templateVars = new Set(info?.manifest?.uriTemplate ? getTemplateVariableNames(info.manifest.uriTemplate) : []);
352
+ const filteredOptions = {};
353
+ if (existing?.options) {
354
+ for (const [k, v] of Object.entries(existing.options)) if (!templateVars.has(k)) filteredOptions[k] = v;
355
+ }
356
+ if (result.collected) {
357
+ for (const [k, v] of Object.entries(result.nonSensitive)) if (!templateVars.has(k)) filteredOptions[k] = v;
358
+ }
359
+ const options$1 = Object.keys(filteredOptions).length > 0 ? filteredOptions : void 0;
360
+ configuredMounts.push({
361
+ target: mountDecl.target,
362
+ uri: finalUri,
363
+ options: options$1
364
+ });
365
+ } else configuredMounts.push({
366
+ target: mountDecl.target,
367
+ uri: startUri,
368
+ options: existing?.options
369
+ });
370
+ } catch (err) {
371
+ const msg = err instanceof Error ? err.message : String(err);
372
+ console.warn(` Skipping ${mountDecl.target}: ${msg}`);
373
+ configuredMounts.push({
374
+ target: mountDecl.target,
375
+ uri: startUri,
376
+ options: existing?.options
377
+ });
378
+ }
379
+ }
380
+ const changedMounts = configuredMounts.filter((m) => {
381
+ const decl = manifest.mounts.find((d) => d.target === m.target);
382
+ return decl && (m.uri !== decl.uri || m.options && Object.keys(m.options).length > 0);
383
+ });
384
+ const dataDir = join(userConfigDir, "data", programId);
385
+ const mountsTomlPath = join(dataDir, "mounts.toml");
386
+ if (changedMounts.length > 0) {
387
+ await mkdir(dataDir, { recursive: true });
388
+ const { stringify } = await import("smol-toml");
389
+ await writeFile(mountsTomlPath, stringify({ mounts: changedMounts.map((m) => ({
390
+ path: m.target,
391
+ uri: m.uri,
392
+ ...m.options && Object.keys(m.options).length > 0 ? { options: m.options } : {}
393
+ })) }), "utf-8");
394
+ } else try {
395
+ await rm(mountsTomlPath);
396
+ } catch {}
397
+ try {
398
+ const { notifyDaemonReload } = await import("../program/daemon-integration.mjs");
399
+ const { getDaemonStatus } = await import("../daemon/manager.mjs");
400
+ await notifyDaemonReload({ getDaemonStatus });
401
+ } catch {}
402
+ return {
403
+ success: true,
404
+ programId,
405
+ configuredMounts: changedMounts
406
+ };
407
+ }
408
+ /**
409
+ * List configurable mounts and their current status for a program.
410
+ */
411
+ async function listProgramMountStatus(programId, options) {
412
+ const userConfigDir = getUserConfigDir(options?.userConfigDir);
413
+ const manifest = await validateProgramDir(join(userConfigDir, "programs", programId));
414
+ const overrides = await readProgramMountOverrides(programId, { userConfigDir });
415
+ const overrideByTarget = new Map(overrides.map((o) => [o.target, o]));
416
+ const { createCredentialStore } = await import("../credential/store.mjs");
417
+ const credentialStore = createCredentialStore();
418
+ const mounts = [];
419
+ for (const mountDecl of manifest.mounts) {
420
+ const override = overrideByTarget.get(mountDecl.target);
421
+ const effectiveUri = override?.uri || mountDecl.uri;
422
+ let hasCredentials = false;
423
+ try {
424
+ const stored = await credentialStore.get(effectiveUri);
425
+ hasCredentials = !!stored && Object.keys(stored).length > 0;
426
+ } catch {}
427
+ mounts.push({
428
+ path: mountDecl.target,
429
+ uri: mountDecl.uri,
430
+ configuredUri: override?.uri !== mountDecl.uri ? override?.uri : void 0,
431
+ required: mountDecl.required,
432
+ hasCredentials,
433
+ hasOptions: !!(override?.options && Object.keys(override.options).length > 0)
434
+ });
435
+ }
436
+ return {
437
+ programId,
438
+ mounts
439
+ };
440
+ }
270
441
 
271
442
  //#endregion
272
- export { getUserConfigDir, installProgram, listInstalledPrograms, removeProgram };
443
+ export { configureProgramMounts, getUserConfigDir, installProgram, listInstalledPrograms, listProgramMountStatus, readProgramMountOverrides, removeProgram };
273
444
  //# sourceMappingURL=program-install.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"program-install.mjs","names":[],"sources":["../../src/config/program-install.ts"],"sourcesContent":["/**\n * Program Installation\n *\n * Business logic for installing, listing, and removing AFS programs.\n * Programs are installed to ~/.afs-config/programs/<id>/ and mounted\n * at /programs/<id> in the CWD config (same as `mount add`).\n */\n\nimport { execSync } from \"node:child_process\";\nimport { cp, mkdir, readdir, readFile, rename, rm } from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { type ProgramManifest, parseProgramManifest } from \"@aigne/afs\";\nimport { AFS_USER_CONFIG_DIR_ENV, CONFIG_DIR_NAME } from \"./loader.js\";\nimport { persistMount, unpersistMount } from \"./mount-commands.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport type SourceType = \"local\" | \"github\" | \"zip\";\n\nexport interface InstallResult {\n success: boolean;\n programId: string;\n programName: string;\n installPath: string;\n mountPath: string;\n}\n\nexport interface InstalledProgram {\n id: string;\n name: string;\n entrypoint: string;\n installPath: string;\n mountPath: string;\n}\n\nexport interface RemoveResult {\n success: boolean;\n programId: string;\n purgedData: boolean;\n}\n\nexport interface InstallOptions {\n /** Override user config dir (for testing) */\n userConfigDir?: string;\n /** CWD for persistMount (for testing) */\n cwd?: string;\n}\n\nexport interface RemoveOptions extends InstallOptions {\n /** Also remove program data directory */\n purge?: boolean;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Get the user config directory.\n */\nexport function getUserConfigDir(override?: string): string {\n return override ?? process.env[AFS_USER_CONFIG_DIR_ENV] ?? join(homedir(), CONFIG_DIR_NAME);\n}\n\n/**\n * Detect the source type from a source string.\n */\nexport function detectSourceType(source: string): SourceType {\n // ZIP file\n if (source.endsWith(\".zip\")) {\n return \"zip\";\n }\n\n // GitHub URL patterns\n if (\n source.startsWith(\"https://github.com/\") ||\n source.startsWith(\"http://github.com/\") ||\n source.startsWith(\"github.com/\")\n ) {\n return \"github\";\n }\n\n // Default: local directory\n return \"local\";\n}\n\n/**\n * Parse a GitHub URL into owner, repo, ref, and optional subdirectory.\n */\nexport function parseGitHubURL(url: string): {\n cloneUrl: string;\n subdir?: string;\n} {\n // Normalize: ensure https://\n let normalized = url;\n if (normalized.startsWith(\"github.com/\")) {\n normalized = `https://${normalized}`;\n }\n\n // Parse: https://github.com/owner/repo[/tree/ref/subdir]\n const match = normalized.match(\n /^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+?)(?:\\.git)?(?:\\/tree\\/([^/]+)\\/(.+))?$/,\n );\n if (!match) {\n // Simple case: just owner/repo\n return { cloneUrl: normalized.replace(/\\/+$/, \"\") };\n }\n\n const [, owner, repo, _ref, subdir] = match;\n const cloneUrl = `https://github.com/${owner}/${repo}.git`;\n\n return {\n cloneUrl,\n subdir: subdir || undefined,\n };\n}\n\n/**\n * Resolve a source to a local directory path containing program.yaml.\n * For github/zip sources, downloads/extracts to a temp directory.\n *\n * Returns [resolvedPath, tempDir?] — tempDir is set if caller should clean up.\n */\nexport async function resolveSource(\n source: string,\n type: SourceType,\n cwd: string,\n): Promise<[string, string | undefined]> {\n switch (type) {\n case \"local\": {\n const dirPath = isAbsolute(source) ? source : resolve(cwd, source);\n // Verify it's a directory by trying to read it\n try {\n await readdir(dirPath);\n } catch {\n throw new Error(`Source directory does not exist: ${dirPath}`);\n }\n return [dirPath, undefined];\n }\n\n case \"github\": {\n const { cloneUrl, subdir } = parseGitHubURL(source);\n const tempDir = join(tmpdir(), `afs-install-${Date.now()}`);\n await mkdir(tempDir, { recursive: true });\n\n try {\n execSync(`git clone --depth 1 ${cloneUrl} ${join(tempDir, \"repo\")}`, {\n stdio: \"pipe\",\n timeout: 60000,\n });\n } catch (err) {\n await rm(tempDir, { recursive: true, force: true });\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to clone GitHub repository: ${msg}`);\n }\n\n const resolvedPath = subdir ? join(tempDir, \"repo\", subdir) : join(tempDir, \"repo\");\n return [resolvedPath, tempDir];\n }\n\n case \"zip\": {\n const zipPath = isAbsolute(source) ? source : resolve(cwd, source);\n const tempDir = join(tmpdir(), `afs-install-${Date.now()}`);\n const extractDir = join(tempDir, \"extracted\");\n await mkdir(extractDir, { recursive: true });\n\n // Security: check zip contents for path traversal before extracting\n try {\n const listing = execSync(`unzip -l \"${zipPath}\"`, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n // Each line after header has format: \" length date time name\"\n const lines = listing.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip header/footer lines\n if (\n !trimmed ||\n trimmed.startsWith(\"Archive:\") ||\n trimmed.startsWith(\"Length\") ||\n trimmed.startsWith(\"---\")\n )\n continue;\n // Extract filename (last column)\n const parts = trimmed.split(/\\s+/);\n if (parts.length >= 4) {\n const name = parts.slice(3).join(\" \");\n if (name.includes(\"..\") || name.startsWith(\"/\")) {\n await rm(tempDir, { recursive: true, force: true });\n throw new Error(`Zip file contains unsafe path: ${name}. Refusing to extract.`);\n }\n }\n }\n } catch (err) {\n if (err instanceof Error && err.message.includes(\"unsafe path\")) {\n throw err;\n }\n await rm(tempDir, { recursive: true, force: true });\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read zip file: ${msg}`);\n }\n\n // Extract\n try {\n execSync(`unzip -o \"${zipPath}\" -d \"${extractDir}\"`, {\n stdio: \"pipe\",\n timeout: 30000,\n });\n } catch (err) {\n await rm(tempDir, { recursive: true, force: true });\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to extract zip file: ${msg}`);\n }\n\n // Find program.yaml: might be at root or in a single subdirectory\n const entries = await readdir(extractDir);\n // Check if program.yaml is at extract root\n if (entries.includes(\"program.yaml\")) {\n return [extractDir, tempDir];\n }\n // Check single subdirectory\n if (entries.length === 1) {\n const subPath = join(extractDir, entries[0]!);\n try {\n const subEntries = await readdir(subPath);\n if (subEntries.includes(\"program.yaml\")) {\n return [subPath, tempDir];\n }\n } catch {\n // not a directory\n }\n }\n\n await rm(tempDir, { recursive: true, force: true });\n throw new Error(\"No program.yaml found in zip archive\");\n }\n\n default:\n throw new Error(`Unsupported source type: ${type}`);\n }\n}\n\n/**\n * Validate a directory contains a valid program.yaml.\n * Returns the parsed manifest.\n */\nexport async function validateProgramDir(dirPath: string): Promise<ProgramManifest> {\n const yamlPath = join(dirPath, \"program.yaml\");\n let content: string;\n try {\n content = await readFile(yamlPath, \"utf-8\");\n } catch {\n throw new Error(`No program.yaml found in ${dirPath}`);\n }\n\n return parseProgramManifest(content);\n}\n\n// ─── Main Functions ───────────────────────────────────────────────────────\n\n/**\n * Install a program from a source (local dir, GitHub URL, or zip file).\n */\nexport async function installProgram(\n source: string,\n options?: InstallOptions,\n): Promise<InstallResult> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const cwd = options?.cwd ?? process.cwd();\n const type = detectSourceType(source);\n\n // 1. Resolve source to local directory\n const [resolvedPath, tempDir] = await resolveSource(source, type, cwd);\n\n try {\n // 2. Validate program.yaml\n const manifest = await validateProgramDir(resolvedPath);\n\n // 3. Determine install target\n const programsDir = join(userConfigDir, \"programs\");\n const installPath = join(programsDir, manifest.id);\n const mountPath = `/programs/${manifest.id}`;\n\n // 4. Atomic install: copy to temp location, swap\n await mkdir(programsDir, { recursive: true });\n const tempInstallPath = `${installPath}.installing`;\n\n // Clean up any previous failed install\n await rm(tempInstallPath, { recursive: true, force: true });\n\n // Copy program files\n await cp(resolvedPath, tempInstallPath, { recursive: true });\n\n // Remove old version if exists, then rename\n await rm(installPath, { recursive: true, force: true });\n await rename(tempInstallPath, installPath);\n\n // 5. Ensure data directory exists\n const dataDir = join(userConfigDir, \"data\", manifest.id);\n await mkdir(dataDir, { recursive: true });\n\n // 6. Persist mount to CWD config (same as mount add)\n await persistMount(cwd, {\n path: mountPath,\n uri: `fs://${installPath}`,\n });\n\n // 7. Notify daemon to reload programs (best-effort)\n const { notifyDaemonReload } = await import(\"../program/daemon-integration.js\");\n const { getDaemonStatus } = await import(\"../daemon/manager.js\");\n await notifyDaemonReload({ getDaemonStatus });\n\n return {\n success: true,\n programId: manifest.id,\n programName: manifest.name,\n installPath,\n mountPath,\n };\n } finally {\n // 8. Cleanup temp dir\n if (tempDir) {\n await rm(tempDir, { recursive: true, force: true });\n }\n }\n}\n\n/**\n * List installed programs by scanning ~/.afs-config/programs/.\n */\nexport async function listInstalledPrograms(options?: InstallOptions): Promise<InstalledProgram[]> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const programsDir = join(userConfigDir, \"programs\");\n\n let entries: string[];\n try {\n entries = await readdir(programsDir);\n } catch {\n return []; // programs/ directory doesn't exist yet\n }\n\n const programs: InstalledProgram[] = [];\n\n for (const entry of entries) {\n const dirPath = join(programsDir, entry);\n try {\n const manifest = await validateProgramDir(dirPath);\n programs.push({\n id: manifest.id,\n name: manifest.name,\n entrypoint: manifest.entrypoint,\n installPath: dirPath,\n mountPath: `/programs/${manifest.id}`,\n });\n } catch {\n // Skip directories without valid program.yaml\n }\n }\n\n return programs;\n}\n\n/**\n * Remove an installed program.\n */\nexport async function removeProgram(\n programId: string,\n options?: RemoveOptions,\n): Promise<RemoveResult> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const cwd = options?.cwd ?? process.cwd();\n const installPath = join(userConfigDir, \"programs\", programId);\n\n // Verify program exists\n try {\n await readdir(installPath);\n } catch {\n throw new Error(`Program \"${programId}\" is not installed`);\n }\n\n // Remove program directory\n await rm(installPath, { recursive: true, force: true });\n\n // Remove mount from config (searches cwd → project → user)\n await unpersistMount(cwd, `/programs/${programId}`);\n\n // Optionally purge data\n let purgedData = false;\n if (options?.purge) {\n const dataDir = join(userConfigDir, \"data\", programId);\n await rm(dataDir, { recursive: true, force: true });\n purgedData = true;\n }\n\n // Notify daemon to reload programs (best-effort)\n const { notifyDaemonReload } = await import(\"../program/daemon-integration.js\");\n const { getDaemonStatus } = await import(\"../daemon/manager.js\");\n await notifyDaemonReload({ getDaemonStatus });\n\n return {\n success: true,\n programId,\n purgedData,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"program-install.mjs","names":["options","ProviderRegistry"],"sources":["../../src/config/program-install.ts"],"sourcesContent":["/**\n * Program Installation\n *\n * Business logic for installing, listing, and removing AFS programs.\n * Programs are installed to ~/.afs-config/programs/<id>/ and mounted\n * at /programs/<id> in the CWD config (same as `mount add`).\n */\n\nimport { execSync } from \"node:child_process\";\nimport { cp, mkdir, readdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { type MountOverride, type ProgramManifest, parseProgramManifest } from \"@aigne/afs\";\nimport { AFS_USER_CONFIG_DIR_ENV, CONFIG_DIR_NAME } from \"./loader.js\";\nimport { persistMount, unpersistMount } from \"./mount-commands.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────\n\nexport type SourceType = \"local\" | \"github\" | \"zip\";\n\nexport interface InstallResult {\n success: boolean;\n programId: string;\n programName: string;\n installPath: string;\n mountPath: string;\n}\n\nexport interface InstalledProgram {\n id: string;\n name: string;\n entrypoint: string;\n installPath: string;\n mountPath: string;\n}\n\nexport interface RemoveResult {\n success: boolean;\n programId: string;\n purgedData: boolean;\n}\n\nexport interface InstallOptions {\n /** Override user config dir (for testing) */\n userConfigDir?: string;\n /** CWD for persistMount (for testing) */\n cwd?: string;\n}\n\nexport interface RemoveOptions extends InstallOptions {\n /** Also remove program data directory */\n purge?: boolean;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Get the user config directory.\n */\nexport function getUserConfigDir(override?: string): string {\n return override ?? process.env[AFS_USER_CONFIG_DIR_ENV] ?? join(homedir(), CONFIG_DIR_NAME);\n}\n\n/**\n * Detect the source type from a source string.\n */\nexport function detectSourceType(source: string): SourceType {\n // ZIP file\n if (source.endsWith(\".zip\")) {\n return \"zip\";\n }\n\n // GitHub URL patterns\n if (\n source.startsWith(\"https://github.com/\") ||\n source.startsWith(\"http://github.com/\") ||\n source.startsWith(\"github.com/\")\n ) {\n return \"github\";\n }\n\n // Default: local directory\n return \"local\";\n}\n\n/**\n * Parse a GitHub URL into owner, repo, ref, and optional subdirectory.\n */\nexport function parseGitHubURL(url: string): {\n cloneUrl: string;\n subdir?: string;\n} {\n // Normalize: ensure https://\n let normalized = url;\n if (normalized.startsWith(\"github.com/\")) {\n normalized = `https://${normalized}`;\n }\n\n // Parse: https://github.com/owner/repo[/tree/ref/subdir]\n const match = normalized.match(\n /^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+?)(?:\\.git)?(?:\\/tree\\/([^/]+)\\/(.+))?$/,\n );\n if (!match) {\n // Simple case: just owner/repo\n return { cloneUrl: normalized.replace(/\\/+$/, \"\") };\n }\n\n const [, owner, repo, _ref, subdir] = match;\n const cloneUrl = `https://github.com/${owner}/${repo}.git`;\n\n return {\n cloneUrl,\n subdir: subdir || undefined,\n };\n}\n\n/**\n * Resolve a source to a local directory path containing program.yaml.\n * For github/zip sources, downloads/extracts to a temp directory.\n *\n * Returns [resolvedPath, tempDir?] — tempDir is set if caller should clean up.\n */\nexport async function resolveSource(\n source: string,\n type: SourceType,\n cwd: string,\n): Promise<[string, string | undefined]> {\n switch (type) {\n case \"local\": {\n const dirPath = isAbsolute(source) ? source : resolve(cwd, source);\n // Verify it's a directory by trying to read it\n try {\n await readdir(dirPath);\n } catch {\n throw new Error(`Source directory does not exist: ${dirPath}`);\n }\n return [dirPath, undefined];\n }\n\n case \"github\": {\n const { cloneUrl, subdir } = parseGitHubURL(source);\n const tempDir = join(tmpdir(), `afs-install-${Date.now()}`);\n await mkdir(tempDir, { recursive: true });\n\n try {\n execSync(`git clone --depth 1 ${cloneUrl} ${join(tempDir, \"repo\")}`, {\n stdio: \"pipe\",\n timeout: 60000,\n });\n } catch (err) {\n await rm(tempDir, { recursive: true, force: true });\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to clone GitHub repository: ${msg}`);\n }\n\n const resolvedPath = subdir ? join(tempDir, \"repo\", subdir) : join(tempDir, \"repo\");\n return [resolvedPath, tempDir];\n }\n\n case \"zip\": {\n const zipPath = isAbsolute(source) ? source : resolve(cwd, source);\n const tempDir = join(tmpdir(), `afs-install-${Date.now()}`);\n const extractDir = join(tempDir, \"extracted\");\n await mkdir(extractDir, { recursive: true });\n\n // Security: check zip contents for path traversal before extracting\n try {\n const listing = execSync(`unzip -l \"${zipPath}\"`, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n // Each line after header has format: \" length date time name\"\n const lines = listing.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip header/footer lines\n if (\n !trimmed ||\n trimmed.startsWith(\"Archive:\") ||\n trimmed.startsWith(\"Length\") ||\n trimmed.startsWith(\"---\")\n )\n continue;\n // Extract filename (last column)\n const parts = trimmed.split(/\\s+/);\n if (parts.length >= 4) {\n const name = parts.slice(3).join(\" \");\n if (name.includes(\"..\") || name.startsWith(\"/\")) {\n await rm(tempDir, { recursive: true, force: true });\n throw new Error(`Zip file contains unsafe path: ${name}. Refusing to extract.`);\n }\n }\n }\n } catch (err) {\n if (err instanceof Error && err.message.includes(\"unsafe path\")) {\n throw err;\n }\n await rm(tempDir, { recursive: true, force: true });\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read zip file: ${msg}`);\n }\n\n // Extract\n try {\n execSync(`unzip -o \"${zipPath}\" -d \"${extractDir}\"`, {\n stdio: \"pipe\",\n timeout: 30000,\n });\n } catch (err) {\n await rm(tempDir, { recursive: true, force: true });\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to extract zip file: ${msg}`);\n }\n\n // Find program.yaml: might be at root or in a single subdirectory\n const entries = await readdir(extractDir);\n // Check if program.yaml is at extract root\n if (entries.includes(\"program.yaml\")) {\n return [extractDir, tempDir];\n }\n // Check single subdirectory\n if (entries.length === 1) {\n const subPath = join(extractDir, entries[0]!);\n try {\n const subEntries = await readdir(subPath);\n if (subEntries.includes(\"program.yaml\")) {\n return [subPath, tempDir];\n }\n } catch {\n // not a directory\n }\n }\n\n await rm(tempDir, { recursive: true, force: true });\n throw new Error(\"No program.yaml found in zip archive\");\n }\n\n default:\n throw new Error(`Unsupported source type: ${type}`);\n }\n}\n\n/**\n * Validate a directory contains a valid program.yaml.\n * Returns the parsed manifest.\n */\nexport async function validateProgramDir(dirPath: string): Promise<ProgramManifest> {\n const yamlPath = join(dirPath, \"program.yaml\");\n let content: string;\n try {\n content = await readFile(yamlPath, \"utf-8\");\n } catch {\n throw new Error(`No program.yaml found in ${dirPath}`);\n }\n\n return parseProgramManifest(content);\n}\n\n// ─── Main Functions ───────────────────────────────────────────────────────\n\n/**\n * Install a program from a source (local dir, GitHub URL, or zip file).\n */\nexport async function installProgram(\n source: string,\n options?: InstallOptions,\n): Promise<InstallResult> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const cwd = options?.cwd ?? process.cwd();\n const type = detectSourceType(source);\n\n // 1. Resolve source to local directory\n const [resolvedPath, tempDir] = await resolveSource(source, type, cwd);\n\n try {\n // 2. Validate program.yaml\n const manifest = await validateProgramDir(resolvedPath);\n\n // 3. Determine install target\n const programsDir = join(userConfigDir, \"programs\");\n const installPath = join(programsDir, manifest.id);\n const mountPath = `/programs/${manifest.id}`;\n\n // 4. Atomic install: copy to temp location, swap\n await mkdir(programsDir, { recursive: true });\n const tempInstallPath = `${installPath}.installing`;\n\n // Clean up any previous failed install\n await rm(tempInstallPath, { recursive: true, force: true });\n\n // Copy program files\n await cp(resolvedPath, tempInstallPath, { recursive: true });\n\n // Remove old version if exists, then rename\n await rm(installPath, { recursive: true, force: true });\n await rename(tempInstallPath, installPath);\n\n // 5. Ensure data directory exists\n const dataDir = join(userConfigDir, \"data\", manifest.id);\n await mkdir(dataDir, { recursive: true });\n\n // 6. Persist mount to CWD config (same as mount add)\n await persistMount(cwd, {\n path: mountPath,\n uri: `fs://${installPath}`,\n });\n\n // 7. Notify daemon to reload programs (best-effort)\n const { notifyDaemonReload } = await import(\"../program/daemon-integration.js\");\n const { getDaemonStatus } = await import(\"../daemon/manager.js\");\n await notifyDaemonReload({ getDaemonStatus });\n\n return {\n success: true,\n programId: manifest.id,\n programName: manifest.name,\n installPath,\n mountPath,\n };\n } finally {\n // 8. Cleanup temp dir\n if (tempDir) {\n await rm(tempDir, { recursive: true, force: true });\n }\n }\n}\n\n/**\n * List installed programs by scanning ~/.afs-config/programs/.\n */\nexport async function listInstalledPrograms(options?: InstallOptions): Promise<InstalledProgram[]> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const programsDir = join(userConfigDir, \"programs\");\n\n let entries: string[];\n try {\n entries = await readdir(programsDir);\n } catch {\n return []; // programs/ directory doesn't exist yet\n }\n\n const programs: InstalledProgram[] = [];\n\n for (const entry of entries) {\n const dirPath = join(programsDir, entry);\n try {\n const manifest = await validateProgramDir(dirPath);\n programs.push({\n id: manifest.id,\n name: manifest.name,\n entrypoint: manifest.entrypoint,\n installPath: dirPath,\n mountPath: `/programs/${manifest.id}`,\n });\n } catch {\n // Skip directories without valid program.yaml\n }\n }\n\n return programs;\n}\n\n/**\n * Remove an installed program.\n */\nexport async function removeProgram(\n programId: string,\n options?: RemoveOptions,\n): Promise<RemoveResult> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const cwd = options?.cwd ?? process.cwd();\n const installPath = join(userConfigDir, \"programs\", programId);\n\n // Verify program exists\n try {\n await readdir(installPath);\n } catch {\n throw new Error(`Program \"${programId}\" is not installed`);\n }\n\n // Remove program directory\n await rm(installPath, { recursive: true, force: true });\n\n // Remove mount from config (searches cwd → project → user)\n await unpersistMount(cwd, `/programs/${programId}`);\n\n // Optionally purge data\n let purgedData = false;\n if (options?.purge) {\n const dataDir = join(userConfigDir, \"data\", programId);\n await rm(dataDir, { recursive: true, force: true });\n purgedData = true;\n }\n\n // Notify daemon to reload programs (best-effort)\n const { notifyDaemonReload } = await import(\"../program/daemon-integration.js\");\n const { getDaemonStatus } = await import(\"../daemon/manager.js\");\n await notifyDaemonReload({ getDaemonStatus });\n\n return {\n success: true,\n programId,\n purgedData,\n };\n}\n\n// ─── Mount Overrides (mounts.toml) ───────────────────────────────────────\n\n/**\n * Read per-program mount overrides from mounts.toml.\n *\n * Location: ~/.afs-config/data/{programId}/mounts.toml\n *\n * Returns [] if the file is missing or invalid.\n */\nexport async function readProgramMountOverrides(\n programId: string,\n options?: { userConfigDir?: string },\n): Promise<MountOverride[]> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const mountsPath = join(userConfigDir, \"data\", programId, \"mounts.toml\");\n\n let content: string;\n try {\n content = await readFile(mountsPath, \"utf-8\");\n } catch {\n return []; // File doesn't exist yet\n }\n\n try {\n const { parse } = await import(\"smol-toml\");\n const parsed = parse(content);\n const mounts = parsed.mounts;\n if (!Array.isArray(mounts)) return [];\n\n const result: MountOverride[] = [];\n for (const entry of mounts) {\n if (typeof entry !== \"object\" || entry === null) continue;\n const { path, uri, options } = entry as Record<string, unknown>;\n if (typeof path !== \"string\" || typeof uri !== \"string\") continue;\n const override: MountOverride = { target: path, uri };\n if (options && typeof options === \"object\" && !Array.isArray(options)) {\n const opts = options as Record<string, unknown>;\n if (Object.keys(opts).length > 0) {\n override.options = opts;\n }\n }\n result.push(override);\n }\n return result;\n } catch {\n return []; // Invalid TOML\n }\n}\n\n// ─── Configure Flow ──────────────────────────────────────────────────────\n\nexport interface ConfigureOptions {\n userConfigDir?: string;\n cwd?: string;\n /** Force re-collection: true/\"\" = all mounts, string = specific mount path */\n force?: string | boolean;\n}\n\nexport interface ConfigureResult {\n success: boolean;\n programId: string;\n configuredMounts: Array<{ target: string; uri: string }>;\n}\n\n/**\n * Interactive configure flow for a program's mount dependencies.\n *\n * Uses the standard credential resolution flow (browser form / OAuth) for each\n * mount. Mounts with already-stored credentials are skipped silently.\n * After resolution, URI templates are rebuilt with collected values and\n * credentials are persisted under the final URI.\n */\nexport async function configureProgramMounts(\n programId: string,\n options?: ConfigureOptions,\n): Promise<ConfigureResult> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const cwd = options?.cwd ?? process.cwd();\n\n // 1. Read program.yaml\n const installPath = join(userConfigDir, \"programs\", programId);\n const manifest = await validateProgramDir(installPath);\n\n // 2. Read existing mounts.toml (if any)\n const existingOverrides = await readProgramMountOverrides(programId, { userConfigDir });\n const existingByTarget = new Map(existingOverrides.map((o) => [o.target, o]));\n\n // 3. Lazy imports\n const { ProviderRegistry } = await import(\"@aigne/afs\");\n const { resolveCredentialsForMount } = await import(\"./credential-helpers.js\");\n const { createCLIAuthContext } = await import(\"../credential/cli-auth-context.js\");\n const { createCredentialStore } = await import(\"../credential/store.js\");\n\n const registry = new ProviderRegistry();\n const authContext = createCLIAuthContext();\n const credentialStore = createCredentialStore();\n\n const configuredMounts: Array<{\n target: string;\n uri: string;\n options?: Record<string, unknown>;\n }> = [];\n\n const { getTemplateVariableNames } = await import(\"@aigne/afs/utils/uri-template\");\n\n for (const mountDecl of manifest.mounts) {\n const existing = existingByTarget.get(mountDecl.target);\n const startUri = existing?.uri || mountDecl.uri;\n\n try {\n const forceThis =\n options?.force === true || options?.force === \"\" || options?.force === mountDecl.target;\n\n const result = await resolveCredentialsForMount({\n cwd,\n uri: startUri,\n mountPath: mountDecl.target,\n authContext,\n credentialStore,\n registry,\n forceCollect: forceThis || undefined,\n extraOptions: existing?.options,\n });\n\n if (result) {\n await result.persistCredentials();\n const finalUri = result.resolvedUri || result.configUri || startUri;\n\n // Filter options: exclude sensitive fields and URI template vars (already in URI)\n const info = await registry.getProviderInfo(startUri);\n const templateVars = new Set(\n info?.manifest?.uriTemplate ? getTemplateVariableNames(info.manifest.uriTemplate) : [],\n );\n const filteredOptions: Record<string, unknown> = {};\n // Preserve existing options from mounts.toml\n if (existing?.options) {\n for (const [k, v] of Object.entries(existing.options)) {\n if (!templateVars.has(k)) filteredOptions[k] = v;\n }\n }\n // Merge newly collected non-sensitive options (overrides existing)\n if (result.collected) {\n for (const [k, v] of Object.entries(result.nonSensitive)) {\n if (!templateVars.has(k)) filteredOptions[k] = v;\n }\n }\n\n const options = Object.keys(filteredOptions).length > 0 ? filteredOptions : undefined;\n configuredMounts.push({ target: mountDecl.target, uri: finalUri, options });\n } else {\n // No schema/credentials needed — preserve existing options\n configuredMounts.push({\n target: mountDecl.target,\n uri: startUri,\n options: existing?.options,\n });\n }\n } catch (err) {\n // Credential resolution failed or user cancelled — keep what we have\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(` Skipping ${mountDecl.target}: ${msg}`);\n configuredMounts.push({\n target: mountDecl.target,\n uri: startUri,\n options: existing?.options,\n });\n }\n }\n\n // 4. Write mounts.toml for mounts whose URI or options changed from program.yaml\n const changedMounts = configuredMounts.filter((m) => {\n const decl = manifest.mounts.find((d: { target: string }) => d.target === m.target);\n return decl && (m.uri !== decl.uri || (m.options && Object.keys(m.options).length > 0));\n });\n\n const dataDir = join(userConfigDir, \"data\", programId);\n const mountsTomlPath = join(dataDir, \"mounts.toml\");\n\n if (changedMounts.length > 0) {\n await mkdir(dataDir, { recursive: true });\n\n const { stringify } = await import(\"smol-toml\");\n const tomlContent = stringify({\n mounts: changedMounts.map((m) => ({\n path: m.target,\n uri: m.uri,\n ...(m.options && Object.keys(m.options).length > 0 ? { options: m.options } : {}),\n })),\n });\n await writeFile(mountsTomlPath, tomlContent, \"utf-8\");\n } else {\n // All mounts match program.yaml defaults — remove stale overrides\n try {\n await rm(mountsTomlPath);\n } catch {\n // File doesn't exist — nothing to clean up\n }\n }\n\n // 5. Notify daemon to reload\n try {\n const { notifyDaemonReload } = await import(\"../program/daemon-integration.js\");\n const { getDaemonStatus } = await import(\"../daemon/manager.js\");\n await notifyDaemonReload({ getDaemonStatus });\n } catch {\n // Best-effort\n }\n\n return {\n success: true,\n programId,\n configuredMounts: changedMounts,\n };\n}\n\n// ─── Mount Status Listing ─────────────────────────────────────────────────\n\nexport interface MountStatus {\n /** Mount target path, e.g. \"/telegram\" */\n path: string;\n /** URI from program.yaml */\n uri: string;\n /** Overridden URI from mounts.toml (if different from program.yaml) */\n configuredUri?: string;\n /** Whether this mount is required */\n required: boolean;\n /** Whether credentials are stored for this mount's effective URI */\n hasCredentials: boolean;\n /** Whether mounts.toml has options for this mount */\n hasOptions: boolean;\n}\n\n/**\n * List configurable mounts and their current status for a program.\n */\nexport async function listProgramMountStatus(\n programId: string,\n options?: { userConfigDir?: string },\n): Promise<{ programId: string; mounts: MountStatus[] }> {\n const userConfigDir = getUserConfigDir(options?.userConfigDir);\n const installPath = join(userConfigDir, \"programs\", programId);\n const manifest = await validateProgramDir(installPath);\n\n const overrides = await readProgramMountOverrides(programId, { userConfigDir });\n const overrideByTarget = new Map(overrides.map((o) => [o.target, o]));\n\n const { createCredentialStore } = await import(\"../credential/store.js\");\n const credentialStore = createCredentialStore();\n\n const mounts: MountStatus[] = [];\n for (const mountDecl of manifest.mounts) {\n const override = overrideByTarget.get(mountDecl.target);\n const effectiveUri = override?.uri || mountDecl.uri;\n\n let hasCredentials = false;\n try {\n const stored = await credentialStore.get(effectiveUri);\n hasCredentials = !!stored && Object.keys(stored).length > 0;\n } catch {\n // Non-fatal\n }\n\n mounts.push({\n path: mountDecl.target,\n uri: mountDecl.uri,\n configuredUri: override?.uri !== mountDecl.uri ? override?.uri : undefined,\n required: mountDecl.required,\n hasCredentials,\n hasOptions: !!(override?.options && Object.keys(override.options).length > 0),\n });\n }\n\n return { programId, mounts };\n}\n"],"mappings":"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"}
@@ -121,7 +121,11 @@ function createServiceCommand(options) {
121
121
  } catch {}
122
122
  return scanProgramTriggers(programDir, compile);
123
123
  },
124
- dataDir: (programId) => `/.data/${programId}`
124
+ dataDir: (programId) => `/.data/${programId}`,
125
+ readMountOverrides: async (programId) => {
126
+ const { readProgramMountOverrides } = await Promise.resolve().then(() => require("../../config/program-install.cjs"));
127
+ return readProgramMountOverrides(programId, { userConfigDir });
128
+ }
125
129
  });
126
130
  try {
127
131
  await programManager.activateAll();
@@ -121,7 +121,11 @@ function createServiceCommand(options) {
121
121
  } catch {}
122
122
  return scanProgramTriggers(programDir, compile);
123
123
  },
124
- dataDir: (programId) => `/.data/${programId}`
124
+ dataDir: (programId) => `/.data/${programId}`,
125
+ readMountOverrides: async (programId) => {
126
+ const { readProgramMountOverrides } = await import("../../config/program-install.mjs");
127
+ return readProgramMountOverrides(programId, { userConfigDir });
128
+ }
125
129
  });
126
130
  try {
127
131
  await programManager.activateAll();
@@ -1 +1 @@
1
- {"version":3,"file":"daemon.mjs","names":[],"sources":["../../../src/core/commands/daemon.ts"],"sourcesContent":["/**\n * AFS Service Command\n *\n * Manages the AFS background service process.\n * Subcommands: start, stop, status, restart, _run (hidden)\n *\n * File is named daemon.ts for historical reasons; the user-facing command is \"service\".\n */\n\nimport type { CommandModule } from \"yargs\";\nimport { colors } from \"../../ui/index.js\";\nimport type { CommandFactoryOptions } from \"./types.js\";\n\nexport interface ServiceArgs {\n port: number;\n cwd?: string;\n}\n\n/** No-op formatter for lifecycle commands that manage their own output. */\nconst noopFormat = () => \"\";\n\n/** Print endpoint table for a running service. */\nfunction printEndpoints(url: string): void {\n console.log(\"\");\n console.log(` ${colors.dim(\"Endpoints:\")}`);\n console.log(` ${colors.brightCyan(`${url}/`)}${colors.dim(\" Explorer UI\")}`);\n console.log(` ${colors.brightCyan(`${url}/ws`)}${colors.dim(\" WebSocket JSON-RPC\")}`);\n console.log(` ${colors.brightCyan(`${url}/afs/*`)}${colors.dim(\" REST API\")}`);\n console.log(` ${colors.brightCyan(`${url}/mcp`)}${colors.dim(\" MCP Streamable HTTP\")}`);\n}\n\nexport function createServiceCommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, ServiceArgs> {\n return {\n command: \"service <action>\",\n describe: \"Manage AFS background service\",\n builder: (yargs) =>\n yargs\n .positional(\"action\", {\n type: \"string\",\n choices: [\"start\", \"stop\", \"status\", \"restart\"],\n description: \"Service action\",\n })\n .option(\"port\", {\n type: \"number\",\n default: 4900,\n description: \"Port for service\",\n })\n .option(\"cwd\", {\n type: \"string\",\n hidden: true,\n description: \"Working directory (used by _run)\",\n }) as any,\n handler: async (argv) => {\n const action = (argv as any).action as string;\n const { getDaemonStatus, stopDaemon, spawnDaemon, getLogFile } = await import(\n \"../../daemon/manager.js\"\n );\n\n switch (action) {\n case \"start\": {\n const existing = await getDaemonStatus();\n if (existing) {\n console.log(\n `${colors.yellow(\"Service already running\")} (PID ${existing.pid}, port ${existing.port})`,\n );\n printEndpoints(existing.url);\n return;\n }\n\n console.log(colors.dim(\"Starting AFS service...\"));\n\n try {\n const info = await spawnDaemon(argv.port);\n console.log(colors.green(\"AFS Service started\"));\n console.log(` ${colors.dim(\"PID:\")} ${info.pid}`);\n console.log(` ${colors.dim(\"Port:\")} ${info.port}`);\n console.log(` ${colors.dim(\"Log:\")} ${getLogFile()}`);\n printEndpoints(info.url);\n } catch (err) {\n console.error(colors.red(`Failed to start service: ${(err as Error).message}`));\n process.exitCode = 1;\n }\n break;\n }\n\n case \"_run\": {\n // Hidden subcommand — the actual service process (runs in detached child)\n\n // Install crash handlers immediately — before any async work.\n // Without these, uncaught errors kill the process with no trace in the log.\n const logTs = () => new Date().toISOString();\n process.on(\"uncaughtException\", (err) => {\n console.error(`[${logTs()}] FATAL uncaughtException: ${err.stack || err.message}`);\n process.exit(1);\n });\n process.on(\"unhandledRejection\", (reason) => {\n const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);\n console.error(`[${logTs()}] FATAL unhandledRejection: ${msg}`);\n process.exit(1);\n });\n\n // Always use home directory for config discovery — daemon serves a\n // global AFS instance, independent of which directory it was started from.\n const { homedir } = await import(\"node:os\");\n const cwd = argv.cwd ?? homedir();\n const { createAFS } = await import(\"../../config/afs-loader.js\");\n const { startDaemonServer } = await import(\"../../daemon/server.js\");\n const { DaemonConfigManager } = await import(\"../../daemon/config-manager.js\");\n const { writePidFile, writePortFile, ensureDaemonDir, cleanPidFiles } = await import(\n \"../../daemon/manager.js\"\n );\n const { createCredentialStore } = await import(\"../../credential/store.js\");\n\n const { afs, failures, configMountPaths, registry } = await createAFS(cwd, {\n credentialStore: createCredentialStore(),\n // No authContext — daemon is non-interactive, relies on stored credentials\n });\n\n if (failures.length > 0) {\n console.warn(`[${logTs()}] ${failures.length} provider(s) failed to mount:`);\n for (const f of failures) {\n console.warn(` ${f.path}: ${f.reason}`);\n }\n }\n\n const configManager = new DaemonConfigManager({\n cwd,\n afs,\n registry,\n configMountPaths,\n failures: failures.map((f) => ({ path: f.path, uri: \"\", reason: f.reason })),\n onConfigChanged: (added, removed) => {\n if (serverInfo?.server) {\n serverInfo.server.broadcast(\"configReloaded\", { added, removed });\n }\n },\n });\n configManager.startWatching();\n\n // Initialize ProgramManager for program activation\n const { ProgramManager } = await import(\"../../program/program-manager.js\");\n const { scanProgramTriggers } = await import(\"../../program/trigger-scanner.js\");\n const { listInstalledPrograms, getUserConfigDir } = await import(\n \"../../config/program-install.js\"\n );\n\n const userConfigDir = getUserConfigDir();\n const programManager = new ProgramManager({\n globalAFS: afs,\n createProvider: afs.createProviderFromMount,\n listPrograms: async () => {\n const programs = await listInstalledPrograms({ userConfigDir });\n return programs.map((p) => ({\n id: p.id,\n installPath: p.installPath,\n mountPath: p.mountPath,\n }));\n },\n scanTriggers: async (programDir: string) => {\n let compile = null;\n try {\n // Dynamic import — @aigne/ash is an optional peer dependency\n const ashModule = \"@aigne/ash\";\n const mod = await import(/* webpackIgnore: true */ ashModule);\n compile = mod.compileSource;\n } catch {\n // @aigne/ash not available — use regex fallback\n }\n return scanProgramTriggers(programDir, compile);\n },\n dataDir: (programId: string) => `/.data/${programId}`,\n });\n\n // Activate all programs (best-effort — failures don't block daemon startup)\n try {\n await programManager.activateAll();\n const activated = programManager.getActivatedPrograms();\n if (activated.length > 0) {\n console.log(\n `[${logTs()}] Activated ${activated.length} program(s): ${activated.join(\", \")}`,\n );\n }\n } catch (err) {\n console.warn(\n `[${logTs()}] Program activation failed: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n let serverInfo: Awaited<ReturnType<typeof startDaemonServer>> | undefined;\n serverInfo = await startDaemonServer({\n afs,\n port: argv.port,\n configManager,\n programManager,\n });\n\n // Write PID and port files so parent (and status/stop) can find us\n await ensureDaemonDir();\n await writePidFile(process.pid);\n await writePortFile(serverInfo.port);\n\n const mounts = afs.getMounts();\n console.log(`[${logTs()}] AFS Service started`);\n console.log(`PID: ${process.pid}, Port: ${serverInfo.port}`);\n console.log(`MCP: ${serverInfo.mcpUrl}`);\n for (const m of mounts) {\n console.log(` ${m.path} → ${m.module.name}`);\n }\n\n // Graceful shutdown\n let shuttingDown = false;\n const shutdown = async () => {\n if (shuttingDown) return;\n shuttingDown = true;\n console.log(`[${logTs()}] Shutting down service...`);\n try {\n await programManager.deactivateAll();\n } catch (err) {\n console.warn(\n `[${logTs()}] Program deactivation error: ${err instanceof Error ? err.message : err}`,\n );\n }\n configManager.stopWatching();\n serverInfo?.stop();\n await cleanPidFiles();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Keep running\n await new Promise(() => {});\n break;\n }\n\n case \"stop\": {\n const stopped = await stopDaemon();\n if (stopped) {\n console.log(colors.green(\"Service stopped\"));\n } else {\n console.log(colors.yellow(\"No running service found\"));\n }\n break;\n }\n\n case \"status\": {\n const info = await getDaemonStatus();\n if (info) {\n console.log(colors.green(\"Service is running\"));\n console.log(` PID: ${info.pid}`);\n console.log(` Port: ${info.port}`);\n printEndpoints(info.url);\n } else {\n console.log(colors.dim(\"Service is not running\"));\n }\n break;\n }\n\n case \"restart\": {\n const wasRunning = await stopDaemon();\n if (wasRunning) {\n console.log(colors.dim(\"Stopped existing service, restarting...\"));\n }\n\n console.log(colors.dim(\"Starting AFS service...\"));\n try {\n const info = await spawnDaemon(argv.port);\n console.log(colors.green(\"AFS Service restarted\"));\n console.log(` ${colors.dim(\"PID:\")} ${info.pid}`);\n console.log(` ${colors.dim(\"Port:\")} ${info.port}`);\n printEndpoints(info.url);\n } catch (err) {\n console.error(colors.red(`Failed to restart service: ${(err as Error).message}`));\n process.exitCode = 1;\n }\n break;\n }\n }\n\n // Signal executor that command ran (output already printed above)\n options.onResult({ command: \"service\", result: null, format: noopFormat });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"daemon.mjs","names":[],"sources":["../../../src/core/commands/daemon.ts"],"sourcesContent":["/**\n * AFS Service Command\n *\n * Manages the AFS background service process.\n * Subcommands: start, stop, status, restart, _run (hidden)\n *\n * File is named daemon.ts for historical reasons; the user-facing command is \"service\".\n */\n\nimport type { CommandModule } from \"yargs\";\nimport { colors } from \"../../ui/index.js\";\nimport type { CommandFactoryOptions } from \"./types.js\";\n\nexport interface ServiceArgs {\n port: number;\n cwd?: string;\n}\n\n/** No-op formatter for lifecycle commands that manage their own output. */\nconst noopFormat = () => \"\";\n\n/** Print endpoint table for a running service. */\nfunction printEndpoints(url: string): void {\n console.log(\"\");\n console.log(` ${colors.dim(\"Endpoints:\")}`);\n console.log(` ${colors.brightCyan(`${url}/`)}${colors.dim(\" Explorer UI\")}`);\n console.log(` ${colors.brightCyan(`${url}/ws`)}${colors.dim(\" WebSocket JSON-RPC\")}`);\n console.log(` ${colors.brightCyan(`${url}/afs/*`)}${colors.dim(\" REST API\")}`);\n console.log(` ${colors.brightCyan(`${url}/mcp`)}${colors.dim(\" MCP Streamable HTTP\")}`);\n}\n\nexport function createServiceCommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, ServiceArgs> {\n return {\n command: \"service <action>\",\n describe: \"Manage AFS background service\",\n builder: (yargs) =>\n yargs\n .positional(\"action\", {\n type: \"string\",\n choices: [\"start\", \"stop\", \"status\", \"restart\"],\n description: \"Service action\",\n })\n .option(\"port\", {\n type: \"number\",\n default: 4900,\n description: \"Port for service\",\n })\n .option(\"cwd\", {\n type: \"string\",\n hidden: true,\n description: \"Working directory (used by _run)\",\n }) as any,\n handler: async (argv) => {\n const action = (argv as any).action as string;\n const { getDaemonStatus, stopDaemon, spawnDaemon, getLogFile } = await import(\n \"../../daemon/manager.js\"\n );\n\n switch (action) {\n case \"start\": {\n const existing = await getDaemonStatus();\n if (existing) {\n console.log(\n `${colors.yellow(\"Service already running\")} (PID ${existing.pid}, port ${existing.port})`,\n );\n printEndpoints(existing.url);\n return;\n }\n\n console.log(colors.dim(\"Starting AFS service...\"));\n\n try {\n const info = await spawnDaemon(argv.port);\n console.log(colors.green(\"AFS Service started\"));\n console.log(` ${colors.dim(\"PID:\")} ${info.pid}`);\n console.log(` ${colors.dim(\"Port:\")} ${info.port}`);\n console.log(` ${colors.dim(\"Log:\")} ${getLogFile()}`);\n printEndpoints(info.url);\n } catch (err) {\n console.error(colors.red(`Failed to start service: ${(err as Error).message}`));\n process.exitCode = 1;\n }\n break;\n }\n\n case \"_run\": {\n // Hidden subcommand — the actual service process (runs in detached child)\n\n // Install crash handlers immediately — before any async work.\n // Without these, uncaught errors kill the process with no trace in the log.\n const logTs = () => new Date().toISOString();\n process.on(\"uncaughtException\", (err) => {\n console.error(`[${logTs()}] FATAL uncaughtException: ${err.stack || err.message}`);\n process.exit(1);\n });\n process.on(\"unhandledRejection\", (reason) => {\n const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);\n console.error(`[${logTs()}] FATAL unhandledRejection: ${msg}`);\n process.exit(1);\n });\n\n // Always use home directory for config discovery — daemon serves a\n // global AFS instance, independent of which directory it was started from.\n const { homedir } = await import(\"node:os\");\n const cwd = argv.cwd ?? homedir();\n const { createAFS } = await import(\"../../config/afs-loader.js\");\n const { startDaemonServer } = await import(\"../../daemon/server.js\");\n const { DaemonConfigManager } = await import(\"../../daemon/config-manager.js\");\n const { writePidFile, writePortFile, ensureDaemonDir, cleanPidFiles } = await import(\n \"../../daemon/manager.js\"\n );\n const { createCredentialStore } = await import(\"../../credential/store.js\");\n\n const { afs, failures, configMountPaths, registry } = await createAFS(cwd, {\n credentialStore: createCredentialStore(),\n // No authContext — daemon is non-interactive, relies on stored credentials\n });\n\n if (failures.length > 0) {\n console.warn(`[${logTs()}] ${failures.length} provider(s) failed to mount:`);\n for (const f of failures) {\n console.warn(` ${f.path}: ${f.reason}`);\n }\n }\n\n const configManager = new DaemonConfigManager({\n cwd,\n afs,\n registry,\n configMountPaths,\n failures: failures.map((f) => ({ path: f.path, uri: \"\", reason: f.reason })),\n onConfigChanged: (added, removed) => {\n if (serverInfo?.server) {\n serverInfo.server.broadcast(\"configReloaded\", { added, removed });\n }\n },\n });\n configManager.startWatching();\n\n // Initialize ProgramManager for program activation\n const { ProgramManager } = await import(\"../../program/program-manager.js\");\n const { scanProgramTriggers } = await import(\"../../program/trigger-scanner.js\");\n const { listInstalledPrograms, getUserConfigDir } = await import(\n \"../../config/program-install.js\"\n );\n\n const userConfigDir = getUserConfigDir();\n const programManager = new ProgramManager({\n globalAFS: afs,\n createProvider: afs.createProviderFromMount,\n listPrograms: async () => {\n const programs = await listInstalledPrograms({ userConfigDir });\n return programs.map((p) => ({\n id: p.id,\n installPath: p.installPath,\n mountPath: p.mountPath,\n }));\n },\n scanTriggers: async (programDir: string) => {\n let compile = null;\n try {\n // Dynamic import — @aigne/ash is an optional peer dependency\n const ashModule = \"@aigne/ash\";\n const mod = await import(/* webpackIgnore: true */ ashModule);\n compile = mod.compileSource;\n } catch {\n // @aigne/ash not available — use regex fallback\n }\n return scanProgramTriggers(programDir, compile);\n },\n dataDir: (programId: string) => `/.data/${programId}`,\n readMountOverrides: async (programId) => {\n const { readProgramMountOverrides } = await import(\"../../config/program-install.js\");\n return readProgramMountOverrides(programId, { userConfigDir });\n },\n });\n\n // Activate all programs (best-effort — failures don't block daemon startup)\n try {\n await programManager.activateAll();\n const activated = programManager.getActivatedPrograms();\n if (activated.length > 0) {\n console.log(\n `[${logTs()}] Activated ${activated.length} program(s): ${activated.join(\", \")}`,\n );\n }\n } catch (err) {\n console.warn(\n `[${logTs()}] Program activation failed: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n let serverInfo: Awaited<ReturnType<typeof startDaemonServer>> | undefined;\n serverInfo = await startDaemonServer({\n afs,\n port: argv.port,\n configManager,\n programManager,\n });\n\n // Write PID and port files so parent (and status/stop) can find us\n await ensureDaemonDir();\n await writePidFile(process.pid);\n await writePortFile(serverInfo.port);\n\n const mounts = afs.getMounts();\n console.log(`[${logTs()}] AFS Service started`);\n console.log(`PID: ${process.pid}, Port: ${serverInfo.port}`);\n console.log(`MCP: ${serverInfo.mcpUrl}`);\n for (const m of mounts) {\n console.log(` ${m.path} → ${m.module.name}`);\n }\n\n // Graceful shutdown\n let shuttingDown = false;\n const shutdown = async () => {\n if (shuttingDown) return;\n shuttingDown = true;\n console.log(`[${logTs()}] Shutting down service...`);\n try {\n await programManager.deactivateAll();\n } catch (err) {\n console.warn(\n `[${logTs()}] Program deactivation error: ${err instanceof Error ? err.message : err}`,\n );\n }\n configManager.stopWatching();\n serverInfo?.stop();\n await cleanPidFiles();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Keep running\n await new Promise(() => {});\n break;\n }\n\n case \"stop\": {\n const stopped = await stopDaemon();\n if (stopped) {\n console.log(colors.green(\"Service stopped\"));\n } else {\n console.log(colors.yellow(\"No running service found\"));\n }\n break;\n }\n\n case \"status\": {\n const info = await getDaemonStatus();\n if (info) {\n console.log(colors.green(\"Service is running\"));\n console.log(` PID: ${info.pid}`);\n console.log(` Port: ${info.port}`);\n printEndpoints(info.url);\n } else {\n console.log(colors.dim(\"Service is not running\"));\n }\n break;\n }\n\n case \"restart\": {\n const wasRunning = await stopDaemon();\n if (wasRunning) {\n console.log(colors.dim(\"Stopped existing service, restarting...\"));\n }\n\n console.log(colors.dim(\"Starting AFS service...\"));\n try {\n const info = await spawnDaemon(argv.port);\n console.log(colors.green(\"AFS Service restarted\"));\n console.log(` ${colors.dim(\"PID:\")} ${info.pid}`);\n console.log(` ${colors.dim(\"Port:\")} ${info.port}`);\n printEndpoints(info.url);\n } catch (err) {\n console.error(colors.red(`Failed to restart service: ${(err as Error).message}`));\n process.exitCode = 1;\n }\n break;\n }\n }\n\n // Signal executor that command ran (output already printed above)\n options.onResult({ command: \"service\", result: null, format: noopFormat });\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/core/commands/index.ts"],"mappings":";;;;;;;;;;;;;;;;;cAmEa,gBAAA,EAAkB,cAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/core/commands/index.ts"],"mappings":";;;;;;;;;;;;;;;;;cAoEa,gBAAA,EAAkB,cAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/commands/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;cAmEa,gBAAA,EAAkB,cAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/commands/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;cAoEa,gBAAA,EAAkB,cAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/core/commands/index.ts"],"sourcesContent":["/**\n * CLI Core Commands\n *\n * Re-exports all command implementations and factories.\n */\n\nexport { type ConnectArgs, createConnectCommand } from \"./connect.js\";\nexport { createServiceCommand, type ServiceArgs } from \"./daemon.js\";\n// Command factories and types\nexport { createDeleteCommand, type DeleteArgs } from \"./delete.js\";\nexport { createExecCommand, type ExecArgs, parseExecArgs, parseValueBySchema } from \"./exec.js\";\nexport { createExplainCommand, type ExplainArgs } from \"./explain.js\";\nexport { createExploreCommand, type ExploreArgs } from \"./explore.js\";\nexport { createGenAgentMdCommand, type GenAgentMdArgs, generateAgentMd } from \"./gen-agent-md.js\";\nexport {\n createInstallCommand,\n type InstallAddArgs,\n type InstallRemoveArgs,\n} from \"./install.js\";\nexport { createLsCommand, type LsArgs } from \"./ls.js\";\nexport { createMcpBridgeCommand, type McpBridgeArgs } from \"./mcp-bridge.js\";\nexport {\n createMountCommand,\n type MountAddArgs,\n type MountListArgs,\n type MountRemoveArgs,\n} from \"./mount.js\";\nexport { createReadCommand, type ReadArgs } from \"./read.js\";\nexport { createSearchCommand, type SearchArgs } from \"./search.js\";\nexport { createServeCommand, type ServeArgs, type ServeResult } from \"./serve.js\";\nexport { createStatCommand, type StatArgs } from \"./stat.js\";\n// Types and helpers\nexport type {\n CommandFactory,\n CommandFactoryOptions,\n CommandOutput,\n FormatFunction,\n} from \"./types.js\";\nexport { resolveAFS } from \"./types.js\";\nexport { createVaultCommand } from \"./vault.js\";\nexport { createWriteCommand, type WriteArgs } from \"./write.js\";\n\n// Import factories for array export\nimport { createConnectCommand } from \"./connect.js\";\nimport { createServiceCommand } from \"./daemon.js\";\nimport { createDeleteCommand } from \"./delete.js\";\nimport { createExecCommand } from \"./exec.js\";\nimport { createExplainCommand } from \"./explain.js\";\nimport { createExploreCommand } from \"./explore.js\";\nimport { createGenAgentMdCommand } from \"./gen-agent-md.js\";\nimport { createInstallCommand } from \"./install.js\";\nimport { createLsCommand } from \"./ls.js\";\nimport { createMcpBridgeCommand } from \"./mcp-bridge.js\";\nimport { createMountCommand } from \"./mount.js\";\nimport { createReadCommand } from \"./read.js\";\nimport { createSearchCommand } from \"./search.js\";\nimport { createServeCommand } from \"./serve.js\";\nimport { createStatCommand } from \"./stat.js\";\nimport type { CommandFactory } from \"./types.js\";\nimport { createVaultCommand } from \"./vault.js\";\nimport { createWriteCommand } from \"./write.js\";\n\n/**\n * Array of all command factories\n *\n * Used by AFSCommandExecutor to register all commands.\n */\nexport const commandFactories: CommandFactory[] = [\n createLsCommand,\n createReadCommand,\n createWriteCommand,\n createDeleteCommand,\n createStatCommand,\n createExecCommand,\n createExplainCommand,\n createSearchCommand,\n createMountCommand,\n createServeCommand,\n createExploreCommand,\n createVaultCommand,\n createServiceCommand,\n createConnectCommand,\n createMcpBridgeCommand,\n createGenAgentMdCommand,\n createInstallCommand,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,MAAa,mBAAqC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/core/commands/index.ts"],"sourcesContent":["/**\n * CLI Core Commands\n *\n * Re-exports all command implementations and factories.\n */\n\nexport { type ConnectArgs, createConnectCommand } from \"./connect.js\";\nexport { createServiceCommand, type ServiceArgs } from \"./daemon.js\";\n// Command factories and types\nexport { createDeleteCommand, type DeleteArgs } from \"./delete.js\";\nexport { createExecCommand, type ExecArgs, parseExecArgs, parseValueBySchema } from \"./exec.js\";\nexport { createExplainCommand, type ExplainArgs } from \"./explain.js\";\nexport { createExploreCommand, type ExploreArgs } from \"./explore.js\";\nexport { createGenAgentMdCommand, type GenAgentMdArgs, generateAgentMd } from \"./gen-agent-md.js\";\nexport {\n createInstallCommand,\n type InstallAddArgs,\n type InstallConfigureArgs,\n type InstallRemoveArgs,\n} from \"./install.js\";\nexport { createLsCommand, type LsArgs } from \"./ls.js\";\nexport { createMcpBridgeCommand, type McpBridgeArgs } from \"./mcp-bridge.js\";\nexport {\n createMountCommand,\n type MountAddArgs,\n type MountListArgs,\n type MountRemoveArgs,\n} from \"./mount.js\";\nexport { createReadCommand, type ReadArgs } from \"./read.js\";\nexport { createSearchCommand, type SearchArgs } from \"./search.js\";\nexport { createServeCommand, type ServeArgs, type ServeResult } from \"./serve.js\";\nexport { createStatCommand, type StatArgs } from \"./stat.js\";\n// Types and helpers\nexport type {\n CommandFactory,\n CommandFactoryOptions,\n CommandOutput,\n FormatFunction,\n} from \"./types.js\";\nexport { resolveAFS } from \"./types.js\";\nexport { createVaultCommand } from \"./vault.js\";\nexport { createWriteCommand, type WriteArgs } from \"./write.js\";\n\n// Import factories for array export\nimport { createConnectCommand } from \"./connect.js\";\nimport { createServiceCommand } from \"./daemon.js\";\nimport { createDeleteCommand } from \"./delete.js\";\nimport { createExecCommand } from \"./exec.js\";\nimport { createExplainCommand } from \"./explain.js\";\nimport { createExploreCommand } from \"./explore.js\";\nimport { createGenAgentMdCommand } from \"./gen-agent-md.js\";\nimport { createInstallCommand } from \"./install.js\";\nimport { createLsCommand } from \"./ls.js\";\nimport { createMcpBridgeCommand } from \"./mcp-bridge.js\";\nimport { createMountCommand } from \"./mount.js\";\nimport { createReadCommand } from \"./read.js\";\nimport { createSearchCommand } from \"./search.js\";\nimport { createServeCommand } from \"./serve.js\";\nimport { createStatCommand } from \"./stat.js\";\nimport type { CommandFactory } from \"./types.js\";\nimport { createVaultCommand } from \"./vault.js\";\nimport { createWriteCommand } from \"./write.js\";\n\n/**\n * Array of all command factories\n *\n * Used by AFSCommandExecutor to register all commands.\n */\nexport const commandFactories: CommandFactory[] = [\n createLsCommand,\n createReadCommand,\n createWriteCommand,\n createDeleteCommand,\n createStatCommand,\n createExecCommand,\n createExplainCommand,\n createSearchCommand,\n createMountCommand,\n createServeCommand,\n createExploreCommand,\n createVaultCommand,\n createServiceCommand,\n createConnectCommand,\n createMcpBridgeCommand,\n createGenAgentMdCommand,\n createInstallCommand,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,MAAa,mBAAqC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD"}
@@ -9,7 +9,7 @@ function createInstallCommand(options) {
9
9
  return {
10
10
  command: "program",
11
11
  describe: "Install and manage AFS programs",
12
- builder: (yargs) => yargs.command(createProgramInstallSubcommand(options)).command(createProgramListSubcommand(options)).command(createProgramUninstallSubcommand(options)).demandCommand(1, "Specify a subcommand: install, list, uninstall").alias("help", "h"),
12
+ builder: (yargs) => yargs.command(createProgramInstallSubcommand(options)).command(createProgramListSubcommand(options)).command(createProgramUninstallSubcommand(options)).command(createProgramConfigureSubcommand(options)).demandCommand(1, "Specify a subcommand: install, list, uninstall, configure").alias("help", "h"),
13
13
  handler: () => {}
14
14
  };
15
15
  }
@@ -86,6 +86,54 @@ function createProgramUninstallSubcommand(options) {
86
86
  }
87
87
  };
88
88
  }
89
+ /**
90
+ * program configure <id>
91
+ */
92
+ function createProgramConfigureSubcommand(options) {
93
+ return {
94
+ command: "configure <id>",
95
+ describe: "Configure mount credentials for an installed program",
96
+ builder: {
97
+ id: {
98
+ type: "string",
99
+ demandOption: true,
100
+ description: "Program ID to configure"
101
+ },
102
+ force: {
103
+ type: "string",
104
+ alias: "f",
105
+ description: "Force re-configure (optionally specify mount path, e.g. /telegram)"
106
+ },
107
+ list: {
108
+ type: "boolean",
109
+ alias: "l",
110
+ default: false,
111
+ description: "List configurable mounts and their status"
112
+ }
113
+ },
114
+ handler: async (argv) => {
115
+ const cwd = options.cwd ?? process.cwd();
116
+ if (argv.list) {
117
+ const result$1 = await require_program_install.listProgramMountStatus(argv.id);
118
+ options.onResult({
119
+ command: "program configure --list",
120
+ result: result$1,
121
+ format: require_install.formatConfigureListOutput
122
+ });
123
+ return;
124
+ }
125
+ const result = await require_program_install.configureProgramMounts(argv.id, {
126
+ cwd,
127
+ force: argv.force
128
+ });
129
+ options.onResult({
130
+ command: "program configure",
131
+ result,
132
+ format: require_install.formatConfigureOutput
133
+ });
134
+ }
135
+ };
136
+ }
89
137
 
90
138
  //#endregion
91
139
  exports.createInstallCommand = createInstallCommand;
@@ -1,5 +1,5 @@
1
- import { formatInstallAddOutput, formatInstallListOutput, formatInstallRemoveOutput } from "../formatters/install.mjs";
2
- import { installProgram, listInstalledPrograms, removeProgram } from "../../config/program-install.mjs";
1
+ import { formatConfigureListOutput, formatConfigureOutput, formatInstallAddOutput, formatInstallListOutput, formatInstallRemoveOutput } from "../formatters/install.mjs";
2
+ import { configureProgramMounts, installProgram, listInstalledPrograms, listProgramMountStatus, removeProgram } from "../../config/program-install.mjs";
3
3
 
4
4
  //#region src/core/commands/install.ts
5
5
  /**
@@ -9,7 +9,7 @@ function createInstallCommand(options) {
9
9
  return {
10
10
  command: "program",
11
11
  describe: "Install and manage AFS programs",
12
- builder: (yargs) => yargs.command(createProgramInstallSubcommand(options)).command(createProgramListSubcommand(options)).command(createProgramUninstallSubcommand(options)).demandCommand(1, "Specify a subcommand: install, list, uninstall").alias("help", "h"),
12
+ builder: (yargs) => yargs.command(createProgramInstallSubcommand(options)).command(createProgramListSubcommand(options)).command(createProgramUninstallSubcommand(options)).command(createProgramConfigureSubcommand(options)).demandCommand(1, "Specify a subcommand: install, list, uninstall, configure").alias("help", "h"),
13
13
  handler: () => {}
14
14
  };
15
15
  }
@@ -86,6 +86,54 @@ function createProgramUninstallSubcommand(options) {
86
86
  }
87
87
  };
88
88
  }
89
+ /**
90
+ * program configure <id>
91
+ */
92
+ function createProgramConfigureSubcommand(options) {
93
+ return {
94
+ command: "configure <id>",
95
+ describe: "Configure mount credentials for an installed program",
96
+ builder: {
97
+ id: {
98
+ type: "string",
99
+ demandOption: true,
100
+ description: "Program ID to configure"
101
+ },
102
+ force: {
103
+ type: "string",
104
+ alias: "f",
105
+ description: "Force re-configure (optionally specify mount path, e.g. /telegram)"
106
+ },
107
+ list: {
108
+ type: "boolean",
109
+ alias: "l",
110
+ default: false,
111
+ description: "List configurable mounts and their status"
112
+ }
113
+ },
114
+ handler: async (argv) => {
115
+ const cwd = options.cwd ?? process.cwd();
116
+ if (argv.list) {
117
+ const result$1 = await listProgramMountStatus(argv.id);
118
+ options.onResult({
119
+ command: "program configure --list",
120
+ result: result$1,
121
+ format: formatConfigureListOutput
122
+ });
123
+ return;
124
+ }
125
+ const result = await configureProgramMounts(argv.id, {
126
+ cwd,
127
+ force: argv.force
128
+ });
129
+ options.onResult({
130
+ command: "program configure",
131
+ result,
132
+ format: formatConfigureOutput
133
+ });
134
+ }
135
+ };
136
+ }
89
137
 
90
138
  //#endregion
91
139
  export { createInstallCommand };
@@ -1 +1 @@
1
- {"version":3,"file":"install.mjs","names":[],"sources":["../../../src/core/commands/install.ts"],"sourcesContent":["/**\n * program Command — Install, list, and uninstall AFS programs.\n *\n * Programs are installed to ~/.afs-config/programs/<id>/\n * and mounted at /programs/<id> in user-level config.\n */\n\nimport type { Argv, CommandModule } from \"yargs\";\nimport {\n installProgram,\n listInstalledPrograms,\n removeProgram,\n} from \"../../config/program-install.js\";\nimport {\n formatInstallAddOutput,\n formatInstallListOutput,\n formatInstallRemoveOutput,\n} from \"../formatters/install.js\";\nimport type { CommandFactoryOptions } from \"./types.js\";\n\nexport interface InstallAddArgs {\n source: string;\n}\n\nexport interface InstallRemoveArgs {\n id: string;\n purge?: boolean;\n}\n\n/**\n * Create program command factory (with subcommands)\n */\nexport function createInstallCommand(options: CommandFactoryOptions): CommandModule {\n return {\n command: \"program\",\n describe: \"Install and manage AFS programs\",\n builder: (yargs: Argv) =>\n yargs\n .command(createProgramInstallSubcommand(options))\n .command(createProgramListSubcommand(options))\n .command(createProgramUninstallSubcommand(options))\n .demandCommand(1, \"Specify a subcommand: install, list, uninstall\")\n .alias(\"help\", \"h\"),\n handler: () => {},\n };\n}\n\n/**\n * program install <source>\n */\nfunction createProgramInstallSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, InstallAddArgs> {\n return {\n command: \"install <source>\",\n describe: \"Install a program from local directory, GitHub URL, or zip file\",\n builder: {\n source: {\n type: \"string\",\n demandOption: true,\n description: \"Source: local path, GitHub URL, or .zip file\",\n },\n },\n handler: async (argv) => {\n const cwd = options.cwd ?? process.cwd();\n const result = await installProgram(argv.source, { cwd });\n\n options.onResult({\n command: \"program install\",\n result,\n format: formatInstallAddOutput,\n });\n },\n };\n}\n\n/**\n * program list\n */\nfunction createProgramListSubcommand(options: CommandFactoryOptions): CommandModule {\n return {\n command: [\"list\", \"ls\"],\n describe: \"List installed programs\",\n handler: async () => {\n const programs = await listInstalledPrograms();\n\n options.onResult({\n command: \"program list\",\n result: programs,\n format: formatInstallListOutput,\n });\n },\n };\n}\n\n/**\n * program uninstall <id>\n */\nfunction createProgramUninstallSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, InstallRemoveArgs> {\n return {\n command: \"uninstall <id>\",\n describe: \"Uninstall an installed program\",\n builder: {\n id: {\n type: \"string\",\n demandOption: true,\n description: \"Program ID to uninstall\",\n },\n purge: {\n type: \"boolean\",\n default: false,\n description: \"Also remove program data directory\",\n },\n },\n handler: async (argv) => {\n const cwd = options.cwd ?? process.cwd();\n const result = await removeProgram(argv.id, { purge: argv.purge, cwd });\n\n options.onResult({\n command: \"program uninstall\",\n result,\n format: formatInstallRemoveOutput,\n });\n },\n };\n}\n"],"mappings":";;;;;;;AAgCA,SAAgB,qBAAqB,SAA+C;AAClF,QAAO;EACL,SAAS;EACT,UAAU;EACV,UAAU,UACR,MACG,QAAQ,+BAA+B,QAAQ,CAAC,CAChD,QAAQ,4BAA4B,QAAQ,CAAC,CAC7C,QAAQ,iCAAiC,QAAQ,CAAC,CAClD,cAAc,GAAG,iDAAiD,CAClE,MAAM,QAAQ,IAAI;EACvB,eAAe;EAChB;;;;;AAMH,SAAS,+BACP,SACwC;AACxC,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS,EACP,QAAQ;GACN,MAAM;GACN,cAAc;GACd,aAAa;GACd,EACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;GACxC,MAAM,SAAS,MAAM,eAAe,KAAK,QAAQ,EAAE,KAAK,CAAC;AAEzD,WAAQ,SAAS;IACf,SAAS;IACT;IACA,QAAQ;IACT,CAAC;;EAEL;;;;;AAMH,SAAS,4BAA4B,SAA+C;AAClF,QAAO;EACL,SAAS,CAAC,QAAQ,KAAK;EACvB,UAAU;EACV,SAAS,YAAY;GACnB,MAAM,WAAW,MAAM,uBAAuB;AAE9C,WAAQ,SAAS;IACf,SAAS;IACT,QAAQ;IACR,QAAQ;IACT,CAAC;;EAEL;;;;;AAMH,SAAS,iCACP,SAC2C;AAC3C,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS;GACP,IAAI;IACF,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,OAAO;IACL,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;GACxC,MAAM,SAAS,MAAM,cAAc,KAAK,IAAI;IAAE,OAAO,KAAK;IAAO;IAAK,CAAC;AAEvE,WAAQ,SAAS;IACf,SAAS;IACT;IACA,QAAQ;IACT,CAAC;;EAEL"}
1
+ {"version":3,"file":"install.mjs","names":["result"],"sources":["../../../src/core/commands/install.ts"],"sourcesContent":["/**\n * program Command — Install, list, and uninstall AFS programs.\n *\n * Programs are installed to ~/.afs-config/programs/<id>/\n * and mounted at /programs/<id> in user-level config.\n */\n\nimport type { Argv, CommandModule } from \"yargs\";\nimport {\n configureProgramMounts,\n installProgram,\n listInstalledPrograms,\n listProgramMountStatus,\n removeProgram,\n} from \"../../config/program-install.js\";\nimport {\n formatConfigureListOutput,\n formatConfigureOutput,\n formatInstallAddOutput,\n formatInstallListOutput,\n formatInstallRemoveOutput,\n} from \"../formatters/install.js\";\nimport type { CommandFactoryOptions } from \"./types.js\";\n\nexport interface InstallAddArgs {\n source: string;\n}\n\nexport interface InstallRemoveArgs {\n id: string;\n purge?: boolean;\n}\n\nexport interface InstallConfigureArgs {\n id: string;\n force?: string;\n list?: boolean;\n}\n\n/**\n * Create program command factory (with subcommands)\n */\nexport function createInstallCommand(options: CommandFactoryOptions): CommandModule {\n return {\n command: \"program\",\n describe: \"Install and manage AFS programs\",\n builder: (yargs: Argv) =>\n yargs\n .command(createProgramInstallSubcommand(options))\n .command(createProgramListSubcommand(options))\n .command(createProgramUninstallSubcommand(options))\n .command(createProgramConfigureSubcommand(options))\n .demandCommand(1, \"Specify a subcommand: install, list, uninstall, configure\")\n .alias(\"help\", \"h\"),\n handler: () => {},\n };\n}\n\n/**\n * program install <source>\n */\nfunction createProgramInstallSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, InstallAddArgs> {\n return {\n command: \"install <source>\",\n describe: \"Install a program from local directory, GitHub URL, or zip file\",\n builder: {\n source: {\n type: \"string\",\n demandOption: true,\n description: \"Source: local path, GitHub URL, or .zip file\",\n },\n },\n handler: async (argv) => {\n const cwd = options.cwd ?? process.cwd();\n const result = await installProgram(argv.source, { cwd });\n\n options.onResult({\n command: \"program install\",\n result,\n format: formatInstallAddOutput,\n });\n },\n };\n}\n\n/**\n * program list\n */\nfunction createProgramListSubcommand(options: CommandFactoryOptions): CommandModule {\n return {\n command: [\"list\", \"ls\"],\n describe: \"List installed programs\",\n handler: async () => {\n const programs = await listInstalledPrograms();\n\n options.onResult({\n command: \"program list\",\n result: programs,\n format: formatInstallListOutput,\n });\n },\n };\n}\n\n/**\n * program uninstall <id>\n */\nfunction createProgramUninstallSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, InstallRemoveArgs> {\n return {\n command: \"uninstall <id>\",\n describe: \"Uninstall an installed program\",\n builder: {\n id: {\n type: \"string\",\n demandOption: true,\n description: \"Program ID to uninstall\",\n },\n purge: {\n type: \"boolean\",\n default: false,\n description: \"Also remove program data directory\",\n },\n },\n handler: async (argv) => {\n const cwd = options.cwd ?? process.cwd();\n const result = await removeProgram(argv.id, { purge: argv.purge, cwd });\n\n options.onResult({\n command: \"program uninstall\",\n result,\n format: formatInstallRemoveOutput,\n });\n },\n };\n}\n\n/**\n * program configure <id>\n */\nfunction createProgramConfigureSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, InstallConfigureArgs> {\n return {\n command: \"configure <id>\",\n describe: \"Configure mount credentials for an installed program\",\n builder: {\n id: {\n type: \"string\",\n demandOption: true,\n description: \"Program ID to configure\",\n },\n force: {\n type: \"string\",\n alias: \"f\",\n description: \"Force re-configure (optionally specify mount path, e.g. /telegram)\",\n },\n list: {\n type: \"boolean\",\n alias: \"l\",\n default: false,\n description: \"List configurable mounts and their status\",\n },\n },\n handler: async (argv) => {\n const cwd = options.cwd ?? process.cwd();\n\n if (argv.list) {\n const result = await listProgramMountStatus(argv.id);\n options.onResult({\n command: \"program configure --list\",\n result,\n format: formatConfigureListOutput,\n });\n return;\n }\n\n const result = await configureProgramMounts(argv.id, {\n cwd,\n force: argv.force,\n });\n\n options.onResult({\n command: \"program configure\",\n result,\n format: formatConfigureOutput,\n });\n },\n };\n}\n"],"mappings":";;;;;;;AA0CA,SAAgB,qBAAqB,SAA+C;AAClF,QAAO;EACL,SAAS;EACT,UAAU;EACV,UAAU,UACR,MACG,QAAQ,+BAA+B,QAAQ,CAAC,CAChD,QAAQ,4BAA4B,QAAQ,CAAC,CAC7C,QAAQ,iCAAiC,QAAQ,CAAC,CAClD,QAAQ,iCAAiC,QAAQ,CAAC,CAClD,cAAc,GAAG,4DAA4D,CAC7E,MAAM,QAAQ,IAAI;EACvB,eAAe;EAChB;;;;;AAMH,SAAS,+BACP,SACwC;AACxC,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS,EACP,QAAQ;GACN,MAAM;GACN,cAAc;GACd,aAAa;GACd,EACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;GACxC,MAAM,SAAS,MAAM,eAAe,KAAK,QAAQ,EAAE,KAAK,CAAC;AAEzD,WAAQ,SAAS;IACf,SAAS;IACT;IACA,QAAQ;IACT,CAAC;;EAEL;;;;;AAMH,SAAS,4BAA4B,SAA+C;AAClF,QAAO;EACL,SAAS,CAAC,QAAQ,KAAK;EACvB,UAAU;EACV,SAAS,YAAY;GACnB,MAAM,WAAW,MAAM,uBAAuB;AAE9C,WAAQ,SAAS;IACf,SAAS;IACT,QAAQ;IACR,QAAQ;IACT,CAAC;;EAEL;;;;;AAMH,SAAS,iCACP,SAC2C;AAC3C,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS;GACP,IAAI;IACF,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,OAAO;IACL,MAAM;IACN,SAAS;IACT,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;GACxC,MAAM,SAAS,MAAM,cAAc,KAAK,IAAI;IAAE,OAAO,KAAK;IAAO;IAAK,CAAC;AAEvE,WAAQ,SAAS;IACf,SAAS;IACT;IACA,QAAQ;IACT,CAAC;;EAEL;;;;;AAMH,SAAS,iCACP,SAC8C;AAC9C,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS;GACP,IAAI;IACF,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,OAAO;IACL,MAAM;IACN,OAAO;IACP,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,OAAO;IACP,SAAS;IACT,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;AAExC,OAAI,KAAK,MAAM;IACb,MAAMA,WAAS,MAAM,uBAAuB,KAAK,GAAG;AACpD,YAAQ,SAAS;KACf,SAAS;KACT;KACA,QAAQ;KACT,CAAC;AACF;;GAGF,MAAM,SAAS,MAAM,uBAAuB,KAAK,IAAI;IACnD;IACA,OAAO,KAAK;IACb,CAAC;AAEF,WAAQ,SAAS;IACf,SAAS;IACT;IACA,QAAQ;IACT,CAAC;;EAEL"}