draftify-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.
package/dist/repl.js ADDED
@@ -0,0 +1,1102 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.globalAbortController = void 0;
7
+ exports.startRepl = startRepl;
8
+ const readline_1 = __importDefault(require("readline"));
9
+ const stream_1 = require("stream");
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const ui_1 = require("./utils/ui");
13
+ const login_1 = require("./commands/login");
14
+ const api_1 = require("./utils/api");
15
+ const config_1 = require("./utils/config");
16
+ const chats_1 = require("./utils/chats");
17
+ const kleur_1 = require("kleur");
18
+ // Helper to gather dynamic context from the workspace based on user prompt
19
+ function getContextForPrompt(dir, prompt) {
20
+ const codeFiles = [];
21
+ const allPaths = [];
22
+ const maxPaths = 200;
23
+ function walk(currentDir, depth = 0) {
24
+ if (depth > 4 || allPaths.length >= maxPaths)
25
+ return;
26
+ let entries;
27
+ try {
28
+ entries = fs_1.default.readdirSync(currentDir, { withFileTypes: true });
29
+ }
30
+ catch {
31
+ return;
32
+ }
33
+ for (const entry of entries) {
34
+ if (allPaths.length >= maxPaths)
35
+ break;
36
+ const fullPath = path_1.default.join(currentDir, entry.name);
37
+ const relPath = path_1.default.relative(dir, fullPath);
38
+ if (entry.isDirectory()) {
39
+ if (!['node_modules', '.git', 'dist', 'build', '.next', '__pycache__', 'venv', 'env'].includes(entry.name)) {
40
+ walk(fullPath, depth + 1);
41
+ }
42
+ }
43
+ else if (entry.isFile()) {
44
+ allPaths.push(relPath);
45
+ }
46
+ }
47
+ }
48
+ walk(dir, 0);
49
+ const lowercasePrompt = prompt.toLowerCase();
50
+ // Check if any specific workspace files are mentioned in the prompt
51
+ const matchedFiles = allPaths.filter(filePath => {
52
+ const baseName = path_1.default.basename(filePath).toLowerCase();
53
+ const nameWithoutExt = path_1.default.parse(baseName).name.toLowerCase();
54
+ const ext = path_1.default.extname(filePath).toLowerCase();
55
+ const validExts = [
56
+ '.ts', '.tsx', '.js', '.jsx', '.css', '.json', '.html',
57
+ '.py', '.md', '.txt', '.c', '.cpp', '.h', '.java', '.go', '.rs', '.php', '.rb', '.sh'
58
+ ];
59
+ if (!validExts.includes(ext))
60
+ return false;
61
+ // Check if the prompt contains the full filename (e.g. 'snake.py') or name without ext (e.g. 'snake')
62
+ return lowercasePrompt.includes(baseName) || (nameWithoutExt.length > 2 && lowercasePrompt.includes(nameWithoutExt));
63
+ });
64
+ let filesToRead = [];
65
+ if (matchedFiles.length > 0) {
66
+ // If specific files are mentioned, only load those files
67
+ filesToRead = matchedFiles.slice(0, 5);
68
+ }
69
+ else {
70
+ // Fallback: Always load the most recently modified source files to maintain context
71
+ const sourceFiles = allPaths.filter(filePath => {
72
+ const ext = path_1.default.extname(filePath).toLowerCase();
73
+ return ['.ts', '.tsx', '.js', '.jsx', '.py', '.c', '.cpp', '.java', '.go', '.rs', '.css', '.html'].includes(ext);
74
+ });
75
+ const filesWithStats = sourceFiles.map(filePath => {
76
+ const fullPath = path_1.default.resolve(dir, filePath);
77
+ try {
78
+ const stats = fs_1.default.statSync(fullPath);
79
+ return { path: filePath, mtime: stats.mtimeMs };
80
+ }
81
+ catch {
82
+ return { path: filePath, mtime: 0 };
83
+ }
84
+ });
85
+ // Sort by last modified time (descending)
86
+ filesWithStats.sort((a, b) => b.mtime - a.mtime);
87
+ filesToRead = filesWithStats.slice(0, 3).map(f => f.path);
88
+ }
89
+ // Load the contents of selected files
90
+ for (const filePath of filesToRead) {
91
+ const fullPath = path_1.default.resolve(dir, filePath);
92
+ try {
93
+ ui_1.ui.fileRead(filePath);
94
+ const content = fs_1.default.readFileSync(fullPath, 'utf-8');
95
+ if (content.length < 50000) { // Limit to 50KB per file
96
+ codeFiles.push({ path: filePath, content });
97
+ }
98
+ }
99
+ catch {
100
+ // Ignore unreadable files
101
+ }
102
+ }
103
+ let structureStr = allPaths.join('\n');
104
+ if (allPaths.length >= maxPaths) {
105
+ structureStr += '\n... (and more files omitted)';
106
+ }
107
+ let combinedCode = `--- PROJECT DIRECTORY STRUCTURE ---\n${structureStr}\n\n`;
108
+ if (codeFiles.length > 0) {
109
+ combinedCode += `--- ATTACHED FILE CONTENTS ---\n`;
110
+ combinedCode += codeFiles.map(f => `--- File: ${f.path} ---\n${f.content}\n`).join('\n');
111
+ }
112
+ return {
113
+ fileName: codeFiles.length > 0 ? codeFiles[0].path : "workspace_context",
114
+ code: combinedCode
115
+ };
116
+ }
117
+ function askSimpleQuestion(questionText) {
118
+ return new Promise((resolve) => {
119
+ process.stdout.write(questionText);
120
+ const isRaw = process.stdin.isRaw;
121
+ process.stdin.setRawMode(false);
122
+ const onData = (data) => {
123
+ process.stdin.off('data', onData);
124
+ process.stdin.setRawMode(isRaw);
125
+ resolve(data.toString().trim());
126
+ };
127
+ process.stdin.on('data', onData);
128
+ });
129
+ }
130
+ let lastSigintTime = 0;
131
+ exports.globalAbortController = null;
132
+ const backgroundProcesses = [];
133
+ function cleanupBackgroundProcesses() {
134
+ if (backgroundProcesses.length === 0)
135
+ return;
136
+ for (const proc of backgroundProcesses) {
137
+ try {
138
+ if (process.platform === "win32") {
139
+ const { execSync } = require('child_process');
140
+ execSync(`taskkill /pid ${proc.pid} /f /t`, { stdio: 'ignore' });
141
+ }
142
+ else {
143
+ process.kill(-proc.pid, 'SIGKILL');
144
+ }
145
+ }
146
+ catch (e) {
147
+ try {
148
+ proc.kill('SIGKILL');
149
+ }
150
+ catch (err) { }
151
+ }
152
+ }
153
+ backgroundProcesses.length = 0;
154
+ }
155
+ process.on('exit', cleanupBackgroundProcesses);
156
+ // Catch global SIGINT for when readline is NOT active (e.g. during API call)
157
+ process.on('SIGINT', () => {
158
+ if (exports.globalAbortController) {
159
+ exports.globalAbortController.abort();
160
+ exports.globalAbortController = null;
161
+ return;
162
+ }
163
+ const now = Date.now();
164
+ if (now - lastSigintTime < 2000) {
165
+ cleanupBackgroundProcesses();
166
+ console.log("\nGoodbye!");
167
+ process.exit(0);
168
+ }
169
+ else {
170
+ lastSigintTime = now;
171
+ console.log("\nPress Ctrl+C again to exit.");
172
+ }
173
+ });
174
+ // Ensure keypress events are emitted on stdin globally
175
+ readline_1.default.emitKeypressEvents(process.stdin);
176
+ function getInteractiveInput(promptText, commands) {
177
+ return new Promise((resolve) => {
178
+ process.stdout.write("\x1b[1 q");
179
+ const inputStream = new stream_1.PassThrough();
180
+ const isRaw = process.stdin.isRaw;
181
+ if (process.stdin.isTTY) {
182
+ process.stdin.setRawMode(true);
183
+ }
184
+ process.stdin.resume(); // Crucial: forces the stream to resume flowing data after being paused
185
+ let isPasting = false;
186
+ let pasteLines = 0;
187
+ let accumulatedLines = [];
188
+ let pasteTimeout = null;
189
+ const rl = readline_1.default.createInterface({
190
+ input: inputStream,
191
+ output: process.stdout,
192
+ prompt: promptText,
193
+ completer: (line) => {
194
+ if (line.startsWith("/")) {
195
+ const hits = commands.filter((c) => c.startsWith(line));
196
+ return [hits.length ? hits : commands, line];
197
+ }
198
+ return [[], line];
199
+ }
200
+ });
201
+ const onData = (data) => {
202
+ const str = data.toString('utf-8');
203
+ // Detect Alt+Enter or Esc+Enter (which sends \x1b\r) or CSI u Shift+Enter (\x1b[13;2u)
204
+ if (str === '\x1b\r' || str === '\x1b\n' || str === '\x1b[13;2u') {
205
+ // User pressed Alt+Enter or Shift+Enter
206
+ accumulatedLines.push(rl.line);
207
+ rl.write(null, { ctrl: true, name: 'u' }); // Clear current line in readline
208
+ process.stdout.write('\n' + ' '.repeat(promptText.length));
209
+ return; // Do not pass to readline, avoiding a submit
210
+ }
211
+ // Paste detection: if data contains multiple lines in one chunk
212
+ if (str.length > 5 && (str.includes('\n') || str.includes('\r'))) {
213
+ isPasting = true;
214
+ const matches = str.match(/\r?\n/g);
215
+ if (matches) {
216
+ pasteLines += matches.length;
217
+ }
218
+ }
219
+ inputStream.write(data);
220
+ };
221
+ process.stdin.on('data', onData);
222
+ rl.prompt();
223
+ const onKeypress = () => {
224
+ const line = rl.line;
225
+ process.stdout.write(`\x1b[s\x1b[K\x1b[u`);
226
+ if (line.startsWith("/") && line.length > 0) {
227
+ const hits = commands.filter((c) => c.startsWith(line));
228
+ if (hits.length >= 1 && hits[0] !== line) {
229
+ const remaining = hits[0].slice(line.length);
230
+ process.stdout.write(`\x1b[s\x1b[90m${remaining}\x1b[0m\x1b[u`);
231
+ }
232
+ }
233
+ };
234
+ const keypressHandler = (str, key) => {
235
+ const line = rl.line;
236
+ // Alt+Enter or Shift+Enter manual interception
237
+ if ((key && key.name === 'return' && key.shift) || (key && key.sequence === '\x1b\r')) {
238
+ // This doesn't trigger 'line' in readline if we intercept it, but readline still saw it.
239
+ }
240
+ if (key && key.name === 'right' && line.startsWith("/")) {
241
+ const hits = commands.filter((c) => c.startsWith(line));
242
+ if (hits.length >= 1 && hits[0] !== line) {
243
+ const remaining = hits[0].slice(line.length);
244
+ rl.write(remaining);
245
+ return;
246
+ }
247
+ }
248
+ setTimeout(onKeypress, 0);
249
+ };
250
+ process.stdin.on('keypress', keypressHandler);
251
+ rl.on('line', (line) => {
252
+ accumulatedLines.push(line);
253
+ // Handle explicit line continuation using backslash
254
+ if (line.trimEnd().endsWith('\\')) {
255
+ accumulatedLines[accumulatedLines.length - 1] = line.trimEnd().slice(0, -1);
256
+ rl.setPrompt(' '.repeat(promptText.length));
257
+ rl.prompt();
258
+ return;
259
+ }
260
+ if (isPasting) {
261
+ // If we are pasting, we don't submit immediately.
262
+ // We wait for the chunk to finish processing.
263
+ if (pasteTimeout)
264
+ clearTimeout(pasteTimeout);
265
+ pasteTimeout = setTimeout(() => {
266
+ isPasting = false; // Paste finished
267
+ if (pasteLines > 1) {
268
+ process.stdout.write(`\n\x1b[90m[${pasteLines} lines pasted...]\x1b[0m\n`);
269
+ }
270
+ pasteLines = 0;
271
+ rl.setPrompt(' '.repeat(promptText.length));
272
+ rl.prompt(); // Provide a prompt for them to press enter again to submit
273
+ }, 400);
274
+ return;
275
+ }
276
+ // If they just press Enter on an empty line while there are accumulated lines,
277
+ // we assume they want to submit the pasted content.
278
+ // Wait, if it wasn't a paste, and they just pressed Enter, we submit.
279
+ process.stdin.off('data', onData);
280
+ process.stdin.off('keypress', keypressHandler);
281
+ if (process.stdin.isTTY) {
282
+ process.stdin.setRawMode(isRaw);
283
+ }
284
+ process.stdin.pause();
285
+ process.stdout.write(`\x1b[s\x1b[K\x1b[u`);
286
+ // Clean up empty line at the end if it was from submitting a paste
287
+ if (accumulatedLines.length > 1 && accumulatedLines[accumulatedLines.length - 1] === "") {
288
+ accumulatedLines.pop();
289
+ }
290
+ const inputStr = accumulatedLines.join('\n').trim();
291
+ rl.close();
292
+ resolve(inputStr);
293
+ });
294
+ rl.on('SIGINT', () => {
295
+ process.stdin.off('data', onData);
296
+ process.stdin.off('keypress', keypressHandler);
297
+ if (process.stdin.isTTY) {
298
+ process.stdin.setRawMode(isRaw);
299
+ }
300
+ process.stdin.pause();
301
+ process.stdout.write(`\x1b[s\x1b[K\x1b[u`);
302
+ rl.close();
303
+ const now = Date.now();
304
+ if (now - lastSigintTime < 2000) {
305
+ console.log("\nGoodbye!");
306
+ process.exit(0);
307
+ }
308
+ else {
309
+ lastSigintTime = now;
310
+ console.log("\nPress Ctrl+C again to exit.");
311
+ resolve(null);
312
+ }
313
+ });
314
+ });
315
+ }
316
+ async function startRepl(initialUsername) {
317
+ // Prevent Node.js from exiting when stdin listeners are temporarily removed
318
+ setInterval(() => { }, 1000000);
319
+ const repoName = path_1.default.basename(process.cwd());
320
+ let currentModel = (0, config_1.getModel)();
321
+ let username = initialUsername;
322
+ let plan = "Draftify Scale";
323
+ const profile = await (0, api_1.getUserProfile)();
324
+ if (profile) {
325
+ username = profile.username;
326
+ plan = profile.plan;
327
+ }
328
+ const renderWelcome = () => {
329
+ console.clear();
330
+ ui_1.ui.welcomeScreen(repoName, currentModel, username, plan, (0, config_1.getThinkingLevel)());
331
+ };
332
+ renderWelcome();
333
+ let conversationHistory = [];
334
+ let currentSessionId = Date.now().toString();
335
+ let autoPrompt = "";
336
+ let alwaysAllowCommands = false;
337
+ const loop = async () => {
338
+ while (true) {
339
+ let inputLine = "";
340
+ if (autoPrompt) {
341
+ inputLine = autoPrompt;
342
+ autoPrompt = "";
343
+ ui_1.ui.info(`Answers automatically sent to the AI.`);
344
+ }
345
+ else {
346
+ const commandsList = ['/new-chat', '/chats', '/skills', '/model', '/thinking-level', '/login', '/logout', '/help', '/exit', '/quit'];
347
+ ui_1.ui.divider();
348
+ let answer = await getInteractiveInput((0, kleur_1.dim)("> "), commandsList);
349
+ ui_1.ui.divider();
350
+ if (answer === null)
351
+ continue;
352
+ inputLine = answer.trim();
353
+ }
354
+ if (!inputLine)
355
+ continue;
356
+ const cmdLower = inputLine.split(" ")[0].toLowerCase();
357
+ const token = (0, config_1.getToken)();
358
+ if (!token && cmdLower !== "/login" && cmdLower !== "/exit" && cmdLower !== "/quit") {
359
+ ui_1.ui.error("You are not logged in! Please use the /login command to continue.");
360
+ continue;
361
+ }
362
+ // 1. Slash commands (/)
363
+ if (inputLine.startsWith("/")) {
364
+ const parts = inputLine.split(" ");
365
+ const cmd = parts[0].slice(1).toLowerCase();
366
+ if (cmd === "exit" || cmd === "quit") {
367
+ ui_1.ui.success("Goodbye!");
368
+ process.exit(0);
369
+ }
370
+ else if (cmd === "clear" || cmd === "new-chat") {
371
+ conversationHistory = [];
372
+ currentSessionId = Date.now().toString(); // Reset session ID for a new chat
373
+ ui_1.ui.success("New chat started! Conversation history cleared.");
374
+ }
375
+ else if (cmd === "login") {
376
+ const newUsername = await (0, login_1.loginCommand)();
377
+ const p = await (0, api_1.getUserProfile)();
378
+ if (p) {
379
+ username = p.username;
380
+ plan = p.plan;
381
+ }
382
+ else {
383
+ username = newUsername;
384
+ }
385
+ conversationHistory = [];
386
+ currentSessionId = Date.now().toString();
387
+ renderWelcome();
388
+ }
389
+ else if (cmd === "logout") {
390
+ (0, config_1.saveConfig)({ token: "" });
391
+ username = undefined;
392
+ plan = "Draftify Scale";
393
+ conversationHistory = [];
394
+ currentSessionId = Date.now().toString();
395
+ renderWelcome();
396
+ ui_1.ui.success("Successfully logged out. Use the /login command to log back in.");
397
+ }
398
+ else if (cmd === "skills") {
399
+ const { MultiSelect } = require('enquirer');
400
+ const { getAvailableSkills } = require('./utils/skills');
401
+ try {
402
+ const availableSkills = getAvailableSkills();
403
+ if (availableSkills.length === 0) {
404
+ ui_1.ui.info("No skills found in .agents/skills directories (neither globally nor locally).");
405
+ continue;
406
+ }
407
+ const currentSkills = (0, config_1.getSkills)();
408
+ const prompt = new MultiSelect({
409
+ name: 'skills',
410
+ message: 'Toggle Draftify Skills (Space to select, Enter to save):',
411
+ choices: availableSkills.map((s) => ({
412
+ name: s.name,
413
+ value: s.name,
414
+ message: `${s.name} ${(0, kleur_1.dim)(`(${s.source})`)}`
415
+ })),
416
+ initial: currentSkills
417
+ });
418
+ const answer = await prompt.run();
419
+ (0, config_1.setSkills)(answer);
420
+ ui_1.ui.success(`Skills saved: ${answer.join(', ') || 'None enabled'}`);
421
+ }
422
+ catch (e) {
423
+ ui_1.ui.info("Skill selection aborted.");
424
+ }
425
+ }
426
+ else if (cmd === "help") {
427
+ ui_1.ui.header("Available commands:");
428
+ console.log(` ${(0, kleur_1.cyan)("/new-chat")} - Start a new chat, clear history`);
429
+ console.log(` ${(0, kleur_1.cyan)("/chats")} - List and load previous chats`);
430
+ console.log(` ${(0, kleur_1.cyan)("/skills")} - Manage and configure skills`);
431
+ console.log(` ${(0, kleur_1.cyan)("/model")} - Switch model (e.g., /model Opus 4.8-level)`);
432
+ console.log(` ${(0, kleur_1.cyan)("/thinking-level")} - Adjust the AI's thinking process depth`);
433
+ console.log(` ${(0, kleur_1.cyan)("/login")} - Log in to your Draftify account`);
434
+ console.log(` ${(0, kleur_1.cyan)("/logout")} - Log out of your account`);
435
+ console.log(` ${(0, kleur_1.cyan)("/help")} - List commands`);
436
+ console.log(` ${(0, kleur_1.cyan)("/clear")} - Clear the terminal screen`);
437
+ console.log(` ${(0, kleur_1.cyan)("/exit")} - Exit the CLI`);
438
+ console.log(` ${(0, kleur_1.cyan)("!")}${(0, kleur_1.dim)("<command>")} - Run a system command in the terminal (e.g., !npm run build)\n`);
439
+ }
440
+ else if (cmd === "clear") {
441
+ renderWelcome();
442
+ }
443
+ else if (cmd === "chats") {
444
+ const chats = (0, chats_1.loadChats)();
445
+ if (chats.length === 0) {
446
+ ui_1.ui.info("No saved chats found.");
447
+ continue;
448
+ }
449
+ const { Select } = require('enquirer');
450
+ try {
451
+ const prompt = new Select({
452
+ name: 'chat',
453
+ message: 'Select a previous conversation:',
454
+ choices: [
455
+ ...chats.map((c, idx) => ({
456
+ name: c.id,
457
+ message: `[${c.model}] ${c.title} (${new Date(c.updatedAt).toLocaleString()})`
458
+ })),
459
+ { name: 'cancel', message: 'Cancel' }
460
+ ]
461
+ });
462
+ const choiceId = await prompt.run();
463
+ if (choiceId !== 'cancel') {
464
+ const selected = chats.find(c => c.id === choiceId);
465
+ currentSessionId = selected.id;
466
+ conversationHistory = selected.history;
467
+ currentModel = selected.model;
468
+ (0, config_1.setModel)(currentModel);
469
+ renderWelcome();
470
+ ui_1.ui.success(`Conversation successfully loaded: "${selected.title}"`);
471
+ }
472
+ else {
473
+ ui_1.ui.info("Loading aborted.");
474
+ }
475
+ }
476
+ catch (e) {
477
+ ui_1.ui.info("Loading aborted.");
478
+ }
479
+ }
480
+ else if (cmd === "model") {
481
+ const newModel = parts.slice(1).join(" ").trim();
482
+ if (newModel) {
483
+ currentModel = newModel;
484
+ (0, config_1.setModel)(currentModel);
485
+ ui_1.ui.success(`Model changed to ${currentModel}`);
486
+ }
487
+ else {
488
+ const { Select } = require('enquirer');
489
+ try {
490
+ const prompt = new Select({
491
+ name: 'model',
492
+ message: 'Select the model to use:',
493
+ choices: [
494
+ 'Opus 4.8-level',
495
+ 'Opus 4.7-level',
496
+ 'Opus 4.6-level',
497
+ 'Sonnet 4.6-level',
498
+ 'Haiku 4.5-level',
499
+ { name: 'Cancel', message: 'Cancel' }
500
+ ]
501
+ });
502
+ const answer = await prompt.run();
503
+ if (answer !== 'Cancel') {
504
+ currentModel = answer;
505
+ (0, config_1.setModel)(currentModel);
506
+ renderWelcome();
507
+ ui_1.ui.success(`Model updated to: ${currentModel}`);
508
+ }
509
+ else {
510
+ ui_1.ui.info("Model selection aborted.");
511
+ }
512
+ }
513
+ catch (e) {
514
+ ui_1.ui.info("Model selection aborted.");
515
+ }
516
+ }
517
+ }
518
+ else if (cmd === "thinking-level") {
519
+ const { Select } = require('enquirer');
520
+ try {
521
+ const prompt = new Select({
522
+ name: 'level',
523
+ message: 'Select Thinking Level:',
524
+ choices: [
525
+ { name: 'Adaptive', message: 'Adaptive - Auto-detect (best)' },
526
+ { name: 'Low', message: 'Low - Ultra-fast' },
527
+ { name: 'Medium', message: 'Medium - Balanced' },
528
+ { name: 'High', message: 'High - Deep logic' },
529
+ { name: 'Cancel', message: 'Cancel' }
530
+ ]
531
+ });
532
+ const answer = await prompt.run();
533
+ if (answer !== 'Cancel') {
534
+ (0, config_1.setThinkingLevel)(answer);
535
+ renderWelcome();
536
+ ui_1.ui.success(`Thinking level updated to: ${answer}`);
537
+ }
538
+ else {
539
+ ui_1.ui.info("Thinking level selection aborted.");
540
+ }
541
+ }
542
+ catch (e) {
543
+ ui_1.ui.info("Thinking level selection aborted.");
544
+ }
545
+ }
546
+ else {
547
+ ui_1.ui.error(`Unknown command: /${cmd}`);
548
+ }
549
+ continue;
550
+ }
551
+ // 2. Shell commands (!)
552
+ if (inputLine.startsWith("!")) {
553
+ const shellCmd = inputLine.slice(1).trim();
554
+ if (!shellCmd) {
555
+ ui_1.ui.error("No shell command provided.");
556
+ continue;
557
+ }
558
+ try {
559
+ ui_1.ui.step(`Running: ${shellCmd}`);
560
+ const outputData = await new Promise((resolve, reject) => {
561
+ const { spawn } = require('child_process');
562
+ const child = spawn(shellCmd, {
563
+ cwd: process.cwd(),
564
+ shell: true
565
+ });
566
+ let outputBuffer = "";
567
+ child.stdout.on('data', (data) => {
568
+ const str = data.toString();
569
+ outputBuffer += str;
570
+ process.stdout.write((0, kleur_1.dim)(str));
571
+ });
572
+ child.stderr.on('data', (data) => {
573
+ const str = data.toString();
574
+ outputBuffer += str;
575
+ process.stderr.write((0, kleur_1.dim)(str));
576
+ });
577
+ child.on('close', (code) => {
578
+ if (code === 0) {
579
+ resolve(outputBuffer);
580
+ }
581
+ else {
582
+ reject(outputBuffer || `Process exited with code ${code}`);
583
+ }
584
+ });
585
+ child.on('error', (err) => {
586
+ reject(err.message);
587
+ });
588
+ });
589
+ conversationHistory.push({
590
+ role: "user",
591
+ content: `Executed shell command: ${shellCmd}\nOutput:\n${outputData}`
592
+ });
593
+ ui_1.ui.success("Command executed and output added to context.");
594
+ }
595
+ catch (err) {
596
+ ui_1.ui.error(`Command failed:\n${err}`);
597
+ conversationHistory.push({
598
+ role: "user",
599
+ content: `Executed shell command: ${shellCmd}\nFailed with error:\n${err}`
600
+ });
601
+ }
602
+ continue;
603
+ }
604
+ // 3. AI Chat Prompt (default)
605
+ async function applyFileOperations(result) {
606
+ let displayResult = result;
607
+ const isSafePath = (targetPath) => {
608
+ const relative = path_1.default.relative(process.cwd(), targetPath);
609
+ return !relative.startsWith('..') && !path_1.default.isAbsolute(relative);
610
+ };
611
+ const createRegex = /<FILE_CREATE\s+path="([^"]+)">([\s\S]*?)<\/FILE_CREATE>/g;
612
+ let match;
613
+ while ((match = createRegex.exec(result)) !== null) {
614
+ const filePath = match[1];
615
+ const fullPath = path_1.default.resolve(process.cwd(), filePath);
616
+ if (!isSafePath(fullPath)) {
617
+ ui_1.ui.error(`Security error: Cannot create file outside workspace (${filePath})`);
618
+ displayResult = displayResult.replace(match[0], `\n[Blocked: ${filePath} is outside workspace]\n`);
619
+ continue;
620
+ }
621
+ // Remove leading/trailing newlines that the LLM might have added immediately inside the tag,
622
+ // but preserve the rest. A simple trim is usually enough, but let's be safe.
623
+ const content = match[2].replace(/^\n|\n$/g, '');
624
+ try {
625
+ fs_1.default.mkdirSync(path_1.default.dirname(fullPath), { recursive: true });
626
+ fs_1.default.writeFileSync(fullPath, content, 'utf-8');
627
+ ui_1.ui.fileSuccess("Created", filePath);
628
+ displayResult = displayResult.replace(match[0], '');
629
+ }
630
+ catch (e) {
631
+ ui_1.ui.error(`Failed to create ${filePath}: ${e.message}`);
632
+ }
633
+ }
634
+ const modifyRegex = /<FILE_MODIFY\s+path="([^"]+)">([\s\S]*?)<\/FILE_MODIFY>/g;
635
+ while ((match = modifyRegex.exec(result)) !== null) {
636
+ const filePath = match[1];
637
+ const block = match[2];
638
+ const fullPath = path_1.default.resolve(process.cwd(), filePath);
639
+ if (!isSafePath(fullPath)) {
640
+ ui_1.ui.error(`Security error: Cannot modify file outside workspace (${filePath})`);
641
+ displayResult = displayResult.replace(match[0], `[Blocked: ${filePath} is outside workspace]`);
642
+ continue;
643
+ }
644
+ if (fs_1.default.existsSync(fullPath)) {
645
+ let fileContent = fs_1.default.readFileSync(fullPath, 'utf-8');
646
+ const searchBlockRegex = /<<<<<<< SEARCH\n([\s\S]*?)\n=======\n([\s\S]*?)\n>>>>>>>/g;
647
+ let diffMatch;
648
+ let diffApplied = false;
649
+ let hasDiffBlocks = false;
650
+ while ((diffMatch = searchBlockRegex.exec(block)) !== null) {
651
+ hasDiffBlocks = true;
652
+ let search = diffMatch[1];
653
+ let replace = diffMatch[2];
654
+ // Normalize CRLF to LF for reliable string matching
655
+ let normalizedFile = fileContent.replace(/\r\n/g, '\n');
656
+ search = search.replace(/\r\n/g, '\n');
657
+ replace = replace.replace(/\r\n/g, '\n');
658
+ if (normalizedFile.includes(search)) {
659
+ fileContent = normalizedFile.replace(search, replace);
660
+ diffApplied = true;
661
+ }
662
+ else {
663
+ // Fallback: try stripping leading/trailing empty lines/whitespace from the search block
664
+ const trimmedSearch = search.trim();
665
+ if (trimmedSearch && normalizedFile.includes(trimmedSearch)) {
666
+ fileContent = normalizedFile.replace(trimmedSearch, replace.trim());
667
+ diffApplied = true;
668
+ }
669
+ else {
670
+ ui_1.ui.error(`Could not apply diff to ${filePath}: SEARCH block not exactly matched.`);
671
+ }
672
+ }
673
+ }
674
+ if (hasDiffBlocks && diffApplied) {
675
+ fs_1.default.writeFileSync(fullPath, fileContent, 'utf-8');
676
+ ui_1.ui.fileSuccess("Modified", filePath);
677
+ displayResult = displayResult.replace(match[0], '');
678
+ }
679
+ else if (!hasDiffBlocks) {
680
+ ui_1.ui.error(`Invalid FILE_MODIFY block for ${filePath}`);
681
+ }
682
+ else {
683
+ displayResult = displayResult.replace(match[0], `[Failed to modify: ${filePath} - SEARCH block mismatch]`);
684
+ }
685
+ }
686
+ else {
687
+ ui_1.ui.error(`Cannot modify ${filePath}: File not found.`);
688
+ }
689
+ }
690
+ const deleteRegex = /<FILE_DELETE\s+path="([^"]+)"\s*\/>/g;
691
+ while ((match = deleteRegex.exec(result)) !== null) {
692
+ const filePath = match[1];
693
+ const fullPath = path_1.default.resolve(process.cwd(), filePath);
694
+ if (!isSafePath(fullPath)) {
695
+ ui_1.ui.error(`Security error: Cannot delete file outside workspace (${filePath})`);
696
+ displayResult = displayResult.replace(match[0], `[Blocked: ${filePath} is outside workspace]`);
697
+ continue;
698
+ }
699
+ if (fs_1.default.existsSync(fullPath)) {
700
+ try {
701
+ fs_1.default.unlinkSync(fullPath);
702
+ ui_1.ui.fileSuccess("Deleted", filePath);
703
+ displayResult = displayResult.replace(match[0], '');
704
+ }
705
+ catch (e) {
706
+ ui_1.ui.error(`Failed to delete ${filePath}: ${e.message}`);
707
+ }
708
+ }
709
+ }
710
+ const commandsToRun = [];
711
+ const runRegex = /<RUN_COMMAND>([\s\S]*?)<\/RUN_COMMAND>/g;
712
+ while ((match = runRegex.exec(result)) !== null) {
713
+ commandsToRun.push(match[1].trim());
714
+ displayResult = displayResult.replace(match[0], '');
715
+ }
716
+ const filesToReadAuto = [];
717
+ const readRegex = /<READ_FILE\s+path="([^"]+)"\s*\/>/g;
718
+ while ((match = readRegex.exec(result)) !== null) {
719
+ filesToReadAuto.push(match[1]);
720
+ displayResult = displayResult.replace(match[0], '');
721
+ }
722
+ const dirsToListAuto = [];
723
+ const listRegex = /<LIST_DIR\s+path="([^"]+)"\s*\/>/g;
724
+ while ((match = listRegex.exec(result)) !== null) {
725
+ dirsToListAuto.push(match[1]);
726
+ displayResult = displayResult.replace(match[0], '');
727
+ }
728
+ // Clean up any remaining excessive newlines from the LLM output around the tags
729
+ return {
730
+ cleanResult: displayResult.replace(/\n\s*\n\s*\n/g, '\n\n').trim(),
731
+ commandsToRun,
732
+ filesToReadAuto,
733
+ dirsToListAuto
734
+ };
735
+ }
736
+ exports.globalAbortController = new AbortController();
737
+ let spinner = null;
738
+ try {
739
+ // Dynamically gather context based on the current request
740
+ const dynamicCtx = getContextForPrompt(process.cwd(), inputLine);
741
+ const requestPayloadFileName = dynamicCtx.fileName;
742
+ const requestPayloadCode = dynamicCtx.code;
743
+ let currentStreamedText = "";
744
+ let finalPrompt = inputLine;
745
+ let activeSkillsData = [];
746
+ const activeSkillNames = (0, config_1.getSkills)();
747
+ if (activeSkillNames.length > 0) {
748
+ const { getAvailableSkills } = require('./utils/skills');
749
+ const allAvailable = getAvailableSkills();
750
+ activeSkillsData = allAvailable.filter((s) => activeSkillNames.includes(s.name));
751
+ }
752
+ const activeActions = new Set();
753
+ spinner = (0, ui_1.createSpinner)("Thinking...").start();
754
+ const rawResult = await (0, api_1.refactorCodeApi)(requestPayloadFileName, requestPayloadCode, finalPrompt, conversationHistory, currentModel, activeSkillsData, (0, config_1.getThinkingLevel)(), (chunk) => {
755
+ currentStreamedText += chunk;
756
+ // Find the last opened tag that hasn't been closed yet
757
+ const createMatch = currentStreamedText.match(/<FILE_CREATE\s+path="([^"]+)"[^>]*>(?![\s\S]*<\/FILE_CREATE>)/);
758
+ const modifyMatch = currentStreamedText.match(/<FILE_MODIFY\s+path="([^"]+)"[^>]*>(?![\s\S]*<\/FILE_MODIFY>)/);
759
+ const deleteMatch = currentStreamedText.match(/<FILE_DELETE\s+path="([^"]+)"/);
760
+ if (createMatch) {
761
+ const filePath = createMatch[1];
762
+ const actionKey = `create:${filePath}`;
763
+ if (!activeActions.has(actionKey)) {
764
+ activeActions.add(actionKey);
765
+ spinner.stop();
766
+ ui_1.ui.fileCreate(filePath);
767
+ spinner.start();
768
+ }
769
+ spinner.setPrefix(`Creating ${filePath}...`);
770
+ }
771
+ else if (modifyMatch) {
772
+ const filePath = modifyMatch[1];
773
+ const actionKey = `modify:${filePath}`;
774
+ if (!activeActions.has(actionKey)) {
775
+ activeActions.add(actionKey);
776
+ spinner.stop();
777
+ ui_1.ui.fileModify(filePath);
778
+ spinner.start();
779
+ }
780
+ spinner.setPrefix(`Modifying ${filePath}...`);
781
+ }
782
+ else if (deleteMatch) {
783
+ const filePath = deleteMatch[1];
784
+ const actionKey = `delete:${filePath}`;
785
+ if (!activeActions.has(actionKey)) {
786
+ activeActions.add(actionKey);
787
+ spinner.stop();
788
+ ui_1.ui.fileDelete(filePath);
789
+ spinner.start();
790
+ }
791
+ spinner.setPrefix(`Deleting ${filePath}...`);
792
+ }
793
+ else if (currentStreamedText.length > 10) {
794
+ spinner.setPrefix("Analyzing...");
795
+ }
796
+ }, exports.globalAbortController.signal);
797
+ spinner.stop();
798
+ const { cleanResult, commandsToRun, filesToReadAuto, dirsToListAuto } = await applyFileOperations(rawResult);
799
+ // --- PARSE CLARIFICATION QUESTIONS ---
800
+ let finalDisplayResult = cleanResult;
801
+ let questions = [];
802
+ const questionMatch = cleanResult.match(/<ASK_QUESTIONS>([\s\S]*?)<\/ASK_QUESTIONS>/);
803
+ if (questionMatch) {
804
+ try {
805
+ questions = JSON.parse(questionMatch[1].trim());
806
+ finalDisplayResult = cleanResult.replace(questionMatch[0], '').trim();
807
+ }
808
+ catch (e) { }
809
+ }
810
+ // --- TOKEN OPTIMIZATION ---
811
+ // Optimize the user input (especially autonomous file reads & command outputs)
812
+ let tokenOptimizedInput = inputLine;
813
+ if (tokenOptimizedInput.includes("I executed your file system requests. Here are the results:")) {
814
+ tokenOptimizedInput = tokenOptimizedInput
815
+ .replace(/File: ([^\n]+)\n```[\s\S]*?```/g, 'File: $1\n[File content read and processed]')
816
+ .replace(/Directory contents of ([^\n]+):\n[\s\S]*?(?=\n\n|\r?\n\r?\n|$)/g, 'Directory contents of $1:\n[Directory listed]');
817
+ }
818
+ if (tokenOptimizedInput.includes("I ran the commands you requested. Here are the results:")) {
819
+ tokenOptimizedInput = tokenOptimizedInput
820
+ .replace(/Command: ([^\n]+)\nOutput:\n[\s\S]*?(?=\n\n|\r?\n\r?\n|$)/g, 'Command: $1\n[Command output omitted from history]')
821
+ .replace(/Command: ([^\n]+)\nFailed with error:\n[\s\S]*?(?=\n\n|\r?\n\r?\n|$)/g, 'Command: $1\n[Command failure output omitted from history]');
822
+ }
823
+ if (tokenOptimizedInput.startsWith("Executed shell command:")) {
824
+ tokenOptimizedInput = tokenOptimizedInput
825
+ .replace(/Output:\n[\s\S]*/, 'Output:\n[Shell output omitted from history]')
826
+ .replace(/Failed with error:\n[\s\S]*/, 'Failed with error:\n[Shell error output omitted from history]');
827
+ }
828
+ // We strip out the huge code blocks from the AI's response before saving to history,
829
+ // because the files are already updated on disk. Re-sending them eats massive tokens!
830
+ const tokenOptimizedResult = finalDisplayResult
831
+ .replace(/<FILE_MODIFY([\s\S]*?)>([\s\S]*?)<\/FILE_MODIFY>/g, '<FILE_MODIFY$1>[Content modified on disk]</FILE_MODIFY>')
832
+ .replace(/<FILE_CREATE([\s\S]*?)>([\s\S]*?)<\/FILE_CREATE>/g, '<FILE_CREATE$1>[Content created on disk]</FILE_CREATE>')
833
+ .replace(/<RUN_COMMAND([\s\S]*?)>([\s\S]*?)<\/RUN_COMMAND>/g, '<RUN_COMMAND$1>[Command execution parsed]</RUN_COMMAND>');
834
+ conversationHistory.push({ role: "user", content: tokenOptimizedInput });
835
+ conversationHistory.push({ role: "assistant", content: tokenOptimizedResult });
836
+ // Keep only the last 6 messages (3 turns) to prevent infinite token growth
837
+ if (conversationHistory.length > 6) {
838
+ conversationHistory = conversationHistory.slice(conversationHistory.length - 6);
839
+ }
840
+ // Save session locally
841
+ const firstUserMsg = conversationHistory.find(h => h.role === "user")?.content || inputLine;
842
+ (0, chats_1.updateChatSession)(currentSessionId, firstUserMsg, currentModel, conversationHistory);
843
+ if (finalDisplayResult) {
844
+ ui_1.ui.box("Draftify AI", finalDisplayResult.split("\n"));
845
+ }
846
+ // --- INTERACTIVE MENU FOR QUESTIONS ---
847
+ if (questions.length > 0) {
848
+ const { Select, Input } = require('enquirer');
849
+ let answers = [];
850
+ for (const q of questions) {
851
+ console.log("");
852
+ const prompt = new Select({
853
+ name: 'answer',
854
+ message: q.question,
855
+ choices: [...q.options, "Provide a custom answer..."]
856
+ });
857
+ let answer = await prompt.run();
858
+ if (answer === "Provide a custom answer...") {
859
+ const customPrompt = new Input({
860
+ message: 'Type your answer:'
861
+ });
862
+ answer = await customPrompt.run();
863
+ }
864
+ answers.push(`- Question: ${q.question}\n Answer: ${answer}`);
865
+ }
866
+ let promptAdditions = [];
867
+ if (autoPrompt)
868
+ promptAdditions.push(autoPrompt);
869
+ promptAdditions.push("My answers to the questions:\n" + answers.join("\n"));
870
+ autoPrompt = promptAdditions.join("\n\n");
871
+ }
872
+ // --- AUTONOMOUS FILE SYSTEM EXPLORATION ---
873
+ let autoExplorationOutputs = [];
874
+ const isSafePathLocal = (targetPath) => {
875
+ const relative = path_1.default.relative(process.cwd(), targetPath);
876
+ return !relative.startsWith('..') && !path_1.default.isAbsolute(relative);
877
+ };
878
+ for (const dirPath of dirsToListAuto) {
879
+ const fullPath = path_1.default.resolve(process.cwd(), dirPath);
880
+ if (!isSafePathLocal(fullPath)) {
881
+ autoExplorationOutputs.push(`Security error: Cannot list directory outside workspace (${dirPath})`);
882
+ ui_1.ui.error(`Blocked attempt to list outside workspace: ${dirPath}`);
883
+ continue;
884
+ }
885
+ try {
886
+ if (fs_1.default.existsSync(fullPath)) {
887
+ const items = fs_1.default.readdirSync(fullPath, { withFileTypes: true });
888
+ const list = items.map((item) => `${item.isDirectory() ? '[DIR]' : '[FILE]'} ${item.name}`).join('\n');
889
+ autoExplorationOutputs.push(`Directory contents of ${dirPath}:\n${list}`);
890
+ ui_1.ui.info(`Silently listed directory: ${dirPath}`);
891
+ }
892
+ else {
893
+ autoExplorationOutputs.push(`Directory ${dirPath} not found.`);
894
+ }
895
+ }
896
+ catch (e) {
897
+ autoExplorationOutputs.push(`Failed to list ${dirPath}: ${e.message}`);
898
+ }
899
+ }
900
+ for (const filePath of filesToReadAuto) {
901
+ const fullPath = path_1.default.resolve(process.cwd(), filePath);
902
+ if (!isSafePathLocal(fullPath)) {
903
+ autoExplorationOutputs.push(`Security error: Cannot read file outside workspace (${filePath})`);
904
+ ui_1.ui.error(`Blocked attempt to read outside workspace: ${filePath}`);
905
+ continue;
906
+ }
907
+ try {
908
+ if (fs_1.default.existsSync(fullPath)) {
909
+ const content = fs_1.default.readFileSync(fullPath, 'utf-8');
910
+ autoExplorationOutputs.push(`File: ${filePath}\n\`\`\`\n${content}\n\`\`\``);
911
+ ui_1.ui.info(`Silently read: ${filePath}`);
912
+ }
913
+ else {
914
+ autoExplorationOutputs.push(`File ${filePath} not found.`);
915
+ }
916
+ }
917
+ catch (e) {
918
+ autoExplorationOutputs.push(`Failed to read ${filePath}: ${e.message}`);
919
+ }
920
+ }
921
+ if (autoExplorationOutputs.length > 0) {
922
+ let promptAdditions = autoPrompt ? [autoPrompt] : [];
923
+ promptAdditions.push(`I executed your file system requests. Here are the results:\n${autoExplorationOutputs.join('\n\n')}`);
924
+ autoPrompt = promptAdditions.join("\n\n");
925
+ }
926
+ // --- EXECUTE COMMANDS ---
927
+ if (commandsToRun.length > 0) {
928
+ let commandOutputs = [];
929
+ let userAbortedEverything = false;
930
+ for (const cmdToRun of commandsToRun) {
931
+ let runIt = alwaysAllowCommands;
932
+ let runInBackground = false;
933
+ const isLongRunning = /dev|start|server|watch|nodemon|vite|next|host/i.test(cmdToRun);
934
+ if (!runIt) {
935
+ const { Select } = require('enquirer');
936
+ try {
937
+ const choices = [
938
+ { name: 'yes', message: 'Yes (foreground)' }
939
+ ];
940
+ if (isLongRunning) {
941
+ choices.push({ name: 'background', message: 'Yes (background - keep terminal free)' });
942
+ }
943
+ else {
944
+ choices.push({ name: 'background', message: 'Run in background' });
945
+ }
946
+ choices.push({ name: 'no', message: 'No' });
947
+ choices.push({ name: 'always', message: 'Always allow for this session' });
948
+ const prompt = new Select({
949
+ name: 'allow',
950
+ message: `Draftify AI wants to run: ${cmdToRun}\nAllow execution?`,
951
+ choices: choices
952
+ });
953
+ const answer = await prompt.run();
954
+ if (answer === 'yes')
955
+ runIt = true;
956
+ if (answer === 'background') {
957
+ runIt = true;
958
+ runInBackground = true;
959
+ }
960
+ if (answer === 'always') {
961
+ runIt = true;
962
+ alwaysAllowCommands = true;
963
+ }
964
+ }
965
+ catch (e) {
966
+ runIt = false;
967
+ userAbortedEverything = true;
968
+ }
969
+ }
970
+ else {
971
+ if (isLongRunning) {
972
+ runInBackground = true;
973
+ }
974
+ }
975
+ if (userAbortedEverything) {
976
+ ui_1.ui.info(`Command execution aborted by user.`);
977
+ break;
978
+ }
979
+ if (runIt) {
980
+ if (runInBackground) {
981
+ ui_1.ui.step(`Starting in background: ${cmdToRun}`);
982
+ try {
983
+ const { spawn } = require('child_process');
984
+ const child = spawn(cmdToRun, {
985
+ cwd: process.cwd(),
986
+ shell: true,
987
+ detached: process.platform !== 'win32'
988
+ });
989
+ backgroundProcesses.push(child);
990
+ let outputBuffer = "";
991
+ const onData = (data) => {
992
+ const str = data.toString();
993
+ outputBuffer += str;
994
+ process.stdout.write((0, kleur_1.dim)(str));
995
+ };
996
+ child.stdout.on('data', onData);
997
+ child.stderr.on('data', onData);
998
+ let hasExited = false;
999
+ let exitCode = null;
1000
+ child.on('close', (code) => {
1001
+ hasExited = true;
1002
+ exitCode = code;
1003
+ });
1004
+ // Let it initialize for 1.5 seconds, displaying standard log output
1005
+ await new Promise((resolve) => setTimeout(resolve, 1500));
1006
+ // Detach listeners from stdout so log lines don't garble CLI prompt
1007
+ child.stdout.off('data', onData);
1008
+ child.stderr.off('data', onData);
1009
+ if (hasExited) {
1010
+ ui_1.ui.error(`Background process failed immediately with exit code ${exitCode}.`);
1011
+ commandOutputs.push(`Command: ${cmdToRun}\nFailed immediately with code ${exitCode}\nOutput:\n${outputBuffer}`);
1012
+ }
1013
+ else {
1014
+ ui_1.ui.success(`Process is running in background (PID: ${child.pid}).`);
1015
+ commandOutputs.push(`Command: ${cmdToRun}\nStarted in background and is running.`);
1016
+ }
1017
+ }
1018
+ catch (err) {
1019
+ ui_1.ui.error(`Failed to start background process:\n${err}`);
1020
+ commandOutputs.push(`Command: ${cmdToRun}\nFailed to start in background: ${err}`);
1021
+ }
1022
+ }
1023
+ else {
1024
+ ui_1.ui.step(`Running: ${cmdToRun}`);
1025
+ try {
1026
+ const outputData = await new Promise((resolve, reject) => {
1027
+ const { spawn } = require('child_process');
1028
+ const child = spawn(cmdToRun, {
1029
+ cwd: process.cwd(),
1030
+ shell: true,
1031
+ signal: exports.globalAbortController?.signal
1032
+ });
1033
+ let outputBuffer = "";
1034
+ child.stdout.on('data', (data) => {
1035
+ const str = data.toString();
1036
+ outputBuffer += str;
1037
+ process.stdout.write((0, kleur_1.dim)(str));
1038
+ });
1039
+ child.stderr.on('data', (data) => {
1040
+ const str = data.toString();
1041
+ outputBuffer += str;
1042
+ process.stderr.write((0, kleur_1.dim)(str));
1043
+ });
1044
+ child.on('close', (code) => {
1045
+ if (code === 0) {
1046
+ resolve(outputBuffer);
1047
+ }
1048
+ else {
1049
+ reject(outputBuffer || `Process exited with code ${code}`);
1050
+ }
1051
+ });
1052
+ child.on('error', (err) => {
1053
+ if (err.name === 'AbortError') {
1054
+ reject("Command aborted by user.");
1055
+ }
1056
+ else {
1057
+ reject(err.message);
1058
+ }
1059
+ });
1060
+ });
1061
+ commandOutputs.push(`Command: ${cmdToRun}\nOutput:\n${outputData}`);
1062
+ ui_1.ui.success(`Command executed successfully.`);
1063
+ }
1064
+ catch (err) {
1065
+ ui_1.ui.error(`Command failed:\n${err}`);
1066
+ commandOutputs.push(`Command: ${cmdToRun}\nFailed with error:\n${err}`);
1067
+ }
1068
+ }
1069
+ }
1070
+ else {
1071
+ ui_1.ui.error(`Command execution denied: ${cmdToRun}`);
1072
+ commandOutputs.push(`Command: ${cmdToRun}\nUser denied execution.`);
1073
+ }
1074
+ }
1075
+ if (commandOutputs.length > 0 && !userAbortedEverything) {
1076
+ let promptAdditions = autoPrompt ? [autoPrompt] : [];
1077
+ promptAdditions.push(`I ran the commands you requested. Here are the results:\n${commandOutputs.join('\n\n')}`);
1078
+ autoPrompt = promptAdditions.join("\n\n");
1079
+ }
1080
+ else if (userAbortedEverything) {
1081
+ autoPrompt = ""; // Clear any queued prompts
1082
+ }
1083
+ }
1084
+ }
1085
+ catch (err) {
1086
+ if (spinner)
1087
+ spinner.stop();
1088
+ if (err.name === "AbortError" || err.message === "terminated") {
1089
+ ui_1.ui.info("Generation cancelled.");
1090
+ }
1091
+ else {
1092
+ ui_1.ui.error(err.message || String(err));
1093
+ }
1094
+ }
1095
+ finally {
1096
+ exports.globalAbortController = null;
1097
+ }
1098
+ }
1099
+ };
1100
+ // Start the loop
1101
+ await loop();
1102
+ }