@hasna/todos 0.9.18 → 0.9.20

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)
@@ -2138,29 +2153,20 @@ function runMigrations(db) {
2138
2153
  const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
2139
2154
  const currentLevel = result?.max_id ?? 0;
2140
2155
  for (let i = currentLevel;i < MIGRATIONS.length; i++) {
2141
- db.exec(MIGRATIONS[i]);
2156
+ try {
2157
+ db.exec(MIGRATIONS[i]);
2158
+ } catch {}
2142
2159
  }
2143
2160
  } catch {
2144
2161
  for (const migration of MIGRATIONS) {
2145
- db.exec(migration);
2162
+ try {
2163
+ db.exec(migration);
2164
+ } catch {}
2146
2165
  }
2147
2166
  }
2148
- ensureTableMigrations(db);
2167
+ ensureSchema(db);
2149
2168
  }
2150
- function ensureTableMigrations(db) {
2151
- try {
2152
- const hasAgents = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='agents'").get();
2153
- if (!hasAgents) {
2154
- db.exec(MIGRATIONS[4]);
2155
- }
2156
- } catch {}
2157
- try {
2158
- db.query("SELECT task_prefix FROM projects LIMIT 0").get();
2159
- } catch {
2160
- try {
2161
- db.exec(MIGRATIONS[5]);
2162
- } catch {}
2163
- }
2169
+ function ensureSchema(db) {
2164
2170
  const ensureColumn = (table, column, type) => {
2165
2171
  try {
2166
2172
  db.query(`SELECT ${column} FROM ${table} LIMIT 0`).get();
@@ -2170,6 +2176,76 @@ function ensureTableMigrations(db) {
2170
2176
  } catch {}
2171
2177
  }
2172
2178
  };
2179
+ const ensureTable = (name, sql) => {
2180
+ try {
2181
+ const exists = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
2182
+ if (!exists)
2183
+ db.exec(sql);
2184
+ } catch {}
2185
+ };
2186
+ const ensureIndex = (sql) => {
2187
+ try {
2188
+ db.exec(sql);
2189
+ } catch {}
2190
+ };
2191
+ ensureTable("agents", `
2192
+ CREATE TABLE agents (
2193
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT,
2194
+ role TEXT DEFAULT 'agent', permissions TEXT DEFAULT '["*"]',
2195
+ metadata TEXT DEFAULT '{}',
2196
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2197
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
2198
+ )`);
2199
+ ensureTable("task_lists", `
2200
+ CREATE TABLE task_lists (
2201
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2202
+ slug TEXT NOT NULL, name TEXT NOT NULL, description TEXT,
2203
+ metadata TEXT DEFAULT '{}',
2204
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2205
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
2206
+ UNIQUE(project_id, slug)
2207
+ )`);
2208
+ ensureTable("plans", `
2209
+ CREATE TABLE plans (
2210
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
2211
+ task_list_id TEXT, agent_id TEXT,
2212
+ name TEXT NOT NULL, description TEXT,
2213
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
2214
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2215
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2216
+ )`);
2217
+ ensureTable("task_tags", `
2218
+ CREATE TABLE task_tags (
2219
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2220
+ tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
2221
+ )`);
2222
+ ensureTable("task_history", `
2223
+ CREATE TABLE task_history (
2224
+ id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2225
+ action TEXT NOT NULL, field TEXT, old_value TEXT, new_value TEXT, agent_id TEXT,
2226
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2227
+ )`);
2228
+ ensureTable("webhooks", `
2229
+ CREATE TABLE webhooks (
2230
+ id TEXT PRIMARY KEY, url TEXT NOT NULL, events TEXT NOT NULL DEFAULT '[]',
2231
+ secret TEXT, active INTEGER NOT NULL DEFAULT 1,
2232
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2233
+ )`);
2234
+ ensureTable("task_templates", `
2235
+ CREATE TABLE task_templates (
2236
+ id TEXT PRIMARY KEY, name TEXT NOT NULL, title_pattern TEXT NOT NULL,
2237
+ description TEXT, priority TEXT DEFAULT 'medium', tags TEXT DEFAULT '[]',
2238
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2239
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
2240
+ metadata TEXT DEFAULT '{}',
2241
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2242
+ )`);
2243
+ ensureColumn("projects", "task_list_id", "TEXT");
2244
+ ensureColumn("projects", "task_prefix", "TEXT");
2245
+ ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
2246
+ ensureColumn("tasks", "plan_id", "TEXT REFERENCES plans(id) ON DELETE SET NULL");
2247
+ ensureColumn("tasks", "task_list_id", "TEXT REFERENCES task_lists(id) ON DELETE SET NULL");
2248
+ ensureColumn("tasks", "short_id", "TEXT");
2173
2249
  ensureColumn("tasks", "due_at", "TEXT");
2174
2250
  ensureColumn("tasks", "estimated_minutes", "INTEGER");
2175
2251
  ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
@@ -2179,6 +2255,21 @@ function ensureTableMigrations(db) {
2179
2255
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
2180
2256
  ensureColumn("plans", "task_list_id", "TEXT");
2181
2257
  ensureColumn("plans", "agent_id", "TEXT");
2258
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
2259
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id)");
2260
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_due_at ON tasks(due_at)");
2261
+ ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL");
2262
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name)");
2263
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_project ON task_lists(project_id)");
2264
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug)");
2265
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag)");
2266
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_task ON task_tags(task_id)");
2267
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
2268
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
2269
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
2270
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
2271
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
2272
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
2182
2273
  }
