@hasna/todos 0.11.83 → 0.11.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -8587,6 +8587,54 @@ async function cloudRegisterAgent(client, input) {
8587
8587
  }
8588
8588
  return raw;
8589
8589
  }
8590
+ async function cloudHeartbeatAgent(client, idOrName) {
8591
+ const raw = await client.transport.post(`/agents/${encodeURIComponent(idOrName)}/heartbeat`, {});
8592
+ if (raw && typeof raw === "object" && "agent" in raw) {
8593
+ return raw.agent;
8594
+ }
8595
+ return raw ?? null;
8596
+ }
8597
+ async function cloudReleaseAgent(client, idOrName, sessionId) {
8598
+ const raw = await client.transport.post(`/agents/${encodeURIComponent(idOrName)}/release`, sessionId ? { session_id: sessionId } : {});
8599
+ const env = raw ?? {};
8600
+ return { agent: env.agent ?? null, released: env.released !== false };
8601
+ }
8602
+ async function cloudLinkCommit(client, taskId, input) {
8603
+ const raw = await client.transport.post(`/tasks/${encodeURIComponent(taskId)}/commits`, input);
8604
+ if (raw && typeof raw === "object" && "commit" in raw) {
8605
+ return raw.commit;
8606
+ }
8607
+ return raw;
8608
+ }
8609
+ async function cloudFindCommit(client, sha) {
8610
+ const raw = await client.transport.get(`/commits/${encodeURIComponent(sha)}`);
8611
+ const env = raw ?? {};
8612
+ return env.commit ?? null;
8613
+ }
8614
+ async function cloudLinkRef(client, taskId, input) {
8615
+ const raw = await client.transport.post(`/tasks/${encodeURIComponent(taskId)}/refs`, input);
8616
+ if (raw && typeof raw === "object" && "ref" in raw) {
8617
+ return raw.ref;
8618
+ }
8619
+ return raw;
8620
+ }
8621
+ async function cloudFindRefs(client, ref) {
8622
+ const raw = await client.transport.get(`/refs/${encodeURIComponent(ref)}`);
8623
+ const env = raw ?? {};
8624
+ return Array.isArray(env.refs) ? env.refs : [];
8625
+ }
8626
+ async function cloudResolvePlan(client, ref, projectId) {
8627
+ const direct = await client.get("plans", ref).catch(() => null);
8628
+ if (direct) {
8629
+ const env = direct;
8630
+ if (env.plan)
8631
+ return env.plan;
8632
+ if (direct.id)
8633
+ return direct;
8634
+ }
8635
+ const plans = await cloudListPlans(client, projectId);
8636
+ return plans.find((p) => p.id === ref) ?? plans.find((p) => p.slug === ref) ?? plans.find((p) => p.name === ref) ?? plans.find((p) => p.id.startsWith(ref)) ?? null;
8637
+ }
8590
8638
  async function cloudLockTask(client, id, agentId) {
8591
8639
  const raw = await client.transport.post(`/tasks/${encodeURIComponent(id)}/lock`, { agent_id: agentId });
8592
8640
  if (raw && typeof raw === "object" && "result" in raw) {
@@ -8627,9 +8675,230 @@ async function cloudRecordVerification(client, id, input) {
8627
8675
  }
8628
8676
  return raw;
8629
8677
  }
8630
- var _cache;
8678
+ function priorityRank(priority) {
8679
+ return PRIORITY_RANK[priority ?? ""] ?? 4;
8680
+ }
8681
+ async function cloudActiveTasks(client, filter = {}) {
8682
+ const [pending, inProgress] = await Promise.all([
8683
+ cloudListTasks(client, { ...filter, status: "pending" }),
8684
+ cloudListTasks(client, { ...filter, status: "in_progress" })
8685
+ ]);
8686
+ return [...pending, ...inProgress];
8687
+ }
8688
+ async function cloudActiveWork(client, filter = {}) {
8689
+ const tasks = await cloudListTasks(client, { ...filter, status: "in_progress" });
8690
+ return tasks.sort((a, b) => priorityRank(a.priority) - priorityRank(b.priority) || (b.updated_at ?? "").localeCompare(a.updated_at ?? ""));
8691
+ }
8692
+ async function cloudStaleTasks(client, minutes, filter = {}) {
8693
+ const cutoff = new Date(Date.now() - minutes * 60 * 1000).toISOString();
8694
+ const tasks = await cloudListTasks(client, { ...filter, status: "in_progress" });
8695
+ return tasks.filter((t) => (t.updated_at ?? "") < cutoff || t.locked_at != null && t.locked_at < cutoff).sort((a, b) => (a.updated_at ?? "").localeCompare(b.updated_at ?? ""));
8696
+ }
8697
+ async function cloudOverdueTasks(client, projectId, at = new Date) {
8698
+ const nowStr = at.toISOString();
8699
+ const filter = projectId ? { project_id: projectId } : {};
8700
+ const active = await cloudActiveTasks(client, filter);
8701
+ return active.filter((t) => !t.archived_at && t.due_at != null && t.due_at < nowStr).sort((a, b) => (a.due_at ?? "").localeCompare(b.due_at ?? ""));
8702
+ }
8703
+ async function cloudEscalatedTasks(client, opts = {}, at = new Date) {
8704
+ const nowMs = at.getTime();
8705
+ const filter = {};
8706
+ if (opts.project_id)
8707
+ filter.project_id = opts.project_id;
8708
+ const active = await cloudActiveTasks(client, filter);
8709
+ return active.filter((t) => !t.archived_at && (opts.agent_id ? t.assigned_to === opts.agent_id : true)).map((task) => {
8710
+ const reasons = [];
8711
+ const breachedTimes = [];
8712
+ if (task.due_at) {
8713
+ const dueMs = new Date(task.due_at).getTime();
8714
+ if (Number.isFinite(dueMs) && dueMs < nowMs) {
8715
+ reasons.push("overdue");
8716
+ breachedTimes.push(dueMs);
8717
+ }
8718
+ }
8719
+ if (task.sla_minutes != null) {
8720
+ const startMs = new Date(task.started_at ?? task.created_at).getTime();
8721
+ const breachedMs = startMs + task.sla_minutes * 60000;
8722
+ if (Number.isFinite(breachedMs) && breachedMs < nowMs) {
8723
+ reasons.push("sla_breached");
8724
+ breachedTimes.push(breachedMs);
8725
+ }
8726
+ }
8727
+ if (reasons.length === 0)
8728
+ return null;
8729
+ return { task, reasons, breached_at: new Date(Math.min(...breachedTimes)).toISOString() };
8730
+ }).filter((item) => item !== null).sort((a, b) => (a.task.due_at ?? "").localeCompare(b.task.due_at ?? "") || a.task.created_at.localeCompare(b.task.created_at));
8731
+ }
8732
+ async function cloudChangedSince(client, since, filter = {}) {
8733
+ const tasks = await cloudListTasks(client, filter);
8734
+ return tasks.filter((t) => (t.updated_at ?? "") > since).sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""));
8735
+ }
8736
+ async function cloudTaskStats(client, filter = {}) {
8737
+ const tasks = await cloudListTasks(client, filter);
8738
+ const by_status = {};
8739
+ const by_priority = {};
8740
+ const by_agent = {};
8741
+ for (const t of tasks) {
8742
+ by_status[t.status] = (by_status[t.status] ?? 0) + 1;
8743
+ by_priority[t.priority] = (by_priority[t.priority] ?? 0) + 1;
8744
+ const agent = t.assigned_to ?? t.agent_id ?? "unassigned";
8745
+ by_agent[agent] = (by_agent[agent] ?? 0) + 1;
8746
+ }
8747
+ const completed = by_status["completed"] ?? 0;
8748
+ return {
8749
+ total: tasks.length,
8750
+ by_status,
8751
+ by_priority,
8752
+ by_agent,
8753
+ completion_rate: tasks.length > 0 ? Math.round(completed / tasks.length * 100) : 0
8754
+ };
8755
+ }
8756
+ async function cloudRecentActivity(client, limit = 50) {
8757
+ const raw = await client.transport.get("/activity", { query: { limit } });
8758
+ const envelope = raw ?? {};
8759
+ if (Array.isArray(envelope.activity))
8760
+ return envelope.activity;
8761
+ if (Array.isArray(envelope.entries))
8762
+ return envelope.entries;
8763
+ return Array.isArray(raw) ? raw : [];
8764
+ }
8765
+ async function cloudListTaskLists(client, projectId) {
8766
+ const query = projectId ? { project_id: projectId } : {};
8767
+ const raw = await client.transport.get("/task-lists", { query });
8768
+ const envelope = raw ?? {};
8769
+ if (Array.isArray(envelope.task_lists))
8770
+ return envelope.task_lists;
8771
+ if (Array.isArray(envelope.taskLists))
8772
+ return envelope.taskLists;
8773
+ return Array.isArray(raw) ? raw : [];
8774
+ }
8775
+ async function cloudNextTask(client, agent, filters) {
8776
+ const query = {};
8777
+ if (agent)
8778
+ query["agent"] = agent;
8779
+ if (filters?.project_id)
8780
+ query["project_id"] = filters.project_id;
8781
+ if (filters?.task_list_id)
8782
+ query["task_list_id"] = filters.task_list_id;
8783
+ if (filters?.plan_id)
8784
+ query["plan_id"] = filters.plan_id;
8785
+ const raw = await client.transport.get("/next", { query });
8786
+ if (raw == null)
8787
+ return null;
8788
+ const task = unwrapTask(raw);
8789
+ return task && task.id ? task : null;
8790
+ }
8791
+ async function cloudAllDependencies(client) {
8792
+ const raw = await client.transport.get("/dependencies");
8793
+ const envelope = raw ?? {};
8794
+ if (Array.isArray(envelope.dependencies))
8795
+ return envelope.dependencies;
8796
+ return Array.isArray(raw) ? raw : [];
8797
+ }
8798
+ async function cloudGetTasksByIds(client, ids) {
8799
+ const unique = Array.from(new Set(ids));
8800
+ const map = new Map;
8801
+ const CONCURRENCY = 8;
8802
+ for (let i = 0;i < unique.length; i += CONCURRENCY) {
8803
+ const batch = unique.slice(i, i + CONCURRENCY);
8804
+ const tasks = await Promise.all(batch.map((id) => cloudGetTask(client, id)));
8805
+ for (const task of tasks)
8806
+ if (task && task.id)
8807
+ map.set(task.id, task);
8808
+ }
8809
+ return map;
8810
+ }
8811
+ async function cloudBlockingDepsMap(client, candidates) {
8812
+ const result = new Map;
8813
+ if (candidates.length === 0)
8814
+ return result;
8815
+ const edges = await cloudAllDependencies(client);
8816
+ const dependsByTask = new Map;
8817
+ for (const edge of edges) {
8818
+ if (!edge.task_id || !edge.depends_on)
8819
+ continue;
8820
+ const arr = dependsByTask.get(edge.task_id) ?? [];
8821
+ arr.push(edge.depends_on);
8822
+ dependsByTask.set(edge.task_id, arr);
8823
+ }
8824
+ const candidateIds = new Set(candidates.map((t) => t.id));
8825
+ const blockerIds = new Set;
8826
+ for (const id of candidateIds)
8827
+ for (const dep of dependsByTask.get(id) ?? [])
8828
+ blockerIds.add(dep);
8829
+ const blockers = await cloudGetTasksByIds(client, Array.from(blockerIds));
8830
+ for (const task of candidates) {
8831
+ const deps = dependsByTask.get(task.id) ?? [];
8832
+ const incomplete = deps.map((depId) => blockers.get(depId)).filter((b) => b != null && b.status !== "completed");
8833
+ if (incomplete.length > 0)
8834
+ result.set(task.id, incomplete);
8835
+ }
8836
+ return result;
8837
+ }
8838
+ async function cloudRecap(client, hours, projectId) {
8839
+ const since = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
8840
+ const staleWindow = new Date(Date.now() - 30 * 60 * 1000).toISOString();
8841
+ const filter = projectId ? { project_id: projectId } : {};
8842
+ const [all, agents] = await Promise.all([cloudListTasks(client, filter), cloudListAgents(client)]);
8843
+ const completed = all.filter((t) => t.status === "completed" && t.completed_at != null && t.completed_at > since).sort((a, b) => (b.completed_at ?? "").localeCompare(a.completed_at ?? "")).map((t) => ({
8844
+ ...t,
8845
+ duration_minutes: t.started_at && t.completed_at ? Math.round((new Date(t.completed_at).getTime() - new Date(t.started_at).getTime()) / 60000) : null
8846
+ }));
8847
+ const created = all.filter((t) => t.created_at > since).sort((a, b) => b.created_at.localeCompare(a.created_at));
8848
+ const in_progress = all.filter((t) => t.status === "in_progress").sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""));
8849
+ const stale = in_progress.filter((t) => (t.updated_at ?? "") < staleWindow).sort((a, b) => (a.updated_at ?? "").localeCompare(b.updated_at ?? ""));
8850
+ const pending = all.filter((t) => t.status === "pending");
8851
+ const blockedMap = await cloudBlockingDepsMap(client, pending);
8852
+ const blocked = pending.filter((t) => blockedMap.has(t.id));
8853
+ const sinceMs = new Date(since).getTime();
8854
+ const agentSummaries = agents.map((agent) => {
8855
+ const owned = all.filter((t) => t.assigned_to === agent.id || t.agent_id === agent.id);
8856
+ return {
8857
+ name: agent.name,
8858
+ completed_count: owned.filter((t) => t.status === "completed" && t.completed_at != null && t.completed_at > since).length,
8859
+ in_progress_count: owned.filter((t) => t.status === "in_progress").length,
8860
+ last_seen_at: agent.last_seen_at
8861
+ };
8862
+ }).filter((a) => a.last_seen_at != null && new Date(a.last_seen_at).getTime() > sinceMs).sort((a, b) => b.completed_count - a.completed_count);
8863
+ return { hours, since, completed, created, in_progress, blocked, stale, agents: agentSummaries };
8864
+ }
8865
+ async function cloudTimeline(client, options = {}) {
8866
+ const activity = await cloudRecentActivity(client, 5000);
8867
+ let entries = activity.map((h) => ({
8868
+ id: h.id,
8869
+ source: "task_history",
8870
+ event_type: h.action,
8871
+ entity_type: "task",
8872
+ entity_id: h.task_id,
8873
+ task_id: h.task_id,
8874
+ project_id: null,
8875
+ plan_id: null,
8876
+ run_id: null,
8877
+ agent_id: h.agent_id ?? null,
8878
+ created_at: h.created_at,
8879
+ title: "",
8880
+ message: h.field ? `${h.field}: ${h.old_value ?? ""}${h.new_value != null ? ` -> ${h.new_value}` : ""}`.trim() : null,
8881
+ metadata: {}
8882
+ }));
8883
+ if (options.entity_type && options.entity_type !== "task") {
8884
+ entries = [];
8885
+ } else if (options.entity_type === "task" && options.entity_id) {
8886
+ entries = entries.filter((e) => e.task_id === options.entity_id);
8887
+ }
8888
+ if (options.since)
8889
+ entries = entries.filter((e) => e.created_at >= options.since);
8890
+ if (options.until)
8891
+ entries = entries.filter((e) => e.created_at <= options.until);
8892
+ entries.sort((a, b) => options.order === "asc" ? a.created_at.localeCompare(b.created_at) : b.created_at.localeCompare(a.created_at));
8893
+ const total = entries.length;
8894
+ const offset = options.offset ?? 0;
8895
+ const limit = options.limit ?? 50;
8896
+ return { entries: entries.slice(offset, offset + limit), total, limit, offset };
8897
+ }
8898
+ var _cache, PRIORITY_RANK;
8631
8899
  var init_cloud_router = __esm(() => {
8632
8900
  init_storage();
8901
+ PRIORITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
8633
8902
  });
