taskqueue-mcp
by chriscarrollsmith
Verified
- taskqueue-mcp
- src
- client
#!/usr/bin/env node
import { Command } from "commander";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as os from "node:os";
import chalk from "chalk";
import { TaskManagerFile, ErrorCode } from "../types/index.js";
import { createError, normalizeError } from "../utils/errors.js";
import { formatCliError } from "./errors.js";
const program = new Command();
const DEFAULT_PATH = path.join(os.homedir(), "Documents", "tasks.json");
const TASK_FILE_PATH = process.env.TASK_MANAGER_FILE_PATH || DEFAULT_PATH;
/**
* Reads task data from the JSON file
* @returns {Promise<TaskManagerFile>} The task manager data
*/
async function readData(): Promise<TaskManagerFile> {
try {
console.log(chalk.blue(`Reading task data from: ${TASK_FILE_PATH}`));
try {
await fs.access(TASK_FILE_PATH);
} catch (error) {
console.warn(chalk.yellow(`Task file does not exist yet. Will create a new one.`));
return { projects: [] };
}
const data = await fs.readFile(TASK_FILE_PATH, "utf-8");
try {
return JSON.parse(data);
} catch (error) {
throw createError(
ErrorCode.FileParseError,
"Failed to parse task file",
{ originalError: error }
);
}
} catch (error) {
if (error instanceof Error && error.message.includes("ENOENT")) {
return { projects: [] };
}
throw createError(
ErrorCode.FileReadError,
"Failed to read task file",
{ originalError: error }
);
}
}
/**
* Writes task data to the JSON file
* @param {TaskManagerFile} data The task manager data to write
* @returns {Promise<void>}
*/
async function writeData(data: TaskManagerFile): Promise<void> {
try {
console.log(chalk.blue(`Writing task data to: ${TASK_FILE_PATH}`));
// Ensure the directory exists
const directory = path.dirname(TASK_FILE_PATH);
await fs.mkdir(directory, { recursive: true });
await fs.writeFile(TASK_FILE_PATH, JSON.stringify(data, null, 2), "utf-8");
console.log(chalk.green('Data saved successfully'));
} catch (error) {
throw createError(
ErrorCode.FileWriteError,
"Failed to write task file",
{ originalError: error }
);
}
}
program
.name("task-manager-cli")
.description("CLI for the Task Manager MCP Server")
.version("1.0.0");
program
.command("approve")
.description("Approve a completed task")
.argument("<projectId>", "Project ID")
.argument("<taskId>", "Task ID")
.option('-f, --force', 'Force approval even if task is not marked as done')
.action(async (projectId, taskId, options) => {
try {
console.log(chalk.blue(`Approving task ${chalk.bold(taskId)} in project ${chalk.bold(projectId)}...`));
const data = await readData();
// Check if we have any projects
if (data.projects.length === 0) {
console.error(chalk.red(`No projects found. The task file is empty or just initialized.`));
process.exit(1);
}
const project = data.projects.find(p => p.projectId === projectId);
if (!project) {
console.error(chalk.red(`Project ${chalk.bold(projectId)} not found.`));
console.log(chalk.yellow('Available projects:'));
data.projects.forEach(p => {
console.log(` - ${p.projectId}: ${p.initialPrompt.substring(0, 50)}${p.initialPrompt.length > 50 ? '...' : ''}`);
});
process.exit(1);
}
const task = project.tasks.find(t => t.id === taskId);
if (!task) {
console.error(chalk.red(`Task ${chalk.bold(taskId)} not found in project ${chalk.bold(projectId)}.`));
console.log(chalk.yellow('Available tasks in this project:'));
project.tasks.forEach(t => {
console.log(` - ${t.id}: ${t.title} (Status: ${t.status}, Approved: ${t.approved ? 'Yes' : 'No'})`);
});
process.exit(1);
}
if (task.status !== "done" && !options.force) {
console.error(chalk.red(`Task ${chalk.bold(taskId)} is not marked as done yet. Current status: ${chalk.bold(task.status)}`));
console.log(chalk.yellow(`Use the --force flag to approve anyway, or wait for the task to be marked as done.`));
process.exit(1);
}
if (task.approved) {
console.log(chalk.yellow(`Task ${chalk.bold(taskId)} is already approved.`));
process.exit(0);
}
task.approved = true;
await writeData(data);
console.log(chalk.green(`✅ Task ${chalk.bold(taskId)} in project ${chalk.bold(projectId)} has been approved.`));
// Show task info
console.log(chalk.cyan('\n📋 Task details:'));
console.log(` - ${chalk.bold('Title:')} ${task.title}`);
console.log(` - ${chalk.bold('Description:')} ${task.description}`);
console.log(` - ${chalk.bold('Status:')} ${task.status === 'done' ? chalk.green('Done ✓') : task.status === 'in progress' ? chalk.yellow('In Progress ⟳') : chalk.blue('Not Started ○')}`);
console.log(` - ${chalk.bold('Completed details:')} ${task.completedDetails || chalk.gray("None")}`);
console.log(` - ${chalk.bold('Approved:')} ${task.approved ? chalk.green('Yes ✓') : chalk.red('No ✗')}`);
// Show progress info
const totalTasks = project.tasks.length;
const completedTasks = project.tasks.filter(t => t.status === "done").length;
const approvedTasks = project.tasks.filter(t => t.approved).length;
console.log(chalk.cyan(`\n📊 Progress: ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`));
// Create a progress bar
const bar = '▓'.repeat(approvedTasks) + '▒'.repeat(completedTasks - approvedTasks) + '░'.repeat(totalTasks - completedTasks);
console.log(` ${bar}`);
if (completedTasks === totalTasks && approvedTasks === totalTasks) {
console.log(chalk.green('\n🎉 All tasks are completed and approved!'));
console.log(chalk.blue('The project can now be finalized.'));
} else {
console.log(chalk.yellow(`\n${totalTasks - completedTasks} tasks remaining to be completed.`));
console.log(chalk.yellow(`${completedTasks - approvedTasks} tasks remaining to be approved.`));
}
} catch (error) {
console.error(chalk.red(formatCliError(normalizeError(error))));
process.exit(1);
}
});
program
.command("finalize")
.description("Mark a project as complete")
.argument("<projectId>", "Project ID")
.action(async (projectId) => {
try {
console.log(chalk.blue(`Approving project ${chalk.bold(projectId)}...`));
const data = await readData();
// Check if we have any projects
if (data.projects.length === 0) {
console.error(chalk.red(`No projects found. The task file is empty or just initialized.`));
process.exit(1);
}
const project = data.projects.find(p => p.projectId === projectId);
if (!project) {
console.error(chalk.red(`Project ${chalk.bold(projectId)} not found.`));
console.log(chalk.yellow('Available projects:'));
data.projects.forEach(p => {
console.log(` - ${p.projectId}: ${p.initialPrompt.substring(0, 50)}${p.initialPrompt.length > 50 ? '...' : ''}`);
});
process.exit(1);
}
// Check if all tasks are done & approved
const allDone = project.tasks.every(t => t.status === "done");
if (!allDone) {
console.error(chalk.red(`Not all tasks in project ${chalk.bold(projectId)} are marked as done.`));
console.log(chalk.yellow('\nPending tasks:'));
project.tasks.filter(t => t.status !== "done").forEach(t => {
console.log(` - ${chalk.bold(t.id)}: ${t.title} (Status: ${t.status})`);
});
process.exit(1);
}
const allApproved = project.tasks.every(t => t.approved);
if (!allApproved) {
console.error(chalk.red(`Not all tasks in project ${chalk.bold(projectId)} are approved yet.`));
console.log(chalk.yellow('\nUnapproved tasks:'));
project.tasks.filter(t => !t.approved).forEach(t => {
console.log(` - ${chalk.bold(t.id)}: ${t.title}`);
});
process.exit(1);
}
if (project.completed) {
console.log(chalk.yellow(`Project ${chalk.bold(projectId)} is already approved and completed.`));
process.exit(0);
}
project.completed = true;
await writeData(data);
console.log(chalk.green(`✅ Project ${chalk.bold(projectId)} has been approved and marked as complete.`));
// Show project info
console.log(chalk.cyan('\n📋 Project details:'));
console.log(` - ${chalk.bold('Initial Prompt:')} ${project.initialPrompt}`);
if (project.projectPlan && project.projectPlan !== project.initialPrompt) {
console.log(` - ${chalk.bold('Project Plan:')} ${project.projectPlan}`);
}
console.log(` - ${chalk.bold('Status:')} ${chalk.green('Completed ✓')}`);
// Show progress info
const totalTasks = project.tasks.length;
const completedTasks = project.tasks.filter(t => t.status === "done").length;
const approvedTasks = project.tasks.filter(t => t.approved).length;
console.log(chalk.cyan(`\n📊 Final Progress: ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`));
// Create a progress bar
const bar = '▓'.repeat(approvedTasks) + '▒'.repeat(completedTasks - approvedTasks) + '░'.repeat(totalTasks - completedTasks);
console.log(` ${bar}`);
console.log(chalk.green('\n🎉 Project successfully completed and approved!'));
console.log(chalk.gray('You can view the project details anytime using:'));
console.log(chalk.blue(` task-manager-cli list -p ${projectId}`));
} catch (error) {
console.error(chalk.red(formatCliError(normalizeError(error))));
process.exit(1);
}
});
program
.command("list")
.description("List all projects and their tasks")
.option('-p, --project <projectId>', 'Show details for a specific project')
.option('-s, --state <state>', "Filter by task/project state (open, pending_approval, completed, all)")
.action(async (options) => {
try {
// Validate state option if provided
if (options.state && !['open', 'pending_approval', 'completed', 'all'].includes(options.state)) {
console.error(chalk.red(`Invalid state value: ${options.state}`));
console.log(chalk.yellow('Valid states are: open, pending_approval, completed, all'));
process.exit(1);
}
const data = await readData();
if (data.projects.length === 0) {
console.log(chalk.yellow('No projects found.'));
return;
}
if (options.project) {
// Show details for a specific project
const project = data.projects.find(p => p.projectId === options.project);
if (!project) {
console.error(chalk.red(`Project ${chalk.bold(options.project)} not found.`));
console.log(chalk.yellow('Available projects:'));
data.projects.forEach(p => {
console.log(` - ${p.projectId}: ${p.initialPrompt.substring(0, 50)}${p.initialPrompt.length > 50 ? '...' : ''}`);
});
process.exit(1);
}
// Filter tasks by state if specified
let tasks = project.tasks;
if (options.state && options.state !== "all") {
tasks = project.tasks.filter(task => {
switch (options.state) {
case "open":
return task.status !== "done";
case "pending_approval":
return task.status === "done" && !task.approved;
case "completed":
return task.status === "done" && task.approved;
default:
return true;
}
});
}
console.log(chalk.cyan(`\n📋 Project ${chalk.bold(options.project)} details:`));
console.log(` - ${chalk.bold('Initial Prompt:')} ${project.initialPrompt}`);
if (project.projectPlan && project.projectPlan !== project.initialPrompt) {
console.log(` - ${chalk.bold('Project Plan:')} ${project.projectPlan}`);
}
console.log(` - ${chalk.bold('Status:')} ${project.completed ? chalk.green('Completed ✓') : chalk.yellow('In Progress')}`);
// Show progress info
const totalTasks = project.tasks.length;
const completedTasks = project.tasks.filter(t => t.status === "done").length;
const approvedTasks = project.tasks.filter(t => t.approved).length;
console.log(chalk.cyan(`\n📊 Progress: ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`));
// Create a progress bar
const bar = '▓'.repeat(approvedTasks) + '▒'.repeat(completedTasks - approvedTasks) + '░'.repeat(totalTasks - completedTasks);
console.log(` ${bar}`);
if (tasks.length > 0) {
console.log(chalk.cyan('\n📝 Tasks:'));
tasks.forEach(t => {
const status = t.status === 'done' ? chalk.green('Done ✓') : t.status === 'in progress' ? chalk.yellow('In Progress ⟳') : chalk.blue('Not Started ○');
const approved = t.approved ? chalk.green('Yes ✓') : chalk.red('No ✗');
console.log(` - ${chalk.bold(t.id)}: ${t.title}`);
console.log(` Status: ${status}, Approved: ${approved}`);
console.log(` Description: ${t.description}`);
if (t.completedDetails) {
console.log(` Completed Details: ${t.completedDetails}`);
}
if (t.toolRecommendations) {
console.log(` Tool Recommendations: ${t.toolRecommendations}`);
}
if (t.ruleRecommendations) {
console.log(` Rule Recommendations: ${t.ruleRecommendations}`);
}
});
} else {
console.log(chalk.yellow('\nNo tasks match the specified state filter.'));
}
} else {
// List all projects
let projectsToList = data.projects;
if (options.state && options.state !== "all") {
projectsToList = data.projects.filter(project => {
switch (options.state) {
case "open":
return !project.completed && project.tasks.some(task => task.status !== "done");
case "pending_approval":
return project.tasks.some(task => task.status === "done" && !task.approved);
case "completed":
return project.completed && project.tasks.every(task => task.status === "done" && task.approved);
default:
return true;
}
});
}
if (projectsToList.length === 0) {
console.log(chalk.yellow('No projects match the specified state filter.'));
return;
}
console.log(chalk.cyan('\n📋 Projects List:'));
projectsToList.forEach(p => {
const totalTasks = p.tasks.length;
const completedTasks = p.tasks.filter(t => t.status === "done").length;
const approvedTasks = p.tasks.filter(t => t.approved).length;
const status = p.completed ? chalk.green('Completed ✓') : chalk.yellow('In Progress');
console.log(`\n${chalk.bold(p.projectId)}: ${status}`);
console.log(` Initial Prompt: ${p.initialPrompt.substring(0, 100)}${p.initialPrompt.length > 100 ? '...' : ''}`);
console.log(` Progress: ${chalk.bold(`${approvedTasks}/${completedTasks}/${totalTasks}`)} (approved/completed/total)`);
// Create a progress bar
const bar = '▓'.repeat(approvedTasks) + '▒'.repeat(completedTasks - approvedTasks) + '░'.repeat(totalTasks - completedTasks);
console.log(` ${bar}`);
});
}
} catch (error) {
console.error(chalk.red(formatCliError(normalizeError(error))));
process.exit(1);
}
});
program.parse(process.argv);