@hasna/todos 0.13.4 → 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 +483 -195
- package/dist/cli/stage-a.d.ts +2 -0
- package/dist/cli/stage-a.d.ts.map +1 -1
- 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,
|
|
@@ -6362,41 +6363,103 @@ function isMetadataInvocation(args, invocation) {
|
|
|
6362
6363
|
}
|
|
6363
6364
|
return invocation.commandArgs.length === 1 && (HELP_FLAGS.has(invocation.commandArgs[0]) || VERSION_FLAGS.has(invocation.commandArgs[0]));
|
|
6364
6365
|
}
|
|
6365
|
-
function
|
|
6366
|
+
function firstPresentOption(args, candidates) {
|
|
6367
|
+
const option = candidates.find((candidate) => hasOption(args, candidate));
|
|
6368
|
+
return option ? dropIt(option) : null;
|
|
6369
|
+
}
|
|
6370
|
+
function disqualifyingArgument(invocation) {
|
|
6366
6371
|
const command = invocation.command;
|
|
6367
|
-
if (!command || COMMAND_CAPABILITY_MATRIX.get(command) !== "remote-http")
|
|
6368
|
-
return false;
|
|
6369
6372
|
const args = invocation.commandArgs;
|
|
6370
6373
|
switch (command) {
|
|
6371
6374
|
case "task":
|
|
6372
|
-
return positionalArgs(args)[0] === "upsert";
|
|
6375
|
+
return positionalArgs(args)[0] === "upsert" ? null : { blame: "any subcommand other than `upsert`", remedy: "use `todos task upsert`" };
|
|
6373
6376
|
case "doctor":
|
|
6374
|
-
|
|
6377
|
+
if (positionalArgs(args)[0] === "routing")
|
|
6378
|
+
return dropIt("the `routing` subcommand");
|
|
6379
|
+
return firstPresentOption(args, ["--apply", "--fix"]);
|
|
6375
6380
|
case "projects":
|
|
6376
|
-
return
|
|
6381
|
+
return firstPresentOption(args, ["--deregister", "--path-prefix", "--dry-run"]);
|
|
6377
6382
|
case "plans":
|
|
6378
|
-
return
|
|
6383
|
+
return firstPresentOption(args, ["--artifact", "--write-artifacts"]);
|
|
6379
6384
|
case "list":
|
|
6380
|
-
return
|
|
6385
|
+
return firstPresentOption(args, ["--recurring"]);
|
|
6381
6386
|
case "claim":
|
|
6382
|
-
|
|
6387
|
+
if (invocation.globalOptions.has("--project"))
|
|
6388
|
+
return dropIt("--project");
|
|
6389
|
+
return firstPresentOption(args, ["--project", "--stale-minutes", "--steal-stale"]);
|
|
6383
6390
|
case "status":
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
return
|
|
6391
|
+
if (invocation.globalOptions.has("--agent"))
|
|
6392
|
+
return dropIt("--agent");
|
|
6393
|
+
return firstPresentOption(args, ["--agent"]);
|
|
6387
6394
|
case "bulk": {
|
|
6388
6395
|
const action = positionalArgs(args)[0];
|
|
6389
6396
|
if (action === "plan" || action === "move-plan")
|
|
6390
|
-
return
|
|
6391
|
-
|
|
6397
|
+
return null;
|
|
6398
|
+
if (!action) {
|
|
6399
|
+
return { blame: "a missing action", remedy: "pass one of done, complete, start, delete, plan, move-plan" };
|
|
6400
|
+
}
|
|
6401
|
+
if (!["done", "complete", "start", "delete"].includes(action)) {
|
|
6402
|
+
return {
|
|
6403
|
+
blame: `the \`${action}\` action`,
|
|
6404
|
+
remedy: "use one of done, complete, start, delete, plan, move-plan"
|
|
6405
|
+
};
|
|
6406
|
+
}
|
|
6407
|
+
return firstPresentOption(args, ["--plan", "--clear-plan"]);
|
|
6392
6408
|
}
|
|
6393
6409
|
default:
|
|
6394
|
-
return
|
|
6410
|
+
return null;
|
|
6395
6411
|
}
|
|
6396
6412
|
}
|
|
6413
|
+
function commandSupportsRemote(invocation) {
|
|
6414
|
+
const command = invocation.command;
|
|
6415
|
+
if (!command || COMMAND_CAPABILITY_MATRIX.get(command) !== "remote-http")
|
|
6416
|
+
return false;
|
|
6417
|
+
return disqualifyingArgument(invocation) === null;
|
|
6418
|
+
}
|
|
6419
|
+
function editDistance(a, b) {
|
|
6420
|
+
const rows = a.length + 1;
|
|
6421
|
+
const cols = b.length + 1;
|
|
6422
|
+
let twoBack = new Array(cols).fill(0);
|
|
6423
|
+
let previous = Array.from({ length: cols }, (_, index) => index);
|
|
6424
|
+
for (let row = 1;row < rows; row += 1) {
|
|
6425
|
+
const current = [row, ...new Array(cols - 1).fill(0)];
|
|
6426
|
+
for (let col = 1;col < cols; col += 1) {
|
|
6427
|
+
const substitution = previous[col - 1] + (a[row - 1] === b[col - 1] ? 0 : 1);
|
|
6428
|
+
current[col] = Math.min(current[col - 1] + 1, previous[col] + 1, substitution);
|
|
6429
|
+
if (row > 1 && col > 1 && a[row - 1] === b[col - 2] && a[row - 2] === b[col - 1]) {
|
|
6430
|
+
current[col] = Math.min(current[col], twoBack[col - 2] + 1);
|
|
6431
|
+
}
|
|
6432
|
+
}
|
|
6433
|
+
twoBack = previous;
|
|
6434
|
+
previous = current;
|
|
6435
|
+
}
|
|
6436
|
+
return previous[cols - 1];
|
|
6437
|
+
}
|
|
6438
|
+
function nearestCommands(command, limit = 3) {
|
|
6439
|
+
const threshold = command.length <= 4 ? 1 : command.length <= 8 ? 2 : 3;
|
|
6440
|
+
return [...COMMAND_CAPABILITY_MATRIX.entries()].filter(([, owner]) => owner !== "local-only").map(([candidate]) => candidate).map((candidate) => ({ candidate, distance: editDistance(command, candidate) })).filter(({ distance }) => distance <= threshold).sort((left, right) => left.distance - right.distance || left.candidate.localeCompare(right.candidate)).slice(0, limit).map(({ candidate }) => candidate);
|
|
6441
|
+
}
|
|
6397
6442
|
function assertRemoteCommandSupported(invocation) {
|
|
6398
|
-
if (invocation.invalidGlobalOption
|
|
6399
|
-
throw new Error(`REMOTE_COMMAND_UNSUPPORTED: ${
|
|
6443
|
+
if (invocation.invalidGlobalOption) {
|
|
6444
|
+
throw new Error(`REMOTE_COMMAND_UNSUPPORTED: the global option ${invocation.invalidGlobalOption} was given without a value; ` + `pass one as \`${invocation.invalidGlobalOption} <value>\``);
|
|
6445
|
+
}
|
|
6446
|
+
if (invocation.unknownLeadingOption) {
|
|
6447
|
+
throw new Error(`REMOTE_COMMAND_UNSUPPORTED: unknown option ${invocation.unknownLeadingOption} before the command; ` + "run `todos --help` for the global options");
|
|
6448
|
+
}
|
|
6449
|
+
const command = invocation.command;
|
|
6450
|
+
const owner = command ? COMMAND_CAPABILITY_MATRIX.get(command) : undefined;
|
|
6451
|
+
if (command && !owner) {
|
|
6452
|
+
const suggestions = nearestCommands(command);
|
|
6453
|
+
const didYouMean = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
|
|
6454
|
+
throw new Error(`UNKNOWN_COMMAND: \`${command}\` is not a built-in todos command on the /v1 route.${didYouMean} ` + "Run `todos --help` for the commands available here; verbs contributed by optional packages are local-only. " + "(This is not a connectivity, storage-mode or credential problem.)");
|
|
6455
|
+
}
|
|
6456
|
+
if (command && owner === "local-only") {
|
|
6457
|
+
throw new Error(`REMOTE_COMMAND_UNSUPPORTED: \`${command}\` is a local-only command and the Todos /v1 authority does not ` + "serve it; local SQLite fallback is disabled. Run `todos --help` to see the commands this route supports.");
|
|
6458
|
+
}
|
|
6459
|
+
if (!command || !commandSupportsRemote(invocation)) {
|
|
6460
|
+
const blame = command ? disqualifyingArgument(invocation) : null;
|
|
6461
|
+
const detail = blame ? `\`${command}\` is served by the Todos /v1 authority but ${blame.blame} is not; ${blame.remedy}` : `${invocationLabel(invocation)} is not supported by the Todos /v1 CLI; local SQLite fallback is disabled`;
|
|
6462
|
+
throw new Error(`REMOTE_COMMAND_UNSUPPORTED: ${detail}`);
|
|
6400
6463
|
}
|
|
6401
6464
|
}
|
|
6402
6465
|
function initializeTodosCliAuthority(args = process.argv.slice(2), env = process.env) {
|
|
@@ -6415,7 +6478,7 @@ function initializeTodosCliAuthority(args = process.argv.slice(2), env = process
|
|
|
6415
6478
|
}
|
|
6416
6479
|
return { route: "remote-http", v1_base_url: client.baseUrl };
|
|
6417
6480
|
}
|
|
6418
|
-
var REGISTERED_CANONICAL_COMMANDS, TODOS_CLI_COMMAND_ALIASES, DIAGNOSTIC_COMMANDS, REMOTE_COMMANDS, COMMAND_CAPABILITY_MATRIX, GLOBAL_OPTIONS_WITH_VALUES, GLOBAL_FLAGS, HELP_FLAGS, VERSION_FLAGS;
|
|
6481
|
+
var REGISTERED_CANONICAL_COMMANDS, TODOS_CLI_COMMAND_ALIASES, DIAGNOSTIC_COMMANDS, REMOTE_COMMANDS, COMMAND_CAPABILITY_MATRIX, GLOBAL_OPTIONS_WITH_VALUES, GLOBAL_FLAGS, HELP_FLAGS, VERSION_FLAGS, dropIt = (blame) => ({ blame, remedy: "re-run without it" });
|
|
6419
6482
|
var init_stage_a = __esm(() => {
|
|
6420
6483
|
init_esm();
|
|
6421
6484
|
init_cloud_router();
|
|
@@ -6594,6 +6657,8 @@ var init_stage_a = __esm(() => {
|
|
|
6594
6657
|
"template-history": ["templates-history"],
|
|
6595
6658
|
"agents-normalize": ["normalize-agents"],
|
|
6596
6659
|
"agent-update": ["agents-update"],
|
|
6660
|
+
done: ["complete"],
|
|
6661
|
+
init: ["register"],
|
|
6597
6662
|
upgrade: ["self-update"],
|
|
6598
6663
|
roadmaps: ["roadmap"],
|
|
6599
6664
|
"env-snapshot": ["environment-snapshot"],
|
|
@@ -8971,6 +9036,7 @@ function ensureSchema(db) {
|
|
|
8971
9036
|
ensureColumn("tasks", "reason", "TEXT");
|
|
8972
9037
|
ensureColumn("tasks", "spawned_from_session", "TEXT");
|
|
8973
9038
|
ensureColumn("tasks", "assigned_by", "TEXT");
|
|
9039
|
+
ensureColumn("tasks", "created_by", "TEXT");
|
|
8974
9040
|
ensureColumn("tasks", "assigned_from_project", "TEXT");
|
|
8975
9041
|
ensureColumn("tasks", "started_at", "TEXT");
|
|
8976
9042
|
ensureColumn("tasks", "task_type", "TEXT");
|
|
@@ -9133,6 +9199,8 @@ function ensureSchema(db) {
|
|
|
9133
9199
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_project ON project_sources(project_id)");
|
|
9134
9200
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_type ON project_sources(type)");
|
|
9135
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)");
|
|
9136
9204
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_source ON task_relationships(source_task_id)");
|
|
9137
9205
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_target ON task_relationships(target_task_id)");
|
|
9138
9206
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_rel_type ON task_relationships(relationship_type)");
|
|
@@ -15282,13 +15350,14 @@ function createTask(input, db) {
|
|
|
15282
15350
|
const timestamp2 = now();
|
|
15283
15351
|
const tags = input.tags || [];
|
|
15284
15352
|
const machineId = currentStorageMachineId(d);
|
|
15353
|
+
const createdBy = input.created_by || input.agent_id || null;
|
|
15285
15354
|
const assignedBy = input.assigned_by || input.agent_id;
|
|
15286
15355
|
const assignedFromProject = input.assigned_from_project || null;
|
|
15287
15356
|
let id = uuid();
|
|
15288
15357
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
15289
15358
|
try {
|
|
15290
|
-
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)
|
|
15291
|
-
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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
15292
15361
|
id,
|
|
15293
15362
|
null,
|
|
15294
15363
|
input.project_id || null,
|
|
@@ -15324,6 +15393,7 @@ function createTask(input, db) {
|
|
|
15324
15393
|
input.reason || null,
|
|
15325
15394
|
input.spawned_from_session || null,
|
|
15326
15395
|
assignedBy || null,
|
|
15396
|
+
createdBy,
|
|
15327
15397
|
assignedFromProject || null,
|
|
15328
15398
|
input.task_type || null,
|
|
15329
15399
|
machineId
|
|
@@ -15433,6 +15503,14 @@ function listTasks(filter = {}, db) {
|
|
|
15433
15503
|
conditions.push("agent_id = ?");
|
|
15434
15504
|
params.push(filter.agent_id);
|
|
15435
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
|
+
}
|
|
15436
15514
|
if (filter.session_id) {
|
|
15437
15515
|
conditions.push("session_id = ?");
|
|
15438
15516
|
params.push(filter.session_id);
|
|
@@ -15586,6 +15664,14 @@ function countTasks(filter = {}, db) {
|
|
|
15586
15664
|
conditions.push("agent_id = ?");
|
|
15587
15665
|
params.push(filter.agent_id);
|
|
15588
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
|
+
}
|
|
15589
15675
|
if (filter.session_id) {
|
|
15590
15676
|
conditions.push("session_id = ?");
|
|
15591
15677
|
params.push(filter.session_id);
|
|
@@ -18746,6 +18832,73 @@ var init_tasks = __esm(() => {
|
|
|
18746
18832
|
init_calendar();
|
|
18747
18833
|
});
|
|
18748
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
|
+
|
|
18749
18902
|
// src/cli/output-redaction.ts
|
|
18750
18903
|
function redactBroadTask(task) {
|
|
18751
18904
|
return redactValue(task);
|
|
@@ -18766,7 +18919,7 @@ __export(exports_task_routing, {
|
|
|
18766
18919
|
setTaskWorkflowPointers: () => setTaskWorkflowPointers,
|
|
18767
18920
|
getTaskRouteState: () => getTaskRouteState
|
|
18768
18921
|
});
|
|
18769
|
-
import { existsSync as
|
|
18922
|
+
import { existsSync as existsSync9, statSync as statSync3 } from "fs";
|
|
18770
18923
|
function machineLocalPath(project, db) {
|
|
18771
18924
|
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
18772
18925
|
if (!machineId)
|
|
@@ -18806,7 +18959,7 @@ function routeConcurrencyKey(task, project, taskList, projectPath) {
|
|
|
18806
18959
|
}
|
|
18807
18960
|
function directoryExists(path) {
|
|
18808
18961
|
try {
|
|
18809
|
-
return
|
|
18962
|
+
return existsSync9(path) && statSync3(path).isDirectory();
|
|
18810
18963
|
} catch {
|
|
18811
18964
|
return false;
|
|
18812
18965
|
}
|
|
@@ -19243,10 +19396,15 @@ function computeLocalReparent(current, opts) {
|
|
|
19243
19396
|
return patch;
|
|
19244
19397
|
}
|
|
19245
19398
|
function registerTaskCommands(program2) {
|
|
19246
|
-
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) => {
|
|
19247
19400
|
const globalOpts = program2.opts();
|
|
19248
19401
|
opts.tags = opts.tags || opts.tag;
|
|
19249
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
|
+
}
|
|
19250
19408
|
const cloud = getTodosCloudClient();
|
|
19251
19409
|
if (cloud) {
|
|
19252
19410
|
let task3;
|
|
@@ -19268,10 +19426,11 @@ function registerTaskCommands(program2) {
|
|
|
19268
19426
|
parent_id: opts.parent ? await resolveTaskIdForCommand(opts.parent, cloud) : undefined,
|
|
19269
19427
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
19270
19428
|
plan_id: cloudPlan?.id,
|
|
19271
|
-
assigned_to:
|
|
19429
|
+
assigned_to: assignee,
|
|
19272
19430
|
status: parseStatus(opts.status),
|
|
19273
19431
|
task_list_id: cloudTaskListId,
|
|
19274
|
-
agent_id: globalOpts.agent,
|
|
19432
|
+
agent_id: globalOpts.agent || creator.agent_id || undefined,
|
|
19433
|
+
created_by: creator.agent_id || undefined,
|
|
19275
19434
|
session_id: globalOpts.session,
|
|
19276
19435
|
project_id: cloudProjectId,
|
|
19277
19436
|
estimated_minutes: opts.estimated !== undefined ? parseIntOption(opts.estimated, "--estimated") : undefined,
|
|
@@ -19313,10 +19472,11 @@ function registerTaskCommands(program2) {
|
|
|
19313
19472
|
parent_id: opts.parent ? resolveTaskId(opts.parent) : undefined,
|
|
19314
19473
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
19315
19474
|
plan_id: opts.plan ? resolvePlanId(opts.plan) : undefined,
|
|
19316
|
-
assigned_to:
|
|
19475
|
+
assigned_to: assignee,
|
|
19317
19476
|
status: parseStatus(opts.status),
|
|
19318
19477
|
task_list_id: taskListId,
|
|
19319
|
-
agent_id: globalOpts.agent,
|
|
19478
|
+
agent_id: globalOpts.agent || creator.agent_id || undefined,
|
|
19479
|
+
created_by: creator.agent_id || undefined,
|
|
19320
19480
|
session_id: globalOpts.session,
|
|
19321
19481
|
project_id: projectId,
|
|
19322
19482
|
working_dir: process.cwd(),
|
|
@@ -19470,14 +19630,14 @@ function registerTaskCommands(program2) {
|
|
|
19470
19630
|
console.log(` ${chalk2.dim("Manifest:")} ${state.pointers.latest_manifest_path}`);
|
|
19471
19631
|
}
|
|
19472
19632
|
});
|
|
19473
|
-
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) => {
|
|
19474
19634
|
const globalOpts = program2.opts();
|
|
19475
19635
|
opts.tags = opts.tags || opts.tag;
|
|
19476
19636
|
opts.list = opts.list || opts.taskList;
|
|
19477
19637
|
const cloud = getTodosCloudClient();
|
|
19478
19638
|
const cloudProjectRef = globalOpts.project || opts.projectName;
|
|
19479
19639
|
const projectId = cloud && cloudProjectRef ? await cloudResolveProjectRef(cloud, cloudProjectRef) : cloud ? undefined : autoProject(globalOpts);
|
|
19480
|
-
const hasAssignedFilter = Boolean(opts.assigned || opts.agentName);
|
|
19640
|
+
const hasAssignedFilter = Boolean(opts.assigned || opts.agentName || opts.inbox);
|
|
19481
19641
|
const hasExplicitProjectFilter = Boolean(globalOpts.project || opts.projectName);
|
|
19482
19642
|
const allowedSortFields = new Set(["updated", "created", "priority", "status"]);
|
|
19483
19643
|
if (opts.sort && !allowedSortFields.has(opts.sort)) {
|
|
@@ -19510,6 +19670,19 @@ function registerTaskCommands(program2) {
|
|
|
19510
19670
|
filter["priority"] = opts.priority;
|
|
19511
19671
|
if (opts.assigned)
|
|
19512
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
|
+
}
|
|
19513
19686
|
if (opts.tags)
|
|
19514
19687
|
filter["tags"] = opts.tags.split(",").map((t) => t.trim());
|
|
19515
19688
|
if (opts.projectName && !cloud) {
|
|
@@ -19534,7 +19707,32 @@ function registerTaskCommands(program2) {
|
|
|
19534
19707
|
}
|
|
19535
19708
|
filter["limit"] = parsedLimit;
|
|
19536
19709
|
}
|
|
19537
|
-
|
|
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
|
+
}
|
|
19538
19736
|
if (opts.dueToday) {
|
|
19539
19737
|
const todayEnd = new Date;
|
|
19540
19738
|
todayEnd.setHours(23, 59, 59, 999);
|
|
@@ -20099,7 +20297,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
20099
20297
|
console.log(formatTaskLine(task2));
|
|
20100
20298
|
}
|
|
20101
20299
|
});
|
|
20102
|
-
program2.command("done <id>").description("Mark a task as completed").option("--attach-ids <ids>", "Comma-separated @hasna/attachments IDs to link as evidence").option("--files-changed <files>", "Comma-separated list of files changed").option("--test-results <results>", "Test results summary").option("--commit-hash <hash>", "Git commit hash").option("--notes <notes>", "Completion notes").option("--confidence <0-1>", "Agent's confidence 0.0-1.0 that the task is fully complete (default: 1.0, <0.7 flagged for review)").action(async (id, opts) => {
|
|
20300
|
+
program2.command("done <id>").alias("complete").description("Mark a task as completed (alias: complete)").option("--attach-ids <ids>", "Comma-separated @hasna/attachments IDs to link as evidence").option("--files-changed <files>", "Comma-separated list of files changed").option("--test-results <results>", "Test results summary").option("--commit-hash <hash>", "Git commit hash").option("--notes <notes>", "Completion notes").option("--confidence <0-1>", "Agent's confidence 0.0-1.0 that the task is fully complete (default: 1.0, <0.7 flagged for review)").action(async (id, opts) => {
|
|
20103
20301
|
const globalOpts = program2.opts();
|
|
20104
20302
|
const attachmentIds = opts.attachIds ? opts.attachIds.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
20105
20303
|
const filesChanged = opts.filesChanged ? opts.filesChanged.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
@@ -20418,14 +20616,15 @@ var init_task_commands = __esm(() => {
|
|
|
20418
20616
|
init_tasks();
|
|
20419
20617
|
init_task_lists();
|
|
20420
20618
|
init_cloud_router();
|
|
20619
|
+
init_creator_identity();
|
|
20421
20620
|
init_helpers();
|
|
20422
20621
|
init_output_redaction();
|
|
20423
20622
|
init_types();
|
|
20424
20623
|
});
|
|
20425
20624
|
|
|
20426
20625
|
// src/lib/plan-artifacts.ts
|
|
20427
|
-
import { existsSync as
|
|
20428
|
-
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";
|
|
20429
20628
|
function assertSafePathSegment(value, label) {
|
|
20430
20629
|
const trimmed = value.trim();
|
|
20431
20630
|
if (!trimmed || trimmed === "." || trimmed === ".." || trimmed.includes("/") || trimmed.includes("\\")) {
|
|
@@ -20486,7 +20685,7 @@ function resolvePlanArtifactPaths(input) {
|
|
|
20486
20685
|
const project = resolvePlanArtifactProject(input);
|
|
20487
20686
|
const projectId = assertSafePathSegment(project.id, "project id");
|
|
20488
20687
|
const projectRoot = resolve10(project.path);
|
|
20489
|
-
const directory =
|
|
20688
|
+
const directory = join8(projectRoot, ".hasna", "todos", "plans", projectId);
|
|
20490
20689
|
const planId = input.plan_id ? assertSafePathSegment(input.plan_id, "plan id") : null;
|
|
20491
20690
|
const planSlug = input.plan_slug ? assertSafePathSegment(slugify(input.plan_slug), "plan slug") : null;
|
|
20492
20691
|
const fileName = planId ? planSlug ? `${planSlug}--${planId.slice(0, 8)}.md` : `${planId}.md` : null;
|
|
@@ -20494,7 +20693,7 @@ function resolvePlanArtifactPaths(input) {
|
|
|
20494
20693
|
project_id: project.id,
|
|
20495
20694
|
project_root: projectRoot,
|
|
20496
20695
|
directory,
|
|
20497
|
-
file_path: fileName ?
|
|
20696
|
+
file_path: fileName ? join8(directory, fileName) : directory
|
|
20498
20697
|
};
|
|
20499
20698
|
}
|
|
20500
20699
|
function resolvePlanArtifactCandidatePaths(plan, db) {
|
|
@@ -20660,7 +20859,7 @@ function readPlanArtifact(plan, db) {
|
|
|
20660
20859
|
return null;
|
|
20661
20860
|
const d = db || getDatabase();
|
|
20662
20861
|
const paths = resolvePlanArtifactCandidatePaths(plan, d);
|
|
20663
|
-
const path =
|
|
20862
|
+
const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
20664
20863
|
if (!path)
|
|
20665
20864
|
return null;
|
|
20666
20865
|
const markdown = readFileSync4(path, "utf8");
|
|
@@ -20675,7 +20874,7 @@ function inspectPlanArtifact(plan, db) {
|
|
|
20675
20874
|
return null;
|
|
20676
20875
|
const d = db || getDatabase();
|
|
20677
20876
|
const paths = resolvePlanArtifactCandidatePaths(plan, d);
|
|
20678
|
-
const path =
|
|
20877
|
+
const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
20679
20878
|
if (!path) {
|
|
20680
20879
|
return {
|
|
20681
20880
|
path: paths.primary.file_path,
|
|
@@ -20755,7 +20954,7 @@ __export(exports_builtin_templates, {
|
|
|
20755
20954
|
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
20756
20955
|
});
|
|
20757
20956
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
20758
|
-
import { join as
|
|
20957
|
+
import { join as join9 } from "path";
|
|
20759
20958
|
function templateMetadata(template) {
|
|
20760
20959
|
return {
|
|
20761
20960
|
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
@@ -20814,7 +21013,7 @@ function writeBuiltinTemplateFiles(directory) {
|
|
|
20814
21013
|
mkdirSync7(directory, { recursive: true });
|
|
20815
21014
|
const files = [];
|
|
20816
21015
|
for (const entry of exportBuiltinTemplateFiles()) {
|
|
20817
|
-
const path =
|
|
21016
|
+
const path = join9(directory, entry.filename);
|
|
20818
21017
|
writeFileSync5(path, `${JSON.stringify(entry.template, null, 2)}
|
|
20819
21018
|
`, "utf-8");
|
|
20820
21019
|
files.push(path);
|
|
@@ -22586,16 +22785,16 @@ var init_saved_search_views = __esm(() => {
|
|
|
22586
22785
|
});
|
|
22587
22786
|
|
|
22588
22787
|
// src/lib/claude-tasks.ts
|
|
22589
|
-
import { existsSync as
|
|
22590
|
-
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";
|
|
22591
22790
|
function getTaskListDir(taskListId) {
|
|
22592
|
-
return
|
|
22791
|
+
return join10(HOME, ".claude", "tasks", taskListId);
|
|
22593
22792
|
}
|
|
22594
22793
|
function readClaudeTask(dir, filename) {
|
|
22595
|
-
return readJsonFile(
|
|
22794
|
+
return readJsonFile(join10(dir, filename));
|
|
22596
22795
|
}
|
|
22597
22796
|
function writeClaudeTask(dir, task) {
|
|
22598
|
-
writeJsonFile(
|
|
22797
|
+
writeJsonFile(join10(dir, `${task.id}.json`), task);
|
|
22599
22798
|
}
|
|
22600
22799
|
function toClaudeStatus(status) {
|
|
22601
22800
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -22607,14 +22806,14 @@ function toSqliteStatus(status) {
|
|
|
22607
22806
|
return status;
|
|
22608
22807
|
}
|
|
22609
22808
|
function readPrefixCounter(dir) {
|
|
22610
|
-
const path =
|
|
22611
|
-
if (!
|
|
22809
|
+
const path = join10(dir, ".prefix-counter");
|
|
22810
|
+
if (!existsSync11(path))
|
|
22612
22811
|
return 0;
|
|
22613
22812
|
const val = parseInt(readFileSync5(path, "utf-8").trim(), 10);
|
|
22614
22813
|
return isNaN(val) ? 0 : val;
|
|
22615
22814
|
}
|
|
22616
22815
|
function writePrefixCounter(dir, value) {
|
|
22617
|
-
writeFileSync6(
|
|
22816
|
+
writeFileSync6(join10(dir, ".prefix-counter"), String(value));
|
|
22618
22817
|
}
|
|
22619
22818
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
22620
22819
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -22641,7 +22840,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
22641
22840
|
}
|
|
22642
22841
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
22643
22842
|
const dir = getTaskListDir(taskListId);
|
|
22644
|
-
if (!
|
|
22843
|
+
if (!existsSync11(dir))
|
|
22645
22844
|
ensureDir(dir);
|
|
22646
22845
|
const filter = {};
|
|
22647
22846
|
if (projectId)
|
|
@@ -22650,7 +22849,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
22650
22849
|
const existingByTodosId = new Map;
|
|
22651
22850
|
const files = listJsonFiles(dir);
|
|
22652
22851
|
for (const f of files) {
|
|
22653
|
-
const path =
|
|
22852
|
+
const path = join10(dir, f);
|
|
22654
22853
|
const ct = readClaudeTask(dir, f);
|
|
22655
22854
|
if (ct?.metadata?.["todos_id"]) {
|
|
22656
22855
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -22737,7 +22936,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
22737
22936
|
}
|
|
22738
22937
|
function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
22739
22938
|
const dir = getTaskListDir(taskListId);
|
|
22740
|
-
if (!
|
|
22939
|
+
if (!existsSync11(dir)) {
|
|
22741
22940
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
22742
22941
|
}
|
|
22743
22942
|
const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -22757,7 +22956,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
22757
22956
|
}
|
|
22758
22957
|
for (const f of files) {
|
|
22759
22958
|
try {
|
|
22760
|
-
const filePath =
|
|
22959
|
+
const filePath = join10(dir, f);
|
|
22761
22960
|
const ct = readClaudeTask(dir, f);
|
|
22762
22961
|
if (!ct)
|
|
22763
22962
|
continue;
|
|
@@ -22830,20 +23029,20 @@ var init_claude_tasks = __esm(() => {
|
|
|
22830
23029
|
});
|
|
22831
23030
|
|
|
22832
23031
|
// src/lib/agent-tasks.ts
|
|
22833
|
-
import { existsSync as
|
|
22834
|
-
import { join as
|
|
23032
|
+
import { existsSync as existsSync12 } from "fs";
|
|
23033
|
+
import { join as join11 } from "path";
|
|
22835
23034
|
function agentBaseDir(agent) {
|
|
22836
23035
|
const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
|
|
22837
|
-
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");
|
|
22838
23037
|
}
|
|
22839
23038
|
function getTaskListDir2(agent, taskListId) {
|
|
22840
|
-
return
|
|
23039
|
+
return join11(agentBaseDir(agent), agent, taskListId);
|
|
22841
23040
|
}
|
|
22842
23041
|
function readAgentTask(dir, filename) {
|
|
22843
|
-
return readJsonFile(
|
|
23042
|
+
return readJsonFile(join11(dir, filename));
|
|
22844
23043
|
}
|
|
22845
23044
|
function writeAgentTask(dir, task) {
|
|
22846
|
-
writeJsonFile(
|
|
23045
|
+
writeJsonFile(join11(dir, `${task.id}.json`), task);
|
|
22847
23046
|
}
|
|
22848
23047
|
function taskToAgentTask(task, externalId, existingMeta) {
|
|
22849
23048
|
return {
|
|
@@ -22868,7 +23067,7 @@ function metadataKey(agent) {
|
|
|
22868
23067
|
}
|
|
22869
23068
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
22870
23069
|
const dir = getTaskListDir2(agent, taskListId);
|
|
22871
|
-
if (!
|
|
23070
|
+
if (!existsSync12(dir))
|
|
22872
23071
|
ensureDir(dir);
|
|
22873
23072
|
const filter = {};
|
|
22874
23073
|
if (projectId)
|
|
@@ -22877,7 +23076,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
22877
23076
|
const existingByTodosId = new Map;
|
|
22878
23077
|
const files = listJsonFiles(dir);
|
|
22879
23078
|
for (const f of files) {
|
|
22880
|
-
const path =
|
|
23079
|
+
const path = join11(dir, f);
|
|
22881
23080
|
const at = readAgentTask(dir, f);
|
|
22882
23081
|
if (at?.metadata?.["todos_id"]) {
|
|
22883
23082
|
existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -22951,7 +23150,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
22951
23150
|
}
|
|
22952
23151
|
function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
22953
23152
|
const dir = getTaskListDir2(agent, taskListId);
|
|
22954
|
-
if (!
|
|
23153
|
+
if (!existsSync12(dir)) {
|
|
22955
23154
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
22956
23155
|
}
|
|
22957
23156
|
const files = listJsonFiles(dir);
|
|
@@ -22970,7 +23169,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
22970
23169
|
}
|
|
22971
23170
|
for (const f of files) {
|
|
22972
23171
|
try {
|
|
22973
|
-
const filePath =
|
|
23172
|
+
const filePath = join11(dir, f);
|
|
22974
23173
|
const at = readAgentTask(dir, f);
|
|
22975
23174
|
if (!at)
|
|
22976
23175
|
continue;
|
|
@@ -23123,7 +23322,7 @@ __export(exports_project_bootstrap, {
|
|
|
23123
23322
|
discoverProjectWorkspace: () => discoverProjectWorkspace,
|
|
23124
23323
|
bootstrapProject: () => bootstrapProject
|
|
23125
23324
|
});
|
|
23126
|
-
import { existsSync as
|
|
23325
|
+
import { existsSync as existsSync13, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
23127
23326
|
import { basename as basename4, dirname as dirname6, resolve as resolve11 } from "path";
|
|
23128
23327
|
function safeStat(path) {
|
|
23129
23328
|
try {
|
|
@@ -23142,7 +23341,7 @@ function canonicalPath(input) {
|
|
|
23142
23341
|
function findUp(start, marker) {
|
|
23143
23342
|
let current = canonicalPath(start);
|
|
23144
23343
|
while (true) {
|
|
23145
|
-
if (
|
|
23344
|
+
if (existsSync13(resolve11(current, marker)))
|
|
23146
23345
|
return current;
|
|
23147
23346
|
const parent = dirname6(current);
|
|
23148
23347
|
if (parent === current)
|
|
@@ -23154,7 +23353,7 @@ function readPackageJson(path) {
|
|
|
23154
23353
|
if (!path)
|
|
23155
23354
|
return null;
|
|
23156
23355
|
const file = resolve11(path, "package.json");
|
|
23157
|
-
if (!
|
|
23356
|
+
if (!existsSync13(file))
|
|
23158
23357
|
return null;
|
|
23159
23358
|
try {
|
|
23160
23359
|
const parsed = JSON.parse(readFileSync6(file, "utf-8"));
|
|
@@ -23176,7 +23375,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
23176
23375
|
if (rootPackage?.workspaces)
|
|
23177
23376
|
markers.push("package.json#workspaces");
|
|
23178
23377
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
23179
|
-
if (
|
|
23378
|
+
if (existsSync13(resolve11(root, marker)))
|
|
23180
23379
|
markers.push(marker);
|
|
23181
23380
|
}
|
|
23182
23381
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -23510,9 +23709,9 @@ __export(exports_extract, {
|
|
|
23510
23709
|
buildCodebaseIndex: () => buildCodebaseIndex,
|
|
23511
23710
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
23512
23711
|
});
|
|
23513
|
-
import { existsSync as
|
|
23712
|
+
import { existsSync as existsSync14, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
23514
23713
|
import { createHash as createHash4 } from "crypto";
|
|
23515
|
-
import { relative as relative3, resolve as resolve12, join as
|
|
23714
|
+
import { relative as relative3, resolve as resolve12, join as join12 } from "path";
|
|
23516
23715
|
function stableHash(value) {
|
|
23517
23716
|
return createHash4("sha256").update(value).digest("hex");
|
|
23518
23717
|
}
|
|
@@ -23521,8 +23720,8 @@ function normalizePathForMatch(value) {
|
|
|
23521
23720
|
}
|
|
23522
23721
|
function readGitignorePatterns(basePath) {
|
|
23523
23722
|
const root = statSync5(basePath).isFile() ? resolve12(basePath, "..") : basePath;
|
|
23524
|
-
const gitignorePath =
|
|
23525
|
-
if (!
|
|
23723
|
+
const gitignorePath = join12(root, ".gitignore");
|
|
23724
|
+
if (!existsSync14(gitignorePath))
|
|
23526
23725
|
return [];
|
|
23527
23726
|
try {
|
|
23528
23727
|
return readFileSync7(gitignorePath, "utf-8").split(`
|
|
@@ -23664,7 +23863,7 @@ function buildCodebaseIndex(options) {
|
|
|
23664
23863
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
23665
23864
|
const indexed = [];
|
|
23666
23865
|
for (const file of files) {
|
|
23667
|
-
const fullPath = statSync5(basePath).isFile() ? basePath :
|
|
23866
|
+
const fullPath = statSync5(basePath).isFile() ? basePath : join12(basePath, file);
|
|
23668
23867
|
try {
|
|
23669
23868
|
const source = readFileSync7(fullPath, "utf-8");
|
|
23670
23869
|
const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
|
|
@@ -23695,7 +23894,7 @@ function extractTodos(options, db) {
|
|
|
23695
23894
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
23696
23895
|
const allComments = [];
|
|
23697
23896
|
for (const file of files) {
|
|
23698
|
-
const fullPath = statSync5(basePath).isFile() ? basePath :
|
|
23897
|
+
const fullPath = statSync5(basePath).isFile() ? basePath : join12(basePath, file);
|
|
23699
23898
|
try {
|
|
23700
23899
|
const source = readFileSync7(fullPath, "utf-8");
|
|
23701
23900
|
const relPath = statSync5(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
|
|
@@ -26746,8 +26945,16 @@ function resolveCloudAgentByNameOrId(agents, nameOrId) {
|
|
|
26746
26945
|
return null;
|
|
26747
26946
|
return matches.reduce((freshest, candidate) => new Date(candidate.last_seen_at).getTime() > new Date(freshest.last_seen_at).getTime() ? candidate : freshest);
|
|
26748
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
|
+
}
|
|
26749
26956
|
function registerAgentCommands(program2) {
|
|
26750
|
-
program2.command("init <name>").description("Register an
|
|
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) => {
|
|
26751
26958
|
const globalOpts = program2.opts();
|
|
26752
26959
|
try {
|
|
26753
26960
|
const cloud = getTodosCloudClient();
|
|
@@ -26757,14 +26964,25 @@ function registerAgentCommands(program2) {
|
|
|
26757
26964
|
console.error(chalk5.red("CONFLICT:"), result.message);
|
|
26758
26965
|
process.exit(1);
|
|
26759
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 } : {} });
|
|
26760
26978
|
if (globalOpts.json) {
|
|
26761
|
-
output(result, true);
|
|
26979
|
+
output({ ...result, identity_persisted: true }, true);
|
|
26762
26980
|
} else {
|
|
26763
26981
|
console.log(chalk5.green("Agent registered:"));
|
|
26764
26982
|
console.log(` ${chalk5.dim("ID:")} ${result.id}`);
|
|
26765
26983
|
console.log(` ${chalk5.dim("Name:")} ${result.name}`);
|
|
26766
26984
|
console.log(`
|
|
26767
|
-
|
|
26985
|
+
${chalk5.dim("Identity saved \u2014 later commands attribute to this agent automatically.")}`);
|
|
26768
26986
|
}
|
|
26769
26987
|
} catch (e) {
|
|
26770
26988
|
handleError(e);
|
|
@@ -26821,6 +27039,7 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
|
|
|
26821
27039
|
if (!result.released) {
|
|
26822
27040
|
handleError(new Error("Release denied: session_id does not match agent's current session."));
|
|
26823
27041
|
}
|
|
27042
|
+
clearIdentityIfMine(result.agent.id, result.agent.name);
|
|
26824
27043
|
if (globalOpts.json) {
|
|
26825
27044
|
console.log(JSON.stringify({ agent_id: result.agent.id, name: result.agent.name, released: true }));
|
|
26826
27045
|
} else {
|
|
@@ -26837,6 +27056,7 @@ Use ${chalk5.cyan(`--agent ${result.id}`)} on future commands.`);
|
|
|
26837
27056
|
if (!released) {
|
|
26838
27057
|
handleError(new Error("Release denied: session_id does not match agent's current session."));
|
|
26839
27058
|
}
|
|
27059
|
+
clearIdentityIfMine(a.id, a.name);
|
|
26840
27060
|
if (globalOpts.json) {
|
|
26841
27061
|
console.log(JSON.stringify({ agent_id: a.id, name: a.name, released: true }));
|
|
26842
27062
|
} else {
|
|
@@ -27177,6 +27397,7 @@ var init_agent_commands = __esm(() => {
|
|
|
27177
27397
|
init_task_lists();
|
|
27178
27398
|
init_tasks();
|
|
27179
27399
|
init_helpers();
|
|
27400
|
+
init_creator_identity();
|
|
27180
27401
|
init_cloud_router();
|
|
27181
27402
|
});
|
|
27182
27403
|
|
|
@@ -27219,8 +27440,8 @@ async function findFreePort(start) {
|
|
|
27219
27440
|
var DEFAULT_PORT = 19427;
|
|
27220
27441
|
|
|
27221
27442
|
// src/lib/db-backup.ts
|
|
27222
|
-
import { existsSync as
|
|
27223
|
-
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";
|
|
27224
27445
|
import { Database as Database2 } from "bun:sqlite";
|
|
27225
27446
|
function resolveDbPath(dbPath) {
|
|
27226
27447
|
if (dbPath)
|
|
@@ -27236,7 +27457,7 @@ function resolveDbPath(dbPath) {
|
|
|
27236
27457
|
}
|
|
27237
27458
|
function backupDatabase(outputPath, sourcePath) {
|
|
27238
27459
|
const source = resolveDbPath(sourcePath);
|
|
27239
|
-
if (!
|
|
27460
|
+
if (!existsSync15(source))
|
|
27240
27461
|
throw new Error(`Database not found: ${source}`);
|
|
27241
27462
|
mkdirSync8(dirname7(outputPath), { recursive: true });
|
|
27242
27463
|
closeDatabase();
|
|
@@ -27261,7 +27482,7 @@ function backupDatabase(outputPath, sourcePath) {
|
|
|
27261
27482
|
function checkDatabaseIntegrity(dbPath) {
|
|
27262
27483
|
const path = dbPath ? resolve14(dbPath) : resolveDbPath();
|
|
27263
27484
|
const errors = [];
|
|
27264
|
-
if (!
|
|
27485
|
+
if (!existsSync15(path)) {
|
|
27265
27486
|
return {
|
|
27266
27487
|
schema_version: DB_BACKUP_SCHEMA,
|
|
27267
27488
|
path,
|
|
@@ -27326,7 +27547,7 @@ function checkDatabaseIntegrity(dbPath) {
|
|
|
27326
27547
|
function defaultBackupPath(dbPath) {
|
|
27327
27548
|
const base = dbPath ? dirname7(resolve14(dbPath)) : dirname7(resolveDbPath());
|
|
27328
27549
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
27329
|
-
return
|
|
27550
|
+
return join13(base, "backups", `todos-${stamp}.db`);
|
|
27330
27551
|
}
|
|
27331
27552
|
var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
|
|
27332
27553
|
var init_db_backup = __esm(() => {
|
|
@@ -27343,7 +27564,7 @@ __export(exports_evidence_redaction, {
|
|
|
27343
27564
|
TODOS_EVIDENCE_REDACTION_CONFIRM: () => TODOS_EVIDENCE_REDACTION_CONFIRM
|
|
27344
27565
|
});
|
|
27345
27566
|
import { chmodSync } from "fs";
|
|
27346
|
-
import { join as
|
|
27567
|
+
import { join as join14 } from "path";
|
|
27347
27568
|
function unique4(values) {
|
|
27348
27569
|
return Array.from(new Set((values ?? []).map((value) => value.trim()).filter(Boolean)));
|
|
27349
27570
|
}
|
|
@@ -27690,7 +27911,7 @@ function redactEvidenceRows(options, db) {
|
|
|
27690
27911
|
}
|
|
27691
27912
|
function defaultEvidenceRedactionBackupPath(dbPath) {
|
|
27692
27913
|
const base = defaultBackupPath(dbPath);
|
|
27693
|
-
return
|
|
27914
|
+
return join14(base.replace(/\.db$/, ""), "pre-redaction.db");
|
|
27694
27915
|
}
|
|
27695
27916
|
var TODOS_EVIDENCE_REDACTION_SCHEMA = "todos.evidence_redaction.v1", TODOS_EVIDENCE_REDACTION_CONFIRM = "REDACT_TODOS_EVIDENCE";
|
|
27696
27917
|
var init_evidence_redaction = __esm(() => {
|
|
@@ -27706,7 +27927,7 @@ __export(exports_retention_cleanup, {
|
|
|
27706
27927
|
applyRetentionCleanup: () => applyRetentionCleanup,
|
|
27707
27928
|
RETENTION_CLEANUP_CONFIRMATION: () => RETENTION_CLEANUP_CONFIRMATION
|
|
27708
27929
|
});
|
|
27709
|
-
import { existsSync as
|
|
27930
|
+
import { existsSync as existsSync16, unlinkSync as unlinkSync2 } from "fs";
|
|
27710
27931
|
function normalizeScopes(scopes) {
|
|
27711
27932
|
if (!scopes || scopes.length === 0)
|
|
27712
27933
|
return [...ALL_SCOPES];
|
|
@@ -27909,7 +28130,7 @@ function applyRetentionCleanup(input, db) {
|
|
|
27909
28130
|
for (const artifact of report.candidates.artifact_files) {
|
|
27910
28131
|
try {
|
|
27911
28132
|
const path = artifactStorePath(artifact.relative_path);
|
|
27912
|
-
if (!
|
|
28133
|
+
if (!existsSync16(path)) {
|
|
27913
28134
|
report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
|
|
27914
28135
|
continue;
|
|
27915
28136
|
}
|
|
@@ -28585,8 +28806,8 @@ __export(exports_local_extensions, {
|
|
|
28585
28806
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
28586
28807
|
});
|
|
28587
28808
|
import { createHash as createHash6, createVerify } from "crypto";
|
|
28588
|
-
import { existsSync as
|
|
28589
|
-
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";
|
|
28590
28811
|
function isObject(value) {
|
|
28591
28812
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
28592
28813
|
}
|
|
@@ -28845,10 +29066,10 @@ function verifyExtensionSignature(input) {
|
|
|
28845
29066
|
}
|
|
28846
29067
|
function inspectExtensionSource(source2) {
|
|
28847
29068
|
const resolved = resolve15(source2);
|
|
28848
|
-
if (!
|
|
29069
|
+
if (!existsSync17(resolved))
|
|
28849
29070
|
throw new Error(`extension source not found: ${source2}`);
|
|
28850
29071
|
const stat = statSync7(resolved);
|
|
28851
|
-
const manifestPath = stat.isDirectory() ? [
|
|
29072
|
+
const manifestPath = stat.isDirectory() ? [join15(resolved, "todos.extension.json"), join15(resolved, "extension.json")].find(existsSync17) : resolved;
|
|
28852
29073
|
if (!manifestPath)
|
|
28853
29074
|
throw new Error(`extension directory ${source2} is missing todos.extension.json`);
|
|
28854
29075
|
const raw = readFileSync9(manifestPath);
|
|
@@ -28944,20 +29165,20 @@ function projectExtensionSources(projectPath) {
|
|
|
28944
29165
|
return [];
|
|
28945
29166
|
const root = resolve15(projectPath);
|
|
28946
29167
|
const candidates = [
|
|
28947
|
-
|
|
28948
|
-
|
|
29168
|
+
join15(root, "todos.extension.json"),
|
|
29169
|
+
join15(root, ".todos", "todos.extension.json")
|
|
28949
29170
|
];
|
|
28950
|
-
const extensionDir =
|
|
28951
|
-
if (
|
|
29171
|
+
const extensionDir = join15(root, ".todos", "extensions");
|
|
29172
|
+
if (existsSync17(extensionDir)) {
|
|
28952
29173
|
for (const entry of readdirSync3(extensionDir)) {
|
|
28953
29174
|
if (entry.startsWith("."))
|
|
28954
29175
|
continue;
|
|
28955
|
-
const full =
|
|
29176
|
+
const full = join15(extensionDir, entry);
|
|
28956
29177
|
if (statSync7(full).isDirectory() || entry.endsWith(".json"))
|
|
28957
29178
|
candidates.push(full);
|
|
28958
29179
|
}
|
|
28959
29180
|
}
|
|
28960
|
-
return candidates.filter(
|
|
29181
|
+
return candidates.filter(existsSync17);
|
|
28961
29182
|
}
|
|
28962
29183
|
function discoverLocalExtensions(options = {}) {
|
|
28963
29184
|
const config = loadConfig();
|
|
@@ -31045,6 +31266,10 @@ class PostgresJsonRecordStore {
|
|
|
31045
31266
|
conds.push(`payload->>'assigned_to' = ${p(filter.assigned_to)}`);
|
|
31046
31267
|
if (filter.agent_id !== undefined)
|
|
31047
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)}))`);
|
|
31048
31273
|
if (filter.session_id !== undefined)
|
|
31049
31274
|
conds.push(`payload->>'session_id' = ${p(filter.session_id)}`);
|
|
31050
31275
|
if (filter.tags?.length) {
|
|
@@ -31558,7 +31783,7 @@ async function createTask2(input, store, context) {
|
|
|
31558
31783
|
description: input.description ?? null,
|
|
31559
31784
|
status: input.status ?? "pending",
|
|
31560
31785
|
priority: input.priority ?? "medium",
|
|
31561
|
-
agent_id: input.agent_id ?? null,
|
|
31786
|
+
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
31562
31787
|
assigned_to: input.assigned_to ?? null,
|
|
31563
31788
|
session_id: input.session_id ?? context?.sessionId ?? null,
|
|
31564
31789
|
working_dir: input.working_dir ?? null,
|
|
@@ -31583,7 +31808,8 @@ async function createTask2(input, store, context) {
|
|
|
31583
31808
|
confidence: input.confidence ?? null,
|
|
31584
31809
|
reason: input.reason ?? null,
|
|
31585
31810
|
spawned_from_session: input.spawned_from_session ?? null,
|
|
31586
|
-
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,
|
|
31587
31813
|
assigned_from_project: input.assigned_from_project ?? null,
|
|
31588
31814
|
task_type: input.task_type ?? null,
|
|
31589
31815
|
cost_tokens: 0,
|
|
@@ -31620,7 +31846,8 @@ async function updateTask2(id, input, store) {
|
|
|
31620
31846
|
tags: input.tags ?? existing.tags,
|
|
31621
31847
|
metadata: input.metadata ?? existing.metadata,
|
|
31622
31848
|
requires_approval: input.requires_approval ?? existing.requires_approval,
|
|
31623
|
-
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
|
|
31624
31851
|
};
|
|
31625
31852
|
await store.upsert("tasks", task);
|
|
31626
31853
|
return task;
|
|
@@ -33348,8 +33575,8 @@ var exports_doctor = {};
|
|
|
33348
33575
|
__export(exports_doctor, {
|
|
33349
33576
|
runTodosDoctor: () => runTodosDoctor
|
|
33350
33577
|
});
|
|
33351
|
-
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as
|
|
33352
|
-
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";
|
|
33353
33580
|
function tableExists3(db, table) {
|
|
33354
33581
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
33355
33582
|
}
|
|
@@ -33443,7 +33670,7 @@ function findMissingProjectRoots(db) {
|
|
|
33443
33670
|
continue;
|
|
33444
33671
|
if (!row.path.startsWith("/"))
|
|
33445
33672
|
continue;
|
|
33446
|
-
if (!
|
|
33673
|
+
if (!existsSync18(row.path))
|
|
33447
33674
|
missing++;
|
|
33448
33675
|
}
|
|
33449
33676
|
return missing;
|
|
@@ -33503,16 +33730,16 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
33503
33730
|
function createBackup(dbPath) {
|
|
33504
33731
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
33505
33732
|
return;
|
|
33506
|
-
if (!
|
|
33733
|
+
if (!existsSync18(dbPath))
|
|
33507
33734
|
return;
|
|
33508
33735
|
const stamp = now().replace(/[:.]/g, "-");
|
|
33509
|
-
const backupDir =
|
|
33736
|
+
const backupDir = join16(dirname8(dbPath), `${basename7(dbPath)}.backup-${stamp}`);
|
|
33510
33737
|
const files = [];
|
|
33511
33738
|
mkdirSync9(backupDir, { recursive: true });
|
|
33512
33739
|
for (const source2 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
33513
|
-
if (!
|
|
33740
|
+
if (!existsSync18(source2))
|
|
33514
33741
|
continue;
|
|
33515
|
-
const target =
|
|
33742
|
+
const target = join16(backupDir, basename7(source2));
|
|
33516
33743
|
copyFileSync2(source2, target);
|
|
33517
33744
|
files.push(target);
|
|
33518
33745
|
}
|
|
@@ -33771,7 +33998,7 @@ var init_doctor = __esm(() => {
|
|
|
33771
33998
|
});
|
|
33772
33999
|
|
|
33773
34000
|
// src/server/routes.ts
|
|
33774
|
-
import { join as
|
|
34001
|
+
import { join as join17, resolve as resolve17, sep as sep4 } from "path";
|
|
33775
34002
|
function parseFieldsParam(url) {
|
|
33776
34003
|
const fieldsParam = url.searchParams.get("fields");
|
|
33777
34004
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -33952,11 +34179,15 @@ async function handleCreateTask(req, ctx, json2, taskToSummary2) {
|
|
|
33952
34179
|
const body = await req.json();
|
|
33953
34180
|
if (!body.title)
|
|
33954
34181
|
return json2({ error: "Missing 'title'" }, 400);
|
|
34182
|
+
const createdBy = body.created_by ?? body.agent_id ?? "dashboard";
|
|
33955
34183
|
const task = createTask({
|
|
33956
34184
|
title: body.title,
|
|
33957
34185
|
description: body.description,
|
|
33958
34186
|
priority: body.priority,
|
|
33959
|
-
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 } : {}
|
|
33960
34191
|
});
|
|
33961
34192
|
ctx.broadcastEvent({ type: "task", task_id: task.id, action: "created", agent_id: task.agent_id, project_id: task.project_id });
|
|
33962
34193
|
return json2(taskToSummary2(task), 201);
|
|
@@ -34562,7 +34793,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
34562
34793
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
34563
34794
|
return null;
|
|
34564
34795
|
if (path !== "/") {
|
|
34565
|
-
const filePath =
|
|
34796
|
+
const filePath = join17(ctx.dashboardDir, path);
|
|
34566
34797
|
const resolvedFile = resolve17(filePath);
|
|
34567
34798
|
const resolvedBase = resolve17(ctx.dashboardDir);
|
|
34568
34799
|
if (!resolvedFile.startsWith(resolvedBase + sep4) && resolvedFile !== resolvedBase) {
|
|
@@ -34572,7 +34803,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
34572
34803
|
if (res2)
|
|
34573
34804
|
return res2;
|
|
34574
34805
|
}
|
|
34575
|
-
const indexPath =
|
|
34806
|
+
const indexPath = join17(ctx.dashboardDir, "index.html");
|
|
34576
34807
|
const res = serveStaticFile2(indexPath);
|
|
34577
34808
|
if (res)
|
|
34578
34809
|
return res;
|
|
@@ -35060,7 +35291,11 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
|
|
|
35060
35291
|
sync: true
|
|
35061
35292
|
},
|
|
35062
35293
|
tasks: {
|
|
35063
|
-
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()),
|
|
35064
35299
|
get: (id) => getTask(id, database()),
|
|
35065
35300
|
resolveRef: (ref) => resolveTaskRefLocal(database(), ref),
|
|
35066
35301
|
list: (filter = {}) => listTasksMaybeSearch(filter, database()),
|
|
@@ -37676,6 +37911,8 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
37676
37911
|
...url.searchParams.get("task_list_id") ? { task_list_id: url.searchParams.get("task_list_id") } : {},
|
|
37677
37912
|
...url.searchParams.get("assigned_to") ? { assigned_to: url.searchParams.get("assigned_to") } : {},
|
|
37678
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") } : {},
|
|
37679
37916
|
...url.searchParams.get("tags") ? {
|
|
37680
37917
|
tags: url.searchParams.get("tags").split(",").map((tag) => tag.trim()).filter(Boolean)
|
|
37681
37918
|
} : {},
|
|
@@ -42921,6 +43158,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
42921
43158
|
project_id: exports_external.string().optional().describe("Project ID"),
|
|
42922
43159
|
task_list_id: exports_external.string().optional().describe("Task list ID"),
|
|
42923
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."),
|
|
42924
43163
|
depends_on: exports_external.array(exports_external.string()).optional().describe("Array of task IDs this task depends on"),
|
|
42925
43164
|
short_id: exports_external.string().nullable().optional().describe("Short ID (auto-generated if not provided, disabled if null)"),
|
|
42926
43165
|
tags: exports_external.array(exports_external.string()).optional().describe("Tags for the task"),
|
|
@@ -42931,12 +43170,18 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
42931
43170
|
retry_count: exports_external.number().optional().describe("Max retry count for agent failures")
|
|
42932
43171
|
}, async (params) => {
|
|
42933
43172
|
try {
|
|
42934
|
-
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);
|
|
42935
43176
|
const cloud = getTodosCloudClient();
|
|
42936
43177
|
if (cloud) {
|
|
42937
43178
|
const payload = { ...rest };
|
|
42938
|
-
if (
|
|
42939
|
-
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;
|
|
42940
43185
|
if (project_id)
|
|
42941
43186
|
payload.project_id = project_id;
|
|
42942
43187
|
if (task_list_id)
|
|
@@ -42957,8 +43202,12 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
42957
43202
|
return { content: [{ type: "text", text: mutationTaskResponse(created) }] };
|
|
42958
43203
|
}
|
|
42959
43204
|
const resolved = { ...rest };
|
|
42960
|
-
if (
|
|
42961
|
-
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);
|
|
42962
43211
|
if (project_id)
|
|
42963
43212
|
resolved.project_id = resolveId(project_id, "projects");
|
|
42964
43213
|
if (task_list_id)
|
|
@@ -43044,6 +43293,8 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
43044
43293
|
project_id: exports_external.string().optional().describe("Filter by project"),
|
|
43045
43294
|
task_list_id: exports_external.string().optional().describe("Filter by task list"),
|
|
43046
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.'),
|
|
43047
43298
|
tags: exports_external.array(exports_external.string()).optional().describe("Filter by tags (AND logic)"),
|
|
43048
43299
|
created_after: exports_external.string().optional().describe("ISO date \u2014 tasks created after this date"),
|
|
43049
43300
|
created_before: exports_external.string().optional().describe("ISO date \u2014 tasks created before this date"),
|
|
@@ -43236,6 +43487,7 @@ var init_task_crud2 = __esm(() => {
|
|
|
43236
43487
|
init_tasks();
|
|
43237
43488
|
init_types();
|
|
43238
43489
|
init_token_utils();
|
|
43490
|
+
init_creator_identity();
|
|
43239
43491
|
init_cloud_router();
|
|
43240
43492
|
});
|
|
43241
43493
|
|
|
@@ -43432,8 +43684,8 @@ var exports_mention_resolver = {};
|
|
|
43432
43684
|
__export(exports_mention_resolver, {
|
|
43433
43685
|
resolveMentions: () => resolveMentions
|
|
43434
43686
|
});
|
|
43435
|
-
import { existsSync as
|
|
43436
|
-
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";
|
|
43437
43689
|
function blankResolution(parsed) {
|
|
43438
43690
|
return {
|
|
43439
43691
|
input: parsed.input,
|
|
@@ -43531,7 +43783,7 @@ function resolveFile(parsed, workspace) {
|
|
|
43531
43783
|
return resolution;
|
|
43532
43784
|
}
|
|
43533
43785
|
resolution.path = relPath;
|
|
43534
|
-
if (!
|
|
43786
|
+
if (!existsSync19(absolutePath)) {
|
|
43535
43787
|
resolution.warnings.push("file does not exist in the local workspace");
|
|
43536
43788
|
return resolution;
|
|
43537
43789
|
}
|
|
@@ -43564,7 +43816,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
43564
43816
|
if (SKIP_DIRS2.has(entry2.name))
|
|
43565
43817
|
continue;
|
|
43566
43818
|
}
|
|
43567
|
-
const absolutePath =
|
|
43819
|
+
const absolutePath = join18(current, entry2.name);
|
|
43568
43820
|
if (entry2.isDirectory()) {
|
|
43569
43821
|
if (!SKIP_DIRS2.has(entry2.name))
|
|
43570
43822
|
walkSourceFiles(root, absolutePath, files);
|
|
@@ -46331,7 +46583,7 @@ __export(exports_release_compatibility, {
|
|
|
46331
46583
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
46332
46584
|
});
|
|
46333
46585
|
import { readFileSync as readFileSync11 } from "fs";
|
|
46334
|
-
import { join as
|
|
46586
|
+
import { join as join19, resolve as resolve19 } from "path";
|
|
46335
46587
|
import { Database as Database3 } from "bun:sqlite";
|
|
46336
46588
|
function pass(id, message, details) {
|
|
46337
46589
|
return { id, status: "passed", message, details };
|
|
@@ -46343,7 +46595,7 @@ function warn(id, message, details) {
|
|
|
46343
46595
|
return { id, status: "warning", message, details };
|
|
46344
46596
|
}
|
|
46345
46597
|
function readPackageJson2(root) {
|
|
46346
|
-
return JSON.parse(readFileSync11(
|
|
46598
|
+
return JSON.parse(readFileSync11(join19(root, "package.json"), "utf8"));
|
|
46347
46599
|
}
|
|
46348
46600
|
function sortedKeys(value) {
|
|
46349
46601
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -52286,7 +52538,7 @@ __export(exports_verification_providers, {
|
|
|
52286
52538
|
getVerificationRecord: () => getVerificationRecord,
|
|
52287
52539
|
discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
|
|
52288
52540
|
});
|
|
52289
|
-
import { existsSync as
|
|
52541
|
+
import { existsSync as existsSync20, readFileSync as readFileSync12 } from "fs";
|
|
52290
52542
|
function normalizeName6(name) {
|
|
52291
52543
|
const normalized = name.trim().toLowerCase();
|
|
52292
52544
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -52438,7 +52690,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
52438
52690
|
};
|
|
52439
52691
|
}
|
|
52440
52692
|
function runCiLogProvider(input) {
|
|
52441
|
-
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") : "");
|
|
52442
52694
|
return {
|
|
52443
52695
|
status: classifyLog(text),
|
|
52444
52696
|
attempts: 1,
|
|
@@ -52450,7 +52702,7 @@ function runBrowserProvider(input) {
|
|
|
52450
52702
|
if (!input.artifact_path) {
|
|
52451
52703
|
return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
|
|
52452
52704
|
}
|
|
52453
|
-
if (!
|
|
52705
|
+
if (!existsSync20(input.artifact_path)) {
|
|
52454
52706
|
return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
|
|
52455
52707
|
}
|
|
52456
52708
|
return {
|
|
@@ -55039,7 +55291,7 @@ __export(exports_onboarding_fixtures, {
|
|
|
55039
55291
|
TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION: () => TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION
|
|
55040
55292
|
});
|
|
55041
55293
|
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
55042
|
-
import { join as
|
|
55294
|
+
import { join as join20 } from "path";
|
|
55043
55295
|
function emptyData() {
|
|
55044
55296
|
return {
|
|
55045
55297
|
projects: [],
|
|
@@ -55107,6 +55359,7 @@ function task(input) {
|
|
|
55107
55359
|
reason: "Bundled deterministic onboarding fixture",
|
|
55108
55360
|
spawned_from_session: null,
|
|
55109
55361
|
assigned_by: null,
|
|
55362
|
+
created_by: null,
|
|
55110
55363
|
assigned_from_project: null,
|
|
55111
55364
|
task_type: "onboarding",
|
|
55112
55365
|
cost_tokens: 0,
|
|
@@ -55373,7 +55626,7 @@ function writeOnboardingFixtureFiles(directory) {
|
|
|
55373
55626
|
mkdirSync11(directory, { recursive: true });
|
|
55374
55627
|
const files = [];
|
|
55375
55628
|
for (const fixture of allFixtures()) {
|
|
55376
|
-
const path =
|
|
55629
|
+
const path = join20(directory, `${fixture.summary.name}.bridge.json`);
|
|
55377
55630
|
writeFileSync9(path, `${JSON.stringify(fixture.bundle, null, 2)}
|
|
55378
55631
|
`, "utf-8");
|
|
55379
55632
|
files.push(path);
|
|
@@ -61585,16 +61838,16 @@ __export(exports_environment_snapshots, {
|
|
|
61585
61838
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
61586
61839
|
});
|
|
61587
61840
|
import { createHash as createHash13 } from "crypto";
|
|
61588
|
-
import { existsSync as
|
|
61841
|
+
import { existsSync as existsSync21, readFileSync as readFileSync15, statSync as statSync10 } from "fs";
|
|
61589
61842
|
import { hostname as hostname2, platform, arch } from "os";
|
|
61590
|
-
import { dirname as dirname10, join as
|
|
61843
|
+
import { dirname as dirname10, join as join21, resolve as resolve21 } from "path";
|
|
61591
61844
|
import { tmpdir as tmpdir4 } from "os";
|
|
61592
61845
|
function sha2567(value) {
|
|
61593
61846
|
return createHash13("sha256").update(value).digest("hex");
|
|
61594
61847
|
}
|
|
61595
61848
|
function fileRecord(root, relativePath) {
|
|
61596
|
-
const path =
|
|
61597
|
-
if (!
|
|
61849
|
+
const path = join21(root, relativePath);
|
|
61850
|
+
if (!existsSync21(path))
|
|
61598
61851
|
return null;
|
|
61599
61852
|
const stat = statSync10(path);
|
|
61600
61853
|
if (!stat.isFile())
|
|
@@ -61606,7 +61859,7 @@ function manifestRecord(root, relativePath) {
|
|
|
61606
61859
|
const base = fileRecord(root, relativePath);
|
|
61607
61860
|
if (!base)
|
|
61608
61861
|
return null;
|
|
61609
|
-
const parsed = readJsonFile(
|
|
61862
|
+
const parsed = readJsonFile(join21(root, relativePath));
|
|
61610
61863
|
if (!parsed)
|
|
61611
61864
|
return { ...base, redacted: {} };
|
|
61612
61865
|
const redacted = redactValue({
|
|
@@ -61701,8 +61954,8 @@ function commandEnv(env, includeValues) {
|
|
|
61701
61954
|
function defaultSnapshotDir() {
|
|
61702
61955
|
const dbPath = getDatabasePath();
|
|
61703
61956
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
61704
|
-
return
|
|
61705
|
-
return
|
|
61957
|
+
return join21(tmpdir4(), "hasna-todos", "environment-snapshots");
|
|
61958
|
+
return join21(dirname10(resolve21(dbPath)), "environment-snapshots");
|
|
61706
61959
|
}
|
|
61707
61960
|
function snapshotWithId(snapshot) {
|
|
61708
61961
|
const digest = sha2567(JSON.stringify(snapshot)).slice(0, 24);
|
|
@@ -61749,7 +62002,7 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
61749
62002
|
});
|
|
61750
62003
|
}
|
|
61751
62004
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
61752
|
-
const path = outputPath ? resolve21(outputPath) :
|
|
62005
|
+
const path = outputPath ? resolve21(outputPath) : join21(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
61753
62006
|
ensureDir(dirname10(path));
|
|
61754
62007
|
writeJsonFile(path, snapshot);
|
|
61755
62008
|
return path;
|
|
@@ -62557,27 +62810,27 @@ __export(exports_serve, {
|
|
|
62557
62810
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
62558
62811
|
MIME_TYPES: () => MIME_TYPES
|
|
62559
62812
|
});
|
|
62560
|
-
import { existsSync as
|
|
62561
|
-
import { join as
|
|
62813
|
+
import { existsSync as existsSync22 } from "fs";
|
|
62814
|
+
import { join as join22, dirname as dirname11, extname } from "path";
|
|
62562
62815
|
import { fileURLToPath } from "url";
|
|
62563
62816
|
function resolveDashboardDir() {
|
|
62564
62817
|
const candidates = [];
|
|
62565
62818
|
try {
|
|
62566
62819
|
const scriptDir = dirname11(fileURLToPath(import.meta.url));
|
|
62567
|
-
candidates.push(
|
|
62568
|
-
candidates.push(
|
|
62820
|
+
candidates.push(join22(scriptDir, "..", "dashboard", "dist"));
|
|
62821
|
+
candidates.push(join22(scriptDir, "..", "..", "dashboard", "dist"));
|
|
62569
62822
|
} catch {}
|
|
62570
62823
|
if (process.argv[1]) {
|
|
62571
62824
|
const mainDir = dirname11(process.argv[1]);
|
|
62572
|
-
candidates.push(
|
|
62573
|
-
candidates.push(
|
|
62825
|
+
candidates.push(join22(mainDir, "..", "dashboard", "dist"));
|
|
62826
|
+
candidates.push(join22(mainDir, "..", "..", "dashboard", "dist"));
|
|
62574
62827
|
}
|
|
62575
|
-
candidates.push(
|
|
62828
|
+
candidates.push(join22(process.cwd(), "dashboard", "dist"));
|
|
62576
62829
|
for (const candidate of candidates) {
|
|
62577
|
-
if (
|
|
62830
|
+
if (existsSync22(candidate))
|
|
62578
62831
|
return candidate;
|
|
62579
62832
|
}
|
|
62580
|
-
return
|
|
62833
|
+
return join22(process.cwd(), "dashboard", "dist");
|
|
62581
62834
|
}
|
|
62582
62835
|
function getProvidedApiKey(req) {
|
|
62583
62836
|
const headerKey = req.headers.get("x-api-key");
|
|
@@ -62649,7 +62902,7 @@ function json(data, status = 200, headers) {
|
|
|
62649
62902
|
});
|
|
62650
62903
|
}
|
|
62651
62904
|
function serveStaticFile(filePath) {
|
|
62652
|
-
if (!
|
|
62905
|
+
if (!existsSync22(filePath))
|
|
62653
62906
|
return null;
|
|
62654
62907
|
const ext = extname(filePath);
|
|
62655
62908
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -62750,7 +63003,7 @@ data: ${data}
|
|
|
62750
63003
|
filteredSseClients.delete(client);
|
|
62751
63004
|
}
|
|
62752
63005
|
const dashboardDir = resolveDashboardDir();
|
|
62753
|
-
const dashboardExists =
|
|
63006
|
+
const dashboardExists = existsSync22(dashboardDir);
|
|
62754
63007
|
if (!dashboardExists) {
|
|
62755
63008
|
console.error(`
|
|
62756
63009
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -64623,12 +64876,12 @@ __export(exports_config_serve_commands, {
|
|
|
64623
64876
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
64624
64877
|
});
|
|
64625
64878
|
import chalk6 from "chalk";
|
|
64626
|
-
import { existsSync as
|
|
64627
|
-
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";
|
|
64628
64881
|
function registerConfigServeCommands(program2) {
|
|
64629
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) => {
|
|
64630
64883
|
const globalOpts = program2.opts();
|
|
64631
|
-
const configPath =
|
|
64884
|
+
const configPath = join23(getTodosGlobalDir(), "config.json");
|
|
64632
64885
|
if (opts.get) {
|
|
64633
64886
|
const config2 = loadConfig();
|
|
64634
64887
|
const keys = opts.get.split(".");
|
|
@@ -64665,7 +64918,7 @@ function registerConfigServeCommands(program2) {
|
|
|
64665
64918
|
}
|
|
64666
64919
|
obj[keys[keys.length - 1]] = parsedValue;
|
|
64667
64920
|
const dir = dirname12(configPath);
|
|
64668
|
-
if (!
|
|
64921
|
+
if (!existsSync23(dir))
|
|
64669
64922
|
mkdirSync12(dir, { recursive: true });
|
|
64670
64923
|
writeFileSync10(configPath, JSON.stringify(config2, null, 2));
|
|
64671
64924
|
if (globalOpts.json) {
|
|
@@ -65845,8 +66098,8 @@ __export(exports_routing_doctor, {
|
|
|
65845
66098
|
classifyTaskRouting: () => classifyTaskRouting,
|
|
65846
66099
|
TODOS_ROUTING_DOCTOR_SCHEMA_VERSION: () => TODOS_ROUTING_DOCTOR_SCHEMA_VERSION
|
|
65847
66100
|
});
|
|
65848
|
-
import { copyFileSync as copyFileSync3, existsSync as
|
|
65849
|
-
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";
|
|
65850
66103
|
function normalizePath5(path) {
|
|
65851
66104
|
if (!path)
|
|
65852
66105
|
return null;
|
|
@@ -66148,16 +66401,16 @@ function evaluateTaskRouting(ctx) {
|
|
|
66148
66401
|
function createBackup2(dbPath, generatedAt) {
|
|
66149
66402
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
66150
66403
|
return;
|
|
66151
|
-
if (!
|
|
66404
|
+
if (!existsSync24(dbPath))
|
|
66152
66405
|
return;
|
|
66153
66406
|
const stamp = generatedAt.replace(/[:.]/g, "-");
|
|
66154
|
-
const backupDir =
|
|
66407
|
+
const backupDir = join24(dirname13(dbPath), `${basename9(dbPath)}.routing-doctor-backup-${stamp}`);
|
|
66155
66408
|
const files = [];
|
|
66156
66409
|
mkdirSync13(backupDir, { recursive: true });
|
|
66157
66410
|
for (const source3 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
66158
|
-
if (!
|
|
66411
|
+
if (!existsSync24(source3))
|
|
66159
66412
|
continue;
|
|
66160
|
-
const target =
|
|
66413
|
+
const target = join24(backupDir, basename9(source3));
|
|
66161
66414
|
copyFileSync3(source3, target);
|
|
66162
66415
|
files.push(target);
|
|
66163
66416
|
}
|
|
@@ -66261,7 +66514,7 @@ function runRoutingDoctor(options = {}) {
|
|
|
66261
66514
|
}
|
|
66262
66515
|
const applied = repairs.filter((r) => r.applied);
|
|
66263
66516
|
if (applied.length > 0) {
|
|
66264
|
-
const undoPath = options.undoRecordPath ??
|
|
66517
|
+
const undoPath = options.undoRecordPath ?? join24(process.cwd(), `todos-routing-doctor-undo-${generatedAt.replace(/[:.]/g, "-")}.json`);
|
|
66265
66518
|
const undoRecord = {
|
|
66266
66519
|
schema_version: TODOS_ROUTING_DOCTOR_SCHEMA_VERSION,
|
|
66267
66520
|
purpose: "Undo record for routing-doctor --apply. Restore each field with the prior value below.",
|
|
@@ -66360,8 +66613,8 @@ __export(exports_task_route_sources, {
|
|
|
66360
66613
|
});
|
|
66361
66614
|
import { Database as Database4 } from "bun:sqlite";
|
|
66362
66615
|
import { createHash as createHash14 } from "crypto";
|
|
66363
|
-
import { existsSync as
|
|
66364
|
-
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";
|
|
66365
66618
|
function normalizePath6(input) {
|
|
66366
66619
|
return resolve22(input);
|
|
66367
66620
|
}
|
|
@@ -66425,8 +66678,8 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
66425
66678
|
const rootPath = normalizePath6(sourceRoot);
|
|
66426
66679
|
const errors2 = [];
|
|
66427
66680
|
const stores = [];
|
|
66428
|
-
if (!
|
|
66429
|
-
const ref = createStoreRef(
|
|
66681
|
+
if (!existsSync25(rootPath)) {
|
|
66682
|
+
const ref = createStoreRef(join25(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
66430
66683
|
errors2.push({
|
|
66431
66684
|
...ref,
|
|
66432
66685
|
code: "SOURCE_ROOT_MISSING",
|
|
@@ -66438,7 +66691,7 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
66438
66691
|
try {
|
|
66439
66692
|
rootStat = statSync11(rootPath);
|
|
66440
66693
|
} catch (error2) {
|
|
66441
|
-
const ref = createStoreRef(
|
|
66694
|
+
const ref = createStoreRef(join25(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
66442
66695
|
errors2.push({
|
|
66443
66696
|
...ref,
|
|
66444
66697
|
code: "SOURCE_ROOT_UNREADABLE",
|
|
@@ -66451,8 +66704,8 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
66451
66704
|
return { stores, errors: errors2 };
|
|
66452
66705
|
}
|
|
66453
66706
|
function scanDirectory(dir, depth) {
|
|
66454
|
-
const candidate =
|
|
66455
|
-
if (
|
|
66707
|
+
const candidate = join25(dir, TODO_STORE_RELATIVE_PATH);
|
|
66708
|
+
if (existsSync25(candidate)) {
|
|
66456
66709
|
stores.push(createStoreRef(candidate));
|
|
66457
66710
|
}
|
|
66458
66711
|
if (depth >= ROOT_SCAN_MAX_DEPTH)
|
|
@@ -66472,7 +66725,7 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
66472
66725
|
for (const entry2 of entries) {
|
|
66473
66726
|
if (!entry2.isDirectory() || SKIPPED_SCAN_DIRS.has(entry2.name))
|
|
66474
66727
|
continue;
|
|
66475
|
-
scanDirectory(
|
|
66728
|
+
scanDirectory(join25(dir, entry2.name), depth + 1);
|
|
66476
66729
|
}
|
|
66477
66730
|
}
|
|
66478
66731
|
scanDirectory(rootPath, 0);
|
|
@@ -66498,7 +66751,7 @@ function collectStoreRefs(input) {
|
|
|
66498
66751
|
};
|
|
66499
66752
|
}
|
|
66500
66753
|
function openReadonlyStore(ref) {
|
|
66501
|
-
if (!
|
|
66754
|
+
if (!existsSync25(ref.source_db_path)) {
|
|
66502
66755
|
throw Object.assign(new Error(`Store does not exist: ${ref.source_db_path}`), { code: "STORE_MISSING" });
|
|
66503
66756
|
}
|
|
66504
66757
|
return new Database4(ref.source_db_path, { readonly: true, create: false });
|
|
@@ -66672,7 +66925,7 @@ var init_task_route_sources = __esm(() => {
|
|
|
66672
66925
|
init_task_crud();
|
|
66673
66926
|
init_redaction();
|
|
66674
66927
|
init_task_routing();
|
|
66675
|
-
TODO_STORE_RELATIVE_PATH =
|
|
66928
|
+
TODO_STORE_RELATIVE_PATH = join25(".hasna", "todos", "todos.db");
|
|
66676
66929
|
SKIPPED_SCAN_DIRS = new Set([
|
|
66677
66930
|
".git",
|
|
66678
66931
|
".hg",
|
|
@@ -68181,9 +68434,9 @@ Findings`));
|
|
|
68181
68434
|
const db = getDatabase();
|
|
68182
68435
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
68183
68436
|
const { statSync: statSync12 } = await import("fs");
|
|
68184
|
-
const { join:
|
|
68437
|
+
const { join: join26 } = await import("path");
|
|
68185
68438
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
68186
|
-
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");
|
|
68187
68440
|
let size = "unknown";
|
|
68188
68441
|
try {
|
|
68189
68442
|
size = `${(statSync12(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
@@ -70097,21 +70350,21 @@ __export(exports_mcp_hooks_commands, {
|
|
|
70097
70350
|
});
|
|
70098
70351
|
import chalk9 from "chalk";
|
|
70099
70352
|
import { execSync as execSync3 } from "child_process";
|
|
70100
|
-
import { existsSync as
|
|
70101
|
-
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";
|
|
70102
70355
|
function getMcpBinaryPath() {
|
|
70103
70356
|
try {
|
|
70104
70357
|
const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
|
|
70105
70358
|
if (p)
|
|
70106
70359
|
return p;
|
|
70107
70360
|
} catch {}
|
|
70108
|
-
const bunBin =
|
|
70109
|
-
if (
|
|
70361
|
+
const bunBin = join26(HOME2, ".bun", "bin", "todos-mcp");
|
|
70362
|
+
if (existsSync26(bunBin))
|
|
70110
70363
|
return bunBin;
|
|
70111
70364
|
return "todos-mcp";
|
|
70112
70365
|
}
|
|
70113
70366
|
function readJsonFile2(path) {
|
|
70114
|
-
if (!
|
|
70367
|
+
if (!existsSync26(path))
|
|
70115
70368
|
return {};
|
|
70116
70369
|
try {
|
|
70117
70370
|
return JSON.parse(readFileSync18(path, "utf-8"));
|
|
@@ -70121,19 +70374,19 @@ function readJsonFile2(path) {
|
|
|
70121
70374
|
}
|
|
70122
70375
|
function writeJsonFile2(path, data) {
|
|
70123
70376
|
const dir = dirname15(path);
|
|
70124
|
-
if (!
|
|
70377
|
+
if (!existsSync26(dir))
|
|
70125
70378
|
mkdirSync14(dir, { recursive: true });
|
|
70126
70379
|
writeFileSync13(path, JSON.stringify(data, null, 2) + `
|
|
70127
70380
|
`);
|
|
70128
70381
|
}
|
|
70129
70382
|
function readTomlFile(path) {
|
|
70130
|
-
if (!
|
|
70383
|
+
if (!existsSync26(path))
|
|
70131
70384
|
return "";
|
|
70132
70385
|
return readFileSync18(path, "utf-8");
|
|
70133
70386
|
}
|
|
70134
70387
|
function writeTomlFile(path, content) {
|
|
70135
70388
|
const dir = dirname15(path);
|
|
70136
|
-
if (!
|
|
70389
|
+
if (!existsSync26(dir))
|
|
70137
70390
|
mkdirSync14(dir, { recursive: true });
|
|
70138
70391
|
writeFileSync13(path, content);
|
|
70139
70392
|
}
|
|
@@ -70198,7 +70451,7 @@ function unregisterClaude(_global) {
|
|
|
70198
70451
|
}
|
|
70199
70452
|
}
|
|
70200
70453
|
function registerCodex(binPath) {
|
|
70201
|
-
const configPath =
|
|
70454
|
+
const configPath = join26(HOME2, ".codex", "config.toml");
|
|
70202
70455
|
let content = readTomlFile(configPath);
|
|
70203
70456
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
70204
70457
|
const block = `
|
|
@@ -70212,7 +70465,7 @@ args = ["--stdio"]
|
|
|
70212
70465
|
console.log(chalk9.green(`Codex CLI: registered in ${configPath}`));
|
|
70213
70466
|
}
|
|
70214
70467
|
function unregisterCodex() {
|
|
70215
|
-
const configPath =
|
|
70468
|
+
const configPath = join26(HOME2, ".codex", "config.toml");
|
|
70216
70469
|
let content = readTomlFile(configPath);
|
|
70217
70470
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
70218
70471
|
console.log(chalk9.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -70224,7 +70477,7 @@ function unregisterCodex() {
|
|
|
70224
70477
|
console.log(chalk9.green(`Codex CLI: unregistered from ${configPath}`));
|
|
70225
70478
|
}
|
|
70226
70479
|
function registerGemini(binPath) {
|
|
70227
|
-
const configPath =
|
|
70480
|
+
const configPath = join26(HOME2, ".gemini", "settings.json");
|
|
70228
70481
|
const config = readJsonFile2(configPath);
|
|
70229
70482
|
if (!config["mcpServers"]) {
|
|
70230
70483
|
config["mcpServers"] = {};
|
|
@@ -70238,7 +70491,7 @@ function registerGemini(binPath) {
|
|
|
70238
70491
|
console.log(chalk9.green(`Gemini CLI: registered in ${configPath}`));
|
|
70239
70492
|
}
|
|
70240
70493
|
function unregisterGemini() {
|
|
70241
|
-
const configPath =
|
|
70494
|
+
const configPath = join26(HOME2, ".gemini", "settings.json");
|
|
70242
70495
|
const config = readJsonFile2(configPath);
|
|
70243
70496
|
const servers = config["mcpServers"];
|
|
70244
70497
|
if (!servers || !("todos" in servers)) {
|
|
@@ -70249,8 +70502,37 @@ function unregisterGemini() {
|
|
|
70249
70502
|
writeJsonFile2(configPath, config);
|
|
70250
70503
|
console.log(chalk9.green(`Gemini CLI: unregistered from ${configPath}`));
|
|
70251
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
|
+
}
|
|
70252
70534
|
function registerMcp(agent, global) {
|
|
70253
|
-
const agents = agent === "all" ? ["claude", "codex", "gemini"] : [agent];
|
|
70535
|
+
const agents = agent === "all" ? ["claude", "codex", "gemini", "cursor"] : [agent];
|
|
70254
70536
|
const binPath = getMcpBinaryPath();
|
|
70255
70537
|
for (const a of agents) {
|
|
70256
70538
|
switch (a) {
|
|
@@ -70263,13 +70545,16 @@ function registerMcp(agent, global) {
|
|
|
70263
70545
|
case "gemini":
|
|
70264
70546
|
registerGemini(binPath);
|
|
70265
70547
|
break;
|
|
70548
|
+
case "cursor":
|
|
70549
|
+
registerCursor(binPath, global);
|
|
70550
|
+
break;
|
|
70266
70551
|
default:
|
|
70267
|
-
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`));
|
|
70268
70553
|
}
|
|
70269
70554
|
}
|
|
70270
70555
|
}
|
|
70271
70556
|
function unregisterMcp(agent, global) {
|
|
70272
|
-
const agents = agent === "all" ? ["claude", "codex", "gemini"] : [agent];
|
|
70557
|
+
const agents = agent === "all" ? ["claude", "codex", "gemini", "cursor"] : [agent];
|
|
70273
70558
|
for (const a of agents) {
|
|
70274
70559
|
switch (a) {
|
|
70275
70560
|
case "claude":
|
|
@@ -70281,8 +70566,11 @@ function unregisterMcp(agent, global) {
|
|
|
70281
70566
|
case "gemini":
|
|
70282
70567
|
unregisterGemini();
|
|
70283
70568
|
break;
|
|
70569
|
+
case "cursor":
|
|
70570
|
+
unregisterCursor(global);
|
|
70571
|
+
break;
|
|
70284
70572
|
default:
|
|
70285
|
-
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`));
|
|
70286
70574
|
}
|
|
70287
70575
|
}
|
|
70288
70576
|
}
|
|
@@ -70295,8 +70583,8 @@ function registerMcpHooksCommands(program2) {
|
|
|
70295
70583
|
if (p)
|
|
70296
70584
|
todosBin = p;
|
|
70297
70585
|
} catch {}
|
|
70298
|
-
const hooksDir =
|
|
70299
|
-
if (!
|
|
70586
|
+
const hooksDir = join26(process.cwd(), ".claude", "hooks");
|
|
70587
|
+
if (!existsSync26(hooksDir))
|
|
70300
70588
|
mkdirSync14(hooksDir, { recursive: true });
|
|
70301
70589
|
const hookScript = `#!/usr/bin/env bash
|
|
70302
70590
|
# Auto-generated by: todos hooks install
|
|
@@ -70321,11 +70609,11 @@ esac
|
|
|
70321
70609
|
|
|
70322
70610
|
exit 0
|
|
70323
70611
|
`;
|
|
70324
|
-
const hookPath =
|
|
70612
|
+
const hookPath = join26(hooksDir, "todos-sync.sh");
|
|
70325
70613
|
writeFileSync13(hookPath, hookScript);
|
|
70326
70614
|
execSync3(`chmod +x "${hookPath}"`);
|
|
70327
70615
|
console.log(chalk9.green(`Hook script created: ${hookPath}`));
|
|
70328
|
-
const settingsPath =
|
|
70616
|
+
const settingsPath = join26(process.cwd(), ".claude", "settings.json");
|
|
70329
70617
|
const settings = readJsonFile2(settingsPath);
|
|
70330
70618
|
if (!settings["hooks"]) {
|
|
70331
70619
|
settings["hooks"] = {};
|
|
@@ -70354,7 +70642,7 @@ exit 0
|
|
|
70354
70642
|
console.log(chalk9.green(`Claude Code hooks configured in: ${settingsPath}`));
|
|
70355
70643
|
console.log(chalk9.dim("Task list ID auto-detected from project."));
|
|
70356
70644
|
});
|
|
70357
|
-
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) => {
|
|
70358
70646
|
if (opts.register) {
|
|
70359
70647
|
registerMcp(opts.register, opts.global);
|
|
70360
70648
|
return;
|
|
@@ -71256,7 +71544,7 @@ Artifacts:`));
|
|
|
71256
71544
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
71257
71545
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
71258
71546
|
const marker = "# todos-auto-link";
|
|
71259
|
-
if (
|
|
71547
|
+
if (existsSync26(hookPath)) {
|
|
71260
71548
|
const existing = readFileSync18(hookPath, "utf-8");
|
|
71261
71549
|
if (existing.includes(marker)) {
|
|
71262
71550
|
console.log(chalk9.yellow("Hook already installed."));
|
|
@@ -71283,7 +71571,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
71283
71571
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
71284
71572
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
71285
71573
|
const marker = "# todos-auto-link";
|
|
71286
|
-
if (!
|
|
71574
|
+
if (!existsSync26(hookPath)) {
|
|
71287
71575
|
console.log(chalk9.dim("No post-commit hook found."));
|
|
71288
71576
|
return;
|
|
71289
71577
|
}
|
|
@@ -71469,7 +71757,7 @@ import chalk11 from "chalk";
|
|
|
71469
71757
|
import { execSync as execSync4 } from "child_process";
|
|
71470
71758
|
import { readFileSync as readFileSync19, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
71471
71759
|
import { tmpdir as tmpdir5 } from "os";
|
|
71472
|
-
import { join as
|
|
71760
|
+
import { join as join27 } from "path";
|
|
71473
71761
|
function getOrCreateLocalMachineName() {
|
|
71474
71762
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
71475
71763
|
}
|
|
@@ -71507,7 +71795,7 @@ function remoteTempPath(sshAddress) {
|
|
|
71507
71795
|
}
|
|
71508
71796
|
function readRemoteBridgeBundle(sshAddress) {
|
|
71509
71797
|
const remotePath = remoteTempPath(sshAddress);
|
|
71510
|
-
const localPath =
|
|
71798
|
+
const localPath = join27(tmpdir5(), `todos-bridge-pull-${uuid()}.json`);
|
|
71511
71799
|
try {
|
|
71512
71800
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
71513
71801
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
@@ -71522,7 +71810,7 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
71522
71810
|
}
|
|
71523
71811
|
}
|
|
71524
71812
|
function writeLocalBridgeBundle() {
|
|
71525
|
-
const localPath =
|
|
71813
|
+
const localPath = join27(tmpdir5(), `todos-bridge-push-${uuid()}.json`);
|
|
71526
71814
|
writeFileSync14(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
|
|
71527
71815
|
return localPath;
|
|
71528
71816
|
}
|
|
@@ -76097,7 +76385,7 @@ __export(exports_sdk_integration_fixtures, {
|
|
|
76097
76385
|
TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT: () => TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT
|
|
76098
76386
|
});
|
|
76099
76387
|
import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
|
|
76100
|
-
import { join as
|
|
76388
|
+
import { join as join28 } from "path";
|
|
76101
76389
|
function source5(version) {
|
|
76102
76390
|
return {
|
|
76103
76391
|
packageName: "@hasna/todos",
|
|
@@ -76204,7 +76492,7 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
|
|
|
76204
76492
|
];
|
|
76205
76493
|
const written = [];
|
|
76206
76494
|
for (const [name, payload] of files) {
|
|
76207
|
-
const file =
|
|
76495
|
+
const file = join28(directory, name);
|
|
76208
76496
|
writeFileSync15(file, `${JSON.stringify(payload, null, 2)}
|
|
76209
76497
|
`, "utf-8");
|
|
76210
76498
|
written.push(file);
|