@hasna/todos 0.10.22 → 0.11.2
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/brains.d.ts +3 -0
- package/dist/cli/brains.d.ts.map +1 -0
- package/dist/cli/index.js +1402 -552
- package/dist/db/budgets.d.ts +27 -0
- package/dist/db/budgets.d.ts.map +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/snapshots.d.ts +37 -0
- package/dist/db/snapshots.d.ts.map +1 -0
- package/dist/db/tasks.d.ts +26 -0
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/db/traces.d.ts +38 -0
- package/dist/db/traces.d.ts.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +481 -44
- package/dist/lib/gatherer.d.ts +16 -0
- package/dist/lib/gatherer.d.ts.map +1 -0
- package/dist/lib/model-config.d.ts +14 -0
- package/dist/lib/model-config.d.ts.map +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +550 -18
- package/dist/server/index.js +141 -17
- package/dist/types/index.d.ts +16 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -2347,6 +2347,15 @@ function ensureSchema(db) {
|
|
|
2347
2347
|
ensureColumn("tasks", "assigned_by", "TEXT");
|
|
2348
2348
|
ensureColumn("tasks", "assigned_from_project", "TEXT");
|
|
2349
2349
|
ensureColumn("tasks", "started_at", "TEXT");
|
|
2350
|
+
ensureColumn("tasks", "task_type", "TEXT");
|
|
2351
|
+
ensureColumn("tasks", "cost_tokens", "INTEGER DEFAULT 0");
|
|
2352
|
+
ensureColumn("tasks", "cost_usd", "REAL DEFAULT 0");
|
|
2353
|
+
ensureColumn("tasks", "delegated_from", "TEXT");
|
|
2354
|
+
ensureColumn("tasks", "delegation_depth", "INTEGER DEFAULT 0");
|
|
2355
|
+
ensureColumn("tasks", "retry_count", "INTEGER DEFAULT 0");
|
|
2356
|
+
ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
|
|
2357
|
+
ensureColumn("tasks", "retry_after", "TEXT");
|
|
2358
|
+
ensureColumn("tasks", "sla_minutes", "INTEGER");
|
|
2350
2359
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
2351
2360
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
2352
2361
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -2960,6 +2969,58 @@ var init_database = __esm(() => {
|
|
|
2960
2969
|
`
|
|
2961
2970
|
ALTER TABLE tasks ADD COLUMN task_type TEXT;
|
|
2962
2971
|
CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type);
|
|
2972
|
+
ALTER TABLE tasks ADD COLUMN cost_tokens INTEGER DEFAULT 0;
|
|
2973
|
+
ALTER TABLE tasks ADD COLUMN cost_usd REAL DEFAULT 0;
|
|
2974
|
+
ALTER TABLE tasks ADD COLUMN delegated_from TEXT;
|
|
2975
|
+
ALTER TABLE tasks ADD COLUMN delegation_depth INTEGER DEFAULT 0;
|
|
2976
|
+
ALTER TABLE tasks ADD COLUMN retry_count INTEGER DEFAULT 0;
|
|
2977
|
+
ALTER TABLE tasks ADD COLUMN max_retries INTEGER DEFAULT 3;
|
|
2978
|
+
ALTER TABLE tasks ADD COLUMN retry_after TEXT;
|
|
2979
|
+
ALTER TABLE tasks ADD COLUMN sla_minutes INTEGER;
|
|
2980
|
+
|
|
2981
|
+
CREATE TABLE IF NOT EXISTS task_traces (
|
|
2982
|
+
id TEXT PRIMARY KEY,
|
|
2983
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
2984
|
+
agent_id TEXT,
|
|
2985
|
+
trace_type TEXT NOT NULL CHECK(trace_type IN ('tool_call','llm_call','error','handoff','custom')),
|
|
2986
|
+
name TEXT,
|
|
2987
|
+
input_summary TEXT,
|
|
2988
|
+
output_summary TEXT,
|
|
2989
|
+
duration_ms INTEGER,
|
|
2990
|
+
tokens INTEGER,
|
|
2991
|
+
cost_usd REAL,
|
|
2992
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2993
|
+
);
|
|
2994
|
+
CREATE INDEX IF NOT EXISTS idx_task_traces_task ON task_traces(task_id);
|
|
2995
|
+
CREATE INDEX IF NOT EXISTS idx_task_traces_agent ON task_traces(agent_id);
|
|
2996
|
+
|
|
2997
|
+
CREATE TABLE IF NOT EXISTS context_snapshots (
|
|
2998
|
+
id TEXT PRIMARY KEY,
|
|
2999
|
+
agent_id TEXT,
|
|
3000
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
3001
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
3002
|
+
snapshot_type TEXT NOT NULL CHECK(snapshot_type IN ('interrupt','complete','handoff','checkpoint')),
|
|
3003
|
+
plan_summary TEXT,
|
|
3004
|
+
files_open TEXT DEFAULT '[]',
|
|
3005
|
+
attempts TEXT DEFAULT '[]',
|
|
3006
|
+
blockers TEXT DEFAULT '[]',
|
|
3007
|
+
next_steps TEXT,
|
|
3008
|
+
metadata TEXT DEFAULT '{}',
|
|
3009
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3010
|
+
);
|
|
3011
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_agent ON context_snapshots(agent_id);
|
|
3012
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_task ON context_snapshots(task_id);
|
|
3013
|
+
|
|
3014
|
+
CREATE TABLE IF NOT EXISTS agent_budgets (
|
|
3015
|
+
agent_id TEXT PRIMARY KEY,
|
|
3016
|
+
max_concurrent INTEGER DEFAULT 5,
|
|
3017
|
+
max_cost_usd REAL,
|
|
3018
|
+
max_task_minutes INTEGER,
|
|
3019
|
+
period_hours INTEGER DEFAULT 24,
|
|
3020
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3021
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3022
|
+
);
|
|
3023
|
+
|
|
2963
3024
|
INSERT OR IGNORE INTO _migrations (id) VALUES (35);
|
|
2964
3025
|
`
|
|
2965
3026
|
];
|
|
@@ -3755,12 +3816,14 @@ var exports_tasks = {};
|
|
|
3755
3816
|
__export(exports_tasks, {
|
|
3756
3817
|
updateTask: () => updateTask,
|
|
3757
3818
|
unlockTask: () => unlockTask,
|
|
3819
|
+
stealTask: () => stealTask,
|
|
3758
3820
|
startTask: () => startTask,
|
|
3759
3821
|
setTaskStatus: () => setTaskStatus,
|
|
3760
3822
|
setTaskPriority: () => setTaskPriority,
|
|
3761
3823
|
removeDependency: () => removeDependency,
|
|
3762
3824
|
redistributeStaleTasks: () => redistributeStaleTasks,
|
|
3763
3825
|
moveTask: () => moveTask,
|
|
3826
|
+
logCost: () => logCost,
|
|
3764
3827
|
lockTask: () => lockTask,
|
|
3765
3828
|
listTasks: () => listTasks,
|
|
3766
3829
|
getTasksChangedSince: () => getTasksChangedSince,
|
|
@@ -3783,6 +3846,7 @@ __export(exports_tasks, {
|
|
|
3783
3846
|
countTasks: () => countTasks,
|
|
3784
3847
|
completeTask: () => completeTask,
|
|
3785
3848
|
cloneTask: () => cloneTask,
|
|
3849
|
+
claimOrSteal: () => claimOrSteal,
|
|
3786
3850
|
claimNextTask: () => claimNextTask,
|
|
3787
3851
|
bulkUpdateTasks: () => bulkUpdateTasks,
|
|
3788
3852
|
bulkCreateTasks: () => bulkCreateTasks,
|
|
@@ -4264,6 +4328,20 @@ function completeTask(id, agentId, db, options) {
|
|
|
4264
4328
|
if (spawnedFromTemplate) {
|
|
4265
4329
|
meta._spawned_task = { id: spawnedFromTemplate.id, short_id: spawnedFromTemplate.short_id, title: spawnedFromTemplate.title };
|
|
4266
4330
|
}
|
|
4331
|
+
const unblockedDeps = d.query(`SELECT DISTINCT t.id, t.short_id, t.title FROM tasks t
|
|
4332
|
+
JOIN task_dependencies td ON td.task_id = t.id
|
|
4333
|
+
WHERE td.depends_on = ? AND t.status = 'pending'
|
|
4334
|
+
AND NOT EXISTS (
|
|
4335
|
+
SELECT 1 FROM task_dependencies td2
|
|
4336
|
+
JOIN tasks dep2 ON dep2.id = td2.depends_on
|
|
4337
|
+
WHERE td2.task_id = t.id AND dep2.status NOT IN ('completed', 'cancelled') AND dep2.id != ?
|
|
4338
|
+
)`).all(id, id);
|
|
4339
|
+
if (unblockedDeps.length > 0) {
|
|
4340
|
+
meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
|
|
4341
|
+
for (const dep of unblockedDeps) {
|
|
4342
|
+
dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4267
4345
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
4268
4346
|
}
|
|
4269
4347
|
function lockTask(id, agentId, db) {
|
|
@@ -4560,24 +4638,36 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
4560
4638
|
};
|
|
4561
4639
|
let retryTask;
|
|
4562
4640
|
if (options?.retry) {
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4641
|
+
const retryCount = (task.retry_count || 0) + 1;
|
|
4642
|
+
const maxRetries = task.max_retries || 3;
|
|
4643
|
+
if (retryCount > maxRetries) {
|
|
4644
|
+
d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [
|
|
4645
|
+
JSON.stringify({ ...meta, _retry_exhausted: { retry_count: retryCount - 1, max_retries: maxRetries } }),
|
|
4646
|
+
id
|
|
4647
|
+
]);
|
|
4648
|
+
} else {
|
|
4649
|
+
const backoffMinutes = Math.pow(5, retryCount - 1);
|
|
4650
|
+
const retryAfter = options.retry_after || new Date(Date.now() + backoffMinutes * 60 * 1000).toISOString();
|
|
4651
|
+
let title = task.title;
|
|
4652
|
+
if (task.short_id && title.startsWith(task.short_id + ": ")) {
|
|
4653
|
+
title = title.slice(task.short_id.length + 2);
|
|
4654
|
+
}
|
|
4655
|
+
retryTask = createTask({
|
|
4656
|
+
title,
|
|
4657
|
+
description: task.description ?? undefined,
|
|
4658
|
+
priority: task.priority,
|
|
4659
|
+
project_id: task.project_id ?? undefined,
|
|
4660
|
+
task_list_id: task.task_list_id ?? undefined,
|
|
4661
|
+
plan_id: task.plan_id ?? undefined,
|
|
4662
|
+
assigned_to: task.assigned_to ?? undefined,
|
|
4663
|
+
tags: task.tags,
|
|
4664
|
+
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_count: retryCount, max_retries: maxRetries, retry_after: retryAfter, failure_reason: reason } },
|
|
4665
|
+
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
4666
|
+
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
4667
|
+
due_at: retryAfter
|
|
4668
|
+
}, d);
|
|
4669
|
+
d.run("UPDATE tasks SET retry_count = ?, max_retries = ?, retry_after = ? WHERE id = ?", [retryCount, maxRetries, retryAfter, retryTask.id]);
|
|
4566
4670
|
}
|
|
4567
|
-
retryTask = createTask({
|
|
4568
|
-
title,
|
|
4569
|
-
description: task.description ?? undefined,
|
|
4570
|
-
priority: task.priority,
|
|
4571
|
-
project_id: task.project_id ?? undefined,
|
|
4572
|
-
task_list_id: task.task_list_id ?? undefined,
|
|
4573
|
-
plan_id: task.plan_id ?? undefined,
|
|
4574
|
-
assigned_to: task.assigned_to ?? undefined,
|
|
4575
|
-
tags: task.tags,
|
|
4576
|
-
metadata: { ...task.metadata, _retry: { original_id: task.id, retry_after: options.retry_after || null, failure_reason: reason } },
|
|
4577
|
-
estimated_minutes: task.estimated_minutes ?? undefined,
|
|
4578
|
-
recurrence_rule: task.recurrence_rule ?? undefined,
|
|
4579
|
-
due_at: options.retry_after || task.due_at || undefined
|
|
4580
|
-
}, d);
|
|
4581
4671
|
}
|
|
4582
4672
|
return { task: failedTask, retryTask };
|
|
4583
4673
|
}
|
|
@@ -4601,6 +4691,40 @@ function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
|
4601
4691
|
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
4602
4692
|
return rows.map(rowToTask);
|
|
4603
4693
|
}
|
|
4694
|
+
function logCost(taskId, tokens, usd, db) {
|
|
4695
|
+
const d = db || getDatabase();
|
|
4696
|
+
d.run("UPDATE tasks SET cost_tokens = cost_tokens + ?, cost_usd = cost_usd + ?, updated_at = ? WHERE id = ?", [tokens, usd, now(), taskId]);
|
|
4697
|
+
}
|
|
4698
|
+
function stealTask(agentId, opts, db) {
|
|
4699
|
+
const d = db || getDatabase();
|
|
4700
|
+
const staleMinutes = opts?.stale_minutes ?? 30;
|
|
4701
|
+
const staleTasks = getStaleTasks(staleMinutes, { project_id: opts?.project_id, task_list_id: opts?.task_list_id }, d);
|
|
4702
|
+
if (staleTasks.length === 0)
|
|
4703
|
+
return null;
|
|
4704
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
4705
|
+
staleTasks.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9));
|
|
4706
|
+
const target = staleTasks[0];
|
|
4707
|
+
const timestamp = now();
|
|
4708
|
+
d.run(`UPDATE tasks SET assigned_to = ?, locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1 WHERE id = ?`, [agentId, agentId, timestamp, timestamp, target.id]);
|
|
4709
|
+
logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
|
|
4710
|
+
dispatchWebhook("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
|
|
4711
|
+
return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
|
|
4712
|
+
}
|
|
4713
|
+
function claimOrSteal(agentId, filters, db) {
|
|
4714
|
+
const d = db || getDatabase();
|
|
4715
|
+
const tx = d.transaction(() => {
|
|
4716
|
+
const next = getNextTask(agentId, filters, d);
|
|
4717
|
+
if (next) {
|
|
4718
|
+
const started = startTask(next.id, agentId, d);
|
|
4719
|
+
return { task: started, stolen: false };
|
|
4720
|
+
}
|
|
4721
|
+
const stolen = stealTask(agentId, { stale_minutes: filters?.stale_minutes, project_id: filters?.project_id, task_list_id: filters?.task_list_id }, d);
|
|
4722
|
+
if (stolen)
|
|
4723
|
+
return { task: stolen, stolen: true };
|
|
4724
|
+
return null;
|
|
4725
|
+
});
|
|
4726
|
+
return tx();
|
|
4727
|
+
}
|
|
4604
4728
|
function getStatus(filters, agentId, options, db) {
|
|
4605
4729
|
const d = db || getDatabase();
|
|
4606
4730
|
const pending = countTasks({ ...filters, status: "pending" }, d);
|
|
@@ -6199,8 +6323,8 @@ __export(exports_extract, {
|
|
|
6199
6323
|
extractFromSource: () => extractFromSource,
|
|
6200
6324
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
6201
6325
|
});
|
|
6202
|
-
import { readFileSync as
|
|
6203
|
-
import { relative, resolve as resolve2, join as
|
|
6326
|
+
import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
6327
|
+
import { relative, resolve as resolve2, join as join8 } from "path";
|
|
6204
6328
|
function tagToPriority(tag) {
|
|
6205
6329
|
switch (tag) {
|
|
6206
6330
|
case "BUG":
|
|
@@ -6272,9 +6396,9 @@ function extractTodos(options, db) {
|
|
|
6272
6396
|
const files = collectFiles(basePath, extensions);
|
|
6273
6397
|
const allComments = [];
|
|
6274
6398
|
for (const file of files) {
|
|
6275
|
-
const fullPath = statSync2(basePath).isFile() ? basePath :
|
|
6399
|
+
const fullPath = statSync2(basePath).isFile() ? basePath : join8(basePath, file);
|
|
6276
6400
|
try {
|
|
6277
|
-
const source =
|
|
6401
|
+
const source = readFileSync4(fullPath, "utf-8");
|
|
6278
6402
|
const relPath = statSync2(basePath).isFile() ? relative(resolve2(basePath, ".."), fullPath) : file;
|
|
6279
6403
|
const comments = extractFromSource(source, relPath, tags);
|
|
6280
6404
|
allComments.push(...comments);
|
|
@@ -10363,6 +10487,219 @@ var init_zod = __esm(() => {
|
|
|
10363
10487
|
init_external();
|
|
10364
10488
|
});
|
|
10365
10489
|
|
|
10490
|
+
// src/db/traces.ts
|
|
10491
|
+
var exports_traces = {};
|
|
10492
|
+
__export(exports_traces, {
|
|
10493
|
+
logTrace: () => logTrace,
|
|
10494
|
+
getTraceStats: () => getTraceStats,
|
|
10495
|
+
getTaskTraces: () => getTaskTraces
|
|
10496
|
+
});
|
|
10497
|
+
function logTrace(input, db) {
|
|
10498
|
+
const d = db || getDatabase();
|
|
10499
|
+
const id = uuid();
|
|
10500
|
+
const timestamp = now();
|
|
10501
|
+
d.run(`INSERT INTO task_traces (id, task_id, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd, created_at)
|
|
10502
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10503
|
+
id,
|
|
10504
|
+
input.task_id,
|
|
10505
|
+
input.agent_id || null,
|
|
10506
|
+
input.trace_type,
|
|
10507
|
+
input.name || null,
|
|
10508
|
+
input.input_summary || null,
|
|
10509
|
+
input.output_summary || null,
|
|
10510
|
+
input.duration_ms ?? null,
|
|
10511
|
+
input.tokens ?? null,
|
|
10512
|
+
input.cost_usd ?? null,
|
|
10513
|
+
timestamp
|
|
10514
|
+
]);
|
|
10515
|
+
return {
|
|
10516
|
+
id,
|
|
10517
|
+
task_id: input.task_id,
|
|
10518
|
+
agent_id: input.agent_id || null,
|
|
10519
|
+
trace_type: input.trace_type,
|
|
10520
|
+
name: input.name || null,
|
|
10521
|
+
input_summary: input.input_summary || null,
|
|
10522
|
+
output_summary: input.output_summary || null,
|
|
10523
|
+
duration_ms: input.duration_ms ?? null,
|
|
10524
|
+
tokens: input.tokens ?? null,
|
|
10525
|
+
cost_usd: input.cost_usd ?? null,
|
|
10526
|
+
created_at: timestamp
|
|
10527
|
+
};
|
|
10528
|
+
}
|
|
10529
|
+
function getTaskTraces(taskId, db) {
|
|
10530
|
+
const d = db || getDatabase();
|
|
10531
|
+
return d.query("SELECT * FROM task_traces WHERE task_id = ? ORDER BY created_at DESC").all(taskId);
|
|
10532
|
+
}
|
|
10533
|
+
function getTraceStats(taskId, db) {
|
|
10534
|
+
const d = db || getDatabase();
|
|
10535
|
+
const row = d.query(`SELECT COUNT(*) as total,
|
|
10536
|
+
SUM(CASE WHEN trace_type = 'tool_call' THEN 1 ELSE 0 END) as tool_calls,
|
|
10537
|
+
SUM(CASE WHEN trace_type = 'llm_call' THEN 1 ELSE 0 END) as llm_calls,
|
|
10538
|
+
SUM(CASE WHEN trace_type = 'error' THEN 1 ELSE 0 END) as errors,
|
|
10539
|
+
COALESCE(SUM(tokens), 0) as total_tokens,
|
|
10540
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd,
|
|
10541
|
+
COALESCE(SUM(duration_ms), 0) as total_duration_ms
|
|
10542
|
+
FROM task_traces WHERE task_id = ?`).get(taskId);
|
|
10543
|
+
return row;
|
|
10544
|
+
}
|
|
10545
|
+
var init_traces = __esm(() => {
|
|
10546
|
+
init_database();
|
|
10547
|
+
});
|
|
10548
|
+
|
|
10549
|
+
// src/db/snapshots.ts
|
|
10550
|
+
var exports_snapshots = {};
|
|
10551
|
+
__export(exports_snapshots, {
|
|
10552
|
+
saveSnapshot: () => saveSnapshot,
|
|
10553
|
+
listSnapshots: () => listSnapshots,
|
|
10554
|
+
getLatestSnapshot: () => getLatestSnapshot
|
|
10555
|
+
});
|
|
10556
|
+
function rowToSnapshot(row) {
|
|
10557
|
+
return {
|
|
10558
|
+
...row,
|
|
10559
|
+
snapshot_type: row.snapshot_type,
|
|
10560
|
+
files_open: JSON.parse(row.files_open || "[]"),
|
|
10561
|
+
attempts: JSON.parse(row.attempts || "[]"),
|
|
10562
|
+
blockers: JSON.parse(row.blockers || "[]"),
|
|
10563
|
+
metadata: JSON.parse(row.metadata || "{}")
|
|
10564
|
+
};
|
|
10565
|
+
}
|
|
10566
|
+
function saveSnapshot(input, db) {
|
|
10567
|
+
const d = db || getDatabase();
|
|
10568
|
+
const id = uuid();
|
|
10569
|
+
const timestamp = now();
|
|
10570
|
+
d.run(`INSERT INTO context_snapshots (id, agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps, metadata, created_at)
|
|
10571
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10572
|
+
id,
|
|
10573
|
+
input.agent_id || null,
|
|
10574
|
+
input.task_id || null,
|
|
10575
|
+
input.project_id || null,
|
|
10576
|
+
input.snapshot_type,
|
|
10577
|
+
input.plan_summary || null,
|
|
10578
|
+
JSON.stringify(input.files_open || []),
|
|
10579
|
+
JSON.stringify(input.attempts || []),
|
|
10580
|
+
JSON.stringify(input.blockers || []),
|
|
10581
|
+
input.next_steps || null,
|
|
10582
|
+
JSON.stringify(input.metadata || {}),
|
|
10583
|
+
timestamp
|
|
10584
|
+
]);
|
|
10585
|
+
return {
|
|
10586
|
+
id,
|
|
10587
|
+
agent_id: input.agent_id || null,
|
|
10588
|
+
task_id: input.task_id || null,
|
|
10589
|
+
project_id: input.project_id || null,
|
|
10590
|
+
snapshot_type: input.snapshot_type,
|
|
10591
|
+
plan_summary: input.plan_summary || null,
|
|
10592
|
+
files_open: input.files_open || [],
|
|
10593
|
+
attempts: input.attempts || [],
|
|
10594
|
+
blockers: input.blockers || [],
|
|
10595
|
+
next_steps: input.next_steps || null,
|
|
10596
|
+
metadata: input.metadata || {},
|
|
10597
|
+
created_at: timestamp
|
|
10598
|
+
};
|
|
10599
|
+
}
|
|
10600
|
+
function getLatestSnapshot(agentId, taskId, db) {
|
|
10601
|
+
const d = db || getDatabase();
|
|
10602
|
+
const conditions = [];
|
|
10603
|
+
const params = [];
|
|
10604
|
+
if (agentId) {
|
|
10605
|
+
conditions.push("agent_id = ?");
|
|
10606
|
+
params.push(agentId);
|
|
10607
|
+
}
|
|
10608
|
+
if (taskId) {
|
|
10609
|
+
conditions.push("task_id = ?");
|
|
10610
|
+
params.push(taskId);
|
|
10611
|
+
}
|
|
10612
|
+
if (conditions.length === 0)
|
|
10613
|
+
return null;
|
|
10614
|
+
const where = conditions.join(" AND ");
|
|
10615
|
+
const row = d.query(`SELECT * FROM context_snapshots WHERE ${where} ORDER BY created_at DESC LIMIT 1`).get(...params);
|
|
10616
|
+
return row ? rowToSnapshot(row) : null;
|
|
10617
|
+
}
|
|
10618
|
+
function listSnapshots(opts, db) {
|
|
10619
|
+
const d = db || getDatabase();
|
|
10620
|
+
const conditions = [];
|
|
10621
|
+
const params = [];
|
|
10622
|
+
if (opts.agent_id) {
|
|
10623
|
+
conditions.push("agent_id = ?");
|
|
10624
|
+
params.push(opts.agent_id);
|
|
10625
|
+
}
|
|
10626
|
+
if (opts.task_id) {
|
|
10627
|
+
conditions.push("task_id = ?");
|
|
10628
|
+
params.push(opts.task_id);
|
|
10629
|
+
}
|
|
10630
|
+
if (opts.project_id) {
|
|
10631
|
+
conditions.push("project_id = ?");
|
|
10632
|
+
params.push(opts.project_id);
|
|
10633
|
+
}
|
|
10634
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
10635
|
+
const limit = opts.limit || 20;
|
|
10636
|
+
params.push(limit);
|
|
10637
|
+
return d.query(`SELECT * FROM context_snapshots ${where} ORDER BY created_at DESC LIMIT ?`).all(...params).map(rowToSnapshot);
|
|
10638
|
+
}
|
|
10639
|
+
var init_snapshots = __esm(() => {
|
|
10640
|
+
init_database();
|
|
10641
|
+
});
|
|
10642
|
+
|
|
10643
|
+
// src/db/budgets.ts
|
|
10644
|
+
var exports_budgets = {};
|
|
10645
|
+
__export(exports_budgets, {
|
|
10646
|
+
setBudget: () => setBudget,
|
|
10647
|
+
getBudget: () => getBudget,
|
|
10648
|
+
checkBudget: () => checkBudget
|
|
10649
|
+
});
|
|
10650
|
+
function setBudget(agentId, opts, db) {
|
|
10651
|
+
const d = db || getDatabase();
|
|
10652
|
+
const timestamp = now();
|
|
10653
|
+
d.run(`INSERT INTO agent_budgets (agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours, created_at, updated_at)
|
|
10654
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
10655
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
10656
|
+
max_concurrent = COALESCE(?, max_concurrent),
|
|
10657
|
+
max_cost_usd = COALESCE(?, max_cost_usd),
|
|
10658
|
+
max_task_minutes = COALESCE(?, max_task_minutes),
|
|
10659
|
+
period_hours = COALESCE(?, period_hours),
|
|
10660
|
+
updated_at = ?`, [
|
|
10661
|
+
agentId,
|
|
10662
|
+
opts.max_concurrent ?? 5,
|
|
10663
|
+
opts.max_cost_usd ?? null,
|
|
10664
|
+
opts.max_task_minutes ?? null,
|
|
10665
|
+
opts.period_hours ?? 24,
|
|
10666
|
+
timestamp,
|
|
10667
|
+
timestamp,
|
|
10668
|
+
opts.max_concurrent ?? null,
|
|
10669
|
+
opts.max_cost_usd ?? null,
|
|
10670
|
+
opts.max_task_minutes ?? null,
|
|
10671
|
+
opts.period_hours ?? null,
|
|
10672
|
+
timestamp
|
|
10673
|
+
]);
|
|
10674
|
+
return getBudget(agentId, d);
|
|
10675
|
+
}
|
|
10676
|
+
function getBudget(agentId, db) {
|
|
10677
|
+
const d = db || getDatabase();
|
|
10678
|
+
return d.query("SELECT * FROM agent_budgets WHERE agent_id = ?").get(agentId);
|
|
10679
|
+
}
|
|
10680
|
+
function checkBudget(agentId, db) {
|
|
10681
|
+
const d = db || getDatabase();
|
|
10682
|
+
const budget = getBudget(agentId, d);
|
|
10683
|
+
if (!budget)
|
|
10684
|
+
return { allowed: true, current_concurrent: 0, max_concurrent: 999 };
|
|
10685
|
+
const concurrent = countTasks({ status: "in_progress", assigned_to: agentId }, d);
|
|
10686
|
+
if (concurrent >= budget.max_concurrent) {
|
|
10687
|
+
return { allowed: false, reason: `Concurrent limit reached (${concurrent}/${budget.max_concurrent})`, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
|
|
10688
|
+
}
|
|
10689
|
+
if (budget.max_cost_usd != null) {
|
|
10690
|
+
const periodStart = new Date(Date.now() - budget.period_hours * 60 * 60 * 1000).toISOString();
|
|
10691
|
+
const costRow = d.query("SELECT COALESCE(SUM(cost_usd), 0) as total FROM tasks WHERE (assigned_to = ? OR agent_id = ?) AND updated_at > ?").get(agentId, agentId, periodStart);
|
|
10692
|
+
if (costRow.total >= budget.max_cost_usd) {
|
|
10693
|
+
return { allowed: false, reason: `Cost limit reached ($${costRow.total.toFixed(2)}/$${budget.max_cost_usd.toFixed(2)} in ${budget.period_hours}h)`, current_concurrent: concurrent, max_concurrent: budget.max_concurrent, current_cost_usd: costRow.total, max_cost_usd: budget.max_cost_usd };
|
|
10694
|
+
}
|
|
10695
|
+
}
|
|
10696
|
+
return { allowed: true, current_concurrent: concurrent, max_concurrent: budget.max_concurrent };
|
|
10697
|
+
}
|
|
10698
|
+
var init_budgets = __esm(() => {
|
|
10699
|
+
init_database();
|
|
10700
|
+
init_tasks();
|
|
10701
|
+
});
|
|
10702
|
+
|
|
10366
10703
|
// src/lib/github.ts
|
|
10367
10704
|
var exports_github = {};
|
|
10368
10705
|
__export(exports_github, {
|
|
@@ -11462,14 +11799,14 @@ __export(exports_mcp, {
|
|
|
11462
11799
|
});
|
|
11463
11800
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11464
11801
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11465
|
-
import { readFileSync as
|
|
11466
|
-
import { join as
|
|
11802
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
11803
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
11467
11804
|
import { fileURLToPath } from "url";
|
|
11468
11805
|
function getMcpVersion() {
|
|
11469
11806
|
try {
|
|
11470
11807
|
const __dir = dirname2(fileURLToPath(import.meta.url));
|
|
11471
|
-
const pkgPath =
|
|
11472
|
-
return JSON.parse(
|
|
11808
|
+
const pkgPath = join9(__dir, "..", "package.json");
|
|
11809
|
+
return JSON.parse(readFileSync5(pkgPath, "utf-8")).version || "0.0.0";
|
|
11473
11810
|
} catch {
|
|
11474
11811
|
return "0.0.0";
|
|
11475
11812
|
}
|
|
@@ -11533,7 +11870,23 @@ function formatError(error) {
|
|
|
11533
11870
|
return JSON.stringify({ code: CompletionGuardError.code, message: error.reason, suggestion: CompletionGuardError.suggestion, ...retry });
|
|
11534
11871
|
}
|
|
11535
11872
|
if (error instanceof Error) {
|
|
11536
|
-
|
|
11873
|
+
const msg = error.message;
|
|
11874
|
+
if (msg.includes("UNIQUE constraint failed: projects.path")) {
|
|
11875
|
+
const db = getDatabase();
|
|
11876
|
+
const existing = db.prepare("SELECT id, name FROM projects WHERE path = ?").get(msg.match(/'([^']+)'$/)?.[1] ?? "");
|
|
11877
|
+
return JSON.stringify({ code: "DUPLICATE_PROJECT", message: `Project already exists at this path${existing ? ` (id: ${existing.id}, name: ${existing.name})` : ""}. Use list_projects to find it.`, suggestion: "Use list_projects or get_project to retrieve the existing project." });
|
|
11878
|
+
}
|
|
11879
|
+
if (msg.includes("UNIQUE constraint failed: projects.name")) {
|
|
11880
|
+
return JSON.stringify({ code: "DUPLICATE_PROJECT", message: "A project with this name already exists. Use a different name or list_projects to find the existing one.", suggestion: "Use list_projects to see existing projects." });
|
|
11881
|
+
}
|
|
11882
|
+
if (msg.includes("UNIQUE constraint failed")) {
|
|
11883
|
+
const table = msg.match(/UNIQUE constraint failed: (\w+)\./)?.[1] ?? "unknown";
|
|
11884
|
+
return JSON.stringify({ code: "DUPLICATE_ENTRY", message: `Duplicate entry in ${table}. The record already exists.`, suggestion: `Use the list or get endpoint for ${table} to find the existing record.` });
|
|
11885
|
+
}
|
|
11886
|
+
if (msg.includes("FOREIGN KEY constraint failed")) {
|
|
11887
|
+
return JSON.stringify({ code: "REFERENCE_ERROR", message: "Referenced record does not exist. Check that the ID is correct.", suggestion: "Verify the referenced ID exists before creating this record." });
|
|
11888
|
+
}
|
|
11889
|
+
return JSON.stringify({ code: "UNKNOWN_ERROR", message: msg });
|
|
11537
11890
|
}
|
|
11538
11891
|
return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
|
|
11539
11892
|
}
|
|
@@ -13037,6 +13390,135 @@ Blocked:`);
|
|
|
13037
13390
|
}
|
|
13038
13391
|
});
|
|
13039
13392
|
}
|
|
13393
|
+
if (shouldRegisterTool("log_cost")) {
|
|
13394
|
+
server.tool("log_cost", "Log token usage and cost to a task. Accumulates \u2014 call after each LLM invocation.", { task_id: exports_external.string(), tokens: exports_external.number().describe("Token count"), usd: exports_external.number().describe("Cost in USD") }, async ({ task_id, tokens, usd }) => {
|
|
13395
|
+
try {
|
|
13396
|
+
const { logCost: logCost2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
13397
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
13398
|
+
logCost2(resolvedId, tokens, usd);
|
|
13399
|
+
return { content: [{ type: "text", text: `Logged ${tokens} tokens ($${usd.toFixed(4)}) to ${task_id}` }] };
|
|
13400
|
+
} catch (e) {
|
|
13401
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13402
|
+
}
|
|
13403
|
+
});
|
|
13404
|
+
}
|
|
13405
|
+
if (shouldRegisterTool("log_trace")) {
|
|
13406
|
+
server.tool("log_trace", "Log a trace entry (tool call, LLM call, error, handoff) to a task for observability.", {
|
|
13407
|
+
task_id: exports_external.string(),
|
|
13408
|
+
agent_id: exports_external.string().optional(),
|
|
13409
|
+
trace_type: exports_external.enum(["tool_call", "llm_call", "error", "handoff", "custom"]),
|
|
13410
|
+
name: exports_external.string().optional(),
|
|
13411
|
+
input_summary: exports_external.string().optional(),
|
|
13412
|
+
output_summary: exports_external.string().optional(),
|
|
13413
|
+
duration_ms: exports_external.number().optional(),
|
|
13414
|
+
tokens: exports_external.number().optional(),
|
|
13415
|
+
cost_usd: exports_external.number().optional()
|
|
13416
|
+
}, async ({ task_id, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd }) => {
|
|
13417
|
+
try {
|
|
13418
|
+
const { logTrace: logTrace2 } = await Promise.resolve().then(() => (init_traces(), exports_traces));
|
|
13419
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
13420
|
+
const trace = logTrace2({ task_id: resolvedId, agent_id, trace_type, name, input_summary, output_summary, duration_ms, tokens, cost_usd });
|
|
13421
|
+
return { content: [{ type: "text", text: `Trace logged: ${trace.id} [${trace_type}] ${name || ""}` }] };
|
|
13422
|
+
} catch (e) {
|
|
13423
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13424
|
+
}
|
|
13425
|
+
});
|
|
13426
|
+
}
|
|
13427
|
+
if (shouldRegisterTool("get_traces")) {
|
|
13428
|
+
server.tool("get_traces", "Get execution traces for a task \u2014 tool calls, LLM invocations, errors, handoffs.", { task_id: exports_external.string() }, async ({ task_id }) => {
|
|
13429
|
+
try {
|
|
13430
|
+
const { getTaskTraces: getTaskTraces2, getTraceStats: getTraceStats2 } = await Promise.resolve().then(() => (init_traces(), exports_traces));
|
|
13431
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
13432
|
+
const traces = getTaskTraces2(resolvedId);
|
|
13433
|
+
const stats = getTraceStats2(resolvedId);
|
|
13434
|
+
const lines = [`Traces for ${task_id}: ${stats.total} total (${stats.tool_calls} tools, ${stats.llm_calls} LLM, ${stats.errors} errors) | ${stats.total_tokens} tokens | $${stats.total_cost_usd.toFixed(4)} | ${stats.total_duration_ms}ms`];
|
|
13435
|
+
for (const t of traces.slice(0, 20)) {
|
|
13436
|
+
lines.push(` ${t.created_at} [${t.trace_type}] ${t.name || ""} ${t.tokens ? t.tokens + "tok" : ""} ${t.duration_ms ? t.duration_ms + "ms" : ""}`);
|
|
13437
|
+
}
|
|
13438
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
13439
|
+
`) }] };
|
|
13440
|
+
} catch (e) {
|
|
13441
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13442
|
+
}
|
|
13443
|
+
});
|
|
13444
|
+
}
|
|
13445
|
+
if (shouldRegisterTool("save_snapshot")) {
|
|
13446
|
+
server.tool("save_snapshot", "Save a structured context snapshot \u2014 what you were working on, what files are open, what was tried, blockers, next steps. Call on session end or before handoff.", {
|
|
13447
|
+
agent_id: exports_external.string().optional(),
|
|
13448
|
+
task_id: exports_external.string().optional(),
|
|
13449
|
+
project_id: exports_external.string().optional(),
|
|
13450
|
+
snapshot_type: exports_external.enum(["interrupt", "complete", "handoff", "checkpoint"]),
|
|
13451
|
+
plan_summary: exports_external.string().optional(),
|
|
13452
|
+
files_open: exports_external.array(exports_external.string()).optional(),
|
|
13453
|
+
attempts: exports_external.array(exports_external.string()).optional(),
|
|
13454
|
+
blockers: exports_external.array(exports_external.string()).optional(),
|
|
13455
|
+
next_steps: exports_external.string().optional()
|
|
13456
|
+
}, async ({ agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps }) => {
|
|
13457
|
+
try {
|
|
13458
|
+
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshots(), exports_snapshots));
|
|
13459
|
+
const snap = saveSnapshot2({ agent_id, task_id, project_id, snapshot_type, plan_summary, files_open, attempts, blockers, next_steps });
|
|
13460
|
+
return { content: [{ type: "text", text: `Snapshot saved: ${snap.id} [${snapshot_type}]${plan_summary ? " \u2014 " + plan_summary.slice(0, 80) : ""}` }] };
|
|
13461
|
+
} catch (e) {
|
|
13462
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13463
|
+
}
|
|
13464
|
+
});
|
|
13465
|
+
}
|
|
13466
|
+
if (shouldRegisterTool("get_snapshot")) {
|
|
13467
|
+
server.tool("get_snapshot", "Get the latest context snapshot for an agent or task \u2014 use to resume work after interruption.", { agent_id: exports_external.string().optional(), task_id: exports_external.string().optional() }, async ({ agent_id, task_id }) => {
|
|
13468
|
+
try {
|
|
13469
|
+
const { getLatestSnapshot: getLatestSnapshot2 } = await Promise.resolve().then(() => (init_snapshots(), exports_snapshots));
|
|
13470
|
+
const snap = getLatestSnapshot2(agent_id, task_id);
|
|
13471
|
+
if (!snap)
|
|
13472
|
+
return { content: [{ type: "text", text: "No snapshot found." }] };
|
|
13473
|
+
const lines = [`Snapshot [${snap.snapshot_type}] from ${snap.created_at}`];
|
|
13474
|
+
if (snap.plan_summary)
|
|
13475
|
+
lines.push(`Plan: ${snap.plan_summary}`);
|
|
13476
|
+
if (snap.files_open.length > 0)
|
|
13477
|
+
lines.push(`Files: ${snap.files_open.join(", ")}`);
|
|
13478
|
+
if (snap.blockers.length > 0)
|
|
13479
|
+
lines.push(`Blockers: ${snap.blockers.join(", ")}`);
|
|
13480
|
+
if (snap.next_steps)
|
|
13481
|
+
lines.push(`Next: ${snap.next_steps}`);
|
|
13482
|
+
if (snap.attempts.length > 0)
|
|
13483
|
+
lines.push(`Attempts: ${snap.attempts.join("; ")}`);
|
|
13484
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
13485
|
+
`) }] };
|
|
13486
|
+
} catch (e) {
|
|
13487
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13488
|
+
}
|
|
13489
|
+
});
|
|
13490
|
+
}
|
|
13491
|
+
if (shouldRegisterTool("set_budget")) {
|
|
13492
|
+
server.tool("set_budget", "Set execution budget for an agent \u2014 max concurrent tasks, cost limit, time limit per period.", {
|
|
13493
|
+
agent_id: exports_external.string(),
|
|
13494
|
+
max_concurrent: exports_external.number().optional(),
|
|
13495
|
+
max_cost_usd: exports_external.number().optional(),
|
|
13496
|
+
max_task_minutes: exports_external.number().optional(),
|
|
13497
|
+
period_hours: exports_external.number().optional()
|
|
13498
|
+
}, async ({ agent_id, max_concurrent, max_cost_usd, max_task_minutes, period_hours }) => {
|
|
13499
|
+
try {
|
|
13500
|
+
const { setBudget: setBudget2 } = await Promise.resolve().then(() => (init_budgets(), exports_budgets));
|
|
13501
|
+
const budget = setBudget2(agent_id, { max_concurrent, max_cost_usd, max_task_minutes, period_hours });
|
|
13502
|
+
return { content: [{ type: "text", text: `Budget set for ${agent_id}: max ${budget.max_concurrent} concurrent, $${budget.max_cost_usd ?? "\u221E"} cost limit, ${budget.period_hours}h period` }] };
|
|
13503
|
+
} catch (e) {
|
|
13504
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13505
|
+
}
|
|
13506
|
+
});
|
|
13507
|
+
}
|
|
13508
|
+
if (shouldRegisterTool("check_budget")) {
|
|
13509
|
+
server.tool("check_budget", "Check if an agent is within their execution budget (concurrent tasks, cost, time).", { agent_id: exports_external.string() }, async ({ agent_id }) => {
|
|
13510
|
+
try {
|
|
13511
|
+
const { checkBudget: checkBudget2 } = await Promise.resolve().then(() => (init_budgets(), exports_budgets));
|
|
13512
|
+
const result = checkBudget2(agent_id);
|
|
13513
|
+
if (result.allowed) {
|
|
13514
|
+
return { content: [{ type: "text", text: `Budget OK: ${result.current_concurrent}/${result.max_concurrent} concurrent tasks` }] };
|
|
13515
|
+
}
|
|
13516
|
+
return { content: [{ type: "text", text: `BUDGET EXCEEDED: ${result.reason}` }], isError: true };
|
|
13517
|
+
} catch (e) {
|
|
13518
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
13519
|
+
}
|
|
13520
|
+
});
|
|
13521
|
+
}
|
|
13040
13522
|
if (shouldRegisterTool("import_github_issue")) {
|
|
13041
13523
|
server.tool("import_github_issue", "Import a GitHub issue as a task. Requires gh CLI installed and authenticated.", {
|
|
13042
13524
|
url: exports_external.string().describe("GitHub issue URL (e.g. https://github.com/owner/repo/issues/42)"),
|
|
@@ -13747,6 +14229,56 @@ ${formatTaskDetail(task, 300)}` }] };
|
|
|
13747
14229
|
}
|
|
13748
14230
|
});
|
|
13749
14231
|
}
|
|
14232
|
+
if (shouldRegisterTool("steal_task")) {
|
|
14233
|
+
server.tool("steal_task", "Work-stealing: take the highest-priority stale in_progress task from another agent and reassign it to you.", {
|
|
14234
|
+
agent_id: exports_external.string().describe("Your agent ID"),
|
|
14235
|
+
stale_minutes: exports_external.number().optional().describe("How long a task must be stale before stealing (default: 30)"),
|
|
14236
|
+
project_id: exports_external.string().optional(),
|
|
14237
|
+
task_list_id: exports_external.string().optional()
|
|
14238
|
+
}, async ({ agent_id, stale_minutes, project_id, task_list_id }) => {
|
|
14239
|
+
try {
|
|
14240
|
+
const task = stealTask(agent_id, { stale_minutes, project_id, task_list_id });
|
|
14241
|
+
if (!task)
|
|
14242
|
+
return { content: [{ type: "text", text: "No stale tasks available to steal." }] };
|
|
14243
|
+
return { content: [{ type: "text", text: `Stolen: ${formatTask(task)}
|
|
14244
|
+
Previous owner: ${task.metadata?._stolen_from || "unknown"}` }] };
|
|
14245
|
+
} catch (e) {
|
|
14246
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
14247
|
+
}
|
|
14248
|
+
});
|
|
14249
|
+
}
|
|
14250
|
+
if (shouldRegisterTool("claim_or_steal")) {
|
|
14251
|
+
server.tool("claim_or_steal", "Try to claim a pending task first; if none available, steal from a stale agent. Best single call for getting work.", {
|
|
14252
|
+
agent_id: exports_external.string(),
|
|
14253
|
+
project_id: exports_external.string().optional(),
|
|
14254
|
+
task_list_id: exports_external.string().optional(),
|
|
14255
|
+
plan_id: exports_external.string().optional(),
|
|
14256
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
14257
|
+
stale_minutes: exports_external.number().optional().describe("Stale threshold for work-stealing fallback (default: 30)")
|
|
14258
|
+
}, async ({ agent_id, project_id, task_list_id, plan_id, tags, stale_minutes }) => {
|
|
14259
|
+
try {
|
|
14260
|
+
const filters = {};
|
|
14261
|
+
if (project_id)
|
|
14262
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
14263
|
+
if (task_list_id)
|
|
14264
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
14265
|
+
if (plan_id)
|
|
14266
|
+
filters.plan_id = resolveId(plan_id, "plans");
|
|
14267
|
+
if (tags)
|
|
14268
|
+
filters.tags = tags;
|
|
14269
|
+
if (stale_minutes)
|
|
14270
|
+
filters.stale_minutes = stale_minutes;
|
|
14271
|
+
const result = claimOrSteal(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
14272
|
+
if (!result)
|
|
14273
|
+
return { content: [{ type: "text", text: "No tasks available to claim or steal." }] };
|
|
14274
|
+
const prefix = result.stolen ? "Stolen" : "Claimed";
|
|
14275
|
+
return { content: [{ type: "text", text: `${prefix}: ${formatTask(result.task)}
|
|
14276
|
+
${formatTaskDetail(result.task, 300)}` }] };
|
|
14277
|
+
} catch (e) {
|
|
14278
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
14279
|
+
}
|
|
14280
|
+
});
|
|
14281
|
+
}
|
|
13750
14282
|
if (shouldRegisterTool("get_stale_tasks")) {
|
|
13751
14283
|
server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
|
|
13752
14284
|
stale_minutes: exports_external.number().optional(),
|
|
@@ -15414,27 +15946,27 @@ var exports_serve = {};
|
|
|
15414
15946
|
__export(exports_serve, {
|
|
15415
15947
|
startServer: () => startServer
|
|
15416
15948
|
});
|
|
15417
|
-
import { existsSync as
|
|
15418
|
-
import { join as
|
|
15949
|
+
import { existsSync as existsSync8 } from "fs";
|
|
15950
|
+
import { join as join10, dirname as dirname3, extname, resolve as resolve3, sep } from "path";
|
|
15419
15951
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15420
15952
|
function resolveDashboardDir() {
|
|
15421
15953
|
const candidates = [];
|
|
15422
15954
|
try {
|
|
15423
15955
|
const scriptDir = dirname3(fileURLToPath2(import.meta.url));
|
|
15424
|
-
candidates.push(
|
|
15425
|
-
candidates.push(
|
|
15956
|
+
candidates.push(join10(scriptDir, "..", "dashboard", "dist"));
|
|
15957
|
+
candidates.push(join10(scriptDir, "..", "..", "dashboard", "dist"));
|
|
15426
15958
|
} catch {}
|
|
15427
15959
|
if (process.argv[1]) {
|
|
15428
15960
|
const mainDir = dirname3(process.argv[1]);
|
|
15429
|
-
candidates.push(
|
|
15430
|
-
candidates.push(
|
|
15961
|
+
candidates.push(join10(mainDir, "..", "dashboard", "dist"));
|
|
15962
|
+
candidates.push(join10(mainDir, "..", "..", "dashboard", "dist"));
|
|
15431
15963
|
}
|
|
15432
|
-
candidates.push(
|
|
15964
|
+
candidates.push(join10(process.cwd(), "dashboard", "dist"));
|
|
15433
15965
|
for (const candidate of candidates) {
|
|
15434
|
-
if (
|
|
15966
|
+
if (existsSync8(candidate))
|
|
15435
15967
|
return candidate;
|
|
15436
15968
|
}
|
|
15437
|
-
return
|
|
15969
|
+
return join10(process.cwd(), "dashboard", "dist");
|
|
15438
15970
|
}
|
|
15439
15971
|
function json(data, status = 200, port) {
|
|
15440
15972
|
return new Response(JSON.stringify(data), {
|
|
@@ -15447,7 +15979,7 @@ function json(data, status = 200, port) {
|
|
|
15447
15979
|
});
|
|
15448
15980
|
}
|
|
15449
15981
|
function serveStaticFile(filePath) {
|
|
15450
|
-
if (!
|
|
15982
|
+
if (!existsSync8(filePath))
|
|
15451
15983
|
return null;
|
|
15452
15984
|
const ext = extname(filePath);
|
|
15453
15985
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -15514,7 +16046,7 @@ data: ${data}
|
|
|
15514
16046
|
}
|
|
15515
16047
|
}
|
|
15516
16048
|
const dashboardDir = resolveDashboardDir();
|
|
15517
|
-
const dashboardExists =
|
|
16049
|
+
const dashboardExists = existsSync8(dashboardDir);
|
|
15518
16050
|
if (!dashboardExists) {
|
|
15519
16051
|
console.error(`
|
|
15520
16052
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -16238,7 +16770,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
16238
16770
|
}
|
|
16239
16771
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
16240
16772
|
if (path !== "/") {
|
|
16241
|
-
const filePath =
|
|
16773
|
+
const filePath = join10(dashboardDir, path);
|
|
16242
16774
|
const resolvedFile = resolve3(filePath);
|
|
16243
16775
|
const resolvedBase = resolve3(dashboardDir);
|
|
16244
16776
|
if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
|
|
@@ -16248,7 +16780,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
16248
16780
|
if (res2)
|
|
16249
16781
|
return res2;
|
|
16250
16782
|
}
|
|
16251
|
-
const indexPath =
|
|
16783
|
+
const indexPath = join10(dashboardDir, "index.html");
|
|
16252
16784
|
const res = serveStaticFile(indexPath);
|
|
16253
16785
|
if (res)
|
|
16254
16786
|
return res;
|
|
@@ -17667,15 +18199,318 @@ init_comments();
|
|
|
17667
18199
|
init_search();
|
|
17668
18200
|
init_sync();
|
|
17669
18201
|
init_config();
|
|
17670
|
-
import
|
|
18202
|
+
import chalk2 from "chalk";
|
|
17671
18203
|
import { execSync as execSync2 } from "child_process";
|
|
17672
|
-
import { existsSync as
|
|
17673
|
-
import { basename, dirname as dirname4, join as
|
|
18204
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
18205
|
+
import { basename, dirname as dirname4, join as join11, resolve as resolve4 } from "path";
|
|
17674
18206
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18207
|
+
|
|
18208
|
+
// src/cli/brains.ts
|
|
18209
|
+
import {
|
|
18210
|
+
existsSync as existsSync7,
|
|
18211
|
+
mkdirSync as mkdirSync4,
|
|
18212
|
+
writeFileSync as writeFileSync4,
|
|
18213
|
+
readdirSync as readdirSync3
|
|
18214
|
+
} from "fs";
|
|
18215
|
+
import { homedir as homedir2 } from "os";
|
|
18216
|
+
import { join as join7 } from "path";
|
|
18217
|
+
import chalk from "chalk";
|
|
18218
|
+
|
|
18219
|
+
// src/lib/gatherer.ts
|
|
18220
|
+
init_tasks();
|
|
18221
|
+
var SYSTEM_PROMPT = "You are a task management assistant that creates, updates, and tracks tasks and projects.";
|
|
18222
|
+
function taskToCreateExample(task) {
|
|
18223
|
+
const userMsg = `Create a task: ${task.title}${task.description ? `
|
|
18224
|
+
|
|
18225
|
+
Description: ${task.description}` : ""}`;
|
|
18226
|
+
const taskDetails = {
|
|
18227
|
+
id: task.short_id ?? task.id,
|
|
18228
|
+
title: task.title,
|
|
18229
|
+
description: task.description ?? "",
|
|
18230
|
+
status: task.status,
|
|
18231
|
+
priority: task.priority,
|
|
18232
|
+
tags: task.tags,
|
|
18233
|
+
created_at: task.created_at
|
|
18234
|
+
};
|
|
18235
|
+
return {
|
|
18236
|
+
messages: [
|
|
18237
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
18238
|
+
{ role: "user", content: userMsg },
|
|
18239
|
+
{
|
|
18240
|
+
role: "assistant",
|
|
18241
|
+
content: `Created task: ${JSON.stringify(taskDetails, null, 2)}`
|
|
18242
|
+
}
|
|
18243
|
+
]
|
|
18244
|
+
};
|
|
18245
|
+
}
|
|
18246
|
+
function taskToStatusUpdateExample(task) {
|
|
18247
|
+
if (!task.completed_at && task.status === "pending")
|
|
18248
|
+
return null;
|
|
18249
|
+
const id = task.short_id ?? task.id;
|
|
18250
|
+
return {
|
|
18251
|
+
messages: [
|
|
18252
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
18253
|
+
{ role: "user", content: `Mark task ${id} as ${task.status}` },
|
|
18254
|
+
{
|
|
18255
|
+
role: "assistant",
|
|
18256
|
+
content: `Task ${id} has been updated to status: ${task.status}. ${task.completed_at ? `Completed at: ${task.completed_at}` : ""}`.trim()
|
|
18257
|
+
}
|
|
18258
|
+
]
|
|
18259
|
+
};
|
|
18260
|
+
}
|
|
18261
|
+
function taskToSearchExample(tasks, query) {
|
|
18262
|
+
const matched = tasks.filter((t) => t.title.toLowerCase().includes(query.toLowerCase())).slice(0, 5);
|
|
18263
|
+
return {
|
|
18264
|
+
messages: [
|
|
18265
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
18266
|
+
{ role: "user", content: `Search tasks for: "${query}"` },
|
|
18267
|
+
{
|
|
18268
|
+
role: "assistant",
|
|
18269
|
+
content: matched.length > 0 ? `Found ${matched.length} task(s):
|
|
18270
|
+
${matched.map((t) => `- [${t.short_id ?? t.id}] ${t.title} (${t.status})`).join(`
|
|
18271
|
+
`)}` : `No tasks found matching "${query}".`
|
|
18272
|
+
}
|
|
18273
|
+
]
|
|
18274
|
+
};
|
|
18275
|
+
}
|
|
18276
|
+
var gatherTrainingData = async (options = {}) => {
|
|
18277
|
+
const allTasks = listTasks({});
|
|
18278
|
+
const filtered = options.since ? allTasks.filter((t) => new Date(t.created_at) >= options.since) : allTasks;
|
|
18279
|
+
const sorted = filtered.slice().sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
18280
|
+
const fetchSet = options.limit ? sorted.slice(0, options.limit * 2) : sorted;
|
|
18281
|
+
const examples = [];
|
|
18282
|
+
for (const task of fetchSet) {
|
|
18283
|
+
examples.push(taskToCreateExample(task));
|
|
18284
|
+
const statusEx = taskToStatusUpdateExample(task);
|
|
18285
|
+
if (statusEx)
|
|
18286
|
+
examples.push(statusEx);
|
|
18287
|
+
}
|
|
18288
|
+
const searchTerms = ["urgent", "fix", "implement", "create", "update", "review"];
|
|
18289
|
+
for (const term of searchTerms) {
|
|
18290
|
+
examples.push(taskToSearchExample(sorted, term));
|
|
18291
|
+
}
|
|
18292
|
+
const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
|
|
18293
|
+
return {
|
|
18294
|
+
source: "todos",
|
|
18295
|
+
examples: finalExamples,
|
|
18296
|
+
count: finalExamples.length
|
|
18297
|
+
};
|
|
18298
|
+
};
|
|
18299
|
+
|
|
18300
|
+
// src/lib/model-config.ts
|
|
18301
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
18302
|
+
import { homedir } from "os";
|
|
18303
|
+
import { join as join6 } from "path";
|
|
18304
|
+
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
18305
|
+
var CONFIG_DIR = join6(homedir(), ".todos");
|
|
18306
|
+
var CONFIG_PATH = join6(CONFIG_DIR, "config.json");
|
|
18307
|
+
function readConfig() {
|
|
18308
|
+
if (!existsSync6(CONFIG_PATH))
|
|
18309
|
+
return {};
|
|
18310
|
+
try {
|
|
18311
|
+
const raw = readFileSync3(CONFIG_PATH, "utf-8");
|
|
18312
|
+
return JSON.parse(raw);
|
|
18313
|
+
} catch {
|
|
18314
|
+
return {};
|
|
18315
|
+
}
|
|
18316
|
+
}
|
|
18317
|
+
function writeConfig(config) {
|
|
18318
|
+
if (!existsSync6(CONFIG_DIR)) {
|
|
18319
|
+
mkdirSync3(CONFIG_DIR, { recursive: true });
|
|
18320
|
+
}
|
|
18321
|
+
writeFileSync3(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
18322
|
+
`, "utf-8");
|
|
18323
|
+
}
|
|
18324
|
+
function getActiveModel() {
|
|
18325
|
+
const config = readConfig();
|
|
18326
|
+
return config.activeModel ?? DEFAULT_MODEL;
|
|
18327
|
+
}
|
|
18328
|
+
function setActiveModel(modelId) {
|
|
18329
|
+
const config = readConfig();
|
|
18330
|
+
config.activeModel = modelId;
|
|
18331
|
+
writeConfig(config);
|
|
18332
|
+
}
|
|
18333
|
+
function clearActiveModel() {
|
|
18334
|
+
const config = readConfig();
|
|
18335
|
+
delete config.activeModel;
|
|
18336
|
+
writeConfig(config);
|
|
18337
|
+
}
|
|
18338
|
+
|
|
18339
|
+
// src/cli/brains.ts
|
|
18340
|
+
function printSuccess(msg) {
|
|
18341
|
+
console.log(chalk.green("\u2713 " + msg));
|
|
18342
|
+
}
|
|
18343
|
+
function printError(msg) {
|
|
18344
|
+
console.error(chalk.red("\u2717 " + msg));
|
|
18345
|
+
}
|
|
18346
|
+
function printInfo(msg) {
|
|
18347
|
+
console.log(chalk.cyan("\u2139 " + msg));
|
|
18348
|
+
}
|
|
18349
|
+
function makeBrainsCommand() {
|
|
18350
|
+
const brains = new Command("brains");
|
|
18351
|
+
brains.description("Fine-tuned model training and management (via @hasna/brains)");
|
|
18352
|
+
brains.command("gather").description("Gather training data from tasks and write to JSONL").option("--limit <n>", "Maximum number of examples to gather", parseInt).option("--since <date>", "Only include tasks created since this date (ISO 8601)").option("--output <dir>", "Output directory (default: ~/.todos/training/)").option("--json", "Output result summary as JSON").action(async (opts) => {
|
|
18353
|
+
try {
|
|
18354
|
+
const since = opts.since ? new Date(opts.since) : undefined;
|
|
18355
|
+
if (since && isNaN(since.getTime())) {
|
|
18356
|
+
printError(`Invalid date: ${opts.since}`);
|
|
18357
|
+
process.exit(1);
|
|
18358
|
+
}
|
|
18359
|
+
if (!opts.json) {
|
|
18360
|
+
printInfo("Gathering training data from tasks...");
|
|
18361
|
+
}
|
|
18362
|
+
const result = await gatherTrainingData({
|
|
18363
|
+
limit: opts.limit,
|
|
18364
|
+
since
|
|
18365
|
+
});
|
|
18366
|
+
const outputDir = opts.output ?? join7(homedir2(), ".todos", "training");
|
|
18367
|
+
if (!existsSync7(outputDir)) {
|
|
18368
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
18369
|
+
}
|
|
18370
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
18371
|
+
const outputPath = join7(outputDir, `todos-training-${timestamp}.jsonl`);
|
|
18372
|
+
const jsonl = result.examples.map((ex) => JSON.stringify(ex)).join(`
|
|
18373
|
+
`);
|
|
18374
|
+
writeFileSync4(outputPath, jsonl + `
|
|
18375
|
+
`, "utf-8");
|
|
18376
|
+
if (opts.json) {
|
|
18377
|
+
console.log(JSON.stringify({
|
|
18378
|
+
source: result.source,
|
|
18379
|
+
count: result.count,
|
|
18380
|
+
path: outputPath
|
|
18381
|
+
}));
|
|
18382
|
+
} else {
|
|
18383
|
+
printSuccess(`Gathered ${result.count} training examples from tasks`);
|
|
18384
|
+
console.log(chalk.dim(` Output: ${outputPath}`));
|
|
18385
|
+
}
|
|
18386
|
+
} catch (err) {
|
|
18387
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
18388
|
+
process.exit(1);
|
|
18389
|
+
}
|
|
18390
|
+
});
|
|
18391
|
+
brains.command("train").description("Start a fine-tuning job using gathered task training data").option("--base-model <model>", "Base model to fine-tune", "gpt-4o-mini-2024-07-18").option("--provider <provider>", "Provider (openai|thinker-labs)", "openai").option("--dataset <path>", "Path to JSONL dataset (auto-detects latest if omitted)").option("--name <name>", "Display name for the fine-tuned model").option("--json", "Output result as JSON").action(async (opts) => {
|
|
18392
|
+
try {
|
|
18393
|
+
let datasetPath = opts.dataset;
|
|
18394
|
+
if (!datasetPath) {
|
|
18395
|
+
const trainingDir = join7(homedir2(), ".todos", "training");
|
|
18396
|
+
if (!existsSync7(trainingDir)) {
|
|
18397
|
+
printError("No training data found. Run `todos brains gather` first.");
|
|
18398
|
+
process.exit(1);
|
|
18399
|
+
}
|
|
18400
|
+
const files = readdirSync3(trainingDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
|
|
18401
|
+
const latestFile = files[0];
|
|
18402
|
+
if (!latestFile) {
|
|
18403
|
+
printError("No JSONL training files found. Run `todos brains gather` first.");
|
|
18404
|
+
process.exit(1);
|
|
18405
|
+
}
|
|
18406
|
+
datasetPath = join7(trainingDir, latestFile);
|
|
18407
|
+
}
|
|
18408
|
+
if (!datasetPath || !existsSync7(datasetPath)) {
|
|
18409
|
+
printError(`Dataset file not found: ${datasetPath ?? "(unresolved)"}`);
|
|
18410
|
+
process.exit(1);
|
|
18411
|
+
}
|
|
18412
|
+
if (!opts.json) {
|
|
18413
|
+
printInfo(`Starting fine-tuning job with dataset: ${datasetPath}`);
|
|
18414
|
+
}
|
|
18415
|
+
let brainsSDK;
|
|
18416
|
+
try {
|
|
18417
|
+
brainsSDK = await import("@hasna/brains");
|
|
18418
|
+
} catch {
|
|
18419
|
+
printError("@hasna/brains is not installed. Run `bun add @hasna/brains` to enable training.");
|
|
18420
|
+
process.exit(1);
|
|
18421
|
+
}
|
|
18422
|
+
const startFinetune = brainsSDK["startFinetune"];
|
|
18423
|
+
if (typeof startFinetune !== "function") {
|
|
18424
|
+
printError("@hasna/brains does not export startFinetune. Please update @hasna/brains.");
|
|
18425
|
+
process.exit(1);
|
|
18426
|
+
}
|
|
18427
|
+
const modelName = opts.name ?? `todos-${new Date().toISOString().slice(0, 10)}`;
|
|
18428
|
+
const jobResult = await startFinetune({
|
|
18429
|
+
provider: opts.provider,
|
|
18430
|
+
baseModel: opts.baseModel,
|
|
18431
|
+
datasetPath,
|
|
18432
|
+
name: modelName
|
|
18433
|
+
});
|
|
18434
|
+
if (opts.json) {
|
|
18435
|
+
console.log(JSON.stringify(jobResult));
|
|
18436
|
+
} else {
|
|
18437
|
+
printSuccess(`Fine-tuning job started: ${String(jobResult["jobId"] ?? "(unknown)")}`);
|
|
18438
|
+
console.log(chalk.dim(` Provider: ${opts.provider}`));
|
|
18439
|
+
console.log(chalk.dim(` Base model: ${opts.baseModel}`));
|
|
18440
|
+
console.log(chalk.dim(` Name: ${modelName}`));
|
|
18441
|
+
if (jobResult["jobId"]) {
|
|
18442
|
+
console.log();
|
|
18443
|
+
printInfo(`Use \`todos brains model set <model-id>\` once training completes.`);
|
|
18444
|
+
}
|
|
18445
|
+
}
|
|
18446
|
+
} catch (err) {
|
|
18447
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
18448
|
+
process.exit(1);
|
|
18449
|
+
}
|
|
18450
|
+
});
|
|
18451
|
+
const modelCmd = brains.command("model").description("Manage the active fine-tuned model");
|
|
18452
|
+
modelCmd.command("get").description("Show the currently active fine-tuned model").option("--json", "Output as JSON").action((opts) => {
|
|
18453
|
+
try {
|
|
18454
|
+
const active = getActiveModel();
|
|
18455
|
+
const isDefault = active === DEFAULT_MODEL;
|
|
18456
|
+
if (opts.json) {
|
|
18457
|
+
console.log(JSON.stringify({ activeModel: active, isDefault }));
|
|
18458
|
+
} else {
|
|
18459
|
+
if (isDefault) {
|
|
18460
|
+
console.log(`Active model: ${chalk.cyan(active)} ${chalk.dim("(default)")}`);
|
|
18461
|
+
} else {
|
|
18462
|
+
console.log(`Active model: ${chalk.green(active)}`);
|
|
18463
|
+
}
|
|
18464
|
+
}
|
|
18465
|
+
} catch (err) {
|
|
18466
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
18467
|
+
process.exit(1);
|
|
18468
|
+
}
|
|
18469
|
+
});
|
|
18470
|
+
modelCmd.command("set <modelId>").description("Set the active fine-tuned model ID").action((modelId) => {
|
|
18471
|
+
try {
|
|
18472
|
+
setActiveModel(modelId);
|
|
18473
|
+
printSuccess(`Active model set to: ${modelId}`);
|
|
18474
|
+
} catch (err) {
|
|
18475
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
18476
|
+
process.exit(1);
|
|
18477
|
+
}
|
|
18478
|
+
});
|
|
18479
|
+
modelCmd.command("clear").description(`Clear the active fine-tuned model (reverts to ${DEFAULT_MODEL})`).action(() => {
|
|
18480
|
+
try {
|
|
18481
|
+
clearActiveModel();
|
|
18482
|
+
printSuccess(`Active model cleared. Using default: ${DEFAULT_MODEL}`);
|
|
18483
|
+
} catch (err) {
|
|
18484
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
18485
|
+
process.exit(1);
|
|
18486
|
+
}
|
|
18487
|
+
});
|
|
18488
|
+
modelCmd.action((opts) => {
|
|
18489
|
+
try {
|
|
18490
|
+
const active = getActiveModel();
|
|
18491
|
+
const isDefault = active === DEFAULT_MODEL;
|
|
18492
|
+
if (opts.json) {
|
|
18493
|
+
console.log(JSON.stringify({ activeModel: active, isDefault }));
|
|
18494
|
+
} else {
|
|
18495
|
+
if (isDefault) {
|
|
18496
|
+
console.log(`Active model: ${chalk.cyan(active)} ${chalk.dim("(default)")}`);
|
|
18497
|
+
} else {
|
|
18498
|
+
console.log(`Active model: ${chalk.green(active)}`);
|
|
18499
|
+
}
|
|
18500
|
+
}
|
|
18501
|
+
} catch (err) {
|
|
18502
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
18503
|
+
process.exit(1);
|
|
18504
|
+
}
|
|
18505
|
+
});
|
|
18506
|
+
return brains;
|
|
18507
|
+
}
|
|
18508
|
+
|
|
18509
|
+
// src/cli/index.tsx
|
|
17675
18510
|
function getPackageVersion() {
|
|
17676
18511
|
try {
|
|
17677
|
-
const pkgPath =
|
|
17678
|
-
return JSON.parse(
|
|
18512
|
+
const pkgPath = join11(dirname4(fileURLToPath3(import.meta.url)), "..", "..", "package.json");
|
|
18513
|
+
return JSON.parse(readFileSync6(pkgPath, "utf-8")).version || "0.0.0";
|
|
17679
18514
|
} catch {
|
|
17680
18515
|
return "0.0.0";
|
|
17681
18516
|
}
|
|
@@ -17686,7 +18521,7 @@ function handleError(e) {
|
|
|
17686
18521
|
if (globalOpts.json) {
|
|
17687
18522
|
console.log(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
17688
18523
|
} else {
|
|
17689
|
-
console.error(
|
|
18524
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
17690
18525
|
}
|
|
17691
18526
|
process.exit(1);
|
|
17692
18527
|
}
|
|
@@ -17696,10 +18531,10 @@ function resolveTaskId(partialId) {
|
|
|
17696
18531
|
if (!id) {
|
|
17697
18532
|
const similar = db.query("SELECT id FROM tasks WHERE id LIKE ? LIMIT 3").all(`%${partialId}%`);
|
|
17698
18533
|
if (similar.length > 0) {
|
|
17699
|
-
console.error(
|
|
17700
|
-
console.error(
|
|
18534
|
+
console.error(chalk2.red(`Could not resolve task ID: ${partialId}`));
|
|
18535
|
+
console.error(chalk2.dim(`Did you mean: ${similar.map((s) => s.id.slice(0, 8)).join(", ")}?`));
|
|
17701
18536
|
} else {
|
|
17702
|
-
console.error(
|
|
18537
|
+
console.error(chalk2.red(`Could not resolve task ID: ${partialId}`));
|
|
17703
18538
|
}
|
|
17704
18539
|
process.exit(1);
|
|
17705
18540
|
}
|
|
@@ -17733,26 +18568,26 @@ function output(data, jsonMode) {
|
|
|
17733
18568
|
}
|
|
17734
18569
|
}
|
|
17735
18570
|
var statusColors4 = {
|
|
17736
|
-
pending:
|
|
17737
|
-
in_progress:
|
|
17738
|
-
completed:
|
|
17739
|
-
failed:
|
|
17740
|
-
cancelled:
|
|
18571
|
+
pending: chalk2.yellow,
|
|
18572
|
+
in_progress: chalk2.blue,
|
|
18573
|
+
completed: chalk2.green,
|
|
18574
|
+
failed: chalk2.red,
|
|
18575
|
+
cancelled: chalk2.gray
|
|
17741
18576
|
};
|
|
17742
18577
|
var priorityColors2 = {
|
|
17743
|
-
critical:
|
|
17744
|
-
high:
|
|
17745
|
-
medium:
|
|
17746
|
-
low:
|
|
18578
|
+
critical: chalk2.red.bold,
|
|
18579
|
+
high: chalk2.red,
|
|
18580
|
+
medium: chalk2.yellow,
|
|
18581
|
+
low: chalk2.gray
|
|
17747
18582
|
};
|
|
17748
18583
|
function formatTaskLine(t) {
|
|
17749
|
-
const statusFn = statusColors4[t.status] ||
|
|
17750
|
-
const priorityFn = priorityColors2[t.priority] ||
|
|
17751
|
-
const lock = t.locked_by ?
|
|
17752
|
-
const assigned = t.assigned_to ?
|
|
17753
|
-
const tags = t.tags.length > 0 ?
|
|
17754
|
-
const plan = t.plan_id ?
|
|
17755
|
-
return `${
|
|
18584
|
+
const statusFn = statusColors4[t.status] || chalk2.white;
|
|
18585
|
+
const priorityFn = priorityColors2[t.priority] || chalk2.white;
|
|
18586
|
+
const lock = t.locked_by ? chalk2.magenta(` [locked:${t.locked_by}]`) : "";
|
|
18587
|
+
const assigned = t.assigned_to ? chalk2.cyan(` -> ${t.assigned_to}`) : "";
|
|
18588
|
+
const tags = t.tags.length > 0 ? chalk2.dim(` [${t.tags.join(",")}]`) : "";
|
|
18589
|
+
const plan = t.plan_id ? chalk2.magenta(` [plan:${t.plan_id.slice(0, 8)}]`) : "";
|
|
18590
|
+
return `${chalk2.dim(t.id.slice(0, 8))} ${statusFn(t.status.padEnd(11))} ${priorityFn(t.priority.padEnd(8))} ${t.title}${assigned}${lock}${tags}${plan}`;
|
|
17756
18591
|
}
|
|
17757
18592
|
program2.name("todos").description("Universal task management for AI coding agents").version(getPackageVersion()).option("--project <path>", "Project path").option("--json", "Output as JSON").option("--agent <name>", "Agent name").option("--session <id>", "Session ID");
|
|
17758
18593
|
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("--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").action((title, opts) => {
|
|
@@ -17764,7 +18599,7 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
|
|
|
17764
18599
|
const db = getDatabase();
|
|
17765
18600
|
const id = resolvePartialId(db, "task_lists", opts.list);
|
|
17766
18601
|
if (!id) {
|
|
17767
|
-
console.error(
|
|
18602
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
17768
18603
|
process.exit(1);
|
|
17769
18604
|
}
|
|
17770
18605
|
return id;
|
|
@@ -17779,7 +18614,7 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
|
|
|
17779
18614
|
const db = getDatabase();
|
|
17780
18615
|
const id = resolvePartialId(db, "plans", opts.plan);
|
|
17781
18616
|
if (!id) {
|
|
17782
|
-
console.error(
|
|
18617
|
+
console.error(chalk2.red(`Could not resolve plan ID: ${opts.plan}`));
|
|
17783
18618
|
process.exit(1);
|
|
17784
18619
|
}
|
|
17785
18620
|
return id;
|
|
@@ -17800,7 +18635,7 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
|
|
|
17800
18635
|
if (globalOpts.json) {
|
|
17801
18636
|
output(task, true);
|
|
17802
18637
|
} else {
|
|
17803
|
-
console.log(
|
|
18638
|
+
console.log(chalk2.green("Task created:"));
|
|
17804
18639
|
console.log(formatTaskLine(task));
|
|
17805
18640
|
}
|
|
17806
18641
|
});
|
|
@@ -17816,7 +18651,7 @@ program2.command("list").description("List tasks").option("-s, --status <status>
|
|
|
17816
18651
|
const db = getDatabase();
|
|
17817
18652
|
const listId = resolvePartialId(db, "task_lists", opts.list);
|
|
17818
18653
|
if (!listId) {
|
|
17819
|
-
console.error(
|
|
18654
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
17820
18655
|
process.exit(1);
|
|
17821
18656
|
}
|
|
17822
18657
|
filter["task_list_id"] = listId;
|
|
@@ -17838,7 +18673,7 @@ program2.command("list").description("List tasks").option("-s, --status <status>
|
|
|
17838
18673
|
if (match) {
|
|
17839
18674
|
filter["project_id"] = match.id;
|
|
17840
18675
|
} else {
|
|
17841
|
-
console.error(
|
|
18676
|
+
console.error(chalk2.red(`No project matching: ${opts.projectName}`));
|
|
17842
18677
|
process.exit(1);
|
|
17843
18678
|
}
|
|
17844
18679
|
}
|
|
@@ -17882,7 +18717,7 @@ program2.command("list").description("List tasks").option("-s, --status <status>
|
|
|
17882
18717
|
if (fmt === "compact" || fmt === "csv")
|
|
17883
18718
|
process.stdout.write("");
|
|
17884
18719
|
else
|
|
17885
|
-
console.log(
|
|
18720
|
+
console.log(chalk2.dim("No tasks found."));
|
|
17886
18721
|
return;
|
|
17887
18722
|
}
|
|
17888
18723
|
if (fmt === "csv") {
|
|
@@ -17909,7 +18744,7 @@ program2.command("list").description("List tasks").option("-s, --status <status>
|
|
|
17909
18744
|
}
|
|
17910
18745
|
return;
|
|
17911
18746
|
}
|
|
17912
|
-
console.log(
|
|
18747
|
+
console.log(chalk2.bold(`${tasks.length} task(s):
|
|
17913
18748
|
`));
|
|
17914
18749
|
for (const t of tasks) {
|
|
17915
18750
|
console.log(formatTaskLine(t));
|
|
@@ -17926,12 +18761,12 @@ program2.command("count").description("Show task count by status").action(() =>
|
|
|
17926
18761
|
output(counts, true);
|
|
17927
18762
|
} else {
|
|
17928
18763
|
const parts = [
|
|
17929
|
-
`total: ${
|
|
17930
|
-
`pending: ${
|
|
17931
|
-
`in_progress: ${
|
|
17932
|
-
`completed: ${
|
|
17933
|
-
`failed: ${
|
|
17934
|
-
`cancelled: ${
|
|
18764
|
+
`total: ${chalk2.bold(String(counts.total))}`,
|
|
18765
|
+
`pending: ${chalk2.yellow(String(counts["pending"] || 0))}`,
|
|
18766
|
+
`in_progress: ${chalk2.blue(String(counts["in_progress"] || 0))}`,
|
|
18767
|
+
`completed: ${chalk2.green(String(counts["completed"] || 0))}`,
|
|
18768
|
+
`failed: ${chalk2.red(String(counts["failed"] || 0))}`,
|
|
18769
|
+
`cancelled: ${chalk2.gray(String(counts["cancelled"] || 0))}`
|
|
17935
18770
|
];
|
|
17936
18771
|
console.log(parts.join(" "));
|
|
17937
18772
|
}
|
|
@@ -17941,83 +18776,83 @@ program2.command("show <id>").description("Show full task details").action((id)
|
|
|
17941
18776
|
const resolvedId = resolveTaskId(id);
|
|
17942
18777
|
const task = getTaskWithRelations(resolvedId);
|
|
17943
18778
|
if (!task) {
|
|
17944
|
-
console.error(
|
|
18779
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
17945
18780
|
process.exit(1);
|
|
17946
18781
|
}
|
|
17947
18782
|
if (globalOpts.json) {
|
|
17948
18783
|
output(task, true);
|
|
17949
18784
|
return;
|
|
17950
18785
|
}
|
|
17951
|
-
console.log(
|
|
18786
|
+
console.log(chalk2.bold(`Task Details:
|
|
17952
18787
|
`));
|
|
17953
|
-
console.log(` ${
|
|
17954
|
-
console.log(` ${
|
|
17955
|
-
console.log(` ${
|
|
17956
|
-
console.log(` ${
|
|
18788
|
+
console.log(` ${chalk2.dim("ID:")} ${task.id}`);
|
|
18789
|
+
console.log(` ${chalk2.dim("Title:")} ${task.title}`);
|
|
18790
|
+
console.log(` ${chalk2.dim("Status:")} ${(statusColors4[task.status] || chalk2.white)(task.status)}`);
|
|
18791
|
+
console.log(` ${chalk2.dim("Priority:")} ${(priorityColors2[task.priority] || chalk2.white)(task.priority)}`);
|
|
17957
18792
|
if (task.description)
|
|
17958
|
-
console.log(` ${
|
|
18793
|
+
console.log(` ${chalk2.dim("Desc:")} ${task.description}`);
|
|
17959
18794
|
if (task.assigned_to)
|
|
17960
|
-
console.log(` ${
|
|
18795
|
+
console.log(` ${chalk2.dim("Assigned:")} ${task.assigned_to}`);
|
|
17961
18796
|
if (task.agent_id)
|
|
17962
|
-
console.log(` ${
|
|
18797
|
+
console.log(` ${chalk2.dim("Agent:")} ${task.agent_id}`);
|
|
17963
18798
|
if (task.session_id)
|
|
17964
|
-
console.log(` ${
|
|
18799
|
+
console.log(` ${chalk2.dim("Session:")} ${task.session_id}`);
|
|
17965
18800
|
if (task.locked_by)
|
|
17966
|
-
console.log(` ${
|
|
18801
|
+
console.log(` ${chalk2.dim("Locked:")} ${task.locked_by} (at ${task.locked_at})`);
|
|
17967
18802
|
if (task.requires_approval) {
|
|
17968
|
-
const approvalStatus = task.approved_by ?
|
|
17969
|
-
console.log(` ${
|
|
18803
|
+
const approvalStatus = task.approved_by ? chalk2.green(`approved by ${task.approved_by}`) : chalk2.yellow("pending approval");
|
|
18804
|
+
console.log(` ${chalk2.dim("Approval:")} ${approvalStatus}`);
|
|
17970
18805
|
}
|
|
17971
18806
|
if (task.estimated_minutes)
|
|
17972
|
-
console.log(` ${
|
|
18807
|
+
console.log(` ${chalk2.dim("Estimate:")} ${task.estimated_minutes} minutes`);
|
|
17973
18808
|
if (task.project_id)
|
|
17974
|
-
console.log(` ${
|
|
18809
|
+
console.log(` ${chalk2.dim("Project:")} ${task.project_id}`);
|
|
17975
18810
|
if (task.plan_id)
|
|
17976
|
-
console.log(` ${
|
|
18811
|
+
console.log(` ${chalk2.dim("Plan:")} ${task.plan_id}`);
|
|
17977
18812
|
if (task.working_dir)
|
|
17978
|
-
console.log(` ${
|
|
18813
|
+
console.log(` ${chalk2.dim("WorkDir:")} ${task.working_dir}`);
|
|
17979
18814
|
if (task.parent)
|
|
17980
|
-
console.log(` ${
|
|
18815
|
+
console.log(` ${chalk2.dim("Parent:")} ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
17981
18816
|
if (task.tags.length > 0)
|
|
17982
|
-
console.log(` ${
|
|
17983
|
-
console.log(` ${
|
|
17984
|
-
console.log(` ${
|
|
18817
|
+
console.log(` ${chalk2.dim("Tags:")} ${task.tags.join(", ")}`);
|
|
18818
|
+
console.log(` ${chalk2.dim("Version:")} ${task.version}`);
|
|
18819
|
+
console.log(` ${chalk2.dim("Created:")} ${task.created_at}`);
|
|
17985
18820
|
if (task.started_at)
|
|
17986
|
-
console.log(` ${
|
|
18821
|
+
console.log(` ${chalk2.dim("Started:")} ${task.started_at}`);
|
|
17987
18822
|
if (task.completed_at) {
|
|
17988
|
-
console.log(` ${
|
|
18823
|
+
console.log(` ${chalk2.dim("Done:")} ${task.completed_at}`);
|
|
17989
18824
|
if (task.started_at) {
|
|
17990
18825
|
const dur = Math.round((new Date(task.completed_at).getTime() - new Date(task.started_at).getTime()) / 60000);
|
|
17991
|
-
console.log(` ${
|
|
18826
|
+
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
17992
18827
|
}
|
|
17993
18828
|
}
|
|
17994
18829
|
if (task.subtasks.length > 0) {
|
|
17995
|
-
console.log(
|
|
18830
|
+
console.log(chalk2.bold(`
|
|
17996
18831
|
Subtasks (${task.subtasks.length}):`));
|
|
17997
18832
|
for (const st of task.subtasks) {
|
|
17998
18833
|
console.log(` ${formatTaskLine(st)}`);
|
|
17999
18834
|
}
|
|
18000
18835
|
}
|
|
18001
18836
|
if (task.dependencies.length > 0) {
|
|
18002
|
-
console.log(
|
|
18837
|
+
console.log(chalk2.bold(`
|
|
18003
18838
|
Depends on (${task.dependencies.length}):`));
|
|
18004
18839
|
for (const dep of task.dependencies) {
|
|
18005
18840
|
console.log(` ${formatTaskLine(dep)}`);
|
|
18006
18841
|
}
|
|
18007
18842
|
}
|
|
18008
18843
|
if (task.blocked_by.length > 0) {
|
|
18009
|
-
console.log(
|
|
18844
|
+
console.log(chalk2.bold(`
|
|
18010
18845
|
Blocks (${task.blocked_by.length}):`));
|
|
18011
18846
|
for (const b of task.blocked_by) {
|
|
18012
18847
|
console.log(` ${formatTaskLine(b)}`);
|
|
18013
18848
|
}
|
|
18014
18849
|
}
|
|
18015
18850
|
if (task.comments.length > 0) {
|
|
18016
|
-
console.log(
|
|
18851
|
+
console.log(chalk2.bold(`
|
|
18017
18852
|
Comments (${task.comments.length}):`));
|
|
18018
18853
|
for (const c of task.comments) {
|
|
18019
|
-
const agent = c.agent_id ?
|
|
18020
|
-
console.log(` ${agent}${
|
|
18854
|
+
const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
|
|
18855
|
+
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
18021
18856
|
}
|
|
18022
18857
|
}
|
|
18023
18858
|
});
|
|
@@ -18031,12 +18866,12 @@ program2.command("inspect [id]").description("Full orientation for a task \u2014
|
|
|
18031
18866
|
resolvedId = active[0].id;
|
|
18032
18867
|
}
|
|
18033
18868
|
if (!resolvedId) {
|
|
18034
|
-
console.error(
|
|
18869
|
+
console.error(chalk2.red("No task ID given and no active task found. Pass an ID or use --agent."));
|
|
18035
18870
|
process.exit(1);
|
|
18036
18871
|
}
|
|
18037
18872
|
const task = getTaskWithRelations(resolvedId);
|
|
18038
18873
|
if (!task) {
|
|
18039
|
-
console.error(
|
|
18874
|
+
console.error(chalk2.red(`Task not found: ${id || resolvedId}`));
|
|
18040
18875
|
process.exit(1);
|
|
18041
18876
|
}
|
|
18042
18877
|
if (globalOpts.json) {
|
|
@@ -18056,59 +18891,59 @@ program2.command("inspect [id]").description("Full orientation for a task \u2014
|
|
|
18056
18891
|
return;
|
|
18057
18892
|
}
|
|
18058
18893
|
const sid = task.short_id || task.id.slice(0, 8);
|
|
18059
|
-
const statusColor = statusColors4[task.status] ||
|
|
18060
|
-
const prioColor = priorityColors2[task.priority] ||
|
|
18061
|
-
console.log(
|
|
18062
|
-
${
|
|
18894
|
+
const statusColor = statusColors4[task.status] || chalk2.white;
|
|
18895
|
+
const prioColor = priorityColors2[task.priority] || chalk2.white;
|
|
18896
|
+
console.log(chalk2.bold(`
|
|
18897
|
+
${chalk2.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${task.title}
|
|
18063
18898
|
`));
|
|
18064
18899
|
if (task.description) {
|
|
18065
|
-
console.log(
|
|
18900
|
+
console.log(chalk2.dim("Description:"));
|
|
18066
18901
|
console.log(` ${task.description}
|
|
18067
18902
|
`);
|
|
18068
18903
|
}
|
|
18069
18904
|
if (task.assigned_to)
|
|
18070
|
-
console.log(` ${
|
|
18905
|
+
console.log(` ${chalk2.dim("Assigned:")} ${task.assigned_to}`);
|
|
18071
18906
|
if (task.locked_by)
|
|
18072
|
-
console.log(` ${
|
|
18907
|
+
console.log(` ${chalk2.dim("Locked by:")} ${task.locked_by}`);
|
|
18073
18908
|
if (task.project_id)
|
|
18074
|
-
console.log(` ${
|
|
18909
|
+
console.log(` ${chalk2.dim("Project:")} ${task.project_id}`);
|
|
18075
18910
|
if (task.plan_id)
|
|
18076
|
-
console.log(` ${
|
|
18911
|
+
console.log(` ${chalk2.dim("Plan:")} ${task.plan_id}`);
|
|
18077
18912
|
if (task.started_at)
|
|
18078
|
-
console.log(` ${
|
|
18913
|
+
console.log(` ${chalk2.dim("Started:")} ${task.started_at}`);
|
|
18079
18914
|
if (task.completed_at) {
|
|
18080
|
-
console.log(` ${
|
|
18915
|
+
console.log(` ${chalk2.dim("Completed:")} ${task.completed_at}`);
|
|
18081
18916
|
if (task.started_at) {
|
|
18082
18917
|
const dur = Math.round((new Date(task.completed_at).getTime() - new Date(task.started_at).getTime()) / 60000);
|
|
18083
|
-
console.log(` ${
|
|
18918
|
+
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
18084
18919
|
}
|
|
18085
18920
|
}
|
|
18086
18921
|
if (task.estimated_minutes)
|
|
18087
|
-
console.log(` ${
|
|
18922
|
+
console.log(` ${chalk2.dim("Estimate:")} ${task.estimated_minutes}m`);
|
|
18088
18923
|
if (task.tags.length > 0)
|
|
18089
|
-
console.log(` ${
|
|
18924
|
+
console.log(` ${chalk2.dim("Tags:")} ${task.tags.join(", ")}`);
|
|
18090
18925
|
const unfinishedDeps = task.dependencies.filter((d) => d.status !== "completed" && d.status !== "cancelled");
|
|
18091
18926
|
if (task.dependencies.length > 0) {
|
|
18092
|
-
console.log(
|
|
18927
|
+
console.log(chalk2.bold(`
|
|
18093
18928
|
Depends on (${task.dependencies.length}):`));
|
|
18094
18929
|
for (const dep of task.dependencies) {
|
|
18095
18930
|
const blocked = dep.status !== "completed" && dep.status !== "cancelled";
|
|
18096
|
-
const icon = blocked ?
|
|
18931
|
+
const icon = blocked ? chalk2.red("\u2717") : chalk2.green("\u2713");
|
|
18097
18932
|
console.log(` ${icon} ${formatTaskLine(dep)}`);
|
|
18098
18933
|
}
|
|
18099
18934
|
}
|
|
18100
18935
|
if (unfinishedDeps.length > 0) {
|
|
18101
|
-
console.log(
|
|
18936
|
+
console.log(chalk2.red(`
|
|
18102
18937
|
BLOCKED by ${unfinishedDeps.length} unfinished dep(s)`));
|
|
18103
18938
|
}
|
|
18104
18939
|
if (task.blocked_by.length > 0) {
|
|
18105
|
-
console.log(
|
|
18940
|
+
console.log(chalk2.bold(`
|
|
18106
18941
|
Blocks (${task.blocked_by.length}):`));
|
|
18107
18942
|
for (const b of task.blocked_by)
|
|
18108
18943
|
console.log(` ${formatTaskLine(b)}`);
|
|
18109
18944
|
}
|
|
18110
18945
|
if (task.subtasks.length > 0) {
|
|
18111
|
-
console.log(
|
|
18946
|
+
console.log(chalk2.bold(`
|
|
18112
18947
|
Subtasks (${task.subtasks.length}):`));
|
|
18113
18948
|
for (const st of task.subtasks)
|
|
18114
18949
|
console.log(` ${formatTaskLine(st)}`);
|
|
@@ -18117,36 +18952,36 @@ ${chalk.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${tas
|
|
|
18117
18952
|
const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
18118
18953
|
const files = listTaskFiles2(task.id);
|
|
18119
18954
|
if (files.length > 0) {
|
|
18120
|
-
console.log(
|
|
18955
|
+
console.log(chalk2.bold(`
|
|
18121
18956
|
Files (${files.length}):`));
|
|
18122
18957
|
for (const f of files)
|
|
18123
|
-
console.log(` ${
|
|
18958
|
+
console.log(` ${chalk2.dim(f.role || "file")} ${f.path}`);
|
|
18124
18959
|
}
|
|
18125
18960
|
} catch {}
|
|
18126
18961
|
try {
|
|
18127
18962
|
const { getTaskCommits: getTaskCommits2 } = (init_task_commits(), __toCommonJS(exports_task_commits));
|
|
18128
18963
|
const commits = getTaskCommits2(task.id);
|
|
18129
18964
|
if (commits.length > 0) {
|
|
18130
|
-
console.log(
|
|
18965
|
+
console.log(chalk2.bold(`
|
|
18131
18966
|
Commits (${commits.length}):`));
|
|
18132
18967
|
for (const c of commits)
|
|
18133
|
-
console.log(` ${
|
|
18968
|
+
console.log(` ${chalk2.yellow(c.commit_hash.slice(0, 7))} ${c.message || ""}`);
|
|
18134
18969
|
}
|
|
18135
18970
|
} catch {}
|
|
18136
18971
|
if (task.comments.length > 0) {
|
|
18137
|
-
console.log(
|
|
18972
|
+
console.log(chalk2.bold(`
|
|
18138
18973
|
Comments (${task.comments.length}):`));
|
|
18139
18974
|
for (const c of task.comments) {
|
|
18140
|
-
const agent = c.agent_id ?
|
|
18141
|
-
console.log(` ${agent}${
|
|
18975
|
+
const agent = c.agent_id ? chalk2.cyan(`[${c.agent_id}] `) : "";
|
|
18976
|
+
console.log(` ${agent}${chalk2.dim(c.created_at)}: ${c.content}`);
|
|
18142
18977
|
}
|
|
18143
18978
|
}
|
|
18144
18979
|
if (task.checklist && task.checklist.length > 0) {
|
|
18145
18980
|
const done = task.checklist.filter((c) => c.checked).length;
|
|
18146
|
-
console.log(
|
|
18981
|
+
console.log(chalk2.bold(`
|
|
18147
18982
|
Checklist (${done}/${task.checklist.length}):`));
|
|
18148
18983
|
for (const item of task.checklist) {
|
|
18149
|
-
const icon = item.checked ?
|
|
18984
|
+
const icon = item.checked ? chalk2.green("\u2611") : chalk2.dim("\u2610");
|
|
18150
18985
|
console.log(` ${icon} ${item.text || item.title}`);
|
|
18151
18986
|
}
|
|
18152
18987
|
}
|
|
@@ -18162,16 +18997,16 @@ program2.command("history <id>").description("Show change history for a task (au
|
|
|
18162
18997
|
return;
|
|
18163
18998
|
}
|
|
18164
18999
|
if (history.length === 0) {
|
|
18165
|
-
console.log(
|
|
19000
|
+
console.log(chalk2.dim("No history for this task."));
|
|
18166
19001
|
return;
|
|
18167
19002
|
}
|
|
18168
|
-
console.log(
|
|
19003
|
+
console.log(chalk2.bold(`${history.length} change(s):
|
|
18169
19004
|
`));
|
|
18170
19005
|
for (const h of history) {
|
|
18171
|
-
const agent = h.agent_id ?
|
|
18172
|
-
const field = h.field ?
|
|
18173
|
-
const change = h.old_value && h.new_value ? ` ${
|
|
18174
|
-
console.log(` ${
|
|
19006
|
+
const agent = h.agent_id ? chalk2.cyan(` by ${h.agent_id}`) : "";
|
|
19007
|
+
const field = h.field ? chalk2.yellow(` ${h.field}`) : "";
|
|
19008
|
+
const change = h.old_value && h.new_value ? ` ${chalk2.red(h.old_value)} \u2192 ${chalk2.green(h.new_value)}` : h.new_value ? ` \u2192 ${chalk2.green(h.new_value)}` : "";
|
|
19009
|
+
console.log(` ${chalk2.dim(h.created_at)} ${chalk2.bold(h.action)}${field}${change}${agent}`);
|
|
18175
19010
|
}
|
|
18176
19011
|
});
|
|
18177
19012
|
program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list").option("--task-list <id>", "Move to a task list (alias for --list)").option("--estimated <minutes>", "Estimated time in minutes").option("--approval", "Require approval before completion").action((id, opts) => {
|
|
@@ -18181,14 +19016,14 @@ program2.command("update <id>").description("Update a task").option("--title <te
|
|
|
18181
19016
|
const resolvedId = resolveTaskId(id);
|
|
18182
19017
|
const current = getTask(resolvedId);
|
|
18183
19018
|
if (!current) {
|
|
18184
|
-
console.error(
|
|
19019
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
18185
19020
|
process.exit(1);
|
|
18186
19021
|
}
|
|
18187
19022
|
const taskListId = opts.list ? (() => {
|
|
18188
19023
|
const db = getDatabase();
|
|
18189
19024
|
const resolved = resolvePartialId(db, "task_lists", opts.list);
|
|
18190
19025
|
if (!resolved) {
|
|
18191
|
-
console.error(
|
|
19026
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
18192
19027
|
process.exit(1);
|
|
18193
19028
|
}
|
|
18194
19029
|
return resolved;
|
|
@@ -18213,7 +19048,7 @@ program2.command("update <id>").description("Update a task").option("--title <te
|
|
|
18213
19048
|
if (globalOpts.json) {
|
|
18214
19049
|
output(task, true);
|
|
18215
19050
|
} else {
|
|
18216
|
-
console.log(
|
|
19051
|
+
console.log(chalk2.green("Task updated:"));
|
|
18217
19052
|
console.log(formatTaskLine(task));
|
|
18218
19053
|
}
|
|
18219
19054
|
});
|
|
@@ -18233,7 +19068,7 @@ program2.command("done <id>").description("Mark a task as completed").option("--
|
|
|
18233
19068
|
if (globalOpts.json) {
|
|
18234
19069
|
output(task, true);
|
|
18235
19070
|
} else {
|
|
18236
|
-
console.log(
|
|
19071
|
+
console.log(chalk2.green("Task completed:"));
|
|
18237
19072
|
console.log(formatTaskLine(task));
|
|
18238
19073
|
}
|
|
18239
19074
|
});
|
|
@@ -18242,15 +19077,15 @@ program2.command("approve <id>").description("Approve a task that requires appro
|
|
|
18242
19077
|
const resolvedId = resolveTaskId(id);
|
|
18243
19078
|
const task = getTask(resolvedId);
|
|
18244
19079
|
if (!task) {
|
|
18245
|
-
console.error(
|
|
19080
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
18246
19081
|
process.exit(1);
|
|
18247
19082
|
}
|
|
18248
19083
|
if (!task.requires_approval) {
|
|
18249
|
-
console.log(
|
|
19084
|
+
console.log(chalk2.yellow("This task does not require approval."));
|
|
18250
19085
|
return;
|
|
18251
19086
|
}
|
|
18252
19087
|
if (task.approved_by) {
|
|
18253
|
-
console.log(
|
|
19088
|
+
console.log(chalk2.yellow(`Already approved by ${task.approved_by}.`));
|
|
18254
19089
|
return;
|
|
18255
19090
|
}
|
|
18256
19091
|
try {
|
|
@@ -18258,7 +19093,7 @@ program2.command("approve <id>").description("Approve a task that requires appro
|
|
|
18258
19093
|
if (globalOpts.json) {
|
|
18259
19094
|
output(updated, true);
|
|
18260
19095
|
} else {
|
|
18261
|
-
console.log(
|
|
19096
|
+
console.log(chalk2.green(`Task approved by ${globalOpts.agent || "cli"}:`));
|
|
18262
19097
|
console.log(formatTaskLine(updated));
|
|
18263
19098
|
}
|
|
18264
19099
|
} catch (e) {
|
|
@@ -18278,7 +19113,7 @@ program2.command("start <id>").description("Claim, lock, and start a task").acti
|
|
|
18278
19113
|
if (globalOpts.json) {
|
|
18279
19114
|
output(task, true);
|
|
18280
19115
|
} else {
|
|
18281
|
-
console.log(
|
|
19116
|
+
console.log(chalk2.green(`Task started by ${agentId}:`));
|
|
18282
19117
|
console.log(formatTaskLine(task));
|
|
18283
19118
|
}
|
|
18284
19119
|
});
|
|
@@ -18295,9 +19130,9 @@ program2.command("lock <id>").description("Acquire exclusive lock on a task").ac
|
|
|
18295
19130
|
if (globalOpts.json) {
|
|
18296
19131
|
output(result, true);
|
|
18297
19132
|
} else if (result.success) {
|
|
18298
|
-
console.log(
|
|
19133
|
+
console.log(chalk2.green(`Lock acquired by ${agentId}`));
|
|
18299
19134
|
} else {
|
|
18300
|
-
console.error(
|
|
19135
|
+
console.error(chalk2.red(`Lock failed: ${result.error}`));
|
|
18301
19136
|
process.exit(1);
|
|
18302
19137
|
}
|
|
18303
19138
|
});
|
|
@@ -18312,7 +19147,7 @@ program2.command("unlock <id>").description("Release lock on a task").action((id
|
|
|
18312
19147
|
if (globalOpts.json) {
|
|
18313
19148
|
output({ success: true }, true);
|
|
18314
19149
|
} else {
|
|
18315
|
-
console.log(
|
|
19150
|
+
console.log(chalk2.green("Lock released."));
|
|
18316
19151
|
}
|
|
18317
19152
|
});
|
|
18318
19153
|
program2.command("delete <id>").description("Delete a task").action((id) => {
|
|
@@ -18322,9 +19157,9 @@ program2.command("delete <id>").description("Delete a task").action((id) => {
|
|
|
18322
19157
|
if (globalOpts.json) {
|
|
18323
19158
|
output({ deleted }, true);
|
|
18324
19159
|
} else if (deleted) {
|
|
18325
|
-
console.log(
|
|
19160
|
+
console.log(chalk2.green("Task deleted."));
|
|
18326
19161
|
} else {
|
|
18327
|
-
console.error(
|
|
19162
|
+
console.error(chalk2.red("Task not found."));
|
|
18328
19163
|
process.exit(1);
|
|
18329
19164
|
}
|
|
18330
19165
|
});
|
|
@@ -18335,9 +19170,9 @@ program2.command("remove <id>").description("Remove/delete a task (alias for del
|
|
|
18335
19170
|
if (globalOpts.json) {
|
|
18336
19171
|
output({ deleted }, true);
|
|
18337
19172
|
} else if (deleted) {
|
|
18338
|
-
console.log(
|
|
19173
|
+
console.log(chalk2.green("Task removed."));
|
|
18339
19174
|
} else {
|
|
18340
|
-
console.error(
|
|
19175
|
+
console.error(chalk2.red("Task not found."));
|
|
18341
19176
|
process.exit(1);
|
|
18342
19177
|
}
|
|
18343
19178
|
});
|
|
@@ -18357,7 +19192,7 @@ program2.command("bulk <action> <ids...>").description("Bulk operation on multip
|
|
|
18357
19192
|
deleteTask(resolvedId);
|
|
18358
19193
|
results.push({ id: resolvedId, success: true });
|
|
18359
19194
|
} else {
|
|
18360
|
-
console.error(
|
|
19195
|
+
console.error(chalk2.red(`Unknown action: ${action}. Use: done, start, delete`));
|
|
18361
19196
|
process.exit(1);
|
|
18362
19197
|
}
|
|
18363
19198
|
} catch (e) {
|
|
@@ -18369,9 +19204,9 @@ program2.command("bulk <action> <ids...>").description("Bulk operation on multip
|
|
|
18369
19204
|
if (globalOpts.json) {
|
|
18370
19205
|
output({ results, succeeded, failed }, true);
|
|
18371
19206
|
} else {
|
|
18372
|
-
console.log(
|
|
19207
|
+
console.log(chalk2.green(`${action}: ${succeeded} succeeded, ${failed} failed`));
|
|
18373
19208
|
for (const r of results.filter((r2) => !r2.success)) {
|
|
18374
|
-
console.log(
|
|
19209
|
+
console.log(chalk2.red(` ${r.id}: ${r.error}`));
|
|
18375
19210
|
}
|
|
18376
19211
|
}
|
|
18377
19212
|
});
|
|
@@ -18387,8 +19222,8 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18387
19222
|
if (globalOpts.json) {
|
|
18388
19223
|
output(plan, true);
|
|
18389
19224
|
} else {
|
|
18390
|
-
console.log(
|
|
18391
|
-
console.log(`${
|
|
19225
|
+
console.log(chalk2.green("Plan created:"));
|
|
19226
|
+
console.log(`${chalk2.dim(plan.id.slice(0, 8))} ${chalk2.bold(plan.name)} ${chalk2.cyan(`[${plan.status}]`)}`);
|
|
18392
19227
|
}
|
|
18393
19228
|
return;
|
|
18394
19229
|
}
|
|
@@ -18396,12 +19231,12 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18396
19231
|
const db = getDatabase();
|
|
18397
19232
|
const resolvedId = resolvePartialId(db, "plans", opts.show);
|
|
18398
19233
|
if (!resolvedId) {
|
|
18399
|
-
console.error(
|
|
19234
|
+
console.error(chalk2.red(`Could not resolve plan ID: ${opts.show}`));
|
|
18400
19235
|
process.exit(1);
|
|
18401
19236
|
}
|
|
18402
19237
|
const plan = getPlan(resolvedId);
|
|
18403
19238
|
if (!plan) {
|
|
18404
|
-
console.error(
|
|
19239
|
+
console.error(chalk2.red(`Plan not found: ${opts.show}`));
|
|
18405
19240
|
process.exit(1);
|
|
18406
19241
|
}
|
|
18407
19242
|
const tasks = listTasks({ plan_id: resolvedId });
|
|
@@ -18409,24 +19244,24 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18409
19244
|
output({ plan, tasks }, true);
|
|
18410
19245
|
return;
|
|
18411
19246
|
}
|
|
18412
|
-
console.log(
|
|
19247
|
+
console.log(chalk2.bold(`Plan Details:
|
|
18413
19248
|
`));
|
|
18414
|
-
console.log(` ${
|
|
18415
|
-
console.log(` ${
|
|
18416
|
-
console.log(` ${
|
|
19249
|
+
console.log(` ${chalk2.dim("ID:")} ${plan.id}`);
|
|
19250
|
+
console.log(` ${chalk2.dim("Name:")} ${plan.name}`);
|
|
19251
|
+
console.log(` ${chalk2.dim("Status:")} ${chalk2.cyan(plan.status)}`);
|
|
18417
19252
|
if (plan.description)
|
|
18418
|
-
console.log(` ${
|
|
19253
|
+
console.log(` ${chalk2.dim("Desc:")} ${plan.description}`);
|
|
18419
19254
|
if (plan.project_id)
|
|
18420
|
-
console.log(` ${
|
|
18421
|
-
console.log(` ${
|
|
19255
|
+
console.log(` ${chalk2.dim("Project:")} ${plan.project_id}`);
|
|
19256
|
+
console.log(` ${chalk2.dim("Created:")} ${plan.created_at}`);
|
|
18422
19257
|
if (tasks.length > 0) {
|
|
18423
|
-
console.log(
|
|
19258
|
+
console.log(chalk2.bold(`
|
|
18424
19259
|
Tasks (${tasks.length}):`));
|
|
18425
19260
|
for (const t of tasks) {
|
|
18426
19261
|
console.log(` ${formatTaskLine(t)}`);
|
|
18427
19262
|
}
|
|
18428
19263
|
} else {
|
|
18429
|
-
console.log(
|
|
19264
|
+
console.log(chalk2.dim(`
|
|
18430
19265
|
No tasks in this plan.`));
|
|
18431
19266
|
}
|
|
18432
19267
|
return;
|
|
@@ -18435,16 +19270,16 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18435
19270
|
const db = getDatabase();
|
|
18436
19271
|
const resolvedId = resolvePartialId(db, "plans", opts.delete);
|
|
18437
19272
|
if (!resolvedId) {
|
|
18438
|
-
console.error(
|
|
19273
|
+
console.error(chalk2.red(`Could not resolve plan ID: ${opts.delete}`));
|
|
18439
19274
|
process.exit(1);
|
|
18440
19275
|
}
|
|
18441
19276
|
const deleted = deletePlan(resolvedId);
|
|
18442
19277
|
if (globalOpts.json) {
|
|
18443
19278
|
output({ deleted }, true);
|
|
18444
19279
|
} else if (deleted) {
|
|
18445
|
-
console.log(
|
|
19280
|
+
console.log(chalk2.green("Plan deleted."));
|
|
18446
19281
|
} else {
|
|
18447
|
-
console.error(
|
|
19282
|
+
console.error(chalk2.red("Plan not found."));
|
|
18448
19283
|
process.exit(1);
|
|
18449
19284
|
}
|
|
18450
19285
|
return;
|
|
@@ -18453,7 +19288,7 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18453
19288
|
const db = getDatabase();
|
|
18454
19289
|
const resolvedId = resolvePartialId(db, "plans", opts.complete);
|
|
18455
19290
|
if (!resolvedId) {
|
|
18456
|
-
console.error(
|
|
19291
|
+
console.error(chalk2.red(`Could not resolve plan ID: ${opts.complete}`));
|
|
18457
19292
|
process.exit(1);
|
|
18458
19293
|
}
|
|
18459
19294
|
try {
|
|
@@ -18461,8 +19296,8 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18461
19296
|
if (globalOpts.json) {
|
|
18462
19297
|
output(plan, true);
|
|
18463
19298
|
} else {
|
|
18464
|
-
console.log(
|
|
18465
|
-
console.log(`${
|
|
19299
|
+
console.log(chalk2.green("Plan completed:"));
|
|
19300
|
+
console.log(`${chalk2.dim(plan.id.slice(0, 8))} ${chalk2.bold(plan.name)} ${chalk2.cyan(`[${plan.status}]`)}`);
|
|
18466
19301
|
}
|
|
18467
19302
|
} catch (e) {
|
|
18468
19303
|
handleError(e);
|
|
@@ -18475,14 +19310,14 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18475
19310
|
return;
|
|
18476
19311
|
}
|
|
18477
19312
|
if (plans.length === 0) {
|
|
18478
|
-
console.log(
|
|
19313
|
+
console.log(chalk2.dim("No plans found."));
|
|
18479
19314
|
return;
|
|
18480
19315
|
}
|
|
18481
|
-
console.log(
|
|
19316
|
+
console.log(chalk2.bold(`${plans.length} plan(s):
|
|
18482
19317
|
`));
|
|
18483
19318
|
for (const p of plans) {
|
|
18484
|
-
const desc = p.description ?
|
|
18485
|
-
console.log(`${
|
|
19319
|
+
const desc = p.description ? chalk2.dim(` - ${p.description}`) : "";
|
|
19320
|
+
console.log(`${chalk2.dim(p.id.slice(0, 8))} ${chalk2.bold(p.name)} ${chalk2.cyan(`[${p.status}]`)}${desc}`);
|
|
18486
19321
|
}
|
|
18487
19322
|
});
|
|
18488
19323
|
program2.command("templates").description("List and manage task templates").option("--add <name>", "Create a template").option("--title <pattern>", "Title pattern (with --add)").option("-d, --description <text>", "Default description").option("-p, --priority <level>", "Default priority").option("-t, --tags <tags>", "Default tags (comma-separated)").option("--delete <id>", "Delete a template").option("--use <id>", "Create a task from a template").action((opts) => {
|
|
@@ -18490,7 +19325,7 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
18490
19325
|
const { createTemplate: createTemplate2, listTemplates: listTemplates2, deleteTemplate: deleteTemplate2, taskFromTemplate: taskFromTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
18491
19326
|
if (opts.add) {
|
|
18492
19327
|
if (!opts.title) {
|
|
18493
|
-
console.error(
|
|
19328
|
+
console.error(chalk2.red("--title is required with --add"));
|
|
18494
19329
|
process.exit(1);
|
|
18495
19330
|
}
|
|
18496
19331
|
const projectId = autoProject(globalOpts);
|
|
@@ -18505,7 +19340,7 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
18505
19340
|
if (globalOpts.json) {
|
|
18506
19341
|
output(template, true);
|
|
18507
19342
|
} else {
|
|
18508
|
-
console.log(
|
|
19343
|
+
console.log(chalk2.green(`Template created: ${template.id.slice(0, 8)} | ${template.name} | "${template.title_pattern}"`));
|
|
18509
19344
|
}
|
|
18510
19345
|
return;
|
|
18511
19346
|
}
|
|
@@ -18514,9 +19349,9 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
18514
19349
|
if (globalOpts.json) {
|
|
18515
19350
|
output({ deleted }, true);
|
|
18516
19351
|
} else if (deleted) {
|
|
18517
|
-
console.log(
|
|
19352
|
+
console.log(chalk2.green("Template deleted."));
|
|
18518
19353
|
} else {
|
|
18519
|
-
console.error(
|
|
19354
|
+
console.error(chalk2.red("Template not found."));
|
|
18520
19355
|
process.exit(1);
|
|
18521
19356
|
}
|
|
18522
19357
|
return;
|
|
@@ -18532,7 +19367,7 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
18532
19367
|
if (globalOpts.json) {
|
|
18533
19368
|
output(task, true);
|
|
18534
19369
|
} else {
|
|
18535
|
-
console.log(
|
|
19370
|
+
console.log(chalk2.green("Task created from template:"));
|
|
18536
19371
|
console.log(formatTaskLine(task));
|
|
18537
19372
|
}
|
|
18538
19373
|
} catch (e) {
|
|
@@ -18546,13 +19381,13 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
18546
19381
|
return;
|
|
18547
19382
|
}
|
|
18548
19383
|
if (templates.length === 0) {
|
|
18549
|
-
console.log(
|
|
19384
|
+
console.log(chalk2.dim("No templates."));
|
|
18550
19385
|
return;
|
|
18551
19386
|
}
|
|
18552
|
-
console.log(
|
|
19387
|
+
console.log(chalk2.bold(`${templates.length} template(s):
|
|
18553
19388
|
`));
|
|
18554
19389
|
for (const t of templates) {
|
|
18555
|
-
console.log(` ${
|
|
19390
|
+
console.log(` ${chalk2.dim(t.id.slice(0, 8))} ${chalk2.bold(t.name)} ${chalk2.cyan(`"${t.title_pattern}"`)} ${chalk2.yellow(t.priority)}`);
|
|
18556
19391
|
}
|
|
18557
19392
|
});
|
|
18558
19393
|
program2.command("comment <id> <text>").description("Add a comment to a task").action((id, text) => {
|
|
@@ -18567,7 +19402,7 @@ program2.command("comment <id> <text>").description("Add a comment to a task").a
|
|
|
18567
19402
|
if (globalOpts.json) {
|
|
18568
19403
|
output(comment, true);
|
|
18569
19404
|
} else {
|
|
18570
|
-
console.log(
|
|
19405
|
+
console.log(chalk2.green("Comment added."));
|
|
18571
19406
|
}
|
|
18572
19407
|
});
|
|
18573
19408
|
program2.command("search <query>").description("Search tasks").option("--status <status>", "Filter by status").option("--priority <p>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--since <date>", "Only tasks updated after this date (ISO)").option("--blocked", "Only blocked tasks (incomplete dependencies)").option("--has-deps", "Only tasks with dependencies").action((query, opts) => {
|
|
@@ -18592,10 +19427,10 @@ program2.command("search <query>").description("Search tasks").option("--status
|
|
|
18592
19427
|
return;
|
|
18593
19428
|
}
|
|
18594
19429
|
if (tasks.length === 0) {
|
|
18595
|
-
console.log(
|
|
19430
|
+
console.log(chalk2.dim(`No tasks matching "${query}".`));
|
|
18596
19431
|
return;
|
|
18597
19432
|
}
|
|
18598
|
-
console.log(
|
|
19433
|
+
console.log(chalk2.bold(`${tasks.length} result(s) for "${query}":
|
|
18599
19434
|
`));
|
|
18600
19435
|
for (const t of tasks) {
|
|
18601
19436
|
console.log(formatTaskLine(t));
|
|
@@ -18609,13 +19444,13 @@ program2.command("deps <id>").description("Manage task dependencies").option("--
|
|
|
18609
19444
|
try {
|
|
18610
19445
|
addDependency(resolvedId, depId);
|
|
18611
19446
|
} catch (e) {
|
|
18612
|
-
console.error(
|
|
19447
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
18613
19448
|
process.exit(1);
|
|
18614
19449
|
}
|
|
18615
19450
|
if (globalOpts.json) {
|
|
18616
19451
|
output({ task_id: resolvedId, depends_on: depId }, true);
|
|
18617
19452
|
} else {
|
|
18618
|
-
console.log(
|
|
19453
|
+
console.log(chalk2.green("Dependency added."));
|
|
18619
19454
|
}
|
|
18620
19455
|
} else if (opts.remove) {
|
|
18621
19456
|
const depId = resolveTaskId(opts.remove);
|
|
@@ -18623,12 +19458,12 @@ program2.command("deps <id>").description("Manage task dependencies").option("--
|
|
|
18623
19458
|
if (globalOpts.json) {
|
|
18624
19459
|
output({ removed }, true);
|
|
18625
19460
|
} else {
|
|
18626
|
-
console.log(removed ?
|
|
19461
|
+
console.log(removed ? chalk2.green("Dependency removed.") : chalk2.red("Dependency not found."));
|
|
18627
19462
|
}
|
|
18628
19463
|
} else {
|
|
18629
19464
|
const task = getTaskWithRelations(resolvedId);
|
|
18630
19465
|
if (!task) {
|
|
18631
|
-
console.error(
|
|
19466
|
+
console.error(chalk2.red("Task not found."));
|
|
18632
19467
|
process.exit(1);
|
|
18633
19468
|
}
|
|
18634
19469
|
if (globalOpts.json) {
|
|
@@ -18636,19 +19471,19 @@ program2.command("deps <id>").description("Manage task dependencies").option("--
|
|
|
18636
19471
|
return;
|
|
18637
19472
|
}
|
|
18638
19473
|
if (task.dependencies.length > 0) {
|
|
18639
|
-
console.log(
|
|
19474
|
+
console.log(chalk2.bold("Depends on:"));
|
|
18640
19475
|
for (const dep of task.dependencies) {
|
|
18641
19476
|
console.log(` ${formatTaskLine(dep)}`);
|
|
18642
19477
|
}
|
|
18643
19478
|
}
|
|
18644
19479
|
if (task.blocked_by.length > 0) {
|
|
18645
|
-
console.log(
|
|
19480
|
+
console.log(chalk2.bold("Blocks:"));
|
|
18646
19481
|
for (const b of task.blocked_by) {
|
|
18647
19482
|
console.log(` ${formatTaskLine(b)}`);
|
|
18648
19483
|
}
|
|
18649
19484
|
}
|
|
18650
19485
|
if (task.dependencies.length === 0 && task.blocked_by.length === 0) {
|
|
18651
|
-
console.log(
|
|
19486
|
+
console.log(chalk2.dim("No dependencies."));
|
|
18652
19487
|
}
|
|
18653
19488
|
}
|
|
18654
19489
|
});
|
|
@@ -18670,9 +19505,9 @@ program2.command("projects").description("List and manage projects").option("--a
|
|
|
18670
19505
|
if (globalOpts.json) {
|
|
18671
19506
|
output(project, true);
|
|
18672
19507
|
} else {
|
|
18673
|
-
console.log(
|
|
19508
|
+
console.log(chalk2.green(`Project registered: ${project.name} (${project.path})`));
|
|
18674
19509
|
if (project.task_list_id)
|
|
18675
|
-
console.log(
|
|
19510
|
+
console.log(chalk2.dim(` Task list: ${project.task_list_id}`));
|
|
18676
19511
|
}
|
|
18677
19512
|
return;
|
|
18678
19513
|
}
|
|
@@ -18682,14 +19517,14 @@ program2.command("projects").description("List and manage projects").option("--a
|
|
|
18682
19517
|
return;
|
|
18683
19518
|
}
|
|
18684
19519
|
if (projects.length === 0) {
|
|
18685
|
-
console.log(
|
|
19520
|
+
console.log(chalk2.dim("No projects registered."));
|
|
18686
19521
|
return;
|
|
18687
19522
|
}
|
|
18688
|
-
console.log(
|
|
19523
|
+
console.log(chalk2.bold(`${projects.length} project(s):
|
|
18689
19524
|
`));
|
|
18690
19525
|
for (const p of projects) {
|
|
18691
|
-
const taskList = p.task_list_id ?
|
|
18692
|
-
console.log(`${
|
|
19526
|
+
const taskList = p.task_list_id ? chalk2.cyan(` [${p.task_list_id}]`) : "";
|
|
19527
|
+
console.log(`${chalk2.dim(p.id.slice(0, 8))} ${chalk2.bold(p.name)} ${chalk2.dim(p.path)}${taskList}${p.description ? ` - ${p.description}` : ""}`);
|
|
18693
19528
|
}
|
|
18694
19529
|
});
|
|
18695
19530
|
program2.command("extract <path>").description("Extract TODO/FIXME/HACK/BUG/XXX/NOTE comments from source files and create tasks").option("--dry-run", "Show extracted comments without creating tasks").option("--pattern <tags>", "Comma-separated tags to look for (default: TODO,FIXME,HACK,XXX,BUG,NOTE)").option("-t, --tags <tags>", "Extra comma-separated tags to add to created tasks").option("--assign <agent>", "Assign extracted tasks to an agent").option("--list <id>", "Task list ID").option("--ext <extensions>", "Comma-separated file extensions to scan (e.g. ts,py,go)").action((scanPath, opts) => {
|
|
@@ -18702,7 +19537,7 @@ program2.command("extract <path>").description("Extract TODO/FIXME/HACK/BUG/XXX/
|
|
|
18702
19537
|
const db = getDatabase();
|
|
18703
19538
|
const id = resolvePartialId(db, "task_lists", opts.list);
|
|
18704
19539
|
if (!id) {
|
|
18705
|
-
console.error(
|
|
19540
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
18706
19541
|
process.exit(1);
|
|
18707
19542
|
}
|
|
18708
19543
|
return id;
|
|
@@ -18721,18 +19556,18 @@ program2.command("extract <path>").description("Extract TODO/FIXME/HACK/BUG/XXX/
|
|
|
18721
19556
|
if (globalOpts.json) {
|
|
18722
19557
|
console.log(JSON.stringify(opts.dryRun ? { comments: result.comments } : { tasks_created: result.tasks.length, skipped: result.skipped, comments: result.comments.length }, null, 2));
|
|
18723
19558
|
} else if (opts.dryRun) {
|
|
18724
|
-
console.log(
|
|
19559
|
+
console.log(chalk2.cyan(`Found ${result.comments.length} comment(s):
|
|
18725
19560
|
`));
|
|
18726
19561
|
for (const c of result.comments) {
|
|
18727
|
-
console.log(` ${
|
|
18728
|
-
console.log(` ${
|
|
19562
|
+
console.log(` ${chalk2.yellow(`[${c.tag}]`)} ${c.message}`);
|
|
19563
|
+
console.log(` ${chalk2.gray(`${c.file}:${c.line}`)}`);
|
|
18729
19564
|
}
|
|
18730
19565
|
} else {
|
|
18731
|
-
console.log(
|
|
19566
|
+
console.log(chalk2.green(`Created ${result.tasks.length} task(s)`));
|
|
18732
19567
|
if (result.skipped > 0) {
|
|
18733
|
-
console.log(
|
|
19568
|
+
console.log(chalk2.gray(`Skipped ${result.skipped} duplicate(s)`));
|
|
18734
19569
|
}
|
|
18735
|
-
console.log(
|
|
19570
|
+
console.log(chalk2.gray(`Total comments found: ${result.comments.length}`));
|
|
18736
19571
|
for (const t of result.tasks) {
|
|
18737
19572
|
console.log(formatTaskLine(t));
|
|
18738
19573
|
}
|
|
@@ -18782,7 +19617,7 @@ program2.command("sync").description("Sync tasks with an agent task list (Claude
|
|
|
18782
19617
|
const agent = opts.agent || "claude";
|
|
18783
19618
|
const taskListId = resolveTaskListId2(agent, opts.taskList, project?.task_list_id);
|
|
18784
19619
|
if (!taskListId) {
|
|
18785
|
-
console.error(
|
|
19620
|
+
console.error(chalk2.red(`Could not detect task list ID for ${agent}. Use --task-list <id> or set appropriate env vars.`));
|
|
18786
19621
|
process.exit(1);
|
|
18787
19622
|
}
|
|
18788
19623
|
result = syncWithAgent(agent, taskListId, projectId, direction, { prefer });
|
|
@@ -18792,14 +19627,14 @@ program2.command("sync").description("Sync tasks with an agent task list (Claude
|
|
|
18792
19627
|
return;
|
|
18793
19628
|
}
|
|
18794
19629
|
if (result.pulled > 0)
|
|
18795
|
-
console.log(
|
|
19630
|
+
console.log(chalk2.green(`Pulled ${result.pulled} task(s).`));
|
|
18796
19631
|
if (result.pushed > 0)
|
|
18797
|
-
console.log(
|
|
19632
|
+
console.log(chalk2.green(`Pushed ${result.pushed} task(s).`));
|
|
18798
19633
|
if (result.pulled === 0 && result.pushed === 0 && result.errors.length === 0) {
|
|
18799
|
-
console.log(
|
|
19634
|
+
console.log(chalk2.dim("Nothing to sync."));
|
|
18800
19635
|
}
|
|
18801
19636
|
for (const err of result.errors) {
|
|
18802
|
-
console.error(
|
|
19637
|
+
console.error(chalk2.red(` Error: ${err}`));
|
|
18803
19638
|
}
|
|
18804
19639
|
});
|
|
18805
19640
|
var hooks = program2.command("hooks").description("Manage Claude Code hook integration");
|
|
@@ -18810,9 +19645,9 @@ hooks.command("install").description("Install Claude Code hooks for auto-sync").
|
|
|
18810
19645
|
if (p)
|
|
18811
19646
|
todosBin = p;
|
|
18812
19647
|
} catch {}
|
|
18813
|
-
const hooksDir =
|
|
18814
|
-
if (!
|
|
18815
|
-
|
|
19648
|
+
const hooksDir = join11(process.cwd(), ".claude", "hooks");
|
|
19649
|
+
if (!existsSync9(hooksDir))
|
|
19650
|
+
mkdirSync5(hooksDir, { recursive: true });
|
|
18816
19651
|
const hookScript = `#!/usr/bin/env bash
|
|
18817
19652
|
# Auto-generated by: todos hooks install
|
|
18818
19653
|
# Syncs todos with Claude Code task list on tool use events.
|
|
@@ -18836,11 +19671,11 @@ esac
|
|
|
18836
19671
|
|
|
18837
19672
|
exit 0
|
|
18838
19673
|
`;
|
|
18839
|
-
const hookPath =
|
|
18840
|
-
|
|
19674
|
+
const hookPath = join11(hooksDir, "todos-sync.sh");
|
|
19675
|
+
writeFileSync5(hookPath, hookScript);
|
|
18841
19676
|
execSync2(`chmod +x "${hookPath}"`);
|
|
18842
|
-
console.log(
|
|
18843
|
-
const settingsPath =
|
|
19677
|
+
console.log(chalk2.green(`Hook script created: ${hookPath}`));
|
|
19678
|
+
const settingsPath = join11(process.cwd(), ".claude", "settings.json");
|
|
18844
19679
|
const settings = readJsonFile2(settingsPath);
|
|
18845
19680
|
if (!settings["hooks"]) {
|
|
18846
19681
|
settings["hooks"] = {};
|
|
@@ -18866,8 +19701,8 @@ exit 0
|
|
|
18866
19701
|
});
|
|
18867
19702
|
hooksConfig["PostToolUse"] = filtered;
|
|
18868
19703
|
writeJsonFile2(settingsPath, settings);
|
|
18869
|
-
console.log(
|
|
18870
|
-
console.log(
|
|
19704
|
+
console.log(chalk2.green(`Claude Code hooks configured in: ${settingsPath}`));
|
|
19705
|
+
console.log(chalk2.dim("Task list ID auto-detected from project."));
|
|
18871
19706
|
});
|
|
18872
19707
|
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) => {
|
|
18873
19708
|
if (opts.register) {
|
|
@@ -18887,37 +19722,37 @@ function getMcpBinaryPath() {
|
|
|
18887
19722
|
if (p)
|
|
18888
19723
|
return p;
|
|
18889
19724
|
} catch {}
|
|
18890
|
-
const bunBin =
|
|
18891
|
-
if (
|
|
19725
|
+
const bunBin = join11(HOME2, ".bun", "bin", "todos-mcp");
|
|
19726
|
+
if (existsSync9(bunBin))
|
|
18892
19727
|
return bunBin;
|
|
18893
19728
|
return "todos-mcp";
|
|
18894
19729
|
}
|
|
18895
19730
|
function readJsonFile2(path) {
|
|
18896
|
-
if (!
|
|
19731
|
+
if (!existsSync9(path))
|
|
18897
19732
|
return {};
|
|
18898
19733
|
try {
|
|
18899
|
-
return JSON.parse(
|
|
19734
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
18900
19735
|
} catch {
|
|
18901
19736
|
return {};
|
|
18902
19737
|
}
|
|
18903
19738
|
}
|
|
18904
19739
|
function writeJsonFile2(path, data) {
|
|
18905
19740
|
const dir = dirname4(path);
|
|
18906
|
-
if (!
|
|
18907
|
-
|
|
18908
|
-
|
|
19741
|
+
if (!existsSync9(dir))
|
|
19742
|
+
mkdirSync5(dir, { recursive: true });
|
|
19743
|
+
writeFileSync5(path, JSON.stringify(data, null, 2) + `
|
|
18909
19744
|
`);
|
|
18910
19745
|
}
|
|
18911
19746
|
function readTomlFile(path) {
|
|
18912
|
-
if (!
|
|
19747
|
+
if (!existsSync9(path))
|
|
18913
19748
|
return "";
|
|
18914
|
-
return
|
|
19749
|
+
return readFileSync6(path, "utf-8");
|
|
18915
19750
|
}
|
|
18916
19751
|
function writeTomlFile(path, content) {
|
|
18917
19752
|
const dir = dirname4(path);
|
|
18918
|
-
if (!
|
|
18919
|
-
|
|
18920
|
-
|
|
19753
|
+
if (!existsSync9(dir))
|
|
19754
|
+
mkdirSync5(dir, { recursive: true });
|
|
19755
|
+
writeFileSync5(path, content);
|
|
18921
19756
|
}
|
|
18922
19757
|
function registerClaude(binPath, global) {
|
|
18923
19758
|
const scope = global ? "user" : "project";
|
|
@@ -18925,24 +19760,24 @@ function registerClaude(binPath, global) {
|
|
|
18925
19760
|
try {
|
|
18926
19761
|
const { execSync: execSync3 } = __require("child_process");
|
|
18927
19762
|
execSync3(cmd, { stdio: "pipe" });
|
|
18928
|
-
console.log(
|
|
19763
|
+
console.log(chalk2.green(`Claude Code (${scope}): registered via 'claude mcp add'`));
|
|
18929
19764
|
} catch {
|
|
18930
|
-
console.log(
|
|
18931
|
-
console.log(
|
|
19765
|
+
console.log(chalk2.yellow(`Claude Code: could not auto-register. Run this command manually:`));
|
|
19766
|
+
console.log(chalk2.cyan(` ${cmd}`));
|
|
18932
19767
|
}
|
|
18933
19768
|
}
|
|
18934
19769
|
function unregisterClaude(_global) {
|
|
18935
19770
|
try {
|
|
18936
19771
|
const { execSync: execSync3 } = __require("child_process");
|
|
18937
19772
|
execSync3("claude mcp remove todos", { stdio: "pipe" });
|
|
18938
|
-
console.log(
|
|
19773
|
+
console.log(chalk2.green(`Claude Code: removed todos MCP server`));
|
|
18939
19774
|
} catch {
|
|
18940
|
-
console.log(
|
|
18941
|
-
console.log(
|
|
19775
|
+
console.log(chalk2.yellow(`Claude Code: could not auto-remove. Run manually:`));
|
|
19776
|
+
console.log(chalk2.cyan(" claude mcp remove todos"));
|
|
18942
19777
|
}
|
|
18943
19778
|
}
|
|
18944
19779
|
function registerCodex(binPath) {
|
|
18945
|
-
const configPath =
|
|
19780
|
+
const configPath = join11(HOME2, ".codex", "config.toml");
|
|
18946
19781
|
let content = readTomlFile(configPath);
|
|
18947
19782
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
18948
19783
|
const block = `
|
|
@@ -18953,19 +19788,19 @@ args = []
|
|
|
18953
19788
|
content = content.trimEnd() + `
|
|
18954
19789
|
` + block;
|
|
18955
19790
|
writeTomlFile(configPath, content);
|
|
18956
|
-
console.log(
|
|
19791
|
+
console.log(chalk2.green(`Codex CLI: registered in ${configPath}`));
|
|
18957
19792
|
}
|
|
18958
19793
|
function unregisterCodex() {
|
|
18959
|
-
const configPath =
|
|
19794
|
+
const configPath = join11(HOME2, ".codex", "config.toml");
|
|
18960
19795
|
let content = readTomlFile(configPath);
|
|
18961
19796
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
18962
|
-
console.log(
|
|
19797
|
+
console.log(chalk2.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
18963
19798
|
return;
|
|
18964
19799
|
}
|
|
18965
19800
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
18966
19801
|
writeTomlFile(configPath, content.trimEnd() + `
|
|
18967
19802
|
`);
|
|
18968
|
-
console.log(
|
|
19803
|
+
console.log(chalk2.green(`Codex CLI: unregistered from ${configPath}`));
|
|
18969
19804
|
}
|
|
18970
19805
|
function removeTomlBlock(content, blockName) {
|
|
18971
19806
|
const lines = content.split(`
|
|
@@ -18989,7 +19824,7 @@ function removeTomlBlock(content, blockName) {
|
|
|
18989
19824
|
`);
|
|
18990
19825
|
}
|
|
18991
19826
|
function registerGemini(binPath) {
|
|
18992
|
-
const configPath =
|
|
19827
|
+
const configPath = join11(HOME2, ".gemini", "settings.json");
|
|
18993
19828
|
const config = readJsonFile2(configPath);
|
|
18994
19829
|
if (!config["mcpServers"]) {
|
|
18995
19830
|
config["mcpServers"] = {};
|
|
@@ -19000,19 +19835,19 @@ function registerGemini(binPath) {
|
|
|
19000
19835
|
args: []
|
|
19001
19836
|
};
|
|
19002
19837
|
writeJsonFile2(configPath, config);
|
|
19003
|
-
console.log(
|
|
19838
|
+
console.log(chalk2.green(`Gemini CLI: registered in ${configPath}`));
|
|
19004
19839
|
}
|
|
19005
19840
|
function unregisterGemini() {
|
|
19006
|
-
const configPath =
|
|
19841
|
+
const configPath = join11(HOME2, ".gemini", "settings.json");
|
|
19007
19842
|
const config = readJsonFile2(configPath);
|
|
19008
19843
|
const servers = config["mcpServers"];
|
|
19009
19844
|
if (!servers || !("todos" in servers)) {
|
|
19010
|
-
console.log(
|
|
19845
|
+
console.log(chalk2.dim(`Gemini CLI: todos not found in ${configPath}`));
|
|
19011
19846
|
return;
|
|
19012
19847
|
}
|
|
19013
19848
|
delete servers["todos"];
|
|
19014
19849
|
writeJsonFile2(configPath, config);
|
|
19015
|
-
console.log(
|
|
19850
|
+
console.log(chalk2.green(`Gemini CLI: unregistered from ${configPath}`));
|
|
19016
19851
|
}
|
|
19017
19852
|
function registerMcp(agent, global) {
|
|
19018
19853
|
const agents = agent === "all" ? ["claude", "codex", "gemini"] : [agent];
|
|
@@ -19029,7 +19864,7 @@ function registerMcp(agent, global) {
|
|
|
19029
19864
|
registerGemini(binPath);
|
|
19030
19865
|
break;
|
|
19031
19866
|
default:
|
|
19032
|
-
console.error(
|
|
19867
|
+
console.error(chalk2.red(`Unknown agent: ${a}. Use: claude, codex, gemini, all`));
|
|
19033
19868
|
}
|
|
19034
19869
|
}
|
|
19035
19870
|
}
|
|
@@ -19047,7 +19882,7 @@ function unregisterMcp(agent, global) {
|
|
|
19047
19882
|
unregisterGemini();
|
|
19048
19883
|
break;
|
|
19049
19884
|
default:
|
|
19050
|
-
console.error(
|
|
19885
|
+
console.error(chalk2.red(`Unknown agent: ${a}. Use: claude, codex, gemini, all`));
|
|
19051
19886
|
}
|
|
19052
19887
|
}
|
|
19053
19888
|
}
|
|
@@ -19056,7 +19891,7 @@ program2.command("import <url>").description("Import a GitHub issue as a task").
|
|
|
19056
19891
|
const { parseGitHubUrl: parseGitHubUrl2, fetchGitHubIssue: fetchGitHubIssue2, issueToTask: issueToTask2 } = (init_github(), __toCommonJS(exports_github));
|
|
19057
19892
|
const parsed = parseGitHubUrl2(url);
|
|
19058
19893
|
if (!parsed) {
|
|
19059
|
-
console.error(
|
|
19894
|
+
console.error(chalk2.red("Invalid GitHub issue URL. Expected: https://github.com/owner/repo/issues/123"));
|
|
19060
19895
|
process.exit(1);
|
|
19061
19896
|
}
|
|
19062
19897
|
try {
|
|
@@ -19068,15 +19903,15 @@ program2.command("import <url>").description("Import a GitHub issue as a task").
|
|
|
19068
19903
|
output(task, true);
|
|
19069
19904
|
return;
|
|
19070
19905
|
}
|
|
19071
|
-
console.log(
|
|
19072
|
-
console.log(` ${
|
|
19073
|
-
console.log(` ${
|
|
19074
|
-
console.log(` ${
|
|
19906
|
+
console.log(chalk2.green(`Imported GH#${issue.number}: ${issue.title}`));
|
|
19907
|
+
console.log(` ${chalk2.dim("Task ID:")} ${task.short_id || task.id}`);
|
|
19908
|
+
console.log(` ${chalk2.dim("Labels:")} ${issue.labels.join(", ") || "none"}`);
|
|
19909
|
+
console.log(` ${chalk2.dim("Priority:")} ${task.priority}`);
|
|
19075
19910
|
} catch (e) {
|
|
19076
19911
|
if (e.message?.includes("gh")) {
|
|
19077
|
-
console.error(
|
|
19912
|
+
console.error(chalk2.red("GitHub CLI (gh) not found or not authenticated. Install: https://cli.github.com"));
|
|
19078
19913
|
} else {
|
|
19079
|
-
console.error(
|
|
19914
|
+
console.error(chalk2.red(`Import failed: ${e.message}`));
|
|
19080
19915
|
}
|
|
19081
19916
|
process.exit(1);
|
|
19082
19917
|
}
|
|
@@ -19096,7 +19931,7 @@ program2.command("link-commit <task-id> <sha>").description("Link a git commit t
|
|
|
19096
19931
|
output(commit, true);
|
|
19097
19932
|
return;
|
|
19098
19933
|
}
|
|
19099
|
-
console.log(
|
|
19934
|
+
console.log(chalk2.green(`Linked commit ${sha.slice(0, 7)} to task ${taskId}`));
|
|
19100
19935
|
});
|
|
19101
19936
|
var hookCmd = program2.command("hook").description("Manage git hooks for auto-linking commits to tasks");
|
|
19102
19937
|
hookCmd.command("install").description("Install post-commit hook that auto-links commits to tasks").action(() => {
|
|
@@ -19104,28 +19939,28 @@ hookCmd.command("install").description("Install post-commit hook that auto-links
|
|
|
19104
19939
|
try {
|
|
19105
19940
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
19106
19941
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
19107
|
-
const { existsSync:
|
|
19942
|
+
const { existsSync: existsSync10, readFileSync: readFileSync7, writeFileSync: writeFileSync6, chmodSync } = __require("fs");
|
|
19108
19943
|
const marker = "# todos-auto-link";
|
|
19109
|
-
if (
|
|
19110
|
-
const existing =
|
|
19944
|
+
if (existsSync10(hookPath)) {
|
|
19945
|
+
const existing = readFileSync7(hookPath, "utf-8");
|
|
19111
19946
|
if (existing.includes(marker)) {
|
|
19112
|
-
console.log(
|
|
19947
|
+
console.log(chalk2.yellow("Hook already installed."));
|
|
19113
19948
|
return;
|
|
19114
19949
|
}
|
|
19115
|
-
|
|
19950
|
+
writeFileSync6(hookPath, existing + `
|
|
19116
19951
|
${marker}
|
|
19117
19952
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
19118
19953
|
`);
|
|
19119
19954
|
} else {
|
|
19120
|
-
|
|
19955
|
+
writeFileSync6(hookPath, `#!/usr/bin/env bash
|
|
19121
19956
|
${marker}
|
|
19122
19957
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
19123
19958
|
`);
|
|
19124
19959
|
chmodSync(hookPath, 493);
|
|
19125
19960
|
}
|
|
19126
|
-
console.log(
|
|
19961
|
+
console.log(chalk2.green("Post-commit hook installed. Commits with task IDs (e.g. OPE-00042) will auto-link."));
|
|
19127
19962
|
} catch (e) {
|
|
19128
|
-
console.error(
|
|
19963
|
+
console.error(chalk2.red("Not in a git repository or hook install failed."));
|
|
19129
19964
|
process.exit(1);
|
|
19130
19965
|
}
|
|
19131
19966
|
});
|
|
@@ -19134,15 +19969,15 @@ hookCmd.command("uninstall").description("Remove the todos post-commit hook").ac
|
|
|
19134
19969
|
try {
|
|
19135
19970
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
19136
19971
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
19137
|
-
const { existsSync:
|
|
19972
|
+
const { existsSync: existsSync10, readFileSync: readFileSync7, writeFileSync: writeFileSync6 } = __require("fs");
|
|
19138
19973
|
const marker = "# todos-auto-link";
|
|
19139
|
-
if (!
|
|
19140
|
-
console.log(
|
|
19974
|
+
if (!existsSync10(hookPath)) {
|
|
19975
|
+
console.log(chalk2.dim("No post-commit hook found."));
|
|
19141
19976
|
return;
|
|
19142
19977
|
}
|
|
19143
|
-
const content =
|
|
19978
|
+
const content = readFileSync7(hookPath, "utf-8");
|
|
19144
19979
|
if (!content.includes(marker)) {
|
|
19145
|
-
console.log(
|
|
19980
|
+
console.log(chalk2.dim("Hook not managed by todos."));
|
|
19146
19981
|
return;
|
|
19147
19982
|
}
|
|
19148
19983
|
const cleaned = content.split(`
|
|
@@ -19151,12 +19986,12 @@ hookCmd.command("uninstall").description("Remove the todos post-commit hook").ac
|
|
|
19151
19986
|
if (cleaned === "#!/usr/bin/env bash" || cleaned === "") {
|
|
19152
19987
|
__require("fs").unlinkSync(hookPath);
|
|
19153
19988
|
} else {
|
|
19154
|
-
|
|
19989
|
+
writeFileSync6(hookPath, cleaned + `
|
|
19155
19990
|
`);
|
|
19156
19991
|
}
|
|
19157
|
-
console.log(
|
|
19992
|
+
console.log(chalk2.green("Post-commit hook removed."));
|
|
19158
19993
|
} catch (e) {
|
|
19159
|
-
console.error(
|
|
19994
|
+
console.error(chalk2.red("Not in a git repository or hook removal failed."));
|
|
19160
19995
|
process.exit(1);
|
|
19161
19996
|
}
|
|
19162
19997
|
});
|
|
@@ -19165,17 +20000,17 @@ program2.command("init <name>").description("Register an agent and get a short U
|
|
|
19165
20000
|
try {
|
|
19166
20001
|
const result = registerAgent({ name, description: opts.description });
|
|
19167
20002
|
if (isAgentConflict(result)) {
|
|
19168
|
-
console.error(
|
|
20003
|
+
console.error(chalk2.red("CONFLICT:"), result.message);
|
|
19169
20004
|
process.exit(1);
|
|
19170
20005
|
}
|
|
19171
20006
|
if (globalOpts.json) {
|
|
19172
20007
|
output(result, true);
|
|
19173
20008
|
} else {
|
|
19174
|
-
console.log(
|
|
19175
|
-
console.log(` ${
|
|
19176
|
-
console.log(` ${
|
|
20009
|
+
console.log(chalk2.green("Agent registered:"));
|
|
20010
|
+
console.log(` ${chalk2.dim("ID:")} ${result.id}`);
|
|
20011
|
+
console.log(` ${chalk2.dim("Name:")} ${result.name}`);
|
|
19177
20012
|
console.log(`
|
|
19178
|
-
Use ${
|
|
20013
|
+
Use ${chalk2.cyan(`--agent ${result.id}`)} on future commands.`);
|
|
19179
20014
|
}
|
|
19180
20015
|
} catch (e) {
|
|
19181
20016
|
handleError(e);
|
|
@@ -19185,51 +20020,51 @@ program2.command("heartbeat [agent]").description("Update last_seen_at to signal
|
|
|
19185
20020
|
const globalOpts = program2.opts();
|
|
19186
20021
|
const agentId = agent || globalOpts.agent;
|
|
19187
20022
|
if (!agentId) {
|
|
19188
|
-
console.error(
|
|
20023
|
+
console.error(chalk2.red("Agent ID required. Use --agent or pass as argument."));
|
|
19189
20024
|
process.exit(1);
|
|
19190
20025
|
}
|
|
19191
20026
|
const { updateAgentActivity: updateAgentActivity2, getAgent: getAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
19192
20027
|
const a = getAgent2(agentId) || (init_agents(), __toCommonJS(exports_agents)).getAgentByName(agentId);
|
|
19193
20028
|
if (!a) {
|
|
19194
|
-
console.error(
|
|
20029
|
+
console.error(chalk2.red(`Agent not found: ${agentId}`));
|
|
19195
20030
|
process.exit(1);
|
|
19196
20031
|
}
|
|
19197
20032
|
updateAgentActivity2(a.id);
|
|
19198
20033
|
if (globalOpts.json) {
|
|
19199
20034
|
console.log(JSON.stringify({ agent_id: a.id, name: a.name, last_seen_at: new Date().toISOString() }));
|
|
19200
20035
|
} else {
|
|
19201
|
-
console.log(
|
|
20036
|
+
console.log(chalk2.green(`\u2665 ${a.name} (${a.id.slice(0, 8)}) \u2014 heartbeat sent`));
|
|
19202
20037
|
}
|
|
19203
20038
|
});
|
|
19204
20039
|
program2.command("release [agent]").description("Release/logout an agent \u2014 clears session binding so the name is immediately available").option("--session-id <id>", "Only release if session ID matches").action((agent, opts) => {
|
|
19205
20040
|
const globalOpts = program2.opts();
|
|
19206
20041
|
const agentId = agent || globalOpts.agent;
|
|
19207
20042
|
if (!agentId) {
|
|
19208
|
-
console.error(
|
|
20043
|
+
console.error(chalk2.red("Agent ID or name required. Use --agent or pass as argument."));
|
|
19209
20044
|
process.exit(1);
|
|
19210
20045
|
}
|
|
19211
20046
|
const { getAgent: getAgent2, getAgentByName: getAgentByName2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
19212
20047
|
const a = getAgent2(agentId) || getAgentByName2(agentId);
|
|
19213
20048
|
if (!a) {
|
|
19214
|
-
console.error(
|
|
20049
|
+
console.error(chalk2.red(`Agent not found: ${agentId}`));
|
|
19215
20050
|
process.exit(1);
|
|
19216
20051
|
}
|
|
19217
20052
|
const released = releaseAgent(a.id, opts?.sessionId);
|
|
19218
20053
|
if (!released) {
|
|
19219
|
-
console.error(
|
|
20054
|
+
console.error(chalk2.red("Release denied: session_id does not match agent's current session."));
|
|
19220
20055
|
process.exit(1);
|
|
19221
20056
|
}
|
|
19222
20057
|
if (globalOpts.json) {
|
|
19223
20058
|
console.log(JSON.stringify({ agent_id: a.id, name: a.name, released: true }));
|
|
19224
20059
|
} else {
|
|
19225
|
-
console.log(
|
|
20060
|
+
console.log(chalk2.green(`\u2713 ${a.name} (${a.id}) released \u2014 name is now available.`));
|
|
19226
20061
|
}
|
|
19227
20062
|
});
|
|
19228
20063
|
program2.command("focus [project]").description("Focus on a project (or clear focus if no project given)").action((project) => {
|
|
19229
20064
|
const globalOpts = program2.opts();
|
|
19230
20065
|
const agentId = globalOpts.agent;
|
|
19231
20066
|
if (!agentId) {
|
|
19232
|
-
console.error(
|
|
20067
|
+
console.error(chalk2.red("Agent ID required. Use --agent."));
|
|
19233
20068
|
process.exit(1);
|
|
19234
20069
|
}
|
|
19235
20070
|
const db = getDatabase();
|
|
@@ -19238,10 +20073,10 @@ program2.command("focus [project]").description("Focus on a project (or clear fo
|
|
|
19238
20073
|
const p = getProjectByPath2(process.cwd(), db) || getProjectByName(project, db);
|
|
19239
20074
|
const projectId = p?.id || project;
|
|
19240
20075
|
db.run("UPDATE agents SET active_project_id = ? WHERE id = ? OR name = ?", [projectId, agentId, agentId]);
|
|
19241
|
-
console.log(
|
|
20076
|
+
console.log(chalk2.green(`Focused on: ${p?.name || projectId}`));
|
|
19242
20077
|
} else {
|
|
19243
20078
|
db.run("UPDATE agents SET active_project_id = NULL WHERE id = ? OR name = ?", [agentId, agentId]);
|
|
19244
|
-
console.log(
|
|
20079
|
+
console.log(chalk2.dim("Focus cleared."));
|
|
19245
20080
|
}
|
|
19246
20081
|
});
|
|
19247
20082
|
program2.command("agents").description("List registered agents").action(() => {
|
|
@@ -19253,11 +20088,11 @@ program2.command("agents").description("List registered agents").action(() => {
|
|
|
19253
20088
|
return;
|
|
19254
20089
|
}
|
|
19255
20090
|
if (agents.length === 0) {
|
|
19256
|
-
console.log(
|
|
20091
|
+
console.log(chalk2.dim("No agents registered. Use 'todos init <name>' to register."));
|
|
19257
20092
|
return;
|
|
19258
20093
|
}
|
|
19259
20094
|
for (const a of agents) {
|
|
19260
|
-
console.log(` ${
|
|
20095
|
+
console.log(` ${chalk2.cyan(a.id)} ${chalk2.bold(a.name)} ${chalk2.dim(a.last_seen_at)}`);
|
|
19261
20096
|
}
|
|
19262
20097
|
} catch (e) {
|
|
19263
20098
|
handleError(e);
|
|
@@ -19269,7 +20104,7 @@ program2.command("agent-update <name>").alias("agents-update").description("Upda
|
|
|
19269
20104
|
const { getAgentByName: findByName, updateAgent: doUpdate } = (init_agents(), __toCommonJS(exports_agents));
|
|
19270
20105
|
const agent = findByName(name);
|
|
19271
20106
|
if (!agent) {
|
|
19272
|
-
console.error(
|
|
20107
|
+
console.error(chalk2.red(`Agent not found: ${name}`));
|
|
19273
20108
|
process.exit(1);
|
|
19274
20109
|
}
|
|
19275
20110
|
const updates = {};
|
|
@@ -19283,11 +20118,11 @@ program2.command("agent-update <name>").alias("agents-update").description("Upda
|
|
|
19283
20118
|
if (globalOpts.json) {
|
|
19284
20119
|
output(updated, true);
|
|
19285
20120
|
} else {
|
|
19286
|
-
console.log(
|
|
20121
|
+
console.log(chalk2.green(`Updated agent: ${updated.name} (${updated.id.slice(0, 8)})`));
|
|
19287
20122
|
if (updated.description)
|
|
19288
|
-
console.log(
|
|
20123
|
+
console.log(chalk2.dim(` Description: ${updated.description}`));
|
|
19289
20124
|
if (updated.role)
|
|
19290
|
-
console.log(
|
|
20125
|
+
console.log(chalk2.dim(` Role: ${updated.role}`));
|
|
19291
20126
|
}
|
|
19292
20127
|
} catch (e) {
|
|
19293
20128
|
handleError(e);
|
|
@@ -19298,7 +20133,7 @@ program2.command("agent <name>").description("Show all info about an agent: task
|
|
|
19298
20133
|
const { getAgentByName: findByName } = (init_agents(), __toCommonJS(exports_agents));
|
|
19299
20134
|
const agent = findByName(name);
|
|
19300
20135
|
if (!agent) {
|
|
19301
|
-
console.error(
|
|
20136
|
+
console.error(chalk2.red(`Agent not found: ${name}`));
|
|
19302
20137
|
process.exit(1);
|
|
19303
20138
|
}
|
|
19304
20139
|
const byAssigned = listTasks({ assigned_to: agent.name });
|
|
@@ -19317,53 +20152,53 @@ program2.command("agent <name>").description("Show all info about an agent: task
|
|
|
19317
20152
|
const rate = allTasks.length > 0 ? Math.round(completed.length / allTasks.length * 100) : 0;
|
|
19318
20153
|
const lastSeenMs = Date.now() - new Date(agent.last_seen_at).getTime();
|
|
19319
20154
|
const lastSeenMins = Math.floor(lastSeenMs / 60000);
|
|
19320
|
-
const lastSeenStr = lastSeenMins < 2 ?
|
|
20155
|
+
const lastSeenStr = lastSeenMins < 2 ? chalk2.green("just now") : lastSeenMins < 60 ? chalk2.yellow(`${lastSeenMins}m ago`) : lastSeenMins < 1440 ? chalk2.yellow(`${Math.floor(lastSeenMins / 60)}h ago`) : chalk2.dim(`${Math.floor(lastSeenMins / 1440)}d ago`);
|
|
19321
20156
|
const isOnline = lastSeenMins < 5;
|
|
19322
20157
|
if (opts.json || globalOpts.json) {
|
|
19323
20158
|
console.log(JSON.stringify({ agent, tasks: { pending: pending.length, in_progress: inProgress.length, completed: completed.length, failed: failed.length, total: allTasks.length, completion_rate: rate }, all_tasks: allTasks }, null, 2));
|
|
19324
20159
|
return;
|
|
19325
20160
|
}
|
|
19326
20161
|
console.log(`
|
|
19327
|
-
${isOnline ?
|
|
20162
|
+
${isOnline ? chalk2.green("\u25CF") : chalk2.dim("\u25CB")} ${chalk2.bold(agent.name)} ${chalk2.dim(`(${agent.id})`)} ${lastSeenStr}`);
|
|
19328
20163
|
if (agent.description)
|
|
19329
|
-
console.log(
|
|
20164
|
+
console.log(chalk2.dim(` ${agent.description}`));
|
|
19330
20165
|
if (agent.role)
|
|
19331
|
-
console.log(
|
|
20166
|
+
console.log(chalk2.dim(` Role: ${agent.role}`));
|
|
19332
20167
|
console.log();
|
|
19333
|
-
console.log(` ${
|
|
20168
|
+
console.log(` ${chalk2.yellow(String(pending.length))} pending ${chalk2.blue(String(inProgress.length))} active ${chalk2.green(String(completed.length))} done ${chalk2.dim(`${rate}% rate`)}`);
|
|
19334
20169
|
console.log();
|
|
19335
20170
|
if (inProgress.length > 0) {
|
|
19336
|
-
console.log(
|
|
20171
|
+
console.log(chalk2.bold(" In progress:"));
|
|
19337
20172
|
for (const t of inProgress) {
|
|
19338
20173
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19339
|
-
const staleFlag = new Date(t.updated_at).getTime() < Date.now() - 30 * 60 * 1000 ?
|
|
19340
|
-
console.log(` ${
|
|
20174
|
+
const staleFlag = new Date(t.updated_at).getTime() < Date.now() - 30 * 60 * 1000 ? chalk2.red(" [stale]") : "";
|
|
20175
|
+
console.log(` ${chalk2.cyan(id)} ${chalk2.yellow(t.priority)} ${t.title}${staleFlag}`);
|
|
19341
20176
|
}
|
|
19342
20177
|
console.log();
|
|
19343
20178
|
}
|
|
19344
20179
|
if (pending.length > 0) {
|
|
19345
|
-
console.log(
|
|
20180
|
+
console.log(chalk2.bold(` Pending (${pending.length}):`));
|
|
19346
20181
|
for (const t of pending.slice(0, 5)) {
|
|
19347
20182
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19348
|
-
const due = t.due_at ?
|
|
19349
|
-
console.log(` ${
|
|
20183
|
+
const due = t.due_at ? chalk2.dim(` due:${t.due_at.slice(0, 10)}`) : "";
|
|
20184
|
+
console.log(` ${chalk2.dim(id)} ${t.priority.padEnd(8)} ${t.title}${due}`);
|
|
19350
20185
|
}
|
|
19351
20186
|
if (pending.length > 5)
|
|
19352
|
-
console.log(
|
|
20187
|
+
console.log(chalk2.dim(` ... and ${pending.length - 5} more`));
|
|
19353
20188
|
console.log();
|
|
19354
20189
|
}
|
|
19355
20190
|
const recentDone = completed.filter((t) => t.completed_at).sort((a, b) => new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime()).slice(0, 3);
|
|
19356
20191
|
if (recentDone.length > 0) {
|
|
19357
|
-
console.log(
|
|
20192
|
+
console.log(chalk2.bold(" Recently completed:"));
|
|
19358
20193
|
for (const t of recentDone) {
|
|
19359
20194
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19360
|
-
const when = t.completed_at ?
|
|
19361
|
-
console.log(` ${
|
|
20195
|
+
const when = t.completed_at ? chalk2.dim(new Date(t.completed_at).toLocaleDateString()) : "";
|
|
20196
|
+
console.log(` ${chalk2.green("\u2713")} ${chalk2.dim(id)} ${t.title} ${when}`);
|
|
19362
20197
|
}
|
|
19363
20198
|
console.log();
|
|
19364
20199
|
}
|
|
19365
20200
|
if (allTasks.length === 0) {
|
|
19366
|
-
console.log(
|
|
20201
|
+
console.log(chalk2.dim(" No tasks assigned to this agent."));
|
|
19367
20202
|
}
|
|
19368
20203
|
});
|
|
19369
20204
|
program2.command("org").description("Show agent org chart \u2014 who reports to who").option("--set <agent=manager>", "Set reporting: 'seneca=julius' or 'seneca=' to clear").action((opts) => {
|
|
@@ -19373,14 +20208,14 @@ program2.command("org").description("Show agent org chart \u2014 who reports to
|
|
|
19373
20208
|
const [agentName, managerName] = opts.set.split("=");
|
|
19374
20209
|
const agent = getByName(agentName);
|
|
19375
20210
|
if (!agent) {
|
|
19376
|
-
console.error(
|
|
20211
|
+
console.error(chalk2.red(`Agent not found: ${agentName}`));
|
|
19377
20212
|
process.exit(1);
|
|
19378
20213
|
}
|
|
19379
20214
|
let managerId = null;
|
|
19380
20215
|
if (managerName) {
|
|
19381
20216
|
const manager = getByName(managerName);
|
|
19382
20217
|
if (!manager) {
|
|
19383
|
-
console.error(
|
|
20218
|
+
console.error(chalk2.red(`Manager not found: ${managerName}`));
|
|
19384
20219
|
process.exit(1);
|
|
19385
20220
|
}
|
|
19386
20221
|
managerId = manager.id;
|
|
@@ -19389,7 +20224,7 @@ program2.command("org").description("Show agent org chart \u2014 who reports to
|
|
|
19389
20224
|
if (globalOpts.json) {
|
|
19390
20225
|
output({ agent: agentName, reports_to: managerName || null }, true);
|
|
19391
20226
|
} else {
|
|
19392
|
-
console.log(
|
|
20227
|
+
console.log(chalk2.green(managerId ? `${agentName} \u2192 ${managerName}` : `${agentName} \u2192 (top-level)`));
|
|
19393
20228
|
}
|
|
19394
20229
|
return;
|
|
19395
20230
|
}
|
|
@@ -19399,15 +20234,15 @@ program2.command("org").description("Show agent org chart \u2014 who reports to
|
|
|
19399
20234
|
return;
|
|
19400
20235
|
}
|
|
19401
20236
|
if (tree.length === 0) {
|
|
19402
|
-
console.log(
|
|
20237
|
+
console.log(chalk2.dim("No agents registered."));
|
|
19403
20238
|
return;
|
|
19404
20239
|
}
|
|
19405
20240
|
function render2(nodes, indent = 0) {
|
|
19406
20241
|
for (const n of nodes) {
|
|
19407
20242
|
const prefix = " ".repeat(indent);
|
|
19408
|
-
const title = n.agent.title ?
|
|
19409
|
-
const level = n.agent.level ?
|
|
19410
|
-
console.log(`${prefix}${indent > 0 ? "\u251C\u2500\u2500 " : ""}${
|
|
20243
|
+
const title = n.agent.title ? chalk2.cyan(` \u2014 ${n.agent.title}`) : "";
|
|
20244
|
+
const level = n.agent.level ? chalk2.dim(` (${n.agent.level})`) : "";
|
|
20245
|
+
console.log(`${prefix}${indent > 0 ? "\u251C\u2500\u2500 " : ""}${chalk2.bold(n.agent.name)}${title}${level}`);
|
|
19411
20246
|
render2(n.reports, indent + 1);
|
|
19412
20247
|
}
|
|
19413
20248
|
}
|
|
@@ -19423,21 +20258,21 @@ program2.command("lists").aliases(["task-lists", "tl"]).description("List and ma
|
|
|
19423
20258
|
output(list, true);
|
|
19424
20259
|
return;
|
|
19425
20260
|
}
|
|
19426
|
-
console.log(
|
|
19427
|
-
console.log(` ${
|
|
19428
|
-
console.log(` ${
|
|
19429
|
-
console.log(` ${
|
|
20261
|
+
console.log(chalk2.green("Task list created:"));
|
|
20262
|
+
console.log(` ${chalk2.dim("ID:")} ${list.id.slice(0, 8)}`);
|
|
20263
|
+
console.log(` ${chalk2.dim("Slug:")} ${list.slug}`);
|
|
20264
|
+
console.log(` ${chalk2.dim("Name:")} ${list.name}`);
|
|
19430
20265
|
return;
|
|
19431
20266
|
}
|
|
19432
20267
|
if (opts.delete) {
|
|
19433
20268
|
const db = getDatabase();
|
|
19434
20269
|
const resolved = resolvePartialId(db, "task_lists", opts.delete);
|
|
19435
20270
|
if (!resolved) {
|
|
19436
|
-
console.error(
|
|
20271
|
+
console.error(chalk2.red("Task list not found"));
|
|
19437
20272
|
process.exit(1);
|
|
19438
20273
|
}
|
|
19439
20274
|
deleteTaskList(resolved);
|
|
19440
|
-
console.log(
|
|
20275
|
+
console.log(chalk2.green("Task list deleted."));
|
|
19441
20276
|
return;
|
|
19442
20277
|
}
|
|
19443
20278
|
const lists = listTaskLists(projectId);
|
|
@@ -19446,11 +20281,11 @@ program2.command("lists").aliases(["task-lists", "tl"]).description("List and ma
|
|
|
19446
20281
|
return;
|
|
19447
20282
|
}
|
|
19448
20283
|
if (lists.length === 0) {
|
|
19449
|
-
console.log(
|
|
20284
|
+
console.log(chalk2.dim("No task lists. Use 'todos lists --add <name>' to create one."));
|
|
19450
20285
|
return;
|
|
19451
20286
|
}
|
|
19452
20287
|
for (const l of lists) {
|
|
19453
|
-
console.log(` ${
|
|
20288
|
+
console.log(` ${chalk2.dim(l.id.slice(0, 8))} ${chalk2.bold(l.name)} ${chalk2.dim(`(${l.slug})`)}`);
|
|
19454
20289
|
}
|
|
19455
20290
|
} catch (e) {
|
|
19456
20291
|
handleError(e);
|
|
@@ -19461,20 +20296,20 @@ program2.command("upgrade").alias("self-update").description("Update todos to th
|
|
|
19461
20296
|
const currentVersion = getPackageVersion();
|
|
19462
20297
|
const res = await fetch("https://registry.npmjs.org/@hasna/todos/latest");
|
|
19463
20298
|
if (!res.ok) {
|
|
19464
|
-
console.error(
|
|
20299
|
+
console.error(chalk2.red("Failed to check for updates."));
|
|
19465
20300
|
process.exit(1);
|
|
19466
20301
|
}
|
|
19467
20302
|
const data = await res.json();
|
|
19468
20303
|
const latestVersion = data.version;
|
|
19469
|
-
console.log(` Current: ${
|
|
19470
|
-
console.log(` Latest: ${
|
|
20304
|
+
console.log(` Current: ${chalk2.dim(currentVersion)}`);
|
|
20305
|
+
console.log(` Latest: ${chalk2.green(latestVersion)}`);
|
|
19471
20306
|
if (currentVersion === latestVersion) {
|
|
19472
|
-
console.log(
|
|
20307
|
+
console.log(chalk2.green(`
|
|
19473
20308
|
Already up to date!`));
|
|
19474
20309
|
return;
|
|
19475
20310
|
}
|
|
19476
20311
|
if (opts.check) {
|
|
19477
|
-
console.log(
|
|
20312
|
+
console.log(chalk2.yellow(`
|
|
19478
20313
|
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
19479
20314
|
return;
|
|
19480
20315
|
}
|
|
@@ -19484,10 +20319,10 @@ Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
|
19484
20319
|
useBun = true;
|
|
19485
20320
|
} catch {}
|
|
19486
20321
|
const cmd = useBun ? "bun add -g @hasna/todos@latest" : "npm install -g @hasna/todos@latest";
|
|
19487
|
-
console.log(
|
|
20322
|
+
console.log(chalk2.dim(`
|
|
19488
20323
|
Running: ${cmd}`));
|
|
19489
20324
|
execSync2(cmd, { stdio: "inherit" });
|
|
19490
|
-
console.log(
|
|
20325
|
+
console.log(chalk2.green(`
|
|
19491
20326
|
Updated to ${latestVersion}!`));
|
|
19492
20327
|
} catch (e) {
|
|
19493
20328
|
handleError(e);
|
|
@@ -19495,7 +20330,7 @@ Updated to ${latestVersion}!`));
|
|
|
19495
20330
|
});
|
|
19496
20331
|
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) => {
|
|
19497
20332
|
const globalOpts = program2.opts();
|
|
19498
|
-
const configPath =
|
|
20333
|
+
const configPath = join11(process.env["HOME"] || "~", ".todos", "config.json");
|
|
19499
20334
|
if (opts.get) {
|
|
19500
20335
|
const config2 = loadConfig();
|
|
19501
20336
|
const keys = opts.get.split(".");
|
|
@@ -19506,7 +20341,7 @@ program2.command("config").description("View or update configuration").option("-
|
|
|
19506
20341
|
if (globalOpts.json) {
|
|
19507
20342
|
output({ key: opts.get, value }, true);
|
|
19508
20343
|
} else {
|
|
19509
|
-
console.log(value !== undefined ? JSON.stringify(value, null, 2) :
|
|
20344
|
+
console.log(value !== undefined ? JSON.stringify(value, null, 2) : chalk2.dim("(not set)"));
|
|
19510
20345
|
}
|
|
19511
20346
|
return;
|
|
19512
20347
|
}
|
|
@@ -19521,7 +20356,7 @@ program2.command("config").description("View or update configuration").option("-
|
|
|
19521
20356
|
}
|
|
19522
20357
|
let config2 = {};
|
|
19523
20358
|
try {
|
|
19524
|
-
config2 = JSON.parse(
|
|
20359
|
+
config2 = JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
19525
20360
|
} catch {}
|
|
19526
20361
|
const keys = key.split(".");
|
|
19527
20362
|
let obj = config2;
|
|
@@ -19532,13 +20367,13 @@ program2.command("config").description("View or update configuration").option("-
|
|
|
19532
20367
|
}
|
|
19533
20368
|
obj[keys[keys.length - 1]] = parsedValue;
|
|
19534
20369
|
const dir = dirname4(configPath);
|
|
19535
|
-
if (!
|
|
19536
|
-
|
|
19537
|
-
|
|
20370
|
+
if (!existsSync9(dir))
|
|
20371
|
+
mkdirSync5(dir, { recursive: true });
|
|
20372
|
+
writeFileSync5(configPath, JSON.stringify(config2, null, 2));
|
|
19538
20373
|
if (globalOpts.json) {
|
|
19539
20374
|
output({ key, value: parsedValue }, true);
|
|
19540
20375
|
} else {
|
|
19541
|
-
console.log(
|
|
20376
|
+
console.log(chalk2.green(`Set ${key} = ${JSON.stringify(parsedValue)}`));
|
|
19542
20377
|
}
|
|
19543
20378
|
return;
|
|
19544
20379
|
}
|
|
@@ -19579,25 +20414,25 @@ program2.command("watch").description("Live-updating task list (refreshes every
|
|
|
19579
20414
|
counts[t.status] = (counts[t.status] || 0) + 1;
|
|
19580
20415
|
process.stdout.write("\x1B[2J\x1B[0f");
|
|
19581
20416
|
const now2 = new Date().toLocaleTimeString();
|
|
19582
|
-
console.log(
|
|
20417
|
+
console.log(chalk2.bold(`todos watch`) + chalk2.dim(` \u2014 ${now2} \u2014 refreshing every ${opts.interval}s \u2014 Ctrl+C to stop
|
|
19583
20418
|
`));
|
|
19584
20419
|
const parts = [
|
|
19585
|
-
`total: ${
|
|
19586
|
-
`pending: ${
|
|
19587
|
-
`in_progress: ${
|
|
19588
|
-
`completed: ${
|
|
19589
|
-
`failed: ${
|
|
20420
|
+
`total: ${chalk2.bold(String(all.length))}`,
|
|
20421
|
+
`pending: ${chalk2.yellow(String(counts["pending"] || 0))}`,
|
|
20422
|
+
`in_progress: ${chalk2.blue(String(counts["in_progress"] || 0))}`,
|
|
20423
|
+
`completed: ${chalk2.green(String(counts["completed"] || 0))}`,
|
|
20424
|
+
`failed: ${chalk2.red(String(counts["failed"] || 0))}`
|
|
19590
20425
|
];
|
|
19591
20426
|
console.log(parts.join(" ") + `
|
|
19592
20427
|
`);
|
|
19593
20428
|
if (tasks.length === 0) {
|
|
19594
|
-
console.log(
|
|
20429
|
+
console.log(chalk2.dim("No matching tasks."));
|
|
19595
20430
|
return;
|
|
19596
20431
|
}
|
|
19597
20432
|
for (const t of tasks) {
|
|
19598
20433
|
console.log(formatTaskLine(t));
|
|
19599
20434
|
}
|
|
19600
|
-
console.log(
|
|
20435
|
+
console.log(chalk2.dim(`
|
|
19601
20436
|
${tasks.length} task(s) shown`));
|
|
19602
20437
|
}
|
|
19603
20438
|
render2();
|
|
@@ -19617,19 +20452,19 @@ program2.command("stream").description("Subscribe to real-time task events via S
|
|
|
19617
20452
|
params.set("events", opts.events);
|
|
19618
20453
|
const url = `${baseUrl}/api/tasks/stream?${params}`;
|
|
19619
20454
|
const eventColors = {
|
|
19620
|
-
"task.created":
|
|
19621
|
-
"task.started":
|
|
19622
|
-
"task.completed":
|
|
19623
|
-
"task.failed":
|
|
19624
|
-
"task.assigned":
|
|
19625
|
-
"task.status_changed":
|
|
20455
|
+
"task.created": chalk2.blue,
|
|
20456
|
+
"task.started": chalk2.cyan,
|
|
20457
|
+
"task.completed": chalk2.green,
|
|
20458
|
+
"task.failed": chalk2.red,
|
|
20459
|
+
"task.assigned": chalk2.yellow,
|
|
20460
|
+
"task.status_changed": chalk2.magenta
|
|
19626
20461
|
};
|
|
19627
|
-
console.log(
|
|
20462
|
+
console.log(chalk2.dim(`Connecting to ${url} \u2014 Ctrl+C to stop
|
|
19628
20463
|
`));
|
|
19629
20464
|
try {
|
|
19630
20465
|
const resp = await fetch(url);
|
|
19631
20466
|
if (!resp.ok || !resp.body) {
|
|
19632
|
-
console.error(
|
|
20467
|
+
console.error(chalk2.red(`Failed to connect: ${resp.status}`));
|
|
19633
20468
|
process.exit(1);
|
|
19634
20469
|
}
|
|
19635
20470
|
const reader = resp.body.getReader();
|
|
@@ -19655,11 +20490,11 @@ program2.command("stream").description("Subscribe to real-time task events via S
|
|
|
19655
20490
|
if (opts.json) {
|
|
19656
20491
|
console.log(JSON.stringify({ event: eventName, ...data }));
|
|
19657
20492
|
} else {
|
|
19658
|
-
const colorFn = eventColors[eventName] ||
|
|
20493
|
+
const colorFn = eventColors[eventName] || chalk2.white;
|
|
19659
20494
|
const ts = new Date(data.timestamp || Date.now()).toLocaleTimeString();
|
|
19660
20495
|
const taskId = data.task_id ? data.task_id.slice(0, 8) : "";
|
|
19661
20496
|
const agentInfo = data.agent_id ? ` [${data.agent_id}]` : "";
|
|
19662
|
-
console.log(`${
|
|
20497
|
+
console.log(`${chalk2.dim(ts)} ${colorFn(eventName.padEnd(25))} ${taskId}${agentInfo}`);
|
|
19663
20498
|
}
|
|
19664
20499
|
} catch {}
|
|
19665
20500
|
eventName = "";
|
|
@@ -19667,8 +20502,8 @@ program2.command("stream").description("Subscribe to real-time task events via S
|
|
|
19667
20502
|
}
|
|
19668
20503
|
}
|
|
19669
20504
|
} catch (e) {
|
|
19670
|
-
console.error(
|
|
19671
|
-
console.error(
|
|
20505
|
+
console.error(chalk2.red(`Connection error: ${e instanceof Error ? e.message : e}`));
|
|
20506
|
+
console.error(chalk2.dim("Is `todos serve` running?"));
|
|
19672
20507
|
process.exit(1);
|
|
19673
20508
|
}
|
|
19674
20509
|
});
|
|
@@ -19689,29 +20524,29 @@ program2.command("blame <file>").description("Show which tasks/agents touched a
|
|
|
19689
20524
|
output({ file: filePath, task_files: taskFiles, commits: commitRows }, true);
|
|
19690
20525
|
return;
|
|
19691
20526
|
}
|
|
19692
|
-
console.log(
|
|
20527
|
+
console.log(chalk2.bold(`
|
|
19693
20528
|
Blame: ${filePath}
|
|
19694
20529
|
`));
|
|
19695
20530
|
if (taskFiles.length > 0) {
|
|
19696
|
-
console.log(
|
|
20531
|
+
console.log(chalk2.bold("Task File Links:"));
|
|
19697
20532
|
for (const tf of taskFiles) {
|
|
19698
20533
|
const task = getTask2(tf.task_id, db);
|
|
19699
20534
|
const title = task ? task.title : "unknown";
|
|
19700
20535
|
const sid = task?.short_id || tf.task_id.slice(0, 8);
|
|
19701
|
-
console.log(` ${
|
|
20536
|
+
console.log(` ${chalk2.cyan(sid)} ${title} \u2014 ${chalk2.dim(tf.role || "file")} ${chalk2.dim(tf.updated_at)}`);
|
|
19702
20537
|
}
|
|
19703
20538
|
}
|
|
19704
20539
|
if (commitRows.length > 0) {
|
|
19705
|
-
console.log(
|
|
20540
|
+
console.log(chalk2.bold(`
|
|
19706
20541
|
Commit Links (${commitRows.length}):`));
|
|
19707
20542
|
for (const c of commitRows) {
|
|
19708
20543
|
const sid = c.short_id || c.task_id.slice(0, 8);
|
|
19709
|
-
console.log(` ${
|
|
20544
|
+
console.log(` ${chalk2.yellow(c.sha?.slice(0, 7) || "?")} ${chalk2.cyan(sid)} ${c.title || ""} \u2014 ${chalk2.dim(c.author || "")} ${chalk2.dim(c.committed_at || "")}`);
|
|
19710
20545
|
}
|
|
19711
20546
|
}
|
|
19712
20547
|
if (taskFiles.length === 0 && commitRows.length === 0) {
|
|
19713
|
-
console.log(
|
|
19714
|
-
console.log(
|
|
20548
|
+
console.log(chalk2.dim("No task or commit links found for this file."));
|
|
20549
|
+
console.log(chalk2.dim("Use 'todos hook install' to auto-link future commits."));
|
|
19715
20550
|
}
|
|
19716
20551
|
console.log();
|
|
19717
20552
|
});
|
|
@@ -19730,17 +20565,17 @@ program2.command("next").description("Show the best pending task to work on next
|
|
|
19730
20565
|
filters.project_id = opts.project;
|
|
19731
20566
|
const task = getNextTask(opts.agent, Object.keys(filters).length ? filters : undefined, db);
|
|
19732
20567
|
if (!task) {
|
|
19733
|
-
console.log(
|
|
20568
|
+
console.log(chalk2.dim("No tasks available."));
|
|
19734
20569
|
return;
|
|
19735
20570
|
}
|
|
19736
20571
|
if (opts.json) {
|
|
19737
20572
|
console.log(JSON.stringify(task, null, 2));
|
|
19738
20573
|
return;
|
|
19739
20574
|
}
|
|
19740
|
-
console.log(
|
|
19741
|
-
console.log(` ${
|
|
20575
|
+
console.log(chalk2.bold("Next task:"));
|
|
20576
|
+
console.log(` ${chalk2.cyan(task.short_id || task.id.slice(0, 8))} ${chalk2.yellow(task.priority)} ${task.title}`);
|
|
19742
20577
|
if (task.description)
|
|
19743
|
-
console.log(
|
|
20578
|
+
console.log(chalk2.dim(` ${task.description.slice(0, 100)}`));
|
|
19744
20579
|
});
|
|
19745
20580
|
program2.command("claim <agent>").description("Atomically claim the best pending task for an agent").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (agent, opts) => {
|
|
19746
20581
|
const db = getDatabase();
|
|
@@ -19749,14 +20584,28 @@ program2.command("claim <agent>").description("Atomically claim the best pending
|
|
|
19749
20584
|
filters.project_id = opts.project;
|
|
19750
20585
|
const task = claimNextTask(agent, Object.keys(filters).length ? filters : undefined, db);
|
|
19751
20586
|
if (!task) {
|
|
19752
|
-
console.log(
|
|
20587
|
+
console.log(chalk2.dim("No tasks available to claim."));
|
|
19753
20588
|
return;
|
|
19754
20589
|
}
|
|
19755
20590
|
if (opts.json) {
|
|
19756
20591
|
console.log(JSON.stringify(task, null, 2));
|
|
19757
20592
|
return;
|
|
19758
20593
|
}
|
|
19759
|
-
console.log(
|
|
20594
|
+
console.log(chalk2.green(`Claimed: ${task.short_id || task.id.slice(0, 8)} | ${task.priority} | ${task.title}`));
|
|
20595
|
+
});
|
|
20596
|
+
program2.command("steal <agent>").description("Work-stealing: take the highest-priority stale task from another agent").option("--stale-minutes <n>", "How long a task must be stale (default: 30)", "30").option("--project <id>", "Filter to project").action((agent, opts) => {
|
|
20597
|
+
const globalOpts = program2.opts();
|
|
20598
|
+
const { stealTask: stealTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
20599
|
+
const task = stealTask2(agent, { stale_minutes: parseInt(opts.staleMinutes, 10), project_id: opts.project });
|
|
20600
|
+
if (!task) {
|
|
20601
|
+
console.log(chalk2.dim("No stale tasks available to steal."));
|
|
20602
|
+
return;
|
|
20603
|
+
}
|
|
20604
|
+
if (globalOpts.json) {
|
|
20605
|
+
output(task, true);
|
|
20606
|
+
return;
|
|
20607
|
+
}
|
|
20608
|
+
console.log(chalk2.green(`Stolen: ${task.short_id || task.id.slice(0, 8)} | ${task.priority} | ${task.title}`));
|
|
19760
20609
|
});
|
|
19761
20610
|
program2.command("status").description("Show full project health snapshot").option("--agent <id>", "Include next task for this agent").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (opts) => {
|
|
19762
20611
|
const db = getDatabase();
|
|
@@ -19768,24 +20617,24 @@ program2.command("status").description("Show full project health snapshot").opti
|
|
|
19768
20617
|
console.log(JSON.stringify(s, null, 2));
|
|
19769
20618
|
return;
|
|
19770
20619
|
}
|
|
19771
|
-
console.log(`Tasks: ${
|
|
20620
|
+
console.log(`Tasks: ${chalk2.yellow(s.pending)} pending | ${chalk2.blue(s.in_progress)} active | ${chalk2.green(s.completed)} done | ${s.total} total`);
|
|
19772
20621
|
if (s.stale_count > 0)
|
|
19773
|
-
console.log(
|
|
20622
|
+
console.log(chalk2.red(`\u26A0\uFE0F ${s.stale_count} stale tasks (stuck in_progress)`));
|
|
19774
20623
|
if (s.overdue_recurring > 0)
|
|
19775
|
-
console.log(
|
|
20624
|
+
console.log(chalk2.yellow(`\uD83D\uDD01 ${s.overdue_recurring} overdue recurring`));
|
|
19776
20625
|
if (s.active_work.length > 0) {
|
|
19777
|
-
console.log(
|
|
20626
|
+
console.log(chalk2.bold(`
|
|
19778
20627
|
Active:`));
|
|
19779
20628
|
for (const w of s.active_work.slice(0, 5)) {
|
|
19780
20629
|
const id = w.short_id || w.id.slice(0, 8);
|
|
19781
|
-
console.log(` ${
|
|
20630
|
+
console.log(` ${chalk2.cyan(id)} | ${w.assigned_to || w.locked_by || "?"} | ${w.title}`);
|
|
19782
20631
|
}
|
|
19783
20632
|
}
|
|
19784
20633
|
if (s.next_task) {
|
|
19785
|
-
console.log(
|
|
20634
|
+
console.log(chalk2.bold(`
|
|
19786
20635
|
Next up:`));
|
|
19787
20636
|
const t = s.next_task;
|
|
19788
|
-
console.log(` ${
|
|
20637
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${chalk2.yellow(t.priority)} ${t.title}`);
|
|
19789
20638
|
}
|
|
19790
20639
|
});
|
|
19791
20640
|
program2.command("recap").description("Show what happened in the last N hours \u2014 completed tasks, new tasks, agent activity, blockers").option("--hours <n>", "Look back N hours (default: 8)", "8").option("--project <id>", "Filter to project").action((opts) => {
|
|
@@ -19796,60 +20645,60 @@ program2.command("recap").description("Show what happened in the last N hours \u
|
|
|
19796
20645
|
output(recap, true);
|
|
19797
20646
|
return;
|
|
19798
20647
|
}
|
|
19799
|
-
console.log(
|
|
20648
|
+
console.log(chalk2.bold(`
|
|
19800
20649
|
Recap \u2014 last ${recap.hours} hours (since ${new Date(recap.since).toLocaleString()})
|
|
19801
20650
|
`));
|
|
19802
20651
|
if (recap.completed.length > 0) {
|
|
19803
|
-
console.log(
|
|
20652
|
+
console.log(chalk2.green.bold(`Completed (${recap.completed.length}):`));
|
|
19804
20653
|
for (const t of recap.completed) {
|
|
19805
20654
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19806
20655
|
const dur = t.duration_minutes != null ? ` (${t.duration_minutes}m)` : "";
|
|
19807
|
-
console.log(` ${
|
|
20656
|
+
console.log(` ${chalk2.green("\u2713")} ${chalk2.cyan(id)} ${t.title}${dur}${t.assigned_to ? ` \u2014 ${chalk2.dim(t.assigned_to)}` : ""}`);
|
|
19808
20657
|
}
|
|
19809
20658
|
} else {
|
|
19810
|
-
console.log(
|
|
20659
|
+
console.log(chalk2.dim("No tasks completed in this period."));
|
|
19811
20660
|
}
|
|
19812
20661
|
if (recap.in_progress.length > 0) {
|
|
19813
|
-
console.log(
|
|
20662
|
+
console.log(chalk2.blue.bold(`
|
|
19814
20663
|
In Progress (${recap.in_progress.length}):`));
|
|
19815
20664
|
for (const t of recap.in_progress) {
|
|
19816
20665
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19817
|
-
console.log(` ${
|
|
20666
|
+
console.log(` ${chalk2.blue("\u2192")} ${chalk2.cyan(id)} ${t.title}${t.assigned_to ? ` \u2014 ${chalk2.dim(t.assigned_to)}` : ""}`);
|
|
19818
20667
|
}
|
|
19819
20668
|
}
|
|
19820
20669
|
if (recap.blocked.length > 0) {
|
|
19821
|
-
console.log(
|
|
20670
|
+
console.log(chalk2.red.bold(`
|
|
19822
20671
|
Blocked (${recap.blocked.length}):`));
|
|
19823
20672
|
for (const t of recap.blocked) {
|
|
19824
20673
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19825
|
-
console.log(` ${
|
|
20674
|
+
console.log(` ${chalk2.red("\u2717")} ${chalk2.cyan(id)} ${t.title}`);
|
|
19826
20675
|
}
|
|
19827
20676
|
}
|
|
19828
20677
|
if (recap.stale.length > 0) {
|
|
19829
|
-
console.log(
|
|
20678
|
+
console.log(chalk2.yellow.bold(`
|
|
19830
20679
|
Stale (${recap.stale.length}):`));
|
|
19831
20680
|
for (const t of recap.stale) {
|
|
19832
20681
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19833
20682
|
const ago = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
19834
|
-
console.log(` ${
|
|
20683
|
+
console.log(` ${chalk2.yellow("!")} ${chalk2.cyan(id)} ${t.title} \u2014 last update ${ago}m ago`);
|
|
19835
20684
|
}
|
|
19836
20685
|
}
|
|
19837
20686
|
if (recap.created.length > 0) {
|
|
19838
|
-
console.log(
|
|
20687
|
+
console.log(chalk2.dim.bold(`
|
|
19839
20688
|
Created (${recap.created.length}):`));
|
|
19840
20689
|
for (const t of recap.created.slice(0, 10)) {
|
|
19841
20690
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19842
|
-
console.log(` ${
|
|
20691
|
+
console.log(` ${chalk2.dim("+")} ${chalk2.cyan(id)} ${t.title}`);
|
|
19843
20692
|
}
|
|
19844
20693
|
if (recap.created.length > 10)
|
|
19845
|
-
console.log(
|
|
20694
|
+
console.log(chalk2.dim(` ... and ${recap.created.length - 10} more`));
|
|
19846
20695
|
}
|
|
19847
20696
|
if (recap.agents.length > 0) {
|
|
19848
|
-
console.log(
|
|
20697
|
+
console.log(chalk2.bold(`
|
|
19849
20698
|
Agents:`));
|
|
19850
20699
|
for (const a of recap.agents) {
|
|
19851
20700
|
const seen = Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000);
|
|
19852
|
-
console.log(` ${a.name}: ${
|
|
20701
|
+
console.log(` ${a.name}: ${chalk2.green(a.completed_count + " done")} | ${chalk2.blue(a.in_progress_count + " active")} | last seen ${seen}m ago`);
|
|
19853
20702
|
}
|
|
19854
20703
|
}
|
|
19855
20704
|
console.log();
|
|
@@ -19864,7 +20713,7 @@ program2.command("standup").description("Generate standup notes \u2014 completed
|
|
|
19864
20713
|
output(recap, true);
|
|
19865
20714
|
return;
|
|
19866
20715
|
}
|
|
19867
|
-
console.log(
|
|
20716
|
+
console.log(chalk2.bold(`
|
|
19868
20717
|
Standup \u2014 since ${sinceDate.toLocaleDateString()}
|
|
19869
20718
|
`));
|
|
19870
20719
|
const byAgent = new Map;
|
|
@@ -19875,29 +20724,29 @@ Standup \u2014 since ${sinceDate.toLocaleDateString()}
|
|
|
19875
20724
|
byAgent.get(agent).push(t);
|
|
19876
20725
|
}
|
|
19877
20726
|
if (byAgent.size > 0) {
|
|
19878
|
-
console.log(
|
|
20727
|
+
console.log(chalk2.green.bold("Done:"));
|
|
19879
20728
|
for (const [agent, tasks] of byAgent) {
|
|
19880
|
-
console.log(` ${
|
|
20729
|
+
console.log(` ${chalk2.cyan(agent)}:`);
|
|
19881
20730
|
for (const t of tasks) {
|
|
19882
20731
|
const dur = t.duration_minutes != null ? ` (${t.duration_minutes}m)` : "";
|
|
19883
|
-
console.log(` ${
|
|
20732
|
+
console.log(` ${chalk2.green("\u2713")} ${t.short_id || t.id.slice(0, 8)} ${t.title}${dur}`);
|
|
19884
20733
|
}
|
|
19885
20734
|
}
|
|
19886
20735
|
} else {
|
|
19887
|
-
console.log(
|
|
20736
|
+
console.log(chalk2.dim("Nothing completed."));
|
|
19888
20737
|
}
|
|
19889
20738
|
if (recap.in_progress.length > 0) {
|
|
19890
|
-
console.log(
|
|
20739
|
+
console.log(chalk2.blue.bold(`
|
|
19891
20740
|
In Progress:`));
|
|
19892
20741
|
for (const t of recap.in_progress) {
|
|
19893
|
-
console.log(` ${
|
|
20742
|
+
console.log(` ${chalk2.blue("\u2192")} ${t.short_id || t.id.slice(0, 8)} ${t.title}${t.assigned_to ? ` \u2014 ${chalk2.dim(t.assigned_to)}` : ""}`);
|
|
19894
20743
|
}
|
|
19895
20744
|
}
|
|
19896
20745
|
if (recap.blocked.length > 0) {
|
|
19897
|
-
console.log(
|
|
20746
|
+
console.log(chalk2.red.bold(`
|
|
19898
20747
|
Blocked:`));
|
|
19899
20748
|
for (const t of recap.blocked) {
|
|
19900
|
-
console.log(` ${
|
|
20749
|
+
console.log(` ${chalk2.red("\u2717")} ${t.short_id || t.id.slice(0, 8)} ${t.title}`);
|
|
19901
20750
|
}
|
|
19902
20751
|
}
|
|
19903
20752
|
console.log();
|
|
@@ -19906,7 +20755,7 @@ program2.command("fail <id>").description("Mark a task as failed with optional r
|
|
|
19906
20755
|
const db = getDatabase();
|
|
19907
20756
|
const resolvedId = resolvePartialId(db, "tasks", id);
|
|
19908
20757
|
if (!resolvedId) {
|
|
19909
|
-
console.error(
|
|
20758
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
19910
20759
|
process.exit(1);
|
|
19911
20760
|
}
|
|
19912
20761
|
const result = failTask(resolvedId, opts.agent, opts.reason, { retry: opts.retry }, db);
|
|
@@ -19914,11 +20763,11 @@ program2.command("fail <id>").description("Mark a task as failed with optional r
|
|
|
19914
20763
|
console.log(JSON.stringify(result, null, 2));
|
|
19915
20764
|
return;
|
|
19916
20765
|
}
|
|
19917
|
-
console.log(
|
|
20766
|
+
console.log(chalk2.red(`Failed: ${result.task.short_id || result.task.id.slice(0, 8)} | ${result.task.title}`));
|
|
19918
20767
|
if (opts.reason)
|
|
19919
|
-
console.log(
|
|
20768
|
+
console.log(chalk2.dim(`Reason: ${opts.reason}`));
|
|
19920
20769
|
if (result.retryTask)
|
|
19921
|
-
console.log(
|
|
20770
|
+
console.log(chalk2.yellow(`Retry created: ${result.retryTask.short_id || result.retryTask.id.slice(0, 8)} | ${result.retryTask.title}`));
|
|
19922
20771
|
});
|
|
19923
20772
|
program2.command("active").description("Show all currently in-progress tasks").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (opts) => {
|
|
19924
20773
|
const db = getDatabase();
|
|
@@ -19931,14 +20780,14 @@ program2.command("active").description("Show all currently in-progress tasks").o
|
|
|
19931
20780
|
return;
|
|
19932
20781
|
}
|
|
19933
20782
|
if (work.length === 0) {
|
|
19934
|
-
console.log(
|
|
20783
|
+
console.log(chalk2.dim("No active work."));
|
|
19935
20784
|
return;
|
|
19936
20785
|
}
|
|
19937
|
-
console.log(
|
|
20786
|
+
console.log(chalk2.bold(`Active work (${work.length}):`));
|
|
19938
20787
|
for (const w of work) {
|
|
19939
20788
|
const id = w.short_id || w.id.slice(0, 8);
|
|
19940
20789
|
const agent = w.assigned_to || w.locked_by || "unassigned";
|
|
19941
|
-
console.log(` ${
|
|
20790
|
+
console.log(` ${chalk2.cyan(id)} | ${chalk2.yellow(w.priority)} | ${agent.padEnd(12)} | ${w.title}`);
|
|
19942
20791
|
}
|
|
19943
20792
|
});
|
|
19944
20793
|
program2.command("stale").description("Find tasks stuck in_progress with no recent activity").option("--minutes <n>", "Stale threshold in minutes", "30").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -19952,14 +20801,14 @@ program2.command("stale").description("Find tasks stuck in_progress with no rece
|
|
|
19952
20801
|
return;
|
|
19953
20802
|
}
|
|
19954
20803
|
if (tasks.length === 0) {
|
|
19955
|
-
console.log(
|
|
20804
|
+
console.log(chalk2.dim("No stale tasks."));
|
|
19956
20805
|
return;
|
|
19957
20806
|
}
|
|
19958
|
-
console.log(
|
|
20807
|
+
console.log(chalk2.bold(`Stale tasks (${tasks.length}):`));
|
|
19959
20808
|
for (const t of tasks) {
|
|
19960
20809
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19961
20810
|
const staleMin = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
19962
|
-
console.log(` ${
|
|
20811
|
+
console.log(` ${chalk2.cyan(id)} | ${t.locked_by || t.assigned_to || "?"} | ${t.title} ${chalk2.dim(`(${staleMin}min stale)`)}`);
|
|
19963
20812
|
}
|
|
19964
20813
|
});
|
|
19965
20814
|
program2.command("redistribute <agent>").description("Release stale in-progress tasks and claim the best one (work-stealing)").option("--max-age <minutes>", "Stale threshold in minutes", "60").option("--project <id>", "Limit to a specific project").option("--limit <n>", "Max stale tasks to release").option("--json", "Output as JSON").action(async (agent, opts) => {
|
|
@@ -19975,17 +20824,17 @@ program2.command("redistribute <agent>").description("Release stale in-progress
|
|
|
19975
20824
|
console.log(JSON.stringify(result, null, 2));
|
|
19976
20825
|
return;
|
|
19977
20826
|
}
|
|
19978
|
-
console.log(
|
|
20827
|
+
console.log(chalk2.bold(`Released ${result.released.length} stale task(s).`));
|
|
19979
20828
|
for (const t of result.released) {
|
|
19980
20829
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19981
|
-
console.log(` ${
|
|
20830
|
+
console.log(` ${chalk2.yellow("released")} ${chalk2.cyan(id)} ${t.title}`);
|
|
19982
20831
|
}
|
|
19983
20832
|
if (result.claimed) {
|
|
19984
20833
|
const id = result.claimed.short_id || result.claimed.id.slice(0, 8);
|
|
19985
|
-
console.log(
|
|
19986
|
-
Claimed: ${
|
|
20834
|
+
console.log(chalk2.green(`
|
|
20835
|
+
Claimed: ${chalk2.cyan(id)} ${result.claimed.title}`));
|
|
19987
20836
|
} else {
|
|
19988
|
-
console.log(
|
|
20837
|
+
console.log(chalk2.dim(`
|
|
19989
20838
|
No task claimed (nothing available).`));
|
|
19990
20839
|
}
|
|
19991
20840
|
});
|
|
@@ -19995,7 +20844,7 @@ program2.command("assign <id> <agent>").description("Assign a task to an agent")
|
|
|
19995
20844
|
const db = getDatabase();
|
|
19996
20845
|
const task = getTask(resolvedId, db);
|
|
19997
20846
|
if (!task) {
|
|
19998
|
-
console.error(
|
|
20847
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
19999
20848
|
process.exit(1);
|
|
20000
20849
|
}
|
|
20001
20850
|
try {
|
|
@@ -20004,7 +20853,7 @@ program2.command("assign <id> <agent>").description("Assign a task to an agent")
|
|
|
20004
20853
|
console.log(JSON.stringify(updated));
|
|
20005
20854
|
return;
|
|
20006
20855
|
}
|
|
20007
|
-
console.log(
|
|
20856
|
+
console.log(chalk2.green(`Assigned to ${agent}: ${formatTaskLine(updated)}`));
|
|
20008
20857
|
} catch {
|
|
20009
20858
|
handleError(new Error("Failed to assign"));
|
|
20010
20859
|
}
|
|
@@ -20015,7 +20864,7 @@ program2.command("unassign <id>").description("Remove task assignment").option("
|
|
|
20015
20864
|
const db = getDatabase();
|
|
20016
20865
|
const task = getTask(resolvedId, db);
|
|
20017
20866
|
if (!task) {
|
|
20018
|
-
console.error(
|
|
20867
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
20019
20868
|
process.exit(1);
|
|
20020
20869
|
}
|
|
20021
20870
|
try {
|
|
@@ -20024,7 +20873,7 @@ program2.command("unassign <id>").description("Remove task assignment").option("
|
|
|
20024
20873
|
console.log(JSON.stringify(updated));
|
|
20025
20874
|
return;
|
|
20026
20875
|
}
|
|
20027
|
-
console.log(
|
|
20876
|
+
console.log(chalk2.green(`Unassigned: ${formatTaskLine(updated)}`));
|
|
20028
20877
|
} catch {
|
|
20029
20878
|
handleError(new Error("Failed to unassign"));
|
|
20030
20879
|
}
|
|
@@ -20035,7 +20884,7 @@ program2.command("tag <id> <tag>").description("Add a tag to a task").option("--
|
|
|
20035
20884
|
const db = getDatabase();
|
|
20036
20885
|
const task = getTask(resolvedId, db);
|
|
20037
20886
|
if (!task) {
|
|
20038
|
-
console.error(
|
|
20887
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
20039
20888
|
process.exit(1);
|
|
20040
20889
|
}
|
|
20041
20890
|
const newTags = [...new Set([...task.tags, tag])];
|
|
@@ -20045,7 +20894,7 @@ program2.command("tag <id> <tag>").description("Add a tag to a task").option("--
|
|
|
20045
20894
|
console.log(JSON.stringify(updated));
|
|
20046
20895
|
return;
|
|
20047
20896
|
}
|
|
20048
|
-
console.log(
|
|
20897
|
+
console.log(chalk2.green(`Tagged [${tag}]: ${formatTaskLine(updated)}`));
|
|
20049
20898
|
} catch {
|
|
20050
20899
|
handleError(new Error("Failed to tag"));
|
|
20051
20900
|
}
|
|
@@ -20056,7 +20905,7 @@ program2.command("untag <id> <tag>").description("Remove a tag from a task").opt
|
|
|
20056
20905
|
const db = getDatabase();
|
|
20057
20906
|
const task = getTask(resolvedId, db);
|
|
20058
20907
|
if (!task) {
|
|
20059
|
-
console.error(
|
|
20908
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
20060
20909
|
process.exit(1);
|
|
20061
20910
|
}
|
|
20062
20911
|
const newTags = task.tags.filter((t) => t !== tag);
|
|
@@ -20066,7 +20915,7 @@ program2.command("untag <id> <tag>").description("Remove a tag from a task").opt
|
|
|
20066
20915
|
console.log(JSON.stringify(updated));
|
|
20067
20916
|
return;
|
|
20068
20917
|
}
|
|
20069
|
-
console.log(
|
|
20918
|
+
console.log(chalk2.green(`Untagged [${tag}]: ${formatTaskLine(updated)}`));
|
|
20070
20919
|
} catch {
|
|
20071
20920
|
handleError(new Error("Failed to untag"));
|
|
20072
20921
|
}
|
|
@@ -20082,7 +20931,7 @@ program2.command("pin <id>").description("Escalate task to critical priority").o
|
|
|
20082
20931
|
console.log(JSON.stringify(updated));
|
|
20083
20932
|
return;
|
|
20084
20933
|
}
|
|
20085
|
-
console.log(
|
|
20934
|
+
console.log(chalk2.red(`\uD83D\uDCCC Pinned (critical): ${formatTaskLine(updated)}`));
|
|
20086
20935
|
} catch {
|
|
20087
20936
|
handleError(new Error("Failed to pin"));
|
|
20088
20937
|
}
|
|
@@ -20169,19 +21018,19 @@ program2.command("doctor").description("Diagnose common task data issues").optio
|
|
|
20169
21018
|
console.log(JSON.stringify({ issues, ok: !issues.some((i) => i.severity === "error") }));
|
|
20170
21019
|
return;
|
|
20171
21020
|
}
|
|
20172
|
-
console.log(
|
|
21021
|
+
console.log(chalk2.bold(`todos doctor
|
|
20173
21022
|
`));
|
|
20174
21023
|
for (const issue of issues) {
|
|
20175
|
-
const icon = issue.severity === "error" ?
|
|
21024
|
+
const icon = issue.severity === "error" ? chalk2.red("\u2717") : issue.severity === "warn" ? chalk2.yellow("\u26A0") : chalk2.green("\u2713");
|
|
20176
21025
|
console.log(` ${icon} ${issue.message}`);
|
|
20177
21026
|
}
|
|
20178
21027
|
const errors2 = issues.filter((i) => i.severity === "error").length;
|
|
20179
21028
|
const warns = issues.filter((i) => i.severity === "warn").length;
|
|
20180
21029
|
if (errors2 === 0 && warns === 0)
|
|
20181
|
-
console.log(
|
|
21030
|
+
console.log(chalk2.green(`
|
|
20182
21031
|
All clear.`));
|
|
20183
21032
|
else
|
|
20184
|
-
console.log(
|
|
21033
|
+
console.log(chalk2[errors2 > 0 ? "red" : "yellow"](`
|
|
20185
21034
|
${errors2} error(s), ${warns} warning(s). Run with --fix to auto-resolve where possible.`));
|
|
20186
21035
|
});
|
|
20187
21036
|
program2.command("health").description("Check todos system health \u2014 database, config, connectivity").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -20196,7 +21045,7 @@ program2.command("health").description("Check todos system health \u2014 databas
|
|
|
20196
21045
|
try {
|
|
20197
21046
|
size = `${(statSync3(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
20198
21047
|
} catch {}
|
|
20199
|
-
checks.push({ name: "Database", ok: true, message: `${row.count} tasks \xB7 ${size} \xB7 ${
|
|
21048
|
+
checks.push({ name: "Database", ok: true, message: `${row.count} tasks \xB7 ${size} \xB7 ${chalk2.dim(dbPath)}` });
|
|
20200
21049
|
} catch (e) {
|
|
20201
21050
|
checks.push({ name: "Database", ok: false, message: e instanceof Error ? e.message : "Failed" });
|
|
20202
21051
|
}
|
|
@@ -20229,15 +21078,15 @@ program2.command("health").description("Check todos system health \u2014 databas
|
|
|
20229
21078
|
console.log(JSON.stringify({ ok, checks }));
|
|
20230
21079
|
return;
|
|
20231
21080
|
}
|
|
20232
|
-
console.log(
|
|
21081
|
+
console.log(chalk2.bold(`todos health
|
|
20233
21082
|
`));
|
|
20234
21083
|
for (const c of checks) {
|
|
20235
|
-
const icon = c.ok ?
|
|
21084
|
+
const icon = c.ok ? chalk2.green("\u2713") : chalk2.yellow("\u26A0");
|
|
20236
21085
|
console.log(` ${icon} ${c.name.padEnd(14)} ${c.message}`);
|
|
20237
21086
|
}
|
|
20238
21087
|
const allOk = checks.every((c) => c.ok);
|
|
20239
21088
|
console.log(`
|
|
20240
|
-
${allOk ?
|
|
21089
|
+
${allOk ? chalk2.green("All checks passed.") : chalk2.yellow("Some checks need attention.")}`);
|
|
20241
21090
|
});
|
|
20242
21091
|
program2.command("report").description("Analytics report: task activity, completion rates, agent breakdown").option("--days <n>", "Days to include in report", "7").option("--project <id>", "Filter to project").option("--markdown", "Output as markdown").option("--json", "Output as JSON").action(async (opts) => {
|
|
20243
21092
|
const globalOpts = program2.opts();
|
|
@@ -20286,15 +21135,15 @@ program2.command("report").description("Analytics report: task activity, complet
|
|
|
20286
21135
|
if (sparkline)
|
|
20287
21136
|
lines.push(`| Activity | \`${sparkline}\` |`);
|
|
20288
21137
|
} else {
|
|
20289
|
-
lines.push(
|
|
21138
|
+
lines.push(chalk2.bold(`todos report \u2014 last ${days} day${days !== 1 ? "s" : ""}`));
|
|
20290
21139
|
lines.push("");
|
|
20291
|
-
lines.push(` Total: ${
|
|
20292
|
-
lines.push(` Changed: ${
|
|
20293
|
-
lines.push(` Completed: ${
|
|
21140
|
+
lines.push(` Total: ${chalk2.bold(String(all.length))} tasks (${chalk2.yellow(String(stats.pending))} pending, ${chalk2.blue(String(stats.in_progress))} active)`);
|
|
21141
|
+
lines.push(` Changed: ${chalk2.bold(String(changed.length))} in period`);
|
|
21142
|
+
lines.push(` Completed: ${chalk2.green(String(completed.length))} (${completionRate}% rate)`);
|
|
20294
21143
|
if (failed.length > 0)
|
|
20295
|
-
lines.push(` Failed: ${
|
|
21144
|
+
lines.push(` Failed: ${chalk2.red(String(failed.length))}`);
|
|
20296
21145
|
if (sparkline)
|
|
20297
|
-
lines.push(` Activity: ${
|
|
21146
|
+
lines.push(` Activity: ${chalk2.dim(sparkline)}`);
|
|
20298
21147
|
if (Object.keys(byAgent).length > 0) {
|
|
20299
21148
|
lines.push(` By agent: ${Object.entries(byAgent).map(([a, n]) => `${a}=${n}`).join(" ")}`);
|
|
20300
21149
|
}
|
|
@@ -20318,21 +21167,21 @@ program2.command("today").description("Show task activity from today").option("-
|
|
|
20318
21167
|
console.log(JSON.stringify({ date: start.toISOString().slice(0, 10), completed, started, changed: other }));
|
|
20319
21168
|
return;
|
|
20320
21169
|
}
|
|
20321
|
-
console.log(
|
|
21170
|
+
console.log(chalk2.bold(`Today \u2014 ${start.toISOString().slice(0, 10)}
|
|
20322
21171
|
`));
|
|
20323
21172
|
if (completed.length > 0) {
|
|
20324
|
-
console.log(
|
|
21173
|
+
console.log(chalk2.green(` \u2713 Completed (${completed.length}):`));
|
|
20325
21174
|
for (const t of completed)
|
|
20326
|
-
console.log(` ${
|
|
21175
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk2.dim(` \u2014 ${t.assigned_to}`) : ""}`);
|
|
20327
21176
|
}
|
|
20328
21177
|
if (started.length > 0) {
|
|
20329
|
-
console.log(
|
|
21178
|
+
console.log(chalk2.blue(`
|
|
20330
21179
|
\u25B6 Started (${started.length}):`));
|
|
20331
21180
|
for (const t of started)
|
|
20332
|
-
console.log(` ${
|
|
21181
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk2.dim(` \u2014 ${t.assigned_to}`) : ""}`);
|
|
20333
21182
|
}
|
|
20334
21183
|
if (completed.length === 0 && started.length === 0)
|
|
20335
|
-
console.log(
|
|
21184
|
+
console.log(chalk2.dim(" No activity today."));
|
|
20336
21185
|
});
|
|
20337
21186
|
program2.command("yesterday").description("Show task activity from yesterday").option("--json", "Output as JSON").action(async (opts) => {
|
|
20338
21187
|
const globalOpts = program2.opts();
|
|
@@ -20351,21 +21200,21 @@ program2.command("yesterday").description("Show task activity from yesterday").o
|
|
|
20351
21200
|
console.log(JSON.stringify({ date: start.toISOString().slice(0, 10), completed, started }));
|
|
20352
21201
|
return;
|
|
20353
21202
|
}
|
|
20354
|
-
console.log(
|
|
21203
|
+
console.log(chalk2.bold(`Yesterday \u2014 ${start.toISOString().slice(0, 10)}
|
|
20355
21204
|
`));
|
|
20356
21205
|
if (completed.length > 0) {
|
|
20357
|
-
console.log(
|
|
21206
|
+
console.log(chalk2.green(` \u2713 Completed (${completed.length}):`));
|
|
20358
21207
|
for (const t of completed)
|
|
20359
|
-
console.log(` ${
|
|
21208
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk2.dim(` \u2014 ${t.assigned_to}`) : ""}`);
|
|
20360
21209
|
}
|
|
20361
21210
|
if (started.length > 0) {
|
|
20362
|
-
console.log(
|
|
21211
|
+
console.log(chalk2.blue(`
|
|
20363
21212
|
\u25B6 Started (${started.length}):`));
|
|
20364
21213
|
for (const t of started)
|
|
20365
|
-
console.log(` ${
|
|
21214
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}`);
|
|
20366
21215
|
}
|
|
20367
21216
|
if (completed.length === 0 && started.length === 0)
|
|
20368
|
-
console.log(
|
|
21217
|
+
console.log(chalk2.dim(" No activity yesterday."));
|
|
20369
21218
|
});
|
|
20370
21219
|
program2.command("mine").description("Show tasks assigned to you, grouped by status").argument("<agent>", "Agent name or ID").option("--json", "Output as JSON").action(async (agent, opts) => {
|
|
20371
21220
|
const globalOpts = program2.opts();
|
|
@@ -20400,23 +21249,23 @@ program2.command("mine").description("Show tasks assigned to you, grouped by sta
|
|
|
20400
21249
|
}
|
|
20401
21250
|
const statusOrder = ["in_progress", "pending", "blocked", "completed", "failed", "cancelled"];
|
|
20402
21251
|
const statusIcons2 = { in_progress: "\u25B6", pending: "\u25CB", blocked: "\u2298", completed: "\u2713", failed: "\u2717", cancelled: "\u2014" };
|
|
20403
|
-
const statusColors5 = { in_progress:
|
|
20404
|
-
console.log(
|
|
21252
|
+
const statusColors5 = { in_progress: chalk2.blue, pending: chalk2.white, blocked: chalk2.red, completed: chalk2.green, failed: chalk2.red, cancelled: chalk2.dim };
|
|
21253
|
+
console.log(chalk2.bold(`Tasks for ${agent} (${tasks.length} total):
|
|
20405
21254
|
`));
|
|
20406
21255
|
for (const status of statusOrder) {
|
|
20407
21256
|
const group = groups[status];
|
|
20408
21257
|
if (!group || group.length === 0)
|
|
20409
21258
|
continue;
|
|
20410
|
-
const color = statusColors5[status] ||
|
|
21259
|
+
const color = statusColors5[status] || chalk2.white;
|
|
20411
21260
|
const icon = statusIcons2[status] || "?";
|
|
20412
21261
|
console.log(color(` ${icon} ${status.replace("_", " ")} (${group.length}):`));
|
|
20413
21262
|
for (const t of group) {
|
|
20414
|
-
const priority = t.priority === "critical" || t.priority === "high" ?
|
|
20415
|
-
console.log(` ${
|
|
21263
|
+
const priority = t.priority === "critical" || t.priority === "high" ? chalk2.red(` [${t.priority}]`) : "";
|
|
21264
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${priority}`);
|
|
20416
21265
|
}
|
|
20417
21266
|
}
|
|
20418
21267
|
if (tasks.length === 0)
|
|
20419
|
-
console.log(
|
|
21268
|
+
console.log(chalk2.dim(` No tasks assigned to ${agent}.`));
|
|
20420
21269
|
});
|
|
20421
21270
|
program2.command("blocked").description("Show tasks blocked by incomplete dependencies").option("--json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
20422
21271
|
const globalOpts = program2.opts();
|
|
@@ -20438,15 +21287,15 @@ program2.command("blocked").description("Show tasks blocked by incomplete depend
|
|
|
20438
21287
|
return;
|
|
20439
21288
|
}
|
|
20440
21289
|
if (blockedTasks.length === 0) {
|
|
20441
|
-
console.log(
|
|
21290
|
+
console.log(chalk2.green(" No blocked tasks!"));
|
|
20442
21291
|
return;
|
|
20443
21292
|
}
|
|
20444
|
-
console.log(
|
|
21293
|
+
console.log(chalk2.bold(`Blocked (${blockedTasks.length}):
|
|
20445
21294
|
`));
|
|
20446
21295
|
for (const { task, blockers } of blockedTasks) {
|
|
20447
|
-
console.log(` ${
|
|
21296
|
+
console.log(` ${chalk2.cyan(task.short_id || task.id.slice(0, 8))} ${task.title}`);
|
|
20448
21297
|
for (const bl of blockers) {
|
|
20449
|
-
console.log(` ${
|
|
21298
|
+
console.log(` ${chalk2.red("\u2298")} ${chalk2.dim(bl.short_id || bl.id.slice(0, 8))} ${chalk2.dim(bl.title)} ${chalk2.yellow(`[${bl.status}]`)}`);
|
|
20450
21299
|
}
|
|
20451
21300
|
}
|
|
20452
21301
|
});
|
|
@@ -20460,16 +21309,16 @@ program2.command("overdue").description("Show tasks past their due date").option
|
|
|
20460
21309
|
return;
|
|
20461
21310
|
}
|
|
20462
21311
|
if (tasks.length === 0) {
|
|
20463
|
-
console.log(
|
|
21312
|
+
console.log(chalk2.green(" No overdue tasks!"));
|
|
20464
21313
|
return;
|
|
20465
21314
|
}
|
|
20466
|
-
console.log(
|
|
21315
|
+
console.log(chalk2.bold.red(`Overdue (${tasks.length}):
|
|
20467
21316
|
`));
|
|
20468
21317
|
for (const t of tasks) {
|
|
20469
21318
|
const dueDate = t.due_at.slice(0, 10);
|
|
20470
21319
|
const daysOverdue = Math.floor((Date.now() - new Date(t.due_at).getTime()) / 86400000);
|
|
20471
|
-
const urgency = daysOverdue > 7 ?
|
|
20472
|
-
console.log(` ${urgency} ${
|
|
21320
|
+
const urgency = daysOverdue > 7 ? chalk2.bgRed.white(` ${daysOverdue}d `) : chalk2.red(`${daysOverdue}d`);
|
|
21321
|
+
console.log(` ${urgency} ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk2.dim(` \u2014 ${t.assigned_to}`) : ""} ${chalk2.dim(`(due ${dueDate})`)}`);
|
|
20473
21322
|
}
|
|
20474
21323
|
});
|
|
20475
21324
|
program2.command("week").description("Show task activity from the past 7 days").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -20504,8 +21353,8 @@ program2.command("week").description("Show task activity from the past 7 days").
|
|
|
20504
21353
|
}
|
|
20505
21354
|
const totalCompleted = tasks.filter((t) => t.status === "completed").length;
|
|
20506
21355
|
const totalStarted = tasks.filter((t) => t.status === "in_progress").length;
|
|
20507
|
-
console.log(
|
|
20508
|
-
console.log(
|
|
21356
|
+
console.log(chalk2.bold(`Week \u2014 ${start.toISOString().slice(0, 10)} to ${now2.toISOString().slice(0, 10)}`));
|
|
21357
|
+
console.log(chalk2.dim(` ${totalCompleted} completed, ${totalStarted} in progress, ${tasks.length} total changes
|
|
20509
21358
|
`));
|
|
20510
21359
|
const sortedDays = Object.keys(days).sort().reverse();
|
|
20511
21360
|
for (const day of sortedDays) {
|
|
@@ -20516,14 +21365,14 @@ program2.command("week").description("Show task activity from the past 7 days").
|
|
|
20516
21365
|
if (completed.length === 0 && started.length === 0)
|
|
20517
21366
|
continue;
|
|
20518
21367
|
const weekday = new Date(day + "T12:00:00").toLocaleDateString("en-US", { weekday: "short" });
|
|
20519
|
-
console.log(
|
|
21368
|
+
console.log(chalk2.bold(` ${weekday} ${day}`));
|
|
20520
21369
|
for (const t of completed)
|
|
20521
|
-
console.log(` ${
|
|
21370
|
+
console.log(` ${chalk2.green("\u2713")} ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk2.dim(` \u2014 ${t.assigned_to}`) : ""}`);
|
|
20522
21371
|
for (const t of started)
|
|
20523
|
-
console.log(` ${
|
|
21372
|
+
console.log(` ${chalk2.blue("\u25B6")} ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk2.dim(` \u2014 ${t.assigned_to}`) : ""}`);
|
|
20524
21373
|
}
|
|
20525
21374
|
if (tasks.length === 0)
|
|
20526
|
-
console.log(
|
|
21375
|
+
console.log(chalk2.dim(" No activity this week."));
|
|
20527
21376
|
});
|
|
20528
21377
|
program2.command("burndown").description("Show task completion velocity over the past 7 days").option("--days <n>", "Number of days", "7").option("--json", "Output as JSON").action(async (opts) => {
|
|
20529
21378
|
const globalOpts = program2.opts();
|
|
@@ -20551,20 +21400,20 @@ program2.command("burndown").description("Show task completion velocity over the
|
|
|
20551
21400
|
}
|
|
20552
21401
|
const maxVal = Math.max(...dayStats.map((d) => Math.max(d.completed, d.created)), 1);
|
|
20553
21402
|
const barWidth = 30;
|
|
20554
|
-
console.log(
|
|
21403
|
+
console.log(chalk2.bold("Burndown (last " + numDays + ` days):
|
|
20555
21404
|
`));
|
|
20556
|
-
console.log(
|
|
21405
|
+
console.log(chalk2.dim(" Date Done New Failed Chart"));
|
|
20557
21406
|
for (const day of dayStats) {
|
|
20558
21407
|
const weekday = new Date(day.date + "T12:00:00").toLocaleDateString("en-US", { weekday: "short" });
|
|
20559
|
-
const completedBar =
|
|
20560
|
-
const createdBar =
|
|
20561
|
-
const failed = day.failed > 0 ?
|
|
20562
|
-
console.log(` ${weekday} ${day.date.slice(5)} ${
|
|
21408
|
+
const completedBar = chalk2.green("\u2588".repeat(Math.round(day.completed / maxVal * barWidth)));
|
|
21409
|
+
const createdBar = chalk2.blue("\u2591".repeat(Math.round(day.created / maxVal * barWidth)));
|
|
21410
|
+
const failed = day.failed > 0 ? chalk2.red(String(day.failed).padStart(4)) : chalk2.dim(" 0");
|
|
21411
|
+
console.log(` ${weekday} ${day.date.slice(5)} ${chalk2.green(String(day.completed).padStart(4))} ${chalk2.blue(String(day.created).padStart(4))} ${failed} ${completedBar}${createdBar}`);
|
|
20563
21412
|
}
|
|
20564
21413
|
const totalCompleted = dayStats.reduce((s, d) => s + d.completed, 0);
|
|
20565
21414
|
const totalCreated = dayStats.reduce((s, d) => s + d.created, 0);
|
|
20566
21415
|
const velocity = (totalCompleted / numDays).toFixed(1);
|
|
20567
|
-
console.log(
|
|
21416
|
+
console.log(chalk2.dim(`
|
|
20568
21417
|
Velocity: ${velocity}/day \xB7 ${totalCompleted} done \xB7 ${totalCreated} created`));
|
|
20569
21418
|
});
|
|
20570
21419
|
program2.command("log").description("Show recent task activity log (git-log style)").option("--limit <n>", "Number of entries", "30").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -20577,38 +21426,38 @@ program2.command("log").description("Show recent task activity log (git-log styl
|
|
|
20577
21426
|
return;
|
|
20578
21427
|
}
|
|
20579
21428
|
if (entries.length === 0) {
|
|
20580
|
-
console.log(
|
|
21429
|
+
console.log(chalk2.dim(" No activity yet."));
|
|
20581
21430
|
return;
|
|
20582
21431
|
}
|
|
20583
21432
|
const actionIcons = {
|
|
20584
|
-
create:
|
|
20585
|
-
start:
|
|
20586
|
-
complete:
|
|
20587
|
-
fail:
|
|
20588
|
-
update:
|
|
20589
|
-
approve:
|
|
20590
|
-
lock:
|
|
20591
|
-
unlock:
|
|
21433
|
+
create: chalk2.green("+"),
|
|
21434
|
+
start: chalk2.blue("\u25B6"),
|
|
21435
|
+
complete: chalk2.green("\u2713"),
|
|
21436
|
+
fail: chalk2.red("\u2717"),
|
|
21437
|
+
update: chalk2.yellow("~"),
|
|
21438
|
+
approve: chalk2.green("\u2605"),
|
|
21439
|
+
lock: chalk2.dim("\uD83D\uDD12"),
|
|
21440
|
+
unlock: chalk2.dim("\uD83D\uDD13")
|
|
20592
21441
|
};
|
|
20593
21442
|
let lastDate = "";
|
|
20594
21443
|
for (const e of entries) {
|
|
20595
21444
|
const date = e.created_at.slice(0, 10);
|
|
20596
21445
|
const time = e.created_at.slice(11, 16);
|
|
20597
21446
|
if (date !== lastDate) {
|
|
20598
|
-
console.log(
|
|
21447
|
+
console.log(chalk2.bold(`
|
|
20599
21448
|
${date}`));
|
|
20600
21449
|
lastDate = date;
|
|
20601
21450
|
}
|
|
20602
|
-
const icon = actionIcons[e.action] ||
|
|
20603
|
-
const agent = e.agent_id ?
|
|
20604
|
-
const taskRef =
|
|
21451
|
+
const icon = actionIcons[e.action] || chalk2.dim("\xB7");
|
|
21452
|
+
const agent = e.agent_id ? chalk2.dim(` (${e.agent_id})`) : "";
|
|
21453
|
+
const taskRef = chalk2.cyan(e.task_id.slice(0, 8));
|
|
20605
21454
|
let detail = "";
|
|
20606
21455
|
if (e.field && e.old_value && e.new_value) {
|
|
20607
|
-
detail =
|
|
21456
|
+
detail = chalk2.dim(` ${e.field}: ${e.old_value} \u2192 ${e.new_value}`);
|
|
20608
21457
|
} else if (e.field && e.new_value) {
|
|
20609
|
-
detail =
|
|
21458
|
+
detail = chalk2.dim(` ${e.field}: ${e.new_value}`);
|
|
20610
21459
|
}
|
|
20611
|
-
console.log(` ${
|
|
21460
|
+
console.log(` ${chalk2.dim(time)} ${icon} ${e.action.padEnd(8)} ${taskRef}${detail}${agent}`);
|
|
20612
21461
|
}
|
|
20613
21462
|
});
|
|
20614
21463
|
program2.command("ready").description("Show all tasks ready to be claimed (pending, unblocked, unlocked)").option("--json", "Output as JSON").option("--project <id>", "Filter to project").option("--limit <n>", "Max tasks to show", "20").action(async (opts) => {
|
|
@@ -20633,15 +21482,15 @@ program2.command("ready").description("Show all tasks ready to be claimed (pendi
|
|
|
20633
21482
|
return;
|
|
20634
21483
|
}
|
|
20635
21484
|
if (limited.length === 0) {
|
|
20636
|
-
console.log(
|
|
21485
|
+
console.log(chalk2.dim(" No tasks ready to claim."));
|
|
20637
21486
|
return;
|
|
20638
21487
|
}
|
|
20639
|
-
console.log(
|
|
21488
|
+
console.log(chalk2.bold(`Ready to claim (${ready.length}${ready.length > limited.length ? `, showing ${limited.length}` : ""}):
|
|
20640
21489
|
`));
|
|
20641
21490
|
for (const t of limited) {
|
|
20642
|
-
const pri = t.priority === "critical" ?
|
|
20643
|
-
const due = t.due_at ?
|
|
20644
|
-
console.log(` ${
|
|
21491
|
+
const pri = t.priority === "critical" ? chalk2.bgRed.white(" CRIT ") : t.priority === "high" ? chalk2.red("[high]") : t.priority === "medium" ? chalk2.yellow("[med]") : "";
|
|
21492
|
+
const due = t.due_at ? chalk2.dim(` due ${t.due_at.slice(0, 10)}`) : "";
|
|
21493
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title} ${pri}${due}`);
|
|
20645
21494
|
}
|
|
20646
21495
|
});
|
|
20647
21496
|
program2.command("sprint").description("Sprint dashboard: in-progress, next up, blockers, and overdue").option("--json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
@@ -20668,41 +21517,41 @@ program2.command("sprint").description("Sprint dashboard: in-progress, next up,
|
|
|
20668
21517
|
console.log(JSON.stringify({ in_progress: inProgress, next_up: nextUp, blocked, overdue }));
|
|
20669
21518
|
return;
|
|
20670
21519
|
}
|
|
20671
|
-
console.log(
|
|
21520
|
+
console.log(chalk2.bold(`Sprint Dashboard
|
|
20672
21521
|
`));
|
|
20673
|
-
console.log(
|
|
21522
|
+
console.log(chalk2.blue(` \u25B6 In Progress (${inProgress.length}):`));
|
|
20674
21523
|
if (inProgress.length === 0)
|
|
20675
|
-
console.log(
|
|
21524
|
+
console.log(chalk2.dim(" (none)"));
|
|
20676
21525
|
for (const t of inProgress) {
|
|
20677
|
-
const agent = t.assigned_to ?
|
|
20678
|
-
console.log(` ${
|
|
21526
|
+
const agent = t.assigned_to ? chalk2.dim(` \u2014 ${t.assigned_to}`) : "";
|
|
21527
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${agent}`);
|
|
20679
21528
|
}
|
|
20680
|
-
console.log(
|
|
21529
|
+
console.log(chalk2.white(`
|
|
20681
21530
|
\u25CB Next Up (${nextUp.length}):`));
|
|
20682
21531
|
if (nextUp.length === 0)
|
|
20683
|
-
console.log(
|
|
21532
|
+
console.log(chalk2.dim(" (none)"));
|
|
20684
21533
|
for (const t of nextUp) {
|
|
20685
|
-
const pri = t.priority === "critical" ?
|
|
20686
|
-
console.log(` ${
|
|
21534
|
+
const pri = t.priority === "critical" ? chalk2.bgRed.white(" CRIT ") : t.priority === "high" ? chalk2.red("[high]") : "";
|
|
21535
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title} ${pri}`);
|
|
20687
21536
|
}
|
|
20688
21537
|
if (blocked.length > 0) {
|
|
20689
|
-
console.log(
|
|
21538
|
+
console.log(chalk2.red(`
|
|
20690
21539
|
\u2298 Blocked (${blocked.length}):`));
|
|
20691
21540
|
for (const { task, blockers } of blocked) {
|
|
20692
|
-
console.log(` ${
|
|
21541
|
+
console.log(` ${chalk2.cyan(task.short_id || task.id.slice(0, 8))} ${task.title}`);
|
|
20693
21542
|
for (const bl of blockers)
|
|
20694
|
-
console.log(` ${
|
|
21543
|
+
console.log(` ${chalk2.dim("\u2190 " + (bl.short_id || bl.id.slice(0, 8)) + " " + bl.title)} ${chalk2.yellow(`[${bl.status}]`)}`);
|
|
20695
21544
|
}
|
|
20696
21545
|
}
|
|
20697
21546
|
if (overdue.length > 0) {
|
|
20698
|
-
console.log(
|
|
21547
|
+
console.log(chalk2.red(`
|
|
20699
21548
|
\u26A0 Overdue (${overdue.length}):`));
|
|
20700
21549
|
for (const t of overdue) {
|
|
20701
21550
|
const daysOver = Math.floor((Date.now() - new Date(t.due_at).getTime()) / 86400000);
|
|
20702
|
-
console.log(` ${
|
|
21551
|
+
console.log(` ${chalk2.red(`${daysOver}d`)} ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}`);
|
|
20703
21552
|
}
|
|
20704
21553
|
}
|
|
20705
|
-
console.log(
|
|
21554
|
+
console.log(chalk2.dim(`
|
|
20706
21555
|
${inProgress.length} active \xB7 ${pending.length} pending \xB7 ${blocked.length} blocked \xB7 ${overdue.length} overdue`));
|
|
20707
21556
|
});
|
|
20708
21557
|
program2.command("handoff").description("Create or view agent session handoffs").option("--create", "Create a new handoff").option("--agent <name>", "Agent name").option("--summary <text>", "Handoff summary").option("--completed <items>", "Comma-separated completed items").option("--in-progress <items>", "Comma-separated in-progress items").option("--blockers <items>", "Comma-separated blockers").option("--next <items>", "Comma-separated next steps").option("--json", "Output as JSON").option("--limit <n>", "Number of handoffs to show", "5").action(async (opts) => {
|
|
@@ -20712,7 +21561,7 @@ program2.command("handoff").description("Create or view agent session handoffs")
|
|
|
20712
21561
|
const projectId = autoProject(globalOpts) || undefined;
|
|
20713
21562
|
if (opts.create || opts.summary) {
|
|
20714
21563
|
if (!opts.summary) {
|
|
20715
|
-
console.error(
|
|
21564
|
+
console.error(chalk2.red(" --summary is required for creating a handoff"));
|
|
20716
21565
|
process.exit(1);
|
|
20717
21566
|
}
|
|
20718
21567
|
const handoff = createHandoff2({
|
|
@@ -20728,7 +21577,7 @@ program2.command("handoff").description("Create or view agent session handoffs")
|
|
|
20728
21577
|
console.log(JSON.stringify(handoff));
|
|
20729
21578
|
return;
|
|
20730
21579
|
}
|
|
20731
|
-
console.log(
|
|
21580
|
+
console.log(chalk2.green(` \u2713 Handoff created by ${handoff.agent_id || "unknown"}`));
|
|
20732
21581
|
return;
|
|
20733
21582
|
}
|
|
20734
21583
|
const handoffs = listHandoffs2(projectId, parseInt(opts.limit, 10), db);
|
|
@@ -20737,31 +21586,31 @@ program2.command("handoff").description("Create or view agent session handoffs")
|
|
|
20737
21586
|
return;
|
|
20738
21587
|
}
|
|
20739
21588
|
if (handoffs.length === 0) {
|
|
20740
|
-
console.log(
|
|
21589
|
+
console.log(chalk2.dim(" No handoffs yet."));
|
|
20741
21590
|
return;
|
|
20742
21591
|
}
|
|
20743
21592
|
for (const h of handoffs) {
|
|
20744
21593
|
const time = h.created_at.slice(0, 16).replace("T", " ");
|
|
20745
|
-
console.log(
|
|
21594
|
+
console.log(chalk2.bold(`
|
|
20746
21595
|
${time} ${h.agent_id || "unknown"}`));
|
|
20747
21596
|
console.log(` ${h.summary}`);
|
|
20748
21597
|
if (h.completed?.length) {
|
|
20749
|
-
console.log(
|
|
21598
|
+
console.log(chalk2.green(` \u2713 Completed:`));
|
|
20750
21599
|
for (const c of h.completed)
|
|
20751
21600
|
console.log(` - ${c}`);
|
|
20752
21601
|
}
|
|
20753
21602
|
if (h.in_progress?.length) {
|
|
20754
|
-
console.log(
|
|
21603
|
+
console.log(chalk2.blue(` \u25B6 In progress:`));
|
|
20755
21604
|
for (const c of h.in_progress)
|
|
20756
21605
|
console.log(` - ${c}`);
|
|
20757
21606
|
}
|
|
20758
21607
|
if (h.blockers?.length) {
|
|
20759
|
-
console.log(
|
|
21608
|
+
console.log(chalk2.red(` \u2298 Blockers:`));
|
|
20760
21609
|
for (const c of h.blockers)
|
|
20761
21610
|
console.log(` - ${c}`);
|
|
20762
21611
|
}
|
|
20763
21612
|
if (h.next_steps?.length) {
|
|
20764
|
-
console.log(
|
|
21613
|
+
console.log(chalk2.cyan(` \u2192 Next steps:`));
|
|
20765
21614
|
for (const c of h.next_steps)
|
|
20766
21615
|
console.log(` - ${c}`);
|
|
20767
21616
|
}
|
|
@@ -20787,16 +21636,16 @@ program2.command("priorities").description("Show task counts grouped by priority
|
|
|
20787
21636
|
console.log(JSON.stringify(counts));
|
|
20788
21637
|
return;
|
|
20789
21638
|
}
|
|
20790
|
-
console.log(
|
|
21639
|
+
console.log(chalk2.bold(`Priority Breakdown:
|
|
20791
21640
|
`));
|
|
20792
|
-
const priColors = { critical:
|
|
21641
|
+
const priColors = { critical: chalk2.bgRed.white, high: chalk2.red, medium: chalk2.yellow, low: chalk2.blue, none: chalk2.dim };
|
|
20793
21642
|
for (const p of priorities) {
|
|
20794
21643
|
const c = counts[p];
|
|
20795
21644
|
if (!c || c.total === 0)
|
|
20796
21645
|
continue;
|
|
20797
|
-
const color = priColors[p] ||
|
|
20798
|
-
const bar =
|
|
20799
|
-
console.log(` ${color(p.padEnd(9))} ${String(c.total).padStart(4)} total ${
|
|
21646
|
+
const color = priColors[p] || chalk2.white;
|
|
21647
|
+
const bar = chalk2.green("\u2588".repeat(Math.min(c.completed, 30))) + chalk2.blue("\u2591".repeat(Math.min(c.in_progress, 10))) + chalk2.dim("\xB7".repeat(Math.min(c.pending, 20)));
|
|
21648
|
+
console.log(` ${color(p.padEnd(9))} ${String(c.total).padStart(4)} total ${chalk2.green(String(c.completed).padStart(3))} done ${chalk2.blue(String(c.in_progress).padStart(3))} active ${chalk2.dim(String(c.pending).padStart(3))} pending ${bar}`);
|
|
20800
21649
|
}
|
|
20801
21650
|
});
|
|
20802
21651
|
program2.command("context").description("Session start context: status, latest handoff, next task, overdue").option("--agent <name>", "Agent name for handoff lookup").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -20816,21 +21665,21 @@ program2.command("context").description("Session start context: status, latest h
|
|
|
20816
21665
|
console.log(JSON.stringify({ status, next_task: nextTask, overdue_count: overdue.length, latest_handoff: handoff, as_of: new Date().toISOString() }));
|
|
20817
21666
|
return;
|
|
20818
21667
|
}
|
|
20819
|
-
console.log(
|
|
21668
|
+
console.log(chalk2.bold(`Session Context
|
|
20820
21669
|
`));
|
|
20821
21670
|
console.log(` ${status.pending} pending \xB7 ${status.in_progress} active \xB7 ${status.completed} done \xB7 ${status.total} total`);
|
|
20822
21671
|
if (status.stale_count > 0)
|
|
20823
|
-
console.log(
|
|
21672
|
+
console.log(chalk2.yellow(` \u26A0 ${status.stale_count} stale tasks`));
|
|
20824
21673
|
if (overdue.length > 0)
|
|
20825
|
-
console.log(
|
|
21674
|
+
console.log(chalk2.red(` \u26A0 ${overdue.length} overdue tasks`));
|
|
20826
21675
|
if (nextTask) {
|
|
20827
|
-
const pri = nextTask.priority === "critical" || nextTask.priority === "high" ?
|
|
20828
|
-
console.log(
|
|
21676
|
+
const pri = nextTask.priority === "critical" || nextTask.priority === "high" ? chalk2.red(` [${nextTask.priority}]`) : "";
|
|
21677
|
+
console.log(chalk2.bold(`
|
|
20829
21678
|
Next up:`));
|
|
20830
|
-
console.log(` ${
|
|
21679
|
+
console.log(` ${chalk2.cyan(nextTask.short_id || nextTask.id.slice(0, 8))} ${nextTask.title}${pri}`);
|
|
20831
21680
|
}
|
|
20832
21681
|
if (handoff) {
|
|
20833
|
-
console.log(
|
|
21682
|
+
console.log(chalk2.bold(`
|
|
20834
21683
|
Last handoff (${handoff.agent_id || "unknown"}, ${handoff.created_at.slice(0, 16).replace("T", " ")}):`));
|
|
20835
21684
|
console.log(` ${handoff.summary}`);
|
|
20836
21685
|
if (handoff.next_steps?.length) {
|
|
@@ -20838,7 +21687,7 @@ program2.command("context").description("Session start context: status, latest h
|
|
|
20838
21687
|
console.log(` \u2192 ${s}`);
|
|
20839
21688
|
}
|
|
20840
21689
|
}
|
|
20841
|
-
console.log(
|
|
21690
|
+
console.log(chalk2.dim(`
|
|
20842
21691
|
as_of: ${new Date().toISOString()}`));
|
|
20843
21692
|
});
|
|
20844
21693
|
program2.command("report-failure").description("Create a task from a test/build/typecheck failure and auto-assign it").requiredOption("--error <message>", "Error message or summary").option("--type <type>", "Failure type: test, build, typecheck, runtime, other", "test").option("--file <path>", "File where failure occurred").option("--stack <trace>", "Stack trace or detailed output").option("--title <title>", "Custom task title (auto-generated if omitted)").option("--priority <p>", "Priority: low, medium, high, critical").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -20877,11 +21726,11 @@ ${opts.stack.slice(0, 1500)}
|
|
|
20877
21726
|
console.log(JSON.stringify({ task_id: task.id, short_id: task.short_id, title: task.title, assigned_to: assignResult.agent_name, method: assignResult.method }));
|
|
20878
21727
|
return;
|
|
20879
21728
|
}
|
|
20880
|
-
console.log(
|
|
21729
|
+
console.log(chalk2.green(`\u2713 Created task ${task.short_id || task.id.slice(0, 8)}: ${task.title}`));
|
|
20881
21730
|
if (assignResult.agent_name) {
|
|
20882
|
-
console.log(
|
|
21731
|
+
console.log(chalk2.cyan(` Assigned to: ${assignResult.agent_name} (via ${assignResult.method})`));
|
|
20883
21732
|
if (assignResult.reason)
|
|
20884
|
-
console.log(
|
|
21733
|
+
console.log(chalk2.dim(` Reason: ${assignResult.reason}`));
|
|
20885
21734
|
}
|
|
20886
21735
|
});
|
|
20887
21736
|
program2.action(async () => {
|
|
@@ -20898,4 +21747,5 @@ program2.action(async () => {
|
|
|
20898
21747
|
program2.help();
|
|
20899
21748
|
}
|
|
20900
21749
|
});
|
|
21750
|
+
program2.addCommand(makeBrainsCommand());
|
|
20901
21751
|
program2.parse();
|