@mastra/archil 0.2.0 → 0.2.1-alpha.0

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/index.js CHANGED
@@ -1,488 +1,441 @@
1
- import { MastraFilesystem, FileNotFoundError, FileExistsError } from '@mastra/core/workspace';
2
- import { Archil } from 'disk';
3
-
4
- // src/filesystem.ts
5
- var MIME_TYPES = {
6
- ".txt": "text/plain",
7
- ".md": "text/markdown",
8
- ".html": "text/html",
9
- ".css": "text/css",
10
- ".csv": "text/csv",
11
- ".xml": "text/xml",
12
- ".js": "text/javascript",
13
- ".mjs": "text/javascript",
14
- ".ts": "text/typescript",
15
- ".tsx": "text/typescript",
16
- ".jsx": "text/javascript",
17
- ".json": "application/json",
18
- ".yaml": "text/yaml",
19
- ".yml": "text/yaml",
20
- ".py": "text/x-python",
21
- ".sh": "text/x-shellscript",
22
- ".png": "image/png",
23
- ".jpg": "image/jpeg",
24
- ".jpeg": "image/jpeg",
25
- ".gif": "image/gif",
26
- ".svg": "image/svg+xml",
27
- ".webp": "image/webp",
28
- ".pdf": "application/pdf",
29
- ".zip": "application/zip",
30
- ".gz": "application/gzip",
31
- ".tar": "application/x-tar"
1
+ import { FileExistsError, FileNotFoundError, MastraFilesystem } from "@mastra/core/workspace";
2
+ import { Archil } from "disk";
3
+ //#region src/filesystem.ts
4
+ const MIME_TYPES = {
5
+ ".txt": "text/plain",
6
+ ".md": "text/markdown",
7
+ ".html": "text/html",
8
+ ".css": "text/css",
9
+ ".csv": "text/csv",
10
+ ".xml": "text/xml",
11
+ ".js": "text/javascript",
12
+ ".mjs": "text/javascript",
13
+ ".ts": "text/typescript",
14
+ ".tsx": "text/typescript",
15
+ ".jsx": "text/javascript",
16
+ ".json": "application/json",
17
+ ".yaml": "text/yaml",
18
+ ".yml": "text/yaml",
19
+ ".py": "text/x-python",
20
+ ".sh": "text/x-shellscript",
21
+ ".png": "image/png",
22
+ ".jpg": "image/jpeg",
23
+ ".jpeg": "image/jpeg",
24
+ ".gif": "image/gif",
25
+ ".svg": "image/svg+xml",
26
+ ".webp": "image/webp",
27
+ ".pdf": "application/pdf",
28
+ ".zip": "application/zip",
29
+ ".gz": "application/gzip",
30
+ ".tar": "application/x-tar"
32
31
  };
33
32
  function getMimeType(path) {
34
- const ext = path.toLowerCase().match(/\.[^.]+$/)?.[0];
35
- return ext ? MIME_TYPES[ext] ?? "application/octet-stream" : "application/octet-stream";
33
+ const ext = path.toLowerCase().match(/\.[^.]+$/)?.[0];
34
+ return ext ? MIME_TYPES[ext] ?? "application/octet-stream" : "application/octet-stream";
36
35
  }
36
+ /** Trim leading and trailing slashes. */
37
37
  function trimSlashes(s) {
38
- let start = 0;
39
- let end = s.length;
40
- while (start < end && s[start] === "/") start++;
41
- while (end > start && s[end - 1] === "/") end--;
42
- return s.slice(start, end);
38
+ let start = 0;
39
+ let end = s.length;
40
+ while (start < end && s[start] === "/") start++;
41
+ while (end > start && s[end - 1] === "/") end--;
42
+ return s.slice(start, end);
43
43
  }
44
+ /** Normalize a workspace path to a key (no leading slash). */
44
45
  function toKey(path) {
45
- return trimSlashes(path) || "";
46
+ return trimSlashes(path) || "";
46
47
  }
48
+ /** Get the basename from a path. */
47
49
  function basename(path) {
48
- const key = toKey(path);
49
- const idx = key.lastIndexOf("/");
50
- return idx === -1 ? key : key.slice(idx + 1);
50
+ const key = toKey(path);
51
+ const idx = key.lastIndexOf("/");
52
+ return idx === -1 ? key : key.slice(idx + 1);
51
53
  }
54
+ /** Get the parent directory key (empty = root). */
52
55
  function dirname(path) {
53
- const key = toKey(path);
54
- const idx = key.lastIndexOf("/");
55
- return idx === -1 ? "" : key.slice(0, idx);
56
+ const key = toKey(path);
57
+ const idx = key.lastIndexOf("/");
58
+ return idx === -1 ? "" : key.slice(0, idx);
56
59
  }
60
+ /** Shell-escape a string for use in exec commands. */
57
61
  function shellEscape(s) {
58
- return "'" + s.replace(/'/g, "'\\''") + "'";
62
+ return "'" + s.replace(/'/g, "'\\''") + "'";
59
63
  }
60
64
  var ArchilFilesystem = class extends MastraFilesystem {
61
- id;
62
- name = "ArchilFilesystem";
63
- provider = "archil";
64
- readOnly;
65
- displayName;
66
- icon;
67
- description;
68
- status = "pending";
69
- _disk = null;
70
- _archil = null;
71
- _diskId;
72
- _createDiskOptions;
73
- _archilOptions;
74
- constructor(options) {
75
- super({ ...options, name: "ArchilFilesystem" });
76
- this.id = options.id ?? `archil-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
77
- this.readOnly = options.readOnly;
78
- this.displayName = options.displayName ?? "Archil";
79
- this.icon = options.icon ?? "cloud";
80
- this.description = options.description ?? "Elastic serverless filesystem powered by Archil";
81
- this._diskId = options.diskId;
82
- this._createDiskOptions = options.createDiskOptions;
83
- this._archilOptions = {
84
- apiKey: options.apiKey,
85
- region: options.region,
86
- baseUrl: options.baseUrl,
87
- s3BaseUrl: options.s3BaseUrl
88
- };
89
- }
90
- // ---------------------------------------------------------------------------
91
- // Public accessors
92
- // ---------------------------------------------------------------------------
93
- /** The underlying Archil Disk instance (available after init). */
94
- get disk() {
95
- if (!this._disk) {
96
- throw new Error("ArchilFilesystem not initialized \u2014 call init() first");
97
- }
98
- return this._disk;
99
- }
100
- /** The Archil SDK client instance. */
101
- get archil() {
102
- if (!this._archil) {
103
- this._archil = new Archil(this._archilOptions);
104
- }
105
- return this._archil;
106
- }
107
- // ---------------------------------------------------------------------------
108
- // Lifecycle
109
- // ---------------------------------------------------------------------------
110
- async init() {
111
- try {
112
- if (this._diskId && this._createDiskOptions) {
113
- throw new Error("diskId and createDiskOptions are mutually exclusive");
114
- }
115
- if (this._diskId) {
116
- this._disk = await this.archil.disks.get(this._diskId);
117
- } else if (this._createDiskOptions) {
118
- const result = await this.archil.disks.create(this._createDiskOptions);
119
- this._disk = result.disk;
120
- } else {
121
- throw new Error("Either diskId or createDiskOptions must be provided");
122
- }
123
- this.status = "ready";
124
- } catch (err) {
125
- this.status = "error";
126
- this.error = err instanceof Error ? err.message : String(err);
127
- throw err;
128
- }
129
- }
130
- async destroy() {
131
- this._disk = null;
132
- this._archil = null;
133
- }
134
- isReady() {
135
- return this.status === "ready" && this._disk !== null;
136
- }
137
- getInfo() {
138
- return {
139
- id: this.id,
140
- name: this.name,
141
- provider: this.provider,
142
- status: this.status,
143
- error: this.error,
144
- readOnly: this.readOnly,
145
- icon: this.icon,
146
- metadata: {
147
- diskId: this._disk?.id ?? "",
148
- region: this._disk?.region ?? "",
149
- diskName: this._disk?.name ?? ""
150
- }
151
- };
152
- }
153
- getInstructions() {
154
- const access = this.readOnly ? "Read-only" : "Persistent";
155
- const diskName = this._disk?.name ?? "Archil disk";
156
- return `Archil elastic filesystem "${diskName}". ${access} storage \u2014 files persist across sessions. Supports serverless execution via exec().`;
157
- }
158
- // ---------------------------------------------------------------------------
159
- // Archil-specific operations
160
- // ---------------------------------------------------------------------------
161
- /**
162
- * Execute a shell command on the disk's filesystem.
163
- * The disk is mounted as the working directory.
164
- */
165
- async exec(command) {
166
- await this.ensureReady();
167
- this.assertWritable();
168
- return this.disk.exec(command);
169
- }
170
- /**
171
- * Parallel grep across files on the disk.
172
- */
173
- async grep(opts) {
174
- await this.ensureReady();
175
- return this.disk.grep(opts);
176
- }
177
- /**
178
- * Create a signed, time-limited download URL for a file.
179
- */
180
- async share(key, opts) {
181
- await this.ensureReady();
182
- return this.disk.share(key, opts);
183
- }
184
- /**
185
- * List objects using the S3-compatible API directly.
186
- */
187
- async listObjects(prefix, opts) {
188
- await this.ensureReady();
189
- return this.disk.listObjects(prefix, opts);
190
- }
191
- /**
192
- * Get object metadata without downloading.
193
- */
194
- async headObject(key) {
195
- await this.ensureReady();
196
- return this.disk.headObject(key);
197
- }
198
- // ---------------------------------------------------------------------------
199
- // File Operations (WorkspaceFilesystem interface)
200
- // ---------------------------------------------------------------------------
201
- async readFile(path, options) {
202
- await this.ensureReady();
203
- const key = toKey(path);
204
- if (!key) {
205
- throw new Error("Cannot read file at root path");
206
- }
207
- try {
208
- const data = await this.disk.getObject(key);
209
- if (options?.encoding) {
210
- return Buffer.from(data).toString(options.encoding);
211
- }
212
- return Buffer.from(data);
213
- } catch (err) {
214
- if (isNotFound(err)) {
215
- throw new FileNotFoundError(path);
216
- }
217
- throw err;
218
- }
219
- }
220
- async writeFile(path, content, options) {
221
- await this.ensureReady();
222
- this.assertWritable();
223
- const key = toKey(path);
224
- if (!key) {
225
- throw new Error("Cannot write file at root path");
226
- }
227
- if (options?.overwrite === false) {
228
- const exists = await this.disk.objectExists(key);
229
- if (exists) {
230
- throw new FileExistsError(path);
231
- }
232
- }
233
- if (options?.recursive) {
234
- const dir = dirname(path);
235
- if (dir) {
236
- await this.disk.exec(`mkdir -p ${shellEscape(dir)}`);
237
- }
238
- }
239
- const body = typeof content === "string" ? content : content instanceof Uint8Array ? content : new Uint8Array(content);
240
- const mimeType = options?.mimeType ?? getMimeType(path);
241
- await this.disk.putObject(key, body, mimeType);
242
- }
243
- async appendFile(path, content) {
244
- await this.ensureReady();
245
- this.assertWritable();
246
- const key = toKey(path);
247
- if (!key) {
248
- throw new Error("Cannot append to root path");
249
- }
250
- const data = typeof content === "string" ? content : Buffer.from(content).toString("base64");
251
- if (typeof content === "string") {
252
- await this.disk.exec(`printf '%s' ${shellEscape(data)} >> ${shellEscape(key)}`);
253
- } else {
254
- await this.disk.exec(`printf '%s' ${shellEscape(data)} | base64 -d >> ${shellEscape(key)}`);
255
- }
256
- }
257
- async deleteFile(path, options) {
258
- await this.ensureReady();
259
- this.assertWritable();
260
- const key = toKey(path);
261
- if (!key) {
262
- throw new Error("Cannot delete root path");
263
- }
264
- if (!options?.force) {
265
- const exists = await this.disk.objectExists(key);
266
- if (!exists) {
267
- throw new FileNotFoundError(path);
268
- }
269
- }
270
- await this.disk.deleteObject(key);
271
- }
272
- async copyFile(src, dest, options) {
273
- await this.ensureReady();
274
- this.assertWritable();
275
- const srcKey = toKey(src);
276
- const destKey = toKey(dest);
277
- if (!options?.overwrite) {
278
- const exists = await this.disk.objectExists(destKey);
279
- if (exists) {
280
- throw new FileExistsError(dest);
281
- }
282
- }
283
- const flags = options?.recursive ? "-r" : "";
284
- const result = await this.disk.exec(`cp ${flags} ${shellEscape(srcKey)} ${shellEscape(destKey)}`);
285
- if (result.exitCode !== 0) {
286
- if (result.stderr.includes("No such file")) {
287
- throw new FileNotFoundError(src);
288
- }
289
- throw new Error(`cp failed: ${result.stderr}`);
290
- }
291
- }
292
- async moveFile(src, dest, options) {
293
- await this.ensureReady();
294
- this.assertWritable();
295
- const srcKey = toKey(src);
296
- const destKey = toKey(dest);
297
- if (!options?.overwrite) {
298
- const exists = await this.disk.objectExists(destKey);
299
- if (exists) {
300
- throw new FileExistsError(dest);
301
- }
302
- }
303
- const result = await this.disk.exec(`mv ${shellEscape(srcKey)} ${shellEscape(destKey)}`);
304
- if (result.exitCode !== 0) {
305
- if (result.stderr.includes("No such file")) {
306
- throw new FileNotFoundError(src);
307
- }
308
- throw new Error(`mv failed: ${result.stderr}`);
309
- }
310
- }
311
- // ---------------------------------------------------------------------------
312
- // Directory Operations
313
- // ---------------------------------------------------------------------------
314
- async mkdir(path, options) {
315
- await this.ensureReady();
316
- this.assertWritable();
317
- const key = toKey(path);
318
- if (!key) return;
319
- const flag = options?.recursive ? "-p" : "";
320
- const result = await this.disk.exec(`mkdir ${flag} ${shellEscape(key)}`);
321
- if (result.exitCode !== 0) {
322
- if (result.stderr.includes("File exists")) {
323
- throw new FileExistsError(path);
324
- }
325
- throw new Error(`mkdir failed: ${result.stderr}`);
326
- }
327
- }
328
- async rmdir(path, options) {
329
- await this.ensureReady();
330
- this.assertWritable();
331
- const key = toKey(path);
332
- if (!key) {
333
- throw new Error("Cannot remove root directory");
334
- }
335
- const cmd = options?.recursive ? `rm -r${options?.force ? "f" : ""} ${shellEscape(key)}` : `rmdir ${shellEscape(key)}`;
336
- const result = await this.disk.exec(options?.force ? `${cmd} 2>/dev/null; true` : cmd);
337
- if (result.exitCode !== 0 && !options?.force) {
338
- if (result.stderr.includes("No such file") || result.stderr.includes("not found")) {
339
- throw new FileNotFoundError(path);
340
- }
341
- throw new Error(`rmdir failed: ${result.stderr}`);
342
- }
343
- }
344
- async readdir(path, options) {
345
- await this.ensureReady();
346
- const key = toKey(path);
347
- const prefix = key ? key + "/" : "";
348
- if (options?.recursive) {
349
- return this.readdirRecursive(prefix, options);
350
- }
351
- const result = await this.disk.listObjects(prefix, { recursive: false });
352
- const entries = [];
353
- for (const obj of result.objects) {
354
- const name = obj.key.slice(prefix.length);
355
- if (!name || name.includes("/")) continue;
356
- if (options?.extension) {
357
- const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
358
- const ext = name.match(/\.[^.]+$/)?.[0] ?? "";
359
- if (!extensions.includes(ext)) continue;
360
- }
361
- entries.push({
362
- name,
363
- type: "file",
364
- size: obj.size
365
- });
366
- }
367
- for (const cp of result.commonPrefixes) {
368
- const name = trimSlashes(cp.slice(prefix.length));
369
- if (!name) continue;
370
- entries.push({
371
- name,
372
- type: "directory"
373
- });
374
- }
375
- return entries;
376
- }
377
- async readdirRecursive(prefix, options) {
378
- const result = await this.disk.listObjects(prefix, { recursive: true });
379
- const entries = [];
380
- for (const obj of result.objects) {
381
- const relativePath = obj.key.slice(prefix.length);
382
- if (!relativePath) continue;
383
- if (options?.maxDepth !== void 0) {
384
- const depth = relativePath.split("/").length - 1;
385
- if (depth > options.maxDepth) continue;
386
- }
387
- if (options?.extension) {
388
- const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
389
- const ext = relativePath.match(/\.[^.]+$/)?.[0] ?? "";
390
- if (!extensions.includes(ext)) continue;
391
- }
392
- entries.push({
393
- name: relativePath,
394
- type: "file",
395
- size: obj.size
396
- });
397
- }
398
- return entries;
399
- }
400
- // ---------------------------------------------------------------------------
401
- // Path Operations
402
- // ---------------------------------------------------------------------------
403
- async exists(path) {
404
- await this.ensureReady();
405
- const key = toKey(path);
406
- if (!key) return true;
407
- const fileExists = await this.disk.objectExists(key);
408
- if (fileExists) return true;
409
- const result = await this.disk.listObjects(key + "/", { singlePage: true, limit: 1 });
410
- return result.objects.length > 0 || result.commonPrefixes.length > 0;
411
- }
412
- async stat(path) {
413
- await this.ensureReady();
414
- const key = toKey(path);
415
- if (!key) {
416
- return {
417
- name: "",
418
- path: "/",
419
- type: "directory",
420
- size: 0,
421
- createdAt: /* @__PURE__ */ new Date(0),
422
- modifiedAt: /* @__PURE__ */ new Date(0)
423
- };
424
- }
425
- const meta = await this.disk.headObject(key);
426
- if (meta) {
427
- return {
428
- name: basename(path),
429
- path: "/" + key,
430
- type: "file",
431
- size: meta.size,
432
- createdAt: meta.lastModified ?? /* @__PURE__ */ new Date(0),
433
- modifiedAt: meta.lastModified ?? /* @__PURE__ */ new Date(0),
434
- mimeType: meta.contentType
435
- };
436
- }
437
- const result = await this.disk.listObjects(key + "/", { singlePage: true, limit: 1 });
438
- if (result.objects.length > 0 || result.commonPrefixes.length > 0) {
439
- return {
440
- name: basename(path),
441
- path: "/" + key,
442
- type: "directory",
443
- size: 0,
444
- createdAt: /* @__PURE__ */ new Date(0),
445
- modifiedAt: /* @__PURE__ */ new Date(0)
446
- };
447
- }
448
- throw new FileNotFoundError(path);
449
- }
450
- // ---------------------------------------------------------------------------
451
- // Helpers
452
- // ---------------------------------------------------------------------------
453
- assertWritable() {
454
- if (this.readOnly) {
455
- throw new Error("Filesystem is read-only");
456
- }
457
- }
65
+ id;
66
+ name = "ArchilFilesystem";
67
+ provider = "archil";
68
+ readOnly;
69
+ displayName;
70
+ icon;
71
+ description;
72
+ status = "pending";
73
+ _disk = null;
74
+ _archil = null;
75
+ _diskId;
76
+ _createDiskOptions;
77
+ _archilOptions;
78
+ constructor(options) {
79
+ super({
80
+ ...options,
81
+ name: "ArchilFilesystem"
82
+ });
83
+ this.id = options.id ?? `archil-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
84
+ this.readOnly = options.readOnly;
85
+ this.displayName = options.displayName ?? "Archil";
86
+ this.icon = options.icon ?? "cloud";
87
+ this.description = options.description ?? "Elastic serverless filesystem powered by Archil";
88
+ this._diskId = options.diskId;
89
+ this._createDiskOptions = options.createDiskOptions;
90
+ this._archilOptions = {
91
+ apiKey: options.apiKey,
92
+ region: options.region,
93
+ baseUrl: options.baseUrl,
94
+ s3BaseUrl: options.s3BaseUrl
95
+ };
96
+ }
97
+ /** The underlying Archil Disk instance (available after init). */
98
+ get disk() {
99
+ if (!this._disk) throw new Error("ArchilFilesystem not initialized — call init() first");
100
+ return this._disk;
101
+ }
102
+ /** The Archil SDK client instance. */
103
+ get archil() {
104
+ if (!this._archil) this._archil = new Archil(this._archilOptions);
105
+ return this._archil;
106
+ }
107
+ async init() {
108
+ try {
109
+ if (this._diskId && this._createDiskOptions) throw new Error("diskId and createDiskOptions are mutually exclusive");
110
+ if (this._diskId) this._disk = await this.archil.disks.get(this._diskId);
111
+ else if (this._createDiskOptions) {
112
+ const result = await this.archil.disks.create(this._createDiskOptions);
113
+ this._disk = result.disk;
114
+ } else throw new Error("Either diskId or createDiskOptions must be provided");
115
+ this.status = "ready";
116
+ } catch (err) {
117
+ this.status = "error";
118
+ this.error = err instanceof Error ? err.message : String(err);
119
+ throw err;
120
+ }
121
+ }
122
+ async destroy() {
123
+ this._disk = null;
124
+ this._archil = null;
125
+ }
126
+ isReady() {
127
+ return this.status === "ready" && this._disk !== null;
128
+ }
129
+ getInfo() {
130
+ return {
131
+ id: this.id,
132
+ name: this.name,
133
+ provider: this.provider,
134
+ status: this.status,
135
+ error: this.error,
136
+ readOnly: this.readOnly,
137
+ icon: this.icon,
138
+ metadata: {
139
+ diskId: this._disk?.id ?? "",
140
+ region: this._disk?.region ?? "",
141
+ diskName: this._disk?.name ?? ""
142
+ }
143
+ };
144
+ }
145
+ getInstructions() {
146
+ const access = this.readOnly ? "Read-only" : "Persistent";
147
+ return `Archil elastic filesystem "${this._disk?.name ?? "Archil disk"}". ${access} storage — files persist across sessions. Supports serverless execution via exec().`;
148
+ }
149
+ /**
150
+ * Execute a shell command on the disk's filesystem.
151
+ * The disk is mounted as the working directory.
152
+ */
153
+ async exec(command) {
154
+ await this.ensureReady();
155
+ this.assertWritable();
156
+ return this.disk.exec(command);
157
+ }
158
+ /**
159
+ * Parallel grep across files on the disk.
160
+ */
161
+ async grep(opts) {
162
+ await this.ensureReady();
163
+ return this.disk.grep(opts);
164
+ }
165
+ /**
166
+ * Create a signed, time-limited download URL for a file.
167
+ */
168
+ async share(key, opts) {
169
+ await this.ensureReady();
170
+ return this.disk.share(key, opts);
171
+ }
172
+ /**
173
+ * List objects using the S3-compatible API directly.
174
+ */
175
+ async listObjects(prefix, opts) {
176
+ await this.ensureReady();
177
+ return this.disk.listObjects(prefix, opts);
178
+ }
179
+ /**
180
+ * Get object metadata without downloading.
181
+ */
182
+ async headObject(key) {
183
+ await this.ensureReady();
184
+ return this.disk.headObject(key);
185
+ }
186
+ async readFile(path, options) {
187
+ await this.ensureReady();
188
+ const key = toKey(path);
189
+ if (!key) throw new Error("Cannot read file at root path");
190
+ try {
191
+ const data = await this.disk.getObject(key);
192
+ if (options?.encoding) return Buffer.from(data).toString(options.encoding);
193
+ return Buffer.from(data);
194
+ } catch (err) {
195
+ if (isNotFound(err)) throw new FileNotFoundError(path);
196
+ throw err;
197
+ }
198
+ }
199
+ async writeFile(path, content, options) {
200
+ await this.ensureReady();
201
+ this.assertWritable();
202
+ const key = toKey(path);
203
+ if (!key) throw new Error("Cannot write file at root path");
204
+ if (options?.overwrite === false) {
205
+ if (await this.disk.objectExists(key)) throw new FileExistsError(path);
206
+ }
207
+ if (options?.recursive) {
208
+ const dir = dirname(path);
209
+ if (dir) await this.disk.exec(`mkdir -p ${shellEscape(dir)}`);
210
+ }
211
+ const body = typeof content === "string" ? content : content instanceof Uint8Array ? content : new Uint8Array(content);
212
+ const mimeType = options?.mimeType ?? getMimeType(path);
213
+ await this.disk.putObject(key, body, mimeType);
214
+ }
215
+ async appendFile(path, content) {
216
+ await this.ensureReady();
217
+ this.assertWritable();
218
+ const key = toKey(path);
219
+ if (!key) throw new Error("Cannot append to root path");
220
+ const data = typeof content === "string" ? content : Buffer.from(content).toString("base64");
221
+ if (typeof content === "string") await this.disk.exec(`printf '%s' ${shellEscape(data)} >> ${shellEscape(key)}`);
222
+ else await this.disk.exec(`printf '%s' ${shellEscape(data)} | base64 -d >> ${shellEscape(key)}`);
223
+ }
224
+ async deleteFile(path, options) {
225
+ await this.ensureReady();
226
+ this.assertWritable();
227
+ const key = toKey(path);
228
+ if (!key) throw new Error("Cannot delete root path");
229
+ if (!options?.force) {
230
+ if (!await this.disk.objectExists(key)) throw new FileNotFoundError(path);
231
+ }
232
+ await this.disk.deleteObject(key);
233
+ }
234
+ async copyFile(src, dest, options) {
235
+ await this.ensureReady();
236
+ this.assertWritable();
237
+ const srcKey = toKey(src);
238
+ const destKey = toKey(dest);
239
+ if (!options?.overwrite) {
240
+ if (await this.disk.objectExists(destKey)) throw new FileExistsError(dest);
241
+ }
242
+ const flags = options?.recursive ? "-r" : "";
243
+ const result = await this.disk.exec(`cp ${flags} ${shellEscape(srcKey)} ${shellEscape(destKey)}`);
244
+ if (result.exitCode !== 0) {
245
+ if (result.stderr.includes("No such file")) throw new FileNotFoundError(src);
246
+ throw new Error(`cp failed: ${result.stderr}`);
247
+ }
248
+ }
249
+ async moveFile(src, dest, options) {
250
+ await this.ensureReady();
251
+ this.assertWritable();
252
+ const srcKey = toKey(src);
253
+ const destKey = toKey(dest);
254
+ if (!options?.overwrite) {
255
+ if (await this.disk.objectExists(destKey)) throw new FileExistsError(dest);
256
+ }
257
+ const result = await this.disk.exec(`mv ${shellEscape(srcKey)} ${shellEscape(destKey)}`);
258
+ if (result.exitCode !== 0) {
259
+ if (result.stderr.includes("No such file")) throw new FileNotFoundError(src);
260
+ throw new Error(`mv failed: ${result.stderr}`);
261
+ }
262
+ }
263
+ async mkdir(path, options) {
264
+ await this.ensureReady();
265
+ this.assertWritable();
266
+ const key = toKey(path);
267
+ if (!key) return;
268
+ const flag = options?.recursive ? "-p" : "";
269
+ const result = await this.disk.exec(`mkdir ${flag} ${shellEscape(key)}`);
270
+ if (result.exitCode !== 0) {
271
+ if (result.stderr.includes("File exists")) throw new FileExistsError(path);
272
+ throw new Error(`mkdir failed: ${result.stderr}`);
273
+ }
274
+ }
275
+ async rmdir(path, options) {
276
+ await this.ensureReady();
277
+ this.assertWritable();
278
+ const key = toKey(path);
279
+ if (!key) throw new Error("Cannot remove root directory");
280
+ const cmd = options?.recursive ? `rm -r${options?.force ? "f" : ""} ${shellEscape(key)}` : `rmdir ${shellEscape(key)}`;
281
+ const result = await this.disk.exec(options?.force ? `${cmd} 2>/dev/null; true` : cmd);
282
+ if (result.exitCode !== 0 && !options?.force) {
283
+ if (result.stderr.includes("No such file") || result.stderr.includes("not found")) throw new FileNotFoundError(path);
284
+ throw new Error(`rmdir failed: ${result.stderr}`);
285
+ }
286
+ }
287
+ async readdir(path, options) {
288
+ await this.ensureReady();
289
+ const key = toKey(path);
290
+ const prefix = key ? key + "/" : "";
291
+ if (options?.recursive) return this.readdirRecursive(prefix, options);
292
+ const result = await this.disk.listObjects(prefix, { recursive: false });
293
+ const entries = [];
294
+ for (const obj of result.objects) {
295
+ const name = obj.key.slice(prefix.length);
296
+ if (!name || name.includes("/")) continue;
297
+ if (options?.extension) {
298
+ const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
299
+ const ext = name.match(/\.[^.]+$/)?.[0] ?? "";
300
+ if (!extensions.includes(ext)) continue;
301
+ }
302
+ entries.push({
303
+ name,
304
+ type: "file",
305
+ size: obj.size
306
+ });
307
+ }
308
+ for (const cp of result.commonPrefixes) {
309
+ const name = trimSlashes(cp.slice(prefix.length));
310
+ if (!name) continue;
311
+ entries.push({
312
+ name,
313
+ type: "directory"
314
+ });
315
+ }
316
+ return entries;
317
+ }
318
+ async readdirRecursive(prefix, options) {
319
+ const result = await this.disk.listObjects(prefix, { recursive: true });
320
+ const entries = [];
321
+ for (const obj of result.objects) {
322
+ const relativePath = obj.key.slice(prefix.length);
323
+ if (!relativePath) continue;
324
+ if (options?.maxDepth !== void 0) {
325
+ if (relativePath.split("/").length - 1 > options.maxDepth) continue;
326
+ }
327
+ if (options?.extension) {
328
+ const extensions = Array.isArray(options.extension) ? options.extension : [options.extension];
329
+ const ext = relativePath.match(/\.[^.]+$/)?.[0] ?? "";
330
+ if (!extensions.includes(ext)) continue;
331
+ }
332
+ entries.push({
333
+ name: relativePath,
334
+ type: "file",
335
+ size: obj.size
336
+ });
337
+ }
338
+ return entries;
339
+ }
340
+ async exists(path) {
341
+ await this.ensureReady();
342
+ const key = toKey(path);
343
+ if (!key) return true;
344
+ if (await this.disk.objectExists(key)) return true;
345
+ const result = await this.disk.listObjects(key + "/", {
346
+ singlePage: true,
347
+ limit: 1
348
+ });
349
+ return result.objects.length > 0 || result.commonPrefixes.length > 0;
350
+ }
351
+ async stat(path) {
352
+ await this.ensureReady();
353
+ const key = toKey(path);
354
+ if (!key) return {
355
+ name: "",
356
+ path: "/",
357
+ type: "directory",
358
+ size: 0,
359
+ createdAt: /* @__PURE__ */ new Date(0),
360
+ modifiedAt: /* @__PURE__ */ new Date(0)
361
+ };
362
+ const meta = await this.disk.headObject(key);
363
+ if (meta) return {
364
+ name: basename(path),
365
+ path: "/" + key,
366
+ type: "file",
367
+ size: meta.size,
368
+ createdAt: meta.lastModified ?? /* @__PURE__ */ new Date(0),
369
+ modifiedAt: meta.lastModified ?? /* @__PURE__ */ new Date(0),
370
+ mimeType: meta.contentType
371
+ };
372
+ const result = await this.disk.listObjects(key + "/", {
373
+ singlePage: true,
374
+ limit: 1
375
+ });
376
+ if (result.objects.length > 0 || result.commonPrefixes.length > 0) return {
377
+ name: basename(path),
378
+ path: "/" + key,
379
+ type: "directory",
380
+ size: 0,
381
+ createdAt: /* @__PURE__ */ new Date(0),
382
+ modifiedAt: /* @__PURE__ */ new Date(0)
383
+ };
384
+ throw new FileNotFoundError(path);
385
+ }
386
+ assertWritable() {
387
+ if (this.readOnly) throw new Error("Filesystem is read-only");
388
+ }
458
389
  };
459
390
  function isNotFound(err) {
460
- if (!err || typeof err !== "object") return false;
461
- const e = err;
462
- return e.status === 404 || e.code === "NoSuchKey";
391
+ if (!err || typeof err !== "object") return false;
392
+ const e = err;
393
+ return e.status === 404 || e.code === "NoSuchKey";
463
394
  }
464
-
465
- // src/provider.ts
466
- var archilFilesystemProvider = {
467
- id: "archil",
468
- name: "Archil",
469
- description: "Elastic, serverless filesystem for AI agents (Archil)",
470
- configSchema: {
471
- type: "object",
472
- oneOf: [{ required: ["diskId"] }, { required: ["createDiskOptions"] }],
473
- properties: {
474
- diskId: { type: "string", description: 'Existing Archil disk ID (e.g. "dsk-0123456789abcdef")' },
475
- createDiskOptions: { type: "object", description: "Options used to create a new Archil disk on init" },
476
- apiKey: { type: "string", description: "Archil API key (falls back to ARCHIL_API_KEY env var)" },
477
- region: { type: "string", description: 'Archil region (e.g. "aws-us-east-1")' },
478
- readOnly: { type: "boolean", description: "Mount as read-only", default: false },
479
- baseUrl: { type: "string", description: "Custom control-plane URL (for testing)" },
480
- s3BaseUrl: { type: "string", description: "Custom S3 API URL" }
481
- }
482
- },
483
- createFilesystem: (config) => new ArchilFilesystem(config)
395
+ //#endregion
396
+ //#region src/provider.ts
397
+ const archilFilesystemProvider = {
398
+ id: "archil",
399
+ name: "Archil",
400
+ description: "Elastic, serverless filesystem for AI agents (Archil)",
401
+ configSchema: {
402
+ type: "object",
403
+ oneOf: [{ required: ["diskId"] }, { required: ["createDiskOptions"] }],
404
+ properties: {
405
+ diskId: {
406
+ type: "string",
407
+ description: "Existing Archil disk ID (e.g. \"dsk-0123456789abcdef\")"
408
+ },
409
+ createDiskOptions: {
410
+ type: "object",
411
+ description: "Options used to create a new Archil disk on init"
412
+ },
413
+ apiKey: {
414
+ type: "string",
415
+ description: "Archil API key (falls back to ARCHIL_API_KEY env var)"
416
+ },
417
+ region: {
418
+ type: "string",
419
+ description: "Archil region (e.g. \"aws-us-east-1\")"
420
+ },
421
+ readOnly: {
422
+ type: "boolean",
423
+ description: "Mount as read-only",
424
+ default: false
425
+ },
426
+ baseUrl: {
427
+ type: "string",
428
+ description: "Custom control-plane URL (for testing)"
429
+ },
430
+ s3BaseUrl: {
431
+ type: "string",
432
+ description: "Custom S3 API URL"
433
+ }
434
+ }
435
+ },
436
+ createFilesystem: (config) => new ArchilFilesystem(config)
484
437
  };
485
-
438
+ //#endregion
486
439
  export { ArchilFilesystem, archilFilesystemProvider };
487
- //# sourceMappingURL=index.js.map
440
+
488
441
  //# sourceMappingURL=index.js.map