@hasna/todos 0.13.5 → 0.13.6
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/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/agent-commands.d.ts.map +1 -1
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +400 -176
- package/dist/contracts.js +25 -3
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/index.js +39 -7
- package/dist/lib/creator-identity.d.ts +70 -0
- package/dist/lib/creator-identity.d.ts.map +1 -0
- package/dist/lib/onboarding-fixtures.d.ts.map +1 -1
- package/dist/mcp/index.js +163 -75
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/registry.js +25 -3
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +153 -65
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/local-sqlite.d.ts.map +1 -1
- package/dist/storage.js +37 -6
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
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.
|
|
2126
|
+
version: "0.13.6",
|
|
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);
|
|
@@ -18810,6 +18832,73 @@ var init_tasks = __esm(() => {
|
|
|
18810
18832
|
init_calendar();
|
|
18811
18833
|
});
|
|
18812
18834
|
|
|
18835
|
+
// src/lib/creator-identity.ts
|
|
18836
|
+
import { existsSync as existsSync8, rmSync as rmSync2 } from "fs";
|
|
18837
|
+
import { join as join7 } from "path";
|
|
18838
|
+
function identityFilePath() {
|
|
18839
|
+
return join7(getTodosGlobalDir(), "identity.json");
|
|
18840
|
+
}
|
|
18841
|
+
function readPersistedIdentity() {
|
|
18842
|
+
const path = identityFilePath();
|
|
18843
|
+
if (!existsSync8(path))
|
|
18844
|
+
return null;
|
|
18845
|
+
const parsed = readJsonFile(path);
|
|
18846
|
+
if (!parsed || typeof parsed.agent_id !== "string" || !parsed.agent_id.trim())
|
|
18847
|
+
return null;
|
|
18848
|
+
return parsed;
|
|
18849
|
+
}
|
|
18850
|
+
function detectIdentityCollision(agentId, agentName) {
|
|
18851
|
+
const existing = readPersistedIdentity();
|
|
18852
|
+
if (!existing)
|
|
18853
|
+
return null;
|
|
18854
|
+
const sameId = canonicalAgentRef(existing.agent_id) === canonicalAgentRef(agentId);
|
|
18855
|
+
const sameName = Boolean(agentName) && canonicalAgentRef(existing.agent_name || "") === canonicalAgentRef(agentName);
|
|
18856
|
+
if (sameId || sameName)
|
|
18857
|
+
return null;
|
|
18858
|
+
return { existing };
|
|
18859
|
+
}
|
|
18860
|
+
function persistIdentity(identity) {
|
|
18861
|
+
const record = {
|
|
18862
|
+
agent_id: identity.agent_id,
|
|
18863
|
+
...identity.agent_name ? { agent_name: identity.agent_name } : {},
|
|
18864
|
+
...identity.session_id ? { session_id: identity.session_id } : {},
|
|
18865
|
+
registered_at: new Date().toISOString()
|
|
18866
|
+
};
|
|
18867
|
+
ensureDir(getTodosGlobalDir());
|
|
18868
|
+
writeJsonFile(identityFilePath(), record);
|
|
18869
|
+
return record;
|
|
18870
|
+
}
|
|
18871
|
+
function clearPersistedIdentity() {
|
|
18872
|
+
const path = identityFilePath();
|
|
18873
|
+
if (!existsSync8(path))
|
|
18874
|
+
return false;
|
|
18875
|
+
try {
|
|
18876
|
+
rmSync2(path);
|
|
18877
|
+
return true;
|
|
18878
|
+
} catch {
|
|
18879
|
+
return false;
|
|
18880
|
+
}
|
|
18881
|
+
}
|
|
18882
|
+
function canonicalAgentRef(value) {
|
|
18883
|
+
return value.trim().toLowerCase();
|
|
18884
|
+
}
|
|
18885
|
+
function resolveCreatorIdentity(explicit) {
|
|
18886
|
+
const fromExplicit = explicit?.trim();
|
|
18887
|
+
if (fromExplicit)
|
|
18888
|
+
return { agent_id: canonicalAgentRef(fromExplicit), source: "explicit" };
|
|
18889
|
+
const fromEnv = (process.env["TODOS_AGENT_ID"] || process.env["HASNA_TODOS_AGENT_ID"] || "").trim();
|
|
18890
|
+
if (fromEnv)
|
|
18891
|
+
return { agent_id: canonicalAgentRef(fromEnv), source: "env" };
|
|
18892
|
+
const persisted = readPersistedIdentity();
|
|
18893
|
+
if (persisted) {
|
|
18894
|
+
return { agent_id: canonicalAgentRef(persisted.agent_name || persisted.agent_id), source: "persisted" };
|
|
18895
|
+
}
|
|
18896
|
+
return { agent_id: null, source: "none" };
|
|
18897
|
+
}
|
|
18898
|
+
var init_creator_identity = __esm(() => {
|
|
18899
|
+
init_sync_utils();
|
|
18900
|
+
});
|
|
18901
|
+
|
|
18813
18902
|
// src/cli/output-redaction.ts
|
|
18814
18903
|
function redactBroadTask(task) {
|
|
18815
18904
|
return redactValue(task);
|
|
@@ -18830,7 +18919,7 @@ __export(exports_task_routing, {
|
|
|
18830
18919
|
setTaskWorkflowPointers: () => setTaskWorkflowPointers,
|
|
18831
18920
|
getTaskRouteState: () => getTaskRouteState
|
|
18832
18921
|
});
|
|
18833
|
-
import { existsSync as
|
|
18922
|
+
import { existsSync as existsSync9, statSync as statSync3 } from "fs";
|
|
18834
18923
|
function machineLocalPath(project, db) {
|
|
18835
18924
|
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
18836
18925
|
if (!machineId)
|
|
@@ -18870,7 +18959,7 @@ function routeConcurrencyKey(task, project, taskList, projectPath) {
|
|
|
18870
18959
|
}
|
|
18871
18960
|
function directoryExists(path) {
|
|
18872
18961
|
try {
|
|
18873
|
-
return
|
|
18962
|
+
return existsSync9(path) && statSync3(path).isDirectory();
|
|
18874
18963
|
} catch {
|
|
18875
18964
|
return false;
|
|
18876
18965
|
}
|
|
@@ -19307,10 +19396,15 @@ function computeLocalReparent(current, opts) {
|
|
|
19307
19396
|
return patch;
|
|
19308
19397
|
}
|
|
19309
19398
|
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) => {
|
|
19399
|
+
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
19400
|
const globalOpts = program2.opts();
|
|
19312
19401
|
opts.tags = opts.tags || opts.tag;
|
|
19313
19402
|
opts.list = opts.list || opts.taskList;
|
|
19403
|
+
const creator = resolveCreatorIdentity(opts.createdBy || globalOpts.agent);
|
|
19404
|
+
const assignee = opts.assign || (opts.unassigned ? undefined : creator.agent_id || undefined);
|
|
19405
|
+
if (!assignee && !opts.unassigned) {
|
|
19406
|
+
console.error(chalk2.yellow("Warning: task is ownerless and unattributable \u2014 run `todos init <name>`, or pass --assign <agent> or --unassigned."));
|
|
19407
|
+
}
|
|
19314
19408
|
const cloud = getTodosCloudClient();
|
|
19315
19409
|
if (cloud) {
|
|
19316
19410
|
let task3;
|
|
@@ -19332,10 +19426,11 @@ function registerTaskCommands(program2) {
|
|
|
19332
19426
|
parent_id: opts.parent ? await resolveTaskIdForCommand(opts.parent, cloud) : undefined,
|
|
19333
19427
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
19334
19428
|
plan_id: cloudPlan?.id,
|
|
19335
|
-
assigned_to:
|
|
19429
|
+
assigned_to: assignee,
|
|
19336
19430
|
status: parseStatus(opts.status),
|
|
19337
19431
|
task_list_id: cloudTaskListId,
|
|
19338
|
-
agent_id: globalOpts.agent,
|
|
19432
|
+
agent_id: globalOpts.agent || creator.agent_id || undefined,
|
|
19433
|
+
created_by: creator.agent_id || undefined,
|
|
19339
19434
|
session_id: globalOpts.session,
|
|
19340
19435
|
project_id: cloudProjectId,
|
|
19341
19436
|
estimated_minutes: opts.estimated !== undefined ? parseIntOption(opts.estimated, "--estimated") : undefined,
|
|
@@ -19377,10 +19472,11 @@ function registerTaskCommands(program2) {
|
|
|
19377
19472
|
parent_id: opts.parent ? resolveTaskId(opts.parent) : undefined,
|
|
19378
19473
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
19379
19474
|
plan_id: opts.plan ? resolvePlanId(opts.plan) : undefined,
|
|
19380
|
-
assigned_to:
|
|
19475
|
+
assigned_to: assignee,
|
|
19381
19476
|
status: parseStatus(opts.status),
|
|
19382
19477
|
task_list_id: taskListId,
|
|
19383
|
-
agent_id: globalOpts.agent,
|
|
19478
|
+
agent_id: globalOpts.agent || creator.agent_id || undefined,
|
|
19479
|
+
created_by: creator.agent_id || undefined,
|
|
19384
19480
|
session_id: globalOpts.session,
|
|
19385
19481
|
project_id: projectId,
|
|
19386
19482
|
working_dir: process.cwd(),
|
|
@@ -19534,14 +19630,14 @@ function registerTaskCommands(program2) {
|
|
|
19534
19630
|
console.log(` ${chalk2.dim("Manifest:")} ${state.pointers.latest_manifest_path}`);
|
|
19535
19631
|
}
|
|
19536
19632
|
});
|
|
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) => {
|
|
19633
|
+
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
19634
|
const globalOpts = program2.opts();
|
|
19539
19635
|
opts.tags = opts.tags || opts.tag;
|
|
19540
19636
|
opts.list = opts.list || opts.taskList;
|
|
19541
19637
|
const cloud = getTodosCloudClient();
|
|
19542
19638
|
const cloudProjectRef = globalOpts.project || opts.projectName;
|
|
19543
19639
|
const projectId = cloud && cloudProjectRef ? await cloudResolveProjectRef(cloud, cloudProjectRef) : cloud ? undefined : autoProject(globalOpts);
|
|
19544
|
-
const hasAssignedFilter = Boolean(opts.assigned || opts.agentName);
|
|
19640
|
+
const hasAssignedFilter = Boolean(opts.assigned || opts.agentName || opts.inbox);
|
|
19545
19641
|
const hasExplicitProjectFilter = Boolean(globalOpts.project || opts.projectName);
|
|
19546
19642
|
const allowedSortFields = new Set(["updated", "created", "priority", "status"]);
|
|
19547
19643
|
if (opts.sort && !allowedSortFields.has(opts.sort)) {
|
|
@@ -19574,6 +19670,19 @@ function registerTaskCommands(program2) {
|
|
|
19574
19670
|
filter["priority"] = opts.priority;
|
|
19575
19671
|
if (opts.assigned)
|
|
19576
19672
|
filter["assigned_to"] = opts.assigned;
|
|
19673
|
+
if (opts.createdBy)
|
|
19674
|
+
filter["created_by"] = canonicalAgentRef(opts.createdBy);
|
|
19675
|
+
if (opts.notCreatedBy)
|
|
19676
|
+
filter["not_created_by"] = canonicalAgentRef(opts.notCreatedBy);
|
|
19677
|
+
if (opts.inbox) {
|
|
19678
|
+
const me = resolveCreatorIdentity(program2.opts().agent);
|
|
19679
|
+
if (!me.agent_id) {
|
|
19680
|
+
console.error(chalk2.red("--inbox needs an agent identity. Run `todos init <name>` or pass --agent <id>."));
|
|
19681
|
+
process.exit(1);
|
|
19682
|
+
}
|
|
19683
|
+
filter["assigned_to"] = me.agent_id;
|
|
19684
|
+
filter["not_created_by"] = me.agent_id;
|
|
19685
|
+
}
|
|
19577
19686
|
if (opts.tags)
|
|
19578
19687
|
filter["tags"] = opts.tags.split(",").map((t) => t.trim());
|
|
19579
19688
|
if (opts.projectName && !cloud) {
|
|
@@ -19598,7 +19707,32 @@ function registerTaskCommands(program2) {
|
|
|
19598
19707
|
}
|
|
19599
19708
|
filter["limit"] = parsedLimit;
|
|
19600
19709
|
}
|
|
19601
|
-
|
|
19710
|
+
const creatorFilterActive = Boolean(filter["created_by"] || filter["not_created_by"]);
|
|
19711
|
+
const requestedLimit = filter["limit"];
|
|
19712
|
+
const serverFilter = creatorFilterActive && cloud && requestedLimit !== undefined ? (() => {
|
|
19713
|
+
const { limit: _dropped, ...rest } = filter;
|
|
19714
|
+
return rest;
|
|
19715
|
+
})() : filter;
|
|
19716
|
+
let tasks = cloud ? await cloudListTasks(cloud, serverFilter) : listTasks(serverFilter);
|
|
19717
|
+
if (cloud && creatorFilterActive) {
|
|
19718
|
+
if (tasks.length > 0 && tasks.every((t) => !("created_by" in t))) {
|
|
19719
|
+
console.error(chalk2.yellow(`Warning: this server does not record task authorship, so the creator filter matched nothing to exclude.
|
|
19720
|
+
` + " Results are unfiltered. The API needs upgrading past the release that added created_by."));
|
|
19721
|
+
}
|
|
19722
|
+
const wantCreatedBy = filter["created_by"];
|
|
19723
|
+
const excludeCreatedBy = filter["not_created_by"];
|
|
19724
|
+
tasks = tasks.filter((t) => {
|
|
19725
|
+
const raw = t.created_by ?? null;
|
|
19726
|
+
const author = raw === null ? null : canonicalAgentRef(raw);
|
|
19727
|
+
if (wantCreatedBy && author !== canonicalAgentRef(wantCreatedBy))
|
|
19728
|
+
return false;
|
|
19729
|
+
if (excludeCreatedBy && author !== null && author === canonicalAgentRef(excludeCreatedBy))
|
|
19730
|
+
return false;
|
|
19731
|
+
return true;
|
|
19732
|
+
});
|
|
19733
|
+
if (requestedLimit !== undefined)
|
|
19734
|
+
tasks = tasks.slice(0, requestedLimit);
|
|
19735
|
+
}
|
|
19602
19736
|
if (opts.dueToday) {
|
|
19603
19737
|
const todayEnd = new Date;
|
|
19604
19738
|
todayEnd.setHours(23, 59, 59, 999);
|
|
@@ -20482,14 +20616,15 @@ var init_task_commands = __esm(() => {
|
|
|
20482
20616
|
init_tasks();
|
|
20483
20617
|
init_task_lists();
|
|
20484
20618
|
init_cloud_router();
|
|
20619
|
+
init_creator_identity();
|
|
20485
20620
|
init_helpers();
|
|
20486
20621
|
init_output_redaction();
|
|
20487
20622
|
init_types();
|
|
20488
20623
|
});
|
|
20489
20624
|
|
|
20490
20625
|
// src/lib/plan-artifacts.ts
|
|
20491
|
-
import { existsSync as
|
|
20492
|
-
import { join as
|
|
20626
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
20627
|
+
import { join as join8, resolve as resolve10 } from "path";
|
|
20493
20628
|
function assertSafePathSegment(value, label) {
|
|
20494
20629
|
const trimmed = value.trim();
|
|
20495
20630
|
if (!trimmed || trimmed === "." || trimmed === ".." || trimmed.includes("/") || trimmed.includes("\\")) {
|
|
@@ -20550,7 +20685,7 @@ function resolvePlanArtifactPaths(input) {
|
|
|
20550
20685
|
const project = resolvePlanArtifactProject(input);
|
|
20551
20686
|
const projectId = assertSafePathSegment(project.id, "project id");
|
|
20552
20687
|
const projectRoot = resolve10(project.path);
|
|
20553
|
-
const directory =
|
|
20688
|
+
const directory = join8(projectRoot, ".hasna", "todos", "plans", projectId);
|
|
20554
20689
|
const planId = input.plan_id ? assertSafePathSegment(input.plan_id, "plan id") : null;
|
|
20555
20690
|
const planSlug = input.plan_slug ? assertSafePathSegment(slugify(input.plan_slug), "plan slug") : null;
|
|
20556
20691
|
const fileName = planId ? planSlug ? `${planSlug}--${planId.slice(0, 8)}.md` : `${planId}.md` : null;
|
|
@@ -20558,7 +20693,7 @@ function resolvePlanArtifactPaths(input) {
|
|
|
20558
20693
|
project_id: project.id,
|
|
20559
20694
|
project_root: projectRoot,
|
|
20560
20695
|
directory,
|
|
20561
|
-
file_path: fileName ?
|
|
20696
|
+
file_path: fileName ? join8(directory, fileName) : directory
|
|
20562
20697
|
};
|
|
20563
20698
|
}
|
|
20564
20699
|
function resolvePlanArtifactCandidatePaths(plan, db) {
|
|
@@ -20724,7 +20859,7 @@ function readPlanArtifact(plan, db) {
|
|
|
20724
20859
|
return null;
|
|
20725
20860
|
const d = db || getDatabase();
|
|
20726
20861
|
const paths = resolvePlanArtifactCandidatePaths(plan, d);
|
|
20727
|
-
const path =
|
|
20862
|
+
const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
20728
20863
|
if (!path)
|
|
20729
20864
|
return null;
|
|
20730
20865
|
const markdown = readFileSync4(path, "utf8");
|
|
@@ -20739,7 +20874,7 @@ function inspectPlanArtifact(plan, db) {
|
|
|
20739
20874
|
return null;
|
|
20740
20875
|
const d = db || getDatabase();
|
|
20741
20876
|
const paths = resolvePlanArtifactCandidatePaths(plan, d);
|
|
20742
|
-
const path =
|
|
20877
|
+
const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
20743
20878
|
if (!path) {
|
|
20744
20879
|
return {
|
|
20745
20880
|
path: paths.primary.file_path,
|
|
@@ -20819,7 +20954,7 @@ __export(exports_builtin_templates, {
|
|
|
20819
20954
|
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
20820
20955
|
});
|
|
20821
20956
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
20822
|
-
import { join as
|
|
20957
|
+
import { join as join9 } from "path";
|
|
20823
20958
|
function templateMetadata(template) {
|
|
20824
20959
|
return {
|
|
20825
20960
|
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
@@ -20878,7 +21013,7 @@ function writeBuiltinTemplateFiles(directory) {
|
|
|
20878
21013
|
mkdirSync7(directory, { recursive: true });
|
|
20879
21014
|
const files = [];
|
|
20880
21015
|
for (const entry of exportBuiltinTemplateFiles()) {
|
|
20881
|
-
const path =
|
|
21016
|
+
const path = join9(directory, entry.filename);
|
|
20882
21017
|
writeFileSync5(path, `${JSON.stringify(entry.template, null, 2)}
|
|
20883
21018
|
`, "utf-8");
|
|
20884
21019
|
files.push(path);
|
|
@@ -22650,16 +22785,16 @@ var init_saved_search_views = __esm(() => {
|
|
|
22650
22785
|
});
|
|
22651
22786
|
|
|
22652
22787
|
// src/lib/claude-tasks.ts
|
|
22653
|
-
import { existsSync as
|
|
22654
|
-
import { join as
|
|
22788
|
+
import { existsSync as existsSync11, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
22789
|
+
import { join as join10 } from "path";
|
|
22655
22790
|
function getTaskListDir(taskListId) {
|
|
22656
|
-
return
|
|
22791
|
+
return join10(HOME, ".claude", "tasks", taskListId);
|
|
22657
22792
|
}
|
|
22658
22793
|
function readClaudeTask(dir, filename) {
|
|
22659
|
-
return readJsonFile(
|
|
22794
|
+
return readJsonFile(join10(dir, filename));
|
|
22660
22795
|
}
|
|
22661
22796
|
function writeClaudeTask(dir, task) {
|
|
22662
|
-
writeJsonFile(
|
|
22797
|
+
writeJsonFile(join10(dir, `${task.id}.json`), task);
|
|
22663
22798
|
}
|
|
22664
22799
|
function toClaudeStatus(status) {
|
|
22665
22800
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -22671,14 +22806,14 @@ function toSqliteStatus(status) {
|
|
|
22671
22806
|
return status;
|
|
22672
22807
|
}
|
|
22673
22808
|
function readPrefixCounter(dir) {
|
|
22674
|
-
const path =
|
|
22675
|
-
if (!
|
|
22809
|
+
const path = join10(dir, ".prefix-counter");
|
|
22810
|
+
if (!existsSync11(path))
|
|
22676
22811
|
return 0;
|
|
22677
22812
|
const val = parseInt(readFileSync5(path, "utf-8").trim(), 10);
|
|
22678
22813
|
return isNaN(val) ? 0 : val;
|
|
22679
22814
|
}
|
|
22680
22815
|
function writePrefixCounter(dir, value) {
|
|
22681
|
-
writeFileSync6(
|
|
22816
|
+
writeFileSync6(join10(dir, ".prefix-counter"), String(value));
|
|
22682
22817
|
}
|
|
22683
22818
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
22684
22819
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -22705,7 +22840,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
22705
22840
|
}
|
|
22706
22841
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
22707
22842
|
const dir = getTaskListDir(taskListId);
|
|
22708
|
-
if (!
|
|
22843
|
+
if (!existsSync11(dir))
|
|
22709
22844
|
ensureDir(dir);
|
|
22710
22845
|
const filter = {};
|
|
22711
22846
|
if (projectId)
|
|
@@ -22714,7 +22849,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
22714
22849
|
const existingByTodosId = new Map;
|
|
22715
22850
|
const files = listJsonFiles(dir);
|
|
22716
22851
|
for (const f of files) {
|
|
22717
|
-
const path =
|
|
22852
|
+
const path = join10(dir, f);
|
|
22718
22853
|
const ct = readClaudeTask(dir, f);
|
|
22719
22854
|
if (ct?.metadata?.["todos_id"]) {
|
|
22720
22855
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -22801,7 +22936,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
22801
22936
|
}
|
|
22802
22937
|
function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
22803
22938
|
const dir = getTaskListDir(taskListId);
|
|
22804
|
-
if (!
|
|
22939
|
+
if (!existsSync11(dir)) {
|
|
22805
22940
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
22806
22941
|
}
|
|
22807
22942
|
const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -22821,7 +22956,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
22821
22956
|
}
|
|
22822
22957
|
for (const f of files) {
|
|
22823
22958
|
try {
|
|
22824
|
-
const filePath =
|
|
22959
|
+
const filePath = join10(dir, f);
|
|
22825
22960
|
const ct = readClaudeTask(dir, f);
|
|
22826
22961
|
if (!ct)
|
|
22827
22962
|
continue;
|
|
@@ -22894,20 +23029,20 @@ var init_claude_tasks = __esm(() => {
|
|
|
22894
23029
|
});
|
|
22895
23030
|
|
|
22896
23031
|
// src/lib/agent-tasks.ts
|
|
22897
|
-
import { existsSync as
|
|
22898
|
-
import { join as
|
|
23032
|
+
import { existsSync as existsSync12 } from "fs";
|
|
23033
|
+
import { join as join11 } from "path";
|
|
22899
23034
|
function agentBaseDir(agent) {
|
|
22900
23035
|
const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
|
|
22901
|
-
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] ||
|
|
23036
|
+
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join11(getTodosGlobalDir(), "agents");
|
|
22902
23037
|
}
|
|
22903
23038
|
function getTaskListDir2(agent, taskListId) {
|
|
22904
|
-
return
|
|
23039
|
+
return join11(agentBaseDir(agent), agent, taskListId);
|
|
22905
23040
|
}
|
|
22906
23041
|
function readAgentTask(dir, filename) {
|
|
22907
|
-
return readJsonFile(
|
|
23042
|
+
return readJsonFile(join11(dir, filename));
|
|
22908
23043
|
}
|
|
22909
23044
|
function writeAgentTask(dir, task) {
|
|
22910
|
-
writeJsonFile(
|
|
23045
|
+
writeJsonFile(join11(dir, `${task.id}.json`), task);
|
|
22911
23046
|
}
|
|
22912
23047
|
function taskToAgentTask(task, externalId, existingMeta) {
|
|
22913
23048
|
return {
|
|
@@ -22932,7 +23067,7 @@ function metadataKey(agent) {
|
|
|
22932
23067
|
}
|
|
22933
23068
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
22934
23069
|
const dir = getTaskListDir2(agent, taskListId);
|
|
22935
|
-
if (!
|
|
23070
|
+
if (!existsSync12(dir))
|
|
22936
23071
|
ensureDir(dir);
|
|
22937
23072
|
const filter = {};
|
|
22938
23073
|
if (projectId)
|
|
@@ -22941,7 +23076,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
22941
23076
|
const existingByTodosId = new Map;
|
|
22942
23077
|
const files = listJsonFiles(dir);
|
|
22943
23078
|
for (const f of files) {
|
|
22944
|
-
const path =
|
|
23079
|
+
const path = join11(dir, f);
|
|
22945
23080
|
const at = readAgentTask(dir, f);
|
|
22946
23081
|
if (at?.metadata?.["todos_id"]) {
|
|
22947
23082
|
existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -23015,7 +23150,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
23015
23150
|
}
|
|
23016
23151
|
function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
23017
23152
|
const dir = getTaskListDir2(agent, taskListId);
|
|
23018
|
-
if (!
|
|
23153
|
+
if (!existsSync12(dir)) {
|
|
23019
23154
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
23020
23155
|
}
|
|
23021
23156
|
const files = listJsonFiles(dir);
|
|
@@ -23034,7 +23169,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
23034
23169
|
}
|
|
23035
23170
|
for (const f of files) {
|
|
23036
23171
|
try {
|
|
23037
|
-
const filePath =
|
|
23172
|
+
const filePath = join11(dir, f);
|
|
23038
23173
|
const at = readAgentTask(dir, f);
|
|
23039
23174
|
if (!at)
|
|
23040
23175
|
continue;
|
|
@@ -23187,7 +23322,7 @@ __export(exports_project_bootstrap, {
|
|
|
23187
23322
|
discoverProjectWorkspace: () => discoverProjectWorkspace,
|
|
23188
23323
|
bootstrapProject: () => bootstrapProject
|
|
23189
23324
|
});
|
|
23190
|
-
import { existsSync as
|
|
23325
|
+
import { existsSync as existsSync13, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
23191
23326
|
import { basename as basename4, dirname as dirname6, resolve as resolve11 } from "path";
|
|
23192
23327
|
function safeStat(path) {
|
|
23193
23328
|
try {
|
|
@@ -23206,7 +23341,7 @@ function canonicalPath(input) {
|
|
|
23206
23341
|
function findUp(start, marker) {
|
|
23207
23342
|
let current = canonicalPath(start);
|
|
23208
23343
|
while (true) {
|
|
23209
|
-
if (
|
|
23344
|
+
if (existsSync13(resolve11(current, marker)))
|
|
23210
23345
|
return current;
|
|
23211
23346
|
const parent = dirname6(current);
|
|
23212
23347
|
if (parent === current)
|
|
@@ -23218,7 +23353,7 @@ function readPackageJson(path) {
|
|
|
23218
23353
|
if (!path)
|
|
23219
23354
|
return null;
|
|
23220
23355
|
const file = resolve11(path, "package.json");
|
|
23221
|
-
if (!
|
|
23356
|
+
if (!existsSync13(file))
|
|
23222
23357
|
return null;
|
|
23223
23358
|
try {
|
|
23224
23359
|
const parsed = JSON.parse(readFileSync6(file, "utf-8"));
|
|
@@ -23240,7 +23375,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
23240
23375
|
if (rootPackage?.workspaces)
|
|
23241
23376
|
markers.push("package.json#workspaces");
|
|
23242
23377
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
23243
|
-
if (
|
|
23378
|
+
if (existsSync13(resolve11(root, marker)))
|
|
23244
23379
|
markers.push(marker);
|
|
23245
23380
|
}
|
|
23246
23381
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -23574,9 +23709,9 @@ __export(exports_extract, {
|
|
|
23574
23709
|
buildCodebaseIndex: () => buildCodebaseIndex,
|
|
23575
23710
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
23576
23711
|
});
|
|
23577
|
-
import { existsSync as
|
|
23712
|
+
import { existsSync as existsSync14, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
23578
23713
|
import { createHash as createHash4 } from "crypto";
|
|
23579
|
-
import { relative as relative3, resolve as resolve12, join as
|
|
23714
|
+
import { relative as relative3, resolve as resolve12, join as join12 } from "path";
|
|
23580
23715
|
function stableHash(value) {
|
|
23581
23716
|
return createHash4("sha256").update(value).digest("hex");
|
|
23582
23717
|
}
|
|
@@ -23585,8 +23720,8 @@ function normalizePathForMatch(value) {
|
|
|
23585
23720
|
}
|
|
23586
23721
|
function readGitignorePatterns(basePath) {
|
|
23587
23722
|
const root = statSync5(basePath).isFile() ? resolve12(basePath, "..") : basePath;
|
|
23588
|
-
const gitignorePath =
|
|
23589
|
-
if (!
|
|
23723
|
+
const gitignorePath = join12(root, ".gitignore");
|
|
23724
|
+
if (!existsSync14(gitignorePath))
|
|
23590
23725
|
return [];
|
|
23591
23726
|
try {
|
|
23592
23727
|
return readFileSync7(gitignorePath, "utf-8").split(`
|
|
@@ -23728,7 +23863,7 @@ function buildCodebaseIndex(options) {
|
|
|
23728
23863
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
23729
23864
|
const indexed = [];
|
|
23730
23865
|
for (const file of files) {
|
|
23731
|
-
const fullPath = statSync5(basePath).isFile() ? basePath :
|
|
23866
|
+
const fullPath = statSync5(basePath).isFile() ? basePath : join12(basePath, file);
|
|
23732
23867
|
try {
|
|
23733
23868
|
const source = readFileSync7(fullPath, "utf-8");
|
|
23734
23869
|
const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
|
|
@@ -23759,7 +23894,7 @@ function extractTodos(options, db) {
|
|
|
23759
23894
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
23760
23895
|
const allComments = [];
|
|
23761
23896
|
for (const file of files) {
|
|
23762
|
-
const fullPath = statSync5(basePath).isFile() ? basePath :
|
|
23897
|
+
const fullPath = statSync5(basePath).isFile() ? basePath : join12(basePath, file);
|
|
23763
23898
|
try {
|
|
23764
23899
|
const source = readFileSync7(fullPath, "utf-8");
|
|
23765
23900
|
const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
|
|
@@ -26810,8 +26945,16 @@ function resolveCloudAgentByNameOrId(agents, nameOrId) {
|
|
|
26810
26945
|
return null;
|
|
26811
26946
|
return matches.reduce((freshest, candidate) => new Date(candidate.last_seen_at).getTime() > new Date(freshest.last_seen_at).getTime() ? candidate : freshest);
|
|
26812
26947
|
}
|
|
26948
|
+
function clearIdentityIfMine(agentId, agentName) {
|
|
26949
|
+
const persisted = readPersistedIdentity();
|
|
26950
|
+
if (!persisted)
|
|
26951
|
+
return;
|
|
26952
|
+
if (persisted.agent_id === agentId || agentName && persisted.agent_name === agentName) {
|
|
26953
|
+
clearPersistedIdentity();
|
|
26954
|
+
}
|
|
26955
|
+
}
|
|
26813
26956
|
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) => {
|
|
26957
|
+
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
26958
|
const globalOpts = program2.opts();
|
|
26816
26959
|
try {
|
|
26817
26960
|
const cloud = getTodosCloudClient();
|
|
@@ -26821,14 +26964,25 @@ function registerAgentCommands(program2) {
|
|
|
26821
26964
|
console.error(chalk5.red("CONFLICT:"), result.message);
|
|
26822
26965
|
process.exit(1);
|
|
26823
26966
|
}
|
|
26967
|
+
const collision = detectIdentityCollision(result.id, result.name);
|
|
26968
|
+
if (collision && !opts.force) {
|
|
26969
|
+
const held = collision.existing.agent_name || collision.existing.agent_id;
|
|
26970
|
+
console.error(chalk5.red(`This machine already has a persisted todos identity: ${held} (registered ${collision.existing.registered_at}).`));
|
|
26971
|
+
console.error(chalk5.yellow(`Overwriting it would make that session attribute its tasks to you.
|
|
26972
|
+
` + `For a concurrent session, set a per-process identity instead \u2014 it outranks the file and cannot collide:
|
|
26973
|
+
` + ` export TODOS_AGENT_ID=${result.name}
|
|
26974
|
+
Or pass --force to take over the machine-wide identity.`));
|
|
26975
|
+
process.exit(2);
|
|
26976
|
+
}
|
|
26977
|
+
persistIdentity({ agent_id: result.id, agent_name: result.name, ...globalOpts.session ? { session_id: globalOpts.session } : {} });
|
|
26824
26978
|
if (globalOpts.json) {
|
|
26825
|
-
output(result, true);
|
|
26979
|
+
output({ ...result, identity_persisted: true }, true);
|
|
26826
26980
|
} else {
|
|
26827
26981
|
console.log(chalk5.green("Agent registered:"));
|
|
26828
26982
|
console.log(` ${chalk5.dim("ID:")} ${result.id}`);
|
|
26829
26983
|
console.log(` ${chalk5.dim("Name:")} ${result.name}`);
|
|
26830
26984
|
console.log(`
|
|
26831
|
-
|
|
26985
|
+
${chalk5.dim("Identity saved \u2014 later commands attribute to this agent automatically.")}`);
|
|
26832
26986
|
}
|
|
26833
26987
|
} catch (e) {
|
|
26834
26988
|
handleError(e);
|
|
@@ -26885,6 +27039,7 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
|
|
|
26885
27039
|
if (!result.released) {
|
|
26886
27040
|
handleError(new Error("Release denied: session_id does not match agent's current session."));
|
|
26887
27041
|
}
|
|
27042
|
+
clearIdentityIfMine(result.agent.id, result.agent.name);
|
|
26888
27043
|
if (globalOpts.json) {
|
|
26889
27044
|
console.log(JSON.stringify({ agent_id: result.agent.id, name: result.agent.name, released: true }));
|
|
26890
27045
|
} else {
|
|
@@ -26901,6 +27056,7 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
|
|
|
26901
27056
|
if (!released) {
|
|
26902
27057
|
handleError(new Error("Release denied: session_id does not match agent's current session."));
|
|
26903
27058
|
}
|
|
27059
|
+
clearIdentityIfMine(a.id, a.name);
|
|
26904
27060
|
if (globalOpts.json) {
|
|
26905
27061
|
console.log(JSON.stringify({ agent_id: a.id, name: a.name, released: true }));
|
|
26906
27062
|
} else {
|
|
@@ -27241,6 +27397,7 @@ var init_agent_commands = __esm(() => {
|
|
|
27241
27397
|
init_task_lists();
|
|
27242
27398
|
init_tasks();
|
|
27243
27399
|
init_helpers();
|
|
27400
|
+
init_creator_identity();
|
|
27244
27401
|
init_cloud_router();
|
|
27245
27402
|
});
|
|
27246
27403
|
|
|
@@ -27283,8 +27440,8 @@ async function findFreePort(start) {
|
|
|
27283
27440
|
var DEFAULT_PORT = 19427;
|
|
27284
27441
|
|
|
27285
27442
|
// src/lib/db-backup.ts
|
|
27286
|
-
import { existsSync as
|
|
27287
|
-
import { dirname as dirname7, join as
|
|
27443
|
+
import { existsSync as existsSync15, copyFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync8, renameSync, statSync as statSync6, writeFileSync as writeFileSync7, unlinkSync } from "fs";
|
|
27444
|
+
import { dirname as dirname7, join as join13, resolve as resolve14 } from "path";
|
|
27288
27445
|
import { Database as Database2 } from "bun:sqlite";
|
|
27289
27446
|
function resolveDbPath(dbPath) {
|
|
27290
27447
|
if (dbPath)
|
|
@@ -27300,7 +27457,7 @@ function resolveDbPath(dbPath) {
|
|
|
27300
27457
|
}
|
|
27301
27458
|
function backupDatabase(outputPath, sourcePath) {
|
|
27302
27459
|
const source = resolveDbPath(sourcePath);
|
|
27303
|
-
if (!
|
|
27460
|
+
if (!existsSync15(source))
|
|
27304
27461
|
throw new Error(`Database not found: ${source}`);
|
|
27305
27462
|
mkdirSync8(dirname7(outputPath), { recursive: true });
|
|
27306
27463
|
closeDatabase();
|
|
@@ -27325,7 +27482,7 @@ function backupDatabase(outputPath, sourcePath) {
|
|
|
27325
27482
|
function checkDatabaseIntegrity(dbPath) {
|
|
27326
27483
|
const path = dbPath ? resolve14(dbPath) : resolveDbPath();
|
|
27327
27484
|
const errors = [];
|
|
27328
|
-
if (!
|
|
27485
|
+
if (!existsSync15(path)) {
|
|
27329
27486
|
return {
|
|
27330
27487
|
schema_version: DB_BACKUP_SCHEMA,
|
|
27331
27488
|
path,
|
|
@@ -27390,7 +27547,7 @@ function checkDatabaseIntegrity(dbPath) {
|
|
|
27390
27547
|
function defaultBackupPath(dbPath) {
|
|
27391
27548
|
const base = dbPath ? dirname7(resolve14(dbPath)) : dirname7(resolveDbPath());
|
|
27392
27549
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
27393
|
-
return
|
|
27550
|
+
return join13(base, "backups", `todos-${stamp}.db`);
|
|
27394
27551
|
}
|
|
27395
27552
|
var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
|
|
27396
27553
|
var init_db_backup = __esm(() => {
|
|
@@ -27407,7 +27564,7 @@ __export(exports_evidence_redaction, {
|
|
|
27407
27564
|
TODOS_EVIDENCE_REDACTION_CONFIRM: () => TODOS_EVIDENCE_REDACTION_CONFIRM
|
|
27408
27565
|
});
|
|
27409
27566
|
import { chmodSync } from "fs";
|
|
27410
|
-
import { join as
|
|
27567
|
+
import { join as join14 } from "path";
|
|
27411
27568
|
function unique4(values) {
|
|
27412
27569
|
return Array.from(new Set((values ?? []).map((value) => value.trim()).filter(Boolean)));
|
|
27413
27570
|
}
|
|
@@ -27754,7 +27911,7 @@ function redactEvidenceRows(options, db) {
|
|
|
27754
27911
|
}
|
|
27755
27912
|
function defaultEvidenceRedactionBackupPath(dbPath) {
|
|
27756
27913
|
const base = defaultBackupPath(dbPath);
|
|
27757
|
-
return
|
|
27914
|
+
return join14(base.replace(/\.db$/, ""), "pre-redaction.db");
|
|
27758
27915
|
}
|
|
27759
27916
|
var TODOS_EVIDENCE_REDACTION_SCHEMA = "todos.evidence_redaction.v1", TODOS_EVIDENCE_REDACTION_CONFIRM = "REDACT_TODOS_EVIDENCE";
|
|
27760
27917
|
var init_evidence_redaction = __esm(() => {
|
|
@@ -27770,7 +27927,7 @@ __export(exports_retention_cleanup, {
|
|
|
27770
27927
|
applyRetentionCleanup: () => applyRetentionCleanup,
|
|
27771
27928
|
RETENTION_CLEANUP_CONFIRMATION: () => RETENTION_CLEANUP_CONFIRMATION
|
|
27772
27929
|
});
|
|
27773
|
-
import { existsSync as
|
|
27930
|
+
import { existsSync as existsSync16, unlinkSync as unlinkSync2 } from "fs";
|
|
27774
27931
|
function normalizeScopes(scopes) {
|
|
27775
27932
|
if (!scopes || scopes.length === 0)
|
|
27776
27933
|
return [...ALL_SCOPES];
|
|
@@ -27973,7 +28130,7 @@ function applyRetentionCleanup(input, db) {
|
|
|
27973
28130
|
for (const artifact of report.candidates.artifact_files) {
|
|
27974
28131
|
try {
|
|
27975
28132
|
const path = artifactStorePath(artifact.relative_path);
|
|
27976
|
-
if (!
|
|
28133
|
+
if (!existsSync16(path)) {
|
|
27977
28134
|
report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
|
|
27978
28135
|
continue;
|
|
27979
28136
|
}
|
|
@@ -28649,8 +28806,8 @@ __export(exports_local_extensions, {
|
|
|
28649
28806
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
28650
28807
|
});
|
|
28651
28808
|
import { createHash as createHash6, createVerify } from "crypto";
|
|
28652
|
-
import { existsSync as
|
|
28653
|
-
import { basename as basename6, join as
|
|
28809
|
+
import { existsSync as existsSync17, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
|
|
28810
|
+
import { basename as basename6, join as join15, resolve as resolve15 } from "path";
|
|
28654
28811
|
function isObject(value) {
|
|
28655
28812
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
28656
28813
|
}
|
|
@@ -28909,10 +29066,10 @@ function verifyExtensionSignature(input) {
|
|
|
28909
29066
|
}
|
|
28910
29067
|
function inspectExtensionSource(source2) {
|
|
28911
29068
|
const resolved = resolve15(source2);
|
|
28912
|
-
if (!
|
|
29069
|
+
if (!existsSync17(resolved))
|
|
28913
29070
|
throw new Error(`extension source not found: ${source2}`);
|
|
28914
29071
|
const stat = statSync7(resolved);
|
|
28915
|
-
const manifestPath = stat.isDirectory() ? [
|
|
29072
|
+
const manifestPath = stat.isDirectory() ? [join15(resolved, "todos.extension.json"), join15(resolved, "extension.json")].find(existsSync17) : resolved;
|
|
28916
29073
|
if (!manifestPath)
|
|
28917
29074
|
throw new Error(`extension directory ${source2} is missing todos.extension.json`);
|
|
28918
29075
|
const raw = readFileSync9(manifestPath);
|
|
@@ -29008,20 +29165,20 @@ function projectExtensionSources(projectPath) {
|
|
|
29008
29165
|
return [];
|
|
29009
29166
|
const root = resolve15(projectPath);
|
|
29010
29167
|
const candidates = [
|
|
29011
|
-
|
|
29012
|
-
|
|
29168
|
+
join15(root, "todos.extension.json"),
|
|
29169
|
+
join15(root, ".todos", "todos.extension.json")
|
|
29013
29170
|
];
|
|
29014
|
-
const extensionDir =
|
|
29015
|
-
if (
|
|
29171
|
+
const extensionDir = join15(root, ".todos", "extensions");
|
|
29172
|
+
if (existsSync17(extensionDir)) {
|
|
29016
29173
|
for (const entry of readdirSync3(extensionDir)) {
|
|
29017
29174
|
if (entry.startsWith("."))
|
|
29018
29175
|
continue;
|
|
29019
|
-
const full =
|
|
29176
|
+
const full = join15(extensionDir, entry);
|
|
29020
29177
|
if (statSync7(full).isDirectory() || entry.endsWith(".json"))
|
|
29021
29178
|
candidates.push(full);
|
|
29022
29179
|
}
|
|
29023
29180
|
}
|
|
29024
|
-
return candidates.filter(
|
|
29181
|
+
return candidates.filter(existsSync17);
|
|
29025
29182
|
}
|
|
29026
29183
|
function discoverLocalExtensions(options = {}) {
|
|
29027
29184
|
const config = loadConfig();
|
|
@@ -31109,6 +31266,10 @@ class PostgresJsonRecordStore {
|
|
|
31109
31266
|
conds.push(`payload->>'assigned_to' = ${p(filter.assigned_to)}`);
|
|
31110
31267
|
if (filter.agent_id !== undefined)
|
|
31111
31268
|
conds.push(`payload->>'agent_id' = ${p(filter.agent_id)}`);
|
|
31269
|
+
if (filter.created_by !== undefined)
|
|
31270
|
+
conds.push(`LOWER(payload->>'created_by') = LOWER(${p(filter.created_by)})`);
|
|
31271
|
+
if (filter.not_created_by !== undefined)
|
|
31272
|
+
conds.push(`(payload->>'created_by' IS NULL OR LOWER(payload->>'created_by') <> LOWER(${p(filter.not_created_by)}))`);
|
|
31112
31273
|
if (filter.session_id !== undefined)
|
|
31113
31274
|
conds.push(`payload->>'session_id' = ${p(filter.session_id)}`);
|
|
31114
31275
|
if (filter.tags?.length) {
|
|
@@ -31622,7 +31783,7 @@ async function createTask2(input, store, context) {
|
|
|
31622
31783
|
description: input.description ?? null,
|
|
31623
31784
|
status: input.status ?? "pending",
|
|
31624
31785
|
priority: input.priority ?? "medium",
|
|
31625
|
-
agent_id: input.agent_id ?? null,
|
|
31786
|
+
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
31626
31787
|
assigned_to: input.assigned_to ?? null,
|
|
31627
31788
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
31628
31789
|
working_dir: input.working_dir ?? null,
|
|
@@ -31647,7 +31808,8 @@ async function createTask2(input, store, context) {
|
|
|
31647
31808
|
confidence: input.confidence ?? null,
|
|
31648
31809
|
reason: input.reason ?? null,
|
|
31649
31810
|
spawned_from_session: input.spawned_from_session ?? null,
|
|
31650
|
-
assigned_by: input.assigned_by ?? null,
|
|
31811
|
+
assigned_by: input.assigned_by ?? input.agent_id ?? context?.agentId ?? null,
|
|
31812
|
+
created_by: input.created_by ?? input.agent_id ?? context?.agentId ?? null,
|
|
31651
31813
|
assigned_from_project: input.assigned_from_project ?? null,
|
|
31652
31814
|
task_type: input.task_type ?? null,
|
|
31653
31815
|
cost_tokens: 0,
|
|
@@ -31684,7 +31846,8 @@ async function updateTask2(id, input, store) {
|
|
|
31684
31846
|
tags: input.tags ?? existing.tags,
|
|
31685
31847
|
metadata: input.metadata ?? existing.metadata,
|
|
31686
31848
|
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
|
|
31849
|
+
task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
|
|
31850
|
+
created_by: existing.created_by
|
|
31688
31851
|
};
|
|
31689
31852
|
await store.upsert("tasks", task);
|
|
31690
31853
|
return task;
|
|
@@ -33412,8 +33575,8 @@ var exports_doctor = {};
|
|
|
33412
33575
|
__export(exports_doctor, {
|
|
33413
33576
|
runTodosDoctor: () => runTodosDoctor
|
|
33414
33577
|
});
|
|
33415
|
-
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as
|
|
33416
|
-
import { basename as basename7, dirname as dirname8, join as
|
|
33578
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync18, mkdirSync as mkdirSync9, statSync as statSync8 } from "fs";
|
|
33579
|
+
import { basename as basename7, dirname as dirname8, join as join16 } from "path";
|
|
33417
33580
|
function tableExists3(db, table) {
|
|
33418
33581
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
33419
33582
|
}
|
|
@@ -33507,7 +33670,7 @@ function findMissingProjectRoots(db) {
|
|
|
33507
33670
|
continue;
|
|
33508
33671
|
if (!row.path.startsWith("/"))
|
|
33509
33672
|
continue;
|
|
33510
|
-
if (!
|
|
33673
|
+
if (!existsSync18(row.path))
|
|
33511
33674
|
missing++;
|
|
33512
33675
|
}
|
|
33513
33676
|
return missing;
|
|
@@ -33567,16 +33730,16 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
33567
33730
|
function createBackup(dbPath) {
|
|
33568
33731
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
33569
33732
|
return;
|
|
33570
|
-
if (!
|
|
33733
|
+
if (!existsSync18(dbPath))
|
|
33571
33734
|
return;
|
|
33572
33735
|
const stamp = now().replace(/[:.]/g, "-");
|
|
33573
|
-
const backupDir =
|
|
33736
|
+
const backupDir = join16(dirname8(dbPath), `${basename7(dbPath)}.backup-${stamp}`);
|
|
33574
33737
|
const files = [];
|
|
33575
33738
|
mkdirSync9(backupDir, { recursive: true });
|
|
33576
33739
|
for (const source2 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
33577
|
-
if (!
|
|
33740
|
+
if (!existsSync18(source2))
|
|
33578
33741
|
continue;
|
|
33579
|
-
const target =
|
|
33742
|
+
const target = join16(backupDir, basename7(source2));
|
|
33580
33743
|
copyFileSync2(source2, target);
|
|
33581
33744
|
files.push(target);
|
|
33582
33745
|
}
|
|
@@ -33835,7 +33998,7 @@ var init_doctor = __esm(() => {
|
|
|
33835
33998
|
});
|
|
33836
33999
|
|
|
33837
34000
|
// src/server/routes.ts
|
|
33838
|
-
import { join as
|
|
34001
|
+
import { join as join17, resolve as resolve17, sep as sep4 } from "path";
|
|
33839
34002
|
function parseFieldsParam(url) {
|
|
33840
34003
|
const fieldsParam = url.searchParams.get("fields");
|
|
33841
34004
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -34016,11 +34179,15 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
|
|
|
34016
34179
|
const body = await req.json();
|
|
34017
34180
|
if (!body.title)
|
|
34018
34181
|
return json2({ error: "Missing 'title'" }, 400);
|
|
34182
|
+
const createdBy = body.created_by ?? body.agent_id ?? "dashboard";
|
|
34019
34183
|
const task = createTask({
|
|
34020
34184
|
title: body.title,
|
|
34021
34185
|
description: body.description,
|
|
34022
34186
|
priority: body.priority,
|
|
34023
|
-
project_id: body.project_id
|
|
34187
|
+
project_id: body.project_id,
|
|
34188
|
+
agent_id: body.agent_id ?? createdBy,
|
|
34189
|
+
created_by: createdBy,
|
|
34190
|
+
...body.assigned_to ? { assigned_to: body.assigned_to } : {}
|
|
34024
34191
|
});
|
|
34025
34192
|
ctx.broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id, project_id: task.project_id });
|
|
34026
34193
|
return json2(taskToSummary2(task), 201);
|
|
@@ -34626,7 +34793,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
34626
34793
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
34627
34794
|
return null;
|
|
34628
34795
|
if (path !== "/") {
|
|
34629
|
-
const filePath =
|
|
34796
|
+
const filePath = join17(ctx.dashboardDir, path);
|
|
34630
34797
|
const resolvedFile = resolve17(filePath);
|
|
34631
34798
|
const resolvedBase = resolve17(ctx.dashboardDir);
|
|
34632
34799
|
if (!resolvedFile.startsWith(resolvedBase + sep4) && resolvedFile !== resolvedBase) {
|
|
@@ -34636,7 +34803,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
34636
34803
|
if (res2)
|
|
34637
34804
|
return res2;
|
|
34638
34805
|
}
|
|
34639
|
-
const indexPath =
|
|
34806
|
+
const indexPath = join17(ctx.dashboardDir, "index.html");
|
|
34640
34807
|
const res = serveStaticFile2(indexPath);
|
|
34641
34808
|
if (res)
|
|
34642
34809
|
return res;
|
|
@@ -35124,7 +35291,11 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
35124
35291
|
sync: true
|
|
35125
35292
|
},
|
|
35126
35293
|
tasks: {
|
|
35127
|
-
create: (input) => createTask(
|
|
35294
|
+
create: (input, context) => createTask({
|
|
35295
|
+
...input,
|
|
35296
|
+
agent_id: input.agent_id ?? context?.agentId,
|
|
35297
|
+
created_by: input.created_by ?? input.agent_id ?? context?.agentId
|
|
35298
|
+
}, database()),
|
|
35128
35299
|
get: (id) => getTask(id, database()),
|
|
35129
35300
|
resolveRef: (ref) => resolveTaskRefLocal(database(), ref),
|
|
35130
35301
|
list: (filter = {}) => listTasksMaybeSearch(filter, database()),
|
|
@@ -37740,6 +37911,8 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
37740
37911
|
...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
|
|
37741
37912
|
...url.searchParams.get("assigned_to") ? { assigned_to: url.searchParams.get("assigned_to") } : {},
|
|
37742
37913
|
...url.searchParams.get("agent_id") ? { agent_id: url.searchParams.get("agent_id") } : {},
|
|
37914
|
+
...url.searchParams.get("created_by") ? { created_by: url.searchParams.get("created_by") } : {},
|
|
37915
|
+
...url.searchParams.get("not_created_by") ? { not_created_by: url.searchParams.get("not_created_by") } : {},
|
|
37743
37916
|
...url.searchParams.get("tags") ? {
|
|
37744
37917
|
tags: url.searchParams.get("tags").split(",").map((tag) => tag.trim()).filter(Boolean)
|
|
37745
37918
|
} : {},
|
|
@@ -42985,6 +43158,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
42985
43158
|
project_id: exports_external.string().optional().describe("Project ID"),
|
|
42986
43159
|
task_list_id: exports_external.string().optional().describe("Task list ID"),
|
|
42987
43160
|
assigned_to: exports_external.string().optional().describe("Agent ID or name to assign to"),
|
|
43161
|
+
created_by: exports_external.string().optional().describe("Agent who FILED this task. Defaults to the ambient agent identity (todos init / TODOS_AGENT_ID)."),
|
|
43162
|
+
unassigned: exports_external.boolean().optional().describe("Deliberately file with no assignee. Without it, the task defaults to the filer."),
|
|
42988
43163
|
depends_on: exports_external.array(exports_external.string()).optional().describe("Array of task IDs this task depends on"),
|
|
42989
43164
|
short_id: exports_external.string().nullable().optional().describe("Short ID (auto-generated if not provided, disabled if null)"),
|
|
42990
43165
|
tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
|
|
@@ -42995,12 +43170,18 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
42995
43170
|
retry_count: exports_external.number().optional().describe("Max retry count for agent failures")
|
|
42996
43171
|
}, async (params) => {
|
|
42997
43172
|
try {
|
|
42998
|
-
const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, ...rest } = params;
|
|
43173
|
+
const { depends_on, assigned_to, project_id, task_list_id, tags, estimate, confidence, retry_count, deadline, created_by, unassigned, ...rest } = params;
|
|
43174
|
+
const creator = resolveCreatorIdentity(created_by);
|
|
43175
|
+
const assignee = assigned_to || (unassigned ? undefined : creator.agent_id || undefined);
|
|
42999
43176
|
const cloud = getTodosCloudClient();
|
|
43000
43177
|
if (cloud) {
|
|
43001
43178
|
const payload = { ...rest };
|
|
43002
|
-
if (
|
|
43003
|
-
payload.
|
|
43179
|
+
if (creator.agent_id) {
|
|
43180
|
+
payload.created_by = creator.agent_id;
|
|
43181
|
+
payload.agent_id = payload.agent_id ?? creator.agent_id;
|
|
43182
|
+
}
|
|
43183
|
+
if (assignee)
|
|
43184
|
+
payload.assigned_to = assignee;
|
|
43004
43185
|
if (project_id)
|
|
43005
43186
|
payload.project_id = project_id;
|
|
43006
43187
|
if (task_list_id)
|
|
@@ -43021,8 +43202,12 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
43021
43202
|
return { content: [{ type: "text", text: mutationTaskResponse(created) }] };
|
|
43022
43203
|
}
|
|
43023
43204
|
const resolved = { ...rest };
|
|
43024
|
-
if (
|
|
43025
|
-
resolved.
|
|
43205
|
+
if (creator.agent_id) {
|
|
43206
|
+
resolved.created_by = creator.agent_id;
|
|
43207
|
+
resolved.agent_id = resolved.agent_id ?? creator.agent_id;
|
|
43208
|
+
}
|
|
43209
|
+
if (assignee)
|
|
43210
|
+
resolved.assigned_to = resolveAssignee(assignee);
|
|
43026
43211
|
if (project_id)
|
|
43027
43212
|
resolved.project_id = resolveId(project_id, "projects");
|
|
43028
43213
|
if (task_list_id)
|
|
@@ -43108,6 +43293,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
43108
43293
|
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
43109
43294
|
task_list_id: exports_external.string().optional().describe("Filter by task list"),
|
|
43110
43295
|
assigned_to: exports_external.string().optional().describe("Filter by assignee (agent ID or name, empty string = unassigned)"),
|
|
43296
|
+
created_by: exports_external.string().optional().describe("Filter by the agent who FILED the task"),
|
|
43297
|
+
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
43298
|
tags: exports_external.array(exports_external.string()).optional().describe("Filter by tags (AND logic)"),
|
|
43112
43299
|
created_after: exports_external.string().optional().describe("ISO date \u2014 tasks created after this date"),
|
|
43113
43300
|
created_before: exports_external.string().optional().describe("ISO date \u2014 tasks created before this date"),
|
|
@@ -43300,6 +43487,7 @@ var init_task_crud2 = __esm(() => {
|
|
|
43300
43487
|
init_tasks();
|
|
43301
43488
|
init_types();
|
|
43302
43489
|
init_token_utils();
|
|
43490
|
+
init_creator_identity();
|
|
43303
43491
|
init_cloud_router();
|
|
43304
43492
|
});
|
|
43305
43493
|
|
|
@@ -43496,8 +43684,8 @@ var exports_mention_resolver = {};
|
|
|
43496
43684
|
__export(exports_mention_resolver, {
|
|
43497
43685
|
resolveMentions: () => resolveMentions
|
|
43498
43686
|
});
|
|
43499
|
-
import { existsSync as
|
|
43500
|
-
import { basename as basename8, isAbsolute, join as
|
|
43687
|
+
import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync9 } from "fs";
|
|
43688
|
+
import { basename as basename8, isAbsolute, join as join18, relative as relative5, resolve as resolve18, sep as sep5 } from "path";
|
|
43501
43689
|
function blankResolution(parsed) {
|
|
43502
43690
|
return {
|
|
43503
43691
|
input: parsed.input,
|
|
@@ -43595,7 +43783,7 @@ function resolveFile(parsed, workspace) {
|
|
|
43595
43783
|
return resolution;
|
|
43596
43784
|
}
|
|
43597
43785
|
resolution.path = relPath;
|
|
43598
|
-
if (!
|
|
43786
|
+
if (!existsSync19(absolutePath)) {
|
|
43599
43787
|
resolution.warnings.push("file does not exist in the local workspace");
|
|
43600
43788
|
return resolution;
|
|
43601
43789
|
}
|
|
@@ -43628,7 +43816,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
43628
43816
|
if (SKIP_DIRS2.has(entry2.name))
|
|
43629
43817
|
continue;
|
|
43630
43818
|
}
|
|
43631
|
-
const absolutePath =
|
|
43819
|
+
const absolutePath = join18(current, entry2.name);
|
|
43632
43820
|
if (entry2.isDirectory()) {
|
|
43633
43821
|
if (!SKIP_DIRS2.has(entry2.name))
|
|
43634
43822
|
walkSourceFiles(root, absolutePath, files);
|
|
@@ -46395,7 +46583,7 @@ __export(exports_release_compatibility, {
|
|
|
46395
46583
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
46396
46584
|
});
|
|
46397
46585
|
import { readFileSync as readFileSync11 } from "fs";
|
|
46398
|
-
import { join as
|
|
46586
|
+
import { join as join19, resolve as resolve19 } from "path";
|
|
46399
46587
|
import { Database as Database3 } from "bun:sqlite";
|
|
46400
46588
|
function pass(id, message, details) {
|
|
46401
46589
|
return { id, status: "passed", message, details };
|
|
@@ -46407,7 +46595,7 @@ function warn(id, message, details) {
|
|
|
46407
46595
|
return { id, status: "warning", message, details };
|
|
46408
46596
|
}
|
|
46409
46597
|
function readPackageJson2(root) {
|
|
46410
|
-
return JSON.parse(readFileSync11(
|
|
46598
|
+
return JSON.parse(readFileSync11(join19(root, "package.json"), "utf8"));
|
|
46411
46599
|
}
|
|
46412
46600
|
function sortedKeys(value) {
|
|
46413
46601
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -52350,7 +52538,7 @@ __export(exports_verification_providers, {
|
|
|
52350
52538
|
getVerificationRecord: () => getVerificationRecord,
|
|
52351
52539
|
discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
|
|
52352
52540
|
});
|
|
52353
|
-
import { existsSync as
|
|
52541
|
+
import { existsSync as existsSync20, readFileSync as readFileSync12 } from "fs";
|
|
52354
52542
|
function normalizeName6(name) {
|
|
52355
52543
|
const normalized = name.trim().toLowerCase();
|
|
52356
52544
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -52502,7 +52690,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
52502
52690
|
};
|
|
52503
52691
|
}
|
|
52504
52692
|
function runCiLogProvider(input) {
|
|
52505
|
-
const text = input.log_text ?? (input.log_path &&
|
|
52693
|
+
const text = input.log_text ?? (input.log_path && existsSync20(input.log_path) ? readFileSync12(input.log_path, "utf-8") : "");
|
|
52506
52694
|
return {
|
|
52507
52695
|
status: classifyLog(text),
|
|
52508
52696
|
attempts: 1,
|
|
@@ -52514,7 +52702,7 @@ function runBrowserProvider(input) {
|
|
|
52514
52702
|
if (!input.artifact_path) {
|
|
52515
52703
|
return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
|
|
52516
52704
|
}
|
|
52517
|
-
if (!
|
|
52705
|
+
if (!existsSync20(input.artifact_path)) {
|
|
52518
52706
|
return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
|
|
52519
52707
|
}
|
|
52520
52708
|
return {
|
|
@@ -55103,7 +55291,7 @@ __export(exports_onboarding_fixtures, {
|
|
|
55103
55291
|
TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION: () => TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION
|
|
55104
55292
|
});
|
|
55105
55293
|
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
55106
|
-
import { join as
|
|
55294
|
+
import { join as join20 } from "path";
|
|
55107
55295
|
function emptyData() {
|
|
55108
55296
|
return {
|
|
55109
55297
|
projects: [],
|
|
@@ -55171,6 +55359,7 @@ function task(input) {
|
|
|
55171
55359
|
reason: "Bundled deterministic onboarding fixture",
|
|
55172
55360
|
spawned_from_session: null,
|
|
55173
55361
|
assigned_by: null,
|
|
55362
|
+
created_by: null,
|
|
55174
55363
|
assigned_from_project: null,
|
|
55175
55364
|
task_type: "onboarding",
|
|
55176
55365
|
cost_tokens: 0,
|
|
@@ -55437,7 +55626,7 @@ function writeOnboardingFixtureFiles(directory) {
|
|
|
55437
55626
|
mkdirSync11(directory, { recursive: true });
|
|
55438
55627
|
const files = [];
|
|
55439
55628
|
for (const fixture of allFixtures()) {
|
|
55440
|
-
const path =
|
|
55629
|
+
const path = join20(directory, `${fixture.summary.name}.bridge.json`);
|
|
55441
55630
|
writeFileSync9(path, `${JSON.stringify(fixture.bundle, null, 2)}
|
|
55442
55631
|
`, "utf-8");
|
|
55443
55632
|
files.push(path);
|
|
@@ -61649,16 +61838,16 @@ __export(exports_environment_snapshots, {
|
|
|
61649
61838
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
61650
61839
|
});
|
|
61651
61840
|
import { createHash as createHash13 } from "crypto";
|
|
61652
|
-
import { existsSync as
|
|
61841
|
+
import { existsSync as existsSync21, readFileSync as readFileSync15, statSync as statSync10 } from "fs";
|
|
61653
61842
|
import { hostname as hostname2, platform, arch } from "os";
|
|
61654
|
-
import { dirname as dirname10, join as
|
|
61843
|
+
import { dirname as dirname10, join as join21, resolve as resolve21 } from "path";
|
|
61655
61844
|
import { tmpdir as tmpdir4 } from "os";
|
|
61656
61845
|
function sha2567(value) {
|
|
61657
61846
|
return createHash13("sha256").update(value).digest("hex");
|
|
61658
61847
|
}
|
|
61659
61848
|
function fileRecord(root, relativePath) {
|
|
61660
|
-
const path =
|
|
61661
|
-
if (!
|
|
61849
|
+
const path = join21(root, relativePath);
|
|
61850
|
+
if (!existsSync21(path))
|
|
61662
61851
|
return null;
|
|
61663
61852
|
const stat = statSync10(path);
|
|
61664
61853
|
if (!stat.isFile())
|
|
@@ -61670,7 +61859,7 @@ function manifestRecord(root, relativePath) {
|
|
|
61670
61859
|
const base = fileRecord(root, relativePath);
|
|
61671
61860
|
if (!base)
|
|
61672
61861
|
return null;
|
|
61673
|
-
const parsed = readJsonFile(
|
|
61862
|
+
const parsed = readJsonFile(join21(root, relativePath));
|
|
61674
61863
|
if (!parsed)
|
|
61675
61864
|
return { ...base, redacted: {} };
|
|
61676
61865
|
const redacted = redactValue({
|
|
@@ -61765,8 +61954,8 @@ function commandEnv(env, includeValues) {
|
|
|
61765
61954
|
function defaultSnapshotDir() {
|
|
61766
61955
|
const dbPath = getDatabasePath();
|
|
61767
61956
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
61768
|
-
return
|
|
61769
|
-
return
|
|
61957
|
+
return join21(tmpdir4(), "hasna-todos", "environment-snapshots");
|
|
61958
|
+
return join21(dirname10(resolve21(dbPath)), "environment-snapshots");
|
|
61770
61959
|
}
|
|
61771
61960
|
function snapshotWithId(snapshot) {
|
|
61772
61961
|
const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
|
|
@@ -61813,7 +62002,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
61813
62002
|
});
|
|
61814
62003
|
}
|
|
61815
62004
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
61816
|
-
const path = outputPath ? resolve21(outputPath) :
|
|
62005
|
+
const path = outputPath ? resolve21(outputPath) : join21(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
61817
62006
|
ensureDir(dirname10(path));
|
|
61818
62007
|
writeJsonFile(path, snapshot);
|
|
61819
62008
|
return path;
|
|
@@ -62621,27 +62810,27 @@ __export(exports_serve, {
|
|
|
62621
62810
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
62622
62811
|
MIME_TYPES: () => MIME_TYPES
|
|
62623
62812
|
});
|
|
62624
|
-
import { existsSync as
|
|
62625
|
-
import { join as
|
|
62813
|
+
import { existsSync as existsSync22 } from "fs";
|
|
62814
|
+
import { join as join22, dirname as dirname11, extname } from "path";
|
|
62626
62815
|
import { fileURLToPath } from "url";
|
|
62627
62816
|
function resolveDashboardDir() {
|
|
62628
62817
|
const candidates = [];
|
|
62629
62818
|
try {
|
|
62630
62819
|
const scriptDir = dirname11(fileURLToPath(import.meta.url));
|
|
62631
|
-
candidates.push(
|
|
62632
|
-
candidates.push(
|
|
62820
|
+
candidates.push(join22(scriptDir, "..", "dashboard", "dist"));
|
|
62821
|
+
candidates.push(join22(scriptDir, "..", "..", "dashboard", "dist"));
|
|
62633
62822
|
} catch {}
|
|
62634
62823
|
if (process.argv[1]) {
|
|
62635
62824
|
const mainDir = dirname11(process.argv[1]);
|
|
62636
|
-
candidates.push(
|
|
62637
|
-
candidates.push(
|
|
62825
|
+
candidates.push(join22(mainDir, "..", "dashboard", "dist"));
|
|
62826
|
+
candidates.push(join22(mainDir, "..", "..", "dashboard", "dist"));
|
|
62638
62827
|
}
|
|
62639
|
-
candidates.push(
|
|
62828
|
+
candidates.push(join22(process.cwd(), "dashboard", "dist"));
|
|
62640
62829
|
for (const candidate of candidates) {
|
|
62641
|
-
if (
|
|
62830
|
+
if (existsSync22(candidate))
|
|
62642
62831
|
return candidate;
|
|
62643
62832
|
}
|
|
62644
|
-
return
|
|
62833
|
+
return join22(process.cwd(), "dashboard", "dist");
|
|
62645
62834
|
}
|
|
62646
62835
|
function getProvidedApiKey(req) {
|
|
62647
62836
|
const headerKey = req.headers.get("x-api-key");
|
|
@@ -62713,7 +62902,7 @@ function json(data, status = 200, headers) {
|
|
|
62713
62902
|
});
|
|
62714
62903
|
}
|
|
62715
62904
|
function serveStaticFile(filePath) {
|
|
62716
|
-
if (!
|
|
62905
|
+
if (!existsSync22(filePath))
|
|
62717
62906
|
return null;
|
|
62718
62907
|
const ext = extname(filePath);
|
|
62719
62908
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -62814,7 +63003,7 @@ data: ${data}
|
|
|
62814
63003
|
filteredSseClients.delete(client);
|
|
62815
63004
|
}
|
|
62816
63005
|
const dashboardDir = resolveDashboardDir();
|
|
62817
|
-
const dashboardExists =
|
|
63006
|
+
const dashboardExists = existsSync22(dashboardDir);
|
|
62818
63007
|
if (!dashboardExists) {
|
|
62819
63008
|
console.error(`
|
|
62820
63009
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -64687,12 +64876,12 @@ __export(exports_config_serve_commands, {
|
|
|
64687
64876
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
64688
64877
|
});
|
|
64689
64878
|
import chalk6 from "chalk";
|
|
64690
|
-
import { existsSync as
|
|
64691
|
-
import { dirname as dirname12, join as
|
|
64879
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
|
|
64880
|
+
import { dirname as dirname12, join as join23 } from "path";
|
|
64692
64881
|
function registerConfigServeCommands(program2) {
|
|
64693
64882
|
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
64883
|
const globalOpts = program2.opts();
|
|
64695
|
-
const configPath =
|
|
64884
|
+
const configPath = join23(getTodosGlobalDir(), "config.json");
|
|
64696
64885
|
if (opts.get) {
|
|
64697
64886
|
const config2 = loadConfig();
|
|
64698
64887
|
const keys = opts.get.split(".");
|
|
@@ -64729,7 +64918,7 @@ function registerConfigServeCommands(program2) {
|
|
|
64729
64918
|
}
|
|
64730
64919
|
obj[keys[keys.length - 1]] = parsedValue;
|
|
64731
64920
|
const dir = dirname12(configPath);
|
|
64732
|
-
if (!
|
|
64921
|
+
if (!existsSync23(dir))
|
|
64733
64922
|
mkdirSync12(dir, { recursive: true });
|
|
64734
64923
|
writeFileSync10(configPath, JSON.stringify(config2, null, 2));
|
|
64735
64924
|
if (globalOpts.json) {
|
|
@@ -65909,8 +66098,8 @@ __export(exports_routing_doctor, {
|
|
|
65909
66098
|
classifyTaskRouting: () => classifyTaskRouting,
|
|
65910
66099
|
TODOS_ROUTING_DOCTOR_SCHEMA_VERSION: () => TODOS_ROUTING_DOCTOR_SCHEMA_VERSION
|
|
65911
66100
|
});
|
|
65912
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
65913
|
-
import { basename as basename9, dirname as dirname13, join as
|
|
66101
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync24, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
66102
|
+
import { basename as basename9, dirname as dirname13, join as join24 } from "path";
|
|
65914
66103
|
function normalizePath5(path) {
|
|
65915
66104
|
if (!path)
|
|
65916
66105
|
return null;
|
|
@@ -66212,16 +66401,16 @@ function evaluateTaskRouting(ctx) {
|
|
|
66212
66401
|
function createBackup2(dbPath, generatedAt) {
|
|
66213
66402
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
66214
66403
|
return;
|
|
66215
|
-
if (!
|
|
66404
|
+
if (!existsSync24(dbPath))
|
|
66216
66405
|
return;
|
|
66217
66406
|
const stamp = generatedAt.replace(/[:.]/g, "-");
|
|
66218
|
-
const backupDir =
|
|
66407
|
+
const backupDir = join24(dirname13(dbPath), `${basename9(dbPath)}.routing-doctor-backup-${stamp}`);
|
|
66219
66408
|
const files = [];
|
|
66220
66409
|
mkdirSync13(backupDir, { recursive: true });
|
|
66221
66410
|
for (const source3 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
66222
|
-
if (!
|
|
66411
|
+
if (!existsSync24(source3))
|
|
66223
66412
|
continue;
|
|
66224
|
-
const target =
|
|
66413
|
+
const target = join24(backupDir, basename9(source3));
|
|
66225
66414
|
copyFileSync3(source3, target);
|
|
66226
66415
|
files.push(target);
|
|
66227
66416
|
}
|
|
@@ -66325,7 +66514,7 @@ function runRoutingDoctor(options = {}) {
|
|
|
66325
66514
|
}
|
|
66326
66515
|
const applied = repairs.filter((r) => r.applied);
|
|
66327
66516
|
if (applied.length > 0) {
|
|
66328
|
-
const undoPath = options.undoRecordPath ??
|
|
66517
|
+
const undoPath = options.undoRecordPath ?? join24(process.cwd(), `todos-routing-doctor-undo-${generatedAt.replace(/[:.]/g, "-")}.json`);
|
|
66329
66518
|
const undoRecord = {
|
|
66330
66519
|
schema_version: TODOS_ROUTING_DOCTOR_SCHEMA_VERSION,
|
|
66331
66520
|
purpose: "Undo record for routing-doctor --apply. Restore each field with the prior value below.",
|
|
@@ -66424,8 +66613,8 @@ __export(exports_task_route_sources, {
|
|
|
66424
66613
|
});
|
|
66425
66614
|
import { Database as Database4 } from "bun:sqlite";
|
|
66426
66615
|
import { createHash as createHash14 } from "crypto";
|
|
66427
|
-
import { existsSync as
|
|
66428
|
-
import { basename as basename10, dirname as dirname14, join as
|
|
66616
|
+
import { existsSync as existsSync25, readdirSync as readdirSync5, statSync as statSync11 } from "fs";
|
|
66617
|
+
import { basename as basename10, dirname as dirname14, join as join25, resolve as resolve22 } from "path";
|
|
66429
66618
|
function normalizePath6(input) {
|
|
66430
66619
|
return resolve22(input);
|
|
66431
66620
|
}
|
|
@@ -66489,8 +66678,8 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
66489
66678
|
const rootPath = normalizePath6(sourceRoot);
|
|
66490
66679
|
const errors2 = [];
|
|
66491
66680
|
const stores = [];
|
|
66492
|
-
if (!
|
|
66493
|
-
const ref = createStoreRef(
|
|
66681
|
+
if (!existsSync25(rootPath)) {
|
|
66682
|
+
const ref = createStoreRef(join25(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
66494
66683
|
errors2.push({
|
|
66495
66684
|
...ref,
|
|
66496
66685
|
code: "SOURCE_ROOT_MISSING",
|
|
@@ -66502,7 +66691,7 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
66502
66691
|
try {
|
|
66503
66692
|
rootStat = statSync11(rootPath);
|
|
66504
66693
|
} catch (error2) {
|
|
66505
|
-
const ref = createStoreRef(
|
|
66694
|
+
const ref = createStoreRef(join25(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
66506
66695
|
errors2.push({
|
|
66507
66696
|
...ref,
|
|
66508
66697
|
code: "SOURCE_ROOT_UNREADABLE",
|
|
@@ -66515,8 +66704,8 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
66515
66704
|
return { stores, errors: errors2 };
|
|
66516
66705
|
}
|
|
66517
66706
|
function scanDirectory(dir, depth) {
|
|
66518
|
-
const candidate =
|
|
66519
|
-
if (
|
|
66707
|
+
const candidate = join25(dir, TODO_STORE_RELATIVE_PATH);
|
|
66708
|
+
if (existsSync25(candidate)) {
|
|
66520
66709
|
stores.push(createStoreRef(candidate));
|
|
66521
66710
|
}
|
|
66522
66711
|
if (depth >= ROOT_SCAN_MAX_DEPTH)
|
|
@@ -66536,7 +66725,7 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
66536
66725
|
for (const entry2 of entries) {
|
|
66537
66726
|
if (!entry2.isDirectory() || SKIPPED_SCAN_DIRS.has(entry2.name))
|
|
66538
66727
|
continue;
|
|
66539
|
-
scanDirectory(
|
|
66728
|
+
scanDirectory(join25(dir, entry2.name), depth + 1);
|
|
66540
66729
|
}
|
|
66541
66730
|
}
|
|
66542
66731
|
scanDirectory(rootPath, 0);
|
|
@@ -66562,7 +66751,7 @@ function collectStoreRefs(input) {
|
|
|
66562
66751
|
};
|
|
66563
66752
|
}
|
|
66564
66753
|
function openReadonlyStore(ref) {
|
|
66565
|
-
if (!
|
|
66754
|
+
if (!existsSync25(ref.source_db_path)) {
|
|
66566
66755
|
throw Object.assign(new Error(`Store does not exist: ${ref.source_db_path}`), { code: "STORE_MISSING" });
|
|
66567
66756
|
}
|
|
66568
66757
|
return new Database4(ref.source_db_path, { readonly: true, create: false });
|
|
@@ -66736,7 +66925,7 @@ var init_task_route_sources = __esm(() => {
|
|
|
66736
66925
|
init_task_crud();
|
|
66737
66926
|
init_redaction();
|
|
66738
66927
|
init_task_routing();
|
|
66739
|
-
TODO_STORE_RELATIVE_PATH =
|
|
66928
|
+
TODO_STORE_RELATIVE_PATH = join25(".hasna", "todos", "todos.db");
|
|
66740
66929
|
SKIPPED_SCAN_DIRS = new Set([
|
|
66741
66930
|
".git",
|
|
66742
66931
|
".hg",
|
|
@@ -68245,9 +68434,9 @@ Findings`));
|
|
|
68245
68434
|
const db = getDatabase();
|
|
68246
68435
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
68247
68436
|
const { statSync: statSync12 } = await import("fs");
|
|
68248
|
-
const { join:
|
|
68437
|
+
const { join: join26 } = await import("path");
|
|
68249
68438
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
68250
|
-
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] ||
|
|
68439
|
+
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join26(home, ".hasna", "todos", "todos.db");
|
|
68251
68440
|
let size = "unknown";
|
|
68252
68441
|
try {
|
|
68253
68442
|
size = `${(statSync12(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
@@ -70161,21 +70350,21 @@ __export(exports_mcp_hooks_commands, {
|
|
|
70161
70350
|
});
|
|
70162
70351
|
import chalk9 from "chalk";
|
|
70163
70352
|
import { execSync as execSync3 } from "child_process";
|
|
70164
|
-
import { existsSync as
|
|
70165
|
-
import { dirname as dirname15, join as
|
|
70353
|
+
import { existsSync as existsSync26, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, chmodSync as chmodSync3 } from "fs";
|
|
70354
|
+
import { dirname as dirname15, join as join26 } from "path";
|
|
70166
70355
|
function getMcpBinaryPath() {
|
|
70167
70356
|
try {
|
|
70168
70357
|
const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
|
|
70169
70358
|
if (p)
|
|
70170
70359
|
return p;
|
|
70171
70360
|
} catch {}
|
|
70172
|
-
const bunBin =
|
|
70173
|
-
if (
|
|
70361
|
+
const bunBin = join26(HOME2, ".bun", "bin", "todos-mcp");
|
|
70362
|
+
if (existsSync26(bunBin))
|
|
70174
70363
|
return bunBin;
|
|
70175
70364
|
return "todos-mcp";
|
|
70176
70365
|
}
|
|
70177
70366
|
function readJsonFile2(path) {
|
|
70178
|
-
if (!
|
|
70367
|
+
if (!existsSync26(path))
|
|
70179
70368
|
return {};
|
|
70180
70369
|
try {
|
|
70181
70370
|
return JSON.parse(readFileSync18(path, "utf-8"));
|
|
@@ -70185,19 +70374,19 @@ function readJsonFile2(path) {
|
|
|
70185
70374
|
}
|
|
70186
70375
|
function writeJsonFile2(path, data) {
|
|
70187
70376
|
const dir = dirname15(path);
|
|
70188
|
-
if (!
|
|
70377
|
+
if (!existsSync26(dir))
|
|
70189
70378
|
mkdirSync14(dir, { recursive: true });
|
|
70190
70379
|
writeFileSync13(path, JSON.stringify(data, null, 2) + `
|
|
70191
70380
|
`);
|
|
70192
70381
|
}
|
|
70193
70382
|
function readTomlFile(path) {
|
|
70194
|
-
if (!
|
|
70383
|
+
if (!existsSync26(path))
|
|
70195
70384
|
return "";
|
|
70196
70385
|
return readFileSync18(path, "utf-8");
|
|
70197
70386
|
}
|
|
70198
70387
|
function writeTomlFile(path, content) {
|
|
70199
70388
|
const dir = dirname15(path);
|
|
70200
|
-
if (!
|
|
70389
|
+
if (!existsSync26(dir))
|
|
70201
70390
|
mkdirSync14(dir, { recursive: true });
|
|
70202
70391
|
writeFileSync13(path, content);
|
|
70203
70392
|
}
|
|
@@ -70262,7 +70451,7 @@ function unregisterClaude(_global) {
|
|
|
70262
70451
|
}
|
|
70263
70452
|
}
|
|
70264
70453
|
function registerCodex(binPath) {
|
|
70265
|
-
const configPath =
|
|
70454
|
+
const configPath = join26(HOME2, ".codex", "config.toml");
|
|
70266
70455
|
let content = readTomlFile(configPath);
|
|
70267
70456
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
70268
70457
|
const block = `
|
|
@@ -70276,7 +70465,7 @@ args = ["--stdio"]
|
|
|
70276
70465
|
console.log(chalk9.green(`Codex CLI: registered in ${configPath}`));
|
|
70277
70466
|
}
|
|
70278
70467
|
function unregisterCodex() {
|
|
70279
|
-
const configPath =
|
|
70468
|
+
const configPath = join26(HOME2, ".codex", "config.toml");
|
|
70280
70469
|
let content = readTomlFile(configPath);
|
|
70281
70470
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
70282
70471
|
console.log(chalk9.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -70288,7 +70477,7 @@ function unregisterCodex() {
|
|
|
70288
70477
|
console.log(chalk9.green(`Codex CLI: unregistered from ${configPath}`));
|
|
70289
70478
|
}
|
|
70290
70479
|
function registerGemini(binPath) {
|
|
70291
|
-
const configPath =
|
|
70480
|
+
const configPath = join26(HOME2, ".gemini", "settings.json");
|
|
70292
70481
|
const config = readJsonFile2(configPath);
|
|
70293
70482
|
if (!config["mcpServers"]) {
|
|
70294
70483
|
config["mcpServers"] = {};
|
|
@@ -70302,7 +70491,7 @@ function registerGemini(binPath) {
|
|
|
70302
70491
|
console.log(chalk9.green(`Gemini CLI: registered in ${configPath}`));
|
|
70303
70492
|
}
|
|
70304
70493
|
function unregisterGemini() {
|
|
70305
|
-
const configPath =
|
|
70494
|
+
const configPath = join26(HOME2, ".gemini", "settings.json");
|
|
70306
70495
|
const config = readJsonFile2(configPath);
|
|
70307
70496
|
const servers = config["mcpServers"];
|
|
70308
70497
|
if (!servers || !("todos" in servers)) {
|
|
@@ -70313,8 +70502,37 @@ function unregisterGemini() {
|
|
|
70313
70502
|
writeJsonFile2(configPath, config);
|
|
70314
70503
|
console.log(chalk9.green(`Gemini CLI: unregistered from ${configPath}`));
|
|
70315
70504
|
}
|
|
70505
|
+
function cursorConfigPath(global) {
|
|
70506
|
+
return global ? join26(HOME2, ".cursor", "mcp.json") : join26(process.cwd(), ".cursor", "mcp.json");
|
|
70507
|
+
}
|
|
70508
|
+
function registerCursor(binPath, global) {
|
|
70509
|
+
const configPath = cursorConfigPath(global);
|
|
70510
|
+
const config = readJsonFile2(configPath);
|
|
70511
|
+
if (!config["mcpServers"]) {
|
|
70512
|
+
config["mcpServers"] = {};
|
|
70513
|
+
}
|
|
70514
|
+
const servers = config["mcpServers"];
|
|
70515
|
+
servers["todos"] = {
|
|
70516
|
+
command: binPath,
|
|
70517
|
+
args: ["--stdio"]
|
|
70518
|
+
};
|
|
70519
|
+
writeJsonFile2(configPath, config);
|
|
70520
|
+
console.log(chalk9.green(`Cursor (${global ? "user" : "project"}): registered in ${configPath}`));
|
|
70521
|
+
}
|
|
70522
|
+
function unregisterCursor(global) {
|
|
70523
|
+
const configPath = cursorConfigPath(global);
|
|
70524
|
+
const config = readJsonFile2(configPath);
|
|
70525
|
+
const servers = config["mcpServers"];
|
|
70526
|
+
if (!servers || !("todos" in servers)) {
|
|
70527
|
+
console.log(chalk9.dim(`Cursor: todos not found in ${configPath}`));
|
|
70528
|
+
return;
|
|
70529
|
+
}
|
|
70530
|
+
delete servers["todos"];
|
|
70531
|
+
writeJsonFile2(configPath, config);
|
|
70532
|
+
console.log(chalk9.green(`Cursor (${global ? "user" : "project"}): unregistered from ${configPath}`));
|
|
70533
|
+
}
|
|
70316
70534
|
function registerMcp(agent, global) {
|
|
70317
|
-
const agents = agent === "all" ? ["claude", "codex", "gemini"] : [agent];
|
|
70535
|
+
const agents = agent === "all" ? ["claude", "codex", "gemini", "cursor"] : [agent];
|
|
70318
70536
|
const binPath = getMcpBinaryPath();
|
|
70319
70537
|
for (const a of agents) {
|
|
70320
70538
|
switch (a) {
|
|
@@ -70327,13 +70545,16 @@ function registerMcp(agent, global) {
|
|
|
70327
70545
|
case "gemini":
|
|
70328
70546
|
registerGemini(binPath);
|
|
70329
70547
|
break;
|
|
70548
|
+
case "cursor":
|
|
70549
|
+
registerCursor(binPath, global);
|
|
70550
|
+
break;
|
|
70330
70551
|
default:
|
|
70331
|
-
console.error(chalk9.red(`Unknown agent: ${a}. Use: claude, codex, gemini, all`));
|
|
70552
|
+
console.error(chalk9.red(`Unknown agent: ${a}. Use: claude, codex, gemini, cursor, all`));
|
|
70332
70553
|
}
|
|
70333
70554
|
}
|
|
70334
70555
|
}
|
|
70335
70556
|
function unregisterMcp(agent, global) {
|
|
70336
|
-
const agents = agent === "all" ? ["claude", "codex", "gemini"] : [agent];
|
|
70557
|
+
const agents = agent === "all" ? ["claude", "codex", "gemini", "cursor"] : [agent];
|
|
70337
70558
|
for (const a of agents) {
|
|
70338
70559
|
switch (a) {
|
|
70339
70560
|
case "claude":
|
|
@@ -70345,8 +70566,11 @@ function unregisterMcp(agent, global) {
|
|
|
70345
70566
|
case "gemini":
|
|
70346
70567
|
unregisterGemini();
|
|
70347
70568
|
break;
|
|
70569
|
+
case "cursor":
|
|
70570
|
+
unregisterCursor(global);
|
|
70571
|
+
break;
|
|
70348
70572
|
default:
|
|
70349
|
-
console.error(chalk9.red(`Unknown agent: ${a}. Use: claude, codex, gemini, all`));
|
|
70573
|
+
console.error(chalk9.red(`Unknown agent: ${a}. Use: claude, codex, gemini, cursor, all`));
|
|
70350
70574
|
}
|
|
70351
70575
|
}
|
|
70352
70576
|
}
|
|
@@ -70359,8 +70583,8 @@ function registerMcpHooksCommands(program2) {
|
|
|
70359
70583
|
if (p)
|
|
70360
70584
|
todosBin = p;
|
|
70361
70585
|
} catch {}
|
|
70362
|
-
const hooksDir =
|
|
70363
|
-
if (!
|
|
70586
|
+
const hooksDir = join26(process.cwd(), ".claude", "hooks");
|
|
70587
|
+
if (!existsSync26(hooksDir))
|
|
70364
70588
|
mkdirSync14(hooksDir, { recursive: true });
|
|
70365
70589
|
const hookScript = `#!/usr/bin/env bash
|
|
70366
70590
|
# Auto-generated by: todos hooks install
|
|
@@ -70385,11 +70609,11 @@ esac
|
|
|
70385
70609
|
|
|
70386
70610
|
exit 0
|
|
70387
70611
|
`;
|
|
70388
|
-
const hookPath =
|
|
70612
|
+
const hookPath = join26(hooksDir, "todos-sync.sh");
|
|
70389
70613
|
writeFileSync13(hookPath, hookScript);
|
|
70390
70614
|
execSync3(`chmod +x "${hookPath}"`);
|
|
70391
70615
|
console.log(chalk9.green(`Hook script created: ${hookPath}`));
|
|
70392
|
-
const settingsPath =
|
|
70616
|
+
const settingsPath = join26(process.cwd(), ".claude", "settings.json");
|
|
70393
70617
|
const settings = readJsonFile2(settingsPath);
|
|
70394
70618
|
if (!settings["hooks"]) {
|
|
70395
70619
|
settings["hooks"] = {};
|
|
@@ -70418,7 +70642,7 @@ exit 0
|
|
|
70418
70642
|
console.log(chalk9.green(`Claude Code hooks configured in: ${settingsPath}`));
|
|
70419
70643
|
console.log(chalk9.dim("Task list ID auto-detected from project."));
|
|
70420
70644
|
});
|
|
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) => {
|
|
70645
|
+
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
70646
|
if (opts.register) {
|
|
70423
70647
|
registerMcp(opts.register, opts.global);
|
|
70424
70648
|
return;
|
|
@@ -71320,7 +71544,7 @@ Artifacts:`));
|
|
|
71320
71544
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
71321
71545
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
71322
71546
|
const marker = "# todos-auto-link";
|
|
71323
|
-
if (
|
|
71547
|
+
if (existsSync26(hookPath)) {
|
|
71324
71548
|
const existing = readFileSync18(hookPath, "utf-8");
|
|
71325
71549
|
if (existing.includes(marker)) {
|
|
71326
71550
|
console.log(chalk9.yellow("Hook already installed."));
|
|
@@ -71347,7 +71571,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
71347
71571
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
71348
71572
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
71349
71573
|
const marker = "# todos-auto-link";
|
|
71350
|
-
if (!
|
|
71574
|
+
if (!existsSync26(hookPath)) {
|
|
71351
71575
|
console.log(chalk9.dim("No post-commit hook found."));
|
|
71352
71576
|
return;
|
|
71353
71577
|
}
|
|
@@ -71533,7 +71757,7 @@ import chalk11 from "chalk";
|
|
|
71533
71757
|
import { execSync as execSync4 } from "child_process";
|
|
71534
71758
|
import { readFileSync as readFileSync19, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
71535
71759
|
import { tmpdir as tmpdir5 } from "os";
|
|
71536
|
-
import { join as
|
|
71760
|
+
import { join as join27 } from "path";
|
|
71537
71761
|
function getOrCreateLocalMachineName() {
|
|
71538
71762
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
71539
71763
|
}
|
|
@@ -71571,7 +71795,7 @@ function remoteTempPath(sshAddress) {
|
|
|
71571
71795
|
}
|
|
71572
71796
|
function readRemoteBridgeBundle(sshAddress) {
|
|
71573
71797
|
const remotePath = remoteTempPath(sshAddress);
|
|
71574
|
-
const localPath =
|
|
71798
|
+
const localPath = join27(tmpdir5(), `todos-bridge-pull-${uuid()}.json`);
|
|
71575
71799
|
try {
|
|
71576
71800
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
71577
71801
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
@@ -71586,7 +71810,7 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
71586
71810
|
}
|
|
71587
71811
|
}
|
|
71588
71812
|
function writeLocalBridgeBundle() {
|
|
71589
|
-
const localPath =
|
|
71813
|
+
const localPath = join27(tmpdir5(), `todos-bridge-push-${uuid()}.json`);
|
|
71590
71814
|
writeFileSync14(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
|
|
71591
71815
|
return localPath;
|
|
71592
71816
|
}
|
|
@@ -76161,7 +76385,7 @@ __export(exports_sdk_integration_fixtures, {
|
|
|
76161
76385
|
TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT: () => TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT
|
|
76162
76386
|
});
|
|
76163
76387
|
import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
|
|
76164
|
-
import { join as
|
|
76388
|
+
import { join as join28 } from "path";
|
|
76165
76389
|
function source5(version) {
|
|
76166
76390
|
return {
|
|
76167
76391
|
packageName: "@hasna/todos",
|
|
@@ -76268,7 +76492,7 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
|
|
|
76268
76492
|
];
|
|
76269
76493
|
const written = [];
|
|
76270
76494
|
for (const [name, payload] of files) {
|
|
76271
|
-
const file =
|
|
76495
|
+
const file = join28(directory, name);
|
|
76272
76496
|
writeFileSync15(file, `${JSON.stringify(payload, null, 2)}
|
|
76273
76497
|
`, "utf-8");
|
|
76274
76498
|
written.push(file);
|