@hasna/todos 0.9.23 → 0.9.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -6196,7 +6196,7 @@ ${text}` }] };
6196
6196
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6197
6197
  }
6198
6198
  });
6199
- server.tool("get_task", "Get full task details including dependencies, subtasks, and comments", {
6199
+ server.tool("get_task", "Get full task details with relations", {
6200
6200
  id: exports_external.string().describe("Task ID (full or partial)")
6201
6201
  }, async ({ id }) => {
6202
6202
  try {
@@ -6244,7 +6244,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
6244
6244
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6245
6245
  }
6246
6246
  });
6247
- server.tool("update_task", "Update task fields (requires version for optimistic locking)", {
6247
+ server.tool("update_task", "Update task fields. Version required for optimistic locking.", {
6248
6248
  id: exports_external.string().describe("Task ID (full or partial)"),
6249
6249
  version: exports_external.number().describe("Current version (for optimistic locking)"),
6250
6250
  title: exports_external.string().optional().describe("New title"),
@@ -6282,7 +6282,7 @@ server.tool("delete_task", "Delete a task permanently", {
6282
6282
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6283
6283
  }
6284
6284
  });
6285
- server.tool("start_task", "Claim a task, lock it, and set status to in_progress", {
6285
+ server.tool("start_task", "Claim, lock, and set task status to in_progress.", {
6286
6286
  id: exports_external.string().describe("Task ID (full or partial)"),
6287
6287
  agent_id: exports_external.string().describe("Agent claiming the task")
6288
6288
  }, async ({ id, agent_id }) => {
@@ -6295,7 +6295,7 @@ ${formatTask(task)}` }] };
6295
6295
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6296
6296
  }
6297
6297
  });
