@loxia-labs/loxia-autopilot-one 1.0.1 → 1.0.4
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/README.md +44 -54
- package/bin/cli.js +1 -115
- package/bin/loxia-terminal-v2.js +3 -0
- package/bin/loxia-terminal.js +3 -0
- package/bin/start-with-terminal.js +3 -0
- package/package.json +15 -15
- package/scripts/install-scanners.js +1 -235
- package/src/analyzers/CSSAnalyzer.js +1 -297
- package/src/analyzers/ConfigValidator.js +1 -690
- package/src/analyzers/ESLintAnalyzer.js +1 -320
- package/src/analyzers/JavaScriptAnalyzer.js +1 -261
- package/src/analyzers/PrettierFormatter.js +1 -247
- package/src/analyzers/PythonAnalyzer.js +1 -266
- package/src/analyzers/SecurityAnalyzer.js +1 -729
- package/src/analyzers/TypeScriptAnalyzer.js +1 -247
- package/src/analyzers/codeCloneDetector/analyzer.js +1 -344
- package/src/analyzers/codeCloneDetector/detector.js +1 -203
- package/src/analyzers/codeCloneDetector/index.js +1 -160
- package/src/analyzers/codeCloneDetector/parser.js +1 -199
- package/src/analyzers/codeCloneDetector/reporter.js +1 -148
- package/src/analyzers/codeCloneDetector/scanner.js +1 -59
- package/src/core/agentPool.js +1 -1474
- package/src/core/agentScheduler.js +1 -2147
- package/src/core/contextManager.js +1 -709
- package/src/core/messageProcessor.js +1 -732
- package/src/core/orchestrator.js +1 -548
- package/src/core/stateManager.js +1 -877
- package/src/index.js +1 -631
- package/src/interfaces/cli.js +1 -549
- package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/agents.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/components.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/messages.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +1 -0
- package/src/interfaces/terminal/api/apiClient.js +1 -0
- package/src/interfaces/terminal/api/messageRouter.js +1 -0
- package/src/interfaces/terminal/api/session.js +1 -0
- package/src/interfaces/terminal/api/websocket.js +1 -0
- package/src/interfaces/terminal/components/AgentCreator.js +1 -0
- package/src/interfaces/terminal/components/AgentEditor.js +1 -0
- package/src/interfaces/terminal/components/AgentSwitcher.js +1 -0
- package/src/interfaces/terminal/components/ErrorBoundary.js +1 -0
- package/src/interfaces/terminal/components/ErrorPanel.js +1 -0
- package/src/interfaces/terminal/components/Header.js +1 -0
- package/src/interfaces/terminal/components/HelpPanel.js +1 -0
- package/src/interfaces/terminal/components/InputBox.js +1 -0
- package/src/interfaces/terminal/components/Layout.js +1 -0
- package/src/interfaces/terminal/components/LoadingSpinner.js +1 -0
- package/src/interfaces/terminal/components/MessageList.js +1 -0
- package/src/interfaces/terminal/components/MultilineTextInput.js +1 -0
- package/src/interfaces/terminal/components/SearchPanel.js +1 -0
- package/src/interfaces/terminal/components/SettingsPanel.js +1 -0
- package/src/interfaces/terminal/components/StatusBar.js +1 -0
- package/src/interfaces/terminal/components/TextInput.js +1 -0
- package/src/interfaces/terminal/config/agentEditorConstants.js +1 -0
- package/src/interfaces/terminal/config/constants.js +1 -0
- package/src/interfaces/terminal/index.js +1 -0
- package/src/interfaces/terminal/state/useAgentControl.js +1 -0
- package/src/interfaces/terminal/state/useAgents.js +1 -0
- package/src/interfaces/terminal/state/useConnection.js +1 -0
- package/src/interfaces/terminal/state/useMessages.js +1 -0
- package/src/interfaces/terminal/state/useTools.js +1 -0
- package/src/interfaces/terminal/utils/debugLogger.js +1 -0
- package/src/interfaces/terminal/utils/settingsStorage.js +1 -0
- package/src/interfaces/terminal/utils/theme.js +1 -0
- package/src/interfaces/webServer.js +1 -2162
- package/src/modules/fileExplorer/controller.js +1 -280
- package/src/modules/fileExplorer/index.js +1 -37
- package/src/modules/fileExplorer/middleware.js +1 -92
- package/src/modules/fileExplorer/routes.js +1 -125
- package/src/modules/fileExplorer/types.js +1 -44
- package/src/services/aiService.js +1 -1232
- package/src/services/apiKeyManager.js +1 -164
- package/src/services/benchmarkService.js +1 -366
- package/src/services/budgetService.js +1 -539
- package/src/services/contextInjectionService.js +1 -247
- package/src/services/conversationCompactionService.js +1 -637
- package/src/services/errorHandler.js +1 -810
- package/src/services/fileAttachmentService.js +1 -544
- package/src/services/modelRouterService.js +1 -366
- package/src/services/modelsService.js +1 -322
- package/src/services/qualityInspector.js +1 -796
- package/src/services/tokenCountingService.js +1 -536
- package/src/tools/agentCommunicationTool.js +1 -1344
- package/src/tools/agentDelayTool.js +1 -485
- package/src/tools/asyncToolManager.js +1 -604
- package/src/tools/baseTool.js +1 -800
- package/src/tools/browserTool.js +1 -920
- package/src/tools/cloneDetectionTool.js +1 -621
- package/src/tools/dependencyResolverTool.js +1 -1215
- package/src/tools/fileContentReplaceTool.js +1 -875
- package/src/tools/fileSystemTool.js +1 -1107
- package/src/tools/fileTreeTool.js +1 -853
- package/src/tools/imageTool.js +1 -901
- package/src/tools/importAnalyzerTool.js +1 -1060
- package/src/tools/jobDoneTool.js +1 -248
- package/src/tools/seekTool.js +1 -956
- package/src/tools/staticAnalysisTool.js +1 -1778
- package/src/tools/taskManagerTool.js +1 -2873
- package/src/tools/terminalTool.js +1 -2304
- package/src/tools/webTool.js +1 -1430
- package/src/types/agent.js +1 -519
- package/src/types/contextReference.js +1 -972
- package/src/types/conversation.js +1 -730
- package/src/types/toolCommand.js +1 -747
- package/src/utilities/attachmentValidator.js +1 -292
- package/src/utilities/configManager.js +1 -582
- package/src/utilities/constants.js +1 -722
- package/src/utilities/directoryAccessManager.js +1 -535
- package/src/utilities/fileProcessor.js +1 -307
- package/src/utilities/logger.js +1 -436
- package/src/utilities/tagParser.js +1 -1246
- package/src/utilities/toolConstants.js +1 -317
- package/web-ui/build/index.html +2 -2
- package/web-ui/build/static/{index-Dy2bYbOa.css → index-CClD1090.css} +1 -1
- package/web-ui/build/static/{index-CjkkcnFA.js → index-lCBai6dX.js} +66 -67
|
@@ -1,875 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FileContentReplaceTool - Replace specific content within files
|
|
3
|
-
*
|
|
4
|
-
* Purpose:
|
|
5
|
-
* - Replace text content in files with precision
|
|
6
|
-
* - Support line-limited replacements
|
|
7
|
-
* - Handle whitespace intelligently with trim modes
|
|
8
|
-
* - Create backups before modifications
|
|
9
|
-
* - Generate diff reports
|
|
10
|
-
* - Support multi-file operations
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { BaseTool } from './baseTool.js';
|
|
14
|
-
import { promises as fs } from 'fs';
|
|
15
|
-
import path from 'path';
|
|
16
|
-
|
|
17
|
-
// Configuration constants
|
|
18
|
-
const REPLACE_CONFIG = {
|
|
19
|
-
// File size limits
|
|
20
|
-
MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB max file size
|
|
21
|
-
MAX_OLD_CONTENT_SIZE: 100 * 1024, // 100KB max old content
|
|
22
|
-
MAX_NEW_CONTENT_SIZE: 100 * 1024, // 100KB max new content
|
|
23
|
-
|
|
24
|
-
// Operation limits
|
|
25
|
-
MAX_REPLACEMENTS_PER_FILE: 1000, // Max replacements in single file
|
|
26
|
-
MAX_FILES_PER_OPERATION: 50, // Max files in one operation
|
|
27
|
-
MAX_LINE_RANGE_SIZE: 10000, // Max lines in a range
|
|
28
|
-
|
|
29
|
-
// Backup settings
|
|
30
|
-
CREATE_BACKUPS: true,
|
|
31
|
-
BACKUP_EXTENSION: '.bak',
|
|
32
|
-
|
|
33
|
-
// Diff settings
|
|
34
|
-
DIFF_CONTEXT_LINES: 3, // Lines of context in diff
|
|
35
|
-
MAX_DIFF_LINES: 100, // Max lines to show in diff
|
|
36
|
-
|
|
37
|
-
// Default settings
|
|
38
|
-
DEFAULT_TRIM_MODE: 'trim'
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
class FileContentReplaceTool extends BaseTool {
|
|
42
|
-
constructor(config = {}, logger = null) {
|
|
43
|
-
super(config, logger);
|
|
44
|
-
|
|
45
|
-
// Tool metadata
|
|
46
|
-
this.requiresProject = true;
|
|
47
|
-
this.isAsync = true;
|
|
48
|
-
this.timeout = config.timeout || 120000; // 2 minutes default
|
|
49
|
-
|
|
50
|
-
// Merge config with defaults
|
|
51
|
-
this.replaceConfig = {
|
|
52
|
-
...REPLACE_CONFIG,
|
|
53
|
-
...config.replaceConfig
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get tool description for LLM consumption
|
|
59
|
-
* @returns {string} Tool description
|
|
60
|
-
*/
|
|
61
|
-
getDescription() {
|
|
62
|
-
return `
|
|
63
|
-
File Content Replace Tool: Replace specific content within files with precision.
|
|
64
|
-
|
|
65
|
-
XML USAGE:
|
|
66
|
-
<file-content-replace>
|
|
67
|
-
<file path="src/app.js">
|
|
68
|
-
<replace mode="trim" lines-limit="5,7-10">
|
|
69
|
-
<old-content>
|
|
70
|
-
const oldFunction = () => {
|
|
71
|
-
console.log('old');
|
|
72
|
-
}
|
|
73
|
-
</old-content>
|
|
74
|
-
<new-content>
|
|
75
|
-
const newFunction = () => {
|
|
76
|
-
console.log('new');
|
|
77
|
-
}
|
|
78
|
-
</new-content>
|
|
79
|
-
</replace>
|
|
80
|
-
</file>
|
|
81
|
-
</file-content-replace>
|
|
82
|
-
|
|
83
|
-
JSON USAGE:
|
|
84
|
-
\`\`\`json
|
|
85
|
-
{
|
|
86
|
-
"toolId": "file-content-replace",
|
|
87
|
-
"files": [
|
|
88
|
-
{
|
|
89
|
-
"path": "src/app.js",
|
|
90
|
-
"replacements": [
|
|
91
|
-
{
|
|
92
|
-
"oldContent": "const oldFunction = () => {}",
|
|
93
|
-
"newContent": "const newFunction = () => {}",
|
|
94
|
-
"mode": "trim",
|
|
95
|
-
"linesLimit": "5,7-10"
|
|
96
|
-
}
|
|
97
|
-
]
|
|
98
|
-
}
|
|
99
|
-
]
|
|
100
|
-
}
|
|
101
|
-
\`\`\`
|
|
102
|
-
|
|
103
|
-
PARAMETERS:
|
|
104
|
-
|
|
105
|
-
path (required):
|
|
106
|
-
- Path to the file to modify
|
|
107
|
-
- Can be relative or absolute
|
|
108
|
-
- Examples: "src/app.js", "./config.json"
|
|
109
|
-
|
|
110
|
-
oldContent (required):
|
|
111
|
-
- Content to find and replace
|
|
112
|
-
- Subject to trim mode processing
|
|
113
|
-
- Must exist in file
|
|
114
|
-
|
|
115
|
-
newContent (required):
|
|
116
|
-
- Replacement content
|
|
117
|
-
- Subject to trim mode processing
|
|
118
|
-
- Can be same length, shorter, or longer
|
|
119
|
-
|
|
120
|
-
mode (optional):
|
|
121
|
-
- Whitespace handling mode
|
|
122
|
-
- Options:
|
|
123
|
-
* "trim" (default): Trim all whitespace from both ends
|
|
124
|
-
* "newlines": Only trim newline characters
|
|
125
|
-
* "none": Use content exactly as provided
|
|
126
|
-
- Helps with matching despite indentation differences
|
|
127
|
-
|
|
128
|
-
linesLimit (optional):
|
|
129
|
-
- Restrict replacement to specific lines
|
|
130
|
-
- Format: Comma-separated line numbers or ranges
|
|
131
|
-
- Examples:
|
|
132
|
-
* "5" - Only line 5
|
|
133
|
-
* "5,10,15" - Lines 5, 10, and 15
|
|
134
|
-
* "5-10" - Lines 5 through 10
|
|
135
|
-
* "1-5,10,15-20" - Lines 1-5, 10, and 15-20
|
|
136
|
-
- Line numbers are 1-based
|
|
137
|
-
|
|
138
|
-
TRIM MODES EXPLAINED:
|
|
139
|
-
|
|
140
|
-
Mode: "trim" (Recommended for most cases)
|
|
141
|
-
Input:
|
|
142
|
-
" const x = 1; \\n"
|
|
143
|
-
After trim:
|
|
144
|
-
"const x = 1;"
|
|
145
|
-
Use when: Indentation may vary
|
|
146
|
-
|
|
147
|
-
Mode: "newlines"
|
|
148
|
-
Input:
|
|
149
|
-
"\\n const x = 1; \\n"
|
|
150
|
-
After trim:
|
|
151
|
-
" const x = 1; "
|
|
152
|
-
Use when: Preserving internal whitespace
|
|
153
|
-
|
|
154
|
-
Mode: "none"
|
|
155
|
-
Input:
|
|
156
|
-
" const x = 1; \\n"
|
|
157
|
-
After trim:
|
|
158
|
-
" const x = 1; \\n"
|
|
159
|
-
Use when: Exact character match needed
|
|
160
|
-
|
|
161
|
-
EXAMPLES:
|
|
162
|
-
|
|
163
|
-
Example 1 - Basic replacement with trim:
|
|
164
|
-
<file-content-replace>
|
|
165
|
-
<file path="src/components/Button.js">
|
|
166
|
-
<replace mode="trim">
|
|
167
|
-
<old-content>
|
|
168
|
-
const handleClick = (event) => {
|
|
169
|
-
console.log('clicked');
|
|
170
|
-
}
|
|
171
|
-
</old-content>
|
|
172
|
-
<new-content>
|
|
173
|
-
const handleClick = (event) => {
|
|
174
|
-
console.log('clicked');
|
|
175
|
-
props.onClick?.(event);
|
|
176
|
-
}
|
|
177
|
-
</new-content>
|
|
178
|
-
</replace>
|
|
179
|
-
</file>
|
|
180
|
-
</file-content-replace>
|
|
181
|
-
|
|
182
|
-
Example 2 - Line-limited replacement:
|
|
183
|
-
<file-content-replace>
|
|
184
|
-
<file path="src/App.js">
|
|
185
|
-
<replace lines-limit="10-20">
|
|
186
|
-
<old-content>const API_URL = 'http://localhost:3000'</old-content>
|
|
187
|
-
<new-content>const API_URL = process.env.API_URL</new-content>
|
|
188
|
-
</replace>
|
|
189
|
-
</file>
|
|
190
|
-
</file-content-replace>
|
|
191
|
-
|
|
192
|
-
Example 3 - Multiple replacements in one file:
|
|
193
|
-
<file-content-replace>
|
|
194
|
-
<file path="src/config.js">
|
|
195
|
-
<replace>
|
|
196
|
-
<old-content>DEBUG = false</old-content>
|
|
197
|
-
<new-content>DEBUG = true</new-content>
|
|
198
|
-
</replace>
|
|
199
|
-
<replace>
|
|
200
|
-
<old-content>LOG_LEVEL = 'error'</old-content>
|
|
201
|
-
<new-content>LOG_LEVEL = 'debug'</new-content>
|
|
202
|
-
</replace>
|
|
203
|
-
</file>
|
|
204
|
-
</file-content-replace>
|
|
205
|
-
|
|
206
|
-
Example 4 - Multiple files:
|
|
207
|
-
<file-content-replace>
|
|
208
|
-
<file path="src/app.js">
|
|
209
|
-
<replace>
|
|
210
|
-
<old-content>version = '1.0.0'</old-content>
|
|
211
|
-
<new-content>version = '1.1.0'</new-content>
|
|
212
|
-
</replace>
|
|
213
|
-
</file>
|
|
214
|
-
<file path="package.json">
|
|
215
|
-
<replace mode="none">
|
|
216
|
-
<old-content>"version": "1.0.0"</old-content>
|
|
217
|
-
<new-content>"version": "1.1.0"</new-content>
|
|
218
|
-
</replace>
|
|
219
|
-
</file>
|
|
220
|
-
</file-content-replace>
|
|
221
|
-
|
|
222
|
-
Example 5 - Exact whitespace matching:
|
|
223
|
-
<file-content-replace>
|
|
224
|
-
<file path="Makefile">
|
|
225
|
-
<replace mode="none">
|
|
226
|
-
<old-content>\\tbuild:\\n\\t\\tgcc</old-content>
|
|
227
|
-
<new-content>\\tbuild:\\n\\t\\tclang</new-content>
|
|
228
|
-
</replace>
|
|
229
|
-
</file>
|
|
230
|
-
</file-content-replace>
|
|
231
|
-
|
|
232
|
-
FEATURES:
|
|
233
|
-
|
|
234
|
-
✓ Automatic backup creation (.bak files)
|
|
235
|
-
✓ Before/after diff reports
|
|
236
|
-
✓ Replacement counting and statistics
|
|
237
|
-
✓ Multi-file operations
|
|
238
|
-
✓ Line-limited replacements
|
|
239
|
-
✓ Intelligent whitespace handling
|
|
240
|
-
✓ Security validation (path access)
|
|
241
|
-
✓ Creates parent directories if needed
|
|
242
|
-
|
|
243
|
-
LIMITATIONS:
|
|
244
|
-
|
|
245
|
-
- Maximum file size: ${REPLACE_CONFIG.MAX_FILE_SIZE / (1024 * 1024)}MB
|
|
246
|
-
- Maximum old content size: ${REPLACE_CONFIG.MAX_OLD_CONTENT_SIZE / 1024}KB
|
|
247
|
-
- Maximum new content size: ${REPLACE_CONFIG.MAX_NEW_CONTENT_SIZE / 1024}KB
|
|
248
|
-
- Maximum replacements per file: ${REPLACE_CONFIG.MAX_REPLACEMENTS_PER_FILE}
|
|
249
|
-
- Maximum files per operation: ${REPLACE_CONFIG.MAX_FILES_PER_OPERATION}
|
|
250
|
-
|
|
251
|
-
NOTES:
|
|
252
|
-
|
|
253
|
-
- Use the seek tool first to verify content exists
|
|
254
|
-
- Backups are created automatically (.bak extension)
|
|
255
|
-
- Replacements are case-sensitive
|
|
256
|
-
- Old content must match exactly (after trim mode applied)
|
|
257
|
-
- Multiple occurrences are all replaced unless linesLimit specified
|
|
258
|
-
- Tool validates paths against agent's accessible directories
|
|
259
|
-
|
|
260
|
-
MULTI-DIRECTORY SUPPORT:
|
|
261
|
-
|
|
262
|
-
Works with agent's configured accessible directories.
|
|
263
|
-
Validates paths against directoryAccess configuration.
|
|
264
|
-
`;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Parse parameters from tool command content
|
|
269
|
-
* @param {string} content - Raw tool command content
|
|
270
|
-
* @returns {Object} Parsed parameters
|
|
271
|
-
*/
|
|
272
|
-
parseParameters(content) {
|
|
273
|
-
try {
|
|
274
|
-
// Try JSON first
|
|
275
|
-
if (content.trim().startsWith('{')) {
|
|
276
|
-
return this.parseJSON(content);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Otherwise parse XML
|
|
280
|
-
return this.parseXML(content);
|
|
281
|
-
} catch (error) {
|
|
282
|
-
this.logger?.error('Failed to parse file-content-replace parameters', {
|
|
283
|
-
error: error.message
|
|
284
|
-
});
|
|
285
|
-
throw new Error(`Parameter parsing failed: ${error.message}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Parse JSON format
|
|
291
|
-
* @param {string} content - JSON string
|
|
292
|
-
* @returns {Object} Parsed parameters
|
|
293
|
-
*/
|
|
294
|
-
parseJSON(content) {
|
|
295
|
-
const parsed = JSON.parse(content);
|
|
296
|
-
|
|
297
|
-
if (!parsed.files || !Array.isArray(parsed.files)) {
|
|
298
|
-
throw new Error('JSON must have "files" array');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return {
|
|
302
|
-
files: parsed.files.map(file => ({
|
|
303
|
-
path: file.path,
|
|
304
|
-
replacements: (file.replacements || []).map(r => ({
|
|
305
|
-
oldContent: r.oldContent,
|
|
306
|
-
newContent: r.newContent,
|
|
307
|
-
mode: r.mode || REPLACE_CONFIG.DEFAULT_TRIM_MODE,
|
|
308
|
-
linesLimit: r.linesLimit || null
|
|
309
|
-
}))
|
|
310
|
-
}))
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Parse XML format
|
|
316
|
-
* @param {string} content - XML string
|
|
317
|
-
* @returns {Object} Parsed parameters
|
|
318
|
-
*/
|
|
319
|
-
parseXML(content) {
|
|
320
|
-
const files = [];
|
|
321
|
-
|
|
322
|
-
// Extract <file> tags
|
|
323
|
-
const filePattern = /<file\s+path="([^"]+)">([\s\S]*?)<\/file>/gi;
|
|
324
|
-
let fileMatch;
|
|
325
|
-
|
|
326
|
-
while ((fileMatch = filePattern.exec(content)) !== null) {
|
|
327
|
-
const filePath = fileMatch[1];
|
|
328
|
-
const fileContent = fileMatch[2];
|
|
329
|
-
|
|
330
|
-
const replacements = [];
|
|
331
|
-
|
|
332
|
-
// Extract <replace> tags within this file
|
|
333
|
-
const replacePattern = /<replace(?:\s+([^>]*?))?>([\s\S]*?)<\/replace>/gi;
|
|
334
|
-
let replaceMatch;
|
|
335
|
-
|
|
336
|
-
while ((replaceMatch = replacePattern.exec(fileContent)) !== null) {
|
|
337
|
-
const attributes = replaceMatch[1] || '';
|
|
338
|
-
const replaceContent = replaceMatch[2];
|
|
339
|
-
|
|
340
|
-
// Parse attributes
|
|
341
|
-
const mode = this.extractAttribute(attributes, 'mode') || REPLACE_CONFIG.DEFAULT_TRIM_MODE;
|
|
342
|
-
const linesLimit = this.extractAttribute(attributes, 'lines-limit');
|
|
343
|
-
|
|
344
|
-
// Extract old-content
|
|
345
|
-
const oldContentMatch = /<old-content>([\s\S]*?)<\/old-content>/i.exec(replaceContent);
|
|
346
|
-
if (!oldContentMatch) {
|
|
347
|
-
this.logger?.warn('Missing old-content in replace tag');
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
const oldContentRaw = oldContentMatch[1];
|
|
351
|
-
|
|
352
|
-
// Extract new-content
|
|
353
|
-
const newContentMatch = /<new-content>([\s\S]*?)<\/new-content>/i.exec(replaceContent);
|
|
354
|
-
if (!newContentMatch) {
|
|
355
|
-
this.logger?.warn('Missing new-content in replace tag');
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
const newContentRaw = newContentMatch[1];
|
|
359
|
-
|
|
360
|
-
// Apply trim mode
|
|
361
|
-
const oldContent = this.applyTrimMode(oldContentRaw, mode);
|
|
362
|
-
const newContent = this.applyTrimMode(newContentRaw, mode);
|
|
363
|
-
|
|
364
|
-
replacements.push({
|
|
365
|
-
oldContent,
|
|
366
|
-
newContent,
|
|
367
|
-
mode,
|
|
368
|
-
linesLimit
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (replacements.length > 0) {
|
|
373
|
-
files.push({
|
|
374
|
-
path: filePath,
|
|
375
|
-
replacements
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return { files };
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Extract attribute value from attribute string
|
|
385
|
-
* @param {string} attributes - Attribute string
|
|
386
|
-
* @param {string} name - Attribute name
|
|
387
|
-
* @returns {string|null} Attribute value
|
|
388
|
-
*/
|
|
389
|
-
extractAttribute(attributes, name) {
|
|
390
|
-
const pattern = new RegExp(`${name}=["']([^"']*)["']`, 'i');
|
|
391
|
-
const match = pattern.exec(attributes);
|
|
392
|
-
return match ? match[1] : null;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Apply trim mode to content
|
|
397
|
-
* @param {string} content - Content to process
|
|
398
|
-
* @param {string} mode - Trim mode
|
|
399
|
-
* @returns {string} Processed content
|
|
400
|
-
*/
|
|
401
|
-
applyTrimMode(content, mode) {
|
|
402
|
-
switch (mode) {
|
|
403
|
-
case 'newlines':
|
|
404
|
-
return content.replace(/^\n+|\n+$/g, '');
|
|
405
|
-
case 'none':
|
|
406
|
-
return content;
|
|
407
|
-
case 'trim':
|
|
408
|
-
default:
|
|
409
|
-
return content.trim();
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Get required parameters
|
|
415
|
-
* @returns {Array<string>} Array of required parameter names
|
|
416
|
-
*/
|
|
417
|
-
getRequiredParameters() {
|
|
418
|
-
return ['files'];
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Custom parameter validation
|
|
423
|
-
* @param {Object} params - Parameters to validate
|
|
424
|
-
* @returns {Object} Validation result
|
|
425
|
-
*/
|
|
426
|
-
customValidateParameters(params) {
|
|
427
|
-
const errors = [];
|
|
428
|
-
|
|
429
|
-
// Validate files array
|
|
430
|
-
if (!params.files || !Array.isArray(params.files)) {
|
|
431
|
-
errors.push('files must be an array');
|
|
432
|
-
} else {
|
|
433
|
-
if (params.files.length === 0) {
|
|
434
|
-
errors.push('files array cannot be empty');
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (params.files.length > this.replaceConfig.MAX_FILES_PER_OPERATION) {
|
|
438
|
-
errors.push(`Cannot process more than ${this.replaceConfig.MAX_FILES_PER_OPERATION} files in one operation`);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Validate each file
|
|
442
|
-
for (const file of params.files) {
|
|
443
|
-
if (!file.path) {
|
|
444
|
-
errors.push('Each file must have a path');
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Check for path traversal
|
|
448
|
-
if (file.path && file.path.includes('..')) {
|
|
449
|
-
errors.push(`Path traversal (..) not allowed for security: ${file.path}`);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (!file.replacements || !Array.isArray(file.replacements)) {
|
|
453
|
-
errors.push(`File ${file.path} must have replacements array`);
|
|
454
|
-
} else if (file.replacements.length === 0) {
|
|
455
|
-
errors.push(`File ${file.path} replacements array cannot be empty`);
|
|
456
|
-
} else {
|
|
457
|
-
// Validate each replacement
|
|
458
|
-
for (const replacement of file.replacements) {
|
|
459
|
-
if (!replacement.oldContent && replacement.oldContent !== '') {
|
|
460
|
-
errors.push(`Replacement in ${file.path} missing oldContent`);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (!replacement.newContent && replacement.newContent !== '') {
|
|
464
|
-
errors.push(`Replacement in ${file.path} missing newContent`);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Validate content sizes
|
|
468
|
-
if (replacement.oldContent && replacement.oldContent.length > this.replaceConfig.MAX_OLD_CONTENT_SIZE) {
|
|
469
|
-
errors.push(`oldContent too large (max ${this.replaceConfig.MAX_OLD_CONTENT_SIZE / 1024}KB)`);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (replacement.newContent && replacement.newContent.length > this.replaceConfig.MAX_NEW_CONTENT_SIZE) {
|
|
473
|
-
errors.push(`newContent too large (max ${this.replaceConfig.MAX_NEW_CONTENT_SIZE / 1024}KB)`);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Validate mode
|
|
477
|
-
if (replacement.mode && !['trim', 'newlines', 'none'].includes(replacement.mode)) {
|
|
478
|
-
errors.push(`Invalid mode: ${replacement.mode}. Must be 'trim', 'newlines', or 'none'`);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Throw error if validation fails
|
|
486
|
-
if (errors.length > 0) {
|
|
487
|
-
throw new Error(`Parameter validation failed: ${errors.join(', ')}`);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
return {
|
|
491
|
-
valid: true,
|
|
492
|
-
errors: []
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Execute tool with parsed parameters
|
|
498
|
-
* @param {Object} params - Parsed parameters
|
|
499
|
-
* @param {Object} context - Execution context
|
|
500
|
-
* @returns {Promise<Object>} Execution result
|
|
501
|
-
*/
|
|
502
|
-
async execute(params, context) {
|
|
503
|
-
const { files } = params;
|
|
504
|
-
const { projectDir, agentId, directoryAccess } = context;
|
|
505
|
-
|
|
506
|
-
// Determine working directory (respect multi-directory access)
|
|
507
|
-
let workingDirectory = projectDir || process.cwd();
|
|
508
|
-
|
|
509
|
-
if (directoryAccess && directoryAccess.workingDirectory) {
|
|
510
|
-
workingDirectory = directoryAccess.workingDirectory;
|
|
511
|
-
this.logger?.info('Using agent configured working directory', {
|
|
512
|
-
workingDirectory: directoryAccess.workingDirectory,
|
|
513
|
-
agentId
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
this.logger?.info('Executing file content replace', {
|
|
518
|
-
fileCount: files.length,
|
|
519
|
-
workingDirectory,
|
|
520
|
-
agentId
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
const results = [];
|
|
524
|
-
const stats = {
|
|
525
|
-
filesProcessed: 0,
|
|
526
|
-
filesModified: 0,
|
|
527
|
-
totalReplacements: 0,
|
|
528
|
-
backupsCreated: 0,
|
|
529
|
-
errors: 0
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
// Process each file
|
|
533
|
-
for (const file of files) {
|
|
534
|
-
try {
|
|
535
|
-
const fileResult = await this.processFile(file, workingDirectory, directoryAccess);
|
|
536
|
-
results.push(fileResult);
|
|
537
|
-
|
|
538
|
-
stats.filesProcessed++;
|
|
539
|
-
if (fileResult.replacementsMade > 0) {
|
|
540
|
-
stats.filesModified++;
|
|
541
|
-
stats.totalReplacements += fileResult.replacementsMade;
|
|
542
|
-
}
|
|
543
|
-
if (fileResult.backupCreated) {
|
|
544
|
-
stats.backupsCreated++;
|
|
545
|
-
}
|
|
546
|
-
} catch (error) {
|
|
547
|
-
this.logger?.error(`Error processing file ${file.path}`, { error: error.message });
|
|
548
|
-
results.push({
|
|
549
|
-
filePath: file.path,
|
|
550
|
-
success: false,
|
|
551
|
-
error: error.message
|
|
552
|
-
});
|
|
553
|
-
stats.errors++;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
return {
|
|
558
|
-
success: stats.errors === 0,
|
|
559
|
-
results,
|
|
560
|
-
statistics: stats,
|
|
561
|
-
summary: this.generateSummary(stats),
|
|
562
|
-
toolUsed: 'file-content-replace'
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Process a single file
|
|
568
|
-
* @param {Object} file - File object with path and replacements
|
|
569
|
-
* @param {string} workingDirectory - Working directory
|
|
570
|
-
* @param {Object} directoryAccess - Directory access config
|
|
571
|
-
* @returns {Promise<Object>} File processing result
|
|
572
|
-
*/
|
|
573
|
-
async processFile(file, workingDirectory, directoryAccess) {
|
|
574
|
-
const { path: filePath, replacements } = file;
|
|
575
|
-
|
|
576
|
-
// Resolve path
|
|
577
|
-
const resolvedPath = path.isAbsolute(filePath)
|
|
578
|
-
? filePath
|
|
579
|
-
: path.resolve(workingDirectory, filePath);
|
|
580
|
-
|
|
581
|
-
// Validate path access (if directoryAccess provided)
|
|
582
|
-
if (directoryAccess) {
|
|
583
|
-
const accessible = this.isPathAccessible(resolvedPath, workingDirectory, directoryAccess);
|
|
584
|
-
if (!accessible) {
|
|
585
|
-
throw new Error(`Path not accessible: ${filePath}`);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Check file exists
|
|
590
|
-
try {
|
|
591
|
-
await fs.access(resolvedPath);
|
|
592
|
-
} catch (error) {
|
|
593
|
-
throw new Error(`File not found: ${filePath}`);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Check file size
|
|
597
|
-
const stats = await fs.stat(resolvedPath);
|
|
598
|
-
if (stats.size > this.replaceConfig.MAX_FILE_SIZE) {
|
|
599
|
-
throw new Error(`File too large (max ${this.replaceConfig.MAX_FILE_SIZE / (1024 * 1024)}MB): ${filePath}`);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// Read file content
|
|
603
|
-
let content = await fs.readFile(resolvedPath, 'utf-8');
|
|
604
|
-
const originalContent = content;
|
|
605
|
-
|
|
606
|
-
// Create backup
|
|
607
|
-
let backupCreated = false;
|
|
608
|
-
if (this.replaceConfig.CREATE_BACKUPS) {
|
|
609
|
-
const backupPath = resolvedPath + this.replaceConfig.BACKUP_EXTENSION;
|
|
610
|
-
try {
|
|
611
|
-
await fs.writeFile(backupPath, originalContent, 'utf-8');
|
|
612
|
-
backupCreated = true;
|
|
613
|
-
} catch (error) {
|
|
614
|
-
this.logger?.warn(`Failed to create backup: ${error.message}`);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// Apply replacements
|
|
619
|
-
let replacementsMade = 0;
|
|
620
|
-
const replacementDetails = [];
|
|
621
|
-
|
|
622
|
-
for (const replacement of replacements) {
|
|
623
|
-
const result = await this.applyReplacement(
|
|
624
|
-
content,
|
|
625
|
-
replacement.oldContent,
|
|
626
|
-
replacement.newContent,
|
|
627
|
-
replacement.linesLimit,
|
|
628
|
-
replacement.mode
|
|
629
|
-
);
|
|
630
|
-
|
|
631
|
-
content = result.newContent;
|
|
632
|
-
replacementsMade += result.count;
|
|
633
|
-
|
|
634
|
-
replacementDetails.push({
|
|
635
|
-
oldContent: replacement.oldContent.substring(0, 50) + (replacement.oldContent.length > 50 ? '...' : ''),
|
|
636
|
-
newContent: replacement.newContent.substring(0, 50) + (replacement.newContent.length > 50 ? '...' : ''),
|
|
637
|
-
count: result.count,
|
|
638
|
-
mode: replacement.mode,
|
|
639
|
-
linesLimit: replacement.linesLimit
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Write back if changes were made
|
|
644
|
-
if (replacementsMade > 0) {
|
|
645
|
-
await fs.writeFile(resolvedPath, content, 'utf-8');
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// Generate diff
|
|
649
|
-
const diff = replacementsMade > 0
|
|
650
|
-
? this.generateDiff(originalContent, content)
|
|
651
|
-
: null;
|
|
652
|
-
|
|
653
|
-
return {
|
|
654
|
-
filePath,
|
|
655
|
-
resolvedPath,
|
|
656
|
-
success: true,
|
|
657
|
-
replacementsMade,
|
|
658
|
-
backupCreated,
|
|
659
|
-
replacementDetails,
|
|
660
|
-
diff,
|
|
661
|
-
message: replacementsMade > 0
|
|
662
|
-
? `Made ${replacementsMade} replacement(s) in ${filePath}`
|
|
663
|
-
: `No replacements made in ${filePath} (content not found)`
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* Check if path is accessible
|
|
669
|
-
* @param {string} targetPath - Path to check
|
|
670
|
-
* @param {string} workingDirectory - Working directory
|
|
671
|
-
* @param {Object} directoryAccess - Directory access config
|
|
672
|
-
* @returns {boolean} True if accessible
|
|
673
|
-
*/
|
|
674
|
-
isPathAccessible(targetPath, workingDirectory, directoryAccess) {
|
|
675
|
-
// Always allow paths within working directory
|
|
676
|
-
const relativeToWorking = path.relative(workingDirectory, targetPath);
|
|
677
|
-
if (!relativeToWorking.startsWith('..') && !path.isAbsolute(relativeToWorking)) {
|
|
678
|
-
return true;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// Check writeEnabledDirectories
|
|
682
|
-
if (directoryAccess.writeEnabledDirectories) {
|
|
683
|
-
for (const dir of directoryAccess.writeEnabledDirectories) {
|
|
684
|
-
const relative = path.relative(dir, targetPath);
|
|
685
|
-
if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
686
|
-
return true;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
return false;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* Apply a single replacement
|
|
696
|
-
* @param {string} content - File content
|
|
697
|
-
* @param {string} oldContent - Content to replace
|
|
698
|
-
* @param {string} newContent - Replacement content
|
|
699
|
-
* @param {string|null} linesLimit - Line limit specification
|
|
700
|
-
* @param {string} mode - Trim mode
|
|
701
|
-
* @returns {Object} Result with newContent and count
|
|
702
|
-
*/
|
|
703
|
-
async applyReplacement(content, oldContent, newContent, linesLimit, mode) {
|
|
704
|
-
if (!linesLimit) {
|
|
705
|
-
// Replace in entire file (simple string replace, not regex)
|
|
706
|
-
const count = this.countOccurrences(content, oldContent);
|
|
707
|
-
|
|
708
|
-
if (count === 0) {
|
|
709
|
-
return { newContent: content, count: 0 };
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// Simple replaceAll
|
|
713
|
-
const newFileContent = content.split(oldContent).join(newContent);
|
|
714
|
-
|
|
715
|
-
return { newContent: newFileContent, count };
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// Line-limited replacement
|
|
719
|
-
const lineNumbers = this.parseLineRanges(linesLimit);
|
|
720
|
-
const lines = content.split('\n');
|
|
721
|
-
let replacementCount = 0;
|
|
722
|
-
|
|
723
|
-
// Process each line
|
|
724
|
-
for (let i = 0; i < lines.length; i++) {
|
|
725
|
-
const lineNumber = i + 1; // 1-based
|
|
726
|
-
|
|
727
|
-
if (lineNumbers.has(lineNumber)) {
|
|
728
|
-
// Check if this line contains the old content
|
|
729
|
-
if (lines[i].includes(oldContent)) {
|
|
730
|
-
const occurrencesInLine = this.countOccurrences(lines[i], oldContent);
|
|
731
|
-
lines[i] = lines[i].split(oldContent).join(newContent);
|
|
732
|
-
replacementCount += occurrencesInLine;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
return {
|
|
738
|
-
newContent: lines.join('\n'),
|
|
739
|
-
count: replacementCount
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* Count occurrences of substring in string
|
|
745
|
-
* @param {string} str - String to search
|
|
746
|
-
* @param {string} substr - Substring to count
|
|
747
|
-
* @returns {number} Count of occurrences
|
|
748
|
-
*/
|
|
749
|
-
countOccurrences(str, substr) {
|
|
750
|
-
if (!substr) return 0;
|
|
751
|
-
return str.split(substr).length - 1;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Parse line ranges from string like "3,5,7-9"
|
|
756
|
-
* @param {string} rangesStr - Line range string
|
|
757
|
-
* @returns {Set<number>} Set of line numbers
|
|
758
|
-
*/
|
|
759
|
-
parseLineRanges(rangesStr) {
|
|
760
|
-
const result = new Set();
|
|
761
|
-
|
|
762
|
-
if (!rangesStr || rangesStr.trim() === '') {
|
|
763
|
-
return result;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
const ranges = rangesStr.split(',');
|
|
767
|
-
|
|
768
|
-
for (const range of ranges) {
|
|
769
|
-
const trimmed = range.trim();
|
|
770
|
-
|
|
771
|
-
if (trimmed === '') continue;
|
|
772
|
-
|
|
773
|
-
// Check if it's a range (e.g., "7-9")
|
|
774
|
-
if (trimmed.includes('-')) {
|
|
775
|
-
const [start, end] = trimmed.split('-').map(n => parseInt(n.trim(), 10));
|
|
776
|
-
|
|
777
|
-
if (!isNaN(start) && !isNaN(end) && end - start < this.replaceConfig.MAX_LINE_RANGE_SIZE) {
|
|
778
|
-
for (let i = start; i <= end; i++) {
|
|
779
|
-
result.add(i);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
} else {
|
|
783
|
-
// Single line number
|
|
784
|
-
const lineNum = parseInt(trimmed, 10);
|
|
785
|
-
if (!isNaN(lineNum)) {
|
|
786
|
-
result.add(lineNum);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
return result;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
/**
|
|
795
|
-
* Generate diff between original and new content
|
|
796
|
-
* @param {string} original - Original content
|
|
797
|
-
* @param {string} modified - Modified content
|
|
798
|
-
* @returns {string} Diff string
|
|
799
|
-
*/
|
|
800
|
-
generateDiff(original, modified) {
|
|
801
|
-
const oldLines = original.split('\n');
|
|
802
|
-
const newLines = modified.split('\n');
|
|
803
|
-
|
|
804
|
-
// Find first and last lines that differ
|
|
805
|
-
let firstDiff = -1;
|
|
806
|
-
let lastDiff = -1;
|
|
807
|
-
|
|
808
|
-
const maxLines = Math.max(oldLines.length, newLines.length);
|
|
809
|
-
|
|
810
|
-
for (let i = 0; i < maxLines; i++) {
|
|
811
|
-
const oldLine = i < oldLines.length ? oldLines[i] : '';
|
|
812
|
-
const newLine = i < newLines.length ? newLines[i] : '';
|
|
813
|
-
|
|
814
|
-
if (oldLine !== newLine) {
|
|
815
|
-
if (firstDiff === -1) firstDiff = i;
|
|
816
|
-
lastDiff = i;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
if (firstDiff === -1) return 'No differences';
|
|
821
|
-
|
|
822
|
-
// Add context
|
|
823
|
-
const contextLines = this.replaceConfig.DIFF_CONTEXT_LINES;
|
|
824
|
-
const startLine = Math.max(0, firstDiff - contextLines);
|
|
825
|
-
const endLine = Math.min(maxLines - 1, lastDiff + contextLines);
|
|
826
|
-
|
|
827
|
-
// Limit total lines shown
|
|
828
|
-
if (endLine - startLine > this.replaceConfig.MAX_DIFF_LINES) {
|
|
829
|
-
return `Diff too large (${endLine - startLine + 1} lines), showing summary only:\n` +
|
|
830
|
-
`Changed lines: ${firstDiff + 1} to ${lastDiff + 1}`;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
let diff = `@@ -${startLine + 1},${endLine - startLine + 1} +${startLine + 1},${endLine - startLine + 1} @@\n`;
|
|
834
|
-
|
|
835
|
-
for (let i = startLine; i <= endLine; i++) {
|
|
836
|
-
const oldLine = i < oldLines.length ? oldLines[i] : '';
|
|
837
|
-
const newLine = i < newLines.length ? newLines[i] : '';
|
|
838
|
-
|
|
839
|
-
if (oldLine === newLine) {
|
|
840
|
-
diff += ` ${oldLine}\n`;
|
|
841
|
-
} else {
|
|
842
|
-
diff += `- ${oldLine}\n`;
|
|
843
|
-
diff += `+ ${newLine}\n`;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
return diff;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* Generate summary text
|
|
852
|
-
* @param {Object} stats - Statistics object
|
|
853
|
-
* @returns {string} Summary text
|
|
854
|
-
*/
|
|
855
|
-
generateSummary(stats) {
|
|
856
|
-
return `
|
|
857
|
-
Processed ${stats.filesProcessed} file(s)
|
|
858
|
-
Modified ${stats.filesModified} file(s)
|
|
859
|
-
Total replacements: ${stats.totalReplacements}
|
|
860
|
-
Backups created: ${stats.backupsCreated}
|
|
861
|
-
Errors: ${stats.errors}
|
|
862
|
-
`.trim();
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
/**
|
|
866
|
-
* Resource cleanup
|
|
867
|
-
* @param {string} operationId - Operation identifier
|
|
868
|
-
*/
|
|
869
|
-
async cleanup(operationId) {
|
|
870
|
-
// No persistent resources to clean up
|
|
871
|
-
this.logger?.info('File content replace tool cleanup completed', { operationId });
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
export default FileContentReplaceTool;
|
|
1
|
+
function a0_0x38d1(_0x6a61df,_0x57874f){_0x6a61df=_0x6a61df-0x196;const _0x471c1a=a0_0x471c();let _0x38d1b4=_0x471c1a[_0x6a61df];if(a0_0x38d1['HtNQta']===undefined){var _0x4ddaca=function(_0x28f6b4){const _0x2cc007='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x58b70e='',_0x11c690='';for(let _0x5f2754=0x0,_0x112521,_0x294c47,_0x3a6c60=0x0;_0x294c47=_0x28f6b4['charAt'](_0x3a6c60++);~_0x294c47&&(_0x112521=_0x5f2754%0x4?_0x112521*0x40+_0x294c47:_0x294c47,_0x5f2754++%0x4)?_0x58b70e+=String['fromCharCode'](0xff&_0x112521>>(-0x2*_0x5f2754&0x6)):0x0){_0x294c47=_0x2cc007['indexOf'](_0x294c47);}for(let _0x25c408=0x0,_0x41f18d=_0x58b70e['length'];_0x25c408<_0x41f18d;_0x25c408++){_0x11c690+='%'+('00'+_0x58b70e['charCodeAt'](_0x25c408)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x11c690);};a0_0x38d1['slNtUV']=_0x4ddaca,a0_0x38d1['AHtYwk']={},a0_0x38d1['HtNQta']=!![];}const _0x4d5fe1=_0x471c1a[0x0],_0x5159f6=_0x6a61df+_0x4d5fe1,_0x367d4d=a0_0x38d1['AHtYwk'][_0x5159f6];return!_0x367d4d?(_0x38d1b4=a0_0x38d1['slNtUV'](_0x38d1b4),a0_0x38d1['AHtYwk'][_0x5159f6]=_0x38d1b4):_0x38d1b4=_0x367d4d,_0x38d1b4;}const a0_0x19bb1a=a0_0x38d1;(function(_0x2f0670,_0x2cc8b2){const _0x1d000a=a0_0x38d1,_0x161e53=_0x2f0670();while(!![]){try{const _0x4aaaf4=parseInt(_0x1d000a(0x1d8))/0x1*(parseInt(_0x1d000a(0x1ed))/0x2)+-parseInt(_0x1d000a(0x1d5))/0x3+-parseInt(_0x1d000a(0x19c))/0x4+-parseInt(_0x1d000a(0x196))/0x5+parseInt(_0x1d000a(0x1f6))/0x6*(-parseInt(_0x1d000a(0x19e))/0x7)+-parseInt(_0x1d000a(0x19f))/0x8*(parseInt(_0x1d000a(0x201))/0x9)+parseInt(_0x1d000a(0x1db))/0xa*(parseInt(_0x1d000a(0x1dc))/0xb);if(_0x4aaaf4===_0x2cc8b2)break;else _0x161e53['push'](_0x161e53['shift']());}catch(_0x5181de){_0x161e53['push'](_0x161e53['shift']());}}}(a0_0x471c,0x9903e));function a0_0x471c(){const _0x3c2704=['tufyx0zjtevtx1bfuL9puevsqvrjt04','tufyx05fv19dt05uru5ux1njwKu','AM9PBG','revgqvvmvf9uuKLnx01preu','zxH0CMfJDef0DhjPyNv0zq','icHJB250zw50ig5VDcbMB3vUzcK','ugf0AcbUB3qGywnJzxnZAwjSztOG','Bg9Nz2vY','CMvWBgfJzw1LBNrZ','zxHLyW','zxjYB3jZ','D3jPDgvgAwXL','cGPot1rfuZOkcI0GvxnLihrOzsbZzwvRihrVB2WGzMLYC3qGDg8GDMvYAwz5ignVBNrLBNqGzxHPC3rZcI0GqMfJA3vWCYbHCMuGy3jLyxrLzcbHDxrVBwf0AwnHBgX5icGUyMfRigv4DgvUC2LVBIKklsbszxbSywnLBwvUDhmGyxjLignHC2uTC2vUC2L0AxzLcI0Gt2XKignVBNrLBNqGBxvZDcbTyxrJAcbLEgfJDgX5icHHzNrLCIb0CMLTig1VzguGyxbWBgLLzcKklsbnDwX0AxbSzsbVy2n1CNjLBMnLCYbHCMuGywXSihjLCgXHy2vKihvUBgvZCYbSAw5LC0XPBwL0ihnWzwnPzMLLzaOTifrVB2WGDMfSAwrHDgvZihbHDgHZigfNywLUC3qGywDLBNqNCYbHy2nLC3nPyMXLigrPCMvJDg9YAwvZcGPnvuXuss1esvjfq1rpuLKGu1vque9svdOkcLDVCMTZihDPDgGGywDLBNqNCYbJB25MAwD1CMvKigfJy2vZC2LIBguGzgLYzwn0B3jPzxmUcLzHBgLKyxrLCYbWyxrOCYbHz2fPBNn0igrPCMvJDg9YEufJy2vZCYbJB25MAwD1CMf0Aw9UlGOGicaG','cIaGica','AxnbCNjHEq','tufyx0zjtevFu0LArq','iebacG','cLbYB2nLC3nLzca','rMfPBgvKihrVihbHCNnLigzPBguTy29UDgvUDc1YzxbSywnLihbHCMfTzxrLCNm','nZuZote1uevIwunr','tuiPoIa','ugf0Acb0CMf2zxjZywWGkc4UksbUB3qGywXSB3DLzcbMB3iGC2vJDxjPDhK6ia','mtjLAKXWq1C','B2XKq29UDgvUDa','twfKzsa','mtmYotbsB2f0BMu','mtGZntLgBePRuwu','twLZC2LUzYbVBgqTy29UDgvUDcbPBIbYzxbSywnLihrHzW','CMvXDwLYzxnqCM9Qzwn0','BwfW','rMLSzsbJB250zw50ihjLCgXHy2uGDg9VBcbJBgvHBNvWignVBxbSzxrLza','rMLSzsbUB3qGzM91BMq6ia','igzPBguOCYKkvg90ywWGCMvWBgfJzw1LBNrZoIa','D3jPDgvfBMfIBgvKrgLYzwn0B3jPzxm','zMLSzxnnB2rPzMLLza','yMfJA3vWC0nYzwf0zwq','AxnbyNnVBhv0zq','CMvSyxrPDMu','C3rHCNrZv2L0Aa','C3bSAxq','rMLSzsa','AxnqyxrOqwnJzxnZAwjSzq','BM9Uzq','mty0otGYzunbteLJ','igzPBgvZigLUig9UzsbVCgvYyxrPB24','ChvZAa','BgLUzxmTBgLTAxq','CMvWBgfJzw1LBNrZtwfKzq','zMLSzxnqCM9JzxnZzwq','DgLTzw91Da','BMv3q29UDgvUDa','CMvWBgfJzunVBMzPzW','mta4mer5tvvpEG','ywnJzxnZ','BwvZC2fNzq','Aw5MBW','CgfYC2vku09o','yMfJA3vWq3jLyxrLza','cKjHy2T1ChmGy3jLyxrLzdOG','Aw5JBhvKzxm','lI4U','DxrMltG','CMvHzezPBgu','mJG4mda5svzNveHX','BMv3BgLUzxm','q1jfqvrfx0jbq0Tvufm','odyXoty1v1rvvePy','tufyx09mrf9dt05uru5ux1njwKu','CMvZB2X2zq','vxnPBMCGywDLBNqGy29UzMLNDxjLzcb3B3jRAw5NigrPCMvJDg9YEq','BgLUzxnmAw1PDa','ig1PC3nPBMCGB2XKq29UDgvUDa','mtGWmdaZmKTvs0ntCa','twLZC2LUzYbUzxCTy29UDgvUDcbPBIbYzxbSywnLihrHzW','nduYndHZugHQDNC','mtm2BxzAr2ru','z2vUzxjHDgveAwzM','rMfPBgvKihrVignYzwf0zsbIywnRDxa6ia','D29YA2LUz0rPCMvJDg9YEq','q2fUBM90ihbYB2nLC3mGBw9Yzsb0AgfUia','BgvUz3rO','yxbWBhLuCMLTtw9Kzq','zMLSzs1JB250zw50lxjLCgXHy2u','yxbWBhLszxbSywnLBwvUDa','BMv3q29UDgvUDcb0B28GBgfYz2uGkg1HEca','lMjHAW','AgfZ','zxjYB3i','C3vIC3rYAw5N','zMLSzxmGBxvZDcbIzsbHBIbHCNjHEq','CgfYC2vytuW','Dg90ywXszxbSywnLBwvUDhm','pvSIj10Ow14Ij10QkvSIj10','cI0Gtwf4Aw11BsbMAwXLCYbWzxiGB3bLCMf0Aw9UoIa','B2XKq29UDgvUDcb0B28GBgfYz2uGkg1HEca','ig11C3qGAgf2zsbYzxbSywnLBwvUDhmGyxjYyxK','tufyx0XjtKvFuKfor0vFu0LArq','DhjPBq','D2fYBG','zMLSzxmGyxjYyxKGy2fUBM90igjLigvTChr5','z2vUzxjHDgvtDw1Tyxj5','ihrVia','CgfYC2vmAw5LuMfUz2vZ','tM8GzgLMzMvYzw5Jzxm','zMLSzxm','y291BNrpy2n1CNjLBMnLCW','Cgf0Aa','z2v0uMvXDwLYzwrqyxjHBwv0zxjZ','Bw9Kzq','ywrK'];a0_0x471c=function(){return _0x3c2704;};return a0_0x471c();}import{BaseTool}from'./baseTool.js';import{promises as a0_0x58b70e}from'fs';import a0_0x11c690 from'path';const REPLACE_CONFIG={'MAX_FILE_SIZE':0xa*0x400*0x400,'MAX_OLD_CONTENT_SIZE':0x64*0x400,'MAX_NEW_CONTENT_SIZE':0x64*0x400,'MAX_REPLACEMENTS_PER_FILE':0x3e8,'MAX_FILES_PER_OPERATION':0x32,'MAX_LINE_RANGE_SIZE':0x2710,'CREATE_BACKUPS':!![],'BACKUP_EXTENSION':a0_0x19bb1a(0x1a9),'DIFF_CONTEXT_LINES':0x3,'MAX_DIFF_LINES':0x64,'DEFAULT_TRIM_MODE':'trim'};class FileContentReplaceTool extends BaseTool{constructor(_0x5f2754={},_0x112521=null){const _0x4e304b=a0_0x19bb1a;super(_0x5f2754,_0x112521),this[_0x4e304b(0x1de)]=!![],this['isAsync']=!![],this[_0x4e304b(0x1f3)]=_0x5f2754['timeout']||0x1d4c0,this['replaceConfig']={...REPLACE_CONFIG,..._0x5f2754['replaceConfig']};}['getDescription'](){const _0x4da637=a0_0x19bb1a;return'\x0aFile\x20Content\x20Replace\x20Tool:\x20Replace\x20specific\x20content\x20within\x20files\x20with\x20precision.\x0a\x0aXML\x20USAGE:\x0a<file-content-replace>\x0a\x20\x20<file\x20path=\x22src/app.js\x22>\x0a\x20\x20\x20\x20<replace\x20mode=\x22trim\x22\x20lines-limit=\x225,7-10\x22>\x0a\x20\x20\x20\x20\x20\x20<old-content>\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20oldFunction\x20=\x20()\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27old\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20</old-content>\x0a\x20\x20\x20\x20\x20\x20<new-content>\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20newFunction\x20=\x20()\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27new\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20</new-content>\x0a\x20\x20\x20\x20</replace>\x0a\x20\x20</file>\x0a</file-content-replace>\x0a\x0aJSON\x20USAGE:\x0a```json\x0a{\x0a\x20\x20\x22toolId\x22:\x20\x22file-content-replace\x22,\x0a\x20\x20\x22files\x22:\x20[\x0a\x20\x20\x20\x20{\x0a\x20\x20\x20\x20\x20\x20\x22path\x22:\x20\x22src/app.js\x22,\x0a\x20\x20\x20\x20\x20\x20\x22replacements\x22:\x20[\x0a\x20\x20\x20\x20\x20\x20\x20\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22oldContent\x22:\x20\x22const\x20oldFunction\x20=\x20()\x20=>\x20{}\x22,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22newContent\x22:\x20\x22const\x20newFunction\x20=\x20()\x20=>\x20{}\x22,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22mode\x22:\x20\x22trim\x22,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22linesLimit\x22:\x20\x225,7-10\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20]\x0a\x20\x20\x20\x20}\x0a\x20\x20]\x0a}\x0a```\x0a\x0aPARAMETERS:\x0a\x0apath\x20(required):\x0a\x20\x20-\x20Path\x20to\x20the\x20file\x20to\x20modify\x0a\x20\x20-\x20Can\x20be\x20relative\x20or\x20absolute\x0a\x20\x20-\x20Examples:\x20\x22src/app.js\x22,\x20\x22./config.json\x22\x0a\x0aoldContent\x20(required):\x0a\x20\x20-\x20Content\x20to\x20find\x20and\x20replace\x0a\x20\x20-\x20Subject\x20to\x20trim\x20mode\x20processing\x0a\x20\x20-\x20Must\x20exist\x20in\x20file\x0a\x0anewContent\x20(required):\x0a\x20\x20-\x20Replacement\x20content\x0a\x20\x20-\x20Subject\x20to\x20trim\x20mode\x20processing\x0a\x20\x20-\x20Can\x20be\x20same\x20length,\x20shorter,\x20or\x20longer\x0a\x0amode\x20(optional):\x0a\x20\x20-\x20Whitespace\x20handling\x20mode\x0a\x20\x20-\x20Options:\x0a\x20\x20\x20\x20*\x20\x22trim\x22\x20(default):\x20Trim\x20all\x20whitespace\x20from\x20both\x20ends\x0a\x20\x20\x20\x20*\x20\x22newlines\x22:\x20Only\x20trim\x20newline\x20characters\x0a\x20\x20\x20\x20*\x20\x22none\x22:\x20Use\x20content\x20exactly\x20as\x20provided\x0a\x20\x20-\x20Helps\x20with\x20matching\x20despite\x20indentation\x20differences\x0a\x0alinesLimit\x20(optional):\x0a\x20\x20-\x20Restrict\x20replacement\x20to\x20specific\x20lines\x0a\x20\x20-\x20Format:\x20Comma-separated\x20line\x20numbers\x20or\x20ranges\x0a\x20\x20-\x20Examples:\x0a\x20\x20\x20\x20*\x20\x225\x22\x20-\x20Only\x20line\x205\x0a\x20\x20\x20\x20*\x20\x225,10,15\x22\x20-\x20Lines\x205,\x2010,\x20and\x2015\x0a\x20\x20\x20\x20*\x20\x225-10\x22\x20-\x20Lines\x205\x20through\x2010\x0a\x20\x20\x20\x20*\x20\x221-5,10,15-20\x22\x20-\x20Lines\x201-5,\x2010,\x20and\x2015-20\x0a\x20\x20-\x20Line\x20numbers\x20are\x201-based\x0a\x0aTRIM\x20MODES\x20EXPLAINED:\x0a\x0aMode:\x20\x22trim\x22\x20(Recommended\x20for\x20most\x20cases)\x0a\x20\x20Input:\x0a\x20\x20\x20\x20\x22\x20\x20const\x20x\x20=\x201;\x20\x20\x5cn\x22\x0a\x20\x20After\x20trim:\x0a\x20\x20\x20\x20\x22const\x20x\x20=\x201;\x22\x0a\x20\x20Use\x20when:\x20Indentation\x20may\x20vary\x0a\x0aMode:\x20\x22newlines\x22\x0a\x20\x20Input:\x0a\x20\x20\x20\x20\x22\x5cn\x20\x20const\x20x\x20=\x201;\x20\x20\x5cn\x22\x0a\x20\x20After\x20trim:\x0a\x20\x20\x20\x20\x22\x20\x20const\x20x\x20=\x201;\x20\x20\x22\x0a\x20\x20Use\x20when:\x20Preserving\x20internal\x20whitespace\x0a\x0aMode:\x20\x22none\x22\x0a\x20\x20Input:\x0a\x20\x20\x20\x20\x22\x20\x20const\x20x\x20=\x201;\x20\x20\x5cn\x22\x0a\x20\x20After\x20trim:\x0a\x20\x20\x20\x20\x22\x20\x20const\x20x\x20=\x201;\x20\x20\x5cn\x22\x0a\x20\x20Use\x20when:\x20Exact\x20character\x20match\x20needed\x0a\x0aEXAMPLES:\x0a\x0aExample\x201\x20-\x20Basic\x20replacement\x20with\x20trim:\x0a<file-content-replace>\x0a\x20\x20<file\x20path=\x22src/components/Button.js\x22>\x0a\x20\x20\x20\x20<replace\x20mode=\x22trim\x22>\x0a\x20\x20\x20\x20\x20\x20<old-content>\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20handleClick\x20=\x20(event)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27clicked\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20</old-content>\x0a\x20\x20\x20\x20\x20\x20<new-content>\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20handleClick\x20=\x20(event)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27clicked\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20props.onClick?.(event);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20</new-content>\x0a\x20\x20\x20\x20</replace>\x0a\x20\x20</file>\x0a</file-content-replace>\x0a\x0aExample\x202\x20-\x20Line-limited\x20replacement:\x0a<file-content-replace>\x0a\x20\x20<file\x20path=\x22src/App.js\x22>\x0a\x20\x20\x20\x20<replace\x20lines-limit=\x2210-20\x22>\x0a\x20\x20\x20\x20\x20\x20<old-content>const\x20API_URL\x20=\x20\x27http://localhost:3000\x27</old-content>\x0a\x20\x20\x20\x20\x20\x20<new-content>const\x20API_URL\x20=\x20process.env.API_URL</new-content>\x0a\x20\x20\x20\x20</replace>\x0a\x20\x20</file>\x0a</file-content-replace>\x0a\x0aExample\x203\x20-\x20Multiple\x20replacements\x20in\x20one\x20file:\x0a<file-content-replace>\x0a\x20\x20<file\x20path=\x22src/config.js\x22>\x0a\x20\x20\x20\x20<replace>\x0a\x20\x20\x20\x20\x20\x20<old-content>DEBUG\x20=\x20false</old-content>\x0a\x20\x20\x20\x20\x20\x20<new-content>DEBUG\x20=\x20true</new-content>\x0a\x20\x20\x20\x20</replace>\x0a\x20\x20\x20\x20<replace>\x0a\x20\x20\x20\x20\x20\x20<old-content>LOG_LEVEL\x20=\x20\x27error\x27</old-content>\x0a\x20\x20\x20\x20\x20\x20<new-content>LOG_LEVEL\x20=\x20\x27debug\x27</new-content>\x0a\x20\x20\x20\x20</replace>\x0a\x20\x20</file>\x0a</file-content-replace>\x0a\x0aExample\x204\x20-\x20Multiple\x20files:\x0a<file-content-replace>\x0a\x20\x20<file\x20path=\x22src/app.js\x22>\x0a\x20\x20\x20\x20<replace>\x0a\x20\x20\x20\x20\x20\x20<old-content>version\x20=\x20\x271.0.0\x27</old-content>\x0a\x20\x20\x20\x20\x20\x20<new-content>version\x20=\x20\x271.1.0\x27</new-content>\x0a\x20\x20\x20\x20</replace>\x0a\x20\x20</file>\x0a\x20\x20<file\x20path=\x22package.json\x22>\x0a\x20\x20\x20\x20<replace\x20mode=\x22none\x22>\x0a\x20\x20\x20\x20\x20\x20<old-content>\x22version\x22:\x20\x221.0.0\x22</old-content>\x0a\x20\x20\x20\x20\x20\x20<new-content>\x22version\x22:\x20\x221.1.0\x22</new-content>\x0a\x20\x20\x20\x20</replace>\x0a\x20\x20</file>\x0a</file-content-replace>\x0a\x0aExample\x205\x20-\x20Exact\x20whitespace\x20matching:\x0a<file-content-replace>\x0a\x20\x20<file\x20path=\x22Makefile\x22>\x0a\x20\x20\x20\x20<replace\x20mode=\x22none\x22>\x0a\x20\x20\x20\x20\x20\x20<old-content>\x5ctbuild:\x5cn\x5ct\x5ctgcc</old-content>\x0a\x20\x20\x20\x20\x20\x20<new-content>\x5ctbuild:\x5cn\x5ct\x5ctclang</new-content>\x0a\x20\x20\x20\x20</replace>\x0a\x20\x20</file>\x0a</file-content-replace>\x0a\x0aFEATURES:\x0a\x0a✓\x20Automatic\x20backup\x20creation\x20(.bak\x20files)\x0a✓\x20Before/after\x20diff\x20reports\x0a✓\x20Replacement\x20counting\x20and\x20statistics\x0a✓\x20Multi-file\x20operations\x0a✓\x20Line-limited\x20replacements\x0a✓\x20Intelligent\x20whitespace\x20handling\x0a✓\x20Security\x20validation\x20(path\x20access)\x0a✓\x20Creates\x20parent\x20directories\x20if\x20needed\x0a\x0aLIMITATIONS:\x0a\x0a-\x20Maximum\x20file\x20size:\x20'+REPLACE_CONFIG[_0x4da637(0x1d1)]/(0x400*0x400)+'MB\x0a-\x20Maximum\x20old\x20content\x20size:\x20'+REPLACE_CONFIG['MAX_OLD_CONTENT_SIZE']/0x400+'KB\x0a-\x20Maximum\x20new\x20content\x20size:\x20'+REPLACE_CONFIG[_0x4da637(0x1c3)]/0x400+'KB\x0a-\x20Maximum\x20replacements\x20per\x20file:\x20'+REPLACE_CONFIG['MAX_REPLACEMENTS_PER_FILE']+_0x4da637(0x1b1)+REPLACE_CONFIG[_0x4da637(0x1c2)]+_0x4da637(0x1ce);}['parseParameters'](_0x294c47){const _0x31f754=a0_0x19bb1a;try{if(_0x294c47[_0x31f754(0x1b5)]()['startsWith']('{'))return this[_0x31f754(0x1fa)](_0x294c47);return this[_0x31f754(0x1ae)](_0x294c47);}catch(_0x3a6c60){this['logger']?.[_0x31f754(0x1ab)](_0x31f754(0x1d4),{'error':_0x3a6c60[_0x31f754(0x1f8)]});throw new Error('Parameter\x20parsing\x20failed:\x20'+_0x3a6c60[_0x31f754(0x1f8)]);}}[a0_0x19bb1a(0x1fa)](_0x25c408){const _0x1b5a78=a0_0x19bb1a,_0x41f18d=JSON['parse'](_0x25c408);if(!_0x41f18d['files']||!Array[_0x1b5a78(0x1d0)](_0x41f18d['files']))throw new Error('JSON\x20must\x20have\x20\x22files\x22\x20array');return{'files':_0x41f18d[_0x1b5a78(0x1bc)][_0x1b5a78(0x1df)](_0x4b9005=>({'path':_0x4b9005['path'],'replacements':(_0x4b9005['replacements']||[])[_0x1b5a78(0x1df)](_0x3e560e=>({'oldContent':_0x3e560e[_0x1b5a78(0x1d9)],'newContent':_0x3e560e[_0x1b5a78(0x1f4)],'mode':_0x3e560e['mode']||REPLACE_CONFIG['DEFAULT_TRIM_MODE'],'linesLimit':_0x3e560e[_0x1b5a78(0x19a)]||null}))}))};}['parseXML'](_0x4e8ea6){const _0x3c6528=a0_0x19bb1a,_0x3a4ca3=[],_0x51dffd=/<file\s+path="([^"]+)">([\s\S]*?)<\/file>/gi;let _0x4e8e1f;while((_0x4e8e1f=_0x51dffd[_0x3c6528(0x1cb)](_0x4e8ea6))!==null){const _0x7c7cd2=_0x4e8e1f[0x1],_0x382fa2=_0x4e8e1f[0x2],_0x26b59a=[],_0x17bf28=/<replace(?:\s+([^>]*?))?>([\s\S]*?)<\/replace>/gi;let _0xf82867;while((_0xf82867=_0x17bf28[_0x3c6528(0x1cb)](_0x382fa2))!==null){const _0x49aee3=_0xf82867[0x1]||'',_0x10e5f2=_0xf82867[0x2],_0x3ad65f=this[_0x3c6528(0x1c6)](_0x49aee3,'mode')||REPLACE_CONFIG[_0x3c6528(0x1c5)],_0x2055eb=this['extractAttribute'](_0x49aee3,_0x3c6528(0x1f0)),_0x2d1557=/<old-content>([\s\S]*?)<\/old-content>/i['exec'](_0x10e5f2);if(!_0x2d1557){this[_0x3c6528(0x1c9)]?.['warn'](_0x3c6528(0x1dd));continue;}const _0x5baccd=_0x2d1557[0x1],_0x16e6af=/<new-content>([\s\S]*?)<\/new-content>/i[_0x3c6528(0x1cb)](_0x10e5f2);if(!_0x16e6af){this[_0x3c6528(0x1c9)]?.[_0x3c6528(0x1b6)](_0x3c6528(0x19d));continue;}const _0x1bd86f=_0x16e6af[0x1],_0x2e6918=this['applyTrimMode'](_0x5baccd,_0x3ad65f),_0x47a77e=this['applyTrimMode'](_0x1bd86f,_0x3ad65f);_0x26b59a['push']({'oldContent':_0x2e6918,'newContent':_0x47a77e,'mode':_0x3ad65f,'linesLimit':_0x2055eb});}_0x26b59a[_0x3c6528(0x1a4)]>0x0&&_0x3a4ca3['push']({'path':_0x7c7cd2,'replacements':_0x26b59a});}return{'files':_0x3a4ca3};}[a0_0x19bb1a(0x1c6)](_0x58d1e6,_0x59ed1b){const _0x364355=a0_0x19bb1a,_0x324499=new RegExp(_0x59ed1b+_0x364355(0x1b0),'i'),_0x1534ed=_0x324499[_0x364355(0x1cb)](_0x58d1e6);return _0x1534ed?_0x1534ed[0x1]:null;}[a0_0x19bb1a(0x1a5)](_0x49c83,_0x3dc50c){const _0x2374da=a0_0x19bb1a;switch(_0x3dc50c){case _0x2374da(0x202):return _0x49c83['replace'](/^\n+|\n+$/g,'');case'none':return _0x49c83;case _0x2374da(0x1b5):default:return _0x49c83['trim']();}}[a0_0x19bb1a(0x1bf)](){const _0x2ede20=a0_0x19bb1a;return[_0x2ede20(0x1bc)];}['customValidateParameters'](_0x4eae62){const _0x1fda5b=a0_0x19bb1a,_0x41d49f=[];if(!_0x4eae62['files']||!Array['isArray'](_0x4eae62[_0x1fda5b(0x1bc)]))_0x41d49f['push'](_0x1fda5b(0x1ad));else{_0x4eae62['files'][_0x1fda5b(0x1a4)]===0x0&&_0x41d49f[_0x1fda5b(0x1ef)](_0x1fda5b(0x1b7));_0x4eae62[_0x1fda5b(0x1bc)][_0x1fda5b(0x1a4)]>this['replaceConfig']['MAX_FILES_PER_OPERATION']&&_0x41d49f['push'](_0x1fda5b(0x1a3)+this['replaceConfig'][_0x1fda5b(0x1c2)]+_0x1fda5b(0x1ee));for(const _0x1fb96c of _0x4eae62['files']){!_0x1fb96c[_0x1fda5b(0x1be)]&&_0x41d49f[_0x1fda5b(0x1ef)]('Each\x20file\x20must\x20have\x20a\x20path');_0x1fb96c['path']&&_0x1fb96c[_0x1fda5b(0x1be)]['includes']('..')&&_0x41d49f['push'](_0x1fda5b(0x1d7)+_0x1fb96c['path']);if(!_0x1fb96c[_0x1fda5b(0x1ca)]||!Array[_0x1fda5b(0x1d0)](_0x1fb96c[_0x1fda5b(0x1ca)]))_0x41d49f[_0x1fda5b(0x1ef)](_0x1fda5b(0x1ea)+_0x1fb96c['path']+_0x1fda5b(0x1b3));else{if(_0x1fb96c['replacements'][_0x1fda5b(0x1a4)]===0x0)_0x41d49f['push'](_0x1fda5b(0x1ea)+_0x1fb96c[_0x1fda5b(0x1be)]+'\x20replacements\x20array\x20cannot\x20be\x20empty');else for(const _0x25c753 of _0x1fb96c[_0x1fda5b(0x1ca)]){!_0x25c753['oldContent']&&_0x25c753[_0x1fda5b(0x1d9)]!==''&&_0x41d49f['push']('Replacement\x20in\x20'+_0x1fb96c['path']+_0x1fda5b(0x19b)),!_0x25c753['newContent']&&_0x25c753['newContent']!==''&&_0x41d49f['push']('Replacement\x20in\x20'+_0x1fb96c[_0x1fda5b(0x1be)]+'\x20missing\x20newContent'),_0x25c753['oldContent']&&_0x25c753['oldContent'][_0x1fda5b(0x1a4)]>this['replaceConfig'][_0x1fda5b(0x197)]&&_0x41d49f['push'](_0x1fda5b(0x1b2)+this[_0x1fda5b(0x1f5)]['MAX_OLD_CONTENT_SIZE']/0x400+'KB)'),_0x25c753[_0x1fda5b(0x1f4)]&&_0x25c753['newContent'][_0x1fda5b(0x1a4)]>this[_0x1fda5b(0x1f5)]['MAX_NEW_CONTENT_SIZE']&&_0x41d49f['push'](_0x1fda5b(0x1a8)+this[_0x1fda5b(0x1f5)]['MAX_NEW_CONTENT_SIZE']/0x400+'KB)'),_0x25c753[_0x1fda5b(0x1c0)]&&!['trim',_0x1fda5b(0x202),_0x1fda5b(0x1ec)][_0x1fda5b(0x1fd)](_0x25c753['mode'])&&_0x41d49f['push']('Invalid\x20mode:\x20'+_0x25c753[_0x1fda5b(0x1c0)]+'.\x20Must\x20be\x20\x27trim\x27,\x20\x27newlines\x27,\x20or\x20\x27none\x27');}}}}if(_0x41d49f['length']>0x0)throw new Error('Parameter\x20validation\x20failed:\x20'+_0x41d49f['join'](',\x20'));return{'valid':!![],'errors':[]};}async['execute'](_0x211131,_0x2797b2){const _0x34e9b5=a0_0x19bb1a,{files:_0x47b86f}=_0x211131,{projectDir:_0xa5b63c,agentId:_0x92a78a,directoryAccess:_0x26263f}=_0x2797b2;let _0x35c7cc=_0xa5b63c||process['cwd']();_0x26263f&&_0x26263f[_0x34e9b5(0x1a2)]&&(_0x35c7cc=_0x26263f['workingDirectory'],this[_0x34e9b5(0x1c9)]?.['info'](_0x34e9b5(0x199),{'workingDirectory':_0x26263f['workingDirectory'],'agentId':_0x92a78a}));this[_0x34e9b5(0x1c9)]?.[_0x34e9b5(0x1f9)]('Executing\x20file\x20content\x20replace',{'fileCount':_0x47b86f['length'],'workingDirectory':_0x35c7cc,'agentId':_0x92a78a});const _0x34bb09=[],_0x57e659={'filesProcessed':0x0,'filesModified':0x0,'totalReplacements':0x0,'backupsCreated':0x0,'errors':0x0};for(const _0x3296c3 of _0x47b86f){try{const _0x535256=await this['processFile'](_0x3296c3,_0x35c7cc,_0x26263f);_0x34bb09[_0x34e9b5(0x1ef)](_0x535256),_0x57e659['filesProcessed']++,_0x535256['replacementsMade']>0x0&&(_0x57e659['filesModified']++,_0x57e659[_0x34e9b5(0x1af)]+=_0x535256[_0x34e9b5(0x1f1)]),_0x535256[_0x34e9b5(0x1fb)]&&_0x57e659[_0x34e9b5(0x1e5)]++;}catch(_0x3a1910){this['logger']?.['error']('Error\x20processing\x20file\x20'+_0x3296c3['path'],{'error':_0x3a1910['message']}),_0x34bb09[_0x34e9b5(0x1ef)]({'filePath':_0x3296c3[_0x34e9b5(0x1be)],'success':![],'error':_0x3a1910['message']}),_0x57e659['errors']++;}}return{'success':_0x57e659['errors']===0x0,'results':_0x34bb09,'statistics':_0x57e659,'summary':this[_0x34e9b5(0x1b8)](_0x57e659),'toolUsed':_0x34e9b5(0x1a6)};}async['processFile'](_0x7901ec,_0x43e2be,_0x42a01d){const _0x10a1f5=a0_0x19bb1a,{path:_0x390276,replacements:_0x48371c}=_0x7901ec,_0x3152f4=a0_0x11c690[_0x10a1f5(0x1e6)](_0x390276)?_0x390276:a0_0x11c690[_0x10a1f5(0x198)](_0x43e2be,_0x390276);if(_0x42a01d){const _0x58e612=this[_0x10a1f5(0x1eb)](_0x3152f4,_0x43e2be,_0x42a01d);if(!_0x58e612)throw new Error(_0x10a1f5(0x1c8)+_0x390276);}try{await a0_0x58b70e[_0x10a1f5(0x1f7)](_0x3152f4);}catch(_0x5b9149){throw new Error(_0x10a1f5(0x1e1)+_0x390276);}const _0x49f216=await a0_0x58b70e['stat'](_0x3152f4);if(_0x49f216['size']>this[_0x10a1f5(0x1f5)][_0x10a1f5(0x1d1)])throw new Error('File\x20too\x20large\x20(max\x20'+this['replaceConfig']['MAX_FILE_SIZE']/(0x400*0x400)+_0x10a1f5(0x1d6)+_0x390276);let _0x3677f1=await a0_0x58b70e[_0x10a1f5(0x200)](_0x3152f4,'utf-8');const _0x364bb2=_0x3677f1;let _0x437981=![];if(this[_0x10a1f5(0x1f5)][_0x10a1f5(0x203)]){const _0x3d2af3=_0x3152f4+this['replaceConfig']['BACKUP_EXTENSION'];try{await a0_0x58b70e[_0x10a1f5(0x1cd)](_0x3d2af3,_0x364bb2,_0x10a1f5(0x1ff)),_0x437981=!![];}catch(_0x2ec4da){this['logger']?.['warn'](_0x10a1f5(0x1a1)+_0x2ec4da[_0x10a1f5(0x1f8)]);}}let _0x2f33f6=0x0;const _0x4b04cc=[];for(const _0x1c0c7b of _0x48371c){const _0x449df1=await this[_0x10a1f5(0x1a7)](_0x3677f1,_0x1c0c7b[_0x10a1f5(0x1d9)],_0x1c0c7b[_0x10a1f5(0x1f4)],_0x1c0c7b['linesLimit'],_0x1c0c7b[_0x10a1f5(0x1c0)]);_0x3677f1=_0x449df1[_0x10a1f5(0x1f4)],_0x2f33f6+=_0x449df1['count'],_0x4b04cc['push']({'oldContent':_0x1c0c7b[_0x10a1f5(0x1d9)]['substring'](0x0,0x32)+(_0x1c0c7b['oldContent']['length']>0x32?_0x10a1f5(0x1fe):''),'newContent':_0x1c0c7b['newContent'][_0x10a1f5(0x1ac)](0x0,0x32)+(_0x1c0c7b[_0x10a1f5(0x1f4)][_0x10a1f5(0x1a4)]>0x32?'...':''),'count':_0x449df1['count'],'mode':_0x1c0c7b[_0x10a1f5(0x1c0)],'linesLimit':_0x1c0c7b[_0x10a1f5(0x19a)]});}_0x2f33f6>0x0&&await a0_0x58b70e[_0x10a1f5(0x1cd)](_0x3152f4,_0x3677f1,_0x10a1f5(0x1ff));const _0x4dc71b=_0x2f33f6>0x0?this[_0x10a1f5(0x1a0)](_0x364bb2,_0x3677f1):null;return{'filePath':_0x390276,'resolvedPath':_0x3152f4,'success':!![],'replacementsMade':_0x2f33f6,'backupCreated':_0x437981,'replacementDetails':_0x4b04cc,'diff':_0x4dc71b,'message':_0x2f33f6>0x0?_0x10a1f5(0x1da)+_0x2f33f6+'\x20replacement(s)\x20in\x20'+_0x390276:'No\x20replacements\x20made\x20in\x20'+_0x390276+_0x10a1f5(0x1c7)};}['isPathAccessible'](_0x14a323,_0x559b91,_0x133e7a){const _0x9b2935=a0_0x19bb1a,_0x548eab=a0_0x11c690[_0x9b2935(0x1e7)](_0x559b91,_0x14a323);if(!_0x548eab[_0x9b2935(0x1e8)]('..')&&!a0_0x11c690['isAbsolute'](_0x548eab))return!![];if(_0x133e7a['writeEnabledDirectories'])for(const _0xb8a267 of _0x133e7a[_0x9b2935(0x1e3)]){const _0x491c27=a0_0x11c690['relative'](_0xb8a267,_0x14a323);if(!_0x491c27['startsWith']('..')&&!a0_0x11c690[_0x9b2935(0x1e6)](_0x491c27))return!![];}return![];}async[a0_0x19bb1a(0x1a7)](_0x1760bd,_0x564158,_0x5657e2,_0x1b3537,_0x1b542d){const _0x1b9ec2=a0_0x19bb1a;if(!_0x1b3537){const _0x56b6eb=this[_0x1b9ec2(0x1bd)](_0x1760bd,_0x564158);if(_0x56b6eb===0x0)return{'newContent':_0x1760bd,'count':0x0};const _0x2e73b3=_0x1760bd[_0x1b9ec2(0x1e9)](_0x564158)[_0x1b9ec2(0x1c4)](_0x5657e2);return{'newContent':_0x2e73b3,'count':_0x56b6eb};}const _0x22a922=this[_0x1b9ec2(0x1ba)](_0x1b3537),_0x17a050=_0x1760bd[_0x1b9ec2(0x1e9)]('\x0a');let _0x593383=0x0;for(let _0x331204=0x0;_0x331204<_0x17a050['length'];_0x331204++){const _0x5b7e3f=_0x331204+0x1;if(_0x22a922[_0x1b9ec2(0x1aa)](_0x5b7e3f)){if(_0x17a050[_0x331204][_0x1b9ec2(0x1fd)](_0x564158)){const _0x145940=this['countOccurrences'](_0x17a050[_0x331204],_0x564158);_0x17a050[_0x331204]=_0x17a050[_0x331204]['split'](_0x564158)[_0x1b9ec2(0x1c4)](_0x5657e2),_0x593383+=_0x145940;}}}return{'newContent':_0x17a050['join']('\x0a'),'count':_0x593383};}['countOccurrences'](_0x10d06a,_0x2a3e6c){const _0x201876=a0_0x19bb1a;if(!_0x2a3e6c)return 0x0;return _0x10d06a[_0x201876(0x1e9)](_0x2a3e6c)[_0x201876(0x1a4)]-0x1;}['parseLineRanges'](_0x4f9679){const _0x1860d1=a0_0x19bb1a,_0x36d334=new Set();if(!_0x4f9679||_0x4f9679[_0x1860d1(0x1b5)]()==='')return _0x36d334;const _0x416a45=_0x4f9679['split'](',');for(const _0x5c5ef6 of _0x416a45){const _0x2ea3a4=_0x5c5ef6[_0x1860d1(0x1b5)]();if(_0x2ea3a4==='')continue;if(_0x2ea3a4['includes']('-')){const [_0x32624f,_0x593ba2]=_0x2ea3a4[_0x1860d1(0x1e9)]('-')['map'](_0x3afda0=>parseInt(_0x3afda0['trim'](),0xa));if(!isNaN(_0x32624f)&&!isNaN(_0x593ba2)&&_0x593ba2-_0x32624f<this['replaceConfig'][_0x1860d1(0x1b4)])for(let _0x4732a7=_0x32624f;_0x4732a7<=_0x593ba2;_0x4732a7++){_0x36d334[_0x1860d1(0x1c1)](_0x4732a7);}}else{const _0x2fb35f=parseInt(_0x2ea3a4,0xa);!isNaN(_0x2fb35f)&&_0x36d334[_0x1860d1(0x1c1)](_0x2fb35f);}}return _0x36d334;}[a0_0x19bb1a(0x1a0)](_0x406bc2,_0x1f8c69){const _0x172b92=a0_0x19bb1a,_0x33ccf9=_0x406bc2['split']('\x0a'),_0x3e97d1=_0x1f8c69['split']('\x0a');let _0x10b914=-0x1,_0x5cf0d2=-0x1;const _0x3143b9=Math['max'](_0x33ccf9[_0x172b92(0x1a4)],_0x3e97d1['length']);for(let _0x35ae53=0x0;_0x35ae53<_0x3143b9;_0x35ae53++){const _0x4f3232=_0x35ae53<_0x33ccf9[_0x172b92(0x1a4)]?_0x33ccf9[_0x35ae53]:'',_0x4baf86=_0x35ae53<_0x3e97d1['length']?_0x3e97d1[_0x35ae53]:'';if(_0x4f3232!==_0x4baf86){if(_0x10b914===-0x1)_0x10b914=_0x35ae53;_0x5cf0d2=_0x35ae53;}}if(_0x10b914===-0x1)return _0x172b92(0x1bb);const _0x297a7a=this['replaceConfig']['DIFF_CONTEXT_LINES'],_0x2a5e54=Math['max'](0x0,_0x10b914-_0x297a7a),_0x44ddc7=Math['min'](_0x3143b9-0x1,_0x5cf0d2+_0x297a7a);if(_0x44ddc7-_0x2a5e54>this['replaceConfig']['MAX_DIFF_LINES'])return'Diff\x20too\x20large\x20('+(_0x44ddc7-_0x2a5e54+0x1)+'\x20lines),\x20showing\x20summary\x20only:\x0a'+('Changed\x20lines:\x20'+(_0x10b914+0x1)+_0x172b92(0x1b9)+(_0x5cf0d2+0x1));let _0x268d18='@@\x20-'+(_0x2a5e54+0x1)+','+(_0x44ddc7-_0x2a5e54+0x1)+'\x20+'+(_0x2a5e54+0x1)+','+(_0x44ddc7-_0x2a5e54+0x1)+_0x172b92(0x1d2);for(let _0x157a8c=_0x2a5e54;_0x157a8c<=_0x44ddc7;_0x157a8c++){const _0x523ba2=_0x157a8c<_0x33ccf9['length']?_0x33ccf9[_0x157a8c]:'',_0x19d02d=_0x157a8c<_0x3e97d1[_0x172b92(0x1a4)]?_0x3e97d1[_0x157a8c]:'';_0x523ba2===_0x19d02d?_0x268d18+='\x20\x20'+_0x523ba2+'\x0a':(_0x268d18+='-\x20'+_0x523ba2+'\x0a',_0x268d18+='+\x20'+_0x19d02d+'\x0a');}return _0x268d18;}['generateSummary'](_0x492de3){const _0x5bada2=a0_0x19bb1a;return(_0x5bada2(0x1d3)+_0x492de3[_0x5bada2(0x1f2)]+'\x20file(s)\x0aModified\x20'+_0x492de3[_0x5bada2(0x1e4)]+_0x5bada2(0x1e2)+_0x492de3['totalReplacements']+_0x5bada2(0x1fc)+_0x492de3['backupsCreated']+'\x0aErrors:\x20'+_0x492de3[_0x5bada2(0x1cc)]+_0x5bada2(0x1cf))[_0x5bada2(0x1b5)]();}async['cleanup'](_0x2eaeaa){const _0x744618=a0_0x19bb1a;this['logger']?.['info'](_0x744618(0x1e0),{'operationId':_0x2eaeaa});}}export default FileContentReplaceTool;
|