@gonzui/claude-task-manager
Version:
Task management extension for Claude Code with archiving and history
250 lines • 10.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMcpServer = createMcpServer;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const zod_1 = require("zod");
function textResult(text) {
return { content: [{ type: 'text', text }] };
}
function errorResult(error) {
const message = error instanceof Error ? error.message : String(error);
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
}
function readPackageVersion() {
try {
const pkg = fs.readJsonSync(path.join(__dirname, '..', '..', 'package.json'));
return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
}
catch {
return '0.0.0';
}
}
/**
* Build the MCP server that exposes TaskManager operations as schema-bound
* tools. All operations route through the given TaskManager so the CLI and
* MCP front-ends stay consistent.
*/
function createMcpServer(taskManager) {
const server = new mcp_js_1.McpServer({
name: 'claude-task',
version: readPackageVersion()
});
server.registerTool('task_new', {
title: 'Create a new task',
description: 'Create a new task in task.md. Without "name" the current task (if any) is archived and replaced. ' +
'With "name" the task is created alongside the current one and becomes active — nothing is archived.',
inputSchema: {
title: zod_1.z.string().describe('Task title'),
description: zod_1.z.string().optional().describe('Task description'),
priority: zod_1.z.enum(['low', 'medium', 'high']).optional().describe('Task priority'),
tags: zod_1.z.array(zod_1.z.string()).optional().describe('Tags for the task'),
name: zod_1.z
.string()
.optional()
.describe('Short handle to store the task under and switch to (no archiving of the current task)')
}
}, async ({ title, description, priority, tags, name }) => {
try {
const filePath = await taskManager.createNewTask({ title, description, priority, tags, name });
const activeName = await taskManager.getActiveTaskName();
const suffix = activeName ? ` (active task: ${activeName})` : '';
return textResult(`Created task "${title}" at ${filePath}${suffix}`);
}
catch (error) {
return errorResult(error);
}
});
server.registerTool('task_status', {
title: 'Show task status',
description: 'Show the current task, archived task count, and execution stats.',
inputSchema: {}
}, async () => {
try {
const status = await taskManager.getStatus();
return textResult(JSON.stringify(status, null, 2));
}
catch (error) {
return errorResult(error);
}
});
server.registerTool('task_progress', {
title: 'Show subtask progress',
description: 'Show subtask checkbox progress for the current task: totals, percentage, and each subtask with its 1-based number. ' +
'Use this to seed or re-sync an in-session todo list mirroring the task — task.md is the persistent source of truth.',
inputSchema: {}
}, async () => {
try {
const progress = await taskManager.getProgress();
const lines = progress.tasks.map((task, index) => `${index + 1}. [${task.completed ? 'x' : ' '}] ${task.text}`);
return textResult([
`Task: ${progress.title}`,
`Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`,
...lines
].join('\n'));
}
catch (error) {
return errorResult(error);
}
});
server.registerTool('task_done', {
title: 'Mark subtasks done',
description: 'Mark subtask checkbox(es) in task.md as done (or uncheck with undo). Numbers are 1-based and match task_progress order. ' +
'Call this before checking off any in-session todo that mirrors the subtask, so the persistent state is updated first.',
inputSchema: {
numbers: zod_1.z.array(zod_1.z.number().int().min(1)).min(1).describe('1-based subtask numbers'),
undo: zod_1.z.boolean().optional().describe('Uncheck instead of checking')
}
}, async ({ numbers, undo }) => {
try {
const { updated, invalid, result } = await taskManager.setTaskCompletion(numbers, !undo);
const parts = [];
if (updated.length > 0) {
parts.push(`${undo ? 'Unchecked' : 'Completed'} subtask(s): ${updated.join(', ')}`);
}
if (invalid.length > 0) {
parts.push(`No subtask at number(s): ${invalid.join(', ')}`);
}
parts.push(`Progress: ${result.completed}/${result.total} (${result.percentage}%)`);
return textResult(parts.join('\n'));
}
catch (error) {
return errorResult(error);
}
});
server.registerTool('task_split', {
title: 'Split task into subtasks',
description: 'Split the current task into subtask checkboxes using AI. Spawns a claude CLI call in the background, so it may take a while.',
inputSchema: {
count: zod_1.z.number().int().min(2).max(20).optional().describe('Desired number of subtasks')
}
}, async ({ count }) => {
try {
const result = await taskManager.splitTask(count);
if (!result.success) {
return errorResult(result.error || 'Failed to split task');
}
return textResult(`Split task into ${result.subtasks.length} subtasks:\n` +
result.subtasks.map((subtask, index) => `${index + 1}. ${subtask}`).join('\n'));
}
catch (error) {
return errorResult(error);
}
});
server.registerTool('task_history', {
title: 'Show task history',
description: 'List archived tasks, newest first.',
inputSchema: {
limit: zod_1.z.number().int().min(1).optional().describe('Maximum entries to return (default 10)')
}
}, async ({ limit }) => {
try {
const history = await taskManager.getHistory(limit ?? 10);
if (history.length === 0) {
return textResult('No archived tasks found.');
}
return textResult(history.map((item) => `- ${item.date}: ${item.title}`).join('\n'));
}
catch (error) {
return errorResult(error);
}
});
server.registerTool('task_archive', {
title: 'Archive current task',
description: 'Move the current task.md into the archive folder with a timestamp.',
inputSchema: {}
}, async () => {
try {
const archivedPath = await taskManager.archiveCurrentTask();
if (!archivedPath) {
return textResult('No task to archive.');
}
return textResult(`Task archived: ${archivedPath}`);
}
catch (error) {
return errorResult(error);
}
});
server.registerTool('task_switch', {
title: 'Switch to another task',
description: 'Make the named task the active one (task.md). The current task is saved first, so its subtask state is preserved. ' +
'Use task_list to see available names. Set "create" to start a new task under that name instead of failing.',
inputSchema: {
name: zod_1.z.string().describe('Name of the task to switch to'),
create: zod_1.z.boolean().optional().describe('Create the task if no task with that name exists')
}
}, async ({ name, create }) => {
try {
const result = await taskManager.switchTask(name, { create });
if (result.created) {
return textResult(`Created and switched to task "${result.name}".`);
}
if (result.previous === result.name) {
return textResult(`Already on task "${result.name}".`);
}
return textResult(`Switched to task "${result.name}"${result.previous ? ` (was "${result.previous}")` : ''}.`);
}
catch (error) {
return errorResult(error);
}
});
server.registerTool('task_list', {
title: 'List tasks',
description: 'List every task with its name, title, and subtask progress. The active one is marked — its name is what task_switch expects.',
inputSchema: {}
}, async () => {
try {
const tasks = await taskManager.listTasks();
if (tasks.length === 0) {
return textResult('No tasks found.');
}
const lines = tasks.map((task) => {
const marker = task.active ? '*' : ' ';
const progress = task.total > 0
? ` — ${task.completed}/${task.total} (${task.percentage}%)`
: '';
return `${marker} ${task.name}: ${task.title}${progress}`;
});
return textResult(lines.join('\n'));
}
catch (error) {
return errorResult(error);
}
});
return server;
}
//# sourceMappingURL=McpServer.js.map