@hasna/todos 0.9.19 → 0.9.21

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/cli/index.js CHANGED
@@ -4,6 +4,7 @@ var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __toESM = (mod, isNodeMode, target) => {
9
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -16,6 +17,20 @@ var __toESM = (mod, isNodeMode, target) => {
16
17
  });
17
18
  return to;
18
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
21
+ var __toCommonJS = (from) => {
22
+ var entry = __moduleCache.get(from), desc;
23
+ if (entry)
24
+ return entry;
25
+ entry = __defProp({}, "__esModule", { value: true });
26
+ if (from && typeof from === "object" || typeof from === "function")
27
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ }));
31
+ __moduleCache.set(from, entry);
32
+ return entry;
33
+ };
19
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
35
  var __export = (target, all) => {
21
36
  for (var name in all)
@@ -2895,6 +2910,33 @@ var init_completion_guard = __esm(() => {
2895
2910
  init_projects();
2896
2911
  });
2897
2912
 
2913
+ // src/db/audit.ts
2914
+ var exports_audit = {};
2915
+ __export(exports_audit, {
2916
+ logTaskChange: () => logTaskChange,
2917
+ getTaskHistory: () => getTaskHistory,
2918
+ getRecentActivity: () => getRecentActivity
2919
+ });
2920
+ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
2921
+ const d = db || getDatabase();
2922
+ const id = uuid();
2923
+ const timestamp = now();
2924
+ d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
2925
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
2926
+ return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
2927
+ }
2928
+ function getTaskHistory(taskId, db) {
2929
+ const d = db || getDatabase();
2930
+ return d.query("SELECT * FROM task_history WHERE task_id = ? ORDER BY created_at DESC").all(taskId);
2931
+ }
2932
+ function getRecentActivity(limit = 50, db) {
2933
+ const d = db || getDatabase();
2934
+ return d.query("SELECT * FROM task_history ORDER BY created_at DESC LIMIT ?").all(limit);
2935
+ }
2936
+ var init_audit = __esm(() => {
2937
+ init_database();
2938
+ });
2939
+
2898
2940
  // src/db/tasks.ts
