@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/dashboard/dist/assets/{index-CpjKnPxm.js → index-DVzieYOj.js} +66 -61
- package/dashboard/dist/index.html +1 -1
- package/dist/cli/index.js +530 -8
- package/dist/index.js +31 -17
- package/dist/mcp/index.js +3099 -2763
- package/dist/server/index.js +212 -0
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -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);
|