8634
8903
 
8635
8904
  // src/db/migrations.ts
@@ -16438,11 +16707,11 @@ function listTasks(filter = {}, db) {
16438
16707
  }
16439
16708
  }
16440
16709
  addMetadataConditions(filter.metadata, conditions, params);
16441
- const PRIORITY_RANK = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
16710
+ const PRIORITY_RANK2 = `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END`;
16442
16711
  if (filter.cursor) {
16443
16712
  try {
16444
16713
  const decoded = JSON.parse(Buffer.from(filter.cursor, "base64").toString("utf8"));
16445
- conditions.push(`(${PRIORITY_RANK} > ? OR (${PRIORITY_RANK} = ? AND created_at < ?) OR (${PRIORITY_RANK} = ? AND created_at = ? AND id > ?))`);
16714
+ conditions.push(`(${PRIORITY_RANK2} > ? OR (${PRIORITY_RANK2} = ? AND created_at < ?) OR (${PRIORITY_RANK2} = ? AND created_at = ? AND id > ?))`);
16446
16715
  params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
16447
16716
  } catch {}
16448
16717
  }
@@ -16459,7 +16728,7 @@ function listTasks(filter = {}, db) {
16459
16728
  params.push(filter.offset);
16460
16729
  }
16461
16730
  }
