@aigne/afs-cli 1.11.0-beta.11 → 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 (157) hide show
  1. package/dist/cli.cjs +3 -2
  2. package/dist/cli.mjs +3 -2
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/config/afs-loader.cjs +36 -315
  5. package/dist/config/afs-loader.d.cts.map +1 -1
  6. package/dist/config/afs-loader.d.mts +2 -1
  7. package/dist/config/afs-loader.d.mts.map +1 -1
  8. package/dist/config/afs-loader.mjs +28 -307
  9. package/dist/config/afs-loader.mjs.map +1 -1
  10. package/dist/config/credential-helpers.cjs +303 -0
  11. package/dist/config/credential-helpers.d.mts +2 -0
  12. package/dist/config/credential-helpers.mjs +300 -0
  13. package/dist/config/credential-helpers.mjs.map +1 -0
  14. package/dist/config/loader.cjs +3 -1
  15. package/dist/config/loader.mjs +3 -2
  16. package/dist/config/loader.mjs.map +1 -1
  17. package/dist/config/program-install.cjs +450 -0
  18. package/dist/config/program-install.d.mts +1 -0
  19. package/dist/config/program-install.mjs +444 -0
  20. package/dist/config/program-install.mjs.map +1 -0
  21. package/dist/core/commands/connect.cjs +53 -0
  22. package/dist/core/commands/connect.d.mts +2 -0
  23. package/dist/core/commands/connect.mjs +55 -0
  24. package/dist/core/commands/connect.mjs.map +1 -0
  25. package/dist/core/commands/daemon.cjs +211 -0
  26. package/dist/core/commands/daemon.d.mts +2 -0
  27. package/dist/core/commands/daemon.mjs +212 -0
  28. package/dist/core/commands/daemon.mjs.map +1 -0
  29. package/dist/core/commands/explain.cjs +3 -1
  30. package/dist/core/commands/explain.mjs +3 -1
  31. package/dist/core/commands/explain.mjs.map +1 -1
  32. package/dist/core/commands/explore.cjs +47 -12
  33. package/dist/core/commands/explore.mjs +47 -12
  34. package/dist/core/commands/explore.mjs.map +1 -1
  35. package/dist/core/commands/gen-agent-md.cjs +126 -0
  36. package/dist/core/commands/gen-agent-md.d.mts +2 -0
  37. package/dist/core/commands/gen-agent-md.mjs +125 -0
  38. package/dist/core/commands/gen-agent-md.mjs.map +1 -0
  39. package/dist/core/commands/index.cjs +13 -1
  40. package/dist/core/commands/index.d.cts.map +1 -1
  41. package/dist/core/commands/index.d.mts +6 -0
  42. package/dist/core/commands/index.d.mts.map +1 -1
  43. package/dist/core/commands/index.mjs +13 -1
  44. package/dist/core/commands/index.mjs.map +1 -1
  45. package/dist/core/commands/install.cjs +139 -0
  46. package/dist/core/commands/install.d.mts +2 -0
  47. package/dist/core/commands/install.mjs +140 -0
  48. package/dist/core/commands/install.mjs.map +1 -0
  49. package/dist/core/commands/ls.cjs +14 -2
  50. package/dist/core/commands/ls.d.cts +2 -0
  51. package/dist/core/commands/ls.d.cts.map +1 -1
  52. package/dist/core/commands/ls.d.mts +2 -0
  53. package/dist/core/commands/ls.d.mts.map +1 -1
  54. package/dist/core/commands/ls.mjs +14 -2
  55. package/dist/core/commands/ls.mjs.map +1 -1
  56. package/dist/core/commands/mcp-bridge.cjs +201 -0
  57. package/dist/core/commands/mcp-bridge.d.mts +2 -0
  58. package/dist/core/commands/mcp-bridge.mjs +201 -0
  59. package/dist/core/commands/mcp-bridge.mjs.map +1 -0
  60. package/dist/core/commands/read.cjs +20 -7
  61. package/dist/core/commands/read.d.cts +2 -0
  62. package/dist/core/commands/read.d.cts.map +1 -1
  63. package/dist/core/commands/read.d.mts +2 -0
  64. package/dist/core/commands/read.d.mts.map +1 -1
  65. package/dist/core/commands/read.mjs +20 -7
  66. package/dist/core/commands/read.mjs.map +1 -1
  67. package/dist/core/commands/search.cjs +5 -1
  68. package/dist/core/commands/search.mjs +5 -1
  69. package/dist/core/commands/search.mjs.map +1 -1
  70. package/dist/core/commands/stat.mjs.map +1 -1
  71. package/dist/core/commands/types.d.cts +2 -0
  72. package/dist/core/commands/types.d.cts.map +1 -1
  73. package/dist/core/commands/types.d.mts +2 -0
  74. package/dist/core/commands/types.d.mts.map +1 -1
  75. package/dist/core/commands/types.mjs.map +1 -1
  76. package/dist/core/commands/vault.cjs +289 -0
  77. package/dist/core/commands/vault.d.mts +2 -0
  78. package/dist/core/commands/vault.mjs +289 -0
  79. package/dist/core/commands/vault.mjs.map +1 -0
  80. package/dist/core/commands/write.cjs +19 -6
  81. package/dist/core/commands/write.d.cts +2 -1
  82. package/dist/core/commands/write.d.cts.map +1 -1
  83. package/dist/core/commands/write.d.mts +2 -1
  84. package/dist/core/commands/write.d.mts.map +1 -1
  85. package/dist/core/commands/write.mjs +19 -6
  86. package/dist/core/commands/write.mjs.map +1 -1
  87. package/dist/core/executor/index.cjs +95 -19
  88. package/dist/core/executor/index.d.cts +4 -0
  89. package/dist/core/executor/index.d.cts.map +1 -1
  90. package/dist/core/executor/index.d.mts +4 -0
  91. package/dist/core/executor/index.d.mts.map +1 -1
  92. package/dist/core/executor/index.mjs +95 -19
  93. package/dist/core/executor/index.mjs.map +1 -1
  94. package/dist/core/formatters/index.d.mts +1 -0
  95. package/dist/core/formatters/install.cjs +40 -0
  96. package/dist/core/formatters/install.d.mts +1 -0
  97. package/dist/core/formatters/install.mjs +36 -0
  98. package/dist/core/formatters/install.mjs.map +1 -0
  99. package/dist/core/formatters/vault.cjs +36 -0
  100. package/dist/core/formatters/vault.mjs +32 -0
  101. package/dist/core/formatters/vault.mjs.map +1 -0
  102. package/dist/credential/auth-server.cjs +22 -4
  103. package/dist/credential/auth-server.mjs +22 -4
  104. package/dist/credential/auth-server.mjs.map +1 -1
  105. package/dist/credential/index.d.mts +2 -1
  106. package/dist/credential/mcp-auth-context.cjs +21 -5
  107. package/dist/credential/mcp-auth-context.mjs +21 -5
  108. package/dist/credential/mcp-auth-context.mjs.map +1 -1
  109. package/dist/credential/resolver.cjs +11 -3
  110. package/dist/credential/resolver.mjs +11 -3
  111. package/dist/credential/resolver.mjs.map +1 -1
  112. package/dist/credential/vault-store.d.mts +1 -0
  113. package/dist/daemon/config-manager.cjs +279 -0
  114. package/dist/daemon/config-manager.mjs +279 -0
  115. package/dist/daemon/config-manager.mjs.map +1 -0
  116. package/dist/daemon/manager.cjs +164 -0
  117. package/dist/daemon/manager.mjs +157 -0
  118. package/dist/daemon/manager.mjs.map +1 -0
  119. package/dist/daemon/server.cjs +220 -0
  120. package/dist/daemon/server.mjs +220 -0
  121. package/dist/daemon/server.mjs.map +1 -0
  122. package/dist/mcp/http-transport.cjs +14 -1
  123. package/dist/mcp/http-transport.mjs +14 -1
  124. package/dist/mcp/http-transport.mjs.map +1 -1
  125. package/dist/mcp/server.cjs +4 -2
  126. package/dist/mcp/server.mjs +4 -2
  127. package/dist/mcp/server.mjs.map +1 -1
  128. package/dist/mcp/tools.cjs +62 -12
  129. package/dist/mcp/tools.mjs +62 -12
  130. package/dist/mcp/tools.mjs.map +1 -1
  131. package/dist/program/daemon-integration.cjs +46 -0
  132. package/dist/program/daemon-integration.mjs +45 -0
  133. package/dist/program/daemon-integration.mjs.map +1 -0
  134. package/dist/program/program-manager.cjs +166 -0
  135. package/dist/program/program-manager.mjs +166 -0
  136. package/dist/program/program-manager.mjs.map +1 -0
  137. package/dist/program/trigger-scanner.cjs +148 -0
  138. package/dist/program/trigger-scanner.mjs +148 -0
  139. package/dist/program/trigger-scanner.mjs.map +1 -0
  140. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  141. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +11 -0
  142. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs.map +1 -0
  143. package/dist/providers/vault/dist/encrypted-file.cjs +158 -0
  144. package/dist/providers/vault/dist/encrypted-file.mjs +153 -0
  145. package/dist/providers/vault/dist/encrypted-file.mjs.map +1 -0
  146. package/dist/providers/vault/dist/index.cjs +405 -0
  147. package/dist/providers/vault/dist/index.mjs +400 -0
  148. package/dist/providers/vault/dist/index.mjs.map +1 -0
  149. package/dist/providers/vault/dist/key-resolver.cjs +181 -0
  150. package/dist/providers/vault/dist/key-resolver.mjs +180 -0
  151. package/dist/providers/vault/dist/key-resolver.mjs.map +1 -0
  152. package/dist/repl.cjs +109 -14
  153. package/dist/repl.d.cts.map +1 -1
  154. package/dist/repl.d.mts.map +1 -1
  155. package/dist/repl.mjs +109 -14
  156. package/dist/repl.mjs.map +1 -1
  157. package/package.json +27 -20
