@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.
Files changed (62) hide show
  1. package/dist/cli.cjs +41 -18
  2. package/dist/cli.mjs +42 -19
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/commands/exec.cjs +132 -14
  5. package/dist/commands/exec.mjs +129 -14
  6. package/dist/commands/exec.mjs.map +1 -1
  7. package/dist/commands/explain.cjs +1 -1
  8. package/dist/commands/explain.mjs +1 -1
  9. package/dist/commands/explain.mjs.map +1 -1
  10. package/dist/commands/index.mjs +1 -1
  11. package/dist/commands/ls.cjs +129 -30
  12. package/dist/commands/ls.mjs +129 -30
  13. package/dist/commands/ls.mjs.map +1 -1
  14. package/dist/commands/read.cjs +213 -14
  15. package/dist/commands/read.mjs +213 -14
  16. package/dist/commands/read.mjs.map +1 -1
  17. package/dist/commands/stat.cjs +116 -34
  18. package/dist/commands/stat.mjs +117 -34
  19. package/dist/commands/stat.mjs.map +1 -1
  20. package/dist/commands/write.cjs +37 -4
  21. package/dist/commands/write.mjs +38 -4
  22. package/dist/commands/write.mjs.map +1 -1
  23. package/dist/config/loader.cjs +12 -1
  24. package/dist/config/loader.mjs +12 -1
  25. package/dist/config/loader.mjs.map +1 -1
  26. package/dist/config/provider-factory.cjs +310 -3
  27. package/dist/config/provider-factory.mjs +310 -3
  28. package/dist/config/provider-factory.mjs.map +1 -1
  29. package/dist/config/uri-parser.cjs +195 -2
  30. package/dist/config/uri-parser.mjs +195 -2
  31. package/dist/config/uri-parser.mjs.map +1 -1
  32. package/dist/explorer/actions.cjs +53 -23
  33. package/dist/explorer/actions.mjs +54 -23
  34. package/dist/explorer/actions.mjs.map +1 -1
  35. package/dist/explorer/components/dialog.cjs +163 -10
  36. package/dist/explorer/components/dialog.mjs +163 -10
  37. package/dist/explorer/components/dialog.mjs.map +1 -1
  38. package/dist/explorer/components/file-list.mjs.map +1 -1
  39. package/dist/explorer/components/metadata-panel.cjs +39 -25
  40. package/dist/explorer/components/metadata-panel.mjs +39 -25
  41. package/dist/explorer/components/metadata-panel.mjs.map +1 -1
  42. package/dist/explorer/screen.cjs +23 -8
  43. package/dist/explorer/screen.mjs +24 -9
  44. package/dist/explorer/screen.mjs.map +1 -1
  45. package/dist/explorer/theme.cjs +3 -1
  46. package/dist/explorer/theme.mjs +3 -1
  47. package/dist/explorer/theme.mjs.map +1 -1
  48. package/dist/path-utils.cjs +2 -1
  49. package/dist/path-utils.mjs +1 -1
  50. package/dist/runtime.cjs +24 -0
  51. package/dist/runtime.mjs +24 -0
  52. package/dist/runtime.mjs.map +1 -1
  53. package/dist/ui/header.cjs +0 -9
  54. package/dist/ui/header.mjs +1 -9
  55. package/dist/ui/header.mjs.map +1 -1
  56. package/dist/ui/index.cjs +0 -2
  57. package/dist/ui/index.mjs +2 -3
  58. package/dist/ui/index.mjs.map +1 -1
  59. package/dist/utils/meta.cjs +51 -0
  60. package/dist/utils/meta.mjs +49 -0
  61. package/dist/utils/meta.mjs.map +1 -0
  62. 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
- if (host) return new AFSGit({
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://")) return new AFSGit({
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
- return new AFSGit({
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-z]+):\/\//i);
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;