@hasna/todos 0.8.0 → 0.9.0

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 (57) hide show
  1. package/README.md +2 -23
  2. package/dist/cli/components/App.d.ts +2 -0
  3. package/dist/cli/components/App.d.ts.map +1 -0
  4. package/dist/cli/components/Header.d.ts +8 -0
  5. package/dist/cli/components/Header.d.ts.map +1 -0
  6. package/dist/cli/components/ProjectList.d.ts +8 -0
  7. package/dist/cli/components/ProjectList.d.ts.map +1 -0
  8. package/dist/cli/components/SearchView.d.ts +10 -0
  9. package/dist/cli/components/SearchView.d.ts.map +1 -0
  10. package/dist/cli/components/TaskDetail.d.ts +7 -0
  11. package/dist/cli/components/TaskDetail.d.ts.map +1 -0
  12. package/dist/cli/components/TaskForm.d.ts +15 -0
  13. package/dist/cli/components/TaskForm.d.ts.map +1 -0
  14. package/dist/cli/components/TaskList.d.ts +8 -0
  15. package/dist/cli/components/TaskList.d.ts.map +1 -0
  16. package/dist/cli/index.d.ts +3 -0
  17. package/dist/cli/index.d.ts.map +1 -0
  18. package/dist/cli/index.js +561 -7192
  19. package/dist/db/agents.d.ts +13 -0
  20. package/dist/db/agents.d.ts.map +1 -0
  21. package/dist/db/comments.d.ts +7 -0
  22. package/dist/db/comments.d.ts.map +1 -0
  23. package/dist/db/database.d.ts +12 -0
  24. package/dist/db/database.d.ts.map +1 -0
  25. package/dist/db/plans.d.ts +8 -0
  26. package/dist/db/plans.d.ts.map +1 -0
  27. package/dist/db/projects.d.ts +11 -0
  28. package/dist/db/projects.d.ts.map +1 -0
  29. package/dist/db/sessions.d.ts +8 -0
  30. package/dist/db/sessions.d.ts.map +1 -0
  31. package/dist/db/task-lists.d.ts +10 -0
  32. package/dist/db/task-lists.d.ts.map +1 -0
  33. package/dist/db/tasks.d.ts +17 -0
  34. package/dist/db/tasks.d.ts.map +1 -0
  35. package/dist/index.d.ts +16 -299
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +194 -272
  38. package/dist/lib/agent-tasks.d.ts +11 -0
  39. package/dist/lib/agent-tasks.d.ts.map +1 -0
  40. package/dist/lib/claude-tasks.d.ts +20 -0
  41. package/dist/lib/claude-tasks.d.ts.map +1 -0
  42. package/dist/lib/config.d.ts +21 -0
  43. package/dist/lib/config.d.ts.map +1 -0
  44. package/dist/lib/search.d.ts +4 -0
  45. package/dist/lib/search.d.ts.map +1 -0
  46. package/dist/lib/sync-types.d.ts +16 -0
  47. package/dist/lib/sync-types.d.ts.map +1 -0
  48. package/dist/lib/sync-utils.d.ts +12 -0
  49. package/dist/lib/sync-utils.d.ts.map +1 -0
  50. package/dist/lib/sync.d.ts +9 -0
  51. package/dist/lib/sync.d.ts.map +1 -0
  52. package/dist/mcp/index.d.ts +3 -0
  53. package/dist/mcp/index.d.ts.map +1 -0
  54. package/dist/mcp/index.js +343 -67
  55. package/dist/types/index.d.ts +275 -0
  56. package/dist/types/index.d.ts.map +1 -0
  57. package/package.json +3 -6
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-types.d.ts","sourceRoot":"","sources":["../../src/lib/sync-types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1,12 @@
1
+ import type { SyncConflict } from "./sync-types.js";
2
+ export declare const HOME: string;
3
+ export declare function ensureDir(dir: string): void;
4
+ export declare function listJsonFiles(dir: string): string[];
5
+ export declare function readJsonFile<T>(path: string): T | null;
6
+ export declare function writeJsonFile(path: string, data: unknown): void;
7
+ export declare function readHighWaterMark(dir: string): number;
8
+ export declare function writeHighWaterMark(dir: string, value: number): void;
9
+ export declare function getFileMtimeMs(path: string): number | null;
10
+ export declare function parseTimestamp(value: unknown): number | null;
11
+ export declare function appendSyncConflict(metadata: Record<string, unknown>, conflict: SyncConflict, limit?: number): Record<string, unknown>;
12
+ //# sourceMappingURL=sync-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-utils.d.ts","sourceRoot":"","sources":["../../src/lib/sync-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,eAAO,MAAM,IAAI,QAA2D,CAAC;AAE7E,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAGnD;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAMtD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAE/D;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEnE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM1D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI5D;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,YAAY,EACtB,KAAK,SAAI,GACR,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIzB"}
@@ -0,0 +1,9 @@
1
+ import type { SyncPrefer, SyncResult } from "./sync-types.js";
2
+ export type SyncDirection = "push" | "pull" | "both";
3
+ export interface SyncOptions {
4
+ prefer?: SyncPrefer;
5
+ }
6
+ export declare function defaultSyncAgents(): string[];
7
+ export declare function syncWithAgent(agent: string, taskListId: string, projectId?: string, direction?: SyncDirection, options?: SyncOptions): SyncResult;
8
+ export declare function syncWithAgents(agents: string[], taskListIdByAgent: (agent: string) => string | null, projectId?: string, direction?: SyncDirection, options?: SyncOptions): SyncResult;
9
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/lib/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAK9D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AACrD,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAWD,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAQ5C;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,GAAE,aAAsB,EACjC,OAAO,GAAE,WAAgB,GACxB,UAAU,CAWZ;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EAAE,EAChB,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EACnD,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,GAAE,aAAsB,EACjC,OAAO,GAAE,WAAgB,GACxB,UAAU,CAoCZ"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":""}
package/dist/mcp/index.js CHANGED
@@ -4029,6 +4029,14 @@ class LockError extends Error {
4029
4029
  this.name = "LockError";
4030
4030
  }
