codeep 1.0.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.
Files changed (103) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +576 -0
  3. package/dist/api/index.d.ts +8 -0
  4. package/dist/api/index.js +421 -0
  5. package/dist/app.d.ts +2 -0
  6. package/dist/app.js +1406 -0
  7. package/dist/components/AgentProgress.d.ts +33 -0
  8. package/dist/components/AgentProgress.js +97 -0
  9. package/dist/components/Export.d.ts +8 -0
  10. package/dist/components/Export.js +27 -0
  11. package/dist/components/Help.d.ts +2 -0
  12. package/dist/components/Help.js +3 -0
  13. package/dist/components/Input.d.ts +9 -0
  14. package/dist/components/Input.js +89 -0
  15. package/dist/components/Loading.d.ts +9 -0
  16. package/dist/components/Loading.js +31 -0
  17. package/dist/components/Login.d.ts +7 -0
  18. package/dist/components/Login.js +77 -0
  19. package/dist/components/Logo.d.ts +8 -0
  20. package/dist/components/Logo.js +89 -0
  21. package/dist/components/LogoutPicker.d.ts +8 -0
  22. package/dist/components/LogoutPicker.js +61 -0
  23. package/dist/components/Message.d.ts +10 -0
  24. package/dist/components/Message.js +234 -0
  25. package/dist/components/MessageList.d.ts +10 -0
  26. package/dist/components/MessageList.js +8 -0
  27. package/dist/components/ProjectPermission.d.ts +7 -0
  28. package/dist/components/ProjectPermission.js +52 -0
  29. package/dist/components/Search.d.ts +10 -0
  30. package/dist/components/Search.js +30 -0
  31. package/dist/components/SessionPicker.d.ts +9 -0
  32. package/dist/components/SessionPicker.js +88 -0
  33. package/dist/components/Sessions.d.ts +12 -0
  34. package/dist/components/Sessions.js +102 -0
  35. package/dist/components/Settings.d.ts +7 -0
  36. package/dist/components/Settings.js +162 -0
  37. package/dist/components/Status.d.ts +2 -0
  38. package/dist/components/Status.js +12 -0
  39. package/dist/config/config.test.d.ts +1 -0
  40. package/dist/config/config.test.js +157 -0
  41. package/dist/config/index.d.ts +121 -0
  42. package/dist/config/index.js +555 -0
  43. package/dist/config/providers.d.ts +43 -0
  44. package/dist/config/providers.js +82 -0
  45. package/dist/config/providers.test.d.ts +1 -0
  46. package/dist/config/providers.test.js +132 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +38 -0
  49. package/dist/utils/agent.d.ts +37 -0
  50. package/dist/utils/agent.js +627 -0
  51. package/dist/utils/codeReview.d.ts +36 -0
  52. package/dist/utils/codeReview.js +390 -0
  53. package/dist/utils/context.d.ts +49 -0
  54. package/dist/utils/context.js +216 -0
  55. package/dist/utils/diffPreview.d.ts +57 -0
  56. package/dist/utils/diffPreview.js +335 -0
  57. package/dist/utils/export.d.ts +19 -0
  58. package/dist/utils/export.js +94 -0
  59. package/dist/utils/git.d.ts +85 -0
  60. package/dist/utils/git.js +399 -0
  61. package/dist/utils/git.test.d.ts +1 -0
  62. package/dist/utils/git.test.js +193 -0
  63. package/dist/utils/history.d.ts +93 -0
  64. package/dist/utils/history.js +348 -0
  65. package/dist/utils/interactive.d.ts +34 -0
  66. package/dist/utils/interactive.js +206 -0
  67. package/dist/utils/keychain.d.ts +17 -0
  68. package/dist/utils/keychain.js +160 -0
  69. package/dist/utils/learning.d.ts +89 -0
  70. package/dist/utils/learning.js +330 -0
  71. package/dist/utils/logger.d.ts +33 -0
  72. package/dist/utils/logger.js +130 -0
  73. package/dist/utils/project.d.ts +86 -0
  74. package/dist/utils/project.js +415 -0
  75. package/dist/utils/project.test.d.ts +1 -0
  76. package/dist/utils/project.test.js +212 -0
  77. package/dist/utils/ratelimit.d.ts +26 -0
  78. package/dist/utils/ratelimit.js +132 -0
  79. package/dist/utils/ratelimit.test.d.ts +1 -0
  80. package/dist/utils/ratelimit.test.js +131 -0
  81. package/dist/utils/retry.d.ts +28 -0
  82. package/dist/utils/retry.js +109 -0
  83. package/dist/utils/retry.test.d.ts +1 -0
  84. package/dist/utils/retry.test.js +163 -0
  85. package/dist/utils/search.d.ts +11 -0
  86. package/dist/utils/search.js +29 -0
  87. package/dist/utils/shell.d.ts +45 -0
  88. package/dist/utils/shell.js +242 -0
  89. package/dist/utils/skills.d.ts +144 -0
  90. package/dist/utils/skills.js +1137 -0
  91. package/dist/utils/smartContext.d.ts +29 -0
  92. package/dist/utils/smartContext.js +441 -0
  93. package/dist/utils/tools.d.ts +224 -0
  94. package/dist/utils/tools.js +731 -0
  95. package/dist/utils/update.d.ts +22 -0
  96. package/dist/utils/update.js +128 -0
  97. package/dist/utils/validation.d.ts +28 -0
  98. package/dist/utils/validation.js +141 -0
  99. package/dist/utils/validation.test.d.ts +1 -0
  100. package/dist/utils/validation.test.js +164 -0
  101. package/dist/utils/verify.d.ts +78 -0
  102. package/dist/utils/verify.js +464 -0
  103. package/package.json +68 -0
