@dexto/tools-filesystem 1.7.1 → 1.8.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/directory-approval.cjs +5 -4
- package/dist/directory-approval.d.ts +2 -1
- package/dist/directory-approval.d.ts.map +1 -1
- package/dist/directory-approval.integration.test.cjs +73 -33
- package/dist/directory-approval.integration.test.js +73 -33
- package/dist/directory-approval.js +2 -1
- package/dist/edit-file-tool.cjs +52 -37
- package/dist/edit-file-tool.d.ts +1 -1
- package/dist/edit-file-tool.d.ts.map +1 -1
- package/dist/edit-file-tool.js +43 -29
- package/dist/edit-file-tool.test.cjs +159 -2
- package/dist/edit-file-tool.test.js +159 -2
- package/dist/errors.cjs +53 -53
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/file-tool-types.d.ts +1 -1
- package/dist/file-tool-types.d.ts.map +1 -1
- package/dist/filesystem-service.cjs +60 -60
- package/dist/filesystem-service.d.ts +1 -1
- package/dist/filesystem-service.d.ts.map +1 -1
- package/dist/filesystem-service.js +5 -5
- package/dist/filesystem-service.test.cjs +1 -3
- package/dist/filesystem-service.test.js +1 -3
- package/dist/glob-files-tool.cjs +27 -24
- package/dist/glob-files-tool.d.ts +1 -1
- package/dist/glob-files-tool.d.ts.map +1 -1
- package/dist/glob-files-tool.js +24 -21
- package/dist/glob-files-tool.test.cjs +100 -88
- package/dist/glob-files-tool.test.js +101 -67
- package/dist/grep-content-tool.cjs +129 -44
- package/dist/grep-content-tool.d.ts +1 -1
- package/dist/grep-content-tool.d.ts.map +1 -1
- package/dist/grep-content-tool.js +120 -41
- package/dist/grep-content-tool.test.cjs +122 -87
- package/dist/grep-content-tool.test.js +123 -66
- package/dist/index.d.cts +3 -4
- package/dist/path-validator.d.ts +1 -1
- package/dist/path-validator.d.ts.map +1 -1
- package/dist/read-file-tool.cjs +43 -14
- package/dist/read-file-tool.d.ts +1 -1
- package/dist/read-file-tool.d.ts.map +1 -1
- package/dist/read-file-tool.js +40 -11
- package/dist/read-file-tool.test.cjs +119 -0
- package/dist/read-file-tool.test.d.ts +2 -0
- package/dist/read-file-tool.test.d.ts.map +1 -0
- package/dist/read-file-tool.test.js +96 -0
- package/dist/read-media-file-tool.cjs +4 -4
- package/dist/read-media-file-tool.d.ts +1 -1
- package/dist/read-media-file-tool.d.ts.map +1 -1
- package/dist/read-media-file-tool.js +1 -1
- package/dist/tool-factory.cjs +2 -2
- package/dist/tool-factory.js +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace-paths.cjs +87 -0
- package/dist/workspace-paths.d.ts +4 -0
- package/dist/workspace-paths.d.ts.map +1 -0
- package/dist/workspace-paths.js +51 -0
- package/dist/write-file-tool.cjs +74 -34
- package/dist/write-file-tool.d.ts +1 -2
- package/dist/write-file-tool.d.ts.map +1 -1
- package/dist/write-file-tool.js +68 -29
- package/dist/write-file-tool.test.cjs +262 -11
- package/dist/write-file-tool.test.js +262 -11
- package/package.json +3 -3
|
@@ -35,9 +35,11 @@ var fs = __toESM(require("node:fs/promises"), 1);
|
|
|
35
35
|
var path = __toESM(require("node:path"), 1);
|
|
36
36
|
var import_glob = require("glob");
|
|
37
37
|
var import_safe_regex = __toESM(require("safe-regex"), 1);
|
|
38
|
-
var
|
|
38
|
+
var import_errors = require("@dexto/core/errors");
|
|
39
|
+
var import_logger = require("@dexto/core/logger");
|
|
40
|
+
var import_path = require("@dexto/core/utils/path.js");
|
|
39
41
|
var import_path_validator = require("./path-validator.js");
|
|
40
|
-
var
|
|
42
|
+
var import_errors2 = require("./errors.js");
|
|
41
43
|
var import_mime_utils = require("./mime-utils.js");
|
|
42
44
|
var import_path_utils = require("./path-utils.js");
|
|
43
45
|
const DEFAULT_ENCODING = "utf-8";
|
|
@@ -61,7 +63,7 @@ class FileSystemService {
|
|
|
61
63
|
*/
|
|
62
64
|
constructor(config, logger) {
|
|
63
65
|
this.config = config;
|
|
64
|
-
this.logger = logger.createChild(
|
|
66
|
+
this.logger = logger.createChild(import_logger.DextoLogComponent.FILESYSTEM);
|
|
65
67
|
this.pathValidator = new import_path_validator.PathValidator(this.config, this.logger);
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
@@ -69,7 +71,7 @@ class FileSystemService {
|
|
|
69
71
|
* TODO: Migrate to explicit configuration via CLI enrichment layer (per-agent paths)
|
|
70
72
|
*/
|
|
71
73
|
getBackupDir() {
|
|
72
|
-
return this.config.backupPath ? (0, import_path_utils.resolveUserPath)(this.getWorkingDirectory(), this.config.backupPath) : (0,
|
|
74
|
+
return this.config.backupPath ? (0, import_path_utils.resolveUserPath)(this.getWorkingDirectory(), this.config.backupPath) : (0, import_path.getDextoPath)("backups");
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* Get the effective working directory for file operations.
|
|
@@ -160,7 +162,7 @@ class FileSystemService {
|
|
|
160
162
|
async validateReadPath(filePath, mode) {
|
|
161
163
|
const validation = mode === "toolPreview" ? await this.pathValidator.validatePathForPreview(filePath) : await this.pathValidator.validatePath(filePath);
|
|
162
164
|
if (!validation.isValid || !validation.normalizedPath) {
|
|
163
|
-
throw
|
|
165
|
+
throw import_errors2.FileSystemError.invalidPath(filePath, validation.error || "Unknown error");
|
|
164
166
|
}
|
|
165
167
|
return validation.normalizedPath;
|
|
166
168
|
}
|
|
@@ -168,29 +170,29 @@ class FileSystemService {
|
|
|
168
170
|
try {
|
|
169
171
|
const stats = await fs.stat(normalizedPath);
|
|
170
172
|
if (!stats.isFile()) {
|
|
171
|
-
throw
|
|
173
|
+
throw import_errors2.FileSystemError.invalidPath(normalizedPath, "Path is not a file");
|
|
172
174
|
}
|
|
173
175
|
if (stats.size > this.config.maxFileSize) {
|
|
174
|
-
throw
|
|
176
|
+
throw import_errors2.FileSystemError.fileTooLarge(
|
|
175
177
|
normalizedPath,
|
|
176
178
|
stats.size,
|
|
177
179
|
this.config.maxFileSize
|
|
178
180
|
);
|
|
179
181
|
}
|
|
180
182
|
} catch (error) {
|
|
181
|
-
if (error instanceof
|
|
183
|
+
if (error instanceof import_errors.DextoRuntimeError && error.scope === "filesystem") {
|
|
182
184
|
throw error;
|
|
183
185
|
}
|
|
184
186
|
if (error.code === "ENOENT") {
|
|
185
|
-
throw
|
|
187
|
+
throw import_errors2.FileSystemError.fileNotFound(normalizedPath);
|
|
186
188
|
}
|
|
187
189
|
if (error.code === "EACCES") {
|
|
188
|
-
throw
|
|
190
|
+
throw import_errors2.FileSystemError.permissionDenied(normalizedPath, "read");
|
|
189
191
|
}
|
|
190
|
-
if (error instanceof
|
|
192
|
+
if (error instanceof import_errors.DextoRuntimeError) {
|
|
191
193
|
throw error;
|
|
192
194
|
}
|
|
193
|
-
throw
|
|
195
|
+
throw import_errors2.FileSystemError.readFailed(
|
|
194
196
|
normalizedPath,
|
|
195
197
|
error instanceof Error ? error.message : String(error)
|
|
196
198
|
);
|
|
@@ -202,7 +204,7 @@ class FileSystemService {
|
|
|
202
204
|
const binaryLike = (0, import_mime_utils.isLikelyBinary)(rawContent);
|
|
203
205
|
const canReadAsText = !binaryLike && ((0, import_mime_utils.isTextMimeType)(mimeType) || mimeType === "image/svg+xml");
|
|
204
206
|
if (!canReadAsText) {
|
|
205
|
-
throw
|
|
207
|
+
throw import_errors2.FileSystemError.readFailed(
|
|
206
208
|
normalizedPath,
|
|
207
209
|
`File is binary (${mimeType}). Use read_media_file instead.`
|
|
208
210
|
);
|
|
@@ -231,10 +233,10 @@ class FileSystemService {
|
|
|
231
233
|
size: Buffer.byteLength(returnedContent, encoding)
|
|
232
234
|
};
|
|
233
235
|
} catch (error) {
|
|
234
|
-
if (error instanceof
|
|
236
|
+
if (error instanceof import_errors.DextoRuntimeError && error.scope === "filesystem") {
|
|
235
237
|
throw error;
|
|
236
238
|
}
|
|
237
|
-
throw
|
|
239
|
+
throw import_errors2.FileSystemError.readFailed(
|
|
238
240
|
normalizedPath,
|
|
239
241
|
error instanceof Error ? error.message : String(error)
|
|
240
242
|
);
|
|
@@ -257,10 +259,10 @@ class FileSystemService {
|
|
|
257
259
|
try {
|
|
258
260
|
const stats = await fs.stat(normalizedPath);
|
|
259
261
|
if (!stats.isFile()) {
|
|
260
|
-
throw
|
|
262
|
+
throw import_errors2.FileSystemError.invalidPath(normalizedPath, "Path is not a file");
|
|
261
263
|
}
|
|
262
264
|
if (stats.size > this.config.maxFileSize) {
|
|
263
|
-
throw
|
|
265
|
+
throw import_errors2.FileSystemError.fileTooLarge(
|
|
264
266
|
normalizedPath,
|
|
265
267
|
stats.size,
|
|
266
268
|
this.config.maxFileSize
|
|
@@ -269,7 +271,7 @@ class FileSystemService {
|
|
|
269
271
|
const rawContent = await fs.readFile(normalizedPath);
|
|
270
272
|
const mimeType = (0, import_mime_utils.detectMimeType)(normalizedPath, rawContent);
|
|
271
273
|
if ((0, import_mime_utils.isTextMimeType)(mimeType) && !(0, import_mime_utils.isLikelyBinary)(rawContent)) {
|
|
272
|
-
throw
|
|
274
|
+
throw import_errors2.FileSystemError.readFailed(
|
|
273
275
|
normalizedPath,
|
|
274
276
|
`File is text (${mimeType}). Use read_file instead.`
|
|
275
277
|
);
|
|
@@ -282,16 +284,16 @@ class FileSystemService {
|
|
|
282
284
|
size: stats.size
|
|
283
285
|
};
|
|
284
286
|
} catch (error) {
|
|
285
|
-
if (error instanceof
|
|
287
|
+
if (error instanceof import_errors.DextoRuntimeError && error.scope === "filesystem") {
|
|
286
288
|
throw error;
|
|
287
289
|
}
|
|
288
290
|
if (error.code === "ENOENT") {
|
|
289
|
-
throw
|
|
291
|
+
throw import_errors2.FileSystemError.fileNotFound(normalizedPath);
|
|
290
292
|
}
|
|
291
293
|
if (error.code === "EACCES") {
|
|
292
|
-
throw
|
|
294
|
+
throw import_errors2.FileSystemError.permissionDenied(normalizedPath, "read");
|
|
293
295
|
}
|
|
294
|
-
throw
|
|
296
|
+
throw import_errors2.FileSystemError.readFailed(
|
|
295
297
|
normalizedPath,
|
|
296
298
|
error instanceof Error ? error.message : String(error)
|
|
297
299
|
);
|
|
@@ -368,7 +370,7 @@ class FileSystemService {
|
|
|
368
370
|
totalFound: validFiles.length
|
|
369
371
|
};
|
|
370
372
|
} catch (error) {
|
|
371
|
-
throw
|
|
373
|
+
throw import_errors2.FileSystemError.globFailed(
|
|
372
374
|
pattern,
|
|
373
375
|
error instanceof Error ? error.message : String(error)
|
|
374
376
|
);
|
|
@@ -381,25 +383,25 @@ class FileSystemService {
|
|
|
381
383
|
await this.ensureInitialized();
|
|
382
384
|
const validation = await this.pathValidator.validatePath(dirPath);
|
|
383
385
|
if (!validation.isValid || !validation.normalizedPath) {
|
|
384
|
-
throw
|
|
386
|
+
throw import_errors2.FileSystemError.invalidPath(dirPath, validation.error || "Unknown error");
|
|
385
387
|
}
|
|
386
388
|
const normalizedPath = validation.normalizedPath;
|
|
387
389
|
try {
|
|
388
390
|
const stats = await fs.stat(normalizedPath);
|
|
389
391
|
if (!stats.isDirectory()) {
|
|
390
|
-
throw
|
|
392
|
+
throw import_errors2.FileSystemError.invalidPath(normalizedPath, "Path is not a directory");
|
|
391
393
|
}
|
|
392
394
|
} catch (error) {
|
|
393
|
-
if (error instanceof
|
|
395
|
+
if (error instanceof import_errors.DextoRuntimeError && error.scope === "filesystem") {
|
|
394
396
|
throw error;
|
|
395
397
|
}
|
|
396
398
|
if (error.code === "ENOENT") {
|
|
397
|
-
throw
|
|
399
|
+
throw import_errors2.FileSystemError.directoryNotFound(normalizedPath);
|
|
398
400
|
}
|
|
399
401
|
if (error.code === "EACCES") {
|
|
400
|
-
throw
|
|
402
|
+
throw import_errors2.FileSystemError.permissionDenied(normalizedPath, "read");
|
|
401
403
|
}
|
|
402
|
-
throw
|
|
404
|
+
throw import_errors2.FileSystemError.listFailed(
|
|
403
405
|
normalizedPath,
|
|
404
406
|
error instanceof Error ? error.message : String(error)
|
|
405
407
|
);
|
|
@@ -495,7 +497,7 @@ class FileSystemService {
|
|
|
495
497
|
totalEntries
|
|
496
498
|
};
|
|
497
499
|
} catch (error) {
|
|
498
|
-
throw
|
|
500
|
+
throw import_errors2.FileSystemError.listFailed(
|
|
499
501
|
normalizedPath,
|
|
500
502
|
error instanceof Error ? error.message : String(error)
|
|
501
503
|
);
|
|
@@ -531,7 +533,7 @@ class FileSystemService {
|
|
|
531
533
|
await this.ensureInitialized();
|
|
532
534
|
const validation = await this.pathValidator.validatePath(dirPath);
|
|
533
535
|
if (!validation.isValid || !validation.normalizedPath) {
|
|
534
|
-
throw
|
|
536
|
+
throw import_errors2.FileSystemError.invalidPath(dirPath, validation.error || "Unknown error");
|
|
535
537
|
}
|
|
536
538
|
const normalizedPath = validation.normalizedPath;
|
|
537
539
|
const recursive = options.recursive ?? false;
|
|
@@ -551,9 +553,9 @@ class FileSystemService {
|
|
|
551
553
|
}
|
|
552
554
|
}
|
|
553
555
|
if (code === "EACCES" || code === "EPERM") {
|
|
554
|
-
throw
|
|
556
|
+
throw import_errors2.FileSystemError.permissionDenied(normalizedPath, "create directory");
|
|
555
557
|
}
|
|
556
|
-
throw
|
|
558
|
+
throw import_errors2.FileSystemError.createDirFailed(
|
|
557
559
|
normalizedPath,
|
|
558
560
|
error instanceof Error ? error.message : String(error)
|
|
559
561
|
);
|
|
@@ -566,7 +568,7 @@ class FileSystemService {
|
|
|
566
568
|
await this.ensureInitialized();
|
|
567
569
|
const validation = await this.pathValidator.validatePath(targetPath);
|
|
568
570
|
if (!validation.isValid || !validation.normalizedPath) {
|
|
569
|
-
throw
|
|
571
|
+
throw import_errors2.FileSystemError.invalidPath(targetPath, validation.error || "Unknown error");
|
|
570
572
|
}
|
|
571
573
|
const normalizedPath = validation.normalizedPath;
|
|
572
574
|
try {
|
|
@@ -575,12 +577,12 @@ class FileSystemService {
|
|
|
575
577
|
} catch (error) {
|
|
576
578
|
const code = error.code;
|
|
577
579
|
if (code === "ENOENT") {
|
|
578
|
-
throw
|
|
580
|
+
throw import_errors2.FileSystemError.fileNotFound(normalizedPath);
|
|
579
581
|
}
|
|
580
582
|
if (code === "EACCES" || code === "EPERM") {
|
|
581
|
-
throw
|
|
583
|
+
throw import_errors2.FileSystemError.permissionDenied(normalizedPath, "delete");
|
|
582
584
|
}
|
|
583
|
-
throw
|
|
585
|
+
throw import_errors2.FileSystemError.deleteFailed(
|
|
584
586
|
normalizedPath,
|
|
585
587
|
error instanceof Error ? error.message : String(error)
|
|
586
588
|
);
|
|
@@ -593,11 +595,11 @@ class FileSystemService {
|
|
|
593
595
|
await this.ensureInitialized();
|
|
594
596
|
const fromValidation = await this.pathValidator.validatePath(fromPath);
|
|
595
597
|
if (!fromValidation.isValid || !fromValidation.normalizedPath) {
|
|
596
|
-
throw
|
|
598
|
+
throw import_errors2.FileSystemError.invalidPath(fromPath, fromValidation.error || "Unknown error");
|
|
597
599
|
}
|
|
598
600
|
const toValidation = await this.pathValidator.validatePath(toPath);
|
|
599
601
|
if (!toValidation.isValid || !toValidation.normalizedPath) {
|
|
600
|
-
throw
|
|
602
|
+
throw import_errors2.FileSystemError.invalidPath(toPath, toValidation.error || "Unknown error");
|
|
601
603
|
}
|
|
602
604
|
const normalizedFrom = fromValidation.normalizedPath;
|
|
603
605
|
const normalizedTo = toValidation.normalizedPath;
|
|
@@ -606,7 +608,7 @@ class FileSystemService {
|
|
|
606
608
|
}
|
|
607
609
|
try {
|
|
608
610
|
await fs.access(normalizedTo);
|
|
609
|
-
throw
|
|
611
|
+
throw import_errors2.FileSystemError.renameFailed(
|
|
610
612
|
normalizedFrom,
|
|
611
613
|
`Target already exists: ${normalizedTo}`
|
|
612
614
|
);
|
|
@@ -617,9 +619,9 @@ class FileSystemService {
|
|
|
617
619
|
}
|
|
618
620
|
if (code === "ENOENT") {
|
|
619
621
|
} else if (code === "EACCES" || code === "EPERM") {
|
|
620
|
-
throw
|
|
622
|
+
throw import_errors2.FileSystemError.permissionDenied(normalizedTo, "rename");
|
|
621
623
|
} else {
|
|
622
|
-
throw
|
|
624
|
+
throw import_errors2.FileSystemError.renameFailed(
|
|
623
625
|
normalizedFrom,
|
|
624
626
|
error instanceof Error ? error.message : String(error)
|
|
625
627
|
);
|
|
@@ -631,12 +633,12 @@ class FileSystemService {
|
|
|
631
633
|
} catch (error) {
|
|
632
634
|
const code = error.code;
|
|
633
635
|
if (code === "ENOENT") {
|
|
634
|
-
throw
|
|
636
|
+
throw import_errors2.FileSystemError.fileNotFound(normalizedFrom);
|
|
635
637
|
}
|
|
636
638
|
if (code === "EACCES" || code === "EPERM") {
|
|
637
|
-
throw
|
|
639
|
+
throw import_errors2.FileSystemError.permissionDenied(normalizedFrom, "rename");
|
|
638
640
|
}
|
|
639
|
-
throw
|
|
641
|
+
throw import_errors2.FileSystemError.renameFailed(
|
|
640
642
|
normalizedFrom,
|
|
641
643
|
error instanceof Error ? error.message : String(error)
|
|
642
644
|
);
|
|
@@ -652,7 +654,7 @@ class FileSystemService {
|
|
|
652
654
|
let globPattern;
|
|
653
655
|
const baseValidation = await this.pathValidator.validatePath(basePath);
|
|
654
656
|
if (!baseValidation.isValid || !baseValidation.normalizedPath) {
|
|
655
|
-
throw
|
|
657
|
+
throw import_errors2.FileSystemError.invalidPath(basePath, baseValidation.error || "Unknown error");
|
|
656
658
|
}
|
|
657
659
|
const resolvedPath = baseValidation.normalizedPath;
|
|
658
660
|
try {
|
|
@@ -674,7 +676,7 @@ class FileSystemService {
|
|
|
674
676
|
const contextLines = options.contextLines || 0;
|
|
675
677
|
try {
|
|
676
678
|
if (!(0, import_safe_regex.default)(pattern)) {
|
|
677
|
-
throw
|
|
679
|
+
throw import_errors2.FileSystemError.invalidPattern(
|
|
678
680
|
pattern,
|
|
679
681
|
"Pattern may cause catastrophic backtracking (ReDoS). Please simplify the regex."
|
|
680
682
|
);
|
|
@@ -739,9 +741,9 @@ class FileSystemService {
|
|
|
739
741
|
};
|
|
740
742
|
} catch (error) {
|
|
741
743
|
if (error instanceof Error && error.message.includes("Invalid regular expression")) {
|
|
742
|
-
throw
|
|
744
|
+
throw import_errors2.FileSystemError.invalidPattern(pattern, "Invalid regular expression syntax");
|
|
743
745
|
}
|
|
744
|
-
throw
|
|
746
|
+
throw import_errors2.FileSystemError.searchFailed(
|
|
745
747
|
pattern,
|
|
746
748
|
error instanceof Error ? error.message : String(error)
|
|
747
749
|
);
|
|
@@ -754,7 +756,7 @@ class FileSystemService {
|
|
|
754
756
|
await this.ensureInitialized();
|
|
755
757
|
const validation = await this.pathValidator.validatePath(filePath);
|
|
756
758
|
if (!validation.isValid || !validation.normalizedPath) {
|
|
757
|
-
throw
|
|
759
|
+
throw import_errors2.FileSystemError.invalidPath(filePath, validation.error || "Unknown error");
|
|
758
760
|
}
|
|
759
761
|
const normalizedPath = validation.normalizedPath;
|
|
760
762
|
const encoding = options.encoding || DEFAULT_ENCODING;
|
|
@@ -769,10 +771,8 @@ class FileSystemService {
|
|
|
769
771
|
backupPath = await this.createBackup(normalizedPath);
|
|
770
772
|
}
|
|
771
773
|
try {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
await fs.mkdir(dir, { recursive: true });
|
|
775
|
-
}
|
|
774
|
+
const dir = path.dirname(normalizedPath);
|
|
775
|
+
await fs.mkdir(dir, { recursive: true });
|
|
776
776
|
await fs.writeFile(normalizedPath, content, encoding);
|
|
777
777
|
const bytesWritten = Buffer.byteLength(content, encoding);
|
|
778
778
|
this.logger.debug(`File written: ${normalizedPath} (${bytesWritten} bytes)`);
|
|
@@ -783,7 +783,7 @@ class FileSystemService {
|
|
|
783
783
|
backupPath
|
|
784
784
|
};
|
|
785
785
|
} catch (error) {
|
|
786
|
-
throw
|
|
786
|
+
throw import_errors2.FileSystemError.writeFailed(
|
|
787
787
|
normalizedPath,
|
|
788
788
|
error instanceof Error ? error.message : String(error)
|
|
789
789
|
);
|
|
@@ -796,7 +796,7 @@ class FileSystemService {
|
|
|
796
796
|
await this.ensureInitialized();
|
|
797
797
|
const validation = await this.pathValidator.validatePath(filePath);
|
|
798
798
|
if (!validation.isValid || !validation.normalizedPath) {
|
|
799
|
-
throw
|
|
799
|
+
throw import_errors2.FileSystemError.invalidPath(filePath, validation.error || "Unknown error");
|
|
800
800
|
}
|
|
801
801
|
const normalizedPath = validation.normalizedPath;
|
|
802
802
|
const fileContent = await this.readFile(normalizedPath);
|
|
@@ -805,10 +805,10 @@ class FileSystemService {
|
|
|
805
805
|
new RegExp(operation.oldString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")
|
|
806
806
|
) || []).length;
|
|
807
807
|
if (occurrences === 0) {
|
|
808
|
-
throw
|
|
808
|
+
throw import_errors2.FileSystemError.stringNotFound(normalizedPath, operation.oldString);
|
|
809
809
|
}
|
|
810
810
|
if (!operation.replaceAll && occurrences > 1) {
|
|
811
|
-
throw
|
|
811
|
+
throw import_errors2.FileSystemError.stringNotUnique(normalizedPath, operation.oldString, occurrences);
|
|
812
812
|
}
|
|
813
813
|
let backupPath;
|
|
814
814
|
if (options.backup ?? this.config.enableBackups) {
|
|
@@ -835,7 +835,7 @@ class FileSystemService {
|
|
|
835
835
|
newContent
|
|
836
836
|
};
|
|
837
837
|
} catch (error) {
|
|
838
|
-
throw
|
|
838
|
+
throw import_errors2.FileSystemError.editFailed(
|
|
839
839
|
normalizedPath,
|
|
840
840
|
error instanceof Error ? error.message : String(error)
|
|
841
841
|
);
|
|
@@ -857,7 +857,7 @@ class FileSystemService {
|
|
|
857
857
|
await this.cleanupOldBackups();
|
|
858
858
|
return backupPath;
|
|
859
859
|
} catch (error) {
|
|
860
|
-
throw
|
|
860
|
+
throw import_errors2.FileSystemError.backupFailed(
|
|
861
861
|
filePath,
|
|
862
862
|
error instanceof Error ? error.message : String(error)
|
|
863
863
|
);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Secure file system operations for Dexto internal tools
|
|
5
5
|
*/
|
|
6
|
-
import { Logger } from '@dexto/core';
|
|
6
|
+
import { type Logger } from '@dexto/core/logger';
|
|
7
7
|
import { FileSystemConfig, FileContent, MediaFileContent, ReadFileOptions, GlobOptions, GlobResult, GrepOptions, SearchResult, WriteFileOptions, WriteResult, EditFileOptions, EditResult, EditOperation, ListDirectoryOptions, ListDirectoryResult, CreateDirectoryOptions, CreateDirectoryResult, DeletePathOptions, DeletePathResult, RenamePathResult } from './types.js';
|
|
8
8
|
/**
|
|
9
9
|
* FileSystemService - Handles all file system operations with security checks
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem-service.d.ts","sourceRoot":"","sources":["../src/filesystem-service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"filesystem-service.d.ts","sourceRoot":"","sources":["../src/filesystem-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,9 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { glob } from "glob";
|
|
4
4
|
import safeRegex from "safe-regex";
|
|
5
|
-
import { DextoRuntimeError
|
|
5
|
+
import { DextoRuntimeError } from "@dexto/core/errors";
|
|
6
|
+
import { DextoLogComponent } from "@dexto/core/logger";
|
|
7
|
+
import { getDextoPath } from "@dexto/core/utils/path.js";
|
|
6
8
|
import { PathValidator } from "./path-validator.js";
|
|
7
9
|
import { FileSystemError } from "./errors.js";
|
|
8
10
|
import { detectMimeType, getMediaFileKind, isLikelyBinary, isTextMimeType } from "./mime-utils.js";
|
|
@@ -736,10 +738,8 @@ class FileSystemService {
|
|
|
736
738
|
backupPath = await this.createBackup(normalizedPath);
|
|
737
739
|
}
|
|
738
740
|
try {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
await fs.mkdir(dir, { recursive: true });
|
|
742
|
-
}
|
|
741
|
+
const dir = path.dirname(normalizedPath);
|
|
742
|
+
await fs.mkdir(dir, { recursive: true });
|
|
743
743
|
await fs.writeFile(normalizedPath, content, encoding);
|
|
744
744
|
const bytesWritten = Buffer.byteLength(content, encoding);
|
|
745
745
|
this.logger.debug(`File written: ${normalizedPath} (${bytesWritten} bytes)`);
|
|
@@ -237,9 +237,7 @@ const createMockLogger = () => {
|
|
|
237
237
|
);
|
|
238
238
|
await fileSystemService.initialize();
|
|
239
239
|
const testFile = path.join(tempDir, "new-file.txt");
|
|
240
|
-
const result = await fileSystemService.writeFile(testFile, "content"
|
|
241
|
-
createDirs: true
|
|
242
|
-
});
|
|
240
|
+
const result = await fileSystemService.writeFile(testFile, "content");
|
|
243
241
|
(0, import_vitest.expect)(result.success).toBe(true);
|
|
244
242
|
(0, import_vitest.expect)(result.backupPath).toBeUndefined();
|
|
245
243
|
});
|
|
@@ -214,9 +214,7 @@ describe("FileSystemService", () => {
|
|
|
214
214
|
);
|
|
215
215
|
await fileSystemService.initialize();
|
|
216
216
|
const testFile = path.join(tempDir, "new-file.txt");
|
|
217
|
-
const result = await fileSystemService.writeFile(testFile, "content"
|
|
218
|
-
createDirs: true
|
|
219
|
-
});
|
|
217
|
+
const result = await fileSystemService.writeFile(testFile, "content");
|
|
220
218
|
expect(result.success).toBe(true);
|
|
221
219
|
expect(result.backupPath).toBeUndefined();
|
|
222
220
|
});
|
package/dist/glob-files-tool.cjs
CHANGED
|
@@ -22,16 +22,17 @@ __export(glob_files_tool_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(glob_files_tool_exports);
|
|
24
24
|
var import_zod = require("zod");
|
|
25
|
-
var
|
|
25
|
+
var import_tools = require("@dexto/core/tools");
|
|
26
26
|
var import_directory_approval = require("./directory-approval.js");
|
|
27
27
|
var import_path_utils = require("./path-utils.js");
|
|
28
|
+
var import_workspace_paths = require("./workspace-paths.js");
|
|
28
29
|
const GlobFilesInputSchema = import_zod.z.object({
|
|
29
30
|
pattern: import_zod.z.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js")'),
|
|
30
31
|
path: import_zod.z.string().optional().describe("Base directory to search from (defaults to working directory)"),
|
|
31
32
|
max_results: import_zod.z.number().int().positive().optional().default(1e3).describe("Maximum number of results to return (default: 1000)")
|
|
32
33
|
}).strict();
|
|
33
34
|
function createGlobFilesTool(getFileSystemService) {
|
|
34
|
-
return (0,
|
|
35
|
+
return (0, import_tools.defineTool)({
|
|
35
36
|
id: "glob_files",
|
|
36
37
|
aliases: ["glob"],
|
|
37
38
|
description: "Find files matching a glob pattern. Supports standard glob syntax like **/*.js for recursive matches, *.ts for files in current directory, and src/**/*.tsx for nested paths. Returns array of file paths with metadata (size, modified date). Results are limited to allowed paths only.",
|
|
@@ -41,9 +42,9 @@ function createGlobFilesTool(getFileSystemService) {
|
|
|
41
42
|
const bits = [`pattern=${input.pattern}`];
|
|
42
43
|
if (input.path) bits.push(`path=${input.path}`);
|
|
43
44
|
if (typeof input.max_results === "number") bits.push(`max=${input.max_results}`);
|
|
44
|
-
return (0,
|
|
45
|
+
return (0, import_tools.createLocalToolCallHeader)({
|
|
45
46
|
title: "Find Files",
|
|
46
|
-
argsText: (0,
|
|
47
|
+
argsText: (0, import_tools.truncateForHeader)(bits.join(", "), 140)
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
},
|
|
@@ -59,40 +60,42 @@ function createGlobFilesTool(getFileSystemService) {
|
|
|
59
60
|
}
|
|
60
61
|
}),
|
|
61
62
|
async execute(input, context) {
|
|
62
|
-
const resolvedFileSystemService = await getFileSystemService(context);
|
|
63
63
|
const { pattern, path: searchPath, max_results } = input;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
if (!context.services) {
|
|
65
|
+
throw new Error("glob_files requires ToolExecutionContext.services");
|
|
66
|
+
}
|
|
67
|
+
const handle = await context.services.workspaceManager.open({ intent: "read" });
|
|
68
|
+
const workspacePattern = joinWorkspacePattern(handle.context.path, searchPath, pattern);
|
|
69
|
+
const matches = await handle.files.glob(workspacePattern);
|
|
70
|
+
const limitedMatches = matches.slice(0, max_results);
|
|
71
|
+
const truncated = matches.length > max_results;
|
|
71
72
|
const _display = {
|
|
72
73
|
type: "search",
|
|
73
74
|
pattern,
|
|
74
|
-
matches:
|
|
75
|
-
file
|
|
75
|
+
matches: limitedMatches.map((file) => ({
|
|
76
|
+
file,
|
|
76
77
|
line: 0,
|
|
77
78
|
// No line number for glob
|
|
78
|
-
content: file
|
|
79
|
+
content: file
|
|
79
80
|
})),
|
|
80
|
-
totalMatches:
|
|
81
|
-
truncated
|
|
81
|
+
totalMatches: limitedMatches.length,
|
|
82
|
+
truncated
|
|
82
83
|
};
|
|
83
84
|
return {
|
|
84
|
-
files:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
modified: file.modified.toISOString()
|
|
88
|
-
})),
|
|
89
|
-
total_found: result.totalFound,
|
|
90
|
-
truncated: result.truncated,
|
|
85
|
+
files: limitedMatches.map((filePath) => ({ path: filePath })),
|
|
86
|
+
total_found: limitedMatches.length,
|
|
87
|
+
truncated,
|
|
91
88
|
_display
|
|
92
89
|
};
|
|
93
90
|
}
|
|
94
91
|
});
|
|
95
92
|
}
|
|
93
|
+
function joinWorkspacePattern(workspaceRoot, searchPath, pattern) {
|
|
94
|
+
(0, import_workspace_paths.assertWorkspaceRelativeGlob)("glob_files", pattern);
|
|
95
|
+
const basePath = (0, import_workspace_paths.toWorkspaceRelativePath)("glob_files", workspaceRoot, searchPath || ".");
|
|
96
|
+
if (basePath === "." || basePath === "") return pattern;
|
|
97
|
+
return `${basePath.replace(/\/$/, "")}/${pattern}`;
|
|
98
|
+
}
|
|
96
99
|
// Annotate the CommonJS export names for ESM import in node:
|
|
97
100
|
0 && (module.exports = {
|
|
98
101
|
createGlobFilesTool
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Internal tool for finding files using glob patterns
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import type { Tool } from '@dexto/core';
|
|
7
|
+
import type { Tool } from '@dexto/core/tools';
|
|
8
8
|
import type { FileSystemServiceGetter } from './file-tool-types.js';
|
|
9
9
|
declare const GlobFilesInputSchema: z.ZodObject<{
|
|
10
10
|
pattern: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob-files-tool.d.ts","sourceRoot":"","sources":["../src/glob-files-tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"glob-files-tool.d.ts","sourceRoot":"","sources":["../src/glob-files-tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAqB,IAAI,EAAwB,MAAM,mBAAmB,CAAC;AACvF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAKpE,QAAA,MAAM,oBAAoB;;;;kBAiBb,CAAC;AAEd;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,oBAAoB,EAAE,uBAAuB,GAC9C,IAAI,CAAC,OAAO,oBAAoB,CAAC,CAiEnC"}
|
package/dist/glob-files-tool.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { createLocalToolCallHeader, defineTool, truncateForHeader } from "@dexto/core";
|
|
2
|
+
import { createLocalToolCallHeader, defineTool, truncateForHeader } from "@dexto/core/tools";
|
|
3
3
|
import { createDirectoryAccessApprovalHandlers } from "./directory-approval.js";
|
|
4
4
|
import { resolveUserPath } from "./path-utils.js";
|
|
5
|
+
import { assertWorkspaceRelativeGlob, toWorkspaceRelativePath } from "./workspace-paths.js";
|
|
5
6
|
const GlobFilesInputSchema = z.object({
|
|
6
7
|
pattern: z.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js")'),
|
|
7
8
|
path: z.string().optional().describe("Base directory to search from (defaults to working directory)"),
|
|
@@ -36,40 +37,42 @@ function createGlobFilesTool(getFileSystemService) {
|
|
|
36
37
|
}
|
|
37
38
|
}),
|
|
38
39
|
async execute(input, context) {
|
|
39
|
-
const resolvedFileSystemService = await getFileSystemService(context);
|
|
40
40
|
const { pattern, path: searchPath, max_results } = input;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
if (!context.services) {
|
|
42
|
+
throw new Error("glob_files requires ToolExecutionContext.services");
|
|
43
|
+
}
|
|
44
|
+
const handle = await context.services.workspaceManager.open({ intent: "read" });
|
|
45
|
+
const workspacePattern = joinWorkspacePattern(handle.context.path, searchPath, pattern);
|
|
46
|
+
const matches = await handle.files.glob(workspacePattern);
|
|
47
|
+
const limitedMatches = matches.slice(0, max_results);
|
|
48
|
+
const truncated = matches.length > max_results;
|
|
48
49
|
const _display = {
|
|
49
50
|
type: "search",
|
|
50
51
|
pattern,
|
|
51
|
-
matches:
|
|
52
|
-
file
|
|
52
|
+
matches: limitedMatches.map((file) => ({
|
|
53
|
+
file,
|
|
53
54
|
line: 0,
|
|
54
55
|
// No line number for glob
|
|
55
|
-
content: file
|
|
56
|
+
content: file
|
|
56
57
|
})),
|
|
57
|
-
totalMatches:
|
|
58
|
-
truncated
|
|
58
|
+
totalMatches: limitedMatches.length,
|
|
59
|
+
truncated
|
|
59
60
|
};
|
|
60
61
|
return {
|
|
61
|
-
files:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
modified: file.modified.toISOString()
|
|
65
|
-
})),
|
|
66
|
-
total_found: result.totalFound,
|
|
67
|
-
truncated: result.truncated,
|
|
62
|
+
files: limitedMatches.map((filePath) => ({ path: filePath })),
|
|
63
|
+
total_found: limitedMatches.length,
|
|
64
|
+
truncated,
|
|
68
65
|
_display
|
|
69
66
|
};
|
|
70
67
|
}
|
|
71
68
|
});
|
|
72
69
|
}
|
|
70
|
+
function joinWorkspacePattern(workspaceRoot, searchPath, pattern) {
|
|
71
|
+
assertWorkspaceRelativeGlob("glob_files", pattern);
|
|
72
|
+
const basePath = toWorkspaceRelativePath("glob_files", workspaceRoot, searchPath || ".");
|
|
73
|
+
if (basePath === "." || basePath === "") return pattern;
|
|
74
|
+
return `${basePath.replace(/\/$/, "")}/${pattern}`;
|
|
75
|
+
}
|
|
73
76
|
export {
|
|
74
77
|
createGlobFilesTool
|
|
75
78
|
};
|