Compare commits

...

8 Commits

Author SHA1 Message Date
Ralph Khreish
36cc324dc3 chore: improve changeset 2025-04-18 23:51:14 +02:00
Kresna Sucandra
d75a6f3d00 fix: Improve error handling in task-master init for Linux containers - Fixes #211 2025-04-18 23:47:39 +02:00
Ralph Khreish
d99fa00980 feat: improve task-master init (#248)
* chore: fix weird bug where package.json is not upgrading its version based on current package version

* feat: improve `tm init`
2025-04-17 19:32:30 +02:00
Ralph Khreish
b2ccd60526 feat: add new bin task-master-ai same name as package to allow npx -y task-master-ai to work (#253) 2025-04-17 19:30:30 +02:00
Ralph Khreish
454a1d9d37 fix: shebang issues (#243)
Closes #241 #211 #184 #193
2025-04-16 11:06:18 +02:00
Eyal Toledano
4c57537157 Merge pull request #239 from eyaltoledano/update-task-id-desc
fix(update/update-task/update-subtask):
2025-04-16 00:42:15 -04:00
Eyal Toledano
6599cb0bf9 fix(update/update-task/update-subtask): Updates the parameter descriptions for update, update-task and update-subtask to ensure the MCP server correctly reaches for the right update command based on what is being updated -- all tasks, one task, or a subtask. 2025-04-16 00:40:32 -04:00
Ralph Khreish
48a8d952bc fix: README bug not showing precise instructions (#190) 2025-04-12 19:44:15 +02:00
16 changed files with 100 additions and 248 deletions

View File

@@ -0,0 +1,6 @@
---
'task-master-ai': patch
---
- Fixes shebang issue not allowing task-master to run on certain windows operating systems
- Resolves #241 #211 #184 #193

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
Updates the parameter descriptions for update, update-task and update-subtask to ensure the MCP server correctly reaches for the right update command based on what is being updated -- all tasks, one task, or a subtask.

View File

@@ -0,0 +1,6 @@
---
'task-master-ai': patch
---
- Fix `task-master init` polluting codebase with new packages inside `package.json` and modifying project `README`
- Now only initializes with cursor rules, windsurf rules, mcp.json, scripts/example_prd.txt, .gitignore modifications, and `README-task-master.md`

View File

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

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
Fixed a bug that prevented the task-master from running in a Linux container

View File

@@ -146,7 +146,7 @@ To enable enhanced task management capabilities directly within Cursor using the
4. Configure with the following details:
- Name: "Task Master"
- Type: "Command"
- Command: "npx -y task-master-mcp"
- Command: "npx -y task-master-ai"
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.

View File

@@ -27,7 +27,7 @@ MCP (Model Control Protocol) provides the easiest way to get started with Task M
"mcpServers": {
"taskmaster-ai": {
"command": "npx",
"args": ["-y", "task-master-mcp"],
"args": ["-y", "task-master-ai"],
"env": {
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env node --trace-deprecation
#!/usr/bin/env node
/**
* Task Master

View File

@@ -17,7 +17,7 @@ MCP (Model Control Protocol) provides the easiest way to get started with Task M
"mcpServers": {
"taskmaster-ai": {
"command": "npx",
"args": ["-y", "task-master-mcp"],
"args": ["-y", "task-master-ai"],
"env": {
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",

View File

@@ -46,22 +46,18 @@ export const initProject = async (options = {}) => {
};
// Export a function to run init as a CLI command
export const runInitCLI = async () => {
// Using spawn to ensure proper handling of stdio and process exit
const child = spawn('node', [resolve(__dirname, './scripts/init.js')], {
stdio: 'inherit',
cwd: process.cwd()
});
return new Promise((resolve, reject) => {
child.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Init script exited with code ${code}`));
}
});
});
export const runInitCLI = async (options = {}) => {
try {
const init = await import('./scripts/init.js');
const result = await init.initializeProject(options);
return result;
} catch (error) {
console.error('Initialization failed:', error.message);
if (process.env.DEBUG === 'true') {
console.error('Debug stack trace:', error.stack);
}
throw error; // Re-throw to be handled by the command handler
}
};
// Export version information
@@ -79,11 +75,21 @@ if (import.meta.url === `file://${process.argv[1]}`) {
program
.command('init')
.description('Initialize a new project')
.action(() => {
runInitCLI().catch((err) => {
.option('-y, --yes', 'Skip prompts and use default values')
.option('-n, --name <n>', 'Project name')
.option('-d, --description <description>', 'Project description')
.option('-v, --version <version>', 'Project version', '0.1.0')
.option('-a, --author <author>', 'Author name')
.option('--skip-install', 'Skip installing dependencies')
.option('--dry-run', 'Show what would be done without making changes')
.option('--aliases', 'Add shell aliases (tm, taskmaster)')
.action(async (cmdOptions) => {
try {
await runInitCLI(cmdOptions);
} catch (err) {
console.error('Init failed:', err.message);
process.exit(1);
});
}
});
program

View File

@@ -20,12 +20,12 @@ export function registerUpdateSubtaskTool(server) {
server.addTool({
name: 'update_subtask',
description:
'Appends additional information to a specific subtask without replacing existing content',
'Appends timestamped information to a specific subtask without replacing existing content',
parameters: z.object({
id: z
.string()
.describe(
'ID of the subtask to update in format "parentId.subtaskId" (e.g., "5.2")'
'ID of the subtask to update in format "parentId.subtaskId" (e.g., "5.2"). Parent ID is the ID of the task that contains the subtask.'
),
prompt: z.string().describe('Information to add to the subtask'),
research: z

View File

@@ -24,7 +24,9 @@ export function registerUpdateTaskTool(server) {
parameters: z.object({
id: z
.string()
.describe("ID of the task or subtask (e.g., '15', '15.2') to update"),
.describe(
"ID of the task (e.g., '15') to update. Subtasks are supported using the update-subtask tool."
),
prompt: z
.string()
.describe('New information or context to incorporate into the task'),

View File

@@ -20,7 +20,7 @@ export function registerUpdateTool(server) {
server.addTool({
name: 'update',
description:
"Update multiple upcoming tasks (with ID >= 'from' ID) based on new context or changes provided in the prompt. Use 'update_task' instead for a single specific task.",
"Update multiple upcoming tasks (with ID >= 'from' ID) based on new context or changes provided in the prompt. Use 'update_task' instead for a single specific task or 'update_subtask' for subtasks.",
parameters: z.object({
from: z
.string()

5
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "task-master-ai",
"version": "0.10.1",
"version": "0.11.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "task-master-ai",
"version": "0.10.1",
"version": "0.11.1",
"license": "MIT WITH Commons-Clause",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
@@ -31,6 +31,7 @@
},
"bin": {
"task-master": "bin/task-master.js",
"task-master-ai": "mcp-server/server.js",
"task-master-mcp": "mcp-server/server.js"
},
"devDependencies": {

View File

@@ -1,12 +1,13 @@
{
"name": "task-master-ai",
"version": "0.11.0",
"version": "0.11.1",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js",
"type": "module",
"bin": {
"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": {
"test": "node --experimental-vm-modules node_modules/.bin/jest",

View File

@@ -15,7 +15,6 @@
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';
@@ -179,9 +178,6 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
// Map template names to their actual source paths
switch (templateName) {
case 'dev.js':
sourcePath = path.join(__dirname, 'dev.js');
break;
case 'scripts_README.md':
sourcePath = path.join(__dirname, '..', 'assets', 'scripts_README.md');
break;
@@ -297,61 +293,8 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
return;
}
// Handle package.json - merge dependencies
if (filename === 'package.json') {
log('info', `${targetPath} already exists, merging dependencies...`);
try {
const existingPackageJson = JSON.parse(
fs.readFileSync(targetPath, 'utf8')
);
const newPackageJson = JSON.parse(content);
// Merge dependencies, preferring existing versions in case of conflicts
existingPackageJson.dependencies = {
...newPackageJson.dependencies,
...existingPackageJson.dependencies
};
// Add our scripts if they don't already exist
existingPackageJson.scripts = {
...existingPackageJson.scripts,
...Object.fromEntries(
Object.entries(newPackageJson.scripts).filter(
([key]) => !existingPackageJson.scripts[key]
)
)
};
// Preserve existing type if present
if (!existingPackageJson.type && newPackageJson.type) {
existingPackageJson.type = newPackageJson.type;
}
fs.writeFileSync(
targetPath,
JSON.stringify(existingPackageJson, null, 2)
);
log(
'success',
`Updated ${targetPath} with required dependencies and scripts`
);
} catch (error) {
log('error', `Failed to merge package.json: ${error.message}`);
// Fallback to writing a backup of the existing file and creating a new one
const backupPath = `${targetPath}.backup-${Date.now()}`;
fs.copyFileSync(targetPath, backupPath);
log('info', `Created backup of existing package.json at ${backupPath}`);
fs.writeFileSync(targetPath, content);
log(
'warn',
`Replaced ${targetPath} with new content (due to JSON parsing error)`
);
}
return;
}
// Handle README.md - offer to preserve or create a different file
if (filename === 'README.md') {
if (filename === 'README-task-master.md') {
log('info', `${targetPath} already exists`);
// Create a separate README file specifically for this project
const taskMasterReadmePath = path.join(
@@ -361,7 +304,7 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
fs.writeFileSync(taskMasterReadmePath, content);
log(
'success',
`Created ${taskMasterReadmePath} (preserved original README.md)`
`Created ${taskMasterReadmePath} (preserved original README-task-master.md)`
);
return;
}
@@ -396,6 +339,30 @@ async function initializeProject(options = {}) {
console.log('==================================================');
}
// Try to get project name from package.json if not provided
if (!options.name) {
const packageJsonPath = path.join(process.cwd(), 'package.json');
try {
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(
fs.readFileSync(packageJsonPath, 'utf8')
);
if (packageJson.name) {
log(
'info',
`Found project name '${packageJson.name}' in package.json`
);
options.name = packageJson.name;
}
}
} catch (error) {
log(
'debug',
`Could not read project name from package.json: ${error.message}`
);
}
}
// Determine if we should skip prompts based on the passed options
const skipPrompts = options.yes || (options.name && options.description);
if (!isSilentMode()) {
@@ -414,7 +381,6 @@ async function initializeProject(options = {}) {
const projectVersion = options.version || '0.1.0'; // Default from commands.js or here
const authorName = options.author || 'Vibe coder'; // Default if not provided
const dryRun = options.dryRun || false;
const skipInstall = options.skipInstall || false;
const addAliases = options.aliases || false;
if (dryRun) {
@@ -429,9 +395,6 @@ async function initializeProject(options = {}) {
if (addAliases) {
log('info', 'Would add shell aliases for task-master');
}
if (!skipInstall) {
log('info', 'Would install dependencies');
}
return {
projectName,
projectDescription,
@@ -447,7 +410,6 @@ async function initializeProject(options = {}) {
projectDescription,
projectVersion,
authorName,
skipInstall,
addAliases
);
} else {
@@ -514,9 +476,8 @@ async function initializeProject(options = {}) {
return; // Added return for clarity
}
// Still respect dryRun/skipInstall if passed initially even when prompting
// Still respect dryRun if passed initially even when prompting
const dryRun = options.dryRun || false;
const skipInstall = options.skipInstall || false;
if (dryRun) {
log('info', 'DRY RUN MODE: No files will be modified');
@@ -530,9 +491,6 @@ async function initializeProject(options = {}) {
if (addAliasesPrompted) {
log('info', 'Would add shell aliases for task-master');
}
if (!skipInstall) {
log('info', 'Would install dependencies');
}
return {
projectName,
projectDescription,
@@ -548,7 +506,6 @@ async function initializeProject(options = {}) {
projectDescription,
projectVersion,
authorName,
skipInstall, // Use value from initial options
addAliasesPrompted // Use value from prompt
);
} catch (error) {
@@ -574,7 +531,6 @@ function createProjectStructure(
projectDescription,
projectVersion,
authorName,
skipInstall,
addAliases
) {
const targetDir = process.cwd();
@@ -585,105 +541,8 @@ function createProjectStructure(
ensureDirectoryExists(path.join(targetDir, 'scripts'));
ensureDirectoryExists(path.join(targetDir, 'tasks'));
// Define our package.json content
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',
boxen: '^8.0.1',
chalk: '^4.1.2',
commander: '^11.1.0',
'cli-table3': '^0.6.5',
cors: '^2.8.5',
dotenv: '^16.3.1',
express: '^4.21.2',
fastmcp: '^1.20.5',
figlet: '^1.8.0',
'fuse.js': '^7.0.0',
'gradient-string': '^3.0.0',
helmet: '^8.1.0',
inquirer: '^12.5.0',
jsonwebtoken: '^9.0.2',
'lru-cache': '^10.2.0',
openai: '^4.89.0',
ora: '^8.2.0'
}
};
// Check if package.json exists and merge if it does
const packageJsonPath = path.join(targetDir, 'package.json');
if (fs.existsSync(packageJsonPath)) {
log('info', 'package.json already exists, merging content...');
try {
const existingPackageJson = JSON.parse(
fs.readFileSync(packageJsonPath, 'utf8')
);
// Preserve existing fields but add our required ones
const mergedPackageJson = {
...existingPackageJson,
scripts: {
...existingPackageJson.scripts,
...Object.fromEntries(
Object.entries(packageJson.scripts).filter(
([key]) =>
!existingPackageJson.scripts ||
!existingPackageJson.scripts[key]
)
)
},
dependencies: {
...(existingPackageJson.dependencies || {}),
...Object.fromEntries(
Object.entries(packageJson.dependencies).filter(
([key]) =>
!existingPackageJson.dependencies ||
!existingPackageJson.dependencies[key]
)
)
}
};
// Ensure type is set if not already present
if (!mergedPackageJson.type && packageJson.type) {
mergedPackageJson.type = packageJson.type;
}
fs.writeFileSync(
packageJsonPath,
JSON.stringify(mergedPackageJson, null, 2)
);
log('success', 'Updated package.json with required fields');
} catch (error) {
log('error', `Failed to merge package.json: ${error.message}`);
// Create a backup before potentially modifying
const backupPath = `${packageJsonPath}.backup-${Date.now()}`;
fs.copyFileSync(packageJsonPath, backupPath);
log('info', `Created backup of existing package.json at ${backupPath}`);
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
log(
'warn',
'Created new package.json (backup of original file was created)'
);
}
} else {
// If package.json doesn't exist, create it
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
log('success', 'Created package.json');
}
// Setup MCP configuration for integration with Cursor
setupMCPConfiguration(targetDir, packageJson.name);
setupMCPConfiguration(targetDir, projectName);
// Copy template files with replacements
const replacements = {
@@ -731,15 +590,6 @@ function createProjectStructure(
// Copy .windsurfrules
copyTemplateFile('windsurfrules', path.join(targetDir, '.windsurfrules'));
// 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',
@@ -749,43 +599,13 @@ function createProjectStructure(
// Create main README.md
copyTemplateFile(
'README-task-master.md',
path.join(targetDir, 'README.md'),
path.join(targetDir, 'README-task-master.md'),
replacements
);
// Initialize git repository if git is available
try {
if (!fs.existsSync(path.join(targetDir, '.git'))) {
log('info', 'Initializing git repository...');
execSync('git init', { stdio: 'ignore' });
log('success', 'Git repository initialized');
}
} catch (error) {
log('warn', 'Git not available, skipping repository initialization');
}
// Run npm install automatically
if (!isSilentMode()) {
console.log(
boxen(chalk.cyan('Installing dependencies...'), {
padding: 0.5,
margin: 0.5,
borderStyle: 'round',
borderColor: 'blue'
})
);
}
try {
if (!skipInstall) {
execSync('npm install', { stdio: 'inherit', cwd: targetDir });
log('success', 'Dependencies installed successfully!');
} else {
log('info', 'Dependencies installation skipped');
}
} catch (error) {
log('error', 'Failed to install dependencies:', error.message);
log('error', 'Please run npm install manually');
// Add shell aliases if requested
if (addAliases) {
addShellAliases();
}
// Display success message
@@ -807,11 +627,6 @@ function createProjectStructure(
);
}
// Add shell aliases if requested
if (addAliases) {
addShellAliases();
}
// Display next steps in a nice box
if (!isSilentMode()) {
console.log(