UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

280 lines 10.6 kB
import { BaseTool } from '../base.js'; import { v4 as uuidv4 } from 'uuid'; const todoStorage = new Map(); export const todoEvents = new EventTarget(); export function getTodosForContext(contextId) { return todoStorage.get(contextId) || []; } export function updateTodoByDescription(contextId, description, updates) { const todos = todoStorage.get(contextId); if (!todos) return false; const todo = todos.find(t => t.description.toLowerCase().includes(description.toLowerCase())); if (!todo) return false; if (updates.status !== undefined) todo.status = updates.status; if (updates.metadata !== undefined) { todo.metadata = { ...todo.metadata, ...updates.metadata }; } todo.updatedAt = new Date(); todoEvents.dispatchEvent(new CustomEvent('todo:updated', { detail: { contextId, todo, previousStatus: todo.status } })); return true; } export class TodoListTool extends BaseTool { name = 'todo_list'; displayName = '📝 Todo'; description = 'Your external brain for complex tasks. Use to track multi-step work and maintain focus. Prevents repetition and confusion.'; category = 'productivity'; icon = '📝'; parameters = [ { name: 'action', type: 'string', description: 'Action to perform: "add" (new task), "list" (view tasks), "update" (change task), "remove" (delete task), "clear" (remove all/completed)', required: true, enum: ['add', 'update', 'remove', 'list', 'clear'] }, { name: 'description', type: 'string', description: 'Todo item description (for add)', required: false }, { name: 'status', type: 'string', description: 'Todo status', required: false, enum: ['pending', 'in_progress', 'completed', 'blocked', 'cancelled'] }, { name: 'priority', type: 'string', description: 'Todo priority', required: false, enum: ['high', 'medium', 'low'] }, { name: 'id', type: 'string', description: 'Todo item ID (for update/remove)', required: false }, { name: 'filterStatus', type: 'string', description: 'Filter by status (for list)', required: false, enum: ['pending', 'in_progress', 'completed', 'blocked', 'cancelled'] } ]; permissions = {}; ui = { showProgress: false, collapsible: true, dangerous: false }; async run(params, context) { const contextId = context.workingDirectory || 'global'; const { action, description, status, priority, id, filterStatus } = params; if (!action) { throw new Error(`Missing required parameter: action. Example usage: {"action": "list"}`); } if (!todoStorage.has(contextId)) { todoStorage.set(contextId, []); } const todos = todoStorage.get(contextId); switch (action) { case 'add': return this.addTodo(todos, { description, status, priority }, contextId); case 'update': return this.updateTodo(todos, id, { description, status, priority }, context); case 'remove': return this.removeTodo(todos, id); case 'list': return this.listTodos(todos, filterStatus); case 'clear': return this.clearTodos(todos, filterStatus); default: throw new Error(`Unknown action: ${action}. Valid actions are: add, list, update, remove, clear`); } } addTodo(todos, item, contextId) { if (!item?.description) { throw new Error('Todo description is required when adding a task. Example: {"action": "add", "description": "Fix the bug"}'); } const newTodo = { id: uuidv4().substring(0, 8), description: item.description, status: item.status || 'pending', priority: item.priority || 'medium', createdAt: new Date(), updatedAt: new Date(), dependencies: item.dependencies, metadata: item.metadata }; todos.push(newTodo); todoEvents.dispatchEvent(new CustomEvent('todo:added', { detail: { contextId, todo: newTodo } })); return { id: newTodo.id, description: newTodo.description, status: newTodo.status, priority: newTodo.priority, total: todos.length, display: this.createCompactDisplay(todos) }; } updateTodo(todos, id, updates, context) { if (!id) { throw new Error('Todo id is required for update. First use {"action": "list"} to see task IDs, then {"action": "update", "id": "<id>", "status": "completed"}'); } const index = todos.findIndex(t => t.id === id); if (index === -1) { throw new Error(`Todo with id ${id} not found. Use {"action": "list"} to see available task IDs.`); } const todo = todos[index]; const previousStatus = todo.status; if (updates.description !== undefined) todo.description = updates.description; if (updates.status !== undefined) todo.status = updates.status; if (updates.priority !== undefined) todo.priority = updates.priority; if (updates.dependencies !== undefined) todo.dependencies = updates.dependencies; if (updates.metadata !== undefined) { todo.metadata = { ...todo.metadata, ...updates.metadata }; } todo.updatedAt = new Date(); if (previousStatus !== 'completed' && todo.status === 'completed' && todo.metadata) { todo.metadata.actualTime = Date.now() - todo.createdAt.getTime(); } const display = this.createCompactDisplay(todos.slice()); const contextId = context?.workingDirectory || 'global'; todoEvents.dispatchEvent(new CustomEvent('todo:updated', { detail: { contextId, todo, previousStatus } })); return { id: todo.id, description: todo.description, status: todo.status, priority: todo.priority, previousStatus, display: display }; } removeTodo(todos, id) { if (!id) { throw new Error('Todo id is required for remove. First use {"action": "list"} to see task IDs, then {"action": "remove", "id": "<id>"}'); } const index = todos.findIndex(t => t.id === id); if (index === -1) { throw new Error(`Todo with id ${id} not found. Use {"action": "list"} to see available task IDs.`); } const removed = todos.splice(index, 1)[0]; return { id: removed.id, description: removed.description, remaining: todos.length, display: this.createCompactDisplay(todos) }; } listTodos(todos, filterStatus) { let filtered = [...todos]; if (filterStatus) { filtered = filtered.filter(t => t.status === filterStatus); } const priorityOrder = { high: 0, medium: 1, low: 2 }; filtered.sort((a, b) => { const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority]; if (priorityDiff !== 0) return priorityDiff; return a.createdAt.getTime() - b.createdAt.getTime(); }); return { todos: filtered.map(todo => ({ id: todo.id, description: todo.description, status: todo.status, priority: todo.priority, created: todo.createdAt.toISOString(), updated: todo.updatedAt.toISOString(), dependencies: todo.dependencies })), total: todos.length, filtered: filtered.length, byStatus: { pending: todos.filter(t => t.status === 'pending').length, in_progress: todos.filter(t => t.status === 'in_progress').length, completed: todos.filter(t => t.status === 'completed').length, blocked: todos.filter(t => t.status === 'blocked').length, cancelled: todos.filter(t => t.status === 'cancelled').length }, display: this.createCompactDisplay(todos) }; } clearTodos(todos, filterStatus) { let toRemove = []; if (filterStatus === 'completed') { toRemove = todos.filter(t => t.status === 'completed'); const remaining = todos.filter(t => t.status !== 'completed'); todos.length = 0; todos.push(...remaining); } else if (!filterStatus) { toRemove = [...todos]; todos.length = 0; } else { throw new Error('Clear only supports filtering by status: "completed"'); } return { cleared: toRemove.length, remaining: todos.length, display: this.createCompactDisplay(todos) }; } createCompactDisplay(todos) { if (todos.length === 0) { return ' ⎿ (No tasks)'; } const sortedTodos = [...todos].sort((a, b) => { const statusOrder = { 'in_progress': 0, 'pending': 1, 'blocked': 2, 'completed': 3, 'cancelled': 4 }; return statusOrder[a.status] - statusOrder[b.status]; }); let display = ''; sortedTodos.forEach((todo, index) => { const prefix = index === 0 ? ' ⎿' : ' '; const checkbox = this.getCheckbox(todo.status); display += `${prefix} ${checkbox} ${todo.description}\n`; }); return display.trimEnd(); } getCheckbox(status) { switch (status) { case 'completed': return '☑'; case 'in_progress': return '◐'; case 'blocked': return '☒'; case 'cancelled': return '⊗'; case 'pending': default: return '☐'; } } } //# sourceMappingURL=todo-list.js.map