@lipter7/blueprint 2.0.0 → 2.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 (49) hide show
  1. package/README.md +20 -16
  2. package/bin/install.js +717 -37
  3. package/bin/install.test.js +870 -0
  4. package/blueprint/bin/blueprint-tools.js +250 -0
  5. package/blueprint/bin/blueprint-tools.test.js +443 -0
  6. package/blueprint/references/planning-config.md +6 -6
  7. package/blueprint/references/verification-gates.md +346 -0
  8. package/blueprint/templates/codebase/architecture.md +2 -2
  9. package/blueprint/templates/codebase/structure.md +3 -3
  10. package/blueprint/templates/config.json +7 -1
  11. package/blueprint/templates/state.md +31 -0
  12. package/blueprint/workflows/complete-milestone.md +94 -5
  13. package/blueprint/workflows/discuss-phase.md +86 -21
  14. package/blueprint/workflows/map-codebase.md +26 -0
  15. package/blueprint/workflows/new-milestone.md +219 -1
  16. package/blueprint/workflows/new-project.md +407 -67
  17. package/blueprint/workflows/plan-phase.md +108 -4
  18. package/blueprint/workflows/progress.md +1 -1
  19. package/blueprint/workflows/research-phase.md +216 -1
  20. package/blueprint/workflows/settings-cursor.md +254 -0
  21. package/blueprint/workflows/settings.md +2 -2
  22. package/blueprint/workflows/verify-work.md +49 -1
  23. package/commands/bp/add-phase.md +1 -1
  24. package/commands/bp/add-todo.md +1 -1
  25. package/commands/bp/audit-milestone.md +1 -1
  26. package/commands/bp/check-todos.md +1 -1
  27. package/commands/bp/complete-milestone.md +1 -1
  28. package/commands/bp/debug.md +18 -1
  29. package/commands/bp/discuss-phase.md +1 -1
  30. package/commands/bp/execute-phase.md +1 -1
  31. package/commands/bp/help.md +1 -1
  32. package/commands/bp/insert-phase.md +1 -1
  33. package/commands/bp/join-discord.md +1 -1
  34. package/commands/bp/list-phase-assumptions.md +1 -1
  35. package/commands/bp/map-codebase.md +1 -1
  36. package/commands/bp/new-milestone.md +1 -1
  37. package/commands/bp/new-project.md +1 -1
  38. package/commands/bp/pause-work.md +1 -1
  39. package/commands/bp/plan-milestone-gaps.md +1 -1
  40. package/commands/bp/plan-phase.md +1 -1
  41. package/commands/bp/progress.md +1 -1
  42. package/commands/bp/quick.md +1 -1
  43. package/commands/bp/remove-phase.md +1 -1
  44. package/commands/bp/research-phase.md +1 -1
  45. package/commands/bp/resume-work.md +1 -1
  46. package/commands/bp/set-profile.md +1 -1
  47. package/commands/bp/settings.md +1 -1
  48. package/commands/bp/verify-work.md +1 -1
  49. package/package.json +1 -1
package/bin/install.js CHANGED
@@ -23,6 +23,7 @@ const hasLocal = args.includes('--local') || args.includes('-l');
23
23
  const hasOpencode = args.includes('--opencode');
24
24
  const hasClaude = args.includes('--claude');
25
25
  const hasGemini = args.includes('--gemini');
26
+ const hasCursor = args.includes('--cursor');
26
27
  const hasBoth = args.includes('--both'); // Legacy flag, keeps working
27
28
  const hasAll = args.includes('--all');
28
29
  const hasUninstall = args.includes('--uninstall') || args.includes('-u');
@@ -30,19 +31,21 @@ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
30
31
  // Runtime selection - can be set by flags or interactive prompt
31
32
  let selectedRuntimes = [];
32
33
  if (hasAll) {
33
- selectedRuntimes = ['claude', 'opencode', 'gemini'];
34
+ selectedRuntimes = ['claude', 'opencode', 'gemini', 'cursor'];
34
35
  } else if (hasBoth) {
35
36
  selectedRuntimes = ['claude', 'opencode'];
36
37
  } else {
37
38
  if (hasOpencode) selectedRuntimes.push('opencode');
38
39
  if (hasClaude) selectedRuntimes.push('claude');
39
40
  if (hasGemini) selectedRuntimes.push('gemini');
41
+ if (hasCursor) selectedRuntimes.push('cursor');
40
42
  }
41
43
 
42
44
  // Helper to get directory name for a runtime (used for local/project installs)
43
45
  function getDirName(runtime) {
44
46
  if (runtime === 'opencode') return '.opencode';
45
47
  if (runtime === 'gemini') return '.gemini';
48
+ if (runtime === 'cursor') return '.cursor';
46
49
  return '.claude';
47
50
  }
48
51
 
@@ -95,7 +98,18 @@ function getGlobalDir(runtime, explicitDir = null) {
95
98
  }
96
99
  return path.join(os.homedir(), '.gemini');
97
100
  }
98
-
101
+
102
+ if (runtime === 'cursor') {
103
+ // Cursor: --config-dir > CURSOR_CONFIG_DIR > ~/.cursor
104
+ if (explicitDir) {
105
+ return expandTilde(explicitDir);
106
+ }
107
+ if (process.env.CURSOR_CONFIG_DIR) {
108
+ return expandTilde(process.env.CURSOR_CONFIG_DIR);
109
+ }
110
+ return path.join(os.homedir(), '.cursor');
111
+ }
112
+
99
113
  // Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
