@hasna/todos 0.9.20 → 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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Hasna Todos</title>
7
- <script type="module" crossorigin src="/assets/index-CpjKnPxm.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-DVzieYOj.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-DWpVlvWb.css">
9
9
  </head>
10
10
  <body>
package/dist/cli/index.js CHANGED
@@ -9495,6 +9495,59 @@ Dashboard not found at: ${dashboardDir}`);
9495
9495
  return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
9496
9496
  }
9497
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
+ }
9498
9551
  if (path === "/api/plans" && method === "GET") {
9499
9552
  const projectId = url.searchParams.get("project_id") || undefined;
9500
9553
  const plans = listPlans(projectId);
@@ -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";
@@ -852,18 +997,8 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
852
997
  }
853
998
  }
854
999
 
855
- // src/db/audit.ts
856
- init_database();
857
- function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
858
- const d = db || getDatabase();
859
- const id = uuid();
860
- const timestamp = now();
861
- d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at)
862
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, oldValue ?? null, newValue ?? null, agentId || null, timestamp]);
863
- return { id, task_id: taskId, action, field: field || null, old_value: oldValue ?? null, new_value: newValue ?? null, agent_id: agentId || null, created_at: timestamp };
864
- }
865
-
866
1000
  // src/db/tasks.ts
1001
+ init_audit();
867
1002
  function rowToTask(row) {
868
1003
  return {
869
1004
  ...row,
@@ -1563,6 +1698,59 @@ Dashboard not found at: ${dashboardDir}`);
1563
1698
  return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
1564
1699
  }
1565
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
+ }
1566
1754
  if (path === "/api/plans" && method === "GET") {
1567
1755
  const projectId = url.searchParams.get("project_id") || undefined;
1568
1756
  const plans = listPlans(projectId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.9.20",
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",