4031
4031
  }
4032
+ class TaskListNotFoundError extends Error {
4033
+ taskListId;
4034
+ constructor(taskListId) {
4035
+ super(`Task list not found: ${taskListId}`);
4036
+ this.taskListId = taskListId;
4037
+ this.name = "TaskListNotFoundError";
4038
+ }
4039
+ }
4032
4040
 
4033
4041
  class DependencyCycleError extends Error {
4034
4042
  taskId;
@@ -4208,74 +4216,34 @@ var MIGRATIONS = [
4208
4216
  INSERT OR IGNORE INTO _migrations (id) VALUES (4);
4209
4217
  `,
4210
4218
  `
4211
- CREATE TABLE IF NOT EXISTS api_keys (
4219
+ CREATE TABLE IF NOT EXISTS agents (
4212
4220
  id TEXT PRIMARY KEY,
4213
- name TEXT NOT NULL,
4214
- key_hash TEXT NOT NULL UNIQUE,
4215
- key_prefix TEXT NOT NULL,
4221
+ name TEXT NOT NULL UNIQUE,
4222
+ description TEXT,
4223
+ metadata TEXT DEFAULT '{}',
4216
4224
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
4217
- last_used_at TEXT,
4218
- expires_at TEXT
4219
- );
4220
- CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
4221
- INSERT OR IGNORE INTO _migrations (id) VALUES (5);
4222
- `,
4223
- `
4224
- CREATE TABLE IF NOT EXISTS audit_log (
4225
- id TEXT PRIMARY KEY,
4226
- entity_type TEXT NOT NULL CHECK(entity_type IN ('task', 'plan', 'project', 'api_key', 'comment')),
4227
- entity_id TEXT NOT NULL,
4228
- action TEXT NOT NULL CHECK(action IN ('create', 'update', 'delete', 'start', 'complete', 'lock', 'unlock')),
4229
- actor TEXT,
4230
- changes TEXT,
4231
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
4225
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
4232
4226
  );
4233
- CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id);
4234
- CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_log(created_at);
4227
+ CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name);
4235
4228
 
4236
- CREATE TABLE IF NOT EXISTS webhooks (
4229
+ CREATE TABLE IF NOT EXISTS task_lists (
4237
4230
  id TEXT PRIMARY KEY,
4238
- url TEXT NOT NULL,
4239
- events TEXT NOT NULL DEFAULT '[]',
4240
- secret TEXT,
4241
- active INTEGER NOT NULL DEFAULT 1,
4242
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
4243
- );
4244
-
4245
- CREATE TABLE IF NOT EXISTS rate_limits (
4246
- key TEXT PRIMARY KEY,
4247
- count INTEGER NOT NULL DEFAULT 0,
4248
- window_start TEXT NOT NULL DEFAULT (datetime('now'))
4249
- );
4250
-
4251
- INSERT OR IGNORE INTO _migrations (id) VALUES (6);
4252
- `,
4253
- `
4254
- CREATE TABLE IF NOT EXISTS billing_customers (
4255
- id TEXT PRIMARY KEY,
4256
- stripe_customer_id TEXT UNIQUE,
4257
- email TEXT,
4258
- name TEXT,
4259
- plan TEXT NOT NULL DEFAULT 'free' CHECK(plan IN ('free', 'pro', 'team', 'enterprise')),
4260
- stripe_subscription_id TEXT,
4261
- subscription_status TEXT DEFAULT 'active' CHECK(subscription_status IN ('active', 'past_due', 'canceled', 'trialing', 'incomplete')),
4262
- current_period_end TEXT,
4231
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
4232
+ slug TEXT NOT NULL,
4233
+ name TEXT NOT NULL,
4234
+ description TEXT,
4235
+ metadata TEXT DEFAULT '{}',
4263
4236
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
4264
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
4237
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
4238
+ UNIQUE(project_id, slug)
4265
4239
  );
4266
- CREATE INDEX IF NOT EXISTS idx_billing_stripe ON billing_customers(stripe_customer_id);
4240
+ CREATE INDEX IF NOT EXISTS idx_task_lists_project ON task_lists(project_id);
4241
+ CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug);
4267
4242
 
4268
- CREATE TABLE IF NOT EXISTS usage_records (
4269
- id TEXT PRIMARY KEY,
4270
- customer_id TEXT REFERENCES billing_customers(id),
4271
- metric TEXT NOT NULL,
4272
- count INTEGER NOT NULL DEFAULT 0,
4273
- period TEXT NOT NULL,
4274
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
4275
- );
4276
- CREATE INDEX IF NOT EXISTS idx_usage_customer ON usage_records(customer_id, period);
4243
+ ALTER TABLE tasks ADD COLUMN task_list_id TEXT REFERENCES task_lists(id) ON DELETE SET NULL;
4244
+ CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id);
4277
4245
 