@@ -2,6 +2,28 @@ import { commandFactories } from "../commands/index.mjs";
2
2
  import yargs from "yargs";
3
3
 
4
4
  //#region src/core/executor/index.ts
5
+ /** Known command names and aliases for suggestion matching. */
6
+ const KNOWN_COMMANDS = [
7
+ "ls",
8
+ "list",
9
+ "read",
10
+ "cat",
11
+ "write",
12
+ "delete",
13
+ "rm",
14
+ "stat",
15
+ "exec",
16
+ "explain",
17
+ "search",
18
+ "grep",
19
+ "find",
20
+ "mount",
21
+ "serve",
22
+ "explore",
23
+ "service",
24
+ "connect",
25
+ "vault"
26
+ ];
5
27
  /**
6
28
  * AFS Command Executor
7
29
  *
@@ -41,6 +63,8 @@ var AFSCommandExecutor = class {
41
63
  commandResult = result;
42
64
  }
43
65
  };
66
+ let failMsg;
67
+ let failErr;
44
68
  let parser = yargs(normalizedArgs).scriptName("afs").usage("$0 <command> [options]").option("json", {
45
69
  type: "boolean",
46
70
  description: "Output in JSON format",
@@ -64,34 +88,42 @@ var AFSCommandExecutor = class {
64
88
  type: "boolean",
65
89
  description: "Start interactive REPL mode",
66
90
  global: false
67
- }).help(true).alias("h", "help").version(this.options.version || "unknown").alias("v", "version").demandCommand().showHelpOnFail(true).exitProcess(false);
91
+ }).help(true).alias("h", "help").version(this.options.version || "unknown").alias("v", "version").demandCommand().strictCommands().exitProcess(false).fail((msg, err) => {
92
+ failErr = err || new Error(msg || "Unknown error");
93
+ failMsg = msg;
94
+ });
68
95
  for (const factory of commandFactories) parser = parser.command(factory(factoryOptions));
69
96
  try {
70
97
  let output;
71
- let error;
72
- const parsed = await parser.parseAsync(normalizedArgs, {}, (e, _, o) => {
73
- if (e) error = e;
98
+ await parser.parseAsync(normalizedArgs, {}, (_e, _, o) => {
74
99
  output = o;
75
100
  });
76
- if (error) return {
77
- success: false,
78
- command: normalizedArgs[0] || "unknown",
79
- result: void 0,
80
- formatted: output,
81
- error: { message: error.message }
82
- };
83
- if (parsed.help) return {
101
+ if (failErr) {
102
+ const formatted$1 = await this.formatFailure(normalizedArgs, failMsg, parser);
103
+ return {
104
+ success: false,
105
+ command: normalizedArgs[0] || "unknown",
106
+ result: void 0,
107
+ formatted: formatted$1,
108
+ error: { message: failMsg || failErr.message }
109
+ };
110
+ }
111
+ if (output) return {
84
112
  success: true,
85
113
  command: "help",
86
114
  formatted: output
87
115
  };
88
- if (parsed.version) return {
89
- success: true,
90
- command: "version",
91
- formatted: output
92
- };
93
- if (!commandResult) throw new Error("Command not found");
94
- const view = outputOptions.json ? "json" : outputOptions.yaml ? "yaml" : outputOptions.view;
116
+ if (!commandResult) {
117
+ const formatted$1 = await this.formatFailure(normalizedArgs, void 0, parser);
118
+ return {
119
+ success: false,
120
+ command: normalizedArgs[0] || "unknown",
121
+ result: void 0,
122
+ formatted: formatted$1,
123
+ error: { message: `Unknown command: "${normalizedArgs[0] || ""}"` }
124
+ };
125
+ }
126
+ const view = outputOptions.json ? "json" : outputOptions.yaml ? "yaml" : commandResult.viewOverride ?? outputOptions.view;
95
127
  const formatted = commandResult.format(commandResult.result, view, { path: this.extractPath(normalizedArgs) });
96
128
  if (commandResult.error) return {
97
129
  success: false,
@@ -118,6 +150,31 @@ var AFSCommandExecutor = class {
118
150
  }
119
151
  }
120
152
  /**
153
+ * Format a friendly error message for unknown/invalid commands.
154
+ */
155
+ async formatFailure(args, failMsg, parser) {
156
+ const cmd = args[0] || "";
157
+ const lines = [];
158
+ if (cmd && failMsg?.includes("Unknown command")) {
159
+ lines.push(`Unknown command: "${cmd}"`);
160
+ const suggestions = suggestCommands(cmd);
161
+ if (suggestions.length > 0) {
162
+ lines.push("");
163
+ lines.push(`Did you mean?`);
164
+ for (const s of suggestions) lines.push(` afs ${s}`);
165
+ }
166
+ } else if (failMsg) lines.push(failMsg);
167
+ else lines.push(`Unknown command: "${cmd}"`);
168
+ lines.push("");
169
+ try {
170
+ const helpText = await parser.getHelp();
171
+ lines.push(helpText);
172
+ } catch {
173
+ lines.push("Run \"afs --help\" to see available commands.");
174
+ }
175
+ return lines.join("\n");
176
+ }
177
+ /**
121
178
  * Normalize argv to an array of strings
122
179
  */
123
180
  normalizeArgv(argv) {
@@ -189,6 +246,25 @@ var AFSCommandExecutor = class {
189
246
  }
190
247
  }
191
248
  };
249
+ /** Levenshtein distance between two strings. */
250
+ function levenshtein(a, b) {
251
+ const m = a.length;
252
+ const n = b.length;
253
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
254
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
255
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
256
+ for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
257
+ return dp[m][n];
258
+ }
259
+ /** Suggest known commands similar to the given input. */
260
+ function suggestCommands(input) {
261
+ const lower = input.toLowerCase();
262
+ return KNOWN_COMMANDS.map((cmd) => ({
263
+ cmd,
264
+ dist: levenshtein(lower, cmd),
265
+ maxLen: Math.max(lower.length, cmd.length)
266
+ })).filter((x) => x.dist > 0 && x.dist < x.maxLen * .5).sort((a, b) => a.dist - b.dist).map((x) => x.cmd).slice(0, 3);
267
+ }
192
268
 
193
269
  //#endregion
