@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.
@@ -646,6 +646,33 @@ var init_projects = __esm(() => {
646
646
  init_database();
647
647
  });
648
648
 
649
+ // src/db/audit.ts
650
+ var exports_audit = {};
651
+ __export(exports_audit, {
652
+ logTaskChange: () => logTaskChange,
653
+ getTaskHistory: () => getTaskHistory,
654
+ getRecentActivity: () => getRecentActivity
655
+ });
656
+ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
657
+ const d = db || getDatabase();
658
+ const id = uuid();
659
+ const timestamp = now();
660
+ d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
661
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
662
+ return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
663
+ }
664
+ function getTaskHistory(taskId, db) {
665
+ const d = db || getDatabase();
666
+ return d.query("SELECT * FROM task_history WHERE task_id = ? ORDER BY created_at DESC").all(taskId);
667
+ }
668
+ function getRecentActivity(limit = 50, db) {
669
+ const d = db || getDatabase();
670
+ return d.query("SELECT * FROM task_history ORDER BY created_at DESC LIMIT ?").all(limit);
671
+ }
672
+ var init_audit = __esm(() => {
673
+ init_database();
674
+ });
675
+
649
676
  // src/db/agents.ts
650
677
  var exports_agents = {};
651
678
  __export(exports_agents, {
@@ -746,6 +773,124 @@ var init_agents = __esm(() => {
746
773
  init_database();
747
774
  });
748
775
 
776
+ // src/db/webhooks.ts
777
+ var exports_webhooks = {};
778
+ __export(exports_webhooks, {
779
+ listWebhooks: () => listWebhooks,
780
+ getWebhook: () => getWebhook,
781
+ dispatchWebhook: () => dispatchWebhook,
782
+ deleteWebhook: () => deleteWebhook,
783
+ createWebhook: () => createWebhook
784
+ });
785
+ function rowToWebhook(row) {
786
+ return { ...row, events: JSON.parse(row.events || "[]"), active: !!row.active };
787
+ }
788
+ function createWebhook(input, db) {
789
+ const d = db || getDatabase();
790
+ const id = uuid();
791
+ d.run(`INSERT INTO webhooks (id, url, events, secret, created_at) VALUES (?, ?, ?, ?, ?)`, [id, input.url, JSON.stringify(input.events || []), input.secret || null, now()]);
792
+ return getWebhook(id, d);
793
+ }
794
+ function getWebhook(id, db) {
795
+ const d = db || getDatabase();
796
+ const row = d.query("SELECT * FROM webhooks WHERE id = ?").get(id);
797
+ return row ? rowToWebhook(row) : null;
798
+ }
799
+ function listWebhooks(db) {
800
+ const d = db || getDatabase();
801
+ return d.query("SELECT * FROM webhooks ORDER BY created_at DESC").all().map(rowToWebhook);
802
+ }
803
+ function deleteWebhook(id, db) {
804
+ const d = db || getDatabase();
805
+ return d.run("DELETE FROM webhooks WHERE id = ?", [id]).changes > 0;
806
+ }
807
+ async function dispatchWebhook(event, payload, db) {
808
+ const webhooks = listWebhooks(db).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
809
+ for (const wh of webhooks) {
810
+ try {
811
+ const body = JSON.stringify({ event, payload, timestamp: now() });
812
+ const headers = { "Content-Type": "application/json" };
813
+ if (wh.secret) {
814
+ const encoder = new TextEncoder;
815
+ const key = await crypto.subtle.importKey("raw", encoder.encode(wh.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
816
+ const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(body));
817
+ headers["X-Webhook-Signature"] = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
818
+ }
819
+ fetch(wh.url, { method: "POST", headers, body }).catch(() => {});
820
+ } catch {}
821
+ }
822
+ }
823
+ var init_webhooks = __esm(() => {
824
+ init_database();
825
+ });
826
+
827
+ // src/db/templates.ts
828
+ var exports_templates = {};
829
+ __export(exports_templates, {
830
+ taskFromTemplate: () => taskFromTemplate,
831
+ listTemplates: () => listTemplates,
832
+ getTemplate: () => getTemplate,
833
+ deleteTemplate: () => deleteTemplate,
834
+ createTemplate: () => createTemplate
835
+ });
836
+ function rowToTemplate(row) {
837
+ return {
838
+ ...row,
839
+ tags: JSON.parse(row.tags || "[]"),
840
+ metadata: JSON.parse(row.metadata || "{}"),
841
+ priority: row.priority || "medium"
842
+ };
843
+ }
844
+ function createTemplate(input, db) {
845
+ const d = db || getDatabase();
846
+ const id = uuid();
847
+ d.run(`INSERT INTO task_templates (id, name, title_pattern, description, priority, tags, project_id, plan_id, metadata, created_at)
848
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
849
+ id,
850
+ input.name,
851
+ input.title_pattern,
852
+ input.description || null,
853
+ input.priority || "medium",
854
+ JSON.stringify(input.tags || []),
855
+ input.project_id || null,
856
+ input.plan_id || null,
857
+ JSON.stringify(input.metadata || {}),
858
+ now()
859
+ ]);
860
+ return getTemplate(id, d);
861
+ }
862
+ function getTemplate(id, db) {
863
+ const d = db || getDatabase();
864
+ const row = d.query("SELECT * FROM task_templates WHERE id = ?").get(id);
865
+ return row ? rowToTemplate(row) : null;
866
+ }
867
+ function listTemplates(db) {
868
+ const d = db || getDatabase();
869
+ return d.query("SELECT * FROM task_templates ORDER BY name").all().map(rowToTemplate);
870
+ }
871
+ function deleteTemplate(id, db) {
872
+ const d = db || getDatabase();
873
+ return d.run("DELETE FROM task_templates WHERE id = ?", [id]).changes > 0;
874
+ }
875
+ function taskFromTemplate(templateId, overrides = {}, db) {
876
+ const t = getTemplate(templateId, db);
877
+ if (!t)
878
+ throw new Error(`Template not found: ${templateId}`);
879
+ return {
880
+ title: overrides.title || t.title_pattern,
881
+ description: overrides.description ?? t.description ?? undefined,
882
+ priority: overrides.priority ?? t.priority,
883
+ tags: overrides.tags ?? t.tags,
884
+ project_id: overrides.project_id ?? t.project_id ?? undefined,
885
+ plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
886
+ metadata: overrides.metadata ?? t.metadata,
887
+ ...overrides
888
+ };
889
+ }
890
+ var init_templates = __esm(() => {
891
+ init_database();
892
+ });
893
+
749
894
  // src/server/serve.ts
750
895
  import { existsSync as existsSync4 } from "fs";
751
896
  import { join as join3, dirname as dirname2, extname } from "path";
@@ -853,6 +998,7 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
853
998
  }
854
999
 
855
1000
  // src/db/tasks.ts
1001
+ init_audit();
856
1002
  function rowToTask(row) {
857
1003
  return {
858
1004
  ...row,
@@ -1076,6 +1222,17 @@ function updateTask(id, input, db) {
1076
1222
  if (input.tags !== undefined) {
1077
1223
  replaceTaskTags(id, input.tags, d);
1078
1224
  }
1225
+ const agentId = task.assigned_to || task.agent_id || null;
1226
+ if (input.status !== undefined && input.status !== task.status)
1227
+ logTaskChange(id, "update", "status", task.status, input.status, agentId, d);
1228
+ if (input.priority !== undefined && input.priority !== task.priority)
1229
+ logTaskChange(id, "update", "priority", task.priority, input.priority, agentId, d);
1230
+ if (input.title !== undefined && input.title !== task.title)
1231
+ logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
1232
+ if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
1233
+ logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
1234
+ if (input.approved_by !== undefined)
1235
+ logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
1079
1236
  return getTask(id, d);
1080
1237
  }
1081
1238
  function deleteTask(id, db) {
@@ -1097,6 +1254,7 @@ function startTask(id, agentId, db) {
1097
1254
  throw new LockError(id, current.locked_by);
1098
1255
  }
1099
1256
  }
1257
+ logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
1100
1258
  return getTask(id, d);
1101
1259
  }
1102
1260
  function completeTask(id, agentId, db) {
@@ -1111,6 +1269,7 @@ function completeTask(id, agentId, db) {
1111
1269
  const timestamp = now();
1112
1270
  d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
1113
1271
  WHERE id = ?`, [timestamp, timestamp, id]);
1272
+ logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
1114
1273
  return getTask(id, d);
1115
1274
  }
1116
1275
 
@@ -1539,6 +1698,59 @@ Dashboard not found at: ${dashboardDir}`);
1539
1698
  return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
1540
1699
  }
1541
1700
  }
1701
+ if (path === "/api/activity" && method === "GET") {
1702
+ const limit = parseInt(url.searchParams.get("limit") || "50", 10);
1703
+ const { getRecentActivity: getRecentActivity2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
1704
+ return json(getRecentActivity2(limit), 200, port);
1705
+ }
1706
+ const historyMatch = path.match(/^\/api\/tasks\/([^/]+)\/history$/);
1707
+ if (historyMatch && method === "GET") {
1708
+ const id = historyMatch[1];
1709
+ const { getTaskHistory: getTaskHistory2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
1710
+ return json(getTaskHistory2(id), 200, port);
1711
+ }
1712
+ if (path === "/api/webhooks" && method === "GET") {
1713
+ const { listWebhooks: listWebhooks2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
1714
+ return json(listWebhooks2(), 200, port);
1715
+ }
1716
+ if (path === "/api/webhooks" && method === "POST") {
1717
+ try {
1718
+ const body = await req.json();
1719
+ if (!body.url)
1720
+ return json({ error: "Missing url" }, 400, port);
1721
+ const { createWebhook: createWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
1722
+ return json(createWebhook2(body), 201, port);
1723
+ } catch (e) {
1724
+ return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
1725
+ }
1726
+ }
1727
+ const webhookMatch = path.match(/^\/api\/webhooks\/([^/]+)$/);
1728
+ if (webhookMatch && method === "DELETE") {
1729
+ const { deleteWebhook: deleteWebhook2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
1730
+ const deleted = deleteWebhook2(webhookMatch[1]);
1731
+ return json(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404, port);
1732
+ }
1733
+ if (path === "/api/templates" && method === "GET") {
1734
+ const { listTemplates: listTemplates2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
1735
+ return json(listTemplates2(), 200, port);
1736
+ }
1737
+ if (path === "/api/templates" && method === "POST") {
1738
+ try {
1739
+ const body = await req.json();
1740
+ if (!body.name || !body.title_pattern)
1741
+ return json({ error: "Missing name or title_pattern" }, 400, port);
1742
+ const { createTemplate: createTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
1743
+ return json(createTemplate2(body), 201, port);
1744
+ } catch (e) {
1745
+ return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
1746
+ }
1747
+ }
1748
+ const templateMatch = path.match(/^\/api\/templates\/([^/]+)$/);
1749
+ if (templateMatch && method === "DELETE") {
1750
+ const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
1751
+ const deleted = deleteTemplate2(templateMatch[1]);
1752
+ return json(deleted ? { success: true } : { error: "Not found" }, deleted ? 200 : 404, port);
1753
+ }
1542
1754
  if (path === "/api/plans" && method === "GET") {
1543
1755
  const projectId = url.searchParams.get("project_id") || undefined;
1544
1756
  const plans = listPlans(projectId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.9.19",
3
+ "version": "0.9.21",
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",