2025-03-04 13:55:17 -05:00
#!/usr/bin/env node
2025-03-22 10:06:02 +01:00
console . log ( 'Starting task-master-ai...' ) ;
2025-03-04 14:46:46 -05:00
2025-03-04 14:11:57 -05:00
import fs from 'fs' ;
import path from 'path' ;
import { execSync } from 'child_process' ;
import readline from 'readline' ;
import { fileURLToPath } from 'url' ;
import { dirname } from 'path' ;
2025-03-22 02:18:59 -04:00
import chalk from 'chalk' ;
import figlet from 'figlet' ;
import boxen from 'boxen' ;
import gradient from 'gradient-string' ;
2025-03-04 14:11:57 -05:00
2025-03-04 14:46:46 -05:00
// Debug information
console . log ( 'Node version:' , process . version ) ;
console . log ( 'Current directory:' , process . cwd ( ) ) ;
console . log ( 'Script path:' , import . meta . url ) ;
2025-03-04 14:11:57 -05:00
const _ _filename = fileURLToPath ( import . meta . url ) ;
const _ _dirname = dirname ( _ _filename ) ;
2025-03-04 13:55:17 -05:00
2025-03-22 02:18:59 -04:00
// Define log levels
2025-03-04 13:55:17 -05:00
const LOG _LEVELS = {
debug : 0 ,
info : 1 ,
warn : 2 ,
2025-03-22 02:18:59 -04:00
error : 3 ,
success : 4
2025-03-04 13:55:17 -05:00
} ;
// Get log level from environment or default to info
const LOG _LEVEL = process . env . LOG _LEVEL ? LOG _LEVELS [ process . env . LOG _LEVEL . toLowerCase ( ) ] : LOG _LEVELS . info ;
2025-03-22 02:18:59 -04:00
// Create a color gradient for the banner
const coolGradient = gradient ( [ '#00b4d8' , '#0077b6' , '#03045e' ] ) ;
const warmGradient = gradient ( [ '#fb8b24' , '#e36414' , '#9a031e' ] ) ;
// Display a fancy banner
function displayBanner ( ) {
console . clear ( ) ;
2025-03-22 03:16:12 -04:00
const bannerText = figlet . textSync ( 'Task Master AI' , {
2025-03-22 02:18:59 -04:00
font : 'Standard' ,
horizontalLayout : 'default' ,
verticalLayout : 'default'
} ) ;
console . log ( coolGradient ( bannerText ) ) ;
2025-03-22 03:16:12 -04:00
// Add creator credit line below the banner
console . log ( chalk . dim ( 'by ' ) + chalk . cyan . underline ( 'https://x.com/eyaltoledano' ) ) ;
2025-03-22 02:18:59 -04:00
console . log ( boxen ( chalk . white ( ` ${ chalk . bold ( 'Initializing' ) } your new project ` ) , {
padding : 1 ,
margin : { top : 0 , bottom : 1 } ,
borderStyle : 'round' ,
borderColor : 'cyan'
} ) ) ;
}
// Logging function with icons and colors
2025-03-04 13:55:17 -05:00
function log ( level , ... args ) {
2025-03-22 02:18:59 -04:00
const icons = {
debug : chalk . gray ( '🔍' ) ,
info : chalk . blue ( 'ℹ ️ ' ) ,
warn : chalk . yellow ( '⚠️' ) ,
error : chalk . red ( '❌' ) ,
success : chalk . green ( '✅' )
} ;
2025-03-04 13:55:17 -05:00
2025-03-22 02:18:59 -04:00
if ( LOG _LEVELS [ level ] >= LOG _LEVEL ) {
const icon = icons [ level ] || '' ;
2025-03-04 13:55:17 -05:00
2025-03-22 02:18:59 -04:00
if ( level === 'error' ) {
console . error ( icon , chalk . red ( ... args ) ) ;
} else if ( level === 'warn' ) {
console . warn ( icon , chalk . yellow ( ... args ) ) ;
} else if ( level === 'success' ) {
console . log ( icon , chalk . green ( ... args ) ) ;
} else if ( level === 'info' ) {
console . log ( icon , chalk . blue ( ... args ) ) ;
} else {
console . log ( icon , ... args ) ;
}
2025-03-04 13:55:17 -05:00
}
// Write to debug log if DEBUG=true
if ( process . env . DEBUG === 'true' ) {
const logMessage = ` [ ${ level . toUpperCase ( ) } ] ${ args . join ( ' ' ) } \n ` ;
fs . appendFileSync ( 'init-debug.log' , logMessage ) ;
}
}
// Function to create directory if it doesn't exist
function ensureDirectoryExists ( dirPath ) {
if ( ! fs . existsSync ( dirPath ) ) {
fs . mkdirSync ( dirPath , { recursive : true } ) ;
log ( 'info' , ` Created directory: ${ dirPath } ` ) ;
}
}
// Function to copy a file from the package to the target directory
function copyTemplateFile ( templateName , targetPath , replacements = { } ) {
2025-03-04 16:50:42 -05:00
// Get the file content from the appropriate source directory
let sourcePath ;
// Map template names to their actual source paths
switch ( templateName ) {
case 'dev.js' :
sourcePath = path . join ( _ _dirname , 'dev.js' ) ;
break ;
case 'scripts_README.md' :
2025-03-21 16:25:12 -04:00
sourcePath = path . join ( _ _dirname , '..' , 'assets' , 'scripts_README.md' ) ;
2025-03-04 16:50:42 -05:00
break ;
case 'dev_workflow.mdc' :
sourcePath = path . join ( _ _dirname , '..' , '.cursor' , 'rules' , 'dev_workflow.mdc' ) ;
break ;
2025-03-21 14:06:36 -04:00
case 'cursor_rules.mdc' :
sourcePath = path . join ( _ _dirname , '..' , '.cursor' , 'rules' , 'cursor_rules.mdc' ) ;
break ;
case 'self_improve.mdc' :
sourcePath = path . join ( _ _dirname , '..' , '.cursor' , 'rules' , 'self_improve.mdc' ) ;
break ;
2025-03-21 14:17:43 -04:00
case 'README-task-master.md' :
sourcePath = path . join ( _ _dirname , '..' , 'README-task-master.md' ) ;
2025-03-04 16:50:42 -05:00
break ;
default :
// For other files like env.example, gitignore, etc. that don't have direct equivalents
sourcePath = path . join ( _ _dirname , '..' , 'assets' , templateName ) ;
}
// Check if the source file exists
if ( ! fs . existsSync ( sourcePath ) ) {
// Fall back to templates directory for files that might not have been moved yet
sourcePath = path . join ( _ _dirname , '..' , 'assets' , templateName ) ;
if ( ! fs . existsSync ( sourcePath ) ) {
log ( 'error' , ` Source file not found: ${ sourcePath } ` ) ;
return ;
}
}
let content = fs . readFileSync ( sourcePath , 'utf8' ) ;
2025-03-04 13:55:17 -05:00
// Replace placeholders with actual values
Object . entries ( replacements ) . forEach ( ( [ key , value ] ) => {
const regex = new RegExp ( ` \\ { \\ { ${ key } \\ } \\ } ` , 'g' ) ;
content = content . replace ( regex , value ) ;
} ) ;
// Write the content to the target path
fs . writeFileSync ( targetPath , content ) ;
log ( 'info' , ` Created file: ${ targetPath } ` ) ;
}
// Main function to initialize a new project
async function initializeProject ( options = { } ) {
2025-03-22 02:18:59 -04:00
// Display the banner
displayBanner ( ) ;
2025-03-04 14:46:46 -05:00
// If options are provided, use them directly without prompting
if ( options . projectName && options . projectDescription ) {
const projectName = options . projectName ;
const projectDescription = options . projectDescription ;
const projectVersion = options . projectVersion || '1.0.0' ;
const authorName = options . authorName || '' ;
createProjectStructure ( projectName , projectDescription , projectVersion , authorName ) ;
return {
projectName ,
projectDescription ,
projectVersion ,
authorName
} ;
}
// Otherwise, prompt the user for input
// Create readline interface only when needed
const rl = readline . createInterface ( {
input : process . stdin ,
output : process . stdout
} ) ;
try {
2025-03-22 02:18:59 -04:00
const projectName = await promptQuestion ( rl , chalk . cyan ( 'Enter project name: ' ) ) ;
const projectDescription = await promptQuestion ( rl , chalk . cyan ( 'Enter project description: ' ) ) ;
const projectVersionInput = await promptQuestion ( rl , chalk . cyan ( 'Enter project version (default: 1.0.0): ' ) ) ;
const authorName = await promptQuestion ( rl , chalk . cyan ( 'Enter your name: ' ) ) ;
2025-03-04 14:46:46 -05:00
// Set default version if not provided
const projectVersion = projectVersionInput . trim ( ) ? projectVersionInput : '1.0.0' ;
// Close the readline interface
rl . close ( ) ;
// Create the project structure
createProjectStructure ( projectName , projectDescription , projectVersion , authorName ) ;
return {
projectName ,
projectDescription ,
projectVersion ,
authorName
} ;
} catch ( error ) {
// Make sure to close readline on error
rl . close ( ) ;
throw error ;
}
}
// Helper function to promisify readline question
function promptQuestion ( rl , question ) {
2025-03-04 13:55:17 -05:00
return new Promise ( ( resolve ) => {
2025-03-04 14:46:46 -05:00
rl . question ( question , ( answer ) => {
resolve ( answer ) ;
} ) ;
2025-03-04 13:55:17 -05:00
} ) ;
}
// Function to create the project structure
function createProjectStructure ( projectName , projectDescription , projectVersion , authorName ) {
const targetDir = process . cwd ( ) ;
log ( 'info' , ` Initializing project in ${ targetDir } ` ) ;
// Create directories
ensureDirectoryExists ( path . join ( targetDir , '.cursor' , 'rules' ) ) ;
ensureDirectoryExists ( path . join ( targetDir , 'scripts' ) ) ;
ensureDirectoryExists ( path . join ( targetDir , 'tasks' ) ) ;
// Create package.json
const packageJson = {
name : projectName . toLowerCase ( ) . replace ( /\s+/g , '-' ) ,
version : projectVersion ,
description : projectDescription ,
author : authorName ,
2025-03-04 14:11:57 -05:00
type : "module" ,
2025-03-04 13:55:17 -05:00
scripts : {
"dev" : "node scripts/dev.js" ,
"list" : "node scripts/dev.js list" ,
"generate" : "node scripts/dev.js generate" ,
"parse-prd" : "node scripts/dev.js parse-prd"
} ,
dependencies : {
2025-03-04 14:11:57 -05:00
"@anthropic-ai/sdk" : "^0.39.0" ,
2025-03-22 02:18:59 -04:00
"chalk" : "^5.3.0" ,
2025-03-04 13:55:17 -05:00
"commander" : "^11.1.0" ,
2025-03-21 14:17:43 -04:00
"dotenv" : "^16.3.1" ,
2025-03-22 02:18:59 -04:00
"openai" : "^4.86.1" ,
"figlet" : "^1.7.0" ,
"boxen" : "^7.1.1" ,
"gradient-string" : "^2.0.2" ,
"cli-table3" : "^0.6.3" ,
"ora" : "^7.0.1"
2025-03-04 13:55:17 -05:00
}
} ;
fs . writeFileSync (
path . join ( targetDir , 'package.json' ) ,
JSON . stringify ( packageJson , null , 2 )
) ;
2025-03-22 02:18:59 -04:00
log ( 'success' , 'Created package.json' ) ;
2025-03-04 13:55:17 -05:00
// Copy template files with replacements
const replacements = {
projectName ,
projectDescription ,
projectVersion ,
authorName ,
year : new Date ( ) . getFullYear ( )
} ;
// Copy .env.example
copyTemplateFile ( 'env.example' , path . join ( targetDir , '.env.example' ) , replacements ) ;
// Copy .gitignore
copyTemplateFile ( 'gitignore' , path . join ( targetDir , '.gitignore' ) ) ;
// Copy dev_workflow.mdc
copyTemplateFile ( 'dev_workflow.mdc' , path . join ( targetDir , '.cursor' , 'rules' , 'dev_workflow.mdc' ) ) ;
2025-03-21 14:06:36 -04:00
// Copy cursor_rules.mdc
copyTemplateFile ( 'cursor_rules.mdc' , path . join ( targetDir , '.cursor' , 'rules' , 'cursor_rules.mdc' ) ) ;
// Copy self_improve.mdc
copyTemplateFile ( 'self_improve.mdc' , path . join ( targetDir , '.cursor' , 'rules' , 'self_improve.mdc' ) ) ;
2025-03-04 13:55:17 -05:00
// Copy scripts/dev.js
copyTemplateFile ( 'dev.js' , path . join ( targetDir , 'scripts' , 'dev.js' ) ) ;
// Copy scripts/README.md
copyTemplateFile ( 'scripts_README.md' , path . join ( targetDir , 'scripts' , 'README.md' ) ) ;
// Copy example_prd.txt
copyTemplateFile ( 'example_prd.txt' , path . join ( targetDir , 'scripts' , 'example_prd.txt' ) ) ;
// Create main README.md
2025-03-21 14:17:43 -04:00
copyTemplateFile ( 'README-task-master.md' , path . join ( targetDir , 'README.md' ) , replacements ) ;
2025-03-04 13:55:17 -05:00
// Initialize git repository if git is available
try {
if ( ! fs . existsSync ( path . join ( targetDir , '.git' ) ) ) {
2025-03-22 02:18:59 -04:00
log ( 'info' , 'Initializing git repository...' ) ;
2025-03-04 13:55:17 -05:00
execSync ( 'git init' , { stdio : 'ignore' } ) ;
2025-03-22 02:18:59 -04:00
log ( 'success' , 'Git repository initialized' ) ;
2025-03-04 13:55:17 -05:00
}
} catch ( error ) {
log ( 'warn' , 'Git not available, skipping repository initialization' ) ;
}
2025-03-21 16:25:12 -04:00
// Run npm install automatically
2025-03-22 02:18:59 -04:00
console . log ( boxen ( chalk . cyan ( 'Installing dependencies...' ) , {
padding : 0.5 ,
margin : 0.5 ,
borderStyle : 'round' ,
borderColor : 'blue'
} ) ) ;
2025-03-21 16:25:12 -04:00
try {
execSync ( 'npm install' , { stdio : 'inherit' , cwd : targetDir } ) ;
2025-03-22 02:18:59 -04:00
log ( 'success' , 'Dependencies installed successfully!' ) ;
2025-03-21 16:25:12 -04:00
} catch ( error ) {
log ( 'error' , 'Failed to install dependencies:' , error . message ) ;
log ( 'error' , 'Please run npm install manually' ) ;
}
2025-03-22 02:18:59 -04:00
// Display success message
console . log ( boxen (
warmGradient . multiline ( figlet . textSync ( 'Success!' , { font : 'Standard' } ) ) +
'\n' + chalk . green ( 'Project initialized successfully!' ) ,
{
padding : 1 ,
margin : 1 ,
borderStyle : 'double' ,
borderColor : 'green'
}
) ) ;
// Display next steps in a nice box
console . log ( boxen (
chalk . cyan . bold ( 'Things you can now do:' ) + '\n\n' +
chalk . white ( '1. ' ) + chalk . yellow ( 'Rename .env.example to .env and add your ANTHROPIC_API_KEY and PERPLEXITY_API_KEY' ) + '\n' +
2025-03-22 02:27:40 -04:00
chalk . white ( '2. ' ) + chalk . yellow ( 'Discuss your idea with AI, and once ready ask for a PRD using the example_prd.txt file, and save what you get to scripts/PRD.txt' ) + '\n' +
2025-03-22 02:18:59 -04:00
chalk . white ( '3. ' ) + chalk . yellow ( 'Ask Cursor Agent to parse your PRD.txt and generate tasks' ) + '\n' +
chalk . white ( ' └─ ' ) + chalk . dim ( 'You can also run ' ) + chalk . cyan ( 'npm run parse-prd -- --input=<your-prd-file.txt>' ) + '\n' +
chalk . white ( '4. ' ) + chalk . yellow ( 'Ask Cursor to analyze the complexity of your tasks' ) + '\n' +
chalk . white ( '5. ' ) + chalk . yellow ( 'Ask Cursor which task is next to determine where to start' ) + '\n' +
chalk . white ( '6. ' ) + chalk . yellow ( 'Ask Cursor to expand any complex tasks that are too large or complex.' ) + '\n' +
chalk . white ( '7. ' ) + chalk . yellow ( 'Ask Cursor to set the status of a task, or multiple tasks. Use the task id from the task lists.' ) + '\n' +
chalk . white ( '8. ' ) + chalk . yellow ( 'Ask Cursor to update all tasks from a specific task id based on new learnings or pivots in your project.' ) + '\n' +
chalk . white ( '9. ' ) + chalk . green . bold ( 'Ship it!' ) + '\n\n' +
chalk . dim ( '* Review the README.md file to learn how to use other commands via Cursor Agent.' ) ,
{
padding : 1 ,
margin : 1 ,
borderStyle : 'round' ,
borderColor : 'yellow' ,
title : 'Getting Started' ,
titleAlignment : 'center'
}
) ) ;
2025-03-04 13:55:17 -05:00
}
// Run the initialization if this script is executed directly
2025-03-04 14:46:46 -05:00
// The original check doesn't work with npx and global commands
// if (process.argv[1] === fileURLToPath(import.meta.url)) {
// Instead, we'll always run the initialization if this file is the main module
console . log ( 'Checking if script should run initialization...' ) ;
console . log ( 'import.meta.url:' , import . meta . url ) ;
console . log ( 'process.argv:' , process . argv ) ;
// Always run initialization when this file is loaded directly
// This works with both direct node execution and npx/global commands
( async function main ( ) {
try {
console . log ( 'Starting initialization...' ) ;
await initializeProject ( ) ;
// Process should exit naturally after completion
console . log ( 'Initialization completed, exiting...' ) ;
process . exit ( 0 ) ;
} catch ( error ) {
console . error ( 'Failed to initialize project:' , error ) ;
log ( 'error' , 'Failed to initialize project:' , error ) ;
process . exit ( 1 ) ;
}
} ) ( ) ;
2025-03-04 13:55:17 -05:00
// Export functions for programmatic use
2025-03-04 14:11:57 -05:00
export {
2025-03-04 13:55:17 -05:00
initializeProject ,
createProjectStructure ,
log
} ;