4278
- INSERT OR IGNORE INTO _migrations (id) VALUES (7);
4246
+ INSERT OR IGNORE INTO _migrations (id) VALUES (5);
4279
4247
  `
4280
4248
  ];
4281
4249
  var _db = null;
@@ -4398,12 +4366,13 @@ function createTask(input, db) {
4398
4366
  const id = uuid();
4399
4367
  const timestamp = now();
4400
4368
  const tags = input.tags || [];
4401
- d.run(`INSERT INTO tasks (id, project_id, parent_id, plan_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at)
4402
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
4369
+ d.run(`INSERT INTO tasks (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)
4370
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
4403
4371
  id,
4404
4372
  input.project_id || null,
4405
4373
  input.parent_id || null,
4406
4374
  input.plan_id || null,
4375
+ input.task_list_id || null,
4407
4376
  input.title,
4408
4377
  input.description || null,
4409
4378
  input.status || "pending",
@@ -4511,6 +4480,10 @@ function listTasks(filter = {}, db) {
4511
4480
  conditions.push("plan_id = ?");
4512
4481
  params.push(filter.plan_id);
4513
4482
  }
4483
+ if (filter.task_list_id) {
4484
+ conditions.push("task_list_id = ?");
4485
+ params.push(filter.task_list_id);
4486
+ }
4514
4487
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
4515
4488
  const limitVal = filter.limit || 100;
4516
4489
  const offsetVal = filter.offset || 0;
@@ -4566,6 +4539,10 @@ function updateTask(id, input, db) {
4566
4539
  sets.push("plan_id = ?");
4567
4540
  params.push(input.plan_id);
4568
4541
  }
4542
+ if (input.task_list_id !== undefined) {
4543
+ sets.push("task_list_id = ?");
4544
+ params.push(input.task_list_id);
4545
+ }
4569
4546
  params.push(id, input.version);
4570
4547
  const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
4571
4548
  if (result.changes === 0) {
@@ -4790,6 +4767,106 @@ function deletePlan(id, db) {
4790
4767
  return result.changes > 0;
4791
4768
  }
4792
4769
 
4770
+ // src/db/agents.ts
4771
+ function shortUuid() {
4772
+ return crypto.randomUUID().slice(0, 8);
4773
+ }
4774
+ function rowToAgent(row) {
4775
+ return {
4776
+ ...row,
4777
+ metadata: JSON.parse(row.metadata || "{}")
4778
+ };
4779
+ }
4780
+ function registerAgent(input, db) {
4781
+ const d = db || getDatabase();
4782
+ const existing = getAgentByName(input.name, d);
4783
+ if (existing) {
4784
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
4785
+ return getAgent(existing.id, d);
4786
+ }
4787
+ const id = shortUuid();
4788
+ const timestamp = now();
4789
+ d.run(`INSERT INTO agents (id, name, description, metadata, created_at, last_seen_at)
4790
+ VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
4791
+ return getAgent(id, d);
4792
+ }
4793
+ function getAgent(id, db) {
4794
+ const d = db || getDatabase();
4795
+ const row = d.query("SELECT * FROM agents WHERE id = ?").get(id);
4796
+ return row ? rowToAgent(row) : null;
4797
+ }
4798
+ function getAgentByName(name, db) {
4799
+ const d = db || getDatabase();
4800
+ const row = d.query("SELECT * FROM agents WHERE name = ?").get(name);
4801
+ return row ? rowToAgent(row) : null;
4802
+ }
4803
+ function listAgents(db) {
4804
+ const d = db || getDatabase();
4805
+ return d.query("SELECT * FROM agents ORDER BY name").all().map(rowToAgent);
4806
+ }
4807
+
4808
+ // src/db/task-lists.ts
4809
+ function rowToTaskList(row) {
4810
+ return {
4811
+ ...row,
4812
+ metadata: JSON.parse(row.metadata || "{}")
4813
+ };
4814
+ }
4815
+ function createTaskList(input, db) {
4816
+ const d = db || getDatabase();
4817
+ const id = uuid();
4818
+ const timestamp = now();
4819
+ const slug = input.slug || slugify(input.name);
4820
+ if (!input.project_id) {
4821
+ const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
4822
+ if (existing) {
4823
+ throw new Error(`Standalone task list with slug "${slug}" already exists`);
4824
+ }
4825
+ }
4826
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
4827
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
4828
+ return getTaskList(id, d);
4829
+ }
4830
+ function getTaskList(id, db) {
4831
+ const d = db || getDatabase();
4832
+ const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
4833
+ return row ? rowToTaskList(row) : null;
4834
+ }
4835
+ function listTaskLists(projectId, db) {
4836
+ const d = db || getDatabase();
4837
+ if (projectId) {
4838
+ return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
4839
+ }
4840
+ return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
4841
+ }
4842
+ function updateTaskList(id, input, db) {
4843
+ const d = db || getDatabase();
4844
+ const existing = getTaskList(id, d);
4845
+ if (!existing)
4846
+ throw new TaskListNotFoundError(id);
4847
+ const sets = ["updated_at = ?"];
4848
+ const params = [now()];
4849
+ if (input.name !== undefined) {
4850
+ sets.push("name = ?");
4851
+ params.push(input.name);
4852
+ }
4853
+ if (input.description !== undefined) {
4854
+ sets.push("description = ?");
4855
+ params.push(input.description);
4856
+ }
4857
+ if (input.metadata !== undefined) {
4858
+ sets.push("metadata = ?");
4859
+ params.push(JSON.stringify(input.metadata));
4860
+ }
4861
+ params.push(id);
4862
+ d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
4863
+ return getTaskList(id, d);
4864
+ }
4865
+ function deleteTaskList(id, db) {
4866
+ const d = db || getDatabase();
4867
+ return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
4868
+ }
4869
+
4793
4870
  // src/lib/search.ts
