@mastra/mesa 0.2.0-alpha.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,520 +1,526 @@
1
- import path from 'path/posix';
2
- import { MastraFilesystem, FileExistsError, NotDirectoryError, FileNotFoundError, DirectoryNotFoundError, IsDirectoryError, DirectoryNotEmptyError, WorkspaceReadOnlyError, StaleFileError } from '@mastra/core/workspace';
3
- import { Mesa } from '@mesadev/sdk';
4
-
5
- // src/filesystem/index.ts
1
+ import path from "path/posix";
2
+ import { DirectoryNotEmptyError, DirectoryNotFoundError, FileExistsError, FileNotFoundError, IsDirectoryError, MastraFilesystem, NotDirectoryError, StaleFileError, WorkspaceReadOnlyError } from "@mastra/core/workspace";
3
+ import { Mesa } from "@mesadev/sdk";
4
+ //#region src/filesystem/index.ts
6
5
  function generateId() {
7
- return `mesa-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
6
+ return `mesa-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
8
7
  }
9
8
  function normalizePath(inputPath) {
10
- return path.normalize(inputPath.startsWith("/") ? inputPath : `/${inputPath}`);
9
+ return path.normalize(inputPath.startsWith("/") ? inputPath : `/${inputPath}`);
11
10
  }
12
11
  function getExtension(name) {
13
- const dot = name.lastIndexOf(".");
14
- return dot === -1 ? "" : name.slice(dot);
12
+ const dot = name.lastIndexOf(".");
13
+ return dot === -1 ? "" : name.slice(dot);
15
14
  }
16
15
  function matchesExtension(name, extensions) {
17
- if (!extensions) return true;
18
- const ext = getExtension(name);
19
- return extensions.some((candidate) => candidate === ext || candidate === ext.slice(1));
16
+ if (!extensions) return true;
17
+ const ext = getExtension(name);
18
+ return extensions.some((candidate) => candidate === ext || candidate === ext.slice(1));
20
19
  }
21
20
  function toMesaContent(content) {
22
- if (typeof content === "string") return content;
23
- if (Buffer.isBuffer(content)) return new Uint8Array(content);
24
- return content;
21
+ if (typeof content === "string") return content;
22
+ if (Buffer.isBuffer(content)) return new Uint8Array(content);
23
+ return content;
25
24
  }
26
25
  function toError(error) {
27
- return error instanceof Error ? error : new Error(String(error));
26
+ return error instanceof Error ? error : new Error(String(error));
28
27
  }
29
28
  function getMesaErrorKind(error) {
30
- const err = error && typeof error === "object" ? error : void 0;
31
- const code = typeof err?.code === "string" ? err.code : void 0;
32
- const name = typeof err?.name === "string" ? err.name : void 0;
33
- for (const value of [code, name]) {
34
- switch (value) {
35
- case "ENOENT":
36
- case "NotFound":
37
- case "NoSuchFile":
38
- case "NoSuchKey":
39
- return "notFound";
40
- case "EEXIST":
41
- case "AlreadyExists":
42
- return "alreadyExists";
43
- case "ENOTDIR":
44
- case "NotDirectory":
45
- return "notDirectory";
46
- case "EISDIR":
47
- case "IsDirectory":
48
- return "isDirectory";
49
- case "ENOTEMPTY":
50
- case "DirectoryNotEmpty":
51
- return "directoryNotEmpty";
52
- }
53
- }
54
- const message = typeof err?.message === "string" ? err.message : error instanceof Error ? error.message : String(error);
55
- if (/\b(no such file|not found|enoent)\b/i.test(message)) return "notFound";
56
- if (/\b(already exists|eexist)\b/i.test(message)) return "alreadyExists";
57
- if (/\b(not a directory|enotdir)\b/i.test(message)) return "notDirectory";
58
- if (/\b(is a directory|eisdir)\b/i.test(message)) return "isDirectory";
59
- if (/\b(directory not empty|enotempty)\b/i.test(message)) return "directoryNotEmpty";
60
- return void 0;
29
+ const err = error && typeof error === "object" ? error : void 0;
30
+ const code = typeof err?.code === "string" ? err.code : void 0;
31
+ const name = typeof err?.name === "string" ? err.name : void 0;
32
+ for (const value of [code, name]) switch (value) {
33
+ case "ENOENT":
34
+ case "NotFound":
35
+ case "NoSuchFile":
36
+ case "NoSuchKey": return "notFound";
37
+ case "EEXIST":
38
+ case "AlreadyExists": return "alreadyExists";
39
+ case "ENOTDIR":
40
+ case "NotDirectory": return "notDirectory";
41
+ case "EISDIR":
42
+ case "IsDirectory": return "isDirectory";
43
+ case "ENOTEMPTY":
44
+ case "DirectoryNotEmpty": return "directoryNotEmpty";
45
+ }
46
+ const message = typeof err?.message === "string" ? err.message : error instanceof Error ? error.message : String(error);
47
+ if (/\b(no such file|not found|enoent)\b/i.test(message)) return "notFound";
48
+ if (/\b(already exists|eexist)\b/i.test(message)) return "alreadyExists";
49
+ if (/\b(not a directory|enotdir)\b/i.test(message)) return "notDirectory";
50
+ if (/\b(is a directory|eisdir)\b/i.test(message)) return "isDirectory";
51
+ if (/\b(directory not empty|enotempty)\b/i.test(message)) return "directoryNotEmpty";
61
52
  }
