Compare commits

...

5 Commits

Author SHA1 Message Date
Ralph Khreish
fbe859f886 chore: add changeset 2025-04-18 23:38:14 +02:00
Ralph Khreish
45cc0cf527 fix: remove the need for projectName, description, version in mcp and cli 2025-04-18 23:33:22 +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
14 changed files with 63 additions and 2996 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,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
---
Remove the need for project name, description, and version. Since we no longer create a package.json for you

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

@@ -20,20 +20,14 @@ 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.
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):
1. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
```json
{
"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

@@ -10,20 +10,14 @@ 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.
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):
1. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
```json
{
"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

@@ -10,7 +10,7 @@ import os from 'os'; // Import os module for home directory check
/**
* Direct function wrapper for initializing a project.
* Derives target directory from session, sets CWD, and calls core init logic.
* @param {object} args - Arguments containing project details and options (projectName, projectDescription, yes, etc.)
* @param {object} args - Arguments containing initialization options (addAliases, skipInstall, yes, projectRoot)
* @param {object} log - The FastMCP logger instance.
* @param {object} context - The context object, must contain { session }.
* @returns {Promise<{success: boolean, data?: any, error?: {code: string, message: string}}>} - Standard result object.
@@ -92,12 +92,8 @@ export async function initializeProjectDirect(args, log, context = {}) {
try {
// Always force yes: true when called via MCP to avoid interactive prompts
const options = {
name: args.projectName,
description: args.projectDescription,
version: args.projectVersion,
author: args.authorName,
skipInstall: args.skipInstall,
aliases: args.addAliases,
skipInstall: args.skipInstall,
yes: true // Force yes mode
};

View File

@@ -10,32 +10,8 @@ export function registerInitializeProjectTool(server) {
server.addTool({
name: 'initialize_project',
description:
"Initializes a new Task Master project structure by calling the core initialization logic. Derives target directory from client session. If project details (name, description, author) are not provided, prompts the user or skips if 'yes' flag is true. DO NOT run without parameters.",
'Initializes a new Task Master project structure by calling the core initialization logic. Creates necessary folders and configuration files for Task Master in the current directory.',
parameters: z.object({
projectName: z
.string()
.optional()
.describe(
'The name for the new project. If not provided, prompt the user for it.'
),
projectDescription: z
.string()
.optional()
.describe(
'A brief description for the project. If not provided, prompt the user for it.'
),
projectVersion: z
.string()
.optional()
.describe(
"The initial version for the project (e.g., '0.1.0'). User input not needed unless user requests to override."
),
authorName: z
.string()
.optional()
.describe(
"The author's name. User input not needed unless user requests to override."
),
skipInstall: z
.boolean()
.optional()
@@ -47,15 +23,13 @@ export function registerInitializeProjectTool(server) {
.boolean()
.optional()
.default(false)
.describe(
'Add shell aliases (tm, taskmaster) to shell config file. User input not needed.'
),
.describe('Add shell aliases (tm, taskmaster) to shell config file.'),
yes: z
.boolean()
.optional()
.default(false)
.default(true)
.describe(
"Skip prompts and use default values or provided arguments. Use true if you wish to skip details like the project name, etc. If the project information required for the initialization is not available or provided by the user, prompt if the user wishes to provide them (name, description, author) or skip them. If the user wishes to skip, set the 'yes' flag to true and do not set any other parameters."
'Skip prompts and use default values. Always set to true for MCP tools.'
),
projectRoot: 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;
}
@@ -392,12 +335,11 @@ async function initializeProject(options = {}) {
console.log('===== DEBUG: INITIALIZE PROJECT OPTIONS RECEIVED =====');
console.log('Full options object:', JSON.stringify(options));
console.log('options.yes:', options.yes);
console.log('options.name:', options.name);
console.log('==================================================');
}
// Determine if we should skip prompts based on the passed options
const skipPrompts = options.yes || (options.name && options.description);
const skipPrompts = options.yes;
if (!isSilentMode()) {
console.log('Skip prompts determined:', skipPrompts);
}
@@ -407,49 +349,24 @@ async function initializeProject(options = {}) {
console.log('SKIPPING PROMPTS - Using defaults or provided values');
}
// Use provided options or defaults
const projectName = options.name || 'task-master-project';
const projectDescription =
options.description || 'A project managed with Task Master AI';
const projectVersion = options.version || '0.1.0'; // Default from commands.js or here
const authorName = options.author || 'Vibe coder'; // Default if not provided
// We no longer need these variables
const dryRun = options.dryRun || false;
const skipInstall = options.skipInstall || false;
const addAliases = options.aliases || false;
if (dryRun) {
log('info', 'DRY RUN MODE: No files will be modified');
log(
'info',
`Would initialize project: ${projectName} (${projectVersion})`
);
log('info', `Description: ${projectDescription}`);
log('info', `Author: ${authorName || 'Not specified'}`);
log('info', 'Would initialize Task Master project');
log('info', 'Would create/update necessary project files');
if (addAliases) {
log('info', 'Would add shell aliases for task-master');
}
if (!skipInstall) {
log('info', 'Would install dependencies');
}
return {
projectName,
projectDescription,
projectVersion,
authorName,
dryRun: true
};
}
// Create structure using determined values
createProjectStructure(
projectName,
projectDescription,
projectVersion,
authorName,
skipInstall,
addAliases
);
// Create structure using only necessary values
createProjectStructure(addAliases);
} else {
// Prompting logic (only runs if skipPrompts is false)
log('info', 'Required options not provided, proceeding with prompts.');
@@ -459,41 +376,17 @@ async function initializeProject(options = {}) {
});
try {
// Prompt user for input...
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): ')
); // Use a default for prompt
const authorName = await promptQuestion(
rl,
chalk.cyan('Enter your name: ')
);
// Only prompt for shell aliases
const addAliasesInput = await promptQuestion(
rl,
chalk.cyan('Add shell aliases for task-master? (Y/n): ')
chalk.cyan(
'Add shell aliases for task-master? This lets you type "tm" instead of "task-master" (Y/n): '
)
);
const addAliasesPrompted = addAliasesInput.trim().toLowerCase() !== 'n';
const projectVersion = projectVersionInput.trim()
? projectVersionInput
: '1.0.0';
// Confirm settings...
console.log('\nProject settings:');
console.log(chalk.blue('Name:'), chalk.white(projectName));
console.log(chalk.blue('Description:'), chalk.white(projectDescription));
console.log(chalk.blue('Version:'), chalk.white(projectVersion));
console.log(
chalk.blue('Author:'),
chalk.white(authorName || 'Not specified')
);
console.log('\nTask Master Project settings:');
console.log(
chalk.blue(
'Add shell aliases (so you can use "tm" instead of "task-master"):'
@@ -514,43 +407,23 @@ 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');
log(
'info',
`Would initialize project: ${projectName} (${projectVersion})`
);
log('info', `Description: ${projectDescription}`);
log('info', `Author: ${authorName || 'Not specified'}`);
log('info', 'Would initialize Task Master project');
log('info', 'Would create/update necessary project files');
if (addAliasesPrompted) {
log('info', 'Would add shell aliases for task-master');
}
if (!skipInstall) {
log('info', 'Would install dependencies');
}
return {
projectName,
projectDescription,
projectVersion,
authorName,
dryRun: true
};
}
// Create structure using prompted values, respecting initial options where relevant
createProjectStructure(
projectName,
projectDescription,
projectVersion,
authorName,
skipInstall, // Use value from initial options
addAliasesPrompted // Use value from prompt
);
// Create structure using only necessary values
createProjectStructure(addAliasesPrompted);
} catch (error) {
rl.close();
log('error', `Error during prompting: ${error.message}`); // Use log function
@@ -569,14 +442,7 @@ function promptQuestion(rl, question) {
}
// Function to create the project structure
function createProjectStructure(
projectName,
projectDescription,
projectVersion,
authorName,
skipInstall,
addAliases
) {
function createProjectStructure(addAliases) {
const targetDir = process.cwd();
log('info', `Initializing project in ${targetDir}`);
@@ -585,112 +451,11 @@ 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);
// Copy template files with replacements
const replacements = {
projectName,
projectDescription,
projectVersion,
authorName,
year: new Date().getFullYear()
};
@@ -731,15 +496,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 +505,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 +533,6 @@ function createProjectStructure(
);
}
// Add shell aliases if requested
if (addAliases) {
addShellAliases();
}
// Display next steps in a nice box
if (!isSilentMode()) {
console.log(
@@ -880,7 +601,7 @@ function createProjectStructure(
}
// Function to setup MCP configuration for Cursor integration
function setupMCPConfiguration(targetDir, projectName) {
function setupMCPConfiguration(targetDir) {
const mcpDirPath = path.join(targetDir, '.cursor');
const mcpJsonPath = path.join(mcpDirPath, 'mcp.json');

File diff suppressed because one or more lines are too long