@hasna/todos 0.9.74 → 0.9.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1021 -2
- package/dist/db/checklists.d.ts +13 -0
- package/dist/db/checklists.d.ts.map +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/handoffs.d.ts +25 -0
- package/dist/db/handoffs.d.ts.map +1 -0
- package/dist/db/projects.d.ts +5 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +1 -0
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +255 -1
- package/dist/mcp/index.js +513 -1
- package/dist/server/index.js +146 -17
- package/dist/types/index.d.ts +55 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2079,6 +2079,19 @@ var require_commander = __commonJS((exports) => {
|
|
|
2079
2079
|
});
|
|
2080
2080
|
|
|
2081
2081
|
// src/db/database.ts
|
|
2082
|
+
var exports_database = {};
|
|
2083
|
+
__export(exports_database, {
|
|
2084
|
+
uuid: () => uuid,
|
|
2085
|
+
resolvePartialId: () => resolvePartialId,
|
|
2086
|
+
resetDatabase: () => resetDatabase,
|
|
2087
|
+
now: () => now,
|
|
2088
|
+
lockExpiryCutoff: () => lockExpiryCutoff,
|
|
2089
|
+
isLockExpired: () => isLockExpired,
|
|
2090
|
+
getDatabase: () => getDatabase,
|
|
2091
|
+
closeDatabase: () => closeDatabase,
|
|
2092
|
+
clearExpiredLocks: () => clearExpiredLocks,
|
|
2093
|
+
LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
|
|
2094
|
+
});
|
|
2082
2095
|
import { Database } from "bun:sqlite";
|
|
2083
2096
|
import { existsSync, mkdirSync } from "fs";
|
|
2084
2097
|
import { dirname, join, resolve } from "path";
|
|
@@ -2247,6 +2260,28 @@ function ensureSchema(db) {
|
|
|
2247
2260
|
metadata TEXT DEFAULT '{}',
|
|
2248
2261
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2249
2262
|
)`);
|
|
2263
|
+
ensureTable("task_checklists", `
|
|
2264
|
+
CREATE TABLE task_checklists (
|
|
2265
|
+
id TEXT PRIMARY KEY,
|
|
2266
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
2267
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
2268
|
+
text TEXT NOT NULL,
|
|
2269
|
+
checked INTEGER NOT NULL DEFAULT 0,
|
|
2270
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2271
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2272
|
+
)`);
|
|
2273
|
+
ensureTable("project_sources", `
|
|
2274
|
+
CREATE TABLE project_sources (
|
|
2275
|
+
id TEXT PRIMARY KEY,
|
|
2276
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
2277
|
+
type TEXT NOT NULL,
|
|
2278
|
+
name TEXT NOT NULL,
|
|
2279
|
+
uri TEXT NOT NULL,
|
|
2280
|
+
description TEXT,
|
|
2281
|
+
metadata TEXT DEFAULT '{}',
|
|
2282
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2283
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2284
|
+
)`);
|
|
2250
2285
|
ensureColumn("projects", "task_list_id", "TEXT");
|
|
2251
2286
|
ensureColumn("projects", "task_prefix", "TEXT");
|
|
2252
2287
|
ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
|
|
@@ -2291,6 +2326,9 @@ function ensureSchema(db) {
|
|
|
2291
2326
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
|
|
2292
2327
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id)");
|
|
2293
2328
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL");
|
|
2329
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_checklists_task ON task_checklists(task_id)");
|
|
2330
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_project ON project_sources(project_id)");
|
|
2331
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_sources_type ON project_sources(type)");
|
|
2294
2332
|
}
|
|
2295
2333
|
function backfillTaskTags(db) {
|
|
2296
2334
|
try {
|
|
@@ -2321,6 +2359,15 @@ function backfillTaskTags(db) {
|
|
|
2321
2359
|
}
|
|
2322
2360
|
} catch {}
|
|
2323
2361
|
}
|
|
2362
|
+
function closeDatabase() {
|
|
2363
|
+
if (_db) {
|
|
2364
|
+
_db.close();
|
|
2365
|
+
_db = null;
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
function resetDatabase() {
|
|
2369
|
+
_db = null;
|
|
2370
|
+
}
|
|
2324
2371
|
function now() {
|
|
2325
2372
|
return new Date().toISOString();
|
|
2326
2373
|
}
|
|
@@ -2668,6 +2715,49 @@ var init_database = __esm(() => {
|
|
|
2668
2715
|
ALTER TABLE tasks ADD COLUMN reason TEXT;
|
|
2669
2716
|
ALTER TABLE tasks ADD COLUMN spawned_from_session TEXT;
|
|
2670
2717
|
INSERT OR IGNORE INTO _migrations (id) VALUES (19);
|
|
2718
|
+
`,
|
|
2719
|
+
`
|
|
2720
|
+
CREATE TABLE IF NOT EXISTS handoffs (
|
|
2721
|
+
id TEXT PRIMARY KEY,
|
|
2722
|
+
agent_id TEXT,
|
|
2723
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
2724
|
+
summary TEXT NOT NULL,
|
|
2725
|
+
completed TEXT,
|
|
2726
|
+
in_progress TEXT,
|
|
2727
|
+
blockers TEXT,
|
|
2728
|
+
next_steps TEXT,
|
|
2729
|
+
created_at TEXT NOT NULL
|
|
2730
|
+
);
|
|
2731
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (20);
|
|
2732
|
+
`,
|
|
2733
|
+
`
|
|
2734
|
+
CREATE TABLE IF NOT EXISTS task_checklists (
|
|
2735
|
+
id TEXT PRIMARY KEY,
|
|
2736
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
2737
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
2738
|
+
text TEXT NOT NULL,
|
|
2739
|
+
checked INTEGER NOT NULL DEFAULT 0,
|
|
2740
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2741
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2742
|
+
);
|
|
2743
|
+
CREATE INDEX IF NOT EXISTS idx_task_checklists_task ON task_checklists(task_id);
|
|
2744
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (21);
|
|
2745
|
+
`,
|
|
2746
|
+
`
|
|
2747
|
+
CREATE TABLE IF NOT EXISTS project_sources (
|
|
2748
|
+
id TEXT PRIMARY KEY,
|
|
2749
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
2750
|
+
type TEXT NOT NULL,
|
|
2751
|
+
name TEXT NOT NULL,
|
|
2752
|
+
uri TEXT NOT NULL,
|
|
2753
|
+
description TEXT,
|
|
2754
|
+
metadata TEXT DEFAULT '{}',
|
|
2755
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2756
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2757
|
+
);
|
|
2758
|
+
CREATE INDEX IF NOT EXISTS idx_project_sources_project ON project_sources(project_id);
|
|
2759
|
+
CREATE INDEX IF NOT EXISTS idx_project_sources_type ON project_sources(type);
|
|
2760
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (22);
|
|
2671
2761
|
`
|
|
2672
2762
|
];
|
|
2673
2763
|
});
|
|
@@ -2782,13 +2872,17 @@ var exports_projects = {};
|
|
|
2782
2872
|
__export(exports_projects, {
|
|
2783
2873
|
updateProject: () => updateProject,
|
|
2784
2874
|
slugify: () => slugify,
|
|
2875
|
+
removeProjectSource: () => removeProjectSource,
|
|
2785
2876
|
nextTaskShortId: () => nextTaskShortId,
|
|
2786
2877
|
listProjects: () => listProjects,
|
|
2878
|
+
listProjectSources: () => listProjectSources,
|
|
2879
|
+
getProjectWithSources: () => getProjectWithSources,
|
|
2787
2880
|
getProjectByPath: () => getProjectByPath,
|
|
2788
2881
|
getProject: () => getProject,
|
|
2789
2882
|
ensureProject: () => ensureProject,
|
|
2790
2883
|
deleteProject: () => deleteProject,
|
|
2791
|
-
createProject: () => createProject
|
|
2884
|
+
createProject: () => createProject,
|
|
2885
|
+
addProjectSource: () => addProjectSource
|
|
2792
2886
|
});
|
|
2793
2887
|
function slugify(name) {
|
|
2794
2888
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -2865,6 +2959,48 @@ function deleteProject(id, db) {
|
|
|
2865
2959
|
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
2866
2960
|
return result.changes > 0;
|
|
2867
2961
|
}
|
|
2962
|
+
function rowToSource(row) {
|
|
2963
|
+
return {
|
|
2964
|
+
...row,
|
|
2965
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {}
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
function addProjectSource(input, db) {
|
|
2969
|
+
const d = db || getDatabase();
|
|
2970
|
+
const id = uuid();
|
|
2971
|
+
const timestamp = now();
|
|
2972
|
+
d.run(`INSERT INTO project_sources (id, project_id, type, name, uri, description, metadata, created_at, updated_at)
|
|
2973
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2974
|
+
id,
|
|
2975
|
+
input.project_id,
|
|
2976
|
+
input.type,
|
|
2977
|
+
input.name,
|
|
2978
|
+
input.uri,
|
|
2979
|
+
input.description || null,
|
|
2980
|
+
JSON.stringify(input.metadata || {}),
|
|
2981
|
+
timestamp,
|
|
2982
|
+
timestamp
|
|
2983
|
+
]);
|
|
2984
|
+
return rowToSource(d.query("SELECT * FROM project_sources WHERE id = ?").get(id));
|
|
2985
|
+
}
|
|
2986
|
+
function removeProjectSource(id, db) {
|
|
2987
|
+
const d = db || getDatabase();
|
|
2988
|
+
const result = d.run("DELETE FROM project_sources WHERE id = ?", [id]);
|
|
2989
|
+
return result.changes > 0;
|
|
2990
|
+
}
|
|
2991
|
+
function listProjectSources(projectId, db) {
|
|
2992
|
+
const d = db || getDatabase();
|
|
2993
|
+
const rows = d.query("SELECT * FROM project_sources WHERE project_id = ? ORDER BY name").all(projectId);
|
|
2994
|
+
return rows.map(rowToSource);
|
|
2995
|
+
}
|
|
2996
|
+
function getProjectWithSources(id, db) {
|
|
2997
|
+
const d = db || getDatabase();
|
|
2998
|
+
const project = getProject(id, d);
|
|
2999
|
+
if (!project)
|
|
3000
|
+
return null;
|
|
3001
|
+
project.sources = listProjectSources(id, d);
|
|
3002
|
+
return project;
|
|
3003
|
+
}
|
|
2868
3004
|
function nextTaskShortId(projectId, db) {
|
|
2869
3005
|
const d = db || getDatabase();
|
|
2870
3006
|
const project = getProject(projectId, d);
|
|
@@ -3320,6 +3456,52 @@ var init_templates = __esm(() => {
|
|
|
3320
3456
|
init_database();
|
|
3321
3457
|
});
|
|
3322
3458
|
|
|
3459
|
+
// src/db/checklists.ts
|
|
3460
|
+
function rowToItem(row) {
|
|
3461
|
+
return { ...row, checked: !!row.checked };
|
|
3462
|
+
}
|
|
3463
|
+
function getChecklist(taskId, db) {
|
|
3464
|
+
const d = db || getDatabase();
|
|
3465
|
+
const rows = d.query("SELECT * FROM task_checklists WHERE task_id = ? ORDER BY position, created_at").all(taskId);
|
|
3466
|
+
return rows.map(rowToItem);
|
|
3467
|
+
}
|
|
3468
|
+
function addChecklistItem(input, db) {
|
|
3469
|
+
const d = db || getDatabase();
|
|
3470
|
+
const id = uuid();
|
|
3471
|
+
const timestamp = now();
|
|
3472
|
+
let position = input.position;
|
|
3473
|
+
if (position === undefined) {
|
|
3474
|
+
const maxRow = d.query("SELECT MAX(position) as max_pos FROM task_checklists WHERE task_id = ?").get(input.task_id);
|
|
3475
|
+
position = (maxRow?.max_pos ?? -1) + 1;
|
|
3476
|
+
}
|
|
3477
|
+
d.run("INSERT INTO task_checklists (id, task_id, position, text, checked, created_at, updated_at) VALUES (?, ?, ?, ?, 0, ?, ?)", [id, input.task_id, position, input.text, timestamp, timestamp]);
|
|
3478
|
+
return rowToItem(d.query("SELECT * FROM task_checklists WHERE id = ?").get(id));
|
|
3479
|
+
}
|
|
3480
|
+
function checkChecklistItem(id, checked, db) {
|
|
3481
|
+
const d = db || getDatabase();
|
|
3482
|
+
const timestamp = now();
|
|
3483
|
+
const result = d.run("UPDATE task_checklists SET checked = ?, updated_at = ? WHERE id = ?", [checked ? 1 : 0, timestamp, id]);
|
|
3484
|
+
if (result.changes === 0)
|
|
3485
|
+
return null;
|
|
3486
|
+
return rowToItem(d.query("SELECT * FROM task_checklists WHERE id = ?").get(id));
|
|
3487
|
+
}
|
|
3488
|
+
function updateChecklistItemText(id, text, db) {
|
|
3489
|
+
const d = db || getDatabase();
|
|
3490
|
+
const timestamp = now();
|
|
3491
|
+
const result = d.run("UPDATE task_checklists SET text = ?, updated_at = ? WHERE id = ?", [text, timestamp, id]);
|
|
3492
|
+
if (result.changes === 0)
|
|
3493
|
+
return null;
|
|
3494
|
+
return rowToItem(d.query("SELECT * FROM task_checklists WHERE id = ?").get(id));
|
|
3495
|
+
}
|
|
3496
|
+
function removeChecklistItem(id, db) {
|
|
3497
|
+
const d = db || getDatabase();
|
|
3498
|
+
const result = d.run("DELETE FROM task_checklists WHERE id = ?", [id]);
|
|
3499
|
+
return result.changes > 0;
|
|
3500
|
+
}
|
|
3501
|
+
var init_checklists = __esm(() => {
|
|
3502
|
+
init_database();
|
|
3503
|
+
});
|
|
3504
|
+
|
|
3323
3505
|
// src/db/tasks.ts
|
|
3324
3506
|
var exports_tasks = {};
|
|
3325
3507
|
__export(exports_tasks, {
|
|
@@ -3342,6 +3524,7 @@ __export(exports_tasks, {
|
|
|
3342
3524
|
getTask: () => getTask,
|
|
3343
3525
|
getStatus: () => getStatus,
|
|
3344
3526
|
getStaleTasks: () => getStaleTasks,
|
|
3527
|
+
getOverdueTasks: () => getOverdueTasks,
|
|
3345
3528
|
getNextTask: () => getNextTask,
|
|
3346
3529
|
getBlockingDeps: () => getBlockingDeps,
|
|
3347
3530
|
getActiveWork: () => getActiveWork,
|
|
@@ -3449,13 +3632,15 @@ function getTaskWithRelations(id, db) {
|
|
|
3449
3632
|
const blocked_by = blockedByRows.map(rowToTask);
|
|
3450
3633
|
const comments = d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(id);
|
|
3451
3634
|
const parent = task.parent_id ? getTask(task.parent_id, d) : null;
|
|
3635
|
+
const checklist = getChecklist(id, d);
|
|
3452
3636
|
return {
|
|
3453
3637
|
...task,
|
|
3454
3638
|
subtasks,
|
|
3455
3639
|
dependencies,
|
|
3456
3640
|
blocked_by,
|
|
3457
3641
|
comments,
|
|
3458
|
-
parent
|
|
3642
|
+
parent,
|
|
3643
|
+
checklist
|
|
3459
3644
|
};
|
|
3460
3645
|
}
|
|
3461
3646
|
function listTasks(filter = {}, db) {
|
|
@@ -4380,6 +4565,19 @@ function bulkUpdateTasks(taskIds, updates, db) {
|
|
|
4380
4565
|
tx();
|
|
4381
4566
|
return { updated, failed };
|
|
4382
4567
|
}
|
|
4568
|
+
function getOverdueTasks(projectId, db) {
|
|
4569
|
+
const d = db || getDatabase();
|
|
4570
|
+
const nowStr = new Date().toISOString();
|
|
4571
|
+
let query = `SELECT * FROM tasks WHERE due_at IS NOT NULL AND due_at < ? AND status NOT IN ('completed', 'cancelled', 'failed')`;
|
|
4572
|
+
const params = [nowStr];
|
|
4573
|
+
if (projectId) {
|
|
4574
|
+
query += ` AND project_id = ?`;
|
|
4575
|
+
params.push(projectId);
|
|
4576
|
+
}
|
|
4577
|
+
query += ` ORDER BY due_at ASC`;
|
|
4578
|
+
const rows = d.query(query).all(...params);
|
|
4579
|
+
return rows.map(rowToTask);
|
|
4580
|
+
}
|
|
4383
4581
|
var init_tasks = __esm(() => {
|
|
4384
4582
|
init_types();
|
|
4385
4583
|
init_database();
|
|
@@ -4389,6 +4587,7 @@ var init_tasks = __esm(() => {
|
|
|
4389
4587
|
init_recurrence();
|
|
4390
4588
|
init_webhooks();
|
|
4391
4589
|
init_templates();
|
|
4590
|
+
init_checklists();
|
|
4392
4591
|
});
|
|
4393
4592
|
|
|
4394
4593
|
// src/db/agents.ts
|
|
@@ -9368,6 +9567,77 @@ var init_zod = __esm(() => {
|
|
|
9368
9567
|
init_external();
|
|
9369
9568
|
});
|
|
9370
9569
|
|
|
9570
|
+
// src/db/handoffs.ts
|
|
9571
|
+
var exports_handoffs = {};
|
|
9572
|
+
__export(exports_handoffs, {
|
|
9573
|
+
listHandoffs: () => listHandoffs,
|
|
9574
|
+
getLatestHandoff: () => getLatestHandoff,
|
|
9575
|
+
createHandoff: () => createHandoff
|
|
9576
|
+
});
|
|
9577
|
+
function createHandoff(input, db) {
|
|
9578
|
+
const d = db || getDatabase();
|
|
9579
|
+
const id = uuid();
|
|
9580
|
+
const timestamp = now();
|
|
9581
|
+
d.run(`INSERT INTO handoffs (id, agent_id, project_id, summary, completed, in_progress, blockers, next_steps, created_at)
|
|
9582
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
9583
|
+
id,
|
|
9584
|
+
input.agent_id || null,
|
|
9585
|
+
input.project_id || null,
|
|
9586
|
+
input.summary,
|
|
9587
|
+
input.completed ? JSON.stringify(input.completed) : null,
|
|
9588
|
+
input.in_progress ? JSON.stringify(input.in_progress) : null,
|
|
9589
|
+
input.blockers ? JSON.stringify(input.blockers) : null,
|
|
9590
|
+
input.next_steps ? JSON.stringify(input.next_steps) : null,
|
|
9591
|
+
timestamp
|
|
9592
|
+
]);
|
|
9593
|
+
return {
|
|
9594
|
+
id,
|
|
9595
|
+
agent_id: input.agent_id || null,
|
|
9596
|
+
project_id: input.project_id || null,
|
|
9597
|
+
summary: input.summary,
|
|
9598
|
+
completed: input.completed || null,
|
|
9599
|
+
in_progress: input.in_progress || null,
|
|
9600
|
+
blockers: input.blockers || null,
|
|
9601
|
+
next_steps: input.next_steps || null,
|
|
9602
|
+
created_at: timestamp
|
|
9603
|
+
};
|
|
9604
|
+
}
|
|
9605
|
+
function rowToHandoff(row) {
|
|
9606
|
+
return {
|
|
9607
|
+
...row,
|
|
9608
|
+
completed: row.completed ? JSON.parse(row.completed) : null,
|
|
9609
|
+
in_progress: row.in_progress ? JSON.parse(row.in_progress) : null,
|
|
9610
|
+
blockers: row.blockers ? JSON.parse(row.blockers) : null,
|
|
9611
|
+
next_steps: row.next_steps ? JSON.parse(row.next_steps) : null
|
|
9612
|
+
};
|
|
9613
|
+
}
|
|
9614
|
+
function listHandoffs(projectId, limit = 10, db) {
|
|
9615
|
+
const d = db || getDatabase();
|
|
9616
|
+
if (projectId) {
|
|
9617
|
+
return d.query("SELECT * FROM handoffs WHERE project_id = ? ORDER BY rowid DESC LIMIT ?").all(projectId, limit).map(rowToHandoff);
|
|
9618
|
+
}
|
|
9619
|
+
return d.query("SELECT * FROM handoffs ORDER BY rowid DESC LIMIT ?").all(limit).map(rowToHandoff);
|
|
9620
|
+
}
|
|
9621
|
+
function getLatestHandoff(agentId, projectId, db) {
|
|
9622
|
+
const d = db || getDatabase();
|
|
9623
|
+
let query = "SELECT * FROM handoffs WHERE 1=1";
|
|
9624
|
+
const params = [];
|
|
9625
|
+
if (agentId) {
|
|
9626
|
+
query += " AND agent_id = ?";
|
|
9627
|
+
params.push(agentId);
|
|
9628
|
+
}
|
|
9629
|
+
if (projectId) {
|
|
9630
|
+
query += " AND project_id = ?";
|
|
9631
|
+
params.push(projectId);
|
|
9632
|
+
}
|
|
9633
|
+
query += " ORDER BY rowid DESC LIMIT 1";
|
|
9634
|
+
const row = d.query(query).get(...params);
|
|
9635
|
+
return row ? rowToHandoff(row) : null;
|
|
9636
|
+
}
|
|
9637
|
+
var init_handoffs = __esm(() => {
|
|
9638
|
+
init_database();
|
|
9639
|
+
});
|
|
9640
|
+
|
|
9371
9641
|
// src/mcp/index.ts
|
|
9372
9642
|
var exports_mcp = {};
|
|
9373
9643
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -9502,6 +9772,7 @@ var init_mcp = __esm(() => {
|
|
|
9502
9772
|
init_sync();
|
|
9503
9773
|
init_config();
|
|
9504
9774
|
init_database();
|
|
9775
|
+
init_checklists();
|
|
9505
9776
|
init_types();
|
|
9506
9777
|
server = new McpServer({
|
|
9507
9778
|
name: "todos",
|
|
@@ -9700,6 +9971,14 @@ Comments (${task.comments.length}):`);
|
|
|
9700
9971
|
parts.push(`
|
|
9701
9972
|
Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
9702
9973
|
}
|
|
9974
|
+
if (task.checklist.length > 0) {
|
|
9975
|
+
const done = task.checklist.filter((i) => i.checked).length;
|
|
9976
|
+
parts.push(`
|
|
9977
|
+
Checklist (${done}/${task.checklist.length}):`);
|
|
9978
|
+
for (const item of task.checklist) {
|
|
9979
|
+
parts.push(` ${item.position + 1}. [${item.checked ? "x" : " "}] ${item.text} (${item.id.slice(0, 8)})`);
|
|
9980
|
+
}
|
|
9981
|
+
}
|
|
9703
9982
|
return { content: [{ type: "text", text: parts.join(`
|
|
9704
9983
|
`) }] };
|
|
9705
9984
|
} catch (e) {
|
|
@@ -9929,6 +10208,171 @@ ${text}` }] };
|
|
|
9929
10208
|
}
|
|
9930
10209
|
});
|
|
9931
10210
|
}
|
|
10211
|
+
if (shouldRegisterTool("add_project_source")) {
|
|
10212
|
+
server.tool("add_project_source", "Add a data source to a project (S3 bucket, Google Drive folder, local path, GitHub repo, Notion page, etc.). Sources are revealed to agents when they load the project.", {
|
|
10213
|
+
project_id: exports_external.string().describe("Project ID"),
|
|
10214
|
+
type: exports_external.string().describe("Source type: 's3', 'gdrive', 'local', 'github', 'notion', 'http', or any custom label"),
|
|
10215
|
+
name: exports_external.string().describe("Human-readable label for this source"),
|
|
10216
|
+
uri: exports_external.string().describe("The source URI (bucket path, folder URL, local path, repo URL, etc.)"),
|
|
10217
|
+
description: exports_external.string().optional().describe("What this source contains or how agents should use it"),
|
|
10218
|
+
metadata: exports_external.record(exports_external.unknown()).optional().describe("Extra config (e.g. region, access role, subfolder)")
|
|
10219
|
+
}, async (params) => {
|
|
10220
|
+
try {
|
|
10221
|
+
const resolvedProjectId = resolveId(params.project_id, "projects");
|
|
10222
|
+
const source = addProjectSource({ ...params, project_id: resolvedProjectId });
|
|
10223
|
+
return {
|
|
10224
|
+
content: [{
|
|
10225
|
+
type: "text",
|
|
10226
|
+
text: `Source added: ${source.id.slice(0, 8)} | [${source.type}] ${source.name} \u2192 ${source.uri}`
|
|
10227
|
+
}]
|
|
10228
|
+
};
|
|
10229
|
+
} catch (e) {
|
|
10230
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10231
|
+
}
|
|
10232
|
+
});
|
|
10233
|
+
}
|
|
10234
|
+
if (shouldRegisterTool("remove_project_source")) {
|
|
10235
|
+
server.tool("remove_project_source", "Remove a data source from a project by source ID.", {
|
|
10236
|
+
source_id: exports_external.string().describe("Source ID to remove")
|
|
10237
|
+
}, async ({ source_id }) => {
|
|
10238
|
+
try {
|
|
10239
|
+
const db = getDatabase();
|
|
10240
|
+
const row = db.query("SELECT * FROM project_sources WHERE id LIKE ?").get(`${source_id}%`);
|
|
10241
|
+
if (!row)
|
|
10242
|
+
return { content: [{ type: "text", text: `Source not found: ${source_id}` }], isError: true };
|
|
10243
|
+
removeProjectSource(row.id);
|
|
10244
|
+
return { content: [{ type: "text", text: `Source removed: ${row.name}` }] };
|
|
10245
|
+
} catch (e) {
|
|
10246
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10247
|
+
}
|
|
10248
|
+
});
|
|
10249
|
+
}
|
|
10250
|
+
if (shouldRegisterTool("list_project_sources")) {
|
|
10251
|
+
server.tool("list_project_sources", "List all data sources attached to a project.", {
|
|
10252
|
+
project_id: exports_external.string().describe("Project ID")
|
|
10253
|
+
}, async ({ project_id }) => {
|
|
10254
|
+
try {
|
|
10255
|
+
const resolvedId = resolveId(project_id, "projects");
|
|
10256
|
+
const sources = listProjectSources(resolvedId);
|
|
10257
|
+
if (sources.length === 0) {
|
|
10258
|
+
return { content: [{ type: "text", text: "No sources configured for this project." }] };
|
|
10259
|
+
}
|
|
10260
|
+
const lines = sources.map((s) => `${s.id.slice(0, 8)} | [${s.type}] ${s.name} \u2192 ${s.uri}${s.description ? `
|
|
10261
|
+
${s.description}` : ""}`);
|
|
10262
|
+
return { content: [{ type: "text", text: `${sources.length} source(s):
|
|
10263
|
+
${lines.join(`
|
|
10264
|
+
`)}` }] };
|
|
10265
|
+
} catch (e) {
|
|
10266
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10267
|
+
}
|
|
10268
|
+
});
|
|
10269
|
+
}
|
|
10270
|
+
if (shouldRegisterTool("add_checklist_item")) {
|
|
10271
|
+
server.tool("add_checklist_item", "Add a checklist item to a task. Items are numbered and individually checkable.", {
|
|
10272
|
+
task_id: exports_external.string().describe("Task ID"),
|
|
10273
|
+
text: exports_external.string().describe("Checklist item text"),
|
|
10274
|
+
position: exports_external.number().optional().describe("Position (0-based). Appended to end if omitted.")
|
|
10275
|
+
}, async ({ task_id, text, position }) => {
|
|
10276
|
+
try {
|
|
10277
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
10278
|
+
const item = addChecklistItem({ task_id: resolvedId, text, position });
|
|
10279
|
+
return {
|
|
10280
|
+
content: [{
|
|
10281
|
+
type: "text",
|
|
10282
|
+
text: `Item added: ${item.position + 1}. [ ] ${item.text} (${item.id.slice(0, 8)})`
|
|
10283
|
+
}]
|
|
10284
|
+
};
|
|
10285
|
+
} catch (e) {
|
|
10286
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10287
|
+
}
|
|
10288
|
+
});
|
|
10289
|
+
}
|
|
10290
|
+
if (shouldRegisterTool("check_checklist_item")) {
|
|
10291
|
+
server.tool("check_checklist_item", "Mark a checklist item as checked or unchecked.", {
|
|
10292
|
+
item_id: exports_external.string().describe("Checklist item ID or prefix"),
|
|
10293
|
+
checked: exports_external.boolean().describe("true to check, false to uncheck")
|
|
10294
|
+
}, async ({ item_id, checked }) => {
|
|
10295
|
+
try {
|
|
10296
|
+
const db = getDatabase();
|
|
10297
|
+
const row = db.query("SELECT id FROM task_checklists WHERE id LIKE ?").get(`${item_id}%`);
|
|
10298
|
+
if (!row)
|
|
10299
|
+
return { content: [{ type: "text", text: `Checklist item not found: ${item_id}` }], isError: true };
|
|
10300
|
+
const item = checkChecklistItem(row.id, checked);
|
|
10301
|
+
if (!item)
|
|
10302
|
+
return { content: [{ type: "text", text: "Update failed" }], isError: true };
|
|
10303
|
+
return {
|
|
10304
|
+
content: [{
|
|
10305
|
+
type: "text",
|
|
10306
|
+
text: `${item.position + 1}. [${item.checked ? "x" : " "}] ${item.text}`
|
|
10307
|
+
}]
|
|
10308
|
+
};
|
|
10309
|
+
} catch (e) {
|
|
10310
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10311
|
+
}
|
|
10312
|
+
});
|
|
10313
|
+
}
|
|
10314
|
+
if (shouldRegisterTool("update_checklist_item")) {
|
|
10315
|
+
server.tool("update_checklist_item", "Update the text of a checklist item.", {
|
|
10316
|
+
item_id: exports_external.string().describe("Checklist item ID or prefix"),
|
|
10317
|
+
text: exports_external.string().describe("New text")
|
|
10318
|
+
}, async ({ item_id, text }) => {
|
|
10319
|
+
try {
|
|
10320
|
+
const db = getDatabase();
|
|
10321
|
+
const row = db.query("SELECT id FROM task_checklists WHERE id LIKE ?").get(`${item_id}%`);
|
|
10322
|
+
if (!row)
|
|
10323
|
+
return { content: [{ type: "text", text: `Checklist item not found: ${item_id}` }], isError: true };
|
|
10324
|
+
const item = updateChecklistItemText(row.id, text);
|
|
10325
|
+
if (!item)
|
|
10326
|
+
return { content: [{ type: "text", text: "Update failed" }], isError: true };
|
|
10327
|
+
return {
|
|
10328
|
+
content: [{
|
|
10329
|
+
type: "text",
|
|
10330
|
+
text: `Updated: ${item.position + 1}. [${item.checked ? "x" : " "}] ${item.text}`
|
|
10331
|
+
}]
|
|
10332
|
+
};
|
|
10333
|
+
} catch (e) {
|
|
10334
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10335
|
+
}
|
|
10336
|
+
});
|
|
10337
|
+
}
|
|
10338
|
+
if (shouldRegisterTool("remove_checklist_item")) {
|
|
10339
|
+
server.tool("remove_checklist_item", "Remove a checklist item from a task.", {
|
|
10340
|
+
item_id: exports_external.string().describe("Checklist item ID or prefix")
|
|
10341
|
+
}, async ({ item_id }) => {
|
|
10342
|
+
try {
|
|
10343
|
+
const db = getDatabase();
|
|
10344
|
+
const row = db.query("SELECT id, text FROM task_checklists WHERE id LIKE ?").get(`${item_id}%`);
|
|
10345
|
+
if (!row)
|
|
10346
|
+
return { content: [{ type: "text", text: `Checklist item not found: ${item_id}` }], isError: true };
|
|
10347
|
+
removeChecklistItem(row.id);
|
|
10348
|
+
return { content: [{ type: "text", text: `Removed: ${row.text}` }] };
|
|
10349
|
+
} catch (e) {
|
|
10350
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10351
|
+
}
|
|
10352
|
+
});
|
|
10353
|
+
}
|
|
10354
|
+
if (shouldRegisterTool("get_checklist")) {
|
|
10355
|
+
server.tool("get_checklist", "Get all checklist items for a task with progress summary.", {
|
|
10356
|
+
task_id: exports_external.string().describe("Task ID")
|
|
10357
|
+
}, async ({ task_id }) => {
|
|
10358
|
+
try {
|
|
10359
|
+
const resolvedId = resolveId(task_id, "tasks");
|
|
10360
|
+
const items = getChecklist(resolvedId);
|
|
10361
|
+
if (items.length === 0) {
|
|
10362
|
+
return { content: [{ type: "text", text: "No checklist items." }] };
|
|
10363
|
+
}
|
|
10364
|
+
const done = items.filter((i) => i.checked).length;
|
|
10365
|
+
const lines = [`Checklist (${done}/${items.length} done):`];
|
|
10366
|
+
for (const item of items) {
|
|
10367
|
+
lines.push(` ${item.position + 1}. [${item.checked ? "x" : " "}] ${item.text} (${item.id.slice(0, 8)})`);
|
|
10368
|
+
}
|
|
10369
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
10370
|
+
`) }] };
|
|
10371
|
+
} catch (e) {
|
|
10372
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10373
|
+
}
|
|
10374
|
+
});
|
|
10375
|
+
}
|
|
9932
10376
|
if (shouldRegisterTool("create_plan")) {
|
|
9933
10377
|
server.tool("create_plan", "Create a plan to group related tasks.", {
|
|
9934
10378
|
name: exports_external.string(),
|
|
@@ -11245,6 +11689,19 @@ ${checks.map((c) => ` ${c.status === "ok" ? "\u2713" : "\u26A0"} ${c.name}: ${c
|
|
|
11245
11689
|
lines.push(`Other agents active: ${others.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ")}`);
|
|
11246
11690
|
}
|
|
11247
11691
|
}
|
|
11692
|
+
if (project_id) {
|
|
11693
|
+
const resolvedId = resolveId(project_id, "projects");
|
|
11694
|
+
const sources = listProjectSources(resolvedId);
|
|
11695
|
+
if (sources.length > 0) {
|
|
11696
|
+
lines.push("");
|
|
11697
|
+
lines.push(`## Data Sources`);
|
|
11698
|
+
for (const s of sources) {
|
|
11699
|
+
lines.push(`[${s.type}] ${s.name}: ${s.uri}${s.description ? ` \u2014 ${s.description}` : ""}`);
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
}
|
|
11703
|
+
lines.push(`
|
|
11704
|
+
as_of: ${new Date().toISOString()}`);
|
|
11248
11705
|
return { content: [{ type: "text", text: lines.join(`
|
|
11249
11706
|
`) }] };
|
|
11250
11707
|
} catch (e) {
|
|
@@ -11295,6 +11752,14 @@ Claimed: ${formatTask(result.claimed)}`);
|
|
|
11295
11752
|
"log_progress",
|
|
11296
11753
|
"create_project",
|
|
11297
11754
|
"list_projects",
|
|
11755
|
+
"add_project_source",
|
|
11756
|
+
"remove_project_source",
|
|
11757
|
+
"list_project_sources",
|
|
11758
|
+
"add_checklist_item",
|
|
11759
|
+
"check_checklist_item",
|
|
11760
|
+
"update_checklist_item",
|
|
11761
|
+
"remove_checklist_item",
|
|
11762
|
+
"get_checklist",
|
|
11298
11763
|
"create_plan",
|
|
11299
11764
|
"list_plans",
|
|
11300
11765
|
"get_plan",
|
|
@@ -11403,6 +11868,30 @@ Claimed: ${formatTask(result.claimed)}`);
|
|
|
11403
11868
|
Params: name(string, req), path(string, req \u2014 unique absolute path), description(string), task_list_id(string)
|
|
11404
11869
|
Example: {name: 'my-app', path: '/Users/dev/my-app'}`,
|
|
11405
11870
|
list_projects: "List all registered projects. No params.",
|
|
11871
|
+
add_checklist_item: `Add a checklist item (numbered sub-step) to a task.
|
|
11872
|
+
Params: task_id(string, req), text(string, req), position(number \u2014 0-based, appended to end if omitted)
|
|
11873
|
+
Example: {task_id: 'a1b2c3d4', text: 'Cancel Slack subscription'}`,
|
|
11874
|
+
check_checklist_item: `Mark a checklist item checked or unchecked.
|
|
11875
|
+
Params: item_id(string, req \u2014 item ID or prefix), checked(boolean, req)
|
|
11876
|
+
Example: {item_id: 'abc12345', checked: true}`,
|
|
11877
|
+
update_checklist_item: `Update the text of a checklist item.
|
|
11878
|
+
Params: item_id(string, req), text(string, req)
|
|
11879
|
+
Example: {item_id: 'abc12345', text: 'Cancel GitHub subscription'}`,
|
|
11880
|
+
remove_checklist_item: `Remove a checklist item permanently.
|
|
11881
|
+
Params: item_id(string, req)
|
|
11882
|
+
Example: {item_id: 'abc12345'}`,
|
|
11883
|
+
get_checklist: `Get all checklist items for a task with progress (done/total).
|
|
11884
|
+
Params: task_id(string, req)
|
|
11885
|
+
Example: {task_id: 'a1b2c3d4'}`,
|
|
11886
|
+
add_project_source: `Add a data source to a project (S3, GDrive, local path, GitHub, Notion, HTTP, etc.).
|
|
11887
|
+
Params: project_id(string, req), type(string, req \u2014 e.g. 's3','gdrive','local','github','notion','http'), name(string, req), uri(string, req), description(string), metadata(object)
|
|
11888
|
+
Example: {project_id: 'a1b2c3d4', type: 's3', name: 'Assets bucket', uri: 's3://my-bucket/assets/', description: 'Project media files'}`,
|
|
11889
|
+
remove_project_source: `Remove a data source from a project.
|
|
11890
|
+
Params: source_id(string, req \u2014 source ID or prefix)
|
|
11891
|
+
Example: {source_id: 'abc12345'}`,
|
|
11892
|
+
list_project_sources: `List all data sources for a project.
|
|
11893
|
+
Params: project_id(string, req)
|
|
11894
|
+
Example: {project_id: 'a1b2c3d4'}`,
|
|
11406
11895
|
create_plan: `Create a plan to group related tasks.
|
|
11407
11896
|
Params: name(string, req), project_id(string), description(string), status(active|completed|archived, default:active), task_list_id(string), agent_id(string)
|
|
11408
11897
|
Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
|
|
@@ -11563,6 +12052,62 @@ Claimed: ${formatTask(result.claimed)}`);
|
|
|
11563
12052
|
const agents = listAgents();
|
|
11564
12053
|
return { contents: [{ uri: "todos://agents", text: JSON.stringify(agents, null, 2), mimeType: "application/json" }] };
|
|
11565
12054
|
});
|
|
12055
|
+
if (shouldRegisterTool("create_handoff")) {
|
|
12056
|
+
server.tool("create_handoff", "Create a session handoff note for agent coordination.", {
|
|
12057
|
+
agent_id: exports_external.string().optional().describe("Agent creating the handoff"),
|
|
12058
|
+
project_id: exports_external.string().optional().describe("Project ID"),
|
|
12059
|
+
summary: exports_external.string().describe("What was accomplished this session"),
|
|
12060
|
+
completed: exports_external.array(exports_external.string()).optional().describe("Items completed"),
|
|
12061
|
+
in_progress: exports_external.array(exports_external.string()).optional().describe("Items still in progress"),
|
|
12062
|
+
blockers: exports_external.array(exports_external.string()).optional().describe("Blocking issues"),
|
|
12063
|
+
next_steps: exports_external.array(exports_external.string()).optional().describe("Recommended next actions")
|
|
12064
|
+
}, async ({ agent_id, project_id, summary, completed, in_progress, blockers, next_steps }) => {
|
|
12065
|
+
try {
|
|
12066
|
+
const { createHandoff: createHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
12067
|
+
const handoff = createHandoff2({
|
|
12068
|
+
agent_id,
|
|
12069
|
+
project_id: project_id ? resolveId(project_id, "projects") : undefined,
|
|
12070
|
+
summary,
|
|
12071
|
+
completed,
|
|
12072
|
+
in_progress,
|
|
12073
|
+
blockers,
|
|
12074
|
+
next_steps
|
|
12075
|
+
});
|
|
12076
|
+
return { content: [{ type: "text", text: `Handoff created: ${handoff.id.slice(0, 8)} by ${handoff.agent_id || "unknown"}` }] };
|
|
12077
|
+
} catch (e) {
|
|
12078
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
12079
|
+
}
|
|
12080
|
+
});
|
|
12081
|
+
}
|
|
12082
|
+
if (shouldRegisterTool("get_latest_handoff")) {
|
|
12083
|
+
server.tool("get_latest_handoff", "Get the most recent handoff for an agent or project.", {
|
|
12084
|
+
agent_id: exports_external.string().optional().describe("Filter by agent"),
|
|
12085
|
+
project_id: exports_external.string().optional().describe("Filter by project")
|
|
12086
|
+
}, async ({ agent_id, project_id }) => {
|
|
12087
|
+
try {
|
|
12088
|
+
const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
12089
|
+
const handoff = getLatestHandoff2(agent_id, project_id ? resolveId(project_id, "projects") : undefined);
|
|
12090
|
+
if (!handoff)
|
|
12091
|
+
return { content: [{ type: "text", text: "No handoffs found." }] };
|
|
12092
|
+
const lines = [
|
|
12093
|
+
`${handoff.created_at.slice(0, 16)} ${handoff.agent_id || "unknown"}`,
|
|
12094
|
+
handoff.summary
|
|
12095
|
+
];
|
|
12096
|
+
if (handoff.completed?.length)
|
|
12097
|
+
lines.push(`Done: ${handoff.completed.join(", ")}`);
|
|
12098
|
+
if (handoff.in_progress?.length)
|
|
12099
|
+
lines.push(`In progress: ${handoff.in_progress.join(", ")}`);
|
|
12100
|
+
if (handoff.blockers?.length)
|
|
12101
|
+
lines.push(`Blocked: ${handoff.blockers.join(", ")}`);
|
|
12102
|
+
if (handoff.next_steps?.length)
|
|
12103
|
+
lines.push(`Next: ${handoff.next_steps.join(", ")}`);
|
|
12104
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
12105
|
+
`) }] };
|
|
12106
|
+
} catch (e) {
|
|
12107
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
12108
|
+
}
|
|
12109
|
+
});
|
|
12110
|
+
}
|
|
11566
12111
|
server.resource("task-lists", "todos://task-lists", { description: "All task lists", mimeType: "application/json" }, async () => {
|
|
11567
12112
|
const lists = listTaskLists();
|
|
11568
12113
|
return { contents: [{ uri: "todos://task-lists", text: JSON.stringify(lists, null, 2), mimeType: "application/json" }] };
|
|
@@ -15749,6 +16294,480 @@ program2.command("yesterday").description("Show task activity from yesterday").o
|
|
|
15749
16294
|
if (completed.length === 0 && started.length === 0)
|
|
15750
16295
|
console.log(chalk.dim(" No activity yesterday."));
|
|
15751
16296
|
});
|
|
16297
|
+
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) => {
|
|
16298
|
+
const globalOpts = program2.opts();
|
|
16299
|
+
const db = getDatabase();
|
|
16300
|
+
const { listTasks: listTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16301
|
+
const projectId = autoProject(globalOpts) || undefined;
|
|
16302
|
+
const filter = { assigned_to: agent };
|
|
16303
|
+
if (projectId)
|
|
16304
|
+
filter.project_id = projectId;
|
|
16305
|
+
const tasks = listTasks2(filter, db);
|
|
16306
|
+
const filterByAgent = { agent_id: agent };
|
|
16307
|
+
if (projectId)
|
|
16308
|
+
filterByAgent.project_id = projectId;
|
|
16309
|
+
const agentTasks = listTasks2(filterByAgent, db);
|
|
16310
|
+
const seen = new Set(tasks.map((t) => t.id));
|
|
16311
|
+
for (const t of agentTasks) {
|
|
16312
|
+
if (!seen.has(t.id)) {
|
|
16313
|
+
tasks.push(t);
|
|
16314
|
+
seen.add(t.id);
|
|
16315
|
+
}
|
|
16316
|
+
}
|
|
16317
|
+
if (opts.json || globalOpts.json) {
|
|
16318
|
+
console.log(JSON.stringify(tasks));
|
|
16319
|
+
return;
|
|
16320
|
+
}
|
|
16321
|
+
const groups = {};
|
|
16322
|
+
for (const t of tasks) {
|
|
16323
|
+
const s = t.status || "unknown";
|
|
16324
|
+
if (!groups[s])
|
|
16325
|
+
groups[s] = [];
|
|
16326
|
+
groups[s].push(t);
|
|
16327
|
+
}
|
|
16328
|
+
const statusOrder = ["in_progress", "pending", "blocked", "completed", "failed", "cancelled"];
|
|
16329
|
+
const statusIcons2 = { in_progress: "\u25B6", pending: "\u25CB", blocked: "\u2298", completed: "\u2713", failed: "\u2717", cancelled: "\u2014" };
|
|
16330
|
+
const statusColors5 = { in_progress: chalk.blue, pending: chalk.white, blocked: chalk.red, completed: chalk.green, failed: chalk.red, cancelled: chalk.dim };
|
|
16331
|
+
console.log(chalk.bold(`Tasks for ${agent} (${tasks.length} total):
|
|
16332
|
+
`));
|
|
16333
|
+
for (const status of statusOrder) {
|
|
16334
|
+
const group = groups[status];
|
|
16335
|
+
if (!group || group.length === 0)
|
|
16336
|
+
continue;
|
|
16337
|
+
const color = statusColors5[status] || chalk.white;
|
|
16338
|
+
const icon = statusIcons2[status] || "?";
|
|
16339
|
+
console.log(color(` ${icon} ${status.replace("_", " ")} (${group.length}):`));
|
|
16340
|
+
for (const t of group) {
|
|
16341
|
+
const priority = t.priority === "critical" || t.priority === "high" ? chalk.red(` [${t.priority}]`) : "";
|
|
16342
|
+
console.log(` ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${priority}`);
|
|
16343
|
+
}
|
|
16344
|
+
}
|
|
16345
|
+
if (tasks.length === 0)
|
|
16346
|
+
console.log(chalk.dim(` No tasks assigned to ${agent}.`));
|
|
16347
|
+
});
|
|
16348
|
+
program2.command("blocked").description("Show tasks blocked by incomplete dependencies").option("--json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
16349
|
+
const globalOpts = program2.opts();
|
|
16350
|
+
const db = getDatabase();
|
|
16351
|
+
const { listTasks: listTasks2, getBlockingDeps: getBlockingDeps2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16352
|
+
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
16353
|
+
const filter = { status: "pending" };
|
|
16354
|
+
if (projectId)
|
|
16355
|
+
filter.project_id = projectId;
|
|
16356
|
+
const allPending = listTasks2(filter, db);
|
|
16357
|
+
const blockedTasks = [];
|
|
16358
|
+
for (const t of allPending) {
|
|
16359
|
+
const blockers = getBlockingDeps2(t.id, db);
|
|
16360
|
+
if (blockers.length > 0)
|
|
16361
|
+
blockedTasks.push({ task: t, blockers });
|
|
16362
|
+
}
|
|
16363
|
+
if (opts.json || globalOpts.json) {
|
|
16364
|
+
console.log(JSON.stringify(blockedTasks.map((b) => ({ ...b.task, blocked_by: b.blockers.map((bl) => ({ id: bl.id, short_id: bl.short_id, title: bl.title, status: bl.status })) }))));
|
|
16365
|
+
return;
|
|
16366
|
+
}
|
|
16367
|
+
if (blockedTasks.length === 0) {
|
|
16368
|
+
console.log(chalk.green(" No blocked tasks!"));
|
|
16369
|
+
return;
|
|
16370
|
+
}
|
|
16371
|
+
console.log(chalk.bold(`Blocked (${blockedTasks.length}):
|
|
16372
|
+
`));
|
|
16373
|
+
for (const { task, blockers } of blockedTasks) {
|
|
16374
|
+
console.log(` ${chalk.cyan(task.short_id || task.id.slice(0, 8))} ${task.title}`);
|
|
16375
|
+
for (const bl of blockers) {
|
|
16376
|
+
console.log(` ${chalk.red("\u2298")} ${chalk.dim(bl.short_id || bl.id.slice(0, 8))} ${chalk.dim(bl.title)} ${chalk.yellow(`[${bl.status}]`)}`);
|
|
16377
|
+
}
|
|
16378
|
+
}
|
|
16379
|
+
});
|
|
16380
|
+
program2.command("overdue").description("Show tasks past their due date").option("--json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
16381
|
+
const globalOpts = program2.opts();
|
|
16382
|
+
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
16383
|
+
const { getOverdueTasks: getOverdueTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16384
|
+
const tasks = getOverdueTasks2(projectId);
|
|
16385
|
+
if (opts.json || globalOpts.json) {
|
|
16386
|
+
console.log(JSON.stringify(tasks));
|
|
16387
|
+
return;
|
|
16388
|
+
}
|
|
16389
|
+
if (tasks.length === 0) {
|
|
16390
|
+
console.log(chalk.green(" No overdue tasks!"));
|
|
16391
|
+
return;
|
|
16392
|
+
}
|
|
16393
|
+
console.log(chalk.bold.red(`Overdue (${tasks.length}):
|
|
16394
|
+
`));
|
|
16395
|
+
for (const t of tasks) {
|
|
16396
|
+
const dueDate = t.due_at.slice(0, 10);
|
|
16397
|
+
const daysOverdue = Math.floor((Date.now() - new Date(t.due_at).getTime()) / 86400000);
|
|
16398
|
+
const urgency = daysOverdue > 7 ? chalk.bgRed.white(` ${daysOverdue}d `) : chalk.red(`${daysOverdue}d`);
|
|
16399
|
+
console.log(` ${urgency} ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk.dim(` \u2014 ${t.assigned_to}`) : ""} ${chalk.dim(`(due ${dueDate})`)}`);
|
|
16400
|
+
}
|
|
16401
|
+
});
|
|
16402
|
+
program2.command("week").description("Show task activity from the past 7 days").option("--json", "Output as JSON").action(async (opts) => {
|
|
16403
|
+
const globalOpts = program2.opts();
|
|
16404
|
+
const db = getDatabase();
|
|
16405
|
+
const { getTasksChangedSince: getTasksChangedSince2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16406
|
+
const now2 = new Date;
|
|
16407
|
+
const start = new Date(now2);
|
|
16408
|
+
start.setDate(start.getDate() - 7);
|
|
16409
|
+
start.setHours(0, 0, 0, 0);
|
|
16410
|
+
const tasks = getTasksChangedSince2(start.toISOString(), undefined, db);
|
|
16411
|
+
const days = {};
|
|
16412
|
+
for (let i = 0;i < 7; i++) {
|
|
16413
|
+
const d = new Date(now2);
|
|
16414
|
+
d.setDate(d.getDate() - i);
|
|
16415
|
+
days[d.toISOString().slice(0, 10)] = { completed: [], started: [], other: [] };
|
|
16416
|
+
}
|
|
16417
|
+
for (const t of tasks) {
|
|
16418
|
+
const day = (t.updated_at || t.created_at).slice(0, 10);
|
|
16419
|
+
if (!days[day])
|
|
16420
|
+
days[day] = { completed: [], started: [], other: [] };
|
|
16421
|
+
if (t.status === "completed")
|
|
16422
|
+
days[day].completed.push(t);
|
|
16423
|
+
else if (t.status === "in_progress")
|
|
16424
|
+
days[day].started.push(t);
|
|
16425
|
+
else
|
|
16426
|
+
days[day].other.push(t);
|
|
16427
|
+
}
|
|
16428
|
+
if (opts.json || globalOpts.json) {
|
|
16429
|
+
console.log(JSON.stringify({ from: start.toISOString().slice(0, 10), to: now2.toISOString().slice(0, 10), days }));
|
|
16430
|
+
return;
|
|
16431
|
+
}
|
|
16432
|
+
const totalCompleted = tasks.filter((t) => t.status === "completed").length;
|
|
16433
|
+
const totalStarted = tasks.filter((t) => t.status === "in_progress").length;
|
|
16434
|
+
console.log(chalk.bold(`Week \u2014 ${start.toISOString().slice(0, 10)} to ${now2.toISOString().slice(0, 10)}`));
|
|
16435
|
+
console.log(chalk.dim(` ${totalCompleted} completed, ${totalStarted} in progress, ${tasks.length} total changes
|
|
16436
|
+
`));
|
|
16437
|
+
const sortedDays = Object.keys(days).sort().reverse();
|
|
16438
|
+
for (const day of sortedDays) {
|
|
16439
|
+
const dayData = days[day];
|
|
16440
|
+
if (!dayData)
|
|
16441
|
+
continue;
|
|
16442
|
+
const { completed, started } = dayData;
|
|
16443
|
+
if (completed.length === 0 && started.length === 0)
|
|
16444
|
+
continue;
|
|
16445
|
+
const weekday = new Date(day + "T12:00:00").toLocaleDateString("en-US", { weekday: "short" });
|
|
16446
|
+
console.log(chalk.bold(` ${weekday} ${day}`));
|
|
16447
|
+
for (const t of completed)
|
|
16448
|
+
console.log(` ${chalk.green("\u2713")} ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk.dim(` \u2014 ${t.assigned_to}`) : ""}`);
|
|
16449
|
+
for (const t of started)
|
|
16450
|
+
console.log(` ${chalk.blue("\u25B6")} ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${t.assigned_to ? chalk.dim(` \u2014 ${t.assigned_to}`) : ""}`);
|
|
16451
|
+
}
|
|
16452
|
+
if (tasks.length === 0)
|
|
16453
|
+
console.log(chalk.dim(" No activity this week."));
|
|
16454
|
+
});
|
|
16455
|
+
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) => {
|
|
16456
|
+
const globalOpts = program2.opts();
|
|
16457
|
+
const db = getDatabase();
|
|
16458
|
+
const { getRecentActivity: getRecentActivity2 } = (init_audit(), __toCommonJS(exports_audit));
|
|
16459
|
+
const numDays = parseInt(opts.days, 10);
|
|
16460
|
+
const entries = getRecentActivity2(5000, db);
|
|
16461
|
+
const now2 = new Date;
|
|
16462
|
+
const dayStats = [];
|
|
16463
|
+
for (let i = numDays - 1;i >= 0; i--) {
|
|
16464
|
+
const d = new Date(now2);
|
|
16465
|
+
d.setDate(d.getDate() - i);
|
|
16466
|
+
const dateStr = d.toISOString().slice(0, 10);
|
|
16467
|
+
const dayEntries = entries.filter((e) => e.created_at.slice(0, 10) === dateStr);
|
|
16468
|
+
dayStats.push({
|
|
16469
|
+
date: dateStr,
|
|
16470
|
+
completed: dayEntries.filter((e) => e.action === "complete").length,
|
|
16471
|
+
created: dayEntries.filter((e) => e.action === "create").length,
|
|
16472
|
+
failed: dayEntries.filter((e) => e.action === "fail").length
|
|
16473
|
+
});
|
|
16474
|
+
}
|
|
16475
|
+
if (opts.json || globalOpts.json) {
|
|
16476
|
+
console.log(JSON.stringify(dayStats));
|
|
16477
|
+
return;
|
|
16478
|
+
}
|
|
16479
|
+
const maxVal = Math.max(...dayStats.map((d) => Math.max(d.completed, d.created)), 1);
|
|
16480
|
+
const barWidth = 30;
|
|
16481
|
+
console.log(chalk.bold("Burndown (last " + numDays + ` days):
|
|
16482
|
+
`));
|
|
16483
|
+
console.log(chalk.dim(" Date Done New Failed Chart"));
|
|
16484
|
+
for (const day of dayStats) {
|
|
16485
|
+
const weekday = new Date(day.date + "T12:00:00").toLocaleDateString("en-US", { weekday: "short" });
|
|
16486
|
+
const completedBar = chalk.green("\u2588".repeat(Math.round(day.completed / maxVal * barWidth)));
|
|
16487
|
+
const createdBar = chalk.blue("\u2591".repeat(Math.round(day.created / maxVal * barWidth)));
|
|
16488
|
+
const failed = day.failed > 0 ? chalk.red(String(day.failed).padStart(4)) : chalk.dim(" 0");
|
|
16489
|
+
console.log(` ${weekday} ${day.date.slice(5)} ${chalk.green(String(day.completed).padStart(4))} ${chalk.blue(String(day.created).padStart(4))} ${failed} ${completedBar}${createdBar}`);
|
|
16490
|
+
}
|
|
16491
|
+
const totalCompleted = dayStats.reduce((s, d) => s + d.completed, 0);
|
|
16492
|
+
const totalCreated = dayStats.reduce((s, d) => s + d.created, 0);
|
|
16493
|
+
const velocity = (totalCompleted / numDays).toFixed(1);
|
|
16494
|
+
console.log(chalk.dim(`
|
|
16495
|
+
Velocity: ${velocity}/day \xB7 ${totalCompleted} done \xB7 ${totalCreated} created`));
|
|
16496
|
+
});
|
|
16497
|
+
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) => {
|
|
16498
|
+
const globalOpts = program2.opts();
|
|
16499
|
+
const db = getDatabase();
|
|
16500
|
+
const { getRecentActivity: getRecentActivity2 } = (init_audit(), __toCommonJS(exports_audit));
|
|
16501
|
+
const entries = getRecentActivity2(parseInt(opts.limit, 10), db);
|
|
16502
|
+
if (opts.json || globalOpts.json) {
|
|
16503
|
+
console.log(JSON.stringify(entries));
|
|
16504
|
+
return;
|
|
16505
|
+
}
|
|
16506
|
+
if (entries.length === 0) {
|
|
16507
|
+
console.log(chalk.dim(" No activity yet."));
|
|
16508
|
+
return;
|
|
16509
|
+
}
|
|
16510
|
+
const actionIcons = {
|
|
16511
|
+
create: chalk.green("+"),
|
|
16512
|
+
start: chalk.blue("\u25B6"),
|
|
16513
|
+
complete: chalk.green("\u2713"),
|
|
16514
|
+
fail: chalk.red("\u2717"),
|
|
16515
|
+
update: chalk.yellow("~"),
|
|
16516
|
+
approve: chalk.green("\u2605"),
|
|
16517
|
+
lock: chalk.dim("\uD83D\uDD12"),
|
|
16518
|
+
unlock: chalk.dim("\uD83D\uDD13")
|
|
16519
|
+
};
|
|
16520
|
+
let lastDate = "";
|
|
16521
|
+
for (const e of entries) {
|
|
16522
|
+
const date = e.created_at.slice(0, 10);
|
|
16523
|
+
const time = e.created_at.slice(11, 16);
|
|
16524
|
+
if (date !== lastDate) {
|
|
16525
|
+
console.log(chalk.bold(`
|
|
16526
|
+
${date}`));
|
|
16527
|
+
lastDate = date;
|
|
16528
|
+
}
|
|
16529
|
+
const icon = actionIcons[e.action] || chalk.dim("\xB7");
|
|
16530
|
+
const agent = e.agent_id ? chalk.dim(` (${e.agent_id})`) : "";
|
|
16531
|
+
const taskRef = chalk.cyan(e.task_id.slice(0, 8));
|
|
16532
|
+
let detail = "";
|
|
16533
|
+
if (e.field && e.old_value && e.new_value) {
|
|
16534
|
+
detail = chalk.dim(` ${e.field}: ${e.old_value} \u2192 ${e.new_value}`);
|
|
16535
|
+
} else if (e.field && e.new_value) {
|
|
16536
|
+
detail = chalk.dim(` ${e.field}: ${e.new_value}`);
|
|
16537
|
+
}
|
|
16538
|
+
console.log(` ${chalk.dim(time)} ${icon} ${e.action.padEnd(8)} ${taskRef}${detail}${agent}`);
|
|
16539
|
+
}
|
|
16540
|
+
});
|
|
16541
|
+
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) => {
|
|
16542
|
+
const globalOpts = program2.opts();
|
|
16543
|
+
const db = getDatabase();
|
|
16544
|
+
const { listTasks: listTasks2, getBlockingDeps: getBlockingDeps2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16545
|
+
const { isLockExpired: isLockExpired2 } = (init_database(), __toCommonJS(exports_database));
|
|
16546
|
+
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
16547
|
+
const filter = { status: "pending" };
|
|
16548
|
+
if (projectId)
|
|
16549
|
+
filter.project_id = projectId;
|
|
16550
|
+
const pending = listTasks2(filter, db);
|
|
16551
|
+
const ready = pending.filter((t) => {
|
|
16552
|
+
if (t.locked_by && !isLockExpired2(t.locked_at))
|
|
16553
|
+
return false;
|
|
16554
|
+
const blockers = getBlockingDeps2(t.id, db);
|
|
16555
|
+
return blockers.length === 0;
|
|
16556
|
+
});
|
|
16557
|
+
const limited = ready.slice(0, parseInt(opts.limit, 10));
|
|
16558
|
+
if (opts.json || globalOpts.json) {
|
|
16559
|
+
console.log(JSON.stringify(limited));
|
|
16560
|
+
return;
|
|
16561
|
+
}
|
|
16562
|
+
if (limited.length === 0) {
|
|
16563
|
+
console.log(chalk.dim(" No tasks ready to claim."));
|
|
16564
|
+
return;
|
|
16565
|
+
}
|
|
16566
|
+
console.log(chalk.bold(`Ready to claim (${ready.length}${ready.length > limited.length ? `, showing ${limited.length}` : ""}):
|
|
16567
|
+
`));
|
|
16568
|
+
for (const t of limited) {
|
|
16569
|
+
const pri = t.priority === "critical" ? chalk.bgRed.white(" CRIT ") : t.priority === "high" ? chalk.red("[high]") : t.priority === "medium" ? chalk.yellow("[med]") : "";
|
|
16570
|
+
const due = t.due_at ? chalk.dim(` due ${t.due_at.slice(0, 10)}`) : "";
|
|
16571
|
+
console.log(` ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${t.title} ${pri}${due}`);
|
|
16572
|
+
}
|
|
16573
|
+
});
|
|
16574
|
+
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) => {
|
|
16575
|
+
const globalOpts = program2.opts();
|
|
16576
|
+
const db = getDatabase();
|
|
16577
|
+
const { listTasks: listTasks2, getBlockingDeps: getBlockingDeps2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16578
|
+
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
16579
|
+
const baseFilter = {};
|
|
16580
|
+
if (projectId)
|
|
16581
|
+
baseFilter.project_id = projectId;
|
|
16582
|
+
const inProgress = listTasks2({ ...baseFilter, status: "in_progress" }, db);
|
|
16583
|
+
const pending = listTasks2({ ...baseFilter, status: "pending" }, db);
|
|
16584
|
+
const nowStr = new Date().toISOString();
|
|
16585
|
+
const blocked = [];
|
|
16586
|
+
for (const t of pending) {
|
|
16587
|
+
const blockers = getBlockingDeps2(t.id, db);
|
|
16588
|
+
if (blockers.length > 0)
|
|
16589
|
+
blocked.push({ task: t, blockers });
|
|
16590
|
+
}
|
|
16591
|
+
const overdue = [...inProgress, ...pending].filter((t) => t.due_at && t.due_at < nowStr);
|
|
16592
|
+
const blockedIds = new Set(blocked.map((b) => b.task.id));
|
|
16593
|
+
const nextUp = pending.filter((t) => !blockedIds.has(t.id)).slice(0, 5);
|
|
16594
|
+
if (opts.json || globalOpts.json) {
|
|
16595
|
+
console.log(JSON.stringify({ in_progress: inProgress, next_up: nextUp, blocked, overdue }));
|
|
16596
|
+
return;
|
|
16597
|
+
}
|
|
16598
|
+
console.log(chalk.bold(`Sprint Dashboard
|
|
16599
|
+
`));
|
|
16600
|
+
console.log(chalk.blue(` \u25B6 In Progress (${inProgress.length}):`));
|
|
16601
|
+
if (inProgress.length === 0)
|
|
16602
|
+
console.log(chalk.dim(" (none)"));
|
|
16603
|
+
for (const t of inProgress) {
|
|
16604
|
+
const agent = t.assigned_to ? chalk.dim(` \u2014 ${t.assigned_to}`) : "";
|
|
16605
|
+
console.log(` ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}${agent}`);
|
|
16606
|
+
}
|
|
16607
|
+
console.log(chalk.white(`
|
|
16608
|
+
\u25CB Next Up (${nextUp.length}):`));
|
|
16609
|
+
if (nextUp.length === 0)
|
|
16610
|
+
console.log(chalk.dim(" (none)"));
|
|
16611
|
+
for (const t of nextUp) {
|
|
16612
|
+
const pri = t.priority === "critical" ? chalk.bgRed.white(" CRIT ") : t.priority === "high" ? chalk.red("[high]") : "";
|
|
16613
|
+
console.log(` ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${t.title} ${pri}`);
|
|
16614
|
+
}
|
|
16615
|
+
if (blocked.length > 0) {
|
|
16616
|
+
console.log(chalk.red(`
|
|
16617
|
+
\u2298 Blocked (${blocked.length}):`));
|
|
16618
|
+
for (const { task, blockers } of blocked) {
|
|
16619
|
+
console.log(` ${chalk.cyan(task.short_id || task.id.slice(0, 8))} ${task.title}`);
|
|
16620
|
+
for (const bl of blockers)
|
|
16621
|
+
console.log(` ${chalk.dim("\u2190 " + (bl.short_id || bl.id.slice(0, 8)) + " " + bl.title)} ${chalk.yellow(`[${bl.status}]`)}`);
|
|
16622
|
+
}
|
|
16623
|
+
}
|
|
16624
|
+
if (overdue.length > 0) {
|
|
16625
|
+
console.log(chalk.red(`
|
|
16626
|
+
\u26A0 Overdue (${overdue.length}):`));
|
|
16627
|
+
for (const t of overdue) {
|
|
16628
|
+
const daysOver = Math.floor((Date.now() - new Date(t.due_at).getTime()) / 86400000);
|
|
16629
|
+
console.log(` ${chalk.red(`${daysOver}d`)} ${chalk.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}`);
|
|
16630
|
+
}
|
|
16631
|
+
}
|
|
16632
|
+
console.log(chalk.dim(`
|
|
16633
|
+
${inProgress.length} active \xB7 ${pending.length} pending \xB7 ${blocked.length} blocked \xB7 ${overdue.length} overdue`));
|
|
16634
|
+
});
|
|
16635
|
+
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) => {
|
|
16636
|
+
const globalOpts = program2.opts();
|
|
16637
|
+
const db = getDatabase();
|
|
16638
|
+
const { createHandoff: createHandoff2, listHandoffs: listHandoffs2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
16639
|
+
const projectId = autoProject(globalOpts) || undefined;
|
|
16640
|
+
if (opts.create || opts.summary) {
|
|
16641
|
+
if (!opts.summary) {
|
|
16642
|
+
console.error(chalk.red(" --summary is required for creating a handoff"));
|
|
16643
|
+
process.exit(1);
|
|
16644
|
+
}
|
|
16645
|
+
const handoff = createHandoff2({
|
|
16646
|
+
agent_id: opts.agent || globalOpts.agent || undefined,
|
|
16647
|
+
project_id: projectId,
|
|
16648
|
+
summary: opts.summary,
|
|
16649
|
+
completed: opts.completed ? opts.completed.split(",").map((s) => s.trim()) : undefined,
|
|
16650
|
+
inProgress: opts.inProgress ? opts.inProgress.split(",").map((s) => s.trim()) : undefined,
|
|
16651
|
+
blockers: opts.blockers ? opts.blockers.split(",").map((s) => s.trim()) : undefined,
|
|
16652
|
+
next_steps: opts.next ? opts.next.split(",").map((s) => s.trim()) : undefined
|
|
16653
|
+
}, db);
|
|
16654
|
+
if (opts.json || globalOpts.json) {
|
|
16655
|
+
console.log(JSON.stringify(handoff));
|
|
16656
|
+
return;
|
|
16657
|
+
}
|
|
16658
|
+
console.log(chalk.green(` \u2713 Handoff created by ${handoff.agent_id || "unknown"}`));
|
|
16659
|
+
return;
|
|
16660
|
+
}
|
|
16661
|
+
const handoffs = listHandoffs2(projectId, parseInt(opts.limit, 10), db);
|
|
16662
|
+
if (opts.json || globalOpts.json) {
|
|
16663
|
+
console.log(JSON.stringify(handoffs));
|
|
16664
|
+
return;
|
|
16665
|
+
}
|
|
16666
|
+
if (handoffs.length === 0) {
|
|
16667
|
+
console.log(chalk.dim(" No handoffs yet."));
|
|
16668
|
+
return;
|
|
16669
|
+
}
|
|
16670
|
+
for (const h of handoffs) {
|
|
16671
|
+
const time = h.created_at.slice(0, 16).replace("T", " ");
|
|
16672
|
+
console.log(chalk.bold(`
|
|
16673
|
+
${time} ${h.agent_id || "unknown"}`));
|
|
16674
|
+
console.log(` ${h.summary}`);
|
|
16675
|
+
if (h.completed?.length) {
|
|
16676
|
+
console.log(chalk.green(` \u2713 Completed:`));
|
|
16677
|
+
for (const c of h.completed)
|
|
16678
|
+
console.log(` - ${c}`);
|
|
16679
|
+
}
|
|
16680
|
+
if (h.in_progress?.length) {
|
|
16681
|
+
console.log(chalk.blue(` \u25B6 In progress:`));
|
|
16682
|
+
for (const c of h.in_progress)
|
|
16683
|
+
console.log(` - ${c}`);
|
|
16684
|
+
}
|
|
16685
|
+
if (h.blockers?.length) {
|
|
16686
|
+
console.log(chalk.red(` \u2298 Blockers:`));
|
|
16687
|
+
for (const c of h.blockers)
|
|
16688
|
+
console.log(` - ${c}`);
|
|
16689
|
+
}
|
|
16690
|
+
if (h.next_steps?.length) {
|
|
16691
|
+
console.log(chalk.cyan(` \u2192 Next steps:`));
|
|
16692
|
+
for (const c of h.next_steps)
|
|
16693
|
+
console.log(` - ${c}`);
|
|
16694
|
+
}
|
|
16695
|
+
}
|
|
16696
|
+
});
|
|
16697
|
+
program2.command("priorities").description("Show task counts grouped by priority").option("--json", "Output as JSON").option("--project <id>", "Filter to project").action(async (opts) => {
|
|
16698
|
+
const globalOpts = program2.opts();
|
|
16699
|
+
const db = getDatabase();
|
|
16700
|
+
const { countTasks: countTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16701
|
+
const projectId = autoProject(globalOpts) || opts.project || undefined;
|
|
16702
|
+
const base = projectId ? { project_id: projectId } : {};
|
|
16703
|
+
const priorities = ["critical", "high", "medium", "low", "none"];
|
|
16704
|
+
const counts = {};
|
|
16705
|
+
for (const p of priorities) {
|
|
16706
|
+
counts[p] = {
|
|
16707
|
+
total: countTasks2({ ...base, priority: p }, db),
|
|
16708
|
+
pending: countTasks2({ ...base, priority: p, status: "pending" }, db),
|
|
16709
|
+
in_progress: countTasks2({ ...base, priority: p, status: "in_progress" }, db),
|
|
16710
|
+
completed: countTasks2({ ...base, priority: p, status: "completed" }, db)
|
|
16711
|
+
};
|
|
16712
|
+
}
|
|
16713
|
+
if (opts.json || globalOpts.json) {
|
|
16714
|
+
console.log(JSON.stringify(counts));
|
|
16715
|
+
return;
|
|
16716
|
+
}
|
|
16717
|
+
console.log(chalk.bold(`Priority Breakdown:
|
|
16718
|
+
`));
|
|
16719
|
+
const priColors = { critical: chalk.bgRed.white, high: chalk.red, medium: chalk.yellow, low: chalk.blue, none: chalk.dim };
|
|
16720
|
+
for (const p of priorities) {
|
|
16721
|
+
const c = counts[p];
|
|
16722
|
+
if (!c || c.total === 0)
|
|
16723
|
+
continue;
|
|
16724
|
+
const color = priColors[p] || chalk.white;
|
|
16725
|
+
const bar = chalk.green("\u2588".repeat(Math.min(c.completed, 30))) + chalk.blue("\u2591".repeat(Math.min(c.in_progress, 10))) + chalk.dim("\xB7".repeat(Math.min(c.pending, 20)));
|
|
16726
|
+
console.log(` ${color(p.padEnd(9))} ${String(c.total).padStart(4)} total ${chalk.green(String(c.completed).padStart(3))} done ${chalk.blue(String(c.in_progress).padStart(3))} active ${chalk.dim(String(c.pending).padStart(3))} pending ${bar}`);
|
|
16727
|
+
}
|
|
16728
|
+
});
|
|
16729
|
+
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) => {
|
|
16730
|
+
const globalOpts = program2.opts();
|
|
16731
|
+
const db = getDatabase();
|
|
16732
|
+
const { getStatus: getStatus2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16733
|
+
const { getNextTask: getNextTask2, getOverdueTasks: getOverdueTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
16734
|
+
const { getLatestHandoff: getLatestHandoff2 } = (init_handoffs(), __toCommonJS(exports_handoffs));
|
|
16735
|
+
const projectId = autoProject(globalOpts) || undefined;
|
|
16736
|
+
const agentName = opts.agent || globalOpts.agent || undefined;
|
|
16737
|
+
const filters = projectId ? { project_id: projectId } : undefined;
|
|
16738
|
+
const status = getStatus2(filters, agentName);
|
|
16739
|
+
const nextTask = getNextTask2(agentName, filters, db);
|
|
16740
|
+
const overdue = getOverdueTasks2(projectId, db);
|
|
16741
|
+
const handoff = agentName ? getLatestHandoff2(agentName, projectId, db) : getLatestHandoff2(undefined, projectId, db);
|
|
16742
|
+
if (opts.json || globalOpts.json) {
|
|
16743
|
+
console.log(JSON.stringify({ status, next_task: nextTask, overdue_count: overdue.length, latest_handoff: handoff, as_of: new Date().toISOString() }));
|
|
16744
|
+
return;
|
|
16745
|
+
}
|
|
16746
|
+
console.log(chalk.bold(`Session Context
|
|
16747
|
+
`));
|
|
16748
|
+
console.log(` ${status.pending} pending \xB7 ${status.in_progress} active \xB7 ${status.completed} done \xB7 ${status.total} total`);
|
|
16749
|
+
if (status.stale_count > 0)
|
|
16750
|
+
console.log(chalk.yellow(` \u26A0 ${status.stale_count} stale tasks`));
|
|
16751
|
+
if (overdue.length > 0)
|
|
16752
|
+
console.log(chalk.red(` \u26A0 ${overdue.length} overdue tasks`));
|
|
16753
|
+
if (nextTask) {
|
|
16754
|
+
const pri = nextTask.priority === "critical" || nextTask.priority === "high" ? chalk.red(` [${nextTask.priority}]`) : "";
|
|
16755
|
+
console.log(chalk.bold(`
|
|
16756
|
+
Next up:`));
|
|
16757
|
+
console.log(` ${chalk.cyan(nextTask.short_id || nextTask.id.slice(0, 8))} ${nextTask.title}${pri}`);
|
|
16758
|
+
}
|
|
16759
|
+
if (handoff) {
|
|
16760
|
+
console.log(chalk.bold(`
|
|
16761
|
+
Last handoff (${handoff.agent_id || "unknown"}, ${handoff.created_at.slice(0, 16).replace("T", " ")}):`));
|
|
16762
|
+
console.log(` ${handoff.summary}`);
|
|
16763
|
+
if (handoff.next_steps?.length) {
|
|
16764
|
+
for (const s of handoff.next_steps)
|
|
16765
|
+
console.log(` \u2192 ${s}`);
|
|
16766
|
+
}
|
|
16767
|
+
}
|
|
16768
|
+
console.log(chalk.dim(`
|
|
16769
|
+
as_of: ${new Date().toISOString()}`));
|
|
16770
|
+
});
|
|
15752
16771
|
program2.action(async () => {
|
|
15753
16772
|
if (process.stdout.isTTY) {
|
|
15754
16773
|
try {
|