62
53
  function mapMesaError(error, inputPath, context = "file") {
63
- switch (getMesaErrorKind(error)) {
64
- case "notFound":
65
- return context === "directory" ? new DirectoryNotFoundError(inputPath) : new FileNotFoundError(inputPath);
66
- case "alreadyExists":
67
- return new FileExistsError(inputPath);
68
- case "notDirectory":
69
- return new NotDirectoryError(inputPath);
70
- case "isDirectory":
71
- return new IsDirectoryError(inputPath);
72
- case "directoryNotEmpty":
73
- return new DirectoryNotEmptyError(inputPath);
74
- default:
75
- return toError(error);
76
- }
54
+ switch (getMesaErrorKind(error)) {
55
+ case "notFound": return context === "directory" ? new DirectoryNotFoundError(inputPath) : new FileNotFoundError(inputPath);
56
+ case "alreadyExists": return new FileExistsError(inputPath);
57
+ case "notDirectory": return new NotDirectoryError(inputPath);
58
+ case "isDirectory": return new IsDirectoryError(inputPath);
59
+ case "directoryNotEmpty": return new DirectoryNotEmptyError(inputPath);
60
+ default: return toError(error);
61
+ }
77
62
  }
63
+ /**
64
+ * Workspace filesystem adapter backed by Mesa.
65
+ *
66
+ * This provider runs in the Mastra process and implements the workspace file
67
+ * API against Mesa repos. It does not mount Mesa into a sandbox.
68
+ */
78
69
  var MesaFilesystem = class extends MastraFilesystem {
79
- id;
80
- name = "MesaFilesystem";
81
- provider = "mesa";
82
- readOnly;
83
- icon = "mesa";
84
- displayName = "Mesa";
85
- description = "Versioned Mesa filesystem for workspace files";
86
- status = "pending";
87
- _apiKey;
88
- _org;
89
- _repos;
90
- _cache;
91
- _ttl;
92
- _telemetry;
93
- _fetch;
94
- _userAgent;
95
- _mesa;
96
- _filesystem;
97
- constructor(options) {
98
- super({ name: "MesaFilesystem", ...options });
99
- this.id = generateId();
100
- this.readOnly = options.readOnly;
101
- this._org = options.org;
102
- this._repos = options.repos;
103
- this._apiKey = options.apiKey;
104
- this._cache = options.cache;
105
- this._ttl = options.ttl;
106
- this._telemetry = options.telemetry;
107
- this._fetch = options.fetch;
108
- this._userAgent = options.userAgent;
109
- }
110
- /**
111
- * The active Mesa client, available after initialization when this instance
112
- * created the client itself.
113
- */
114
- get client() {
115
- return this._mesa;
116
- }
117
- /**
118
- * The active Mesa filesystem. Accessing this before initialization throws.
119
- */
120
- get filesystem() {
121
- if (!this._filesystem) {
122
- throw new Error("MesaFilesystem is not initialized. Call init() first or perform a filesystem operation.");
123
- }
124
- return this._filesystem;
125
- }
126
- async init() {
127
- if (!this._repos || this._repos.length === 0) {
128
- throw new Error("MesaFilesystem requires at least one repo.");
129
- }
130
- this._mesa = new Mesa({
131
- apiKey: this._apiKey,
132
- org: this._org,
133
- fetch: this._fetch,
134
- userAgent: this._userAgent
135
- });
136
- const repos = this.readOnly ? this._repos.map((repo) => ({ ...repo, readOnly: true })) : this._repos;
137
- this._filesystem = await this._mesa.fs.mount({
138
- repos,
139
- cache: this._cache,
140
- ttl: this._ttl,
141
- telemetry: this._telemetry
142
- });
143
- }
144
- getInfo() {
145
- return {
146
- id: this.id,
147
- name: this.name,
148
- provider: this.provider,
149
- status: this.status,
150
- error: this.error,
151
- readOnly: this.readOnly,
152
- icon: this.icon,
153
- metadata: {
154
- ...this._org && { org: this._org },
155
- ...this._repos && { repos: this._repos.map((repo) => repo.name) },
156
- mode: "client"
157
- }
158
- };
159
- }
160
- getInstructions() {
161
- const parts = ["Mesa filesystem. Paths are rooted at the Mesa mount. Include the org and repo name in paths."];
162
- if (this._org) {
163
- parts.push(`Org: "${this._org}".`);
164
- } else {
165
- parts.push("Use the Mesa org resolved by the SDK as the first path segment.");
166
- }
167
- if (this._repos && this._repos.length > 0) {
168
- const repoNames = this._repos.map((repo) => `"${repo.name}"`).join(", ");
169
- const firstRepo = this._repos[0]?.name ?? "repo";
170
- const orgSegment = this._org ?? "org";
171
- parts.push(`Mounted repos: ${repoNames}. For example "/${orgSegment}/${firstRepo}/file.txt".`);
172
- }
173
- parts.push("Files are versioned by Mesa.");
174
- if (this.readOnly) {
175
- parts.push("Mounted read-only.");
176
- }
177
- return parts.join(" ");
178
- }
179
- async readFile(inputPath, options) {
180
- await this.ensureReady();
181
- const target = normalizePath(inputPath);
182
- try {
183
- const buffer = Buffer.from(await this.filesystem.readFileBuffer(target));
184
- if (options?.encoding) return buffer.toString(options.encoding);
185
- return buffer;
186
- } catch (error) {
187
- throw mapMesaError(error, inputPath);
188
- }
189
- }
190
- async writeFile(inputPath, content, options) {
191
- await this.ensureReady();
192
- this.assertWritable("writeFile");
193
- const target = normalizePath(inputPath);
194
- if (options?.overwrite === false && await this.exists(target)) {
195
- throw new FileExistsError(inputPath);
196
- }
197
- if (options?.expectedMtime) {
198
- await this.assertExpectedMtime(inputPath, options.expectedMtime);
199
- }
200
- if (options?.recursive === false) {
201
- await this.assertParentDirectoryExists(target);
202
- } else {
203
- await this.ensureParentDirectory(target);
204
- }
205
- try {
206
- await this.filesystem.writeFile(target, toMesaContent(content));
207
- } catch (error) {
208
- const mapped = mapMesaError(error, inputPath);
209
- if (mapped instanceof NotDirectoryError) throw new NotDirectoryError(path.dirname(target));
210
- if (mapped instanceof FileNotFoundError) throw new DirectoryNotFoundError(path.dirname(target));
211
- throw mapped;
212
- }
213
- }
214
- async appendFile(inputPath, content) {
215
- await this.ensureReady();
216
- this.assertWritable("appendFile");
217
- const target = normalizePath(inputPath);
218
- await this.ensureParentDirectory(target);
219
- try {
220
- await this.filesystem.appendFile(target, toMesaContent(content));
221
- } catch (error) {
222
- const mapped = mapMesaError(error, inputPath);
223
- if (mapped instanceof FileNotFoundError) throw new DirectoryNotFoundError(path.dirname(target));
224
- throw mapped;
225
- }
226
- }
227
- async deleteFile(inputPath, options) {
228
- await this.ensureReady();
229
- this.assertWritable("deleteFile");
230
- const target = normalizePath(inputPath);
231
- try {
232
- const stats = await this.filesystem.stat(target);
233
- if (stats.isDirectory) throw new IsDirectoryError(inputPath);
234
- await this.filesystem.rm(target, { force: options?.force });
235
- } catch (error) {
236
- if (error instanceof IsDirectoryError) throw error;
237
- const mapped = mapMesaError(error, inputPath);
238
- if (mapped instanceof FileNotFoundError) {
239
- if (options?.force) return;
240
- throw mapped;
241
- }
242
- throw mapped;
243
- }
244
- }
245
- async copyFile(src, dest, options) {
246
- await this.ensureReady();
247
- this.assertWritable("copyFile");
248
- const source = normalizePath(src);
249
- const target = normalizePath(dest);
250
- if (options?.overwrite === false && await this.exists(target)) {
251
- throw new FileExistsError(dest);
252
- }
253
- await this.ensureParentDirectory(target);
254
- try {
255
- await this.filesystem.cp(source, target, { recursive: options?.recursive });
256
- } catch (error) {
257
- const mapped = mapMesaError(error, src);
258
- if (mapped instanceof FileExistsError) throw new FileExistsError(dest);
259
- throw mapped;
260
- }
261
- }
262
- async moveFile(src, dest, options) {
263
- await this.ensureReady();
264
- this.assertWritable("moveFile");
265
- const source = normalizePath(src);
266
- const target = normalizePath(dest);
267
- if (options?.overwrite === false && await this.exists(target)) {
268
- throw new FileExistsError(dest);
269
- }
270
- await this.ensureParentDirectory(target);
271
- try {
272
- await this.filesystem.mv(source, target);
273
- } catch (error) {
274
- const mapped = mapMesaError(error, src);
275
- if (mapped instanceof FileExistsError) throw new FileExistsError(dest);
276
- throw mapped;
277
- }
278
- }
279
- async mkdir(inputPath, options) {
280
- await this.ensureReady();
281
- this.assertWritable("mkdir");
282
- const target = normalizePath(inputPath);
283
- try {
284
- await this.filesystem.mkdir(target, { recursive: options?.recursive ?? true });
285
- } catch (error) {
286
- const mapped = mapMesaError(error, inputPath);
287
- if (mapped instanceof FileNotFoundError) throw new DirectoryNotFoundError(path.dirname(target));
288
- throw mapped;
289
- }
290
- }
291
- async rmdir(inputPath, options) {
292
- await this.ensureReady();
293
- this.assertWritable("rmdir");
294
- const target = normalizePath(inputPath);
295
- try {
296
- const stats = await this.filesystem.stat(target);
297
- if (!stats.isDirectory) throw new NotDirectoryError(inputPath);
298
- if (!options?.recursive) {
299
- const entries = await this.filesystem.readdirWithFileTypes(target);
300
- if (entries.length > 0) throw new DirectoryNotEmptyError(inputPath);
301
- }
302
- await this.filesystem.rm(target, { recursive: options?.recursive ?? true, force: options?.force });
303
- } catch (error) {
304
- if (error instanceof NotDirectoryError) throw error;
305
- if (error instanceof DirectoryNotEmptyError) throw error;
306
- const mapped = mapMesaError(error, inputPath, "directory");
307
- if (mapped instanceof DirectoryNotFoundError) {
308
- if (options?.force) return;
309
- throw mapped;
310
- }
311
- throw mapped;
312
- }
313
- }
314
- async readdir(inputPath, options) {
315
- await this.ensureReady();
316
- const target = normalizePath(inputPath);
317
- try {
318
- return await this.readDirectory(target, options);
319
- } catch (error) {
320
- throw mapMesaError(error, inputPath, "directory");
321
- }
322
- }
323
- async exists(inputPath) {
324
- await this.ensureReady();
325
- const target = normalizePath(inputPath);
326
- try {
327
- return await this.filesystem.exists(target);
328
- } catch (error) {
329
- if (getMesaErrorKind(error) === "notFound") return false;
330
- throw mapMesaError(error, inputPath);
331
- }
332
- }
333
- async stat(inputPath) {
334
- await this.ensureReady();
335
- const target = normalizePath(inputPath);
336
- try {
337
- const stats = await this.filesystem.stat(target);
338
- return this.toFileStat(target, stats);
339
- } catch (error) {
340
- throw mapMesaError(error, inputPath);
341
- }
342
- }
343
- async realpath(inputPath) {
344
- await this.ensureReady();
345
- try {
346
- return await this.filesystem.realpath(normalizePath(inputPath));
347
- } catch (error) {
348
- throw mapMesaError(error, inputPath);
349
- }
350
- }
351
- /**
352
- * Create a Mesa-backed Bash runtime for this filesystem.
353
- */
354
- async bash(options) {
355
- await this.ensureReady();
356
- return this.filesystem.bash(options);
357
- }
358
- /**
359
- * Mesa change management operations for the mounted filesystem.
360
- */
361
- get change() {
362
- return this.filesystem.change;
363
- }
364
- /**
365
- * Mesa bookmark management operations for the mounted filesystem.
366
- */
367
- get bookmark() {
368
- return this.filesystem.bookmark;
369
- }
370
- assertWritable(operation) {
371
- if (this.readOnly) {
372
- throw new WorkspaceReadOnlyError(operation);
373
- }
374
- }
375
- async assertExpectedMtime(inputPath, expectedMtime) {
376
- try {
377
- const currentStat = await this.stat(inputPath);
378
- if (currentStat.modifiedAt.getTime() !== expectedMtime.getTime()) {
379
- throw new StaleFileError(inputPath, expectedMtime, currentStat.modifiedAt);
380
- }
381
- } catch (error) {
382
- if (error instanceof StaleFileError) throw error;
383
- if (error instanceof FileNotFoundError) return;
384
- throw error;
385
- }
386
- }
387
- async ensureParentDirectory(inputPath) {
388
- const parent = path.dirname(inputPath);
389
- if (parent === "/" || parent === inputPath) return;
390
- try {
391
- await this.filesystem.mkdir(parent, { recursive: true });
392
- } catch (error) {
393
- const mapped = mapMesaError(error, parent, "directory");
394
- if (!(mapped instanceof FileExistsError)) throw mapped;
395
- try {
396
- const stats = await this.filesystem.stat(parent);
397
- if (!stats.isDirectory) {
398
- throw new NotDirectoryError(parent);
399
- }
400
- } catch (statError) {
401
- if (statError instanceof NotDirectoryError) throw statError;
402
- throw mapMesaError(statError, parent, "directory");
403
- }
404
- }
405
- }
406
- async assertParentDirectoryExists(inputPath) {
407
- const parent = path.dirname(inputPath);
408
- if (parent === "/" || parent === inputPath) return;
409
- try {
410
- const stats = await this.filesystem.stat(parent);
411
- if (!stats.isDirectory) {
412
- throw new NotDirectoryError(parent);
413
- }
414
- } catch (error) {
415
- if (error instanceof NotDirectoryError) throw error;
416
- throw mapMesaError(error, parent, "directory");
417
- }
418
- }
419
- async readDirectory(inputPath, options, depth = 0) {
420
- const entries = await this.filesystem.readdirWithFileTypes(inputPath);
421
- const extensions = options?.extension ? Array.isArray(options.extension) ? options.extension : [options.extension] : void 0;
422
- const result = [];
423
- for (const entry of entries) {
424
- const childPath = path.join(inputPath, entry.name);
425
- if (entry.isFile) {
426
- if (matchesExtension(entry.name, extensions)) {
427
- const stat = await this.safeStat(childPath);
428
- result.push({ name: entry.name, type: "file", size: stat?.size });
429
- }
430
- continue;
431
- }
432
- if (entry.isDirectory) {
433
- result.push({ name: entry.name, type: "directory" });
434
- if (options?.recursive && (options.maxDepth === void 0 || depth < options.maxDepth)) {
435
- const childEntries = await this.readDirectory(childPath, options, depth + 1);
436
- result.push(...childEntries.map((child) => ({ ...child, name: `${entry.name}/${child.name}` })));
437
- }
438
- continue;
439
- }
440
- result.push(this.toFileEntry(entry));
441
- }
442
- return result;
443
- }
444
- async safeStat(inputPath) {
445
- try {
446
- return await this.filesystem.stat(inputPath);
447
- } catch {
448
- return void 0;
449
- }
450
- }
451
- toFileEntry(entry) {
452
- return {
453
- name: entry.name,
454
- type: entry.isDirectory ? "directory" : "file",
455
- isSymlink: entry.isSymbolicLink || void 0
456
- };
457
- }
458
- toFileStat(inputPath, stats) {
459
- const target = normalizePath(inputPath);
460
- return {
461
- name: target === "/" ? "/" : path.basename(target),
462
- path: target,
463
- type: stats.isDirectory ? "directory" : "file",
464
- size: stats.isDirectory ? 0 : stats.size,
465
- createdAt: stats.mtime,
466
- modifiedAt: stats.mtime
467
- };
468
- }
70
+ id;
71
+ name = "MesaFilesystem";
72
+ provider = "mesa";
73
+ readOnly;
74
+ icon = "mesa";
75
+ displayName = "Mesa";
76
+ description = "Versioned Mesa filesystem for workspace files";
77
+ status = "pending";
78
+ _apiKey;
79
+ _org;
80
+ _repos;
81
+ _cache;
82
+ _ttl;
83
+ _telemetry;
84
+ _fetch;
85
+ _userAgent;
86
+ _mesa;
87
+ _filesystem;
88
+ constructor(options) {
89
+ super({
90
+ name: "MesaFilesystem",
91
+ ...options
92
+ });
93
+ this.id = generateId();
94
+ this.readOnly = options.readOnly;
95
+ this._org = options.org;
96
+ this._repos = options.repos;
97
+ this._apiKey = options.apiKey;
98
+ this._cache = options.cache;
99
+ this._ttl = options.ttl;
100
+ this._telemetry = options.telemetry;
101
+ this._fetch = options.fetch;
102
+ this._userAgent = options.userAgent;
103
+ }
104
+ /**
105
+ * The active Mesa client, available after initialization when this instance
106
+ * created the client itself.
107
+ */
108
+ get client() {
109
+ return this._mesa;
110
+ }
111
+ /**
112
+ * The active Mesa filesystem. Accessing this before initialization throws.
113
+ */
114
+ get filesystem() {
115
+ if (!this._filesystem) throw new Error("MesaFilesystem is not initialized. Call init() first or perform a filesystem operation.");
116
+ return this._filesystem;
117
+ }
118
+ async init() {
119
+ if (!this._repos || this._repos.length === 0) throw new Error("MesaFilesystem requires at least one repo.");
120
+ this._mesa = new Mesa({
121
+ apiKey: this._apiKey,
122
+ org: this._org,
123
+ fetch: this._fetch,
124
+ userAgent: this._userAgent
125
+ });
126
+ const repos = this.readOnly ? this._repos.map((repo) => ({
127
+ ...repo,
128
+ readOnly: true
129
+ })) : this._repos;
130
+ this._filesystem = await this._mesa.fs.mount({
131
+ repos,
132
+ cache: this._cache,
133
+ ttl: this._ttl,
134
+ telemetry: this._telemetry
135
+ });
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
+ ...this._org && { org: this._org },
148
+ ...this._repos && { repos: this._repos.map((repo) => repo.name) },
149
+ mode: "client"
150
+ }
151
+ };
152
+ }
153
+ getInstructions() {
154
+ const parts = ["Mesa filesystem. Paths are rooted at the Mesa mount. Include the org and repo name in paths."];
155
+ if (this._org) parts.push(`Org: "${this._org}".`);
156
+ else parts.push("Use the Mesa org resolved by the SDK as the first path segment.");
157
+ if (this._repos && this._repos.length > 0) {
158
+ const repoNames = this._repos.map((repo) => `"${repo.name}"`).join(", ");
159
+ const firstRepo = this._repos[0]?.name ?? "repo";
160
+ const orgSegment = this._org ?? "org";
161
+ parts.push(`Mounted repos: ${repoNames}. For example "/${orgSegment}/${firstRepo}/file.txt".`);
162
+ }
163
+ parts.push("Files are versioned by Mesa.");
164
+ if (this.readOnly) parts.push("Mounted read-only.");
165
+ return parts.join(" ");
166
+ }
167
+ async readFile(inputPath, options) {
168
+ await this.ensureReady();
169
+ const target = normalizePath(inputPath);
170
+ try {
171
+ const buffer = Buffer.from(await this.filesystem.readFileBuffer(target));
172
+ if (options?.encoding) return buffer.toString(options.encoding);
173
+ return buffer;
174
+ } catch (error) {
175
+ throw mapMesaError(error, inputPath);
176
+ }
177
+ }
178
+ async writeFile(inputPath, content, options) {
179
+ await this.ensureReady();
180
+ this.assertWritable("writeFile");
181
+ const target = normalizePath(inputPath);
182
+ if (options?.overwrite === false && await this.exists(target)) throw new FileExistsError(inputPath);
183
+ if (options?.expectedMtime) await this.assertExpectedMtime(inputPath, options.expectedMtime);
184
+ if (options?.recursive === false) await this.assertParentDirectoryExists(target);
185
+ else await this.ensureParentDirectory(target);
186
+ try {
187
+ await this.filesystem.writeFile(target, toMesaContent(content));
188
+ } catch (error) {
189
+ const mapped = mapMesaError(error, inputPath);
190
+ if (mapped instanceof NotDirectoryError) throw new NotDirectoryError(path.dirname(target));
191
+ if (mapped instanceof FileNotFoundError) throw new DirectoryNotFoundError(path.dirname(target));
192
+ throw mapped;
193
+ }
194
+ }
195
+ async appendFile(inputPath, content) {
196
+ await this.ensureReady();
197
+ this.assertWritable("appendFile");
198
+ const target = normalizePath(inputPath);
199
+ await this.ensureParentDirectory(target);
200
+ try {
201
+ await this.filesystem.appendFile(target, toMesaContent(content));
202
+ } catch (error) {
203
+ const mapped = mapMesaError(error, inputPath);
204
+ if (mapped instanceof FileNotFoundError) throw new DirectoryNotFoundError(path.dirname(target));
205
+ throw mapped;
206
+ }
207
+ }
208
+ async deleteFile(inputPath, options) {
209
+ await this.ensureReady();
210
+ this.assertWritable("deleteFile");
211
+ const target = normalizePath(inputPath);
212
+ try {
213
+ if ((await this.filesystem.stat(target)).isDirectory) throw new IsDirectoryError(inputPath);
214
+ await this.filesystem.rm(target, { force: options?.force });
215
+ } catch (error) {
216
+ if (error instanceof IsDirectoryError) throw error;
217
+ const mapped = mapMesaError(error, inputPath);
218
+ if (mapped instanceof FileNotFoundError) {
219
+ if (options?.force) return;
220
+ throw mapped;
221
+ }
222
+ throw mapped;
223
+ }
224
+ }
225
+ async copyFile(src, dest, options) {
226
+ await this.ensureReady();
227
+ this.assertWritable("copyFile");
228
+ const source = normalizePath(src);
229
+ const target = normalizePath(dest);
230
+ if (options?.overwrite === false && await this.exists(target)) throw new FileExistsError(dest);
231
+ await this.ensureParentDirectory(target);
232
+ try {
233
+ await this.filesystem.cp(source, target, { recursive: options?.recursive });
234
+ } catch (error) {
235
+ const mapped = mapMesaError(error, src);
236
+ if (mapped instanceof FileExistsError) throw new FileExistsError(dest);
237
+ throw mapped;
238
+ }
239
+ }
240
+ async moveFile(src, dest, options) {
241
+ await this.ensureReady();
242
+ this.assertWritable("moveFile");
243
+ const source = normalizePath(src);
244
+ const target = normalizePath(dest);
245
+ if (options?.overwrite === false && await this.exists(target)) throw new FileExistsError(dest);
246
+ await this.ensureParentDirectory(target);
247
+ try {
248
+ await this.filesystem.mv(source, target);
249
+ } catch (error) {
250
+ const mapped = mapMesaError(error, src);
251
+ if (mapped instanceof FileExistsError) throw new FileExistsError(dest);
252
+ throw mapped;
253
+ }
254
+ }
255
+ async mkdir(inputPath, options) {
256
+ await this.ensureReady();
257
+ this.assertWritable("mkdir");
258
+ const target = normalizePath(inputPath);
259
+ try {
260
+ await this.filesystem.mkdir(target, { recursive: options?.recursive ?? true });
261
+ } catch (error) {
262
+ const mapped = mapMesaError(error, inputPath);
263
+ if (mapped instanceof FileNotFoundError) throw new DirectoryNotFoundError(path.dirname(target));
264
+ throw mapped;
265
+ }
266
+ }
267
+ async rmdir(inputPath, options) {
268
+ await this.ensureReady();
269
+ this.assertWritable("rmdir");
270
+ const target = normalizePath(inputPath);
271
+ try {
272
+ if (!(await this.filesystem.stat(target)).isDirectory) throw new NotDirectoryError(inputPath);
273
+ if (!options?.recursive) {
274
+ if ((await this.filesystem.readdirWithFileTypes(target)).length > 0) throw new DirectoryNotEmptyError(inputPath);
275
+ }
276
+ await this.filesystem.rm(target, {
277
+ recursive: options?.recursive ?? true,
278
+ force: options?.force
279
+ });
280
+ } catch (error) {
281
+ if (error instanceof NotDirectoryError) throw error;
282
+ if (error instanceof DirectoryNotEmptyError) throw error;
283
+ const mapped = mapMesaError(error, inputPath, "directory");
284
+ if (mapped instanceof DirectoryNotFoundError) {
285
+ if (options?.force) return;
286
+ throw mapped;
287
+ }
288
+ throw mapped;
289
+ }
290
+ }
291
+ async readdir(inputPath, options) {
292
+ await this.ensureReady();
293
+ const target = normalizePath(inputPath);
294
+ try {
295
+ return await this.readDirectory(target, options);
296
+ } catch (error) {
297
+ throw mapMesaError(error, inputPath, "directory");
298
+ }
299
+ }
300
+ async exists(inputPath) {
301
+ await this.ensureReady();
302
+ const target = normalizePath(inputPath);
303
+ try {
304
+ return await this.filesystem.exists(target);
305
+ } catch (error) {
306
+ if (getMesaErrorKind(error) === "notFound") return false;
307
+ throw mapMesaError(error, inputPath);
308
+ }
309
+ }
310
+ async stat(inputPath) {
311
+ await this.ensureReady();
312
+ const target = normalizePath(inputPath);
313
+ try {
314
+ const stats = await this.filesystem.stat(target);
315
+ return this.toFileStat(target, stats);
316
+ } catch (error) {
317
+ throw mapMesaError(error, inputPath);
318
+ }
319
+ }
320
+ async realpath(inputPath) {
321
+ await this.ensureReady();
322
+ try {
323
+ return await this.filesystem.realpath(normalizePath(inputPath));
324
+ } catch (error) {
325
+ throw mapMesaError(error, inputPath);
326
+ }
327
+ }
328
+ /**
329
+ * Create a Mesa-backed Bash runtime for this filesystem.
330
+ */
331
+ async bash(options) {
332
+ await this.ensureReady();
333
+ return this.filesystem.bash(options);
334
+ }
335
+ /**
336
+ * Mesa change management operations for the mounted filesystem.
337
+ */
338
+ get change() {
339
+ return this.filesystem.change;
340
+ }
341
+ /**
342
+ * Mesa bookmark management operations for the mounted filesystem.
343
+ */
344
+ get bookmark() {
345
+ return this.filesystem.bookmark;
346
+ }
347
+ assertWritable(operation) {
348
+ if (this.readOnly) throw new WorkspaceReadOnlyError(operation);
349
+ }
350
+ async assertExpectedMtime(inputPath, expectedMtime) {
351
+ try {
352
+ const currentStat = await this.stat(inputPath);
353
+ if (currentStat.modifiedAt.getTime() !== expectedMtime.getTime()) throw new StaleFileError(inputPath, expectedMtime, currentStat.modifiedAt);
354
+ } catch (error) {
355
+ if (error instanceof StaleFileError) throw error;
356
+ if (error instanceof FileNotFoundError) return;
357
+ throw error;
358
+ }
359
+ }
360
+ async ensureParentDirectory(inputPath) {
361
+ const parent = path.dirname(inputPath);
362
+ if (parent === "/" || parent === inputPath) return;
363
+ try {
364
+ await this.filesystem.mkdir(parent, { recursive: true });
365
+ } catch (error) {
366
+ const mapped = mapMesaError(error, parent, "directory");
367
+ if (!(mapped instanceof FileExistsError)) throw mapped;
368
+ try {
369
+ if (!(await this.filesystem.stat(parent)).isDirectory) throw new NotDirectoryError(parent);
370
+ } catch (statError) {
371
+ if (statError instanceof NotDirectoryError) throw statError;
372
+ throw mapMesaError(statError, parent, "directory");
373
+ }
374
+ }
375
+ }
376
+ async assertParentDirectoryExists(inputPath) {
377
+ const parent = path.dirname(inputPath);
378
+ if (parent === "/" || parent === inputPath) return;
379
+ try {
380
+ if (!(await this.filesystem.stat(parent)).isDirectory) throw new NotDirectoryError(parent);
381
+ } catch (error) {
382
+ if (error instanceof NotDirectoryError) throw error;
383
+ throw mapMesaError(error, parent, "directory");
384
+ }
385
+ }
386
+ async readDirectory(inputPath, options, depth = 0) {
387
+ const entries = await this.filesystem.readdirWithFileTypes(inputPath);
388
+ const extensions = options?.extension ? Array.isArray(options.extension) ? options.extension : [options.extension] : void 0;
389
+ const result = [];
390
+ for (const entry of entries) {
391
+ const childPath = path.join(inputPath, entry.name);
392
+ if (entry.isFile) {
393
+ if (matchesExtension(entry.name, extensions)) {
394
+ const stat = await this.safeStat(childPath);
395
+ result.push({
396
+ name: entry.name,
397
+ type: "file",
398
+ size: stat?.size
399
+ });
400
+ }
401
+ continue;
402
+ }
403
+ if (entry.isDirectory) {
404
+ result.push({
405
+ name: entry.name,
406
+ type: "directory"
407
+ });
408
+ if (options?.recursive && (options.maxDepth === void 0 || depth < options.maxDepth)) {
409
+ const childEntries = await this.readDirectory(childPath, options, depth + 1);
410
+ result.push(...childEntries.map((child) => ({
411
+ ...child,
412
+ name: `${entry.name}/${child.name}`
413
+ })));
414
+ }
415
+ continue;
416
+ }
417
+ result.push(this.toFileEntry(entry));
418
+ }
419
+ return result;
420
+ }
421
+ async safeStat(inputPath) {
422
+ try {
423
+ return await this.filesystem.stat(inputPath);
424
+ } catch {
425
+ return;
426
+ }
427
+ }
428
+ toFileEntry(entry) {
429
+ return {
430
+ name: entry.name,
431
+ type: entry.isDirectory ? "directory" : "file",
432
+ isSymlink: entry.isSymbolicLink || void 0
433
+ };
434
+ }
435
+ toFileStat(inputPath, stats) {
436
+ const target = normalizePath(inputPath);
437
+ return {
438
+ name: target === "/" ? "/" : path.basename(target),
439
+ path: target,
440
+ type: stats.isDirectory ? "directory" : "file",
441
+ size: stats.isDirectory ? 0 : stats.size,
442
+ createdAt: stats.mtime,
443
+ modifiedAt: stats.mtime
444
+ };
445
+ }
469
446
  };