16462
- const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK}, created_at DESC, id ASC${limitClause}`).all(...params);
16731
+ const rows = d.query(`SELECT * FROM tasks ${where} ORDER BY ${PRIORITY_RANK2}, created_at DESC, id ASC${limitClause}`).all(...params);
16463
16732
  return rows.map(rowToTask);
16464
16733
  }
16465
16734
  function getTaskByFingerprint(fingerprint, db) {
@@ -20873,28 +21142,54 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
20873
21142
  console.log(formatTaskLine(task2));
20874
21143
  }
20875
21144
  });
20876
- program2.command("approve <id>").description("Approve a task that requires approval").action((id) => {
21145
+ program2.command("approve <id>").description("Approve a task that requires approval").action(async (id) => {
20877
21146
  const globalOpts = program2.opts();
20878
- const resolvedId = resolveTaskId(id);
20879
- const task2 = getTask(resolvedId);
20880
- if (!task2) {
20881
- console.error(chalk2.red(`Task not found: ${id}`));
20882
- process.exit(1);
20883
- }
20884
- if (!task2.requires_approval) {
20885
- console.log(chalk2.yellow("This task does not require approval."));
20886
- return;
20887
- }
20888
- if (task2.approved_by) {
20889
- console.log(chalk2.yellow(`Already approved by ${task2.approved_by}.`));
20890
- return;
20891
- }
21147
+ const approver = globalOpts.agent || "cli";
20892
21148
  try {
20893
- const updated = updateTask(resolvedId, { approved_by: globalOpts.agent || "cli", version: task2.version });
21149
+ const cloud = getTodosCloudClient();
21150
+ if (cloud) {
21151
+ const cloudId = resolveTaskId(id);
21152
+ const task3 = await cloudGetTask(cloud, cloudId);
21153
+ if (!task3) {
21154
+ console.error(chalk2.red(`Task not found: ${id}`));
21155
+ process.exit(1);
21156
+ }
21157
+ if (!task3.requires_approval) {
21158
+ console.log(chalk2.yellow("This task does not require approval."));
21159
+ return;
21160
+ }
21161
+ if (task3.approved_by) {
21162
+ console.log(chalk2.yellow(`Already approved by ${task3.approved_by}.`));
21163
+ return;
21164
+ }
21165
+ const updated2 = await cloudUpdateTask(cloud, cloudId, { approved_by: approver, version: task3.version });
21166
+ if (globalOpts.json) {
21167
+ output(updated2, true);
21168
+ } else {
21169
+ console.log(chalk2.green(`Task approved by ${approver}:`));
21170
+ console.log(formatTaskLine(updated2));
21171
+ }
21172
+ return;
21173
+ }
21174
+ const resolvedId = resolveTaskId(id);
21175
+ const task2 = getTask(resolvedId);
21176
+ if (!task2) {
21177
+ console.error(chalk2.red(`Task not found: ${id}`));
21178
+ process.exit(1);
21179
+ }
21180
+ if (!task2.requires_approval) {
21181
+ console.log(chalk2.yellow("This task does not require approval."));
21182
+ return;
21183
+ }
21184
+ if (task2.approved_by) {
21185
+ console.log(chalk2.yellow(`Already approved by ${task2.approved_by}.`));
21186
+ return;
21187
+ }
21188
+ const updated = updateTask(resolvedId, { approved_by: approver, version: task2.version });
20894
21189
  if (globalOpts.json) {
20895
21190
  output(updated, true);
20896
21191
  } else {
20897
- console.log(chalk2.green(`Task approved by ${globalOpts.agent || "cli"}:`));
21192
+ console.log(chalk2.green(`Task approved by ${approver}:`));
20898
21193
  console.log(formatTaskLine(updated));
20899
21194
  }
20900
21195
  } catch (e) {
@@ -21812,6 +22107,40 @@ function registerPlanTemplateCommands(program2) {
21812
22107
  return;
21813
22108
  }
21814
22109
  if (opts.show) {
22110
+ if (cloud) {
22111
+ const plan2 = await cloudResolvePlan(cloud, opts.show, projectId);
22112
+ if (!plan2) {
22113
+ console.error(chalk3.red(`Plan not found: ${opts.show}`));
22114
+ process.exit(1);
22115
+ }
22116
+ const tasks2 = await cloudListTasks(cloud, { plan_id: plan2.id });
22117
+ if (globalOpts.json) {
22118
+ output({ plan: plan2, tasks: tasks2, artifact: null }, true);
22119
+ return;
22120
+ }
22121
+ console.log(chalk3.bold(`Plan Details:
22122
+ `));
22123
+ console.log(` ${chalk3.dim("ID:")} ${plan2.id}`);
22124
+ if (plan2.slug)
22125
+ console.log(` ${chalk3.dim("Slug:")} ${plan2.slug}`);
22126
+ console.log(` ${chalk3.dim("Name:")} ${plan2.name}`);
22127
+ console.log(` ${chalk3.dim("Status:")} ${chalk3.cyan(plan2.status)}`);
22128
+ if (plan2.description)
22129
+ console.log(` ${chalk3.dim("Desc:")} ${plan2.description}`);
22130
+ if (plan2.project_id)
22131
+ console.log(` ${chalk3.dim("Project:")} ${plan2.project_id}`);
22132
+ console.log(` ${chalk3.dim("Created:")} ${plan2.created_at}`);
22133
+ if (tasks2.length > 0) {
22134
+ console.log(chalk3.bold(`
22135
+ Tasks (${tasks2.length}):`));
22136
+ for (const t of tasks2)
22137
+ console.log(` ${formatTaskLine(t)}`);
22138
+ } else {
22139
+ console.log(chalk3.dim(`
22140
+ No tasks in this plan.`));
22141
+ }
22142
+ return;
22143
+ }
21815
22144
  const db = getDatabase();
21816
22145
  const resolvedId = resolvePlanCliRef(opts.show, projectId);
21817
22146
  const plan = getPlan(resolvedId);
@@ -29707,11 +30036,11 @@ function taskSummary(task, blockers) {
29707
30036
  const firstLine = task.description?.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
29708
30037
  return firstLine || undefined;
29709
30038
  }
29710
- function priorityRank(priority) {
30039
+ function priorityRank2(priority) {
29711
30040
  return { critical: 0, high: 1, medium: 2, low: 3 }[priority];
29712
30041
  }
29713
30042
  function sortActionableTasks(a, b) {
29714
- const priority = priorityRank(a.priority) - priorityRank(b.priority);
30043
+ const priority = priorityRank2(a.priority) - priorityRank2(b.priority);
29715
30044
  if (priority !== 0)
29716
30045
  return priority;
29717
30046
  return b.updated_at.localeCompare(a.updated_at);
@@ -32911,17 +33240,35 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
32911
33240
  console.error(chalk5.red("Agent ID required. Use --agent or pass as argument."));
32912
33241
  process.exit(1);
32913
33242
  }
32914
- const { updateAgentActivity: updateAgentActivity2, getAgent: getAgent2, getAgentByName: getAgentByName2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
32915
- const a = getAgent2(agentId) || getAgentByName2(agentId);
32916
- if (!a) {
32917
- console.error(chalk5.red(`Agent not found: ${agentId}`));
32918
- process.exit(1);
32919
- }
32920
- updateAgentActivity2(a.id);
32921
- if (globalOpts.json) {
32922
- console.log(JSON.stringify({ agent_id: a.id, name: a.name, last_seen_at: new Date().toISOString() }));
32923
- } else {
32924
- console.log(chalk5.green(`\u2665 ${a.name} (${a.id.slice(0, 8)}) \u2014 heartbeat sent`));
33243
+ try {
33244
+ const cloud = getTodosCloudClient();
33245
+ if (cloud) {
33246
+ const a2 = await cloudHeartbeatAgent(cloud, agentId);
33247
+ if (!a2) {
33248
+ console.error(chalk5.red(`Agent not found: ${agentId}`));
33249
+ process.exit(1);
33250
+ }
33251
+ if (globalOpts.json) {
33252
+ console.log(JSON.stringify({ agent_id: a2.id, name: a2.name, last_seen_at: a2.last_seen_at }));
33253
+ } else {
33254
+ console.log(chalk5.green(`\u2665 ${a2.name} (${a2.id.slice(0, 8)}) \u2014 heartbeat sent`));
33255
+ }
33256
+ return;
33257
+ }
33258
+ const { updateAgentActivity: updateAgentActivity2, getAgent: getAgent2, getAgentByName: getAgentByName2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
33259
+ const a = getAgent2(agentId) || getAgentByName2(agentId);
33260
+ if (!a) {
33261
+ console.error(chalk5.red(`Agent not found: ${agentId}`));
33262
+ process.exit(1);
33263
+ }
33264
+ updateAgentActivity2(a.id);
33265
+ if (globalOpts.json) {
33266
+ console.log(JSON.stringify({ agent_id: a.id, name: a.name, last_seen_at: new Date().toISOString() }));
33267
+ } else {
33268
+ console.log(chalk5.green(`\u2665 ${a.name} (${a.id.slice(0, 8)}) \u2014 heartbeat sent`));
33269
+ }
33270
+ } catch (e) {
33271
+ handleError(e);
32925
33272
  }
32926
33273
  });
32927
33274
  program2.command("release [agent]").description("Release/logout an agent \u2014 clears session binding so the name is immediately available").option("--session-id <id>", "Only release if session ID matches").action(async (agent, opts) => {
@@ -32931,21 +33278,43 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
32931
33278
  console.error(chalk5.red("Agent ID or name required. Use --agent or pass as argument."));
32932
33279
  process.exit(1);
32933
33280
  }
32934
- const { getAgent: getAgent2, getAgentByName: getAgentByName2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
32935
- const a = getAgent2(agentId) || getAgentByName2(agentId);
32936
- if (!a) {
32937
- console.error(chalk5.red(`Agent not found: ${agentId}`));
32938
- process.exit(1);
32939
- }
32940
- const released = releaseAgent(a.id, opts?.sessionId);
32941
- if (!released) {
32942
- console.error(chalk5.red("Release denied: session_id does not match agent's current session."));
32943
- process.exit(1);
32944
- }
32945
- if (globalOpts.json) {
32946
- console.log(JSON.stringify({ agent_id: a.id, name: a.name, released: true }));
32947
- } else {
32948
- console.log(chalk5.green(`\u2713 ${a.name} (${a.id}) released \u2014 name is now available.`));
33281
+ try {
33282
+ const cloud = getTodosCloudClient();
33283
+ if (cloud) {
33284
+ const result = await cloudReleaseAgent(cloud, agentId, opts?.sessionId);
33285
+ if (!result.agent) {
33286
+ console.error(chalk5.red(`Agent not found: ${agentId}`));
33287
+ process.exit(1);
33288
+ }
33289
+ if (!result.released) {
33290
+ console.error(chalk5.red("Release denied: session_id does not match agent's current session."));
33291
+ process.exit(1);
33292
+ }
33293
+ if (globalOpts.json) {
33294
+ console.log(JSON.stringify({ agent_id: result.agent.id, name: result.agent.name, released: true }));
33295
+ } else {
33296
+ console.log(chalk5.green(`\u2713 ${result.agent.name} (${result.agent.id}) released \u2014 name is now available.`));
33297
+ }
33298
+ return;
33299
+ }
33300
+ const { getAgent: getAgent2, getAgentByName: getAgentByName2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
33301
+ const a = getAgent2(agentId) || getAgentByName2(agentId);
33302
+ if (!a) {
33303
+ console.error(chalk5.red(`Agent not found: ${agentId}`));
33304
+ process.exit(1);
33305
+ }
33306
+ const released = releaseAgent(a.id, opts?.sessionId);
33307
+ if (!released) {
33308
+ console.error(chalk5.red("Release denied: session_id does not match agent's current session."));
33309
+ process.exit(1);
33310
+ }
33311
+ if (globalOpts.json) {
33312
+ console.log(JSON.stringify({ agent_id: a.id, name: a.name, released: true }));
33313
+ } else {
33314
+ console.log(chalk5.green(`\u2713 ${a.name} (${a.id}) released \u2014 name is now available.`));
33315
+ }
33316
+ } catch (e) {
33317
+ handleError(e);
32949
33318
  }
32950
33319
  });
32951
33320
  program2.command("focus [project]").description("Focus on a project (or clear focus if no project given)").action(async (project) => {
@@ -33043,14 +33412,15 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
33043
33412
  });
33044
33413
  program2.command("agent <name>").description("Show all info about an agent: tasks, status, last seen, stats").option("-j, --json", "Output as JSON").action(async (name, opts) => {
33045
33414
  const globalOpts = program2.opts();
33415
+ const cloud = getTodosCloudClient();
33046
33416
  const { getAgentByName: findByName } = await Promise.resolve().then(() => (init_agents(), exports_agents));
33047
- const agent = findByName(name);
33417
+ const agent = cloud ? (await cloudListAgents(cloud)).find((a) => a.name === name || a.id === name) ?? null : findByName(name);
33048
33418
  if (!agent) {
33049
33419
  console.error(chalk5.red(`Agent not found: ${name}`));
33050
33420
  process.exit(1);
33051
33421
  }
33052
- const byAssigned = listTasks({ assigned_to: agent.name });
33053
- const byId = listTasks({ agent_id: agent.id });
33422
+ const byAssigned = cloud ? await cloudListTasks(cloud, { assigned_to: agent.name }) : listTasks({ assigned_to: agent.name });
33423
+ const byId = cloud ? await cloudListTasks(cloud, { agent_id: agent.id }) : listTasks({ agent_id: agent.id });
33054
33424
  const seen = new Set;
33055
33425
  const allTasks = [...byAssigned, ...byId].filter((t) => {
33056
33426
  if (seen.has(t.id))
@@ -33188,7 +33558,8 @@ ${isOnline ? chalk5.green("\u25CF") : chalk5.dim("\u25CB")} ${chalk5.bold(agent.
33188
33558
  console.log(chalk5.green("Task list deleted."));
33189
33559
  return;
33190
33560
  }
33191
- const lists = listTaskLists(projectId);
33561
+ const cloud = getTodosCloudClient();
33562
+ const lists = cloud ? await cloudListTaskLists(cloud, projectId ?? undefined) : listTaskLists(projectId);
33192
33563
  if (globalOpts.json) {
33193
33564
  output(lists, true);
33194
33565
  return;
@@ -38467,12 +38838,23 @@ function createPostgresTodosStorageAdapter(options) {
38467
38838
  dependencies: {
38468
38839
  add: (taskId, dependsOn, context) => addDependency2(taskId, dependsOn, store, context),
38469
38840
  remove: (taskId, dependsOn) => removeDependency2(taskId, dependsOn, store),
38470
- list: (taskId) => listDependencies(taskId, store)
38841
+ list: (taskId) => listDependencies(taskId, store),
38842
+ listAll: () => store.list("dependencies")
38471
38843
  },
38472
38844
  verifications: {
38473
38845
  add: (input, context) => addVerification(input, store, context),
38474
38846
  list: (taskId) => listVerifications(taskId, store)
38475
38847
  },
38848
+ commits: {
38849
+ add: (input, context) => addCommit(input, store, context),
38850
+ list: (taskId) => listCommits(taskId, store),
38851
+ find: (sha) => findCommit(sha, store)
38852
+ },
38853
+ gitRefs: {
38854
+ add: (input, context) => addGitRef(input, store, context),
38855
+ list: (taskId) => listGitRefs(taskId, store),
38856
+ find: (ref) => findGitRefs(ref, store)
38857
+ },
38476
38858
  projects: {
38477
38859
  create: (input, context) => createProject2(input, store, context),
38478
38860
  get: (id) => store.get("projects", id),
@@ -38493,7 +38875,9 @@ function createPostgresTodosStorageAdapter(options) {
38493
38875
  get: (id) => store.get("agents", id),
38494
38876
  getByName: async (name) => (await store.list("agents")).find((agent) => agent.name === name) ?? null,
38495
38877
  list: async (options2) => (await store.list("agents")).filter((agent) => options2?.include_archived || agent.status !== "archived").sort((a, b) => a.name.localeCompare(b.name)),
38496
- update: (id, input) => updateAgent2(id, input, store)
38878
+ update: (id, input) => updateAgent2(id, input, store),
38879
+ heartbeat: (idOrName, context) => heartbeatAgent(idOrName, store, context),
38880
+ release: (idOrName, sessionId, context) => releaseAgent2(idOrName, sessionId, store, context)
38497
38881
  },
38498
38882
  taskLists: {
38499
38883
  create: (input, context) => createTaskList2(input, store, context),
@@ -39023,6 +39407,53 @@ async function addVerification(input, store, context) {
39023
39407
  async function listVerifications(taskId, store) {
39024
39408
  return (await store.list("verifications")).filter((verification) => verification.task_id === taskId).sort((a, b) => b.run_at.localeCompare(a.run_at));
39025
39409
  }
39410
+ async function addCommit(input, store, context) {
39411
+ if (!await store.get("tasks", input.task_id))
39412
+ throw new Error(`Task not found: ${input.task_id}`);
39413
+ const timestamp = new Date().toISOString();
39414
+ const commit = {
39415
+ id: randomUUID3(),
39416
+ task_id: input.task_id,
39417
+ sha: input.sha,
39418
+ message: input.message ?? null,
39419
+ author: input.author ?? null,
39420
+ files_changed: input.files_changed ?? null,
39421
+ created_at: timestamp
39422
+ };
39423
+ await store.upsert("commits", { ...commit, updated_at: timestamp }, context);
39424
+ return commit;
39425
+ }
39426
+ async function listCommits(taskId, store) {
39427
+ return (await store.list("commits")).filter((commit) => commit.task_id === taskId).sort((a, b) => b.created_at.localeCompare(a.created_at));
39428
+ }
39429
+ async function findCommit(sha, store) {
39430
+ const matches = (await store.list("commits")).filter((commit) => commit.sha === sha || commit.sha.startsWith(sha) || sha.startsWith(commit.sha)).sort((a, b) => b.created_at.localeCompare(a.created_at));
39431
+ return matches[0] ?? null;
39432
+ }
39433
+ async function addGitRef(input, store, context) {
39434
+ if (!await store.get("tasks", input.task_id))
39435
+ throw new Error(`Task not found: ${input.task_id}`);
39436
+ const timestamp = new Date().toISOString();
39437
+ const gitRef = {
39438
+ id: randomUUID3(),
39439
+ task_id: input.task_id,
39440
+ ref_type: input.ref_type,
39441
+ name: input.name,
39442
+ url: input.url ?? null,
39443
+ provider: input.provider ?? null,
39444
+ metadata: input.metadata ?? {},
39445
+ created_at: timestamp,
39446
+ updated_at: timestamp
39447
+ };
39448
+ await store.upsert("refs", gitRef, context);
39449
+ return gitRef;
39450
+ }
39451
+ async function listGitRefs(taskId, store) {
39452
+ return (await store.list("refs")).filter((ref) => ref.task_id === taskId).sort((a, b) => b.created_at.localeCompare(a.created_at));
39453
+ }
39454
+ async function findGitRefs(ref, store) {
39455
+ return (await store.list("refs")).filter((r) => r.name === ref).sort((a, b) => b.created_at.localeCompare(a.created_at));
39456
+ }
39026
39457
  function toFilterArray(value) {
39027
39458
  return Array.isArray(value) ? value : [value];
39028
39459
  }
@@ -39169,6 +39600,28 @@ async function updateAgent2(id, input, store) {
39169
39600
  last_seen_at: new Date().toISOString()
39170
39601
  });
39171
39602
  }
39603
+ async function resolveAgent(idOrName, store) {
39604
+ const byId = await store.get("agents", idOrName);
39605
+ if (byId)
39606
+ return byId;
39607
+ return (await store.list("agents")).find((agent) => agent.name === idOrName) ?? null;
39608
+ }
39609
+ async function heartbeatAgent(idOrName, store, context) {
39610
+ const agent = await resolveAgent(idOrName, store);
39611
+ if (!agent)
39612
+ return null;
39613
+ return store.upsert("agents", { ...agent, last_seen_at: new Date().toISOString() }, context);
39614
+ }
39615
+ async function releaseAgent2(idOrName, sessionId, store, context) {
39616
+ const agent = await resolveAgent(idOrName, store);
39617
+ if (!agent)
39618
+ return null;
39619
+ if (sessionId && agent.session_id && agent.session_id !== sessionId) {
39620
+ return { agent, released: false };
39621
+ }
39622
+ const updated = await store.upsert("agents", { ...agent, session_id: null, last_seen_at: new Date().toISOString() }, context);
39623
+ return { agent: updated, released: true };
39624
+ }
39172
39625
  async function createTaskList2(input, store, context) {
39173
39626
  const timestamp = new Date().toISOString();
39174
39627
  return store.upsert("task_lists", {
@@ -40053,6 +40506,70 @@ async function handleV1Request(req, url) {
40053
40506
  }
40054
40507
  return error(405, `method ${method} not allowed on /v1/tasks/:id/verifications`);
40055
40508
  }
40509
+ if (action === "commits") {
40510
+ if (!store.commits)
40511
+ return error(501, "commit links are not supported by this storage backend");
40512
+ if (method === "GET") {
40513
+ if (!await store.tasks.get(id))
40514
+ return error(404, "task not found");
40515
+ const commits = await store.commits.list(id);
40516
+ return json2({ commits, count: commits.length });
40517
+ }
40518
+ if (method === "POST") {
40519
+ const body2 = await readJson(req) ?? {};
40520
+ if (typeof body2.sha !== "string" || !body2.sha.trim())
40521
+ return error(400, "sha is required");
40522
+ try {
40523
+ const commit = await store.commits.add({
40524
+ task_id: id,
40525
+ sha: body2.sha,
40526
+ message: body2.message,
40527
+ author: body2.author,
40528
+ files_changed: Array.isArray(body2.files_changed) ? body2.files_changed : undefined
40529
+ }, contextFromPrincipal(principal));
40530
+ return json2({ commit }, 201);
40531
+ } catch (e) {
40532
+ const msg = e.message || "";
40533
+ if (msg.includes("not found"))
40534
+ return error(404, msg);
40535
+ throw e;
40536
+ }
40537
+ }
40538
+ return error(405, `method ${method} not allowed on /v1/tasks/:id/commits`);
40539
+ }
40540
+ if (action === "refs") {
40541
+ if (!store.gitRefs)
40542
+ return error(501, "git ref links are not supported by this storage backend");
40543
+ if (method === "GET") {
40544
+ if (!await store.tasks.get(id))
40545
+ return error(404, "task not found");
40546
+ const refs = await store.gitRefs.list(id);
40547
+ return json2({ refs, count: refs.length });
40548
+ }
40549
+ if (method === "POST") {
40550
+ const body2 = await readJson(req) ?? {};
40551
+ const refType = body2.ref_type === "pull_request" || body2.ref_type === "branch" ? body2.ref_type : "branch";
40552
+ if (typeof body2.name !== "string" || !body2.name.trim())
40553
+ return error(400, "name is required");
40554
+ try {
40555
+ const ref = await store.gitRefs.add({
40556
+ task_id: id,
40557
+ ref_type: refType,
40558
+ name: body2.name,
40559
+ url: body2.url,
40560
+ provider: body2.provider,
40561
+ metadata: body2.metadata
40562
+ }, contextFromPrincipal(principal));
40563
+ return json2({ ref }, 201);
40564
+ } catch (e) {
40565
+ const msg = e.message || "";
40566
+ if (msg.includes("not found"))
40567
+ return error(404, msg);
40568
+ throw e;
40569
+ }
40570
+ }
40571
+ return error(405, `method ${method} not allowed on /v1/tasks/:id/refs`);
40572
+ }
40056
40573
  const body = await readJson(req) ?? {};
40057
40574
  const agentId = body.agent_id || principal.agent || "todos-serve";
40058
40575
  if (action === "start" && method === "POST") {
@@ -40165,11 +40682,87 @@ async function handleV1Request(req, url) {
40165
40682
  }
40166
40683
  return json2({ agent: result }, 201);
40167
40684
  }
40685
+ if (id && action === "heartbeat") {
40686
+ if (method !== "POST")
40687
+ return error(405, `method ${method} not allowed on /v1/agents/:id/heartbeat`);
40688
+ if (typeof store.agents.heartbeat !== "function") {
40689
+ return error(501, "agent heartbeat is not supported by this storage backend");
40690
+ }
40691
+ const agent = await store.agents.heartbeat(id, contextFromPrincipal(principal));
40692
+ return agent ? json2({ agent }) : error(404, "agent not found");
40693
+ }
40694
+ if (id && action === "release") {
40695
+ if (method !== "POST")
40696
+ return error(405, `method ${method} not allowed on /v1/agents/:id/release`);
40697
+ if (typeof store.agents.release !== "function") {
40698
+ return error(501, "agent release is not supported by this storage backend");
40699
+ }
40700
+ const body = await readJson(req) ?? {};
40701
+ const result = await store.agents.release(id, body.session_id, contextFromPrincipal(principal));
40702
+ if (!result)
40703
+ return error(404, "agent not found");
40704
+ if (!result.released) {
40705
+ return error(409, "release denied: session_id does not match agent's current session", { released: false });
40706
+ }
40707
+ return json2({ agent: result.agent, released: true });
40708
+ }
40168
40709
  if (id && method === "GET") {
40169
40710
  const agent = await store.agents.get(id);
40170
40711
  return agent ? json2({ agent }) : error(404, "agent not found");
40171
40712
  }
40172
40713
  }
40714
+ if (resource === "activity" && !id) {
40715
+ if (method !== "GET")
40716
+ return error(405, `method ${method} not allowed on /v1/activity`);
40717
+ const limitParam = url.searchParams.get("limit");
40718
+ const limit = limitParam ? Math.max(1, Math.min(1e4, Number(limitParam) || 50)) : 50;
40719
+ const activity = await store.audit.getRecentActivity(limit);
40720
+ return json2({ activity, count: activity.length });
40721
+ }
40722
+ if (resource === "task-lists" && !id) {
40723
+ if (method !== "GET")
40724
+ return error(405, `method ${method} not allowed on /v1/task-lists`);
40725
+ const projectId = url.searchParams.get("project_id") ?? undefined;
40726
+ const taskLists = await store.taskLists.list(projectId);
40727
+ return json2({ task_lists: taskLists, count: taskLists.length });
40728
+ }
40729
+ if (resource === "dependencies" && !id) {
40730
+ if (method !== "GET")
40731
+ return error(405, `method ${method} not allowed on /v1/dependencies`);
40732
+ if (typeof store.dependencies?.listAll !== "function") {
40733
+ return error(501, "dependency edge listing is not supported by this storage backend");
40734
+ }
40735
+ const dependencies = await store.dependencies.listAll();
40736
+ return json2({ dependencies, count: dependencies.length });
40737
+ }
40738
+ if (resource === "commits" && id) {
40739
+ if (method !== "GET")
40740
+ return error(405, `method ${method} not allowed on /v1/commits/:sha`);
40741
+ if (!store.commits)
40742
+ return error(501, "commit links are not supported by this storage backend");
40743
+ const commit = await store.commits.find(id);
40744
+ return json2({ commit: commit ?? null });
40745
+ }
40746
+ if (resource === "refs" && id) {
40747
+ if (method !== "GET")
40748
+ return error(405, `method ${method} not allowed on /v1/refs/:ref`);
40749
+ if (!store.gitRefs)
40750
+ return error(501, "git ref links are not supported by this storage backend");
40751
+ const refs = await store.gitRefs.find(id);
40752
+ return json2({ refs, count: refs.length });
40753
+ }
40754
+ if (resource === "next" && !id) {
40755
+ if (method !== "GET")
40756
+ return error(405, `method ${method} not allowed on /v1/next`);
40757
+ const agent = url.searchParams.get("agent") ?? undefined;
40758
+ const filters = {
40759
+ ...url.searchParams.get("project_id") ? { project_id: url.searchParams.get("project_id") } : {},
40760
+ ...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
40761
+ ...url.searchParams.get("plan_id") ? { plan_id: url.searchParams.get("plan_id") } : {}
40762
+ };
40763
+ const task = await store.tasks.getNext(agent, filters);
40764
+ return json2({ task: task ?? null });
40765
+ }
40173
40766
  if (resource === "stats" && method === "GET") {
40174
40767
  const [tasks, tasksAll, projects] = await Promise.all([
40175
40768
  store.tasks.count(),
@@ -45514,7 +46107,7 @@ function resolveRun(parsed, db) {
45514
46107
  }
45515
46108
  return resolution;
45516
46109
  }
45517
- function resolveAgent(parsed, db) {
46110
+ function resolveAgent2(parsed, db) {
45518
46111
  const resolution = blankResolution(parsed);
45519
46112
  const id = resolvePartialId(db, "agents", parsed.target);
45520
46113
  const agent = id ? getAgent(id, db) : getAgentByName(parsed.target, db);
@@ -45551,7 +46144,7 @@ function resolveTask(parsed, db) {
45551
46144
  function resolveUnknown(parsed, workspace, db, maxSymbolMatches) {
45552
46145
  const attempts = [
45553
46146
  () => resolveTask({ ...parsed, kind: "task" }, db),
45554
- () => resolveAgent({ ...parsed, kind: "agent" }, db),
46147
+ () => resolveAgent2({ ...parsed, kind: "agent" }, db),
45555
46148
  () => resolvePlan({ ...parsed, kind: "plan" }, db),
45556
46149
  () => resolveSymbol({ ...parsed, kind: "symbol" }, workspace, maxSymbolMatches)
45557
46150
  ];
@@ -45586,7 +46179,7 @@ function resolveMentions(input, db) {
45586
46179
  case "run":
45587
46180
  return resolveRun(parsed, d);
45588
46181
  case "agent":
45589
- return resolveAgent(parsed, d);
46182
+ return resolveAgent2(parsed, d);
45590
46183
  case "task":
45591
46184
  return resolveTask(parsed, d);
45592
46185
  default:
@@ -55400,7 +55993,7 @@ function resolveProjectId(value, db) {
55400
55993
  return null;
55401
55994
  return resolvePartialId(db, "projects", value) || value;
55402
55995
  }
55403
- function resolveAgent2(agentId, db) {
55996
+ function resolveAgent3(agentId, db) {
55404
55997
  return db.query("SELECT id, name FROM agents WHERE id = ? OR LOWER(name) = LOWER(?)").get(agentId, agentId);
55405
55998
  }
55406
55999
  function agentTaskWhere(agent, options, db) {
@@ -55444,7 +56037,7 @@ function scoreHandoffs(created, withTaskRefs, withBlockers) {
55444
56037
  }
55445
56038
  function getAgentReliabilityScorecard(agentId, options = {}, db) {
55446
56039
  const d = db || getDatabase();
55447
- const agent = resolveAgent2(agentId, d);
56040
+ const agent = resolveAgent3(agentId, d);
55448
56041
  if (!agent)
55449
56042
  return null;
55450
56043
  const generatedAt = now();
@@ -55613,7 +56206,7 @@ function getAgentReliabilityScorecard(agentId, options = {}, db) {
55613
56206
  }
55614
56207
  function listAgentReliabilityScorecards(options = {}, db) {
55615
56208
  const d = db || getDatabase();
55616
- const agents = options.agent_id ? resolveAgent2(options.agent_id, d) ? [resolveAgent2(options.agent_id, d)] : [] : d.query("SELECT id, name FROM agents ORDER BY name").all();
56209
+ const agents = options.agent_id ? resolveAgent3(options.agent_id, d) ? [resolveAgent3(options.agent_id, d)] : [] : d.query("SELECT id, name FROM agents ORDER BY name").all();
55617
56210
  const limit = Number.isFinite(options.limit) && options.limit > 0 ? Math.min(Math.floor(options.limit), 500) : 50;
55618
56211
  return agents.map((agent) => getAgentReliabilityScorecard(agent.id, options, d)).filter((scorecard) => Boolean(scorecard)).filter((scorecard) => {
55619
56212
  const signals = scorecard.signals;
@@ -62984,6 +63577,19 @@ ID: ${updated.id}${taskNote}`
62984
63577
  agent_id: exports_external3.string().describe("Your agent ID or name.")
