@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
@@ -0,0 +1,289 @@
1
+ import { formatVaultDeleteOutput, formatVaultGetOutput, formatVaultInitOutput, formatVaultListOutput, formatVaultSetOutput } from "../formatters/vault.mjs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ //#region src/core/commands/vault.ts
6
+ /**
7
+ * vault Command - Core Implementation
8
+ *
9
+ * Vault management commands for encrypted secret storage.
10
+ * Subcommands: init, get, set, list, delete.
11
+ */
12
+ /** Default vault file location */
13
+ const DEFAULT_VAULT_DIR = ".afs-config";
14
+ const DEFAULT_VAULT_FILE = "vault.enc";
15
+ function defaultVaultPath() {
16
+ return join(homedir(), DEFAULT_VAULT_DIR, DEFAULT_VAULT_FILE);
17
+ }
18
+ /**
19
+ * Resolve master key using the vault's key resolution chain:
20
+ * OS keychain → AFS_VAULT_KEY env var → passphrase prompt.
21
+ */
22
+ async function loadMasterKey() {
23
+ const { resolveMasterKey } = await import("../../providers/vault/dist/index.mjs");
24
+ return resolveMasterKey();
25
+ }
26
+ /**
27
+ * Create vault command factory (with subcommands)
28
+ */
29
+ function createVaultCommand(options) {
30
+ return {
31
+ command: "vault",
32
+ describe: "Encrypted secret storage",
33
+ builder: (yargs) => yargs.command(createVaultInitSubcommand(options)).command(createVaultGetSubcommand(options)).command(createVaultSetSubcommand(options)).command(createVaultListSubcommand(options)).command(createVaultDeleteSubcommand(options)).demandCommand(1, "Please specify a subcommand").alias("help", "h"),
34
+ handler: () => {}
35
+ };
36
+ }
37
+ function createVaultInitSubcommand(options) {
38
+ return {
39
+ command: "init",
40
+ describe: "Initialize a new encrypted vault",
41
+ builder: {
42
+ path: {
43
+ type: "string",
44
+ description: "Path for vault file",
45
+ default: defaultVaultPath()
46
+ },
47
+ migrate: {
48
+ type: "boolean",
49
+ description: "Migrate existing credentials.toml into vault",
50
+ default: true
51
+ }
52
+ },
53
+ handler: async (argv) => {
54
+ const vaultPath = argv.path ?? defaultVaultPath();
55
+ const { generateMasterKey, writeEncryptedVault, vaultFileExists } = await import("../../providers/vault/dist/index.mjs");
56
+ if (await vaultFileExists(vaultPath)) throw new Error(`Vault already exists at ${vaultPath}. Delete it first to re-initialize.`);
57
+ const masterKey = generateMasterKey();
58
+ await writeEncryptedVault(vaultPath, { secrets: {} }, masterKey);
59
+ const { storeKeychain } = await import("../../providers/vault/dist/index.mjs");
60
+ const stored = await storeKeychain(masterKey);
61
+ let migrated = 0;
62
+ if (argv.migrate !== false) migrated = await migrateFromToml(vaultPath, masterKey);
63
+ const hexKey = masterKey.toString("hex");
64
+ options.onResult({
65
+ command: "vault init",
66
+ result: {
67
+ success: true,
68
+ vaultPath,
69
+ migrated,
70
+ keychainStored: stored
71
+ },
72
+ format: (result, view) => {
73
+ const base = formatVaultInitOutput(result, view);
74
+ if (view === "json") return base;
75
+ const lines = [base];
76
+ if (stored) lines.push("\nMaster key stored in OS keychain.");
77
+ else lines.push(`\nMaster key (save this securely — it cannot be recovered):\n${hexKey}`, `\nSet it as: export AFS_VAULT_KEY=${hexKey}`);
78
+ return lines.join("");
79
+ }
80
+ });
81
+ }
82
+ };
83
+ }
84
+ /**
85
+ * Migrate credentials.toml entries into the vault.
86
+ * Returns count of migrated credential groups.
87
+ */
88
+ async function migrateFromToml(vaultPath, masterKey) {
89
+ const { readFile } = await import("node:fs/promises");
90
+ const { parse } = await import("smol-toml");
91
+ const { AFSVault } = await import("../../providers/vault/dist/index.mjs");
92
+ const tomlPath = join(homedir(), DEFAULT_VAULT_DIR, "credentials.toml");
93
+ let content;
94
+ try {
95
+ content = await readFile(tomlPath, "utf-8");
96
+ } catch {
97
+ return 0;
98
+ }
99
+ let data;
100
+ try {
101
+ data = parse(content);
102
+ } catch {
103
+ return 0;
104
+ }
105
+ const vault = new AFSVault({
106
+ vaultPath,
107
+ masterKey,
108
+ accessMode: "readwrite"
109
+ });
110
+ let count = 0;
111
+ for (const [group, values] of Object.entries(data)) {
112
+ if (typeof values !== "object" || values === null || Array.isArray(values)) continue;
113
+ const safeGroup = group.replace(/^[a-z0-9]+:\/\//, "").replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
114
+ if (!safeGroup) continue;
115
+ for (const [key, val] of Object.entries(values)) if (typeof val === "string") await vault.setSecret(safeGroup, key, val);
116
+ count++;
117
+ }
118
+ return count;
119
+ }
120
+ function createVaultGetSubcommand(options) {
121
+ return {
122
+ command: "get <group> <name>",
123
+ describe: "Read a secret value",
124
+ builder: {
125
+ group: {
126
+ type: "string",
127
+ demandOption: true,
128
+ description: "Secret group (e.g., aws, github)"
129
+ },
130
+ name: {
131
+ type: "string",
132
+ demandOption: true,
133
+ description: "Secret name (e.g., token, access-key-id)"
134
+ },
135
+ "vault-path": {
136
+ type: "string",
137
+ description: "Path to vault file"
138
+ }
139
+ },
140
+ handler: async (argv) => {
141
+ const { AFSVault } = await import("../../providers/vault/dist/index.mjs");
142
+ const value = await new AFSVault({
143
+ vaultPath: argv["vault-path"] ?? defaultVaultPath(),
144
+ masterKey: await loadMasterKey(),
145
+ accessMode: "readonly"
146
+ }).getSecret(argv.group, argv.name);
147
+ if (value === void 0) throw new Error(`Secret not found: ${argv.group}/${argv.name}`);
148
+ options.onResult({
149
+ command: "vault get",
150
+ result: {
151
+ group: argv.group,
152
+ name: argv.name,
153
+ value
154
+ },
155
+ format: formatVaultGetOutput
156
+ });
157
+ }
158
+ };
159
+ }
160
+ function createVaultSetSubcommand(options) {
161
+ return {
162
+ command: "set <group> <name> <value>",
163
+ describe: "Store a secret",
164
+ builder: {
165
+ group: {
166
+ type: "string",
167
+ demandOption: true,
168
+ description: "Secret group (e.g., aws, github)"
169
+ },
170
+ name: {
171
+ type: "string",
172
+ demandOption: true,
173
+ description: "Secret name (e.g., token, access-key-id)"
174
+ },
175
+ value: {
176
+ type: "string",
177
+ demandOption: true,
178
+ description: "Secret value"
179
+ },
180
+ "vault-path": {
181
+ type: "string",
182
+ description: "Path to vault file"
183
+ }
184
+ },
185
+ handler: async (argv) => {
186
+ const { AFSVault } = await import("../../providers/vault/dist/index.mjs");
187
+ await new AFSVault({
188
+ vaultPath: argv["vault-path"] ?? defaultVaultPath(),
189
+ masterKey: await loadMasterKey(),
190
+ accessMode: "readwrite"
191
+ }).setSecret(argv.group, argv.name, argv.value);
192
+ options.onResult({
193
+ command: "vault set",
194
+ result: {
195
+ group: argv.group,
196
+ name: argv.name
197
+ },
198
+ format: formatVaultSetOutput
199
+ });
200
+ }
201
+ };
202
+ }
203
+ function createVaultListSubcommand(options) {
204
+ return {
205
+ command: ["list [group]", "ls [group]"],
206
+ describe: "List secrets",
207
+ builder: {
208
+ group: {
209
+ type: "string",
210
+ description: "Secret group to list (omit for all groups)"
211
+ },
212
+ "vault-path": {
213
+ type: "string",
214
+ description: "Path to vault file"
215
+ }
216
+ },
217
+ handler: async (argv) => {
218
+ const { AFSVault } = await import("../../providers/vault/dist/index.mjs");
219
+ const secrets = await new AFSVault({
220
+ vaultPath: argv["vault-path"] ?? defaultVaultPath(),
221
+ masterKey: await loadMasterKey(),
222
+ accessMode: "readonly"
223
+ }).listSecrets(argv.group);
224
+ options.onResult({
225
+ command: "vault list",
226
+ result: {
227
+ group: argv.group,
228
+ secrets
229
+ },
230
+ format: formatVaultListOutput
231
+ });
232
+ }
233
+ };
234
+ }
235
+ function createVaultDeleteSubcommand(options) {
236
+ return {
237
+ command: ["delete <group> [name]", "rm <group> [name]"],
238
+ describe: "Delete a secret or group",
239
+ builder: {
240
+ group: {
241
+ type: "string",
242
+ demandOption: true,
243
+ description: "Secret group"
244
+ },
245
+ name: {
246
+ type: "string",
247
+ description: "Secret name (omit to delete entire group)"
248
+ },
249
+ "vault-path": {
250
+ type: "string",
251
+ description: "Path to vault file"
252
+ }
253
+ },
254
+ handler: async (argv) => {
255
+ const { AFSVault } = await import("../../providers/vault/dist/index.mjs");
256
+ const vault = new AFSVault({
257
+ vaultPath: argv["vault-path"] ?? defaultVaultPath(),
258
+ masterKey: await loadMasterKey(),
259
+ accessMode: "readwrite"
260
+ });
261
+ let deleted;
262
+ if (argv.name) deleted = await vault.deleteSecret(argv.group, argv.name);
263
+ else {
264
+ const secrets = await vault.listSecrets(argv.group);
265
+ if (secrets.length === 0) deleted = false;
266
+ else {
267
+ for (const secretPath of secrets) {
268
+ const secretName = secretPath.split("/").pop();
269
+ await vault.deleteSecret(argv.group, secretName);
270
+ }
271
+ deleted = true;
272
+ }
273
+ }
274
+ options.onResult({
275
+ command: "vault delete",
276
+ result: {
277
+ group: argv.group,
278
+ name: argv.name,
279
+ deleted
280
+ },
281
+ format: formatVaultDeleteOutput
282
+ });
283
+ }
284
+ };
285
+ }
286
+
287
+ //#endregion
288
+ export { createVaultCommand };
289
+ //# sourceMappingURL=vault.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.mjs","names":[],"sources":["../../../src/core/commands/vault.ts"],"sourcesContent":["/**\n * vault Command - Core Implementation\n *\n * Vault management commands for encrypted secret storage.\n * Subcommands: init, get, set, list, delete.\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Argv, CommandModule } from \"yargs\";\nimport {\n formatVaultDeleteOutput,\n formatVaultGetOutput,\n formatVaultInitOutput,\n formatVaultListOutput,\n formatVaultSetOutput,\n} from \"../formatters/vault.js\";\nimport type { CommandFactoryOptions } from \"./types.js\";\n\n/** Default vault file location */\nconst DEFAULT_VAULT_DIR = \".afs-config\";\nconst DEFAULT_VAULT_FILE = \"vault.enc\";\n\nfunction defaultVaultPath(): string {\n return join(homedir(), DEFAULT_VAULT_DIR, DEFAULT_VAULT_FILE);\n}\n\n/**\n * Resolve master key using the vault's key resolution chain:\n * OS keychain → AFS_VAULT_KEY env var → passphrase prompt.\n */\nasync function loadMasterKey(): Promise<Buffer> {\n const { resolveMasterKey } = await import(\"@aigne/afs-vault\");\n return resolveMasterKey();\n}\n\n/**\n * Create vault command factory (with subcommands)\n */\nexport function createVaultCommand(options: CommandFactoryOptions): CommandModule {\n return {\n command: \"vault\",\n describe: \"Encrypted secret storage\",\n builder: (yargs: Argv) =>\n yargs\n .command(createVaultInitSubcommand(options))\n .command(createVaultGetSubcommand(options))\n .command(createVaultSetSubcommand(options))\n .command(createVaultListSubcommand(options))\n .command(createVaultDeleteSubcommand(options))\n .demandCommand(1, \"Please specify a subcommand\")\n .alias(\"help\", \"h\"),\n handler: () => {},\n };\n}\n\n// ── init ──────────────────────────────────────────────────────────────\n\ninterface VaultInitArgs {\n path?: string;\n migrate?: boolean;\n}\n\nfunction createVaultInitSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, VaultInitArgs> {\n return {\n command: \"init\",\n describe: \"Initialize a new encrypted vault\",\n builder: {\n path: {\n type: \"string\",\n description: \"Path for vault file\",\n default: defaultVaultPath(),\n },\n migrate: {\n type: \"boolean\",\n description: \"Migrate existing credentials.toml into vault\",\n default: true,\n },\n },\n handler: async (argv) => {\n const vaultPath = argv.path ?? defaultVaultPath();\n\n const { generateMasterKey, writeEncryptedVault, vaultFileExists } = await import(\n \"@aigne/afs-vault\"\n );\n\n if (await vaultFileExists(vaultPath)) {\n throw new Error(`Vault already exists at ${vaultPath}. Delete it first to re-initialize.`);\n }\n\n const masterKey = generateMasterKey();\n\n // Create empty vault\n await writeEncryptedVault(vaultPath, { secrets: {} }, masterKey);\n\n // Try to store in OS keychain\n const { storeKeychain } = await import(\"@aigne/afs-vault\");\n const stored = await storeKeychain(masterKey);\n\n let migrated = 0;\n\n // Migrate from credentials.toml if requested\n if (argv.migrate !== false) {\n migrated = await migrateFromToml(vaultPath, masterKey);\n }\n\n const hexKey = masterKey.toString(\"hex\");\n\n options.onResult({\n command: \"vault init\",\n result: { success: true, vaultPath, migrated, keychainStored: stored },\n format: (result, view) => {\n const base = formatVaultInitOutput(result, view);\n if (view === \"json\") return base;\n const lines = [base];\n if (stored) {\n lines.push(\"\\nMaster key stored in OS keychain.\");\n } else {\n lines.push(\n `\\nMaster key (save this securely — it cannot be recovered):\\n${hexKey}`,\n `\\nSet it as: export AFS_VAULT_KEY=${hexKey}`,\n );\n }\n return lines.join(\"\");\n },\n });\n },\n };\n}\n\n/**\n * Migrate credentials.toml entries into the vault.\n * Returns count of migrated credential groups.\n */\nasync function migrateFromToml(vaultPath: string, masterKey: Buffer): Promise<number> {\n const { readFile } = await import(\"node:fs/promises\");\n const { parse } = await import(\"smol-toml\");\n const { AFSVault } = await import(\"@aigne/afs-vault\");\n\n const tomlPath = join(homedir(), DEFAULT_VAULT_DIR, \"credentials.toml\");\n\n let content: string;\n try {\n content = await readFile(tomlPath, \"utf-8\");\n } catch {\n return 0; // No credentials.toml — nothing to migrate\n }\n\n let data: Record<string, unknown>;\n try {\n data = parse(content) as Record<string, unknown>;\n } catch {\n return 0; // Corrupted file — skip migration\n }\n\n const vault = new AFSVault({ vaultPath, masterKey, accessMode: \"readwrite\" });\n let count = 0;\n\n for (const [group, values] of Object.entries(data)) {\n if (typeof values !== \"object\" || values === null || Array.isArray(values)) continue;\n // Sanitize group name for vault\n const safeGroup = group\n .replace(/^[a-z0-9]+:\\/\\//, \"\")\n .replace(/[^a-zA-Z0-9._-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (!safeGroup) continue;\n\n for (const [key, val] of Object.entries(values as Record<string, unknown>)) {\n if (typeof val === \"string\") {\n await vault.setSecret(safeGroup, key, val);\n }\n }\n count++;\n }\n\n return count;\n}\n\n// ── get ──────────────────────────────────────────────────────────────\n\ninterface VaultGetArgs {\n group: string;\n name: string;\n \"vault-path\"?: string;\n}\n\nfunction createVaultGetSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, VaultGetArgs> {\n return {\n command: \"get <group> <name>\",\n describe: \"Read a secret value\",\n builder: {\n group: {\n type: \"string\",\n demandOption: true,\n description: \"Secret group (e.g., aws, github)\",\n },\n name: {\n type: \"string\",\n demandOption: true,\n description: \"Secret name (e.g., token, access-key-id)\",\n },\n \"vault-path\": {\n type: \"string\",\n description: \"Path to vault file\",\n },\n },\n handler: async (argv) => {\n const { AFSVault } = await import(\"@aigne/afs-vault\");\n const vaultPath = argv[\"vault-path\"] ?? defaultVaultPath();\n const masterKey = await loadMasterKey();\n const vault = new AFSVault({ vaultPath, masterKey, accessMode: \"readonly\" });\n\n const value = await vault.getSecret(argv.group, argv.name);\n if (value === undefined) {\n throw new Error(`Secret not found: ${argv.group}/${argv.name}`);\n }\n\n options.onResult({\n command: \"vault get\",\n result: { group: argv.group, name: argv.name, value },\n format: formatVaultGetOutput,\n });\n },\n };\n}\n\n// ── set ──────────────────────────────────────────────────────────────\n\ninterface VaultSetArgs {\n group: string;\n name: string;\n value: string;\n \"vault-path\"?: string;\n}\n\nfunction createVaultSetSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, VaultSetArgs> {\n return {\n command: \"set <group> <name> <value>\",\n describe: \"Store a secret\",\n builder: {\n group: {\n type: \"string\",\n demandOption: true,\n description: \"Secret group (e.g., aws, github)\",\n },\n name: {\n type: \"string\",\n demandOption: true,\n description: \"Secret name (e.g., token, access-key-id)\",\n },\n value: {\n type: \"string\",\n demandOption: true,\n description: \"Secret value\",\n },\n \"vault-path\": {\n type: \"string\",\n description: \"Path to vault file\",\n },\n },\n handler: async (argv) => {\n const { AFSVault } = await import(\"@aigne/afs-vault\");\n const vaultPath = argv[\"vault-path\"] ?? defaultVaultPath();\n const masterKey = await loadMasterKey();\n const vault = new AFSVault({ vaultPath, masterKey, accessMode: \"readwrite\" });\n\n await vault.setSecret(argv.group, argv.name, argv.value);\n\n options.onResult({\n command: \"vault set\",\n result: { group: argv.group, name: argv.name },\n format: formatVaultSetOutput,\n });\n },\n };\n}\n\n// ── list ─────────────────────────────────────────────────────────────\n\ninterface VaultListArgs {\n group?: string;\n \"vault-path\"?: string;\n}\n\nfunction createVaultListSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, VaultListArgs> {\n return {\n command: [\"list [group]\", \"ls [group]\"],\n describe: \"List secrets\",\n builder: {\n group: {\n type: \"string\",\n description: \"Secret group to list (omit for all groups)\",\n },\n \"vault-path\": {\n type: \"string\",\n description: \"Path to vault file\",\n },\n },\n handler: async (argv) => {\n const { AFSVault } = await import(\"@aigne/afs-vault\");\n const vaultPath = argv[\"vault-path\"] ?? defaultVaultPath();\n const masterKey = await loadMasterKey();\n const vault = new AFSVault({ vaultPath, masterKey, accessMode: \"readonly\" });\n\n const secrets = await vault.listSecrets(argv.group);\n\n options.onResult({\n command: \"vault list\",\n result: { group: argv.group, secrets },\n format: formatVaultListOutput,\n });\n },\n };\n}\n\n// ── delete ───────────────────────────────────────────────────────────\n\ninterface VaultDeleteArgs {\n group: string;\n name?: string;\n \"vault-path\"?: string;\n}\n\nfunction createVaultDeleteSubcommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, VaultDeleteArgs> {\n return {\n command: [\"delete <group> [name]\", \"rm <group> [name]\"],\n describe: \"Delete a secret or group\",\n builder: {\n group: {\n type: \"string\",\n demandOption: true,\n description: \"Secret group\",\n },\n name: {\n type: \"string\",\n description: \"Secret name (omit to delete entire group)\",\n },\n \"vault-path\": {\n type: \"string\",\n description: \"Path to vault file\",\n },\n },\n handler: async (argv) => {\n const { AFSVault } = await import(\"@aigne/afs-vault\");\n const vaultPath = argv[\"vault-path\"] ?? defaultVaultPath();\n const masterKey = await loadMasterKey();\n const vault = new AFSVault({ vaultPath, masterKey, accessMode: \"readwrite\" });\n\n let deleted: boolean;\n if (argv.name) {\n deleted = await vault.deleteSecret(argv.group, argv.name);\n } else {\n // Delete entire group by listing all secrets and deleting each\n const secrets = await vault.listSecrets(argv.group);\n if (secrets.length === 0) {\n deleted = false;\n } else {\n for (const secretPath of secrets) {\n const secretName = secretPath.split(\"/\").pop()!;\n await vault.deleteSecret(argv.group, secretName);\n }\n deleted = true;\n }\n }\n\n options.onResult({\n command: \"vault delete\",\n result: { group: argv.group, name: argv.name, deleted },\n format: formatVaultDeleteOutput,\n });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAE3B,SAAS,mBAA2B;AAClC,QAAO,KAAK,SAAS,EAAE,mBAAmB,mBAAmB;;;;;;AAO/D,eAAe,gBAAiC;CAC9C,MAAM,EAAE,qBAAqB,MAAM,OAAO;AAC1C,QAAO,kBAAkB;;;;;AAM3B,SAAgB,mBAAmB,SAA+C;AAChF,QAAO;EACL,SAAS;EACT,UAAU;EACV,UAAU,UACR,MACG,QAAQ,0BAA0B,QAAQ,CAAC,CAC3C,QAAQ,yBAAyB,QAAQ,CAAC,CAC1C,QAAQ,yBAAyB,QAAQ,CAAC,CAC1C,QAAQ,0BAA0B,QAAQ,CAAC,CAC3C,QAAQ,4BAA4B,QAAQ,CAAC,CAC7C,cAAc,GAAG,8BAA8B,CAC/C,MAAM,QAAQ,IAAI;EACvB,eAAe;EAChB;;AAUH,SAAS,0BACP,SACuC;AACvC,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS;GACP,MAAM;IACJ,MAAM;IACN,aAAa;IACb,SAAS,kBAAkB;IAC5B;GACD,SAAS;IACP,MAAM;IACN,aAAa;IACb,SAAS;IACV;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,YAAY,KAAK,QAAQ,kBAAkB;GAEjD,MAAM,EAAE,mBAAmB,qBAAqB,oBAAoB,MAAM,OACxE;AAGF,OAAI,MAAM,gBAAgB,UAAU,CAClC,OAAM,IAAI,MAAM,2BAA2B,UAAU,qCAAqC;GAG5F,MAAM,YAAY,mBAAmB;AAGrC,SAAM,oBAAoB,WAAW,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU;GAGhE,MAAM,EAAE,kBAAkB,MAAM,OAAO;GACvC,MAAM,SAAS,MAAM,cAAc,UAAU;GAE7C,IAAI,WAAW;AAGf,OAAI,KAAK,YAAY,MACnB,YAAW,MAAM,gBAAgB,WAAW,UAAU;GAGxD,MAAM,SAAS,UAAU,SAAS,MAAM;AAExC,WAAQ,SAAS;IACf,SAAS;IACT,QAAQ;KAAE,SAAS;KAAM;KAAW;KAAU,gBAAgB;KAAQ;IACtE,SAAS,QAAQ,SAAS;KACxB,MAAM,OAAO,sBAAsB,QAAQ,KAAK;AAChD,SAAI,SAAS,OAAQ,QAAO;KAC5B,MAAM,QAAQ,CAAC,KAAK;AACpB,SAAI,OACF,OAAM,KAAK,sCAAsC;SAEjD,OAAM,KACJ,gEAAgE,UAChE,qCAAqC,SACtC;AAEH,YAAO,MAAM,KAAK,GAAG;;IAExB,CAAC;;EAEL;;;;;;AAOH,eAAe,gBAAgB,WAAmB,WAAoC;CACpF,MAAM,EAAE,aAAa,MAAM,OAAO;CAClC,MAAM,EAAE,UAAU,MAAM,OAAO;CAC/B,MAAM,EAAE,aAAa,MAAM,OAAO;CAElC,MAAM,WAAW,KAAK,SAAS,EAAE,mBAAmB,mBAAmB;CAEvE,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,SAAS,UAAU,QAAQ;SACrC;AACN,SAAO;;CAGT,IAAI;AACJ,KAAI;AACF,SAAO,MAAM,QAAQ;SACf;AACN,SAAO;;CAGT,MAAM,QAAQ,IAAI,SAAS;EAAE;EAAW;EAAW,YAAY;EAAa,CAAC;CAC7E,IAAI,QAAQ;AAEZ,MAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,KAAK,EAAE;AAClD,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,OAAO,CAAE;EAE5E,MAAM,YAAY,MACf,QAAQ,mBAAmB,GAAG,CAC9B,QAAQ,oBAAoB,IAAI,CAChC,QAAQ,OAAO,IAAI,CACnB,QAAQ,UAAU,GAAG;AACxB,MAAI,CAAC,UAAW;AAEhB,OAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,OAAkC,CACxE,KAAI,OAAO,QAAQ,SACjB,OAAM,MAAM,UAAU,WAAW,KAAK,IAAI;AAG9C;;AAGF,QAAO;;AAWT,SAAS,yBACP,SACsC;AACtC,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS;GACP,OAAO;IACL,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,cAAc;IACZ,MAAM;IACN,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,EAAE,aAAa,MAAM,OAAO;GAKlC,MAAM,QAAQ,MAFA,IAAI,SAAS;IAAE,WAFX,KAAK,iBAAiB,kBAAkB;IAElB,WADtB,MAAM,eAAe;IACY,YAAY;IAAY,CAAC,CAElD,UAAU,KAAK,OAAO,KAAK,KAAK;AAC1D,OAAI,UAAU,OACZ,OAAM,IAAI,MAAM,qBAAqB,KAAK,MAAM,GAAG,KAAK,OAAO;AAGjE,WAAQ,SAAS;IACf,SAAS;IACT,QAAQ;KAAE,OAAO,KAAK;KAAO,MAAM,KAAK;KAAM;KAAO;IACrD,QAAQ;IACT,CAAC;;EAEL;;AAYH,SAAS,yBACP,SACsC;AACtC,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS;GACP,OAAO;IACL,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,OAAO;IACL,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,cAAc;IACZ,MAAM;IACN,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,EAAE,aAAa,MAAM,OAAO;AAKlC,SAFc,IAAI,SAAS;IAAE,WAFX,KAAK,iBAAiB,kBAAkB;IAElB,WADtB,MAAM,eAAe;IACY,YAAY;IAAa,CAAC,CAEjE,UAAU,KAAK,OAAO,KAAK,MAAM,KAAK,MAAM;AAExD,WAAQ,SAAS;IACf,SAAS;IACT,QAAQ;KAAE,OAAO,KAAK;KAAO,MAAM,KAAK;KAAM;IAC9C,QAAQ;IACT,CAAC;;EAEL;;AAUH,SAAS,0BACP,SACuC;AACvC,QAAO;EACL,SAAS,CAAC,gBAAgB,aAAa;EACvC,UAAU;EACV,SAAS;GACP,OAAO;IACL,MAAM;IACN,aAAa;IACd;GACD,cAAc;IACZ,MAAM;IACN,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,EAAE,aAAa,MAAM,OAAO;GAKlC,MAAM,UAAU,MAFF,IAAI,SAAS;IAAE,WAFX,KAAK,iBAAiB,kBAAkB;IAElB,WADtB,MAAM,eAAe;IACY,YAAY;IAAY,CAAC,CAEhD,YAAY,KAAK,MAAM;AAEnD,WAAQ,SAAS;IACf,SAAS;IACT,QAAQ;KAAE,OAAO,KAAK;KAAO;KAAS;IACtC,QAAQ;IACT,CAAC;;EAEL;;AAWH,SAAS,4BACP,SACyC;AACzC,QAAO;EACL,SAAS,CAAC,yBAAyB,oBAAoB;EACvD,UAAU;EACV,SAAS;GACP,OAAO;IACL,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,aAAa;IACd;GACD,cAAc;IACZ,MAAM;IACN,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,EAAE,aAAa,MAAM,OAAO;GAGlC,MAAM,QAAQ,IAAI,SAAS;IAAE,WAFX,KAAK,iBAAiB,kBAAkB;IAElB,WADtB,MAAM,eAAe;IACY,YAAY;IAAa,CAAC;GAE7E,IAAI;AACJ,OAAI,KAAK,KACP,WAAU,MAAM,MAAM,aAAa,KAAK,OAAO,KAAK,KAAK;QACpD;IAEL,MAAM,UAAU,MAAM,MAAM,YAAY,KAAK,MAAM;AACnD,QAAI,QAAQ,WAAW,EACrB,WAAU;SACL;AACL,UAAK,MAAM,cAAc,SAAS;MAChC,MAAM,aAAa,WAAW,MAAM,IAAI,CAAC,KAAK;AAC9C,YAAM,MAAM,aAAa,KAAK,OAAO,WAAW;;AAElD,eAAU;;;AAId,WAAQ,SAAS;IACf,SAAS;IACT,QAAQ;KAAE,OAAO,KAAK;KAAO,MAAM,KAAK;KAAM;KAAS;IACvD,QAAQ;IACT,CAAC;;EAEL"}
@@ -36,10 +36,22 @@ function createWriteCommand(options) {
36
36
  type: "string",
37
37
  description: "Content to write"
38
38
  },
39
- append: {
40
- type: "boolean",
41
- description: "Append to file instead of overwriting",
42
- default: false
39
+ mode: {
40
+ type: "string",
41
+ choices: [
42
+ "replace",
43
+ "append",
44
+ "prepend",
45
+ "patch",
46
+ "create",
47
+ "update"
48
+ ],
49
+ description: "Write mode",
50
+ default: "replace"
51
+ },
52
+ patch: {
53
+ type: "string",
54
+ description: "JSON array of patch operations (for mode=patch)"
43
55
  },
44
56
  meta: {
45
57
  type: "string",
@@ -50,13 +62,14 @@ function createWriteCommand(options) {
50
62
  handler: async (argv) => {
51
63
  const metadata = parseMetaValues(argv.meta);
52
64
  const fields = metadata ? Object.keys(metadata) : void 0;
53
- if (argv.content === void 0 && !metadata) throw new Error("write requires content (use --content or provide as second argument)");
65
+ if (argv.content === void 0 && !metadata && argv.mode !== "patch") throw new Error("write requires content (use --content or provide as second argument)");
54
66
  const afs = await require_types.resolveAFS(options);
55
67
  const canonicalPath = require_path_utils.cliPathToCanonical(argv.path);
56
68
  const writeData = {};
57
69
  if (argv.content !== void 0) writeData.content = argv.content;
58
70
  if (metadata) writeData.meta = metadata;
59
- const result = await afs.write(canonicalPath, writeData, { append: argv.append });
71
+ if (argv.patch) writeData.patches = JSON.parse(argv.patch);
72
+ const result = await afs.write(canonicalPath, writeData, { mode: argv.mode });
60
73
  options.onResult({
61
74
  command: "write",
62
75
  result,
@@ -8,7 +8,8 @@ import { CommandModule } from "yargs";
8
8
  interface WriteArgs {
9
9
  path: string;
10
10
  content?: string;
11
- append: boolean;
11
+ mode: "replace" | "append" | "prepend" | "patch" | "create" | "update";
12
+ patch?: string;
12
13
  meta?: string[];
13
14
  }
14
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"write.d.cts","names":[],"sources":["../../../src/core/commands/write.ts"],"mappings":";;;;;;;UAgBiB,SAAA;EACf,IAAA;EACA,OAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;iBAwBc,kBAAA,CACd,OAAA,EAAS,qBAAA,GACR,aAAA,UAAuB,SAAA"}
1
+ {"version":3,"file":"write.d.cts","names":[],"sources":["../../../src/core/commands/write.ts"],"mappings":";;;;;;;UAgBiB,SAAA;EACf,IAAA;EACA,OAAA;EACA,IAAA;EACA,KAAA;EACA,IAAA;AAAA;;;;iBAwBc,kBAAA,CACd,OAAA,EAAS,qBAAA,GACR,aAAA,UAAuB,SAAA"}
@@ -8,7 +8,8 @@ import { CommandModule } from "yargs";
8
8
  interface WriteArgs {
9
9
  path: string;
10
10
  content?: string;
11
- append: boolean;
11
+ mode: "replace" | "append" | "prepend" | "patch" | "create" | "update";
12
+ patch?: string;
12
13
  meta?: string[];
13
14
  }
14
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"write.d.mts","names":[],"sources":["../../../src/core/commands/write.ts"],"mappings":";;;;;;;UAgBiB,SAAA;EACf,IAAA;EACA,OAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;iBAwBc,kBAAA,CACd,OAAA,EAAS,qBAAA,GACR,aAAA,UAAuB,SAAA"}
1
+ {"version":3,"file":"write.d.mts","names":[],"sources":["../../../src/core/commands/write.ts"],"mappings":";;;;;;;UAgBiB,SAAA;EACf,IAAA;EACA,OAAA;EACA,IAAA;EACA,KAAA;EACA,IAAA;AAAA;;;;iBAwBc,kBAAA,CACd,OAAA,EAAS,qBAAA,GACR,aAAA,UAAuB,SAAA"}
@@ -36,10 +36,22 @@ function createWriteCommand(options) {
36
36
  type: "string",
37
37
  description: "Content to write"
38
38
  },
39
- append: {
40
- type: "boolean",
41
- description: "Append to file instead of overwriting",
42
- default: false
39
+ mode: {
40
+ type: "string",
41
+ choices: [
42
+ "replace",
43
+ "append",
44
+ "prepend",
45
+ "patch",
46
+ "create",
47
+ "update"
48
+ ],
49
+ description: "Write mode",
50
+ default: "replace"
51
+ },
52
+ patch: {
53
+ type: "string",
54
+ description: "JSON array of patch operations (for mode=patch)"
43
55
  },
44
56
  meta: {
45
57
  type: "string",
@@ -50,13 +62,14 @@ function createWriteCommand(options) {
50
62
  handler: async (argv) => {
51
63
  const metadata = parseMetaValues(argv.meta);
52
64
  const fields = metadata ? Object.keys(metadata) : void 0;
53
- if (argv.content === void 0 && !metadata) throw new Error("write requires content (use --content or provide as second argument)");
65
+ if (argv.content === void 0 && !metadata && argv.mode !== "patch") throw new Error("write requires content (use --content or provide as second argument)");
54
66
  const afs = await resolveAFS(options);
55
67
  const canonicalPath = cliPathToCanonical(argv.path);
56
68
  const writeData = {};
57
69
  if (argv.content !== void 0) writeData.content = argv.content;
58
70
  if (metadata) writeData.meta = metadata;
59
- const result = await afs.write(canonicalPath, writeData, { append: argv.append });
71
+ if (argv.patch) writeData.patches = JSON.parse(argv.patch);
72
+ const result = await afs.write(canonicalPath, writeData, { mode: argv.mode });
60
73
  options.onResult({
61
74
  command: "write",
62
75
  result,
@@ -1 +1 @@
1
- {"version":3,"file":"write.mjs","names":[],"sources":["../../../src/core/commands/write.ts"],"sourcesContent":["/**\n * write Command - Core Implementation\n *\n * Writes content to a file/node. Accepts AFS instance directly.\n * Returns AFSWriteResult directly (no custom type).\n */\n\nimport type { AFSWriteEntryPayload } from \"@aigne/afs\";\nimport type { CommandModule } from \"yargs\";\nimport { formatWriteOutput } from \"../formatters/index.js\";\nimport { cliPathToCanonical } from \"../path-utils.js\";\nimport { type CommandFactoryOptions, resolveAFS } from \"./types.js\";\n\n/**\n * Write command arguments\n */\nexport interface WriteArgs {\n path: string;\n content?: string;\n append: boolean;\n meta?: string[];\n}\n\n/**\n * Parse --meta values into metadata object\n */\nfunction parseMetaValues(metaValues?: string[]): Record<string, string> | undefined {\n if (!metaValues || metaValues.length === 0) return undefined;\n\n const meta: Record<string, string> = {};\n for (const item of metaValues) {\n const idx = item.indexOf(\"=\");\n if (idx > 0) {\n const key = item.slice(0, idx);\n const value = item.slice(idx + 1);\n meta[key] = value;\n }\n }\n return Object.keys(meta).length > 0 ? meta : undefined;\n}\n\n/**\n * Create write command factory\n */\nexport function createWriteCommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, WriteArgs> {\n return {\n command: \"write <path> [content]\",\n describe: \"Write content to file\",\n builder: {\n path: {\n type: \"string\",\n demandOption: true,\n description: \"Path to write\",\n },\n content: {\n type: \"string\",\n description: \"Content to write\",\n },\n append: {\n type: \"boolean\",\n description: \"Append to file instead of overwriting\",\n default: false,\n },\n meta: {\n type: \"string\",\n array: true,\n description: \"Set metadata field (key=value)\",\n },\n },\n handler: async (argv) => {\n const metadata = parseMetaValues(argv.meta);\n const fields = metadata ? Object.keys(metadata) : undefined;\n\n // Content is required unless only setting metadata\n if (argv.content === undefined && !metadata) {\n throw new Error(\"write requires content (use --content or provide as second argument)\");\n }\n\n const afs = await resolveAFS(options);\n const canonicalPath = cliPathToCanonical(argv.path);\n const writeData: AFSWriteEntryPayload = {};\n\n if (argv.content !== undefined) {\n writeData.content = argv.content;\n }\n if (metadata) {\n writeData.meta = metadata;\n }\n\n const result = await afs.write(canonicalPath, writeData, { append: argv.append });\n options.onResult({\n command: \"write\",\n result,\n format: (res, view) => formatWriteOutput(res, view, { fields }),\n });\n },\n };\n}\n"],"mappings":";;;;;;;;;AA0BA,SAAS,gBAAgB,YAA2D;AAClF,KAAI,CAAC,cAAc,WAAW,WAAW,EAAG,QAAO;CAEnD,MAAM,OAA+B,EAAE;AACvC,MAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,MAAM,KAAK,QAAQ,IAAI;AAC7B,MAAI,MAAM,GAAG;GACX,MAAM,MAAM,KAAK,MAAM,GAAG,IAAI;AAE9B,QAAK,OADS,KAAK,MAAM,MAAM,EAAE;;;AAIrC,QAAO,OAAO,KAAK,KAAK,CAAC,SAAS,IAAI,OAAO;;;;;AAM/C,SAAgB,mBACd,SACmC;AACnC,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS;GACP,MAAM;IACJ,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,SAAS;IACP,MAAM;IACN,aAAa;IACd;GACD,QAAQ;IACN,MAAM;IACN,aAAa;IACb,SAAS;IACV;GACD,MAAM;IACJ,MAAM;IACN,OAAO;IACP,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,WAAW,gBAAgB,KAAK,KAAK;GAC3C,MAAM,SAAS,WAAW,OAAO,KAAK,SAAS,GAAG;AAGlD,OAAI,KAAK,YAAY,UAAa,CAAC,SACjC,OAAM,IAAI,MAAM,uEAAuE;GAGzF,MAAM,MAAM,MAAM,WAAW,QAAQ;GACrC,MAAM,gBAAgB,mBAAmB,KAAK,KAAK;GACnD,MAAM,YAAkC,EAAE;AAE1C,OAAI,KAAK,YAAY,OACnB,WAAU,UAAU,KAAK;AAE3B,OAAI,SACF,WAAU,OAAO;GAGnB,MAAM,SAAS,MAAM,IAAI,MAAM,eAAe,WAAW,EAAE,QAAQ,KAAK,QAAQ,CAAC;AACjF,WAAQ,SAAS;IACf,SAAS;IACT;IACA,SAAS,KAAK,SAAS,kBAAkB,KAAK,MAAM,EAAE,QAAQ,CAAC;IAChE,CAAC;;EAEL"}
1
+ {"version":3,"file":"write.mjs","names":[],"sources":["../../../src/core/commands/write.ts"],"sourcesContent":["/**\n * write Command - Core Implementation\n *\n * Writes content to a file/node. Accepts AFS instance directly.\n * Returns AFSWriteResult directly (no custom type).\n */\n\nimport type { AFSWriteEntryPayload } from \"@aigne/afs\";\nimport type { CommandModule } from \"yargs\";\nimport { formatWriteOutput } from \"../formatters/index.js\";\nimport { cliPathToCanonical } from \"../path-utils.js\";\nimport { type CommandFactoryOptions, resolveAFS } from \"./types.js\";\n\n/**\n * Write command arguments\n */\nexport interface WriteArgs {\n path: string;\n content?: string;\n mode: \"replace\" | \"append\" | \"prepend\" | \"patch\" | \"create\" | \"update\";\n patch?: string;\n meta?: string[];\n}\n\n/**\n * Parse --meta values into metadata object\n */\nfunction parseMetaValues(metaValues?: string[]): Record<string, string> | undefined {\n if (!metaValues || metaValues.length === 0) return undefined;\n\n const meta: Record<string, string> = {};\n for (const item of metaValues) {\n const idx = item.indexOf(\"=\");\n if (idx > 0) {\n const key = item.slice(0, idx);\n const value = item.slice(idx + 1);\n meta[key] = value;\n }\n }\n return Object.keys(meta).length > 0 ? meta : undefined;\n}\n\n/**\n * Create write command factory\n */\nexport function createWriteCommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, WriteArgs> {\n return {\n command: \"write <path> [content]\",\n describe: \"Write content to file\",\n builder: {\n path: {\n type: \"string\",\n demandOption: true,\n description: \"Path to write\",\n },\n content: {\n type: \"string\",\n description: \"Content to write\",\n },\n mode: {\n type: \"string\",\n choices: [\"replace\", \"append\", \"prepend\", \"patch\", \"create\", \"update\"] as const,\n description: \"Write mode\",\n default: \"replace\" as const,\n },\n patch: {\n type: \"string\",\n description: \"JSON array of patch operations (for mode=patch)\",\n },\n meta: {\n type: \"string\",\n array: true,\n description: \"Set metadata field (key=value)\",\n },\n },\n handler: async (argv) => {\n const metadata = parseMetaValues(argv.meta);\n const fields = metadata ? Object.keys(metadata) : undefined;\n\n // Content is required unless only setting metadata or using patch mode\n if (argv.content === undefined && !metadata && argv.mode !== \"patch\") {\n throw new Error(\"write requires content (use --content or provide as second argument)\");\n }\n\n const afs = await resolveAFS(options);\n const canonicalPath = cliPathToCanonical(argv.path);\n const writeData: AFSWriteEntryPayload = {};\n\n if (argv.content !== undefined) {\n writeData.content = argv.content;\n }\n if (metadata) {\n writeData.meta = metadata;\n }\n if (argv.patch) {\n writeData.patches = JSON.parse(argv.patch);\n }\n\n const result = await afs.write(canonicalPath, writeData, { mode: argv.mode });\n options.onResult({\n command: \"write\",\n result,\n format: (res, view) => formatWriteOutput(res, view, { fields }),\n });\n },\n };\n}\n"],"mappings":";;;;;;;;;AA2BA,SAAS,gBAAgB,YAA2D;AAClF,KAAI,CAAC,cAAc,WAAW,WAAW,EAAG,QAAO;CAEnD,MAAM,OAA+B,EAAE;AACvC,MAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,MAAM,KAAK,QAAQ,IAAI;AAC7B,MAAI,MAAM,GAAG;GACX,MAAM,MAAM,KAAK,MAAM,GAAG,IAAI;AAE9B,QAAK,OADS,KAAK,MAAM,MAAM,EAAE;;;AAIrC,QAAO,OAAO,KAAK,KAAK,CAAC,SAAS,IAAI,OAAO;;;;;AAM/C,SAAgB,mBACd,SACmC;AACnC,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS;GACP,MAAM;IACJ,MAAM;IACN,cAAc;IACd,aAAa;IACd;GACD,SAAS;IACP,MAAM;IACN,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,SAAS;KAAC;KAAW;KAAU;KAAW;KAAS;KAAU;KAAS;IACtE,aAAa;IACb,SAAS;IACV;GACD,OAAO;IACL,MAAM;IACN,aAAa;IACd;GACD,MAAM;IACJ,MAAM;IACN,OAAO;IACP,aAAa;IACd;GACF;EACD,SAAS,OAAO,SAAS;GACvB,MAAM,WAAW,gBAAgB,KAAK,KAAK;GAC3C,MAAM,SAAS,WAAW,OAAO,KAAK,SAAS,GAAG;AAGlD,OAAI,KAAK,YAAY,UAAa,CAAC,YAAY,KAAK,SAAS,QAC3D,OAAM,IAAI,MAAM,uEAAuE;GAGzF,MAAM,MAAM,MAAM,WAAW,QAAQ;GACrC,MAAM,gBAAgB,mBAAmB,KAAK,KAAK;GACnD,MAAM,YAAkC,EAAE;AAE1C,OAAI,KAAK,YAAY,OACnB,WAAU,UAAU,KAAK;AAE3B,OAAI,SACF,WAAU,OAAO;AAEnB,OAAI,KAAK,MACP,WAAU,UAAU,KAAK,MAAM,KAAK,MAAM;GAG5C,MAAM,SAAS,MAAM,IAAI,MAAM,eAAe,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;AAC7E,WAAQ,SAAS;IACf,SAAS;IACT;IACA,SAAS,KAAK,SAAS,kBAAkB,KAAK,MAAM,EAAE,QAAQ,CAAC;IAChE,CAAC;;EAEL"}
@@ -4,6 +4,28 @@ let yargs = require("yargs");
4
4
  yargs = require_rolldown_runtime.__toESM(yargs);
5
5
 
6
6
  //#region src/core/executor/index.ts
7
+ /** Known command names and aliases for suggestion matching. */
8
+ const KNOWN_COMMANDS = [
9
+ "ls",
10
+ "list",
11
+ "read",
12
+ "cat",
13
+ "write",
14
+ "delete",
15
+ "rm",
16
+ "stat",
17
+ "exec",
18
+ "explain",
19
+ "search",
20
+ "grep",
21
+ "find",
22
+ "mount",
23
+ "serve",
24
+ "explore",
25
+ "service",
26
+ "connect",
27
+ "vault"
28
+ ];
7
29
  /**
8
30
  * AFS Command Executor
9
31
  *
@@ -43,6 +65,8 @@ var AFSCommandExecutor = class {
43
65
  commandResult = result;
44
66
  }
45
67
  };
68
+ let failMsg;
69
+ let failErr;
46
70
  let parser = (0, yargs.default)(normalizedArgs).scriptName("afs").usage("$0 <command> [options]").option("json", {
47
71
  type: "boolean",
48
72
  description: "Output in JSON format",
@@ -66,34 +90,42 @@ var AFSCommandExecutor = class {
66
90
  type: "boolean",
67
91
  description: "Start interactive REPL mode",
68
92
  global: false
69
- }).help(true).alias("h", "help").version(this.options.version || "unknown").alias("v", "version").demandCommand().showHelpOnFail(true).exitProcess(false);
93
+ }).help(true).alias("h", "help").version(this.options.version || "unknown").alias("v", "version").demandCommand().strictCommands().exitProcess(false).fail((msg, err) => {
94
+ failErr = err || new Error(msg || "Unknown error");
95
+ failMsg = msg;
96
+ });
70
97
  for (const factory of require_index.commandFactories) parser = parser.command(factory(factoryOptions));
71
98
  try {
72
99
  let output;
73
- let error;
74
- const parsed = await parser.parseAsync(normalizedArgs, {}, (e, _, o) => {
75
- if (e) error = e;
100
+ await parser.parseAsync(normalizedArgs, {}, (_e, _, o) => {
76
101
  output = o;
77
102
  });
78
- if (error) return {
79
- success: false,
80
- command: normalizedArgs[0] || "unknown",
81
- result: void 0,
82
- formatted: output,
83
- error: { message: error.message }
84
- };
85
- if (parsed.help) return {
103
+ if (failErr) {
104
+ const formatted$1 = await this.formatFailure(normalizedArgs, failMsg, parser);
105
+ return {
106
+ success: false,
107
+ command: normalizedArgs[0] || "unknown",
108
+ result: void 0,
109
+ formatted: formatted$1,
110
+ error: { message: failMsg || failErr.message }
111
+ };
112
+ }
113
+ if (output) return {
86
114
  success: true,
87
115
  command: "help",
88
116
  formatted: output
89
117
  };
90
- if (parsed.version) return {
91
- success: true,
92
- command: "version",
93
- formatted: output
94
- };
95
- if (!commandResult) throw new Error("Command not found");
96
- const view = outputOptions.json ? "json" : outputOptions.yaml ? "yaml" : outputOptions.view;
118
+ if (!commandResult) {
119
+ const formatted$1 = await this.formatFailure(normalizedArgs, void 0, parser);
120
+ return {
121
+ success: false,
122
+ command: normalizedArgs[0] || "unknown",
123
+ result: void 0,
124
+ formatted: formatted$1,
125
+ error: { message: `Unknown command: "${normalizedArgs[0] || ""}"` }
126
+ };
127
+ }
128
+ const view = outputOptions.json ? "json" : outputOptions.yaml ? "yaml" : commandResult.viewOverride ?? outputOptions.view;
97
129
  const formatted = commandResult.format(commandResult.result, view, { path: this.extractPath(normalizedArgs) });
98
130
  if (commandResult.error) return {
99
131
  success: false,
@@ -120,6 +152,31 @@ var AFSCommandExecutor = class {
120
152
  }
121
153
  }
122
154
  /**
155
+ * Format a friendly error message for unknown/invalid commands.
156
+ */
157
+ async formatFailure(args, failMsg, parser) {
158
+ const cmd = args[0] || "";
159
+ const lines = [];
160
+ if (cmd && failMsg?.includes("Unknown command")) {
161
+ lines.push(`Unknown command: "${cmd}"`);
162
+ const suggestions = suggestCommands(cmd);
163
+ if (suggestions.length > 0) {
164
+ lines.push("");
165
+ lines.push(`Did you mean?`);
166
+ for (const s of suggestions) lines.push(` afs ${s}`);
167
+ }
168
+ } else if (failMsg) lines.push(failMsg);
169
+ else lines.push(`Unknown command: "${cmd}"`);
170
+ lines.push("");
171
+ try {
172
+ const helpText = await parser.getHelp();
173
+ lines.push(helpText);
174
+ } catch {
175
+ lines.push("Run \"afs --help\" to see available commands.");
176
+ }
177
+ return lines.join("\n");
178
+ }
179
+ /**
123
180
  * Normalize argv to an array of strings
124
181
  */
125
182
  normalizeArgv(argv) {
@@ -191,6 +248,25 @@ var AFSCommandExecutor = class {
191
248
  }
192
249
  }
193
250
  };
251
+ /** Levenshtein distance between two strings. */
252
+ function levenshtein(a, b) {
253
+ const m = a.length;
254
+ const n = b.length;
255
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
256
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
257
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
258
+ 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]);
259
+ return dp[m][n];
260
+ }
261
+ /** Suggest known commands similar to the given input. */
262
+ function suggestCommands(input) {
263
+ const lower = input.toLowerCase();
264
+ return KNOWN_COMMANDS.map((cmd) => ({
265
+ cmd,
266
+ dist: levenshtein(lower, cmd),
267
+ maxLen: Math.max(lower.length, cmd.length)
268
+ })).filter((x) => x.dist > 0 && x.dist < x.maxLen * .5).sort((a, b) => a.dist - b.dist).map((x) => x.cmd).slice(0, 3);
269
+ }
194
270
 
195
271
  //#endregion
196
272
  exports.AFSCommandExecutor = AFSCommandExecutor;
@@ -55,6 +55,10 @@ declare class AFSCommandExecutor {
55
55
  * @returns Execution result with formatted output
56
56
  */
57
57
  execute(argv: string | string[]): Promise<ExecuteResult>;
58
+ /**
59
+ * Format a friendly error message for unknown/invalid commands.
60
+ */
61
+ private formatFailure;
58
62
  /**
59
63
  * Normalize argv to an array of strings
60
64
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/core/executor/index.ts"],"mappings":";;;;;;UAkBiB,aAAA;EAQf;EANA,OAAA;EAUE;EARF,OAAA;EAUS;EART,MAAA;EAee;EAbf,SAAA;;EAEA,KAAA;IAaA,qCAXE,IAAA,WAeF;IAbE,OAAA;EAAA;AAAA;;;;UAOa,eAAA;EAsCiC;EApChD,GAAA;EAoC+C;EAlC/C,GAAA;EAkBQ;EAhBR,OAAA;AAAA;;;;;;;;;;;;;cAeW,kBAAA;EAAA,QACH,GAAA;EAAA,QACA,OAAA;cAEI,GAAA,GAAM,GAAA,EAAK,OAAA,GAAU,eAAA;;;;;;;;;EAa3B,OAAA,CAAQ,IAAA,sBAA0B,OAAA,CAAQ,aAAA;;;;UA6IxC,aAAA;;;;UAoCA,QAAA;;;;UA2CA,oBAAA;;;;UA+BA,WAAA;AAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/core/executor/index.ts"],"mappings":";;;;;;UAkBiB,aAAA;EAQf;EANA,OAAA;EAUE;EARF,OAAA;EAUS;EART,MAAA;EAee;EAbf,SAAA;;EAEA,KAAA;IAaA,qCAXE,IAAA,WAeF;IAbE,OAAA;EAAA;AAAA;;;;UAOa,eAAA;EA6DiC;EA3DhD,GAAA;EA2D+C;EAzD/C,GAAA;EAyCQ;EAvCR,OAAA;AAAA;;;;;;;;;;;;;cAsCW,kBAAA;EAAA,QACH,GAAA;EAAA,QACA,OAAA;cAEI,GAAA,GAAM,GAAA,EAAK,OAAA,GAAU,eAAA;;;;;;;;;EAa3B,OAAA,CAAQ,IAAA,sBAA0B,OAAA,CAAQ,aAAA;;;;UAuJlC,aAAA;;;;UAwCN,aAAA;;;;UAoCA,QAAA;;;;UA2CA,oBAAA;;;;UA+BA,WAAA;AAAA"}
@@ -55,6 +55,10 @@ declare class AFSCommandExecutor {
55
55
  * @returns Execution result with formatted output
56
56
  */
57
57
  execute(argv: string | string[]): Promise<ExecuteResult>;
58
+ /**
59
+ * Format a friendly error message for unknown/invalid commands.
60
+ */
61
+ private formatFailure;
58
62
  /**
59
63
  * Normalize argv to an array of strings
60
64
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/executor/index.ts"],"mappings":";;;;;;UAkBiB,aAAA;EAQf;EANA,OAAA;EAUE;EARF,OAAA;EAUS;EART,MAAA;EAee;EAbf,SAAA;;EAEA,KAAA;IAaA,qCAXE,IAAA,WAeF;IAbE,OAAA;EAAA;AAAA;;;;UAOa,eAAA;EAsCiC;EApChD,GAAA;EAoC+C;EAlC/C,GAAA;EAkBQ;EAhBR,OAAA;AAAA;;;;;;;;;;;;;cAeW,kBAAA;EAAA,QACH,GAAA;EAAA,QACA,OAAA;cAEI,GAAA,GAAM,GAAA,EAAK,OAAA,GAAU,eAAA;;;;;;;;;EAa3B,OAAA,CAAQ,IAAA,sBAA0B,OAAA,CAAQ,aAAA;;;;UA6IxC,aAAA;;;;UAoCA,QAAA;;;;UA2CA,oBAAA;;;;UA+BA,WAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/executor/index.ts"],"mappings":";;;;;;UAkBiB,aAAA;EAQf;EANA,OAAA;EAUE;EARF,OAAA;EAUS;EART,MAAA;EAee;EAbf,SAAA;;EAEA,KAAA;IAaA,qCAXE,IAAA,WAeF;IAbE,OAAA;EAAA;AAAA;;;;UAOa,eAAA;EA6DiC;EA3DhD,GAAA;EA2D+C;EAzD/C,GAAA;EAyCQ;EAvCR,OAAA;AAAA;;;;;;;;;;;;;cAsCW,kBAAA;EAAA,QACH,GAAA;EAAA,QACA,OAAA;cAEI,GAAA,GAAM,GAAA,EAAK,OAAA,GAAU,eAAA;;;;;;;;;EAa3B,OAAA,CAAQ,IAAA,sBAA0B,OAAA,CAAQ,aAAA;;;;UAuJlC,aAAA;;;;UAwCN,aAAA;;;;UAoCA,QAAA;;;;UA2CA,oBAAA;;;;UA+BA,WAAA;AAAA"}