470
-
471
- // src/provider.ts
472
- var mesaFilesystemProvider = {
473
- id: "mesa",
474
- name: "Mesa",
475
- description: "Versioned Mesa filesystem for workspace files",
476
- configSchema: {
477
- type: "object",
478
- required: ["repos"],
479
- properties: {
480
- apiKey: { type: "string", description: "Mesa API key. Falls back to MESA_API_KEY when omitted." },
481
- org: { type: "string", description: "Mesa org slug" },
482
- repos: {
483
- type: "array",
484
- description: "Mesa repos to mount",
485
- minItems: 1,
486
- items: {
487
- type: "object",
488
- required: ["name"],
489
- properties: {
490
- name: { type: "string", description: "Mesa repo name" },
491
- bookmark: { type: "string", description: "Bookmark to mount" },
492
- changeId: { type: "string", description: "Change ID to mount" },
493
- readOnly: { type: "boolean", description: "Mount this repo as read-only" }
494
- }
495
- }
496
- },
497
- cache: {
498
- type: "object",
499
- description: "Mesa filesystem cache configuration",
500
- properties: {
501
- diskCache: {
502
- type: "object",
503
- required: ["path"],
504
- properties: {
505
- path: { type: "string", description: "Disk cache path" },
506
- maxSizeBytes: { type: "number", description: "Maximum disk cache size in bytes" }
507
- }
508
- }
509
- }
510
- },
511
- ttl: { type: "number", description: "Mesa mount token lifetime in seconds" },
512
- readOnly: { type: "boolean", description: "Mount all repos as read-only", default: false }
513
- }
514
- },
515
- createFilesystem: (config) => new MesaFilesystem(config)
447
+ //#endregion
448
+ //#region src/provider.ts
449
+ const mesaFilesystemProvider = {
450
+ id: "mesa",
451
+ name: "Mesa",
452
+ description: "Versioned Mesa filesystem for workspace files",
453
+ configSchema: {
454
+ type: "object",
455
+ required: ["repos"],
456
+ properties: {
457
+ apiKey: {
458
+ type: "string",
459
+ description: "Mesa API key. Falls back to MESA_API_KEY when omitted."
460
+ },
461
+ org: {
462
+ type: "string",
463
+ description: "Mesa org slug"
464
+ },
465
+ repos: {
466
+ type: "array",
467
+ description: "Mesa repos to mount",
468
+ minItems: 1,
469
+ items: {
470
+ type: "object",
471
+ required: ["name"],
472
+ properties: {
473
+ name: {
474
+ type: "string",
475
+ description: "Mesa repo name"
476
+ },
477
+ bookmark: {
478
+ type: "string",
479
+ description: "Bookmark to mount"
480
+ },
481
+ changeId: {
482
+ type: "string",
483
+ description: "Change ID to mount"
484
+ },
485
+ readOnly: {
486
+ type: "boolean",
487
+ description: "Mount this repo as read-only"
488
+ }
489
+ }
490
+ }
491
+ },
492
+ cache: {
493
+ type: "object",
494
+ description: "Mesa filesystem cache configuration",
495
+ properties: { diskCache: {
496
+ type: "object",
497
+ required: ["path"],
498
+ properties: {
499
+ path: {
500
+ type: "string",
501
+ description: "Disk cache path"
502
+ },
503
+ maxSizeBytes: {
504
+ type: "number",
505
+ description: "Maximum disk cache size in bytes"
506
+ }
507
+ }
508
+ } }
509
+ },
510
+ ttl: {
511
+ type: "number",
512
+ description: "Mesa mount token lifetime in seconds"
513
+ },
514
+ readOnly: {
515
+ type: "boolean",
516
+ description: "Mount all repos as read-only",
517
+ default: false
518
+ }
519
+ }
520
+ },
521
+ createFilesystem: (config) => new MesaFilesystem(config)
516
522
  };
517
-
523
+ //#endregion
518
524
  export { MesaFilesystem, mesaFilesystemProvider };
519
- //# sourceMappingURL=index.js.map
525
+
520
526
  //# sourceMappingURL=index.js.map