62985
63578
  }, async ({ agent_id }) => {
62986
63579
  try {
63580
+ const cloud = getTodosCloudClient();
63581
+ if (cloud) {
63582
+ const a = await cloudHeartbeatAgent(cloud, agent_id);
63583
+ if (!a) {
63584
+ return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
63585
+ }
63586
+ return {
63587
+ content: [{
63588
+ type: "text",
63589
+ text: `Heartbeat: ${a.name} (${a.id}) \u2014 last_seen_at updated to ${a.last_seen_at}`
63590
+ }]
63591
+ };
63592
+ }
62987
63593
  const agent = getAgent(agent_id) || getAgentByName(agent_id);
62988
63594
  if (!agent) {
62989
63595
  return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
@@ -63006,6 +63612,22 @@ ID: ${updated.id}${taskNote}`
63006
63612
  session_id: exports_external3.string().optional().describe("Your session ID \u2014 if provided, release only succeeds if it matches (prevents other sessions from releasing your agent).")
63007
63613
  }, async ({ agent_id, session_id }) => {
63008
63614
  try {
63615
+ const cloud = getTodosCloudClient();
63616
+ if (cloud) {
63617
+ const result = await cloudReleaseAgent(cloud, agent_id, session_id);
63618
+ if (!result.agent) {
63619
+ return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
63620
+ }
63621
+ if (!result.released) {
63622
+ return { content: [{ type: "text", text: `Release denied: session_id does not match agent's current session.` }], isError: true };
63623
+ }
63624
+ return {
63625
+ content: [{
63626
+ type: "text",
63627
+ text: `Agent released: ${result.agent.name} (${result.agent.id}) \u2014 session cleared, name is now available.`
63628
+ }]
63629
+ };
63630
+ }
63009
63631
  const agent = getAgent(agent_id) || getAgentByName(agent_id);
63010
63632
  if (!agent) {
63011
63633
  return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
@@ -68640,7 +69262,8 @@ function registerQueryCommands(program2) {
68640
69262
  if (pid)
68641
69263
  filters.project_id = pid;
68642
69264
  }
68643
- const task2 = getNextTask(opts.agent, Object.keys(filters).length ? filters : undefined, db);
69265
+ const cloud = getTodosCloudClient();
69266
+ const task2 = cloud ? await cloudNextTask(cloud, opts.agent, Object.keys(filters).length ? filters : undefined) : getNextTask(opts.agent, Object.keys(filters).length ? filters : undefined, db);
68644
69267
  if (!task2) {
68645
69268
  if (json3) {
68646
69269
  console.log(JSON.stringify(null));
@@ -68761,9 +69384,10 @@ Next up:`));
68761
69384
  console.log(` ${chalk7.cyan(t.short_id || t.id.slice(0, 8))} ${chalk7.yellow(t.priority)} ${t.title}`);
68762
69385
  }
68763
69386
  });
68764
- program2.command("recap").description("Show what happened in the last N hours \u2014 completed tasks, new tasks, agent activity, blockers").option("--hours <n>", "Look back N hours (default: 8)", "8").option("--project <id>", "Filter to project").action((opts) => {
69387
+ program2.command("recap").description("Show what happened in the last N hours \u2014 completed tasks, new tasks, agent activity, blockers").option("--hours <n>", "Look back N hours (default: 8)", "8").option("--project <id>", "Filter to project").action(async (opts) => {
68765
69388
  const globalOpts = program2.opts();
68766
- const recap = getRecap(parseInt(opts.hours, 10), opts.project);
69389
+ const cloud = getTodosCloudClient();
69390
+ const recap = cloud ? await cloudRecap(cloud, parseInt(opts.hours, 10), opts.project) : getRecap(parseInt(opts.hours, 10), opts.project);
68767
69391
  if (globalOpts.json) {
68768
69392
  output(recap, true);
68769
69393
  return;
@@ -68826,11 +69450,12 @@ Agents:`));
68826
69450
  }
68827
69451
  console.log();
68828
69452
  });