@@ -0,0 +1,731 @@
1
+ /**
2
+ * Agent tools - definitions and execution
3
+ */
4
+ import { existsSync, readdirSync, statSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, rmSync } from 'fs';
5
+ import { join, dirname, relative, resolve, isAbsolute } from 'path';
6
+ import { executeCommand } from './shell';
7
+ import { recordWrite, recordEdit, recordDelete, recordMkdir, recordCommand } from './history';
8
+ // Tool definitions for system prompt
9
+ export const AGENT_TOOLS = {
10
+ read_file: {
11
+ name: 'read_file',
12
+ description: 'Read the contents of a file. Use this to examine existing code.',
13
+ parameters: {
14
+ path: { type: 'string', description: 'Path to the file relative to project root', required: true },
15
+ },
16
+ },
17
+ write_file: {
18
+ name: 'write_file',
19
+ description: 'Create a new file or completely overwrite an existing file with new content.',
20
+ parameters: {
21
+ path: { type: 'string', description: 'Path to the file relative to project root', required: true },
22
+ content: { type: 'string', description: 'The complete content to write to the file', required: true },
23
+ },
24
+ },
25
+ edit_file: {
26
+ name: 'edit_file',
27
+ description: 'Edit an existing file by replacing specific text. Use for targeted changes.',
28
+ parameters: {
29
+ path: { type: 'string', description: 'Path to the file relative to project root', required: true },
30
+ old_text: { type: 'string', description: 'The exact text to find and replace', required: true },
31
+ new_text: { type: 'string', description: 'The new text to replace with', required: true },
32
+ },
33
+ },
34
+ delete_file: {
35
+ name: 'delete_file',
36
+ description: 'Delete a file or directory from the project. For directories, deletes recursively.',
37
+ parameters: {
38
+ path: { type: 'string', description: 'Path to the file or directory relative to project root', required: true },
39
+ },
40
+ },
41
+ list_files: {
42
+ name: 'list_files',
43
+ description: 'List files and directories in a path. Use to explore project structure.',
44
+ parameters: {
45
+ path: { type: 'string', description: 'Path to directory relative to project root (use "." for root)', required: true },
46
+ recursive: { type: 'boolean', description: 'Whether to list recursively (default: false)', required: false },
47
+ },
48
+ },
49
+ create_directory: {
50
+ name: 'create_directory',
51
+ description: 'Create a new directory (folder). Creates parent directories if needed.',
52
+ parameters: {
53
+ path: { type: 'string', description: 'Path to the directory to create, relative to project root', required: true },
54
+ },
55
+ },
56
+ execute_command: {
57
+ name: 'execute_command',
58
+ description: 'Execute a shell command. Use for npm, git, build tools, tests, etc.',
59
+ parameters: {
60
+ command: { type: 'string', description: 'The command to run (e.g., npm, git, node)', required: true },
61
+ args: { type: 'array', description: 'Command arguments as array (e.g., ["install", "lodash"])', required: false },
62
+ },
63
+ },
64
+ search_code: {
65
+ name: 'search_code',
66
+ description: 'Search for a text pattern in the codebase. Returns matching files and lines.',
67
+ parameters: {
68
+ pattern: { type: 'string', description: 'Text or regex pattern to search for', required: true },
69
+ path: { type: 'string', description: 'Path to search in (default: entire project)', required: false },
70
+ },
71
+ },
72
+ fetch_url: {
73
+ name: 'fetch_url',
74
+ description: 'Fetch content from a URL (documentation, APIs, web pages). Returns text content.',
75
+ parameters: {
76
+ url: { type: 'string', description: 'The URL to fetch content from', required: true },
77
+ },
78
+ },
79
+ };
80
+ /**
81
+ * Format tool definitions for system prompt (text-based fallback)
82
+ */
83
+ export function formatToolDefinitions() {
84
+ const lines = [];
85
+ for (const [name, tool] of Object.entries(AGENT_TOOLS)) {
86
+ lines.push(`### ${name}`);
87
+ lines.push(tool.description);
88
+ lines.push('Parameters:');
89
+ for (const [param, info] of Object.entries(tool.parameters)) {
90
+ const required = info.required ? '(required)' : '(optional)';
91
+ lines.push(` - ${param}: ${info.type} ${required} - ${info.description}`);
92
+ }
93
+ lines.push('');
94
+ }
95
+ return lines.join('\n');
96
+ }
97
+ /**
98
+ * Get tools in OpenAI Function Calling format
99
+ */
100
+ export function getOpenAITools() {
101
+ return Object.entries(AGENT_TOOLS).map(([name, tool]) => {
102
+ const properties = {};
103
+ const required = [];
104
+ for (const [param, info] of Object.entries(tool.parameters)) {
105
+ const paramInfo = info;
106
+ if (paramInfo.type === 'array') {
107
+ properties[param] = {
108
+ type: 'array',
109
+ description: paramInfo.description,
110
+ items: { type: 'string' },
111
+ };
112
+ }
113
+ else {
114
+ properties[param] = {
115
+ type: paramInfo.type,
116
+ description: paramInfo.description,
117
+ };
118
+ }
119
+ if (paramInfo.required) {
120
+ required.push(param);
121
+ }
122
+ }
123
+ return {
124
+ type: 'function',
125
+ function: {
126
+ name,
127
+ description: tool.description,
128
+ parameters: {
129
+ type: 'object',
130
+ properties,
131
+ required,
132
+ },
133
+ },
134
+ };
135
+ });
136
+ }
137
+ /**
138
+ * Get tools in Anthropic Tool Use format
139
+ */
140
+ export function getAnthropicTools() {
141
+ return Object.entries(AGENT_TOOLS).map(([name, tool]) => {
142
+ const properties = {};
143
+ const required = [];
144
+ for (const [param, info] of Object.entries(tool.parameters)) {
145
+ const paramInfo = info;
146
+ if (paramInfo.type === 'array') {
147
+ properties[param] = {
148
+ type: 'array',
149
+ description: paramInfo.description,
150
+ items: { type: 'string' },
151
+ };
152
+ }
153
+ else {
154
+ properties[param] = {
155
+ type: paramInfo.type,
156
+ description: paramInfo.description,
157
+ };
158
+ }
159
+ if (paramInfo.required) {
160
+ required.push(param);
161
+ }
162
+ }
163
+ return {
164
+ name,
165
+ description: tool.description,
166
+ input_schema: {
167
+ type: 'object',
168
+ properties,
169
+ required,
170
+ },
171
+ };
172
+ });
173
+ }
174
+ /**
175
+ * Parse tool calls from OpenAI response
176
+ */
177
+ export function parseOpenAIToolCalls(toolCalls) {
178
+ if (!toolCalls || !Array.isArray(toolCalls))
179
+ return [];
180
+ return toolCalls.map(tc => {
181
+ let parameters = {};
182
+ try {
183
+ parameters = JSON.parse(tc.function?.arguments || '{}');
184
+ }
185
+ catch {
186
+ parameters = {};
187
+ }
188
+ return {
189
+ tool: tc.function?.name || '',
190
+ parameters,
191
+ id: tc.id,
192
+ };
193
+ }).filter(tc => tc.tool);
194
+ }
195
+ /**
196
+ * Parse tool calls from Anthropic response
197
+ */
198
+ export function parseAnthropicToolCalls(content) {
199
+ if (!content || !Array.isArray(content))
200
+ return [];
201
+ return content
202
+ .filter(block => block.type === 'tool_use')
203
+ .map(block => ({
204
+ tool: block.name || '',
205
+ parameters: block.input || {},
206
+ id: block.id,
207
+ }))
208
+ .filter(tc => tc.tool);
209
+ }
210
+ /**
211
+ * Parse tool calls from LLM response
212
+ * Supports multiple formats:
213
+ * - <tool_call>{"tool": "name", "parameters": {...}}</tool_call>
214
+ * - <toolcall>{"tool": "name", "parameters": {...}}</toolcall>
215
+ * - <toolcall>toolname{"parameters": {...}}</toolcall>
216
+ * - ```tool\n{"tool": "name", "parameters": {...}}\n```
217
+ * - Direct JSON blocks with tool property
218
+ */
219
+ export function parseToolCalls(response) {
220
+ const toolCalls = [];
221
+ // Format 1: <tool_call>...</tool_call> or <toolcall>...</toolcall> with JSON inside
222
+ const toolCallRegex = /<tool_?call>\s*([\s\S]*?)\s*<\/tool_?call>/gi;
223
+ let match;
224
+ while ((match = toolCallRegex.exec(response)) !== null) {
225
+ const parsed = tryParseToolCall(match[1].trim());
226
+ if (parsed)
227
+ toolCalls.push(parsed);
228
+ }
229
+ // Format 2: <toolcall>toolname{...} or <toolcall>toolname, "parameters": {...}
230
+ const malformedRegex = /<toolcall>(\w+)[\s,]*(?:"parameters"\s*:\s*)?(\{[\s\S]*?\})/gi;
231
+ while ((match = malformedRegex.exec(response)) !== null) {
232
+ const toolName = match[1].toLowerCase();
233
+ let jsonPart = match[2];
234
+ // Map common variations to actual tool names
235
+ const toolNameMap = {
236
+ 'executecommand': 'execute_command',
237
+ 'execute_command': 'execute_command',
238
+ 'readfile': 'read_file',
239
+ 'read_file': 'read_file',
240
+ 'writefile': 'write_file',
241
+ 'write_file': 'write_file',
242
+ 'editfile': 'edit_file',
243
+ 'edit_file': 'edit_file',
244
+ 'deletefile': 'delete_file',
245
+ 'delete_file': 'delete_file',
246
+ 'listfiles': 'list_files',
247
+ 'list_files': 'list_files',
248
+ 'searchcode': 'search_code',
249
+ 'search_code': 'search_code',
250
+ };
251
+ const actualToolName = toolNameMap[toolName] || toolName;
252
+ try {
253
+ const parsed = JSON.parse(jsonPart);
254
+ const params = parsed.parameters || parsed;
255
+ toolCalls.push({
256
+ tool: actualToolName,
257
+ parameters: params,
258
+ });
259
+ }
260
+ catch {
261
+ // Try to extract parameters manually
262
+ const params = tryExtractParams(jsonPart);
263
+ if (params) {
264
+ toolCalls.push({
265
+ tool: actualToolName,
266
+ parameters: params,
267
+ });
268
+ }
269
+ }
270
+ }
271
+ // Format 2b: Even more malformed - toolname followed by loose JSON-like content
272
+ const looseRegex = /<toolcall>(\w+)[,\s]+["']?parameters["']?\s*:\s*(\{[\s\S]*?\})(?:<\/toolcall>|<|$)/gi;
273
+ while ((match = looseRegex.exec(response)) !== null) {
274
+ // Skip if already parsed
275
+ const toolName = match[1].toLowerCase();
276
+ const toolNameMap = {
277
+ 'executecommand': 'execute_command',
278
+ 'readfile': 'read_file',
279
+ 'writefile': 'write_file',
280
+ 'editfile': 'edit_file',
281
+ 'deletefile': 'delete_file',
282
+ 'listfiles': 'list_files',
283
+ 'searchcode': 'search_code',
284
+ };
285
+ const actualToolName = toolNameMap[toolName] || toolName;
286
+ // Check if we already have this tool call
287
+ if (toolCalls.some(t => t.tool === actualToolName))
288
+ continue;
289
+ const params = tryExtractParams(match[2]);
290
+ if (params) {
291
+ toolCalls.push({
292
+ tool: actualToolName,
293
+ parameters: params,
294
+ });
295
+ }
296
+ }
297
+ // Format 3: ```tool or ```json with tool calls
298
+ const codeBlockRegex = /```(?:tool|json)?\s*\n?([\s\S]*?)\n?```/g;
299
+ while ((match = codeBlockRegex.exec(response)) !== null) {
300
+ const content = match[1].trim();
301
+ // Only parse if it looks like a tool call JSON
302
+ if (content.includes('"tool"') || content.includes('"parameters"')) {
303
+ const parsed = tryParseToolCall(content);
304
+ if (parsed && !toolCalls.some(t => t.tool === parsed.tool && JSON.stringify(t.parameters) === JSON.stringify(parsed.parameters))) {
305
+ toolCalls.push(parsed);
306
+ }
307
+ }
308
+ }
309
+ // Format 4: Inline JSON objects with tool property (fallback)
310
+ if (toolCalls.length === 0) {
311
+ const jsonRegex = /\{[^{}]*"tool"\s*:\s*"[^"]+"\s*,\s*"parameters"\s*:\s*\{[^{}]*\}[^{}]*\}/g;
312
+ while ((match = jsonRegex.exec(response)) !== null) {
313
+ const parsed = tryParseToolCall(match[0]);
314
+ if (parsed)
315
+ toolCalls.push(parsed);
316
+ }
317
+ }
318
+ return toolCalls;
319
+ }
320
+ /**
321
+ * Try to extract parameters from a malformed JSON string
322
+ */
323
+ function tryExtractParams(str) {
324
+ const params = {};
325
+ // Extract "args": [...]
326
+ const argsMatch = str.match(/"args"\s*:\s*\[([\s\S]*?)\]/);
327
+ if (argsMatch) {
328
+ try {
329
+ params.args = JSON.parse(`[${argsMatch[1]}]`);
330
+ }
331
+ catch {
332
+ // Try to extract string array manually
333
+ const items = argsMatch[1].match(/"([^"]*)"/g);
334
+ if (items) {
335
+ params.args = items.map(i => i.replace(/"/g, ''));
336
+ }
337
+ }
338
+ }
339
+ // Extract "command": "..."
340
+ const cmdMatch = str.match(/"command"\s*:\s*"([^"]*)"/);
341
+ if (cmdMatch) {
342
+ params.command = cmdMatch[1];
343
+ }
344
+ // Extract "path": "..."
345
+ const pathMatch = str.match(/"path"\s*:\s*"([^"]*)"/);
346
+ if (pathMatch) {
347
+ params.path = pathMatch[1];
348
+ }
349
+ // Extract "content": "..."
350
+ const contentMatch = str.match(/"content"\s*:\s*"([\s\S]*?)(?<!\\)"/);
351
+ if (contentMatch) {
352
+ params.content = contentMatch[1].replace(/\\n/g, '\n').replace(/\\"/g, '"');
353
+ }
354
+ // Extract "old_text" and "new_text"
355
+ const oldTextMatch = str.match(/"old_text"\s*:\s*"([\s\S]*?)(?<!\\)"/);
356
+ if (oldTextMatch) {
357
+ params.old_text = oldTextMatch[1];
358
+ }
359
+ const newTextMatch = str.match(/"new_text"\s*:\s*"([\s\S]*?)(?<!\\)"/);
360
+ if (newTextMatch) {
361
+ params.new_text = newTextMatch[1];
362
+ }
363
+ // Extract "pattern": "..."
364
+ const patternMatch = str.match(/"pattern"\s*:\s*"([^"]*)"/);
365
+ if (patternMatch) {
366
+ params.pattern = patternMatch[1];
367
+ }
368
+ // Extract "recursive": true/false
369
+ const recursiveMatch = str.match(/"recursive"\s*:\s*(true|false)/i);
370
+ if (recursiveMatch) {
371
+ params.recursive = recursiveMatch[1].toLowerCase() === 'true';
372
+ }
373
+ return Object.keys(params).length > 0 ? params : null;
374
+ }
375
+ /**
376
+ * Try to parse a string as a tool call
377
+ */
378
+ function tryParseToolCall(str) {
379
+ try {
380
+ // Clean up common issues
381
+ let cleaned = str
382
+ .replace(/[\r\n]+/g, ' ') // Remove newlines
383
+ .replace(/,\s*}/g, '}') // Remove trailing commas
384
+ .replace(/,\s*]/g, ']') // Remove trailing commas in arrays
385
+ .trim();
386
+ const parsed = JSON.parse(cleaned);
387
+ if (parsed.tool && typeof parsed.tool === 'string') {
388
+ return {
389
+ tool: parsed.tool,
390
+ parameters: parsed.parameters || {},
391
+ id: parsed.id,
392
+ };
393
+ }
394
+ }
395
+ catch {
396
+ // Try to extract tool name and parameters manually for malformed JSON
397
+ const toolMatch = str.match(/"tool"\s*:\s*"([^"]+)"/);
398
+ if (toolMatch) {
399
+ const tool = toolMatch[1];
400
+ const params = {};
401
+ // Extract simple string parameters
402
+ const paramMatches = str.matchAll(/"(\w+)"\s*:\s*"([^"]*)"/g);
403
+ for (const m of paramMatches) {
404
+ if (m[1] !== 'tool') {
405
+ params[m[1]] = m[2];
406
+ }
407
+ }
408
+ // Extract boolean parameters
409
+ const boolMatches = str.matchAll(/"(\w+)"\s*:\s*(true|false)/gi);
410
+ for (const m of boolMatches) {
411
+ params[m[1]] = m[2].toLowerCase() === 'true';
412
+ }
413
+ if (Object.keys(params).length > 0 || AGENT_TOOLS[tool]) {
414
+ return { tool, parameters: params };
415
+ }
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+ /**
421
+ * Validate path is within project
422
+ */
423
+ function validatePath(path, projectRoot) {
424
+ const absolutePath = isAbsolute(path) ? path : resolve(projectRoot, path);
425
+ const relativePath = relative(projectRoot, absolutePath);
426
+ if (relativePath.startsWith('..')) {
427
+ return { valid: false, absolutePath, error: `Path '${path}' is outside project directory` };
428
+ }
429
+ return { valid: true, absolutePath };
430
+ }
431
+ /**
432
+ * Execute a tool call
433
+ */
434
+ export function executeTool(toolCall, projectRoot) {
435
+ const { tool, parameters } = toolCall;
436
+ try {
437
+ switch (tool) {
438
+ case 'read_file': {
439
+ const path = parameters.path;
440
+ if (!path) {
441
+ return { success: false, output: '', error: 'Missing required parameter: path', tool, parameters };
442
+ }
443
+ const validation = validatePath(path, projectRoot);
444
+ if (!validation.valid) {
445
+ return { success: false, output: '', error: validation.error, tool, parameters };
446
+ }
447
+ if (!existsSync(validation.absolutePath)) {
448
+ return { success: false, output: '', error: `File not found: ${path}`, tool, parameters };
449
+ }
450
+ const stat = statSync(validation.absolutePath);
451
+ if (stat.isDirectory()) {
452
+ return { success: false, output: '', error: `Path is a directory, not a file: ${path}`, tool, parameters };
453
+ }
454
+ // Limit file size
455
+ if (stat.size > 100 * 1024) { // 100KB
456
+ return { success: false, output: '', error: `File too large (${stat.size} bytes). Max: 100KB`, tool, parameters };
457
+ }
458
+ const content = readFileSync(validation.absolutePath, 'utf-8');
459
+ return { success: true, output: content, tool, parameters };
460
+ }
461
+ case 'write_file': {
462
+ const path = parameters.path;
463
+ const content = parameters.content;
464
+ if (!path) {
465
+ return { success: false, output: '', error: 'Missing required parameter: path', tool, parameters };
466
+ }
467
+ if (content === undefined) {
468
+ return { success: false, output: '', error: 'Missing required parameter: content', tool, parameters };
469
+ }
470
+ const validation = validatePath(path, projectRoot);
471
+ if (!validation.valid) {
472
+ return { success: false, output: '', error: validation.error, tool, parameters };
473
+ }
474
+ // Create directory if needed
475
+ const dir = dirname(validation.absolutePath);
476
+ if (!existsSync(dir)) {
477
+ mkdirSync(dir, { recursive: true });
478
+ }
479
+ // Record for undo
480
+ recordWrite(validation.absolutePath);
481
+ const existed = existsSync(validation.absolutePath);
482
+ writeFileSync(validation.absolutePath, content, 'utf-8');
483
+ const action = existed ? 'Updated' : 'Created';
484
+ return { success: true, output: `${action} file: ${path}`, tool, parameters };
485
+ }
486
+ case 'edit_file': {
487
+ const path = parameters.path;
488
+ const oldText = parameters.old_text;
489
+ const newText = parameters.new_text;
490
+ if (!path || oldText === undefined || newText === undefined) {
491
+ return { success: false, output: '', error: 'Missing required parameters', tool, parameters };
492
+ }
493
+ const validation = validatePath(path, projectRoot);
494
+ if (!validation.valid) {
495
+ return { success: false, output: '', error: validation.error, tool, parameters };
496
+ }
497
+ if (!existsSync(validation.absolutePath)) {
498
+ return { success: false, output: '', error: `File not found: ${path}`, tool, parameters };
499
+ }
500
+ const content = readFileSync(validation.absolutePath, 'utf-8');
501
+ if (!content.includes(oldText)) {
502
+ return { success: false, output: '', error: `Text not found in file. Make sure old_text matches exactly.`, tool, parameters };
503
+ }
504
+ // Record for undo
505
+ recordEdit(validation.absolutePath);
506
+ const newContent = content.replace(oldText, newText);
507
+ writeFileSync(validation.absolutePath, newContent, 'utf-8');
508
+ return { success: true, output: `Edited file: ${path}`, tool, parameters };
509
+ }
510
+ case 'delete_file': {
511
+ const path = parameters.path;
512
+ if (!path) {
513
+ return { success: false, output: '', error: 'Missing required parameter: path', tool, parameters };
514
+ }
515
+ const validation = validatePath(path, projectRoot);
516
+ if (!validation.valid) {
517
+ return { success: false, output: '', error: validation.error, tool, parameters };
518
+ }
519
+ if (!existsSync(validation.absolutePath)) {
520
+ return { success: false, output: '', error: `Path not found: ${path}`, tool, parameters };
521
+ }
522
+ // Record for undo (before delete)
523
+ recordDelete(validation.absolutePath);
524
+ const stat = statSync(validation.absolutePath);
525
+ if (stat.isDirectory()) {
526
+ // Delete directory recursively
527
+ rmSync(validation.absolutePath, { recursive: true, force: true });
528
+ return { success: true, output: `Deleted directory: ${path}`, tool, parameters };
529
+ }
530
+ else {
531
+ unlinkSync(validation.absolutePath);
532
+ return { success: true, output: `Deleted file: ${path}`, tool, parameters };
533
+ }
534
+ }
535
+ case 'list_files': {
536
+ const path = parameters.path || '.';
537
+ const recursive = parameters.recursive || false;
538
+ const validation = validatePath(path, projectRoot);
539
+ if (!validation.valid) {
540
+ return { success: false, output: '', error: validation.error, tool, parameters };
541
+ }
542
+ if (!existsSync(validation.absolutePath)) {
543
+ return { success: false, output: '', error: `Directory not found: ${path}`, tool, parameters };
544
+ }
545
+ const stat = statSync(validation.absolutePath);
546
+ if (!stat.isDirectory()) {
547
+ return { success: false, output: '', error: `Path is not a directory: ${path}`, tool, parameters };
548
+ }
549
+ const files = listDirectory(validation.absolutePath, projectRoot, recursive);
550
+ return { success: true, output: files.join('\n'), tool, parameters };
551
+ }
552
+ case 'create_directory': {
553
+ const path = parameters.path;
554
+ if (!path) {
555
+ return { success: false, output: '', error: 'Missing required parameter: path', tool, parameters };
556
+ }
557
+ const validation = validatePath(path, projectRoot);
558
+ if (!validation.valid) {
559
+ return { success: false, output: '', error: validation.error, tool, parameters };
560
+ }
561
+ if (existsSync(validation.absolutePath)) {
562
+ const stat = statSync(validation.absolutePath);
563
+ if (stat.isDirectory()) {
564
+ return { success: true, output: `Directory already exists: ${path}`, tool, parameters };
565
+ }
566
+ else {
567
+ return { success: false, output: '', error: `Path exists but is a file: ${path}`, tool, parameters };
568
+ }
569
+ }
570
+ // Record for undo
571
+ recordMkdir(validation.absolutePath);
572
+ mkdirSync(validation.absolutePath, { recursive: true });
573
+ return { success: true, output: `Created directory: ${path}`, tool, parameters };
574
+ }
575
+ case 'execute_command': {
576
+ const command = parameters.command;
577
+ const args = parameters.args || [];
578
+ if (!command) {
579
+ return { success: false, output: '', error: 'Missing required parameter: command', tool, parameters };
580
+ }
581
+ // Record command (can't undo but tracked)
582
+ recordCommand(command, args);
583
+ const result = executeCommand(command, args, {
584
+ cwd: projectRoot,
585
+ projectRoot,
586
+ timeout: 120000, // 2 minutes for commands
587
+ });
588
+ if (result.success) {
589
+ return { success: true, output: result.stdout || '(no output)', tool, parameters };
590
+ }
591
+ else {
592
+ return { success: false, output: result.stdout, error: result.stderr, tool, parameters };
593
+ }
594
+ }
595
+ case 'search_code': {
596
+ const pattern = parameters.pattern;
597
+ const searchPath = parameters.path || '.';
598
+ if (!pattern) {
599
+ return { success: false, output: '', error: 'Missing required parameter: pattern', tool, parameters };
600
+ }
601
+ const validation = validatePath(searchPath, projectRoot);
602
+ if (!validation.valid) {
603
+ return { success: false, output: '', error: validation.error, tool, parameters };
604
+ }
605
+ // Use grep for search
606
+ const result = executeCommand('grep', ['-rn', '--include=*.{ts,tsx,js,jsx,json,md,css,html,py,go,rs}', pattern, validation.absolutePath], {
607
+ cwd: projectRoot,
608
+ projectRoot,
609
+ timeout: 30000,
610
+ });
611
+ if (result.exitCode === 0) {
612
+ // Limit output
613
+ const lines = result.stdout.split('\n').slice(0, 50);
614
+ return { success: true, output: lines.join('\n') || 'No matches found', tool, parameters };
615
+ }
616
+ else if (result.exitCode === 1) {
617
+ return { success: true, output: 'No matches found', tool, parameters };
618
+ }
619
+ else {
620
+ return { success: false, output: '', error: result.stderr || 'Search failed', tool, parameters };
621
+ }
622
+ }
623
+ case 'fetch_url': {
624
+ const url = parameters.url;
625
+ if (!url) {
626
+ return { success: false, output: '', error: 'Missing required parameter: url', tool, parameters };
627
+ }
628
+ // Validate URL
629
+ try {
630
+ new URL(url);
631
+ }
632
+ catch {
633
+ return { success: false, output: '', error: 'Invalid URL format', tool, parameters };
634
+ }
635
+ // Use curl to fetch URL
636
+ const result = executeCommand('curl', [
637
+ '-s', '-L',
638
+ '-m', '30', // 30 second timeout
639
+ '-A', 'Codeep/1.0',
640
+ '--max-filesize', '1000000', // 1MB max
641
+ url
642
+ ], {
643
+ cwd: projectRoot,
644
+ projectRoot,
645
+ timeout: 35000,
646
+ });
647
+ if (result.success) {
648
+ // Try to extract text content (strip HTML tags for basic display)
649
+ let content = result.stdout;
650
+ // If it looks like HTML, try to extract text
651
+ if (content.includes('<html') || content.includes('<!DOCTYPE')) {
652
+ // Simple HTML to text - remove script/style and tags
653
+ content = content
654
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
655
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
656
+ .replace(/<[^>]+>/g, ' ')
657
+ .replace(/\s+/g, ' ')
658
+ .trim();
659
+ }
660
+ // Limit output
661
+ if (content.length > 10000) {
662
+ content = content.substring(0, 10000) + '\n\n... (truncated)';
663
+ }
664
+ return { success: true, output: content, tool, parameters };
665
+ }
666
+ else {
667
+ return { success: false, output: '', error: result.stderr || 'Failed to fetch URL', tool, parameters };
668
+ }
669
+ }
670
+ default:
671
+ return { success: false, output: '', error: `Unknown tool: ${tool}`, tool, parameters };
672
+ }
673
+ }
674
+ catch (error) {
675
+ const err = error;
676
+ return { success: false, output: '', error: err.message, tool, parameters };
677
+ }
678
+ }
679
+ /**
680
+ * List directory contents
681
+ */
682
+ function listDirectory(dir, projectRoot, recursive, prefix = '') {
683
+ const entries = readdirSync(dir, { withFileTypes: true });
684
+ const files = [];
685
+ // Skip common directories
686
+ const skipDirs = new Set(['node_modules', '.git', 'dist', 'build', '.next', '__pycache__', '.venv', 'venv']);
687
+ for (const entry of entries) {
688
+ const relativePath = relative(projectRoot, join(dir, entry.name));
689
+ if (entry.isDirectory()) {
690
+ if (skipDirs.has(entry.name))
691
+ continue;
692
+ files.push(`${prefix}${entry.name}/`);
693
+ if (recursive) {
694
+ const subFiles = listDirectory(join(dir, entry.name), projectRoot, true, prefix + ' ');
695
+ files.push(...subFiles);
696
+ }
697
+ }
698
+ else {
699
+ files.push(`${prefix}${entry.name}`);
700
+ }
701
+ }
702
+ return files;
703
+ }
704
+ /**
705
+ * Create action log from tool result
706
+ */
707
+ export function createActionLog(toolCall, result) {
708
+ const typeMap = {
709
+ read_file: 'read',
710
+ write_file: 'write',
711
+ edit_file: 'edit',
712
+ delete_file: 'delete',
713
+ execute_command: 'command',
714
+ search_code: 'search',
715
+ list_files: 'list',
716
+ create_directory: 'mkdir',
717
+ fetch_url: 'fetch',
718
+ };
719
+ const target = toolCall.parameters.path ||
720
+ toolCall.parameters.command ||
721
+ toolCall.parameters.pattern ||
722
+ toolCall.parameters.url ||
723
+ 'unknown';
724
+ return {
725
+ type: typeMap[toolCall.tool] || 'command',
726
+ target,
727
+ result: result.success ? 'success' : 'error',
728
+ details: result.success ? result.output.slice(0, 200) : result.error,
729
+ timestamp: Date.now(),
730
+ };
731
+ }