diff --git a/README.md b/README.md index aa993ece..59dc791f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,24 @@ This will prompt you for project details and set up a new project with the neces 1. This package uses ES modules. Your package.json should include `"type": "module"`. 2. The Anthropic SDK version should be 0.39.0 or higher. +## Troubleshooting + +### If `npx claude-task-init` doesn't respond: + +Try running it with Node directly: + +```bash +node node_modules/claude-task-master/scripts/init.js +``` + +Or clone the repository and run: + +```bash +git clone https://github.com/eyaltoledano/claude-task-master.git +cd claude-task-master +node scripts/init.js +``` + ## Integrating with Cursor AI Claude Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development. diff --git a/package.json b/package.json index ea23e387..b7fdb4f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-task-master", - "version": "1.0.2", + "version": "1.0.7", "description": "A task management system for AI-driven development with Claude", "main": "index.js", "type": "module", diff --git a/scripts/dev.js b/scripts/dev.js index 77450fb1..052865f2 100755 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -15,7 +15,8 @@ * -> Generates per-task files (e.g., task_001.txt) from tasks.json * * 4) set-status --id=4 --status=done - * -> Updates a single task's status to done (or pending, deferred, etc.). + * -> Updates a single task's status to done (or pending, deferred, in-progress, etc.). + * -> Supports comma-separated IDs for updating multiple tasks: --id=1,2,3,1.1,1.2 * * 5) list * -> Lists tasks in a brief console view (ID, title, status). @@ -302,13 +303,65 @@ function generateTaskFiles(tasksPath, outputDir) { // // 4) set-status // -function setTaskStatus(tasksPath, taskId, newStatus) { +function setTaskStatus(tasksPath, taskIdInput, newStatus) { + // For recursive calls with multiple IDs, we need to read the latest data each time const data = readJSON(tasksPath); if (!data || !data.tasks) { log('error', "No valid tasks found."); process.exit(1); } + // Handle multiple task IDs (comma-separated) + if (typeof taskIdInput === 'string' && taskIdInput.includes(',')) { + const taskIds = taskIdInput.split(',').map(id => id.trim()); + log('info', `Processing multiple tasks: ${taskIds.join(', ')}`); + + // Process each task ID individually + for (const taskId of taskIds) { + // Create a new instance for each task to ensure we're working with fresh data + setTaskStatus(tasksPath, taskId, newStatus); + } + + return; + } + + // Convert numeric taskId to number if it's not a subtask ID + const taskId = (!isNaN(taskIdInput) && !String(taskIdInput).includes('.')) + ? parseInt(taskIdInput, 10) + : taskIdInput; + + // Check if this is a subtask ID (e.g., "1.1") + if (typeof taskId === 'string' && taskId.includes('.')) { + const [parentIdStr, subtaskIdStr] = taskId.split('.'); + const parentId = parseInt(parentIdStr, 10); + const subtaskId = parseInt(subtaskIdStr, 10); + + const parentTask = data.tasks.find(t => t.id === parentId); + + if (!parentTask) { + log('error', `Parent task with ID=${parentId} not found.`); + process.exit(1); + } + + if (!parentTask.subtasks || parentTask.subtasks.length === 0) { + log('error', `Parent task with ID=${parentId} has no subtasks.`); + process.exit(1); + } + + const subtask = parentTask.subtasks.find(st => st.id === subtaskId); + if (!subtask) { + log('error', `Subtask with ID=${subtaskId} not found in parent task ID=${parentId}.`); + process.exit(1); + } + + const oldStatus = subtask.status; + subtask.status = newStatus; + writeJSON(tasksPath, data); + log('info', `Subtask ${parentId}.${subtaskId} status changed from '${oldStatus}' to '${newStatus}'.`); + return; + } + + // Handle regular task ID const task = data.tasks.find(t => t.id === taskId); if (!task) { log('error', `Task with ID=${taskId} not found.`); @@ -701,11 +754,11 @@ function parseSubtasksFromText(text, startId, expectedCount) { process.exit(1); } if (!statusArg) { - log('error', "Missing --status= argument (e.g. done, pending, deferred)."); + log('error', "Missing --status= argument (e.g., done, pending, deferred, in-progress)."); process.exit(1); } - log('info', `Setting task ${idArg} status to "${statusArg}"...`); - setTaskStatus(tasksPath, parseInt(idArg, 10), statusArg); + log('info', `Setting task(s) ${idArg} status to "${statusArg}"...`); + setTaskStatus(tasksPath, idArg, statusArg); break; case 'list': @@ -744,7 +797,8 @@ Subcommands: -> Generates per-task files (e.g., task_001.txt) from tasks.json 4) set-status --id=4 --status=done - -> Updates a single task's status to done (or pending, deferred, etc.). + -> Updates a single task's status to done (or pending, deferred, in-progress, etc.). + -> Supports comma-separated IDs for updating multiple tasks: --id=1,2,3,1.1,1.2 5) list -> Lists tasks in a brief console view (ID, title, status). diff --git a/scripts/init.js b/scripts/init.js index 72e3ec65..668a1228 100755 --- a/scripts/init.js +++ b/scripts/init.js @@ -1,5 +1,7 @@ #!/usr/bin/env node +console.log('Starting claude-task-init...'); + import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; @@ -7,15 +9,14 @@ 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); -// Create readline interface for user input -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - // Define log levels and colors const LOG_LEVELS = { debug: 0, @@ -88,47 +89,63 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) { // 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) => { - // If options are provided, use them directly - 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); - resolve({ - projectName, - projectDescription, - projectVersion, - authorName - }); - } else { - // Otherwise, prompt the user for input - rl.question('Enter project name: ', (projectName) => { - rl.question('Enter project description: ', (projectDescription) => { - rl.question('Enter project version (default: 1.0.0): ', (projectVersion) => { - rl.question('Enter your name: ', (authorName) => { - // Set default version if not provided - if (!projectVersion.trim()) { - projectVersion = '1.0.0'; - } - - // Create the project structure - createProjectStructure(projectName, projectDescription, projectVersion, authorName); - - rl.close(); - resolve({ - projectName, - projectDescription, - projectVersion, - authorName - }); - }); - }); - }); - }); - } + rl.question(question, (answer) => { + resolve(answer); + }); }); } @@ -199,22 +216,6 @@ function createProjectStructure(projectName, projectDescription, projectVersion, // Create main README.md copyTemplateFile('README.md', path.join(targetDir, 'README.md'), replacements); - // Create empty tasks.json - const tasksJson = { - meta: { - name: projectName, - version: projectVersion, - description: projectDescription - }, - tasks: [] - }; - - fs.writeFileSync( - path.join(targetDir, 'tasks.json'), - JSON.stringify(tasksJson, null, 2) - ); - log('info', 'Created tasks.json'); - // Initialize git repository if git is available try { if (!fs.existsSync(path.join(targetDir, '.git'))) { @@ -236,16 +237,28 @@ function createProjectStructure(projectName, projectDescription, projectVersion, } // Run the initialization if this script is executed directly -if (process.argv[1] === fileURLToPath(import.meta.url)) { - (async function main() { - try { - await initializeProject(); - } catch (error) { - log('error', 'Failed to initialize project:', error); - process.exit(1); - } - })(); -} +// 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 { diff --git a/scripts/prepare-package.js b/scripts/prepare-package.js index 873d3c79..86a9aa50 100755 --- a/scripts/prepare-package.js +++ b/scripts/prepare-package.js @@ -144,6 +144,17 @@ function preparePackage() { process.exit(1); } + // Make scripts executable + log('info', 'Making scripts executable...'); + try { + execSync('chmod +x scripts/init.js', { stdio: 'ignore' }); + log('info', 'Made scripts/init.js executable'); + execSync('chmod +x scripts/dev.js', { stdio: 'ignore' }); + log('info', 'Made scripts/dev.js executable'); + } catch (error) { + log('error', 'Failed to make scripts executable:', error.message); + } + log('success', 'Package preparation completed successfully!'); log('info', 'You can now publish the package with:'); log('info', ' npm publish');