68829
- program2.command("standup").description("Generate standup notes \u2014 completed since yesterday, in progress, blocked. Grouped by agent.").option("--since <date>", "ISO date or 'yesterday' (default: yesterday)").option("--project <id>", "Filter to project").action((opts) => {
69453
+ program2.command("standup").description("Generate standup notes \u2014 completed since yesterday, in progress, blocked. Grouped by agent.").option("--since <date>", "ISO date or 'yesterday' (default: yesterday)").option("--project <id>", "Filter to project").action(async (opts) => {
68830
69454
  const globalOpts = program2.opts();
68831
69455
  const sinceDate = opts.since === "yesterday" || !opts.since ? new Date(Date.now() - 24 * 60 * 60 * 1000) : new Date(opts.since);
68832
69456
  const hours = Math.max(1, Math.round((Date.now() - sinceDate.getTime()) / (60 * 60 * 1000)));
68833
- const recap = getRecap(hours, opts.project);
69457
+ const cloud = getTodosCloudClient();
69458
+ const recap = cloud ? await cloudRecap(cloud, hours, opts.project) : getRecap(hours, opts.project);
68834
69459
  if (globalOpts.json) {
68835
69460
  output(recap, true);
68836
69461
  return;
@@ -68900,7 +69525,8 @@ Blocked:`));
68900
69525
  const filters = {};
68901
69526
  if (opts.project)
68902
69527
  filters.project_id = opts.project;
68903
- const work = getActiveWork(Object.keys(filters).length ? filters : undefined, db);
69528
+ const cloud = getTodosCloudClient();
69529
+ const work = cloud ? await cloudActiveWork(cloud, Object.keys(filters).length ? filters : {}) : getActiveWork(Object.keys(filters).length ? filters : undefined, db);
68904
69530
  if (json3) {
68905
69531
  console.log(JSON.stringify(work, null, 2));
68906
69532
  return;
@@ -68923,7 +69549,8 @@ Blocked:`));
68923
69549
  const filters = {};
68924
69550
  if (opts.project)
68925
69551
  filters.project_id = opts.project;
68926
- const tasks = getStaleTasks(parseInt(opts.minutes, 10), Object.keys(filters).length ? filters : undefined, db);
69552
+ const cloud = getTodosCloudClient();
69553
+ const tasks = cloud ? await cloudStaleTasks(cloud, parseInt(opts.minutes, 10), Object.keys(filters).length ? filters : {}) : getStaleTasks(parseInt(opts.minutes, 10), Object.keys(filters).length ? filters : undefined, db);
68927
69554
  if (json3) {
68928
69555
  console.log(JSON.stringify(tasks, null, 2));
68929
69556
  return;
@@ -69075,12 +69702,13 @@ No task claimed (nothing available).`));
69075
69702
  filter.project_id = projectId;
69076
69703
  if (opts.agent)
69077
69704
  filter.assigned_to = opts.agent;
69705
+ const cloud = getTodosCloudClient();
69078
69706
  const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
69079
- const changed = getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
69707
+ const changed = cloud ? await cloudChangedSince(cloud, since, filter) : getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
69080
69708
  const completed = changed.filter((t) => t.status === "completed");
69081
69709
  const inProgress = changed.filter((t) => t.status === "in_progress");
69082
69710
  const failed = changed.filter((t) => t.status === "failed");
69083
- const allTasks = listTasks({ ...filter, status: "pending" });
69711
+ const allTasks = cloud ? await cloudListTasks(cloud, { ...filter, status: "pending" }) : listTasks({ ...filter, status: "pending" });
69084
69712
  if (opts.json || globalOpts.json) {
69085
69713
  console.log(JSON.stringify({ completed, in_progress: inProgress, failed, pending: allTasks.length, period_days: days }, null, 2));
69086
69714
  return;
@@ -69347,12 +69975,13 @@ Findings`));
69347
69975
  const filter = {};
69348
69976
  if (projectId)
69349
69977
  filter.project_id = projectId;
69978
+ const cloud = getTodosCloudClient();
69350
69979
  const { getTasksChangedSince: getTasksChangedSince2, getTaskStats: getTaskStats2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
69351
- const changed = getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
69980
+ const changed = cloud ? await cloudChangedSince(cloud, since, filter) : getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
69352
69981
  const completed = changed.filter((t) => t.status === "completed");
69353
69982
  const failed = changed.filter((t) => t.status === "failed");
69354
- const all = listTasks(filter);
69355
- const stats2 = getTaskStats2(Object.keys(filter).length ? filter : undefined, db);
69983
+ const all = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter);
69984
+ const stats2 = cloud ? await cloudTaskStats(cloud, filter) : getTaskStats2(Object.keys(filter).length ? filter : undefined, db);
69356
69985
  const byDay = {};
69357
69986
  for (const t of changed) {
69358
69987
  const day = t.updated_at.slice(0, 10);
@@ -69406,10 +70035,11 @@ Findings`));
69406
70035
  program2.command("today").description("Show task activity from today").option("-j, --json", "Output as JSON").action(async (opts) => {
69407
70036
  const globalOpts = program2.opts();
69408
70037
  const db = getDatabase();
70038
+ const cloud = getTodosCloudClient();
69409
70039
  const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
69410
70040
  const start = new Date;
69411
70041
  start.setHours(0, 0, 0, 0);
69412
- const tasks = getTasksChangedSince2(start.toISOString(), undefined, db);
70042
+ const tasks = cloud ? await cloudChangedSince(cloud, start.toISOString()) : getTasksChangedSince2(start.toISOString(), undefined, db);
69413
70043
  const completed = tasks.filter((t) => t.status === "completed");
69414
70044
  const started = tasks.filter((t) => t.status === "in_progress");
69415
70045
  const other = tasks.filter((t) => t.status !== "completed" && t.status !== "in_progress");
@@ -69436,13 +70066,14 @@ Findings`));
69436
70066
  program2.command("yesterday").description("Show task activity from yesterday").option("-j, --json", "Output as JSON").action(async (opts) => {
69437
70067
  const globalOpts = program2.opts();
69438
70068
  const db = getDatabase();
70069
+ const cloud = getTodosCloudClient();
69439
70070
  const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
69440
70071
  const start = new Date;
69441
70072
  start.setDate(start.getDate() - 1);
69442
70073
  start.setHours(0, 0, 0, 0);
69443
70074
  const end = new Date(start);
69444
70075
  end.setHours(23, 59, 59, 999);
69445
- const allChanged = getTasksChangedSince2(start.toISOString(), undefined, db);
70076
+ const allChanged = cloud ? await cloudChangedSince(cloud, start.toISOString()) : getTasksChangedSince2(start.toISOString(), undefined, db);
69446
70077
  const tasks = allChanged.filter((t) => t.updated_at <= end.toISOString());
69447
70078
  const completed = tasks.filter((t) => t.status === "completed");
69448
70079
  const started = tasks.filter((t) => t.status === "in_progress");
@@ -69470,14 +70101,15 @@ Findings`));
69470
70101
  const globalOpts = program2.opts();
69471
70102
  const db = getDatabase();
69472
70103
  const projectId = globalOpts.project ? autoProject(globalOpts) || undefined : undefined;
70104
+ const cloud = getTodosCloudClient();
69473
70105
  const filter = { assigned_to: agent };
69474
70106
  if (projectId)
69475
70107
  filter.project_id = projectId;
69476
- const tasks = listTasks(filter, db);
70108
+ const tasks = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter, db);
69477
70109
  const filterByAgent = { agent_id: agent };
69478
70110
  if (projectId)
69479
70111
  filterByAgent.project_id = projectId;
69480
- const agentTasks = listTasks(filterByAgent, db);
70112
+ const agentTasks = cloud ? await cloudListTasks(cloud, filterByAgent) : listTasks(filterByAgent, db);
69481
70113
  const seen = new Set(tasks.map((t) => t.id));
69482
70114
  for (const t of agentTasks) {
69483
70115
  if (!seen.has(t.id)) {
@@ -69519,17 +70151,27 @@ Findings`));
69519
70151
  program2.command("blocked").description("Show tasks blocked by incomplete dependencies").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
69520
70152
  const globalOpts = program2.opts();
69521
70153
  const db = getDatabase();
69522
- const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
70154
+ const cloud = getTodosCloudClient();
69523
70155
  const projectId = autoProject(globalOpts) || opts.project || undefined;
69524
70156
  const filter = { status: "pending" };
69525
70157
  if (projectId)
69526
70158
  filter.project_id = projectId;
69527
- const allPending = listTasks(filter, db);
70159
+ const allPending = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter, db);
69528
70160
  const blockedTasks = [];
