@hasna/todos 0.11.52 → 0.11.53

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.
Files changed (210) hide show
  1. package/README.md +14 -1
  2. package/dashboard/dist/assets/{index-C3fBxEWP.js → index-aJefI7kh.js} +1 -1
  3. package/dashboard/dist/index.html +1 -1
  4. package/dist/cli/commands/config-serve-commands.d.ts.map +1 -1
  5. package/dist/cli/commands/machines.d.ts.map +1 -1
  6. package/dist/cli/commands/storage-commands.d.ts +3 -0
  7. package/dist/cli/commands/storage-commands.d.ts.map +1 -0
  8. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  9. package/dist/cli/helpers.d.ts +6 -0
  10. package/dist/cli/helpers.d.ts.map +1 -1
  11. package/dist/cli/index.js +3306 -445
  12. package/dist/contracts.js +289 -8
  13. package/dist/db/artifacts.d.ts.map +1 -1
  14. package/dist/db/builtin-templates.d.ts +0 -1
  15. package/dist/db/builtin-templates.d.ts.map +1 -1
  16. package/dist/db/database.d.ts.map +1 -1
  17. package/dist/db/schema.d.ts.map +1 -1
  18. package/dist/db/tags.d.ts +26 -0
  19. package/dist/db/tags.d.ts.map +1 -0
  20. package/dist/db/task-commits.d.ts +14 -6
  21. package/dist/db/task-commits.d.ts.map +1 -1
  22. package/dist/db/tasks.d.ts +1 -1
  23. package/dist/db/tasks.d.ts.map +1 -1
  24. package/dist/index.d.ts +32 -60
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2473 -200
  27. package/dist/lib/agent-adapter-docs.d.ts +1 -1
  28. package/dist/lib/agent-adapter-docs.d.ts.map +1 -1
  29. package/dist/lib/agent-coordination.d.ts.map +1 -1
  30. package/dist/lib/agent-run-dispatcher.d.ts +5 -2
  31. package/dist/lib/agent-run-dispatcher.d.ts.map +1 -1
  32. package/dist/lib/agent-workflow-demo.d.ts +1 -1
  33. package/dist/lib/agent-workflow-demo.d.ts.map +1 -1
  34. package/dist/lib/db-backup.d.ts.map +1 -1
  35. package/dist/lib/headless-boundaries.d.ts +2 -2
  36. package/dist/lib/headless-boundaries.d.ts.map +1 -1
  37. package/dist/lib/import-export-bridge.d.ts.map +1 -1
  38. package/dist/lib/inbox-intake.d.ts.map +1 -1
  39. package/dist/lib/issue-importers.d.ts.map +1 -1
  40. package/dist/lib/machine-topology.d.ts +1 -1
  41. package/dist/lib/machine-topology.d.ts.map +1 -1
  42. package/dist/lib/native-storage-status.d.ts +65 -0
  43. package/dist/lib/native-storage-status.d.ts.map +1 -0
  44. package/dist/lib/nl-intake.d.ts.map +1 -1
  45. package/dist/lib/notification-reminders.d.ts.map +1 -1
  46. package/dist/lib/plan-execution.d.ts +1 -0
  47. package/dist/lib/plan-execution.d.ts.map +1 -1
  48. package/dist/lib/release-checks.d.ts.map +1 -1
  49. package/dist/lib/resource-snapshots.d.ts.map +1 -1
  50. package/dist/lib/saved-views.d.ts.map +1 -1
  51. package/dist/lib/secret-redaction.d.ts +1 -1
  52. package/dist/lib/secret-redaction.d.ts.map +1 -1
  53. package/dist/lib/task-scheduling.d.ts +1 -1
  54. package/dist/lib/task-scheduling.d.ts.map +1 -1
  55. package/dist/lib/template-library.d.ts +1 -1
  56. package/dist/lib/template-library.d.ts.map +1 -1
  57. package/dist/lib/user-scaffolds.d.ts.map +1 -1
  58. package/dist/lib/verification-evidence.d.ts +4 -4
  59. package/dist/lib/verification-evidence.d.ts.map +1 -1
  60. package/dist/lib/verification-providers.d.ts +3 -3
  61. package/dist/lib/verification-providers.d.ts.map +1 -1
  62. package/dist/mcp/index.d.ts.map +1 -1
  63. package/dist/mcp/index.js +526 -50
  64. package/dist/mcp/tools/task-crud.d.ts.map +1 -1
  65. package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
  66. package/dist/registry.js +289 -8
  67. package/dist/release-provenance.json +7 -0
  68. package/dist/server/index.js +552 -52
  69. package/dist/server/serve.d.ts.map +1 -1
  70. package/dist/storage/config.d.ts +86 -0
  71. package/dist/storage/config.d.ts.map +1 -0
  72. package/dist/storage/factory.d.ts +17 -0
  73. package/dist/storage/factory.d.ts.map +1 -0
  74. package/dist/storage/hybrid.d.ts +26 -0
  75. package/dist/storage/hybrid.d.ts.map +1 -0
  76. package/dist/storage/index.d.ts +15 -0
  77. package/dist/storage/index.d.ts.map +1 -1
  78. package/dist/storage/local-sqlite.d.ts.map +1 -1
  79. package/dist/storage/postgres-adapter.d.ts +11 -0
  80. package/dist/storage/postgres-adapter.d.ts.map +1 -0
  81. package/dist/storage/postgres-sync.d.ts +44 -0
  82. package/dist/storage/postgres-sync.d.ts.map +1 -0
  83. package/dist/storage/s3-artifact-sync.d.ts +75 -0
  84. package/dist/storage/s3-artifact-sync.d.ts.map +1 -0
  85. package/dist/storage/s3-artifacts.d.ts +51 -0
  86. package/dist/storage/s3-artifacts.d.ts.map +1 -0
  87. package/dist/storage/sqlite-snapshot.d.ts +5 -0
  88. package/dist/storage/sqlite-snapshot.d.ts.map +1 -0
  89. package/dist/storage.d.ts +2 -1
  90. package/dist/storage.d.ts.map +1 -1
  91. package/dist/storage.js +2053 -10
  92. package/package.json +1 -1
  93. package/dist/cli/brains.d.ts +0 -3
  94. package/dist/cli/brains.d.ts.map +0 -1
  95. package/dist/db/file-locks.d.ts +0 -43
  96. package/dist/db/file-locks.d.ts.map +0 -1
  97. package/dist/db/project-agent-roles.d.ts +0 -34
  98. package/dist/db/project-agent-roles.d.ts.map +0 -1
  99. package/dist/db/task-claim.d.ts +0 -7
  100. package/dist/db/task-claim.d.ts.map +0 -1
  101. package/dist/db/task-workflow.d.ts +0 -7
  102. package/dist/db/task-workflow.d.ts.map +0 -1
  103. package/dist/lib/north-star.d.ts +0 -33
  104. package/dist/lib/north-star.d.ts.map +0 -1
  105. package/dist/lib/public-release-gate.d.ts +0 -57
  106. package/dist/lib/public-release-gate.d.ts.map +0 -1
  107. package/dist/lib/task-runner.d.ts +0 -101
  108. package/dist/lib/task-runner.d.ts.map +0 -1
  109. package/dist/mcp/tools/access-profiles.d.ts +0 -8
  110. package/dist/mcp/tools/access-profiles.d.ts.map +0 -1
  111. package/dist/mcp/tools/activity-audit.d.ts +0 -9
  112. package/dist/mcp/tools/activity-audit.d.ts.map +0 -1
  113. package/dist/mcp/tools/agent-adapter-docs.d.ts +0 -8
  114. package/dist/mcp/tools/agent-adapter-docs.d.ts.map +0 -1
  115. package/dist/mcp/tools/agent-coordination.d.ts +0 -9
  116. package/dist/mcp/tools/agent-coordination.d.ts.map +0 -1
  117. package/dist/mcp/tools/agent-runs.d.ts +0 -9
  118. package/dist/mcp/tools/agent-runs.d.ts.map +0 -1
  119. package/dist/mcp/tools/agent-workflow-demo.d.ts +0 -8
  120. package/dist/mcp/tools/agent-workflow-demo.d.ts.map +0 -1
  121. package/dist/mcp/tools/approval-gates.d.ts +0 -9
  122. package/dist/mcp/tools/approval-gates.d.ts.map +0 -1
  123. package/dist/mcp/tools/artifacts.d.ts +0 -9
  124. package/dist/mcp/tools/artifacts.d.ts.map +0 -1
  125. package/dist/mcp/tools/branch-work-plans.d.ts +0 -8
  126. package/dist/mcp/tools/branch-work-plans.d.ts.map +0 -1
  127. package/dist/mcp/tools/cli-docs.d.ts +0 -8
  128. package/dist/mcp/tools/cli-docs.d.ts.map +0 -1
  129. package/dist/mcp/tools/command-aliases.d.ts +0 -8
  130. package/dist/mcp/tools/command-aliases.d.ts.map +0 -1
  131. package/dist/mcp/tools/context-packs.d.ts +0 -9
  132. package/dist/mcp/tools/context-packs.d.ts.map +0 -1
  133. package/dist/mcp/tools/crypto.d.ts +0 -8
  134. package/dist/mcp/tools/crypto.d.ts.map +0 -1
  135. package/dist/mcp/tools/db-backup.d.ts +0 -8
  136. package/dist/mcp/tools/db-backup.d.ts.map +0 -1
  137. package/dist/mcp/tools/decision-records.d.ts +0 -9
  138. package/dist/mcp/tools/decision-records.d.ts.map +0 -1
  139. package/dist/mcp/tools/dependency-graph.d.ts +0 -9
  140. package/dist/mcp/tools/dependency-graph.d.ts.map +0 -1
  141. package/dist/mcp/tools/failure-triage.d.ts +0 -9
  142. package/dist/mcp/tools/failure-triage.d.ts.map +0 -1
  143. package/dist/mcp/tools/feature-manifest.d.ts +0 -8
  144. package/dist/mcp/tools/feature-manifest.d.ts.map +0 -1
  145. package/dist/mcp/tools/git-traceability.d.ts +0 -9
  146. package/dist/mcp/tools/git-traceability.d.ts.map +0 -1
  147. package/dist/mcp/tools/goal.d.ts +0 -9
  148. package/dist/mcp/tools/goal.d.ts.map +0 -1
  149. package/dist/mcp/tools/handoff-packets.d.ts +0 -9
  150. package/dist/mcp/tools/handoff-packets.d.ts.map +0 -1
  151. package/dist/mcp/tools/import-export-bridge.d.ts +0 -9
  152. package/dist/mcp/tools/import-export-bridge.d.ts.map +0 -1
  153. package/dist/mcp/tools/inbox-intake.d.ts +0 -9
  154. package/dist/mcp/tools/inbox-intake.d.ts.map +0 -1
  155. package/dist/mcp/tools/issue-importers.d.ts +0 -9
  156. package/dist/mcp/tools/issue-importers.d.ts.map +0 -1
  157. package/dist/mcp/tools/json-schemas.d.ts +0 -8
  158. package/dist/mcp/tools/json-schemas.d.ts.map +0 -1
  159. package/dist/mcp/tools/machine-topology.d.ts +0 -8
  160. package/dist/mcp/tools/machine-topology.d.ts.map +0 -1
  161. package/dist/mcp/tools/mention-resolver.d.ts +0 -9
  162. package/dist/mcp/tools/mention-resolver.d.ts.map +0 -1
  163. package/dist/mcp/tools/nl-intake.d.ts +0 -9
  164. package/dist/mcp/tools/nl-intake.d.ts.map +0 -1
  165. package/dist/mcp/tools/notification-reminders.d.ts +0 -9
  166. package/dist/mcp/tools/notification-reminders.d.ts.map +0 -1
  167. package/dist/mcp/tools/parity.d.ts +0 -8
  168. package/dist/mcp/tools/parity.d.ts.map +0 -1
  169. package/dist/mcp/tools/plan-execution.d.ts +0 -9
  170. package/dist/mcp/tools/plan-execution.d.ts.map +0 -1
  171. package/dist/mcp/tools/policy-packs.d.ts +0 -9
  172. package/dist/mcp/tools/policy-packs.d.ts.map +0 -1
  173. package/dist/mcp/tools/project-bootstrap.d.ts +0 -8
  174. package/dist/mcp/tools/project-bootstrap.d.ts.map +0 -1
  175. package/dist/mcp/tools/release-checks.d.ts +0 -8
  176. package/dist/mcp/tools/release-checks.d.ts.map +0 -1
  177. package/dist/mcp/tools/release-notes.d.ts +0 -9
  178. package/dist/mcp/tools/release-notes.d.ts.map +0 -1
  179. package/dist/mcp/tools/report-exports.d.ts +0 -9
  180. package/dist/mcp/tools/report-exports.d.ts.map +0 -1
  181. package/dist/mcp/tools/resource-subscriptions.d.ts +0 -8
  182. package/dist/mcp/tools/resource-subscriptions.d.ts.map +0 -1
  183. package/dist/mcp/tools/run-records.d.ts +0 -9
  184. package/dist/mcp/tools/run-records.d.ts.map +0 -1
  185. package/dist/mcp/tools/sandbox.d.ts +0 -8
  186. package/dist/mcp/tools/sandbox.d.ts.map +0 -1
  187. package/dist/mcp/tools/saved-views.d.ts +0 -9
  188. package/dist/mcp/tools/saved-views.d.ts.map +0 -1
  189. package/dist/mcp/tools/secret-redaction.d.ts +0 -8
  190. package/dist/mcp/tools/secret-redaction.d.ts.map +0 -1
  191. package/dist/mcp/tools/task-dedupe.d.ts +0 -9
  192. package/dist/mcp/tools/task-dedupe.d.ts.map +0 -1
  193. package/dist/mcp/tools/task-scheduling.d.ts +0 -9
  194. package/dist/mcp/tools/task-scheduling.d.ts.map +0 -1
  195. package/dist/mcp/tools/template-library.d.ts +0 -8
  196. package/dist/mcp/tools/template-library.d.ts.map +0 -1
  197. package/dist/mcp/tools/terminal-notifications.d.ts +0 -9
  198. package/dist/mcp/tools/terminal-notifications.d.ts.map +0 -1
  199. package/dist/mcp/tools/todos-md.d.ts +0 -8
  200. package/dist/mcp/tools/todos-md.d.ts.map +0 -1
  201. package/dist/mcp/tools/user-scaffolds.d.ts +0 -8
  202. package/dist/mcp/tools/user-scaffolds.d.ts.map +0 -1
  203. package/dist/mcp/tools/verification.d.ts +0 -9
  204. package/dist/mcp/tools/verification.d.ts.map +0 -1
  205. package/dist/mcp/tools/webhooks.d.ts +0 -8
  206. package/dist/mcp/tools/webhooks.d.ts.map +0 -1
  207. package/dist/mcp/tools/workspace-trust.d.ts +0 -8
  208. package/dist/mcp/tools/workspace-trust.d.ts.map +0 -1
  209. package/dist/test/no-network.d.ts +0 -7
  210. package/dist/test/no-network.d.ts.map +0 -1
