2025-12-23 15:42:35 -08:00
#!/usr/bin/env node
/**
* ██████╗ ██╗ █████╗ ███╗ ██╗██████╗ ██████╗ ██████╗ ██████╗ ███████╗
* ██╔══██╗██║ ██╔══██╗████╗ ██║╚════██╗██╔════╝██╔═══██╗██╔══██╗██╔════╝
* ██████╔╝██║ ███████║██╔██╗ ██║ █████╔╝██║ ██║ ██║██║ ██║█████╗
* ██╔═══╝ ██║ ██╔══██║██║╚██╗██║██╔═══╝ ██║ ██║ ██║██║ ██║██╔══╝
* ██║ ███████╗██║ ██║██║ ╚████║███████╗╚██████╗╚██████╔╝██████╔╝███████╗
* ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
*
* GLOBAL INSTALLATION SYSTEM
*
* DESCRIPTION:
* Install Plan2Code prompts to user's home directory for global use.
* Generates distribution files on-the-fly from source prompts in src/.
*
* OPTIONS:
* (no options) INTERACTIVE - Select platforms from menu
* --dry-run PREVIEW - Show what would be installed without changes
* --platform X TARGETED - Install to specific platform only
* --local LOCAL - Show instructions for project-level installation
* --uninstall REMOVE - Remove installed files
* --help HELP - Display usage information
*
*/
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
const os = require ( 'os' ) ;
const readline = require ( 'readline' ) ;
// Load project version metadata
let projectVersion = { name : 'Plan2Code' , version : 'unknown' } ;
try {
const versionPath = path . join ( _ _dirname , 'version.json' ) ;
if ( fs . existsSync ( versionPath ) ) {
const versionData = JSON . parse ( fs . readFileSync ( versionPath , 'utf8' ) ) ;
projectVersion = { name : versionData . name , version : versionData . version } ;
}
} catch ( err ) {
// Silently continue with default version if file is not found or invalid
}
// Color codes for display (ANSI escape sequences)
// Use these in console.log() for colored output
const COLORS = {
RESET : '\x1b[0m' ,
BRIGHT : '\x1b[1m' ,
DIM : '\x1b[2m' ,
// Colors
GREEN : '\x1b[32m' , // Success
YELLOW : '\x1b[33m' , // Warning
RED : '\x1b[31m' , // Error
CYAN : '\x1b[36m' , // Info/Headers
BLUE : '\x1b[34m' , // Data/Paths
MAGENTA : '\x1b[35m' , // Special/Highlight
// Background colors
BG _BLACK : '\x1b[40m' ,
BG _GREEN : '\x1b[42m' ,
BG _YELLOW : '\x1b[43m' ,
BG _RED : '\x1b[41m' ,
} ;
// Symbols for status reporting
const SYMBOLS = {
SUCCESS : '▰▰▰' , // Success
ACTIVE : '►►►' , // Active/In Progress
WARNING : '⚠ ⚠ ⚠' , // Warning
ERROR : '✖✖✖' , // Error
INFO : '◆' , // Info
SELECT : '◉' , // Selection marker
COMPLETE : '█' , // Complete
PENDING : '░' , // Pending
DIVIDER : '═' , // Divider
CORNER _TL : '╔' , // Box corners
CORNER _TR : '╗' ,
CORNER _BL : '╚' ,
CORNER _BR : '╝' ,
HORIZONTAL : '═' ,
VERTICAL : '║' ,
TEE _RIGHT : '╠' ,
TEE _LEFT : '╣' ,
} ;
2026-01-27 12:11:00 -08:00
// Plan2Code Mascot - appears during user interactions
const MASCOT = {
// Full mascot for headers
full : [
' ╭───╮ ' ,
' │ ● │ ' ,
' │ ◡ │ ' ,
' ╰───╯ ' ,
] ,
// Mini mascot for inline use
mini : '(◉‿◉)' ,
// Waving mascot for greetings
wave : [
' ╭───╮ ' ,
' │ ● │ ' ,
' │ ◡ │ ' ,
' ╰───╯ ' ,
] ,
// Thinking mascot for prompts
thinking : [
' ╭───╮ ' ,
' │ ● │ ?' ,
' │ ~ │ ' ,
' ╰───╯ ' ,
] ,
} ;
2026-02-18 15:16:11 -08:00
2025-12-23 15:42:35 -08:00
// Source directory containing pre-formatted global installation files
const SOURCE _BASE = 'dist/global-commands' ;
// Source prompts directory
const SRC _DIR = path . join ( _ _dirname , 'src' ) ;
// ============================================================================
// SYNC PROMPTS CONFIGURATION (merged from sync-prompts.js)
// ============================================================================
// Configuration for source prompts
const SOURCE _PROMPTS = [
{
source : 'plan2code---init.md' ,
stepNumber : 'init' ,
name : 'init' ,
displayName : 'Init Mode' ,
description : 'Generate AGENTS.md file for project-specific guidance' ,
isUtility : true
} ,
{
source : 'plan2code---init-update.md' ,
stepNumber : 'update' ,
name : 'init-update' ,
displayName : 'Init Update Mode' ,
description : 'Update existing AGENTS.md with new learnings' ,
isUtility : true
} ,
{
source : 'plan2code---quick-task.md' ,
stepNumber : 0 ,
name : 'quick-task' ,
displayName : 'Quick Task Mode' ,
description : 'Lightweight planning for small tasks'
} ,
{
source : 'plan2code-1--plan.md' ,
stepNumber : 1 ,
name : 'plan' ,
displayName : 'Planning Mode' ,
description : 'Requirements analysis and architecture design'
} ,
{
source : 'plan2code-1b--revise-plan.md' ,
stepNumber : '1b' ,
name : 'revise-plan' ,
displayName : 'Revision Mode' ,
description : 'Modify specs mid-implementation when requirements change'
} ,
{
source : 'plan2code-2--document.md' ,
stepNumber : 2 ,
name : 'document' ,
displayName : 'Documentation Mode' ,
description : 'Transform planning output into structured implementation docs'
} ,
{
source : 'plan2code-3--implement.md' ,
stepNumber : 3 ,
name : 'implement' ,
displayName : 'Implementation Mode' ,
description : 'Execute implementation phase by phase'
} ,
{
source : 'plan2code-4--finalize.md' ,
stepNumber : 4 ,
name : 'finalize' ,
displayName : 'Finalization Mode' ,
description : 'Validate, summarize, and archive completed work'
}
] ;
// Helper function to generate destination filename based on stepNumber
function generateFilename ( prompt , extension = '.md' ) {
if ( prompt . isUtility || prompt . stepNumber === 0 || prompt . stepNumber === 'init' ) {
return ` plan2code--- ${ prompt . name } ${ extension } ` ;
}
return ` plan2code- ${ prompt . stepNumber } -- ${ prompt . name } ${ extension } ` ;
}
// Destination configurations for project-level installation (local)
const LOCAL _DESTINATIONS = [
{
name : 'Claude Code Commands' ,
dir : '.claude/commands' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : null
} ,
{
name : 'Cursor Commands' ,
dir : '.cursor/commands' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : null
} ,
{
name : 'GitHub Copilot Prompts' ,
dir : '.github/prompts' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.prompt.md' ) ,
header : ( prompt ) => [
'---' ,
'mode: agent' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'GitHub Copilot Agents' ,
dir : '.github/agents' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.agent.md' ) ,
header : ( prompt ) => [
'---' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'Continue Prompts' ,
dir : '.continue/prompts' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.prompt.md' ) ,
header : ( prompt ) => [
'---' ,
` name: ${ prompt . name } ` ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'Windsurf Workflows' ,
dir : '.windsurf/workflows' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : ( prompt ) => [
'---' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'Windsurf Global Workflows' ,
dir : '.codeium/windsurf/global_workflows' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : ( prompt ) => [
'---' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'Agent Workflows' ,
dir : '.agent/workflows' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : ( prompt ) => [
'---' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'Codeium Global Workflows' ,
dir : '.codeium/global_workflows' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : ( prompt ) => [
'---' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
}
] ;
// Destination configurations for global (home directory) installation
const GLOBAL _DESTINATIONS = [
{
name : 'Claude Code (Global)' ,
dir : '.claude/commands' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : null
} ,
{
name : 'Copilot CLI (Global)' ,
dir : '.copilot/agents' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : ( prompt ) => [
'---' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'Cursor (Global)' ,
dir : '.cursor/commands' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : null
} ,
{
name : 'Continue (Global)' ,
dir : '.continue/prompts' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.prompt.md' ) ,
header : ( prompt ) => [
'---' ,
` name: ${ prompt . name } ` ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'Windsurf (Global)' ,
dir : '.codeium/windsurf/global_workflows' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : ( prompt ) => [
'---' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'VS Code Copilot (Global)' ,
dir : 'vscode-copilot-prompts' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.prompt.md' ) ,
header : ( prompt ) => [
'---' ,
'agent: agent' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
} ,
{
name : 'Codeium (Global)' ,
dir : '.codeium/global_workflows' ,
filePattern : ( prompt ) => generateFilename ( prompt , '.md' ) ,
header : ( prompt ) => [
'---' ,
` description: "Plan2Code ${ prompt . stepNumber === 'init' ? 'Init' : ` Step ${ prompt . stepNumber } ` } : ${ prompt . displayName } - ${ prompt . description } " ` ,
'---'
] . join ( '\n' )
}
] ;
// Helper function to get VS Code Copilot prompts directory based on platform
function getVSCodeCopilotDir ( ) {
const platform = process . platform ;
if ( platform === 'win32' ) {
// Windows: %APPDATA%\Code\User\prompts
return path . join ( process . env . APPDATA || path . join ( os . homedir ( ) , 'AppData' , 'Roaming' ) , 'Code' , 'User' , 'prompts' ) ;
} else if ( platform === 'darwin' ) {
// macOS: ~/Library/Application Support/Code/User/prompts
return path . join ( os . homedir ( ) , 'Library' , 'Application Support' , 'Code' , 'User' , 'prompts' ) ;
} else {
// Linux: ~/.config/Code/User/prompts
return path . join ( os . homedir ( ) , '.config' , 'Code' , 'User' , 'prompts' ) ;
}
}
// Helper function to resolve target directory (handles both static and dynamic paths)
function resolveTargetDir ( target ) {
if ( typeof target . dir === 'function' ) {
return target . dir ( ) ;
}
return path . join ( os . homedir ( ) , target . dir ) ;
}
// Helper function to get display path for a target
function getDisplayPath ( target ) {
if ( target . displayPath ) {
return target . displayPath ;
}
return ` ~/ ${ target . dir } / ` ;
}
// Helper function to pad strings with ANSI codes correctly
function stripAnsi ( str ) {
return str . replace ( /\x1b\[[0-9;]*m/g , '' ) ;
}
function padEndVisible ( str , length , char = ' ' ) {
const visibleLength = stripAnsi ( str ) . length ;
const paddingNeeded = Math . max ( 0 , length - visibleLength ) ;
return str + char . repeat ( paddingNeeded ) ;
}
// Installation targets
const INSTALL _TARGETS = [
{
name : 'Claude Code' ,
id : 'claude' ,
dir : '.claude/commands' ,
filePattern : /^plan2code-.*\.md$/ ,
icon : '◉ '
} ,
{
name : 'Copilot CLI' ,
id : 'copilot' ,
dir : '.copilot/agents' ,
filePattern : /^plan2code-.*\.md$/ ,
icon : '◉ '
} ,
{
name : 'Cursor' ,
id : 'cursor' ,
dir : '.cursor/commands' ,
filePattern : /^plan2code-.*\.md$/ ,
icon : '◉ '
} ,
{
name : 'Continue' ,
id : 'continue' ,
dir : '.continue/prompts' ,
filePattern : /^plan2code-.*\.prompt\.md$/ ,
icon : '◉ '
} ,
{
name : 'Windsurf' ,
id : 'windsurf' ,
dir : '.codeium/windsurf/global_workflows' ,
filePattern : /^plan2code-.*\.md$/ ,
icon : '◉ '
} ,
{
name : 'Codeium (IntelliJ)' ,
id : 'codeium' ,
dir : '.codeium/global_workflows' ,
filePattern : /^plan2code-.*\.md$/ ,
icon : '◉ '
} ,
{
name : 'VS Code Copilot' ,
id : 'vscode-copilot' ,
dir : getVSCodeCopilotDir ,
sourceDir : 'vscode-copilot-prompts' ,
displayPath : process . platform === 'win32'
? '%APPDATA%\\Code\\User\\prompts\\'
: process . platform === 'darwin'
? '~/Library/Application Support/Code/User/prompts/'
: '~/.config/Code/User/prompts/' ,
filePattern : /^plan2code-.*\.prompt\.md$/ ,
icon : '◉ '
}
] ;
// Parse command line arguments
const args = process . argv . slice ( 2 ) ;
const dryRun = args . includes ( '--dry-run' ) ;
const uninstall = args . includes ( '--uninstall' ) ;
const localInstall = args . includes ( '--local' ) ;
const platformIndex = args . indexOf ( '--platform' ) ;
const selectedPlatform = platformIndex !== - 1 ? args [ platformIndex + 1 ] : null ;
const hasArgs = args . length > 0 ;
// ============================================================================
// DISPLAY FUNCTIONS
// ============================================================================
2026-01-27 12:11:00 -08:00
/**
* Display mascot with optional message
*/
function displayMascot ( variant = 'full' , message = '' ) {
const mascotLines = MASCOT [ variant ] || MASCOT . full ;
console . log ( '' ) ;
mascotLines . forEach ( line => {
console . log ( ` ${ COLORS . MAGENTA } ${ line } ${ COLORS . RESET } ` ) ;
} ) ;
if ( message ) {
console . log ( ` ${ COLORS . CYAN } ${ message } ${ COLORS . RESET } ` ) ;
}
console . log ( '' ) ;
}
2025-12-23 15:42:35 -08:00
/**
* Display header
*/
function displayHeader ( ) {
console . log ( '' ) ;
console . log ( ` ${ COLORS . GREEN } ${ COLORS . BRIGHT } ` ) ;
console . log ( '╔═════════════════════════════════════════════════════════════════════════════════╗' ) ;
2026-01-27 12:11:00 -08:00
console . log ( '║ ╭───╮ ║' ) ;
console . log ( '║ │ ● │ Hi! ║' ) ;
console . log ( '║ │ ◡ │ Nice to meet you ║' ) ;
console . log ( '║ ╰───╯ ║' ) ;
2025-12-23 15:42:35 -08:00
console . log ( '║ ║' ) ;
console . log ( '║ ██████╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ║' ) ;
console . log ( '║ ██╔══██╗██║ ██╔══██╗████╗ ██║ ╚════██╗ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ║' ) ;
console . log ( '║ ██████╔╝██║ ███████║██╔██╗ ██║ █████╔╝ ██║ ██║ ██║██║ ██║█████╗ ║' ) ;
console . log ( '║ ██╔═══╝ ██║ ██╔══██║██║╚██╗██║ ██╔═══╝ ██║ ██║ ██║██║ ██║██╔══╝ ║' ) ;
console . log ( '║ ██║ ███████╗██║ ██║██║ ╚████║ ███████╗ ╚██████╗╚██████╔╝██████╔╝███████╗ ║' ) ;
console . log ( '║ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ║' ) ;
console . log ( '║ ║' ) ;
console . log ( '║ G L O B A L I N S T A L L A T I O N S Y S T E M ║' ) ;
console . log ( '║ https://plan2code.jparkerweb.com ║' ) ;
console . log ( '║ ║' ) ;
console . log ( '╚═════════════════════════════════════════════════════════════════════════════════╝' ) ;
console . log ( COLORS . RESET ) ;
}
/**
* Display section header with border
*/
function displaySectionHeader ( title , mode = '' ) {
const innerWidth = 75 ;
// Center the title with ═ padding
const titleContent = ` [ ${ title } ] ` ;
const titlePadTotal = innerWidth - titleContent . length ;
const titlePadLeft = Math . floor ( titlePadTotal / 2 ) ;
const titlePadRight = titlePadTotal - titlePadLeft ;
const titleLine = '═' . repeat ( titlePadLeft ) + titleContent + '═' . repeat ( titlePadRight ) ;
console . log ( ` ${ COLORS . CYAN } ${ COLORS . BRIGHT } ` ) ;
console . log ( '╔═══════════════════════════════════════════════════════════════════════════╗' ) ;
console . log ( ` ║ ${ ' ' . repeat ( innerWidth ) } ║ ` ) ;
console . log ( ` ║ ${ titleLine } ║ ` ) ;
if ( mode ) {
// Center the mode text with space padding
const modePadTotal = innerWidth - mode . length ;
const modePadLeft = Math . floor ( modePadTotal / 2 ) ;
const modePadRight = modePadTotal - modePadLeft ;
console . log ( ` ║ ${ ' ' . repeat ( modePadLeft ) } ${ mode } ${ ' ' . repeat ( modePadRight ) } ║ ` ) ;
}
console . log ( ` ║ ${ ' ' . repeat ( 75 ) } ║ ` ) ;
console . log ( '╚═══════════════════════════════════════════════════════════════════════════╝' ) ;
console . log ( COLORS . RESET ) ;
}
/**
* Display operation status box
*/
function displayStatusBox ( title , lines ) {
console . log ( ` ${ COLORS . BLUE } ` ) ;
console . log ( '┌─────────────────────────────────────────────────────────────────────────┐' ) ;
console . log ( ` │ ${ COLORS . BRIGHT } ${ title } ${ COLORS . RESET } ${ COLORS . BLUE } ${ ' ' . repeat ( 72 - title . length ) } │ ` ) ;
console . log ( '├─────────────────────────────────────────────────────────────────────────┤' ) ;
lines . forEach ( line => {
const cleanLine = line . replace ( /\x1b\[[0-9;]*m/g , '' ) ; // Remove color codes for length calc
const padding = ' ' . repeat ( Math . max ( 0 , 72 - cleanLine . length ) ) ;
console . log ( ` │ ${ line } ${ padding } │ ` ) ;
} ) ;
console . log ( '└─────────────────────────────────────────────────────────────────────────┘' ) ;
console . log ( COLORS . RESET ) ;
}
/**
* Display help information
*/
function displayHelp ( ) {
displayHeader ( ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . ACTIVE } USAGE GUIDE ${ COLORS . RESET } \n ` ) ;
displayStatusBox ( 'OPTIONS' , [
` ${ COLORS . CYAN } ◆ ${ COLORS . RESET } ${ COLORS . BRIGHT } node install.js ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Interactive mode - Select platforms from menu ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . CYAN } ◆ ${ COLORS . RESET } ${ COLORS . BRIGHT } node install.js --dry-run ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Preview mode - Show what would be installed without changes ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . CYAN } ◆ ${ COLORS . RESET } ${ COLORS . BRIGHT } node install.js --platform <ID> ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Install to specific platform only ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Valid IDs: claude, copilot, cursor, continue, windsurf, codeium, vscode-copilot ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . CYAN } ◆ ${ COLORS . RESET } ${ COLORS . BRIGHT } node install.js --local ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Show local (project-level) installation instructions ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . CYAN } ◆ ${ COLORS . RESET } ${ COLORS . BRIGHT } node install.js --uninstall ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Remove all installed Plan2Code files ${ COLORS . RESET } ` ,
'' ,
2026-01-27 12:11:00 -08:00
` ${ COLORS . CYAN } ◆ ${ COLORS . RESET } ${ COLORS . BRIGHT } node install.js --loop ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Build and install plan2code-loop CLI tool ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . CYAN } ◆ ${ COLORS . RESET } ${ COLORS . BRIGHT } node install.js --uninstall-loop ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Remove plan2code-loop global symlink ${ COLORS . RESET } ` ,
'' ,
2025-12-23 15:42:35 -08:00
` ${ COLORS . CYAN } ◆ ${ COLORS . RESET } ${ COLORS . BRIGHT } node install.js --help ${ COLORS . RESET } ` ,
` ${ COLORS . DIM } Display this help information ${ COLORS . RESET } ` ,
] ) ;
console . log ( '' ) ;
displayStatusBox ( 'INSTALLATION PATHS' , [
` ${ COLORS . YELLOW } [1] ${ COLORS . RESET } Claude Code ${ COLORS . DIM } → ~/.claude/commands/ ${ COLORS . RESET } ` ,
` ${ COLORS . YELLOW } [2] ${ COLORS . RESET } Copilot CLI ${ COLORS . DIM } → ~/.copilot/agents/ ${ COLORS . RESET } ` ,
` ${ COLORS . YELLOW } [3] ${ COLORS . RESET } Cursor ${ COLORS . DIM } → ~/.cursor/commands/ ${ COLORS . RESET } ` ,
` ${ COLORS . YELLOW } [4] ${ COLORS . RESET } Continue ${ COLORS . DIM } → ~/.continue/prompts/ ${ COLORS . RESET } ` ,
` ${ COLORS . YELLOW } [5] ${ COLORS . RESET } Windsurf ${ COLORS . DIM } → ~/.codeium/windsurf/global_workflows/ ${ COLORS . RESET } ` ,
` ${ COLORS . YELLOW } [6] ${ COLORS . RESET } Codeium (IJ) ${ COLORS . DIM } → ~/.codeium/global_workflows/ ${ COLORS . RESET } ` ,
` ${ COLORS . YELLOW } [7] ${ COLORS . RESET } VS Code Copilot ${ COLORS . DIM } → <platform-specific>/Code/User/prompts/ ${ COLORS . RESET } ` ,
] ) ;
console . log ( '' ) ;
displayStatusBox ( 'EXAMPLES' , [
` ${ COLORS . GREEN } $ ${ COLORS . RESET } node install.js ` ,
` ${ COLORS . DIM } Open interactive menu to select platforms ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . GREEN } $ ${ COLORS . RESET } node install.js --platform copilot ` ,
` ${ COLORS . DIM } Install to Copilot CLI only ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . GREEN } $ ${ COLORS . RESET } node install.js --dry-run ` ,
` ${ COLORS . DIM } Preview installation for all platforms ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . GREEN } $ ${ COLORS . RESET } node install.js --uninstall ` ,
` ${ COLORS . DIM } Remove files from all platforms ${ COLORS . RESET } ` ,
] ) ;
console . log ( '' ) ;
console . log ( ` ${ COLORS . DIM } This utility installs Plan2Code prompts from dist/global-commands/ to your ` ) ;
console . log ( ` home directory for global access across all projects. ${ COLORS . RESET } ` ) ;
console . log ( '' ) ;
}
/**
* Display progress indicator
*/
function displayProgress ( current , total , label ) {
const barLength = 30 ;
const filled = Math . floor ( ( current / total ) * barLength ) ;
const empty = barLength - filled ;
const percent = Math . floor ( ( current / total ) * 100 ) ;
const bar = ` ${ COLORS . GREEN } ${ '█' . repeat ( filled ) } ${ COLORS . DIM } ${ '░' . repeat ( empty ) } ${ COLORS . RESET } ` ;
process . stdout . write ( ` \r ${ COLORS . CYAN } [ ${ bar } ${ COLORS . CYAN } ] ${ COLORS . RESET } ${ percent } % ${ label } ` ) ;
}
// ============================================================================
// COMMAND LINE ARGUMENT PROCESSING
// ============================================================================
if ( args . includes ( '--help' ) || args . includes ( '-h' ) ) {
displayHelp ( ) ;
process . exit ( 0 ) ;
}
// Validate platform argument
if ( selectedPlatform ) {
const validPlatforms = INSTALL _TARGETS . map ( t => t . id ) ;
if ( ! validPlatforms . includes ( selectedPlatform ) ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ERROR ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . RED } Unknown platform: ' ${ selectedPlatform } ' ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . YELLOW } Valid platforms: ${ validPlatforms . join ( ', ' ) } ${ COLORS . RESET } \n ` ) ;
process . exit ( 1 ) ;
}
}
// ============================================================================
// SYNC PROMPTS FUNCTIONS
// ============================================================================
/**
* Helper function to recursively delete directories
*/
function deleteDirectory ( dirPath ) {
if ( ! fs . existsSync ( dirPath ) ) {
return ;
}
try {
const files = fs . readdirSync ( dirPath ) ;
for ( const file of files ) {
const filePath = path . join ( dirPath , file ) ;
const stat = fs . statSync ( filePath ) ;
if ( stat . isDirectory ( ) ) {
deleteDirectory ( filePath ) ;
} else {
fs . unlinkSync ( filePath ) ;
}
}
fs . rmdirSync ( dirPath ) ;
} catch ( err ) {
console . error ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ${ COLORS . RESET } Could not delete directory ${ dirPath } : ${ err . message } ` ) ;
}
}
/**
* Write a file to a destination with optional YAML header
*/
function writeToDestination ( rootDir , baseDir , dest , prompt , sourceContent , stats , quiet = false ) {
const destDir = path . join ( rootDir , baseDir , dest . dir ) ;
const destFile = dest . filePattern ( prompt ) ;
const destPath = path . join ( destDir , destFile ) ;
// Build the output content
let outputContent ;
if ( dest . header ) {
const header = dest . header ( prompt ) ;
outputContent = header + '\n\n' + sourceContent ;
} else {
outputContent = sourceContent ;
}
// Ensure destination directory exists
if ( ! dryRun && ! fs . existsSync ( destDir ) ) {
fs . mkdirSync ( destDir , { recursive : true } ) ;
}
// Check if file needs updating
let needsUpdate = true ;
if ( fs . existsSync ( destPath ) ) {
try {
const existingContent = fs . readFileSync ( destPath , 'utf8' ) ;
needsUpdate = existingContent !== outputContent ;
} catch ( err ) {
// File exists but can't be read, will try to write
}
}
if ( needsUpdate ) {
if ( ! dryRun ) {
try {
fs . writeFileSync ( destPath , outputContent , 'utf8' ) ;
if ( ! quiet ) {
console . log ( ` ${ COLORS . GREEN } ▰▰▰ ${ COLORS . RESET } ${ destFile } ` ) ;
}
} catch ( err ) {
console . error ( ` ${ COLORS . RED } ✖✖✖ ${ COLORS . RESET } ${ destFile } : ${ err . message } ` ) ;
stats . errors ++ ;
return ;
}
}
stats . updated ++ ;
} else {
stats . skipped ++ ;
}
}
/**
* Sync prompts from src/ to dist/ directories
* This generates the distribution files before installation
*/
function syncPrompts ( quiet = false ) {
const stats = { updated : 0 , skipped : 0 , errors : 0 } ;
const projectRoot = _ _dirname ;
if ( ! quiet ) {
console . log ( ` ${ COLORS . CYAN } ${ SYMBOLS . ACTIVE } GENERATING DISTRIBUTION FILES ${ COLORS . RESET } \n ` ) ;
}
// Clean up old dist directories
if ( ! dryRun ) {
const localDistPath = path . join ( projectRoot , 'dist/local-commands' ) ;
const globalDistPath = path . join ( projectRoot , 'dist/global-commands' ) ;
if ( fs . existsSync ( localDistPath ) ) {
if ( ! quiet ) console . log ( ` ${ COLORS . YELLOW } ░░░ ${ COLORS . RESET } Cleaning: dist/local-commands/ ` ) ;
deleteDirectory ( localDistPath ) ;
}
if ( fs . existsSync ( globalDistPath ) ) {
if ( ! quiet ) console . log ( ` ${ COLORS . YELLOW } ░░░ ${ COLORS . RESET } Cleaning: dist/global-commands/ ` ) ;
deleteDirectory ( globalDistPath ) ;
}
}
// Process each source prompt
for ( const prompt of SOURCE _PROMPTS ) {
const sourcePath = path . join ( SRC _DIR , prompt . source ) ;
// Read source content
let sourceContent ;
try {
sourceContent = fs . readFileSync ( sourcePath , 'utf8' ) ;
} catch ( err ) {
console . error ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ${ COLORS . RESET } Could not read: src/ ${ prompt . source } ` ) ;
stats . errors ++ ;
continue ;
}
// Write to dist/local-commands/
for ( const dest of LOCAL _DESTINATIONS ) {
writeToDestination ( projectRoot , 'dist/local-commands' , dest , prompt , sourceContent , stats , quiet ) ;
}
// Write to dist/global-commands/
for ( const dest of GLOBAL _DESTINATIONS ) {
writeToDestination ( projectRoot , 'dist/global-commands' , dest , prompt , sourceContent , stats , quiet ) ;
}
}
if ( ! quiet ) {
console . log ( '' ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } ${ COLORS . RESET } Generated ${ stats . updated } files ( ${ stats . skipped } unchanged) ` ) ;
if ( stats . errors > 0 ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ${ COLORS . RESET } ${ stats . errors } errors ` ) ;
}
console . log ( '' ) ;
}
return stats . errors === 0 ;
}
// ============================================================================
// TARGET SELECTION
// ============================================================================
/**
* Get targets to process based on selected platforms
*/
function getTargets ( platformIds = null ) {
if ( platformIds && platformIds . length > 0 ) {
return INSTALL _TARGETS . filter ( t => platformIds . includes ( t . id ) ) ;
}
if ( selectedPlatform ) {
return INSTALL _TARGETS . filter ( t => t . id === selectedPlatform ) ;
}
return INSTALL _TARGETS ;
}
// ============================================================================
// INSTALLATION
// ============================================================================
/**
* Execute installation
*/
function install ( targets = null ) {
const projectRoot = _ _dirname ;
const homeDir = os . homedir ( ) ;
const targetList = targets || getTargets ( ) ;
let totalInstalled = 0 ;
let totalSkipped = 0 ;
let totalErrors = 0 ;
displaySectionHeader (
'INSTALLATION' ,
dryRun ? '[ PREVIEW MODE - NO CHANGES WILL BE MADE ]' : '[ INSTALLING FILES ]'
) ;
// Generate distribution files first
if ( ! syncPrompts ( true ) ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } Failed to generate distribution files ${ COLORS . RESET } ` ) ;
return 1 ;
}
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } Distribution files generated ${ COLORS . RESET } \n ` ) ;
console . log ( '' ) ;
displayStatusBox ( 'PARAMETERS' , [
` ${ COLORS . CYAN } Source: ${ COLORS . RESET } ${ SOURCE _BASE } / ` ,
` ${ COLORS . CYAN } Home Directory: ${ COLORS . RESET } ${ homeDir } ` ,
` ${ COLORS . CYAN } Platforms: ${ COLORS . RESET } ${ targetList . length } ` ,
` ${ COLORS . CYAN } Mode: ${ COLORS . RESET } ${ dryRun ? 'PREVIEW' : 'INSTALL' } ` ,
] ) ;
console . log ( '' ) ;
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . ACTIVE } STARTING INSTALLATION ${ COLORS . RESET } \n ` ) ;
for ( const target of targetList ) {
// Handle both static dir strings and dynamic dir functions
const sourceSubDir = target . sourceDir || target . dir ;
const sourcePath = path . join ( projectRoot , SOURCE _BASE , typeof sourceSubDir === 'function' ? sourceSubDir ( ) : sourceSubDir ) ;
const destPath = resolveTargetDir ( target ) ;
const displayPathStr = getDisplayPath ( target ) ;
console . log ( ` ${ COLORS . CYAN } ╔═══════════════════════════════════════════════════════════════════════════╗ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } ${ target . icon } ${ target . name } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . DIM } Path: ${ displayPathStr } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╚═══════════════════════════════════════════════════════════════════════════╝ ${ COLORS . RESET } ` ) ;
// Check if source directory exists
if ( ! fs . existsSync ( sourcePath ) ) {
const sourceDisplayDir = target . sourceDir || ( typeof target . dir === 'string' ? target . dir : 'vscode-copilot-prompts' ) ;
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . WARNING } WARNING ${ COLORS . RESET } Source not found: ${ SOURCE _BASE } / ${ sourceDisplayDir } ` ) ;
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . INFO } ${ COLORS . RESET } Distribution files may be missing. Check src/ directory for source prompts. \n ` ) ;
totalErrors ++ ;
continue ;
}
// Get files to install
let files ;
try {
files = fs . readdirSync ( sourcePath ) . filter ( f => target . filePattern . test ( f ) ) ;
} catch ( err ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ERROR ${ COLORS . RESET } Failed to read source directory: ${ err . message } \n ` ) ;
totalErrors ++ ;
continue ;
}
if ( files . length === 0 ) {
const sourceDisplayDir = target . sourceDir || ( typeof target . dir === 'string' ? target . dir : 'vscode-copilot-prompts' ) ;
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . WARNING } WARNING ${ COLORS . RESET } No Plan2Code files found in ${ SOURCE _BASE } / ${ sourceDisplayDir } \n ` ) ;
continue ;
}
// Create destination directory if needed
if ( ! dryRun && ! fs . existsSync ( destPath ) ) {
try {
fs . mkdirSync ( destPath , { recursive : true } ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } CREATED ${ COLORS . RESET } Directory: ${ displayPathStr } ` ) ;
} catch ( err ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ERROR ${ COLORS . RESET } Cannot create directory: ${ err . message } \n ` ) ;
totalErrors ++ ;
continue ;
}
}
// Clean up old files before copying new ones
try {
const existingFiles = fs . readdirSync ( destPath ) . filter ( f => target . filePattern . test ( f ) ) ;
if ( existingFiles . length > 0 ) {
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . ACTIVE } CLEANING ${ COLORS . RESET } ${ existingFiles . length } existing file(s)... ` ) ;
for ( const oldFile of existingFiles ) {
const oldFilePath = path . join ( destPath , oldFile ) ;
try {
if ( ! dryRun ) {
fs . unlinkSync ( oldFilePath ) ;
}
console . log ( ` ${ COLORS . YELLOW } ░░░ ${ COLORS . RESET } Removed: ${ oldFile } ` ) ;
} catch ( err ) {
console . log ( ` ${ COLORS . RED } ✖✖✖ ${ COLORS . RESET } Failed to remove ${ oldFile } : ${ err . message } ` ) ;
}
}
}
} catch ( err ) {
// Directory might be newly created, ignore read errors
}
// Copy files
console . log ( ` ${ COLORS . CYAN } ${ SYMBOLS . ACTIVE } COPYING ${ COLORS . RESET } ${ files . length } file(s)... ` ) ;
for ( const file of files ) {
const srcFile = path . join ( sourcePath , file ) ;
const destFile = path . join ( destPath , file ) ;
try {
if ( ! dryRun ) {
fs . copyFileSync ( srcFile , destFile ) ;
}
console . log ( ` ${ COLORS . GREEN } ▰▰▰ ${ COLORS . RESET } ${ file } ` ) ;
totalInstalled ++ ;
} catch ( err ) {
console . log ( ` ${ COLORS . RED } ✖✖✖ ${ COLORS . RESET } ${ file } : ${ err . message } ` ) ;
totalErrors ++ ;
}
}
console . log ( '' ) ;
}
// Summary
console . log ( ` ${ COLORS . CYAN } ╔═══════════════════════════════════════════════════════════════════════════╗ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ ' ' . repeat ( 75 ) } ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ ' ' . repeat ( 31 ) } ${ COLORS . BRIGHT } S U M M A R Y ${ COLORS . RESET } ${ ' ' . repeat ( 31 ) } ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ ' ' . repeat ( 75 ) } ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╠═══════════════════════════════════════════════════════════════════════════╣ ${ COLORS . RESET } ` ) ;
if ( dryRun ) {
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . YELLOW } Mode: ${ COLORS . RESET } PREVIEW - No files were copied ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
} else {
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . GREEN } Mode: ${ COLORS . RESET } INSTALL ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
}
const statusColor = totalErrors > 0 ? COLORS . RED : COLORS . GREEN ;
const statusText = totalErrors > 0 ? 'COMPLETED WITH ERRORS' : 'SUCCESS' ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ statusColor } Status: ${ COLORS . RESET } ${ statusColor } ${ statusText } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } Files Installed: ${ COLORS . GREEN } ${ totalInstalled } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
if ( totalSkipped > 0 ) {
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } Files Skipped: ${ COLORS . YELLOW } ${ totalSkipped } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
}
if ( totalErrors > 0 ) {
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } Errors: ${ COLORS . RED } ${ totalErrors } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
}
console . log ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ ' ' . repeat ( 75 ) } ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╚═══════════════════════════════════════════════════════════════════════════╝ ${ COLORS . RESET } ` ) ;
2026-01-27 12:11:00 -08:00
// Show celebratory or sad mascot based on result
console . log ( '' ) ;
if ( totalErrors === 0 && ! dryRun ) {
console . log ( ` ${ COLORS . GREEN } ╭───╮ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . GREEN } │ ${ COLORS . CYAN } ★ ${ COLORS . GREEN } │ ${ COLORS . RESET } ${ COLORS . BRIGHT } All done! Happy coding! ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . GREEN } │ ${ COLORS . BRIGHT } ◡ ${ COLORS . GREEN } │ ${ COLORS . RESET } ${ COLORS . BRIGHT } If this is your first time using Plan2Code, read the docs here: ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . GREEN } ╰───╯ ${ COLORS . RESET } ${ COLORS . BRIGHT } https://github.com/jparkerweb/plan2code ${ COLORS . RESET } ` ) ;
} else if ( dryRun ) {
console . log ( ` ${ COLORS . YELLOW } ╭───╮ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . YELLOW } │ ${ COLORS . CYAN } ● ${ COLORS . YELLOW } │ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . YELLOW } │ ○ │ ${ COLORS . RESET } ${ COLORS . DIM } That was just a preview! ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . YELLOW } ╰───╯ ${ COLORS . RESET } ` ) ;
2025-12-23 15:42:35 -08:00
console . log ( '' ) ;
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . INFO } Run without --dry-run to install files ${ COLORS . RESET } ` ) ;
}
console . log ( '' ) ;
return totalErrors === 0 ? 0 : 1 ;
}
// ============================================================================
// UNINSTALLATION
// ============================================================================
/**
* Execute uninstallation
*/
function uninstallFiles ( targets = null ) {
const homeDir = os . homedir ( ) ;
const targetList = targets || getTargets ( ) ;
let totalRemoved = 0 ;
let totalErrors = 0 ;
displaySectionHeader (
'UNINSTALLATION' ,
dryRun ? '[ PREVIEW MODE - NO CHANGES WILL BE MADE ]' : '[ REMOVING FILES ]'
) ;
console . log ( '' ) ;
displayStatusBox ( 'PARAMETERS' , [
` ${ COLORS . CYAN } Home Directory: ${ COLORS . RESET } ${ homeDir } ` ,
` ${ COLORS . CYAN } Platforms: ${ COLORS . RESET } ${ targetList . length } ` ,
` ${ COLORS . CYAN } Mode: ${ COLORS . RESET } ${ dryRun ? 'PREVIEW' : 'UNINSTALL' } ` ,
] ) ;
console . log ( '' ) ;
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . ACTIVE } STARTING UNINSTALLATION ${ COLORS . RESET } \n ` ) ;
for ( const target of targetList ) {
const destPath = resolveTargetDir ( target ) ;
const displayPathStr = getDisplayPath ( target ) ;
console . log ( ` ${ COLORS . CYAN } ╔═══════════════════════════════════════════════════════════════════════════╗ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } ${ target . icon } ${ target . name } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . DIM } Path: ${ displayPathStr } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╚═══════════════════════════════════════════════════════════════════════════╝ ${ COLORS . RESET } ` ) ;
// Check if destination directory exists
if ( ! fs . existsSync ( destPath ) ) {
console . log ( ` ${ COLORS . DIM } ${ SYMBOLS . INFO } Directory not found - nothing to remove \n ${ COLORS . RESET } ` ) ;
continue ;
}
// Get files to remove
let files ;
try {
files = fs . readdirSync ( destPath ) . filter ( f => target . filePattern . test ( f ) ) ;
} catch ( err ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ERROR ${ COLORS . RESET } Failed to read directory: ${ err . message } \n ` ) ;
totalErrors ++ ;
continue ;
}
if ( files . length === 0 ) {
console . log ( ` ${ COLORS . DIM } ${ SYMBOLS . INFO } No Plan2Code files found - nothing to remove \n ${ COLORS . RESET } ` ) ;
continue ;
}
// Remove files
console . log ( ` ${ COLORS . CYAN } ${ SYMBOLS . ACTIVE } REMOVING ${ COLORS . RESET } ${ files . length } file(s)... ` ) ;
for ( const file of files ) {
const destFile = path . join ( destPath , file ) ;
try {
if ( ! dryRun ) {
fs . unlinkSync ( destFile ) ;
}
console . log ( ` ${ COLORS . GREEN } ▰▰▰ ${ COLORS . RESET } Removed: ${ file } ` ) ;
totalRemoved ++ ;
} catch ( err ) {
console . log ( ` ${ COLORS . RED } ✖✖✖ ${ COLORS . RESET } ${ file } : ${ err . message } ` ) ;
totalErrors ++ ;
}
}
console . log ( '' ) ;
}
// Summary
console . log ( ` ${ COLORS . CYAN } ╔═══════════════════════════════════════════════════════════════════════════╗ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ ' ' . repeat ( 75 ) } ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ ' ' . repeat ( 31 ) } ${ COLORS . BRIGHT } S U M M A R Y ${ COLORS . RESET } ${ ' ' . repeat ( 31 ) } ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ ' ' . repeat ( 75 ) } ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╠═══════════════════════════════════════════════════════════════════════════╣ ${ COLORS . RESET } ` ) ;
if ( dryRun ) {
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . YELLOW } Mode: ${ COLORS . RESET } PREVIEW - No files were removed ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
} else {
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . GREEN } Mode: ${ COLORS . RESET } UNINSTALL ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
}
const statusColor = totalErrors > 0 ? COLORS . RED : COLORS . GREEN ;
const statusText = totalErrors > 0 ? 'COMPLETED WITH ERRORS' : 'SUCCESS' ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ statusColor } Status: ${ COLORS . RESET } ${ statusColor } ${ statusText } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } Files Removed: ${ COLORS . GREEN } ${ totalRemoved } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
if ( totalErrors > 0 ) {
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } Errors: ${ COLORS . RED } ${ totalErrors } ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
}
console . log ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ ' ' . repeat ( 75 ) } ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╚═══════════════════════════════════════════════════════════════════════════╝ ${ COLORS . RESET } ` ) ;
if ( dryRun ) {
console . log ( '' ) ;
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . INFO } Run without --dry-run to remove files ${ COLORS . RESET } ` ) ;
}
console . log ( '' ) ;
return totalErrors === 0 ? 0 : 1 ;
}
// ============================================================================
// LOCAL INSTALLATION INSTRUCTIONS
// ============================================================================
/**
* Display local installation instructions for manual copy/paste
*/
function displayLocalInstructions ( ) {
const projectRoot = _ _dirname ;
displaySectionHeader ( 'LOCAL INSTALLATION' , '[ MANUAL COPY/PASTE INSTRUCTIONS ]' ) ;
// Generate dist files first
console . log ( ` ${ COLORS . CYAN } ${ SYMBOLS . ACTIVE } GENERATING DISTRIBUTION FILES ${ COLORS . RESET } \n ` ) ;
if ( ! syncPrompts ( true ) ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } Failed to generate distribution files ${ COLORS . RESET } ` ) ;
return 1 ;
}
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } Distribution files generated ${ COLORS . RESET } \n ` ) ;
const localDistPath = path . join ( projectRoot , 'dist' , 'local-commands' ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . ACTIVE } LOCAL INSTALLATION INSTRUCTIONS ${ COLORS . RESET } \n ` ) ;
displayStatusBox ( 'OVERVIEW' , [
` Local installation copies prompts to your ${ COLORS . BRIGHT } project directory ${ COLORS . RESET } ` ,
` (not your home directory). This is useful for project-specific setups. ` ,
'' ,
` Files have been generated in: ` ,
` ${ COLORS . CYAN } ${ localDistPath } ${ COLORS . RESET } ` ,
] ) ;
console . log ( '' ) ;
console . log ( ` ${ COLORS . CYAN } ╔═══════════════════════════════════════════════════════════════════════════╗ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } COPY THE APPROPRIATE FOLDER TO YOUR PROJECT ROOT ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╠═══════════════════════════════════════════════════════════════════════════╣ ${ COLORS . RESET } ` ) ;
// Show local destinations
const localDirs = [
{ name : 'Claude Code' , dir : '.claude/commands/' , desc : 'Slash commands for Claude Code' } ,
{ name : 'Cursor' , dir : '.cursor/commands/' , desc : 'Slash commands for Cursor IDE' } ,
{ name : 'GitHub Copilot' , dir : '.github/prompts/' , desc : 'Prompts for GitHub Copilot' } ,
{ name : 'Continue' , dir : '.continue/prompts/' , desc : 'Prompts for Continue extension' } ,
{ name : 'Windsurf' , dir : '.windsurf/workflows/' , desc : 'Workflows for Windsurf' } ,
2026-01-27 12:11:00 -08:00
{ name : 'Codeium (IntelliJ)' , dir : '.codeium/global_workflows/' , desc : 'Codeium global workflows' } ,
2025-12-23 15:42:35 -08:00
] ;
localDirs . forEach ( ( item , i ) => {
const num = ` ${ COLORS . BRIGHT } ${ i + 1 } . ${ COLORS . RESET } ` ;
const name = ` ${ COLORS . GREEN } ${ item . name . padEnd ( 16 ) } ${ COLORS . RESET } ` ;
const dir = ` ${ COLORS . DIM } ${ item . dir } ${ COLORS . RESET } ` ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ num } ${ name } ${ dir } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
} ) ;
console . log ( ` ${ COLORS . CYAN } ╚═══════════════════════════════════════════════════════════════════════════╝ ${ COLORS . RESET } ` ) ;
console . log ( '' ) ;
displayStatusBox ( 'EXAMPLE COMMANDS' , [
` ${ COLORS . BRIGHT } For Claude Code: ${ COLORS . RESET } ` ,
` ${ COLORS . GREEN } cp -r dist/local-commands/.claude YOUR_PROJECT/ ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . BRIGHT } For Cursor: ${ COLORS . RESET } ` ,
` ${ COLORS . GREEN } cp -r dist/local-commands/.cursor YOUR_PROJECT/ ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . BRIGHT } For GitHub Copilot: ${ COLORS . RESET } ` ,
` ${ COLORS . GREEN } cp -r dist/local-commands/.github YOUR_PROJECT/ ${ COLORS . RESET } ` ,
'' ,
` ${ COLORS . BRIGHT } For Continue: ${ COLORS . RESET } ` ,
` ${ COLORS . GREEN } cp -r dist/local-commands/.continue YOUR_PROJECT/ ${ COLORS . RESET } ` ,
] ) ;
console . log ( '' ) ;
console . log ( ` ${ COLORS . DIM } The dist/local-commands/ folder contains all necessary files organized by tool. ` ) ;
console . log ( ` Copy the appropriate subfolder(s) to your project root directory. ${ COLORS . RESET } ` ) ;
console . log ( '' ) ;
return 0 ;
}
// ============================================================================
// INTERACTIVE MENU
// ============================================================================
/**
* Run interactive menu interface
*/
function runInteractive ( ) {
const rl = readline . createInterface ( {
input : process . stdin ,
output : process . stdout
} ) ;
const question = ( prompt ) => new Promise ( resolve => rl . question ( prompt , resolve ) ) ;
async function main ( ) {
displayHeader ( ) ;
console . log ( ` ${ COLORS . BLUE } ${ COLORS . BRIGHT } Version Info: ${ COLORS . RESET } ${ projectVersion . name } ${ projectVersion . version } ` ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . ACTIVE } SELECT PLATFORMS ${ COLORS . RESET } \n ` ) ;
2026-01-27 12:11:00 -08:00
console . log ( ` ${ COLORS . CYAN } ╔════════════════════════════════════════════════════════════════╗ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } AVAILABLE PLATFORMS ${ COLORS . RESET } ` , 65 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╠════════════════════════════════════════════════════════════════╣ ${ COLORS . RESET } ` ) ;
2025-12-23 15:42:35 -08:00
INSTALL _TARGETS . forEach ( ( target , i ) => {
const num = ` ${ COLORS . BRIGHT } ${ i + 1 } . ${ COLORS . RESET } ` ;
const icon = ` ${ COLORS . GREEN } ${ target . icon } ${ COLORS . RESET } ` ;
const name = ` ${ COLORS . BRIGHT } ${ target . name . padEnd ( 14 ) } ${ COLORS . RESET } ` ;
const targetPath = ` ${ COLORS . DIM } ${ getDisplayPath ( target ) } ${ COLORS . RESET } ` ;
2026-01-27 12:11:00 -08:00
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ num } ${ icon } ${ name } ${ targetPath } ` , 65 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
2025-12-23 15:42:35 -08:00
} ) ;
2026-01-27 12:11:00 -08:00
console . log ( ` ${ COLORS . CYAN } ╠════════════════════════════════════════════════════════════════╣ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } A. ${ COLORS . RESET } ${ COLORS . MAGENTA } ◉ ALL ${ COLORS . RESET } ${ COLORS . BRIGHT } Install to ALL platforms + loop CLI ${ COLORS . RESET } ` , 65 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } L. ${ COLORS . RESET } ${ COLORS . BLUE } ◉ LOCAL ${ COLORS . RESET } ${ COLORS . BRIGHT } Show local (project) install instructions ${ COLORS . RESET } ` , 65 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } O. ${ COLORS . RESET } ${ COLORS . GREEN } ◉ LOOP ${ COLORS . RESET } ${ COLORS . BRIGHT } Install plan2code-loop CLI only ${ COLORS . RESET } ` , 65 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } U. ${ COLORS . RESET } ${ COLORS . RED } ◉ UNINSTALL ${ COLORS . RESET } ${ COLORS . BRIGHT } Remove Plan2Code files + loop CLI ${ COLORS . RESET } ` , 65 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } Q. ${ COLORS . RESET } ${ COLORS . DIM } ◉ QUIT ${ COLORS . RESET } ${ COLORS . BRIGHT } Exit ${ COLORS . RESET } ` , 65 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╚════════════════════════════════════════════════════════════════╝ ${ COLORS . RESET } ` ) ;
2025-12-23 15:42:35 -08:00
console . log ( '' ) ;
2026-01-27 12:11:00 -08:00
// Show thinking mascot for input prompt
console . log ( ` ${ COLORS . MAGENTA } ╭───╮ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . MAGENTA } │ ${ COLORS . CYAN } ● ${ COLORS . MAGENTA } │ ${ COLORS . RESET } ${ COLORS . DIM } What would you like to do? ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . MAGENTA } │ ${ COLORS . YELLOW } ~ ${ COLORS . MAGENTA } │ ${ COLORS . RESET } ${ COLORS . GREEN } I suggest A: 'Install to ALL platforms + loop CLI' ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . MAGENTA } ╰───╯ ${ COLORS . RESET } ` ) ;
console . log ( '' ) ;
const answer = await question ( ` ${ COLORS . CYAN } ${ SYMBOLS . SELECT } SELECT OPTION ${ COLORS . RESET } (1-7, A, L, O, U, Q, or 1,3,5): ` ) ;
2025-12-23 15:42:35 -08:00
const input = answer . trim ( ) . toUpperCase ( ) ;
if ( input === 'Q' || input === '' ) {
console . log ( ` \n ${ COLORS . YELLOW } ${ SYMBOLS . WARNING } CANCELLED ${ COLORS . RESET } \n ` ) ;
rl . close ( ) ;
process . exit ( 0 ) ;
}
if ( input === 'L' ) {
// Local installation instructions
rl . close ( ) ;
process . exit ( displayLocalInstructions ( ) ) ;
}
2026-02-18 15:16:11 -08:00
2026-01-27 12:11:00 -08:00
if ( input === 'O' ) {
// Install plan2code-loop
rl . close ( ) ;
process . exit ( installPlan2CodeLoop ( ) ) ;
}
2025-12-23 15:42:35 -08:00
if ( input === 'U' ) {
// Uninstall flow
console . log ( '' ) ;
console . log ( ` ${ COLORS . CYAN } ╔═══════════════════════════════════════════════════════════════════════════╗ ${ COLORS . RESET } ` ) ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } ${ COLORS . RED } UNINSTALL FROM PLATFORMS ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╠═══════════════════════════════════════════════════════════════════════════╣ ${ COLORS . RESET } ` ) ;
INSTALL _TARGETS . forEach ( ( target , i ) => {
const num = ` ${ COLORS . BRIGHT } ${ i + 1 } . ${ COLORS . RESET } ` ;
const icon = ` ${ COLORS . RED } ${ target . icon } ${ COLORS . RESET } ` ;
const name = ` ${ COLORS . BRIGHT } ${ target . name . padEnd ( 14 ) } ${ COLORS . RESET } ` ;
const targetPath = ` ${ COLORS . DIM } ${ getDisplayPath ( target ) } ${ COLORS . RESET } ` ;
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ num } ${ icon } ${ name } ${ targetPath } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
} ) ;
console . log ( ` ${ COLORS . CYAN } ╠═══════════════════════════════════════════════════════════════════════════╣ ${ COLORS . RESET } ` ) ;
2026-01-27 12:11:00 -08:00
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } A. ${ COLORS . RESET } ${ COLORS . RED } ◉ ALL ${ COLORS . RESET } ${ COLORS . BRIGHT } Uninstall from ALL platforms + loop CLI ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
2025-12-23 15:42:35 -08:00
console . log ( padEndVisible ( ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ${ COLORS . BRIGHT } Q. ${ COLORS . RESET } ${ COLORS . DIM } ◉ CANCEL ${ COLORS . RESET } ${ COLORS . BRIGHT } Cancel ${ COLORS . RESET } ` , 76 ) + ` ${ COLORS . CYAN } ║ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . CYAN } ╚═══════════════════════════════════════════════════════════════════════════╝ ${ COLORS . RESET } ` ) ;
console . log ( '' ) ;
2026-01-27 12:11:00 -08:00
// Show worried mascot for uninstall prompt
console . log ( ` ${ COLORS . RED } ╭───╮ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . RED } │ ${ COLORS . YELLOW } ○ ${ COLORS . RED } │ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . RED } │ ${ COLORS . YELLOW } ~ ${ COLORS . RED } │ ${ COLORS . RESET } ${ COLORS . DIM } Are you sure about this? ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . RED } ╰───╯ ${ COLORS . RESET } ` ) ;
console . log ( '' ) ;
2025-12-23 15:42:35 -08:00
const uninstallAnswer = await question ( ` ${ COLORS . RED } ${ SYMBOLS . SELECT } SELECT PLATFORMS TO UNINSTALL ${ COLORS . RESET } (1-7, A, Q, or 1,3,5): ` ) ;
const uninstallInput = uninstallAnswer . trim ( ) . toUpperCase ( ) ;
if ( uninstallInput === 'Q' || uninstallInput === '' ) {
console . log ( ` \n ${ COLORS . YELLOW } ${ SYMBOLS . WARNING } CANCELLED ${ COLORS . RESET } \n ` ) ;
rl . close ( ) ;
process . exit ( 0 ) ;
}
let targets ;
if ( uninstallInput === 'A' ) {
targets = INSTALL _TARGETS ;
} else {
const indices = uninstallInput . split ( ',' ) . map ( s => parseInt ( s . trim ( ) ) - 1 ) ;
const validIndices = indices . filter ( i => i >= 0 && i < INSTALL _TARGETS . length ) ;
if ( validIndices . length === 0 ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } Invalid selection ${ COLORS . RESET } \n ` ) ;
rl . close ( ) ;
process . exit ( 1 ) ;
}
targets = validIndices . map ( i => INSTALL _TARGETS [ i ] ) ;
}
const platformNames = targets . map ( t => ` ${ COLORS . YELLOW } ${ t . name } ${ COLORS . RESET } ` ) . join ( ', ' ) ;
const confirm = await question ( ` \n ${ COLORS . RED } ${ SYMBOLS . WARNING } CONFIRM UNINSTALL ${ COLORS . RESET } from ${ targets . length } platform(s): ${ platformNames } ? (y/N): ` ) ;
if ( confirm . trim ( ) . toLowerCase ( ) !== 'y' ) {
console . log ( ` \n ${ COLORS . YELLOW } ${ SYMBOLS . WARNING } CANCELLED ${ COLORS . RESET } \n ` ) ;
rl . close ( ) ;
process . exit ( 0 ) ;
}
rl . close ( ) ;
2026-01-27 12:11:00 -08:00
const uninstallResult = uninstallFiles ( targets ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
// If uninstalling from ALL platforms, also unlink plan2code-loop
if ( uninstallInput === 'A' ) {
console . log ( '' ) ;
uninstallPlan2CodeLoop ( ) ;
}
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
process . exit ( uninstallResult ) ;
2025-12-23 15:42:35 -08:00
}
// Install flow
let targets ;
if ( input === 'A' ) {
targets = INSTALL _TARGETS ;
} else {
const indices = input . split ( ',' ) . map ( s => parseInt ( s . trim ( ) ) - 1 ) ;
const validIndices = indices . filter ( i => i >= 0 && i < INSTALL _TARGETS . length ) ;
if ( validIndices . length === 0 ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } Invalid selection ${ COLORS . RESET } \n ` ) ;
rl . close ( ) ;
process . exit ( 1 ) ;
}
targets = validIndices . map ( i => INSTALL _TARGETS [ i ] ) ;
}
const platformNames = targets . map ( t => ` ${ COLORS . YELLOW } ${ t . name } ${ COLORS . RESET } ` ) . join ( ', ' ) ;
2026-01-27 12:11:00 -08:00
// Show happy mascot for install confirmation
console . log ( '' ) ;
console . log ( ` ${ COLORS . GREEN } ╭───╮ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . GREEN } │ ${ COLORS . CYAN } ● ${ COLORS . GREEN } │ ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . GREEN } │ ${ COLORS . BRIGHT } ◡ ${ COLORS . GREEN } │ ${ COLORS . RESET } ${ COLORS . DIM } Ready to install! ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . GREEN } ╰───╯ ${ COLORS . RESET } ` ) ;
const confirm = await question ( ` \n ${ COLORS . GREEN } ${ SYMBOLS . SELECT } CONFIRM INSTALL ${ COLORS . RESET } ? (Y/n): ` ) ;
2025-12-23 15:42:35 -08:00
if ( confirm . trim ( ) . toLowerCase ( ) === 'n' ) {
console . log ( ` \n ${ COLORS . YELLOW } ${ SYMBOLS . WARNING } CANCELLED ${ COLORS . RESET } \n ` ) ;
rl . close ( ) ;
process . exit ( 0 ) ;
}
rl . close ( ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
// If installing ALL platforms, install plan2code-loop first
let loopResult = 0 ;
if ( input === 'A' ) {
loopResult = installPlan2CodeLoop ( ) ;
console . log ( '' ) ;
}
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
const installResult = install ( targets ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
if ( input === 'A' ) {
process . exit ( installResult === 0 && loopResult === 0 ? 0 : 1 ) ;
}
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
process . exit ( installResult ) ;
2025-12-23 15:42:35 -08:00
}
main ( ) . catch ( err => {
console . error ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ERROR: ${ COLORS . RESET } ` , err . message ) ;
rl . close ( ) ;
process . exit ( 1 ) ;
} ) ;
}
2026-01-27 12:11:00 -08:00
// ============================================================================
// PLAN2CODE-LOOP INSTALLATION
// ============================================================================
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
const { execSync , spawn } = require ( 'child_process' ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
/**
* Build plan2code-loop package
*/
function buildPlan2CodeLoop ( ) {
const loopDir = path . join ( _ _dirname , 'plan2code-loop' ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
// Check if directory exists
if ( ! fs . existsSync ( loopDir ) ) {
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . WARNING } ${ COLORS . RESET } plan2code-loop directory not found ` ) ;
return false ;
}
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
console . log ( ` ${ COLORS . CYAN } ${ SYMBOLS . ACTIVE } Building plan2code-loop... ${ COLORS . RESET } ` ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
try {
// Install dependencies
console . log ( ` ${ COLORS . DIM } Installing dependencies... ${ COLORS . RESET } ` ) ;
execSync ( 'npm install' , { cwd : loopDir , stdio : 'pipe' } ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
// Build
console . log ( ` ${ COLORS . DIM } Running build... ${ COLORS . RESET } ` ) ;
execSync ( 'npm run build' , { cwd : loopDir , stdio : 'pipe' } ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } ${ COLORS . RESET } Build complete ` ) ;
return true ;
} catch ( error ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ${ COLORS . RESET } Build failed: ${ error . message } ` ) ;
return false ;
}
}
/**
* Clean up existing plan2code-loop symlinks/files before creating new ones
* This prevents the Windows lstat error with stale symlinks
*/
function cleanupExistingSymlinks ( ) {
console . log ( ` ${ COLORS . DIM } Cleaning up existing symlinks... ${ COLORS . RESET } ` ) ;
try {
// Get npm global prefix - more reliable than npm bin -g on Windows
const npmPrefix = execSync ( 'npm config get prefix' , { encoding : 'utf8' } ) . trim ( ) ;
// On Windows, bin files are directly in prefix; on Unix they're in prefix/bin
const npmBinGlobal = process . platform === 'win32' ? npmPrefix : path . join ( npmPrefix , 'bin' ) ;
const npmRootGlobal = path . join ( npmPrefix , 'node_modules' ) ;
// Files/symlinks to clean up in npm bin directory
const binFiles = [
path . join ( npmBinGlobal , 'plan2code-loop' ) ,
path . join ( npmBinGlobal , 'plan2code-loop.cmd' ) ,
path . join ( npmBinGlobal , 'plan2code-loop.ps1' )
] ;
// Symlink in node_modules - this is the main culprit for the lstat error
const nodeModulesLink = path . join ( npmRootGlobal , 'plan2code-loop' ) ;
// Remove bin files first
for ( const file of binFiles ) {
try {
const stats = fs . lstatSync ( file ) ;
if ( stats ) {
fs . unlinkSync ( file ) ;
console . log ( ` ${ COLORS . DIM } Removed: ${ path . basename ( file ) } ${ COLORS . RESET } ` ) ;
}
} catch ( err ) {
if ( err . code !== 'ENOENT' ) {
// File exists but can't be removed normally, try rmSync
try {
fs . rmSync ( file , { force : true , recursive : true } ) ;
console . log ( ` ${ COLORS . DIM } Removed: ${ path . basename ( file ) } ${ COLORS . RESET } ` ) ;
} catch ( rmErr ) {
console . log ( ` ${ COLORS . DIM } Could not remove: ${ path . basename ( file ) } ${ COLORS . RESET } ` ) ;
}
}
}
}
// Remove node_modules symlink - this is critical for fixing the lstat error
try {
const stats = fs . lstatSync ( nodeModulesLink ) ;
if ( stats ) {
// On Windows, junction points need special handling
if ( process . platform === 'win32' ) {
// Try using rmdir for junction points
try {
fs . rmdirSync ( nodeModulesLink ) ;
console . log ( ` ${ COLORS . DIM } Removed: node_modules/plan2code-loop (junction) ${ COLORS . RESET } ` ) ;
} catch ( rmdirErr ) {
// Fall back to rmSync
fs . rmSync ( nodeModulesLink , { force : true , recursive : true } ) ;
console . log ( ` ${ COLORS . DIM } Removed: node_modules/plan2code-loop ${ COLORS . RESET } ` ) ;
}
} else {
fs . rmSync ( nodeModulesLink , { force : true , recursive : true } ) ;
console . log ( ` ${ COLORS . DIM } Removed: node_modules/plan2code-loop ${ COLORS . RESET } ` ) ;
}
}
} catch ( err ) {
if ( err . code !== 'ENOENT' ) {
console . log ( ` ${ COLORS . DIM } Could not remove node_modules symlink: ${ err . message } ${ COLORS . RESET } ` ) ;
}
}
return true ;
} catch ( error ) {
// Non-fatal - npm link might still work
console . log ( ` ${ COLORS . DIM } Cleanup skipped: ${ error . message } ${ COLORS . RESET } ` ) ;
return true ;
}
}
/**
* Create global symlink for plan2code-loop
*/
function createGlobalSymlink ( ) {
const loopDir = path . join ( _ _dirname , 'plan2code-loop' ) ;
const distBin = path . join ( loopDir , 'dist' , 'bin' , 'plan2code-loop.js' ) ;
// Check if built binary exists
if ( ! fs . existsSync ( distBin ) ) {
console . log ( ` ${ COLORS . YELLOW } ${ SYMBOLS . WARNING } ${ COLORS . RESET } plan2code-loop binary not found. Run build first. ` ) ;
return false ;
}
console . log ( ` ${ COLORS . CYAN } ${ SYMBOLS . ACTIVE } Creating global symlink... ${ COLORS . RESET } ` ) ;
// Clean up existing symlinks first to prevent Windows lstat errors
cleanupExistingSymlinks ( ) ;
// On Windows, skip npm link entirely and create batch file directly
// npm link has persistent issues with junctions and lstat errors on Windows
if ( process . platform === 'win32' ) {
try {
const npmPrefix = execSync ( 'npm config get prefix' , { encoding : 'utf8' } ) . trim ( ) ;
const symlinkCmd = path . join ( npmPrefix , 'plan2code-loop.cmd' ) ;
const symlinkPs1 = path . join ( npmPrefix , 'plan2code-loop.ps1' ) ;
// Create batch file wrapper for cmd.exe
const batchContent = ` @echo off \r \n node " ${ distBin } " %* \r \n ` ;
fs . writeFileSync ( symlinkCmd , batchContent ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } ${ COLORS . RESET } Created: plan2code-loop.cmd ` ) ;
// Create PowerShell wrapper for better shell support
const ps1Content = ` #!/usr/bin/env pwsh \r \n node " ${ distBin } " $ args \r \n ` ;
fs . writeFileSync ( symlinkPs1 , ps1Content ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } ${ COLORS . RESET } Created: plan2code-loop.ps1 ` ) ;
console . log ( ` ${ COLORS . DIM } Run 'plan2code-loop --help' from any directory ${ COLORS . RESET } ` ) ;
return true ;
} catch ( error ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ${ COLORS . RESET } Failed to create wrappers: ${ error . message } ` ) ;
return false ;
}
}
// On Unix systems, use npm link as it works reliably
try {
execSync ( 'npm link' , { cwd : loopDir , stdio : 'pipe' } ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } ${ COLORS . RESET } Global symlink created ` ) ;
console . log ( ` ${ COLORS . DIM } Run 'plan2code-loop --help' from any directory ${ COLORS . RESET } ` ) ;
return true ;
} catch ( error ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ${ COLORS . RESET } Symlink failed: ${ error . message } ` ) ;
return false ;
}
}
/**
* Remove global symlink for plan2code-loop
*/
function removeGlobalSymlink ( ) {
console . log ( ` ${ COLORS . CYAN } ${ SYMBOLS . ACTIVE } Removing global symlink... ${ COLORS . RESET } ` ) ;
let removedCount = 0 ;
try {
// Get npm global prefix
const npmPrefix = execSync ( 'npm config get prefix' , { encoding : 'utf8' } ) . trim ( ) ;
// On Windows, bin files are directly in prefix; on Unix they're in prefix/bin
const npmBinGlobal = process . platform === 'win32' ? npmPrefix : path . join ( npmPrefix , 'bin' ) ;
const npmRootGlobal = path . join ( npmPrefix , 'node_modules' ) ;
// Files to remove
const filesToRemove = [
{ path : path . join ( npmBinGlobal , 'plan2code-loop' ) , name : 'plan2code-loop' } ,
{ path : path . join ( npmBinGlobal , 'plan2code-loop.cmd' ) , name : 'plan2code-loop.cmd' } ,
{ path : path . join ( npmBinGlobal , 'plan2code-loop.ps1' ) , name : 'plan2code-loop.ps1' } ,
{ path : path . join ( npmRootGlobal , 'plan2code-loop' ) , name : 'node_modules/plan2code-loop' }
] ;
for ( const file of filesToRemove ) {
try {
fs . lstatSync ( file . path ) ;
// File exists, try to remove it
try {
// Try rmdirSync first for junctions/symlinks
fs . rmdirSync ( file . path ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } ${ COLORS . RESET } Removed: ${ file . name } ` ) ;
removedCount ++ ;
} catch ( rmdirErr ) {
// Fall back to unlinkSync for regular files
try {
fs . unlinkSync ( file . path ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } ${ COLORS . RESET } Removed: ${ file . name } ` ) ;
removedCount ++ ;
} catch ( unlinkErr ) {
// Last resort: rmSync
fs . rmSync ( file . path , { force : true , recursive : true } ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } ${ COLORS . RESET } Removed: ${ file . name } ` ) ;
removedCount ++ ;
}
}
} catch ( err ) {
// File doesn't exist, skip it
}
}
if ( removedCount === 0 ) {
console . log ( ` ${ COLORS . DIM } ${ SYMBOLS . INFO } No files found to remove ${ COLORS . RESET } ` ) ;
}
return true ;
} catch ( error ) {
console . log ( ` ${ COLORS . RED } ${ SYMBOLS . ERROR } ${ COLORS . RESET } Uninstall failed: ${ error . message } ` ) ;
return false ;
}
}
/**
* Install plan2code-loop (build + symlink)
*/
function installPlan2CodeLoop ( ) {
displaySectionHeader ( 'PLAN2CODE-LOOP' , '[ BUILD & INSTALL ]' ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
const buildSuccess = buildPlan2CodeLoop ( ) ;
if ( ! buildSuccess ) {
return 1 ;
}
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
const symlinkSuccess = createGlobalSymlink ( ) ;
if ( ! symlinkSuccess ) {
return 1 ;
}
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
console . log ( '' ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } plan2code-loop installed successfully! ${ COLORS . RESET } ` ) ;
console . log ( '' ) ;
console . log ( ` ${ COLORS . CYAN } Usage: ${ COLORS . RESET } ` ) ;
console . log ( ` ${ COLORS . BRIGHT } plan2code-loop ${ COLORS . RESET } Start the autonomous loop ` ) ;
console . log ( ` ${ COLORS . BRIGHT } plan2code-loop -c ${ COLORS . RESET } Resume previous session ` ) ;
console . log ( ` ${ COLORS . BRIGHT } plan2code-loop -v ${ COLORS . RESET } Verbose mode ` ) ;
console . log ( ` ${ COLORS . BRIGHT } plan2code-loop --help ${ COLORS . RESET } Show all options ` ) ;
console . log ( '' ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
return 0 ;
}
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
/**
* Uninstall plan2code-loop
*/
function uninstallPlan2CodeLoop ( ) {
displaySectionHeader ( 'PLAN2CODE-LOOP' , '[ UNINSTALL ]' ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
removeGlobalSymlink ( ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
console . log ( '' ) ;
console . log ( ` ${ COLORS . GREEN } ${ SYMBOLS . SUCCESS } plan2code-loop uninstalled ${ COLORS . RESET } ` ) ;
console . log ( '' ) ;
2026-02-03 20:32:18 -08:00
2026-01-27 12:11:00 -08:00
return 0 ;
}
2026-02-03 20:32:18 -08:00
2025-12-23 15:42:35 -08:00
// ============================================================================
// MAIN
// ============================================================================
2026-01-27 12:11:00 -08:00
// Additional CLI arguments for plan2code-loop
const installLoop = args . includes ( '--loop' ) ;
const uninstallLoop = args . includes ( '--uninstall-loop' ) ;
2026-02-03 20:32:18 -08:00
2025-12-23 15:42:35 -08:00
// Run the appropriate function
2026-01-27 12:11:00 -08:00
if ( installLoop ) {
process . exit ( installPlan2CodeLoop ( ) ) ;
} else if ( uninstallLoop ) {
process . exit ( uninstallPlan2CodeLoop ( ) ) ;
} else if ( ! hasArgs ) {
2025-12-23 15:42:35 -08:00
// No arguments - run interactive menu
runInteractive ( ) ;
} else if ( localInstall ) {
process . exit ( displayLocalInstructions ( ) ) ;
} else if ( uninstall ) {
process . exit ( uninstallFiles ( ) ) ;
} else {
process . exit ( install ( ) ) ;
}