markov-cli 1.0.5 → 1.0.6
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/interactive.js +70 -46
package/package.json
CHANGED
package/src/interactive.js
CHANGED
|
@@ -431,11 +431,7 @@ function buildSystemMessage() {
|
|
|
431
431
|
`\nSETUP NEXT.JS APP (or any new project in a subfolder):\n` +
|
|
432
432
|
`1. Create the folder first: !!mkdir: next-app (or the requested name).\n` +
|
|
433
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`;
|
|
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`;
|
|
439
435
|
return { role: 'system', content: `You are Markov, an AI coding assistant.\nWorking directory: ${process.cwd()}\n${fileList}${fileOpsInstructions}` };
|
|
440
436
|
}
|
|
441
437
|
|
|
@@ -524,11 +520,12 @@ const HELP_TEXT =
|
|
|
524
520
|
'\n' +
|
|
525
521
|
chalk.bold('Commands:\n') +
|
|
526
522
|
chalk.cyan(' /help') + chalk.dim(' show this help\n') +
|
|
527
|
-
chalk.cyan(' /plan <message>') + chalk.dim(' ask AI to create a plan (no files written)\n') +
|
|
528
523
|
chalk.cyan(' /build') + chalk.dim(' execute the stored plan\n') +
|
|
524
|
+
chalk.cyan(' /setup-nextjs') + chalk.dim(' scaffold a Next.js app\n') +
|
|
525
|
+
chalk.cyan(' /setup-laravel') + chalk.dim(' scaffold a Laravel API\n') +
|
|
529
526
|
chalk.cyan(' /models') + chalk.dim(' switch the active AI model\n') +
|
|
530
527
|
chalk.cyan(' /cd [path]') + chalk.dim(' change working directory\n') +
|
|
531
|
-
chalk.dim('\nNormal chat: plan
|
|
528
|
+
chalk.dim('\nNormal chat: generates a plan · /build to execute · ') + chalk.cyan('@filename') + chalk.dim(' to attach a file · ctrl+q to cancel\n');
|
|
532
529
|
|
|
533
530
|
export async function startInteractive() {
|
|
534
531
|
printLogo();
|
|
@@ -579,28 +576,18 @@ export async function startInteractive() {
|
|
|
579
576
|
continue;
|
|
580
577
|
}
|
|
581
578
|
|
|
582
|
-
// /
|
|
583
|
-
if (trimmed
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
`Create a detailed, numbered plan for the following task:\n\n${planRequest}\n\n` +
|
|
587
|
-
`For each step, specify exactly what will happen and which syntax will be used:\n` +
|
|
588
|
-
`- Writing or editing a file → !!write: path/to/file then fenced code block\n` +
|
|
589
|
-
`- Creating an empty file → !!touch: path/to/file\n` +
|
|
590
|
-
`- Creating a folder → !!mkdir: path/to/folder\n` +
|
|
591
|
-
`- Removing a folder → !!rmdir: path/to/folder\n` +
|
|
592
|
-
`- Deleting a file → !!delete: path/to/file\n\n` +
|
|
593
|
-
`Do NOT output any actual file contents or commands yet — only the plan.`;
|
|
594
|
-
chatMessages.push({ role: 'user', content: planPrompt });
|
|
579
|
+
// /setup-nextjs — scaffold a Next.js app
|
|
580
|
+
if (trimmed === '/setup-nextjs') {
|
|
581
|
+
const msg = 'Set up a new Next.js app in a subfolder. Follow the SETUP NEXT.JS APP instructions exactly.';
|
|
582
|
+
chatMessages.push({ role: 'user', content: msg });
|
|
595
583
|
const abortController = new AbortController();
|
|
596
584
|
try {
|
|
597
585
|
const reply = await streamWithViewport(chatMessages, abortController.signal);
|
|
598
|
-
if (reply === null) {
|
|
599
|
-
|
|
600
|
-
} else {
|
|
586
|
+
if (reply === null) { chatMessages.pop(); }
|
|
587
|
+
else {
|
|
601
588
|
chatMessages.push({ role: 'assistant', content: reply });
|
|
602
|
-
|
|
603
|
-
console.log(chalk.green('
|
|
589
|
+
allFiles = await handleFileOps(reply, [], { autoConfirm: true });
|
|
590
|
+
console.log(chalk.green('✓ Next.js app created.\n'));
|
|
604
591
|
}
|
|
605
592
|
} catch (err) {
|
|
606
593
|
if (!abortController.signal.aborted) console.log(chalk.red(`\n${err.message}\n`));
|
|
@@ -608,13 +595,61 @@ export async function startInteractive() {
|
|
|
608
595
|
continue;
|
|
609
596
|
}
|
|
610
597
|
|
|
611
|
-
// /
|
|
598
|
+
// /setup-laravel — scaffold a Laravel API (hardcoded, no AI)
|
|
599
|
+
if (trimmed === '/setup-laravel') {
|
|
600
|
+
const steps = [
|
|
601
|
+
{ type: 'mkdir', path: 'laravel-api' },
|
|
602
|
+
{ type: 'cd', path: 'laravel-api' },
|
|
603
|
+
{ type: 'run', cmd: 'composer create-project --prefer-dist laravel/laravel .' },
|
|
604
|
+
{ type: 'run', cmd: 'php artisan serve' },
|
|
605
|
+
];
|
|
606
|
+
for (const step of steps) {
|
|
607
|
+
if (step.type === 'mkdir') {
|
|
608
|
+
process.stdout.write(chalk.dim(` mkdir: ${step.path}\n`));
|
|
609
|
+
try {
|
|
610
|
+
mkdirSync(resolve(process.cwd(), step.path), { recursive: true });
|
|
611
|
+
allFiles = getFilesAndDirs();
|
|
612
|
+
console.log(chalk.green(` ✓ created ${step.path}\n`));
|
|
613
|
+
} catch (err) {
|
|
614
|
+
console.log(chalk.red(` ✗ ${err.message}\n`));
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
} else if (step.type === 'cd') {
|
|
618
|
+
try {
|
|
619
|
+
process.chdir(resolve(process.cwd(), step.path));
|
|
620
|
+
allFiles = getFilesAndDirs();
|
|
621
|
+
console.log(chalk.dim(` 📁 ${process.cwd()}\n`));
|
|
622
|
+
} catch (err) {
|
|
623
|
+
console.log(chalk.red(` ✗ no such directory: ${step.path}\n`));
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
} else if (step.type === 'run') {
|
|
627
|
+
process.stdout.write(chalk.dim(` running: ${step.cmd}\n`));
|
|
628
|
+
const { stdout, stderr, exitCode } = await execCommand(step.cmd);
|
|
629
|
+
const output = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
630
|
+
if (output) console.log(chalk.dim(output));
|
|
631
|
+
if (exitCode !== 0) {
|
|
632
|
+
console.log(chalk.red(` ✗ command failed (exit ${exitCode})\n`));
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
console.log(chalk.green(` ✓ done\n`));
|
|
636
|
+
allFiles = getFilesAndDirs();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
console.log(chalk.green('✓ Laravel API created.\n'));
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
// /build — execute the stored plan with file ops
|
|
612
645
|
if (trimmed === '/build') {
|
|
613
646
|
if (!currentPlan) {
|
|
614
|
-
console.log(chalk.yellow('\n⚠ No plan stored.
|
|
647
|
+
console.log(chalk.yellow('\n⚠ No plan stored. Describe what you want first.\n'));
|
|
615
648
|
continue;
|
|
616
649
|
}
|
|
617
650
|
|
|
651
|
+
const autoConfirm = await confirm(chalk.bold('Auto-confirm all operations? [y/N] '));
|
|
652
|
+
|
|
618
653
|
const buildPrompt =
|
|
619
654
|
`Execute the following plan. Use !!write: path then a fenced code block for file writes, !!mkdir: for folders, !!delete: for deletions.\n\n` +
|
|
620
655
|
`Plan:\n${currentPlan.text}`;
|
|
@@ -627,7 +662,7 @@ export async function startInteractive() {
|
|
|
627
662
|
chatMessages.pop();
|
|
628
663
|
} else {
|
|
629
664
|
chatMessages.push({ role: 'assistant', content: reply });
|
|
630
|
-
allFiles = await handleFileOps(reply, []);
|
|
665
|
+
allFiles = await handleFileOps(reply, [], { autoConfirm });
|
|
631
666
|
currentPlan = null;
|
|
632
667
|
console.log(chalk.green('✓ Plan executed.\n'));
|
|
633
668
|
}
|
|
@@ -637,7 +672,7 @@ export async function startInteractive() {
|
|
|
637
672
|
continue;
|
|
638
673
|
}
|
|
639
674
|
|
|
640
|
-
// Normal chat —
|
|
675
|
+
// Normal chat — generate a plan, store it, require /build to execute
|
|
641
676
|
const { loaded, failed } = resolveFileRefs(trimmed);
|
|
642
677
|
|
|
643
678
|
if (loaded.length > 0) {
|
|
@@ -660,25 +695,14 @@ export async function startInteractive() {
|
|
|
660
695
|
chatMessages.push({ role: 'user', content: planPrompt });
|
|
661
696
|
const abortController = new AbortController();
|
|
662
697
|
try {
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
698
|
+
const reply = await streamWithViewport(chatMessages, abortController.signal);
|
|
699
|
+
if (reply === null) {
|
|
665
700
|
chatMessages.pop();
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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;
|
|
701
|
+
} else {
|
|
702
|
+
chatMessages.push({ role: 'assistant', content: reply });
|
|
703
|
+
currentPlan = { text: reply };
|
|
704
|
+
console.log(chalk.green('\n✓ Plan stored. Use /build to execute it.\n'));
|
|
679
705
|
}
|
|
680
|
-
chatMessages.push({ role: 'assistant', content: buildReply });
|
|
681
|
-
allFiles = await handleFileOps(buildReply, loaded, { autoConfirm: true });
|
|
682
706
|
} catch (err) {
|
|
683
707
|
if (!abortController.signal.aborted) {
|
|
684
708
|
console.log(chalk.red(`\n${err.message}\n`));
|