@@ -1273,6 +1273,15 @@ function ensureSchema(db) {
1273
1273
  task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1274
1274
  tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
1275
1275
  )`);
1276
+ ensureTable("tags", `
1277
+ CREATE TABLE tags (
1278
+ id TEXT PRIMARY KEY,
1279
+ name TEXT NOT NULL UNIQUE,
1280
+ color TEXT,
1281
+ description TEXT,
1282
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1283
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
1284
+ )`);
1276
1285
  ensureTable("task_dependencies", `
1277
1286
  CREATE TABLE task_dependencies (
1278
1287
  task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
@@ -1486,6 +1495,36 @@ function ensureSchema(db) {
1486
1495
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_task ON task_git_refs(task_id)");
1487
1496
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_lookup ON task_git_refs(ref_type, name)");
1488
1497
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_url ON task_git_refs(url)");
1498
+ ensureTable("task_commits", `
1499
+ CREATE TABLE task_commits (
1500
+ id TEXT PRIMARY KEY,
1501
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1502
+ sha TEXT NOT NULL,
1503
+ message TEXT,
1504
+ author TEXT,
1505
+ files_changed TEXT,
1506
+ committed_at TEXT,
1507
+ branch TEXT,
1508
+ pr_url TEXT,
1509
+ pr_number INTEGER,
1510
+ pr_state TEXT,
1511
+ ci_snapshot TEXT,
1512
+ release_tag TEXT,
1513
+ repo_path TEXT,
1514
+ traceability TEXT DEFAULT '{}',
1515
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1516
+ UNIQUE(task_id, sha)
1517
+ )`);
1518
+ ensureColumn("task_commits", "branch", "TEXT");
1519
+ ensureColumn("task_commits", "pr_url", "TEXT");
1520
+ ensureColumn("task_commits", "pr_number", "INTEGER");
1521
+ ensureColumn("task_commits", "pr_state", "TEXT");
1522
+ ensureColumn("task_commits", "ci_snapshot", "TEXT");
1523
+ ensureColumn("task_commits", "release_tag", "TEXT");
1524
+ ensureColumn("task_commits", "repo_path", "TEXT");
1525
+ ensureColumn("task_commits", "traceability", "TEXT DEFAULT '{}'");
1526
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_commits_task ON task_commits(task_id)");
1527
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_commits_sha ON task_commits(sha)");
1489
1528
  ensureTable("task_verifications", `
1490
1529
  CREATE TABLE task_verifications (
1491
1530
  id TEXT PRIMARY KEY,
@@ -1670,6 +1709,9 @@ function ensureSchema(db) {
1670
1709
  ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
1671
1710
  ensureColumn("tasks", "retry_after", "TEXT");
1672
1711
  ensureColumn("tasks", "sla_minutes", "INTEGER");
1712
+ ensureColumn("tasks", "scheduled_start_at", "TEXT");
1713
+ ensureColumn("tasks", "priority_score", "INTEGER");
1714
+ ensureColumn("tasks", "priority_reason", "TEXT");
1673
1715
  ensureColumn("tasks", "archived_at", "TEXT");
1674
1716
  ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
1675
1717
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
@@ -1792,6 +1834,7 @@ function ensureSchema(db) {
1792
1834
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug)");
1793
1835
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag)");
1794
1836
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_task ON task_tags(task_id)");
1837
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name)");
1795
1838
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
1796
1839
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
1797
1840
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
@@ -1929,6 +1972,217 @@ function ensureSchema(db) {
1929
1972
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_cycles_dates ON cycles(start_date, end_date)");
1930
1973
  ensureColumn("tasks", "cycle_id", "TEXT REFERENCES cycles(id) ON DELETE SET NULL");
1931
1974
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_cycle ON tasks(cycle_id) WHERE cycle_id IS NOT NULL");
1975
+ ensureTable("labels", `
1976
+ CREATE TABLE labels (
1977
+ id TEXT PRIMARY KEY,
1978
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
1979
+ name TEXT NOT NULL,
1980
+ color TEXT,
1981
+ description TEXT,
1982
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1983
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
1984
+ UNIQUE(project_id, name)
1985
+ )`);
1986
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_labels_project ON labels(project_id)");
1987
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_labels_name ON labels(name)");
1988
+ ensureTable("task_labels", `
1989
+ CREATE TABLE task_labels (
1990
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1991
+ label_id TEXT NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
1992
+ PRIMARY KEY (task_id, label_id)
1993
+ )`);
1994
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_labels_task ON task_labels(task_id)");
1995
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_labels_label ON task_labels(label_id)");
1996
+ ensureTable("custom_field_definitions", `
1997
+ CREATE TABLE custom_field_definitions (
1998
+ id TEXT PRIMARY KEY,
1999
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
2000
+ name TEXT NOT NULL,
2001
+ slug TEXT NOT NULL,
2002
+ field_type TEXT NOT NULL CHECK(field_type IN ('text', 'number', 'boolean', 'date', 'enum')),
2003
+ options TEXT DEFAULT '[]',
2004
+ required INTEGER NOT NULL DEFAULT 0,
2005
+ default_value TEXT,
2006
+ sort_order INTEGER NOT NULL DEFAULT 0,
2007
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2008
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
2009
+ UNIQUE(project_id, slug)
2010
+ )`);
2011
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_custom_fields_project ON custom_field_definitions(project_id)");
2012
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_custom_fields_slug ON custom_field_definitions(slug)");
2013
+ ensureTable("task_custom_field_values", `
2014
+ CREATE TABLE task_custom_field_values (
2015
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2016
+ field_id TEXT NOT NULL REFERENCES custom_field_definitions(id) ON DELETE CASCADE,
2017
+ value TEXT,
2018
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
2019
+ PRIMARY KEY (task_id, field_id)
2020
+ )`);
2021
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_custom_values_task ON task_custom_field_values(task_id)");
2022
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_custom_values_field ON task_custom_field_values(field_id)");
2023
+ ensureTable("saved_views", `
2024
+ CREATE TABLE saved_views (
2025
+ id TEXT PRIMARY KEY,
2026
+ name TEXT NOT NULL UNIQUE,
2027
+ slug TEXT NOT NULL UNIQUE,
2028
+ entity_type TEXT NOT NULL DEFAULT 'task',
2029
+ filters TEXT NOT NULL DEFAULT '{}',
2030
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2031
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2032
+ )`);
2033
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_saved_views_slug ON saved_views(slug)");
2034
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_saved_views_entity ON saved_views(entity_type)");
2035
+ ensureTable("decision_records", `
2036
+ CREATE TABLE decision_records (
2037
+ id TEXT PRIMARY KEY,
2038
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2039
+ task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
2040
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
2041
+ agent_id TEXT,
2042
+ sequence_num INTEGER NOT NULL,
2043
+ short_ref TEXT NOT NULL UNIQUE,
2044
+ title TEXT NOT NULL,
2045
+ status TEXT NOT NULL DEFAULT 'proposed' CHECK(status IN ('proposed', 'accepted', 'deprecated', 'superseded', 'rejected')),
2046
+ context TEXT,
2047
+ decision TEXT NOT NULL,
2048
+ consequences TEXT,
2049
+ alternatives TEXT DEFAULT '[]',
2050
+ tags TEXT DEFAULT '[]',
2051
+ supersedes_id TEXT REFERENCES decision_records(id) ON DELETE SET NULL,
2052
+ superseded_by_id TEXT REFERENCES decision_records(id) ON DELETE SET NULL,
2053
+ metadata TEXT DEFAULT '{}',
2054
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2055
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2056
+ )`);
2057
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_project ON decision_records(project_id)");
2058
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_task ON decision_records(task_id)");
2059
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_plan ON decision_records(plan_id)");
2060
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_status ON decision_records(status)");
2061
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_decision_records_short_ref ON decision_records(short_ref)");
2062
+ ensureTable("knowledge_snapshots", `
2063
+ CREATE TABLE knowledge_snapshots (
2064
+ id TEXT PRIMARY KEY,
2065
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2066
+ title TEXT NOT NULL,
2067
+ summary TEXT,
2068
+ content_hash TEXT NOT NULL,
2069
+ snapshot TEXT NOT NULL,
2070
+ decision_ids TEXT DEFAULT '[]',
2071
+ topics TEXT DEFAULT '[]',
2072
+ source TEXT NOT NULL DEFAULT 'auto' CHECK(source IN ('manual', 'auto', 'import')),
2073
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2074
+ )`);
2075
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_knowledge_snapshots_project ON knowledge_snapshots(project_id)");
2076
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_knowledge_snapshots_hash ON knowledge_snapshots(content_hash)");
2077
+ ensureTable("verification_records", `
2078
+ CREATE TABLE verification_records (
2079
+ id TEXT PRIMARY KEY,
2080
+ task_id TEXT REFERENCES tasks(id) ON DELETE CASCADE,
2081
+ provider_name TEXT NOT NULL,
2082
+ provider_type TEXT NOT NULL,
2083
+ status TEXT NOT NULL DEFAULT 'unknown' CHECK(status IN ('passed', 'failed', 'unknown')),
2084
+ summary TEXT,
2085
+ evidence TEXT DEFAULT '{}',
2086
+ artifact_id TEXT,
2087
+ started_at TEXT,
2088
+ completed_at TEXT,
2089
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2090
+ )`);
2091
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_verification_records_task ON verification_records(task_id)");
2092
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_verification_records_status ON verification_records(status)");
2093
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_verification_records_created ON verification_records(created_at)");
2094
+ ensureTable("task_leases", `
2095
+ CREATE TABLE task_leases (
2096
+ task_id TEXT PRIMARY KEY REFERENCES tasks(id) ON DELETE CASCADE,
2097
+ agent_id TEXT NOT NULL,
2098
+ acquired_at TEXT NOT NULL,
2099
+ expires_at TEXT NOT NULL,
2100
+ heartbeat_at TEXT,
2101
+ steal_count INTEGER NOT NULL DEFAULT 0,
2102
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2103
+ )`);
2104
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_leases_agent ON task_leases(agent_id)");
2105
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_leases_expires ON task_leases(expires_at)");
2106
+ ensureTable("reminder_preferences", `
2107
+ CREATE TABLE reminder_preferences (
2108
+ id TEXT PRIMARY KEY,
2109
+ due_soon_hours INTEGER NOT NULL DEFAULT 24,
2110
+ sla_warning_minutes INTEGER NOT NULL DEFAULT 30,
2111
+ enabled INTEGER NOT NULL DEFAULT 1,
2112
+ desktop_notify INTEGER NOT NULL DEFAULT 0,
2113
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2114
+ )`);
2115
+ ensureTable("notification_reminders", `
2116
+ CREATE TABLE notification_reminders (
2117
+ id TEXT PRIMARY KEY,
2118
+ task_id TEXT REFERENCES tasks(id) ON DELETE CASCADE,
2119
+ reminder_type TEXT NOT NULL CHECK(reminder_type IN ('due_soon', 'due_overdue', 'sla_warning', 'sla_breach', 'custom')),
2120
+ title TEXT NOT NULL,
2121
+ message TEXT,
2122
+ trigger_at TEXT NOT NULL,
2123
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'fired', 'dismissed', 'snoozed')),
2124
+ snoozed_until TEXT,
2125
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2126
+ agent_id TEXT,
2127
+ priority TEXT NOT NULL DEFAULT 'medium',
2128
+ metadata TEXT DEFAULT '{}',
2129
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2130
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
2131
+ fired_at TEXT,
2132
+ dismissed_at TEXT
2133
+ )`);
2134
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_task ON notification_reminders(task_id)");
2135
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_status ON notification_reminders(status)");
2136
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_trigger ON notification_reminders(trigger_at)");
2137
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_project ON notification_reminders(project_id)");
2138
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_notification_reminders_agent ON notification_reminders(agent_id)");
2139
+ ensureTable("run_records", `
2140
+ CREATE TABLE run_records (
2141
+ id TEXT PRIMARY KEY,
2142
+ agent_run_id TEXT,
2143
+ agent_id TEXT,
2144
+ objective TEXT,
2145
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
2146
+ claimed_task_ids TEXT DEFAULT '[]',
2147
+ commands TEXT DEFAULT '[]',
2148
+ stdout_summary TEXT,
2149
+ stderr_summary TEXT,
2150
+ files_touched TEXT DEFAULT '[]',
2151
+ verification_results TEXT DEFAULT '[]',
2152
+ artifact_ids TEXT DEFAULT '[]',
2153
+ status_transitions TEXT DEFAULT '[]',
2154
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'failed', 'archived')),
2155
+ replay_bundle TEXT,
2156
+ metadata TEXT DEFAULT '{}',
2157
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
2158
+ completed_at TEXT,
2159
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2160
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2161
+ )`);
2162
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_agent_run ON run_records(agent_run_id)");
2163
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_agent ON run_records(agent_id)");
2164
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_plan ON run_records(plan_id)");
2165
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_status ON run_records(status)");
2166
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_run_records_started ON run_records(started_at)");
2167
+ ensureTable("activity_log", `
2168
+ CREATE TABLE activity_log (
2169
+ id TEXT PRIMARY KEY,
2170
+ entity_type TEXT NOT NULL,
2171
+ entity_id TEXT NOT NULL,
2172
+ action TEXT NOT NULL,
2173
+ field TEXT,
2174
+ old_value TEXT,
2175
+ new_value TEXT,
2176
+ actor_id TEXT,
2177
+ session_id TEXT,
2178
+ machine_id TEXT,
2179
+ metadata TEXT DEFAULT '{}',
2180
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2181
+ )`);
2182
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_activity_log_entity ON activity_log(entity_type, entity_id)");
2183
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_activity_log_actor ON activity_log(actor_id)");
2184
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_activity_log_action ON activity_log(action)");
2185
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_activity_log_created ON activity_log(created_at)");
1932
2186
  ensureTable("api_keys", `