69529
- for (const t of allPending) {
69530
- const blockers = getBlockingDeps2(t.id, db);
69531
- if (blockers.length > 0)
69532
- blockedTasks.push({ task: t, blockers });
70161
+ if (cloud) {
70162
+ const map = await cloudBlockingDepsMap(cloud, allPending);
70163
+ for (const t of allPending) {
70164
+ const blockers = map.get(t.id);
70165
+ if (blockers && blockers.length > 0)
70166
+ blockedTasks.push({ task: t, blockers });
70167
+ }
70168
+ } else {
70169
+ const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
70170
+ for (const t of allPending) {
70171
+ const blockers = getBlockingDeps2(t.id, db);
70172
+ if (blockers.length > 0)
70173
+ blockedTasks.push({ task: t, blockers });
70174
+ }
69533
70175
  }
69534
70176
  if (opts.json || globalOpts.json) {
69535
70177
  console.log(JSON.stringify(blockedTasks.map((b) => ({ ...b.task, blocked_by: b.blockers.map((bl) => ({ id: bl.id, short_id: bl.short_id, title: bl.title, status: bl.status })) }))));
@@ -69551,8 +70193,9 @@ Findings`));
69551
70193
  program2.command("overdue").description("Show tasks past their due date").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
69552
70194
  const globalOpts = program2.opts();
69553
70195
  const projectId = autoProject(globalOpts) || opts.project || undefined;
70196
+ const cloud = getTodosCloudClient();
69554
70197
  const { getOverdueTasks: getOverdueTasks2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
69555
- const tasks = getOverdueTasks2(projectId);
70198
+ const tasks = cloud ? await cloudOverdueTasks(cloud, projectId) : getOverdueTasks2(projectId);
69556
70199
  if (opts.json || globalOpts.json) {
69557
70200
  console.log(JSON.stringify(tasks));
69558
70201
  return;
@@ -69573,10 +70216,8 @@ Findings`));
69573
70216
  program2.command("sla").description("Show overdue or SLA-breached tasks that need escalation").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").option("--agent <id>", "Filter to assigned agent").option("--limit <n>", "Max tasks to show", "50").action(async (opts) => {
69574
70217
  const globalOpts = program2.opts();
69575
70218
  const projectId = autoProject(globalOpts) || opts.project || undefined;
