@hasna/todos 0.13.5 → 0.13.7

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
@@ -2123,7 +2123,7 @@ var package_default;
2123
2123
  var init_package = __esm(() => {
2124
2124
  package_default = {
2125
2125
  name: "@hasna/todos",
2126
- version: "0.13.5",
2126
+ version: "0.13.7",
2127
2127
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
2128
2128
  type: "module",
2129
2129
  main: "dist/index.js",
@@ -5871,6 +5871,7 @@ function unresolvedRelatedTask(id) {
5871
5871
  reason: null,
5872
5872
  spawned_from_session: null,
5873
5873
  assigned_by: null,
5874
+ created_by: null,
5874
5875
  assigned_from_project: null,
5875
5876
  task_type: null,
5876
5877
  cost_tokens: 0,
@@ -9035,6 +9036,7 @@ function ensureSchema(db) {
9035
9036
  ensureColumn("tasks", "reason", "TEXT");
9036
9037
  ensureColumn("tasks", "spawned_from_session", "TEXT");
9037
9038
  ensureColumn("tasks", "assigned_by", "TEXT");
9039
+ ensureColumn("tasks", "created_by", "TEXT");
9038
9040
  ensureColumn("tasks", "assigned_from_project", "TEXT");
9039
9041
  ensureColumn("tasks", "started_at", "TEXT");
9040
9042
  ensureColumn("tasks", "task_type", "TEXT");
@@ -9197,6 +9199,8 @@ function ensureSchema(db) {
9197
9199
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_project ON project_sources(project_id)");
9198
9200
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_type ON project_sources(type)");
9199
9201
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_assigned_by ON tasks(assigned_by)");
9202
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_created_by ON tasks(created_by)");
9203
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_assigned_created ON tasks(assigned_to, created_by)");
9200
9204
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_source ON task_relationships(source_task_id)");
9201
9205
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_target ON task_relationships(target_task_id)");
9202
9206
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_type ON task_relationships(relationship_type)");
@@ -15346,13 +15350,14 @@ function createTask(input, db) {
15346
15350
  const timestamp2 = now();
15347
15351
  const tags = input.tags || [];
15348
15352
  const machineId = currentStorageMachineId(d);
15353
+ const createdBy = input.created_by || input.agent_id || null;
15349
15354
  const assignedBy = input.assigned_by || input.agent_id;
15350
15355
  const assignedFromProject = input.assigned_from_project || null;
15351
15356
  let id = uuid();
15352
15357
  for (let attempt = 0;attempt < 3; attempt++) {
15353
15358
  try {
15354
- d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, sla_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type, machine_id)
15355
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
15359
+ d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, sla_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, created_by, assigned_from_project, task_type, machine_id)
15360
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
15356
15361
  id,
15357
15362
  null,
15358
15363
  input.project_id || null,
@@ -15388,6 +15393,7 @@ function createTask(input, db) {
15388
15393
  input.reason || null,
15389
15394
  input.spawned_from_session || null,
15390
15395
  assignedBy || null,
15396
+ createdBy,
15391
15397
  assignedFromProject || null,
15392
15398
  input.task_type || null,
15393
15399
  machineId
@@ -15497,6 +15503,14 @@ function listTasks(filter = {}, db) {
15497
15503
  conditions.push("agent_id = ?");
15498
15504
  params.push(filter.agent_id);
15499
15505
  }
15506
+ if (filter.created_by) {
15507
+ conditions.push("LOWER(created_by) = LOWER(?)");
15508
+ params.push(filter.created_by);
15509
+ }
15510
+ if (filter.not_created_by) {
15511
+ conditions.push("(created_by IS NULL OR LOWER(created_by) != LOWER(?))");
15512
+ params.push(filter.not_created_by);
15513
+ }
15500
15514
  if (filter.session_id) {
15501
15515
  conditions.push("session_id = ?");
15502
15516
  params.push(filter.session_id);
@@ -15650,6 +15664,14 @@ function countTasks(filter = {}, db) {
15650
15664
  conditions.push("agent_id = ?");
15651
15665
  params.push(filter.agent_id);
15652
15666
  }
15667
+ if (filter.created_by) {
15668
+ conditions.push("LOWER(created_by) = LOWER(?)");
15669
+ params.push(filter.created_by);
15670
+ }
15671
+ if (filter.not_created_by) {
15672
+ conditions.push("(created_by IS NULL OR LOWER(created_by) != LOWER(?))");
15673
+ params.push(filter.not_created_by);
15674
+ }
15653
15675
  if (filter.session_id) {
15654
15676
  conditions.push("session_id = ?");
15655
15677
  params.push(filter.session_id);
@@ -15710,6 +15732,10 @@ function updateTask(id, input, db) {
15710
15732
  sets.push("description = ?");
15711
15733
  params.push(input.description);
15712
15734
  }
15735
+ if (input.agent_id !== undefined) {
15736
+ sets.push("agent_id = ?");
15737
+ params.push(input.agent_id);
15738
+ }
15713
15739
  if (input.status !== undefined) {
15714
15740
  if (input.status === "completed") {
15715
15741
  checkCompletionGuard(task, task.assigned_to || task.agent_id || null, d);
@@ -18810,6 +18836,82 @@ var init_tasks = __esm(() => {
18810
18836
  init_calendar();
18811
18837
  });
18812
18838
 
18839
+ // src/lib/creator-identity.ts
18840
+ import { existsSync as existsSync8, rmSync as rmSync2 } from "fs";
18841
+ import { join as join7 } from "path";
18842
+ function identityFilePath() {
18843
+ return join7(getTodosGlobalDir(), "identity.json");
18844
+ }
18845
+ function readPersistedIdentity() {
18846
+ const path = identityFilePath();
18847
+ if (!existsSync8(path))
18848
+ return null;
18849
+ const parsed = readJsonFile(path);
18850
+ if (!parsed || typeof parsed.agent_id !== "string" || !parsed.agent_id.trim())
18851
+ return null;
18852
+ return parsed;
18853
+ }
18854
+ function detectIdentityCollision(agentId, agentName) {
18855
+ const existing = readPersistedIdentity();
18856
+ if (!existing)
18857
+ return null;
18858
+ const sameId = canonicalAgentRef(existing.agent_id) === canonicalAgentRef(agentId);
18859
+ const sameName = Boolean(agentName) && canonicalAgentRef(existing.agent_name || "") === canonicalAgentRef(agentName);
18860
+ if (sameId || sameName)
18861
+ return null;
18862
+ return { existing };
18863
+ }
18864
+ function persistIdentity(identity) {
18865
+ const record = {
18866
+ agent_id: identity.agent_id,
18867
+ ...identity.agent_name ? { agent_name: identity.agent_name } : {},
18868
+ ...identity.session_id ? { session_id: identity.session_id } : {},
18869
+ registered_at: new Date().toISOString()
18870
+ };
18871
+ ensureDir(getTodosGlobalDir());
18872
+ writeJsonFile(identityFilePath(), record);
18873
+ return record;
18874
+ }
18875
+ function clearPersistedIdentity() {
18876
+ const path = identityFilePath();
18877
+ if (!existsSync8(path))
18878
+ return false;
18879
+ try {
18880
+ rmSync2(path);
18881
+ return true;
18882
+ } catch {
18883
+ return false;
18884
+ }
18885
+ }
18886
+ function canonicalAgentRef(value) {
18887
+ return value.trim().toLowerCase();
18888
+ }
18889
+ function isProcessBoundSource(source) {
18890
+ return source === "explicit" || source === "env";
18891
+ }
18892
+ function resolveWritableIdentity(explicit) {
18893
+ const resolved = resolveCreatorIdentity(explicit);
18894
+ if (!isProcessBoundSource(resolved.source))
18895
+ return { agent_id: null, source: "none" };
18896
+ return resolved;
18897
+ }
18898
+ function resolveCreatorIdentity(explicit) {
18899
+ const fromExplicit = explicit?.trim();
18900
+ if (fromExplicit)
18901
+ return { agent_id: canonicalAgentRef(fromExplicit), source: "explicit" };
18902
+ const fromEnv = (process.env["TODOS_AGENT_ID"] || process.env["HASNA_TODOS_AGENT_ID"] || "").trim();
18903
+ if (fromEnv)
18904
+ return { agent_id: canonicalAgentRef(fromEnv), source: "env" };
18905
+ const persisted = readPersistedIdentity();
18906
+ if (persisted) {
18907
+ return { agent_id: canonicalAgentRef(persisted.agent_name || persisted.agent_id), source: "persisted" };
18908
+ }
18909
+ return { agent_id: null, source: "none" };
18910
+ }
18911
+ var init_creator_identity = __esm(() => {
18912
+ init_sync_utils();
18913
+ });
18914
+
18813
18915
  // src/cli/output-redaction.ts
18814
18916
  function redactBroadTask(task) {
18815
18917
  return redactValue(task);
@@ -18830,7 +18932,7 @@ __export(exports_task_routing, {
18830
18932
  setTaskWorkflowPointers: () => setTaskWorkflowPointers,
18831
18933
  getTaskRouteState: () => getTaskRouteState
18832
18934
  });
18833
- import { existsSync as existsSync8, statSync as statSync3 } from "fs";
18935
+ import { existsSync as existsSync9, statSync as statSync3 } from "fs";
18834
18936
  function machineLocalPath(project, db) {
18835
18937
  const machineId = process.env["TODOS_MACHINE_ID"];
18836
18938
  if (!machineId)
@@ -18870,7 +18972,7 @@ function routeConcurrencyKey(task, project, taskList, projectPath) {
18870
18972
  }
18871
18973
  function directoryExists(path) {
18872
18974
  try {
18873
- return existsSync8(path) && statSync3(path).isDirectory();
18975
+ return existsSync9(path) && statSync3(path).isDirectory();
18874
18976
  } catch {
18875
18977
  return false;
18876
18978
  }
@@ -19307,10 +19409,16 @@ function computeLocalReparent(current, opts) {
19307
19409
  return patch;
19308
19410
  }
19309
19411
  function registerTaskCommands(program2) {
19310
- program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--approval", "Require approval before completion").option("--recurrence <rule>", "Recurrence rule, e.g. 'every day', 'every weekday', 'every 2 weeks'").option("--due <date>", "Due date (ISO string or YYYY-MM-DD)").option("--reason <text>", "Why this task exists").option("--project <id>", "Assign to project by ID or slug (overrides auto-detect)").action(async (title, opts) => {
19412
+ program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--approval", "Require approval before completion").option("--recurrence <rule>", "Recurrence rule, e.g. 'every day', 'every weekday', 'every 2 weeks'").option("--due <date>", "Due date (ISO string or YYYY-MM-DD)").option("--reason <text>", "Why this task exists").option("--project <id>", "Assign to project by ID or slug (overrides auto-detect)").option("--unassigned", "Deliberately file this task with no assignee").option("--created-by <agent>", "Record a different filer than the resolved agent identity").action(async (title, opts) => {
19311
19413
  const globalOpts = program2.opts();
19312
19414
  opts.tags = opts.tags || opts.tag;
19313
19415
  opts.list = opts.list || opts.taskList;
19416
+ const creator = resolveCreatorIdentity(opts.createdBy || globalOpts.agent);
19417
+ const router = resolveWritableIdentity(opts.createdBy || globalOpts.agent);
19418
+ const assignee = opts.assign || (opts.unassigned ? undefined : router.agent_id || undefined);
19419
+ if (!assignee && !opts.unassigned) {
19420
+ console.error(chalk2.yellow("Warning: task is ownerless and unattributable \u2014 export TODOS_AGENT_ID=<name> for this session, or pass --agent/--assign <agent> or --unassigned."));
19421
+ }
19314
19422
  const cloud = getTodosCloudClient();
19315
19423
  if (cloud) {
19316
19424
  let task3;
@@ -19332,10 +19440,11 @@ function registerTaskCommands(program2) {
19332
19440
  parent_id: opts.parent ? await resolveTaskIdForCommand(opts.parent, cloud) : undefined,
19333
19441
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
19334
19442
  plan_id: cloudPlan?.id,
19335
- assigned_to: opts.assign,
19443
+ assigned_to: assignee,
19336
19444
  status: parseStatus(opts.status),
19337
19445
  task_list_id: cloudTaskListId,
19338
- agent_id: globalOpts.agent,
19446
+ agent_id: globalOpts.agent || router.agent_id || undefined,
19447
+ created_by: creator.agent_id || undefined,
19339
19448
  session_id: globalOpts.session,
19340
19449
  project_id: cloudProjectId,
19341
19450
  estimated_minutes: opts.estimated !== undefined ? parseIntOption(opts.estimated, "--estimated") : undefined,
@@ -19377,10 +19486,11 @@ function registerTaskCommands(program2) {
19377
19486
  parent_id: opts.parent ? resolveTaskId(opts.parent) : undefined,
19378
19487
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
19379
19488
  plan_id: opts.plan ? resolvePlanId(opts.plan) : undefined,
19380
- assigned_to: opts.assign,
19489
+ assigned_to: assignee,
19381
19490
  status: parseStatus(opts.status),
19382
19491
  task_list_id: taskListId,
19383
- agent_id: globalOpts.agent,
19492
+ agent_id: globalOpts.agent || router.agent_id || undefined,
19493
+ created_by: creator.agent_id || undefined,
19384
19494
  session_id: globalOpts.session,
19385
19495
  project_id: projectId,
19386
19496
  working_dir: process.cwd(),
@@ -19534,14 +19644,14 @@ function registerTaskCommands(program2) {
19534
19644
  console.log(` ${chalk2.dim("Manifest:")} ${state.pointers.latest_manifest_path}`);
19535
19645
  }
19536
19646
  });
19537
- program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <ref>", "Filter by task list UUID, unique UUID prefix, or project-scoped slug").option("--task-list <ref>", "Filter by task list UUID, unique UUID prefix, or project-scoped slug (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").option("--due-today", "Only tasks due today or earlier").option("--overdue", "Only overdue tasks (past due_at)").option("--recurring", "Only recurring tasks").option("--limit <n>", "Max tasks to return").action(async (opts) => {
19647
+ program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--created-by <agent>", "Filter by the agent who FILED the task").option("--not-created-by <agent>", "Exclude tasks filed by this agent").option("--inbox", "Work assigned to my identity that a DIFFERENT agent filed").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <ref>", "Filter by task list UUID, unique UUID prefix, or project-scoped slug").option("--task-list <ref>", "Filter by task list UUID, unique UUID prefix, or project-scoped slug (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").option("--due-today", "Only tasks due today or earlier").option("--overdue", "Only overdue tasks (past due_at)").option("--recurring", "Only recurring tasks").option("--limit <n>", "Max tasks to return").action(async (opts) => {
19538
19648
  const globalOpts = program2.opts();
19539
19649
  opts.tags = opts.tags || opts.tag;
19540
19650
  opts.list = opts.list || opts.taskList;
19541
19651
  const cloud = getTodosCloudClient();
19542
19652
  const cloudProjectRef = globalOpts.project || opts.projectName;
19543
19653
  const projectId = cloud && cloudProjectRef ? await cloudResolveProjectRef(cloud, cloudProjectRef) : cloud ? undefined : autoProject(globalOpts);
19544
- const hasAssignedFilter = Boolean(opts.assigned || opts.agentName);
19654
+ const hasAssignedFilter = Boolean(opts.assigned || opts.agentName || opts.inbox);
19545
19655
  const hasExplicitProjectFilter = Boolean(globalOpts.project || opts.projectName);
19546
19656
  const allowedSortFields = new Set(["updated", "created", "priority", "status"]);
19547
19657
  if (opts.sort && !allowedSortFields.has(opts.sort)) {
@@ -19574,6 +19684,19 @@ function registerTaskCommands(program2) {
19574
19684
  filter["priority"] = opts.priority;
19575
19685
  if (opts.assigned)
19576
19686
  filter["assigned_to"] = opts.assigned;
19687
+ if (opts.createdBy)
19688
+ filter["created_by"] = canonicalAgentRef(opts.createdBy);
19689
+ if (opts.notCreatedBy)
19690
+ filter["not_created_by"] = canonicalAgentRef(opts.notCreatedBy);
19691
+ if (opts.inbox) {
19692
+ const me = resolveCreatorIdentity(program2.opts().agent);
19693
+ if (!me.agent_id) {
19694
+ console.error(chalk2.red("--inbox needs an agent identity. Run `todos init <name>` or pass --agent <id>."));
19695
+ process.exit(1);
19696
+ }
19697
+ filter["assigned_to"] = me.agent_id;
19698
+ filter["not_created_by"] = me.agent_id;
19699
+ }
19577
19700
  if (opts.tags)
19578
19701
  filter["tags"] = opts.tags.split(",").map((t) => t.trim());
19579
19702
  if (opts.projectName && !cloud) {
@@ -19598,7 +19721,32 @@ function registerTaskCommands(program2) {
19598
19721
  }
19599
19722
  filter["limit"] = parsedLimit;
19600
19723
  }
19601
- let tasks = cloud ? await cloudListTasks(cloud, filter) : listTasks(filter);
19724
+ const creatorFilterActive = Boolean(filter["created_by"] || filter["not_created_by"]);
19725
+ const requestedLimit = filter["limit"];
19726
+ const serverFilter = creatorFilterActive && cloud && requestedLimit !== undefined ? (() => {
19727
+ const { limit: _dropped, ...rest } = filter;
19728
+ return rest;
19729
+ })() : filter;
19730
+ let tasks = cloud ? await cloudListTasks(cloud, serverFilter) : listTasks(serverFilter);
19731
+ if (cloud && creatorFilterActive) {
19732
+ if (tasks.length > 0 && tasks.every((t) => !("created_by" in t))) {
19733
+ console.error(chalk2.yellow(`Warning: this server does not record task authorship, so the creator filter matched nothing to exclude.
19734
+ ` + " Results are unfiltered. The API needs upgrading past the release that added created_by."));
19735
+ }
19736
+ const wantCreatedBy = filter["created_by"];
19737
+ const excludeCreatedBy = filter["not_created_by"];
19738
+ tasks = tasks.filter((t) => {
19739
+ const raw = t.created_by ?? null;
19740
+ const author = raw === null ? null : canonicalAgentRef(raw);
19741
+ if (wantCreatedBy && author !== canonicalAgentRef(wantCreatedBy))
19742
+ return false;
19743
+ if (excludeCreatedBy && author !== null && author === canonicalAgentRef(excludeCreatedBy))
19744
+ return false;
19745
+ return true;
19746
+ });
19747
+ if (requestedLimit !== undefined)
19748
+ tasks = tasks.slice(0, requestedLimit);
19749
+ }
19602
19750
  if (opts.dueToday) {
19603
19751
  const todayEnd = new Date;
19604
19752
  todayEnd.setHours(23, 59, 59, 999);
@@ -20004,7 +20152,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
20004
20152
  console.log(` ${chalk2.dim(h.created_at)} ${chalk2.bold(h.action)}${field}${change}${agent}`);
20005
20153
  }
20006
20154
  });
20007
- program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list (UUID authoritative; project-scoped slug accepted)").option("--task-list <id>", "Move to a task list (alias for --list)").option("--clear-list", "Detach from its task list (reset task_list_id to null)").option("--project <id>", "Re-parent the task to another project (by ID, slug, or path); see also `todos move`").option("--working-dir <path>", "Repair the task's working_dir to a specific path (routing metadata)").option("--clear-working-dir", "Reset the task's working_dir to null (undo path for routing repairs)").option("--plan <id>", "Move to a plan").option("--clear-plan", "Remove from its current plan").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--due <date>", "Due date (ISO string or YYYY-MM-DD), empty to clear").option("--recurrence <rule>", "Recurrence rule, empty to clear").option("--approval", "Require approval before completion").option("--clear-approval", "Remove the approval requirement").action(async (id, opts) => {
20155
+ program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--set-agent <agent>", 'Repair the agent_id stamped on this row (use "" to clear it as unattributable)').option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list (UUID authoritative; project-scoped slug accepted)").option("--task-list <id>", "Move to a task list (alias for --list)").option("--clear-list", "Detach from its task list (reset task_list_id to null)").option("--project <id>", "Re-parent the task to another project (by ID, slug, or path); see also `todos move`").option("--working-dir <path>", "Repair the task's working_dir to a specific path (routing metadata)").option("--clear-working-dir", "Reset the task's working_dir to null (undo path for routing repairs)").option("--plan <id>", "Move to a plan").option("--clear-plan", "Remove from its current plan").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--due <date>", "Due date (ISO string or YYYY-MM-DD), empty to clear").option("--recurrence <rule>", "Recurrence rule, empty to clear").option("--approval", "Require approval before completion").option("--clear-approval", "Remove the approval requirement").action(async (id, opts) => {
20008
20156
  const globalOpts = program2.opts();
20009
20157
  opts.tags = opts.tags || opts.tag;
20010
20158
  opts.list = opts.list || opts.taskList;
@@ -20042,6 +20190,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
20042
20190
  status: parseStatus(opts.status),
20043
20191
  priority: parsePriority(opts.priority),
20044
20192
  assigned_to: opts.assign,
20193
+ agent_id: opts.setAgent !== undefined ? opts.setAgent === "" ? null : canonicalAgentRef(opts.setAgent) : undefined,
20045
20194
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
20046
20195
  plan_id: plan?.id ?? (opts.clearPlan ? null : undefined),
20047
20196
  ...reparent2,
@@ -20083,6 +20232,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
20083
20232
  status: parseStatus(opts.status),
20084
20233
  priority: parsePriority(opts.priority),
20085
20234
  assigned_to: opts.assign,
20235
+ agent_id: opts.setAgent !== undefined ? opts.setAgent === "" ? null : canonicalAgentRef(opts.setAgent) : undefined,
20086
20236
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
20087
20237
  plan_id: planId,
20088
20238
  ...reparent,
@@ -20482,14 +20632,15 @@ var init_task_commands = __esm(() => {
20482
20632
  init_tasks();
20483
20633
  init_task_lists();
20484
20634
  init_cloud_router();
20635
+ init_creator_identity();
20485
20636
  init_helpers();
20486
20637
  init_output_redaction();
20487
20638
  init_types();
20488
20639
  });
20489
20640
 
20490
20641
  // src/lib/plan-artifacts.ts
20491
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
20492
- import { join as join7, resolve as resolve10 } from "path";
20642
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
20643
+ import { join as join8, resolve as resolve10 } from "path";
20493
20644
  function assertSafePathSegment(value, label) {
20494
20645
  const trimmed = value.trim();
20495
20646
  if (!trimmed || trimmed === "." || trimmed === ".." || trimmed.includes("/") || trimmed.includes("\\")) {
@@ -20550,7 +20701,7 @@ function resolvePlanArtifactPaths(input) {
20550
20701
  const project = resolvePlanArtifactProject(input);
20551
20702
  const projectId = assertSafePathSegment(project.id, "project id");
20552
20703
  const projectRoot = resolve10(project.path);
20553
- const directory = join7(projectRoot, ".hasna", "todos", "plans", projectId);
20704
+ const directory = join8(projectRoot, ".hasna", "todos", "plans", projectId);
20554
20705
  const planId = input.plan_id ? assertSafePathSegment(input.plan_id, "plan id") : null;
20555
20706
  const planSlug = input.plan_slug ? assertSafePathSegment(slugify(input.plan_slug), "plan slug") : null;
20556
20707
  const fileName = planId ? planSlug ? `${planSlug}--${planId.slice(0, 8)}.md` : `${planId}.md` : null;
@@ -20558,7 +20709,7 @@ function resolvePlanArtifactPaths(input) {
20558
20709
  project_id: project.id,
20559
20710
  project_root: projectRoot,
20560
20711
  directory,
20561
- file_path: fileName ? join7(directory, fileName) : directory
20712
+ file_path: fileName ? join8(directory, fileName) : directory
20562
20713
  };
20563
20714
  }
20564
20715
  function resolvePlanArtifactCandidatePaths(plan, db) {
@@ -20724,7 +20875,7 @@ function readPlanArtifact(plan, db) {
20724
20875
  return null;
20725
20876
  const d = db || getDatabase();
20726
20877
  const paths = resolvePlanArtifactCandidatePaths(plan, d);
20727
- const path = existsSync9(paths.primary.file_path) ? paths.primary.file_path : existsSync9(paths.legacy.file_path) ? paths.legacy.file_path : null;
20878
+ const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
20728
20879
  if (!path)
20729
20880
  return null;
20730
20881
  const markdown = readFileSync4(path, "utf8");
@@ -20739,7 +20890,7 @@ function inspectPlanArtifact(plan, db) {
20739
20890
  return null;
20740
20891
  const d = db || getDatabase();
20741
20892
  const paths = resolvePlanArtifactCandidatePaths(plan, d);
20742
- const path = existsSync9(paths.primary.file_path) ? paths.primary.file_path : existsSync9(paths.legacy.file_path) ? paths.legacy.file_path : null;
20893
+ const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
20743
20894
  if (!path) {
20744
20895
  return {
20745
20896
  path: paths.primary.file_path,
@@ -20819,7 +20970,7 @@ __export(exports_builtin_templates, {
20819
20970
  BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
20820
20971
  });
20821
20972
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
20822
- import { join as join8 } from "path";
20973
+ import { join as join9 } from "path";
20823
20974
  function templateMetadata(template) {
20824
20975
  return {
20825
20976
  source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
@@ -20878,7 +21029,7 @@ function writeBuiltinTemplateFiles(directory) {
20878
21029
  mkdirSync7(directory, { recursive: true });
20879
21030
  const files = [];
20880
21031
  for (const entry of exportBuiltinTemplateFiles()) {
20881
- const path = join8(directory, entry.filename);
21032
+ const path = join9(directory, entry.filename);
20882
21033
  writeFileSync5(path, `${JSON.stringify(entry.template, null, 2)}
20883
21034
  `, "utf-8");
20884
21035
  files.push(path);
@@ -22650,16 +22801,16 @@ var init_saved_search_views = __esm(() => {
22650
22801
  });
22651
22802
 
22652
22803
  // src/lib/claude-tasks.ts
22653
- import { existsSync as existsSync10, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync6 } from "fs";
22654
- import { join as join9 } from "path";
22804
+ import { existsSync as existsSync11, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync6 } from "fs";
22805
+ import { join as join10 } from "path";
22655
22806
  function getTaskListDir(taskListId) {
22656
- return join9(HOME, ".claude", "tasks", taskListId);
22807
+ return join10(HOME, ".claude", "tasks", taskListId);
22657
22808
  }
22658
22809
  function readClaudeTask(dir, filename) {
22659
- return readJsonFile(join9(dir, filename));
22810
+ return readJsonFile(join10(dir, filename));
22660
22811
  }
22661
22812
  function writeClaudeTask(dir, task) {
22662
- writeJsonFile(join9(dir, `${task.id}.json`), task);
22813
+ writeJsonFile(join10(dir, `${task.id}.json`), task);
22663
22814
  }
22664
22815
  function toClaudeStatus(status) {
22665
22816
  if (status === "pending" || status === "in_progress" || status === "completed") {
@@ -22671,14 +22822,14 @@ function toSqliteStatus(status) {
22671
22822
  return status;
22672
22823
  }
22673
22824
  function readPrefixCounter(dir) {
22674
- const path = join9(dir, ".prefix-counter");
22675
- if (!existsSync10(path))
22825
+ const path = join10(dir, ".prefix-counter");
22826
+ if (!existsSync11(path))
22676
22827
  return 0;
22677
22828
  const val = parseInt(readFileSync5(path, "utf-8").trim(), 10);
22678
22829
  return isNaN(val) ? 0 : val;
22679
22830
  }
22680
22831
  function writePrefixCounter(dir, value) {
22681
- writeFileSync6(join9(dir, ".prefix-counter"), String(value));
22832
+ writeFileSync6(join10(dir, ".prefix-counter"), String(value));
22682
22833
  }
22683
22834
  function formatPrefixedSubject(title, prefix, counter) {
22684
22835
  const padded = String(counter).padStart(5, "0");
@@ -22705,7 +22856,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
22705
22856
  }
22706
22857
  function pushToClaudeTaskList(taskListId, projectId, options = {}) {
22707
22858
  const dir = getTaskListDir(taskListId);
22708
- if (!existsSync10(dir))
22859
+ if (!existsSync11(dir))
22709
22860
  ensureDir(dir);
22710
22861
  const filter = {};
22711
22862
  if (projectId)
@@ -22714,7 +22865,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
22714
22865
  const existingByTodosId = new Map;
22715
22866
  const files = listJsonFiles(dir);
22716
22867
  for (const f of files) {
22717
- const path = join9(dir, f);
22868
+ const path = join10(dir, f);
22718
22869
  const ct = readClaudeTask(dir, f);
22719
22870
  if (ct?.metadata?.["todos_id"]) {
22720
22871
  existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
@@ -22801,7 +22952,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
22801
22952
  }
22802
22953
  function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
22803
22954
  const dir = getTaskListDir(taskListId);
22804
- if (!existsSync10(dir)) {
22955
+ if (!existsSync11(dir)) {
22805
22956
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
22806
22957
  }
22807
22958
  const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
@@ -22821,7 +22972,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
22821
22972
  }
22822
22973
  for (const f of files) {
22823
22974
  try {
22824
- const filePath = join9(dir, f);
22975
+ const filePath = join10(dir, f);
22825
22976
  const ct = readClaudeTask(dir, f);
22826
22977
  if (!ct)
22827
22978
  continue;
@@ -22894,20 +23045,20 @@ var init_claude_tasks = __esm(() => {
22894
23045
  });
22895
23046
 
22896
23047
  // src/lib/agent-tasks.ts
22897
- import { existsSync as existsSync11 } from "fs";
22898
- import { join as join10 } from "path";
23048
+ import { existsSync as existsSync12 } from "fs";
23049
+ import { join as join11 } from "path";
22899
23050
  function agentBaseDir(agent) {
22900
23051
  const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
22901
- return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join10(getTodosGlobalDir(), "agents");
23052
+ return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join11(getTodosGlobalDir(), "agents");
22902
23053
  }
22903
23054
  function getTaskListDir2(agent, taskListId) {
22904
- return join10(agentBaseDir(agent), agent, taskListId);
23055
+ return join11(agentBaseDir(agent), agent, taskListId);
22905
23056
  }
22906
23057
  function readAgentTask(dir, filename) {
22907
- return readJsonFile(join10(dir, filename));
23058
+ return readJsonFile(join11(dir, filename));
22908
23059
  }
22909
23060
  function writeAgentTask(dir, task) {
22910
- writeJsonFile(join10(dir, `${task.id}.json`), task);
23061
+ writeJsonFile(join11(dir, `${task.id}.json`), task);
22911
23062
  }
22912
23063
  function taskToAgentTask(task, externalId, existingMeta) {
22913
23064
  return {
@@ -22932,7 +23083,7 @@ function metadataKey(agent) {
22932
23083
  }
22933
23084
  function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
22934
23085
  const dir = getTaskListDir2(agent, taskListId);
22935
- if (!existsSync11(dir))
23086
+ if (!existsSync12(dir))
22936
23087
  ensureDir(dir);
22937
23088
  const filter = {};
22938
23089
  if (projectId)
@@ -22941,7 +23092,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
22941
23092
  const existingByTodosId = new Map;
22942
23093
  const files = listJsonFiles(dir);
22943
23094
  for (const f of files) {
22944
- const path = join10(dir, f);
23095
+ const path = join11(dir, f);
22945
23096
  const at = readAgentTask(dir, f);
22946
23097
  if (at?.metadata?.["todos_id"]) {
22947
23098
  existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
@@ -23015,7 +23166,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
23015
23166
  }
23016
23167
  function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
23017
23168
  const dir = getTaskListDir2(agent, taskListId);
23018
- if (!existsSync11(dir)) {
23169
+ if (!existsSync12(dir)) {
23019
23170
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
23020
23171
  }
23021
23172
  const files = listJsonFiles(dir);
@@ -23034,7 +23185,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
23034
23185
  }
23035
23186
  for (const f of files) {
23036
23187
  try {
23037
- const filePath = join10(dir, f);
23188
+ const filePath = join11(dir, f);
23038
23189
  const at = readAgentTask(dir, f);
23039
23190
  if (!at)
23040
23191
  continue;
@@ -23187,7 +23338,7 @@ __export(exports_project_bootstrap, {
23187
23338
  discoverProjectWorkspace: () => discoverProjectWorkspace,
23188
23339
  bootstrapProject: () => bootstrapProject
23189
23340
  });
23190
- import { existsSync as existsSync12, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
23341
+ import { existsSync as existsSync13, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
23191
23342
  import { basename as basename4, dirname as dirname6, resolve as resolve11 } from "path";
23192
23343
  function safeStat(path) {
23193
23344
  try {
@@ -23206,7 +23357,7 @@ function canonicalPath(input) {
23206
23357
  function findUp(start, marker) {
23207
23358
  let current = canonicalPath(start);
23208
23359
  while (true) {
23209
- if (existsSync12(resolve11(current, marker)))
23360
+ if (existsSync13(resolve11(current, marker)))
23210
23361
  return current;
23211
23362
  const parent = dirname6(current);
23212
23363
  if (parent === current)
@@ -23218,7 +23369,7 @@ function readPackageJson(path) {
23218
23369
  if (!path)
23219
23370
  return null;
23220
23371
  const file = resolve11(path, "package.json");
23221
- if (!existsSync12(file))
23372
+ if (!existsSync13(file))
23222
23373
  return null;
23223
23374
  try {
23224
23375
  const parsed = JSON.parse(readFileSync6(file, "utf-8"));
@@ -23240,7 +23391,7 @@ function workspaceMarker(root, rootPackage) {
23240
23391
  if (rootPackage?.workspaces)
23241
23392
  markers.push("package.json#workspaces");
23242
23393
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
23243
- if (existsSync12(resolve11(root, marker)))
23394
+ if (existsSync13(resolve11(root, marker)))
23244
23395
  markers.push(marker);
23245
23396
  }
23246
23397
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -23574,9 +23725,9 @@ __export(exports_extract, {
23574
23725
  buildCodebaseIndex: () => buildCodebaseIndex,
23575
23726
  EXTRACT_TAGS: () => EXTRACT_TAGS
23576
23727
  });
23577
- import { existsSync as existsSync13, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
23728
+ import { existsSync as existsSync14, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
23578
23729
  import { createHash as createHash4 } from "crypto";
23579
- import { relative as relative3, resolve as resolve12, join as join11 } from "path";
23730
+ import { relative as relative3, resolve as resolve12, join as join12 } from "path";
23580
23731
  function stableHash(value) {
23581
23732
  return createHash4("sha256").update(value).digest("hex");
23582
23733
  }
@@ -23585,8 +23736,8 @@ function normalizePathForMatch(value) {
23585
23736
  }
23586
23737
  function readGitignorePatterns(basePath) {
23587
23738
  const root = statSync5(basePath).isFile() ? resolve12(basePath, "..") : basePath;
23588
- const gitignorePath = join11(root, ".gitignore");
23589
- if (!existsSync13(gitignorePath))
23739
+ const gitignorePath = join12(root, ".gitignore");
23740
+ if (!existsSync14(gitignorePath))
23590
23741
  return [];
23591
23742
  try {
23592
23743
  return readFileSync7(gitignorePath, "utf-8").split(`
@@ -23728,7 +23879,7 @@ function buildCodebaseIndex(options) {
23728
23879
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
23729
23880
  const indexed = [];
23730
23881
  for (const file of files) {
23731
- const fullPath = statSync5(basePath).isFile() ? basePath : join11(basePath, file);
23882
+ const fullPath = statSync5(basePath).isFile() ? basePath : join12(basePath, file);
23732
23883
  try {
23733
23884
  const source = readFileSync7(fullPath, "utf-8");
23734
23885
  const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
@@ -23759,7 +23910,7 @@ function extractTodos(options, db) {
23759
23910
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
23760
23911
  const allComments = [];
23761
23912
  for (const file of files) {
23762
- const fullPath = statSync5(basePath).isFile() ? basePath : join11(basePath, file);
23913
+ const fullPath = statSync5(basePath).isFile() ? basePath : join12(basePath, file);
23763
23914
  try {
23764
23915
  const source = readFileSync7(fullPath, "utf-8");
23765
23916
  const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
@@ -26810,8 +26961,16 @@ function resolveCloudAgentByNameOrId(agents, nameOrId) {
26810
26961
  return null;
26811
26962
  return matches.reduce((freshest, candidate) => new Date(candidate.last_seen_at).getTime() > new Date(freshest.last_seen_at).getTime() ? candidate : freshest);
26812
26963
  }
26964
+ function clearIdentityIfMine(agentId, agentName) {
26965
+ const persisted = readPersistedIdentity();
26966
+ if (!persisted)
26967
+ return;
26968
+ if (persisted.agent_id === agentId || agentName && persisted.agent_name === agentName) {
26969
+ clearPersistedIdentity();
26970
+ }
26971
+ }
26813
26972
  function registerAgentCommands(program2) {
26814
- program2.command("init <name>").alias("register").description("Register an agent and get a short UUID (alias: register)").option("-d, --description <text>", "Agent description").action(async (name, opts) => {
26973
+ program2.command("init <name>").alias("register").description("Register an agent and get a short UUID (alias: register)").option("-d, --description <text>", "Agent description").option("--force", "Take over the machine-wide persisted identity even if another session holds it").action(async (name, opts) => {
26815
26974
  const globalOpts = program2.opts();
26816
26975
  try {
26817
26976
  const cloud = getTodosCloudClient();
@@ -26821,14 +26980,25 @@ function registerAgentCommands(program2) {
26821
26980
  console.error(chalk5.red("CONFLICT:"), result.message);
26822
26981
  process.exit(1);
26823
26982
  }
26983
+ const collision = detectIdentityCollision(result.id, result.name);
26984
+ if (collision && !opts.force) {
26985
+ const held = collision.existing.agent_name || collision.existing.agent_id;
26986
+ console.error(chalk5.red(`This machine already has a persisted todos identity: ${held} (registered ${collision.existing.registered_at}).`));
26987
+ console.error(chalk5.yellow(`Overwriting it would make that session attribute its tasks to you.
26988
+ ` + `For a concurrent session, set a per-process identity instead \u2014 it outranks the file and cannot collide:
26989
+ ` + ` export TODOS_AGENT_ID=${result.name}
26990
+ Or pass --force to take over the machine-wide identity.`));
26991
+ process.exit(2);
26992
+ }
26993
+ persistIdentity({ agent_id: result.id, agent_name: result.name, ...globalOpts.session ? { session_id: globalOpts.session } : {} });
26824
26994
  if (globalOpts.json) {
26825
- output(result, true);
26995
+ output({ ...result, identity_persisted: true }, true);
26826
26996
  } else {
26827
26997
  console.log(chalk5.green("Agent registered:"));
26828
26998
  console.log(` ${chalk5.dim("ID:")} ${result.id}`);
26829
26999
  console.log(` ${chalk5.dim("Name:")} ${result.name}`);
26830
27000
  console.log(`
26831
- Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
27001
+ ${chalk5.dim("Identity saved \u2014 later commands attribute to this agent automatically.")}`);
26832
27002
  }
26833
27003
  } catch (e) {
26834
27004
  handleError(e);
@@ -26885,6 +27055,7 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
26885
27055
  if (!result.released) {
26886
27056
  handleError(new Error("Release denied: session_id does not match agent's current session."));
26887
27057
  }
27058
+ clearIdentityIfMine(result.agent.id, result.agent.name);
26888
27059
  if (globalOpts.json) {
26889
27060
  console.log(JSON.stringify({ agent_id: result.agent.id, name: result.agent.name, released: true }));
26890
27061
  } else {
@@ -26901,6 +27072,7 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
26901
27072
  if (!released) {
26902
27073
  handleError(new Error("Release denied: session_id does not match agent's current session."));
26903
27074
  }
27075
+ clearIdentityIfMine(a.id, a.name);
26904
27076
  if (globalOpts.json) {
26905
27077
  console.log(JSON.stringify({ agent_id: a.id, name: a.name, released: true }));
26906
27078
  } else {
@@ -27241,6 +27413,7 @@ var init_agent_commands = __esm(() => {
27241
27413
  init_task_lists();
27242
27414
  init_tasks();
27243
27415
  init_helpers();
27416
+ init_creator_identity();
27244
27417
  init_cloud_router();
27245
27418
  });
27246
27419
 
@@ -27283,8 +27456,8 @@ async function findFreePort(start) {
27283
27456
  var DEFAULT_PORT = 19427;
27284
27457
 
27285
27458
  // src/lib/db-backup.ts
27286
- import { existsSync as existsSync14, copyFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync8, renameSync, statSync as statSync6, writeFileSync as writeFileSync7, unlinkSync } from "fs";
27287
- import { dirname as dirname7, join as join12, resolve as resolve14 } from "path";
27459
+ import { existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync8, renameSync, statSync as statSync6, writeFileSync as writeFileSync7, unlinkSync } from "fs";
27460
+ import { dirname as dirname7, join as join13, resolve as resolve14 } from "path";
27288
27461
  import { Database as Database2 } from "bun:sqlite";
27289
27462
  function resolveDbPath(dbPath) {
27290
27463
  if (dbPath)
@@ -27300,7 +27473,7 @@ function resolveDbPath(dbPath) {
27300
27473
  }
27301
27474
  function backupDatabase(outputPath, sourcePath) {
27302
27475
  const source = resolveDbPath(sourcePath);
27303
- if (!existsSync14(source))
27476
+ if (!existsSync15(source))
27304
27477
  throw new Error(`Database not found: ${source}`);
27305
27478
  mkdirSync8(dirname7(outputPath), { recursive: true });
27306
27479
  closeDatabase();
@@ -27325,7 +27498,7 @@ function backupDatabase(outputPath, sourcePath) {
27325
27498
  function checkDatabaseIntegrity(dbPath) {
27326
27499
  const path = dbPath ? resolve14(dbPath) : resolveDbPath();
27327
27500
  const errors = [];
27328
- if (!existsSync14(path)) {
27501
+ if (!existsSync15(path)) {
27329
27502
  return {
27330
27503
  schema_version: DB_BACKUP_SCHEMA,
27331
27504
  path,
@@ -27390,7 +27563,7 @@ function checkDatabaseIntegrity(dbPath) {
27390
27563
  function defaultBackupPath(dbPath) {
27391
27564
  const base = dbPath ? dirname7(resolve14(dbPath)) : dirname7(resolveDbPath());
27392
27565
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
27393
- return join12(base, "backups", `todos-${stamp}.db`);
27566
+ return join13(base, "backups", `todos-${stamp}.db`);
27394
27567
  }
27395
27568
  var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
27396
27569
  var init_db_backup = __esm(() => {
@@ -27407,7 +27580,7 @@ __export(exports_evidence_redaction, {
27407
27580
  TODOS_EVIDENCE_REDACTION_CONFIRM: () => TODOS_EVIDENCE_REDACTION_CONFIRM
27408
27581
  });
27409
27582
  import { chmodSync } from "fs";
27410
- import { join as join13 } from "path";
27583
+ import { join as join14 } from "path";
27411
27584
  function unique4(values) {
27412
27585
  return Array.from(new Set((values ?? []).map((value) => value.trim()).filter(Boolean)));
27413
27586
  }
@@ -27754,7 +27927,7 @@ function redactEvidenceRows(options, db) {
27754
27927
  }
27755
27928
  function defaultEvidenceRedactionBackupPath(dbPath) {
27756
27929
  const base = defaultBackupPath(dbPath);
27757
- return join13(base.replace(/\.db$/, ""), "pre-redaction.db");
27930
+ return join14(base.replace(/\.db$/, ""), "pre-redaction.db");
27758
27931
  }
27759
27932
  var TODOS_EVIDENCE_REDACTION_SCHEMA = "todos.evidence_redaction.v1", TODOS_EVIDENCE_REDACTION_CONFIRM = "REDACT_TODOS_EVIDENCE";
27760
27933
  var init_evidence_redaction = __esm(() => {
@@ -27770,7 +27943,7 @@ __export(exports_retention_cleanup, {
27770
27943
  applyRetentionCleanup: () => applyRetentionCleanup,
27771
27944
  RETENTION_CLEANUP_CONFIRMATION: () => RETENTION_CLEANUP_CONFIRMATION
27772
27945
  });
27773
- import { existsSync as existsSync15, unlinkSync as unlinkSync2 } from "fs";
27946
+ import { existsSync as existsSync16, unlinkSync as unlinkSync2 } from "fs";
27774
27947
  function normalizeScopes(scopes) {
27775
27948
  if (!scopes || scopes.length === 0)
27776
27949
  return [...ALL_SCOPES];
@@ -27973,7 +28146,7 @@ function applyRetentionCleanup(input, db) {
27973
28146
  for (const artifact of report.candidates.artifact_files) {
27974
28147
  try {
27975
28148
  const path = artifactStorePath(artifact.relative_path);
27976
- if (!existsSync15(path)) {
28149
+ if (!existsSync16(path)) {
27977
28150
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
27978
28151
  continue;
27979
28152
  }
@@ -28649,8 +28822,8 @@ __export(exports_local_extensions, {
28649
28822
  discoverLocalExtensions: () => discoverLocalExtensions
28650
28823
  });
28651
28824
  import { createHash as createHash6, createVerify } from "crypto";
28652
- import { existsSync as existsSync16, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
28653
- import { basename as basename6, join as join14, resolve as resolve15 } from "path";
28825
+ import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
28826
+ import { basename as basename6, join as join15, resolve as resolve15 } from "path";
28654
28827
  function isObject(value) {
28655
28828
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
28656
28829
  }
@@ -28909,10 +29082,10 @@ function verifyExtensionSignature(input) {
28909
29082
  }
28910
29083
  function inspectExtensionSource(source2) {
28911
29084
  const resolved = resolve15(source2);
28912
- if (!existsSync16(resolved))
29085
+ if (!existsSync17(resolved))
28913
29086
  throw new Error(`extension source not found: ${source2}`);
28914
29087
  const stat = statSync7(resolved);
28915
- const manifestPath = stat.isDirectory() ? [join14(resolved, "todos.extension.json"), join14(resolved, "extension.json")].find(existsSync16) : resolved;
29088
+ const manifestPath = stat.isDirectory() ? [join15(resolved, "todos.extension.json"), join15(resolved, "extension.json")].find(existsSync17) : resolved;
28916
29089
  if (!manifestPath)
28917
29090
  throw new Error(`extension directory ${source2} is missing todos.extension.json`);
28918
29091
  const raw = readFileSync9(manifestPath);
@@ -29008,20 +29181,20 @@ function projectExtensionSources(projectPath) {
29008
29181
  return [];
29009
29182
  const root = resolve15(projectPath);
29010
29183
  const candidates = [
29011
- join14(root, "todos.extension.json"),
29012
- join14(root, ".todos", "todos.extension.json")
29184
+ join15(root, "todos.extension.json"),
29185
+ join15(root, ".todos", "todos.extension.json")
29013
29186
  ];
29014
- const extensionDir = join14(root, ".todos", "extensions");
29015
- if (existsSync16(extensionDir)) {
29187
+ const extensionDir = join15(root, ".todos", "extensions");
29188
+ if (existsSync17(extensionDir)) {
29016
29189
  for (const entry of readdirSync3(extensionDir)) {
29017
29190
  if (entry.startsWith("."))
29018
29191
  continue;
29019
- const full = join14(extensionDir, entry);
29192
+ const full = join15(extensionDir, entry);
29020
29193
  if (statSync7(full).isDirectory() || entry.endsWith(".json"))
29021
29194
  candidates.push(full);
29022
29195
  }
29023
29196
  }
29024
- return candidates.filter(existsSync16);
29197
+ return candidates.filter(existsSync17);
29025
29198
  }
29026
29199
  function discoverLocalExtensions(options = {}) {
29027
29200
  const config = loadConfig();
@@ -31109,6 +31282,10 @@ class PostgresJsonRecordStore {
31109
31282
  conds.push(`payload->>'assigned_to' = ${p(filter.assigned_to)}`);
31110
31283
  if (filter.agent_id !== undefined)
31111
31284
  conds.push(`payload->>'agent_id' = ${p(filter.agent_id)}`);
31285
+ if (filter.created_by !== undefined)
31286
+ conds.push(`LOWER(payload->>'created_by') = LOWER(${p(filter.created_by)})`);
31287
+ if (filter.not_created_by !== undefined)
31288
+ conds.push(`(payload->>'created_by' IS NULL OR LOWER(payload->>'created_by') <> LOWER(${p(filter.not_created_by)}))`);
31112
31289
  if (filter.session_id !== undefined)
31113
31290
  conds.push(`payload->>'session_id' = ${p(filter.session_id)}`);
31114
31291
  if (filter.tags?.length) {
@@ -31622,7 +31799,7 @@ async function createTask2(input, store, context) {
31622
31799
  description: input.description ?? null,
31623
31800
  status: input.status ?? "pending",
31624
31801
  priority: input.priority ?? "medium",
31625
- agent_id: input.agent_id ?? null,
31802
+ agent_id: input.agent_id ?? context?.agentId ?? null,
31626
31803
  assigned_to: input.assigned_to ?? null,
31627
31804
  session_id: input.session_id ?? context?.sessionId ?? null,
31628
31805
  working_dir: input.working_dir ?? null,
@@ -31647,7 +31824,8 @@ async function createTask2(input, store, context) {
31647
31824
  confidence: input.confidence ?? null,
31648
31825
  reason: input.reason ?? null,
31649
31826
  spawned_from_session: input.spawned_from_session ?? null,
31650
- assigned_by: input.assigned_by ?? null,
31827
+ assigned_by: input.assigned_by ?? input.agent_id ?? context?.agentId ?? null,
31828
+ created_by: input.created_by ?? input.agent_id ?? context?.agentId ?? null,
31651
31829
  assigned_from_project: input.assigned_from_project ?? null,
31652
31830
  task_type: input.task_type ?? null,
31653
31831
  cost_tokens: 0,
@@ -31684,7 +31862,8 @@ async function updateTask2(id, input, store) {
31684
31862
  tags: input.tags ?? existing.tags,
31685
31863
  metadata: input.metadata ?? existing.metadata,
31686
31864
  requires_approval: input.requires_approval ?? existing.requires_approval,
31687
- task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id
31865
+ task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
31866
+ created_by: existing.created_by
31688
31867
  };
31689
31868
  await store.upsert("tasks", task);
31690
31869
  return task;
@@ -33412,8 +33591,8 @@ var exports_doctor = {};
33412
33591
  __export(exports_doctor, {
33413
33592
  runTodosDoctor: () => runTodosDoctor
33414
33593
  });
33415
- import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync9, statSync as statSync8 } from "fs";
33416
- import { basename as basename7, dirname as dirname8, join as join15 } from "path";
33594
+ import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync18, mkdirSync as mkdirSync9, statSync as statSync8 } from "fs";
33595
+ import { basename as basename7, dirname as dirname8, join as join16 } from "path";
33417
33596
  function tableExists3(db, table) {
33418
33597
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
33419
33598
  }
@@ -33507,7 +33686,7 @@ function findMissingProjectRoots(db) {
33507
33686
  continue;
33508
33687
  if (!row.path.startsWith("/"))
33509
33688
  continue;
33510
- if (!existsSync17(row.path))
33689
+ if (!existsSync18(row.path))
33511
33690
  missing++;
33512
33691
  }
33513
33692
  return missing;
@@ -33567,16 +33746,16 @@ function databasePermissionsAreUnsafe(dbPath) {
33567
33746
  function createBackup(dbPath) {
33568
33747
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
33569
33748
  return;
33570
- if (!existsSync17(dbPath))
33749
+ if (!existsSync18(dbPath))
33571
33750
  return;
33572
33751
  const stamp = now().replace(/[:.]/g, "-");
33573
- const backupDir = join15(dirname8(dbPath), `${basename7(dbPath)}.backup-${stamp}`);
33752
+ const backupDir = join16(dirname8(dbPath), `${basename7(dbPath)}.backup-${stamp}`);
33574
33753
  const files = [];
33575
33754
  mkdirSync9(backupDir, { recursive: true });
33576
33755
  for (const source2 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
33577
- if (!existsSync17(source2))
33756
+ if (!existsSync18(source2))
33578
33757
  continue;
33579
- const target = join15(backupDir, basename7(source2));
33758
+ const target = join16(backupDir, basename7(source2));
33580
33759
  copyFileSync2(source2, target);
33581
33760
  files.push(target);
33582
33761
  }
@@ -33835,7 +34014,7 @@ var init_doctor = __esm(() => {
33835
34014
  });
33836
34015
 
33837
34016
  // src/server/routes.ts
33838
- import { join as join16, resolve as resolve17, sep as sep4 } from "path";
34017
+ import { join as join17, resolve as resolve17, sep as sep4 } from "path";
33839
34018
  function parseFieldsParam(url) {
33840
34019
  const fieldsParam = url.searchParams.get("fields");
33841
34020
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -34016,11 +34195,15 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
34016
34195
  const body = await req.json();
34017
34196
  if (!body.title)
34018
34197
  return json2({ error: "Missing 'title'" }, 400);
34198
+ const createdBy = body.created_by ?? body.agent_id ?? "dashboard";
34019
34199
  const task = createTask({
34020
34200
  title: body.title,
34021
34201
  description: body.description,
34022
34202
  priority: body.priority,
34023
- project_id: body.project_id
34203
+ project_id: body.project_id,
34204
+ agent_id: body.agent_id ?? createdBy,
34205
+ created_by: createdBy,
34206
+ ...body.assigned_to ? { assigned_to: body.assigned_to } : {}
34024
34207
  });
34025
34208
  ctx.broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id, project_id: task.project_id });
34026
34209
  return json2(taskToSummary2(task), 201);
@@ -34626,7 +34809,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
34626
34809
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
34627
34810
  return null;
34628
34811
  if (path !== "/") {
34629
- const filePath = join16(ctx.dashboardDir, path);
34812
+ const filePath = join17(ctx.dashboardDir, path);
34630
34813
  const resolvedFile = resolve17(filePath);
34631
34814
  const resolvedBase = resolve17(ctx.dashboardDir);
34632
34815
  if (!resolvedFile.startsWith(resolvedBase + sep4) && resolvedFile !== resolvedBase) {
@@ -34636,7 +34819,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
34636
34819
  if (res2)
34637
34820
  return res2;
34638
34821
  }
34639
- const indexPath = join16(ctx.dashboardDir, "index.html");
34822
+ const indexPath = join17(ctx.dashboardDir, "index.html");
34640
34823
  const res = serveStaticFile2(indexPath);
34641
34824
  if (res)
34642
34825
  return res;
@@ -35124,7 +35307,11 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
35124
35307
  sync: true
35125
35308
  },
35126
35309
  tasks: {
35127
- create: (input) => createTask(input, database()),
35310
+ create: (input, context) => createTask({
35311
+ ...input,
35312
+ agent_id: input.agent_id ?? context?.agentId,
35313
+ created_by: input.created_by ?? input.agent_id ?? context?.agentId
35314
+ }, database()),
35128
35315
  get: (id) => getTask(id, database()),
35129
35316
  resolveRef: (ref) => resolveTaskRefLocal(database(), ref),
35130
35317
  list: (filter = {}) => listTasksMaybeSearch(filter, database()),
@@ -37740,6 +37927,8 @@ async function handleV1Request(req, url, dependencies = {}) {
37740
37927
  ...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
37741
37928
  ...url.searchParams.get("assigned_to") ? { assigned_to: url.searchParams.get("assigned_to") } : {},
37742
37929
  ...url.searchParams.get("agent_id") ? { agent_id: url.searchParams.get("agent_id") } : {},
37930
+ ...url.searchParams.get("created_by") ? { created_by: url.searchParams.get("created_by") } : {},
37931
+ ...url.searchParams.get("not_created_by") ? { not_created_by: url.searchParams.get("not_created_by") } : {},
37743
37932
  ...url.searchParams.get("tags") ? {
37744
37933
  tags: url.searchParams.get("tags").split(",").map((tag) => tag.trim()).filter(Boolean)
37745
37934
  } : {},
@@ -42985,6 +43174,8 @@ function registerTaskCrudTools(server, ctx) {
42985
43174
  project_id: exports_external.string().optional().describe("Project ID"),
42986
43175
  task_list_id: exports_external.string().optional().describe("Task list ID"),
42987
43176
  assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
43177
+ created_by: exports_external.string().optional().describe("Agent who FILED this task. Defaults to the ambient agent identity (todos init / TODOS_AGENT_ID)."),
43178
+ unassigned: exports_external.boolean().optional().describe("Deliberately file with no assignee. Without it, the task defaults to the filer."),
42988
43179
  depends_on: exports_external.array(exports_external.string()).optional().describe("Array of task IDs this task depends on"),
42989
43180
  short_id: exports_external.string().nullable().optional().describe("Short ID (auto-generated if not provided, disabled if null)"),
42990
43181
  tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
@@ -42995,12 +43186,19 @@ function registerTaskCrudTools(server, ctx) {
42995
43186
  retry_count: exports_external.number().optional().describe("Max retry count for agent failures")
42996
43187
  }, async (params) => {
42997
43188
  try {
42998
- const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, ...rest } = params;
43189
+ const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, created_by, unassigned, ...rest } = params;
43190
+ const creator = resolveCreatorIdentity(created_by);
43191
+ const router = resolveWritableIdentity(created_by);
43192
+ const assignee = assigned_to || (unassigned ? undefined : router.agent_id || undefined);
42999
43193
  const cloud = getTodosCloudClient();
43000
43194
  if (cloud) {
43001
43195
  const payload = { ...rest };
43002
- if (assigned_to)
43003
- payload.assigned_to = assigned_to;
43196
+ if (creator.agent_id)
43197
+ payload.created_by = creator.agent_id;
43198
+ if (router.agent_id)
43199
+ payload.agent_id = payload.agent_id ?? router.agent_id;
43200
+ if (assignee)
43201
+ payload.assigned_to = assignee;
43004
43202
  if (project_id)
43005
43203
  payload.project_id = project_id;
43006
43204
  if (task_list_id)
@@ -43021,8 +43219,12 @@ function registerTaskCrudTools(server, ctx) {
43021
43219
  return { content: [{ type: "text", text: mutationTaskResponse(created) }] };
43022
43220
  }
43023
43221
  const resolved = { ...rest };
43024
- if (assigned_to)
43025
- resolved.assigned_to = resolveAssignee(assigned_to);
43222
+ if (creator.agent_id)
43223
+ resolved.created_by = creator.agent_id;
43224
+ if (router.agent_id)
43225
+ resolved.agent_id = resolved.agent_id ?? router.agent_id;
43226
+ if (assignee)
43227
+ resolved.assigned_to = resolveAssignee(assignee);
43026
43228
  if (project_id)
43027
43229
  resolved.project_id = resolveId(project_id, "projects");
43028
43230
  if (task_list_id)
@@ -43108,6 +43310,8 @@ function registerTaskCrudTools(server, ctx) {
43108
43310
  project_id: exports_external.string().optional().describe("Filter by project"),
43109
43311
  task_list_id: exports_external.string().optional().describe("Filter by task list"),
43110
43312
  assigned_to: exports_external.string().optional().describe("Filter by assignee (agent ID or name, empty string = unassigned)"),
43313
+ created_by: exports_external.string().optional().describe("Filter by the agent who FILED the task"),
43314
+ not_created_by: exports_external.string().optional().describe('Exclude tasks filed by this agent. With assigned_to=<me> this is the "my inbox, minus my own filings" query.'),
43111
43315
  tags: exports_external.array(exports_external.string()).optional().describe("Filter by tags (AND logic)"),
43112
43316
  created_after: exports_external.string().optional().describe("ISO date \u2014 tasks created after this date"),
43113
43317
  created_before: exports_external.string().optional().describe("ISO date \u2014 tasks created before this date"),
@@ -43300,6 +43504,7 @@ var init_task_crud2 = __esm(() => {
43300
43504
  init_tasks();
43301
43505
  init_types();
43302
43506
  init_token_utils();
43507
+ init_creator_identity();
43303
43508
  init_cloud_router();
43304
43509
  });
43305
43510
 
@@ -43496,8 +43701,8 @@ var exports_mention_resolver = {};
43496
43701
  __export(exports_mention_resolver, {
43497
43702
  resolveMentions: () => resolveMentions
43498
43703
  });
43499
- import { existsSync as existsSync18, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync9 } from "fs";
43500
- import { basename as basename8, isAbsolute, join as join17, relative as relative5, resolve as resolve18, sep as sep5 } from "path";
43704
+ import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync9 } from "fs";
43705
+ import { basename as basename8, isAbsolute, join as join18, relative as relative5, resolve as resolve18, sep as sep5 } from "path";
43501
43706
  function blankResolution(parsed) {
43502
43707
  return {
43503
43708
  input: parsed.input,
@@ -43595,7 +43800,7 @@ function resolveFile(parsed, workspace) {
43595
43800
  return resolution;
43596
43801
  }
43597
43802
  resolution.path = relPath;
43598
- if (!existsSync18(absolutePath)) {
43803
+ if (!existsSync19(absolutePath)) {
43599
43804
  resolution.warnings.push("file does not exist in the local workspace");
43600
43805
  return resolution;
43601
43806
  }
@@ -43628,7 +43833,7 @@ function walkSourceFiles(root, current = root, files = []) {
43628
43833
  if (SKIP_DIRS2.has(entry2.name))
43629
43834
  continue;
43630
43835
  }
43631
- const absolutePath = join17(current, entry2.name);
43836
+ const absolutePath = join18(current, entry2.name);
43632
43837
  if (entry2.isDirectory()) {
43633
43838
  if (!SKIP_DIRS2.has(entry2.name))
43634
43839
  walkSourceFiles(root, absolutePath, files);
@@ -46395,7 +46600,7 @@ __export(exports_release_compatibility, {
46395
46600
  LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
46396
46601
  });
46397
46602
  import { readFileSync as readFileSync11 } from "fs";
46398
- import { join as join18, resolve as resolve19 } from "path";
46603
+ import { join as join19, resolve as resolve19 } from "path";
46399
46604
  import { Database as Database3 } from "bun:sqlite";
46400
46605
  function pass(id, message, details) {
46401
46606
  return { id, status: "passed", message, details };
@@ -46407,7 +46612,7 @@ function warn(id, message, details) {
46407
46612
  return { id, status: "warning", message, details };
46408
46613
  }
46409
46614
  function readPackageJson2(root) {
46410
- return JSON.parse(readFileSync11(join18(root, "package.json"), "utf8"));
46615
+ return JSON.parse(readFileSync11(join19(root, "package.json"), "utf8"));
46411
46616
  }
46412
46617
  function sortedKeys(value) {
46413
46618
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -52350,7 +52555,7 @@ __export(exports_verification_providers, {
52350
52555
  getVerificationRecord: () => getVerificationRecord,
52351
52556
  discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
52352
52557
  });
52353
- import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
52558
+ import { existsSync as existsSync20, readFileSync as readFileSync12 } from "fs";
52354
52559
  function normalizeName6(name) {
52355
52560
  const normalized = name.trim().toLowerCase();
52356
52561
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -52502,7 +52707,7 @@ Timed out after ${provider.timeout_ms}ms`);
52502
52707
  };
52503
52708
  }
52504
52709
  function runCiLogProvider(input) {
52505
- const text = input.log_text ?? (input.log_path && existsSync19(input.log_path) ? readFileSync12(input.log_path, "utf-8") : "");
52710
+ const text = input.log_text ?? (input.log_path && existsSync20(input.log_path) ? readFileSync12(input.log_path, "utf-8") : "");
52506
52711
  return {
52507
52712
  status: classifyLog(text),
52508
52713
  attempts: 1,
@@ -52514,7 +52719,7 @@ function runBrowserProvider(input) {
52514
52719
  if (!input.artifact_path) {
52515
52720
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
52516
52721
  }
52517
- if (!existsSync19(input.artifact_path)) {
52722
+ if (!existsSync20(input.artifact_path)) {
52518
52723
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
52519
52724
  }
52520
52725
  return {
@@ -55103,7 +55308,7 @@ __export(exports_onboarding_fixtures, {
55103
55308
  TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION: () => TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION
55104
55309
  });
55105
55310
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
55106
- import { join as join19 } from "path";
55311
+ import { join as join20 } from "path";
55107
55312
  function emptyData() {
55108
55313
  return {
55109
55314
  projects: [],
@@ -55171,6 +55376,7 @@ function task(input) {
55171
55376
  reason: "Bundled deterministic onboarding fixture",
55172
55377
  spawned_from_session: null,
55173
55378
  assigned_by: null,
55379
+ created_by: null,
55174
55380
  assigned_from_project: null,
55175
55381
  task_type: "onboarding",
55176
55382
  cost_tokens: 0,
@@ -55437,7 +55643,7 @@ function writeOnboardingFixtureFiles(directory) {
55437
55643
  mkdirSync11(directory, { recursive: true });
55438
55644
  const files = [];
55439
55645
  for (const fixture of allFixtures()) {
55440
- const path = join19(directory, `${fixture.summary.name}.bridge.json`);
55646
+ const path = join20(directory, `${fixture.summary.name}.bridge.json`);
55441
55647
  writeFileSync9(path, `${JSON.stringify(fixture.bundle, null, 2)}
55442
55648
  `, "utf-8");
55443
55649
  files.push(path);
@@ -61649,16 +61855,16 @@ __export(exports_environment_snapshots, {
61649
61855
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
61650
61856
  });
61651
61857
  import { createHash as createHash13 } from "crypto";
61652
- import { existsSync as existsSync20, readFileSync as readFileSync15, statSync as statSync10 } from "fs";
61858
+ import { existsSync as existsSync21, readFileSync as readFileSync15, statSync as statSync10 } from "fs";
61653
61859
  import { hostname as hostname2, platform, arch } from "os";
61654
- import { dirname as dirname10, join as join20, resolve as resolve21 } from "path";
61860
+ import { dirname as dirname10, join as join21, resolve as resolve21 } from "path";
61655
61861
  import { tmpdir as tmpdir4 } from "os";
61656
61862
  function sha2567(value) {
61657
61863
  return createHash13("sha256").update(value).digest("hex");
61658
61864
  }
61659
61865
  function fileRecord(root, relativePath) {
61660
- const path = join20(root, relativePath);
61661
- if (!existsSync20(path))
61866
+ const path = join21(root, relativePath);
61867
+ if (!existsSync21(path))
61662
61868
  return null;
61663
61869
  const stat = statSync10(path);
61664
61870
  if (!stat.isFile())
@@ -61670,7 +61876,7 @@ function manifestRecord(root, relativePath) {
61670
61876
  const base = fileRecord(root, relativePath);
61671
61877
  if (!base)
61672
61878
  return null;
61673
- const parsed = readJsonFile(join20(root, relativePath));
61879
+ const parsed = readJsonFile(join21(root, relativePath));
61674
61880
  if (!parsed)
61675
61881
  return { ...base, redacted: {} };
61676
61882
  const redacted = redactValue({
@@ -61765,8 +61971,8 @@ function commandEnv(env, includeValues) {
61765
61971
  function defaultSnapshotDir() {
61766
61972
  const dbPath = getDatabasePath();
61767
61973
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
61768
- return join20(tmpdir4(), "hasna-todos", "environment-snapshots");
61769
- return join20(dirname10(resolve21(dbPath)), "environment-snapshots");
61974
+ return join21(tmpdir4(), "hasna-todos", "environment-snapshots");
61975
+ return join21(dirname10(resolve21(dbPath)), "environment-snapshots");
61770
61976
  }
61771
61977
  function snapshotWithId(snapshot) {
61772
61978
  const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
@@ -61813,7 +62019,7 @@ function captureEnvironmentSnapshot(input = {}) {
61813
62019
  });
61814
62020
  }
61815
62021
  function writeEnvironmentSnapshot(snapshot, outputPath) {
61816
- const path = outputPath ? resolve21(outputPath) : join20(defaultSnapshotDir(), `${snapshot.id}.json`);
62022
+ const path = outputPath ? resolve21(outputPath) : join21(defaultSnapshotDir(), `${snapshot.id}.json`);
61817
62023
  ensureDir(dirname10(path));
61818
62024
  writeJsonFile(path, snapshot);
61819
62025
  return path;
@@ -62621,27 +62827,27 @@ __export(exports_serve, {
62621
62827
  SECURITY_HEADERS: () => SECURITY_HEADERS,
62622
62828
  MIME_TYPES: () => MIME_TYPES
62623
62829
  });
62624
- import { existsSync as existsSync21 } from "fs";
62625
- import { join as join21, dirname as dirname11, extname } from "path";
62830
+ import { existsSync as existsSync22 } from "fs";
62831
+ import { join as join22, dirname as dirname11, extname } from "path";
62626
62832
  import { fileURLToPath } from "url";
62627
62833
  function resolveDashboardDir() {
62628
62834
  const candidates = [];
62629
62835
  try {
62630
62836
  const scriptDir = dirname11(fileURLToPath(import.meta.url));
62631
- candidates.push(join21(scriptDir, "..", "dashboard", "dist"));
62632
- candidates.push(join21(scriptDir, "..", "..", "dashboard", "dist"));
62837
+ candidates.push(join22(scriptDir, "..", "dashboard", "dist"));
62838
+ candidates.push(join22(scriptDir, "..", "..", "dashboard", "dist"));
62633
62839
  } catch {}
62634
62840
  if (process.argv[1]) {
62635
62841
  const mainDir = dirname11(process.argv[1]);
62636
- candidates.push(join21(mainDir, "..", "dashboard", "dist"));
62637
- candidates.push(join21(mainDir, "..", "..", "dashboard", "dist"));
62842
+ candidates.push(join22(mainDir, "..", "dashboard", "dist"));
62843
+ candidates.push(join22(mainDir, "..", "..", "dashboard", "dist"));
62638
62844
  }
62639
- candidates.push(join21(process.cwd(), "dashboard", "dist"));
62845
+ candidates.push(join22(process.cwd(), "dashboard", "dist"));
62640
62846
  for (const candidate of candidates) {
62641
- if (existsSync21(candidate))
62847
+ if (existsSync22(candidate))
62642
62848
  return candidate;
62643
62849
  }
62644
- return join21(process.cwd(), "dashboard", "dist");
62850
+ return join22(process.cwd(), "dashboard", "dist");
62645
62851
  }
62646
62852
  function getProvidedApiKey(req) {
62647
62853
  const headerKey = req.headers.get("x-api-key");
@@ -62713,7 +62919,7 @@ function json(data, status = 200, headers) {
62713
62919
  });
62714
62920
  }
62715
62921
  function serveStaticFile(filePath) {
62716
- if (!existsSync21(filePath))
62922
+ if (!existsSync22(filePath))
62717
62923
  return null;
62718
62924
  const ext = extname(filePath);
62719
62925
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -62814,7 +63020,7 @@ data: ${data}
62814
63020
  filteredSseClients.delete(client);
62815
63021
  }
62816
63022
  const dashboardDir = resolveDashboardDir();
62817
- const dashboardExists = existsSync21(dashboardDir);
63023
+ const dashboardExists = existsSync22(dashboardDir);
62818
63024
  if (!dashboardExists) {
62819
63025
  console.error(`
62820
63026
  Dashboard not found at: ${dashboardDir}`);
@@ -64687,12 +64893,12 @@ __export(exports_config_serve_commands, {
64687
64893
  registerConfigServeCommands: () => registerConfigServeCommands
64688
64894
  });
64689
64895
  import chalk6 from "chalk";
64690
- import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
64691
- import { dirname as dirname12, join as join22 } from "path";
64896
+ import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
64897
+ import { dirname as dirname12, join as join23 } from "path";
64692
64898
  function registerConfigServeCommands(program2) {
64693
64899
  program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
64694
64900
  const globalOpts = program2.opts();
64695
- const configPath = join22(getTodosGlobalDir(), "config.json");
64901
+ const configPath = join23(getTodosGlobalDir(), "config.json");
64696
64902
  if (opts.get) {
64697
64903
  const config2 = loadConfig();
64698
64904
  const keys = opts.get.split(".");
@@ -64729,7 +64935,7 @@ function registerConfigServeCommands(program2) {
64729
64935
  }
64730
64936
  obj[keys[keys.length - 1]] = parsedValue;
64731
64937
  const dir = dirname12(configPath);
64732
- if (!existsSync22(dir))
64938
+ if (!existsSync23(dir))
64733
64939
  mkdirSync12(dir, { recursive: true });
64734
64940
  writeFileSync10(configPath, JSON.stringify(config2, null, 2));
64735
64941
  if (globalOpts.json) {
@@ -65909,8 +66115,8 @@ __export(exports_routing_doctor, {
65909
66115
  classifyTaskRouting: () => classifyTaskRouting,
65910
66116
  TODOS_ROUTING_DOCTOR_SCHEMA_VERSION: () => TODOS_ROUTING_DOCTOR_SCHEMA_VERSION
65911
66117
  });
65912
- import { copyFileSync as copyFileSync3, existsSync as existsSync23, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
65913
- import { basename as basename9, dirname as dirname13, join as join23 } from "path";
66118
+ import { copyFileSync as copyFileSync3, existsSync as existsSync24, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
66119
+ import { basename as basename9, dirname as dirname13, join as join24 } from "path";
65914
66120
  function normalizePath5(path) {
65915
66121
  if (!path)
65916
66122
  return null;
@@ -66212,16 +66418,16 @@ function evaluateTaskRouting(ctx) {
66212
66418
  function createBackup2(dbPath, generatedAt) {
66213
66419
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
66214
66420
  return;
66215
- if (!existsSync23(dbPath))
66421
+ if (!existsSync24(dbPath))
66216
66422
  return;
66217
66423
  const stamp = generatedAt.replace(/[:.]/g, "-");
66218
- const backupDir = join23(dirname13(dbPath), `${basename9(dbPath)}.routing-doctor-backup-${stamp}`);
66424
+ const backupDir = join24(dirname13(dbPath), `${basename9(dbPath)}.routing-doctor-backup-${stamp}`);
66219
66425
  const files = [];
66220
66426
  mkdirSync13(backupDir, { recursive: true });
66221
66427
  for (const source3 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
66222
- if (!existsSync23(source3))
66428
+ if (!existsSync24(source3))
66223
66429
  continue;
66224
- const target = join23(backupDir, basename9(source3));
66430
+ const target = join24(backupDir, basename9(source3));
66225
66431
  copyFileSync3(source3, target);
66226
66432
  files.push(target);
66227
66433
  }
@@ -66325,7 +66531,7 @@ function runRoutingDoctor(options = {}) {
66325
66531
  }
66326
66532
  const applied = repairs.filter((r) => r.applied);
66327
66533
  if (applied.length > 0) {
66328
- const undoPath = options.undoRecordPath ?? join23(process.cwd(), `todos-routing-doctor-undo-${generatedAt.replace(/[:.]/g, "-")}.json`);
66534
+ const undoPath = options.undoRecordPath ?? join24(process.cwd(), `todos-routing-doctor-undo-${generatedAt.replace(/[:.]/g, "-")}.json`);
66329
66535
  const undoRecord = {
66330
66536
  schema_version: TODOS_ROUTING_DOCTOR_SCHEMA_VERSION,
66331
66537
  purpose: "Undo record for routing-doctor --apply. Restore each field with the prior value below.",
@@ -66424,8 +66630,8 @@ __export(exports_task_route_sources, {
66424
66630
  });
66425
66631
  import { Database as Database4 } from "bun:sqlite";
66426
66632
  import { createHash as createHash14 } from "crypto";
66427
- import { existsSync as existsSync24, readdirSync as readdirSync5, statSync as statSync11 } from "fs";
66428
- import { basename as basename10, dirname as dirname14, join as join24, resolve as resolve22 } from "path";
66633
+ import { existsSync as existsSync25, readdirSync as readdirSync5, statSync as statSync11 } from "fs";
66634
+ import { basename as basename10, dirname as dirname14, join as join25, resolve as resolve22 } from "path";
66429
66635
  function normalizePath6(input) {
66430
66636
  return resolve22(input);
66431
66637
  }
@@ -66489,8 +66695,8 @@ function discoverStoresUnderRoot(sourceRoot) {
66489
66695
  const rootPath = normalizePath6(sourceRoot);
66490
66696
  const errors2 = [];
66491
66697
  const stores = [];
66492
- if (!existsSync24(rootPath)) {
66493
- const ref = createStoreRef(join24(rootPath, TODO_STORE_RELATIVE_PATH));
66698
+ if (!existsSync25(rootPath)) {
66699
+ const ref = createStoreRef(join25(rootPath, TODO_STORE_RELATIVE_PATH));
66494
66700
  errors2.push({
66495
66701
  ...ref,
66496
66702
  code: "SOURCE_ROOT_MISSING",
@@ -66502,7 +66708,7 @@ function discoverStoresUnderRoot(sourceRoot) {
66502
66708
  try {
66503
66709
  rootStat = statSync11(rootPath);
66504
66710
  } catch (error2) {
66505
- const ref = createStoreRef(join24(rootPath, TODO_STORE_RELATIVE_PATH));
66711
+ const ref = createStoreRef(join25(rootPath, TODO_STORE_RELATIVE_PATH));
66506
66712
  errors2.push({
66507
66713
  ...ref,
66508
66714
  code: "SOURCE_ROOT_UNREADABLE",
@@ -66515,8 +66721,8 @@ function discoverStoresUnderRoot(sourceRoot) {
66515
66721
  return { stores, errors: errors2 };
66516
66722
  }
66517
66723
  function scanDirectory(dir, depth) {
66518
- const candidate = join24(dir, TODO_STORE_RELATIVE_PATH);
66519
- if (existsSync24(candidate)) {
66724
+ const candidate = join25(dir, TODO_STORE_RELATIVE_PATH);
66725
+ if (existsSync25(candidate)) {
66520
66726
  stores.push(createStoreRef(candidate));
66521
66727
  }
66522
66728
  if (depth >= ROOT_SCAN_MAX_DEPTH)
@@ -66536,7 +66742,7 @@ function discoverStoresUnderRoot(sourceRoot) {
66536
66742
  for (const entry2 of entries) {
66537
66743
  if (!entry2.isDirectory() || SKIPPED_SCAN_DIRS.has(entry2.name))
66538
66744
  continue;
66539
- scanDirectory(join24(dir, entry2.name), depth + 1);
66745
+ scanDirectory(join25(dir, entry2.name), depth + 1);
66540
66746
  }
66541
66747
  }
66542
66748
  scanDirectory(rootPath, 0);
@@ -66562,7 +66768,7 @@ function collectStoreRefs(input) {
66562
66768
  };
66563
66769
  }
66564
66770
  function openReadonlyStore(ref) {
66565
- if (!existsSync24(ref.source_db_path)) {
66771
+ if (!existsSync25(ref.source_db_path)) {
66566
66772
  throw Object.assign(new Error(`Store does not exist: ${ref.source_db_path}`), { code: "STORE_MISSING" });
66567
66773
  }
66568
66774
  return new Database4(ref.source_db_path, { readonly: true, create: false });
@@ -66736,7 +66942,7 @@ var init_task_route_sources = __esm(() => {
66736
66942
  init_task_crud();
66737
66943
  init_redaction();
66738
66944
  init_task_routing();
66739
- TODO_STORE_RELATIVE_PATH = join24(".hasna", "todos", "todos.db");
66945
+ TODO_STORE_RELATIVE_PATH = join25(".hasna", "todos", "todos.db");
66740
66946
  SKIPPED_SCAN_DIRS = new Set([
66741
66947
  ".git",
66742
66948
  ".hg",
@@ -68245,9 +68451,9 @@ Findings`));
68245
68451
  const db = getDatabase();
68246
68452
  const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
68247
68453
  const { statSync: statSync12 } = await import("fs");
68248
- const { join: join25 } = await import("path");
68454
+ const { join: join26 } = await import("path");
68249
68455
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
68250
- const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join25(home, ".hasna", "todos", "todos.db");
68456
+ const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join26(home, ".hasna", "todos", "todos.db");
68251
68457
  let size = "unknown";
68252
68458
  try {
68253
68459
  size = `${(statSync12(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
@@ -70161,21 +70367,21 @@ __export(exports_mcp_hooks_commands, {
70161
70367
  });
70162
70368
  import chalk9 from "chalk";
70163
70369
  import { execSync as execSync3 } from "child_process";
70164
- import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, chmodSync as chmodSync3 } from "fs";
70165
- import { dirname as dirname15, join as join25 } from "path";
70370
+ import { existsSync as existsSync26, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, chmodSync as chmodSync3 } from "fs";
70371
+ import { dirname as dirname15, join as join26 } from "path";
70166
70372
  function getMcpBinaryPath() {
70167
70373
  try {
70168
70374
  const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
70169
70375
  if (p)
70170
70376
  return p;
70171
70377
  } catch {}
70172
- const bunBin = join25(HOME2, ".bun", "bin", "todos-mcp");
70173
- if (existsSync25(bunBin))
70378
+ const bunBin = join26(HOME2, ".bun", "bin", "todos-mcp");
70379
+ if (existsSync26(bunBin))
70174
70380
  return bunBin;
70175
70381
  return "todos-mcp";
70176
70382
  }
70177
70383
  function readJsonFile2(path) {
70178
- if (!existsSync25(path))
70384
+ if (!existsSync26(path))
70179
70385
  return {};
70180
70386
  try {
70181
70387
  return JSON.parse(readFileSync18(path, "utf-8"));
@@ -70185,19 +70391,19 @@ function readJsonFile2(path) {
70185
70391
  }
70186
70392
  function writeJsonFile2(path, data) {
70187
70393
  const dir = dirname15(path);
70188
- if (!existsSync25(dir))
70394
+ if (!existsSync26(dir))
70189
70395
  mkdirSync14(dir, { recursive: true });
70190
70396
  writeFileSync13(path, JSON.stringify(data, null, 2) + `
70191
70397
  `);
70192
70398
  }
70193
70399
  function readTomlFile(path) {
70194
- if (!existsSync25(path))
70400
+ if (!existsSync26(path))
70195
70401
  return "";
70196
70402
  return readFileSync18(path, "utf-8");
70197
70403
  }
70198
70404
  function writeTomlFile(path, content) {
70199
70405
  const dir = dirname15(path);
70200
- if (!existsSync25(dir))
70406
+ if (!existsSync26(dir))
70201
70407
  mkdirSync14(dir, { recursive: true });
70202
70408
  writeFileSync13(path, content);
70203
70409
  }
@@ -70262,7 +70468,7 @@ function unregisterClaude(_global) {
70262
70468
  }
70263
70469
  }
70264
70470
  function registerCodex(binPath) {
70265
- const configPath = join25(HOME2, ".codex", "config.toml");
70471
+ const configPath = join26(HOME2, ".codex", "config.toml");
70266
70472
  let content = readTomlFile(configPath);
70267
70473
  content = removeTomlBlock(content, "mcp_servers.todos");
70268
70474
  const block = `
@@ -70276,7 +70482,7 @@ args = ["--stdio"]
70276
70482
  console.log(chalk9.green(`Codex CLI: registered in ${configPath}`));
70277
70483
  }
70278
70484
  function unregisterCodex() {
70279
- const configPath = join25(HOME2, ".codex", "config.toml");
70485
+ const configPath = join26(HOME2, ".codex", "config.toml");
70280
70486
  let content = readTomlFile(configPath);
70281
70487
  if (!content.includes("[mcp_servers.todos]")) {
70282
70488
  console.log(chalk9.dim(`Codex CLI: todos not found in ${configPath}`));
@@ -70288,7 +70494,7 @@ function unregisterCodex() {
70288
70494
  console.log(chalk9.green(`Codex CLI: unregistered from ${configPath}`));
70289
70495
  }
70290
70496
  function registerGemini(binPath) {
70291
- const configPath = join25(HOME2, ".gemini", "settings.json");
70497
+ const configPath = join26(HOME2, ".gemini", "settings.json");
70292
70498
  const config = readJsonFile2(configPath);
70293
70499
  if (!config["mcpServers"]) {
70294
70500
  config["mcpServers"] = {};
@@ -70302,7 +70508,7 @@ function registerGemini(binPath) {
70302
70508
  console.log(chalk9.green(`Gemini CLI: registered in ${configPath}`));
70303
70509
  }
70304
70510
  function unregisterGemini() {
70305
- const configPath = join25(HOME2, ".gemini", "settings.json");
70511
+ const configPath = join26(HOME2, ".gemini", "settings.json");
70306
70512
  const config = readJsonFile2(configPath);
70307
70513
  const servers = config["mcpServers"];
70308
70514
  if (!servers || !("todos" in servers)) {
@@ -70313,8 +70519,37 @@ function unregisterGemini() {
70313
70519
  writeJsonFile2(configPath, config);
70314
70520
  console.log(chalk9.green(`Gemini CLI: unregistered from ${configPath}`));
70315
70521
  }
70522
+ function cursorConfigPath(global) {
70523
+ return global ? join26(HOME2, ".cursor", "mcp.json") : join26(process.cwd(), ".cursor", "mcp.json");
70524
+ }
70525
+ function registerCursor(binPath, global) {
70526
+ const configPath = cursorConfigPath(global);
70527
+ const config = readJsonFile2(configPath);
70528
+ if (!config["mcpServers"]) {
70529
+ config["mcpServers"] = {};
70530
+ }
70531
+ const servers = config["mcpServers"];
70532
+ servers["todos"] = {
70533
+ command: binPath,
70534
+ args: ["--stdio"]
70535
+ };
70536
+ writeJsonFile2(configPath, config);
70537
+ console.log(chalk9.green(`Cursor (${global ? "user" : "project"}): registered in ${configPath}`));
70538
+ }
70539
+ function unregisterCursor(global) {
70540
+ const configPath = cursorConfigPath(global);
70541
+ const config = readJsonFile2(configPath);
70542
+ const servers = config["mcpServers"];
70543
+ if (!servers || !("todos" in servers)) {
70544
+ console.log(chalk9.dim(`Cursor: todos not found in ${configPath}`));
70545
+ return;
70546
+ }
70547
+ delete servers["todos"];
70548
+ writeJsonFile2(configPath, config);
70549
+ console.log(chalk9.green(`Cursor (${global ? "user" : "project"}): unregistered from ${configPath}`));
70550
+ }
70316
70551
  function registerMcp(agent, global) {
70317
- const agents = agent === "all" ? ["claude", "codex", "gemini"] : [agent];
70552
+ const agents = agent === "all" ? ["claude", "codex", "gemini", "cursor"] : [agent];
70318
70553
  const binPath = getMcpBinaryPath();
70319
70554
  for (const a of agents) {
70320
70555
  switch (a) {
@@ -70327,13 +70562,16 @@ function registerMcp(agent, global) {
70327
70562
  case "gemini":
70328
70563
  registerGemini(binPath);
70329
70564
  break;
70565
+ case "cursor":
70566
+ registerCursor(binPath, global);
70567
+ break;
70330
70568
  default:
70331
- console.error(chalk9.red(`Unknown agent: ${a}. Use: claude, codex, gemini, all`));
70569
+ console.error(chalk9.red(`Unknown agent: ${a}. Use: claude, codex, gemini, cursor, all`));
70332
70570
  }
70333
70571
  }
70334
70572
  }
70335
70573
  function unregisterMcp(agent, global) {
70336
- const agents = agent === "all" ? ["claude", "codex", "gemini"] : [agent];
70574
+ const agents = agent === "all" ? ["claude", "codex", "gemini", "cursor"] : [agent];
70337
70575
  for (const a of agents) {
70338
70576
  switch (a) {
70339
70577
  case "claude":
@@ -70345,8 +70583,11 @@ function unregisterMcp(agent, global) {
70345
70583
  case "gemini":
70346
70584
  unregisterGemini();
70347
70585
  break;
70586
+ case "cursor":
70587
+ unregisterCursor(global);
70588
+ break;
70348
70589
  default:
70349
- console.error(chalk9.red(`Unknown agent: ${a}. Use: claude, codex, gemini, all`));
70590
+ console.error(chalk9.red(`Unknown agent: ${a}. Use: claude, codex, gemini, cursor, all`));
70350
70591
  }
70351
70592
  }
70352
70593
  }
@@ -70359,8 +70600,8 @@ function registerMcpHooksCommands(program2) {
70359
70600
  if (p)
70360
70601
  todosBin = p;
70361
70602
  } catch {}
70362
- const hooksDir = join25(process.cwd(), ".claude", "hooks");
70363
- if (!existsSync25(hooksDir))
70603
+ const hooksDir = join26(process.cwd(), ".claude", "hooks");
70604
+ if (!existsSync26(hooksDir))
70364
70605
  mkdirSync14(hooksDir, { recursive: true });
70365
70606
  const hookScript = `#!/usr/bin/env bash
70366
70607
  # Auto-generated by: todos hooks install
@@ -70385,11 +70626,11 @@ esac
70385
70626
 
70386
70627
  exit 0
70387
70628
  `;
70388
- const hookPath = join25(hooksDir, "todos-sync.sh");
70629
+ const hookPath = join26(hooksDir, "todos-sync.sh");
70389
70630
  writeFileSync13(hookPath, hookScript);
70390
70631
  execSync3(`chmod +x "${hookPath}"`);
70391
70632
  console.log(chalk9.green(`Hook script created: ${hookPath}`));
70392
- const settingsPath = join25(process.cwd(), ".claude", "settings.json");
70633
+ const settingsPath = join26(process.cwd(), ".claude", "settings.json");
70393
70634
  const settings = readJsonFile2(settingsPath);
70394
70635
  if (!settings["hooks"]) {
70395
70636
  settings["hooks"] = {};
@@ -70418,7 +70659,7 @@ exit 0
70418
70659
  console.log(chalk9.green(`Claude Code hooks configured in: ${settingsPath}`));
70419
70660
  console.log(chalk9.dim("Task list ID auto-detected from project."));
70420
70661
  });
70421
- program2.command("mcp").description("Start MCP server (stdio)").option("--register <agent>", "Register MCP server with an agent (claude, codex, gemini, all)").option("--unregister <agent>", "Unregister MCP server from an agent (claude, codex, gemini, all)").option("-g, --global", "Register/unregister globally (user-level) instead of project-level").action(async (opts) => {
70662
+ program2.command("mcp").description("Start MCP server (stdio)").option("--register <agent>", "Register MCP server with an agent (claude, codex, gemini, cursor, all)").option("--unregister <agent>", "Unregister MCP server from an agent (claude, codex, gemini, cursor, all)").option("-g, --global", "Register/unregister globally (user-level) instead of project-level").action(async (opts) => {
70422
70663
  if (opts.register) {
70423
70664
  registerMcp(opts.register, opts.global);
70424
70665
  return;
@@ -71320,7 +71561,7 @@ Artifacts:`));
71320
71561
  const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
71321
71562
  const hookPath = `${gitDir}/hooks/post-commit`;
71322
71563
  const marker = "# todos-auto-link";
71323
- if (existsSync25(hookPath)) {
71564
+ if (existsSync26(hookPath)) {
71324
71565
  const existing = readFileSync18(hookPath, "utf-8");
71325
71566
  if (existing.includes(marker)) {
71326
71567
  console.log(chalk9.yellow("Hook already installed."));
@@ -71347,7 +71588,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
71347
71588
  const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
71348
71589
  const hookPath = `${gitDir}/hooks/post-commit`;
71349
71590
  const marker = "# todos-auto-link";
71350
- if (!existsSync25(hookPath)) {
71591
+ if (!existsSync26(hookPath)) {
71351
71592
  console.log(chalk9.dim("No post-commit hook found."));
71352
71593
  return;
71353
71594
  }
@@ -71533,7 +71774,7 @@ import chalk11 from "chalk";
71533
71774
  import { execSync as execSync4 } from "child_process";
71534
71775
  import { readFileSync as readFileSync19, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
71535
71776
  import { tmpdir as tmpdir5 } from "os";
71536
- import { join as join26 } from "path";
71777
+ import { join as join27 } from "path";
71537
71778
  function getOrCreateLocalMachineName() {
71538
71779
  return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
71539
71780
  }
@@ -71571,7 +71812,7 @@ function remoteTempPath(sshAddress) {
71571
71812
  }
71572
71813
  function readRemoteBridgeBundle(sshAddress) {
71573
71814
  const remotePath = remoteTempPath(sshAddress);
71574
- const localPath = join26(tmpdir5(), `todos-bridge-pull-${uuid()}.json`);
71815
+ const localPath = join27(tmpdir5(), `todos-bridge-pull-${uuid()}.json`);
71575
71816
  try {
71576
71817
  runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
71577
71818
  scpFromRemote(sshAddress, remotePath, localPath);
@@ -71586,7 +71827,7 @@ function readRemoteBridgeBundle(sshAddress) {
71586
71827
  }
71587
71828
  }
71588
71829
  function writeLocalBridgeBundle() {
71589
- const localPath = join26(tmpdir5(), `todos-bridge-push-${uuid()}.json`);
71830
+ const localPath = join27(tmpdir5(), `todos-bridge-push-${uuid()}.json`);
71590
71831
  writeFileSync14(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
71591
71832
  return localPath;
71592
71833
  }
@@ -76161,7 +76402,7 @@ __export(exports_sdk_integration_fixtures, {
76161
76402
  TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT: () => TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT
76162
76403
  });
76163
76404
  import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
76164
- import { join as join27 } from "path";
76405
+ import { join as join28 } from "path";
76165
76406
  function source5(version) {
76166
76407
  return {
76167
76408
  packageName: "@hasna/todos",
@@ -76268,7 +76509,7 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
76268
76509
  ];
76269
76510
  const written = [];
76270
76511
  for (const [name, payload] of files) {
76271
- const file = join27(directory, name);
76512
+ const file = join28(directory, name);
76272
76513
  writeFileSync15(file, `${JSON.stringify(payload, null, 2)}
76273
76514
  `, "utf-8");
76274
76515
  written.push(file);