2899
2941
  function rowToTask(row) {
2900
2942
  return {
@@ -3145,6 +3187,17 @@ function updateTask(id, input, db) {
3145
3187
  if (input.tags !== undefined) {
3146
3188
  replaceTaskTags(id, input.tags, d);
3147
3189
  }
3190
+ const agentId = task.assigned_to || task.agent_id || null;
3191
+ if (input.status !== undefined && input.status !== task.status)
3192
+ logTaskChange(id, "update", "status", task.status, input.status, agentId, d);
3193
+ if (input.priority !== undefined && input.priority !== task.priority)
3194
+ logTaskChange(id, "update", "priority", task.priority, input.priority, agentId, d);
3195
+ if (input.title !== undefined && input.title !== task.title)
3196
+ logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
3197
+ if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
3198
+ logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
3199
+ if (input.approved_by !== undefined)
3200
+ logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
3148
3201
  return getTask(id, d);
3149
3202
  }
3150
3203
  function deleteTask(id, db) {
@@ -3166,6 +3219,7 @@ function startTask(id, agentId, db) {
3166
3219
  throw new LockError(id, current.locked_by);
3167
3220
  }
3168
3221
  }
3222
+ logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
3169
3223
  return getTask(id, d);
3170
3224
  }
3171
3225
  function completeTask(id, agentId, db) {
@@ -3180,6 +3234,7 @@ function completeTask(id, agentId, db) {
3180
3234
  const timestamp = now();
3181
3235
  d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
3182
3236
  WHERE id = ?`, [timestamp, timestamp, id]);
3237
+ logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
3183
3238
  return getTask(id, d);
3184
3239
  }
3185
3240
  function lockTask(id, agentId, db) {
@@ -3260,6 +3315,7 @@ var init_tasks = __esm(() => {
3260
3315
  init_database();
3261
3316
  init_projects();
3262
3317
  init_completion_guard();
3318
+ init_audit();
3263
3319
  });
3264
3320
 
3265
3321
  // src/db/agents.ts
@@ -4096,6 +4152,73 @@ var init_sync = __esm(() => {
4096
4152
  init_config();
4097
4153
  });
4098
4154
 
4155
+ // src/db/templates.ts
4156
+ var exports_templates = {};
4157
+ __export(exports_templates, {
4158
+ taskFromTemplate: () => taskFromTemplate,
4159
+ listTemplates: () => listTemplates,
4160
+ getTemplate: () => getTemplate,
4161
+ deleteTemplate: () => deleteTemplate,
4162
+ createTemplate: () => createTemplate
4163
+ });
4164
+ function rowToTemplate(row) {
4165
+ return {
4166
+ ...row,
4167
+ tags: JSON.parse(row.tags || "[]"),
4168
+ metadata: JSON.parse(row.metadata || "{}"),
4169
+ priority: row.priority || "medium"
4170
+ };
4171
+ }
4172
+ function createTemplate(input, db) {
4173
+ const d = db || getDatabase();
4174
+ const id = uuid();
4175
+ d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
4176
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
4177
+ id,
4178
+ input.name,
4179
+ input.title_pattern,
4180
+ input.description || null,
4181
+ input.priority || "medium",
4182
+ JSON.stringify(input.tags || []),
4183
+ input.project_id || null,
4184
+ input.plan_id || null,
4185
+ JSON.stringify(input.metadata || {}),
4186
+ now()
4187
+ ]);
4188
+ return getTemplate(id, d);
4189
+ }
4190
+ function getTemplate(id, db) {
4191
+ const d = db || getDatabase();
4192
+ const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(id);
4193
+ return row ? rowToTemplate(row) : null;
4194
+ }
4195
+ function listTemplates(db) {
4196
+ const d = db || getDatabase();
4197
+ return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
4198
+ }
4199
+ function deleteTemplate(id, db) {
4200
+ const d = db || getDatabase();
4201
+ return d.run("DELETE FROM task_templates WHERE id = ?", [id]).changes > 0;
4202
+ }
4203
+ function taskFromTemplate(templateId, overrides = {}, db) {
4204
+ const t = getTemplate(templateId, db);
4205
+ if (!t)
4206
+ throw new Error(`Template not found: ${templateId}`);
4207
+ return {
4208
+ title: overrides.title || t.title_pattern,
4209
+ description: overrides.description ?? t.description ?? undefined,
4210
+ priority: overrides.priority ?? t.priority,
4211
+ tags: overrides.tags ?? t.tags,
4212
+ project_id: overrides.project_id ?? t.project_id ?? undefined,
4213
+ plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
4214
+ metadata: overrides.metadata ?? t.metadata,
4215
+ ...overrides
4216
+ };
4217
+ }
4218
+ var init_templates = __esm(() => {
4219
+ init_database();
4220
+ });
4221
+
4099
4222
  // node_modules/zod/v3/helpers/util.js
4100
4223
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
4101
4224
  const t = typeof data;
@@ -8062,6 +8185,57 @@ var init_zod = __esm(() => {
8062
8185
  init_external();
8063
8186
  });
8064
8187
 
8188
+ // src/db/webhooks.ts
8189
+ var exports_webhooks = {};
8190
+ __export(exports_webhooks, {
8191
+ listWebhooks: () => listWebhooks,
8192
+ getWebhook: () => getWebhook,
8193
+ dispatchWebhook: () => dispatchWebhook,
8194
+ deleteWebhook: () => deleteWebhook,
8195
+ createWebhook: () => createWebhook
8196
+ });
8197
+ function rowToWebhook(row) {
8198
+ return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
8199
+ }
8200
+ function createWebhook(input, db) {
8201
+ const d = db || getDatabase();
8202
+ const id = uuid();
8203
+ d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
8204
+ return getWebhook(id, d);
8205
+ }
8206
+ function getWebhook(id, db) {
8207
+ const d = db || getDatabase();
8208
+ const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
8209
+ return row ? rowToWebhook(row) : null;
8210
+ }
8211
+ function listWebhooks(db) {
8212
+ const d = db || getDatabase();
8213
+ return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
8214
+ }
8215
+ function deleteWebhook(id, db) {
8216
+ const d = db || getDatabase();
8217
+ return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
8218
+ }
8219
+ async function dispatchWebhook(event, payload, db) {
8220
+ const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
8221
+ for (const wh of webhooks) {
8222
+ try {
8223
+ const body = JSON.stringify({ event, payload, timestamp: now() });
8224
+ const headers = { "Content-Type": "application/json" };
8225
+ if (wh.secret) {
8226
+ const encoder = new TextEncoder;
8227
+ const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
8228
+ const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
8229
+ headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
8230
+ }
8231
+ fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
8232
+ } catch {}
8233
+ }
8234
+ }
8235
+ var init_webhooks = __esm(() => {
8236
+ init_database();
8237
+ });
8238
+
8065
8239
  // src/mcp/index.ts
8066
8240
  var exports_mcp = {};
8067
8241
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -8170,7 +8344,9 @@ var init_mcp = __esm(() => {
8170
8344
  plan_id: exports_external.string().optional().describe("Plan ID to assign task to"),
8171
8345
  task_list_id: exports_external.string().optional().describe("Task list ID to assign task to"),
8172
8346
  tags: exports_external.array(exports_external.string()).optional().describe("Task tags"),
8173
- metadata: exports_external.record(exports_external.unknown()).optional().describe("Arbitrary metadata")
8347
+ metadata: exports_external.record(exports_external.unknown()).optional().describe("Arbitrary metadata"),
8348
+ estimated_minutes: exports_external.number().optional().describe("Estimated time in minutes"),
8349
+ requires_approval: exports_external.boolean().optional().describe("Require approval before completion")
8174
8350
  }, async (params) => {
8175
8351
  try {
8176
8352
  const resolved = { ...params };
@@ -8452,12 +8628,16 @@ ${text}` }] };
8452
8628
  name: exports_external.string().describe("Plan name"),
