@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.
- package/dist/cli.cjs +3 -2
- package/dist/cli.mjs +3 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/config/afs-loader.cjs +36 -315
- package/dist/config/afs-loader.d.cts.map +1 -1
- package/dist/config/afs-loader.d.mts +2 -1
- package/dist/config/afs-loader.d.mts.map +1 -1
- package/dist/config/afs-loader.mjs +28 -307
- package/dist/config/afs-loader.mjs.map +1 -1
- package/dist/config/credential-helpers.cjs +303 -0
- package/dist/config/credential-helpers.d.mts +2 -0
- package/dist/config/credential-helpers.mjs +300 -0
- package/dist/config/credential-helpers.mjs.map +1 -0
- package/dist/config/loader.cjs +3 -1
- package/dist/config/loader.mjs +3 -2
- package/dist/config/loader.mjs.map +1 -1
- package/dist/config/program-install.cjs +450 -0
- package/dist/config/program-install.d.mts +1 -0
- package/dist/config/program-install.mjs +444 -0
- package/dist/config/program-install.mjs.map +1 -0
- package/dist/core/commands/connect.cjs +53 -0
- package/dist/core/commands/connect.d.mts +2 -0
- package/dist/core/commands/connect.mjs +55 -0
- package/dist/core/commands/connect.mjs.map +1 -0
- package/dist/core/commands/daemon.cjs +211 -0
- package/dist/core/commands/daemon.d.mts +2 -0
- package/dist/core/commands/daemon.mjs +212 -0
- package/dist/core/commands/daemon.mjs.map +1 -0
- package/dist/core/commands/explain.cjs +3 -1
- package/dist/core/commands/explain.mjs +3 -1
- package/dist/core/commands/explain.mjs.map +1 -1
- package/dist/core/commands/explore.cjs +47 -12
- package/dist/core/commands/explore.mjs +47 -12
- package/dist/core/commands/explore.mjs.map +1 -1
- package/dist/core/commands/gen-agent-md.cjs +126 -0
- package/dist/core/commands/gen-agent-md.d.mts +2 -0
- package/dist/core/commands/gen-agent-md.mjs +125 -0
- package/dist/core/commands/gen-agent-md.mjs.map +1 -0
- package/dist/core/commands/index.cjs +13 -1
- package/dist/core/commands/index.d.cts.map +1 -1
- package/dist/core/commands/index.d.mts +6 -0
- package/dist/core/commands/index.d.mts.map +1 -1
- package/dist/core/commands/index.mjs +13 -1
- package/dist/core/commands/index.mjs.map +1 -1
- package/dist/core/commands/install.cjs +139 -0
- package/dist/core/commands/install.d.mts +2 -0
- package/dist/core/commands/install.mjs +140 -0
- package/dist/core/commands/install.mjs.map +1 -0
- package/dist/core/commands/ls.cjs +14 -2
- package/dist/core/commands/ls.d.cts +2 -0
- package/dist/core/commands/ls.d.cts.map +1 -1
- package/dist/core/commands/ls.d.mts +2 -0
- package/dist/core/commands/ls.d.mts.map +1 -1
- package/dist/core/commands/ls.mjs +14 -2
- package/dist/core/commands/ls.mjs.map +1 -1
- package/dist/core/commands/mcp-bridge.cjs +201 -0
- package/dist/core/commands/mcp-bridge.d.mts +2 -0
- package/dist/core/commands/mcp-bridge.mjs +201 -0
- package/dist/core/commands/mcp-bridge.mjs.map +1 -0
- package/dist/core/commands/read.cjs +20 -7
- package/dist/core/commands/read.d.cts +2 -0
- package/dist/core/commands/read.d.cts.map +1 -1
- package/dist/core/commands/read.d.mts +2 -0
- package/dist/core/commands/read.d.mts.map +1 -1
- package/dist/core/commands/read.mjs +20 -7
- package/dist/core/commands/read.mjs.map +1 -1
- package/dist/core/commands/search.cjs +5 -1
- package/dist/core/commands/search.mjs +5 -1
- package/dist/core/commands/search.mjs.map +1 -1
- package/dist/core/commands/stat.mjs.map +1 -1
- package/dist/core/commands/types.d.cts +2 -0
- package/dist/core/commands/types.d.cts.map +1 -1
- package/dist/core/commands/types.d.mts +2 -0
- package/dist/core/commands/types.d.mts.map +1 -1
- package/dist/core/commands/types.mjs.map +1 -1
- package/dist/core/commands/vault.cjs +289 -0
- package/dist/core/commands/vault.d.mts +2 -0
- package/dist/core/commands/vault.mjs +289 -0
- package/dist/core/commands/vault.mjs.map +1 -0
- package/dist/core/commands/write.cjs +19 -6
- package/dist/core/commands/write.d.cts +2 -1
- package/dist/core/commands/write.d.cts.map +1 -1
- package/dist/core/commands/write.d.mts +2 -1
- package/dist/core/commands/write.d.mts.map +1 -1
- package/dist/core/commands/write.mjs +19 -6
- package/dist/core/commands/write.mjs.map +1 -1
- package/dist/core/executor/index.cjs +95 -19
- package/dist/core/executor/index.d.cts +4 -0
- package/dist/core/executor/index.d.cts.map +1 -1
- package/dist/core/executor/index.d.mts +4 -0
- package/dist/core/executor/index.d.mts.map +1 -1
- package/dist/core/executor/index.mjs +95 -19
- package/dist/core/executor/index.mjs.map +1 -1
- package/dist/core/formatters/index.d.mts +1 -0
- package/dist/core/formatters/install.cjs +40 -0
- package/dist/core/formatters/install.d.mts +1 -0
- package/dist/core/formatters/install.mjs +36 -0
- package/dist/core/formatters/install.mjs.map +1 -0
- package/dist/core/formatters/vault.cjs +36 -0
- package/dist/core/formatters/vault.mjs +32 -0
- package/dist/core/formatters/vault.mjs.map +1 -0
- package/dist/credential/auth-server.cjs +22 -4
- package/dist/credential/auth-server.mjs +22 -4
- package/dist/credential/auth-server.mjs.map +1 -1
- package/dist/credential/index.d.mts +2 -1
- package/dist/credential/mcp-auth-context.cjs +21 -5
- package/dist/credential/mcp-auth-context.mjs +21 -5
- package/dist/credential/mcp-auth-context.mjs.map +1 -1
- package/dist/credential/resolver.cjs +11 -3
- package/dist/credential/resolver.mjs +11 -3
- package/dist/credential/resolver.mjs.map +1 -1
- package/dist/credential/vault-store.d.mts +1 -0
- package/dist/daemon/config-manager.cjs +279 -0
- package/dist/daemon/config-manager.mjs +279 -0
- package/dist/daemon/config-manager.mjs.map +1 -0
- package/dist/daemon/manager.cjs +164 -0
- package/dist/daemon/manager.mjs +157 -0
- package/dist/daemon/manager.mjs.map +1 -0
- package/dist/daemon/server.cjs +220 -0
- package/dist/daemon/server.mjs +220 -0
- package/dist/daemon/server.mjs.map +1 -0
- package/dist/mcp/http-transport.cjs +14 -1
- package/dist/mcp/http-transport.mjs +14 -1
- package/dist/mcp/http-transport.mjs.map +1 -1
- package/dist/mcp/server.cjs +4 -2
- package/dist/mcp/server.mjs +4 -2
- package/dist/mcp/server.mjs.map +1 -1
- package/dist/mcp/tools.cjs +62 -12
- package/dist/mcp/tools.mjs +62 -12
- package/dist/mcp/tools.mjs.map +1 -1
- package/dist/program/daemon-integration.cjs +46 -0
- package/dist/program/daemon-integration.mjs +45 -0
- package/dist/program/daemon-integration.mjs.map +1 -0
- package/dist/program/program-manager.cjs +166 -0
- package/dist/program/program-manager.mjs +166 -0
- package/dist/program/program-manager.mjs.map +1 -0
- package/dist/program/trigger-scanner.cjs +148 -0
- package/dist/program/trigger-scanner.mjs +148 -0
- package/dist/program/trigger-scanner.mjs.map +1 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +11 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs.map +1 -0
- package/dist/providers/vault/dist/encrypted-file.cjs +158 -0
- package/dist/providers/vault/dist/encrypted-file.mjs +153 -0
- package/dist/providers/vault/dist/encrypted-file.mjs.map +1 -0
- package/dist/providers/vault/dist/index.cjs +405 -0
- package/dist/providers/vault/dist/index.mjs +400 -0
- package/dist/providers/vault/dist/index.mjs.map +1 -0
- package/dist/providers/vault/dist/key-resolver.cjs +181 -0
- package/dist/providers/vault/dist/key-resolver.mjs +180 -0
- package/dist/providers/vault/dist/key-resolver.mjs.map +1 -0
- package/dist/repl.cjs +109 -14
- package/dist/repl.d.cts.map +1 -1
- package/dist/repl.d.mts.map +1 -1
- package/dist/repl.mjs +109 -14
- package/dist/repl.mjs.map +1 -1
- 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":"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"}
|
|
@@ -36,10 +36,22 @@ function createWriteCommand(options) {
|
|
|
36
36
|
type: "string",
|
|
37
37
|
description: "Content to write"
|
|
38
38
|
},
|
|
39
|
-
|
|
40
|
-
type: "
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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.d.cts","names":[],"sources":["../../../src/core/commands/write.ts"],"mappings":";;;;;;;UAgBiB,SAAA;EACf,IAAA;EACA,OAAA;EACA,
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write.d.mts","names":[],"sources":["../../../src/core/commands/write.ts"],"mappings":";;;;;;;UAgBiB,SAAA;EACf,IAAA;EACA,OAAA;EACA,
|
|
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
|
-
|
|
40
|
-
type: "
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
|
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().
|
|
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
|
-
|
|
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 (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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;
|
|
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;
|
|
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"}
|