@hasna/todos 0.9.7 → 0.9.9
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-BiBRhfMn.js +311 -0
- package/dashboard/dist/assets/index-gYTrU_vV.css +1 -0
- package/dashboard/dist/index.html +13 -0
- package/dashboard/dist/logo.jpg +0 -0
- package/dist/cli/index.js +468 -29
- package/dist/index.js +23 -7
- package/dist/mcp/index.js +23 -7
- package/dist/server/index.js +1285 -0
- package/package.json +11 -5
package/dist/cli/index.js
CHANGED
|
@@ -2374,6 +2374,11 @@ var init_database = __esm(() => {
|
|
|
2374
2374
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL;
|
|
2375
2375
|
|
|
2376
2376
|
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
2377
|
+
`,
|
|
2378
|
+
`
|
|
2379
|
+
ALTER TABLE tasks ADD COLUMN due_at TEXT;
|
|
2380
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_due_at ON tasks(due_at);
|
|
2381
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (7);
|
|
2377
2382
|
`
|
|
2378
2383
|
];
|
|
2379
2384
|
});
|
|
@@ -2458,6 +2463,18 @@ var init_types = __esm(() => {
|
|
|
2458
2463
|
});
|
|
2459
2464
|
|
|
2460
2465
|
// src/db/projects.ts
|
|
2466
|
+
var exports_projects = {};
|
|
2467
|
+
__export(exports_projects, {
|
|
2468
|
+
updateProject: () => updateProject,
|
|
2469
|
+
slugify: () => slugify,
|
|
2470
|
+
nextTaskShortId: () => nextTaskShortId,
|
|
2471
|
+
listProjects: () => listProjects,
|
|
2472
|
+
getProjectByPath: () => getProjectByPath,
|
|
2473
|
+
getProject: () => getProject,
|
|
2474
|
+
ensureProject: () => ensureProject,
|
|
2475
|
+
deleteProject: () => deleteProject,
|
|
2476
|
+
createProject: () => createProject
|
|
2477
|
+
});
|
|
2461
2478
|
function slugify(name) {
|
|
2462
2479
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2463
2480
|
}
|
|
@@ -2528,6 +2545,11 @@ function updateProject(id, input, db) {
|
|
|
2528
2545
|
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
2529
2546
|
return getProject(id, d);
|
|
2530
2547
|
}
|
|
2548
|
+
function deleteProject(id, db) {
|
|
2549
|
+
const d = db || getDatabase();
|
|
2550
|
+
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
2551
|
+
return result.changes > 0;
|
|
2552
|
+
}
|
|
2531
2553
|
function nextTaskShortId(projectId, db) {
|
|
2532
2554
|
const d = db || getDatabase();
|
|
2533
2555
|
const project = getProject(projectId, d);
|
|
@@ -2753,8 +2775,8 @@ function createTask(input, db) {
|
|
|
2753
2775
|
const tags = input.tags || [];
|
|
2754
2776
|
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
2755
2777
|
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
2756
|
-
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at)
|
|
2757
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
|
|
2778
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at)
|
|
2779
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`, [
|
|
2758
2780
|
id,
|
|
2759
2781
|
shortId,
|
|
2760
2782
|
input.project_id || null,
|
|
@@ -2772,7 +2794,8 @@ function createTask(input, db) {
|
|
|
2772
2794
|
JSON.stringify(tags),
|
|
2773
2795
|
JSON.stringify(input.metadata || {}),
|
|
2774
2796
|
timestamp,
|
|
2775
|
-
timestamp
|
|
2797
|
+
timestamp,
|
|
2798
|
+
input.due_at || null
|
|
2776
2799
|
]);
|
|
2777
2800
|
if (tags.length > 0) {
|
|
2778
2801
|
insertTaskTags(id, tags, d);
|
|
@@ -2873,12 +2896,18 @@ function listTasks(filter = {}, db) {
|
|
|
2873
2896
|
params.push(filter.task_list_id);
|
|
2874
2897
|
}
|
|
2875
2898
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2899
|
+
let limitClause = "";
|
|
2900
|
+
if (filter.limit) {
|
|
2901
|
+
limitClause = " LIMIT ?";
|
|
2902
|
+
params.push(filter.limit);
|
|
2903
|
+
if (filter.offset) {
|
|
2904
|
+
limitClause += " OFFSET ?";
|
|
2905
|
+
params.push(filter.offset);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2879
2908
|
const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY
|
|
2880
2909
|
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
2881
|
-
created_at DESC
|
|
2910
|
+
created_at DESC${limitClause}`).all(...params);
|
|
2882
2911
|
return rows.map(rowToTask);
|
|
2883
2912
|
}
|
|
2884
2913
|
function updateTask(id, input, db) {
|
|
@@ -2934,6 +2963,10 @@ function updateTask(id, input, db) {
|
|
|
2934
2963
|
sets.push("task_list_id = ?");
|
|
2935
2964
|
params.push(input.task_list_id);
|
|
2936
2965
|
}
|
|
2966
|
+
if (input.due_at !== undefined) {
|
|
2967
|
+
sets.push("due_at = ?");
|
|
2968
|
+
params.push(input.due_at);
|
|
2969
|
+
}
|
|
2937
2970
|
params.push(id, input.version);
|
|
2938
2971
|
const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
|
|
2939
2972
|
if (result.changes === 0) {
|
|
@@ -3061,6 +3094,15 @@ var init_tasks = __esm(() => {
|
|
|
3061
3094
|
});
|
|
3062
3095
|
|
|
3063
3096
|
// src/db/agents.ts
|
|
3097
|
+
var exports_agents = {};
|
|
3098
|
+
__export(exports_agents, {
|
|
3099
|
+
updateAgentActivity: () => updateAgentActivity,
|
|
3100
|
+
registerAgent: () => registerAgent,
|
|
3101
|
+
listAgents: () => listAgents,
|
|
3102
|
+
getAgentByName: () => getAgentByName,
|
|
3103
|
+
getAgent: () => getAgent,
|
|
3104
|
+
deleteAgent: () => deleteAgent
|
|
3105
|
+
});
|
|
3064
3106
|
function shortUuid() {
|
|
3065
3107
|
return crypto.randomUUID().slice(0, 8);
|
|
3066
3108
|
}
|
|
@@ -3097,6 +3139,14 @@ function listAgents(db) {
|
|
|
3097
3139
|
const d = db || getDatabase();
|
|
3098
3140
|
return d.query("SELECT * FROM agents ORDER BY name").all().map(rowToAgent);
|
|
3099
3141
|
}
|
|
3142
|
+
function updateAgentActivity(id, db) {
|
|
3143
|
+
const d = db || getDatabase();
|
|
3144
|
+
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), id]);
|
|
3145
|
+
}
|
|
3146
|
+
function deleteAgent(id, db) {
|
|
3147
|
+
const d = db || getDatabase();
|
|
3148
|
+
return d.run("DELETE FROM agents WHERE id = ?", [id]).changes > 0;
|
|
3149
|
+
}
|
|
3100
3150
|
var init_agents = __esm(() => {
|
|
3101
3151
|
init_database();
|
|
3102
3152
|
});
|
|
@@ -8544,6 +8594,391 @@ Slug: ${list.slug}`
|
|
|
8544
8594
|
});
|
|
8545
8595
|
});
|
|
8546
8596
|
|
|
8597
|
+
// src/server/serve.ts
|
|
8598
|
+
var exports_serve = {};
|
|
8599
|
+
__export(exports_serve, {
|
|
8600
|
+
startServer: () => startServer
|
|
8601
|
+
});
|
|
8602
|
+
import { existsSync as existsSync6 } from "fs";
|
|
8603
|
+
import { join as join6, dirname as dirname2, extname } from "path";
|
|
8604
|
+
import { fileURLToPath } from "url";
|
|
8605
|
+
function resolveDashboardDir() {
|
|
8606
|
+
const candidates = [];
|
|
8607
|
+
try {
|
|
8608
|
+
const scriptDir = dirname2(fileURLToPath(import.meta.url));
|
|
8609
|
+
candidates.push(join6(scriptDir, "..", "dashboard", "dist"));
|
|
8610
|
+
candidates.push(join6(scriptDir, "..", "..", "dashboard", "dist"));
|
|
8611
|
+
} catch {}
|
|
8612
|
+
if (process.argv[1]) {
|
|
8613
|
+
const mainDir = dirname2(process.argv[1]);
|
|
8614
|
+
candidates.push(join6(mainDir, "..", "dashboard", "dist"));
|
|
8615
|
+
candidates.push(join6(mainDir, "..", "..", "dashboard", "dist"));
|
|
8616
|
+
}
|
|
8617
|
+
candidates.push(join6(process.cwd(), "dashboard", "dist"));
|
|
8618
|
+
for (const candidate of candidates) {
|
|
8619
|
+
if (existsSync6(candidate))
|
|
8620
|
+
return candidate;
|
|
8621
|
+
}
|
|
8622
|
+
return join6(process.cwd(), "dashboard", "dist");
|
|
8623
|
+
}
|
|
8624
|
+
function json(data, status = 200, port) {
|
|
8625
|
+
return new Response(JSON.stringify(data), {
|
|
8626
|
+
status,
|
|
8627
|
+
headers: {
|
|
8628
|
+
"Content-Type": "application/json",
|
|
8629
|
+
"Access-Control-Allow-Origin": port ? `http://localhost:${port}` : "*",
|
|
8630
|
+
...SECURITY_HEADERS
|
|
8631
|
+
}
|
|
8632
|
+
});
|
|
8633
|
+
}
|
|
8634
|
+
function serveStaticFile(filePath) {
|
|
8635
|
+
if (!existsSync6(filePath))
|
|
8636
|
+
return null;
|
|
8637
|
+
const ext = extname(filePath);
|
|
8638
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
8639
|
+
return new Response(Bun.file(filePath), {
|
|
8640
|
+
headers: { "Content-Type": contentType }
|
|
8641
|
+
});
|
|
8642
|
+
}
|
|
8643
|
+
function taskToSummary(task) {
|
|
8644
|
+
return {
|
|
8645
|
+
id: task.id,
|
|
8646
|
+
short_id: task.short_id,
|
|
8647
|
+
title: task.title,
|
|
8648
|
+
description: task.description,
|
|
8649
|
+
status: task.status,
|
|
8650
|
+
priority: task.priority,
|
|
8651
|
+
project_id: task.project_id,
|
|
8652
|
+
plan_id: task.plan_id,
|
|
8653
|
+
task_list_id: task.task_list_id,
|
|
8654
|
+
agent_id: task.agent_id,
|
|
8655
|
+
assigned_to: task.assigned_to,
|
|
8656
|
+
locked_by: task.locked_by,
|
|
8657
|
+
tags: task.tags,
|
|
8658
|
+
version: task.version,
|
|
8659
|
+
created_at: task.created_at,
|
|
8660
|
+
updated_at: task.updated_at,
|
|
8661
|
+
completed_at: task.completed_at,
|
|
8662
|
+
due_at: task.due_at
|
|
8663
|
+
};
|
|
8664
|
+
}
|
|
8665
|
+
async function startServer(port, options) {
|
|
8666
|
+
const shouldOpen = options?.open ?? true;
|
|
8667
|
+
getDatabase();
|
|
8668
|
+
const dashboardDir = resolveDashboardDir();
|
|
8669
|
+
const dashboardExists = existsSync6(dashboardDir);
|
|
8670
|
+
if (!dashboardExists) {
|
|
8671
|
+
console.error(`
|
|
8672
|
+
Dashboard not found at: ${dashboardDir}`);
|
|
8673
|
+
console.error(`Run this to build it:
|
|
8674
|
+
`);
|
|
8675
|
+
console.error(` cd dashboard && bun install && bun run build
|
|
8676
|
+
`);
|
|
8677
|
+
}
|
|
8678
|
+
const server2 = Bun.serve({
|
|
8679
|
+
port,
|
|
8680
|
+
async fetch(req) {
|
|
8681
|
+
const url = new URL(req.url);
|
|
8682
|
+
const path = url.pathname;
|
|
8683
|
+
const method = req.method;
|
|
8684
|
+
if (method === "OPTIONS") {
|
|
8685
|
+
return new Response(null, {
|
|
8686
|
+
headers: {
|
|
8687
|
+
"Access-Control-Allow-Origin": `http://localhost:${port}`,
|
|
8688
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
8689
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
8690
|
+
}
|
|
8691
|
+
});
|
|
8692
|
+
}
|
|
8693
|
+
if (path === "/api/stats" && method === "GET") {
|
|
8694
|
+
const all = listTasks({ limit: 1e4 });
|
|
8695
|
+
const projects = listProjects();
|
|
8696
|
+
const agents = listAgents();
|
|
8697
|
+
return json({
|
|
8698
|
+
total_tasks: all.length,
|
|
8699
|
+
pending: all.filter((t) => t.status === "pending").length,
|
|
8700
|
+
in_progress: all.filter((t) => t.status === "in_progress").length,
|
|
8701
|
+
completed: all.filter((t) => t.status === "completed").length,
|
|
8702
|
+
failed: all.filter((t) => t.status === "failed").length,
|
|
8703
|
+
cancelled: all.filter((t) => t.status === "cancelled").length,
|
|
8704
|
+
projects: projects.length,
|
|
8705
|
+
agents: agents.length
|
|
8706
|
+
}, 200, port);
|
|
8707
|
+
}
|
|
8708
|
+
if (path === "/api/tasks" && method === "GET") {
|
|
8709
|
+
const status = url.searchParams.get("status") || undefined;
|
|
8710
|
+
const projectId = url.searchParams.get("project_id") || undefined;
|
|
8711
|
+
const limitParam = url.searchParams.get("limit");
|
|
8712
|
+
const tasks = listTasks({
|
|
8713
|
+
status,
|
|
8714
|
+
project_id: projectId,
|
|
8715
|
+
limit: limitParam ? parseInt(limitParam, 10) : undefined
|
|
8716
|
+
});
|
|
8717
|
+
return json(tasks.map(taskToSummary), 200, port);
|
|
8718
|
+
}
|
|
8719
|
+
if (path === "/api/tasks" && method === "POST") {
|
|
8720
|
+
try {
|
|
8721
|
+
const body = await req.json();
|
|
8722
|
+
if (!body.title)
|
|
8723
|
+
return json({ error: "Missing 'title'" }, 400, port);
|
|
8724
|
+
const task = createTask({
|
|
8725
|
+
title: body.title,
|
|
8726
|
+
description: body.description,
|
|
8727
|
+
priority: body.priority,
|
|
8728
|
+
project_id: body.project_id
|
|
8729
|
+
});
|
|
8730
|
+
return json(taskToSummary(task), 201, port);
|
|
8731
|
+
} catch (e) {
|
|
8732
|
+
return json({ error: e instanceof Error ? e.message : "Failed to create task" }, 500, port);
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8735
|
+
if (path === "/api/tasks/export" && method === "GET") {
|
|
8736
|
+
const format = url.searchParams.get("format") || "json";
|
|
8737
|
+
const status = url.searchParams.get("status") || undefined;
|
|
8738
|
+
const projectId = url.searchParams.get("project_id") || undefined;
|
|
8739
|
+
const tasks = listTasks({ status, project_id: projectId, limit: 1e4 });
|
|
8740
|
+
const summaries = tasks.map(taskToSummary);
|
|
8741
|
+
if (format === "csv") {
|
|
8742
|
+
const headers = ["id", "short_id", "title", "status", "priority", "project_id", "assigned_to", "agent_id", "created_at", "updated_at", "completed_at", "due_at"];
|
|
8743
|
+
const rows = summaries.map((t) => headers.map((h) => {
|
|
8744
|
+
const val = t[h];
|
|
8745
|
+
if (val === null || val === undefined)
|
|
8746
|
+
return "";
|
|
8747
|
+
const str = String(val);
|
|
8748
|
+
return str.includes(",") || str.includes('"') || str.includes(`
|
|
8749
|
+
`) ? `"${str.replace(/"/g, '""')}"` : str;
|
|
8750
|
+
}).join(","));
|
|
8751
|
+
const csv = [headers.join(","), ...rows].join(`
|
|
8752
|
+
`);
|
|
8753
|
+
return new Response(csv, {
|
|
8754
|
+
headers: {
|
|
8755
|
+
"Content-Type": "text/csv",
|
|
8756
|
+
"Content-Disposition": "attachment; filename=tasks.csv",
|
|
8757
|
+
...SECURITY_HEADERS
|
|
8758
|
+
}
|
|
8759
|
+
});
|
|
8760
|
+
}
|
|
8761
|
+
return new Response(JSON.stringify(summaries, null, 2), {
|
|
8762
|
+
headers: {
|
|
8763
|
+
"Content-Type": "application/json",
|
|
8764
|
+
"Content-Disposition": "attachment; filename=tasks.json",
|
|
8765
|
+
...SECURITY_HEADERS
|
|
8766
|
+
}
|
|
8767
|
+
});
|
|
8768
|
+
}
|
|
8769
|
+
if (path === "/api/tasks/bulk" && method === "POST") {
|
|
8770
|
+
try {
|
|
8771
|
+
const body = await req.json();
|
|
8772
|
+
if (!body.ids?.length || !body.action)
|
|
8773
|
+
return json({ error: "Missing ids or action" }, 400, port);
|
|
8774
|
+
const results = [];
|
|
8775
|
+
for (const id of body.ids) {
|
|
8776
|
+
try {
|
|
8777
|
+
if (body.action === "delete") {
|
|
8778
|
+
deleteTask(id);
|
|
8779
|
+
results.push({ id, success: true });
|
|
8780
|
+
} else if (body.action === "start") {
|
|
8781
|
+
startTask(id, "dashboard");
|
|
8782
|
+
results.push({ id, success: true });
|
|
8783
|
+
} else if (body.action === "complete") {
|
|
8784
|
+
completeTask(id, "dashboard");
|
|
8785
|
+
results.push({ id, success: true });
|
|
8786
|
+
}
|
|
8787
|
+
} catch (e) {
|
|
8788
|
+
results.push({ id, success: false, error: e instanceof Error ? e.message : "Failed" });
|
|
8789
|
+
}
|
|
8790
|
+
}
|
|
8791
|
+
return json({ results, succeeded: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length }, 200, port);
|
|
8792
|
+
} catch (e) {
|
|
8793
|
+
return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
|
|
8794
|
+
}
|
|
8795
|
+
}
|
|
8796
|
+
const taskMatch = path.match(/^\/api\/tasks\/([^/]+)$/);
|
|
8797
|
+
if (taskMatch) {
|
|
8798
|
+
const id = taskMatch[1];
|
|
8799
|
+
if (method === "GET") {
|
|
8800
|
+
const task = getTask(id);
|
|
8801
|
+
if (!task)
|
|
8802
|
+
return json({ error: "Task not found" }, 404, port);
|
|
8803
|
+
return json(taskToSummary(task), 200, port);
|
|
8804
|
+
}
|
|
8805
|
+
if (method === "PATCH") {
|
|
8806
|
+
try {
|
|
8807
|
+
const body = await req.json();
|
|
8808
|
+
const task = getTask(id);
|
|
8809
|
+
if (!task)
|
|
8810
|
+
return json({ error: "Task not found" }, 404, port);
|
|
8811
|
+
const updated = updateTask(id, {
|
|
8812
|
+
...body,
|
|
8813
|
+
version: task.version
|
|
8814
|
+
});
|
|
8815
|
+
return json(taskToSummary(updated), 200, port);
|
|
8816
|
+
} catch (e) {
|
|
8817
|
+
return json({ error: e instanceof Error ? e.message : "Failed to update task" }, 500, port);
|
|
8818
|
+
}
|
|
8819
|
+
}
|
|
8820
|
+
if (method === "DELETE") {
|
|
8821
|
+
const deleted = deleteTask(id);
|
|
8822
|
+
if (!deleted)
|
|
8823
|
+
return json({ error: "Task not found" }, 404, port);
|
|
8824
|
+
return json({ success: true }, 200, port);
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
const startMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
|
|
8828
|
+
if (startMatch && method === "POST") {
|
|
8829
|
+
const id = startMatch[1];
|
|
8830
|
+
try {
|
|
8831
|
+
const task = startTask(id, "dashboard");
|
|
8832
|
+
return json(taskToSummary(task), 200, port);
|
|
8833
|
+
} catch (e) {
|
|
8834
|
+
return json({ error: e instanceof Error ? e.message : "Failed to start task" }, 500, port);
|
|
8835
|
+
}
|
|
8836
|
+
}
|
|
8837
|
+
const completeMatch = path.match(/^\/api\/tasks\/([^/]+)\/complete$/);
|
|
8838
|
+
if (completeMatch && method === "POST") {
|
|
8839
|
+
const id = completeMatch[1];
|
|
8840
|
+
try {
|
|
8841
|
+
const task = completeTask(id, "dashboard");
|
|
8842
|
+
return json(taskToSummary(task), 200, port);
|
|
8843
|
+
} catch (e) {
|
|
8844
|
+
return json({ error: e instanceof Error ? e.message : "Failed to complete task" }, 500, port);
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8847
|
+
if (path === "/api/projects" && method === "GET") {
|
|
8848
|
+
return json(listProjects(), 200, port);
|
|
8849
|
+
}
|
|
8850
|
+
if (path === "/api/agents" && method === "GET") {
|
|
8851
|
+
return json(listAgents(), 200, port);
|
|
8852
|
+
}
|
|
8853
|
+
if (path === "/api/projects" && method === "POST") {
|
|
8854
|
+
try {
|
|
8855
|
+
const body = await req.json();
|
|
8856
|
+
if (!body.name || !body.path)
|
|
8857
|
+
return json({ error: "Missing name or path" }, 400, port);
|
|
8858
|
+
const { createProject: createProject2 } = await Promise.resolve().then(() => (init_projects(), exports_projects));
|
|
8859
|
+
const project = createProject2({ name: body.name, path: body.path, description: body.description });
|
|
8860
|
+
return json(project, 201, port);
|
|
8861
|
+
} catch (e) {
|
|
8862
|
+
return json({ error: e instanceof Error ? e.message : "Failed to create project" }, 500, port);
|
|
8863
|
+
}
|
|
8864
|
+
}
|
|
8865
|
+
const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
8866
|
+
if (projectDeleteMatch && method === "DELETE") {
|
|
8867
|
+
const id = projectDeleteMatch[1];
|
|
8868
|
+
const { deleteProject: deleteProject2 } = await Promise.resolve().then(() => (init_projects(), exports_projects));
|
|
8869
|
+
const deleted = deleteProject2(id);
|
|
8870
|
+
if (!deleted)
|
|
8871
|
+
return json({ error: "Project not found" }, 404, port);
|
|
8872
|
+
return json({ success: true }, 200, port);
|
|
8873
|
+
}
|
|
8874
|
+
if (path === "/api/agents" && method === "POST") {
|
|
8875
|
+
try {
|
|
8876
|
+
const body = await req.json();
|
|
8877
|
+
if (!body.name)
|
|
8878
|
+
return json({ error: "Missing name" }, 400, port);
|
|
8879
|
+
const { registerAgent: registerAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
8880
|
+
const agent = registerAgent2({ name: body.name, description: body.description });
|
|
8881
|
+
return json(agent, 201, port);
|
|
8882
|
+
} catch (e) {
|
|
8883
|
+
return json({ error: e instanceof Error ? e.message : "Failed to register agent" }, 500, port);
|
|
8884
|
+
}
|
|
8885
|
+
}
|
|
8886
|
+
const agentDeleteMatch = path.match(/^\/api\/agents\/([^/]+)$/);
|
|
8887
|
+
if (agentDeleteMatch && method === "DELETE") {
|
|
8888
|
+
const id = agentDeleteMatch[1];
|
|
8889
|
+
const { deleteAgent: deleteAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
8890
|
+
const deleted = deleteAgent2(id);
|
|
8891
|
+
if (!deleted)
|
|
8892
|
+
return json({ error: "Agent not found" }, 404, port);
|
|
8893
|
+
return json({ success: true }, 200, port);
|
|
8894
|
+
}
|
|
8895
|
+
if (path === "/api/agents/bulk" && method === "POST") {
|
|
8896
|
+
try {
|
|
8897
|
+
const body = await req.json();
|
|
8898
|
+
if (!body.ids?.length || body.action !== "delete")
|
|
8899
|
+
return json({ error: "Missing ids or invalid action" }, 400, port);
|
|
8900
|
+
const { deleteAgent: deleteAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
8901
|
+
let succeeded = 0;
|
|
8902
|
+
for (const id of body.ids) {
|
|
8903
|
+
if (deleteAgent2(id))
|
|
8904
|
+
succeeded++;
|
|
8905
|
+
}
|
|
8906
|
+
return json({ succeeded, failed: body.ids.length - succeeded }, 200, port);
|
|
8907
|
+
} catch (e) {
|
|
8908
|
+
return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
|
|
8909
|
+
}
|
|
8910
|
+
}
|
|
8911
|
+
if (path === "/api/projects/bulk" && method === "POST") {
|
|
8912
|
+
try {
|
|
8913
|
+
const body = await req.json();
|
|
8914
|
+
if (!body.ids?.length || body.action !== "delete")
|
|
8915
|
+
return json({ error: "Missing ids or invalid action" }, 400, port);
|
|
8916
|
+
const { deleteProject: deleteProject2 } = await Promise.resolve().then(() => (init_projects(), exports_projects));
|
|
8917
|
+
let succeeded = 0;
|
|
8918
|
+
for (const id of body.ids) {
|
|
8919
|
+
if (deleteProject2(id))
|
|
8920
|
+
succeeded++;
|
|
8921
|
+
}
|
|
8922
|
+
return json({ succeeded, failed: body.ids.length - succeeded }, 200, port);
|
|
8923
|
+
} catch (e) {
|
|
8924
|
+
return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
|
|
8925
|
+
}
|
|
8926
|
+
}
|
|
8927
|
+
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
8928
|
+
if (path !== "/") {
|
|
8929
|
+
const filePath = join6(dashboardDir, path);
|
|
8930
|
+
const res2 = serveStaticFile(filePath);
|
|
8931
|
+
if (res2)
|
|
8932
|
+
return res2;
|
|
8933
|
+
}
|
|
8934
|
+
const indexPath = join6(dashboardDir, "index.html");
|
|
8935
|
+
const res = serveStaticFile(indexPath);
|
|
8936
|
+
if (res)
|
|
8937
|
+
return res;
|
|
8938
|
+
}
|
|
8939
|
+
return json({ error: "Not found" }, 404, port);
|
|
8940
|
+
}
|
|
8941
|
+
});
|
|
8942
|
+
const shutdown = () => {
|
|
8943
|
+
server2.stop();
|
|
8944
|
+
process.exit(0);
|
|
8945
|
+
};
|
|
8946
|
+
process.on("SIGINT", shutdown);
|
|
8947
|
+
process.on("SIGTERM", shutdown);
|
|
8948
|
+
const serverUrl = `http://localhost:${port}`;
|
|
8949
|
+
console.log(`Todos Dashboard running at ${serverUrl}`);
|
|
8950
|
+
if (shouldOpen) {
|
|
8951
|
+
try {
|
|
8952
|
+
const { exec } = await import("child_process");
|
|
8953
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
8954
|
+
exec(`${openCmd} ${serverUrl}`);
|
|
8955
|
+
} catch {}
|
|
8956
|
+
}
|
|
8957
|
+
}
|
|
8958
|
+
var MIME_TYPES, SECURITY_HEADERS;
|
|
8959
|
+
var init_serve = __esm(() => {
|
|
8960
|
+
init_tasks();
|
|
8961
|
+
init_projects();
|
|
8962
|
+
init_agents();
|
|
8963
|
+
init_database();
|
|
8964
|
+
MIME_TYPES = {
|
|
8965
|
+
".html": "text/html; charset=utf-8",
|
|
8966
|
+
".js": "application/javascript",
|
|
8967
|
+
".css": "text/css",
|
|
8968
|
+
".json": "application/json",
|
|
8969
|
+
".png": "image/png",
|
|
8970
|
+
".jpg": "image/jpeg",
|
|
8971
|
+
".svg": "image/svg+xml",
|
|
8972
|
+
".ico": "image/x-icon",
|
|
8973
|
+
".woff": "font/woff",
|
|
8974
|
+
".woff2": "font/woff2"
|
|
8975
|
+
};
|
|
8976
|
+
SECURITY_HEADERS = {
|
|
8977
|
+
"X-Content-Type-Options": "nosniff",
|
|
8978
|
+
"X-Frame-Options": "DENY"
|
|
8979
|
+
};
|
|
8980
|
+
});
|
|
8981
|
+
|
|
8547
8982
|
// src/cli/components/Header.tsx
|
|
8548
8983
|
import { Box, Text } from "ink";
|
|
8549
8984
|
import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
@@ -9630,12 +10065,12 @@ init_sync();
|
|
|
9630
10065
|
init_config();
|
|
9631
10066
|
import chalk from "chalk";
|
|
9632
10067
|
import { execSync } from "child_process";
|
|
9633
|
-
import { existsSync as
|
|
9634
|
-
import { basename, dirname as
|
|
9635
|
-
import { fileURLToPath } from "url";
|
|
10068
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
10069
|
+
import { basename, dirname as dirname3, join as join7, resolve as resolve2 } from "path";
|
|
10070
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9636
10071
|
function getPackageVersion() {
|
|
9637
10072
|
try {
|
|
9638
|
-
const pkgPath =
|
|
10073
|
+
const pkgPath = join7(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
|
|
9639
10074
|
return JSON.parse(readFileSync3(pkgPath, "utf-8")).version || "0.0.0";
|
|
9640
10075
|
} catch {
|
|
9641
10076
|
return "0.0.0";
|
|
@@ -10287,8 +10722,8 @@ hooks.command("install").description("Install Claude Code hooks for auto-sync").
|
|
|
10287
10722
|
if (p)
|
|
10288
10723
|
todosBin = p;
|
|
10289
10724
|
} catch {}
|
|
10290
|
-
const hooksDir =
|
|
10291
|
-
if (!
|
|
10725
|
+
const hooksDir = join7(process.cwd(), ".claude", "hooks");
|
|
10726
|
+
if (!existsSync7(hooksDir))
|
|
10292
10727
|
mkdirSync3(hooksDir, { recursive: true });
|
|
10293
10728
|
const hookScript = `#!/usr/bin/env bash
|
|
10294
10729
|
# Auto-generated by: todos hooks install
|
|
@@ -10313,11 +10748,11 @@ esac
|
|
|
10313
10748
|
|
|
10314
10749
|
exit 0
|
|
10315
10750
|
`;
|
|
10316
|
-
const hookPath =
|
|
10751
|
+
const hookPath = join7(hooksDir, "todos-sync.sh");
|
|
10317
10752
|
writeFileSync3(hookPath, hookScript);
|
|
10318
10753
|
execSync(`chmod +x "${hookPath}"`);
|
|
10319
10754
|
console.log(chalk.green(`Hook script created: ${hookPath}`));
|
|
10320
|
-
const settingsPath =
|
|
10755
|
+
const settingsPath = join7(process.cwd(), ".claude", "settings.json");
|
|
10321
10756
|
const settings = readJsonFile2(settingsPath);
|
|
10322
10757
|
if (!settings["hooks"]) {
|
|
10323
10758
|
settings["hooks"] = {};
|
|
@@ -10364,13 +10799,13 @@ function getMcpBinaryPath() {
|
|
|
10364
10799
|
if (p)
|
|
10365
10800
|
return p;
|
|
10366
10801
|
} catch {}
|
|
10367
|
-
const bunBin =
|
|
10368
|
-
if (
|
|
10802
|
+
const bunBin = join7(HOME2, ".bun", "bin", "todos-mcp");
|
|
10803
|
+
if (existsSync7(bunBin))
|
|
10369
10804
|
return bunBin;
|
|
10370
10805
|
return "todos-mcp";
|
|
10371
10806
|
}
|
|
10372
10807
|
function readJsonFile2(path) {
|
|
10373
|
-
if (!
|
|
10808
|
+
if (!existsSync7(path))
|
|
10374
10809
|
return {};
|
|
10375
10810
|
try {
|
|
10376
10811
|
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
@@ -10379,25 +10814,25 @@ function readJsonFile2(path) {
|
|
|
10379
10814
|
}
|
|
10380
10815
|
}
|
|
10381
10816
|
function writeJsonFile2(path, data) {
|
|
10382
|
-
const dir =
|
|
10383
|
-
if (!
|
|
10817
|
+
const dir = dirname3(path);
|
|
10818
|
+
if (!existsSync7(dir))
|
|
10384
10819
|
mkdirSync3(dir, { recursive: true });
|
|
10385
10820
|
writeFileSync3(path, JSON.stringify(data, null, 2) + `
|
|
10386
10821
|
`);
|
|
10387
10822
|
}
|
|
10388
10823
|
function readTomlFile(path) {
|
|
10389
|
-
if (!
|
|
10824
|
+
if (!existsSync7(path))
|
|
10390
10825
|
return "";
|
|
10391
10826
|
return readFileSync3(path, "utf-8");
|
|
10392
10827
|
}
|
|
10393
10828
|
function writeTomlFile(path, content) {
|
|
10394
|
-
const dir =
|
|
10395
|
-
if (!
|
|
10829
|
+
const dir = dirname3(path);
|
|
10830
|
+
if (!existsSync7(dir))
|
|
10396
10831
|
mkdirSync3(dir, { recursive: true });
|
|
10397
10832
|
writeFileSync3(path, content);
|
|
10398
10833
|
}
|
|
10399
10834
|
function registerClaude(binPath, global) {
|
|
10400
|
-
const configPath = global ?
|
|
10835
|
+
const configPath = global ? join7(HOME2, ".claude", ".mcp.json") : join7(process.cwd(), ".mcp.json");
|
|
10401
10836
|
const config = readJsonFile2(configPath);
|
|
10402
10837
|
if (!config["mcpServers"]) {
|
|
10403
10838
|
config["mcpServers"] = {};
|
|
@@ -10412,7 +10847,7 @@ function registerClaude(binPath, global) {
|
|
|
10412
10847
|
console.log(chalk.green(`Claude Code (${scope}): registered in ${configPath}`));
|
|
10413
10848
|
}
|
|
10414
10849
|
function unregisterClaude(global) {
|
|
10415
|
-
const configPath = global ?
|
|
10850
|
+
const configPath = global ? join7(HOME2, ".claude", ".mcp.json") : join7(process.cwd(), ".mcp.json");
|
|
10416
10851
|
const config = readJsonFile2(configPath);
|
|
10417
10852
|
const servers = config["mcpServers"];
|
|
10418
10853
|
if (!servers || !("todos" in servers)) {
|
|
@@ -10425,7 +10860,7 @@ function unregisterClaude(global) {
|
|
|
10425
10860
|
console.log(chalk.green(`Claude Code (${scope}): unregistered from ${configPath}`));
|
|
10426
10861
|
}
|
|
10427
10862
|
function registerCodex(binPath) {
|
|
10428
|
-
const configPath =
|
|
10863
|
+
const configPath = join7(HOME2, ".codex", "config.toml");
|
|
10429
10864
|
let content = readTomlFile(configPath);
|
|
10430
10865
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
10431
10866
|
const block = `
|
|
@@ -10439,7 +10874,7 @@ args = []
|
|
|
10439
10874
|
console.log(chalk.green(`Codex CLI: registered in ${configPath}`));
|
|
10440
10875
|
}
|
|
10441
10876
|
function unregisterCodex() {
|
|
10442
|
-
const configPath =
|
|
10877
|
+
const configPath = join7(HOME2, ".codex", "config.toml");
|
|
10443
10878
|
let content = readTomlFile(configPath);
|
|
10444
10879
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
10445
10880
|
console.log(chalk.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -10472,7 +10907,7 @@ function removeTomlBlock(content, blockName) {
|
|
|
10472
10907
|
`);
|
|
10473
10908
|
}
|
|
10474
10909
|
function registerGemini(binPath) {
|
|
10475
|
-
const configPath =
|
|
10910
|
+
const configPath = join7(HOME2, ".gemini", "settings.json");
|
|
10476
10911
|
const config = readJsonFile2(configPath);
|
|
10477
10912
|
if (!config["mcpServers"]) {
|
|
10478
10913
|
config["mcpServers"] = {};
|
|
@@ -10486,7 +10921,7 @@ function registerGemini(binPath) {
|
|
|
10486
10921
|
console.log(chalk.green(`Gemini CLI: registered in ${configPath}`));
|
|
10487
10922
|
}
|
|
10488
10923
|
function unregisterGemini() {
|
|
10489
|
-
const configPath =
|
|
10924
|
+
const configPath = join7(HOME2, ".gemini", "settings.json");
|
|
10490
10925
|
const config = readJsonFile2(configPath);
|
|
10491
10926
|
const servers = config["mcpServers"];
|
|
10492
10927
|
if (!servers || !("todos" in servers)) {
|
|
@@ -10650,6 +11085,10 @@ Updated to ${latestVersion}!`));
|
|
|
10650
11085
|
handleError(e);
|
|
10651
11086
|
}
|
|
10652
11087
|
});
|
|
11088
|
+
program2.command("serve").description("Start the web dashboard").option("--port <port>", "Port number", "19427").option("--no-open", "Don't open browser automatically").action(async (opts) => {
|
|
11089
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
11090
|
+
await startServer2(parseInt(opts.port, 10), { open: opts.open !== false });
|
|
11091
|
+
});
|
|
10653
11092
|
program2.command("interactive").description("Launch interactive TUI").action(async () => {
|
|
10654
11093
|
const { renderApp: renderApp2 } = await Promise.resolve().then(() => (init_App(), exports_App));
|
|
10655
11094
|
const globalOpts = program2.opts();
|