6298
- server.tool("complete_task", "Mark a task as completed and release lock", {
6298
+ server.tool("complete_task", "Mark task completed and release lock.", {
6299
6299
  id: exports_external.string().describe("Task ID (full or partial)"),
6300
6300
  agent_id: exports_external.string().optional().describe("Agent completing the task")
6301
6301
  }, async ({ id, agent_id }) => {
@@ -6335,7 +6335,7 @@ server.tool("unlock_task", "Release exclusive lock on a task", {
6335
6335
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6336
6336
  }
6337
6337
  });
6338
- server.tool("add_dependency", "Add a dependency between tasks (task_id depends on depends_on)", {
6338
+ server.tool("add_dependency", "Add a dependency: task_id depends on depends_on.", {
6339
6339
  task_id: exports_external.string().describe("Task that depends on another"),
6340
6340
  depends_on: exports_external.string().describe("Task that must complete first")
6341
6341
  }, async ({ task_id, depends_on }) => {
@@ -6526,7 +6526,7 @@ server.tool("delete_plan", "Delete a plan", {
6526
6526
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6527
6527
  }
6528
6528
  });
6529
- server.tool("search_tasks", "Full-text search across task titles, descriptions, and tags", {
6529
+ server.tool("search_tasks", "Full-text search across task titles, descriptions, tags.", {
6530
6530
  query: exports_external.string().describe("Search query"),
6531
6531
  project_id: exports_external.string().optional().describe("Limit to project"),
6532
6532
  task_list_id: exports_external.string().optional().describe("Filter by task list")
@@ -6546,7 +6546,7 @@ ${text}` }] };
6546
6546
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6547
6547
  }
6548
6548
  });
6549
- server.tool("sync", "Sync tasks with an agent task list (Claude uses native task list; others use JSON lists).", {
6549
+ server.tool("sync", "Sync tasks with an agent task list.", {
6550
6550
  task_list_id: exports_external.string().optional().describe("Task list ID (required for Claude)"),
6551
6551
  agent: exports_external.string().optional().describe("Agent/provider name (default: claude)"),
6552
6552
  all_agents: exports_external.boolean().optional().describe("Sync across all configured agents"),
@@ -6594,7 +6594,7 @@ server.tool("sync", "Sync tasks with an agent task list (Claude uses native task
6594
6594
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6595
6595
  }
6596
6596
  });
6597
- server.tool("register_agent", "Register an agent and get a short UUID. Idempotent: same name returns existing agent.", {
6597
+ server.tool("register_agent", "Register an agent (idempotent by name).", {
6598
6598
  name: exports_external.string().describe("Agent name"),
6599
6599
  description: exports_external.string().optional().describe("Agent description")
6600
6600
  }, async ({ name, description }) => {
@@ -6754,7 +6754,7 @@ Slug: ${list.slug}`
6754
6754
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6755
6755
  }
6756
6756
  });
6757
- server.tool("delete_task_list", "Delete a task list. Tasks in this list keep their data but lose their list association.", {
6757
+ server.tool("delete_task_list", "Delete a task list. Tasks lose association but keep data.", {
6758
6758
  id: exports_external.string().describe("Task list ID (full or partial)")
6759
6759
  }, async ({ id }) => {
6760
6760
  try {
@@ -6770,7 +6770,7 @@ server.tool("delete_task_list", "Delete a task list. Tasks in this list keep the
6770
6770
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6771
6771
  }
6772
6772
  });
6773
- server.tool("get_task_history", "Get change history for a task (audit log)", {
6773
+ server.tool("get_task_history", "Get audit log for a task.", {
6774
6774
  task_id: exports_external.string().describe("Task ID (full or partial)")
6775
6775
  }, async ({ task_id }) => {
6776
6776
  try {
@@ -6787,7 +6787,7 @@ ${text}` }] };
6787
6787
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6788
6788
  }
6789
6789
  });
6790
- server.tool("get_recent_activity", "Get recent task changes across all tasks (audit log)", {
6790
+ server.tool("get_recent_activity", "Get recent task changes across all tasks.", {
6791
6791
  limit: exports_external.number().optional().describe("Max entries (default 50)")
6792
6792
  }, async ({ limit }) => {
6793
6793
  try {
@@ -6803,7 +6803,7 @@ ${text}` }] };
6803
6803
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6804
6804
  }
6805
6805
  });
6806
- server.tool("create_webhook", "Register a webhook URL to receive task change notifications", {
6806
+ server.tool("create_webhook", "Register a webhook to receive task change events.", {
6807
6807
  url: exports_external.string().describe("Webhook URL"),
6808
6808
  events: exports_external.array(exports_external.string()).optional().describe("Event types to subscribe to (empty = all)"),
6809
6809
  secret: exports_external.string().optional().describe("HMAC secret for signature verification")
@@ -6841,7 +6841,7 @@ server.tool("delete_webhook", "Delete a webhook", {
6841
6841
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6842
6842
  }
6843
6843
  });
6844
- server.tool("create_template", "Create a reusable task template", {
6844
+ server.tool("create_template", "Create a reusable task template.", {
6845
6845
  name: exports_external.string().describe("Template name"),
6846
6846
  title_pattern: exports_external.string().describe("Title pattern for tasks created from this template"),
6847
6847
  description: exports_external.string().optional().describe("Default description"),
@@ -6872,7 +6872,7 @@ ${text}` }] };
6872
6872
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6873
6873
  }
6874
6874
  });
6875
- server.tool("create_task_from_template", "Create a task from a template with optional overrides", {
6875
+ server.tool("create_task_from_template", "Create a task from a template with optional overrides.", {
6876
6876
  template_id: exports_external.string().describe("Template ID"),
6877
6877
  title: exports_external.string().optional().describe("Override title"),
6878
6878
  description: exports_external.string().optional().describe("Override description"),
@@ -6905,7 +6905,7 @@ server.tool("delete_template", "Delete a task template", { id: exports_external.
6905
6905
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6906
6906
  }
6907
6907
  });
6908
- server.tool("approve_task", "Approve a task that requires approval before completion", {
6908
+ server.tool("approve_task", "Approve a task that requires approval.", {
6909
6909
  id: exports_external.string().describe("Task ID (full or partial)"),
6910
6910
  agent_id: exports_external.string().optional().describe("Agent approving the task")
6911
6911
  }, async ({ id, agent_id }) => {
@@ -6924,7 +6924,7 @@ server.tool("approve_task", "Approve a task that requires approval before comple
6924
6924
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6925
6925
  }
6926
6926
  });
6927
- server.tool("get_my_tasks", "Get your assigned tasks and stats. Auto-registers if needed.", {
6927
+ server.tool("get_my_tasks", "Get assigned tasks and stats for an agent.", {
6928
6928
  agent_name: exports_external.string().describe("Your agent name")
6929
6929
  }, async ({ agent_name }) => {
6930
6930
  try {
@@ -6957,6 +6957,79 @@ In Progress:`);
6957
6957
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
6958
6958
  }
6959
6959
  });
6960
+ server.tool("search_tools", "List tool names matching a query.", { query: exports_external.string().optional().describe("Keyword to filter tools") }, async ({ query }) => {
6961
+ const all = [
6962
+ "create_task",
6963
+ "list_tasks",
6964
+ "get_task",
6965
+ "update_task",
6966
+ "delete_task",
6967
+ "start_task",
6968
+ "complete_task",
6969
+ "lock_task",
6970
+ "unlock_task",
6971
+ "approve_task",
6972
+ "add_dependency",
6973
+ "remove_dependency",
6974
+ "add_comment",
6975
+ "create_project",
6976
+ "list_projects",
6977
+ "create_plan",
6978
+ "list_plans",
6979
+ "get_plan",
6980
+ "update_plan",
6981
+ "delete_plan",
6982
+ "register_agent",
6983
+ "list_agents",
6984
+ "get_agent",
6985
+ "get_my_tasks",
6986
+ "create_task_list",
6987
+ "list_task_lists",
6988
+ "get_task_list",
6989
+ "update_task_list",
6990
+ "delete_task_list",
6991
+ "search_tasks",
6992
+ "sync",
6993
+ "get_task_history",
6994
+ "get_recent_activity",
6995
+ "create_webhook",
6996
+ "list_webhooks",
6997
+ "delete_webhook",
6998
+ "create_template",
6999
+ "list_templates",
7000
+ "create_task_from_template",
7001
+ "delete_template",
7002
+ "search_tools",
7003
+ "describe_tools"
7004
+ ];
7005
+ const q = query?.toLowerCase();
7006
+ const matches = q ? all.filter((n) => n.includes(q)) : all;
7007
+ return { content: [{ type: "text", text: matches.join(", ") }] };
7008
+ });
7009
+ server.tool("describe_tools", "Get descriptions for specific tools by name.", { names: exports_external.array(exports_external.string()).describe("Tool names from search_tools") }, async ({ names }) => {
7010
+ const descriptions = {
7011
+ create_task: "Create a task. Params: title(req), description, priority, project_id, plan_id, tags, assigned_to, estimated_minutes, requires_approval",
7012
+ list_tasks: "List tasks. Params: status, priority, project_id, plan_id, assigned_to, tags, limit",
7013
+ get_task: "Get full task details. Params: id",
7014
+ update_task: "Update task fields. Params: id, version(req), title, description, status, priority, tags, assigned_to, due_at",
7015
+ delete_task: "Delete a task. Params: id",
7016
+ start_task: "Claim, lock, and start a task. Params: id",
7017
+ complete_task: "Mark task completed. Params: id, agent_id",
7018
+ approve_task: "Approve task requiring approval. Params: id, agent_id",
7019
+ create_plan: "Create a plan. Params: name, description, project_id, task_list_id, agent_id, status",
7020
+ list_plans: "List plans. Params: project_id",
7021
+ get_plan: "Get plan with tasks. Params: id",
7022
+ search_tasks: "Full-text search tasks. Params: query, project_id, task_list_id",
7023
+ get_my_tasks: "Get your tasks and stats. Params: agent_name",
7024
+ get_task_history: "Get task audit log. Params: task_id",
7025
+ get_recent_activity: "Recent changes across all tasks. Params: limit",
7026
+ create_template: "Create task template. Params: name, title_pattern, description, priority, tags",
7027
+ create_task_from_template: "Create task from template. Params: template_id, title, priority, assigned_to"
7028
+ };
7029
+ const result = names.map((n) => `${n}: ${descriptions[n] || "See tool schema"}`).join(`
7030
+ `);
7031
+ return { content: [{ type: "text", text: result }] };
7032
+ });
6960
7033
  server.resource("tasks", "todos://tasks", { description: "All active tasks", mimeType: "application/json" }, async () => {
6961
7034
  const tasks = listTasks({ status: ["pending", "in_progress"] });
6962
7035
  return { contents: [{ uri: "todos://tasks", text: JSON.stringify(tasks, null, 2), mimeType: "application/json" }] };
@@ -1429,8 +1429,8 @@ function serveStaticFile(filePath) {
1429
1429
  headers: { "Content-Type": contentType }
1430
1430
  });
1431
1431
  }
1432
- function taskToSummary(task) {
1433
- return {
1432
+ function taskToSummary(task, fields) {
1433
+ const full = {
1434
1434
  id: task.id,
1435
1435
  short_id: task.short_id,
1436
1436
  title: task.title,
@@ -1450,6 +1450,9 @@ function taskToSummary(task) {
1450
1450
  completed_at: task.completed_at,
1451
1451
  due_at: task.due_at
1452
1452
  };
1453
+ if (!fields || fields.length === 0)
1454
+ return full;
1455
+ return Object.fromEntries(fields.map((f) => [f, full[f] ?? null]));
1453
1456
  }
1454
1457
  async function startServer(port, options) {
1455
1458
  const shouldOpen = options?.open ?? true;
@@ -1532,12 +1535,14 @@ Dashboard not found at: ${dashboardDir}`);
1532
1535
  const status = url.searchParams.get("status") || undefined;
1533
1536
  const projectId = url.searchParams.get("project_id") || undefined;
1534
1537
  const limitParam = url.searchParams.get("limit");
1538
+ const fieldsParam = url.searchParams.get("fields");
1539
+ const fields = fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
1535
1540
  const tasks = listTasks({
1536
1541
  status,
1537
1542
  project_id: projectId,
1538
1543
  limit: limitParam ? parseInt(limitParam, 10) : undefined
1539
1544
  });
1540
- return json(tasks.map(taskToSummary), 200, port);
1545
+ return json(tasks.map((t) => taskToSummary(t, fields)), 200, port);
1541
1546
  }
1542
1547
  if (path === "/api/tasks" && method === "POST") {
1543
1548
  try {
@@ -1561,7 +1566,7 @@ Dashboard not found at: ${dashboardDir}`);
1561
1566
  const status = url.searchParams.get("status") || undefined;
1562
1567
  const projectId = url.searchParams.get("project_id") || undefined;
1563
1568
  const tasks = listTasks({ status, project_id: projectId, limit: 1e4 });
1564
- const summaries = tasks.map(taskToSummary);
1569
+ const summaries = tasks.map((t) => taskToSummary(t));
1565
1570
  if (format === "csv") {
1566
1571
  const headers = ["id", "short_id", "title", "status", "priority", "project_id", "assigned_to", "agent_id", "created_at", "updated_at", "completed_at", "due_at"];
1567
1572
  const rows = summaries.map((t) => headers.map((h) => {
@@ -1687,8 +1692,8 @@ Dashboard not found at: ${dashboardDir}`);
1687
1692
  const completed = allTasks.filter((t) => t.status === "completed");
1688
1693
  return json({
1689
1694
  agent,
1690
- pending_tasks: pending.map(taskToSummary),
1691
- in_progress_tasks: inProgress.map(taskToSummary),
1695
+ pending_tasks: pending.map((t) => taskToSummary(t)),
1696
+ in_progress_tasks: inProgress.map((t) => taskToSummary(t)),
1692
1697
  stats: {
1693
1698
  total: allTasks.length,
1694
1699
  pending: pending.length,
@@ -1705,7 +1710,7 @@ Dashboard not found at: ${dashboardDir}`);
1705
1710
  const queue = pending.filter((t) => t.assigned_to === agentId || t.agent_id === agentId || !t.assigned_to && !t.locked_by);
1706
1711
  const order = { critical: 0, high: 1, medium: 2, low: 3 };
1707
1712
  queue.sort((a, b) => (order[a.priority] ?? 4) - (order[b.priority] ?? 4) || new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
1708
- return json(queue.map(taskToSummary), 200, port);
1713
+ return json(queue.map((t) => taskToSummary(t)), 200, port);
1709
1714
  }
1710
1715
  if (path === "/api/tasks/claim" && method === "POST") {
1711
1716
  try {
@@ -1921,7 +1926,7 @@ Dashboard not found at: ${dashboardDir}`);
1921
1926
  if (!plan)
1922
1927
  return json({ error: "Plan not found" }, 404, port);
1923
1928
  const tasks = listTasks({ plan_id: id });
1924
- return json({ ...plan, tasks: tasks.map(taskToSummary) }, 200, port);
1929
+ return json({ ...plan, tasks: tasks.map((t) => taskToSummary(t)) }, 200, port);
1925
1930
  }
1926
1931
  if (method === "PATCH") {
1927
1932
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.9.23",
3
+ "version": "0.9.25",
4
4
  "description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",