1933
2187
  CREATE TABLE api_keys (
1934
2188
  id TEXT PRIMARY KEY,
@@ -2380,11 +2634,17 @@ function ensureDir(filePath) {
2380
2634
  }
2381
2635
  }
2382
2636
  function getDatabase(dbPath) {
2383
- if (_db)
2384
- return _db;
2385
2637
  const path = dbPath || getDbPath();
2638
+ if (_db && _dbPath === path)
2639
+ return _db;
2640
+ if (_db && _dbPath !== path) {
2641
+ _db.close();
2642
+ _db = null;
2643
+ _dbPath = null;
2644
+ }
2386
2645
  ensureDir(path);
2387
2646
  _db = new Database(path);
2647
+ _dbPath = path;
2388
2648
  _db.run("PRAGMA journal_mode = WAL");
2389
2649
  _db.run("PRAGMA busy_timeout = 5000");
2390
2650
  _db.run("PRAGMA foreign_keys = ON");
@@ -2397,10 +2657,12 @@ function closeDatabase() {
2397
2657
  if (_db) {
2398
2658
  _db.close();
2399
2659
  _db = null;
2660
+ _dbPath = null;
2400
2661
  }
2401
2662
  }
2402
2663
  function resetDatabase() {
2403
2664
  _db = null;
2665
+ _dbPath = null;
2404
2666
  }
2405
2667
  function now() {
2406
2668
  return new Date().toISOString();
@@ -2461,7 +2723,7 @@ function resolvePartialId(db, table, partialId) {
2461
2723
  }
2462
2724
  return null;
2463
2725
  }
2464
- var LOCK_EXPIRY_MINUTES = 30, _db = null, ALLOWED_TABLES;
2726
+ var LOCK_EXPIRY_MINUTES = 30, _db = null, _dbPath = null, ALLOWED_TABLES;
2465
2727
  var init_database = __esm(() => {
2466
2728
  init_schema();
2467
2729
  init_machines();
@@ -3784,9 +4046,10 @@ function redactExportRecord(record) {
3784
4046
  }
3785
4047
  return base;
3786
4048
  }
3787
- var REDACTION_PLACEHOLDER = "[REDACTED]", DEFAULT_PATTERNS, DEFAULT_ALLOWLIST, customRedactors;
4049
+ var SECRET_REDACTION_SCHEMA, REDACTION_PLACEHOLDER = "[REDACTED]", DEFAULT_PATTERNS, DEFAULT_ALLOWLIST, customRedactors;
3788
4050
  var init_secret_redaction = __esm(() => {
3789
4051
  init_redaction();
4052
+ SECRET_REDACTION_SCHEMA = ["todos", "secret_redaction", "v1"].join(".");
3790
4053
  DEFAULT_PATTERNS = [
3791
4054
  { name: "openai_sk", pattern: /\bsk-[a-zA-Z0-9]{10,}\b/g },
3792
4055
  { name: "github_pat", pattern: /\bghp_[a-zA-Z0-9]{20,}\b/g },
@@ -7654,11 +7917,19 @@ function getTaskVerifications(taskId, db) {
7654
7917
  }
7655
7918
  function getTaskTraceability(taskId, db) {
7656
7919
  const d = db || getDatabase();
7920
+ const commits = getTaskCommits(taskId, d);
7921
+ const gitRefs = getTaskGitRefs(taskId, d);
7657
7922
  return {
7658
7923
  task_id: taskId,
7659
- commits: getTaskCommits(taskId, d),
7660
- git_refs: getTaskGitRefs(taskId, d),
7661
- verifications: getTaskVerifications(taskId, d)
7924
+ commits,
7925
+ git_refs: gitRefs,
7926
+ verifications: getTaskVerifications(taskId, d),
7927
+ branches: Array.from(new Set([
7928
+ ...commits.map((commit) => commit.branch).filter((branch) => Boolean(branch)),
7929
+ ...gitRefs.filter((ref) => ref.ref_type === "branch").map((ref) => ref.name)
7930
+ ])).sort(),
7931
+ release_tags: Array.from(new Set(commits.map((commit) => commit.release_tag).filter((tag) => Boolean(tag)))).sort(),
7932
+ pull_requests: commits.filter((commit) => commit.pr_url).map((commit) => ({ url: commit.pr_url, state: commit.pr_state, number: commit.pr_number }))
7662
7933
  };
7663
7934
  }
7664
7935
  var init_task_commits = __esm(() => {
@@ -9184,22 +9455,27 @@ function getHeadlessBoundaryManifest() {
9184
9455
  notes: [
9185
9456
  "Use todos CLI or todos-mcp for agent workflows.",
9186
9457
  "todos serve exposes a local-only REST API on 127.0.0.1 \u2014 not a hosted SaaS.",
9187
- "Cloud sync via @hasna/cloud is explicit opt-in from CLI/MCP, never from the dashboard."
9458
+ "Remote sync is explicit opt-in from CLI/MCP, never from the dashboard."
9188
9459
  ]
9189
9460
  };
9190
9461
  }
9191
- var HEADLESS_BOUNDARY_VERSION = "todos.headless-boundary.v1", FORBIDDEN_HOSTED_HOSTS, FORBIDDEN_WEB_PATTERNS, LOCAL_HOSTS;
9462
+ var HEADLESS_BOUNDARY_VERSION = "todos.headless-boundary.v1", FORBIDDEN_HOSTED_HOSTS, PRIVATE_PLATFORM_ORG, HOSTED_TODOS_PACKAGE, FORBIDDEN_WEB_PATTERNS, LOCAL_HOSTS;
9192
9463
  var init_headless_boundaries = __esm(() => {
9193
9464
  FORBIDDEN_HOSTED_HOSTS = [
9194
9465
  "todos.md",
9195
9466
  "www.todos.md",
9196
9467
  "preview.todos.md",
9197
9468
  "pay.hasna.tools",
9198
- "platform-todos"
9469
+ ["platform", "todos"].join("-")
9199
9470
  ];
9471
+ PRIVATE_PLATFORM_ORG = ["hasna", "studio"].join("");
9472
+ HOSTED_TODOS_PACKAGE = ["platform", "todos"].join("-");
9200
9473
  FORBIDDEN_WEB_PATTERNS = [
9201
9474
  { name: "hosted todos.md API", pattern: /https?:\/\/(?:www\.)?todos\.md/i },
9202
- { name: "platform-todos package", pattern: /@hasnastudio\/platform-todos|hasnastudio\/platform-todos/i },
9475
+ {
9476
+ name: "hosted platform package",
9477
+ pattern: new RegExp(`@${PRIVATE_PLATFORM_ORG}/${HOSTED_TODOS_PACKAGE}|${PRIVATE_PLATFORM_ORG}/${HOSTED_TODOS_PACKAGE}`, "i")
9478
+ },
9203
9479
  { name: "browser sign-in flow", pattern: /\/sign-?in\b|\/login\b.*(?:oauth|session|auth)/i },
9204
9480
  { name: "Stripe billing UI", pattern: /\bstripe\.(?:com|js)\b|\bcheckout\.sessions?\b/i },
9205
9481
  { name: "hosted OAuth redirect", pattern: /oauth.*redirect.*todos\.md/i }
@@ -31716,6 +31992,13 @@ function registerTaskCrudTools(server, ctx) {
31716
31992
  }
31717
31993
  return current.version;
31718
31994
  }
31995
+ function resolveAssignee(value) {
31996
+ try {
31997
+ return resolveId(value, "agents");
31998
+ } catch {
31999
+ return value;
32000
+ }
32001
+ }
31719
32002
  if (shouldRegisterTool("create_task")) {
31720
32003
  server.tool("create_task", "Create a new task in a project. Pass short_id=null to auto-generate.", {
31721
32004
  title: exports_external.string().describe("Task title"),
@@ -31738,7 +32021,7 @@ function registerTaskCrudTools(server, ctx) {
31738
32021
  const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, ...rest } = params;
31739
32022
  const resolved = { ...rest };
31740
32023
  if (assigned_to)
31741
- resolved.assigned_to = resolveId(assigned_to, "agents");
32024
+ resolved.assigned_to = resolveAssignee(assigned_to);
31742
32025
  if (project_id)
31743
32026
  resolved.project_id = resolveId(project_id, "projects");
31744
32027
  if (task_list_id)
@@ -31782,7 +32065,7 @@ function registerTaskCrudTools(server, ctx) {
31782
32065
  if (params.task_list_id)
31783
32066
  resolved.task_list_id = resolveId(params.task_list_id, "task_lists");
31784
32067
  if (params.assigned_to)
31785
- resolved.assigned_to = resolveId(params.assigned_to, "agents");
32068
+ resolved.assigned_to = resolveAssignee(params.assigned_to);
31786
32069
  const tasks = listTasks(resolved, undefined);
31787
32070
  if (tasks.length === 0)
31788
32071
  return { content: [{ type: "text", text: "No tasks found." }] };
@@ -31879,7 +32162,7 @@ ${task.description}` : null
31879
32162
  if (resolved.assigned_to === "")
31880
32163
  resolved.assigned_to = null;
31881
32164
  if (resolved.assigned_to && typeof resolved.assigned_to === "string")
31882
- resolved.assigned_to = resolveId(resolved.assigned_to, "agents");
32165
+ resolved.assigned_to = resolveAssignee(resolved.assigned_to);
31883
32166
  if (resolved.project_id && typeof resolved.project_id === "string")
31884
32167
  resolved.project_id = resolveId(resolved.project_id, "projects");
31885
32168
  if (resolved.task_list_id && typeof resolved.task_list_id === "string")
@@ -32093,6 +32376,25 @@ var init_project_bootstrap = __esm(() => {
32093
32376
  function rowToLabel(row) {
32094
32377
  return { ...row };
32095
32378
  }
32379
+ function normalizeName(name) {
32380
+ return name.trim().toLowerCase();
32381
+ }
32382
+ function createLabel(input, db) {
32383
+ const d = db || getDatabase();
32384
+ const id = uuid();
32385
+ const ts = now();
32386
+ d.run(`INSERT INTO labels (id, project_id, name, color, description, created_at, updated_at)
32387
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id ?? null, input.name.trim(), input.color ?? null, input.description ?? null, ts, ts]);
32388
+ return rowToLabel(d.query("SELECT * FROM labels WHERE id = ?").get(id));
32389
+ }
32390
+ function getLabel(idOrName, db) {
32391
+ const d = db || getDatabase();
32392
+ let row = d.query("SELECT * FROM labels WHERE id = ?").get(idOrName);
32393
+ if (!row) {
32394
+ row = d.query("SELECT * FROM labels WHERE lower(name) = ?").get(normalizeName(idOrName));
32395
+ }
32396
+ return row ? rowToLabel(row) : null;
32397
+ }
32096
32398
  function listLabels(projectId, db) {
32097
32399
  const d = db || getDatabase();
32098
32400
  if (projectId) {
@@ -32100,10 +32402,164 @@ function listLabels(projectId, db) {
32100
32402
  }
32101
32403
  return d.query("SELECT * FROM labels ORDER BY name").all().map(rowToLabel);
32102
32404
  }
32405
+ function updateLabel(idOrName, input, db) {
32406
+ const d = db || getDatabase();
32407
+ const existing = getLabel(idOrName, d);
32408
+ if (!existing)
32409
+ throw new Error(`Label not found: ${idOrName}`);
32410
+ const ts = now();
32411
+ d.run(`UPDATE labels SET
32412
+ name = COALESCE(?, name),
32413
+ color = COALESCE(?, color),
32414
+ description = COALESCE(?, description),
32415
+ updated_at = ?
32416
+ WHERE id = ?`, [input.name?.trim() ?? null, input.color ?? null, input.description ?? null, ts, existing.id]);
32417
+ return getLabel(existing.id, d);
32418
+ }
32419
+ function deleteLabel(idOrName, db) {
32420
+ const d = db || getDatabase();
32421
+ const existing = getLabel(idOrName, d);
32422
+ if (!existing)
32423
+ return false;
32424
+ d.run("DELETE FROM task_labels WHERE label_id = ?", [existing.id]);
32425
+ return d.run("DELETE FROM labels WHERE id = ?", [existing.id]).changes > 0;
32426
+ }
32427
+ function assignLabelToTask(taskId, labelIdOrName, db) {
32428
+ const d = db || getDatabase();
32429
+ const label = getLabel(labelIdOrName, d);
32430
+ if (!label)
32431
+ throw new Error(`Label not found: ${labelIdOrName}`);
32432
+ d.run("INSERT OR IGNORE INTO task_labels (task_id, label_id) VALUES (?, ?)", [taskId, label.id]);
32433
+ d.run("INSERT OR IGNORE INTO task_tags (task_id, tag) VALUES (?, ?)", [taskId, label.name]);
32434
+ return label;
32435
+ }
32103
32436
  var init_labels = __esm(() => {
32104
32437
  init_database();
32105
32438
  });
32106
32439
 
32440
+ // src/db/tags.ts
32441
+ function normalizeName2(name) {
32442
+ return name.trim();
32443
+ }
32444
+ function taskCount(name, db) {
32445
+ const row = db.query("SELECT COUNT(*) AS count FROM task_tags WHERE tag = ?").get(name);
32446
+ return row?.count ?? 0;
32447
+ }
32448
+ function rowToTag(row, db) {
32449
+ return {
32450
+ ...row,
32451
+ task_count: taskCount(row.name, db)
32452
+ };
32453
+ }
32454
+ function virtualTag(name, db) {
32455
+ return {
32456
+ id: name,
32457
+ name,
32458
+ color: null,
32459
+ description: null,
32460
+ task_count: taskCount(name, db),
32461
+ created_at: null,
32462
+ updated_at: null
32463
+ };
32464
+ }
32465
+ function getTagRow(idOrName, db) {
32466
+ return db.query("SELECT * FROM tags WHERE id = ?").get(idOrName) ?? db.query("SELECT * FROM tags WHERE lower(name) = lower(?)").get(idOrName);
32467
+ }
32468
+ function updateTaskTagJson(oldName, newName, db) {
32469
+ const rows = db.query("SELECT id, tags FROM tasks WHERE tags IS NOT NULL AND tags != '[]'").all();
32470
+ const update = db.prepare("UPDATE tasks SET tags = ?, updated_at = ? WHERE id = ?");
32471
+ const ts = now();
32472
+ for (const row of rows) {
32473
+ if (!row.tags)
32474
+ continue;
32475
+ let tags;
32476
+ try {
32477
+ tags = JSON.parse(row.tags);
32478
+ } catch {
32479
+ continue;
32480
+ }
32481
+ if (!tags.includes(oldName))
32482
+ continue;
32483
+ const next = newName ? Array.from(new Set(tags.map((tag) => tag === oldName ? newName : tag))) : tags.filter((tag) => tag !== oldName);
32484
+ update.run(JSON.stringify(next), ts, row.id);
32485
+ }
32486
+ }
32487
+ function renameTaskTagRows(oldName, newName, db) {
32488
+ const rows = db.query("SELECT task_id FROM task_tags WHERE tag = ?").all(oldName);
32489
+ const insert = db.prepare("INSERT OR IGNORE INTO task_tags (task_id, tag) VALUES (?, ?)");
32490
+ for (const row of rows)
32491
+ insert.run(row.task_id, newName);
32492
+ db.run("DELETE FROM task_tags WHERE tag = ?", [oldName]);
32493
+ }
32494
+ function createTag(input, db) {
32495
+ const d = db || getDatabase();
32496
+ const name = normalizeName2(input.name);
32497
+ if (!name)
32498
+ throw new Error("Tag name is required");
32499
+ const id = uuid();
32500
+ const ts = now();
32501
+ d.run(`INSERT INTO tags (id, name, color, description, created_at, updated_at)
32502
+ VALUES (?, ?, ?, ?, ?, ?)`, [id, name, input.color ?? null, input.description ?? null, ts, ts]);
32503
+ return rowToTag(d.query("SELECT * FROM tags WHERE id = ?").get(id), d);
32504
+ }
32505
+ function listTags(db) {
32506
+ const d = db || getDatabase();
32507
+ const rows = d.query("SELECT * FROM tags ORDER BY name").all();
32508
+ const tagsByName = new Map(rows.map((row) => [row.name.toLowerCase(), rowToTag(row, d)]));
32509
+ const taskTags = d.query("SELECT tag, COUNT(*) AS count FROM task_tags GROUP BY tag ORDER BY tag").all();
32510
+ for (const row of taskTags) {
32511
+ const key = row.tag.toLowerCase();
32512
+ if (!tagsByName.has(key))
32513
+ tagsByName.set(key, { ...virtualTag(row.tag, d), task_count: row.count });
32514
+ }
32515
+ return [...tagsByName.values()].sort((a, b) => a.name.localeCompare(b.name));
32516
+ }
32517
+ function getTag(idOrName, db) {
32518
+ const d = db || getDatabase();
32519
+ const row = getTagRow(idOrName, d);
32520
+ if (row)
32521
+ return rowToTag(row, d);
32522
+ const existing = d.query("SELECT tag FROM task_tags WHERE lower(tag) = lower(?) LIMIT 1").get(idOrName);
32523
+ return existing ? virtualTag(existing.tag, d) : null;
32524
+ }
32525
+ function updateTag(idOrName, input, db) {
32526
+ const d = db || getDatabase();
32527
+ const existing = getTag(idOrName, d);
32528
+ if (!existing)
32529
+ throw new Error(`Tag not found: ${idOrName}`);
32530
+ const row = getTagRow(existing.id, d);
32531
+ if (!row)
32532
+ createTag({ name: existing.name, color: existing.color ?? undefined, description: existing.description ?? undefined }, d);
32533
+ const stored = getTagRow(existing.id, d) ?? getTagRow(existing.name, d);
32534
+ if (!stored)
32535
+ throw new Error(`Tag not found: ${idOrName}`);
32536
+ const nextName = input.name !== undefined ? normalizeName2(input.name) : stored.name;
32537
+ if (!nextName)
32538
+ throw new Error("Tag name is required");
32539
+ const duplicate = getTagRow(nextName, d);
32540
+ if (duplicate && duplicate.id !== stored.id)
32541
+ throw new Error(`Tag already exists: ${nextName}`);
32542
+ if (nextName !== stored.name) {
32543
+ renameTaskTagRows(stored.name, nextName, d);
32544
+ updateTaskTagJson(stored.name, nextName, d);
32545
+ }
32546
+ d.run(`UPDATE tags SET name = ?, color = COALESCE(?, color), description = COALESCE(?, description), updated_at = ? WHERE id = ?`, [nextName, input.color ?? null, input.description ?? null, now(), stored.id]);
32547
+ return getTag(stored.id, d);
32548
+ }
32549
+ function deleteTag(idOrName, db) {
32550
+ const d = db || getDatabase();
32551
+ const existing = getTag(idOrName, d);
32552
+ if (!existing)
32553
+ return false;
32554
+ d.run("DELETE FROM tags WHERE id = ? OR lower(name) = lower(?)", [existing.id, existing.name]);
32555
+ d.run("DELETE FROM task_tags WHERE tag = ?", [existing.name]);
32556
+ updateTaskTagJson(existing.name, null, d);
32557
+ return true;
32558
+ }
32559
+ var init_tags = __esm(() => {
32560
+ init_database();
32561
+ });
32562
+
32107
32563
  // src/lib/retention-cleanup.ts
32108
32564
  import { existsSync as existsSync9, unlinkSync } from "fs";
32109
32565
  function normalizeScopes(scopes) {
@@ -34338,18 +34794,18 @@ var init_local_fields = __esm(() => {
34338
34794
  });
34339
34795
 
34340
34796
  // src/lib/workflow-states.ts
34341
- function normalizeName(value) {
34797
+ function normalizeName3(value) {
34342
34798
  return value.trim().toLowerCase().replace(/[\s-]+/g, "_");
34343
34799
  }
34344
34800
  function isTaskStatus(value) {
34345
34801
  return TASK_STATUSES.includes(value);
34346
34802
  }
34347
34803
  function normalizeState(input) {
34348
- const name = normalizeName(input.name || "");
34804
+ const name = normalizeName3(input.name || "");
34349
34805
  if (!name || !isTaskStatus(input.canonical_status))
34350
34806
  return null;
34351
- const aliases = [...new Set((input.aliases || []).map(normalizeName).filter((alias) => alias && alias !== name))].sort();
34352
- const transitions = input.transitions ? [...new Set(input.transitions.map(normalizeName).filter(Boolean))].sort() : null;
34807
+ const aliases = [...new Set((input.aliases || []).map(normalizeName3).filter((alias) => alias && alias !== name))].sort();
34808
+ const transitions = input.transitions ? [...new Set(input.transitions.map(normalizeName3).filter(Boolean))].sort() : null;
34353
34809
  return {
34354
34810
  name,
34355
34811
  canonical_status: input.canonical_status,
@@ -34378,7 +34834,7 @@ function listWorkflowStates(projectPath) {
34378
34834
  return mergeWorkflowConfig(workflowConfig);
34379
34835
  }
34380
34836
  function resolveWorkflowState(input, projectPath) {
34381
- const normalized = normalizeName(input);
34837
+ const normalized = normalizeName3(input);
34382
34838
  const states = listWorkflowStates(projectPath);
34383
34839
  const byName = states.find((state) => state.name === normalized);
34384
34840
  if (byName)
@@ -35368,10 +35824,10 @@ function summarizeTasks(taskIds, planIds, runIds, db) {
35368
35824
  const completed = tasks.filter((task) => task.status === "completed");
35369
35825
  const inProgress = tasks.filter((task) => task.status === "in_progress");
35370
35826
  const pending = tasks.filter((task) => task.status === "pending");
35371
- const taskCount = tasks.length;
35372
- const percent = taskCount === 0 ? 0 : Math.round(completed.length / taskCount * 100);
35827
+ const taskCount2 = tasks.length;
35828
+ const percent = taskCount2 === 0 ? 0 : Math.round(completed.length / taskCount2 * 100);
35373
35829
  return {
35374
- task_count: taskCount,
35830
+ task_count: taskCount2,
35375
35831
  completed_count: completed.length,
35376
35832
  in_progress_count: inProgress.length,
35377
35833
  pending_count: pending.length,
@@ -35379,7 +35835,7 @@ function summarizeTasks(taskIds, planIds, runIds, db) {
35379
35835
  plan_count: planIds.length,
35380
35836
  run_count: runIds.length,
35381
35837
  percent_complete: percent,
35382
- readiness: taskCount === 0 ? "empty" : blocked.length > 0 ? "blocked" : completed.length === taskCount ? "complete" : inProgress.length > 0 ? "in_progress" : "ready"
35838
+ readiness: taskCount2 === 0 ? "empty" : blocked.length > 0 ? "blocked" : completed.length === taskCount2 ? "complete" : inProgress.length > 0 ? "in_progress" : "ready"
35383
35839
  };
35384
35840
  }
35385
35841
  function createRoadmap(input) {
@@ -36558,7 +37014,7 @@ function normalizeScope(scope) {
36558
37014
  }
36559
37015
  return "tasks";
36560
37016
  }
36561
- function normalizeName2(name) {
37017
+ function normalizeName4(name) {
36562
37018
  const normalized = name.trim();
36563
37019
  if (!normalized)
36564
37020
  throw new Error("Saved view name is required");
@@ -36808,7 +37264,7 @@ function runSavedSearch(filters = {}, scope = "tasks", db) {
36808
37264
  }
36809
37265
  function saveSearchView(input, db) {
36810
37266
  const d = db || getDatabase();
36811
- const name = normalizeName2(input.name);
37267
+ const name = normalizeName4(input.name);
36812
37268
  const timestamp3 = now();
36813
37269
  const existing = getSearchView(name, d);
36814
37270
  if (existing) {
@@ -38465,10 +38921,10 @@ Tasks:` : null,
38465
38921
  if (shouldRegisterTool("list_tags")) {
38466
38922
  server.tool("list_tags", "List all distinct task tags in use, with task counts.", async () => {
38467
38923
  try {
38468
- const rows = getDatabase().query("SELECT tag, COUNT(*) AS count FROM task_tags GROUP BY tag ORDER BY tag").all();
38924
+ const rows = listTags();
38469
38925
  if (rows.length === 0)
38470
38926
  return { content: [{ type: "text", text: "No tags found." }] };
38471
- const lines = rows.map((r) => `${r.tag} (${r.count})`);
38927
+ const lines = rows.map((r) => `${r.color ? "[" + r.color + "] " : ""}${r.name} (${r.task_count})`);
38472
38928
  return { content: [{ type: "text", text: lines.join(`
38473
38929
  `) }] };
38474
38930
  } catch (e) {
@@ -39037,8 +39493,8 @@ var init_task_project_tools = __esm(() => {
39037
39493
  init_comments();
39038
39494
  init_task_runs();
39039
39495
  init_project_bootstrap();
39040
- init_database();
39041
39496
  init_labels();
39497
+ init_tags();
39042
39498
  init_redaction();
39043
39499
  init_retention_cleanup();
39044
39500
  init_mention_resolver();
@@ -65564,7 +66020,7 @@ var init_agent_run_dispatcher = __esm(() => {
65564
66020
 
65565
66021
  // src/lib/verification-providers.ts
65566
66022
  import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
65567
- function normalizeName3(name) {
66023
+ function normalizeName5(name) {
65568
66024
  const normalized = name.trim().toLowerCase();
65569
66025
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
65570
66026
  throw new Error("verification provider name must use lowercase letters, numbers, dashes, or underscores");
@@ -65583,10 +66039,10 @@ function timeoutMs(value) {
65583
66039
  return Math.max(1, Math.min(24 * 60 * 60000, Math.floor(value)));
65584
66040
  }
65585
66041
  function getProvider(name) {
65586
- return loadConfig().verification_providers?.[normalizeName3(name)] || null;
66042
+ return loadConfig().verification_providers?.[normalizeName5(name)] || null;
65587
66043
  }
65588
66044
  function upsertVerificationProvider(input) {
65589
- const name = normalizeName3(input.name);
66045
+ const name = normalizeName5(input.name);
65590
66046
  const config2 = loadConfig();
65591
66047
  const existing = config2.verification_providers?.[name];
65592
66048
  const timestamp3 = new Date().toISOString();
@@ -65616,7 +66072,7 @@ function listVerificationProviders() {
65616
66072
  return Object.values(loadConfig().verification_providers || {}).sort((a, b) => a.name.localeCompare(b.name));
65617
66073
  }
65618
66074
  function removeVerificationProvider(name) {
65619
- const normalized = normalizeName3(name);
66075
+ const normalized = normalizeName5(name);
65620
66076
  const config2 = loadConfig();
65621
66077
  if (!config2.verification_providers?.[normalized])
65622
66078
  return false;
@@ -69952,7 +70408,7 @@ import { basename as basename5, join as join10, resolve as resolve13 } from "pat
69952
70408
  function isObject3(value) {
69953
70409
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
69954
70410
  }
69955
- function normalizeName4(name) {
70411
+ function normalizeName6(name) {
69956
70412
  const normalized = name.trim().toLowerCase();
69957
70413
  if (!/^[a-z0-9][a-z0-9_.-]{0,63}$/.test(normalized)) {
69958
70414
  throw new Error("extension name must use lowercase letters, numbers, dots, dashes, or underscores");
@@ -69970,7 +70426,7 @@ function permissionList(value) {
69970
70426
  function normalizeManifest(input) {
69971
70427
  if (!isObject3(input))
69972
70428
  throw new Error("extension manifest must be a JSON object");
69973
- const name = normalizeName4(String(input["name"] || ""));
70429
+ const name = normalizeName6(String(input["name"] || ""));
69974
70430
  const version2 = String(input["version"] || "").trim();
69975
70431
  if (!version2)
69976
70432
  throw new Error("extension manifest requires version");
@@ -69978,7 +70434,7 @@ function normalizeManifest(input) {
69978
70434
  todos: typeof input["compatibility"]["todos"] === "string" ? input["compatibility"]["todos"] : undefined
69979
70435
  } : undefined;
69980
70436
  const commands = Array.isArray(input["commands"]) ? input["commands"].filter(isObject3).map((command) => ({
69981
- name: normalizeName4(String(command["name"] || "")),
70437
+ name: normalizeName6(String(command["name"] || "")),
69982
70438
  command: typeof command["command"] === "string" ? command["command"] : undefined,
69983
70439
  description: typeof command["description"] === "string" ? command["description"] : undefined,
69984
70440
  permissions: permissionList(command["permissions"]),
@@ -69987,12 +70443,12 @@ function normalizeManifest(input) {
69987
70443
  network: typeof command["network"] === "boolean" ? command["network"] : undefined
69988
70444
  })) : [];
69989
70445
  const mcpTools = Array.isArray(input["mcp_tools"]) ? input["mcp_tools"].filter(isObject3).map((tool) => ({
69990
- name: normalizeName4(String(tool["name"] || "")),
70446
+ name: normalizeName6(String(tool["name"] || "")),
69991
70447
  description: typeof tool["description"] === "string" ? tool["description"] : undefined,
69992
70448
  permissions: permissionList(tool["permissions"])
69993
70449
  })) : [];
69994
70450
  const templates = Array.isArray(input["templates"]) ? input["templates"].filter(isObject3).map((template) => ({
69995
- name: normalizeName4(String(template["name"] || "")),
70451
+ name: normalizeName6(String(template["name"] || "")),
69996
70452
  kind: typeof template["kind"] === "string" ? template["kind"] : undefined,
69997
70453
  description: typeof template["description"] === "string" ? template["description"] : undefined,
69998
70454
  path: typeof template["path"] === "string" ? template["path"] : undefined,
@@ -70001,7 +70457,7 @@ function normalizeManifest(input) {
70001
70457
  permissions: permissionList(template["permissions"])
70002
70458
  })) : [];
70003
70459
  const renderers = Array.isArray(input["renderers"]) ? input["renderers"].filter(isObject3).map((renderer) => ({
70004
- name: normalizeName4(String(renderer["name"] || "")),
70460
+ name: normalizeName6(String(renderer["name"] || "")),
70005
70461
  target: typeof renderer["target"] === "string" ? renderer["target"] : "",
70006
70462
  description: typeof renderer["description"] === "string" ? renderer["description"] : undefined,
70007
70463
  command: typeof renderer["command"] === "string" ? renderer["command"] : undefined,
@@ -70400,10 +70856,10 @@ function listLocalExtensions() {
70400
70856
  return Object.values(loadConfig().extension_registry || {}).sort((a, b) => a.name.localeCompare(b.name));
70401
70857
  }
70402
70858
  function getLocalExtension(name) {
70403
- return loadConfig().extension_registry?.[normalizeName4(name)] || null;
70859
+ return loadConfig().extension_registry?.[normalizeName6(name)] || null;
70404
70860
  }
70405
70861
  function removeLocalExtension(name) {
70406
- const normalized = normalizeName4(name);
70862
+ const normalized = normalizeName6(name);
70407
70863
  const config2 = loadConfig();
70408
70864
  if (!config2.extension_registry?.[normalized])
70409
70865
  return false;
@@ -75914,8 +76370,6 @@ var init_builtin_templates = __esm(() => {
75914
76370
  },
75915
76371
  {
75916
76372
  name: "open-source-project",
75917
- version: 1,
75918
- category: "project",
75919
76373
  description: "Full open-source project bootstrap \u2014 scaffold to publish",
75920
76374
  category: "open-source",
75921
76375
  version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
@@ -75941,7 +76395,7 @@ var init_builtin_templates = __esm(() => {
75941
76395
  },
75942
76396
  {
75943
76397
  name: "release",
75944
- version: 1,
76398
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
75945
76399
  category: "ops",
75946
76400
  description: "Version release workflow \u2014 test, changelog, publish, verify",
75947
76401
  variables: [
@@ -75958,7 +76412,7 @@ var init_builtin_templates = __esm(() => {
75958
76412
  },
75959
76413
  {
75960
76414
  name: "docs-refresh",
75961
- version: 1,
76415
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
75962
76416
  category: "workflow",
75963
76417
  description: "Documentation refresh \u2014 audit, update, verify links",
75964
76418
  variables: [{ name: "scope", required: true, description: "Docs scope (README, API, AGENTS.md)" }],
@@ -75971,7 +76425,7 @@ var init_builtin_templates = __esm(() => {
75971
76425
  },
75972
76426
  {
75973
76427
  name: "migration",
75974
- version: 1,
76428
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
75975
76429
  category: "ops",
75976
76430
  description: "Schema/data migration workflow",
75977
76431
  variables: [
@@ -75988,7 +76442,7 @@ var init_builtin_templates = __esm(() => {
75988
76442
  },
75989
76443
  {
75990
76444
  name: "incident-response",
75991
- version: 1,
76445
+ version: BUILTIN_TEMPLATE_LIBRARY_VERSION,
75992
76446
  category: "ops",
75993
76447
  description: "Incident triage, mitigation, postmortem",
75994
76448
  variables: [{ name: "incident", required: true, description: "Incident summary" }],
@@ -76035,8 +76489,8 @@ function registerTemplateTools(server, { shouldRegisterTool, resolveId, formatEr
76035
76489
  const { createTemplate: createTemplate2, getTemplateWithTasks: getTemplateWithTasks2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
76036
76490
  const t = createTemplate2(params);
76037
76491
  const withTasks = getTemplateWithTasks2(t.id);
76038
- const taskCount = withTasks?.tasks.length ?? 0;
76039
- const taskInfo = taskCount > 0 ? ` | ${taskCount} task(s)` : "";
76492
+ const taskCount2 = withTasks?.tasks.length ?? 0;
76493
+ const taskInfo = taskCount2 > 0 ? ` | ${taskCount2} task(s)` : "";
76040
76494
  return { content: [{ type: "text", text: `Template created: ${t.id.slice(0, 8)} | ${t.name} | "${t.title_pattern}"${taskInfo}` }] };
76041
76495
  } catch (e) {
76042
76496
  return { content: [{ type: "text", text: formatError2(e) }], isError: true };
@@ -76740,6 +77194,26 @@ function getMcpVersion() {
76740
77194
  function hasVersionFlag() {
76741
77195
  return process.argv.includes("--version") || process.argv.includes("-V");
76742
77196
  }
77197
+ function hasHelpFlag() {
77198
+ return process.argv.includes("--help") || process.argv.includes("-h");
77199
+ }
77200
+ function printHelp() {
77201
+ console.log(`Usage: todos-mcp [options]
77202
+
77203
+ Start the @hasna/todos MCP server.
77204
+
77205
+ Options:
77206
+ --stdio Use stdio transport
77207
+ --port <port> Use Streamable HTTP on the given port
77208
+ -V, --version output the version number
77209
+ -h, --help display help for command
77210
+
77211
+ Environment:
77212
+ TODOS_MCP_STDIO=true Force stdio transport
77213
+ TODOS_MCP_PORT=<port> HTTP port when not using stdio
77214
+ TODOS_PROFILE=<profile> Tool profile filter
77215
+ TODOS_TOOL_GROUPS=<list> Comma-separated tool group filter`);
77216
+ }
76743
77217
  function shouldRegisterTool(name) {
76744
77218
  return shouldRegisterToolForProfile(name);
76745
77219
  }
@@ -76955,6 +77429,10 @@ var init_mcp3 = __esm(() => {
76955
77429
  console.log(getMcpVersion());
76956
77430
  process.exit(0);
76957
77431
  }
77432
+ if (hasHelpFlag()) {
77433
+ printHelp();
77434
+ process.exit(0);
77435
+ }
76958
77436
  agentFocusMap = new Map;
76959
77437
  isDirectRun = process.argv[1]?.endsWith("/mcp/index.ts") || process.argv[1]?.endsWith("/mcp/index.js");
76960
77438
  if (isDirectRun) {
@@ -77247,8 +77725,6 @@ Dashboard not found at: ${dashboardDir}`);
77247
77725
  return handleMcpHttpRequest2(req, buildServer2);
77248
77726
  }
77249
77727
  if (method === "OPTIONS") {
77250
- const reqOrigin2 = req.headers.get("origin") || undefined;
77251
- const allowed = reqOrigin2 && (reqOrigin2 === `http://localhost:${port}` || reqOrigin2 === "http://localhost:0");
77252
77728
  return new Response(null, {
77253
77729
  headers: corsHeaders || {
77254
77730
  Vary: "Origin"
@@ -77517,11 +77993,30 @@ var init_serve = __esm(() => {
77517
77993
 
77518
77994
  // src/server/index.ts
77519
77995
  init_package_version();
77520
- init_serve();
77521
77996
  var DEFAULT_PORT = 19427;
77522
77997
  function hasVersionFlag2() {
77523
77998
  return process.argv.includes("--version") || process.argv.includes("-V");
77524
77999
  }
78000
+ function hasHelpFlag2() {
78001
+ return process.argv.includes("--help") || process.argv.includes("-h");
78002
+ }
78003
+ function printHelp2() {
78004
+ console.log(`Usage: todos-serve [options]
78005
+
78006
+ Start the @hasna/todos dashboard server.
78007
+
78008
+ Options:
78009
+ --port <port> HTTP port to bind. Defaults to ${DEFAULT_PORT}
78010
+ --host <host> Hostname to bind. Defaults to 127.0.0.1
78011
+ --api-key <key> Require this API key for dashboard/API requests
78012
+ --no-open Do not open the dashboard in a browser
78013
+ -V, --version output the version number
78014
+ -h, --help display help for command
78015
+
78016
+ Environment:
78017
+ TODOS_NO_OPEN=true Do not open the dashboard in a browser
78018
+ TODOS_API_KEY=<key> Require this API key for dashboard/API requests`);
78019
+ }
77525
78020
  function parsePort() {
77526
78021
  const portArg = process.argv.find((a) => a === "--port" || a.startsWith("--port="));
77527
78022
  if (portArg) {
@@ -77557,13 +78052,18 @@ async function main2() {
77557
78052
  console.log(getPackageVersion());
77558
78053
  return;
77559
78054
  }
78055
+ if (hasHelpFlag2()) {
78056
+ printHelp2();
78057
+ return;
78058
+ }
77560
78059
  const requestedPort = parsePort();
77561
78060
  const port = await findFreePort(requestedPort);
77562
78061
  if (port !== requestedPort) {
77563
78062
  console.log(`Port ${requestedPort} in use, using ${port}`);
77564
78063
  }
77565
78064
  const noOpen = process.argv.includes("--no-open") || process.env["TODOS_NO_OPEN"] === "true";
77566
- startServer(port, {
78065
+ const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
78066
+ startServer2(port, {
77567
78067
  open: !noOpen,
77568
78068
  host: parseStringArg("--host"),
77569
78069
  apiKey: parseStringArg("--api-key")