69576
- const escalations = getEscalatedTasks({
69577
- project_id: projectId,
69578
- agent_id: opts.agent
69579
- }).slice(0, parseInt(opts.limit, 10));
70219
+ const cloud = getTodosCloudClient();
70220
+ const escalations = (cloud ? await cloudEscalatedTasks(cloud, { project_id: projectId, agent_id: opts.agent }) : getEscalatedTasks({ project_id: projectId, agent_id: opts.agent })).slice(0, parseInt(opts.limit, 10));
69580
70221
  if (opts.json || globalOpts.json) {
69581
70222
  console.log(JSON.stringify(escalations));
69582
70223
  return;
@@ -69596,12 +70237,13 @@ Findings`));
69596
70237
  program2.command("week").description("Show task activity from the past 7 days").option("-j, --json", "Output as JSON").action(async (opts) => {
69597
70238
  const globalOpts = program2.opts();
69598
70239
  const db = getDatabase();
70240
+ const cloud = getTodosCloudClient();
69599
70241
  const { getTasksChangedSince: getTasksChangedSince2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
69600
70242
  const now4 = new Date;
69601
70243
  const start = new Date(now4);
69602
70244
  start.setDate(start.getDate() - 7);
69603
70245
  start.setHours(0, 0, 0, 0);
69604
- const tasks = getTasksChangedSince2(start.toISOString(), undefined, db);
70246
+ const tasks = cloud ? await cloudChangedSince(cloud, start.toISOString()) : getTasksChangedSince2(start.toISOString(), undefined, db);
69605
70247
  const days = {};
69606
70248
  for (let i = 0;i < 7; i++) {
69607
70249
  const d = new Date(now4);
@@ -69649,9 +70291,10 @@ Findings`));
69649
70291
  program2.command("burndown").description("Show task completion velocity over the past 7 days").option("--days <n>", "Number of days", "7").option("-j, --json", "Output as JSON").action(async (opts) => {
69650
70292
  const globalOpts = program2.opts();
69651
70293
  const db = getDatabase();
70294
+ const cloud = getTodosCloudClient();
69652
70295
  const { getRecentActivity: getRecentActivity2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
69653
70296
  const numDays = parseInt(opts.days, 10);
69654
- const entries = getRecentActivity2(5000, db);
70297
+ const entries = cloud ? await cloudRecentActivity(cloud, 5000) : getRecentActivity2(5000, db);
69655
70298
  const now4 = new Date;
69656
70299
  const dayStats = [];
69657
70300
  for (let i = numDays - 1;i >= 0; i--) {
@@ -69691,8 +70334,9 @@ Findings`));
69691
70334
  program2.command("log").description("Show recent task activity log (git-log style)").option("--limit <n>", "Number of entries", "30").option("-j, --json", "Output as JSON").action(async (opts) => {
69692
70335
  const globalOpts = program2.opts();
69693
70336
  const db = getDatabase();
70337
+ const cloud = getTodosCloudClient();
69694
70338
  const { getRecentActivity: getRecentActivity2 } = await Promise.resolve().then(() => (init_audit(), exports_audit));
69695
- const entries = getRecentActivity2(parseInt(opts.limit, 10), db);
70339
+ const entries = cloud ? await cloudRecentActivity(cloud, parseInt(opts.limit, 10)) : getRecentActivity2(parseInt(opts.limit, 10), db);
69696
70340
  if (opts.json || globalOpts.json) {
69697
70341
  console.log(JSON.stringify(entries));
69698
70342
  return;
@@ -69735,6 +70379,39 @@ Findings`));
69735
70379
  program2.command("timeline").description("Show a unified local activity timeline for tasks, projects, plans, or runs").option("--task <id>", "Filter to a task").option("--project <id>", "Filter to a project").option("--plan <id>", "Filter to a plan").option("--run <id>", "Filter to a run ledger").option("--since <iso>", "Only include entries at or after this ISO timestamp").option("--until <iso>", "Only include entries at or before this ISO timestamp").option("--limit <n>", "Number of entries", "50").option("--offset <n>", "Entries to skip", "0").option("--order <order>", "Sort order: asc or desc", "desc").option("-j, --json", "Output as JSON").action(async (opts) => {
69736
70380
  const globalOpts = program2.opts();
69737
70381
  const db = getDatabase();
70382
+ const cloud = getTodosCloudClient();
70383
+ if (cloud) {
70384
+ const cloudOptions = {
70385
+ since: opts.since,
70386
+ until: opts.until,
70387
+ limit: parseInt(opts.limit, 10),
70388
+ offset: parseInt(opts.offset, 10),
70389
+ order: opts.order === "asc" ? "asc" : "desc",
70390
+ ...opts.task ? { entity_type: "task", entity_id: opts.task } : {},
70391
+ ...opts.project ? { entity_type: "project", entity_id: opts.project } : {},
70392
+ ...opts.plan ? { entity_type: "plan", entity_id: opts.plan } : {},
70393
+ ...opts.run ? { entity_type: "run", entity_id: opts.run } : {}
70394
+ };
70395
+ const timeline2 = await cloudTimeline(cloud, cloudOptions);
70396
+ if (opts.json || globalOpts.json) {
70397
+ console.log(JSON.stringify(timeline2, null, 2));
70398
+ return;
70399
+ }
70400
+ if (timeline2.entries.length === 0) {
70401
+ console.log(chalk7.dim(" No activity yet."));
70402
+ return;
70403
+ }
70404
+ console.log(chalk7.bold(`Activity timeline (${timeline2.total}${timeline2.total > timeline2.entries.length ? `, showing ${timeline2.entries.length}` : ""}):
70405
+ `));
70406
+ for (const entry2 of timeline2.entries) {
70407
+ const time2 = entry2.created_at.replace("T", " ").slice(0, 16);
70408
+ const ref = entry2.run_id ? `run ${entry2.run_id.slice(0, 8)}` : `task ${entry2.task_id.slice(0, 8)}`;
70409
+ const agent = entry2.agent_id ? chalk7.dim(` (${entry2.agent_id})`) : "";
70410
+ const message = entry2.message ? ` ${entry2.message}` : "";
70411
+ console.log(` ${chalk7.dim(time2)} ${chalk7.cyan(entry2.source)} ${chalk7.dim(ref)} ${entry2.event_type}${message}${agent}`);
70412
+ }
70413
+ return;
70414
+ }
69738
70415
  const { getLocalActivityTimeline: getLocalActivityTimeline2 } = await Promise.resolve().then(() => (init_activity_timeline(), exports_activity_timeline));
69739
70416
  const { resolveTaskRunId: resolveTaskRunId2 } = await Promise.resolve().then(() => (init_task_runs(), exports_task_runs));
69740
70417
  const options = {
@@ -69819,21 +70496,35 @@ Findings`));
69819
70496
  return;
69820
70497
  }
69821
70498
  const db = getDatabase();
69822
- const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
70499
+ const cloud = getTodosCloudClient();
69823
70500
  const { isLockExpired: isLockExpired2 } = await Promise.resolve().then(() => (init_database(), exports_database));
69824
70501
  const projectId = autoProject(globalOpts) || opts.project || undefined;
69825
70502
  const filter = { status: "pending" };
69826
70503
  if (projectId)
69827
70504
  filter.project_id = projectId;
69828
- const pending = listTasks(filter, db);
69829
- const ready = pending.filter((t) => {
69830
- if (t.locked_by && !isLockExpired2(t.locked_at))
69831
- return false;
69832
- const blockers = getBlockingDeps2(t.id, db);
69833
- return blockers.length === 0;
69834
- });
70505
+ const pending = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter, db);
70506
+ let ready;
70507
+ if (cloud) {
70508
+ const map = await cloudBlockingDepsMap(cloud, pending);
70509
+ ready = pending.filter((t) => {
70510
+ if (t.locked_by && !isLockExpired2(t.locked_at))
70511
+ return false;
70512
+ return !map.has(t.id);
70513
+ });
70514
+ } else {
70515
+ const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
70516
+ ready = pending.filter((t) => {
70517
+ if (t.locked_by && !isLockExpired2(t.locked_at))
70518
+ return false;
70519
+ return getBlockingDeps2(t.id, db).length === 0;
70520
+ });
70521
+ }
69835
70522
  const limited2 = ready.slice(0, parseInt(opts.limit, 10));
69836
70523
  if (opts.json || globalOpts.json) {
70524
+ if (cloud) {
70525
+ console.log(JSON.stringify(limited2));
70526
+ return;
70527
+ }
69837
70528
  const { getTaskRouteState: getTaskRouteState2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
69838
70529
  console.log(JSON.stringify(limited2.map((task2) => ({ ...task2, route_state: getTaskRouteState2(task2, db) }))));
69839
70530
  return;
@@ -69853,19 +70544,29 @@ Findings`));
69853
70544
  program2.command("sprint").description("Sprint dashboard: in-progress, next up, blockers, and overdue").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
69854
70545
  const globalOpts = program2.opts();
69855
70546
  const db = getDatabase();
69856
- const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
70547
+ const cloud = getTodosCloudClient();
69857
70548
  const projectId = autoProject(globalOpts) || opts.project || undefined;
69858
70549
  const baseFilter = {};
69859
70550
  if (projectId)
69860
70551
  baseFilter.project_id = projectId;
69861
- const inProgress = listTasks({ ...baseFilter, status: "in_progress" }, db);
69862
- const pending = listTasks({ ...baseFilter, status: "pending" }, db);
70552
+ const inProgress = cloud ? await cloudListTasks(cloud, { ...baseFilter, status: "in_progress" }) : listTasks({ ...baseFilter, status: "in_progress" }, db);
70553
+ const pending = cloud ? await cloudListTasks(cloud, { ...baseFilter, status: "pending" }) : listTasks({ ...baseFilter, status: "pending" }, db);
69863
70554
  const nowStr = new Date().toISOString();
69864
70555
  const blocked = [];
69865
- for (const t of pending) {
69866
- const blockers = getBlockingDeps2(t.id, db);
69867
- if (blockers.length > 0)
69868
- blocked.push({ task: t, blockers });
70556
+ if (cloud) {
70557
+ const map = await cloudBlockingDepsMap(cloud, pending);
70558
+ for (const t of pending) {
70559
+ const blockers = map.get(t.id);
70560
+ if (blockers && blockers.length > 0)
70561
+ blocked.push({ task: t, blockers });
70562
+ }
70563
+ } else {
70564
+ const { getBlockingDeps: getBlockingDeps2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
70565
+ for (const t of pending) {
70566
+ const blockers = getBlockingDeps2(t.id, db);
70567
+ if (blockers.length > 0)
70568
+ blocked.push({ task: t, blockers });
70569
+ }
69869
70570
  }
69870
70571
  const overdue = [...inProgress, ...pending].filter((t) => t.due_at && t.due_at < nowStr);
69871
70572
  const blockedIds = new Set(blocked.map((b) => b.task.id));
@@ -70096,17 +70797,20 @@ Findings`));
70096
70797
  program2.command("priorities").description("Show task counts grouped by priority").option("-j, --json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
70097
70798
  const globalOpts = program2.opts();
70098
70799
  const db = getDatabase();
70800
+ const cloud = getTodosCloudClient();
70099
70801
  const { countTasks: countTasks2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
70802
+ const countFn = cloud ? (f) => cloudCountTasks(cloud, f) : (f) => Promise.resolve(countTasks2(f, db));
70100
70803
  const projectId = autoProject(globalOpts) || opts.project || undefined;
70101
70804
  const base = projectId ? { project_id: projectId } : {};
70102
70805
  const priorities = ["critical", "high", "medium", "low", "none"];
70103
70806
  const counts = {};
70104
70807
  for (const p of priorities) {
70808
+ const base2 = { ...base, priority: p === "none" ? undefined : p };
70105
70809
  counts[p] = {
70106
- total: countTasks2({ ...base, priority: p === "none" ? undefined : p }, db),
70107
- pending: countTasks2({ ...base, priority: p === "none" ? undefined : p, status: "pending" }, db),
70108
- in_progress: countTasks2({ ...base, priority: p === "none" ? undefined : p, status: "in_progress" }, db),
70109
- completed: countTasks2({ ...base, priority: p === "none" ? undefined : p, status: "completed" }, db)
70810
+ total: await countFn(base2),
70811
+ pending: await countFn({ ...base2, status: "pending" }),
70812
+ in_progress: await countFn({ ...base2, status: "in_progress" }),
70813
+ completed: await countFn({ ...base2, status: "completed" })
70110
70814
  };
70111
70815
  }
70112
70816
  if (opts.json || globalOpts.json) {
@@ -71447,38 +72151,68 @@ exit 0
71447
72151
  program2.command("link-commit <task-id> <sha>").description("Link a git commit to a task").option("--message <text>", "Commit message").option("--author <name>", "Commit author").option("--files <list>", "Comma-separated list of changed files").action(async (taskId, sha, opts) => {
71448
72152
  const globalOpts = program2.opts();
71449
72153
  const resolvedId = resolveTaskId(taskId);
71450
- const { linkTaskToCommit: linkTaskToCommit2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
71451
- const commit = linkTaskToCommit2({
71452
- task_id: resolvedId,
71453
- sha,
71454
- message: opts.message,
71455
- author: opts.author,
71456
- files_changed: opts.files ? opts.files.split(",").filter(Boolean) : undefined
71457
- });
71458
- if (globalOpts.json) {
71459
- output(commit, true);
71460
- return;
72154
+ const files = opts.files ? opts.files.split(",").filter(Boolean) : undefined;
72155
+ try {
72156
+ const cloud = getTodosCloudClient();
72157
+ const commit = cloud ? await cloudLinkCommit(cloud, resolvedId, {
72158
+ sha,
72159
+ ...opts.message !== undefined ? { message: opts.message } : {},
72160
+ ...opts.author !== undefined ? { author: opts.author } : {},
72161
+ ...files ? { files_changed: files } : {}
72162
+ }) : (await Promise.resolve().then(() => (init_task_commits(), exports_task_commits))).linkTaskToCommit({
72163
+ task_id: resolvedId,
72164
+ sha,
72165
+ message: opts.message,
72166
+ author: opts.author,
72167
+ files_changed: files
72168
+ });
72169
+ if (globalOpts.json) {
72170
+ output(commit, true);
72171
+ return;
72172
+ }
72173
+ console.log(chalk8.green(`Linked commit ${sha.slice(0, 7)} to task ${taskId}`));
72174
+ } catch (e) {
72175
+ handleError(e);
71461
72176
  }
71462
- console.log(chalk8.green(`Linked commit ${sha.slice(0, 7)} to task ${taskId}`));
71463
72177
  });
71464
72178
  program2.command("find-commit <sha>").description("Find which task explains a git commit SHA").action(async (sha) => {
71465
72179
  const globalOpts = program2.opts();
71466
- const { findTaskByCommit: findTaskByCommit2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
71467
- const { getTask: getTask3 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
71468
- const result = findTaskByCommit2(sha);
71469
- if (globalOpts.json) {
71470
- output(result, true);
71471
- return;
71472
- }
71473
- if (!result) {
71474
- console.log(chalk8.dim(`No task linked to commit ${sha}.`));
71475
- return;
72180
+ try {
72181
+ const cloud = getTodosCloudClient();
72182
+ if (cloud) {
72183
+ const commit = await cloudFindCommit(cloud, sha);
72184
+ if (globalOpts.json) {
72185
+ output(commit ? { task_id: commit.task_id, commit } : null, true);
72186
+ return;
72187
+ }
72188
+ if (!commit) {
72189
+ console.log(chalk8.dim(`No task linked to commit ${sha}.`));
72190
+ return;
72191
+ }
72192
+ console.log(`${chalk8.yellow(commit.sha.slice(0, 7))} -> ${chalk8.cyan(commit.task_id)}`);
72193
+ if (commit.message)
72194
+ console.log(chalk8.dim(` ${commit.message}`));
72195
+ return;
72196
+ }
72197
+ const { findTaskByCommit: findTaskByCommit2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
72198
+ const { getTask: getTask3 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
72199
+ const result = findTaskByCommit2(sha);
72200
+ if (globalOpts.json) {
72201
+ output(result, true);
72202
+ return;
72203
+ }
72204
+ if (!result) {
72205
+ console.log(chalk8.dim(`No task linked to commit ${sha}.`));
72206
+ return;
72207
+ }
72208
+ const task2 = getTask3(result.task_id);
72209
+ const taskLabel = task2 ? `${task2.short_id || task2.id.slice(0, 8)} ${task2.title}` : result.task_id;
72210
+ console.log(`${chalk8.yellow(result.commit.sha.slice(0, 7))} -> ${chalk8.cyan(taskLabel)}`);
72211
+ if (result.commit.message)
72212
+ console.log(chalk8.dim(` ${result.commit.message}`));
72213
+ } catch (e) {
72214
+ handleError(e);
71476
72215
  }
71477
- const task2 = getTask3(result.task_id);
71478
- const taskLabel = task2 ? `${task2.short_id || task2.id.slice(0, 8)} ${task2.title}` : result.task_id;
71479
- console.log(`${chalk8.yellow(result.commit.sha.slice(0, 7))} -> ${chalk8.cyan(taskLabel)}`);
71480
- if (result.commit.message)
71481
- console.log(chalk8.dim(` ${result.commit.message}`));
71482
72216
  });
71483
72217
  program2.command("link-ref <task-id> <ref>").description("Link a git branch or pull request to a task").option("--type <type>", "Ref type: branch or pull_request", "branch").option("--url <url>", "Remote URL for the branch or pull request").option("--provider <name>", "Provider name, e.g. git or github").option("--metadata <json>", "Additional JSON metadata").action(async (taskId, ref, opts) => {
71484
72218
  const globalOpts = program2.opts();
@@ -71498,38 +72232,70 @@ exit 0
71498
72232
  process.exit(1);
71499
72233
  }
71500
72234
  }
71501
- const gitRef = linkTaskGitRef2({
71502
- task_id: resolvedId,
71503
- ref_type: refType,
71504
- name: ref,
71505
- url: opts.url,
71506
- provider: opts.provider,
71507
- metadata
71508
- });
71509
- if (globalOpts.json) {
71510
- output(gitRef, true);
71511
- return;
72235
+ try {
72236
+ const cloud = getTodosCloudClient();
72237
+ const gitRef = cloud ? await cloudLinkRef(cloud, resolvedId, {
72238
+ ref_type: refType,
72239
+ name: ref,
72240
+ ...opts.url !== undefined ? { url: opts.url } : {},
72241
+ ...opts.provider !== undefined ? { provider: opts.provider } : {},
72242
+ ...metadata ? { metadata } : {}
72243
+ }) : linkTaskGitRef2({
72244
+ task_id: resolvedId,
72245
+ ref_type: refType,
72246
+ name: ref,
72247
+ url: opts.url,
72248
+ provider: opts.provider,
72249
+ metadata
72250
+ });
72251
+ if (globalOpts.json) {
72252
+ output(gitRef, true);
72253
+ return;
72254
+ }
72255
+ console.log(chalk8.green(`Linked ${gitRef.ref_type} ${gitRef.name} to task ${taskId}`));
72256
+ } catch (e) {
72257
+ handleError(e);
71512
72258
  }
71513
- console.log(chalk8.green(`Linked ${gitRef.ref_type} ${gitRef.name} to task ${taskId}`));
71514
72259
  });
71515
72260
  program2.command("find-ref <ref>").description("Find tasks linked to a git branch or pull request").action(async (ref) => {
71516
72261
  const globalOpts = program2.opts();
71517
- const { findTasksByGitRef: findTasksByGitRef2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
71518
- const { getTask: getTask3 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
71519
- const refs = findTasksByGitRef2(ref);
71520
- if (globalOpts.json) {
71521
- output(refs, true);
71522
- return;
71523
- }
71524
- if (refs.length === 0) {
71525
- console.log(chalk8.dim(`No tasks linked to ${ref}.`));
71526
- return;
71527
- }
71528
- for (const gitRef of refs) {
71529
- const task2 = getTask3(gitRef.task_id);
71530
- const label = task2 ? `${task2.short_id || task2.id.slice(0, 8)} ${task2.title}` : gitRef.task_id;
71531
- const url = gitRef.url ? chalk8.dim(` ${gitRef.url}`) : "";
71532
- console.log(`${chalk8.cyan(label)} <- ${gitRef.ref_type} ${chalk8.yellow(gitRef.name)}${url}`);
72262
+ try {
72263
+ const cloud = getTodosCloudClient();
72264
+ if (cloud) {
72265
+ const refs2 = await cloudFindRefs(cloud, ref);
72266
+ if (globalOpts.json) {
72267
+ output(refs2, true);
72268
+ return;
72269
+ }
72270
+ if (refs2.length === 0) {
72271
+ console.log(chalk8.dim(`No tasks linked to ${ref}.`));
72272
+ return;
72273
+ }
72274
+ for (const gitRef of refs2) {
72275
+ const url = gitRef.url ? chalk8.dim(` ${gitRef.url}`) : "";
72276
+ console.log(`${chalk8.cyan(gitRef.task_id)} <- ${gitRef.ref_type} ${chalk8.yellow(gitRef.name)}${url}`);
72277
+ }
72278
+ return;
72279
+ }
72280
+ const { findTasksByGitRef: findTasksByGitRef2 } = await Promise.resolve().then(() => (init_task_commits(), exports_task_commits));
72281
+ const { getTask: getTask3 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
72282
+ const refs = findTasksByGitRef2(ref);
72283
+ if (globalOpts.json) {
72284
+ output(refs, true);
72285
+ return;
72286
+ }
72287
+ if (refs.length === 0) {
72288
+ console.log(chalk8.dim(`No tasks linked to ${ref}.`));
72289
+ return;
72290
+ }
72291
+ for (const gitRef of refs) {
72292
+ const task2 = getTask3(gitRef.task_id);
72293
+ const label = task2 ? `${task2.short_id || task2.id.slice(0, 8)} ${task2.title}` : gitRef.task_id;
72294
+ const url = gitRef.url ? chalk8.dim(` ${gitRef.url}`) : "";
72295
+ console.log(`${chalk8.cyan(label)} <- ${gitRef.ref_type} ${chalk8.yellow(gitRef.name)}${url}`);
72296
+ }
72297
+ } catch (e) {
72298
+ handleError(e);
71533
72299
  }
71534
72300
  });
71535
72301
  program2.command("branch-plan [task-id]").description("Create a local branch-safe work plan from task or plan files").requiredOption("--branch <name>", "Branch name to plan").option("--base <name>", "Base branch", "main").option("--plan <id>", "Plan ID scope instead of a single task").option("--path <list>", "Comma-separated extra paths expected for this branch").option("--root <path>", "Git root to inspect", process.cwd()).option("--no-git-status", "Skip local git status checks").action(async (taskId, opts) => {