mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-06-27 00:29:58 +00:00

This commit includes several important improvements: 1. Add support for updating multiple tasks at once with comma-separated IDs - Modify setTaskStatus to handle lists like id=1,1.1,1.2 - Fix subtask handling to properly update subtask statuses - Add in-progress as a valid status option 2. Fix initialization script issues - Add debugging information to help diagnose npx execution problems - Improve error handling and readline interface management - Remove conditional check that prevented script from running in some environments - Add troubleshooting section to README.md 3. Improve package preparation - Make scripts executable during package preparation - Update version to 1.0.7 These changes make the package more robust and user-friendly, particularly for first-time users and those managing complex task hierarchies.
268 lines
8.3 KiB
JavaScript
Executable File
268 lines
8.3 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
console.log('Starting claude-task-init...');
|
|
|
|
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';
|
|
|
|
// Debug information
|
|
console.log('Node version:', process.version);
|
|
console.log('Current directory:', process.cwd());
|
|
console.log('Script path:', import.meta.url);
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
// Define log levels and colors
|
|
const LOG_LEVELS = {
|
|
debug: 0,
|
|
info: 1,
|
|
warn: 2,
|
|
error: 3
|
|
};
|
|
|
|
const COLORS = {
|
|
reset: '\x1b[0m',
|
|
bright: '\x1b[1m',
|
|
dim: '\x1b[2m',
|
|
red: '\x1b[31m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
blue: '\x1b[34m',
|
|
magenta: '\x1b[35m',
|
|
cyan: '\x1b[36m'
|
|
};
|
|
|
|
// 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;
|
|
|
|
// Logging function
|
|
function log(level, ...args) {
|
|
const levelValue = LOG_LEVELS[level.toLowerCase()];
|
|
|
|
if (levelValue >= LOG_LEVEL) {
|
|
const prefix = {
|
|
debug: `${COLORS.dim}[DEBUG]${COLORS.reset}`,
|
|
info: `${COLORS.blue}[INFO]${COLORS.reset}`,
|
|
warn: `${COLORS.yellow}[WARN]${COLORS.reset}`,
|
|
error: `${COLORS.red}[ERROR]${COLORS.reset}`
|
|
}[level.toLowerCase()];
|
|
|
|
console.log(prefix, ...args);
|
|
}
|
|
|
|
// 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 = {}) {
|
|
// Get the template content from the templates directory
|
|
const templatePath = path.join(__dirname, '..', 'templates', templateName);
|
|
let content = fs.readFileSync(templatePath, 'utf8');
|
|
|
|
// 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 = {}) {
|
|
// 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 {
|
|
const projectName = await promptQuestion(rl, 'Enter project name: ');
|
|
const projectDescription = await promptQuestion(rl, 'Enter project description: ');
|
|
const projectVersionInput = await promptQuestion(rl, 'Enter project version (default: 1.0.0): ');
|
|
const authorName = await promptQuestion(rl, 'Enter your name: ');
|
|
|
|
// 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) {
|
|
return new Promise((resolve) => {
|
|
rl.question(question, (answer) => {
|
|
resolve(answer);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 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,
|
|
type: "module",
|
|
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: {
|
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
"chalk": "^4.1.2",
|
|
"commander": "^11.1.0",
|
|
"dotenv": "^16.3.1"
|
|
}
|
|
};
|
|
|
|
fs.writeFileSync(
|
|
path.join(targetDir, 'package.json'),
|
|
JSON.stringify(packageJson, null, 2)
|
|
);
|
|
log('info', 'Created package.json');
|
|
|
|
// 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'));
|
|
|
|
// 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
|
|
copyTemplateFile('README.md', path.join(targetDir, 'README.md'), replacements);
|
|
|
|
// Initialize git repository if git is available
|
|
try {
|
|
if (!fs.existsSync(path.join(targetDir, '.git'))) {
|
|
execSync('git init', { stdio: 'ignore' });
|
|
log('info', 'Initialized git repository');
|
|
}
|
|
} catch (error) {
|
|
log('warn', 'Git not available, skipping repository initialization');
|
|
}
|
|
|
|
log('info', `${COLORS.green}${COLORS.bright}Project initialized successfully!${COLORS.reset}`);
|
|
log('info', '');
|
|
log('info', 'Next steps:');
|
|
log('info', '1. Run `npm install` to install dependencies');
|
|
log('info', '2. Create a .env file with your ANTHROPIC_API_KEY (see .env.example)');
|
|
log('info', '3. Add your PRD to the project');
|
|
log('info', '4. Run `npm run parse-prd -- --input=<your-prd-file.txt>` to generate tasks');
|
|
log('info', '');
|
|
}
|
|
|
|
// Run the initialization if this script is executed directly
|
|
// 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);
|
|
}
|
|
})();
|
|
|
|
// Export functions for programmatic use
|
|
export {
|
|
initializeProject,
|
|
createProjectStructure,
|
|
log
|
|
};
|