@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/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
- const limitVal = filter.limit || 100;
2877
- const offsetVal = filter.offset || 0;
2878
- params.push(limitVal, offsetVal);
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 LIMIT ? OFFSET ?`).all(...params);
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 existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
9634
- import { basename, dirname as dirname2, join as join6, resolve as resolve2 } from "path";
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 = join6(dirname2(fileURLToPath(import.meta.url)), "..", "..", "package.json");
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 = join6(process.cwd(), ".claude", "hooks");
10291
- if (!existsSync6(hooksDir))
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 = join6(hooksDir, "todos-sync.sh");
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 = join6(process.cwd(), ".claude", "settings.json");
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 = join6(HOME2, ".bun", "bin", "todos-mcp");
10368
- if (existsSync6(bunBin))
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 (!existsSync6(path))
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 = dirname2(path);
10383
- if (!existsSync6(dir))
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 (!existsSync6(path))
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 = dirname2(path);
10395
- if (!existsSync6(dir))
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 ? join6(HOME2, ".claude", ".mcp.json") : join6(process.cwd(), ".mcp.json");
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 ? join6(HOME2, ".claude", ".mcp.json") : join6(process.cwd(), ".mcp.json");
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 = join6(HOME2, ".codex", "config.toml");
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 = join6(HOME2, ".codex", "config.toml");
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 = join6(HOME2, ".gemini", "settings.json");
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 = join6(HOME2, ".gemini", "settings.json");
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();