194
270
  export { AFSCommandExecutor };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/core/executor/index.ts"],"sourcesContent":["/**\n * AFS Command Executor\n *\n * Unified command execution interface using yargs and CommandFactory pattern.\n */\n\nimport type { AFS } from \"@aigne/afs\";\nimport yargs from \"yargs\";\nimport {\n type CommandFactoryOptions,\n type CommandOutput,\n commandFactories,\n} from \"../commands/index.js\";\nimport type { ViewType } from \"../types.js\";\n\n/**\n * Result from executing a command\n */\nexport interface ExecuteResult {\n /** Whether the command executed successfully */\n success: boolean;\n /** The command that was executed (ls, read, write, etc.) */\n command: string;\n /** The raw result data from AFS */\n result?: unknown;\n /** Formatted output string */\n formatted: string;\n /** Error info if the command failed */\n error?: {\n /** Exit code (from ExitCode enum) */\n code?: number;\n /** Error message */\n message: string;\n };\n}\n\n/**\n * Options for the executor\n */\nexport interface ExecutorOptions {\n /** Whether the output is for a TTY (enables colors) */\n tty?: boolean;\n /** Current working directory (for explore command) */\n cwd?: string;\n /** Version of the AFS CLI */\n version?: string;\n}\n\n/**\n * AFS Command Executor\n *\n * Provides a unified interface for executing AFS commands using yargs.\n *\n * @example\n * ```typescript\n * const executor = new AFSCommandExecutor(afs, { tty: false });\n * const result = await executor.execute(\"afs ls /path --depth=2\");\n * console.log(result.formatted);\n * ```\n */\nexport class AFSCommandExecutor {\n private afs?: AFS;\n private options: ExecutorOptions;\n\n constructor(afs?: AFS, options?: ExecutorOptions) {\n this.afs = afs;\n this.options = options ?? {};\n }\n\n /**\n * Execute an AFS command\n *\n * @param argv - Command string or array of arguments\n * - String: \"afs ls /path --depth=2\" or \"ls /path --depth=2\"\n * - Array: [\"ls\", \"/path\", \"--depth=2\"]\n * @returns Execution result with formatted output\n */\n async execute(argv: string | string[]): Promise<ExecuteResult> {\n const normalizedArgs = this.normalizeArgv(argv);\n\n // Capture command result\n let commandResult: CommandOutput | undefined;\n\n // Determine output format from args\n const outputOptions = this.extractOutputOptions(normalizedArgs);\n\n // Create factory options\n const factoryOptions: CommandFactoryOptions = {\n afs: this.afs,\n argv: normalizedArgs,\n cwd: this.options.cwd,\n onResult: (result) => {\n commandResult = result;\n },\n };\n\n // Build yargs parser with all commands\n let parser = yargs(normalizedArgs)\n .scriptName(\"afs\")\n .usage(\"$0 <command> [options]\")\n .option(\"json\", {\n type: \"boolean\",\n description: \"Output in JSON format\",\n global: true,\n })\n .option(\"yaml\", {\n type: \"boolean\",\n description: \"Output in YAML format\",\n global: true,\n })\n .option(\"view\", {\n type: \"string\",\n choices: [\"default\", \"llm\", \"human\"],\n default: \"default\",\n description: \"Output view format\",\n global: true,\n })\n .option(\"interactive\", {\n alias: \"i\",\n type: \"boolean\",\n description: \"Start interactive REPL mode\",\n global: false,\n })\n .help(true)\n .alias(\"h\", \"help\")\n .version(this.options.version || \"unknown\")\n .alias(\"v\", \"version\")\n .demandCommand()\n .showHelpOnFail(true)\n .exitProcess(false);\n\n // Register all commands from factories\n for (const factory of commandFactories) {\n parser = parser.command(factory(factoryOptions));\n }\n\n // Parse and execute\n try {\n let output: string | undefined;\n let error: Error | undefined;\n\n const parsed = await parser.parseAsync(normalizedArgs, {}, (e, _, o) => {\n if (e) error = e;\n output = o;\n });\n\n if (error) {\n return {\n success: false,\n command: normalizedArgs[0] || \"unknown\",\n result: undefined,\n formatted: output!,\n error: { message: error.message },\n };\n }\n\n if (parsed.help) {\n return {\n success: true,\n command: \"help\",\n formatted: output!,\n };\n }\n if (parsed.version) {\n return {\n success: true,\n command: \"version\",\n formatted: output!,\n };\n }\n\n if (!commandResult) {\n throw new Error(\"Command not found\");\n }\n\n // Always use the formatter - it handles json/llm/human views\n const view: ViewType = outputOptions.json\n ? \"json\"\n : outputOptions.yaml\n ? \"yaml\"\n : outputOptions.view;\n const formatted = commandResult.format(commandResult.result, view, {\n path: this.extractPath(normalizedArgs),\n });\n\n // Check if command indicated failure\n if (commandResult.error) {\n return {\n success: false,\n command: commandResult.command,\n result: commandResult.result,\n formatted,\n error: commandResult.error,\n };\n }\n\n return {\n success: true,\n command: commandResult.command,\n result: commandResult.result,\n formatted,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n return {\n success: false,\n command: normalizedArgs[0] || \"unknown\",\n result: undefined,\n formatted: `ERROR: ${errorMessage}`,\n error: { message: errorMessage },\n };\n }\n }\n\n /**\n * Normalize argv to an array of strings\n */\n private normalizeArgv(argv: string | string[]): string[] {\n if (typeof argv === \"string\") {\n // Parse quoted strings properly\n return this.tokenize(argv);\n }\n\n // Filter array input\n const filtered: string[] = [];\n let foundCommand = false;\n\n for (const arg of argv) {\n // Skip node and script paths (for process.argv format)\n if (\n !foundCommand &&\n (arg.includes(\"node\") || arg.includes(\"bun\") || arg.endsWith(\".js\") || arg.endsWith(\".ts\"))\n ) {\n continue;\n }\n\n // Skip \"afs\" prefix\n if (!foundCommand && arg === \"afs\") {\n continue;\n }\n\n filtered.push(arg);\n if (!arg.startsWith(\"-\")) {\n foundCommand = true;\n }\n }\n\n return filtered;\n }\n\n /**\n * Tokenize a command string, respecting quotes\n */\n private tokenize(input: string): string[] {\n const tokens: string[] = [];\n let current = \"\";\n let inQuote = false;\n let quoteChar = \"\";\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i]!;\n\n if (inQuote) {\n if (char === quoteChar) {\n inQuote = false;\n } else {\n current += char;\n }\n } else if (char === '\"' || char === \"'\") {\n inQuote = true;\n quoteChar = char;\n } else if (char === \" \" || char === \"\\t\") {\n if (current) {\n tokens.push(current);\n current = \"\";\n }\n } else {\n current += char;\n }\n }\n\n if (current) {\n tokens.push(current);\n }\n\n // Filter out \"afs\" prefix if present\n if (tokens[0] === \"afs\") {\n tokens.shift();\n }\n\n return tokens;\n }\n\n /**\n * Extract output options from args\n */\n private extractOutputOptions(args: string[]): {\n json: boolean;\n yaml: boolean;\n view: ViewType;\n } {\n let json = false;\n let yaml = false;\n let view: ViewType | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === \"--json\") json = true;\n if (arg === \"--yaml\") yaml = true;\n if (arg.startsWith(\"--view=\")) {\n view = arg.slice(7) as ViewType;\n } else if (arg === \"--view\" && args[i + 1] && !args[i + 1]!.startsWith(\"-\")) {\n view = args[i + 1] as ViewType;\n }\n }\n\n // Default to human view in TTY, default otherwise\n if (!view) {\n view = this.options.tty ? \"human\" : \"default\";\n }\n\n return { json, yaml, view };\n }\n\n /**\n * Extract path from args (first non-option argument after command)\n */\n private extractPath(args: string[]): string | undefined {\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (!arg.startsWith(\"-\")) {\n return arg;\n }\n }\n return undefined;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["formatted"],"sources":["../../../src/core/executor/index.ts"],"sourcesContent":["/**\n * AFS Command Executor\n *\n * Unified command execution interface using yargs and CommandFactory pattern.\n */\n\nimport type { AFS } from \"@aigne/afs\";\nimport yargs from \"yargs\";\nimport {\n type CommandFactoryOptions,\n type CommandOutput,\n commandFactories,\n} from \"../commands/index.js\";\nimport type { ViewType } from \"../types.js\";\n\n/**\n * Result from executing a command\n */\nexport interface ExecuteResult {\n /** Whether the command executed successfully */\n success: boolean;\n /** The command that was executed (ls, read, write, etc.) */\n command: string;\n /** The raw result data from AFS */\n result?: unknown;\n /** Formatted output string */\n formatted: string;\n /** Error info if the command failed */\n error?: {\n /** Exit code (from ExitCode enum) */\n code?: number;\n /** Error message */\n message: string;\n };\n}\n\n/**\n * Options for the executor\n */\nexport interface ExecutorOptions {\n /** Whether the output is for a TTY (enables colors) */\n tty?: boolean;\n /** Current working directory (for explore command) */\n cwd?: string;\n /** Version of the AFS CLI */\n version?: string;\n}\n\n/** Known command names and aliases for suggestion matching. */\nconst KNOWN_COMMANDS = [\n \"ls\",\n \"list\",\n \"read\",\n \"cat\",\n \"write\",\n \"delete\",\n \"rm\",\n \"stat\",\n \"exec\",\n \"explain\",\n \"search\",\n \"grep\",\n \"find\",\n \"mount\",\n \"serve\",\n \"explore\",\n \"service\",\n \"connect\",\n \"vault\",\n];\n\n/**\n * AFS Command Executor\n *\n * Provides a unified interface for executing AFS commands using yargs.\n *\n * @example\n * ```typescript\n * const executor = new AFSCommandExecutor(afs, { tty: false });\n * const result = await executor.execute(\"afs ls /path --depth=2\");\n * console.log(result.formatted);\n * ```\n */\nexport class AFSCommandExecutor {\n private afs?: AFS;\n private options: ExecutorOptions;\n\n constructor(afs?: AFS, options?: ExecutorOptions) {\n this.afs = afs;\n this.options = options ?? {};\n }\n\n /**\n * Execute an AFS command\n *\n * @param argv - Command string or array of arguments\n * - String: \"afs ls /path --depth=2\" or \"ls /path --depth=2\"\n * - Array: [\"ls\", \"/path\", \"--depth=2\"]\n * @returns Execution result with formatted output\n */\n async execute(argv: string | string[]): Promise<ExecuteResult> {\n const normalizedArgs = this.normalizeArgv(argv);\n\n // Capture command result\n let commandResult: CommandOutput | undefined;\n\n // Determine output format from args\n const outputOptions = this.extractOutputOptions(normalizedArgs);\n\n // Create factory options\n const factoryOptions: CommandFactoryOptions = {\n afs: this.afs,\n argv: normalizedArgs,\n cwd: this.options.cwd,\n onResult: (result) => {\n commandResult = result;\n },\n };\n\n // Capture fail() info so we can format errors ourselves\n let failMsg: string | undefined;\n let failErr: Error | undefined;\n\n // Build yargs parser with all commands\n let parser = yargs(normalizedArgs)\n .scriptName(\"afs\")\n .usage(\"$0 <command> [options]\")\n .option(\"json\", {\n type: \"boolean\",\n description: \"Output in JSON format\",\n global: true,\n })\n .option(\"yaml\", {\n type: \"boolean\",\n description: \"Output in YAML format\",\n global: true,\n })\n .option(\"view\", {\n type: \"string\",\n choices: [\"default\", \"llm\", \"human\"],\n default: \"default\",\n description: \"Output view format\",\n global: true,\n })\n .option(\"interactive\", {\n alias: \"i\",\n type: \"boolean\",\n description: \"Start interactive REPL mode\",\n global: false,\n })\n .help(true)\n .alias(\"h\", \"help\")\n .version(this.options.version || \"unknown\")\n .alias(\"v\", \"version\")\n .demandCommand()\n .strictCommands()\n .exitProcess(false)\n .fail((msg, err) => {\n // Prevent yargs from writing to stderr; we handle output ourselves.\n failErr = err || new Error(msg || \"Unknown error\");\n failMsg = msg;\n });\n\n // Register all commands from factories\n for (const factory of commandFactories) {\n parser = parser.command(factory(factoryOptions));\n }\n\n // Parse and execute\n try {\n let output: string | undefined;\n\n await parser.parseAsync(normalizedArgs, {}, (_e, _, o) => {\n output = o;\n });\n\n if (failErr) {\n const formatted = await this.formatFailure(normalizedArgs, failMsg, parser);\n return {\n success: false,\n command: normalizedArgs[0] || \"unknown\",\n result: undefined,\n formatted,\n error: { message: failMsg || failErr.message },\n };\n }\n\n if (output) {\n // --help or --version produced output\n return {\n success: true,\n command: \"help\",\n formatted: output,\n };\n }\n\n if (!commandResult) {\n const formatted = await this.formatFailure(normalizedArgs, undefined, parser);\n return {\n success: false,\n command: normalizedArgs[0] || \"unknown\",\n result: undefined,\n formatted,\n error: { message: `Unknown command: \"${normalizedArgs[0] || \"\"}\"` },\n };\n }\n\n // Always use the formatter - it handles json/llm/human views\n // Explicit format flags (--json, --yaml, --view) take precedence over command viewOverride (e.g. -l)\n const view: ViewType = outputOptions.json\n ? \"json\"\n : outputOptions.yaml\n ? \"yaml\"\n : (commandResult.viewOverride ?? outputOptions.view);\n const formatted = commandResult.format(commandResult.result, view, {\n path: this.extractPath(normalizedArgs),\n });\n\n // Check if command indicated failure\n if (commandResult.error) {\n return {\n success: false,\n command: commandResult.command,\n result: commandResult.result,\n formatted,\n error: commandResult.error,\n };\n }\n\n return {\n success: true,\n command: commandResult.command,\n result: commandResult.result,\n formatted,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n return {\n success: false,\n command: normalizedArgs[0] || \"unknown\",\n result: undefined,\n formatted: `ERROR: ${errorMessage}`,\n error: { message: errorMessage },\n };\n }\n }\n\n /**\n * Format a friendly error message for unknown/invalid commands.\n */\n private async formatFailure(\n args: string[],\n failMsg: string | undefined,\n parser: ReturnType<typeof yargs>,\n ): Promise<string> {\n const cmd = args[0] || \"\";\n const lines: string[] = [];\n\n // Show what went wrong\n if (cmd && failMsg?.includes(\"Unknown command\")) {\n lines.push(`Unknown command: \"${cmd}\"`);\n const suggestions = suggestCommands(cmd);\n if (suggestions.length > 0) {\n lines.push(\"\");\n lines.push(`Did you mean?`);\n for (const s of suggestions) {\n lines.push(` afs ${s}`);\n }\n }\n } else if (failMsg) {\n lines.push(failMsg);\n } else {\n lines.push(`Unknown command: \"${cmd}\"`);\n }\n\n // Append help text\n lines.push(\"\");\n try {\n const helpText = await parser.getHelp();\n lines.push(helpText);\n } catch {\n lines.push('Run \"afs --help\" to see available commands.');\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Normalize argv to an array of strings\n */\n private normalizeArgv(argv: string | string[]): string[] {\n if (typeof argv === \"string\") {\n // Parse quoted strings properly\n return this.tokenize(argv);\n }\n\n // Filter array input\n const filtered: string[] = [];\n let foundCommand = false;\n\n for (const arg of argv) {\n // Skip node and script paths (for process.argv format)\n if (\n !foundCommand &&\n (arg.includes(\"node\") || arg.includes(\"bun\") || arg.endsWith(\".js\") || arg.endsWith(\".ts\"))\n ) {\n continue;\n }\n\n // Skip \"afs\" prefix\n if (!foundCommand && arg === \"afs\") {\n continue;\n }\n\n filtered.push(arg);\n if (!arg.startsWith(\"-\")) {\n foundCommand = true;\n }\n }\n\n return filtered;\n }\n\n /**\n * Tokenize a command string, respecting quotes\n */\n private tokenize(input: string): string[] {\n const tokens: string[] = [];\n let current = \"\";\n let inQuote = false;\n let quoteChar = \"\";\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i]!;\n\n if (inQuote) {\n if (char === quoteChar) {\n inQuote = false;\n } else {\n current += char;\n }\n } else if (char === '\"' || char === \"'\") {\n inQuote = true;\n quoteChar = char;\n } else if (char === \" \" || char === \"\\t\") {\n if (current) {\n tokens.push(current);\n current = \"\";\n }\n } else {\n current += char;\n }\n }\n\n if (current) {\n tokens.push(current);\n }\n\n // Filter out \"afs\" prefix if present\n if (tokens[0] === \"afs\") {\n tokens.shift();\n }\n\n return tokens;\n }\n\n /**\n * Extract output options from args\n */\n private extractOutputOptions(args: string[]): {\n json: boolean;\n yaml: boolean;\n view: ViewType;\n } {\n let json = false;\n let yaml = false;\n let view: ViewType | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === \"--json\") json = true;\n if (arg === \"--yaml\") yaml = true;\n if (arg.startsWith(\"--view=\")) {\n view = arg.slice(7) as ViewType;\n } else if (arg === \"--view\" && args[i + 1] && !args[i + 1]!.startsWith(\"-\")) {\n view = args[i + 1] as ViewType;\n }\n }\n\n // Default to human view in TTY, default otherwise\n if (!view) {\n view = this.options.tty ? \"human\" : \"default\";\n }\n\n return { json, yaml, view };\n }\n\n /**\n * Extract path from args (first non-option argument after command)\n */\n private extractPath(args: string[]): string | undefined {\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (!arg.startsWith(\"-\")) {\n return arg;\n }\n }\n return undefined;\n }\n}\n\n/** Levenshtein distance between two strings. */\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0) as number[]);\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n dp[i]![j] =\n a[i - 1] === b[j - 1]\n ? dp[i - 1]![j - 1]!\n : 1 + Math.min(dp[i - 1]![j]!, dp[i]![j - 1]!, dp[i - 1]![j - 1]!);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest known commands similar to the given input. */\nfunction suggestCommands(input: string): string[] {\n const lower = input.toLowerCase();\n const scored = KNOWN_COMMANDS.map((cmd) => ({\n cmd,\n dist: levenshtein(lower, cmd),\n maxLen: Math.max(lower.length, cmd.length),\n }))\n // Only suggest when edit distance < 50% of the longer string\n .filter((x) => x.dist > 0 && x.dist < x.maxLen * 0.5)\n .sort((a, b) => a.dist - b.dist);\n return scored.map((x) => x.cmd).slice(0, 3);\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { formatDeleteOutput } from "./delete.mjs";