100
114
  if (explicitDir) {
101
115
  return expandTilde(explicitDir);
@@ -116,7 +130,7 @@ const banner = '\n' +
116
130
  '\n' +
117
131
  ' Blueprint ' + dim + 'v' + pkg.version + reset + '\n' +
118
132
  ' A meta-prompting, context engineering and spec-driven\n' +
119
- ' development system for Claude Code, OpenCode, and Gemini by TÂCHES.\n';
133
+ ' development system for Claude Code, OpenCode, Gemini, and Cursor by TÂCHES.\n';
120
134
 
121
135
  // Parse --config-dir argument
122
136
  function parseConfigDirArg() {
@@ -150,7 +164,7 @@ console.log(banner);
150
164
 
151
165
  // Show help if requested
152
166
  if (hasHelp) {
153
- console.log(` ${yellow}Usage:${reset} npx @lipter7/blueprint [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall Blueprint (remove all Blueprint files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx @lipter7/blueprint\n\n ${dim}# Install for Claude Code globally${reset}\n npx @lipter7/blueprint --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx @lipter7/blueprint --gemini --global\n\n ${dim}# Install for all runtimes globally${reset}\n npx @lipter7/blueprint --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx @lipter7/blueprint --claude --global --config-dir ~/.claude-bc\n\n ${dim}# Install to current project only${reset}\n npx @lipter7/blueprint --claude --local\n\n ${dim}# Uninstall Blueprint from Claude Code globally${reset}\n npx @lipter7/blueprint --claude --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR environment variables.\n`);
167
+ console.log(` ${yellow}Usage:${reset} npx @lipter7/blueprint [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall Blueprint (remove all Blueprint files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx @lipter7/blueprint\n\n ${dim}# Install for Claude Code globally${reset}\n npx @lipter7/blueprint --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx @lipter7/blueprint --gemini --global\n\n ${dim}# Install for Cursor globally${reset}\n npx @lipter7/blueprint --cursor --global\n\n ${dim}# Install for all runtimes globally${reset}\n npx @lipter7/blueprint --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx @lipter7/blueprint --claude --global --config-dir ~/.claude-bc\n\n ${dim}# Install to current project only${reset}\n npx @lipter7/blueprint --claude --local\n\n ${dim}# Uninstall Blueprint from Claude Code globally${reset}\n npx @lipter7/blueprint --claude --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR / CURSOR_CONFIG_DIR environment variables.\n`);
154
168
  process.exit(0);
155
169
  }
156
170
 
@@ -224,6 +238,16 @@ function getCommitAttribution(runtime) {
224
238
  } else {
225
239
  result = settings.attribution.commit;
226
240
  }
241
+ } else if (runtime === 'cursor') {
242
+ // Cursor: check cursor settings.json for attribution config
243
+ const settings = readSettings(path.join(getGlobalDir('cursor', explicitConfigDir), 'settings.json'));
244
+ if (!settings.attribution || settings.attribution.commit === undefined) {
245
+ result = undefined;
246
+ } else if (settings.attribution.commit === '') {
247
+ result = null;
248
+ } else {
249
+ result = settings.attribution.commit;
250
+ }
227
251
  } else {
228
252
  // Claude Code
229
253
  const settings = readSettings(path.join(getGlobalDir('claude', explicitConfigDir), 'settings.json'));
@@ -308,6 +332,438 @@ const claudeToGeminiTools = {
308
332
  AskUserQuestion: 'ask_user',
309
333
  };
310
334
 
335
+ // Cursor skill ordering — numbered prefixes for palette sorting
336
+ const CURSOR_SKILL_ORDER = {
337
+ 'map-codebase': 1,
338
+ 'new-project': 2,
339
+ 'new-milestone': 3,
340
+ 'discuss-phase': 4,
341
+ 'research-phase': 5,
342
+ 'plan-phase': 6,
343
+ 'execute-phase': 7,
344
+ 'verify-work': 8,
345
+ 'audit-milestone': 9,
346
+ 'plan-milestone-gaps': 10,
347
+ 'complete-milestone': 11,
348
+ 'add-phase': 12,
349
+ 'insert-phase': 13,
350
+ 'remove-phase': 14,
351
+ 'progress': 15,
352
+ 'resume-work': 16,
353
+ 'pause-work': 17,
354
+ 'quick': 18,
355
+ 'debug': 19,
356
+ 'list-phase-assumptions': 20,
357
+ 'add-todo': 21,
358
+ 'check-todos': 22,
359
+ 'settings': 23,
360
+ 'set-profile': 24,
361
+ 'update': 25,
362
+ 'reapply-patches': 26,
363
+ 'help': 27,
364
+ 'join-discord': 28,
365
+ };
366
+
367
+ const GATE_TEMPLATES = {
368
+ confidence_gate: `
369
+ <cursor_interaction type="confidence_gate" id="{gate_id}">
370
+ IMPORTANT: You MUST use the AskQuestion tool here. Do NOT proceed without user input.
371
+
372
+ Present the following to the user via AskQuestion:
373
+ - Context: {what_was_just_completed}
374
+ - Options: {confidence_options}
375
+ - Wait for response before continuing
376
+
377
+ Based on user's choice:
378
+ - If "{option_a}": {action_a}
379
+ - If "{option_b}": {action_b}
380
+ - If user provides custom input: incorporate their guidance and proceed accordingly
381
+ </cursor_interaction>`,
382
+
383
+ decision_gate: `
384
+ <cursor_interaction type="decision_gate" id="{gate_id}">
385
+ IMPORTANT: You MUST use the AskQuestion tool here. Do NOT proceed without user input.
386
+
387
+ Present the following choice to the user via AskQuestion:
388
+ - Context: {decision_context}
389
+ - Options:
390
+ 1. {option_1} — {description_1}
391
+ 2. {option_2} — {description_2}
392
+ {option_3_line}
393
+ - Wait for response before continuing
394
+
395
+ Route based on user's choice:
396
+ - "{option_1}": {action_1}
397
+ - "{option_2}": {action_2}
398
+ - Custom input: {fallback_action}
399
+ </cursor_interaction>`,
400
+
401
+ continuation_gate: `
402
+ <cursor_interaction type="continuation_gate" id="{gate_id}">
403
+ IMPORTANT: You MUST use the AskQuestion tool here. Do NOT proceed without user input.
404
+
405
+ Ask the user via AskQuestion:
406
+ - Context: {current_state_summary}
407
+ - Options:
408
+ 1. {proceed_option} — {proceed_description}
409
+ 2. {continue_option} — {continue_description}
410
+ - Wait for response before continuing
411
+
412
+ If "{continue_option}": loop back to {loop_target_step}
413
+ If "{proceed_option}": continue to next step
414
+ </cursor_interaction>`,
415
+
416
+ action_gate: `
417
+ <cursor_interaction type="action_gate" id="{gate_id}">
418
+ IMPORTANT: You MUST use the AskQuestion tool here. Do NOT proceed without user input.
419
+
420
+ Present available actions to the user via AskQuestion:
421
+ - Context: {action_context}
422
+ - Options:
423
+ 1. {action_1} — {description_1}
424
+ 2. {action_2} — {description_2}
425
+ 3. {action_3} — {description_3}
426
+ {action_4_line}
427
+ - Wait for response before continuing
428
+
429
+ Execute the user's chosen action:
430
+ - "{action_1}": {execute_1}
431
+ - "{action_2}": {execute_2}
432
+ - "{action_3}": {execute_3}
433
+ - Custom input: {fallback_action}
434
+ </cursor_interaction>`
435
+ };
436
+
437
+ const DISCUSS_DEEP_DIVE_BLOCK = `
438
+ <cursor_interaction type="deep_dive" id="discuss-area-questioning">
439
+ IMPORTANT: For EACH selected gray area, you MUST conduct a focused discussion using AskQuestion.
440
+
441
+ For each area in the selected list:
442
+ 1. Ask 4 specific decision questions about this area using AskQuestion
443
+ - Each question should have 2-3 concrete options
444
+ - Include a "You decide" option when the decision is genuinely discretionary
445
+ - Wait for each response before asking the next question
446
+ 2. After all 4 questions for an area, use AskQuestion to ask:
447
+ - "More questions about {area_name}?" / "Move to next area" / "Done discussing"
448
+ 3. If "More questions": ask additional questions about this area
449
+ 4. If "Move to next area": proceed to the next selected area
450
+ 5. If "Done discussing": stop discussion, move to CONTEXT.md creation
451
+
452
+ Record every user decision. Each answer populates a decision record in CONTEXT.md.
453
+ Do NOT skip areas. Do NOT proceed to CONTEXT.md creation until the user says "Done" or all areas are covered.
454
+ </cursor_interaction>`;
455
+
456
+ const SETTINGS_CONFIG_BLOCK = `
457
+ <cursor_interaction type="configuration_chain" id="settings-configuration">
458
+ IMPORTANT: You MUST ask ALL configuration questions using AskQuestion, one at a time. Do NOT skip any.
459
+
460
+ Ask each question in sequence via AskQuestion. Wait for each response before asking the next.
461
+
462
+ 1. Per-Agent Model Configuration:
463
+ For each of the 11 agent roles, present available models and let the user select:
464
+ - bp-planner, bp-executor, bp-verifier, bp-debugger, bp-codebase-mapper
465
+ - bp-phase-researcher, bp-project-researcher, bp-research-synthesizer
466
+ - bp-roadmapper, bp-plan-checker, bp-integration-checker
467
+
468
+ 2. Plan Researcher (research before planning):
469
+ - "Yes" — Run phase researcher before planner
470
+ - "No" — Skip research, plan from existing context
471
+
472
+ 3. Plan Checker (verify plans after creation):
473
+ - "Yes" — Run plan checker after planner
474
+ - "No" — Skip plan verification
475
+
476
+ 4. Execution Verifier (verify after execution):
477
+ - "Yes" — Run verifier after executor
478
+ - "No" — Skip execution verification
479
+
480
+ 5. Git Branching Strategy:
481
+ - "None (Recommended)" — All work on current branch
482
+ - "Per Phase" — Create branch per phase
483
+ - "Per Milestone" — Create branch per milestone
484
+
485
+ After all responses are collected, write the configuration to .blueprint/config.json with both agent_models and workflow settings.
486
+ </cursor_interaction>`;
487
+
488
+ const DEBUG_SYMPTOMS_BLOCK = `
489
+ <cursor_interaction type="symptom_gathering" id="debug-symptoms">
490
+ IMPORTANT: You MUST ask ALL 5 diagnostic questions using AskQuestion, one at a time. Do NOT skip any. Do NOT start debugging until all 5 are answered.
491
+
492
+ Ask each question in sequence via AskQuestion:
493
+
494
+ 1. Expected behavior: "What should happen? Describe the correct behavior."
495
+ (Freeform response — no predefined options)
496
+
497
+ 2. Actual behavior: "What happens instead? Describe what you observe."
498
+ (Freeform response)
499
+
500
+ 3. Error messages: "Are there any error messages? Paste or describe them."
501
+ (Freeform response — "None" is valid)
502
+
503
+ 4. Timeline: "When did this start? Has it ever worked correctly?"
504
+ (Freeform response)
505
+
506
+ 5. Reproduction: "How do you trigger this issue? What steps reproduce it?"
507
+ (Freeform response)
508
+
509
+ After collecting all 5 responses, populate the debug session file at .blueprint/debug/{slug}.md with the gathered information, then proceed to hypothesis formation.
510
+ </cursor_interaction>`;
511
+
512
+ // Cursor interaction map — defines where AskUserQuestion blocks appear in workflow files
513
+ // and how to convert them to Cursor-compatible AskQuestion interaction blocks.
514
+ // Each entry has: id, type (gate template or bespoke), marker (regex matching the source block),
515
+ // and either params (for gate templates) or block (for bespoke replacements).
516
+ const CURSOR_INTERACTION_MAP = {
517
+ 'workflows/discovery-phase.md': [
518
+ {
519
+ id: 'discovery-confidence',
520
+ type: 'confidence_gate',
521
+ marker: /If confidence is LOW:\nUse AskUserQuestion:[\s\S]*?- "Pause" - I need to think about this/,
522
+ params: {
523
+ gate_id: 'discovery-confidence',
524
+ what_was_just_completed: 'Discovery research completed but confidence is LOW',
525
+ confidence_options: '"Dig deeper" to do more research, or "Proceed anyway" to accept uncertainty',
526
+ option_a: 'Dig deeper',
527
+ action_a: 'Do more research before planning',
528
+ option_b: 'Proceed anyway',
529
+ action_b: 'Accept uncertainty, plan with caveats'
530
+ }
531
+ }
532
+ ],
533
+ 'workflows/discuss-phase.md': [
534
+ {
535
+ id: 'discuss-check-existing',
536
+ type: 'decision_gate',
537
+ marker: /\*\*If exists:\*\*\nUse AskUserQuestion:[\s\S]*?If "Skip": Exit workflow/,
538
+ params: {
539
+ gate_id: 'discuss-check-existing',
540
+ decision_context: 'Phase already has existing context (CONTEXT.md found)',
541
+ option_1: 'Update it',
542
+ description_1: 'Review and revise existing context',
543
+ option_2: 'View it',
544
+ description_2: 'Show me what\'s there',
545
+ option_3_line: '3. Skip — Use existing context as-is',
546
+ action_1: 'Load existing context, continue to analyze_phase',
547
+ action_2: 'Display CONTEXT.md, then offer update/skip',
548
+ fallback_action: 'Exit workflow (treat as skip)'
549
+ }
550
+ },
551
+ {
552
+ id: 'discuss-deep-dive',
553
+ type: 'bespoke',
554
+ marker: /\*\*Then use AskUserQuestion \(multiSelect: true\):\*\*[\s\S]*?Continue to discuss_areas with selected areas\.\n<\/step>\n\n<step name="discuss_areas">[\s\S]*?Track deferred ideas internally\.\n<\/step>/,
555
+ block: DISCUSS_DEEP_DIVE_BLOCK
556
+ },
557
+ {
558
+ id: 'discuss-verify-context',
559
+ type: 'confidence_gate',
560
+ marker: /AskUserQuestion:\n- header: "Context"\n- question: "Does this accurately capture what you described\?"\n- options:[\s\S]*?- "Review full file" — Show me the raw file first/,
561
+ params: {
562
+ gate_id: 'discuss-verify-context',
563
+ what_was_just_completed: 'CONTEXT.md has been created with your implementation decisions',
564
+ confidence_options: '"Approve" to proceed, "Corrections" to change things, or "Review full file" to see the raw file',
565
+ option_a: 'Approve',
566
+ action_a: 'Proceed to git commit',
567
+ option_b: 'Corrections',
568
+ action_b: 'Ask what to change, apply edits, re-present for approval'
569
+ }
570
+ }
571
+ ],
572
+ 'workflows/quick.md': [
573
+ {
574
+ id: 'quick-task-description',
575
+ type: 'action_gate',
576
+ marker: /AskUserQuestion\(\n\s*header: "Quick Task",\n\s*question: "What do you want to do\?",\n\s*followUp: null\n\)/,
577
+ params: {
578
+ gate_id: 'quick-task-description',
579
+ action_context: 'Starting a quick task — need task description',
580
+ action_1: 'Describe your task',
581
+ description_1: 'Type what you want to do (freeform)',
582
+ action_2: 'Cancel',
583
+ description_2: 'Exit quick task mode',
584
+ action_3: 'View recent tasks',
585
+ description_3: 'See previously completed quick tasks',
586
+ action_4_line: '',
587
+ execute_1: 'Store response as task description and proceed to initialization',
588
+ execute_2: 'Exit workflow',
589
+ execute_3: 'Show quick task history from STATE.md',
590
+ fallback_action: 'Use input as the task description'
591
+ }
592
+ }
593
+ ],
594
+ 'workflows/add-todo.md': [
595
+ {
596
+ id: 'add-todo-duplicate',
597
+ type: 'decision_gate',
598
+ marker: /If overlapping, use AskUserQuestion:[\s\S]*?- "Add anyway" — create as separate todo/,
599
+ params: {
600
+ gate_id: 'add-todo-duplicate',
601
+ decision_context: 'A similar todo already exists',
602
+ option_1: 'Skip',
603
+ description_1: 'Keep existing todo',
604
+ option_2: 'Replace',
605
+ description_2: 'Update existing with new context',
606
+ option_3_line: '3. Add anyway — Create as separate todo',
607
+ action_1: 'Keep existing todo, exit without creating new one',
608
+ action_2: 'Update existing todo file with new context',
609
+ fallback_action: 'Create as separate todo alongside existing one'
610
+ }
611
+ }
612
+ ],
613
+ 'workflows/settings.md': [
614
+ {
615
+ id: 'settings-configuration',
616
+ type: 'bespoke',
617
+ marker: /Use AskUserQuestion with current values pre-selected:[\s\S]*?\]\)\n```/,
618
+ block: SETTINGS_CONFIG_BLOCK
619
+ }
620
+ ],
621
+ 'workflows/complete-milestone.md': [
622
+ {
623
+ id: 'complete-milestone-branches',
624
+ type: 'action_gate',
625
+ marker: /AskUserQuestion with options: Squash merge[\s\S]*?Keep branches\./,
626
+ params: {
627
+ gate_id: 'complete-milestone-branches',
628
+ action_context: 'Git branches detected for completed milestone. Choose how to handle them.',
629
+ action_1: 'Squash merge (Recommended)',
630
+ description_1: 'Merge branches into main with a single squash commit',
631
+ action_2: 'Merge with history',
632
+ description_2: 'Merge branches preserving full commit history',
633
+ action_3: 'Delete without merging',
634
+ description_3: 'Remove branches (already merged or not needed)',
635
+ action_4_line: '4. Keep branches — Leave for manual handling',
636
+ execute_1: 'Squash merge each branch into main',
637
+ execute_2: 'Merge each branch with --no-ff into main',
638
+ execute_3: 'Delete the branch(es)',
639
+ fallback_action: 'Report "Branches preserved for manual handling"'
640
+ }
641
+ }
642
+ ],
643
+ 'workflows/new-project.md': [
644
+ {
645
+ id: 'new-project-brownfield',
646
+ type: 'decision_gate',
647
+ marker: /Use AskUserQuestion:\n- header: "Existing Code"[\s\S]*?- "Skip mapping" — Proceed with project initialization/,
648
+ params: {
649
+ gate_id: 'new-project-brownfield',
650
+ decision_context: 'Existing code detected in this directory but no codebase map exists',
651
+ option_1: 'Map codebase first',
652
+ description_1: 'Run /bp:map-codebase to understand existing architecture (Recommended)',
653
+ option_2: 'Skip mapping',
654
+ description_2: 'Proceed with project initialization',
655
+ option_3_line: '',
656
+ action_1: 'Exit and run /bp:map-codebase first, then return to /bp:new-project',
657
+ action_2: 'Continue with project initialization without codebase mapping',
658
+ fallback_action: 'Continue with project initialization'
659
+ }
660
+ },
661
+ {
662
+ id: 'new-project-ready',
663
+ type: 'continuation_gate',
664
+ marker: /When you could write a clear PROJECT\.md, use AskUserQuestion:[\s\S]*?- "Keep exploring" — I want to share more \/ ask me more/,
665
+ params: {
666
+ gate_id: 'new-project-ready',
667
+ current_state_summary: 'Deep questioning phase — enough context gathered to write PROJECT.md',
668
+ proceed_option: 'Create PROJECT.md',
669
+ proceed_description: 'Let\'s move forward with what we have',
670
+ continue_option: 'Keep exploring',
671
+ continue_description: 'I want to share more / ask me more',
672
+ loop_target_step: 'deep questioning (ask what they want to add, or identify gaps and probe naturally)'
673
+ }
674
+ }
675
+ ],
676
+ 'workflows/new-milestone.md': [
677
+ {
678
+ id: 'new-milestone-staleness',
679
+ type: 'decision_gate',
680
+ marker: /Present to the user via `AskUserQuestion`:[\s\S]*?\*\*Options:\*\*\n1\. \*\*Full remap\*\*[\s\S]*?2\. \*\*Skip\*\* — Continue with current codebase docs/,
681
+ params: {
682
+ gate_id: 'new-milestone-staleness',
683
+ decision_context: 'Codebase mapping may be stale — significant changes since last mapping',
684
+ option_1: 'Full remap',
685
+ description_1: 'Re-run all 4 mapping agents (recommended if significant structural changes)',
686
+ option_2: 'Skip',
687
+ description_2: 'Continue with current codebase docs',
688
+ option_3_line: '',
689
+ action_1: 'Spawn 4 bp-codebase-mapper agents in parallel and update mapping metadata',
690
+ action_2: 'Continue to the next step with existing codebase docs',
691
+ fallback_action: 'Continue with existing codebase docs'
692
+ }
693
+ }
694
+ ],
695
+ 'workflows/check-todos.md': [
696
+ {
697
+ id: 'check-todos-action',
698
+ type: 'action_gate',
699
+ marker: /Use AskUserQuestion:\n- header: "Action"\n- question: "This todo relates to Phase[\s\S]*?"Put it back" — return to list/,
700
+ params: {
701
+ gate_id: 'check-todos-action',
702
+ action_context: 'Todo selected — choose what to do with it',
703
+ action_1: 'Work on it now',
704
+ description_1: 'Move to done, start working',
705
+ action_2: 'Add to phase plan',
706
+ description_2: 'Include when planning the related phase',
707
+ action_3: 'Brainstorm approach',
708
+ description_3: 'Think through before deciding',
709
+ action_4_line: '4. Put it back — Return to list',
710
+ execute_1: 'Move todo to done/ directory, update STATE.md, begin work',
711
+ execute_2: 'Note todo reference in phase planning notes, keep in pending',
712
+ execute_3: 'Keep in pending, start discussion about problem and approaches',
713
+ fallback_action: 'Return to todo list'
714
+ }
715
+ }
716
+ ],
717
+ 'commands/bp/debug.md': [
718
+ {
719
+ id: 'debug-symptoms',
720
+ type: 'bespoke',
721
+ marker: /## 2\. Gather Symptoms \(if new issue\)\n\nUse AskUserQuestion for each:[\s\S]*?After all gathered, confirm ready to investigate\./,
722
+ block: DEBUG_SYMPTOMS_BLOCK
723
+ }
724
+ ]
725
+ };
726
+
727
+ function applyInteractionConversions(content, relativePath) {
728
+ const normalizedPath = relativePath
729
+ .replace(/^blueprint\//, '')
730
+ .replace(/^commands\/bp\//, 'commands/bp/');
731
+
732
+ const interactions = CURSOR_INTERACTION_MAP[normalizedPath];
733
+ if (!interactions || interactions.length === 0) {
734
+ return content.replace(/\bAskUserQuestion\b/g, 'AskQuestion');
735
+ }
736
+
737
+ let result = content;
738
+
739
+ for (const interaction of interactions) {
740
+ if (interaction.type === 'bespoke') {
741
+ result = result.replace(interaction.marker, interaction.block);
742
+ } else {
743
+ let filled = GATE_TEMPLATES[interaction.type];
744
+ for (const [key, value] of Object.entries(interaction.params)) {
745
+ filled = filled.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
746
+ }
747
+ result = result.replace(interaction.marker, filled);
748
+ }
749
+ }
750
+
751
+ // Catch any remaining AskUserQuestion references
752
+ result = result.replace(/\bAskUserQuestion\b/g, 'AskQuestion');
753
+
754
+ return result;
755
+ }
756
+
757
+ function convertCommandReferences(content) {
758
+ return content.replace(/\/bp:([a-z-]+)/g, (match, name) => {
759
+ const num = CURSOR_SKILL_ORDER[name];
760
+ if (num != null) {
761
+ return `/bp-${String(num).padStart(2, '0')}-${name}`;
762
+ }
763
+ return match;
764
+ });
765
+ }
766
+
311
767
  /**
312
768
  * Convert a Claude Code tool name to OpenCode format
313
769
  * - Applies special mappings (AskUserQuestion -> question, etc.)
@@ -583,6 +1039,169 @@ function convertClaudeToGeminiToml(content) {
583
1039
  return toml;
584
1040
  }
585
1041
 
1042
+ /**
1043
+ * Convert Claude Code command file to Cursor Skill format
1044
+ * Strips disallowed frontmatter fields and converts name format
1045
+ * @param {string} content - Markdown file content with YAML frontmatter
1046
+ * @param {string} pathPrefix - Path prefix to replace ~/.claude/ with
1047
+ * @returns {string} - Converted content
1048
+ */
1049
+ function convertClaudeToCursorSkill(content, pathPrefix) {
1050
+ let converted = content;
1051
+ converted = converted.replace(/~\/\.claude\//g, pathPrefix);
1052
+
1053
+ if (!converted.startsWith('---')) return converted;
1054
+
1055
+ const endIndex = converted.indexOf('---', 3);
1056
+ if (endIndex === -1) return converted;
1057
+
1058
+ const frontmatter = converted.substring(3, endIndex).trim();
1059
+ const body = converted.substring(endIndex + 3);
1060
+
1061
+ const lines = frontmatter.split('\n');
1062
+ const newLines = [];
1063
+ let inAllowedTools = false;
1064
+
1065
+ for (const line of lines) {
1066
+ const trimmed = line.trim();
1067
+
1068
+ // Strip allowed-tools YAML array
1069
+ if (trimmed.startsWith('allowed-tools:')) { inAllowedTools = true; continue; }
1070
+ if (inAllowedTools) {
1071
+ if (trimmed.startsWith('- ')) continue;
1072
+ if (trimmed && !trimmed.startsWith('-')) inAllowedTools = false;
1073
+ }
1074
+
1075
+ // Strip argument-hint
1076
+ if (trimmed.startsWith('argument-hint:')) continue;
1077
+
1078
+ // Strip agent
1079
+ if (trimmed.startsWith('agent:')) continue;
1080
+
1081
+ // Strip tools (comma-separated string format)
1082
+ if (trimmed.startsWith('tools:')) continue;
1083
+
1084
+ // Convert name from bp:command to bp-command
1085
+ if (trimmed.startsWith('name:')) {
1086
+ const name = trimmed.substring(5).trim().replace(/^bp:/, 'bp-');
1087
+ newLines.push(`name: ${name}`);
1088
+ continue;
1089
+ }
1090
+
1091
+ if (!inAllowedTools) newLines.push(line);
1092
+ }
1093
+
1094
+ // Add disable-model-invocation
1095
+ newLines.push('disable-model-invocation: true');
1096
+
1097
+ const newFrontmatter = newLines.join('\n').trim();
1098
+ return `---\n${newFrontmatter}\n---${body}`;
1099
+ }
1100
+
1101
+ /**
1102
+ * Convert Claude Code agent file to Cursor agent format
1103
+ * Strips disallowed frontmatter fields and adds model: inherit
1104
+ * @param {string} content - Markdown file content with YAML frontmatter
1105
+ * @param {string} pathPrefix - Path prefix to replace ~/.claude/ with
1106
+ * @returns {string} - Converted content
1107
+ */
1108
+ function convertClaudeToCursorAgent(content, pathPrefix) {
1109
+ let converted = content;
1110
+ converted = converted.replace(/~\/\.claude\//g, pathPrefix);
1111
+
1112
+ if (!converted.startsWith('---')) return converted;
1113
+
1114
+ const endIndex = converted.indexOf('---', 3);
1115
+ if (endIndex === -1) return converted;
1116
+
1117
+ const frontmatter = converted.substring(3, endIndex).trim();
1118
+ const body = converted.substring(endIndex + 3);
1119
+
1120
+ const lines = frontmatter.split('\n');
1121
+ const newLines = [];
1122
+ let inAllowedTools = false;
1123
+
1124
+ for (const line of lines) {
1125
+ const trimmed = line.trim();
1126
+
1127
+ // Strip allowed-tools array
1128
+ if (trimmed.startsWith('allowed-tools:')) { inAllowedTools = true; continue; }
1129
+ if (inAllowedTools) {
1130
+ if (trimmed.startsWith('- ')) continue;
1131
+ if (trimmed && !trimmed.startsWith('-')) inAllowedTools = false;
1132
+ }
1133
+
1134
+ // Strip tools (comma-separated)
1135
+ if (trimmed.startsWith('tools:')) {
1136
+ const toolsValue = trimmed.substring(6).trim();
1137
+ if (toolsValue) continue; // inline tools: value
1138
+ inAllowedTools = true; // tools: with YAML array following
1139
+ continue;
1140
+ }
1141
+
1142
+ // Strip color
1143
+ if (trimmed.startsWith('color:')) continue;
1144
+
1145
+ if (!inAllowedTools) newLines.push(line);
1146
+ }
1147
+
1148
+ // Add model: inherit
1149
+ newLines.push('model: inherit');
1150
+
1151
+ const newFrontmatter = newLines.join('\n').trim();
1152
+ return `---\n${newFrontmatter}\n---${body}`;
1153
+ }
1154
+
1155
+ /**
1156
+ * Copy commands to Cursor's nested skills directory structure
1157
+ * Cursor expects: skills/bp-01-commandname/SKILL.md
1158
+ * Source structure: commands/bp/commandname.md
1159
+ *
1160
+ * @param {string} srcDir - Source directory (e.g., commands/bp/)
1161
+ * @param {string} destDir - Destination directory (e.g., skills/)
1162
+ * @param {string} pathPrefix - Path prefix for file references
1163
+ * @param {string} runtime - Target runtime
1164
+ * @returns {number} - Number of skills installed
1165
+ */
1166
+ function copySkillsFromCommands(srcDir, destDir, pathPrefix, runtime) {
1167
+ if (!fs.existsSync(srcDir)) return 0;
1168
+
1169
+ // Clean up old skills
1170
+ if (fs.existsSync(destDir)) {
1171
+ for (const dir of fs.readdirSync(destDir)) {
1172
+ if (dir.startsWith('bp-') && fs.statSync(path.join(destDir, dir)).isDirectory()) {
1173
+ fs.rmSync(path.join(destDir, dir), { recursive: true });
1174
+ }
1175
+ }
1176
+ }
1177
+
1178
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
1179
+ let installedCount = 0;
1180
+
1181
+ for (const entry of entries) {
1182
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
1183
+
1184
+ const commandName = entry.name.replace('.md', '');
1185
+ const skillNumber = CURSOR_SKILL_ORDER[commandName];
1186
+ const skillDirName = skillNumber != null
1187
+ ? `bp-${String(skillNumber).padStart(2, '0')}-${commandName}`
1188
+ : `bp-${commandName}`;
1189
+
1190
+ const skillDir = path.join(destDir, skillDirName);
1191
+ fs.mkdirSync(skillDir, { recursive: true });
1192
+
1193
+ let content = fs.readFileSync(path.join(srcDir, entry.name), 'utf8');
1194
+ content = convertClaudeToCursorSkill(content, pathPrefix);
1195
+ content = applyInteractionConversions(content, `commands/bp/${entry.name}`);
1196
+ content = processAttribution(content, getCommitAttribution(runtime));
1197
+
1198
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
1199
+ installedCount++;
1200
+ }
1201
+
1202
+ return installedCount;
1203
+ }
1204
+
586
1205
  /**
587
1206
  * Copy commands to a flat structure for OpenCode
588
1207
  * OpenCode expects: command/bp-help.md (invoked as /bp-help)
@@ -671,7 +1290,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
671
1290
  content = content.replace(claudeDirRegex, pathPrefix);
672
1291
  content = processAttribution(content, getCommitAttribution(runtime));
673
1292
 
674
- // Convert frontmatter for opencode compatibility
1293
+ // Convert for runtime compatibility
675
1294
  if (isOpencode) {
676
1295
  content = convertClaudeToOpencodeFrontmatter(content);
677
1296
  fs.writeFileSync(destPath, content);
@@ -682,6 +1301,12 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime) {
682
1301
  // Replace extension with .toml
683
1302
  const tomlPath = destPath.replace(/\.md$/, '.toml');
684
1303
  fs.writeFileSync(tomlPath, tomlContent);
1304
+ } else if (runtime === 'cursor') {
1305
+ // Apply Cursor interaction conversions and command reference conversions
1306
+ const relativePath = srcPath.replace(/^.*?\/blueprint\//, 'blueprint/').replace(/^.*?\/commands\//, 'commands/');
1307
+ content = applyInteractionConversions(content, relativePath);
1308
+ content = convertCommandReferences(content);
1309
+ fs.writeFileSync(destPath, content);
685
1310
  } else {
686
1311
  fs.writeFileSync(destPath, content);
687
1312
  }
@@ -803,6 +1428,7 @@ function uninstall(isGlobal, runtime = 'claude') {
803
1428
  let runtimeLabel = 'Claude Code';
804
1429
  if (runtime === 'opencode') runtimeLabel = 'OpenCode';
805
1430
  if (runtime === 'gemini') runtimeLabel = 'Gemini';
1431
+ if (runtime === 'cursor') runtimeLabel = 'Cursor';
806
1432
 
807
1433
  console.log(` Uninstalling Blueprint from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
808
1434
 
@@ -815,8 +1441,27 @@ function uninstall(isGlobal, runtime = 'claude') {
815
1441
 
816
1442
  let removedCount = 0;
817
1443
 
818
- // 1. Remove Blueprint commands directory
819
- if (isOpencode) {
1444
+ // 1. Remove Blueprint commands/skills directory
1445
+ const isCursor = runtime === 'cursor';
1446
+ if (isCursor) {
1447
+ // Cursor: remove skills/bp-*/ directories
1448
+ const skillsDir = path.join(targetDir, 'skills');
1449
+ if (fs.existsSync(skillsDir)) {
1450
+ const dirs = fs.readdirSync(skillsDir);
1451
+ let skillCount = 0;
1452
+ for (const dir of dirs) {
1453
+ const fullPath = path.join(skillsDir, dir);
1454
+ if (dir.startsWith('bp-') && fs.statSync(fullPath).isDirectory()) {
1455
+ fs.rmSync(fullPath, { recursive: true });
1456
+ skillCount++;
1457
+ }
1458
+ }
1459
+ if (skillCount > 0) {
1460
+ removedCount++;
1461
+ console.log(` ${green}✓${reset} Removed ${skillCount} Blueprint skills`);
1462
+ }
1463
+ }
1464
+ } else if (isOpencode) {
820
1465
  // OpenCode: remove command/bp-*.md files
821
1466
  const commandDir = path.join(targetDir, 'command');
822
1467
  if (fs.existsSync(commandDir)) {
@@ -1284,6 +1929,7 @@ function reportLocalPatches(configDir) {
1284
1929
  function install(isGlobal, runtime = 'claude') {
1285
1930
  const isOpencode = runtime === 'opencode';
1286
1931
  const isGemini = runtime === 'gemini';
1932
+ const isCursor = runtime === 'cursor';
1287
1933
  const dirName = getDirName(runtime);
1288
1934
  const src = path.join(__dirname, '..');
1289
1935
 
@@ -1306,6 +1952,7 @@ function install(isGlobal, runtime = 'claude') {
1306
1952
  let runtimeLabel = 'Claude Code';
1307
1953
  if (isOpencode) runtimeLabel = 'OpenCode';
1308
1954
  if (isGemini) runtimeLabel = 'Gemini';
1955
+ if (isCursor) runtimeLabel = 'Cursor';
1309
1956
 
1310
1957
  console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
1311
1958
 
@@ -1318,13 +1965,24 @@ function install(isGlobal, runtime = 'claude') {
1318
1965
  // Clean up orphaned files from previous versions
1319
1966
  cleanupOrphanedFiles(targetDir);
1320
1967
 
1321
- // OpenCode uses 'command/' (singular) with flat structure
1322
- // Claude Code & Gemini use 'commands/' (plural) with nested structure
1323
- if (isOpencode) {
1968
+ // Runtime-specific command installation
1969
+ if (isCursor) {
1970
+ // Cursor: Skills in skills/ directory with nested structure
1971
+ const skillsDir = path.join(targetDir, 'skills');
1972
+ fs.mkdirSync(skillsDir, { recursive: true });
1973
+
1974
+ const bpSrc = path.join(src, 'commands', 'bp');
1975
+ const count = copySkillsFromCommands(bpSrc, skillsDir, pathPrefix, runtime);
1976
+ if (count > 0) {
1977
+ console.log(` ${green}✓${reset} Installed ${count} skills to skills/`);
1978
+ } else {
1979
+ failures.push('skills');
1980
+ }
1981
+ } else if (isOpencode) {
1324
1982
  // OpenCode: flat structure in command/ directory
1325
1983
  const commandDir = path.join(targetDir, 'command');
1326
1984
  fs.mkdirSync(commandDir, { recursive: true });
1327
-
1985
+
1328
1986
  // Copy commands/bp/*.md as command/bp-*.md (flatten structure)
1329
1987
  const bpSrc = path.join(src, 'commands', 'bp');
1330
1988
  copyFlattenedCommands(bpSrc, commandDir, 'bp', pathPrefix, runtime);
@@ -1338,7 +1996,7 @@ function install(isGlobal, runtime = 'claude') {
1338
1996
  // Claude Code & Gemini: nested structure in commands/ directory
1339
1997
  const commandsDir = path.join(targetDir, 'commands');
1340
1998
  fs.mkdirSync(commandsDir, { recursive: true });
1341
-
1999
+
1342
2000
  const bpSrc = path.join(src, 'commands', 'bp');
1343
2001
  const bpDest = path.join(commandsDir, 'bp');
1344
2002
  copyWithPathReplacement(bpSrc, bpDest, pathPrefix, runtime);
@@ -1388,6 +2046,8 @@ function install(isGlobal, runtime = 'claude') {
1388
2046
  content = convertClaudeToOpencodeFrontmatter(content);
1389
2047
  } else if (isGemini) {
1390
2048
  content = convertClaudeToGeminiAgent(content);
2049
+ } else if (isCursor) {
2050
+ content = convertClaudeToCursorAgent(content, pathPrefix);
1391
2051
  }
1392
2052
  fs.writeFileSync(path.join(agentsDest, entry.name), content);
1393
2053
  }
@@ -1421,22 +2081,25 @@ function install(isGlobal, runtime = 'claude') {
1421
2081
  }
1422
2082
 
1423
2083
  // Copy hooks from dist/ (bundled with dependencies)
1424
- const hooksSrc = path.join(src, 'hooks', 'dist');
1425
- if (fs.existsSync(hooksSrc)) {
1426
- const hooksDest = path.join(targetDir, 'hooks');
1427
- fs.mkdirSync(hooksDest, { recursive: true });
1428
- const hookEntries = fs.readdirSync(hooksSrc);
1429
- for (const entry of hookEntries) {
1430
- const srcFile = path.join(hooksSrc, entry);
1431
- if (fs.statSync(srcFile).isFile()) {
1432
- const destFile = path.join(hooksDest, entry);
1433
- fs.copyFileSync(srcFile, destFile);
2084
+ // Skip for Cursor hooks deferred to v2
2085
+ if (!isCursor) {
2086
+ const hooksSrc = path.join(src, 'hooks', 'dist');
2087
+ if (fs.existsSync(hooksSrc)) {
2088
+ const hooksDest = path.join(targetDir, 'hooks');
2089
+ fs.mkdirSync(hooksDest, { recursive: true });
2090
+ const hookEntries = fs.readdirSync(hooksSrc);
2091
+ for (const entry of hookEntries) {
2092
+ const srcFile = path.join(hooksSrc, entry);
2093
+ if (fs.statSync(srcFile).isFile()) {
2094
+ const destFile = path.join(hooksDest, entry);
2095
+ fs.copyFileSync(srcFile, destFile);
2096
+ }
2097
+ }
2098
+ if (verifyInstalled(hooksDest, 'hooks')) {
2099
+ console.log(` ${green}✓${reset} Installed hooks (bundled)`);
2100
+ } else {
2101
+ failures.push('hooks');
1434
2102
  }
1435
- }
1436
- if (verifyInstalled(hooksDest, 'hooks')) {
1437
- console.log(` ${green}✓${reset} Installed hooks (bundled)`);
1438
- } else {
1439
- failures.push('hooks');
1440
2103
  }
1441
2104
  }
1442
2105
 
@@ -1467,8 +2130,8 @@ function install(isGlobal, runtime = 'claude') {
1467
2130
  }
1468
2131
  }
1469
2132
 
1470
- // Configure SessionStart hook for update checking (skip for opencode)
1471
- if (!isOpencode) {
2133
+ // Configure SessionStart hook for update checking (skip for opencode and cursor)
2134
+ if (!isOpencode && !isCursor) {
1472
2135
  if (!settings.hooks) {
1473
2136
  settings.hooks = {};
1474
2137
  }
@@ -1528,8 +2191,10 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
1528
2191
  let program = 'Claude Code';
1529
2192
  if (runtime === 'opencode') program = 'OpenCode';
1530
2193
  if (runtime === 'gemini') program = 'Gemini';
2194
+ if (runtime === 'cursor') program = 'Cursor';
1531
2195
 
1532
- const command = isOpencode ? '/bp-help' : '/bp:help';
2196
+ const isCursor = runtime === 'cursor';
2197
+ const command = isOpencode ? '/bp-help' : isCursor ? '/bp-27-help' : '/bp:help';
1533
2198
  console.log(`
1534
2199
  ${green}Done!${reset} Launch ${program} and run ${cyan}${command}${reset}.
1535
2200
 
@@ -1610,15 +2275,18 @@ function promptRuntime(callback) {
1610
2275
  console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}\n\n ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
1611
2276
  ${cyan}2${reset}) OpenCode ${dim}(~/.config/opencode)${reset} - open source, free models
1612
2277
  ${cyan}3${reset}) Gemini ${dim}(~/.gemini)${reset}
1613
- ${cyan}4${reset}) All
2278
+ ${cyan}4${reset}) Cursor ${dim}(~/.cursor)${reset}
2279
+ ${cyan}5${reset}) All
1614
2280
  `);
1615
2281
 
1616
2282
  rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
1617
2283
  answered = true;
1618
2284
  rl.close();
1619
2285
  const choice = answer.trim() || '1';
1620
- if (choice === '4') {
1621
- callback(['claude', 'opencode', 'gemini']);
2286
+ if (choice === '5') {
2287
+ callback(['claude', 'opencode', 'gemini', 'cursor']);
2288
+ } else if (choice === '4') {
2289
+ callback(['cursor']);
1622
2290
  } else if (choice === '3') {
1623
2291
  callback(['gemini']);
1624
2292
  } else if (choice === '2') {
@@ -1703,16 +2371,28 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
1703
2371
  if (geminiResult) {
1704
2372
  finishInstall(geminiResult.settingsPath, geminiResult.settings, geminiResult.statuslineCommand, shouldInstallStatusline, 'gemini');
1705
2373
  }
1706
-
2374
+
1707
2375
  const opencodeResult = results.find(r => r.runtime === 'opencode');
1708
2376
  if (opencodeResult) {
1709
2377
  finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
1710
2378
  }
2379
+
2380
+ // Cursor doesn't use statusline — finishInstall with shouldInstallStatusline=false
2381
+ const cursorResult = results.find(r => r.runtime === 'cursor');
2382
+ if (cursorResult) {
2383
+ finishInstall(cursorResult.settingsPath, cursorResult.settings, cursorResult.statuslineCommand, false, 'cursor');
2384
+ }
1711
2385
  });
1712
2386
  } else {
1713
- // Only OpenCode
1714
- const opencodeResult = results[0];
1715
- finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
2387
+ // Only runtimes without statusline (OpenCode and/or Cursor)
2388
+ const opencodeResult = results.find(r => r.runtime === 'opencode');
2389
+ if (opencodeResult) {
2390
+ finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
2391
+ }
2392
+ const cursorResult = results.find(r => r.runtime === 'cursor');
2393
+ if (cursorResult) {
2394
+ finishInstall(cursorResult.settingsPath, cursorResult.settings, cursorResult.statuslineCommand, false, 'cursor');
2395
+ }
1716
2396
  }
1717
2397
  }
1718
2398