Compare commits

..

2 Commits

Author SHA1 Message Date
Ralph Khreish
879409ac66 feat: improve tm init 2025-04-16 14:51:02 +02:00
Ralph Khreish
d9296614f8 chore: fix weird bug where package.json is not upgrading its version based on current package version 2025-04-16 14:48:20 +02:00
10 changed files with 116 additions and 234 deletions

View File

@@ -1,5 +0,0 @@
---
'task-master-ai': patch
---
Fix remove-task command to handle multiple comma-separated task IDs

View File

@@ -1,5 +0,0 @@
---
'task-master-ai': minor
---
Add `npx task-master-ai` that runs mcp instead of using `task-master-mcp``

View File

@@ -146,7 +146,7 @@ To enable enhanced task management capabilities directly within Cursor using the
4. Configure with the following details: 4. Configure with the following details:
- Name: "Task Master" - Name: "Task Master"
- Type: "Command" - Type: "Command"
- Command: "npx -y task-master-ai" - Command: "npx -y task-master-mcp"
5. Save the settings 5. Save the settings
Once configured, you can interact with Task Master's task management commands directly through Cursor's interface, providing a more integrated experience. Once configured, you can interact with Task Master's task management commands directly through Cursor's interface, providing a more integrated experience.

View File

@@ -20,14 +20,20 @@ A task management system for AI-driven development with Claude, designed to work
MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor. MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor.
1. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors): 1. **Install the package**
```bash
npm i -g task-master-ai
```
2. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
```json ```json
{ {
"mcpServers": { "mcpServers": {
"taskmaster-ai": { "taskmaster-ai": {
"command": "npx", "command": "npx",
"args": ["-y", "task-master-ai"], "args": ["-y", "task-master-mcp"],
"env": { "env": {
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE", "ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE", "PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",

View File

@@ -10,14 +10,20 @@ There are two ways to set up Task Master: using MCP (recommended) or via npm ins
MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor. MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor.
1. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors): 1. **Install the package**
```bash
npm i -g task-master-ai
```
2. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
```json ```json
{ {
"mcpServers": { "mcpServers": {
"taskmaster-ai": { "taskmaster-ai": {
"command": "npx", "command": "npx",
"args": ["-y", "task-master-ai"], "args": ["-y", "task-master-mcp"],
"env": { "env": {
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE", "ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE", "PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",

View File

@@ -3,23 +3,18 @@
* Direct function implementation for removing a task * Direct function implementation for removing a task
*/ */
import { import { removeTask } from '../../../../scripts/modules/task-manager.js';
removeTask,
taskExists
} from '../../../../scripts/modules/task-manager.js';
import { import {
enableSilentMode, enableSilentMode,
disableSilentMode, disableSilentMode
readJSON
} from '../../../../scripts/modules/utils.js'; } from '../../../../scripts/modules/utils.js';
/** /**
* Direct function wrapper for removeTask with error handling. * Direct function wrapper for removeTask with error handling.
* Supports removing multiple tasks at once with comma-separated IDs.
* *
* @param {Object} args - Command arguments * @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple). * @param {string} args.id - The ID of the task or subtask to remove.
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false } * @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false }
*/ */
@@ -41,7 +36,8 @@ export async function removeTaskDirect(args, log) {
} }
// Validate task ID parameter // Validate task ID parameter
if (!id) { const taskId = id;
if (!taskId) {
log.error('Task ID is required'); log.error('Task ID is required');
return { return {
success: false, success: false,
@@ -53,103 +49,46 @@ export async function removeTaskDirect(args, log) {
}; };
} }
// Split task IDs if comma-separated // Skip confirmation in the direct function since it's handled by the client
const taskIdArray = id.split(',').map((taskId) => taskId.trim()); log.info(`Removing task with ID: ${taskId} from ${tasksJsonPath}`);
log.info(
`Removing ${taskIdArray.length} task(s) with ID(s): ${taskIdArray.join(', ')} from ${tasksJsonPath}`
);
// Validate all task IDs exist before proceeding
const data = readJSON(tasksJsonPath);
if (!data || !data.tasks) {
return {
success: false,
error: {
code: 'INVALID_TASKS_FILE',
message: `No valid tasks found in ${tasksJsonPath}`
},
fromCache: false
};
}
const invalidTasks = taskIdArray.filter(
(taskId) => !taskExists(data.tasks, taskId)
);
if (invalidTasks.length > 0) {
return {
success: false,
error: {
code: 'INVALID_TASK_ID',
message: `The following tasks were not found: ${invalidTasks.join(', ')}`
},
fromCache: false
};
}
// Remove tasks one by one
const results = [];
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
try { try {
for (const taskId of taskIdArray) { // Enable silent mode to prevent console logs from interfering with JSON response
try { enableSilentMode();
const result = await removeTask(tasksJsonPath, taskId);
results.push({ // Call the core removeTask function using the provided path
taskId, const result = await removeTask(tasksJsonPath, taskId);
success: true,
message: result.message,
removedTask: result.removedTask
});
log.info(`Successfully removed task: ${taskId}`);
} catch (error) {
results.push({
taskId,
success: false,
error: error.message
});
log.error(`Error removing task ${taskId}: ${error.message}`);
}
}
} finally {
// Restore normal logging // Restore normal logging
disableSilentMode(); disableSilentMode();
}
// Check if all tasks were successfully removed log.info(`Successfully removed task: ${taskId}`);
const successfulRemovals = results.filter((r) => r.success);
const failedRemovals = results.filter((r) => !r.success);
if (successfulRemovals.length === 0) { // Return the result
// All removals failed return {
success: true,
data: {
message: result.message,
taskId: taskId,
tasksPath: tasksJsonPath,
removedTask: result.removedTask
},
fromCache: false
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error removing task: ${error.message}`);
return { return {
success: false, success: false,
error: { error: {
code: 'REMOVE_TASK_ERROR', code: error.code || 'REMOVE_TASK_ERROR',
message: 'Failed to remove any tasks', message: error.message || 'Failed to remove task'
details: failedRemovals
.map((r) => `${r.taskId}: ${r.error}`)
.join('; ')
}, },
fromCache: false fromCache: false
}; };
} }
// At least some tasks were removed successfully
return {
success: true,
data: {
totalTasks: taskIdArray.length,
successful: successfulRemovals.length,
failed: failedRemovals.length,
results: results,
tasksPath: tasksJsonPath
},
fromCache: false
};
} catch (error) { } catch (error) {
// Ensure silent mode is disabled even if an outer error occurs // Ensure silent mode is disabled even if an outer error occurs
disableSilentMode(); disableSilentMode();

View File

@@ -23,9 +23,7 @@ export function registerRemoveTaskTool(server) {
parameters: z.object({ parameters: z.object({
id: z id: z
.string() .string()
.describe( .describe("ID of the task or subtask to remove (e.g., '5' or '5.2')"),
"ID(s) of the task(s) or subtask(s) to remove (e.g., '5' or '5.2' or '5,6,7')"
),
file: z.string().optional().describe('Absolute path to the tasks file'), file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z projectRoot: z
.string() .string()
@@ -37,7 +35,7 @@ export function registerRemoveTaskTool(server) {
}), }),
execute: async (args, { log, session }) => { execute: async (args, { log, session }) => {
try { try {
log.info(`Removing task(s) with ID(s): ${args.id}`); log.info(`Removing task with ID: ${args.id}`);
// Get project root from args or session // Get project root from args or session
const rootFolder = const rootFolder =

1
package-lock.json generated
View File

@@ -31,7 +31,6 @@
}, },
"bin": { "bin": {
"task-master": "bin/task-master.js", "task-master": "bin/task-master.js",
"task-master-ai": "mcp-server/server.js",
"task-master-mcp": "mcp-server/server.js" "task-master-mcp": "mcp-server/server.js"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -6,8 +6,7 @@
"type": "module", "type": "module",
"bin": { "bin": {
"task-master": "bin/task-master.js", "task-master": "bin/task-master.js",
"task-master-mcp": "mcp-server/server.js", "task-master-mcp": "mcp-server/server.js"
"task-master-ai": "mcp-server/server.js"
}, },
"scripts": { "scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest", "test": "node --experimental-vm-modules node_modules/.bin/jest",

View File

@@ -1374,18 +1374,18 @@ function registerCommands(programInstance) {
// remove-task command // remove-task command
programInstance programInstance
.command('remove-task') .command('remove-task')
.description('Remove one or more tasks or subtasks permanently') .description('Remove a task or subtask permanently')
.option( .option(
'-i, --id <id>', '-i, --id <id>',
'ID(s) of the task(s) or subtask(s) to remove (e.g., "5" or "5.2" or "5,6,7")' 'ID of the task or subtask to remove (e.g., "5" or "5.2")'
) )
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json') .option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option('-y, --yes', 'Skip confirmation prompt', false) .option('-y, --yes', 'Skip confirmation prompt', false)
.action(async (options) => { .action(async (options) => {
const tasksPath = options.file; const tasksPath = options.file;
const taskIds = options.id; const taskId = options.id;
if (!taskIds) { if (!taskId) {
console.error(chalk.red('Error: Task ID is required')); console.error(chalk.red('Error: Task ID is required'));
console.error( console.error(
chalk.yellow('Usage: task-master remove-task --id=<taskId>') chalk.yellow('Usage: task-master remove-task --id=<taskId>')
@@ -1394,7 +1394,7 @@ function registerCommands(programInstance) {
} }
try { try {
// Check if the tasks file exists and is valid // Check if the task exists
const data = readJSON(tasksPath); const data = readJSON(tasksPath);
if (!data || !data.tasks) { if (!data || !data.tasks) {
console.error( console.error(
@@ -1403,89 +1403,75 @@ function registerCommands(programInstance) {
process.exit(1); process.exit(1);
} }
// Split task IDs if comma-separated if (!taskExists(data.tasks, taskId)) {
const taskIdArray = taskIds.split(',').map((id) => id.trim()); console.error(chalk.red(`Error: Task with ID ${taskId} not found`));
// Validate all task IDs exist before proceeding
const invalidTasks = taskIdArray.filter(
(id) => !taskExists(data.tasks, id)
);
if (invalidTasks.length > 0) {
console.error(
chalk.red(
`Error: The following tasks were not found: ${invalidTasks.join(', ')}`
)
);
process.exit(1); process.exit(1);
} }
// Load task for display
const task = findTaskById(data.tasks, taskId);
// Skip confirmation if --yes flag is provided // Skip confirmation if --yes flag is provided
if (!options.yes) { if (!options.yes) {
// Display tasks to be removed // Display task information
console.log(); console.log();
console.log( console.log(
chalk.red.bold( chalk.red.bold(
'⚠️ WARNING: This will permanently delete the following tasks:' '⚠️ WARNING: This will permanently delete the following task:'
) )
); );
console.log(); console.log();
for (const taskId of taskIdArray) { if (typeof taskId === 'string' && taskId.includes('.')) {
const task = findTaskById(data.tasks, taskId); // It's a subtask
const [parentId, subtaskId] = taskId.split('.');
console.log(chalk.white.bold(`Subtask ${taskId}: ${task.title}`));
console.log(
chalk.gray(
`Parent Task: ${task.parentTask.id} - ${task.parentTask.title}`
)
);
} else {
// It's a main task
console.log(chalk.white.bold(`Task ${taskId}: ${task.title}`));
if (typeof taskId === 'string' && taskId.includes('.')) { // Show if it has subtasks
// It's a subtask if (task.subtasks && task.subtasks.length > 0) {
const [parentId, subtaskId] = taskId.split('.');
console.log(chalk.white.bold(`Subtask ${taskId}: ${task.title}`));
console.log( console.log(
chalk.gray( chalk.yellow(
`Parent Task: ${task.parentTask.id} - ${task.parentTask.title}` `⚠️ This task has ${task.subtasks.length} subtasks that will also be deleted!`
) )
); );
} else {
// It's a main task
console.log(chalk.white.bold(`Task ${taskId}: ${task.title}`));
// Show if it has subtasks
if (task.subtasks && task.subtasks.length > 0) {
console.log(
chalk.yellow(
`⚠️ This task has ${task.subtasks.length} subtasks that will also be deleted!`
)
);
}
// Show if other tasks depend on it
const dependentTasks = data.tasks.filter(
(t) =>
t.dependencies &&
t.dependencies.includes(parseInt(taskId, 10))
);
if (dependentTasks.length > 0) {
console.log(
chalk.yellow(
`⚠️ Warning: ${dependentTasks.length} other tasks depend on this task!`
)
);
console.log(
chalk.yellow('These dependencies will be removed:')
);
dependentTasks.forEach((t) => {
console.log(chalk.yellow(` - Task ${t.id}: ${t.title}`));
});
}
} }
console.log();
// Show if other tasks depend on it
const dependentTasks = data.tasks.filter(
(t) =>
t.dependencies && t.dependencies.includes(parseInt(taskId, 10))
);
if (dependentTasks.length > 0) {
console.log(
chalk.yellow(
`⚠️ Warning: ${dependentTasks.length} other tasks depend on this task!`
)
);
console.log(chalk.yellow('These dependencies will be removed:'));
dependentTasks.forEach((t) => {
console.log(chalk.yellow(` - Task ${t.id}: ${t.title}`));
});
}
} }
console.log();
// Prompt for confirmation // Prompt for confirmation
const { confirm } = await inquirer.prompt([ const { confirm } = await inquirer.prompt([
{ {
type: 'confirm', type: 'confirm',
name: 'confirm', name: 'confirm',
message: chalk.red.bold( message: chalk.red.bold(
`Are you sure you want to permanently delete ${taskIdArray.length > 1 ? 'these tasks' : 'this task'}?` 'Are you sure you want to permanently delete this task?'
), ),
default: false default: false
} }
@@ -1497,72 +1483,31 @@ function registerCommands(programInstance) {
} }
} }
const indicator = startLoadingIndicator('Removing tasks...'); const indicator = startLoadingIndicator('Removing task...');
// Remove each task // Remove the task
const results = []; const result = await removeTask(tasksPath, taskId);
for (const taskId of taskIdArray) {
try {
const result = await removeTask(tasksPath, taskId);
results.push({ taskId, success: true, ...result });
} catch (error) {
results.push({ taskId, success: false, error: error.message });
}
}
stopLoadingIndicator(indicator); stopLoadingIndicator(indicator);
// Display results // Display success message with appropriate color based on task or subtask
const successfulRemovals = results.filter((r) => r.success); if (typeof taskId === 'string' && taskId.includes('.')) {
const failedRemovals = results.filter((r) => !r.success); // It was a subtask
if (successfulRemovals.length > 0) {
console.log( console.log(
boxen( boxen(
chalk.green( chalk.green(`Subtask ${taskId} has been successfully removed`),
`Successfully removed ${successfulRemovals.length} task${successfulRemovals.length > 1 ? 's' : ''}` { padding: 1, borderColor: 'green', borderStyle: 'round' }
) +
'\n\n' +
successfulRemovals
.map((r) =>
chalk.white(
`${r.taskId.includes('.') ? 'Subtask' : 'Task'} ${r.taskId}`
)
)
.join('\n'),
{
padding: 1,
borderColor: 'green',
borderStyle: 'round',
margin: { top: 1 }
}
) )
); );
} } else {
// It was a main task
if (failedRemovals.length > 0) {
console.log( console.log(
boxen( boxen(chalk.green(`Task ${taskId} has been successfully removed`), {
chalk.red( padding: 1,
`Failed to remove ${failedRemovals.length} task${failedRemovals.length > 1 ? 's' : ''}` borderColor: 'green',
) + borderStyle: 'round'
'\n\n' + })
failedRemovals
.map((r) => chalk.white(`${r.taskId}: ${r.error}`))
.join('\n'),
{
padding: 1,
borderColor: 'red',
borderStyle: 'round',
margin: { top: 1 }
}
)
); );
// Exit with error if any removals failed
if (successfulRemovals.length === 0) {
process.exit(1);
}
} }
} catch (error) { } catch (error) {
console.error( console.error(