markov-cli 1.0.3 โ 1.0.5
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/package.json +1 -1
- package/src/editor.js +2 -9
- package/src/interactive.js +165 -72
package/package.json
CHANGED
package/src/editor.js
CHANGED
|
@@ -141,16 +141,9 @@ export function renderDiff(filepath, newContent, cwd = process.cwd()) {
|
|
|
141
141
|
|
|
142
142
|
export function parseEdits(responseText, loadedFiles = []) {
|
|
143
143
|
const edits = [];
|
|
144
|
-
//
|
|
145
|
-
const filePrefixRegex = /FILE:\s*([^\n]+)\s*\n\s*```(?:[\w./\-]*)\n([\s\S]*?)```/g;
|
|
146
|
-
let match;
|
|
147
|
-
while ((match = filePrefixRegex.exec(responseText)) !== null) {
|
|
148
|
-
const filepath = match[1].trim();
|
|
149
|
-
const content = match[2];
|
|
150
|
-
if (filepath) edits.push({ filepath, content });
|
|
151
|
-
}
|
|
152
|
-
|
|
144
|
+
// File writes use !!write: in interactive.js; here we only handle fenced blocks with path/language or single attached file
|
|
153
145
|
const regex = /```([\w./\-]*)\n([\s\S]*?)```/g;
|
|
146
|
+
let match;
|
|
154
147
|
while ((match = regex.exec(responseText)) !== null) {
|
|
155
148
|
const tag = match[1].trim();
|
|
156
149
|
const content = match[2];
|
package/src/interactive.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import gradient from 'gradient-string';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { resolve } from 'path';
|
|
4
|
+
import { resolve, relative, isAbsolute } from 'path';
|
|
5
5
|
import { mkdirSync, rmSync, writeFileSync, unlinkSync, existsSync } from 'fs';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { printLogo } from './ui/logo.js';
|
|
@@ -14,6 +14,15 @@ import { getFiles, getFilesAndDirs } from './ui/picker.js';
|
|
|
14
14
|
|
|
15
15
|
const markovGradient = gradient(['#6366f1', '#a855f7', '#ec4899']);
|
|
16
16
|
|
|
17
|
+
/** If path is absolute and under cwd, return relative path; otherwise return as-is. */
|
|
18
|
+
function toRelativePath(p) {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
if (!isAbsolute(p)) return p;
|
|
21
|
+
const rel = relative(cwd, p);
|
|
22
|
+
if (!rel.startsWith('..') && !isAbsolute(rel)) return rel;
|
|
23
|
+
return p;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
/** Extract every !!run: command from a model response. */
|
|
18
27
|
function parseRunCommands(text) {
|
|
19
28
|
const commands = [];
|
|
@@ -64,109 +73,163 @@ function parseDeleteCommands(text) {
|
|
|
64
73
|
return paths;
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
/** Extract every !!write: path and its following fenced code block from a model response. */
|
|
77
|
+
function parseWriteCommands(text) {
|
|
78
|
+
const edits = [];
|
|
79
|
+
const lines = text.split('\n');
|
|
80
|
+
let i = 0;
|
|
81
|
+
const blockRegex = /```(?:[\w./\-]*)\n([\s\S]*?)```/;
|
|
82
|
+
while (i < lines.length) {
|
|
83
|
+
const line = lines[i];
|
|
84
|
+
const writeMatch = line.match(/^!!write:\s*(.+)$/);
|
|
85
|
+
if (writeMatch) {
|
|
86
|
+
const filepath = writeMatch[1].trim();
|
|
87
|
+
if (filepath) {
|
|
88
|
+
const rest = lines.slice(i + 1).join('\n');
|
|
89
|
+
const blockMatch = rest.match(blockRegex);
|
|
90
|
+
if (blockMatch) {
|
|
91
|
+
edits.push({ filepath, content: blockMatch[1] });
|
|
92
|
+
const blockText = blockMatch[0];
|
|
93
|
+
const beforeBlock = rest.substring(0, rest.indexOf(blockText));
|
|
94
|
+
const linesBeforeBlock = beforeBlock.split('\n').length;
|
|
95
|
+
const linesInBlock = blockText.split('\n').length;
|
|
96
|
+
i += 1 + linesBeforeBlock + linesInBlock;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
i++;
|
|
102
|
+
}
|
|
103
|
+
return edits;
|
|
104
|
+
}
|
|
105
|
+
|
|
67
106
|
/**
|
|
68
107
|
* Parse and apply all file operations from a model reply.
|
|
69
108
|
* Shows diffs/confirmations for each op. Returns updated allFiles list.
|
|
109
|
+
* options.autoConfirm: if true, skip y/n prompts and apply all ops.
|
|
70
110
|
*/
|
|
71
|
-
async function handleFileOps(reply, loadedFiles) {
|
|
111
|
+
async function handleFileOps(reply, loadedFiles, options = {}) {
|
|
112
|
+
const autoConfirm = options.autoConfirm === true;
|
|
72
113
|
let allFiles = getFilesAndDirs();
|
|
73
114
|
|
|
74
|
-
//
|
|
75
|
-
for (const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
} catch (err) {
|
|
87
|
-
console.log(chalk.red(` no such directory: ${target}\n`));
|
|
88
|
-
}
|
|
89
|
-
continue;
|
|
115
|
+
// Create folders first (so !!run: cd <folder> can succeed later)
|
|
116
|
+
for (const folderPath of parseMkdirCommands(reply)) {
|
|
117
|
+
const path = toRelativePath(folderPath);
|
|
118
|
+
process.stdout.write(chalk.dim(` mkdir: ${path} โ `));
|
|
119
|
+
const confirmed = autoConfirm ? true : await confirm(chalk.bold(`Create folder ${chalk.cyan(path)}? [y/N] `));
|
|
120
|
+
if (confirmed) {
|
|
121
|
+
try {
|
|
122
|
+
mkdirSync(resolve(process.cwd(), path), { recursive: true });
|
|
123
|
+
allFiles = getFilesAndDirs();
|
|
124
|
+
console.log(chalk.green(`โ created ${path}\n`));
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.log(chalk.red(`โ could not create ${path}: ${err.message}\n`));
|
|
90
127
|
}
|
|
91
|
-
process.stdout.write(chalk.dim(` running: ${cmd}\n`));
|
|
92
|
-
const { stdout, stderr, exitCode } = await execCommand(cmd);
|
|
93
|
-
const output = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
94
|
-
console.log(output ? chalk.dim(output) + '\n' : chalk.dim(` (exit ${exitCode})\n`));
|
|
95
|
-
allFiles = getFilesAndDirs();
|
|
96
128
|
} else {
|
|
97
129
|
console.log(chalk.dim('skipped\n'));
|
|
98
130
|
}
|
|
99
131
|
}
|
|
100
132
|
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const confirmed = await confirm(chalk.bold(`Write ${chalk.cyan(filepath)}? [y/N] `));
|
|
133
|
+
// Create empty files
|
|
134
|
+
for (const filePath of parseTouchCommands(reply)) {
|
|
135
|
+
const path = toRelativePath(filePath);
|
|
136
|
+
const confirmed = autoConfirm ? true : await confirm(chalk.bold(`Create file ${chalk.cyan(path)}? [y/N] `));
|
|
106
137
|
if (confirmed) {
|
|
107
138
|
try {
|
|
108
|
-
|
|
139
|
+
const abs = resolve(process.cwd(), path);
|
|
140
|
+
const parentDir = abs.split('/').slice(0, -1).join('/');
|
|
141
|
+
mkdirSync(parentDir, { recursive: true });
|
|
142
|
+
writeFileSync(abs, '', { flag: 'wx' });
|
|
109
143
|
allFiles = getFilesAndDirs();
|
|
110
|
-
console.log(chalk.green(`โ
|
|
144
|
+
console.log(chalk.green(`โ created ${path}\n`));
|
|
111
145
|
} catch (err) {
|
|
112
|
-
console.log(chalk.red(`โ could not
|
|
146
|
+
console.log(chalk.red(`โ could not create ${path}: ${err.message}\n`));
|
|
113
147
|
}
|
|
114
148
|
} else {
|
|
115
149
|
console.log(chalk.dim('skipped\n'));
|
|
116
150
|
}
|
|
117
151
|
}
|
|
118
152
|
|
|
119
|
-
//
|
|
120
|
-
for (const
|
|
121
|
-
|
|
122
|
-
|
|
153
|
+
// Write files via !!write: path + fenced block
|
|
154
|
+
for (const { filepath, content } of parseWriteCommands(reply)) {
|
|
155
|
+
const path = toRelativePath(filepath);
|
|
156
|
+
renderDiff(path, content);
|
|
157
|
+
const confirmed = autoConfirm ? true : await confirm(chalk.bold(`Write ${chalk.cyan(path)}? [y/N] `));
|
|
123
158
|
if (confirmed) {
|
|
124
159
|
try {
|
|
125
|
-
|
|
160
|
+
applyEdit(path, content);
|
|
126
161
|
allFiles = getFilesAndDirs();
|
|
127
|
-
console.log(chalk.green(`โ
|
|
162
|
+
console.log(chalk.green(`โ wrote ${path}\n`));
|
|
128
163
|
} catch (err) {
|
|
129
|
-
console.log(chalk.red(`โ could not
|
|
164
|
+
console.log(chalk.red(`โ could not write ${path}: ${err.message}\n`));
|
|
130
165
|
}
|
|
131
166
|
} else {
|
|
132
167
|
console.log(chalk.dim('skipped\n'));
|
|
133
168
|
}
|
|
134
169
|
}
|
|
135
170
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
171
|
+
// Write / edit files (fenced blocks with path/language or single attached file)
|
|
172
|
+
const edits = parseEdits(reply, loadedFiles);
|
|
173
|
+
for (const { filepath, content } of edits) {
|
|
174
|
+
const path = toRelativePath(filepath);
|
|
175
|
+
renderDiff(path, content);
|
|
176
|
+
const confirmed = autoConfirm ? true : await confirm(chalk.bold(`Write ${chalk.cyan(path)}? [y/N] `));
|
|
139
177
|
if (confirmed) {
|
|
140
178
|
try {
|
|
141
|
-
|
|
142
|
-
const parentDir = abs.split('/').slice(0, -1).join('/');
|
|
143
|
-
mkdirSync(parentDir, { recursive: true });
|
|
144
|
-
writeFileSync(abs, '', { flag: 'wx' });
|
|
179
|
+
applyEdit(path, content);
|
|
145
180
|
allFiles = getFilesAndDirs();
|
|
146
|
-
console.log(chalk.green(`โ
|
|
181
|
+
console.log(chalk.green(`โ wrote ${path}\n`));
|
|
147
182
|
} catch (err) {
|
|
148
|
-
console.log(chalk.red(`โ could not
|
|
183
|
+
console.log(chalk.red(`โ could not write ${path}: ${err.message}\n`));
|
|
149
184
|
}
|
|
150
185
|
} else {
|
|
151
186
|
console.log(chalk.dim('skipped\n'));
|
|
152
187
|
}
|
|
153
188
|
}
|
|
154
189
|
|
|
190
|
+
// Run terminal commands (after folders/files exist, so cd works)
|
|
191
|
+
for (const cmd of parseRunCommands(reply)) {
|
|
192
|
+
const ok = autoConfirm ? true : await confirm(chalk.bold(`Run: ${chalk.cyan(cmd)}? [y/N] `));
|
|
193
|
+
if (ok) {
|
|
194
|
+
// cd must be handled in-process โ child processes can't change the parent's cwd
|
|
195
|
+
const cdMatch = cmd.match(/^cd\s+(.+)$/);
|
|
196
|
+
if (cdMatch) {
|
|
197
|
+
const target = resolve(process.cwd(), cdMatch[1].trim().replace(/^~/, homedir()));
|
|
198
|
+
try {
|
|
199
|
+
process.chdir(target);
|
|
200
|
+
allFiles = getFilesAndDirs();
|
|
201
|
+
console.log(chalk.dim(` ๐ ${process.cwd()}\n`));
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.log(chalk.red(` no such directory: ${target}\n`));
|
|
204
|
+
}
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
process.stdout.write(chalk.dim(` running: ${cmd}\n`));
|
|
208
|
+
const { stdout, stderr, exitCode } = await execCommand(cmd);
|
|
209
|
+
const output = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
210
|
+
console.log(output ? chalk.dim(output) + '\n' : chalk.dim(` (exit ${exitCode})\n`));
|
|
211
|
+
allFiles = getFilesAndDirs();
|
|
212
|
+
} else {
|
|
213
|
+
console.log(chalk.dim('skipped\n'));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
155
217
|
// Remove directories
|
|
156
218
|
for (const dirPath of parseRmdirCommands(reply)) {
|
|
157
|
-
const
|
|
219
|
+
const path = toRelativePath(dirPath);
|
|
220
|
+
const confirmed = autoConfirm ? true : await confirm(chalk.bold(`Remove directory ${chalk.cyan(path)}? [y/N] `));
|
|
158
221
|
if (confirmed) {
|
|
159
|
-
const abs = resolve(process.cwd(),
|
|
222
|
+
const abs = resolve(process.cwd(), path);
|
|
160
223
|
if (existsSync(abs)) {
|
|
161
224
|
try {
|
|
162
225
|
rmSync(abs, { recursive: true, force: true });
|
|
163
226
|
allFiles = getFilesAndDirs();
|
|
164
|
-
console.log(chalk.green(`โ removed ${
|
|
227
|
+
console.log(chalk.green(`โ removed ${path}\n`));
|
|
165
228
|
} catch (err) {
|
|
166
|
-
console.log(chalk.red(`โ could not remove ${
|
|
229
|
+
console.log(chalk.red(`โ could not remove ${path}: ${err.message}\n`));
|
|
167
230
|
}
|
|
168
231
|
} else {
|
|
169
|
-
console.log(chalk.yellow(`โ not found: ${
|
|
232
|
+
console.log(chalk.yellow(`โ not found: ${path}\n`));
|
|
170
233
|
}
|
|
171
234
|
} else {
|
|
172
235
|
console.log(chalk.dim('skipped\n'));
|
|
@@ -175,19 +238,20 @@ async function handleFileOps(reply, loadedFiles) {
|
|
|
175
238
|
|
|
176
239
|
// Delete files
|
|
177
240
|
for (const filePath of parseDeleteCommands(reply)) {
|
|
178
|
-
const
|
|
241
|
+
const path = toRelativePath(filePath);
|
|
242
|
+
const confirmed = autoConfirm ? true : await confirm(chalk.bold(`Delete ${chalk.cyan(path)}? [y/N] `));
|
|
179
243
|
if (confirmed) {
|
|
180
|
-
const abs = resolve(process.cwd(),
|
|
244
|
+
const abs = resolve(process.cwd(), path);
|
|
181
245
|
if (existsSync(abs)) {
|
|
182
246
|
try {
|
|
183
247
|
unlinkSync(abs);
|
|
184
248
|
allFiles = getFilesAndDirs();
|
|
185
|
-
console.log(chalk.green(`โ deleted ${
|
|
249
|
+
console.log(chalk.green(`โ deleted ${path}\n`));
|
|
186
250
|
} catch (err) {
|
|
187
|
-
console.log(chalk.red(`โ could not delete ${
|
|
251
|
+
console.log(chalk.red(`โ could not delete ${path}: ${err.message}\n`));
|
|
188
252
|
}
|
|
189
253
|
} else {
|
|
190
|
-
console.log(chalk.yellow(`โ not found: ${
|
|
254
|
+
console.log(chalk.yellow(`โ not found: ${path}\n`));
|
|
191
255
|
}
|
|
192
256
|
} else {
|
|
193
257
|
console.log(chalk.dim('skipped\n'));
|
|
@@ -354,14 +418,24 @@ function buildSystemMessage() {
|
|
|
354
418
|
const fileList = files.length > 0 ? `\nFiles in working directory:\n${files.map(f => ` ${f}`).join('\n')}\n` : '';
|
|
355
419
|
const fileOpsInstructions =
|
|
356
420
|
`\nFILE OPERATIONS โ use these exact syntaxes when needed:\n` +
|
|
421
|
+
`- Always use RELATIVE paths (e.g. path/to/file or markov/next-app/README.md), never absolute paths like /Users/...\n` +
|
|
357
422
|
`- Run a terminal command: output exactly on its own line: !!run: <command>\n` +
|
|
358
|
-
`- Write or edit a file: output
|
|
423
|
+
`- Write or edit a file: output exactly on its own line !!write: path/to/file, then a fenced code block with the full file content.\n` +
|
|
359
424
|
`- Create an empty file: output exactly on its own line: !!touch: path/to/file\n` +
|
|
360
425
|
`- Create a folder: output exactly on its own line: !!mkdir: path/to/folder\n` +
|
|
361
426
|
`- Remove a folder: output exactly on its own line: !!rmdir: path/to/folder\n` +
|
|
362
427
|
`- Delete a file: output exactly on its own line: !!delete: path/to/file\n` +
|
|
363
428
|
`- You may combine multiple operations in one response.\n` +
|
|
364
|
-
`- NEVER put commands in fenced code blocks โ always use !!run: syntax for commands.\n
|
|
429
|
+
`- NEVER put commands in fenced code blocks โ always use !!run: syntax for commands.\n` +
|
|
430
|
+
`- NEVER use && inside !!run: โ use one !!run: per command. For "cd then do X", output !!run: cd <path> on one line, then !!run: <command> on the next (cd changes the working directory for all following commands).\n` +
|
|
431
|
+
`\nSETUP NEXT.JS APP (or any new project in a subfolder):\n` +
|
|
432
|
+
`1. Create the folder first: !!mkdir: next-app (or the requested name).\n` +
|
|
433
|
+
`2. Change into it on its own line: !!run: cd next-app (nothing after the path).\n` +
|
|
434
|
+
`3. Run each following command on its own !!run: line: e.g. !!run: npx create-next-app@latest . --yes, then !!run: git init, !!run: git add ., !!run: git commit -m "Initial commit".\n` +
|
|
435
|
+
`\nSETUP LARAVEL API (output exactly this):\n` +
|
|
436
|
+
`!!mkdir: laravel-api then !!run: cd laravel-api then !!run: composer create-project --prefer-dist laravel/laravel . then !!run: php artisan serve. One per line; nothing after the dot in composer; no composer run dev or dev-server. No custom routes.\n` +
|
|
437
|
+
`\nSETUP ROUTES (API only โ use when user asks to add routes, /health, /users, or API endpoints):\n` +
|
|
438
|
+
`Only after the Laravel app exists. Add in routes/api.php only (not web.php): GET /api/health returning response()->json(['status' => 'ok']), and /api/users as a resource (index, show, store, update, destroy) using User model and a controller. Register api in bootstrap/app.php if needed. Use !!write: for routes/api.php and the controller; prefix with laravel-api/ if cwd is parent. Do not use routes/web.php.\n`;
|
|
365
439
|
return { role: 'system', content: `You are Markov, an AI coding assistant.\nWorking directory: ${process.cwd()}\n${fileList}${fileOpsInstructions}` };
|
|
366
440
|
}
|
|
367
441
|
|
|
@@ -454,7 +528,7 @@ const HELP_TEXT =
|
|
|
454
528
|
chalk.cyan(' /build') + chalk.dim(' execute the stored plan\n') +
|
|
455
529
|
chalk.cyan(' /models') + chalk.dim(' switch the active AI model\n') +
|
|
456
530
|
chalk.cyan(' /cd [path]') + chalk.dim(' change working directory\n') +
|
|
457
|
-
chalk.dim('\
|
|
531
|
+
chalk.dim('\nNormal chat: plan then build (no y/n). Tips: ') + chalk.cyan('@filename') + chalk.dim(' to attach a file ยท ctrl+q to cancel\n');
|
|
458
532
|
|
|
459
533
|
export async function startInteractive() {
|
|
460
534
|
printLogo();
|
|
@@ -511,7 +585,7 @@ export async function startInteractive() {
|
|
|
511
585
|
const planPrompt =
|
|
512
586
|
`Create a detailed, numbered plan for the following task:\n\n${planRequest}\n\n` +
|
|
513
587
|
`For each step, specify exactly what will happen and which syntax will be used:\n` +
|
|
514
|
-
`- Writing or editing a file โ
|
|
588
|
+
`- Writing or editing a file โ !!write: path/to/file then fenced code block\n` +
|
|
515
589
|
`- Creating an empty file โ !!touch: path/to/file\n` +
|
|
516
590
|
`- Creating a folder โ !!mkdir: path/to/folder\n` +
|
|
517
591
|
`- Removing a folder โ !!rmdir: path/to/folder\n` +
|
|
@@ -542,7 +616,7 @@ export async function startInteractive() {
|
|
|
542
616
|
}
|
|
543
617
|
|
|
544
618
|
const buildPrompt =
|
|
545
|
-
`Execute the following plan. Use
|
|
619
|
+
`Execute the following plan. Use !!write: path then a fenced code block for file writes, !!mkdir: for folders, !!delete: for deletions.\n\n` +
|
|
546
620
|
`Plan:\n${currentPlan.text}`;
|
|
547
621
|
|
|
548
622
|
chatMessages.push({ role: 'user', content: buildPrompt });
|
|
@@ -563,7 +637,7 @@ export async function startInteractive() {
|
|
|
563
637
|
continue;
|
|
564
638
|
}
|
|
565
639
|
|
|
566
|
-
// Normal chat โ file ops
|
|
640
|
+
// Normal chat โ plan then build (two phases), apply file ops without y/n
|
|
567
641
|
const { loaded, failed } = resolveFileRefs(trimmed);
|
|
568
642
|
|
|
569
643
|
if (loaded.length > 0) {
|
|
@@ -573,19 +647,38 @@ export async function startInteractive() {
|
|
|
573
647
|
console.log(chalk.yellow(`\nโ not found: ${failed.map(f => `@${f}`).join(', ')}`));
|
|
574
648
|
}
|
|
575
649
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
650
|
+
const planPrompt =
|
|
651
|
+
`Create a detailed, numbered plan for the following task:\n\n${trimmed}\n\n` +
|
|
652
|
+
`For each step, specify exactly what will happen and which syntax will be used:\n` +
|
|
653
|
+
`- Writing or editing a file โ !!write: path/to/file then fenced code block\n` +
|
|
654
|
+
`- Creating an empty file โ !!touch: path/to/file\n` +
|
|
655
|
+
`- Creating a folder โ !!mkdir: path/to/folder\n` +
|
|
656
|
+
`- Removing a folder โ !!rmdir: path/to/folder\n` +
|
|
657
|
+
`- Deleting a file โ !!delete: path/to/file\n\n` +
|
|
658
|
+
`Do NOT output any actual file contents or commands yet โ only the plan.`;
|
|
659
|
+
|
|
660
|
+
chatMessages.push({ role: 'user', content: planPrompt });
|
|
579
661
|
const abortController = new AbortController();
|
|
580
662
|
try {
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
if (reply === null) {
|
|
663
|
+
const planReply = await streamWithViewport(chatMessages, abortController.signal);
|
|
664
|
+
if (planReply === null) {
|
|
584
665
|
chatMessages.pop();
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
chatMessages.push({ role: 'assistant', content: planReply });
|
|
669
|
+
|
|
670
|
+
const buildPrompt =
|
|
671
|
+
`Execute the following plan. Use !!write: path then a fenced code block for file writes, !!mkdir: for folders, !!delete: for deletions.\n\n` +
|
|
672
|
+
`Plan:\n${planReply}`;
|
|
673
|
+
|
|
674
|
+
chatMessages.push({ role: 'user', content: buildPrompt });
|
|
675
|
+
const buildReply = await streamWithViewport(chatMessages, abortController.signal);
|
|
676
|
+
if (buildReply === null) {
|
|
677
|
+
chatMessages.pop(); // remove build prompt
|
|
678
|
+
continue;
|
|
588
679
|
}
|
|
680
|
+
chatMessages.push({ role: 'assistant', content: buildReply });
|
|
681
|
+
allFiles = await handleFileOps(buildReply, loaded, { autoConfirm: true });
|
|
589
682
|
} catch (err) {
|
|
590
683
|
if (!abortController.signal.aborted) {
|
|
591
684
|
console.log(chalk.red(`\n${err.message}\n`));
|