8453
8629
  project_id: exports_external.string().optional().describe("Project ID"),
8454
8630
  description: exports_external.string().optional().describe("Plan description"),
8455
- status: exports_external.enum(["active", "completed", "archived"]).optional().describe("Plan status")
8631
+ status: exports_external.enum(["active", "completed", "archived"]).optional().describe("Plan status"),
8632
+ task_list_id: exports_external.string().optional().describe("Task list ID"),
8633
+ agent_id: exports_external.string().optional().describe("Owner agent ID")
8456
8634
  }, async (params) => {
8457
8635
  try {
8458
8636
  const resolved = { ...params };
8459
8637
  if (resolved.project_id)
8460
8638
  resolved.project_id = resolveId(resolved.project_id, "projects");
8639
+ if (resolved.task_list_id)
8640
+ resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
8461
8641
  const plan = createPlan(resolved);
8462
8642
  return {
8463
8643
  content: [{
@@ -8518,11 +8698,16 @@ ${text}` }] };
8518
8698
  id: exports_external.string().describe("Plan ID (full or partial)"),
8519
8699
  name: exports_external.string().optional().describe("New name"),
8520
8700
  description: exports_external.string().optional().describe("New description"),
8521
- status: exports_external.enum(["active", "completed", "archived"]).optional().describe("New status")
8701
+ status: exports_external.enum(["active", "completed", "archived"]).optional().describe("New status"),
8702
+ task_list_id: exports_external.string().optional().describe("Task list ID"),
8703
+ agent_id: exports_external.string().optional().describe("Owner agent ID")
8522
8704
  }, async ({ id, ...rest }) => {
8523
8705
  try {
8524
8706
  const resolvedId = resolveId(id, "plans");
8525
- const plan = updatePlan(resolvedId, rest);
8707
+ const resolved = { ...rest };
8708
+ if (resolved.task_list_id)
8709
+ resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
8710
+ const plan = updatePlan(resolvedId, resolved);
8526
8711
  return {
8527
8712
  content: [{
8528
8713
  type: "text",
@@ -8793,6 +8978,160 @@ Slug: ${list.slug}`
8793
8978
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8794
8979
  }
8795
8980
  });
8981
+ server.tool("get_task_history", "Get change history for a task (audit log)", {
8982
+ task_id: exports_external.string().describe("Task ID (full or partial)")
8983
+ }, async ({ task_id }) => {
8984
+ try {
8985
+ const resolvedId = resolveId(task_id);
8986
+ const { getTaskHistory: getTaskHistory2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
8987
+ const history = getTaskHistory2(resolvedId);
8988
+ if (history.length === 0)
8989
+ return { content: [{ type: "text", text: "No history for this task." }] };
8990
+ const text = history.map((h) => `${h.created_at} | ${h.action}${h.field ? ` ${h.field}` : ""}${h.old_value ? ` from "${h.old_value}"` : ""}${h.new_value ? ` to "${h.new_value}"` : ""}${h.agent_id ? ` by ${h.agent_id}` : ""}`).join(`
8991
+ `);
8992
+ return { content: [{ type: "text", text: `${history.length} change(s):
8993
+ ${text}` }] };
8994
+ } catch (e) {
8995
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
8996
+ }
8997
+ });
8998
+ server.tool("get_recent_activity", "Get recent task changes across all tasks (audit log)", {
8999
+ limit: exports_external.number().optional().describe("Max entries (default 50)")
9000
+ }, async ({ limit }) => {
9001
+ try {
9002
+ const { getRecentActivity: getRecentActivity2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
9003
+ const activity = getRecentActivity2(limit || 50);
9004
+ if (activity.length === 0)
9005
+ return { content: [{ type: "text", text: "No recent activity." }] };
9006
+ const text = activity.map((h) => `${h.created_at} | ${h.task_id.slice(0, 8)} | ${h.action}${h.field ? ` ${h.field}` : ""}${h.agent_id ? ` by ${h.agent_id}` : ""}`).join(`
9007
+ `);
9008
+ return { content: [{ type: "text", text: `${activity.length} recent change(s):
9009
+ ${text}` }] };
9010
+ } catch (e) {
9011
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9012
+ }
9013
+ });
9014
+ server.tool("create_webhook", "Register a webhook URL to receive task change notifications", {
9015
+ url: exports_external.string().describe("Webhook URL"),
9016
+ events: exports_external.array(exports_external.string()).optional().describe("Event types to subscribe to (empty = all)"),
9017
+ secret: exports_external.string().optional().describe("HMAC secret for signature verification")
9018
+ }, async (params) => {
9019
+ try {
9020
+ const { createWebhook: createWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
9021
+ const wh = createWebhook2(params);
9022
+ return { content: [{ type: "text", text: `Webhook created: ${wh.id.slice(0, 8)} | ${wh.url} | events: ${wh.events.length === 0 ? "all" : wh.events.join(",")}` }] };
9023
+ } catch (e) {
9024
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9025
+ }
9026
+ });
9027
+ server.tool("list_webhooks", "List all registered webhooks", {}, async () => {
9028
+ try {
9029
+ const { listWebhooks: listWebhooks2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
9030
+ const webhooks = listWebhooks2();
9031
+ if (webhooks.length === 0)
9032
+ return { content: [{ type: "text", text: "No webhooks registered." }] };
9033
+ const text = webhooks.map((w) => `${w.id.slice(0, 8)} | ${w.active ? "active" : "inactive"} | ${w.url} | events: ${w.events.length === 0 ? "all" : w.events.join(",")}`).join(`
9034
+ `);
9035
+ return { content: [{ type: "text", text: `${webhooks.length} webhook(s):
9036
+ ${text}` }] };
9037
+ } catch (e) {
9038
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9039
+ }
9040
+ });
9041
+ server.tool("delete_webhook", "Delete a webhook", {
9042
+ id: exports_external.string().describe("Webhook ID")
9043
+ }, async ({ id }) => {
9044
+ try {
9045
+ const { deleteWebhook: deleteWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
9046
+ const deleted = deleteWebhook2(id);
9047
+ return { content: [{ type: "text", text: deleted ? "Webhook deleted." : "Webhook not found." }] };
9048
+ } catch (e) {
9049
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9050
+ }
9051
+ });
9052
+ server.tool("create_template", "Create a reusable task template", {
9053
+ name: exports_external.string().describe("Template name"),
9054
+ title_pattern: exports_external.string().describe("Title pattern for tasks created from this template"),
9055
+ description: exports_external.string().optional().describe("Default description"),
9056
+ priority: exports_external.enum(["low", "medium", "high", "critical"]).optional().describe("Default priority"),
9057
+ tags: exports_external.array(exports_external.string()).optional().describe("Default tags"),
9058
+ project_id: exports_external.string().optional().describe("Default project"),
9059
+ plan_id: exports_external.string().optional().describe("Default plan")
9060
+ }, async (params) => {
9061
+ try {
9062
+ const { createTemplate: createTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
9063
+ const t = createTemplate2(params);
9064
+ return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"` }] };
9065
+ } catch (e) {
9066
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9067
+ }
9068
+ });
9069
+ server.tool("list_templates", "List all task templates", {}, async () => {
9070
+ try {
9071
+ const { listTemplates: listTemplates2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
9072
+ const templates = listTemplates2();
9073
+ if (templates.length === 0)
9074
+ return { content: [{ type: "text", text: "No templates." }] };
9075
+ const text = templates.map((t) => `${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}" | ${t.priority}`).join(`
9076
+ `);
9077
+ return { content: [{ type: "text", text: `${templates.length} template(s):
9078
+ ${text}` }] };
9079
+ } catch (e) {
9080
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9081
+ }
9082
+ });
9083
+ server.tool("create_task_from_template", "Create a task from a template with optional overrides", {
9084
+ template_id: exports_external.string().describe("Template ID"),
9085
+ title: exports_external.string().optional().describe("Override title"),
9086
+ description: exports_external.string().optional().describe("Override description"),
9087
+ priority: exports_external.enum(["low", "medium", "high", "critical"]).optional().describe("Override priority"),
9088
+ assigned_to: exports_external.string().optional().describe("Assign to agent"),
9089
+ project_id: exports_external.string().optional().describe("Override project")
9090
+ }, async (params) => {
9091
+ try {
9092
+ const { taskFromTemplate: taskFromTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
9093
+ const input = taskFromTemplate2(params.template_id, {
9094
+ title: params.title,
9095
+ description: params.description,
9096
+ priority: params.priority,
9097
+ assigned_to: params.assigned_to,
9098
+ project_id: params.project_id
9099
+ });
9100
+ const task = createTask(input);
9101
+ return { content: [{ type: "text", text: `Task created from template:
9102
+ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
9103
+ } catch (e) {
9104
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9105
+ }
9106
+ });
9107
+ server.tool("delete_template", "Delete a task template", { id: exports_external.string().describe("Template ID") }, async ({ id }) => {
9108
+ try {
9109
+ const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
9110
+ const deleted = deleteTemplate2(id);
9111
+ return { content: [{ type: "text", text: deleted ? "Template deleted." : "Template not found." }] };
9112
+ } catch (e) {
9113
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9114
+ }
9115
+ });
9116
+ server.tool("approve_task", "Approve a task that requires approval before completion", {
9117
+ id: exports_external.string().describe("Task ID (full or partial)"),
9118
+ agent_id: exports_external.string().optional().describe("Agent approving the task")
9119
+ }, async ({ id, agent_id }) => {
9120
+ try {
9121
+ const resolvedId = resolveId(id);
9122
+ const task = getTaskWithRelations(resolvedId);
9123
+ if (!task)
9124
+ return { content: [{ type: "text", text: `Task not found: ${id}` }], isError: true };
9125
+ if (!task.requires_approval)
9126
+ return { content: [{ type: "text", text: `Task ${id} does not require approval.` }] };
9127
+ if (task.approved_by)
9128
+ return { content: [{ type: "text", text: `Task already approved by ${task.approved_by}.` }] };
9129
+ const updated = updateTask(resolvedId, { approved_by: agent_id || "system", version: task.version });
9130
+ return { content: [{ type: "text", text: `Task approved by ${agent_id || "system"}: ${updated.id.slice(0, 8)} | ${updated.title}` }] };
9131
+ } catch (e) {
9132
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9133
+ }
9134
+ });
8796
9135
  server.resource("tasks", "todos://tasks", { description: "All active tasks", mimeType: "application/json" }, async () => {
8797
9136
  const tasks = listTasks({ status: ["pending", "in_progress"] });
8798
9137
  return { contents: [{ uri: "todos://tasks", text: JSON.stringify(tasks, null, 2), mimeType: "application/json" }] };
@@ -9156,6 +9495,59 @@ Dashboard not found at: ${dashboardDir}`);
9156
9495
  return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
9157
9496
  }
9158
9497
  }
9498
+ if (path === "/api/activity" && method === "GET") {
9499
+ const limit = parseInt(url.searchParams.get("limit") || "50", 10);
9500
+ const { getRecentActivity: getRecentActivity2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
9501
+ return json(getRecentActivity2(limit), 200, port);
9502
+ }
9503
+ const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
9504
+ if (historyMatch && method === "GET") {
9505
+ const id = historyMatch[1];
9506
+ const { getTaskHistory: getTaskHistory2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
9507
+ return json(getTaskHistory2(id), 200, port);
9508
+ }
9509
+ if (path === "/api/webhooks" && method === "GET") {
9510
+ const { listWebhooks: listWebhooks2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
9511
+ return json(listWebhooks2(), 200, port);
9512
+ }
9513
+ if (path === "/api/webhooks" && method === "POST") {
9514
+ try {
9515
+ const body = await req.json();
9516
+ if (!body.url)
9517
+ return json({ error: "Missing url" }, 400, port);
9518
+ const { createWebhook: createWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
9519
+ return json(createWebhook2(body), 201, port);
9520
+ } catch (e) {
9521
+ return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
9522
+ }
9523
+ }
9524
+ const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
9525
+ if (webhookMatch && method === "DELETE") {
9526
+ const { deleteWebhook: deleteWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
9527
+ const deleted = deleteWebhook2(webhookMatch[1]);
9528
+ return json(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404, port);
9529
+ }
9530
+ if (path === "/api/templates" && method === "GET") {
9531
+ const { listTemplates: listTemplates2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
9532
+ return json(listTemplates2(), 200, port);
9533
+ }
9534
+ if (path === "/api/templates" && method === "POST") {
9535
+ try {
9536
+ const body = await req.json();
9537
+ if (!body.name || !body.title_pattern)
9538
+ return json({ error: "Missing name or title_pattern" }, 400, port);
9539
+ const { createTemplate: createTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
9540
+ return json(createTemplate2(body), 201, port);
9541
+ } catch (e) {
9542
+ return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
9543
+ }
9544
+ }
9545
+ const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
9546
+ if (templateMatch && method === "DELETE") {
9547
+ const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
9548
+ const deleted = deleteTemplate2(templateMatch[1]);
9549
+ return json(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404, port);
9550
+ }
9159
9551
  if (path === "/api/plans" && method === "GET") {
9160
9552
  const projectId = url.searchParams.get("project_id") || undefined;
9161
9553
  const plans = listPlans(projectId);
@@ -10448,7 +10840,7 @@ function formatTaskLine(t) {
10448
10840
  return `${chalk.dim(t.id.slice(0, 8))} ${statusFn(t.status.padEnd(11))} ${priorityFn(t.priority.padEnd(8))} ${t.title}${assigned}${lock}${tags}${plan}`;
10449
10841
  }
10450
10842
  program2.name("todos").description("Universal task management for AI coding agents").version(getPackageVersion()).option("--project <path>", "Project path").option("--json", "Output as JSON").option("--agent <name>", "Agent name").option("--session <id>", "Session ID");
10451
- program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").action((title, opts) => {
10843
+ program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("--estimated <minutes>", "Estimated time in minutes").option("--approval", "Require approval before completion").action((title, opts) => {
10452
10844
  const globalOpts = program2.opts();
10453
10845
  const projectId = autoProject(globalOpts);
10454
10846
  opts.tags = opts.tags || opts.tag;
@@ -10483,7 +10875,9 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
10483
10875
  agent_id: globalOpts.agent,
10484
10876
  session_id: globalOpts.session,
10485
10877
  project_id: projectId,
10486
- working_dir: process.cwd()
10878
+ working_dir: process.cwd(),
10879
+ estimated_minutes: opts.estimated ? parseInt(opts.estimated, 10) : undefined,
10880
+ requires_approval: opts.approval || false
10487
10881
  });
10488
10882
  if (globalOpts.json) {
10489
10883
  output(task, true);
@@ -10611,6 +11005,12 @@ program2.command("show <id>").description("Show full task details").action((id)
10611
11005
  console.log(` ${chalk.dim("Session:")} ${task.session_id}`);
10612
11006
  if (task.locked_by)
10613
11007
  console.log(` ${chalk.dim("Locked:")} ${task.locked_by} (at ${task.locked_at})`);
11008
+ if (task.requires_approval) {
11009
+ const approvalStatus = task.approved_by ? chalk.green(`approved by ${task.approved_by}`) : chalk.yellow("pending approval");
11010
+ console.log(` ${chalk.dim("Approval:")} ${approvalStatus}`);
11011
+ }
11012
+ if (task.estimated_minutes)
11013
+ console.log(` ${chalk.dim("Estimate:")} ${task.estimated_minutes} minutes`);
10614
11014
  if (task.project_id)
10615
11015
  console.log(` ${chalk.dim("Project:")} ${task.project_id}`);
10616
11016
  if (task.plan_id)
@@ -10655,7 +11055,29 @@ program2.command("show <id>").description("Show full task details").action((id)
10655
11055
  }
10656
11056
  }
10657
11057
  });
10658
- program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list").option("--task-list <id>", "Move to a task list (alias for --list)").action((id, opts) => {
11058
+ program2.command("history <id>").description("Show change history for a task (audit log)").action((id) => {
11059
+ const globalOpts = program2.opts();
11060
+ const resolvedId = resolveTaskId(id);
11061
+ const { getTaskHistory: getTaskHistory2 } = (init_audit(), __toCommonJS(exports_audit));
11062
+ const history = getTaskHistory2(resolvedId);
11063
+ if (globalOpts.json) {
11064
+ output(history, true);
11065
+ return;
11066
+ }
11067
+ if (history.length === 0) {
11068
+ console.log(chalk.dim("No history for this task."));
11069
+ return;
11070
+ }
11071
+ console.log(chalk.bold(`${history.length} change(s):
11072
+ `));
11073
+ for (const h of history) {
11074
+ const agent = h.agent_id ? chalk.cyan(` by ${h.agent_id}`) : "";
11075
+ const field = h.field ? chalk.yellow(` ${h.field}`) : "";
11076
+ const change = h.old_value && h.new_value ? ` ${chalk.red(h.old_value)} \u2192 ${chalk.green(h.new_value)}` : h.new_value ? ` \u2192 ${chalk.green(h.new_value)}` : "";
11077
+ console.log(` ${chalk.dim(h.created_at)} ${chalk.bold(h.action)}${field}${change}${agent}`);
11078
+ }
11079
+ });
11080
+ program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list").option("--task-list <id>", "Move to a task list (alias for --list)").option("--estimated <minutes>", "Estimated time in minutes").option("--approval", "Require approval before completion").action((id, opts) => {
10659
11081
  const globalOpts = program2.opts();
10660
11082
  opts.tags = opts.tags || opts.tag;
10661
11083
  opts.list = opts.list || opts.taskList;
@@ -10684,7 +11106,9 @@ program2.command("update <id>").description("Update a task").option("--title <te
10684
11106
  priority: opts.priority,
10685
11107
  assigned_to: opts.assign,
10686
11108
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
10687
- task_list_id: taskListId
11109
+ task_list_id: taskListId,
11110
+ estimated_minutes: opts.estimated !== undefined ? parseInt(opts.estimated, 10) : undefined,
11111
+ requires_approval: opts.approval !== undefined ? true : undefined
10688
11112
  });
10689
11113
  } catch (e) {
10690
11114
  handleError(e);
@@ -10712,6 +11136,34 @@ program2.command("done <id>").description("Mark a task as completed").action((id
10712
11136
  console.log(formatTaskLine(task));
10713
11137
  }
10714
11138
  });
11139
+ program2.command("approve <id>").description("Approve a task that requires approval").action((id) => {
11140
+ const globalOpts = program2.opts();
11141
+ const resolvedId = resolveTaskId(id);
11142
+ const task = getTask(resolvedId);
11143
+ if (!task) {
11144
+ console.error(chalk.red(`Task not found: ${id}`));
11145
+ process.exit(1);
11146
+ }
11147
+ if (!task.requires_approval) {
11148
+ console.log(chalk.yellow("This task does not require approval."));
11149
+ return;
11150
+ }
11151
+ if (task.approved_by) {
11152
+ console.log(chalk.yellow(`Already approved by ${task.approved_by}.`));
11153
+ return;
11154
+ }
11155
+ try {
11156
+ const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version: task.version });
11157
+ if (globalOpts.json) {
11158
+ output(updated, true);
11159
+ } else {
11160
+ console.log(chalk.green(`Task approved by ${globalOpts.agent || "cli"}:`));
11161
+ console.log(formatTaskLine(updated));
11162
+ }
11163
+ } catch (e) {
11164
+ handleError(e);
11165
+ }
11166
+ });
10715
11167
  program2.command("start <id>").description("Claim, lock, and start a task").action((id) => {
10716
11168
  const globalOpts = program2.opts();
10717
11169
  const agentId = globalOpts.agent || "cli";
@@ -10919,6 +11371,76 @@ program2.command("plans").description("List and manage plans").option("--add <na
10919
11371
  console.log(`${chalk.dim(p.id.slice(0, 8))} ${chalk.bold(p.name)} ${chalk.cyan(`[${p.status}]`)}${desc}`);
10920
11372
  }
10921
11373
  });
11374
+ program2.command("templates").description("List and manage task templates").option("--add <name>", "Create a template").option("--title <pattern>", "Title pattern (with --add)").option("-d, --description <text>", "Default description").option("-p, --priority <level>", "Default priority").option("-t, --tags <tags>", "Default tags (comma-separated)").option("--delete <id>", "Delete a template").option("--use <id>", "Create a task from a template").action((opts) => {
11375
+ const globalOpts = program2.opts();
11376
+ const { createTemplate: createTemplate2, listTemplates: listTemplates2, deleteTemplate: deleteTemplate2, taskFromTemplate: taskFromTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
11377
+ if (opts.add) {
11378
+ if (!opts.title) {
11379
+ console.error(chalk.red("--title is required with --add"));
11380
+ process.exit(1);
11381
+ }
11382
+ const projectId = autoProject(globalOpts);
11383
+ const template = createTemplate2({
11384
+ name: opts.add,
11385
+ title_pattern: opts.title,
11386
+ description: opts.description,
11387
+ priority: opts.priority || "medium",
11388
+ tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : [],
11389
+ project_id: projectId
11390
+ });
11391
+ if (globalOpts.json) {
11392
+ output(template, true);
11393
+ } else {
11394
+ console.log(chalk.green(`Template created: ${template.id.slice(0, 8)} | ${template.name} | "${template.title_pattern}"`));
11395
+ }
11396
+ return;
11397
+ }
11398
+ if (opts.delete) {
11399
+ const deleted = deleteTemplate2(opts.delete);
11400
+ if (globalOpts.json) {
11401
+ output({ deleted }, true);
11402
+ } else if (deleted) {
11403
+ console.log(chalk.green("Template deleted."));
11404
+ } else {
11405
+ console.error(chalk.red("Template not found."));
11406
+ process.exit(1);
11407
+ }
11408
+ return;
11409
+ }
11410
+ if (opts.use) {
11411
+ try {
11412
+ const input = taskFromTemplate2(opts.use, {
11413
+ title: opts.title,
11414
+ description: opts.description,
11415
+ priority: opts.priority
11416
+ });
11417
+ const task = createTask({ ...input, project_id: input.project_id || autoProject(globalOpts) });
11418
+ if (globalOpts.json) {
11419
+ output(task, true);
11420
+ } else {
11421
+ console.log(chalk.green("Task created from template:"));
11422
+ console.log(formatTaskLine(task));
11423
+ }
11424
+ } catch (e) {
11425
+ handleError(e);
11426
+ }
11427
+ return;
11428
+ }
11429
+ const templates = listTemplates2();
11430
+ if (globalOpts.json) {
11431
+ output(templates, true);
11432
+ return;
11433
+ }
11434
+ if (templates.length === 0) {
11435
+ console.log(chalk.dim("No templates."));
11436
+ return;
11437
+ }
11438
+ console.log(chalk.bold(`${templates.length} template(s):
11439
+ `));
11440
+ for (const t of templates) {
11441
+ console.log(` ${chalk.dim(t.id.slice(0, 8))} ${chalk.bold(t.name)} ${chalk.cyan(`"${t.title_pattern}"`)} ${chalk.yellow(t.priority)}`);
11442
+ }
11443
+ });
10922
11444
  program2.command("comment <id> <text>").description("Add a comment to a task").action((id, text) => {
10923
11445
  const globalOpts = program2.opts();
10924
11446
  const resolvedId = resolveTaskId(id);