@aigne/afs-cli 1.11.0-beta.5 → 1.11.0-beta.6
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 +41 -18
- package/dist/cli.mjs +42 -19
- package/dist/cli.mjs.map +1 -1
- package/dist/commands/exec.cjs +132 -14
- package/dist/commands/exec.mjs +129 -14
- package/dist/commands/exec.mjs.map +1 -1
- package/dist/commands/explain.cjs +1 -1
- package/dist/commands/explain.mjs +1 -1
- package/dist/commands/explain.mjs.map +1 -1
- package/dist/commands/index.mjs +1 -1
- package/dist/commands/ls.cjs +129 -30
- package/dist/commands/ls.mjs +129 -30
- package/dist/commands/ls.mjs.map +1 -1
- package/dist/commands/read.cjs +213 -14
- package/dist/commands/read.mjs +213 -14
- package/dist/commands/read.mjs.map +1 -1
- package/dist/commands/stat.cjs +116 -34
- package/dist/commands/stat.mjs +117 -34
- package/dist/commands/stat.mjs.map +1 -1
- package/dist/commands/write.cjs +37 -4
- package/dist/commands/write.mjs +38 -4
- package/dist/commands/write.mjs.map +1 -1
- package/dist/config/loader.cjs +12 -1
- package/dist/config/loader.mjs +12 -1
- package/dist/config/loader.mjs.map +1 -1
- package/dist/config/provider-factory.cjs +310 -3
- package/dist/config/provider-factory.mjs +310 -3
- package/dist/config/provider-factory.mjs.map +1 -1
- package/dist/config/uri-parser.cjs +195 -2
- package/dist/config/uri-parser.mjs +195 -2
- package/dist/config/uri-parser.mjs.map +1 -1
- package/dist/explorer/actions.cjs +53 -23
- package/dist/explorer/actions.mjs +54 -23
- package/dist/explorer/actions.mjs.map +1 -1
- package/dist/explorer/components/dialog.cjs +163 -10
- package/dist/explorer/components/dialog.mjs +163 -10
- package/dist/explorer/components/dialog.mjs.map +1 -1
- package/dist/explorer/components/file-list.mjs.map +1 -1
- package/dist/explorer/components/metadata-panel.cjs +39 -25
- package/dist/explorer/components/metadata-panel.mjs +39 -25
- package/dist/explorer/components/metadata-panel.mjs.map +1 -1
- package/dist/explorer/screen.cjs +23 -8
- package/dist/explorer/screen.mjs +24 -9
- package/dist/explorer/screen.mjs.map +1 -1
- package/dist/explorer/theme.cjs +3 -1
- package/dist/explorer/theme.mjs +3 -1
- package/dist/explorer/theme.mjs.map +1 -1
- package/dist/path-utils.cjs +2 -1
- package/dist/path-utils.mjs +1 -1
- package/dist/runtime.cjs +24 -0
- package/dist/runtime.mjs +24 -0
- package/dist/runtime.mjs.map +1 -1
- package/dist/ui/header.cjs +0 -9
- package/dist/ui/header.mjs +1 -9
- package/dist/ui/header.mjs.map +1 -1
- package/dist/ui/index.cjs +0 -2
- package/dist/ui/index.mjs +2 -3
- package/dist/ui/index.mjs.map +1 -1
- package/dist/utils/meta.cjs +51 -0
- package/dist/utils/meta.mjs +49 -0
- package/dist/utils/meta.mjs.map +1 -0
- package/package.json +19 -9
|
@@ -15,8 +15,20 @@ async function createProvider(mount) {
|
|
|
15
15
|
case "git": return createGitProvider(mount, parsed.path, parsed.params, parsed.host);
|
|
16
16
|
case "sqlite": return createSQLiteProvider(mount, parsed.path);
|
|
17
17
|
case "json": return createJSONProvider(mount, parsed.path);
|
|
18
|
+
case "toml": return createTOMLProvider(mount, parsed.path);
|
|
19
|
+
case "s3": return createS3Provider(mount, parsed.path, parsed.params);
|
|
20
|
+
case "gs": return createGCSProvider(mount, parsed.path, parsed.params);
|
|
21
|
+
case "ec2": return createEC2Provider(mount, parsed.path, parsed.params);
|
|
22
|
+
case "gce": return createGCEProvider(mount, parsed.path, parsed.params);
|
|
23
|
+
case "dns": return createDNSProvider(mount, parsed.path, parsed.params);
|
|
24
|
+
case "github": return createGitHubProvider(mount, parsed.path, parsed.params);
|
|
25
|
+
case "sandbox": return createSandboxProvider(mount);
|
|
18
26
|
case "http":
|
|
19
27
|
case "https": return createHttpProvider(mount);
|
|
28
|
+
case "mcp":
|
|
29
|
+
case "mcp+stdio":
|
|
30
|
+
case "mcp+http":
|
|
31
|
+
case "mcp+sse": return createMCPProvider(mount, parsed);
|
|
20
32
|
default: throw new Error(`Unknown URI scheme: ${parsed.scheme}`);
|
|
21
33
|
}
|
|
22
34
|
}
|
|
@@ -32,7 +44,8 @@ async function createAFSFSProvider(mount, localPath) {
|
|
|
32
44
|
}
|
|
33
45
|
async function createGitProvider(mount, repoPath, params, host) {
|
|
34
46
|
const { AFSGit } = await import("@aigne/afs-git");
|
|
35
|
-
|
|
47
|
+
let provider;
|
|
48
|
+
if (host) provider = new AFSGit({
|
|
36
49
|
remoteUrl: `git@${host}:${repoPath}`,
|
|
37
50
|
name: mount.path.slice(1).replace(/\//g, "-") || "git",
|
|
38
51
|
description: mount.description,
|
|
@@ -40,7 +53,7 @@ async function createGitProvider(mount, repoPath, params, host) {
|
|
|
40
53
|
branches: params.branch ? [params.branch] : void 0,
|
|
41
54
|
...mount.options
|
|
42
55
|
});
|
|
43
|
-
if (repoPath.startsWith("https://") || repoPath.startsWith("http://"))
|
|
56
|
+
else if (repoPath.startsWith("https://") || repoPath.startsWith("http://")) provider = new AFSGit({
|
|
44
57
|
remoteUrl: repoPath,
|
|
45
58
|
name: mount.path.slice(1).replace(/\//g, "-") || "git",
|
|
46
59
|
description: mount.description,
|
|
@@ -48,7 +61,7 @@ async function createGitProvider(mount, repoPath, params, host) {
|
|
|
48
61
|
branches: params.branch ? [params.branch] : void 0,
|
|
49
62
|
...mount.options
|
|
50
63
|
});
|
|
51
|
-
|
|
64
|
+
else provider = new AFSGit({
|
|
52
65
|
repoPath,
|
|
53
66
|
name: mount.path.slice(1).replace(/\//g, "-") || "git",
|
|
54
67
|
description: mount.description,
|
|
@@ -56,6 +69,8 @@ async function createGitProvider(mount, repoPath, params, host) {
|
|
|
56
69
|
branches: params.branch ? [params.branch] : void 0,
|
|
57
70
|
...mount.options
|
|
58
71
|
});
|
|
72
|
+
await provider.ready();
|
|
73
|
+
return provider;
|
|
59
74
|
}
|
|
60
75
|
async function createSQLiteProvider(mount, dbPath) {
|
|
61
76
|
const { SQLiteAFS } = await import("@aigne/afs-sqlite");
|
|
@@ -77,6 +92,126 @@ async function createJSONProvider(mount, jsonPath) {
|
|
|
77
92
|
...mount.options
|
|
78
93
|
});
|
|
79
94
|
}
|
|
95
|
+
async function createTOMLProvider(mount, tomlPath) {
|
|
96
|
+
const { AFSTOML } = await import("@aigne/afs-toml");
|
|
97
|
+
return new AFSTOML({
|
|
98
|
+
tomlPath,
|
|
99
|
+
name: mount.path.slice(1).replace(/\//g, "-") || "toml",
|
|
100
|
+
description: mount.description,
|
|
101
|
+
accessMode: mount.access_mode,
|
|
102
|
+
...mount.options
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create an S3 provider from mount configuration
|
|
107
|
+
*
|
|
108
|
+
* Supported URI formats:
|
|
109
|
+
* - s3://bucket (bucket only)
|
|
110
|
+
* - s3://bucket/prefix (bucket with prefix)
|
|
111
|
+
*
|
|
112
|
+
* Options:
|
|
113
|
+
* - endpoint: S3-compatible endpoint URL (for MinIO, R2, B2, etc.)
|
|
114
|
+
* - forcePathStyle: Use path-style URLs instead of virtual-hosted style
|
|
115
|
+
* - region: AWS region
|
|
116
|
+
* - profile: AWS profile name
|
|
117
|
+
*/
|
|
118
|
+
async function createS3Provider(mount, path, params) {
|
|
119
|
+
const { AFSS3 } = await import("@aigne/afs-s3");
|
|
120
|
+
const parts = path.split("/");
|
|
121
|
+
const bucket = parts[0] || "";
|
|
122
|
+
const prefix = parts.slice(1).join("/") || void 0;
|
|
123
|
+
if (!bucket) throw new Error("S3 URI requires a bucket name: s3://bucket/prefix");
|
|
124
|
+
return new AFSS3({
|
|
125
|
+
bucket,
|
|
126
|
+
prefix,
|
|
127
|
+
name: mount.path.slice(1).replace(/\//g, "-") || bucket,
|
|
128
|
+
description: mount.description,
|
|
129
|
+
accessMode: mount.access_mode,
|
|
130
|
+
region: params.region ?? mount.options?.region,
|
|
131
|
+
endpoint: params.endpoint ?? mount.options?.endpoint,
|
|
132
|
+
forcePathStyle: params.forcePathStyle === "true" || mount.options?.forcePathStyle,
|
|
133
|
+
profile: params.profile ?? mount.options?.profile,
|
|
134
|
+
...mount.options
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Create a GCS provider from mount configuration
|
|
139
|
+
*
|
|
140
|
+
* Supported URI formats:
|
|
141
|
+
* - gs://bucket (bucket only)
|
|
142
|
+
* - gs://bucket/prefix (bucket with prefix)
|
|
143
|
+
*
|
|
144
|
+
* Options:
|
|
145
|
+
* - projectId: Google Cloud project ID
|
|
146
|
+
* - keyFilename: Path to service account key file
|
|
147
|
+
* - apiEndpoint: Custom API endpoint (for emulators)
|
|
148
|
+
*/
|
|
149
|
+
async function createGCSProvider(mount, path, params) {
|
|
150
|
+
const { AFSGCS } = await import("@aigne/afs-gcs");
|
|
151
|
+
const parts = path.split("/");
|
|
152
|
+
const bucket = parts[0] || "";
|
|
153
|
+
const prefix = parts.slice(1).join("/") || void 0;
|
|
154
|
+
if (!bucket) throw new Error("GCS URI requires a bucket name: gs://bucket/prefix");
|
|
155
|
+
return new AFSGCS({
|
|
156
|
+
bucket,
|
|
157
|
+
prefix,
|
|
158
|
+
name: mount.path.slice(1).replace(/\//g, "-") || bucket,
|
|
159
|
+
description: mount.description,
|
|
160
|
+
accessMode: mount.access_mode,
|
|
161
|
+
projectId: params.projectId ?? mount.options?.projectId,
|
|
162
|
+
keyFilename: params.keyFilename ?? mount.options?.keyFilename,
|
|
163
|
+
endpoint: params.endpoint ?? mount.options?.endpoint,
|
|
164
|
+
...mount.options
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create a GitHub provider from mount configuration
|
|
169
|
+
*
|
|
170
|
+
* Supported URI formats:
|
|
171
|
+
* - github://owner/repo (single-repo mode)
|
|
172
|
+
* - github://owner (org mode - list all repos in organization)
|
|
173
|
+
* - github:// (multi-repo mode, requires options.mode = "multi-repo")
|
|
174
|
+
*
|
|
175
|
+
* Options:
|
|
176
|
+
* - mode: "single-repo" | "multi-repo" | "org" (default: auto-detected from URI)
|
|
177
|
+
* - baseUrl: GitHub API base URL (for GitHub Enterprise)
|
|
178
|
+
* - cache: { enabled: boolean; ttl: number }
|
|
179
|
+
* - rateLimit: { autoRetry: boolean; maxRetries: number }
|
|
180
|
+
*/
|
|
181
|
+
async function createGitHubProvider(mount, repoPath, params) {
|
|
182
|
+
const { AFSGitHub } = await import("@aigne/afs-github");
|
|
183
|
+
const parts = repoPath.split("/").filter(Boolean);
|
|
184
|
+
const owner = parts[0];
|
|
185
|
+
const repo = parts[1];
|
|
186
|
+
let mode = params.mode ?? mount.options?.mode;
|
|
187
|
+
if (!mode) if (owner && repo) mode = "single-repo";
|
|
188
|
+
else if (owner && !repo) mode = "org";
|
|
189
|
+
else mode = "multi-repo";
|
|
190
|
+
if (mode === "single-repo" && (!owner || !repo)) throw new Error("GitHub single-repo mode requires owner/repo in URI: github://owner/repo");
|
|
191
|
+
if (mode === "org" && !owner) throw new Error("GitHub org mode requires owner in URI: github://owner");
|
|
192
|
+
const authToken = mount.auth ?? mount.token;
|
|
193
|
+
const ownerType = mount.options?.owner_type ?? mount.options?.ownerType;
|
|
194
|
+
return new AFSGitHub({
|
|
195
|
+
name: mount.path.slice(1).replace(/\//g, "-") || "github",
|
|
196
|
+
description: mount.description,
|
|
197
|
+
owner,
|
|
198
|
+
repo,
|
|
199
|
+
auth: authToken ? { token: authToken } : void 0,
|
|
200
|
+
mode,
|
|
201
|
+
ownerType,
|
|
202
|
+
accessMode: mount.access_mode ?? "readonly",
|
|
203
|
+
...mount.options
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async function createSandboxProvider(mount) {
|
|
207
|
+
const { AFSSandbox } = await import("@aigne/afs-sandbox");
|
|
208
|
+
return new AFSSandbox({
|
|
209
|
+
name: mount.path.slice(1).replace(/\//g, "-") || "sandbox",
|
|
210
|
+
description: mount.description,
|
|
211
|
+
accessMode: mount.access_mode,
|
|
212
|
+
...mount.options
|
|
213
|
+
});
|
|
214
|
+
}
|
|
80
215
|
async function createHttpProvider(mount) {
|
|
81
216
|
const { AFSHttpClient } = await import("@aigne/afs-http");
|
|
82
217
|
return new AFSHttpClient({
|
|
@@ -88,6 +223,178 @@ async function createHttpProvider(mount) {
|
|
|
88
223
|
...mount.options
|
|
89
224
|
});
|
|
90
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Create an MCP provider from mount configuration
|
|
228
|
+
*
|
|
229
|
+
* Supported URI formats:
|
|
230
|
+
* - mcp://name (requires options.transport, options.command/url)
|
|
231
|
+
* - mcp+stdio://command/args... (e.g., mcp+stdio://npx/-y/@modelcontextprotocol/server-sqlite/test.db)
|
|
232
|
+
* - mcp+http://host/path (e.g., mcp+http://mcp.notion.com/mcp)
|
|
233
|
+
* - mcp+sse://host/path (e.g., mcp+sse://api.example.com/sse)
|
|
234
|
+
*
|
|
235
|
+
* Options:
|
|
236
|
+
* - transport: "stdio" | "http" | "sse" (required for mcp:// scheme)
|
|
237
|
+
* - command: string (for stdio transport)
|
|
238
|
+
* - args: string[] (for stdio transport)
|
|
239
|
+
* - env: Record<string, string> (for stdio transport)
|
|
240
|
+
* - url: string (for http/sse transport)
|
|
241
|
+
* - headers: Record<string, string> (for http/sse transport)
|
|
242
|
+
*/
|
|
243
|
+
async function createMCPProvider(mount, parsed) {
|
|
244
|
+
const { AFSMCP } = await import("@aigne/afs-mcp");
|
|
245
|
+
const name = mount.path.slice(1).replace(/\//g, "-") || "mcp";
|
|
246
|
+
const options = mount.options || {};
|
|
247
|
+
let transport;
|
|
248
|
+
let command;
|
|
249
|
+
let args;
|
|
250
|
+
let url;
|
|
251
|
+
if (parsed.scheme === "mcp+stdio") {
|
|
252
|
+
transport = "stdio";
|
|
253
|
+
const parts = parsed.path.split("/").filter(Boolean).map(decodeURIComponent);
|
|
254
|
+
command = parts[0];
|
|
255
|
+
args = parts.slice(1);
|
|
256
|
+
} else if (parsed.scheme === "mcp+http") {
|
|
257
|
+
transport = "http";
|
|
258
|
+
url = `https://${parsed.host || ""}${parsed.path}`;
|
|
259
|
+
} else if (parsed.scheme === "mcp+sse") {
|
|
260
|
+
transport = "sse";
|
|
261
|
+
url = `https://${parsed.host || ""}${parsed.path}`;
|
|
262
|
+
} else {
|
|
263
|
+
transport = options.transport || "stdio";
|
|
264
|
+
command = options.command;
|
|
265
|
+
args = options.args;
|
|
266
|
+
url = options.url;
|
|
267
|
+
}
|
|
268
|
+
const mcpOptions = {
|
|
269
|
+
name,
|
|
270
|
+
description: mount.description,
|
|
271
|
+
transport
|
|
272
|
+
};
|
|
273
|
+
if (transport === "stdio") {
|
|
274
|
+
if (!command) throw new Error("MCP stdio transport requires 'command' option");
|
|
275
|
+
mcpOptions.command = command;
|
|
276
|
+
mcpOptions.args = args || [];
|
|
277
|
+
if (options.env) mcpOptions.env = options.env;
|
|
278
|
+
} else {
|
|
279
|
+
if (!url) throw new Error(`MCP ${transport} transport requires 'url' option`);
|
|
280
|
+
mcpOptions.url = url;
|
|
281
|
+
if (options.headers) mcpOptions.headers = options.headers;
|
|
282
|
+
}
|
|
283
|
+
if (options.timeout) mcpOptions.timeout = options.timeout;
|
|
284
|
+
if (options.maxReconnects) mcpOptions.maxReconnects = options.maxReconnects;
|
|
285
|
+
return new AFSMCP(mcpOptions);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Create an EC2 provider from mount configuration
|
|
289
|
+
*
|
|
290
|
+
* Supported URI formats:
|
|
291
|
+
* - ec2://us-east-1 (single region)
|
|
292
|
+
* - ec2://us-east-1,us-west-2 (multi-region)
|
|
293
|
+
* - ec2://?profile=myprofile (use default region from profile)
|
|
294
|
+
*
|
|
295
|
+
* Options:
|
|
296
|
+
* - endpoint: Custom endpoint URL (for LocalStack, etc.)
|
|
297
|
+
* - profile: AWS profile name
|
|
298
|
+
* - filters: Array of { name, values } filters
|
|
299
|
+
* - cache: { ttl: number, instanceTtl: number, staticTtl: number }
|
|
300
|
+
*/
|
|
301
|
+
async function createEC2Provider(mount, path, params) {
|
|
302
|
+
const { AFSEC2 } = await import("@aigne/afs-ec2");
|
|
303
|
+
const regions = path.split(",").map((r) => r.trim()).filter(Boolean);
|
|
304
|
+
const config = {
|
|
305
|
+
name: mount.path.slice(1).replace(/\//g, "-") || "ec2",
|
|
306
|
+
description: mount.description,
|
|
307
|
+
accessMode: mount.access_mode ?? "readonly"
|
|
308
|
+
};
|
|
309
|
+
if (regions.length === 1) config.region = regions[0];
|
|
310
|
+
else if (regions.length > 1) config.regions = regions;
|
|
311
|
+
const endpoint = params.endpoint ?? mount.options?.endpoint;
|
|
312
|
+
if (endpoint) config.endpoint = endpoint;
|
|
313
|
+
const profile = params.profile ?? mount.options?.profile;
|
|
314
|
+
if (profile) config.profile = profile;
|
|
315
|
+
if (mount.options?.credentials) config.credentials = mount.options.credentials;
|
|
316
|
+
if (mount.options?.filters) config.filters = mount.options.filters;
|
|
317
|
+
if (mount.options?.cache) config.cache = mount.options.cache;
|
|
318
|
+
return new AFSEC2(config);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Create a GCE provider from mount configuration
|
|
322
|
+
*
|
|
323
|
+
* Supported URI formats:
|
|
324
|
+
* - gce://project-id/zone (project and zone)
|
|
325
|
+
* - gce://project-id (project only, zone from options)
|
|
326
|
+
*
|
|
327
|
+
* Options:
|
|
328
|
+
* - zone: GCE zone
|
|
329
|
+
* - keyFilename: Path to service account key file
|
|
330
|
+
* - credentials: Service account credentials object
|
|
331
|
+
* - cache: { ttl: number, instanceTtl: number, staticTtl: number }
|
|
332
|
+
*/
|
|
333
|
+
async function createGCEProvider(mount, path, params) {
|
|
334
|
+
const { AFSGCE } = await import("@aigne/afs-gce");
|
|
335
|
+
const parts = path.split("/").filter(Boolean);
|
|
336
|
+
const projectId = parts[0] || "";
|
|
337
|
+
const zoneFromPath = parts[1];
|
|
338
|
+
if (!projectId) throw new Error("GCE URI requires a project ID: gce://project-id/zone");
|
|
339
|
+
const zone = zoneFromPath ?? params.zone ?? mount.options?.zone;
|
|
340
|
+
if (!zone) throw new Error("GCE requires a zone: gce://project-id/zone or use ?zone= parameter");
|
|
341
|
+
const config = {
|
|
342
|
+
name: mount.path.slice(1).replace(/\//g, "-") || "gce",
|
|
343
|
+
description: mount.description,
|
|
344
|
+
projectId,
|
|
345
|
+
zone,
|
|
346
|
+
accessMode: mount.access_mode ?? "readonly"
|
|
347
|
+
};
|
|
348
|
+
const keyFilename = params.keyFilename ?? mount.options?.keyFilename;
|
|
349
|
+
if (keyFilename) config.keyFilename = keyFilename;
|
|
350
|
+
if (mount.options?.credentials) config.credentials = mount.options.credentials;
|
|
351
|
+
if (mount.options?.cache) config.cache = mount.options.cache;
|
|
352
|
+
return new AFSGCE(config);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Create a DNS provider from mount configuration
|
|
356
|
+
*
|
|
357
|
+
* Supported URI formats:
|
|
358
|
+
* - dns://zone.domain.com (single zone)
|
|
359
|
+
*
|
|
360
|
+
* Options:
|
|
361
|
+
* - provider: DNS provider type ("route53" | "clouddns")
|
|
362
|
+
* - endpoint: Custom endpoint URL (for LocalStack, etc.)
|
|
363
|
+
* - region: AWS region (for Route53)
|
|
364
|
+
* - credentials: { accessKeyId, secretAccessKey } (for Route53)
|
|
365
|
+
* - projectId: Google Cloud project ID (for Cloud DNS)
|
|
366
|
+
* - keyFilename: Path to service account key file (for Cloud DNS)
|
|
367
|
+
* - permissions: { preset, dangerous }
|
|
368
|
+
*/
|
|
369
|
+
async function createDNSProvider(mount, zoneDomain, params) {
|
|
370
|
+
const { DNSProvider, Route53Adapter, CloudDNSAdapter } = await import("@aigne/afs-dns");
|
|
371
|
+
const providerType = params.provider ?? mount.options?.provider ?? "route53";
|
|
372
|
+
let adapter;
|
|
373
|
+
if (providerType === "route53") {
|
|
374
|
+
const adapterConfig = {};
|
|
375
|
+
const region = params.region ?? mount.options?.region;
|
|
376
|
+
if (region) adapterConfig.region = region;
|
|
377
|
+
const endpoint = params.endpoint ?? mount.options?.endpoint;
|
|
378
|
+
if (endpoint) adapterConfig.endpoint = endpoint;
|
|
379
|
+
if (mount.options?.credentials) adapterConfig.credentials = mount.options.credentials;
|
|
380
|
+
adapter = new Route53Adapter(adapterConfig);
|
|
381
|
+
} else if (providerType === "clouddns") {
|
|
382
|
+
const adapterConfig = {};
|
|
383
|
+
const projectId = params.projectId ?? mount.options?.projectId;
|
|
384
|
+
if (projectId) adapterConfig.projectId = projectId;
|
|
385
|
+
const keyFilename = params.keyFilename ?? mount.options?.keyFilename;
|
|
386
|
+
if (keyFilename) adapterConfig.keyFilename = keyFilename;
|
|
387
|
+
adapter = new CloudDNSAdapter(adapterConfig);
|
|
388
|
+
} else throw new Error(`Unsupported DNS provider: ${providerType}. Supported providers: 'route53', 'clouddns'.`);
|
|
389
|
+
return new DNSProvider({
|
|
390
|
+
zone: zoneDomain,
|
|
391
|
+
adapter,
|
|
392
|
+
accessMode: mount.access_mode ?? "readonly",
|
|
393
|
+
permissions: mount.options?.permissions,
|
|
394
|
+
auditLog: mount.options?.auditLog,
|
|
395
|
+
rateLimiting: mount.options?.rateLimiting
|
|
396
|
+
});
|
|
397
|
+
}
|
|
91
398
|
|
|
92
399
|
//#endregion
|
|
93
400
|
export { createProvider };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-factory.mjs","names":[],"sources":["../../src/config/provider-factory.ts"],"sourcesContent":["import type { AFSModule } from \"@aigne/afs\";\nimport type { MountConfig } from \"./schema.js\";\nimport { parseURI } from \"./uri-parser.js\";\n\n/**\n * Create an AFS provider from a mount configuration\n *\n * @param mount - Mount configuration with URI and options\n * @returns Provider instance\n * @throws Error if scheme is unknown or not implemented\n */\nexport async function createProvider(mount: MountConfig): Promise<AFSModule> {\n const parsed = parseURI(mount.uri);\n\n switch (parsed.scheme) {\n case \"fs\":\n return createAFSFSProvider(mount, parsed.path);\n\n case \"git\":\n return createGitProvider(mount, parsed.path, parsed.params, parsed.host);\n\n case \"sqlite\":\n return createSQLiteProvider(mount, parsed.path);\n\n case \"json\":\n return createJSONProvider(mount, parsed.path);\n\n case \"http\":\n case \"https\":\n return createHttpProvider(mount);\n\n default:\n throw new Error(`Unknown URI scheme: ${parsed.scheme}`);\n }\n}\n\nasync function createAFSFSProvider(mount: MountConfig, localPath: string): Promise<AFSModule> {\n const { AFSFS } = await import(\"@aigne/afs-fs\");\n\n return new AFSFS({\n localPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"fs\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n}\n\nasync function createGitProvider(\n mount: MountConfig,\n repoPath: string,\n params: Record<string, string>,\n host?: string,\n): Promise<AFSModule> {\n const { AFSGit } = await import(\"@aigne/afs-git\");\n\n // For remote repos (SSH-style or https), use remoteUrl\n // For local repos, use repoPath directly\n if (host) {\n const remoteUrl = `git@${host}:${repoPath}`;\n return new AFSGit({\n remoteUrl,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"git\",\n description: mount.description,\n accessMode: mount.access_mode ?? \"readonly\",\n branches: params.branch ? [params.branch] : undefined,\n ...mount.options,\n });\n }\n\n // Check if repoPath is actually a remote URL (https:// or http://)\n if (repoPath.startsWith(\"https://\") || repoPath.startsWith(\"http://\")) {\n return new AFSGit({\n remoteUrl: repoPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"git\",\n description: mount.description,\n accessMode: mount.access_mode ?? \"readonly\",\n branches: params.branch ? [params.branch] : undefined,\n ...mount.options,\n });\n }\n\n return new AFSGit({\n repoPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"git\",\n description: mount.description,\n accessMode: mount.access_mode ?? \"readonly\",\n branches: params.branch ? [params.branch] : undefined,\n ...mount.options,\n });\n}\n\nasync function createSQLiteProvider(mount: MountConfig, dbPath: string): Promise<AFSModule> {\n const { SQLiteAFS } = await import(\"@aigne/afs-sqlite\");\n\n // SQLiteAFS auto-initializes via onMount when mounted to AFS\n return new SQLiteAFS({\n url: `file:${dbPath}`,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"sqlite\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n}\n\nasync function createJSONProvider(mount: MountConfig, jsonPath: string): Promise<AFSModule> {\n const { AFSJSON } = await import(\"@aigne/afs-json\");\n\n return new AFSJSON({\n jsonPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"json\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n}\n\nasync function createHttpProvider(mount: MountConfig): Promise<AFSModule> {\n const { AFSHttpClient } = await import(\"@aigne/afs-http\");\n\n return new AFSHttpClient({\n url: mount.uri,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"http\",\n description: mount.description,\n accessMode: mount.access_mode,\n token: mount.token,\n ...mount.options,\n });\n}\n"],"mappings":";;;;;;;;;;AAWA,eAAsB,eAAe,OAAwC;CAC3E,MAAM,SAAS,SAAS,MAAM,IAAI;AAElC,SAAQ,OAAO,QAAf;EACE,KAAK,KACH,QAAO,oBAAoB,OAAO,OAAO,KAAK;EAEhD,KAAK,MACH,QAAO,kBAAkB,OAAO,OAAO,MAAM,OAAO,QAAQ,OAAO,KAAK;EAE1E,KAAK,SACH,QAAO,qBAAqB,OAAO,OAAO,KAAK;EAEjD,KAAK,OACH,QAAO,mBAAmB,OAAO,OAAO,KAAK;EAE/C,KAAK;EACL,KAAK,QACH,QAAO,mBAAmB,MAAM;EAElC,QACE,OAAM,IAAI,MAAM,uBAAuB,OAAO,SAAS;;;AAI7D,eAAe,oBAAoB,OAAoB,WAAuC;CAC5F,MAAM,EAAE,UAAU,MAAM,OAAO;AAE/B,QAAO,IAAI,MAAM;EACf;EACA,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC,QAAQ,OAAO,IAAI,IAAI;EACjD,aAAa,MAAM;EACnB,YAAY,MAAM;EAClB,GAAG,MAAM;EACV,CAAC;;AAGJ,eAAe,kBACb,OACA,UACA,QACA,MACoB;CACpB,MAAM,EAAE,WAAW,MAAM,OAAO;AAIhC,KAAI,KAEF,QAAO,IAAI,OAAO;EAChB,WAFgB,OAAO,KAAK,GAAG;EAG/B,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC,QAAQ,OAAO,IAAI,IAAI;EACjD,aAAa,MAAM;EACnB,YAAY,MAAM,eAAe;EACjC,UAAU,OAAO,SAAS,CAAC,OAAO,OAAO,GAAG;EAC5C,GAAG,MAAM;EACV,CAAC;AAIJ,KAAI,SAAS,WAAW,WAAW,IAAI,SAAS,WAAW,UAAU,CACnE,QAAO,IAAI,OAAO;EAChB,WAAW;EACX,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC,QAAQ,OAAO,IAAI,IAAI;EACjD,aAAa,MAAM;EACnB,YAAY,MAAM,eAAe;EACjC,UAAU,OAAO,SAAS,CAAC,OAAO,OAAO,GAAG;EAC5C,GAAG,MAAM;EACV,CAAC;AAGJ,QAAO,IAAI,OAAO;EAChB;EACA,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC,QAAQ,OAAO,IAAI,IAAI;EACjD,aAAa,MAAM;EACnB,YAAY,MAAM,eAAe;EACjC,UAAU,OAAO,SAAS,CAAC,OAAO,OAAO,GAAG;EAC5C,GAAG,MAAM;EACV,CAAC;;AAGJ,eAAe,qBAAqB,OAAoB,QAAoC;CAC1F,MAAM,EAAE,cAAc,MAAM,OAAO;AAGnC,QAAO,IAAI,UAAU;EACnB,KAAK,QAAQ;EACb,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC,QAAQ,OAAO,IAAI,IAAI;EACjD,aAAa,MAAM;EACnB,YAAY,MAAM;EAClB,GAAG,MAAM;EACV,CAAC;;AAGJ,eAAe,mBAAmB,OAAoB,UAAsC;CAC1F,MAAM,EAAE,YAAY,MAAM,OAAO;AAEjC,QAAO,IAAI,QAAQ;EACjB;EACA,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC,QAAQ,OAAO,IAAI,IAAI;EACjD,aAAa,MAAM;EACnB,YAAY,MAAM;EAClB,GAAG,MAAM;EACV,CAAC;;AAGJ,eAAe,mBAAmB,OAAwC;CACxE,MAAM,EAAE,kBAAkB,MAAM,OAAO;AAEvC,QAAO,IAAI,cAAc;EACvB,KAAK,MAAM;EACX,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC,QAAQ,OAAO,IAAI,IAAI;EACjD,aAAa,MAAM;EACnB,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,GAAG,MAAM;EACV,CAAC"}
|
|
1
|
+
{"version":3,"file":"provider-factory.mjs","names":[],"sources":["../../src/config/provider-factory.ts"],"sourcesContent":["import type { AFSModule } from \"@aigne/afs\";\nimport type { MountConfig } from \"./schema.js\";\nimport { type ParsedURI, parseURI } from \"./uri-parser.js\";\n\n/**\n * Create an AFS provider from a mount configuration\n *\n * @param mount - Mount configuration with URI and options\n * @returns Provider instance\n * @throws Error if scheme is unknown or not implemented\n */\nexport async function createProvider(mount: MountConfig): Promise<AFSModule> {\n const parsed = parseURI(mount.uri);\n\n switch (parsed.scheme) {\n case \"fs\":\n return createAFSFSProvider(mount, parsed.path);\n\n case \"git\":\n return createGitProvider(mount, parsed.path, parsed.params, parsed.host);\n\n case \"sqlite\":\n return createSQLiteProvider(mount, parsed.path);\n\n case \"json\":\n return createJSONProvider(mount, parsed.path);\n\n case \"toml\":\n return createTOMLProvider(mount, parsed.path);\n\n case \"s3\":\n return createS3Provider(mount, parsed.path, parsed.params);\n\n case \"gs\":\n return createGCSProvider(mount, parsed.path, parsed.params);\n\n case \"ec2\":\n return createEC2Provider(mount, parsed.path, parsed.params);\n\n case \"gce\":\n return createGCEProvider(mount, parsed.path, parsed.params);\n\n case \"dns\":\n return createDNSProvider(mount, parsed.path, parsed.params);\n\n case \"github\":\n return createGitHubProvider(mount, parsed.path, parsed.params);\n\n case \"sandbox\":\n return createSandboxProvider(mount);\n\n case \"http\":\n case \"https\":\n return createHttpProvider(mount);\n\n case \"mcp\":\n case \"mcp+stdio\":\n case \"mcp+http\":\n case \"mcp+sse\":\n return createMCPProvider(mount, parsed);\n\n default:\n throw new Error(`Unknown URI scheme: ${parsed.scheme}`);\n }\n}\n\nasync function createAFSFSProvider(mount: MountConfig, localPath: string): Promise<AFSModule> {\n const { AFSFS } = await import(\"@aigne/afs-fs\");\n\n return new AFSFS({\n localPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"fs\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n}\n\nasync function createGitProvider(\n mount: MountConfig,\n repoPath: string,\n params: Record<string, string>,\n host?: string,\n): Promise<AFSModule> {\n const { AFSGit } = await import(\"@aigne/afs-git\");\n\n let provider: InstanceType<typeof AFSGit>;\n\n // For remote repos (SSH-style or https), use remoteUrl\n // For local repos, use repoPath directly\n if (host) {\n const remoteUrl = `git@${host}:${repoPath}`;\n provider = new AFSGit({\n remoteUrl,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"git\",\n description: mount.description,\n accessMode: mount.access_mode ?? \"readonly\",\n branches: params.branch ? [params.branch] : undefined,\n ...mount.options,\n });\n } else if (repoPath.startsWith(\"https://\") || repoPath.startsWith(\"http://\")) {\n // Check if repoPath is actually a remote URL (https:// or http://)\n provider = new AFSGit({\n remoteUrl: repoPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"git\",\n description: mount.description,\n accessMode: mount.access_mode ?? \"readonly\",\n branches: params.branch ? [params.branch] : undefined,\n ...mount.options,\n });\n } else {\n provider = new AFSGit({\n repoPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"git\",\n description: mount.description,\n accessMode: mount.access_mode ?? \"readonly\",\n branches: params.branch ? [params.branch] : undefined,\n ...mount.options,\n });\n }\n\n // Wait for async initialization to complete (cloning, validation, etc.)\n await provider.ready();\n return provider;\n}\n\nasync function createSQLiteProvider(mount: MountConfig, dbPath: string): Promise<AFSModule> {\n const { SQLiteAFS } = await import(\"@aigne/afs-sqlite\");\n\n // SQLiteAFS auto-initializes via onMount when mounted to AFS\n return new SQLiteAFS({\n url: `file:${dbPath}`,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"sqlite\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n}\n\nasync function createJSONProvider(mount: MountConfig, jsonPath: string): Promise<AFSModule> {\n const { AFSJSON } = await import(\"@aigne/afs-json\");\n\n return new AFSJSON({\n jsonPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"json\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n}\n\nasync function createTOMLProvider(mount: MountConfig, tomlPath: string): Promise<AFSModule> {\n const { AFSTOML } = await import(\"@aigne/afs-toml\");\n\n return new AFSTOML({\n tomlPath,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"toml\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n}\n\n/**\n * Create an S3 provider from mount configuration\n *\n * Supported URI formats:\n * - s3://bucket (bucket only)\n * - s3://bucket/prefix (bucket with prefix)\n *\n * Options:\n * - endpoint: S3-compatible endpoint URL (for MinIO, R2, B2, etc.)\n * - forcePathStyle: Use path-style URLs instead of virtual-hosted style\n * - region: AWS region\n * - profile: AWS profile name\n */\nasync function createS3Provider(\n mount: MountConfig,\n path: string,\n params: Record<string, string>,\n): Promise<AFSModule> {\n const { AFSS3 } = await import(\"@aigne/afs-s3\");\n\n // Parse bucket and prefix from path\n const parts = path.split(\"/\");\n const bucket = parts[0] || \"\";\n const prefix = parts.slice(1).join(\"/\") || undefined;\n\n if (!bucket) {\n throw new Error(\"S3 URI requires a bucket name: s3://bucket/prefix\");\n }\n\n return new AFSS3({\n bucket,\n prefix,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || bucket,\n description: mount.description,\n accessMode: mount.access_mode,\n region: (params.region ?? mount.options?.region) as string | undefined,\n endpoint: (params.endpoint ?? mount.options?.endpoint) as string | undefined,\n forcePathStyle: (params.forcePathStyle === \"true\" || mount.options?.forcePathStyle) as\n | boolean\n | undefined,\n profile: (params.profile ?? mount.options?.profile) as string | undefined,\n ...mount.options,\n });\n}\n\n/**\n * Create a GCS provider from mount configuration\n *\n * Supported URI formats:\n * - gs://bucket (bucket only)\n * - gs://bucket/prefix (bucket with prefix)\n *\n * Options:\n * - projectId: Google Cloud project ID\n * - keyFilename: Path to service account key file\n * - apiEndpoint: Custom API endpoint (for emulators)\n */\nasync function createGCSProvider(\n mount: MountConfig,\n path: string,\n params: Record<string, string>,\n): Promise<AFSModule> {\n const { AFSGCS } = await import(\"@aigne/afs-gcs\");\n\n // Parse bucket and prefix from path\n const parts = path.split(\"/\");\n const bucket = parts[0] || \"\";\n const prefix = parts.slice(1).join(\"/\") || undefined;\n\n if (!bucket) {\n throw new Error(\"GCS URI requires a bucket name: gs://bucket/prefix\");\n }\n\n return new AFSGCS({\n bucket,\n prefix,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || bucket,\n description: mount.description,\n accessMode: mount.access_mode,\n projectId: (params.projectId ?? mount.options?.projectId) as string | undefined,\n keyFilename: (params.keyFilename ?? mount.options?.keyFilename) as string | undefined,\n endpoint: (params.endpoint ?? mount.options?.endpoint) as string | undefined,\n ...mount.options,\n });\n}\n\n/**\n * Create a GitHub provider from mount configuration\n *\n * Supported URI formats:\n * - github://owner/repo (single-repo mode)\n * - github://owner (org mode - list all repos in organization)\n * - github:// (multi-repo mode, requires options.mode = \"multi-repo\")\n *\n * Options:\n * - mode: \"single-repo\" | \"multi-repo\" | \"org\" (default: auto-detected from URI)\n * - baseUrl: GitHub API base URL (for GitHub Enterprise)\n * - cache: { enabled: boolean; ttl: number }\n * - rateLimit: { autoRetry: boolean; maxRetries: number }\n */\nasync function createGitHubProvider(\n mount: MountConfig,\n repoPath: string,\n params: Record<string, string>,\n): Promise<AFSModule> {\n const { AFSGitHub } = await import(\"@aigne/afs-github\");\n\n // Parse owner/repo from path\n const parts = repoPath.split(\"/\").filter(Boolean);\n const owner = parts[0];\n const repo = parts[1];\n\n // Determine mode from params, options, or auto-detect from URI\n let mode = (params.mode ?? mount.options?.mode) as\n | \"single-repo\"\n | \"multi-repo\"\n | \"org\"\n | undefined;\n\n if (!mode) {\n // Auto-detect mode from URI structure\n if (owner && repo) {\n mode = \"single-repo\";\n } else if (owner && !repo) {\n mode = \"org\";\n } else {\n mode = \"multi-repo\";\n }\n }\n\n // Validate mode requirements\n if (mode === \"single-repo\" && (!owner || !repo)) {\n throw new Error(\"GitHub single-repo mode requires owner/repo in URI: github://owner/repo\");\n }\n if (mode === \"org\" && !owner) {\n throw new Error(\"GitHub org mode requires owner in URI: github://owner\");\n }\n\n // Get auth token from mount.auth or mount.token\n const authToken = mount.auth ?? mount.token;\n\n // Extract ownerType from options (supports both snake_case and camelCase)\n const ownerType = (mount.options?.owner_type ?? mount.options?.ownerType) as\n | \"org\"\n | \"user\"\n | undefined;\n\n return new AFSGitHub({\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"github\",\n description: mount.description,\n owner,\n repo,\n auth: authToken ? { token: authToken } : undefined,\n mode,\n ownerType,\n accessMode: mount.access_mode ?? \"readonly\",\n ...mount.options,\n });\n}\n\nasync function createSandboxProvider(mount: MountConfig): Promise<AFSModule> {\n const { AFSSandbox } = await import(\"@aigne/afs-sandbox\");\n\n return new AFSSandbox({\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"sandbox\",\n description: mount.description,\n accessMode: mount.access_mode,\n ...mount.options,\n });\n}\n\nasync function createHttpProvider(mount: MountConfig): Promise<AFSModule> {\n const { AFSHttpClient } = await import(\"@aigne/afs-http\");\n\n return new AFSHttpClient({\n url: mount.uri,\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"http\",\n description: mount.description,\n accessMode: mount.access_mode,\n token: mount.token,\n ...mount.options,\n });\n}\n\n/**\n * Create an MCP provider from mount configuration\n *\n * Supported URI formats:\n * - mcp://name (requires options.transport, options.command/url)\n * - mcp+stdio://command/args... (e.g., mcp+stdio://npx/-y/@modelcontextprotocol/server-sqlite/test.db)\n * - mcp+http://host/path (e.g., mcp+http://mcp.notion.com/mcp)\n * - mcp+sse://host/path (e.g., mcp+sse://api.example.com/sse)\n *\n * Options:\n * - transport: \"stdio\" | \"http\" | \"sse\" (required for mcp:// scheme)\n * - command: string (for stdio transport)\n * - args: string[] (for stdio transport)\n * - env: Record<string, string> (for stdio transport)\n * - url: string (for http/sse transport)\n * - headers: Record<string, string> (for http/sse transport)\n */\nasync function createMCPProvider(mount: MountConfig, parsed: ParsedURI): Promise<AFSModule> {\n const { AFSMCP } = await import(\"@aigne/afs-mcp\");\n\n const name = mount.path.slice(1).replace(/\\//g, \"-\") || \"mcp\";\n const options = mount.options || {};\n\n // Determine transport from scheme or options\n let transport: \"stdio\" | \"http\" | \"sse\";\n let command: string | undefined;\n let args: string[] | undefined;\n let url: string | undefined;\n\n if (parsed.scheme === \"mcp+stdio\") {\n transport = \"stdio\";\n // Parse path as: command/arg1/arg2/...\n // Split by / then decode each part - this allows %2F to represent literal / in arguments\n const parts = parsed.path.split(\"/\").filter(Boolean).map(decodeURIComponent);\n command = parts[0];\n args = parts.slice(1);\n } else if (parsed.scheme === \"mcp+http\") {\n transport = \"http\";\n url = `https://${parsed.host || \"\"}${parsed.path}`;\n } else if (parsed.scheme === \"mcp+sse\") {\n transport = \"sse\";\n url = `https://${parsed.host || \"\"}${parsed.path}`;\n } else {\n // mcp:// scheme - use options\n transport = (options.transport as \"stdio\" | \"http\" | \"sse\") || \"stdio\";\n command = options.command as string | undefined;\n args = options.args as string[] | undefined;\n url = options.url as string | undefined;\n }\n\n // Build provider options\n const mcpOptions: Record<string, unknown> = {\n name,\n description: mount.description,\n transport,\n };\n\n if (transport === \"stdio\") {\n if (!command) {\n throw new Error(\"MCP stdio transport requires 'command' option\");\n }\n mcpOptions.command = command;\n mcpOptions.args = args || [];\n if (options.env) {\n mcpOptions.env = options.env;\n }\n } else {\n if (!url) {\n throw new Error(`MCP ${transport} transport requires 'url' option`);\n }\n mcpOptions.url = url;\n if (options.headers) {\n mcpOptions.headers = options.headers;\n }\n }\n\n // Pass through additional options\n if (options.timeout) mcpOptions.timeout = options.timeout;\n if (options.maxReconnects) mcpOptions.maxReconnects = options.maxReconnects;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new AFSMCP(mcpOptions as any);\n}\n\n/**\n * Create an EC2 provider from mount configuration\n *\n * Supported URI formats:\n * - ec2://us-east-1 (single region)\n * - ec2://us-east-1,us-west-2 (multi-region)\n * - ec2://?profile=myprofile (use default region from profile)\n *\n * Options:\n * - endpoint: Custom endpoint URL (for LocalStack, etc.)\n * - profile: AWS profile name\n * - filters: Array of { name, values } filters\n * - cache: { ttl: number, instanceTtl: number, staticTtl: number }\n */\nasync function createEC2Provider(\n mount: MountConfig,\n path: string,\n params: Record<string, string>,\n): Promise<AFSModule> {\n const { AFSEC2 } = await import(\"@aigne/afs-ec2\");\n\n // Parse region(s) from path - can be comma-separated for multi-region\n const regions = path\n .split(\",\")\n .map((r) => r.trim())\n .filter(Boolean);\n\n // Build config\n const config: Record<string, unknown> = {\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"ec2\",\n description: mount.description,\n accessMode: mount.access_mode ?? \"readonly\",\n };\n\n // Set region or regions\n if (regions.length === 1) {\n config.region = regions[0];\n } else if (regions.length > 1) {\n config.regions = regions;\n } else {\n // No region specified - will use default from profile/env\n // Provider will throw if no region can be determined\n }\n\n // Set endpoint (for LocalStack, etc.)\n const endpoint = params.endpoint ?? mount.options?.endpoint;\n if (endpoint) {\n config.endpoint = endpoint;\n }\n\n // Set profile\n const profile = params.profile ?? mount.options?.profile;\n if (profile) {\n config.profile = profile;\n }\n\n // Set credentials if provided\n if (mount.options?.credentials) {\n config.credentials = mount.options.credentials;\n }\n\n // Set filters if provided\n if (mount.options?.filters) {\n config.filters = mount.options.filters;\n }\n\n // Set cache config if provided\n if (mount.options?.cache) {\n config.cache = mount.options.cache;\n }\n\n return new AFSEC2(config as ConstructorParameters<typeof AFSEC2>[0]);\n}\n\n/**\n * Create a GCE provider from mount configuration\n *\n * Supported URI formats:\n * - gce://project-id/zone (project and zone)\n * - gce://project-id (project only, zone from options)\n *\n * Options:\n * - zone: GCE zone\n * - keyFilename: Path to service account key file\n * - credentials: Service account credentials object\n * - cache: { ttl: number, instanceTtl: number, staticTtl: number }\n */\nasync function createGCEProvider(\n mount: MountConfig,\n path: string,\n params: Record<string, string>,\n): Promise<AFSModule> {\n const { AFSGCE } = await import(\"@aigne/afs-gce\");\n\n // Parse project and zone from path: project-id/zone\n const parts = path.split(\"/\").filter(Boolean);\n const projectId = parts[0] || \"\";\n const zoneFromPath = parts[1];\n\n if (!projectId) {\n throw new Error(\"GCE URI requires a project ID: gce://project-id/zone\");\n }\n\n // Get zone from path, params, or options\n const zone = zoneFromPath ?? params.zone ?? (mount.options?.zone as string | undefined);\n\n if (!zone) {\n throw new Error(\"GCE requires a zone: gce://project-id/zone or use ?zone= parameter\");\n }\n\n // Build config\n const config: Record<string, unknown> = {\n name: mount.path.slice(1).replace(/\\//g, \"-\") || \"gce\",\n description: mount.description,\n projectId,\n zone,\n accessMode: mount.access_mode ?? \"readonly\",\n };\n\n // Set keyFilename if provided\n const keyFilename = params.keyFilename ?? mount.options?.keyFilename;\n if (keyFilename) {\n config.keyFilename = keyFilename;\n }\n\n // Set credentials if provided\n if (mount.options?.credentials) {\n config.credentials = mount.options.credentials;\n }\n\n // Set cache config if provided\n if (mount.options?.cache) {\n config.cache = mount.options.cache;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new AFSGCE(config as any);\n}\n\n/**\n * Create a DNS provider from mount configuration\n *\n * Supported URI formats:\n * - dns://zone.domain.com (single zone)\n *\n * Options:\n * - provider: DNS provider type (\"route53\" | \"clouddns\")\n * - endpoint: Custom endpoint URL (for LocalStack, etc.)\n * - region: AWS region (for Route53)\n * - credentials: { accessKeyId, secretAccessKey } (for Route53)\n * - projectId: Google Cloud project ID (for Cloud DNS)\n * - keyFilename: Path to service account key file (for Cloud DNS)\n * - permissions: { preset, dangerous }\n */\nasync function createDNSProvider(\n mount: MountConfig,\n zoneDomain: string,\n params: Record<string, string>,\n): Promise<AFSModule> {\n const { DNSProvider, Route53Adapter, CloudDNSAdapter } = await import(\"@aigne/afs-dns\");\n\n // Get provider type (default to route53)\n const providerType = params.provider ?? mount.options?.provider ?? \"route53\";\n\n let adapter: InstanceType<typeof Route53Adapter> | InstanceType<typeof CloudDNSAdapter>;\n\n if (providerType === \"route53\") {\n // Create Route53 adapter\n const adapterConfig: Record<string, unknown> = {};\n\n // Set region\n const region = params.region ?? mount.options?.region;\n if (region) {\n adapterConfig.region = region;\n }\n\n // Set endpoint (for LocalStack, etc.)\n const endpoint = params.endpoint ?? mount.options?.endpoint;\n if (endpoint) {\n adapterConfig.endpoint = endpoint;\n }\n\n // Set credentials if provided\n if (mount.options?.credentials) {\n adapterConfig.credentials = mount.options.credentials;\n }\n\n adapter = new Route53Adapter(adapterConfig);\n } else if (providerType === \"clouddns\") {\n // Create Cloud DNS adapter\n const adapterConfig: Record<string, unknown> = {};\n\n // Set project ID\n const projectId = params.projectId ?? mount.options?.projectId;\n if (projectId) {\n adapterConfig.projectId = projectId;\n }\n\n // Set key filename (service account JSON file)\n const keyFilename = params.keyFilename ?? mount.options?.keyFilename;\n if (keyFilename) {\n adapterConfig.keyFilename = keyFilename;\n }\n\n adapter = new CloudDNSAdapter(adapterConfig);\n } else {\n throw new Error(\n `Unsupported DNS provider: ${providerType}. Supported providers: 'route53', 'clouddns'.`,\n );\n }\n\n // Build DNS provider config\n return new DNSProvider({\n zone: zoneDomain,\n adapter,\n accessMode: mount.access_mode ?? \"readonly\",\n permissions: mount.options?.permissions as ConstructorParameters<\n typeof DNSProvider\n >[0][\"permissions\"],\n auditLog: mount.options?.auditLog as ConstructorParameters<typeof DNSProvider>[0][\"auditLog\"],\n rateLimiting: mount.options?.rateLimiting as boolean | undefined,\n });\n}\n"],"mappings":"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"}
|
|
@@ -3,10 +3,22 @@
|
|
|
3
3
|
const SUPPORTED_SCHEMES = new Set([
|
|
4
4
|
"fs",
|
|
5
5
|
"git",
|
|
6
|
+
"github",
|
|
6
7
|
"sqlite",
|
|
7
8
|
"json",
|
|
9
|
+
"toml",
|
|
10
|
+
"s3",
|
|
11
|
+
"gs",
|
|
12
|
+
"ec2",
|
|
13
|
+
"gce",
|
|
14
|
+
"dns",
|
|
15
|
+
"sandbox",
|
|
8
16
|
"http",
|
|
9
|
-
"https"
|
|
17
|
+
"https",
|
|
18
|
+
"mcp",
|
|
19
|
+
"mcp+stdio",
|
|
20
|
+
"mcp+http",
|
|
21
|
+
"mcp+sse"
|
|
10
22
|
]);
|
|
11
23
|
/**
|
|
12
24
|
* Parse an AFS URI into components
|
|
@@ -29,11 +41,17 @@ function parseURI(uri) {
|
|
|
29
41
|
path: sshGitMatch[2],
|
|
30
42
|
params: {}
|
|
31
43
|
};
|
|
32
|
-
const schemeMatch = uri.match(/^([a-
|
|
44
|
+
const schemeMatch = uri.match(/^([a-z0-9+]+):\/\//i);
|
|
33
45
|
if (!schemeMatch?.[1]) throw new Error(`Invalid URI format: ${uri}`);
|
|
34
46
|
const scheme = schemeMatch[1].toLowerCase();
|
|
35
47
|
if (!SUPPORTED_SCHEMES.has(scheme)) throw new Error(`Unknown URI scheme: ${scheme}`);
|
|
36
48
|
if (scheme === "http" || scheme === "https") return parseHttpURI(uri, scheme);
|
|
49
|
+
if (scheme.startsWith("mcp")) return parseMCPURI(uri, scheme);
|
|
50
|
+
if (scheme === "s3") return parseS3URI(uri);
|
|
51
|
+
if (scheme === "gs") return parseGCSURI(uri);
|
|
52
|
+
if (scheme === "ec2") return parseEC2URI(uri);
|
|
53
|
+
if (scheme === "gce") return parseGCEURI(uri);
|
|
54
|
+
if (scheme === "dns") return parseDNSURI(uri);
|
|
37
55
|
return parseLocalURI(uri, scheme);
|
|
38
56
|
}
|
|
39
57
|
function parseHttpURI(uri, scheme) {
|
|
@@ -87,6 +105,181 @@ function parseLocalURI(uri, scheme) {
|
|
|
87
105
|
params
|
|
88
106
|
};
|
|
89
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Parse MCP URI preserving URL encoding in path
|
|
110
|
+
*
|
|
111
|
+
* For MCP URIs, we don't decode the path to allow %2F to represent
|
|
112
|
+
* literal / in arguments (e.g., @playwright%2Fmcp -> @playwright/mcp after split)
|
|
113
|
+
*/
|
|
114
|
+
function parseMCPURI(uri, scheme) {
|
|
115
|
+
const withoutScheme = uri.slice(scheme.length + 3);
|
|
116
|
+
const queryIndex = withoutScheme.indexOf("?");
|
|
117
|
+
let path;
|
|
118
|
+
let queryString;
|
|
119
|
+
if (queryIndex >= 0) {
|
|
120
|
+
path = withoutScheme.slice(0, queryIndex);
|
|
121
|
+
queryString = withoutScheme.slice(queryIndex + 1);
|
|
122
|
+
} else path = withoutScheme;
|
|
123
|
+
const params = {};
|
|
124
|
+
if (queryString) new URLSearchParams(queryString).forEach((value, key) => {
|
|
125
|
+
params[key] = value;
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
scheme,
|
|
129
|
+
path,
|
|
130
|
+
params
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse S3 URI into bucket and prefix
|
|
135
|
+
*
|
|
136
|
+
* Format: s3://bucket/prefix
|
|
137
|
+
*/
|
|
138
|
+
function parseS3URI(uri) {
|
|
139
|
+
const withoutScheme = uri.slice(5);
|
|
140
|
+
const queryIndex = withoutScheme.indexOf("?");
|
|
141
|
+
let pathPart;
|
|
142
|
+
let queryString;
|
|
143
|
+
if (queryIndex >= 0) {
|
|
144
|
+
pathPart = withoutScheme.slice(0, queryIndex);
|
|
145
|
+
queryString = withoutScheme.slice(queryIndex + 1);
|
|
146
|
+
} else pathPart = withoutScheme;
|
|
147
|
+
const parts = pathPart.split("/").filter(Boolean);
|
|
148
|
+
const bucket = parts[0] || "";
|
|
149
|
+
const prefix = parts.slice(1).join("/");
|
|
150
|
+
if (!bucket) throw new Error("S3 URI requires a bucket name: s3://bucket/prefix");
|
|
151
|
+
const params = {};
|
|
152
|
+
if (queryString) new URLSearchParams(queryString).forEach((value, key) => {
|
|
153
|
+
params[key] = value;
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
scheme: "s3",
|
|
157
|
+
path: prefix ? `${bucket}/${prefix}` : bucket,
|
|
158
|
+
params,
|
|
159
|
+
host: bucket
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Parse EC2 URI into region(s)
|
|
164
|
+
*
|
|
165
|
+
* Formats:
|
|
166
|
+
* - ec2://us-east-1 (single region)
|
|
167
|
+
* - ec2://us-east-1,us-west-2 (multi-region)
|
|
168
|
+
* - ec2://?profile=myprofile (use default region from profile)
|
|
169
|
+
*
|
|
170
|
+
* Query params:
|
|
171
|
+
* - profile: AWS profile name
|
|
172
|
+
* - endpoint: Custom endpoint (for LocalStack)
|
|
173
|
+
*/
|
|
174
|
+
function parseEC2URI(uri) {
|
|
175
|
+
const withoutScheme = uri.slice(6);
|
|
176
|
+
const queryIndex = withoutScheme.indexOf("?");
|
|
177
|
+
let pathPart;
|
|
178
|
+
let queryString;
|
|
179
|
+
if (queryIndex >= 0) {
|
|
180
|
+
pathPart = withoutScheme.slice(0, queryIndex);
|
|
181
|
+
queryString = withoutScheme.slice(queryIndex + 1);
|
|
182
|
+
} else pathPart = withoutScheme;
|
|
183
|
+
const params = {};
|
|
184
|
+
if (queryString) new URLSearchParams(queryString).forEach((value, key) => {
|
|
185
|
+
params[key] = value;
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
scheme: "ec2",
|
|
189
|
+
path: pathPart.trim(),
|
|
190
|
+
params
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Parse DNS URI into zone domain
|
|
195
|
+
*
|
|
196
|
+
* Format: dns://zone.domain.com
|
|
197
|
+
*
|
|
198
|
+
* Query params:
|
|
199
|
+
* - provider: DNS provider (route53, cloudflare, etc.)
|
|
200
|
+
*/
|
|
201
|
+
function parseDNSURI(uri) {
|
|
202
|
+
const withoutScheme = uri.slice(6);
|
|
203
|
+
const queryIndex = withoutScheme.indexOf("?");
|
|
204
|
+
let pathPart;
|
|
205
|
+
let queryString;
|
|
206
|
+
if (queryIndex >= 0) {
|
|
207
|
+
pathPart = withoutScheme.slice(0, queryIndex);
|
|
208
|
+
queryString = withoutScheme.slice(queryIndex + 1);
|
|
209
|
+
} else pathPart = withoutScheme;
|
|
210
|
+
const params = {};
|
|
211
|
+
if (queryString) new URLSearchParams(queryString).forEach((value, key) => {
|
|
212
|
+
params[key] = value;
|
|
213
|
+
});
|
|
214
|
+
const path = pathPart.trim();
|
|
215
|
+
if (!path) throw new Error("DNS URI requires a zone domain: dns://example.com");
|
|
216
|
+
return {
|
|
217
|
+
scheme: "dns",
|
|
218
|
+
path,
|
|
219
|
+
params
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Parse GCS URI into bucket and prefix
|
|
224
|
+
*
|
|
225
|
+
* Format: gs://bucket/prefix
|
|
226
|
+
*/
|
|
227
|
+
function parseGCSURI(uri) {
|
|
228
|
+
const withoutScheme = uri.slice(5);
|
|
229
|
+
const queryIndex = withoutScheme.indexOf("?");
|
|
230
|
+
let pathPart;
|
|
231
|
+
let queryString;
|
|
232
|
+
if (queryIndex >= 0) {
|
|
233
|
+
pathPart = withoutScheme.slice(0, queryIndex);
|
|
234
|
+
queryString = withoutScheme.slice(queryIndex + 1);
|
|
235
|
+
} else pathPart = withoutScheme;
|
|
236
|
+
const parts = pathPart.split("/").filter(Boolean);
|
|
237
|
+
const bucket = parts[0] || "";
|
|
238
|
+
const prefix = parts.slice(1).join("/");
|
|
239
|
+
if (!bucket) throw new Error("GCS URI requires a bucket name: gs://bucket/prefix");
|
|
240
|
+
const params = {};
|
|
241
|
+
if (queryString) new URLSearchParams(queryString).forEach((value, key) => {
|
|
242
|
+
params[key] = value;
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
scheme: "gs",
|
|
246
|
+
path: prefix ? `${bucket}/${prefix}` : bucket,
|
|
247
|
+
params,
|
|
248
|
+
host: bucket
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Parse GCE URI into project and zone
|
|
253
|
+
*
|
|
254
|
+
* Formats:
|
|
255
|
+
* - gce://project-id/zone (project and zone)
|
|
256
|
+
* - gce://project-id (project only, zone from options)
|
|
257
|
+
*
|
|
258
|
+
* Query params:
|
|
259
|
+
* - zone: GCE zone (alternative to path)
|
|
260
|
+
* - keyFilename: Path to service account key file
|
|
261
|
+
*/
|
|
262
|
+
function parseGCEURI(uri) {
|
|
263
|
+
const withoutScheme = uri.slice(6);
|
|
264
|
+
const queryIndex = withoutScheme.indexOf("?");
|
|
265
|
+
let pathPart;
|
|
266
|
+
let queryString;
|
|
267
|
+
if (queryIndex >= 0) {
|
|
268
|
+
pathPart = withoutScheme.slice(0, queryIndex);
|
|
269
|
+
queryString = withoutScheme.slice(queryIndex + 1);
|
|
270
|
+
} else pathPart = withoutScheme;
|
|
271
|
+
const params = {};
|
|
272
|
+
if (queryString) new URLSearchParams(queryString).forEach((value, key) => {
|
|
273
|
+
params[key] = value;
|
|
274
|
+
});
|
|
275
|
+
const path = pathPart.trim();
|
|
276
|
+
if (!path) throw new Error("GCE URI requires a project ID: gce://project-id/zone");
|
|
277
|
+
return {
|
|
278
|
+
scheme: "gce",
|
|
279
|
+
path,
|
|
280
|
+
params
|
|
281
|
+
};
|
|
282
|
+
}
|
|
90
283
|
|
|
91
284
|
//#endregion
|
|
92
285
|
exports.parseURI = parseURI;
|