xibecode 0.0.1

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 (42) hide show
  1. package/README.md +434 -0
  2. package/dist/commands/chat.d.ts +8 -0
  3. package/dist/commands/chat.d.ts.map +1 -0
  4. package/dist/commands/chat.js +259 -0
  5. package/dist/commands/chat.js.map +1 -0
  6. package/dist/commands/config.d.ts +10 -0
  7. package/dist/commands/config.d.ts.map +1 -0
  8. package/dist/commands/config.js +196 -0
  9. package/dist/commands/config.js.map +1 -0
  10. package/dist/commands/run.d.ts +11 -0
  11. package/dist/commands/run.d.ts.map +1 -0
  12. package/dist/commands/run.js +143 -0
  13. package/dist/commands/run.js.map +1 -0
  14. package/dist/core/agent.d.ts +49 -0
  15. package/dist/core/agent.d.ts.map +1 -0
  16. package/dist/core/agent.js +392 -0
  17. package/dist/core/agent.js.map +1 -0
  18. package/dist/core/context.d.ts +82 -0
  19. package/dist/core/context.d.ts.map +1 -0
  20. package/dist/core/context.js +273 -0
  21. package/dist/core/context.js.map +1 -0
  22. package/dist/core/editor.d.ts +68 -0
  23. package/dist/core/editor.d.ts.map +1 -0
  24. package/dist/core/editor.js +271 -0
  25. package/dist/core/editor.js.map +1 -0
  26. package/dist/core/tools.d.ts +34 -0
  27. package/dist/core/tools.d.ts.map +1 -0
  28. package/dist/core/tools.js +675 -0
  29. package/dist/core/tools.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +80 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/ui/enhanced-tui.d.ts +51 -0
  35. package/dist/ui/enhanced-tui.d.ts.map +1 -0
  36. package/dist/ui/enhanced-tui.js +438 -0
  37. package/dist/ui/enhanced-tui.js.map +1 -0
  38. package/dist/utils/config.d.ts +76 -0
  39. package/dist/utils/config.d.ts.map +1 -0
  40. package/dist/utils/config.js +144 -0
  41. package/dist/utils/config.js.map +1 -0
  42. package/package.json +66 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/core/tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sCAAsC,CAAC;AAOjE,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACpD,QAAQ,IAAI,IAAI,EAAE,CAAC;CACpB;AAED,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAS;gBAEb,UAAU,GAAE,MAAsB;IAO9C;;OAEG;IACH,OAAO,CAAC,UAAU;IASZ,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAgIzD,QAAQ,IAAI,IAAI,EAAE;IA8QlB,OAAO,CAAC,WAAW;YAIL,QAAQ;YA6BR,iBAAiB;YAkBjB,SAAS;YAiBT,QAAQ;YAKR,SAAS;YAKT,YAAY;YAKZ,aAAa;YA8Bb,WAAW;YAaX,UAAU;YAyFV,eAAe;YAUf,UAAU;YAeV,QAAQ;YAWR,UAAU;YAkBV,UAAU;CAIzB"}