2
2
  import { formatExecOutput } from "./exec.mjs";
3
3
  import { formatExplainOutput } from "./explain.mjs";
4
+ import "./install.mjs";
4
5
  import { formatLsOutput } from "./ls.mjs";
5
6
  import { formatMountListOutput } from "./mount.mjs";
6
7
  import { formatReadOutput } from "./read.mjs";
@@ -0,0 +1,40 @@
1
+
2
+ //#region src/core/formatters/install.ts
3
+ function formatInstallAddOutput(result, view) {
4
+ if (view === "json") return JSON.stringify(result, null, 2);
5
+ return `Installed "${result.programName}" (${result.programId}) at ${result.mountPath}`;
6
+ }
7
+ function formatInstallListOutput(programs, view) {
8
+ if (view === "json") return JSON.stringify(programs, null, 2);
9
+ if (programs.length === 0) return "No programs installed";
10
+ return ["Installed programs:", ...programs.map((p) => ` ${p.id} ${p.name} ${p.entrypoint} ${p.mountPath}`)].join("\n");
11
+ }
12
+ function formatInstallRemoveOutput(result, view) {
13
+ if (view === "json") return JSON.stringify(result, null, 2);
14
+ const suffix = result.purgedData ? " (data purged)" : "";
15
+ return `Removed program "${result.programId}"${suffix}`;
16
+ }
17
+ function formatConfigureListOutput(result, view) {
18
+ if (view === "json") return JSON.stringify(result, null, 2);
19
+ if (result.mounts.length === 0) return `Program "${result.programId}" has no mounts`;
20
+ const lines = result.mounts.map((m) => {
21
+ const uri = m.configuredUri || m.uri;
22
+ const req = m.required ? "required" : "optional";
23
+ const status = m.hasCredentials || m.configuredUri || m.hasOptions ? "configured" : "not configured";
24
+ return ` ${m.path.padEnd(14)} ${uri.padEnd(24)} (${req}) ${status}`;
25
+ });
26
+ return [`Mounts for "${result.programId}":`, ...lines].join("\n");
27
+ }
28
+ function formatConfigureOutput(result, view) {
29
+ if (view === "json") return JSON.stringify(result, null, 2);
30
+ if (result.configuredMounts.length === 0) return `Program "${result.programId}" has no mounts to configure`;
31
+ const lines = result.configuredMounts.map((m) => ` ${m.target} → ${m.uri}`);
32
+ return [`Configured ${result.configuredMounts.length} mount(s) for "${result.programId}":`, ...lines].join("\n");
33
+ }
34
+
35
+ //#endregion
36
+ exports.formatConfigureListOutput = formatConfigureListOutput;
37
+ exports.formatConfigureOutput = formatConfigureOutput;
38
+ exports.formatInstallAddOutput = formatInstallAddOutput;
39
+ exports.formatInstallListOutput = formatInstallListOutput;
40
+ exports.formatInstallRemoveOutput = formatInstallRemoveOutput;
@@ -0,0 +1 @@
1
+ import "../../config/program-install.mjs";
@@ -0,0 +1,36 @@
1
+ //#region src/core/formatters/install.ts
2
+ function formatInstallAddOutput(result, view) {
3
+ if (view === "json") return JSON.stringify(result, null, 2);
4
+ return `Installed "${result.programName}" (${result.programId}) at ${result.mountPath}`;
5
+ }
6
+ function formatInstallListOutput(programs, view) {
7
+ if (view === "json") return JSON.stringify(programs, null, 2);
8
+ if (programs.length === 0) return "No programs installed";
9
+ return ["Installed programs:", ...programs.map((p) => ` ${p.id} ${p.name} ${p.entrypoint} ${p.mountPath}`)].join("\n");
10
+ }
11
+ function formatInstallRemoveOutput(result, view) {
12
+ if (view === "json") return JSON.stringify(result, null, 2);
13
+ const suffix = result.purgedData ? " (data purged)" : "";
14
+ return `Removed program "${result.programId}"${suffix}`;
15
+ }
16
+ function formatConfigureListOutput(result, view) {
17
+ if (view === "json") return JSON.stringify(result, null, 2);
18
+ if (result.mounts.length === 0) return `Program "${result.programId}" has no mounts`;
19
+ const lines = result.mounts.map((m) => {
20
+ const uri = m.configuredUri || m.uri;
21
+ const req = m.required ? "required" : "optional";
22
+ const status = m.hasCredentials || m.configuredUri || m.hasOptions ? "configured" : "not configured";
23
+ return ` ${m.path.padEnd(14)} ${uri.padEnd(24)} (${req}) ${status}`;
24
+ });
25
+ return [`Mounts for "${result.programId}":`, ...lines].join("\n");
26
+ }
27
+ function formatConfigureOutput(result, view) {
28
+ if (view === "json") return JSON.stringify(result, null, 2);
29
+ if (result.configuredMounts.length === 0) return `Program "${result.programId}" has no mounts to configure`;
30
+ const lines = result.configuredMounts.map((m) => ` ${m.target} → ${m.uri}`);
31
+ return [`Configured ${result.configuredMounts.length} mount(s) for "${result.programId}":`, ...lines].join("\n");
32
+ }
33
+
34
+ //#endregion
35
+ export { formatConfigureListOutput, formatConfigureOutput, formatInstallAddOutput, formatInstallListOutput, formatInstallRemoveOutput };
36
+ //# sourceMappingURL=install.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.mjs","names":[],"sources":["../../../src/core/formatters/install.ts"],"sourcesContent":["/**\n * Install Command Formatters\n */\n\nimport type {\n ConfigureResult,\n InstalledProgram,\n InstallResult,\n MountStatus,\n RemoveResult,\n} from \"../../config/program-install.js\";\nimport type { ViewType } from \"../types.js\";\n\nexport function formatInstallAddOutput(result: InstallResult, view?: ViewType): string {\n if (view === \"json\") {\n return JSON.stringify(result, null, 2);\n }\n return `Installed \"${result.programName}\" (${result.programId}) at ${result.mountPath}`;\n}\n\nexport function formatInstallListOutput(programs: InstalledProgram[], view?: ViewType): string {\n if (view === \"json\") {\n return JSON.stringify(programs, null, 2);\n }\n if (programs.length === 0) {\n return \"No programs installed\";\n }\n const lines = programs.map((p) => ` ${p.id} ${p.name} ${p.entrypoint} ${p.mountPath}`);\n return [\"Installed programs:\", ...lines].join(\"\\n\");\n}\n\nexport function formatInstallRemoveOutput(result: RemoveResult, view?: ViewType): string {\n if (view === \"json\") {\n return JSON.stringify(result, null, 2);\n }\n const suffix = result.purgedData ? \" (data purged)\" : \"\";\n return `Removed program \"${result.programId}\"${suffix}`;\n}\n\nexport function formatConfigureListOutput(\n result: { programId: string; mounts: MountStatus[] },\n view?: ViewType,\n): string {\n if (view === \"json\") {\n return JSON.stringify(result, null, 2);\n }\n if (result.mounts.length === 0) {\n return `Program \"${result.programId}\" has no mounts`;\n }\n const lines = result.mounts.map((m) => {\n const uri = m.configuredUri || m.uri;\n const req = m.required ? \"required\" : \"optional\";\n const status =\n m.hasCredentials || m.configuredUri || m.hasOptions ? \"configured\" : \"not configured\";\n return ` ${m.path.padEnd(14)} ${uri.padEnd(24)} (${req}) ${status}`;\n });\n return [`Mounts for \"${result.programId}\":`, ...lines].join(\"\\n\");\n}\n\nexport function formatConfigureOutput(result: ConfigureResult, view?: ViewType): string {\n if (view === \"json\") {\n return JSON.stringify(result, null, 2);\n }\n if (result.configuredMounts.length === 0) {\n return `Program \"${result.programId}\" has no mounts to configure`;\n }\n const lines = result.configuredMounts.map((m) => ` ${m.target} → ${m.uri}`);\n return [\n `Configured ${result.configuredMounts.length} mount(s) for \"${result.programId}\":`,\n ...lines,\n ].join(\"\\n\");\n}\n"],"mappings":";AAaA,SAAgB,uBAAuB,QAAuB,MAAyB;AACrF,KAAI,SAAS,OACX,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;AAExC,QAAO,cAAc,OAAO,YAAY,KAAK,OAAO,UAAU,OAAO,OAAO;;AAG9E,SAAgB,wBAAwB,UAA8B,MAAyB;AAC7F,KAAI,SAAS,OACX,QAAO,KAAK,UAAU,UAAU,MAAM,EAAE;AAE1C,KAAI,SAAS,WAAW,EACtB,QAAO;AAGT,QAAO,CAAC,uBAAuB,GADjB,SAAS,KAAK,MAAM,KAAK,EAAE,GAAG,IAAI,EAAE,KAAK,IAAI,EAAE,WAAW,IAAI,EAAE,YAAY,CAClD,CAAC,KAAK,KAAK;;AAGrD,SAAgB,0BAA0B,QAAsB,MAAyB;AACvF,KAAI,SAAS,OACX,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;CAExC,MAAM,SAAS,OAAO,aAAa,mBAAmB;AACtD,QAAO,oBAAoB,OAAO,UAAU,GAAG;;AAGjD,SAAgB,0BACd,QACA,MACQ;AACR,KAAI,SAAS,OACX,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;AAExC,KAAI,OAAO,OAAO,WAAW,EAC3B,QAAO,YAAY,OAAO,UAAU;CAEtC,MAAM,QAAQ,OAAO,OAAO,KAAK,MAAM;EACrC,MAAM,MAAM,EAAE,iBAAiB,EAAE;EACjC,MAAM,MAAM,EAAE,WAAW,aAAa;EACtC,MAAM,SACJ,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,aAAa,eAAe;AACvE,SAAO,KAAK,EAAE,KAAK,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,IAAI,KAAK;GAC7D;AACF,QAAO,CAAC,eAAe,OAAO,UAAU,KAAK,GAAG,MAAM,CAAC,KAAK,KAAK;;AAGnE,SAAgB,sBAAsB,QAAyB,MAAyB;AACtF,KAAI,SAAS,OACX,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;AAExC,KAAI,OAAO,iBAAiB,WAAW,EACrC,QAAO,YAAY,OAAO,UAAU;CAEtC,MAAM,QAAQ,OAAO,iBAAiB,KAAK,MAAM,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM;AAC5E,QAAO,CACL,cAAc,OAAO,iBAAiB,OAAO,iBAAiB,OAAO,UAAU,KAC/E,GAAG,MACJ,CAAC,KAAK,KAAK"}
@@ -0,0 +1,36 @@
1
+
2
+ //#region src/core/formatters/vault.ts
3
+ function formatVaultInitOutput(result, view) {
4
+ if (view === "json") return JSON.stringify(result, null, 2);
5
+ const lines = [`Vault initialized at ${result.vaultPath}`];
6
+ if (result.migrated && result.migrated > 0) lines.push(`Migrated ${result.migrated} credential(s) from credentials.toml`);
7
+ return lines.join("\n");
8
+ }
9
+ function formatVaultGetOutput(result, view) {
10
+ if (view === "json") return JSON.stringify(result, null, 2);
11
+ return result.value;
12
+ }
13
+ function formatVaultSetOutput(result, view) {
14
+ if (view === "json") return JSON.stringify({
15
+ ...result,
16
+ success: true
17
+ }, null, 2);
18
+ return `OK ${result.group}/${result.name}`;
19
+ }
20
+ function formatVaultDeleteOutput(result, view) {
21
+ if (view === "json") return JSON.stringify(result, null, 2);
22
+ const target = result.name ? `${result.group}/${result.name}` : result.group;
23
+ return result.deleted ? `Deleted ${target}` : `Not found: ${target}`;
24
+ }
25
+ function formatVaultListOutput(result, view) {
26
+ if (view === "json") return JSON.stringify(result, null, 2);
27
+ if (result.secrets.length === 0) return result.group ? `No secrets in group: ${result.group}` : "Vault is empty";
28
+ return result.secrets.join("\n");
29
+ }
30
+
31
+ //#endregion
32
+ exports.formatVaultDeleteOutput = formatVaultDeleteOutput;
33
+ exports.formatVaultGetOutput = formatVaultGetOutput;
34
+ exports.formatVaultInitOutput = formatVaultInitOutput;
35
+ exports.formatVaultListOutput = formatVaultListOutput;
36
+ exports.formatVaultSetOutput = formatVaultSetOutput;
@@ -0,0 +1,32 @@
1
+ //#region src/core/formatters/vault.ts
2
+ function formatVaultInitOutput(result, view) {
3
+ if (view === "json") return JSON.stringify(result, null, 2);
4
+ const lines = [`Vault initialized at ${result.vaultPath}`];
5
+ if (result.migrated && result.migrated > 0) lines.push(`Migrated ${result.migrated} credential(s) from credentials.toml`);
6
+ return lines.join("\n");
7
+ }
8
+ function formatVaultGetOutput(result, view) {
9
+ if (view === "json") return JSON.stringify(result, null, 2);
10
+ return result.value;
11
+ }
12
+ function formatVaultSetOutput(result, view) {
13
+ if (view === "json") return JSON.stringify({
14
+ ...result,
15
+ success: true
16
+ }, null, 2);
17
+ return `OK ${result.group}/${result.name}`;
18
+ }
19
+ function formatVaultDeleteOutput(result, view) {
20
+ if (view === "json") return JSON.stringify(result, null, 2);
21
+ const target = result.name ? `${result.group}/${result.name}` : result.group;
22
+ return result.deleted ? `Deleted ${target}` : `Not found: ${target}`;
23
+ }
24
+ function formatVaultListOutput(result, view) {
25
+ if (view === "json") return JSON.stringify(result, null, 2);
26
+ if (result.secrets.length === 0) return result.group ? `No secrets in group: ${result.group}` : "Vault is empty";
27
+ return result.secrets.join("\n");
28
+ }
29
+
30
+ //#endregion
31
+ export { formatVaultDeleteOutput, formatVaultGetOutput, formatVaultInitOutput, formatVaultListOutput, formatVaultSetOutput };
32
+ //# sourceMappingURL=vault.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.mjs","names":[],"sources":["../../../src/core/formatters/vault.ts"],"sourcesContent":["/**\n * vault Formatter - Core Implementation\n *\n * Formats vault command output without colors.\n */\n\nimport type { ViewType } from \"../types.js\";\n\nexport interface VaultInitResult {\n success: boolean;\n vaultPath: string;\n migrated?: number;\n}\n\nexport interface VaultGetResult {\n group: string;\n name: string;\n value: string;\n}\n\nexport interface VaultSetResult {\n group: string;\n name: string;\n}\n\nexport interface VaultDeleteResult {\n group: string;\n name?: string;\n deleted: boolean;\n}\n\nexport interface VaultListResult {\n group?: string;\n secrets: string[];\n}\n\nexport function formatVaultInitOutput(result: VaultInitResult, view: ViewType): string {\n if (view === \"json\") return JSON.stringify(result, null, 2);\n const lines = [`Vault initialized at ${result.vaultPath}`];\n if (result.migrated && result.migrated > 0) {\n lines.push(`Migrated ${result.migrated} credential(s) from credentials.toml`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function formatVaultGetOutput(result: VaultGetResult, view: ViewType): string {\n if (view === \"json\") return JSON.stringify(result, null, 2);\n return result.value;\n}\n\nexport function formatVaultSetOutput(result: VaultSetResult, view: ViewType): string {\n if (view === \"json\") return JSON.stringify({ ...result, success: true }, null, 2);\n return `OK ${result.group}/${result.name}`;\n}\n\nexport function formatVaultDeleteOutput(result: VaultDeleteResult, view: ViewType): string {\n if (view === \"json\") return JSON.stringify(result, null, 2);\n const target = result.name ? `${result.group}/${result.name}` : result.group;\n return result.deleted ? `Deleted ${target}` : `Not found: ${target}`;\n}\n\nexport function formatVaultListOutput(result: VaultListResult, view: ViewType): string {\n if (view === \"json\") return JSON.stringify(result, null, 2);\n if (result.secrets.length === 0) {\n return result.group ? `No secrets in group: ${result.group}` : \"Vault is empty\";\n }\n return result.secrets.join(\"\\n\");\n}\n"],"mappings":";AAoCA,SAAgB,sBAAsB,QAAyB,MAAwB;AACrF,KAAI,SAAS,OAAQ,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;CAC3D,MAAM,QAAQ,CAAC,wBAAwB,OAAO,YAAY;AAC1D,KAAI,OAAO,YAAY,OAAO,WAAW,EACvC,OAAM,KAAK,YAAY,OAAO,SAAS,sCAAsC;AAE/E,QAAO,MAAM,KAAK,KAAK;;AAGzB,SAAgB,qBAAqB,QAAwB,MAAwB;AACnF,KAAI,SAAS,OAAQ,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;AAC3D,QAAO,OAAO;;AAGhB,SAAgB,qBAAqB,QAAwB,MAAwB;AACnF,KAAI,SAAS,OAAQ,QAAO,KAAK,UAAU;EAAE,GAAG;EAAQ,SAAS;EAAM,EAAE,MAAM,EAAE;AACjF,QAAO,MAAM,OAAO,MAAM,GAAG,OAAO;;AAGtC,SAAgB,wBAAwB,QAA2B,MAAwB;AACzF,KAAI,SAAS,OAAQ,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;CAC3D,MAAM,SAAS,OAAO,OAAO,GAAG,OAAO,MAAM,GAAG,OAAO,SAAS,OAAO;AACvE,QAAO,OAAO,UAAU,WAAW,WAAW,cAAc;;AAG9D,SAAgB,sBAAsB,QAAyB,MAAwB;AACrF,KAAI,SAAS,OAAQ,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;AAC3D,KAAI,OAAO,QAAQ,WAAW,EAC5B,QAAO,OAAO,QAAQ,wBAAwB,OAAO,UAAU;AAEjE,QAAO,OAAO,QAAQ,KAAK,KAAK"}
@@ -200,9 +200,26 @@ function renderFormHTML(schema, title, nonce) {
200
200
  const label = prop.description || key;
201
201
  const isSensitive = prop.sensitive === true;
202
202
  const isRequired = required.has(key);
203
- const inputType = isSensitive ? "password" : "text";
204
- const defaultValue = prop.default != null ? String(prop.default) : "";
205
- fields += `
203
+ const defaultValue = prop.default != null ? prop.default : "";
204
+ if (prop.type === "boolean") {
205
+ const checked = defaultValue === true || defaultValue === "true";
206
+ fields += `
207
+ <div style="margin-bottom: 12px;">
208
+ <label style="display: flex; align-items: center; gap: 8px; font-weight: 600; cursor: pointer;">
209
+ <input type="hidden" name="${escapeHTML(key)}" value="false" />
210
+ <input
211
+ type="checkbox"
212
+ name="${escapeHTML(key)}"
213
+ value="true"
214
+ ${checked ? "checked" : ""}
215
+ style="width: 18px; height: 18px;"
216
+ />
217
+ ${escapeHTML(label)}
218
+ </label>
219
+ </div>`;
220
+ } else {
221
+ const inputType = isSensitive ? "password" : "text";
222
+ fields += `
206
223
  <div style="margin-bottom: 12px;">
207
224
  <label for="${escapeHTML(key)}" style="display: block; font-weight: 600; margin-bottom: 4px;">
208
225
  ${escapeHTML(label)}${isRequired ? " *" : ""}
@@ -211,12 +228,13 @@ function renderFormHTML(schema, title, nonce) {
211
228
  type="${inputType}"
212
229
  id="${escapeHTML(key)}"
213
230
  name="${escapeHTML(key)}"
214
- value="${escapeHTML(defaultValue)}"
231
+ value="${escapeHTML(String(defaultValue))}"
215
232
  ${isRequired ? "required" : ""}
216
233
  style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px;"
217
234
  autocomplete="off"
218
235
  />
219
236
  </div>`;
237
+ }
220
238
  }
221
239
  return `<!DOCTYPE html>
222
240
  <html>
@@ -199,9 +199,26 @@ function renderFormHTML(schema, title, nonce) {
199
199
  const label = prop.description || key;
200
200
  const isSensitive = prop.sensitive === true;
201
201
  const isRequired = required.has(key);
202
- const inputType = isSensitive ? "password" : "text";
203
- const defaultValue = prop.default != null ? String(prop.default) : "";
204
- fields += `
202
+ const defaultValue = prop.default != null ? prop.default : "";
203
+ if (prop.type === "boolean") {
204
+ const checked = defaultValue === true || defaultValue === "true";
205
+ fields += `
206
+ <div style="margin-bottom: 12px;">
207
+ <label style="display: flex; align-items: center; gap: 8px; font-weight: 600; cursor: pointer;">
208
+ <input type="hidden" name="${escapeHTML(key)}" value="false" />
209
+ <input
210
+ type="checkbox"
211
+ name="${escapeHTML(key)}"
212
+ value="true"
213
+ ${checked ? "checked" : ""}
214
+ style="width: 18px; height: 18px;"
215
+ />
216
+ ${escapeHTML(label)}
217
+ </label>
218
+ </div>`;
219
+ } else {
220
+ const inputType = isSensitive ? "password" : "text";
221
+ fields += `
205
222
  <div style="margin-bottom: 12px;">
206
223
  <label for="${escapeHTML(key)}" style="display: block; font-weight: 600; margin-bottom: 4px;">
207
224
  ${escapeHTML(label)}${isRequired ? " *" : ""}
@@ -210,12 +227,13 @@ function renderFormHTML(schema, title, nonce) {
210
227
  type="${inputType}"
211
228
  id="${escapeHTML(key)}"
212
229
  name="${escapeHTML(key)}"
213
- value="${escapeHTML(defaultValue)}"
230
+ value="${escapeHTML(String(defaultValue))}"
214
231
  ${isRequired ? "required" : ""}
215
232
  style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px;"
216
233
  autocomplete="off"
217
234
  />
218
235
  </div>`;
236
+ }
219
237
  }
220
238
  return `<!DOCTYPE html>
221
239
  <html>
@@ -1 +1 @@
1
- {"version":3,"file":"auth-server.mjs","names":[],"sources":["../../src/credential/auth-server.ts"],"sourcesContent":["/**\n * Temporary HTTP Server for credential collection.\n *\n * Supports two modes:\n * 1. Form collection: GET /auth renders a form from JSON Schema, POST /auth returns submitted data\n * 2. OAuth callback: GET /callback captures query params for waitForCallback()\n *\n * Security:\n * - Binds to 127.0.0.1 only (no external connections)\n * - One-time nonce per request to prevent CSRF\n * - Auto-closes after 5 minutes\n * - POST data is never logged\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\n\nconst DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes\nconst MAX_PORT_RETRIES = 3;\n\nexport interface AuthServer {\n /** Base URL of the server, e.g. http://127.0.0.1:12345 */\n baseURL: string;\n\n /** Callback URL for OAuth redirects: baseURL + /callback */\n callbackURL: string;\n\n /** Wait for a callback request at /callback. Returns query params or null on timeout/close. */\n waitForCallback(options?: { timeout?: number }): Promise<Record<string, string> | null>;\n\n /**\n * Serve a form for collecting fields, wait for submission.\n * Returns submitted values or null on timeout/close.\n *\n * @param schema - JSON Schema describing fields to collect\n * @param options - Optional title and timeout\n */\n waitForForm(\n schema: Record<string, any>,\n options?: { title?: string; timeout?: number },\n ): Promise<Record<string, unknown> | null>;\n\n /** Close the server. Idempotent. */\n close(): void;\n\n /** The one-time nonce for this server session */\n readonly nonce: string;\n\n /** The port the server is listening on */\n readonly port: number;\n}\n\nexport interface CreateAuthServerOptions {\n /** Timeout in ms before auto-close. Default: 5 minutes. */\n timeout?: number;\n}\n\n/**\n * Create a temporary auth server bound to 127.0.0.1 with a random port.\n */\nexport async function createAuthServer(options?: CreateAuthServerOptions): Promise<AuthServer> {\n const timeout = options?.timeout ?? DEFAULT_TIMEOUT;\n const nonce = randomBytes(16).toString(\"hex\");\n\n let closed = false;\n let callbackResolve: ((value: Record<string, string> | null) => void) | null = null;\n let formResolve: ((value: Record<string, unknown> | null) => void) | null = null;\n let formSchema: Record<string, any> | null = null;\n let formTitle = \"AFS Credential Collection\";\n\n const server = createServer((req, res) => {\n const url = new URL(req.url || \"/\", `http://127.0.0.1`);\n const pathname = url.pathname;\n\n // CORS headers for local browser forms\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (pathname === \"/callback\") {\n handleCallback(url, req, res, nonce);\n } else if (pathname === \"/auth\" && req.method === \"GET\") {\n handleFormGet(url, res, nonce);\n } else if (pathname === \"/auth\" && req.method === \"POST\") {\n handleFormPost(req, res, nonce, url);\n } else {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\"Not Found\");\n }\n });\n\n function handleCallback(\n url: URL,\n _req: IncomingMessage,\n res: ServerResponse,\n expectedNonce: string,\n ): void {\n const reqNonce = url.searchParams.get(\"nonce\");\n if (reqNonce !== expectedNonce) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden: invalid nonce\");\n return;\n }\n\n const params: Record<string, string> = {};\n for (const [key, value] of url.searchParams.entries()) {\n if (key !== \"nonce\") {\n params[key] = value;\n }\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h2>Authorization complete.</h2><p>You can close this tab.</p></body></html>\",\n );\n\n if (callbackResolve) {\n callbackResolve(params);\n callbackResolve = null;\n }\n }\n\n function handleFormGet(url: URL, res: ServerResponse, expectedNonce: string): void {\n const reqNonce = url.searchParams.get(\"nonce\");\n if (reqNonce !== expectedNonce) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden: invalid nonce\");\n return;\n }\n\n if (!formSchema) {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"No form schema available\");\n return;\n }\n\n const html = renderFormHTML(formSchema, formTitle, expectedNonce);\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n }\n\n function handleFormPost(\n req: IncomingMessage,\n res: ServerResponse,\n expectedNonce: string,\n url: URL,\n ): void {\n const reqNonce = url.searchParams.get(\"nonce\");\n if (reqNonce !== expectedNonce) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden: invalid nonce\");\n return;\n }\n\n let body = \"\";\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString();\n });\n req.on(\"end\", () => {\n let data: Record<string, unknown>;\n const contentType = req.headers[\"content-type\"] || \"\";\n\n if (contentType.includes(\"application/json\")) {\n try {\n data = JSON.parse(body);\n } catch {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"Invalid JSON\");\n return;\n }\n } else {\n // URL-encoded form data\n const params = new URLSearchParams(body);\n data = {};\n for (const [key, value] of params.entries()) {\n if (key !== \"nonce\") {\n data[key] = value;\n }\n }\n // Coerce string values to match schema types (HTML forms always submit strings)\n if (formSchema?.properties) {\n for (const [key, value] of Object.entries(data)) {\n const prop = (formSchema.properties as Record<string, any>)[key];\n if (!prop) continue;\n const strValue = String(value);\n if (prop.type === \"number\" || prop.type === \"integer\") {\n const num = Number(strValue);\n if (!Number.isNaN(num) && strValue !== \"\") data[key] = num;\n } else if (prop.type === \"boolean\") {\n if (strValue === \"true\" || strValue === \"1\") data[key] = true;\n else if (strValue === \"false\" || strValue === \"0\" || strValue === \"\")\n data[key] = false;\n }\n // Remove empty strings for non-string optional fields (let defaults apply)\n if (strValue === \"\" && prop.type !== \"string\") {\n delete data[key];\n }\n }\n }\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n \"<html><body><h2>Credentials received.</h2><p>You can close this tab.</p></body></html>\",\n );\n\n if (formResolve) {\n formResolve(data);\n formResolve = null;\n }\n });\n }\n\n // Start server with port retry\n const port = await startServer(server, MAX_PORT_RETRIES);\n\n // Auto-close timeout\n const autoCloseTimer = setTimeout(() => {\n closeServer();\n }, timeout);\n\n function closeServer(): void {\n if (closed) return;\n closed = true;\n clearTimeout(autoCloseTimer);\n server.close();\n\n // Resolve any pending waiters with null\n if (callbackResolve) {\n callbackResolve(null);\n callbackResolve = null;\n }\n if (formResolve) {\n formResolve(null);\n formResolve = null;\n }\n }\n\n const baseURL = `http://127.0.0.1:${port}`;\n\n return {\n baseURL,\n callbackURL: `${baseURL}/callback?nonce=${nonce}`,\n nonce,\n port,\n\n waitForCallback(opts?: { timeout?: number }): Promise<Record<string, string> | null> {\n if (closed) return Promise.resolve(null);\n\n return new Promise((resolve) => {\n callbackResolve = resolve;\n\n if (opts?.timeout) {\n setTimeout(() => {\n if (callbackResolve === resolve) {\n callbackResolve = null;\n resolve(null);\n }\n }, opts.timeout);\n }\n });\n },\n\n waitForForm(\n schema: Record<string, any>,\n opts?: { title?: string; timeout?: number },\n ): Promise<Record<string, unknown> | null> {\n if (closed) return Promise.resolve(null);\n\n formSchema = schema;\n if (opts?.title) formTitle = opts.title;\n\n return new Promise((resolve) => {\n formResolve = resolve;\n\n if (opts?.timeout) {\n setTimeout(() => {\n if (formResolve === resolve) {\n formResolve = null;\n resolve(null);\n }\n }, opts.timeout);\n }\n });\n },\n\n close: closeServer,\n };\n}\n\nasync function startServer(server: Server, maxRetries: number): Promise<number> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await new Promise<number>((resolve, reject) => {\n server.listen(0, \"127.0.0.1\", () => {\n const addr = server.address();\n if (addr && typeof addr === \"object\") {\n resolve(addr.port);\n } else {\n reject(new Error(\"Failed to get server address\"));\n }\n });\n server.on(\"error\", reject);\n });\n } catch (err: any) {\n if (attempt === maxRetries) {\n throw new Error(\n `Failed to start auth server after ${maxRetries + 1} attempts: ${err.message}`,\n );\n }\n // Close and retry\n server.close();\n }\n }\n throw new Error(\"Failed to start auth server\");\n}\n\nfunction renderFormHTML(schema: Record<string, any>, title: string, nonce: string): string {\n const properties = schema.properties || {};\n const required = new Set(schema.required || []);\n\n let fields = \"\";\n for (const [key, prop] of Object.entries(properties) as [string, any][]) {\n const label = prop.description || key;\n const isSensitive = prop.sensitive === true;\n const isRequired = required.has(key);\n const inputType = isSensitive ? \"password\" : \"text\";\n const defaultValue = prop.default != null ? String(prop.default) : \"\";\n\n fields += `\n <div style=\"margin-bottom: 12px;\">\n <label for=\"${escapeHTML(key)}\" style=\"display: block; font-weight: 600; margin-bottom: 4px;\">\n ${escapeHTML(label)}${isRequired ? \" *\" : \"\"}\n </label>\n <input\n type=\"${inputType}\"\n id=\"${escapeHTML(key)}\"\n name=\"${escapeHTML(key)}\"\n value=\"${escapeHTML(defaultValue)}\"\n ${isRequired ? \"required\" : \"\"}\n style=\"width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px;\"\n autocomplete=\"off\"\n />\n </div>`;\n }\n\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>${escapeHTML(title)}</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 480px; margin: 40px auto; padding: 0 20px; }\n h2 { color: #333; }\n button { background: #2563eb; color: white; border: none; padding: 10px 24px; border-radius: 4px; font-size: 14px; cursor: pointer; }\n button:hover { background: #1d4ed8; }\n </style>\n</head>\n<body>\n <h2>${escapeHTML(title)}</h2>\n <form method=\"POST\" action=\"/auth?nonce=${escapeHTML(nonce)}\">\n ${fields}\n <button type=\"submit\">Submit</button>\n </form>\n</body>\n</html>`;\n}\n\nfunction escapeHTML(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth-server.mjs","names":[],"sources":["../../src/credential/auth-server.ts"],"sourcesContent":["/**\n * Temporary HTTP Server for credential collection.\n *\n * Supports two modes:\n * 1. Form collection: GET /auth renders a form from JSON Schema, POST /auth returns submitted data\n * 2. OAuth callback: GET /callback captures query params for waitForCallback()\n *\n * Security:\n * - Binds to 127.0.0.1 only (no external connections)\n * - One-time nonce per request to prevent CSRF\n * - Auto-closes after 5 minutes\n * - POST data is never logged\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\n\nconst DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes\nconst MAX_PORT_RETRIES = 3;\n\nexport interface AuthServer {\n /** Base URL of the server, e.g. http://127.0.0.1:12345 */\n baseURL: string;\n\n /** Callback URL for OAuth redirects: baseURL + /callback */\n callbackURL: string;\n\n /** Wait for a callback request at /callback. Returns query params or null on timeout/close. */\n waitForCallback(options?: { timeout?: number }): Promise<Record<string, string> | null>;\n\n /**\n * Serve a form for collecting fields, wait for submission.\n * Returns submitted values or null on timeout/close.\n *\n * @param schema - JSON Schema describing fields to collect\n * @param options - Optional title and timeout\n */\n waitForForm(\n schema: Record<string, any>,\n options?: { title?: string; timeout?: number },\n ): Promise<Record<string, unknown> | null>;\n\n /** Close the server. Idempotent. */\n close(): void;\n\n /** The one-time nonce for this server session */\n readonly nonce: string;\n\n /** The port the server is listening on */\n readonly port: number;\n}\n\nexport interface CreateAuthServerOptions {\n /** Timeout in ms before auto-close. Default: 5 minutes. */\n timeout?: number;\n}\n\n/**\n * Create a temporary auth server bound to 127.0.0.1 with a random port.\n */\nexport async function createAuthServer(options?: CreateAuthServerOptions): Promise<AuthServer> {\n const timeout = options?.timeout ?? DEFAULT_TIMEOUT;\n const nonce = randomBytes(16).toString(\"hex\");\n\n let closed = false;\n let callbackResolve: ((value: Record<string, string> | null) => void) | null = null;\n let formResolve: ((value: Record<string, unknown> | null) => void) | null = null;\n let formSchema: Record<string, any> | null = null;\n let formTitle = \"AFS Credential Collection\";\n\n const server = createServer((req, res) => {\n const url = new URL(req.url || \"/\", `http://127.0.0.1`);\n const pathname = url.pathname;\n\n // CORS headers for local browser forms\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (pathname === \"/callback\") {\n handleCallback(url, req, res, nonce);\n } else if (pathname === \"/auth\" && req.method === \"GET\") {\n handleFormGet(url, res, nonce);\n } else if (pathname === \"/auth\" && req.method === \"POST\") {\n handleFormPost(req, res, nonce, url);\n } else {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\"Not Found\");\n }\n });\n\n function handleCallback(\n url: URL,\n _req: IncomingMessage,\n res: ServerResponse,\n expectedNonce: string,\n ): void {\n const reqNonce = url.searchParams.get(\"nonce\");\n if (reqNonce !== expectedNonce) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden: invalid nonce\");\n return;\n }\n\n const params: Record<string, string> = {};\n for (const [key, value] of url.searchParams.entries()) {\n if (key !== \"nonce\") {\n params[key] = value;\n }\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h2>Authorization complete.</h2><p>You can close this tab.</p></body></html>\",\n );\n\n if (callbackResolve) {\n callbackResolve(params);\n callbackResolve = null;\n }\n }\n\n function handleFormGet(url: URL, res: ServerResponse, expectedNonce: string): void {\n const reqNonce = url.searchParams.get(\"nonce\");\n if (reqNonce !== expectedNonce) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden: invalid nonce\");\n return;\n }\n\n if (!formSchema) {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"No form schema available\");\n return;\n }\n\n const html = renderFormHTML(formSchema, formTitle, expectedNonce);\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n }\n\n function handleFormPost(\n req: IncomingMessage,\n res: ServerResponse,\n expectedNonce: string,\n url: URL,\n ): void {\n const reqNonce = url.searchParams.get(\"nonce\");\n if (reqNonce !== expectedNonce) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden: invalid nonce\");\n return;\n }\n\n let body = \"\";\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString();\n });\n req.on(\"end\", () => {\n let data: Record<string, unknown>;\n const contentType = req.headers[\"content-type\"] || \"\";\n\n if (contentType.includes(\"application/json\")) {\n try {\n data = JSON.parse(body);\n } catch {\n res.writeHead(400, { \"Content-Type\": \"text/plain\" });\n res.end(\"Invalid JSON\");\n return;\n }\n } else {\n // URL-encoded form data\n const params = new URLSearchParams(body);\n data = {};\n for (const [key, value] of params.entries()) {\n if (key !== \"nonce\") {\n data[key] = value;\n }\n }\n // Coerce string values to match schema types (HTML forms always submit strings)\n if (formSchema?.properties) {\n for (const [key, value] of Object.entries(data)) {\n const prop = (formSchema.properties as Record<string, any>)[key];\n if (!prop) continue;\n const strValue = String(value);\n if (prop.type === \"number\" || prop.type === \"integer\") {\n const num = Number(strValue);\n if (!Number.isNaN(num) && strValue !== \"\") data[key] = num;\n } else if (prop.type === \"boolean\") {\n if (strValue === \"true\" || strValue === \"1\") data[key] = true;\n else if (strValue === \"false\" || strValue === \"0\" || strValue === \"\")\n data[key] = false;\n }\n // Remove empty strings for non-string optional fields (let defaults apply)\n if (strValue === \"\" && prop.type !== \"string\") {\n delete data[key];\n }\n }\n }\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n \"<html><body><h2>Credentials received.</h2><p>You can close this tab.</p></body></html>\",\n );\n\n if (formResolve) {\n formResolve(data);\n formResolve = null;\n }\n });\n }\n\n // Start server with port retry\n const port = await startServer(server, MAX_PORT_RETRIES);\n\n // Auto-close timeout\n const autoCloseTimer = setTimeout(() => {\n closeServer();\n }, timeout);\n\n function closeServer(): void {\n if (closed) return;\n closed = true;\n clearTimeout(autoCloseTimer);\n server.close();\n\n // Resolve any pending waiters with null\n if (callbackResolve) {\n callbackResolve(null);\n callbackResolve = null;\n }\n if (formResolve) {\n formResolve(null);\n formResolve = null;\n }\n }\n\n const baseURL = `http://127.0.0.1:${port}`;\n\n return {\n baseURL,\n callbackURL: `${baseURL}/callback?nonce=${nonce}`,\n nonce,\n port,\n\n waitForCallback(opts?: { timeout?: number }): Promise<Record<string, string> | null> {\n if (closed) return Promise.resolve(null);\n\n return new Promise((resolve) => {\n callbackResolve = resolve;\n\n if (opts?.timeout) {\n setTimeout(() => {\n if (callbackResolve === resolve) {\n callbackResolve = null;\n resolve(null);\n }\n }, opts.timeout);\n }\n });\n },\n\n waitForForm(\n schema: Record<string, any>,\n opts?: { title?: string; timeout?: number },\n ): Promise<Record<string, unknown> | null> {\n if (closed) return Promise.resolve(null);\n\n formSchema = schema;\n if (opts?.title) formTitle = opts.title;\n\n return new Promise((resolve) => {\n formResolve = resolve;\n\n if (opts?.timeout) {\n setTimeout(() => {\n if (formResolve === resolve) {\n formResolve = null;\n resolve(null);\n }\n }, opts.timeout);\n }\n });\n },\n\n close: closeServer,\n };\n}\n\nasync function startServer(server: Server, maxRetries: number): Promise<number> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await new Promise<number>((resolve, reject) => {\n server.listen(0, \"127.0.0.1\", () => {\n const addr = server.address();\n if (addr && typeof addr === \"object\") {\n resolve(addr.port);\n } else {\n reject(new Error(\"Failed to get server address\"));\n }\n });\n server.on(\"error\", reject);\n });\n } catch (err: any) {\n if (attempt === maxRetries) {\n throw new Error(\n `Failed to start auth server after ${maxRetries + 1} attempts: ${err.message}`,\n );\n }\n // Close and retry\n server.close();\n }\n }\n throw new Error(\"Failed to start auth server\");\n}\n\nfunction renderFormHTML(schema: Record<string, any>, title: string, nonce: string): string {\n const properties = schema.properties || {};\n const required = new Set(schema.required || []);\n\n let fields = \"\";\n for (const [key, prop] of Object.entries(properties) as [string, any][]) {\n const label = prop.description || key;\n const isSensitive = prop.sensitive === true;\n const isRequired = required.has(key);\n const defaultValue = prop.default != null ? prop.default : \"\";\n\n if (prop.type === \"boolean\") {\n const checked = defaultValue === true || defaultValue === \"true\";\n fields += `\n <div style=\"margin-bottom: 12px;\">\n <label style=\"display: flex; align-items: center; gap: 8px; font-weight: 600; cursor: pointer;\">\n <input type=\"hidden\" name=\"${escapeHTML(key)}\" value=\"false\" />\n <input\n type=\"checkbox\"\n name=\"${escapeHTML(key)}\"\n value=\"true\"\n ${checked ? \"checked\" : \"\"}\n style=\"width: 18px; height: 18px;\"\n />\n ${escapeHTML(label)}\n </label>\n </div>`;\n } else {\n const inputType = isSensitive ? \"password\" : \"text\";\n fields += `\n <div style=\"margin-bottom: 12px;\">\n <label for=\"${escapeHTML(key)}\" style=\"display: block; font-weight: 600; margin-bottom: 4px;\">\n ${escapeHTML(label)}${isRequired ? \" *\" : \"\"}\n </label>\n <input\n type=\"${inputType}\"\n id=\"${escapeHTML(key)}\"\n name=\"${escapeHTML(key)}\"\n value=\"${escapeHTML(String(defaultValue))}\"\n ${isRequired ? \"required\" : \"\"}\n style=\"width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px;\"\n autocomplete=\"off\"\n />\n </div>`;\n }\n }\n\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>${escapeHTML(title)}</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 480px; margin: 40px auto; padding: 0 20px; }\n h2 { color: #333; }\n button { background: #2563eb; color: white; border: none; padding: 10px 24px; border-radius: 4px; font-size: 14px; cursor: pointer; }\n button:hover { background: #1d4ed8; }\n </style>\n</head>\n<body>\n <h2>${escapeHTML(title)}</h2>\n <form method=\"POST\" action=\"/auth?nonce=${escapeHTML(nonce)}\">\n ${fields}\n <button type=\"submit\">Submit</button>\n </form>\n</body>\n</html>`;\n}\n\nfunction escapeHTML(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import "./cli-auth-context.mjs";
2
2
  import "./mcp-auth-context.mjs";
3
3
  import { CredentialStore, CredentialStoreOptions, Credentials, createCredentialStore } from "./store.mjs";
4
- import "./resolver.mjs";
4
+ import "./resolver.mjs";
5
+ import "./vault-store.mjs";