vigthoria-cli 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 (75) hide show
  1. package/README.md +413 -0
  2. package/dist/commands/auth.d.ts +24 -0
  3. package/dist/commands/auth.d.ts.map +1 -0
  4. package/dist/commands/auth.js +194 -0
  5. package/dist/commands/auth.js.map +1 -0
  6. package/dist/commands/chat.d.ts +64 -0
  7. package/dist/commands/chat.d.ts.map +1 -0
  8. package/dist/commands/chat.js +596 -0
  9. package/dist/commands/chat.js.map +1 -0
  10. package/dist/commands/config.d.ts +25 -0
  11. package/dist/commands/config.d.ts.map +1 -0
  12. package/dist/commands/config.js +291 -0
  13. package/dist/commands/config.js.map +1 -0
  14. package/dist/commands/edit.d.ts +28 -0
  15. package/dist/commands/edit.d.ts.map +1 -0
  16. package/dist/commands/edit.js +257 -0
  17. package/dist/commands/edit.js.map +1 -0
  18. package/dist/commands/explain.d.ts +21 -0
  19. package/dist/commands/explain.d.ts.map +1 -0
  20. package/dist/commands/explain.js +98 -0
  21. package/dist/commands/explain.js.map +1 -0
  22. package/dist/commands/generate.d.ts +25 -0
  23. package/dist/commands/generate.d.ts.map +1 -0
  24. package/dist/commands/generate.js +155 -0
  25. package/dist/commands/generate.js.map +1 -0
  26. package/dist/commands/review.d.ts +24 -0
  27. package/dist/commands/review.d.ts.map +1 -0
  28. package/dist/commands/review.js +153 -0
  29. package/dist/commands/review.js.map +1 -0
  30. package/dist/index.d.ts +16 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +205 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/utils/api.d.ts +88 -0
  35. package/dist/utils/api.d.ts.map +1 -0
  36. package/dist/utils/api.js +431 -0
  37. package/dist/utils/api.js.map +1 -0
  38. package/dist/utils/config.d.ts +57 -0
  39. package/dist/utils/config.d.ts.map +1 -0
  40. package/dist/utils/config.js +167 -0
  41. package/dist/utils/config.js.map +1 -0
  42. package/dist/utils/files.d.ts +31 -0
  43. package/dist/utils/files.d.ts.map +1 -0
  44. package/dist/utils/files.js +217 -0
  45. package/dist/utils/files.js.map +1 -0
  46. package/dist/utils/logger.d.ts +23 -0
  47. package/dist/utils/logger.d.ts.map +1 -0
  48. package/dist/utils/logger.js +104 -0
  49. package/dist/utils/logger.js.map +1 -0
  50. package/dist/utils/session.d.ts +61 -0
  51. package/dist/utils/session.d.ts.map +1 -0
  52. package/dist/utils/session.js +172 -0
  53. package/dist/utils/session.js.map +1 -0
  54. package/dist/utils/tools.d.ts +145 -0
  55. package/dist/utils/tools.d.ts.map +1 -0
  56. package/dist/utils/tools.js +781 -0
  57. package/dist/utils/tools.js.map +1 -0
  58. package/install.sh +248 -0
  59. package/package.json +52 -0
  60. package/src/commands/auth.ts +225 -0
  61. package/src/commands/chat.ts +690 -0
  62. package/src/commands/config.ts +297 -0
  63. package/src/commands/edit.ts +310 -0
  64. package/src/commands/explain.ts +115 -0
  65. package/src/commands/generate.ts +177 -0
  66. package/src/commands/review.ts +186 -0
  67. package/src/index.ts +221 -0
  68. package/src/types/marked-terminal.d.ts +31 -0
  69. package/src/utils/api.ts +531 -0
  70. package/src/utils/config.ts +224 -0
  71. package/src/utils/files.ts +212 -0
  72. package/src/utils/logger.ts +125 -0
  73. package/src/utils/session.ts +167 -0
  74. package/src/utils/tools.ts +933 -0
  75. package/tsconfig.json +20 -0
