capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
280 lines • 10.6 kB
JavaScript
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