UNPKG

@gonzui/claude-task-manager

Version:

Task management extension for Claude Code with archiving and history

250 lines 10.9 kB
"use strict"; 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