@@ -0,0 +1,781 @@
1
+ "use strict";
2
+ /**
3
+ * Vigthoria CLI Tools - Agentic Tool System
4
+ *
5
+ * This module provides Claude Code-like autonomous tool execution.
6
+ * Tools can be called by the AI to perform actions.
7
+ *
8
+ * Enhanced with:
9
+ * - Risk-based permission system
10
+ * - Automatic retry logic with exponential backoff
11
+ * - Undo functionality for file operations
12
+ * - Detailed error messages with suggestions
13
+ *
14
+ * @version 1.1.0
15
+ * @author Vigthoria Labs
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ var __importDefault = (this && this.__importDefault) || function (mod) {
51
+ return (mod && mod.__esModule) ? mod : { "default": mod };
52
+ };
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.AgenticTools = exports.ToolErrorType = void 0;
55
+ const fs = __importStar(require("fs"));
56
+ const path = __importStar(require("path"));
57
+ const child_process_1 = require("child_process");
58
+ const chalk_1 = __importDefault(require("chalk"));
59
+ // Error types for better handling
60
+ var ToolErrorType;
61
+ (function (ToolErrorType) {
62
+ ToolErrorType["FILE_NOT_FOUND"] = "FILE_NOT_FOUND";
63
+ ToolErrorType["PERMISSION_DENIED"] = "PERMISSION_DENIED";
64
+ ToolErrorType["NETWORK_ERROR"] = "NETWORK_ERROR";
65
+ ToolErrorType["TIMEOUT"] = "TIMEOUT";
66
+ ToolErrorType["INVALID_ARGS"] = "INVALID_ARGS";
67
+ ToolErrorType["EXECUTION_FAILED"] = "EXECUTION_FAILED";
68
+ ToolErrorType["USER_CANCELLED"] = "USER_CANCELLED";
69
+ })(ToolErrorType || (exports.ToolErrorType = ToolErrorType = {}));
70
+ class AgenticTools {
71
+ logger;
72
+ cwd;
73
+ permissionCallback;
74
+ autoApprove;
75
+ undoStack = [];
76
+ maxUndoStack = 50;
77
+ retryConfig = {
78
+ maxRetries: 3,
79
+ baseDelayMs: 1000,
80
+ maxDelayMs: 10000,
81
+ retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED', 'EAI_AGAIN'],
82
+ };
83
+ constructor(logger, cwd, permissionCallback, autoApprove = false) {
84
+ this.logger = logger;
85
+ this.cwd = cwd;
86
+ this.permissionCallback = permissionCallback;
87
+ this.autoApprove = autoApprove;
88
+ }
89
+ /**
90
+ * Get the undo stack for inspection
91
+ */
92
+ getUndoStack() {
93
+ return [...this.undoStack];
94
+ }
95
+ /**
96
+ * Undo the last file operation
97
+ */
98
+ async undo() {
99
+ const lastOp = this.undoStack.pop();
100
+ if (!lastOp) {
101
+ return {
102
+ success: false,
103
+ error: 'Nothing to undo',
104
+ suggestion: 'The undo stack is empty. Only file write/edit operations can be undone.',
105
+ };
106
+ }
107
+ if (lastOp.filePath) {
108
+ try {
109
+ if (lastOp.originalContent === null) {
110
+ // File was created, so delete it
111
+ if (fs.existsSync(lastOp.filePath)) {
112
+ fs.unlinkSync(lastOp.filePath);
113
+ return {
114
+ success: true,
115
+ output: `✓ Undone: ${lastOp.description}\n File deleted: ${lastOp.filePath}`,
116
+ metadata: { remainingUndos: this.undoStack.length },
117
+ };
118
+ }
119
+ }
120
+ else if (lastOp.originalContent !== undefined) {
121
+ // Restore original content
122
+ fs.writeFileSync(lastOp.filePath, lastOp.originalContent, 'utf-8');
123
+ return {
124
+ success: true,
125
+ output: `✓ Undone: ${lastOp.description}\n File restored: ${lastOp.filePath}`,
126
+ metadata: { remainingUndos: this.undoStack.length },
127
+ };
128
+ }
129
+ }
130
+ catch (error) {
131
+ return {
132
+ success: false,
133
+ error: `Failed to undo: ${error.message}`,
134
+ suggestion: 'The file may have been moved or permissions changed.',
135
+ };
136
+ }
137
+ }
138
+ return {
139
+ success: false,
140
+ error: 'This operation cannot be undone',
141
+ };
142
+ }
143
+ /**
144
+ * List of available tools - similar to Claude Code
145
+ * Enhanced with risk levels and categories
146
+ */
147
+ static getToolDefinitions() {
148
+ return [
149
+ {
150
+ name: 'read_file',
151
+ description: 'Read the contents of a file',
152
+ parameters: [
153
+ { name: 'path', description: 'File path (relative or absolute)', required: true },
154
+ { name: 'start_line', description: 'Start line (1-indexed)', required: false },
155
+ { name: 'end_line', description: 'End line (1-indexed)', required: false },
156
+ ],
157
+ requiresPermission: false,
158
+ dangerous: false,
159
+ riskLevel: 'low',
160
+ category: 'read',
161
+ },
162
+ {
163
+ name: 'write_file',
164
+ description: 'Write content to a file (creates if not exists)',
165
+ parameters: [
166
+ { name: 'path', description: 'File path', required: true },
167
+ { name: 'content', description: 'Content to write', required: true },
168
+ ],
169
+ requiresPermission: true,
170
+ dangerous: false,
171
+ riskLevel: 'medium',
172
+ category: 'write',
173
+ },
174
+ {
175
+ name: 'edit_file',
176
+ description: 'Make specific edits to a file by replacing text',
177
+ parameters: [
178
+ { name: 'path', description: 'File path', required: true },
179
+ { name: 'old_text', description: 'Text to replace', required: true },
180
+ { name: 'new_text', description: 'New text', required: true },
181
+ ],
182
+ requiresPermission: true,
183
+ dangerous: false,
184
+ riskLevel: 'medium',
185
+ category: 'write',
186
+ },
187
+ {
188
+ name: 'bash',
189
+ description: 'Run a shell command',
190
+ parameters: [
191
+ { name: 'command', description: 'Shell command to execute', required: true },
192
+ { name: 'cwd', description: 'Working directory', required: false },
193
+ { name: 'timeout', description: 'Timeout in seconds', required: false },
194
+ ],
195
+ requiresPermission: true,
196
+ dangerous: true,
197
+ riskLevel: 'high',
198
+ category: 'execute',
199
+ },
200
+ {
201
+ name: 'grep',
202
+ description: 'Search for patterns in files',
203
+ parameters: [
204
+ { name: 'pattern', description: 'Search pattern (regex)', required: true },
205
+ { name: 'path', description: 'File or directory path', required: false },
206
+ { name: 'include', description: 'File pattern to include (e.g., *.ts)', required: false },
207
+ ],
208
+ requiresPermission: false,
209
+ dangerous: false,
210
+ riskLevel: 'low',
211
+ category: 'search',
212
+ },
213
+ {
214
+ name: 'list_dir',
215
+ description: 'List contents of a directory',
216
+ parameters: [
217
+ { name: 'path', description: 'Directory path', required: true },
218
+ { name: 'recursive', description: 'List recursively', required: false },
219
+ ],
220
+ requiresPermission: false,
221
+ dangerous: false,
222
+ riskLevel: 'low',
223
+ category: 'read',
224
+ },
225
+ {
226
+ name: 'glob',
227
+ description: 'Find files matching a pattern',
228
+ parameters: [
229
+ { name: 'pattern', description: 'Glob pattern (e.g., **/*.ts)', required: true },
230
+ ],
231
+ requiresPermission: false,
232
+ dangerous: false,
233
+ riskLevel: 'low',
234
+ category: 'search',
235
+ },
236
+ {
237
+ name: 'git',
238
+ description: 'Run git commands',
239
+ parameters: [
240
+ { name: 'args', description: 'Git command arguments', required: true },
241
+ ],
242
+ requiresPermission: true,
243
+ dangerous: false,
244
+ riskLevel: 'medium',
245
+ category: 'execute',
246
+ },
247
+ ];
248
+ }
249
+ /**
250
+ * Execute a tool call with enhanced error handling and retry logic
251
+ */
252
+ async execute(call) {
253
+ const tool = AgenticTools.getToolDefinitions().find(t => t.name === call.tool);
254
+ if (!tool) {
255
+ return this.createErrorResult(ToolErrorType.INVALID_ARGS, `Unknown tool: ${call.tool}`, `Available tools: ${AgenticTools.getToolDefinitions().map(t => t.name).join(', ')}`);
256
+ }
257
+ // Validate required parameters
258
+ const validationError = this.validateParameters(call, tool);
259
+ if (validationError) {
260
+ return validationError;
261
+ }
262
+ // Check permission for dangerous/modifying actions
263
+ if (tool.requiresPermission && !this.autoApprove) {
264
+ const approved = await this.permissionCallback(this.formatPermissionRequest(call, tool));
265
+ if (!approved) {
266
+ return {
267
+ success: false,
268
+ error: 'Permission denied by user',
269
+ canRetry: true,
270
+ };
271
+ }
272
+ }
273
+ // Execute with retry logic for applicable operations
274
+ return this.executeWithRetry(call, tool);
275
+ }
276
+ /**
277
+ * Execute tool with automatic retry for transient failures
278
+ */
279
+ async executeWithRetry(call, tool) {
280
+ let lastError = null;
281
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
282
+ try {
283
+ const result = await this.executeTool(call);
284
+ if (result.success) {
285
+ return result;
286
+ }
287
+ // Check if error is retryable
288
+ const isRetryable = result.canRetry !== false &&
289
+ this.isRetryableError(result.error || '');
290
+ if (!isRetryable || attempt === this.retryConfig.maxRetries) {
291
+ return result;
292
+ }
293
+ lastError = result;
294
+ // Calculate delay with exponential backoff
295
+ const delay = Math.min(this.retryConfig.baseDelayMs * Math.pow(2, attempt), this.retryConfig.maxDelayMs);
296
+ this.logger.warn(`Retrying ${call.tool} in ${delay}ms (attempt ${attempt + 2}/${this.retryConfig.maxRetries + 1})...`);
297
+ await this.sleep(delay);
298
+ }
299
+ catch (error) {
300
+ lastError = this.createErrorResult(ToolErrorType.EXECUTION_FAILED, error.message);
301
+ if (attempt === this.retryConfig.maxRetries) {
302
+ return lastError;
303
+ }
304
+ }
305
+ }
306
+ return lastError || this.createErrorResult(ToolErrorType.EXECUTION_FAILED, 'Unknown error after retries');
307
+ }
308
+ /**
309
+ * Execute the actual tool operation
310
+ */
311
+ async executeTool(call) {
312
+ switch (call.tool) {
313
+ case 'read_file':
314
+ return this.readFile(call.args);
315
+ case 'write_file':
316
+ return this.writeFile(call.args);
317
+ case 'edit_file':
318
+ return this.editFile(call.args);
319
+ case 'bash':
320
+ return this.bash(call.args);
321
+ case 'grep':
322
+ return this.grep(call.args);
323
+ case 'list_dir':
324
+ return this.listDir(call.args);
325
+ case 'glob':
326
+ return this.glob(call.args);
327
+ case 'git':
328
+ return this.git(call.args);
329
+ default:
330
+ return this.createErrorResult(ToolErrorType.INVALID_ARGS, `Tool not implemented: ${call.tool}`);
331
+ }
332
+ }
333
+ /**
334
+ * Check if an error is retryable
335
+ */
336
+ isRetryableError(error) {
337
+ return this.retryConfig.retryableErrors.some(e => error.includes(e));
338
+ }
339
+ /**
340
+ * Validate tool parameters
341
+ */
342
+ validateParameters(call, tool) {
343
+ for (const param of tool.parameters) {
344
+ if (param.required && !call.args[param.name]) {
345
+ return this.createErrorResult(ToolErrorType.INVALID_ARGS, `Missing required parameter: ${param.name}`, `The ${call.tool} tool requires the '${param.name}' parameter. ${param.description}`);
346
+ }
347
+ }
348
+ return null;
349
+ }
350
+ /**
351
+ * Create a standardized error result
352
+ */
353
+ createErrorResult(type, message, suggestion) {
354
+ const suggestions = {
355
+ [ToolErrorType.FILE_NOT_FOUND]: 'Check if the file path is correct. Use list_dir to see available files.',
356
+ [ToolErrorType.PERMISSION_DENIED]: 'You may need elevated permissions. Try using sudo or check file ownership.',
357
+ [ToolErrorType.NETWORK_ERROR]: 'Check your internet connection. The operation will retry automatically.',
358
+ [ToolErrorType.TIMEOUT]: 'The operation took too long. Try with a shorter timeout or simpler command.',
359
+ [ToolErrorType.INVALID_ARGS]: 'Check the tool documentation for correct parameter usage.',
360
+ [ToolErrorType.EXECUTION_FAILED]: 'Review the command syntax and try again.',
361
+ [ToolErrorType.USER_CANCELLED]: 'Operation cancelled. You can retry if needed.',
362
+ };
363
+ return {
364
+ success: false,
365
+ error: message,
366
+ suggestion: suggestion || suggestions[type],
367
+ canRetry: type !== ToolErrorType.USER_CANCELLED && type !== ToolErrorType.INVALID_ARGS,
368
+ };
369
+ }
370
+ /**
371
+ * Enhanced permission request with risk visualization
372
+ */
373
+ formatPermissionRequest(call, tool) {
374
+ const riskColors = {
375
+ low: chalk_1.default.green,
376
+ medium: chalk_1.default.yellow,
377
+ high: chalk_1.default.red,
378
+ critical: chalk_1.default.bgRed.white,
379
+ };
380
+ const riskIcons = {
381
+ low: '🟢',
382
+ medium: '🟡',
383
+ high: '🔴',
384
+ critical: '⛔',
385
+ };
386
+ const riskColor = riskColors[tool.riskLevel];
387
+ const riskIcon = riskIcons[tool.riskLevel];
388
+ let msg = `\n${riskIcon} ${riskColor(`${tool.riskLevel.toUpperCase()} RISK`)} - AI wants to use ${chalk_1.default.cyan.bold(call.tool)}\n`;
389
+ msg += chalk_1.default.gray('─'.repeat(50)) + '\n';
390
+ // Tool-specific details
391
+ if (call.tool === 'bash') {
392
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('Command:')} ${chalk_1.default.yellow(call.args.command)}\n`;
393
+ if (call.args.cwd) {
394
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('Directory:')} ${call.args.cwd}\n`;
395
+ }
396
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('Timeout:')} ${call.args.timeout || '30'}s\n`;
397
+ }
398
+ else if (call.tool === 'write_file') {
399
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('File:')} ${chalk_1.default.cyan(call.args.path)}\n`;
400
+ const preview = call.args.content.substring(0, 100);
401
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('Content preview:')} ${chalk_1.default.gray(preview)}${call.args.content.length > 100 ? '...' : ''}\n`;
402
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('Size:')} ${call.args.content.length} bytes\n`;
403
+ }
404
+ else if (call.tool === 'edit_file') {
405
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('File:')} ${chalk_1.default.cyan(call.args.path)}\n`;
406
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('Replace:')} ${chalk_1.default.red(call.args.old_text.substring(0, 50))}${call.args.old_text.length > 50 ? '...' : ''}\n`;
407
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('With:')} ${chalk_1.default.green(call.args.new_text.substring(0, 50))}${call.args.new_text.length > 50 ? '...' : ''}\n`;
408
+ }
409
+ else if (call.tool === 'git') {
410
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('Command:')} git ${chalk_1.default.yellow(call.args.args)}\n`;
411
+ }
412
+ // Safety info
413
+ msg += chalk_1.default.gray('─'.repeat(50)) + '\n';
414
+ const canUndo = ['write_file', 'edit_file'].includes(call.tool);
415
+ msg += `${chalk_1.default.gray('├─')} ${chalk_1.default.white('Undo:')} ${canUndo ? chalk_1.default.green('✓ Available (use /undo)') : chalk_1.default.gray('✗ Not available')}\n`;
416
+ msg += `${chalk_1.default.gray('└─')} ${chalk_1.default.white('Category:')} ${tool.category}\n`;
417
+ if (tool.dangerous) {
418
+ msg += `\n${chalk_1.default.red.bold('⚠️ WARNING: This is a potentially dangerous action!')}\n`;
419
+ msg += chalk_1.default.red(' The AI is requesting to execute commands on your system.\n');
420
+ }
421
+ return msg;
422
+ }
423
+ sleep(ms) {
424
+ return new Promise(resolve => setTimeout(resolve, ms));
425
+ }
426
+ // Tool implementations with enhanced error handling and undo support
427
+ /**
428
+ * Read file with enhanced error handling and suggestions
429
+ */
430
+ readFile(args) {
431
+ const filePath = this.resolvePath(args.path);
432
+ if (!fs.existsSync(filePath)) {
433
+ // Try to find similar files
434
+ const dir = path.dirname(filePath);
435
+ const basename = path.basename(filePath);
436
+ let suggestions = [];
437
+ if (fs.existsSync(dir)) {
438
+ const files = fs.readdirSync(dir);
439
+ suggestions = files
440
+ .filter(f => f.toLowerCase().includes(basename.toLowerCase().slice(0, 3)))
441
+ .slice(0, 3);
442
+ }
443
+ return this.createErrorResult(ToolErrorType.FILE_NOT_FOUND, `File not found: ${args.path}`, suggestions.length > 0
444
+ ? `Did you mean one of these? ${suggestions.join(', ')}`
445
+ : 'Use list_dir to see available files in the directory.');
446
+ }
447
+ try {
448
+ const stats = fs.statSync(filePath);
449
+ if (stats.size > 1024 * 1024) { // 1MB warning
450
+ this.logger.warn(`Large file (${(stats.size / 1024 / 1024).toFixed(2)}MB) - consider using start_line/end_line`);
451
+ }
452
+ const content = fs.readFileSync(filePath, 'utf-8');
453
+ const lines = content.split('\n');
454
+ const startLine = args.start_line ? parseInt(args.start_line) - 1 : 0;
455
+ const endLine = args.end_line ? parseInt(args.end_line) : lines.length;
456
+ // Validate line numbers
457
+ if (startLine < 0 || startLine >= lines.length) {
458
+ return this.createErrorResult(ToolErrorType.INVALID_ARGS, `Invalid start_line: ${args.start_line}. File has ${lines.length} lines.`);
459
+ }
460
+ const selectedLines = lines.slice(startLine, Math.min(endLine, lines.length));
461
+ return {
462
+ success: true,
463
+ output: selectedLines.join('\n'),
464
+ metadata: {
465
+ totalLines: lines.length,
466
+ linesReturned: selectedLines.length,
467
+ filePath: args.path,
468
+ },
469
+ };
470
+ }
471
+ catch (error) {
472
+ if (error.code === 'EACCES') {
473
+ return this.createErrorResult(ToolErrorType.PERMISSION_DENIED, `Permission denied: ${args.path}`);
474
+ }
475
+ return this.createErrorResult(ToolErrorType.EXECUTION_FAILED, error.message);
476
+ }
477
+ }
478
+ /**
479
+ * Write file with undo support
480
+ */
481
+ writeFile(args) {
482
+ const filePath = this.resolvePath(args.path);
483
+ const dir = path.dirname(filePath);
484
+ // Save original content for undo if file exists
485
+ let originalContent = null;
486
+ if (fs.existsSync(filePath)) {
487
+ originalContent = fs.readFileSync(filePath, 'utf-8');
488
+ }
489
+ try {
490
+ // Create directory if needed
491
+ if (!fs.existsSync(dir)) {
492
+ fs.mkdirSync(dir, { recursive: true });
493
+ }
494
+ fs.writeFileSync(filePath, args.content, 'utf-8');
495
+ // Store undo operation
496
+ const undoOp = {
497
+ id: `undo-${Date.now()}`,
498
+ tool: 'write_file',
499
+ timestamp: Date.now(),
500
+ filePath: filePath,
501
+ originalContent: originalContent,
502
+ description: originalContent
503
+ ? `Restore ${args.path} to previous version`
504
+ : `Delete ${args.path} (was created)`,
505
+ };
506
+ this.undoStack.push(undoOp);
507
+ // Limit undo stack size
508
+ if (this.undoStack.length > 50) {
509
+ this.undoStack.shift();
510
+ }
511
+ const isNew = originalContent === null;
512
+ return {
513
+ success: true,
514
+ output: `File ${isNew ? 'created' : 'updated'}: ${args.path}`,
515
+ metadata: {
516
+ bytes: args.content.length,
517
+ isNew,
518
+ canUndo: true,
519
+ },
520
+ };
521
+ }
522
+ catch (error) {
523
+ if (error.code === 'EACCES') {
524
+ return this.createErrorResult(ToolErrorType.PERMISSION_DENIED, `Permission denied writing to: ${args.path}`);
525
+ }
526
+ if (error.code === 'ENOSPC') {
527
+ return this.createErrorResult(ToolErrorType.EXECUTION_FAILED, 'No space left on device');
528
+ }
529
+ return this.createErrorResult(ToolErrorType.EXECUTION_FAILED, error.message);
530
+ }
531
+ }
532
+ /**
533
+ * Edit file with undo support and helpful error messages
534
+ */
535
+ editFile(args) {
536
+ const filePath = this.resolvePath(args.path);
537
+ if (!fs.existsSync(filePath)) {
538
+ return this.createErrorResult(ToolErrorType.FILE_NOT_FOUND, `File not found: ${args.path}`, 'Use write_file to create a new file, or check the path.');
539
+ }
540
+ try {
541
+ let content = fs.readFileSync(filePath, 'utf-8');
542
+ const originalContent = content;
543
+ if (!content.includes(args.old_text)) {
544
+ // Try to help identify the issue
545
+ const lines = content.split('\n');
546
+ const searchTerms = args.old_text.split('\n')[0].trim().slice(0, 30);
547
+ const possibleMatches = lines
548
+ .map((line, i) => ({ line: i + 1, content: line }))
549
+ .filter(l => l.content.includes(searchTerms.slice(0, 10)))
550
+ .slice(0, 3);
551
+ let suggestion = 'The exact text was not found. ';
552
+ if (possibleMatches.length > 0) {
553
+ suggestion += `Similar content found at lines: ${possibleMatches.map(m => m.line).join(', ')}. `;
554
+ }
555
+ suggestion += 'Ensure whitespace and indentation match exactly.';
556
+ return this.createErrorResult(ToolErrorType.EXECUTION_FAILED, 'Old text not found in file', suggestion);
557
+ }
558
+ // Check for multiple matches
559
+ const matchCount = (content.match(new RegExp(args.old_text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
560
+ if (matchCount > 1) {
561
+ this.logger.warn(`Found ${matchCount} matches - only replacing first occurrence`);
562
+ }
563
+ content = content.replace(args.old_text, args.new_text);
564
+ fs.writeFileSync(filePath, content, 'utf-8');
565
+ // Store undo operation
566
+ const undoOp = {
567
+ id: `undo-${Date.now()}`,
568
+ tool: 'edit_file',
569
+ timestamp: Date.now(),
570
+ filePath: filePath,
571
+ originalContent: originalContent,
572
+ description: `Restore ${args.path} (reverts edit)`,
573
+ };
574
+ this.undoStack.push(undoOp);
575
+ // Limit undo stack size
576
+ if (this.undoStack.length > 50) {
577
+ this.undoStack.shift();
578
+ }
579
+ return {
580
+ success: true,
581
+ output: `File edited: ${args.path}`,
582
+ metadata: {
583
+ matchesFound: matchCount,
584
+ canUndo: true,
585
+ },
586
+ };
587
+ }
588
+ catch (error) {
589
+ if (error.code === 'EACCES') {
590
+ return this.createErrorResult(ToolErrorType.PERMISSION_DENIED, `Permission denied: ${args.path}`);
591
+ }
592
+ return this.createErrorResult(ToolErrorType.EXECUTION_FAILED, error.message);
593
+ }
594
+ }
595
+ /**
596
+ * Execute bash command with enhanced error handling
597
+ */
598
+ bash(args) {
599
+ const cwd = args.cwd ? this.resolvePath(args.cwd) : this.cwd;
600
+ const timeout = args.timeout ? parseInt(args.timeout) * 1000 : 30000;
601
+ // Validate working directory
602
+ if (!fs.existsSync(cwd)) {
603
+ return this.createErrorResult(ToolErrorType.FILE_NOT_FOUND, `Working directory not found: ${cwd}`, 'Check that the directory exists or use an absolute path.');
604
+ }
605
+ try {
606
+ const startTime = Date.now();
607
+ const output = (0, child_process_1.execSync)(args.command, {
608
+ cwd,
609
+ timeout,
610
+ encoding: 'utf-8',
611
+ maxBuffer: 10 * 1024 * 1024, // 10MB
612
+ stdio: ['pipe', 'pipe', 'pipe'],
613
+ });
614
+ const duration = Date.now() - startTime;
615
+ return {
616
+ success: true,
617
+ output: output.trim(),
618
+ metadata: {
619
+ durationMs: duration,
620
+ cwd,
621
+ },
622
+ };
623
+ }
624
+ catch (error) {
625
+ // Command failed but may have output
626
+ const output = error.stdout || '';
627
+ const stderr = error.stderr || '';
628
+ // Provide helpful error messages for common cases
629
+ if (error.killed) {
630
+ return this.createErrorResult(ToolErrorType.TIMEOUT, `Command timed out after ${timeout / 1000}s`, 'Try increasing the timeout or breaking the command into smaller parts.');
631
+ }
632
+ if (stderr.includes('command not found') || stderr.includes('not recognized')) {
633
+ const cmd = args.command.split(' ')[0];
634
+ return this.createErrorResult(ToolErrorType.EXECUTION_FAILED, `Command not found: ${cmd}`, 'Check that the command is installed and in PATH.');
635
+ }
636
+ if (stderr.includes('Permission denied')) {
637
+ return this.createErrorResult(ToolErrorType.PERMISSION_DENIED, 'Permission denied executing command', 'Try using sudo or check file/directory permissions.');
638
+ }
639
+ return {
640
+ success: false,
641
+ output,
642
+ error: stderr || error.message,
643
+ suggestion: 'Check command syntax and try again.',
644
+ canRetry: !error.killed,
645
+ };
646
+ }
647
+ }
648
+ grep(args) {
649
+ const searchPath = args.path ? this.resolvePath(args.path) : this.cwd;
650
+ const include = args.include || '*';
651
+ let cmd = `grep -rn --color=never "${args.pattern}" ${searchPath}`;
652
+ if (args.include) {
653
+ cmd = `grep -rn --color=never --include="${args.include}" "${args.pattern}" ${searchPath}`;
654
+ }
655
+ try {
656
+ const output = (0, child_process_1.execSync)(cmd, {
657
+ cwd: this.cwd,
658
+ encoding: 'utf-8',
659
+ maxBuffer: 5 * 1024 * 1024,
660
+ timeout: 30000,
661
+ });
662
+ return { success: true, output: output.trim() };
663
+ }
664
+ catch (error) {
665
+ if (error.status === 1) {
666
+ // No matches found
667
+ return { success: true, output: 'No matches found' };
668
+ }
669
+ return { success: false, error: error.message };
670
+ }
671
+ }
672
+ listDir(args) {
673
+ const dirPath = this.resolvePath(args.path);
674
+ if (!fs.existsSync(dirPath)) {
675
+ return { success: false, error: `Directory not found: ${args.path}` };
676
+ }
677
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
678
+ const output = entries.map(e => {
679
+ const suffix = e.isDirectory() ? '/' : '';
680
+ return e.name + suffix;
681
+ }).join('\n');
682
+ return { success: true, output };
683
+ }
684
+ glob(args) {
685
+ const { globSync } = require('glob');
686
+ try {
687
+ const files = globSync(args.pattern, { cwd: this.cwd });
688
+ return { success: true, output: files.join('\n') };
689
+ }
690
+ catch (error) {
691
+ return { success: false, error: error.message };
692
+ }
693
+ }
694
+ git(args) {
695
+ try {
696
+ const output = (0, child_process_1.execSync)(`git ${args.args}`, {
697
+ cwd: this.cwd,
698
+ encoding: 'utf-8',
699
+ timeout: 60000,
700
+ });
701
+ return { success: true, output: output.trim() };
702
+ }
703
+ catch (error) {
704
+ return { success: false, error: error.stderr || error.message };
705
+ }
706
+ }
707
+ resolvePath(p) {
708
+ if (path.isAbsolute(p)) {
709
+ return p;
710
+ }
711
+ return path.join(this.cwd, p);
712
+ }
713
+ /**
714
+ * Parse tool calls from AI response (Claude Code format)
715
+ */
716
+ static parseToolCalls(text) {
717
+ const calls = [];
718
+ // Match <tool_call>...</tool_call> blocks
719
+ const toolCallRegex = /<tool_call>\s*(\{[\s\S]*?\})\s*<\/tool_call>/g;
720
+ let match;
721
+ while ((match = toolCallRegex.exec(text)) !== null) {
722
+ try {
723
+ const parsed = JSON.parse(match[1]);
724
+ if (parsed.tool && parsed.args) {
725
+ calls.push(parsed);
726
+ }
727
+ }
728
+ catch (e) {
729
+ // Invalid JSON, skip
730
+ }
731
+ }
732
+ // Also match ```tool format
733
+ const codeBlockRegex = /```tool\s*\n(\{[\s\S]*?\})\n```/g;
734
+ while ((match = codeBlockRegex.exec(text)) !== null) {
735
+ try {
736
+ const parsed = JSON.parse(match[1]);
737
+ if (parsed.tool && parsed.args) {
738
+ calls.push(parsed);
739
+ }
740
+ }
741
+ catch (e) {
742
+ // Invalid JSON, skip
743
+ }
744
+ }
745
+ return calls;
746
+ }
747
+ /**
748
+ * Get tools formatted for AI system prompt
749
+ */
750
+ static getToolsForPrompt() {
751
+ const tools = AgenticTools.getToolDefinitions();
752
+ let prompt = `You have access to these tools to help accomplish tasks:
753
+
754
+ `;
755
+ for (const tool of tools) {
756
+ prompt += `## ${tool.name}
757
+ ${tool.description}
758
+ Parameters:
759
+ ${tool.parameters.map(p => ` - ${p.name}${p.required ? ' (required)' : ''}: ${p.description}`).join('\n')}
760
+
761
+ `;
762
+ }
763
+ prompt += `
764
+ To use a tool, respond with a tool_call block:
765
+ \`\`\`tool
766
+ {
767
+ "tool": "tool_name",
768
+ "args": {
769
+ "param1": "value1"
770
+ }
771
+ }
772
+ \`\`\`
773
+
774
+ You can use multiple tool calls in one response. After tool execution, you'll receive the results and can continue.
775
+ Always explain what you're doing before using tools.
776
+ `;
777
+ return prompt;
778
+ }
779
+ }
780
+ exports.AgenticTools = AgenticTools;
781
+ //# sourceMappingURL=tools.js.map