@aigne/afs-cli 1.11.0-beta.7 → 1.11.0-beta.9
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/_virtual/rolldown_runtime.mjs +7 -0
- package/dist/config/afs-loader.cjs +466 -22
- package/dist/config/afs-loader.d.cts +6 -1
- package/dist/config/afs-loader.d.cts.map +1 -1
- package/dist/config/afs-loader.d.mts +6 -1
- package/dist/config/afs-loader.d.mts.map +1 -1
- package/dist/config/afs-loader.mjs +465 -22
- package/dist/config/afs-loader.mjs.map +1 -1
- package/dist/config/loader.cjs +28 -11
- package/dist/config/loader.mjs +28 -11
- package/dist/config/loader.mjs.map +1 -1
- package/dist/config/mount-commands.cjs +96 -46
- package/dist/config/mount-commands.d.cts +0 -6
- package/dist/config/mount-commands.d.cts.map +1 -1
- package/dist/config/mount-commands.d.mts +0 -6
- package/dist/config/mount-commands.d.mts.map +1 -1
- package/dist/config/mount-commands.mjs +93 -45
- package/dist/config/mount-commands.mjs.map +1 -1
- package/dist/config/schema.cjs +2 -2
- package/dist/config/schema.mjs +2 -2
- package/dist/config/schema.mjs.map +1 -1
- package/dist/core/commands/exec.cjs +6 -3
- package/dist/core/commands/exec.d.cts.map +1 -1
- package/dist/core/commands/exec.d.mts.map +1 -1
- package/dist/core/commands/exec.mjs +6 -3
- package/dist/core/commands/exec.mjs.map +1 -1
- package/dist/core/commands/explain.cjs +1 -1
- package/dist/core/commands/explain.mjs +1 -1
- package/dist/core/commands/mount.cjs +106 -23
- package/dist/core/commands/mount.d.cts +2 -0
- package/dist/core/commands/mount.d.cts.map +1 -1
- package/dist/core/commands/mount.d.mts +2 -0
- package/dist/core/commands/mount.d.mts.map +1 -1
- package/dist/core/commands/mount.mjs +106 -23
- package/dist/core/commands/mount.mjs.map +1 -1
- package/dist/core/commands/serve.cjs +38 -13
- package/dist/core/commands/serve.mjs +38 -13
- package/dist/core/commands/serve.mjs.map +1 -1
- package/dist/core/commands/types.cjs +6 -1
- package/dist/core/commands/types.d.cts.map +1 -1
- package/dist/core/commands/types.d.mts.map +1 -1
- package/dist/core/commands/types.mjs +6 -1
- package/dist/core/commands/types.mjs.map +1 -1
- package/dist/credential/auth-server.cjs +247 -0
- package/dist/credential/auth-server.mjs +247 -0
- package/dist/credential/auth-server.mjs.map +1 -0
- package/dist/credential/cli-auth-context.cjs +86 -0
- package/dist/credential/cli-auth-context.d.mts +1 -0
- package/dist/credential/cli-auth-context.mjs +86 -0
- package/dist/credential/cli-auth-context.mjs.map +1 -0
- package/dist/credential/index.cjs +5 -0
- package/dist/credential/index.d.mts +4 -0
- package/dist/credential/index.mjs +7 -0
- package/dist/credential/mcp-auth-context.cjs +186 -0
- package/dist/credential/mcp-auth-context.d.mts +1 -0
- package/dist/credential/mcp-auth-context.mjs +186 -0
- package/dist/credential/mcp-auth-context.mjs.map +1 -0
- package/dist/credential/resolver.cjs +125 -0
- package/dist/credential/resolver.d.mts +1 -0
- package/dist/credential/resolver.mjs +125 -0
- package/dist/credential/resolver.mjs.map +1 -0
- package/dist/credential/store.cjs +106 -0
- package/dist/credential/store.d.cts +30 -0
- package/dist/credential/store.d.cts.map +1 -0
- package/dist/credential/store.d.mts +30 -0
- package/dist/credential/store.d.mts.map +1 -0
- package/dist/credential/store.mjs +106 -0
- package/dist/credential/store.mjs.map +1 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +3 -1
- package/dist/mcp/http-transport.cjs +22 -3
- package/dist/mcp/http-transport.mjs +22 -3
- package/dist/mcp/http-transport.mjs.map +1 -1
- package/dist/mcp/prompts.cjs +2 -2
- package/dist/mcp/prompts.mjs +2 -2
- package/dist/mcp/prompts.mjs.map +1 -1
- package/dist/mcp/server.cjs +16 -6
- package/dist/mcp/server.mjs +15 -6
- package/dist/mcp/server.mjs.map +1 -1
- package/dist/mcp/tools.cjs +2 -46
- package/dist/mcp/tools.mjs +2 -46
- package/dist/mcp/tools.mjs.map +1 -1
- package/dist/repl.cjs +9 -3
- package/dist/repl.d.cts.map +1 -1
- package/dist/repl.d.mts.map +1 -1
- package/dist/repl.mjs +9 -3
- package/dist/repl.mjs.map +1 -1
- package/package.json +22 -22
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { MountSchema } from "./schema.mjs";
|
|
2
2
|
import { CONFIG_DIR_NAME, CONFIG_FILE_NAME, ConfigLoader } from "./loader.mjs";
|
|
3
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
4
3
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
6
|
import { parse, stringify } from "smol-toml";
|
|
6
7
|
|
|
7
8
|
//#region src/config/mount-commands.ts
|
|
@@ -47,71 +48,118 @@ async function configMountListCommand(cwd) {
|
|
|
47
48
|
return { mounts: (await new ConfigLoader().load(cwd)).mounts };
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
50
|
-
*
|
|
51
|
+
* Remove a mount from config
|
|
51
52
|
*/
|
|
52
|
-
async function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
53
|
+
async function mountRemoveCommand(cwd, path) {
|
|
54
|
+
const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);
|
|
55
|
+
try {
|
|
56
|
+
const config = parse(await readFile(configPath, "utf-8"));
|
|
57
|
+
const mounts = config.mounts ?? [];
|
|
58
|
+
const index = mounts.findIndex((m) => m.path === path);
|
|
59
|
+
if (index === -1) return {
|
|
60
|
+
success: false,
|
|
61
|
+
message: `Mount path "${path}" not found`
|
|
62
|
+
};
|
|
63
|
+
mounts.splice(index, 1);
|
|
64
|
+
config.mounts = mounts;
|
|
65
|
+
await writeFile(configPath, stringify(config), "utf-8");
|
|
66
|
+
return { success: true };
|
|
67
|
+
} catch {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
message: `Mount path "${path}" not found`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a PersistScope to the config directory path.
|
|
76
|
+
*
|
|
77
|
+
* - "cwd" → <cwd>/.afs-config/
|
|
78
|
+
* - "project" → <projectRoot>/.afs-config/ (falls back to cwd if no .git found)
|
|
79
|
+
* - "user" → ~/.afs-config/
|
|
80
|
+
*/
|
|
81
|
+
async function resolveScopeDir(cwd, scope) {
|
|
82
|
+
if (scope === "user") return join(homedir(), CONFIG_DIR_NAME);
|
|
83
|
+
if (scope === "project") {
|
|
84
|
+
const projectRoot = await new ConfigLoader().findProjectRoot(cwd);
|
|
85
|
+
if (projectRoot) return join(projectRoot, CONFIG_DIR_NAME);
|
|
86
|
+
return join(cwd, CONFIG_DIR_NAME);
|
|
87
|
+
}
|
|
88
|
+
return join(cwd, CONFIG_DIR_NAME);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Persist a mount entry to config.toml at the given scope.
|
|
92
|
+
*
|
|
93
|
+
* Uses upsert semantics: if a mount with the same path already exists,
|
|
94
|
+
* it is replaced. Otherwise the new entry is appended.
|
|
95
|
+
*/
|
|
96
|
+
async function persistMount(cwd, entry, scope = "cwd") {
|
|
97
|
+
const configDir = await resolveScopeDir(cwd, scope);
|
|
74
98
|
const configPath = join(configDir, CONFIG_FILE_NAME);
|
|
75
99
|
const config = { mounts: [] };
|
|
76
100
|
try {
|
|
77
|
-
|
|
101
|
+
const parsed = parse(await readFile(configPath, "utf-8"));
|
|
102
|
+
config.mounts = parsed.mounts ?? [];
|
|
103
|
+
for (const key of Object.keys(parsed)) if (key !== "mounts") config[key] = parsed[key];
|
|
78
104
|
} catch {}
|
|
79
|
-
const
|
|
80
|
-
if (config.mounts
|
|
81
|
-
|
|
82
|
-
message: `Mount path "${normalizedPath}" already exists`
|
|
83
|
-
};
|
|
84
|
-
config.mounts.push(newMount);
|
|
105
|
+
const existingIndex = config.mounts.findIndex((m) => m.path === entry.path);
|
|
106
|
+
if (existingIndex >= 0) config.mounts[existingIndex] = entry;
|
|
107
|
+
else config.mounts.push(entry);
|
|
85
108
|
try {
|
|
86
109
|
await mkdir(configDir, { recursive: true });
|
|
87
110
|
} catch {}
|
|
88
111
|
await writeFile(configPath, stringify(config), "utf-8");
|
|
89
112
|
return {
|
|
90
113
|
success: true,
|
|
91
|
-
|
|
114
|
+
configPath
|
|
92
115
|
};
|
|
93
116
|
}
|
|
94
117
|
/**
|
|
95
|
-
* Remove a mount from config
|
|
118
|
+
* Remove a mount entry from config.toml.
|
|
119
|
+
*
|
|
120
|
+
* If `scope` is provided, only searches the specific config file for that scope.
|
|
121
|
+
* If `scope` is undefined, searches all config files (cwd, project, user) and
|
|
122
|
+
* removes from the first one that contains the mount path.
|
|
96
123
|
*/
|
|
97
|
-
async function
|
|
98
|
-
|
|
124
|
+
async function unpersistMount(cwd, mountPath, scope) {
|
|
125
|
+
if (scope) return removeFromConfigFile(cwd, mountPath, scope);
|
|
126
|
+
for (const s of [
|
|
127
|
+
"cwd",
|
|
128
|
+
"project",
|
|
129
|
+
"user"
|
|
130
|
+
]) {
|
|
131
|
+
const result = await removeFromConfigFile(cwd, mountPath, s);
|
|
132
|
+
if (result.success) return result;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
message: `Mount path "${mountPath}" not found in any config`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Remove a mount from a specific config file.
|
|
141
|
+
*/
|
|
142
|
+
async function removeFromConfigFile(cwd, mountPath, scope) {
|
|
143
|
+
const configPath = join(await resolveScopeDir(cwd, scope), CONFIG_FILE_NAME);
|
|
99
144
|
try {
|
|
100
|
-
const
|
|
101
|
-
const mounts =
|
|
102
|
-
const index = mounts.findIndex((m) => m.path ===
|
|
145
|
+
const parsed = parse(await readFile(configPath, "utf-8"));
|
|
146
|
+
const mounts = parsed.mounts ?? [];
|
|
147
|
+
const index = mounts.findIndex((m) => m.path === mountPath);
|
|
103
148
|
if (index === -1) return {
|
|
104
149
|
success: false,
|
|
105
|
-
message: `Mount path "${
|
|
150
|
+
message: `Mount path "${mountPath}" not found in ${configPath}`
|
|
106
151
|
};
|
|
107
152
|
mounts.splice(index, 1);
|
|
108
|
-
|
|
109
|
-
await writeFile(configPath, stringify(
|
|
110
|
-
return {
|
|
153
|
+
parsed.mounts = mounts;
|
|
154
|
+
await writeFile(configPath, stringify(parsed), "utf-8");
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
configPath
|
|
158
|
+
};
|
|
111
159
|
} catch {
|
|
112
160
|
return {
|
|
113
161
|
success: false,
|
|
114
|
-
message: `
|
|
162
|
+
message: `Config file not found or unreadable: ${configPath}`
|
|
115
163
|
};
|
|
116
164
|
}
|
|
117
165
|
}
|
|
@@ -126,7 +174,7 @@ async function mountValidateCommand(cwd) {
|
|
|
126
174
|
for (const mount of mounts) {
|
|
127
175
|
const validation = MountSchema.safeParse(mount);
|
|
128
176
|
if (!validation.success) {
|
|
129
|
-
for (const err of validation.error.
|
|
177
|
+
for (const err of validation.error.issues) errors.push(`Mount "${mount.path}": ${err.message}`);
|
|
130
178
|
continue;
|
|
131
179
|
}
|
|
132
180
|
if (mount.uri.startsWith("fs://")) {
|
|
@@ -151,5 +199,5 @@ async function mountValidateCommand(cwd) {
|
|
|
151
199
|
}
|
|
152
200
|
|
|
153
201
|
//#endregion
|
|
154
|
-
export { configMountListCommand,
|
|
202
|
+
export { configMountListCommand, mountRemoveCommand, mountValidateCommand, persistMount, resolveUriPath, unpersistMount };
|
|
155
203
|
//# sourceMappingURL=mount-commands.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-commands.mjs","names":[],"sources":["../../src/config/mount-commands.ts"],"sourcesContent":["/**\n * Mount Configuration Commands\n *\n * CLI-specific commands for managing mount configuration files.\n * These operate on afs.toml config files, not the AFS instance directly.\n */\n\nimport { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { parse, stringify } from \"smol-toml\";\nimport { CONFIG_DIR_NAME, CONFIG_FILE_NAME, ConfigLoader } from \"./loader.js\";\nimport { MountSchema } from \"./schema.js\";\n\nexport interface ConfigMountEntry {\n path: string;\n uri: string;\n namespace?: string;\n description?: string;\n access_mode?: \"readonly\" | \"readwrite\";\n auth?: string;\n token?: string;\n options?: Record<string, unknown>;\n}\n\nexport interface ConfigMountListResult {\n mounts: ConfigMountEntry[];\n}\n\nexport interface MountCommandResult {\n success: boolean;\n message?: string;\n normalizedPath?: string;\n}\n\nexport interface MountValidateResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Check if a path looks like a remote Git URL\n */\nfunction isRemoteGitUrl(path: string): boolean {\n if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:/.test(path)) {\n return true;\n }\n if (/^(https?|ssh|git):\\/\\//.test(path)) {\n return true;\n }\n return false;\n}\n\n/**\n * Resolve relative paths in URI to absolute paths\n */\nfunction resolveUriPath(uri: string, cwd: string): string {\n const schemeMatch = uri.match(/^([a-z][a-z0-9+.-]*):\\/\\//i);\n\n if (!schemeMatch) {\n if (isRemoteGitUrl(uri)) {\n return `git://${uri}`;\n }\n const absolutePath = isAbsolute(uri) ? uri : resolve(cwd, uri);\n return `fs://${absolutePath}`;\n }\n\n const scheme = schemeMatch[1];\n const pathPart = uri.slice(schemeMatch[0].length);\n\n if (![\"fs\", \"git\", \"sqlite\", \"json\"].includes(scheme!)) {\n return uri;\n }\n\n if (scheme === \"git\" && isRemoteGitUrl(pathPart)) {\n return uri;\n }\n\n if (isAbsolute(pathPart)) {\n return uri;\n }\n\n const absolutePath = resolve(cwd, pathPart);\n return `${scheme}://${absolutePath}`;\n}\n\n/**\n * List all mounts from config (merged from all config layers)\n */\nexport async function configMountListCommand(cwd: string): Promise<ConfigMountListResult> {\n const loader = new ConfigLoader();\n const config = await loader.load(cwd);\n return {\n mounts: config.mounts as ConfigMountEntry[],\n };\n}\n\n/**\n * Add a mount to config\n */\nexport async function mountAddCommand(\n cwd: string,\n path: string,\n uri: string,\n options: { description?: string; token?: string } = {},\n): Promise<MountCommandResult> {\n if (!uri || uri.trim() === \"\") {\n return {\n success: false,\n message: \"URI is required\",\n };\n }\n\n const resolvedUri = resolveUriPath(uri, cwd);\n\n const validation = MountSchema.safeParse({\n path,\n uri: resolvedUri,\n ...options,\n });\n if (!validation.success) {\n const errors = validation.error.errors.map((e) => e.message).join(\"; \");\n return {\n success: false,\n message: errors,\n };\n }\n\n const newMount: ConfigMountEntry = {\n path: validation.data.path,\n uri: validation.data.uri,\n ...(options.description && { description: options.description }),\n ...(options.token && { token: options.token }),\n };\n\n const configDir = join(cwd, CONFIG_DIR_NAME);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n const config: { mounts: ConfigMountEntry[] } = { mounts: [] };\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as { mounts?: ConfigMountEntry[] };\n config.mounts = parsed.mounts ?? [];\n } catch {\n // Config doesn't exist, will create\n }\n\n const normalizedPath = validation.data.path;\n if (config.mounts.some((m) => m.path === normalizedPath)) {\n return {\n success: false,\n message: `Mount path \"${normalizedPath}\" already exists`,\n };\n }\n\n config.mounts.push(newMount);\n\n try {\n await mkdir(configDir, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n normalizedPath,\n };\n}\n\n/**\n * Remove a mount from config\n */\nexport async function mountRemoveCommand(cwd: string, path: string): Promise<MountCommandResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: ConfigMountEntry[] };\n const mounts = config.mounts ?? [];\n\n const index = mounts.findIndex((m) => m.path === path);\n if (index === -1) {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n\n mounts.splice(index, 1);\n config.mounts = mounts;\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n };\n } catch {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n}\n\n/**\n * Validate mount configuration\n */\nexport async function mountValidateCommand(cwd: string): Promise<MountValidateResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n const errors: string[] = [];\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: ConfigMountEntry[] };\n const mounts = config.mounts ?? [];\n\n for (const mount of mounts) {\n const validation = MountSchema.safeParse(mount);\n if (!validation.success) {\n for (const err of validation.error.errors) {\n errors.push(`Mount \"${mount.path}\": ${err.message}`);\n }\n continue;\n }\n\n if (mount.uri.startsWith(\"fs://\")) {\n const targetPath = mount.uri.replace(\"fs://\", \"\");\n try {\n await access(targetPath);\n } catch {\n errors.push(`Mount target \"${targetPath}\" does not exist`);\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n } catch {\n return {\n valid: true,\n errors: [],\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mount-commands.mjs","names":[],"sources":["../../src/config/mount-commands.ts"],"sourcesContent":["/**\n * Mount Configuration Commands\n *\n * CLI-specific commands for managing mount configuration files.\n * These operate on afs.toml config files, not the AFS instance directly.\n */\n\nimport { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { parse, stringify } from \"smol-toml\";\nimport { CONFIG_DIR_NAME, CONFIG_FILE_NAME, ConfigLoader } from \"./loader.js\";\nimport { MountSchema } from \"./schema.js\";\n\n// ─── Persistence Types ───────────────────────────────────────────────────\n\nexport type PersistScope = \"cwd\" | \"project\" | \"user\";\n\nexport interface PersistResult {\n success: boolean;\n configPath?: string;\n message?: string;\n}\n\nexport interface ConfigMountEntry {\n path: string;\n uri: string;\n namespace?: string;\n description?: string;\n access_mode?: \"readonly\" | \"readwrite\";\n auth?: string;\n token?: string;\n options?: Record<string, unknown>;\n}\n\nexport interface ConfigMountListResult {\n mounts: ConfigMountEntry[];\n}\n\nexport interface MountCommandResult {\n success: boolean;\n message?: string;\n normalizedPath?: string;\n}\n\nexport interface MountValidateResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Check if a path looks like a remote Git URL\n */\nfunction isRemoteGitUrl(path: string): boolean {\n if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:/.test(path)) {\n return true;\n }\n if (/^(https?|ssh|git):\\/\\//.test(path)) {\n return true;\n }\n return false;\n}\n\n/**\n * Resolve relative paths in URI to absolute paths\n */\nexport function resolveUriPath(uri: string, cwd: string): string {\n const schemeMatch = uri.match(/^([a-z][a-z0-9+.-]*):\\/\\//i);\n\n if (!schemeMatch) {\n if (isRemoteGitUrl(uri)) {\n return `git://${uri}`;\n }\n const absolutePath = isAbsolute(uri) ? uri : resolve(cwd, uri);\n return `fs://${absolutePath}`;\n }\n\n const scheme = schemeMatch[1];\n const pathPart = uri.slice(schemeMatch[0].length);\n\n if (![\"fs\", \"git\", \"sqlite\", \"json\"].includes(scheme!)) {\n return uri;\n }\n\n if (scheme === \"git\" && isRemoteGitUrl(pathPart)) {\n return uri;\n }\n\n if (isAbsolute(pathPart)) {\n return uri;\n }\n\n const absolutePath = resolve(cwd, pathPart);\n return `${scheme}://${absolutePath}`;\n}\n\n/**\n * List all mounts from config (merged from all config layers)\n */\nexport async function configMountListCommand(cwd: string): Promise<ConfigMountListResult> {\n const loader = new ConfigLoader();\n const config = await loader.load(cwd);\n return {\n mounts: config.mounts as ConfigMountEntry[],\n };\n}\n\n/**\n * Add a mount to config\n */\nexport async function mountAddCommand(\n cwd: string,\n path: string,\n uri: string,\n options: { description?: string; token?: string } = {},\n): Promise<MountCommandResult> {\n if (!uri || uri.trim() === \"\") {\n return {\n success: false,\n message: \"URI is required\",\n };\n }\n\n const resolvedUri = resolveUriPath(uri, cwd);\n\n const validation = MountSchema.safeParse({\n path,\n uri: resolvedUri,\n ...options,\n });\n if (!validation.success) {\n const errors = validation.error.issues.map((e) => e.message).join(\"; \");\n return {\n success: false,\n message: errors,\n };\n }\n\n const newMount: ConfigMountEntry = {\n path: validation.data.path,\n uri: validation.data.uri,\n ...(options.description && { description: options.description }),\n ...(options.token && { token: options.token }),\n };\n\n const configDir = join(cwd, CONFIG_DIR_NAME);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n const config: { mounts: ConfigMountEntry[] } = { mounts: [] };\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as { mounts?: ConfigMountEntry[] };\n config.mounts = parsed.mounts ?? [];\n } catch {\n // Config doesn't exist, will create\n }\n\n const normalizedPath = validation.data.path;\n if (config.mounts.some((m) => m.path === normalizedPath)) {\n return {\n success: false,\n message: `Mount path \"${normalizedPath}\" already exists`,\n };\n }\n\n config.mounts.push(newMount);\n\n try {\n await mkdir(configDir, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n normalizedPath,\n };\n}\n\n/**\n * Remove a mount from config\n */\nexport async function mountRemoveCommand(cwd: string, path: string): Promise<MountCommandResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: ConfigMountEntry[] };\n const mounts = config.mounts ?? [];\n\n const index = mounts.findIndex((m) => m.path === path);\n if (index === -1) {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n\n mounts.splice(index, 1);\n config.mounts = mounts;\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return {\n success: true,\n };\n } catch {\n return {\n success: false,\n message: `Mount path \"${path}\" not found`,\n };\n }\n}\n\n// ─── Persistence Functions ────────────────────────────────────────────────\n\n/**\n * Resolve a PersistScope to the config directory path.\n *\n * - \"cwd\" → <cwd>/.afs-config/\n * - \"project\" → <projectRoot>/.afs-config/ (falls back to cwd if no .git found)\n * - \"user\" → ~/.afs-config/\n */\nexport async function resolveScopeDir(cwd: string, scope: PersistScope): Promise<string> {\n if (scope === \"user\") {\n return join(homedir(), CONFIG_DIR_NAME);\n }\n\n if (scope === \"project\") {\n const loader = new ConfigLoader();\n const projectRoot = await loader.findProjectRoot(cwd);\n if (projectRoot) {\n return join(projectRoot, CONFIG_DIR_NAME);\n }\n // Fall back to cwd when no .git found\n return join(cwd, CONFIG_DIR_NAME);\n }\n\n // \"cwd\"\n return join(cwd, CONFIG_DIR_NAME);\n}\n\n/**\n * Persist a mount entry to config.toml at the given scope.\n *\n * Uses upsert semantics: if a mount with the same path already exists,\n * it is replaced. Otherwise the new entry is appended.\n */\nexport async function persistMount(\n cwd: string,\n entry: ConfigMountEntry,\n scope: PersistScope = \"cwd\",\n): Promise<PersistResult> {\n const configDir = await resolveScopeDir(cwd, scope);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n // Read existing config (or start empty)\n const config: Record<string, unknown> & { mounts: ConfigMountEntry[] } = { mounts: [] };\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as Record<string, unknown>;\n config.mounts = (parsed.mounts as ConfigMountEntry[] | undefined) ?? [];\n // Preserve non-mount sections (serve, registry, etc.)\n for (const key of Object.keys(parsed)) {\n if (key !== \"mounts\") {\n config[key] = parsed[key];\n }\n }\n } catch {\n // Config doesn't exist yet, will create\n }\n\n // Upsert: replace existing entry at same path, or append\n const existingIndex = config.mounts.findIndex((m) => m.path === entry.path);\n if (existingIndex >= 0) {\n config.mounts[existingIndex] = entry;\n } else {\n config.mounts.push(entry);\n }\n\n // Write back\n try {\n await mkdir(configDir, { recursive: true });\n } catch {\n // Directory might already exist\n }\n\n await writeFile(configPath, stringify(config), \"utf-8\");\n\n return { success: true, configPath };\n}\n\n/**\n * Remove a mount entry from config.toml.\n *\n * If `scope` is provided, only searches the specific config file for that scope.\n * If `scope` is undefined, searches all config files (cwd, project, user) and\n * removes from the first one that contains the mount path.\n */\nexport async function unpersistMount(\n cwd: string,\n mountPath: string,\n scope?: PersistScope,\n): Promise<PersistResult> {\n if (scope) {\n return removeFromConfigFile(cwd, mountPath, scope);\n }\n\n // Search all scopes: cwd → project → user\n for (const s of [\"cwd\", \"project\", \"user\"] as PersistScope[]) {\n const result = await removeFromConfigFile(cwd, mountPath, s);\n if (result.success) return result;\n }\n\n return { success: false, message: `Mount path \"${mountPath}\" not found in any config` };\n}\n\n/**\n * Remove a mount from a specific config file.\n */\nasync function removeFromConfigFile(\n cwd: string,\n mountPath: string,\n scope: PersistScope,\n): Promise<PersistResult> {\n const configDir = await resolveScopeDir(cwd, scope);\n const configPath = join(configDir, CONFIG_FILE_NAME);\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const parsed = parse(content) as Record<string, unknown>;\n const mounts = (parsed.mounts as ConfigMountEntry[] | undefined) ?? [];\n\n const index = mounts.findIndex((m) => m.path === mountPath);\n if (index === -1) {\n return { success: false, message: `Mount path \"${mountPath}\" not found in ${configPath}` };\n }\n\n mounts.splice(index, 1);\n parsed.mounts = mounts;\n\n await writeFile(configPath, stringify(parsed), \"utf-8\");\n\n return { success: true, configPath };\n } catch {\n return { success: false, message: `Config file not found or unreadable: ${configPath}` };\n }\n}\n\n/**\n * Validate mount configuration\n */\nexport async function mountValidateCommand(cwd: string): Promise<MountValidateResult> {\n const configPath = join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n const errors: string[] = [];\n\n try {\n const content = await readFile(configPath, \"utf-8\");\n const config = parse(content) as { mounts?: ConfigMountEntry[] };\n const mounts = config.mounts ?? [];\n\n for (const mount of mounts) {\n const validation = MountSchema.safeParse(mount);\n if (!validation.success) {\n for (const err of validation.error.issues) {\n errors.push(`Mount \"${mount.path}\": ${err.message}`);\n }\n continue;\n }\n\n if (mount.uri.startsWith(\"fs://\")) {\n const targetPath = mount.uri.replace(\"fs://\", \"\");\n try {\n await access(targetPath);\n } catch {\n errors.push(`Mount target \"${targetPath}\" does not exist`);\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n } catch {\n return {\n valid: true,\n errors: [],\n };\n }\n}\n"],"mappings":"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"}
|
package/dist/config/schema.cjs
CHANGED
|
@@ -64,7 +64,7 @@ const MountSchema = zod.z.object({
|
|
|
64
64
|
access_mode: zod.z.enum(["readonly", "readwrite"]).optional(),
|
|
65
65
|
auth: zod.z.string().optional(),
|
|
66
66
|
token: zod.z.string().optional(),
|
|
67
|
-
options: zod.z.record(zod.z.unknown()).optional()
|
|
67
|
+
options: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
68
68
|
});
|
|
69
69
|
/**
|
|
70
70
|
* Serve configuration schema
|
|
@@ -83,7 +83,7 @@ const ServeSchema = zod.z.object({
|
|
|
83
83
|
*/
|
|
84
84
|
const RegistrySchema = zod.z.object({
|
|
85
85
|
enabled: zod.z.boolean().default(true),
|
|
86
|
-
providers: zod.z.array(zod.z.record(zod.z.unknown())).optional()
|
|
86
|
+
providers: zod.z.array(zod.z.record(zod.z.string(), zod.z.unknown())).optional()
|
|
87
87
|
});
|
|
88
88
|
/**
|
|
89
89
|
* Root configuration schema for afs.toml
|
package/dist/config/schema.mjs
CHANGED
|
@@ -63,7 +63,7 @@ const MountSchema = z.object({
|
|
|
63
63
|
access_mode: z.enum(["readonly", "readwrite"]).optional(),
|
|
64
64
|
auth: z.string().optional(),
|
|
65
65
|
token: z.string().optional(),
|
|
66
|
-
options: z.record(z.unknown()).optional()
|
|
66
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
67
67
|
});
|
|
68
68
|
/**
|
|
69
69
|
* Serve configuration schema
|
|
@@ -82,7 +82,7 @@ const ServeSchema = z.object({
|
|
|
82
82
|
*/
|
|
83
83
|
const RegistrySchema = z.object({
|
|
84
84
|
enabled: z.boolean().default(true),
|
|
85
|
-
providers: z.array(z.record(z.unknown())).optional()
|
|
85
|
+
providers: z.array(z.record(z.string(), z.unknown())).optional()
|
|
86
86
|
});
|
|
87
87
|
/**
|
|
88
88
|
* Root configuration schema for afs.toml
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.mjs","names":[],"sources":["../../src/config/schema.ts"],"sourcesContent":["import { AFSPathError, validatePath } from \"@aigne/afs\";\nimport { z } from \"zod\";\n\n/**\n * Characters forbidden in namespace names (security-sensitive)\n */\nconst NAMESPACE_FORBIDDEN_CHARS = [\n \"/\", // Path separator\n \"\\\\\", // Windows path separator\n \":\", // Namespace separator\n \";\", // Shell metachar\n \"|\", // Shell pipe\n \"&\", // Shell background\n \"`\", // Shell command substitution\n \"$\", // Shell variable\n \"(\", // Shell subshell\n \")\", // Shell subshell\n \">\", // Shell redirect\n \"<\", // Shell redirect\n \"\\n\", // Newline\n \"\\r\", // Carriage return\n \"\\t\", // Tab\n \"\\x00\", // NUL\n];\n\n/**\n * Validate namespace name\n */\nfunction validateNamespace(namespace: string): string {\n if (!namespace || namespace.trim() === \"\") {\n throw new Error(\"Namespace cannot be empty or whitespace-only\");\n }\n\n for (const char of NAMESPACE_FORBIDDEN_CHARS) {\n if (namespace.includes(char)) {\n throw new Error(`Namespace contains forbidden character: '${char}'`);\n }\n }\n\n return namespace;\n}\n\n/**\n * Mount configuration schema\n */\nexport const MountSchema = z.object({\n /** Mount path (must follow Unix path semantics) */\n path: z.string().transform((val, ctx) => {\n try {\n return validatePath(val);\n } catch (e) {\n const message = e instanceof AFSPathError ? e.message : \"Invalid path\";\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message,\n });\n return z.NEVER;\n }\n }),\n\n /** Provider URI (e.g., fs:///path, git:///repo?branch=main) */\n uri: z.string().min(1, \"URI is required\"),\n\n /** Namespace for this mount (optional, defaults to default namespace) */\n namespace: z\n .string()\n .transform((val, ctx) => {\n try {\n return validateNamespace(val);\n } catch (e) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: e instanceof Error ? e.message : \"Invalid namespace\",\n });\n return z.NEVER;\n }\n })\n .optional(),\n\n /** Human/LLM readable description */\n description: z.string().optional(),\n\n /** Access mode: readonly or readwrite */\n access_mode: z.enum([\"readonly\", \"readwrite\"]).optional(),\n\n /** Authentication string (supports ${ENV_VAR} references) */\n auth: z.string().optional(),\n\n /** Authorization token for HTTP providers (supports ${ENV_VAR} references) */\n token: z.string().optional(),\n\n /** Provider-specific options (passed through to provider) */\n options: z.record(z.unknown()).optional(),\n});\n\n/**\n * Serve configuration schema\n */\nexport const ServeSchema = z.object({\n /** Host address to listen on */\n host: z.string().default(\"localhost\"),\n\n /** Port to listen on */\n port: z.number().int().positive().default(3000),\n\n /** Base path for the server */\n path: z.string().default(\"/afs\"),\n\n /** Run in readonly mode (disable write operations) */\n readonly: z.boolean().default(false),\n\n /** Enable CORS support */\n cors: z.boolean().default(false),\n\n /** Maximum request body size in bytes */\n max_body_size: z\n .number()\n .int()\n .positive()\n .default(10 * 1024 * 1024), // 10MB\n\n /** Bearer token for authorization (supports ${ENV_VAR} references) */\n token: z.string().optional(),\n});\n\n/**\n * Registry configuration schema\n */\nexport const RegistrySchema = z.object({\n /** Enable/disable auto-mounting the official registry (default: true) */\n enabled: z.boolean().default(true),\n\n /** Static provider manifests (for offline/testing use) */\n providers: z.array(z.record(z.unknown())).optional(),\n});\n\n/**\n * Root configuration schema for afs.toml\n */\nexport const ConfigSchema = z.object({\n /** List of mount configurations */\n mounts: z.array(MountSchema).default([]),\n\n /** HTTP server configuration */\n serve: ServeSchema.optional(),\n\n /** Provider registry configuration */\n registry: RegistrySchema.optional(),\n});\n\n/** Type for a single mount configuration */\nexport type MountConfig = z.infer<typeof MountSchema>;\n\n/** Type for serve configuration */\nexport type ServeConfig = z.infer<typeof ServeSchema>;\n\n/** Type for the root AFS configuration */\nexport type AFSConfig = z.infer<typeof ConfigSchema>;\n"],"mappings":";;;;;;;AAMA,MAAM,4BAA4B;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AAKD,SAAS,kBAAkB,WAA2B;AACpD,KAAI,CAAC,aAAa,UAAU,MAAM,KAAK,GACrC,OAAM,IAAI,MAAM,+CAA+C;AAGjE,MAAK,MAAM,QAAQ,0BACjB,KAAI,UAAU,SAAS,KAAK,CAC1B,OAAM,IAAI,MAAM,4CAA4C,KAAK,GAAG;AAIxE,QAAO;;;;;AAMT,MAAa,cAAc,EAAE,OAAO;CAElC,MAAM,EAAE,QAAQ,CAAC,WAAW,KAAK,QAAQ;AACvC,MAAI;AACF,UAAO,aAAa,IAAI;WACjB,GAAG;GACV,MAAM,UAAU,aAAa,eAAe,EAAE,UAAU;AACxD,OAAI,SAAS;IACX,MAAM,EAAE,aAAa;IACrB;IACD,CAAC;AACF,UAAO,EAAE;;GAEX;CAGF,KAAK,EAAE,QAAQ,CAAC,IAAI,GAAG,kBAAkB;CAGzC,WAAW,EACR,QAAQ,CACR,WAAW,KAAK,QAAQ;AACvB,MAAI;AACF,UAAO,kBAAkB,IAAI;WACtB,GAAG;AACV,OAAI,SAAS;IACX,MAAM,EAAE,aAAa;IACrB,SAAS,aAAa,QAAQ,EAAE,UAAU;IAC3C,CAAC;AACF,UAAO,EAAE;;GAEX,CACD,UAAU;CAGb,aAAa,EAAE,QAAQ,CAAC,UAAU;CAGlC,aAAa,EAAE,KAAK,CAAC,YAAY,YAAY,CAAC,CAAC,UAAU;CAGzD,MAAM,EAAE,QAAQ,CAAC,UAAU;CAG3B,OAAO,EAAE,QAAQ,CAAC,UAAU;CAG5B,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,UAAU;
|
|
1
|
+
{"version":3,"file":"schema.mjs","names":[],"sources":["../../src/config/schema.ts"],"sourcesContent":["import { AFSPathError, validatePath } from \"@aigne/afs\";\nimport { z } from \"zod\";\n\n/**\n * Characters forbidden in namespace names (security-sensitive)\n */\nconst NAMESPACE_FORBIDDEN_CHARS = [\n \"/\", // Path separator\n \"\\\\\", // Windows path separator\n \":\", // Namespace separator\n \";\", // Shell metachar\n \"|\", // Shell pipe\n \"&\", // Shell background\n \"`\", // Shell command substitution\n \"$\", // Shell variable\n \"(\", // Shell subshell\n \")\", // Shell subshell\n \">\", // Shell redirect\n \"<\", // Shell redirect\n \"\\n\", // Newline\n \"\\r\", // Carriage return\n \"\\t\", // Tab\n \"\\x00\", // NUL\n];\n\n/**\n * Validate namespace name\n */\nfunction validateNamespace(namespace: string): string {\n if (!namespace || namespace.trim() === \"\") {\n throw new Error(\"Namespace cannot be empty or whitespace-only\");\n }\n\n for (const char of NAMESPACE_FORBIDDEN_CHARS) {\n if (namespace.includes(char)) {\n throw new Error(`Namespace contains forbidden character: '${char}'`);\n }\n }\n\n return namespace;\n}\n\n/**\n * Mount configuration schema\n */\nexport const MountSchema = z.object({\n /** Mount path (must follow Unix path semantics) */\n path: z.string().transform((val, ctx) => {\n try {\n return validatePath(val);\n } catch (e) {\n const message = e instanceof AFSPathError ? e.message : \"Invalid path\";\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message,\n });\n return z.NEVER;\n }\n }),\n\n /** Provider URI (e.g., fs:///path, git:///repo?branch=main) */\n uri: z.string().min(1, \"URI is required\"),\n\n /** Namespace for this mount (optional, defaults to default namespace) */\n namespace: z\n .string()\n .transform((val, ctx) => {\n try {\n return validateNamespace(val);\n } catch (e) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: e instanceof Error ? e.message : \"Invalid namespace\",\n });\n return z.NEVER;\n }\n })\n .optional(),\n\n /** Human/LLM readable description */\n description: z.string().optional(),\n\n /** Access mode: readonly or readwrite */\n access_mode: z.enum([\"readonly\", \"readwrite\"]).optional(),\n\n /** Authentication string (supports ${ENV_VAR} references) */\n auth: z.string().optional(),\n\n /** Authorization token for HTTP providers (supports ${ENV_VAR} references) */\n token: z.string().optional(),\n\n /** Provider-specific options (passed through to provider) */\n options: z.record(z.string(), z.unknown()).optional(),\n});\n\n/**\n * Serve configuration schema\n */\nexport const ServeSchema = z.object({\n /** Host address to listen on */\n host: z.string().default(\"localhost\"),\n\n /** Port to listen on */\n port: z.number().int().positive().default(3000),\n\n /** Base path for the server */\n path: z.string().default(\"/afs\"),\n\n /** Run in readonly mode (disable write operations) */\n readonly: z.boolean().default(false),\n\n /** Enable CORS support */\n cors: z.boolean().default(false),\n\n /** Maximum request body size in bytes */\n max_body_size: z\n .number()\n .int()\n .positive()\n .default(10 * 1024 * 1024), // 10MB\n\n /** Bearer token for authorization (supports ${ENV_VAR} references) */\n token: z.string().optional(),\n});\n\n/**\n * Registry configuration schema\n */\nexport const RegistrySchema = z.object({\n /** Enable/disable auto-mounting the official registry (default: true) */\n enabled: z.boolean().default(true),\n\n /** Static provider manifests (for offline/testing use) */\n providers: z.array(z.record(z.string(), z.unknown())).optional(),\n});\n\n/**\n * Root configuration schema for afs.toml\n */\nexport const ConfigSchema = z.object({\n /** List of mount configurations */\n mounts: z.array(MountSchema).default([]),\n\n /** HTTP server configuration */\n serve: ServeSchema.optional(),\n\n /** Provider registry configuration */\n registry: RegistrySchema.optional(),\n});\n\n/** Type for a single mount configuration */\nexport type MountConfig = z.infer<typeof MountSchema>;\n\n/** Type for serve configuration */\nexport type ServeConfig = z.infer<typeof ServeSchema>;\n\n/** Type for the root AFS configuration */\nexport type AFSConfig = z.infer<typeof ConfigSchema>;\n"],"mappings":";;;;;;;AAMA,MAAM,4BAA4B;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AAKD,SAAS,kBAAkB,WAA2B;AACpD,KAAI,CAAC,aAAa,UAAU,MAAM,KAAK,GACrC,OAAM,IAAI,MAAM,+CAA+C;AAGjE,MAAK,MAAM,QAAQ,0BACjB,KAAI,UAAU,SAAS,KAAK,CAC1B,OAAM,IAAI,MAAM,4CAA4C,KAAK,GAAG;AAIxE,QAAO;;;;;AAMT,MAAa,cAAc,EAAE,OAAO;CAElC,MAAM,EAAE,QAAQ,CAAC,WAAW,KAAK,QAAQ;AACvC,MAAI;AACF,UAAO,aAAa,IAAI;WACjB,GAAG;GACV,MAAM,UAAU,aAAa,eAAe,EAAE,UAAU;AACxD,OAAI,SAAS;IACX,MAAM,EAAE,aAAa;IACrB;IACD,CAAC;AACF,UAAO,EAAE;;GAEX;CAGF,KAAK,EAAE,QAAQ,CAAC,IAAI,GAAG,kBAAkB;CAGzC,WAAW,EACR,QAAQ,CACR,WAAW,KAAK,QAAQ;AACvB,MAAI;AACF,UAAO,kBAAkB,IAAI;WACtB,GAAG;AACV,OAAI,SAAS;IACX,MAAM,EAAE,aAAa;IACrB,SAAS,aAAa,QAAQ,EAAE,UAAU;IAC3C,CAAC;AACF,UAAO,EAAE;;GAEX,CACD,UAAU;CAGb,aAAa,EAAE,QAAQ,CAAC,UAAU;CAGlC,aAAa,EAAE,KAAK,CAAC,YAAY,YAAY,CAAC,CAAC,UAAU;CAGzD,MAAM,EAAE,QAAQ,CAAC,UAAU;CAG3B,OAAO,EAAE,QAAQ,CAAC,UAAU;CAG5B,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU;CACtD,CAAC;;;;AAKF,MAAa,cAAc,EAAE,OAAO;CAElC,MAAM,EAAE,QAAQ,CAAC,QAAQ,YAAY;CAGrC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,IAAK;CAG/C,MAAM,EAAE,QAAQ,CAAC,QAAQ,OAAO;CAGhC,UAAU,EAAE,SAAS,CAAC,QAAQ,MAAM;CAGpC,MAAM,EAAE,SAAS,CAAC,QAAQ,MAAM;CAGhC,eAAe,EACZ,QAAQ,CACR,KAAK,CACL,UAAU,CACV,QAAQ,KAAK,OAAO,KAAK;CAG5B,OAAO,EAAE,QAAQ,CAAC,UAAU;CAC7B,CAAC;;;;AAKF,MAAa,iBAAiB,EAAE,OAAO;CAErC,SAAS,EAAE,SAAS,CAAC,QAAQ,KAAK;CAGlC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,UAAU;CACjE,CAAC;;;;AAKF,MAAa,eAAe,EAAE,OAAO;CAEnC,QAAQ,EAAE,MAAM,YAAY,CAAC,QAAQ,EAAE,CAAC;CAGxC,OAAO,YAAY,UAAU;CAG7B,UAAU,eAAe,UAAU;CACpC,CAAC"}
|
|
@@ -14,9 +14,10 @@ async function fetchActionMeta(afs, path) {
|
|
|
14
14
|
const readResult = await afs.read(canonicalPath);
|
|
15
15
|
const meta = readResult.data?.meta ?? {};
|
|
16
16
|
const content = readResult.data?.content ?? {};
|
|
17
|
+
const firstAction = readResult.data?.actions?.[0];
|
|
17
18
|
return {
|
|
18
|
-
description: meta.description ?? content.description ?? readResult.data?.summary,
|
|
19
|
-
inputSchema: meta.inputSchema ?? content.inputSchema
|
|
19
|
+
description: meta.description ?? content.description ?? firstAction?.description ?? readResult.data?.summary,
|
|
20
|
+
inputSchema: meta.inputSchema ?? content.inputSchema ?? firstAction?.inputSchema
|
|
20
21
|
};
|
|
21
22
|
} catch {
|
|
22
23
|
return {};
|
|
@@ -59,7 +60,9 @@ function createExecCommand(options) {
|
|
|
59
60
|
group: "Action Parameters:"
|
|
60
61
|
};
|
|
61
62
|
if (prop.default !== void 0) optConfig.default = prop.default;
|
|
62
|
-
if (!hasArgsOption && cachedSchema.required?.includes(name))
|
|
63
|
+
if (!hasArgsOption && cachedSchema.required?.includes(name)) {
|
|
64
|
+
if (!(prop.sensitive === true || Array.isArray(prop.env))) optConfig.demandOption = true;
|
|
65
|
+
}
|
|
63
66
|
yargs.option(name, optConfig);
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.d.cts","names":[],"sources":["../../../src/core/commands/exec.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"exec.d.cts","names":[],"sources":["../../../src/core/commands/exec.ts"],"mappings":";;;;;AAsEA;;;;AAAA,UA5CiB,QAAA;EACf,eAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;;;;;;;;;iBAyCa,iBAAA,CACd,OAAA,EAAS,qBAAA,GACR,aAAA,UAAuB,QAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.d.mts","names":[],"sources":["../../../src/core/commands/exec.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"exec.d.mts","names":[],"sources":["../../../src/core/commands/exec.ts"],"mappings":";;;;;AAsEA;;;;AAAA,UA5CiB,QAAA;EACf,eAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;;;;;;;;;iBAyCa,iBAAA,CACd,OAAA,EAAS,qBAAA,GACR,aAAA,UAAuB,QAAA"}
|
|
@@ -14,9 +14,10 @@ async function fetchActionMeta(afs, path) {
|
|
|
14
14
|
const readResult = await afs.read(canonicalPath);
|
|
15
15
|
const meta = readResult.data?.meta ?? {};
|
|
16
16
|
const content = readResult.data?.content ?? {};
|
|
17
|
+
const firstAction = readResult.data?.actions?.[0];
|
|
17
18
|
return {
|
|
18
|
-
description: meta.description ?? content.description ?? readResult.data?.summary,
|
|
19
|
-
inputSchema: meta.inputSchema ?? content.inputSchema
|
|
19
|
+
description: meta.description ?? content.description ?? firstAction?.description ?? readResult.data?.summary,
|
|
20
|
+
inputSchema: meta.inputSchema ?? content.inputSchema ?? firstAction?.inputSchema
|
|
20
21
|
};
|
|
21
22
|
} catch {
|
|
22
23
|
return {};
|
|
@@ -59,7 +60,9 @@ function createExecCommand(options) {
|
|
|
59
60
|
group: "Action Parameters:"
|
|
60
61
|
};
|
|
61
62
|
if (prop.default !== void 0) optConfig.default = prop.default;
|
|
62
|
-
if (!hasArgsOption && cachedSchema.required?.includes(name))
|
|
63
|
+
if (!hasArgsOption && cachedSchema.required?.includes(name)) {
|
|
64
|
+
if (!(prop.sensitive === true || Array.isArray(prop.env))) optConfig.demandOption = true;
|
|
65
|
+
}
|
|
63
66
|
yargs.option(name, optConfig);
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.mjs","names":["description"],"sources":["../../../src/core/commands/exec.ts"],"sourcesContent":["/**\n * exec Command - Core Implementation\n *\n * Executes actions on AFS paths. Accepts AFS instance directly.\n * Returns AFSExecResult directly (no custom type).\n *\n * NOTE: The yargs positional is named \"executable_path\" to avoid name collisions\n * with action inputSchema properties. Common schema property names like \"path\",\n * \"action\", \"name\" etc. won't collide with this compound internal name.\n */\n\nimport type { AFS } from \"@aigne/afs\";\nimport type { CommandModule, Options } from \"yargs\";\nimport { formatExecOutput } from \"../formatters/index.js\";\nimport { parseExecArgs, parseValueBySchema, schemaTypeToYargs } from \"../helpers/exec-args.js\";\nimport { cliPathToCanonical } from \"../path-utils.js\";\nimport type { JSONSchema } from \"../types.js\";\nimport { type CommandFactoryOptions, resolveAFS } from \"./types.js\";\n\n// Re-export helpers for backward compatibility\nexport { parseExecArgs, parseValueBySchema };\n\n/**\n * Exec command base arguments (known fields)\n * Note: Dynamic options are added from inputSchema at runtime\n */\nexport interface ExecArgs {\n executable_path: string;\n args?: string;\n [key: string]: unknown;\n}\n\n/**\n * Fetch action metadata (description, inputSchema)\n */\nasync function fetchActionMeta(\n afs: AFS,\n path: string,\n): Promise<{ description?: string; inputSchema?: JSONSchema }> {\n const canonicalPath = cliPathToCanonical(path);\n\n try {\n const readResult = await afs.read(canonicalPath);\n const meta = readResult.data?.meta ?? {};\n const content = readResult.data?.content ?? {};\n\n return {\n description: (meta.description
|
|
1
|
+
{"version":3,"file":"exec.mjs","names":["description"],"sources":["../../../src/core/commands/exec.ts"],"sourcesContent":["/**\n * exec Command - Core Implementation\n *\n * Executes actions on AFS paths. Accepts AFS instance directly.\n * Returns AFSExecResult directly (no custom type).\n *\n * NOTE: The yargs positional is named \"executable_path\" to avoid name collisions\n * with action inputSchema properties. Common schema property names like \"path\",\n * \"action\", \"name\" etc. won't collide with this compound internal name.\n */\n\nimport type { AFS } from \"@aigne/afs\";\nimport type { CommandModule, Options } from \"yargs\";\nimport { formatExecOutput } from \"../formatters/index.js\";\nimport { parseExecArgs, parseValueBySchema, schemaTypeToYargs } from \"../helpers/exec-args.js\";\nimport { cliPathToCanonical } from \"../path-utils.js\";\nimport type { JSONSchema } from \"../types.js\";\nimport { type CommandFactoryOptions, resolveAFS } from \"./types.js\";\n\n// Re-export helpers for backward compatibility\nexport { parseExecArgs, parseValueBySchema };\n\n/**\n * Exec command base arguments (known fields)\n * Note: Dynamic options are added from inputSchema at runtime\n */\nexport interface ExecArgs {\n executable_path: string;\n args?: string;\n [key: string]: unknown;\n}\n\n/**\n * Fetch action metadata (description, inputSchema)\n */\nasync function fetchActionMeta(\n afs: AFS,\n path: string,\n): Promise<{ description?: string; inputSchema?: JSONSchema }> {\n const canonicalPath = cliPathToCanonical(path);\n\n try {\n const readResult = await afs.read(canonicalPath);\n const meta = readResult.data?.meta ?? {};\n const content = readResult.data?.content ?? {};\n // Actions list entries may carry inputSchema in the actions array\n const firstAction = readResult.data?.actions?.[0];\n\n return {\n description: (meta.description ??\n content.description ??\n firstAction?.description ??\n readResult.data?.summary) as string | undefined,\n inputSchema: (meta.inputSchema ?? content.inputSchema ?? firstAction?.inputSchema) as\n | JSONSchema\n | undefined,\n };\n } catch {\n return {};\n }\n}\n\n/**\n * Create exec command factory\n *\n * This command has special handling:\n * 1. Factory function caches inputSchema\n * 2. Builder pre-parses argv to get action path\n * 3. Dynamically adds options from inputSchema\n */\nexport function createExecCommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, ExecArgs> {\n // Cache schema in closure for reuse between builder and handler\n let cachedSchema: JSONSchema | undefined;\n // Store the pre-parsed exec target path\n let resolvedExecAction: string | undefined;\n\n return {\n command: \"exec <executable_path>\",\n describe: \"Execute an action\",\n builder: async (yargs) => {\n // Pre-parse argv to get the action path (first positional after \"exec\")\n const execIndex = options.argv.indexOf(\"exec\");\n const actionArg = execIndex >= 0 ? options.argv[execIndex + 1] : undefined;\n\n if (actionArg && typeof actionArg === \"string\" && !actionArg.startsWith(\"-\")) {\n resolvedExecAction = actionArg;\n // Fetch action metadata\n const afs = await resolveAFS(options);\n const actionMeta = await fetchActionMeta(afs, actionArg);\n cachedSchema = actionMeta.inputSchema;\n\n // Set custom usage with path and description\n const description = actionMeta.description || \"Execute an action\";\n yargs.usage(`afs exec ${actionArg}\\n\\n${description}`);\n\n // Check if --args is provided - if so, don't require individual options\n const hasArgsOption = options.argv.includes(\"--args\");\n\n if (cachedSchema?.properties) {\n // Add dynamic options from schema\n for (const [name, propSchema] of Object.entries(cachedSchema.properties)) {\n const prop = propSchema as JSONSchema;\n let description = prop.description || \"\";\n\n // For object-type properties with sub-properties, show them in the description\n if (prop.type === \"object\" && prop.properties) {\n const subKeys = Object.keys(prop.properties).join(\", \");\n description += `${description ? \" \" : \"\"}(JSON object with: ${subKeys})`;\n }\n\n const optConfig: Options = {\n type: schemaTypeToYargs(prop.type),\n description: description || undefined,\n group: \"Action Parameters:\",\n };\n\n if (prop.default !== undefined) {\n optConfig.default = prop.default;\n }\n\n // Only require options when --args is not provided.\n // Skip demandOption for fields with sensitive/env metadata — these\n // can be resolved through credential collection (browser, env vars,\n // credential store) rather than CLI args.\n if (!hasArgsOption && cachedSchema.required?.includes(name)) {\n const hasAlternateResolution =\n (prop as Record<string, unknown>).sensitive === true ||\n Array.isArray((prop as Record<string, unknown>).env);\n if (!hasAlternateResolution) {\n optConfig.demandOption = true;\n }\n }\n\n yargs.option(name, optConfig);\n }\n }\n }\n\n return yargs\n .positional(\"executable_path\", {\n type: \"string\",\n demandOption: true,\n description: \"Action path to execute\",\n })\n .option(\"args\", {\n type: \"string\",\n description: 'JSON arguments: --args \\'{\"key\": \"value\"}\\'',\n })\n .strict(false);\n },\n handler: async (argv) => {\n const afs = await resolveAFS(options);\n // Use pre-parsed action path from builder\n const execPath = resolvedExecAction ?? argv.executable_path;\n // Use cached schema or fetch again\n const schema = cachedSchema ?? (await fetchActionMeta(afs, execPath)).inputSchema;\n\n // Strip positional \"executable_path\" from argv before parsing exec args\n const argvForParsing: Record<string, unknown> = { ...argv };\n delete argvForParsing.executable_path;\n // yargs also creates a camelCase alias \"executablePath\" — remove it too\n delete argvForParsing.executablePath;\n\n // Parse exec arguments with schema\n const execArgs = parseExecArgs(argvForParsing, schema);\n\n const canonicalPath = cliPathToCanonical(execPath);\n const result = await afs.exec(canonicalPath, execArgs, {});\n options.onResult({\n command: \"exec\",\n result,\n format: formatExecOutput,\n });\n },\n };\n}\n"],"mappings":";;;;;;;;;;AAmCA,eAAe,gBACb,KACA,MAC6D;CAC7D,MAAM,gBAAgB,mBAAmB,KAAK;AAE9C,KAAI;EACF,MAAM,aAAa,MAAM,IAAI,KAAK,cAAc;EAChD,MAAM,OAAO,WAAW,MAAM,QAAQ,EAAE;EACxC,MAAM,UAAU,WAAW,MAAM,WAAW,EAAE;EAE9C,MAAM,cAAc,WAAW,MAAM,UAAU;AAE/C,SAAO;GACL,aAAc,KAAK,eACjB,QAAQ,eACR,aAAa,eACb,WAAW,MAAM;GACnB,aAAc,KAAK,eAAe,QAAQ,eAAe,aAAa;GAGvE;SACK;AACN,SAAO,EAAE;;;;;;;;;;;AAYb,SAAgB,kBACd,SACkC;CAElC,IAAI;CAEJ,IAAI;AAEJ,QAAO;EACL,SAAS;EACT,UAAU;EACV,SAAS,OAAO,UAAU;GAExB,MAAM,YAAY,QAAQ,KAAK,QAAQ,OAAO;GAC9C,MAAM,YAAY,aAAa,IAAI,QAAQ,KAAK,YAAY,KAAK;AAEjE,OAAI,aAAa,OAAO,cAAc,YAAY,CAAC,UAAU,WAAW,IAAI,EAAE;AAC5E,yBAAqB;IAGrB,MAAM,aAAa,MAAM,gBADb,MAAM,WAAW,QAAQ,EACS,UAAU;AACxD,mBAAe,WAAW;IAG1B,MAAM,cAAc,WAAW,eAAe;AAC9C,UAAM,MAAM,YAAY,UAAU,MAAM,cAAc;IAGtD,MAAM,gBAAgB,QAAQ,KAAK,SAAS,SAAS;AAErD,QAAI,cAAc,WAEhB,MAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,aAAa,WAAW,EAAE;KACxE,MAAM,OAAO;KACb,IAAIA,gBAAc,KAAK,eAAe;AAGtC,SAAI,KAAK,SAAS,YAAY,KAAK,YAAY;MAC7C,MAAM,UAAU,OAAO,KAAK,KAAK,WAAW,CAAC,KAAK,KAAK;AACvD,uBAAe,GAAGA,gBAAc,MAAM,GAAG,qBAAqB,QAAQ;;KAGxE,MAAM,YAAqB;MACzB,MAAM,kBAAkB,KAAK,KAAK;MAClC,aAAaA,iBAAe;MAC5B,OAAO;MACR;AAED,SAAI,KAAK,YAAY,OACnB,WAAU,UAAU,KAAK;AAO3B,SAAI,CAAC,iBAAiB,aAAa,UAAU,SAAS,KAAK,EAIzD;UAAI,EAFD,KAAiC,cAAc,QAChD,MAAM,QAAS,KAAiC,IAAI,EAEpD,WAAU,eAAe;;AAI7B,WAAM,OAAO,MAAM,UAAU;;;AAKnC,UAAO,MACJ,WAAW,mBAAmB;IAC7B,MAAM;IACN,cAAc;IACd,aAAa;IACd,CAAC,CACD,OAAO,QAAQ;IACd,MAAM;IACN,aAAa;IACd,CAAC,CACD,OAAO,MAAM;;EAElB,SAAS,OAAO,SAAS;GACvB,MAAM,MAAM,MAAM,WAAW,QAAQ;GAErC,MAAM,WAAW,sBAAsB,KAAK;GAE5C,MAAM,SAAS,iBAAiB,MAAM,gBAAgB,KAAK,SAAS,EAAE;GAGtE,MAAM,iBAA0C,EAAE,GAAG,MAAM;AAC3D,UAAO,eAAe;AAEtB,UAAO,eAAe;GAGtB,MAAM,WAAW,cAAc,gBAAgB,OAAO;GAEtD,MAAM,gBAAgB,mBAAmB,SAAS;GAClD,MAAM,SAAS,MAAM,IAAI,KAAK,eAAe,UAAU,EAAE,CAAC;AAC1D,WAAQ,SAAS;IACf,SAAS;IACT;IACA,QAAQ;IACT,CAAC;;EAEL"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
const require_mount_commands = require('../../config/mount-commands.cjs');
|
|
1
2
|
const require_explain = require('../formatters/explain.cjs');
|
|
2
3
|
const require_path_utils = require('../../path-utils.cjs');
|
|
3
4
|
require('../path-utils.cjs');
|
|
4
5
|
const require_types = require('./types.cjs');
|
|
5
|
-
const require_mount_commands = require('../../config/mount-commands.cjs');
|
|
6
6
|
|
|
7
7
|
//#region src/core/commands/explain.ts
|
|
8
8
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { configMountListCommand } from "../../config/mount-commands.mjs";
|
|
1
2
|
import { formatExplainOutput, formatPathExplainOutput } from "../formatters/explain.mjs";
|
|
2
3
|
import { cliPathToCanonical } from "../../path-utils.mjs";
|
|
3
4
|
import "../path-utils.mjs";
|
|
4
5
|
import { resolveAFS } from "./types.mjs";
|
|
5
|
-
import { configMountListCommand } from "../../config/mount-commands.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/core/commands/explain.ts
|
|
8
8
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_schema = require('../../config/schema.cjs');
|
|
2
2
|
const require_mount_commands = require('../../config/mount-commands.cjs');
|
|
3
|
+
const require_mount = require('../formatters/mount.cjs');
|
|
3
4
|
|
|
4
5
|
//#region src/core/commands/mount.ts
|
|
5
6
|
/**
|
|
@@ -37,39 +38,121 @@ function createMountListSubcommand(options) {
|
|
|
37
38
|
/**
|
|
38
39
|
* Create mount add subcommand
|
|
39
40
|
*/
|
|
41
|
+
/** Known argv keys that are NOT provider options */
|
|
42
|
+
const KNOWN_MOUNT_KEYS = new Set([
|
|
43
|
+
"path",
|
|
44
|
+
"uri",
|
|
45
|
+
"namespace",
|
|
46
|
+
"description",
|
|
47
|
+
"sensitive-args",
|
|
48
|
+
"sensitiveArgs",
|
|
49
|
+
"_",
|
|
50
|
+
"$0",
|
|
51
|
+
"help",
|
|
52
|
+
"h",
|
|
53
|
+
"version",
|
|
54
|
+
"v",
|
|
55
|
+
"json",
|
|
56
|
+
"yaml",
|
|
57
|
+
"view",
|
|
58
|
+
"interactive"
|
|
59
|
+
]);
|
|
40
60
|
function createMountAddSubcommand(options) {
|
|
41
61
|
return {
|
|
42
62
|
command: "add <path> <uri>",
|
|
43
63
|
describe: "Add a mount",
|
|
44
|
-
builder: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
+
builder: (yargs) => yargs.strict(false).positional("path", {
|
|
65
|
+
type: "string",
|
|
66
|
+
demandOption: true,
|
|
67
|
+
description: "Mount path (e.g., /src)"
|
|
68
|
+
}).positional("uri", {
|
|
69
|
+
type: "string",
|
|
70
|
+
demandOption: true,
|
|
71
|
+
description: "Provider URI (e.g., fs://./src)"
|
|
72
|
+
}).option("namespace", {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Mount namespace"
|
|
75
|
+
}).option("description", {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Mount description"
|
|
78
|
+
}).option("sensitive-args", {
|
|
79
|
+
type: "string",
|
|
80
|
+
array: true,
|
|
81
|
+
description: "Field names to treat as sensitive credentials"
|
|
82
|
+
}),
|
|
64
83
|
handler: async (argv) => {
|
|
65
|
-
const
|
|
84
|
+
const cwd = options.cwd ?? process.cwd();
|
|
85
|
+
if (!argv.uri || argv.uri.trim() === "") throw new Error("URI is required");
|
|
86
|
+
const resolvedUri = require_mount_commands.resolveUriPath(argv.uri, cwd);
|
|
87
|
+
const validation = require_schema.MountSchema.safeParse({
|
|
88
|
+
path: argv.path,
|
|
89
|
+
uri: resolvedUri,
|
|
90
|
+
description: argv.description
|
|
91
|
+
});
|
|
92
|
+
if (!validation.success) {
|
|
93
|
+
const errors = validation.error.issues.map((e) => e.message).join("; ");
|
|
94
|
+
throw new Error(errors);
|
|
95
|
+
}
|
|
96
|
+
const extraOptions = {};
|
|
97
|
+
for (const [key, value] of Object.entries(argv)) if (!KNOWN_MOUNT_KEYS.has(key) && !key.startsWith("-")) extraOptions[key] = value;
|
|
98
|
+
const sensitiveArgs = argv["sensitive-args"] ?? [];
|
|
99
|
+
const { resolveCredentialsForMount } = await Promise.resolve().then(() => require("../../config/afs-loader.cjs"));
|
|
100
|
+
const { createCLIAuthContext } = await Promise.resolve().then(() => require("../../credential/cli-auth-context.cjs"));
|
|
101
|
+
const { createCredentialStore } = await Promise.resolve().then(() => require("../../credential/store.cjs"));
|
|
102
|
+
const authContext = createCLIAuthContext();
|
|
103
|
+
const credentialStore = createCredentialStore();
|
|
104
|
+
const credResolveOptions = {
|
|
105
|
+
cwd,
|
|
106
|
+
uri: resolvedUri,
|
|
107
|
+
mountPath: validation.data.path,
|
|
108
|
+
authContext,
|
|
109
|
+
credentialStore,
|
|
110
|
+
extraOptions: Object.keys(extraOptions).length > 0 ? extraOptions : void 0,
|
|
111
|
+
sensitiveArgs
|
|
112
|
+
};
|
|
113
|
+
let credResult = await resolveCredentialsForMount(credResolveOptions);
|
|
114
|
+
const { verifyMount } = await Promise.resolve().then(() => require("../../config/afs-loader.cjs"));
|
|
115
|
+
let verifyOptions = {
|
|
116
|
+
...extraOptions,
|
|
117
|
+
...credResult?.allValues ?? {}
|
|
118
|
+
};
|
|
119
|
+
try {
|
|
120
|
+
await verifyMount(resolvedUri, validation.data.path, Object.keys(verifyOptions).length > 0 ? verifyOptions : void 0);
|
|
121
|
+
} catch (verifyError) {
|
|
122
|
+
if (credResult && !credResult.collected) {
|
|
123
|
+
credResult = await resolveCredentialsForMount({
|
|
124
|
+
...credResolveOptions,
|
|
125
|
+
forceCollect: true
|
|
126
|
+
});
|
|
127
|
+
verifyOptions = {
|
|
128
|
+
...extraOptions,
|
|
129
|
+
...credResult?.allValues ?? {}
|
|
130
|
+
};
|
|
131
|
+
await verifyMount(resolvedUri, validation.data.path, Object.keys(verifyOptions).length > 0 ? verifyOptions : void 0);
|
|
132
|
+
} else throw verifyError;
|
|
133
|
+
}
|
|
134
|
+
if (credResult) await credResult.persistCredentials();
|
|
135
|
+
const nonSensitiveFromCreds = credResult?.nonSensitive ?? {};
|
|
136
|
+
const mountOptions = {
|
|
137
|
+
...extraOptions,
|
|
138
|
+
...nonSensitiveFromCreds
|
|
139
|
+
};
|
|
140
|
+
if (credResult?.sensitiveFields) for (const field of credResult.sensitiveFields) delete mountOptions[field];
|
|
141
|
+
const finalMountOptions = Object.keys(mountOptions).length > 0 ? mountOptions : void 0;
|
|
142
|
+
const persistUri = credResult?.configUri ?? validation.data.uri;
|
|
143
|
+
const result = await require_mount_commands.persistMount(cwd, {
|
|
144
|
+
path: validation.data.path,
|
|
145
|
+
uri: persistUri,
|
|
146
|
+
description: argv.description,
|
|
147
|
+
options: finalMountOptions
|
|
148
|
+
});
|
|
66
149
|
if (!result.success) throw new Error(result.message ?? "Failed to add mount");
|
|
67
150
|
options.onResult({
|
|
68
151
|
command: "mount add",
|
|
69
152
|
result: {
|
|
70
153
|
success: true,
|
|
71
154
|
path: argv.path,
|
|
72
|
-
uri:
|
|
155
|
+
uri: resolvedUri
|
|
73
156
|
},
|
|
74
157
|
format: (res) => `Mounted ${res.uri} at ${res.path}`
|
|
75
158
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.d.cts","names":[],"sources":["../../../src/core/commands/mount.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"mount.d.cts","names":[],"sources":["../../../src/core/commands/mount.ts"],"mappings":";;;;;AA+BA;;UAPiB,aAAA;EACf,SAAA;AAAA;;;;UAMe,YAAA;EACf,IAAA;EACA,GAAA;EACA,SAAA;EACA,WAAA;EACA,gBAAA;EAAA,CACC,GAAA;AAAA;;AAcH;;UARiB,eAAA;EACf,IAAA;EACA,SAAA;AAAA;;;;iBAMc,kBAAA,CAAmB,OAAA,EAAS,qBAAA,GAAwB,aAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.d.mts","names":[],"sources":["../../../src/core/commands/mount.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"mount.d.mts","names":[],"sources":["../../../src/core/commands/mount.ts"],"mappings":";;;;;AA+BA;;UAPiB,aAAA;EACf,SAAA;AAAA;;;;UAMe,YAAA;EACf,IAAA;EACA,GAAA;EACA,SAAA;EACA,WAAA;EACA,gBAAA;EAAA,CACC,GAAA;AAAA;;AAcH;;UARiB,eAAA;EACf,IAAA;EACA,SAAA;AAAA;;;;iBAMc,kBAAA,CAAmB,OAAA,EAAS,qBAAA,GAAwB,aAAA"}
|