4794
4871
  function rowToTask2(row) {
4795
4872
  return {
@@ -4800,7 +4877,7 @@ function rowToTask2(row) {
4800
4877
  priority: row.priority
4801
4878
  };
4802
4879
  }
4803
- function searchTasks(query, projectId, db) {
4880
+ function searchTasks(query, projectId, taskListId, db) {
4804
4881
  const d = db || getDatabase();
4805
4882
  clearExpiredLocks(d);
4806
4883
  const pattern = `%${query}%`;
@@ -4810,6 +4887,10 @@ function searchTasks(query, projectId, db) {
4810
4887
  sql += " AND project_id = ?";
4811
4888
  params.push(projectId);
4812
4889
  }
4890
+ if (taskListId) {
4891
+ sql += " AND task_list_id = ?";
4892
+ params.push(taskListId);
4893
+ }
4813
4894
  sql += ` ORDER BY
4814
4895
  CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
4815
4896
  created_at DESC`;
@@ -5447,6 +5528,8 @@ function formatError(error) {
5447
5528
  return `Not found: ${error.message}`;
5448
5529
  if (error instanceof PlanNotFoundError)
5449
5530
  return `Not found: ${error.message}`;
5531
+ if (error instanceof TaskListNotFoundError)
5532
+ return `Not found: ${error.message}`;
5450
5533
  if (error instanceof LockError)
5451
5534
  return `Lock error: ${error.message}`;
5452
5535
  if (error instanceof DependencyCycleError)
@@ -5514,6 +5597,7 @@ server.tool("create_task", "Create a new task", {
5514
5597
  session_id: exports_external.string().optional().describe("Session ID"),
5515
5598
  working_dir: exports_external.string().optional().describe("Working directory context"),
5516
5599
  plan_id: exports_external.string().optional().describe("Plan ID to assign task to"),
5600
+ task_list_id: exports_external.string().optional().describe("Task list ID to assign task to"),
5517
5601
  tags: exports_external.array(exports_external.string()).optional().describe("Task tags"),
5518
5602
  metadata: exports_external.record(exports_external.unknown()).optional().describe("Arbitrary metadata")
5519
5603
  }, async (params) => {
@@ -5525,6 +5609,8 @@ server.tool("create_task", "Create a new task", {
5525
5609
  resolved.parent_id = resolveId(resolved.parent_id);
5526
5610
  if (resolved.plan_id)
5527
5611
  resolved.plan_id = resolveId(resolved.plan_id, "plans");
5612
+ if (resolved.task_list_id)
5613
+ resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
5528
5614
  const task = createTask(resolved);
5529
5615
  return { content: [{ type: "text", text: `Task created:
5530
5616
  ${formatTask(task)}` }] };
@@ -5544,7 +5630,8 @@ server.tool("list_tasks", "List tasks with optional filters", {
5544
5630
  ]).optional().describe("Filter by priority"),
5545
5631
  assigned_to: exports_external.string().optional().describe("Filter by assigned agent"),
5546
5632
  tags: exports_external.array(exports_external.string()).optional().describe("Filter by tags (any match)"),
5547
- plan_id: exports_external.string().optional().describe("Filter by plan")
5633
+ plan_id: exports_external.string().optional().describe("Filter by plan"),
5634
+ task_list_id: exports_external.string().optional().describe("Filter by task list")
5548
5635
  }, async (params) => {
5549
5636
  try {
5550
5637
  const resolved = { ...params };
@@ -5552,6 +5639,8 @@ server.tool("list_tasks", "List tasks with optional filters", {
5552
5639
  resolved.project_id = resolveId(resolved.project_id, "projects");
5553
5640
  if (resolved.plan_id)
5554
5641
  resolved.plan_id = resolveId(resolved.plan_id, "plans");
5642
+ if (resolved.task_list_id)
5643
+ resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
5555
5644
  const tasks = listTasks(resolved);
5556
5645
  if (tasks.length === 0) {
5557
5646
  return { content: [{ type: "text", text: "No tasks found." }] };
@@ -5626,7 +5715,8 @@ server.tool("update_task", "Update task fields (requires version for optimistic
5626
5715
  assigned_to: exports_external.string().optional().describe("Assign to agent"),
5627
5716
  tags: exports_external.array(exports_external.string()).optional().describe("New tags"),
5628
5717
  metadata: exports_external.record(exports_external.unknown()).optional().describe("New metadata"),
5629
- plan_id: exports_external.string().optional().describe("Plan ID to assign task to")
5718
+ plan_id: exports_external.string().optional().describe("Plan ID to assign task to"),
5719
+ task_list_id: exports_external.string().optional().describe("Task list ID")
5630
5720
  }, async ({ id, ...rest }) => {
5631
5721
  try {
5632
5722
  const resolvedId = resolveId(id);
@@ -5890,11 +5980,13 @@ server.tool("delete_plan", "Delete a plan", {
5890
5980
  });
5891
5981
  server.tool("search_tasks", "Full-text search across task titles, descriptions, and tags", {
5892
5982
  query: exports_external.string().describe("Search query"),
5893
- project_id: exports_external.string().optional().describe("Limit to project")
5894
- }, async ({ query, project_id }) => {
5983
+ project_id: exports_external.string().optional().describe("Limit to project"),
5984
+ task_list_id: exports_external.string().optional().describe("Filter by task list")
5985
+ }, async ({ query, project_id, task_list_id }) => {
5895
5986
  try {
5896
5987
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
5897
- const tasks = searchTasks(query, resolvedProjectId);
5988
+ const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
5989
+ const tasks = searchTasks(query, resolvedProjectId, resolvedTaskListId);
5898
5990
  if (tasks.length === 0) {
5899
5991
  return { content: [{ type: "text", text: `No tasks matching "${query}".` }] };
5900
5992
  }
@@ -5954,6 +6046,182 @@ server.tool("sync", "Sync tasks with an agent task list (Claude uses native task
5954
6046
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
5955
6047
  }
5956
6048
  });
6049
+ server.tool("register_agent", "Register an agent and get a short UUID. Idempotent: same name returns existing agent.", {
6050
+ name: exports_external.string().describe("Agent name"),
6051
+ description: exports_external.string().optional().describe("Agent description")
6052
+ }, async ({ name, description }) => {
6053
+ try {
6054
+ const agent = registerAgent({ name, description });
6055
+ return {
6056
+ content: [{
6057
+ type: "text",
6058
+ text: `Agent registered:
6059
+ ID: ${agent.id}
6060
+ Name: ${agent.name}${agent.description ? `
6061
+ Description: ${agent.description}` : ""}
6062
+ Created: ${agent.created_at}
6063
+ Last seen: ${agent.last_seen_at}`
6064
+ }]
6065
+ };
6066
+ } catch (e) {
6067
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6068
+ }
6069
+ });
6070
+ server.tool("list_agents", "List all registered agents", {}, async () => {
6071
+ try {
6072
+ const agents = listAgents();
6073
+ if (agents.length === 0) {
6074
+ return { content: [{ type: "text", text: "No agents registered." }] };
6075
+ }
6076
+ const text = agents.map((a) => {
6077
+ return `${a.id} | ${a.name}${a.description ? ` - ${a.description}` : ""} (last seen: ${a.last_seen_at})`;
6078
+ }).join(`
6079
+ `);
6080
+ return { content: [{ type: "text", text: `${agents.length} agent(s):
6081
+ ${text}` }] };
6082
+ } catch (e) {
6083
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6084
+ }
6085
+ });
6086
+ server.tool("get_agent", "Get agent details by ID or name", {
6087
+ id: exports_external.string().optional().describe("Agent ID"),
6088
+ name: exports_external.string().optional().describe("Agent name")
6089
+ }, async ({ id, name }) => {
6090
+ try {
6091
+ if (!id && !name) {
6092
+ return { content: [{ type: "text", text: "Provide either id or name." }], isError: true };
6093
+ }
6094
+ const agent = id ? getAgent(id) : getAgentByName(name);
6095
+ if (!agent) {
6096
+ return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
6097
+ }
6098
+ const parts = [
6099
+ `ID: ${agent.id}`,
6100
+ `Name: ${agent.name}`
6101
+ ];
6102
+ if (agent.description)
6103
+ parts.push(`Description: ${agent.description}`);
6104
+ if (Object.keys(agent.metadata).length > 0)
6105
+ parts.push(`Metadata: ${JSON.stringify(agent.metadata)}`);
6106
+ parts.push(`Created: ${agent.created_at}`);
6107
+ parts.push(`Last seen: ${agent.last_seen_at}`);
6108
+ return { content: [{ type: "text", text: parts.join(`
6109
+ `) }] };
6110
+ } catch (e) {
6111
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6112
+ }
6113
+ });
6114
+ server.tool("create_task_list", "Create a new task list", {
6115
+ name: exports_external.string().describe("Task list name"),
6116
+ slug: exports_external.string().optional().describe("URL-friendly slug (auto-generated from name if omitted)"),
6117
+ project_id: exports_external.string().optional().describe("Project ID to associate with"),
6118
+ description: exports_external.string().optional().describe("Task list description")
6119
+ }, async (params) => {
6120
+ try {
6121
+ const resolved = { ...params };
6122
+ if (resolved.project_id)
6123
+ resolved.project_id = resolveId(resolved.project_id, "projects");
6124
+ const list = createTaskList(resolved);
6125
+ return {
6126
+ content: [{
6127
+ type: "text",
6128
+ text: `Task list created:
6129
+ ID: ${list.id}
6130
+ Name: ${list.name}
6131
+ Slug: ${list.slug}${list.project_id ? `
6132
+ Project: ${list.project_id}` : ""}${list.description ? `
6133
+ Description: ${list.description}` : ""}`
6134
+ }]
6135
+ };
6136
+ } catch (e) {
6137
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6138
+ }
6139
+ });
6140
+ server.tool("list_task_lists", "List task lists, optionally filtered by project", {
6141
+ project_id: exports_external.string().optional().describe("Filter by project")
6142
+ }, async ({ project_id }) => {
6143
+ try {
6144
+ const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
6145
+ const lists = listTaskLists(resolvedProjectId);
6146
+ if (lists.length === 0) {
6147
+ return { content: [{ type: "text", text: "No task lists found." }] };
6148
+ }
6149
+ const text = lists.map((l) => {
6150
+ const project = l.project_id ? ` (project: ${l.project_id.slice(0, 8)})` : "";
6151
+ return `${l.id.slice(0, 8)} | ${l.name} [${l.slug}]${project}${l.description ? ` - ${l.description}` : ""}`;
6152
+ }).join(`
6153
+ `);
6154
+ return { content: [{ type: "text", text: `${lists.length} task list(s):
6155
+ ${text}` }] };
6156
+ } catch (e) {
6157
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6158
+ }
6159
+ });
6160
+ server.tool("get_task_list", "Get task list details", {
6161
+ id: exports_external.string().describe("Task list ID (full or partial)")
6162
+ }, async ({ id }) => {
6163
+ try {
6164
+ const resolvedId = resolveId(id, "task_lists");
6165
+ const list = getTaskList(resolvedId);
6166
+ if (!list) {
6167
+ return { content: [{ type: "text", text: `Task list not found: ${id}` }], isError: true };
6168
+ }
6169
+ const parts = [
6170
+ `ID: ${list.id}`,
6171
+ `Name: ${list.name}`,
6172
+ `Slug: ${list.slug}`
6173
+ ];
6174
+ if (list.project_id)
6175
+ parts.push(`Project: ${list.project_id}`);
6176
+ if (list.description)
6177
+ parts.push(`Description: ${list.description}`);
6178
+ if (Object.keys(list.metadata).length > 0)
6179
+ parts.push(`Metadata: ${JSON.stringify(list.metadata)}`);
6180
+ parts.push(`Created: ${list.created_at}`);
6181
+ parts.push(`Updated: ${list.updated_at}`);
6182
+ return { content: [{ type: "text", text: parts.join(`
6183
+ `) }] };
6184
+ } catch (e) {
6185
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6186
+ }
6187
+ });
6188
+ server.tool("update_task_list", "Update a task list", {
6189
+ id: exports_external.string().describe("Task list ID (full or partial)"),
6190
+ name: exports_external.string().optional().describe("New name"),
6191
+ description: exports_external.string().optional().describe("New description")
6192
+ }, async ({ id, ...rest }) => {
6193
+ try {
6194
+ const resolvedId = resolveId(id, "task_lists");
6195
+ const list = updateTaskList(resolvedId, rest);
6196
+ return {
6197
+ content: [{
6198
+ type: "text",
6199
+ text: `Task list updated:
6200
+ ID: ${list.id}
6201
+ Name: ${list.name}
6202
+ Slug: ${list.slug}`
6203
+ }]
6204
+ };
6205
+ } catch (e) {
6206
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6207
+ }
6208
+ });
6209
+ server.tool("delete_task_list", "Delete a task list. Tasks in this list keep their data but lose their list association.", {
6210
+ id: exports_external.string().describe("Task list ID (full or partial)")
6211
+ }, async ({ id }) => {
6212
+ try {
6213
+ const resolvedId = resolveId(id, "task_lists");
6214
+ const deleted = deleteTaskList(resolvedId);
6215
+ return {
6216
+ content: [{
6217
+ type: "text",
6218
+ text: deleted ? `Task list ${id} deleted.` : `Task list ${id} not found.`
6219
+ }]
6220
+ };
6221
+ } catch (e) {
6222
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
6223
+ }
6224
+ });
5957
6225
  server.resource("tasks", "todos://tasks", { description: "All active tasks", mimeType: "application/json" }, async () => {
5958
6226
  const tasks = listTasks({ status: ["pending", "in_progress"] });
5959
6227
  return { contents: [{ uri: "todos://tasks", text: JSON.stringify(tasks, null, 2), mimeType: "application/json" }] };
@@ -5962,6 +6230,14 @@ server.resource("projects", "todos://projects", { description: "All registered p
5962
6230
  const projects = listProjects();
5963
6231
  return { contents: [{ uri: "todos://projects", text: JSON.stringify(projects, null, 2), mimeType: "application/json" }] };
5964
6232
  });
6233
+ server.resource("agents", "todos://agents", { description: "All registered agents", mimeType: "application/json" }, async () => {
6234
+ const agents = listAgents();
6235
+ return { contents: [{ uri: "todos://agents", text: JSON.stringify(agents, null, 2), mimeType: "application/json" }] };
6236
+ });
6237
+ server.resource("task-lists", "todos://task-lists", { description: "All task lists", mimeType: "application/json" }, async () => {
6238
+ const lists = listTaskLists();
6239
+ return { contents: [{ uri: "todos://task-lists", text: JSON.stringify(lists, null, 2), mimeType: "application/json" }] };
6240
+ });
5965
6241
  async function main() {
5966
6242
  const transport = new StdioServerTransport;
5967
6243
  await server.connect(transport);