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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/interactive.js +70 -46
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markov-cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "LivingCloud's CLI AI Agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 then build (no y/n). Tips: ') + chalk.cyan('@filename') + chalk.dim(' to attach a file · ctrl+q to cancel\n');
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
- // /plan <message> ask LLM to produce a plan as a normal chat message, store it
583
- if (trimmed.startsWith('/plan ')) {
584
- const planRequest = trimmed.slice(6).trim();
585
- const planPrompt =
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-nextjsscaffold 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
- chatMessages.pop();
600
- } else {
586
+ if (reply === null) { chatMessages.pop(); }
587
+ else {
601
588
  chatMessages.push({ role: 'assistant', content: reply });
602
- currentPlan = { text: reply };
603
- console.log(chalk.green('\nPlan stored. Use /build to execute it.\n'));
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
- // /buildexecute the stored plan with full file ops
598
+ // /setup-laravelscaffold 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. Use /plan <message> first.\n'));
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 — plan then build (two phases), apply file ops without y/n
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 planReply = await streamWithViewport(chatMessages, abortController.signal);
664
- if (planReply === null) {
698
+ const reply = await streamWithViewport(chatMessages, abortController.signal);
699
+ if (reply === null) {
665
700
  chatMessages.pop();
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;
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`));