2183
2274
  function backfillTaskTags(db) {
2184
2275
  try {
@@ -2819,6 +2910,33 @@ var init_completion_guard = __esm(() => {
2819
2910
  init_projects();
2820
2911
  });
2821
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
+
2822
2940
  // src/db/tasks.ts
2823
2941
  function rowToTask(row) {
2824
2942
  return {
@@ -3069,6 +3187,17 @@ function updateTask(id, input, db) {
3069
3187
  if (input.tags !== undefined) {
3070
3188
  replaceTaskTags(id, input.tags, d);
3071
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);
3072
3201
  return getTask(id, d);
3073
3202
  }
3074
3203
  function deleteTask(id, db) {
@@ -3090,6 +3219,7 @@ function startTask(id, agentId, db) {
3090
3219
  throw new LockError(id, current.locked_by);
3091
3220
  }
3092
3221
  }
3222
+ logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
3093
3223
  return getTask(id, d);
3094
3224
  }
3095
3225
  function completeTask(id, agentId, db) {
@@ -3104,6 +3234,7 @@ function completeTask(id, agentId, db) {
3104
3234
  const timestamp = now();
3105
3235
  d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
3106
3236
  WHERE id = ?`, [timestamp, timestamp, id]);
3237
+ logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
3107
3238
  return getTask(id, d);
3108
3239
  }
3109
3240
  function lockTask(id, agentId, db) {
@@ -3184,6 +3315,7 @@ var init_tasks = __esm(() => {
3184
3315
  init_database();
3185
3316
  init_projects();
3186
3317
  init_completion_guard();
3318
+ init_audit();
3187
3319
  });
3188
3320
 
3189
3321
  // src/db/agents.ts
@@ -4020,6 +4152,73 @@ var init_sync = __esm(() => {
4020
4152
  init_config();
4021
4153
  });
4022
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
+
4023
4222
  // node_modules/zod/v3/helpers/util.js
4024
4223
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
4025
4224
  const t = typeof data;
@@ -7986,6 +8185,57 @@ var init_zod = __esm(() => {
7986
8185
  init_external();
7987
8186
  });
7988
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
+
7989
8239
  // src/mcp/index.ts
7990
8240
  var exports_mcp = {};
7991
8241
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -8094,7 +8344,9 @@ var init_mcp = __esm(() => {
8094
8344
  plan_id: exports_external.string().optional().describe("Plan ID to assign task to"),
8095
8345
  task_list_id: exports_external.string().optional().describe("Task list ID to assign task to"),
8096
8346
  tags: exports_external.array(exports_external.string()).optional().describe("Task tags"),
8097
- 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")
8098
8350
  }, async (params) => {
8099
8351
  try {
8100
8352
  const resolved = { ...params };
@@ -8376,12 +8628,16 @@ ${text}` }] };
8376
8628
  name: exports_external.string().describe("Plan name"),
8377
8629
  project_id: exports_external.string().optional().describe("Project ID"),
8378
8630
  description: exports_external.string().optional().describe("Plan description"),
8379
- 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")
8380
8634
  }, async (params) => {
8381
8635
  try {
8382
8636
  const resolved = { ...params };
8383
8637
  if (resolved.project_id)
8384
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");
8385
8641
  const plan = createPlan(resolved);
8386
8642
  return {
8387
8643
  content: [{
@@ -8442,11 +8698,16 @@ ${text}` }] };
8442
8698
  id: exports_external.string().describe("Plan ID (full or partial)"),
8443
8699
  name: exports_external.string().optional().describe("New name"),
8444
8700
  description: exports_external.string().optional().describe("New description"),
8445
- 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")
8446
8704
  }, async ({ id, ...rest }) => {
8447
8705
  try {
8448
8706
  const resolvedId = resolveId(id, "plans");
8449
- 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);
8450
8711
  return {
8451
8712
  content: [{
8452
8713
  type: "text",
@@ -8717,6 +8978,160 @@ Slug: ${list.slug}`
8717
8978
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8718
8979
  }
8719
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
+ });
8720
9135
  server.resource("tasks", "todos://tasks", { description: "All active tasks", mimeType: "application/json" }, async () => {
8721
9136
  const tasks = listTasks({ status: ["pending", "in_progress"] });
8722
9137
  return { contents: [{ uri: "todos://tasks", text: JSON.stringify(tasks, null, 2), mimeType: "application/json" }] };
@@ -10372,7 +10787,7 @@ function formatTaskLine(t) {
10372
10787
  return `${chalk.dim(t.id.slice(0, 8))} ${statusFn(t.status.padEnd(11))} ${priorityFn(t.priority.padEnd(8))} ${t.title}${assigned}${lock}${tags}${plan}`;
10373
10788
  }
10374
10789
  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");
10375
- 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) => {
10790
+ 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) => {
10376
10791
  const globalOpts = program2.opts();
10377
10792
  const projectId = autoProject(globalOpts);
10378
10793
  opts.tags = opts.tags || opts.tag;
@@ -10407,7 +10822,9 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
10407
10822
  agent_id: globalOpts.agent,
10408
10823
  session_id: globalOpts.session,
10409
10824
  project_id: projectId,
10410
- working_dir: process.cwd()
10825
+ working_dir: process.cwd(),
10826
+ estimated_minutes: opts.estimated ? parseInt(opts.estimated, 10) : undefined,
10827
+ requires_approval: opts.approval || false
10411
10828
  });
10412
10829
  if (globalOpts.json) {
10413
10830
  output(task, true);
@@ -10535,6 +10952,12 @@ program2.command("show <id>").description("Show full task details").action((id)
10535
10952
  console.log(` ${chalk.dim("Session:")} ${task.session_id}`);
10536
10953
  if (task.locked_by)
10537
10954
  console.log(` ${chalk.dim("Locked:")} ${task.locked_by} (at ${task.locked_at})`);
10955
+ if (task.requires_approval) {
10956
+ const approvalStatus = task.approved_by ? chalk.green(`approved by ${task.approved_by}`) : chalk.yellow("pending approval");
10957
+ console.log(` ${chalk.dim("Approval:")} ${approvalStatus}`);
10958
+ }
10959
+ if (task.estimated_minutes)
10960
+ console.log(` ${chalk.dim("Estimate:")} ${task.estimated_minutes} minutes`);
10538
10961
  if (task.project_id)
10539
10962
  console.log(` ${chalk.dim("Project:")} ${task.project_id}`);
10540
10963
  if (task.plan_id)
@@ -10579,7 +11002,29 @@ program2.command("show <id>").description("Show full task details").action((id)
10579
11002
  }
10580
11003
  }
10581
11004
  });
10582
- 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) => {
11005
+ program2.command("history <id>").description("Show change history for a task (audit log)").action((id) => {
11006
+ const globalOpts = program2.opts();
11007
+ const resolvedId = resolveTaskId(id);
11008
+ const { getTaskHistory: getTaskHistory2 } = (init_audit(), __toCommonJS(exports_audit));
11009
+ const history = getTaskHistory2(resolvedId);
11010
+ if (globalOpts.json) {
11011
+ output(history, true);
11012
+ return;
11013
+ }
11014
+ if (history.length === 0) {
11015
+ console.log(chalk.dim("No history for this task."));
11016
+ return;
11017
+ }
11018
+ console.log(chalk.bold(`${history.length} change(s):
11019
+ `));
11020
+ for (const h of history) {
11021
+ const agent = h.agent_id ? chalk.cyan(` by ${h.agent_id}`) : "";
11022
+ const field = h.field ? chalk.yellow(` ${h.field}`) : "";
11023
+ 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)}` : "";
11024
+ console.log(` ${chalk.dim(h.created_at)} ${chalk.bold(h.action)}${field}${change}${agent}`);
11025
+ }
11026
+ });
11027
+ 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) => {
10583
11028
  const globalOpts = program2.opts();
10584
11029
  opts.tags = opts.tags || opts.tag;
10585
11030
  opts.list = opts.list || opts.taskList;
@@ -10608,7 +11053,9 @@ program2.command("update <id>").description("Update a task").option("--title <te
10608
11053
  priority: opts.priority,
10609
11054
  assigned_to: opts.assign,
10610
11055
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
10611
- task_list_id: taskListId
11056
+ task_list_id: taskListId,
11057
+ estimated_minutes: opts.estimated !== undefined ? parseInt(opts.estimated, 10) : undefined,
11058
+ requires_approval: opts.approval !== undefined ? true : undefined
10612
11059
  });
10613
11060
  } catch (e) {
10614
11061
  handleError(e);
@@ -10636,6 +11083,34 @@ program2.command("done <id>").description("Mark a task as completed").action((id
10636
11083
  console.log(formatTaskLine(task));
10637
11084
  }
10638
11085
  });
11086
+ program2.command("approve <id>").description("Approve a task that requires approval").action((id) => {
11087
+ const globalOpts = program2.opts();
11088
+ const resolvedId = resolveTaskId(id);
11089
+ const task = getTask(resolvedId);
11090
+ if (!task) {
11091
+ console.error(chalk.red(`Task not found: ${id}`));
11092
+ process.exit(1);
11093
+ }
11094
+ if (!task.requires_approval) {
11095
+ console.log(chalk.yellow("This task does not require approval."));
11096
+ return;
11097
+ }
11098
+ if (task.approved_by) {
11099
+ console.log(chalk.yellow(`Already approved by ${task.approved_by}.`));
11100
+ return;
11101
+ }
11102
+ try {
11103
+ const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version: task.version });
11104
+ if (globalOpts.json) {
11105
+ output(updated, true);
11106
+ } else {
11107
+ console.log(chalk.green(`Task approved by ${globalOpts.agent || "cli"}:`));
11108
+ console.log(formatTaskLine(updated));
11109
+ }
11110
+ } catch (e) {
11111
+ handleError(e);
11112
+ }
11113
+ });
10639
11114
  program2.command("start <id>").description("Claim, lock, and start a task").action((id) => {
10640
11115
  const globalOpts = program2.opts();
10641
11116
  const agentId = globalOpts.agent || "cli";
@@ -10843,6 +11318,76 @@ program2.command("plans").description("List and manage plans").option("--add <na
10843
11318
  console.log(`${chalk.dim(p.id.slice(0, 8))} ${chalk.bold(p.name)} ${chalk.cyan(`[${p.status}]`)}${desc}`);
10844
11319
  }
10845
11320
  });
11321
+ 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) => {
11322
+ const globalOpts = program2.opts();
11323
+ const { createTemplate: createTemplate2, listTemplates: listTemplates2, deleteTemplate: deleteTemplate2, taskFromTemplate: taskFromTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
11324
+ if (opts.add) {
11325
+ if (!opts.title) {
11326
+ console.error(chalk.red("--title is required with --add"));
11327
+ process.exit(1);
11328
+ }
11329
+ const projectId = autoProject(globalOpts);
11330
+ const template = createTemplate2({
11331
+ name: opts.add,
11332
+ title_pattern: opts.title,
11333
+ description: opts.description,
11334
+ priority: opts.priority || "medium",
11335
+ tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : [],
11336
+ project_id: projectId
11337
+ });
11338
+ if (globalOpts.json) {
11339
+ output(template, true);
11340
+ } else {
11341
+ console.log(chalk.green(`Template created: ${template.id.slice(0, 8)} | ${template.name} | "${template.title_pattern}"`));
11342
+ }
11343
+ return;
11344
+ }
11345
+ if (opts.delete) {
11346
+ const deleted = deleteTemplate2(opts.delete);
11347
+ if (globalOpts.json) {
11348
+ output({ deleted }, true);
11349
+ } else if (deleted) {
11350
+ console.log(chalk.green("Template deleted."));
11351
+ } else {
11352
+ console.error(chalk.red("Template not found."));
11353
+ process.exit(1);
11354
+ }
11355
+ return;
11356
+ }
11357
+ if (opts.use) {
11358
+ try {
11359
+ const input = taskFromTemplate2(opts.use, {
11360
+ title: opts.title,
11361
+ description: opts.description,
11362
+ priority: opts.priority
11363
+ });
11364
+ const task = createTask({ ...input, project_id: input.project_id || autoProject(globalOpts) });
11365
+ if (globalOpts.json) {
11366
+ output(task, true);
11367
+ } else {
11368
+ console.log(chalk.green("Task created from template:"));
11369
+ console.log(formatTaskLine(task));
11370
+ }
11371
+ } catch (e) {
11372
+ handleError(e);
11373
+ }
11374
+ return;
11375
+ }
11376
+ const templates = listTemplates2();
11377
+ if (globalOpts.json) {
11378
+ output(templates, true);
11379
+ return;
11380
+ }
11381
+ if (templates.length === 0) {
11382
+ console.log(chalk.dim("No templates."));
11383
+ return;
11384
+ }
11385
+ console.log(chalk.bold(`${templates.length} template(s):
11386
+ `));
11387
+ for (const t of templates) {
11388
+ console.log(` ${chalk.dim(t.id.slice(0, 8))} ${chalk.bold(t.name)} ${chalk.cyan(`"${t.title_pattern}"`)} ${chalk.yellow(t.priority)}`);
11389
+ }
11390
+ });
10846
11391
  program2.command("comment <id> <text>").description("Add a comment to a task").action((id, text) => {
10847
11392
  const globalOpts = program2.opts();
10848
11393
  const resolvedId = resolveTaskId(id);