@@ -0,0 +1,675 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { exec, spawn } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import { ContextManager } from './context.js';
6
+ import { FileEditor } from './editor.js';
7
+ import * as os from 'os';
8
+ const execAsync = promisify(exec);
9
+ export class CodingToolExecutor {
10
+ workingDir;
11
+ contextManager;
12
+ fileEditor;
13
+ platform;
14
+ constructor(workingDir = process.cwd()) {
15
+ this.workingDir = workingDir;
16
+ this.contextManager = new ContextManager(workingDir);
17
+ this.fileEditor = new FileEditor(workingDir);
18
+ this.platform = os.platform();
19
+ }
20
+ /**
21
+ * Safely parse tool input - handles string JSON, null, undefined
22
+ */
23
+ parseInput(input) {
24
+ if (!input)
25
+ return {};
26
+ if (typeof input === 'string') {
27
+ try {
28
+ return JSON.parse(input);
29
+ }
30
+ catch {
31
+ return {};
32
+ }
33
+ }
34
+ if (typeof input === 'object')
35
+ return input;
36
+ return {};
37
+ }
38
+ async execute(toolName, input) {
39
+ const p = this.parseInput(input);
40
+ switch (toolName) {
41
+ case 'read_file': {
42
+ if (!p.path || typeof p.path !== 'string') {
43
+ return { error: true, success: false, message: 'Missing required parameter: path (string). Example: {"path": "src/index.ts"}' };
44
+ }
45
+ return this.readFile(p.path, p.start_line, p.end_line);
46
+ }
47
+ case 'read_multiple_files': {
48
+ if (!Array.isArray(p.paths) || p.paths.length === 0) {
49
+ return { error: true, success: false, message: 'Missing required parameter: paths (non-empty array of strings). Example: {"paths": ["file1.ts", "file2.ts"]}' };
50
+ }
51
+ const validPaths = p.paths.filter((x) => typeof x === 'string');
52
+ if (validPaths.length === 0) {
53
+ return { error: true, success: false, message: 'paths array must contain strings. Example: {"paths": ["file1.ts", "file2.ts"]}' };
54
+ }
55
+ return this.readMultipleFiles(validPaths);
56
+ }
57
+ case 'write_file': {
58
+ if (!p.path || typeof p.path !== 'string') {
59
+ return { error: true, success: false, message: 'Missing required parameter: path (string)' };
60
+ }
61
+ if (typeof p.content !== 'string') {
62
+ return { error: true, success: false, message: 'Missing required parameter: content (string)' };
63
+ }
64
+ return this.writeFile(p.path, p.content);
65
+ }
66
+ case 'edit_file': {
67
+ if (!p.path || typeof p.path !== 'string') {
68
+ return { error: true, success: false, message: 'Missing required parameter: path (string)' };
69
+ }
70
+ if (typeof p.search !== 'string') {
71
+ return { error: true, success: false, message: 'Missing required parameter: search (string)' };
72
+ }
73
+ if (typeof p.replace !== 'string') {
74
+ return { error: true, success: false, message: 'Missing required parameter: replace (string)' };
75
+ }
76
+ return this.editFile(p.path, p.search, p.replace, p.all);
77
+ }
78
+ case 'edit_lines': {
79
+ if (!p.path || typeof p.path !== 'string') {
80
+ return { error: true, success: false, message: 'Missing required parameter: path (string)' };
81
+ }
82
+ if (typeof p.start_line !== 'number' || typeof p.end_line !== 'number') {
83
+ return { error: true, success: false, message: 'Missing required parameters: start_line, end_line (numbers)' };
84
+ }
85
+ if (typeof p.new_content !== 'string') {
86
+ return { error: true, success: false, message: 'Missing required parameter: new_content (string)' };
87
+ }
88
+ return this.editLines(p.path, p.start_line, p.end_line, p.new_content);
89
+ }
90
+ case 'insert_at_line': {
91
+ if (!p.path || typeof p.path !== 'string') {
92
+ return { error: true, success: false, message: 'Missing required parameter: path (string)' };
93
+ }
94
+ if (typeof p.line !== 'number') {
95
+ return { error: true, success: false, message: 'Missing required parameter: line (number)' };
96
+ }
97
+ return this.insertAtLine(p.path, p.line, p.content ?? '');
98
+ }
99
+ case 'list_directory':
100
+ return this.listDirectory(p.path || '.');
101
+ case 'search_files': {
102
+ if (!p.pattern || typeof p.pattern !== 'string') {
103
+ return { error: true, success: false, message: 'Missing required parameter: pattern (string). Example: {"pattern": "**/*.ts"}' };
104
+ }
105
+ return this.searchFiles(p.pattern, p.path);
106
+ }
107
+ case 'run_command': {
108
+ if (!p.command || typeof p.command !== 'string') {
109
+ return { error: true, success: false, message: 'Missing required parameter: command (string)' };
110
+ }
111
+ return this.runCommand(p.command, p.cwd, p.input, p.timeout);
112
+ }
113
+ case 'create_directory': {
114
+ if (!p.path || typeof p.path !== 'string') {
115
+ return { error: true, success: false, message: 'Missing required parameter: path (string)' };
116
+ }
117
+ return this.createDirectory(p.path);
118
+ }
119
+ case 'delete_file': {
120
+ if (!p.path || typeof p.path !== 'string') {
121
+ return { error: true, success: false, message: 'Missing required parameter: path (string)' };
122
+ }
123
+ return this.deleteFile(p.path);
124
+ }
125
+ case 'move_file': {
126
+ if (!p.source || typeof p.source !== 'string') {
127
+ return { error: true, success: false, message: 'Missing required parameter: source (string)' };
128
+ }
129
+ if (!p.destination || typeof p.destination !== 'string') {
130
+ return { error: true, success: false, message: 'Missing required parameter: destination (string)' };
131
+ }
132
+ return this.moveFile(p.source, p.destination);
133
+ }
134
+ case 'get_context': {
135
+ if (!Array.isArray(p.files)) {
136
+ return { error: true, success: false, message: 'Missing required parameter: files (array of strings)' };
137
+ }
138
+ return this.getContext(p.files.filter((f) => typeof f === 'string'));
139
+ }
140
+ case 'revert_file': {
141
+ if (!p.path || typeof p.path !== 'string') {
142
+ return { error: true, success: false, message: 'Missing required parameter: path (string)' };
143
+ }
144
+ return this.revertFile(p.path, p.backup_index);
145
+ }
146
+ default:
147
+ return { error: true, success: false, message: `Unknown tool: ${toolName}. Available tools: read_file, read_multiple_files, write_file, edit_file, edit_lines, insert_at_line, list_directory, search_files, run_command, create_directory, delete_file, move_file, get_context, revert_file` };
148
+ }
149
+ }
150
+ getTools() {
151
+ return [
152
+ {
153
+ name: 'read_file',
154
+ description: 'Read file contents. For large files, can read specific line ranges to avoid token limits. Always use this before editing files.',
155
+ input_schema: {
156
+ type: 'object',
157
+ properties: {
158
+ path: {
159
+ type: 'string',
160
+ description: 'Path to the file to read',
161
+ },
162
+ start_line: {
163
+ type: 'number',
164
+ description: 'Optional: Start line number (1-indexed) for partial read',
165
+ },
166
+ end_line: {
167
+ type: 'number',
168
+ description: 'Optional: End line number for partial read',
169
+ },
170
+ },
171
+ required: ['path'],
172
+ },
173
+ },
174
+ {
175
+ name: 'read_multiple_files',
176
+ description: 'Read multiple files at once efficiently. Good for getting project context.',
177
+ input_schema: {
178
+ type: 'object',
179
+ properties: {
180
+ paths: {
181
+ type: 'array',
182
+ items: { type: 'string' },
183
+ description: 'Array of file paths to read',
184
+ },
185
+ },
186
+ required: ['paths'],
187
+ },
188
+ },
189
+ {
190
+ name: 'write_file',
191
+ description: 'Write content to a file. Creates new file or overwrites existing. For editing existing files, prefer edit_file instead.',
192
+ input_schema: {
193
+ type: 'object',
194
+ properties: {
195
+ path: {
196
+ type: 'string',
197
+ description: 'Path to the file',
198
+ },
199
+ content: {
200
+ type: 'string',
201
+ description: 'Full content to write',
202
+ },
203
+ },
204
+ required: ['path', 'content'],
205
+ },
206
+ },
207
+ {
208
+ name: 'edit_file',
209
+ description: 'Edit file by searching for exact text and replacing it. MOST RELIABLE for making changes. The search string must be unique in the file.',
210
+ input_schema: {
211
+ type: 'object',
212
+ properties: {
213
+ path: {
214
+ type: 'string',
215
+ description: 'Path to file to edit',
216
+ },
217
+ search: {
218
+ type: 'string',
219
+ description: 'Exact text to find (must be unique)',
220
+ },
221
+ replace: {
222
+ type: 'string',
223
+ description: 'Text to replace it with',
224
+ },
225
+ all: {
226
+ type: 'boolean',
227
+ description: 'Replace all occurrences (default: false, requires unique match)',
228
+ },
229
+ },
230
+ required: ['path', 'search', 'replace'],
231
+ },
232
+ },
233
+ {
234
+ name: 'edit_lines',
235
+ description: 'Edit specific line range in a file. Good for large files when you know the line numbers.',
236
+ input_schema: {
237
+ type: 'object',
238
+ properties: {
239
+ path: {
240
+ type: 'string',
241
+ description: 'Path to file',
242
+ },
243
+ start_line: {
244
+ type: 'number',
245
+ description: 'Start line number (1-indexed)',
246
+ },
247
+ end_line: {
248
+ type: 'number',
249
+ description: 'End line number (inclusive)',
250
+ },
251
+ new_content: {
252
+ type: 'string',
253
+ description: 'New content to replace those lines',
254
+ },
255
+ },
256
+ required: ['path', 'start_line', 'end_line', 'new_content'],
257
+ },
258
+ },
259
+ {
260
+ name: 'insert_at_line',
261
+ description: 'Insert content at a specific line number without replacing existing content.',
262
+ input_schema: {
263
+ type: 'object',
264
+ properties: {
265
+ path: {
266
+ type: 'string',
267
+ description: 'Path to file',
268
+ },
269
+ line: {
270
+ type: 'number',
271
+ description: 'Line number to insert at (1-indexed)',
272
+ },
273
+ content: {
274
+ type: 'string',
275
+ description: 'Content to insert',
276
+ },
277
+ },
278
+ required: ['path', 'line', 'content'],
279
+ },
280
+ },
281
+ {
282
+ name: 'list_directory',
283
+ description: 'List files and directories in a path with metadata.',
284
+ input_schema: {
285
+ type: 'object',
286
+ properties: {
287
+ path: {
288
+ type: 'string',
289
+ description: 'Directory path (default: current directory)',
290
+ },
291
+ },
292
+ },
293
+ },
294
+ {
295
+ name: 'search_files',
296
+ description: 'Search for files matching a glob pattern. Cross-platform compatible.',
297
+ input_schema: {
298
+ type: 'object',
299
+ properties: {
300
+ pattern: {
301
+ type: 'string',
302
+ description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.js")',
303
+ },
304
+ path: {
305
+ type: 'string',
306
+ description: 'Base path to search from',
307
+ },
308
+ },
309
+ required: ['pattern'],
310
+ },
311
+ },
312
+ {
313
+ name: 'run_command',
314
+ description: `Execute shell command. Platform: ${this.platform}. Commands have a timeout (default 120s). IMPORTANT: Always use non-interactive flags when available (e.g. --yes, --default, -y). For interactive prompts, use the "input" parameter to send stdin (newline-separated answers). Example: npx create-next-app@latest myapp --yes --typescript --tailwind --app --use-pnpm`,
315
+ input_schema: {
316
+ type: 'object',
317
+ properties: {
318
+ command: {
319
+ type: 'string',
320
+ description: 'Command to execute. Prefer non-interactive flags like --yes, -y, --default to avoid prompts.',
321
+ },
322
+ cwd: {
323
+ type: 'string',
324
+ description: 'Working directory (optional)',
325
+ },
326
+ input: {
327
+ type: 'string',
328
+ description: 'Stdin input to send to the command (for interactive prompts). Use \\n to separate multiple answers. Example: "yes\\n\\nmy-project\\n"',
329
+ },
330
+ timeout: {
331
+ type: 'number',
332
+ description: 'Timeout in seconds (default: 120). Increase for long-running commands like installs.',
333
+ },
334
+ },
335
+ required: ['command'],
336
+ },
337
+ },
338
+ {
339
+ name: 'create_directory',
340
+ description: 'Create a directory (including parent directories).',
341
+ input_schema: {
342
+ type: 'object',
343
+ properties: {
344
+ path: {
345
+ type: 'string',
346
+ description: 'Directory path to create',
347
+ },
348
+ },
349
+ required: ['path'],
350
+ },
351
+ },
352
+ {
353
+ name: 'delete_file',
354
+ description: 'Delete a file or directory. USE WITH CAUTION.',
355
+ input_schema: {
356
+ type: 'object',
357
+ properties: {
358
+ path: {
359
+ type: 'string',
360
+ description: 'Path to delete',
361
+ },
362
+ },
363
+ required: ['path'],
364
+ },
365
+ },
366
+ {
367
+ name: 'move_file',
368
+ description: 'Move or rename a file.',
369
+ input_schema: {
370
+ type: 'object',
371
+ properties: {
372
+ source: {
373
+ type: 'string',
374
+ description: 'Source path',
375
+ },
376
+ destination: {
377
+ type: 'string',
378
+ description: 'Destination path',
379
+ },
380
+ },
381
+ required: ['source', 'destination'],
382
+ },
383
+ },
384
+ {
385
+ name: 'get_context',
386
+ description: 'Get intelligent context about files including related files (imports, tests, configs). Use this to understand project structure.',
387
+ input_schema: {
388
+ type: 'object',
389
+ properties: {
390
+ files: {
391
+ type: 'array',
392
+ items: { type: 'string' },
393
+ description: 'Primary files to get context for',
394
+ },
395
+ },
396
+ required: ['files'],
397
+ },
398
+ },
399
+ {
400
+ name: 'revert_file',
401
+ description: 'Revert a file to a previous backup. Backups are created automatically on edits.',
402
+ input_schema: {
403
+ type: 'object',
404
+ properties: {
405
+ path: {
406
+ type: 'string',
407
+ description: 'File path to revert',
408
+ },
409
+ backup_index: {
410
+ type: 'number',
411
+ description: 'Backup index (0 = most recent, default: 0)',
412
+ },
413
+ },
414
+ required: ['path'],
415
+ },
416
+ },
417
+ ];
418
+ }
419
+ resolvePath(filePath) {
420
+ return path.resolve(this.workingDir, filePath);
421
+ }
422
+ async readFile(filePath, startLine, endLine) {
423
+ const fullPath = this.resolvePath(filePath);
424
+ try {
425
+ const content = await fs.readFile(fullPath, 'utf-8');
426
+ if (startLine !== undefined && endLine !== undefined) {
427
+ const lines = content.split('\n');
428
+ const chunk = lines.slice(startLine - 1, endLine).join('\n');
429
+ return {
430
+ path: filePath,
431
+ content: chunk,
432
+ lines: endLine - startLine + 1,
433
+ total_lines: lines.length,
434
+ partial: true,
435
+ };
436
+ }
437
+ const lines = content.split('\n');
438
+ return {
439
+ path: filePath,
440
+ content,
441
+ lines: lines.length,
442
+ size: content.length,
443
+ };
444
+ }
445
+ catch (error) {
446
+ return { error: true, success: false, message: `Failed to read ${filePath}: ${error.message}` };
447
+ }
448
+ }
449
+ async readMultipleFiles(paths) {
450
+ const results = await Promise.allSettled(paths.map(async (p) => {
451
+ const content = await this.readFile(p);
452
+ return { path: p, ...content };
453
+ }));
454
+ return {
455
+ files: results
456
+ .filter((r) => r.status === 'fulfilled')
457
+ .map(r => r.value),
458
+ errors: results
459
+ .filter((r) => r.status === 'rejected')
460
+ .map((r, i) => ({ path: paths[i], error: r.reason.message })),
461
+ };
462
+ }
463
+ async writeFile(filePath, content) {
464
+ const fullPath = this.resolvePath(filePath);
465
+ try {
466
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
467
+ await fs.writeFile(fullPath, content, 'utf-8');
468
+ const lines = content.split('\n').length;
469
+ return {
470
+ success: true,
471
+ path: filePath,
472
+ lines,
473
+ size: content.length,
474
+ };
475
+ }
476
+ catch (error) {
477
+ return { error: true, success: false, message: `Failed to write ${filePath}: ${error.message}` };
478
+ }
479
+ }
480
+ async editFile(filePath, search, replace, all) {
481
+ const result = await this.fileEditor.smartEdit(filePath, { search, replace, all });
482
+ return result;
483
+ }
484
+ async editLines(filePath, startLine, endLine, newContent) {
485
+ const result = await this.fileEditor.editLineRange(filePath, { startLine, endLine, newContent });
486
+ return result;
487
+ }
488
+ async insertAtLine(filePath, line, content) {
489
+ const result = await this.fileEditor.insertAtLine(filePath, line, content);
490
+ return result;
491
+ }
492
+ async listDirectory(dirPath) {
493
+ const fullPath = this.resolvePath(dirPath);
494
+ try {
495
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
496
+ const results = await Promise.all(entries.map(async (entry) => {
497
+ const entryPath = path.join(fullPath, entry.name);
498
+ try {
499
+ const stats = await fs.stat(entryPath);
500
+ return {
501
+ name: entry.name,
502
+ type: entry.isDirectory() ? 'directory' : 'file',
503
+ size: stats.size,
504
+ modified: stats.mtime,
505
+ };
506
+ }
507
+ catch {
508
+ return {
509
+ name: entry.name,
510
+ type: entry.isDirectory() ? 'directory' : 'file',
511
+ size: 0,
512
+ };
513
+ }
514
+ }));
515
+ return { path: dirPath, entries: results, count: results.length };
516
+ }
517
+ catch (error) {
518
+ return { error: true, success: false, message: `Failed to list directory ${dirPath}: ${error.message}` };
519
+ }
520
+ }
521
+ async searchFiles(pattern, searchPath = '.') {
522
+ try {
523
+ const files = await this.contextManager.searchFiles(pattern, { maxResults: 100 });
524
+ return {
525
+ pattern,
526
+ files,
527
+ count: files.length,
528
+ };
529
+ }
530
+ catch (error) {
531
+ return { error: true, success: false, message: `Failed to search files: ${error.message}` };
532
+ }
533
+ }
534
+ async runCommand(command, cwd, input, timeout) {
535
+ const workDir = cwd ? this.resolvePath(cwd) : this.workingDir;
536
+ const timeoutMs = (timeout || 120) * 1000;
537
+ const shell = this.platform === 'win32' ? 'powershell.exe' : '/bin/sh';
538
+ // ── Use spawn when stdin input is provided ──
539
+ if (input) {
540
+ return new Promise((resolve) => {
541
+ const child = spawn(shell, this.platform === 'win32' ? ['-Command', command] : ['-c', command], {
542
+ cwd: workDir,
543
+ env: { ...process.env },
544
+ stdio: ['pipe', 'pipe', 'pipe'],
545
+ });
546
+ let stdout = '';
547
+ let stderr = '';
548
+ let killed = false;
549
+ const timer = setTimeout(() => {
550
+ killed = true;
551
+ child.kill('SIGTERM');
552
+ }, timeoutMs);
553
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
554
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
555
+ // Pipe stdin input (supports \n for multiple answers)
556
+ const stdinData = input.replace(/\\n/g, '\n');
557
+ child.stdin.write(stdinData);
558
+ child.stdin.end();
559
+ child.on('close', (code) => {
560
+ clearTimeout(timer);
561
+ resolve({
562
+ stdout: stdout.trim(),
563
+ stderr: stderr.trim(),
564
+ success: !killed && code === 0,
565
+ exitCode: code,
566
+ timedOut: killed,
567
+ platform: this.platform,
568
+ });
569
+ });
570
+ child.on('error', (err) => {
571
+ clearTimeout(timer);
572
+ resolve({
573
+ stdout: stdout.trim(),
574
+ stderr: err.message,
575
+ success: false,
576
+ platform: this.platform,
577
+ });
578
+ });
579
+ });
580
+ }
581
+ // ── Regular exec with timeout ──
582
+ try {
583
+ const { stdout, stderr } = await execAsync(command, {
584
+ cwd: workDir,
585
+ maxBuffer: 1024 * 1024 * 10,
586
+ timeout: timeoutMs,
587
+ shell: this.platform === 'win32' ? 'powershell.exe' : undefined,
588
+ });
589
+ return {
590
+ stdout: stdout.trim(),
591
+ stderr: stderr.trim(),
592
+ success: true,
593
+ platform: this.platform,
594
+ };
595
+ }
596
+ catch (error) {
597
+ if (error.killed) {
598
+ return {
599
+ stdout: error.stdout?.trim() || '',
600
+ stderr: 'Command timed out after ' + (timeout || 120) + 's. Try increasing the timeout parameter, or use non-interactive flags like --yes to avoid prompts.',
601
+ success: false,
602
+ timedOut: true,
603
+ platform: this.platform,
604
+ };
605
+ }
606
+ return {
607
+ stdout: error.stdout?.trim() || '',
608
+ stderr: error.stderr?.trim() || error.message,
609
+ success: false,
610
+ exitCode: error.code,
611
+ platform: this.platform,
612
+ };
613
+ }
614
+ }
615
+ async createDirectory(dirPath) {
616
+ const fullPath = this.resolvePath(dirPath);
617
+ try {
618
+ await fs.mkdir(fullPath, { recursive: true });
619
+ return { success: true, path: dirPath };
620
+ }
621
+ catch (error) {
622
+ return { error: true, success: false, message: `Failed to create directory ${dirPath}: ${error.message}` };
623
+ }
624
+ }
625
+ async deleteFile(filePath) {
626
+ const fullPath = this.resolvePath(filePath);
627
+ try {
628
+ const stats = await fs.stat(fullPath);
629
+ if (stats.isDirectory()) {
630
+ await fs.rm(fullPath, { recursive: true, force: true });
631
+ }
632
+ else {
633
+ await fs.unlink(fullPath);
634
+ }
635
+ return { success: true, path: filePath };
636
+ }
637
+ catch (error) {
638
+ return { error: true, success: false, message: `Failed to delete ${filePath}: ${error.message}` };
639
+ }
640
+ }
641
+ async moveFile(source, destination) {
642
+ const sourcePath = this.resolvePath(source);
643
+ const destPath = this.resolvePath(destination);
644
+ try {
645
+ await fs.rename(sourcePath, destPath);
646
+ return { success: true, source, destination };
647
+ }
648
+ catch (error) {
649
+ return { error: true, success: false, message: `Failed to move ${source}: ${error.message}` };
650
+ }
651
+ }
652
+ async getContext(files) {
653
+ try {
654
+ const context = await this.contextManager.buildContext(files);
655
+ return {
656
+ files: context.files.map(f => ({
657
+ path: f.path,
658
+ lines: f.lines,
659
+ language: f.language,
660
+ size: f.size,
661
+ })),
662
+ totalFiles: context.files.length,
663
+ estimatedTokens: context.totalTokens,
664
+ };
665
+ }
666
+ catch (error) {
667
+ return { error: true, success: false, message: `Failed to get context: ${error.message}` };
668
+ }
669
+ }
670
+ async revertFile(filePath, backupIndex = 0) {
671
+ const result = await this.fileEditor.revertToBackup(filePath, backupIndex);
672
+ return result;
673
+ }
674
+ }
675
+ //# sourceMappingURL=tools.js.map