@dexto/tools-filesystem 1.5.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/LICENSE +44 -0
- package/dist/directory-approval.integration.test.cjs +467 -0
- package/dist/directory-approval.integration.test.d.cts +2 -0
- package/dist/directory-approval.integration.test.d.ts +2 -0
- package/dist/directory-approval.integration.test.js +444 -0
- package/dist/edit-file-tool.cjs +181 -0
- package/dist/edit-file-tool.d.cts +17 -0
- package/dist/edit-file-tool.d.ts +17 -0
- package/dist/edit-file-tool.js +147 -0
- package/dist/error-codes.cjs +53 -0
- package/dist/error-codes.d.cts +32 -0
- package/dist/error-codes.d.ts +32 -0
- package/dist/error-codes.js +29 -0
- package/dist/errors.cjs +302 -0
- package/dist/errors.d.cts +112 -0
- package/dist/errors.d.ts +112 -0
- package/dist/errors.js +278 -0
- package/dist/file-tool-types.cjs +16 -0
- package/dist/file-tool-types.d.cts +46 -0
- package/dist/file-tool-types.d.ts +46 -0
- package/dist/file-tool-types.js +0 -0
- package/dist/filesystem-service.cjs +526 -0
- package/dist/filesystem-service.d.cts +107 -0
- package/dist/filesystem-service.d.ts +107 -0
- package/dist/filesystem-service.js +492 -0
- package/dist/glob-files-tool.cjs +70 -0
- package/dist/glob-files-tool.d.cts +16 -0
- package/dist/glob-files-tool.d.ts +16 -0
- package/dist/glob-files-tool.js +46 -0
- package/dist/grep-content-tool.cjs +86 -0
- package/dist/grep-content-tool.d.cts +16 -0
- package/dist/grep-content-tool.d.ts +16 -0
- package/dist/grep-content-tool.js +62 -0
- package/dist/index.cjs +55 -0
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +22 -0
- package/dist/path-validator.cjs +232 -0
- package/dist/path-validator.d.cts +90 -0
- package/dist/path-validator.d.ts +90 -0
- package/dist/path-validator.js +198 -0
- package/dist/path-validator.test.cjs +444 -0
- package/dist/path-validator.test.d.cts +2 -0
- package/dist/path-validator.test.d.ts +2 -0
- package/dist/path-validator.test.js +443 -0
- package/dist/read-file-tool.cjs +117 -0
- package/dist/read-file-tool.d.cts +17 -0
- package/dist/read-file-tool.d.ts +17 -0
- package/dist/read-file-tool.js +83 -0
- package/dist/tool-provider.cjs +108 -0
- package/dist/tool-provider.d.cts +74 -0
- package/dist/tool-provider.d.ts +74 -0
- package/dist/tool-provider.js +84 -0
- package/dist/types.cjs +16 -0
- package/dist/types.d.cts +172 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.js +0 -0
- package/dist/write-file-tool.cjs +177 -0
- package/dist/write-file-tool.d.cts +17 -0
- package/dist/write-file-tool.d.ts +17 -0
- package/dist/write-file-tool.js +143 -0
- package/package.json +42 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileSystem Service Types
|
|
3
|
+
*
|
|
4
|
+
* Types and interfaces for file system operations including reading, writing,
|
|
5
|
+
* searching, and validation.
|
|
6
|
+
*/
|
|
7
|
+
type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex';
|
|
8
|
+
/**
|
|
9
|
+
* File content with metadata
|
|
10
|
+
*/
|
|
11
|
+
interface FileContent {
|
|
12
|
+
content: string;
|
|
13
|
+
lines: number;
|
|
14
|
+
encoding: string;
|
|
15
|
+
mimeType?: string;
|
|
16
|
+
truncated: boolean;
|
|
17
|
+
size: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Options for reading files
|
|
21
|
+
*/
|
|
22
|
+
interface ReadFileOptions {
|
|
23
|
+
/** Maximum number of lines to read */
|
|
24
|
+
limit?: number | undefined;
|
|
25
|
+
/** Starting line number (1-based) */
|
|
26
|
+
offset?: number | undefined;
|
|
27
|
+
/** File encoding (default: utf-8) */
|
|
28
|
+
encoding?: BufferEncoding | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* File metadata for glob results
|
|
32
|
+
*/
|
|
33
|
+
interface FileMetadata {
|
|
34
|
+
path: string;
|
|
35
|
+
size: number;
|
|
36
|
+
modified: Date;
|
|
37
|
+
isDirectory: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for glob operations
|
|
41
|
+
*/
|
|
42
|
+
interface GlobOptions {
|
|
43
|
+
/** Base directory to search from */
|
|
44
|
+
cwd?: string | undefined;
|
|
45
|
+
/** Maximum number of results */
|
|
46
|
+
maxResults?: number | undefined;
|
|
47
|
+
/** Include file metadata */
|
|
48
|
+
includeMetadata?: boolean | undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Glob result
|
|
52
|
+
*/
|
|
53
|
+
interface GlobResult {
|
|
54
|
+
files: FileMetadata[];
|
|
55
|
+
truncated: boolean;
|
|
56
|
+
totalFound: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Search match with context
|
|
60
|
+
*/
|
|
61
|
+
interface SearchMatch {
|
|
62
|
+
file: string;
|
|
63
|
+
lineNumber: number;
|
|
64
|
+
line: string;
|
|
65
|
+
context?: {
|
|
66
|
+
before: string[];
|
|
67
|
+
after: string[];
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Options for content search (grep)
|
|
72
|
+
*/
|
|
73
|
+
interface GrepOptions {
|
|
74
|
+
/** Base directory to search */
|
|
75
|
+
path?: string | undefined;
|
|
76
|
+
/** Glob pattern to filter files */
|
|
77
|
+
glob?: string | undefined;
|
|
78
|
+
/** Number of context lines before/after match */
|
|
79
|
+
contextLines?: number | undefined;
|
|
80
|
+
/** Case-insensitive search */
|
|
81
|
+
caseInsensitive?: boolean | undefined;
|
|
82
|
+
/** Maximum number of results */
|
|
83
|
+
maxResults?: number | undefined;
|
|
84
|
+
/** Include line numbers */
|
|
85
|
+
lineNumbers?: boolean | undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Search result
|
|
89
|
+
*/
|
|
90
|
+
interface SearchResult {
|
|
91
|
+
matches: SearchMatch[];
|
|
92
|
+
totalMatches: number;
|
|
93
|
+
truncated: boolean;
|
|
94
|
+
filesSearched: number;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Options for writing files
|
|
98
|
+
*/
|
|
99
|
+
interface WriteFileOptions {
|
|
100
|
+
/** Create parent directories if they don't exist */
|
|
101
|
+
createDirs?: boolean | undefined;
|
|
102
|
+
/** File encoding (default: utf-8) */
|
|
103
|
+
encoding?: BufferEncoding | undefined;
|
|
104
|
+
/** Create backup before overwriting */
|
|
105
|
+
backup?: boolean | undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Write result
|
|
109
|
+
*/
|
|
110
|
+
interface WriteResult {
|
|
111
|
+
success: boolean;
|
|
112
|
+
path: string;
|
|
113
|
+
bytesWritten: number;
|
|
114
|
+
backupPath?: string | undefined;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Edit operation
|
|
118
|
+
*/
|
|
119
|
+
interface EditOperation {
|
|
120
|
+
oldString: string;
|
|
121
|
+
newString: string;
|
|
122
|
+
replaceAll?: boolean | undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Options for editing files
|
|
126
|
+
*/
|
|
127
|
+
interface EditFileOptions {
|
|
128
|
+
/** Create backup before editing */
|
|
129
|
+
backup?: boolean;
|
|
130
|
+
/** File encoding */
|
|
131
|
+
encoding?: BufferEncoding;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Edit result
|
|
135
|
+
*/
|
|
136
|
+
interface EditResult {
|
|
137
|
+
success: boolean;
|
|
138
|
+
path: string;
|
|
139
|
+
changesCount: number;
|
|
140
|
+
backupPath?: string | undefined;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Path validation result
|
|
144
|
+
*/
|
|
145
|
+
interface PathValidation {
|
|
146
|
+
isValid: boolean;
|
|
147
|
+
error?: string;
|
|
148
|
+
normalizedPath?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* File system configuration
|
|
152
|
+
*/
|
|
153
|
+
interface FileSystemConfig {
|
|
154
|
+
/** Allowed base paths */
|
|
155
|
+
allowedPaths: string[];
|
|
156
|
+
/** Blocked paths (relative to allowed paths) */
|
|
157
|
+
blockedPaths: string[];
|
|
158
|
+
/** Blocked file extensions */
|
|
159
|
+
blockedExtensions: string[];
|
|
160
|
+
/** Maximum file size in bytes */
|
|
161
|
+
maxFileSize: number;
|
|
162
|
+
/** Enable automatic backups */
|
|
163
|
+
enableBackups: boolean;
|
|
164
|
+
/** Backup directory absolute path (required when enableBackups is true - provided by CLI enrichment) */
|
|
165
|
+
backupPath?: string | undefined;
|
|
166
|
+
/** Backup retention period in days (default: 7) */
|
|
167
|
+
backupRetentionDays: number;
|
|
168
|
+
/** Working directory for glob/grep operations (defaults to process.cwd()) */
|
|
169
|
+
workingDirectory?: string | undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type { BufferEncoding, EditFileOptions, EditOperation, EditResult, FileContent, FileMetadata, FileSystemConfig, GlobOptions, GlobResult, GrepOptions, PathValidation, ReadFileOptions, SearchMatch, SearchResult, WriteFileOptions, WriteResult };
|
package/dist/types.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var write_file_tool_exports = {};
|
|
30
|
+
__export(write_file_tool_exports, {
|
|
31
|
+
createWriteFileTool: () => createWriteFileTool
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(write_file_tool_exports);
|
|
34
|
+
var path = __toESM(require("node:path"), 1);
|
|
35
|
+
var import_zod = require("zod");
|
|
36
|
+
var import_diff = require("diff");
|
|
37
|
+
var import_core = require("@dexto/core");
|
|
38
|
+
var import_error_codes = require("./error-codes.js");
|
|
39
|
+
const WriteFileInputSchema = import_zod.z.object({
|
|
40
|
+
file_path: import_zod.z.string().describe("Absolute path where the file should be written"),
|
|
41
|
+
content: import_zod.z.string().describe("Content to write to the file"),
|
|
42
|
+
create_dirs: import_zod.z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
|
|
43
|
+
encoding: import_zod.z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
|
|
44
|
+
}).strict();
|
|
45
|
+
function generateDiffPreview(filePath, originalContent, newContent) {
|
|
46
|
+
const unified = (0, import_diff.createPatch)(filePath, originalContent, newContent, "before", "after", {
|
|
47
|
+
context: 3
|
|
48
|
+
});
|
|
49
|
+
const additions = (unified.match(/^\+[^+]/gm) || []).length;
|
|
50
|
+
const deletions = (unified.match(/^-[^-]/gm) || []).length;
|
|
51
|
+
return {
|
|
52
|
+
type: "diff",
|
|
53
|
+
unified,
|
|
54
|
+
filename: filePath,
|
|
55
|
+
additions,
|
|
56
|
+
deletions
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function createWriteFileTool(options) {
|
|
60
|
+
const { fileSystemService, directoryApproval } = options;
|
|
61
|
+
let pendingApprovalParentDir;
|
|
62
|
+
return {
|
|
63
|
+
id: "write_file",
|
|
64
|
+
description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
|
|
65
|
+
inputSchema: WriteFileInputSchema,
|
|
66
|
+
/**
|
|
67
|
+
* Check if this write operation needs directory access approval.
|
|
68
|
+
* Returns custom approval request if the file is outside allowed paths.
|
|
69
|
+
*/
|
|
70
|
+
getApprovalOverride: (args) => {
|
|
71
|
+
const { file_path } = args;
|
|
72
|
+
if (!file_path) return null;
|
|
73
|
+
const isAllowed = fileSystemService.isPathWithinConfigAllowed(file_path);
|
|
74
|
+
if (isAllowed) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if (directoryApproval?.isSessionApproved(file_path)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const absolutePath = path.resolve(file_path);
|
|
81
|
+
const parentDir = path.dirname(absolutePath);
|
|
82
|
+
pendingApprovalParentDir = parentDir;
|
|
83
|
+
return {
|
|
84
|
+
type: import_core.ApprovalType.DIRECTORY_ACCESS,
|
|
85
|
+
metadata: {
|
|
86
|
+
path: absolutePath,
|
|
87
|
+
parentDir,
|
|
88
|
+
operation: "write",
|
|
89
|
+
toolName: "write_file"
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* Handle approved directory access - remember the directory for session
|
|
95
|
+
*/
|
|
96
|
+
onApprovalGranted: (response) => {
|
|
97
|
+
if (!directoryApproval || !pendingApprovalParentDir) return;
|
|
98
|
+
const data = response.data;
|
|
99
|
+
const rememberDirectory = data?.rememberDirectory ?? false;
|
|
100
|
+
directoryApproval.addApproved(
|
|
101
|
+
pendingApprovalParentDir,
|
|
102
|
+
rememberDirectory ? "session" : "once"
|
|
103
|
+
);
|
|
104
|
+
pendingApprovalParentDir = void 0;
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* Generate preview for approval UI - shows diff or file creation info
|
|
108
|
+
*/
|
|
109
|
+
generatePreview: async (input, _context) => {
|
|
110
|
+
const { file_path, content } = input;
|
|
111
|
+
try {
|
|
112
|
+
const originalFile = await fileSystemService.readFile(file_path);
|
|
113
|
+
const originalContent = originalFile.content;
|
|
114
|
+
return generateDiffPreview(file_path, originalContent, content);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
117
|
+
const lineCount = content.split("\n").length;
|
|
118
|
+
const preview = {
|
|
119
|
+
type: "file",
|
|
120
|
+
path: file_path,
|
|
121
|
+
operation: "create",
|
|
122
|
+
size: content.length,
|
|
123
|
+
lineCount,
|
|
124
|
+
content
|
|
125
|
+
// Include content for approval preview
|
|
126
|
+
};
|
|
127
|
+
return preview;
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
execute: async (input, _context) => {
|
|
133
|
+
const { file_path, content, create_dirs, encoding } = input;
|
|
134
|
+
let originalContent = null;
|
|
135
|
+
try {
|
|
136
|
+
const originalFile = await fileSystemService.readFile(file_path);
|
|
137
|
+
originalContent = originalFile.content;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
140
|
+
originalContent = null;
|
|
141
|
+
} else {
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const result = await fileSystemService.writeFile(file_path, content, {
|
|
146
|
+
createDirs: create_dirs,
|
|
147
|
+
encoding,
|
|
148
|
+
backup: true
|
|
149
|
+
// Always create backup for internal tools
|
|
150
|
+
});
|
|
151
|
+
let _display;
|
|
152
|
+
if (originalContent === null) {
|
|
153
|
+
const lineCount = content.split("\n").length;
|
|
154
|
+
_display = {
|
|
155
|
+
type: "file",
|
|
156
|
+
path: file_path,
|
|
157
|
+
operation: "create",
|
|
158
|
+
size: result.bytesWritten,
|
|
159
|
+
lineCount
|
|
160
|
+
};
|
|
161
|
+
} else {
|
|
162
|
+
_display = generateDiffPreview(file_path, originalContent, content);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
success: result.success,
|
|
166
|
+
path: result.path,
|
|
167
|
+
bytes_written: result.bytesWritten,
|
|
168
|
+
...result.backupPath && { backup_path: result.backupPath },
|
|
169
|
+
_display
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
175
|
+
0 && (module.exports = {
|
|
176
|
+
createWriteFileTool
|
|
177
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InternalTool } from '@dexto/core';
|
|
2
|
+
import { FileToolOptions } from './file-tool-types.cjs';
|
|
3
|
+
import './filesystem-service.cjs';
|
|
4
|
+
import './types.cjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Write File Tool
|
|
8
|
+
*
|
|
9
|
+
* Internal tool for writing content to files (requires approval)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create the write_file internal tool with directory approval support
|
|
14
|
+
*/
|
|
15
|
+
declare function createWriteFileTool(options: FileToolOptions): InternalTool;
|
|
16
|
+
|
|
17
|
+
export { createWriteFileTool };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InternalTool } from '@dexto/core';
|
|
2
|
+
import { FileToolOptions } from './file-tool-types.js';
|
|
3
|
+
import './filesystem-service.js';
|
|
4
|
+
import './types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Write File Tool
|
|
8
|
+
*
|
|
9
|
+
* Internal tool for writing content to files (requires approval)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create the write_file internal tool with directory approval support
|
|
14
|
+
*/
|
|
15
|
+
declare function createWriteFileTool(options: FileToolOptions): InternalTool;
|
|
16
|
+
|
|
17
|
+
export { createWriteFileTool };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createPatch } from "diff";
|
|
4
|
+
import { DextoRuntimeError, ApprovalType } from "@dexto/core";
|
|
5
|
+
import { FileSystemErrorCode } from "./error-codes.js";
|
|
6
|
+
const WriteFileInputSchema = z.object({
|
|
7
|
+
file_path: z.string().describe("Absolute path where the file should be written"),
|
|
8
|
+
content: z.string().describe("Content to write to the file"),
|
|
9
|
+
create_dirs: z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
|
|
10
|
+
encoding: z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
|
|
11
|
+
}).strict();
|
|
12
|
+
function generateDiffPreview(filePath, originalContent, newContent) {
|
|
13
|
+
const unified = createPatch(filePath, originalContent, newContent, "before", "after", {
|
|
14
|
+
context: 3
|
|
15
|
+
});
|
|
16
|
+
const additions = (unified.match(/^\+[^+]/gm) || []).length;
|
|
17
|
+
const deletions = (unified.match(/^-[^-]/gm) || []).length;
|
|
18
|
+
return {
|
|
19
|
+
type: "diff",
|
|
20
|
+
unified,
|
|
21
|
+
filename: filePath,
|
|
22
|
+
additions,
|
|
23
|
+
deletions
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function createWriteFileTool(options) {
|
|
27
|
+
const { fileSystemService, directoryApproval } = options;
|
|
28
|
+
let pendingApprovalParentDir;
|
|
29
|
+
return {
|
|
30
|
+
id: "write_file",
|
|
31
|
+
description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
|
|
32
|
+
inputSchema: WriteFileInputSchema,
|
|
33
|
+
/**
|
|
34
|
+
* Check if this write operation needs directory access approval.
|
|
35
|
+
* Returns custom approval request if the file is outside allowed paths.
|
|
36
|
+
*/
|
|
37
|
+
getApprovalOverride: (args) => {
|
|
38
|
+
const { file_path } = args;
|
|
39
|
+
if (!file_path) return null;
|
|
40
|
+
const isAllowed = fileSystemService.isPathWithinConfigAllowed(file_path);
|
|
41
|
+
if (isAllowed) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (directoryApproval?.isSessionApproved(file_path)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const absolutePath = path.resolve(file_path);
|
|
48
|
+
const parentDir = path.dirname(absolutePath);
|
|
49
|
+
pendingApprovalParentDir = parentDir;
|
|
50
|
+
return {
|
|
51
|
+
type: ApprovalType.DIRECTORY_ACCESS,
|
|
52
|
+
metadata: {
|
|
53
|
+
path: absolutePath,
|
|
54
|
+
parentDir,
|
|
55
|
+
operation: "write",
|
|
56
|
+
toolName: "write_file"
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
/**
|
|
61
|
+
* Handle approved directory access - remember the directory for session
|
|
62
|
+
*/
|
|
63
|
+
onApprovalGranted: (response) => {
|
|
64
|
+
if (!directoryApproval || !pendingApprovalParentDir) return;
|
|
65
|
+
const data = response.data;
|
|
66
|
+
const rememberDirectory = data?.rememberDirectory ?? false;
|
|
67
|
+
directoryApproval.addApproved(
|
|
68
|
+
pendingApprovalParentDir,
|
|
69
|
+
rememberDirectory ? "session" : "once"
|
|
70
|
+
);
|
|
71
|
+
pendingApprovalParentDir = void 0;
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Generate preview for approval UI - shows diff or file creation info
|
|
75
|
+
*/
|
|
76
|
+
generatePreview: async (input, _context) => {
|
|
77
|
+
const { file_path, content } = input;
|
|
78
|
+
try {
|
|
79
|
+
const originalFile = await fileSystemService.readFile(file_path);
|
|
80
|
+
const originalContent = originalFile.content;
|
|
81
|
+
return generateDiffPreview(file_path, originalContent, content);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
84
|
+
const lineCount = content.split("\n").length;
|
|
85
|
+
const preview = {
|
|
86
|
+
type: "file",
|
|
87
|
+
path: file_path,
|
|
88
|
+
operation: "create",
|
|
89
|
+
size: content.length,
|
|
90
|
+
lineCount,
|
|
91
|
+
content
|
|
92
|
+
// Include content for approval preview
|
|
93
|
+
};
|
|
94
|
+
return preview;
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
execute: async (input, _context) => {
|
|
100
|
+
const { file_path, content, create_dirs, encoding } = input;
|
|
101
|
+
let originalContent = null;
|
|
102
|
+
try {
|
|
103
|
+
const originalFile = await fileSystemService.readFile(file_path);
|
|
104
|
+
originalContent = originalFile.content;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.FILE_NOT_FOUND) {
|
|
107
|
+
originalContent = null;
|
|
108
|
+
} else {
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const result = await fileSystemService.writeFile(file_path, content, {
|
|
113
|
+
createDirs: create_dirs,
|
|
114
|
+
encoding,
|
|
115
|
+
backup: true
|
|
116
|
+
// Always create backup for internal tools
|
|
117
|
+
});
|
|
118
|
+
let _display;
|
|
119
|
+
if (originalContent === null) {
|
|
120
|
+
const lineCount = content.split("\n").length;
|
|
121
|
+
_display = {
|
|
122
|
+
type: "file",
|
|
123
|
+
path: file_path,
|
|
124
|
+
operation: "create",
|
|
125
|
+
size: result.bytesWritten,
|
|
126
|
+
lineCount
|
|
127
|
+
};
|
|
128
|
+
} else {
|
|
129
|
+
_display = generateDiffPreview(file_path, originalContent, content);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
success: result.success,
|
|
133
|
+
path: result.path,
|
|
134
|
+
bytes_written: result.bytesWritten,
|
|
135
|
+
...result.backupPath && { backup_path: result.backupPath },
|
|
136
|
+
_display
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
createWriteFileTool
|
|
143
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dexto/tools-filesystem",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "FileSystem tools provider for Dexto agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"dexto",
|
|
16
|
+
"tools",
|
|
17
|
+
"filesystem",
|
|
18
|
+
"file-operations"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"diff": "^7.0.0",
|
|
22
|
+
"glob": "^11.1.0",
|
|
23
|
+
"safe-regex": "^2.1.1",
|
|
24
|
+
"zod": "^3.25.0",
|
|
25
|
+
"@dexto/core": "1.5.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/diff": "^5.2.3",
|
|
29
|
+
"@types/safe-regex": "^1.1.6",
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"typescript": "^5.3.3"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"clean": "rm -rf dist"
|
|
41
|
+
}
|
|
42
|
+
}
|