@hasna/todos 0.11.1 → 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 +887 -546
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +149 -27
- 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.js +35 -9
- package/dist/server/index.js +5 -16
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -6,39 +6,60 @@ var __defProp = Object.defineProperty;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
function __accessProp(key) {
|
|
10
|
+
return this[key];
|
|
11
|
+
}
|
|
12
|
+
var __toESMCache_node;
|
|
13
|
+
var __toESMCache_esm;
|
|
9
14
|
var __toESM = (mod, isNodeMode, target) => {
|
|
15
|
+
var canCache = mod != null && typeof mod === "object";
|
|
16
|
+
if (canCache) {
|
|
17
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
18
|
+
var cached = cache.get(mod);
|
|
19
|
+
if (cached)
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
10
22
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
11
23
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
12
24
|
for (let key of __getOwnPropNames(mod))
|
|
13
25
|
if (!__hasOwnProp.call(to, key))
|
|
14
26
|
__defProp(to, key, {
|
|
15
|
-
get: (
|
|
27
|
+
get: __accessProp.bind(mod, key),
|
|
16
28
|
enumerable: true
|
|
17
29
|
});
|
|
30
|
+
if (canCache)
|
|
31
|
+
cache.set(mod, to);
|
|
18
32
|
return to;
|
|
19
33
|
};
|
|
20
|
-
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
21
34
|
var __toCommonJS = (from) => {
|
|
22
|
-
var entry = __moduleCache.get(from), desc;
|
|
35
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
23
36
|
if (entry)
|
|
24
37
|
return entry;
|
|
25
38
|
entry = __defProp({}, "__esModule", { value: true });
|
|
26
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
40
|
+
for (var key of __getOwnPropNames(from))
|
|
41
|
+
if (!__hasOwnProp.call(entry, key))
|
|
42
|
+
__defProp(entry, key, {
|
|
43
|
+
get: __accessProp.bind(from, key),
|
|
44
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
45
|
+
});
|
|
46
|
+
}
|
|
31
47
|
__moduleCache.set(from, entry);
|
|
32
48
|
return entry;
|
|
33
49
|
};
|
|
50
|
+
var __moduleCache;
|
|
34
51
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
52
|
+
var __returnValue = (v) => v;
|
|
53
|
+
function __exportSetter(name, newValue) {
|
|
54
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
55
|
+
}
|
|
35
56
|
var __export = (target, all) => {
|
|
36
57
|
for (var name in all)
|
|
37
58
|
__defProp(target, name, {
|
|
38
59
|
get: all[name],
|
|
39
60
|
enumerable: true,
|
|
40
61
|
configurable: true,
|
|
41
|
-
set: (
|
|
62
|
+
set: __exportSetter.bind(all, name)
|
|
42
63
|
});
|
|
43
64
|
};
|
|
44
65
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -6302,8 +6323,8 @@ __export(exports_extract, {
|
|
|
6302
6323
|
extractFromSource: () => extractFromSource,
|
|
6303
6324
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
6304
6325
|
});
|
|
6305
|
-
import { readFileSync as
|
|
6306
|
-
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";
|
|
6307
6328
|
function tagToPriority(tag) {
|
|
6308
6329
|
switch (tag) {
|
|
6309
6330
|
case "BUG":
|
|
@@ -6375,9 +6396,9 @@ function extractTodos(options, db) {
|
|
|
6375
6396
|
const files = collectFiles(basePath, extensions);
|
|
6376
6397
|
const allComments = [];
|
|
6377
6398
|
for (const file of files) {
|
|
6378
|
-
const fullPath = statSync2(basePath).isFile() ? basePath :
|
|
6399
|
+
const fullPath = statSync2(basePath).isFile() ? basePath : join8(basePath, file);
|
|
6379
6400
|
try {
|
|
6380
|
-
const source =
|
|
6401
|
+
const source = readFileSync4(fullPath, "utf-8");
|
|
6381
6402
|
const relPath = statSync2(basePath).isFile() ? relative(resolve2(basePath, ".."), fullPath) : file;
|
|
6382
6403
|
const comments = extractFromSource(source, relPath, tags);
|
|
6383
6404
|
allComments.push(...comments);
|
|
@@ -11778,14 +11799,14 @@ __export(exports_mcp, {
|
|
|
11778
11799
|
});
|
|
11779
11800
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11780
11801
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11781
|
-
import { readFileSync as
|
|
11782
|
-
import { join as
|
|
11802
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
11803
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
11783
11804
|
import { fileURLToPath } from "url";
|
|
11784
11805
|
function getMcpVersion() {
|
|
11785
11806
|
try {
|
|
11786
11807
|
const __dir = dirname2(fileURLToPath(import.meta.url));
|
|
11787
|
-
const pkgPath =
|
|
11788
|
-
return JSON.parse(
|
|
11808
|
+
const pkgPath = join9(__dir, "..", "package.json");
|
|
11809
|
+
return JSON.parse(readFileSync5(pkgPath, "utf-8")).version || "0.0.0";
|
|
11789
11810
|
} catch {
|
|
11790
11811
|
return "0.0.0";
|
|
11791
11812
|
}
|
|
@@ -11849,7 +11870,23 @@ function formatError(error) {
|
|
|
11849
11870
|
return JSON.stringify({ code: CompletionGuardError.code, message: error.reason, suggestion: CompletionGuardError.suggestion, ...retry });
|
|
11850
11871
|
}
|
|
11851
11872
|
if (error instanceof Error) {
|
|
11852
|
-
|
|
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 });
|
|
11853
11890
|
}
|
|
11854
11891
|
return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
|
|
11855
11892
|
}
|
|
@@ -15909,27 +15946,27 @@ var exports_serve = {};
|
|
|
15909
15946
|
__export(exports_serve, {
|
|
15910
15947
|
startServer: () => startServer
|
|
15911
15948
|
});
|
|
15912
|
-
import { existsSync as
|
|
15913
|
-
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";
|
|
15914
15951
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15915
15952
|
function resolveDashboardDir() {
|
|
15916
15953
|
const candidates = [];
|
|
15917
15954
|
try {
|
|
15918
15955
|
const scriptDir = dirname3(fileURLToPath2(import.meta.url));
|
|
15919
|
-
candidates.push(
|
|
15920
|
-
candidates.push(
|
|
15956
|
+
candidates.push(join10(scriptDir, "..", "dashboard", "dist"));
|
|
15957
|
+
candidates.push(join10(scriptDir, "..", "..", "dashboard", "dist"));
|
|
15921
15958
|
} catch {}
|
|
15922
15959
|
if (process.argv[1]) {
|
|
15923
15960
|
const mainDir = dirname3(process.argv[1]);
|
|
15924
|
-
candidates.push(
|
|
15925
|
-
candidates.push(
|
|
15961
|
+
candidates.push(join10(mainDir, "..", "dashboard", "dist"));
|
|
15962
|
+
candidates.push(join10(mainDir, "..", "..", "dashboard", "dist"));
|
|
15926
15963
|
}
|
|
15927
|
-
candidates.push(
|
|
15964
|
+
candidates.push(join10(process.cwd(), "dashboard", "dist"));
|
|
15928
15965
|
for (const candidate of candidates) {
|
|
15929
|
-
if (
|
|
15966
|
+
if (existsSync8(candidate))
|
|
15930
15967
|
return candidate;
|
|
15931
15968
|
}
|
|
15932
|
-
return
|
|
15969
|
+
return join10(process.cwd(), "dashboard", "dist");
|
|
15933
15970
|
}
|
|
15934
15971
|
function json(data, status = 200, port) {
|
|
15935
15972
|
return new Response(JSON.stringify(data), {
|
|
@@ -15942,7 +15979,7 @@ function json(data, status = 200, port) {
|
|
|
15942
15979
|
});
|
|
15943
15980
|
}
|
|
15944
15981
|
function serveStaticFile(filePath) {
|
|
15945
|
-
if (!
|
|
15982
|
+
if (!existsSync8(filePath))
|
|
15946
15983
|
return null;
|
|
15947
15984
|
const ext = extname(filePath);
|
|
15948
15985
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -16009,7 +16046,7 @@ data: ${data}
|
|
|
16009
16046
|
}
|
|
16010
16047
|
}
|
|
16011
16048
|
const dashboardDir = resolveDashboardDir();
|
|
16012
|
-
const dashboardExists =
|
|
16049
|
+
const dashboardExists = existsSync8(dashboardDir);
|
|
16013
16050
|
if (!dashboardExists) {
|
|
16014
16051
|
console.error(`
|
|
16015
16052
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -16733,7 +16770,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
16733
16770
|
}
|
|
16734
16771
|
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
16735
16772
|
if (path !== "/") {
|
|
16736
|
-
const filePath =
|
|
16773
|
+
const filePath = join10(dashboardDir, path);
|
|
16737
16774
|
const resolvedFile = resolve3(filePath);
|
|
16738
16775
|
const resolvedBase = resolve3(dashboardDir);
|
|
16739
16776
|
if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
|
|
@@ -16743,7 +16780,7 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
16743
16780
|
if (res2)
|
|
16744
16781
|
return res2;
|
|
16745
16782
|
}
|
|
16746
|
-
const indexPath =
|
|
16783
|
+
const indexPath = join10(dashboardDir, "index.html");
|
|
16747
16784
|
const res = serveStaticFile(indexPath);
|
|
16748
16785
|
if (res)
|
|
16749
16786
|
return res;
|
|
@@ -18162,15 +18199,318 @@ init_comments();
|
|
|
18162
18199
|
init_search();
|
|
18163
18200
|
init_sync();
|
|
18164
18201
|
init_config();
|
|
18165
|
-
import
|
|
18202
|
+
import chalk2 from "chalk";
|
|
18166
18203
|
import { execSync as execSync2 } from "child_process";
|
|
18167
|
-
import { existsSync as
|
|
18168
|
-
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";
|
|
18169
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
|
|
18170
18510
|
function getPackageVersion() {
|
|
18171
18511
|
try {
|
|
18172
|
-
const pkgPath =
|
|
18173
|
-
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";
|
|
18174
18514
|
} catch {
|
|
18175
18515
|
return "0.0.0";
|
|
18176
18516
|
}
|
|
@@ -18181,7 +18521,7 @@ function handleError(e) {
|
|
|
18181
18521
|
if (globalOpts.json) {
|
|
18182
18522
|
console.log(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }));
|
|
18183
18523
|
} else {
|
|
18184
|
-
console.error(
|
|
18524
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
18185
18525
|
}
|
|
18186
18526
|
process.exit(1);
|
|
18187
18527
|
}
|
|
@@ -18191,10 +18531,10 @@ function resolveTaskId(partialId) {
|
|
|
18191
18531
|
if (!id) {
|
|
18192
18532
|
const similar = db.query("SELECT id FROM tasks WHERE id LIKE ? LIMIT 3").all(`%${partialId}%`);
|
|
18193
18533
|
if (similar.length > 0) {
|
|
18194
|
-
console.error(
|
|
18195
|
-
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(", ")}?`));
|
|
18196
18536
|
} else {
|
|
18197
|
-
console.error(
|
|
18537
|
+
console.error(chalk2.red(`Could not resolve task ID: ${partialId}`));
|
|
18198
18538
|
}
|
|
18199
18539
|
process.exit(1);
|
|
18200
18540
|
}
|
|
@@ -18228,26 +18568,26 @@ function output(data, jsonMode) {
|
|
|
18228
18568
|
}
|
|
18229
18569
|
}
|
|
18230
18570
|
var statusColors4 = {
|
|
18231
|
-
pending:
|
|
18232
|
-
in_progress:
|
|
18233
|
-
completed:
|
|
18234
|
-
failed:
|
|
18235
|
-
cancelled:
|
|
18571
|
+
pending: chalk2.yellow,
|
|
18572
|
+
in_progress: chalk2.blue,
|
|
18573
|
+
completed: chalk2.green,
|
|
18574
|
+
failed: chalk2.red,
|
|
18575
|
+
cancelled: chalk2.gray
|
|
18236
18576
|
};
|
|
18237
18577
|
var priorityColors2 = {
|
|
18238
|
-
critical:
|
|
18239
|
-
high:
|
|
18240
|
-
medium:
|
|
18241
|
-
low:
|
|
18578
|
+
critical: chalk2.red.bold,
|
|
18579
|
+
high: chalk2.red,
|
|
18580
|
+
medium: chalk2.yellow,
|
|
18581
|
+
low: chalk2.gray
|
|
18242
18582
|
};
|
|
18243
18583
|
function formatTaskLine(t) {
|
|
18244
|
-
const statusFn = statusColors4[t.status] ||
|
|
18245
|
-
const priorityFn = priorityColors2[t.priority] ||
|
|
18246
|
-
const lock = t.locked_by ?
|
|
18247
|
-
const assigned = t.assigned_to ?
|
|
18248
|
-
const tags = t.tags.length > 0 ?
|
|
18249
|
-
const plan = t.plan_id ?
|
|
18250
|
-
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}`;
|
|
18251
18591
|
}
|
|
18252
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");
|
|
18253
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) => {
|
|
@@ -18259,7 +18599,7 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
|
|
|
18259
18599
|
const db = getDatabase();
|
|
18260
18600
|
const id = resolvePartialId(db, "task_lists", opts.list);
|
|
18261
18601
|
if (!id) {
|
|
18262
|
-
console.error(
|
|
18602
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
18263
18603
|
process.exit(1);
|
|
18264
18604
|
}
|
|
18265
18605
|
return id;
|
|
@@ -18274,7 +18614,7 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
|
|
|
18274
18614
|
const db = getDatabase();
|
|
18275
18615
|
const id = resolvePartialId(db, "plans", opts.plan);
|
|
18276
18616
|
if (!id) {
|
|
18277
|
-
console.error(
|
|
18617
|
+
console.error(chalk2.red(`Could not resolve plan ID: ${opts.plan}`));
|
|
18278
18618
|
process.exit(1);
|
|
18279
18619
|
}
|
|
18280
18620
|
return id;
|
|
@@ -18295,7 +18635,7 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
|
|
|
18295
18635
|
if (globalOpts.json) {
|
|
18296
18636
|
output(task, true);
|
|
18297
18637
|
} else {
|
|
18298
|
-
console.log(
|
|
18638
|
+
console.log(chalk2.green("Task created:"));
|
|
18299
18639
|
console.log(formatTaskLine(task));
|
|
18300
18640
|
}
|
|
18301
18641
|
});
|
|
@@ -18311,7 +18651,7 @@ program2.command("list").description("List tasks").option("-s, --status <status>
|
|
|
18311
18651
|
const db = getDatabase();
|
|
18312
18652
|
const listId = resolvePartialId(db, "task_lists", opts.list);
|
|
18313
18653
|
if (!listId) {
|
|
18314
|
-
console.error(
|
|
18654
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
18315
18655
|
process.exit(1);
|
|
18316
18656
|
}
|
|
18317
18657
|
filter["task_list_id"] = listId;
|
|
@@ -18333,7 +18673,7 @@ program2.command("list").description("List tasks").option("-s, --status <status>
|
|
|
18333
18673
|
if (match) {
|
|
18334
18674
|
filter["project_id"] = match.id;
|
|
18335
18675
|
} else {
|
|
18336
|
-
console.error(
|
|
18676
|
+
console.error(chalk2.red(`No project matching: ${opts.projectName}`));
|
|
18337
18677
|
process.exit(1);
|
|
18338
18678
|
}
|
|
18339
18679
|
}
|
|
@@ -18377,7 +18717,7 @@ program2.command("list").description("List tasks").option("-s, --status <status>
|
|
|
18377
18717
|
if (fmt === "compact" || fmt === "csv")
|
|
18378
18718
|
process.stdout.write("");
|
|
18379
18719
|
else
|
|
18380
|
-
console.log(
|
|
18720
|
+
console.log(chalk2.dim("No tasks found."));
|
|
18381
18721
|
return;
|
|
18382
18722
|
}
|
|
18383
18723
|
if (fmt === "csv") {
|
|
@@ -18404,7 +18744,7 @@ program2.command("list").description("List tasks").option("-s, --status <status>
|
|
|
18404
18744
|
}
|
|
18405
18745
|
return;
|
|
18406
18746
|
}
|
|
18407
|
-
console.log(
|
|
18747
|
+
console.log(chalk2.bold(`${tasks.length} task(s):
|
|
18408
18748
|
`));
|
|
18409
18749
|
for (const t of tasks) {
|
|
18410
18750
|
console.log(formatTaskLine(t));
|
|
@@ -18421,12 +18761,12 @@ program2.command("count").description("Show task count by status").action(() =>
|
|
|
18421
18761
|
output(counts, true);
|
|
18422
18762
|
} else {
|
|
18423
18763
|
const parts = [
|
|
18424
|
-
`total: ${
|
|
18425
|
-
`pending: ${
|
|
18426
|
-
`in_progress: ${
|
|
18427
|
-
`completed: ${
|
|
18428
|
-
`failed: ${
|
|
18429
|
-
`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))}`
|
|
18430
18770
|
];
|
|
18431
18771
|
console.log(parts.join(" "));
|
|
18432
18772
|
}
|
|
@@ -18436,83 +18776,83 @@ program2.command("show <id>").description("Show full task details").action((id)
|
|
|
18436
18776
|
const resolvedId = resolveTaskId(id);
|
|
18437
18777
|
const task = getTaskWithRelations(resolvedId);
|
|
18438
18778
|
if (!task) {
|
|
18439
|
-
console.error(
|
|
18779
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
18440
18780
|
process.exit(1);
|
|
18441
18781
|
}
|
|
18442
18782
|
if (globalOpts.json) {
|
|
18443
18783
|
output(task, true);
|
|
18444
18784
|
return;
|
|
18445
18785
|
}
|
|
18446
|
-
console.log(
|
|
18786
|
+
console.log(chalk2.bold(`Task Details:
|
|
18447
18787
|
`));
|
|
18448
|
-
console.log(` ${
|
|
18449
|
-
console.log(` ${
|
|
18450
|
-
console.log(` ${
|
|
18451
|
-
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)}`);
|
|
18452
18792
|
if (task.description)
|
|
18453
|
-
console.log(` ${
|
|
18793
|
+
console.log(` ${chalk2.dim("Desc:")} ${task.description}`);
|
|
18454
18794
|
if (task.assigned_to)
|
|
18455
|
-
console.log(` ${
|
|
18795
|
+
console.log(` ${chalk2.dim("Assigned:")} ${task.assigned_to}`);
|
|
18456
18796
|
if (task.agent_id)
|
|
18457
|
-
console.log(` ${
|
|
18797
|
+
console.log(` ${chalk2.dim("Agent:")} ${task.agent_id}`);
|
|
18458
18798
|
if (task.session_id)
|
|
18459
|
-
console.log(` ${
|
|
18799
|
+
console.log(` ${chalk2.dim("Session:")} ${task.session_id}`);
|
|
18460
18800
|
if (task.locked_by)
|
|
18461
|
-
console.log(` ${
|
|
18801
|
+
console.log(` ${chalk2.dim("Locked:")} ${task.locked_by} (at ${task.locked_at})`);
|
|
18462
18802
|
if (task.requires_approval) {
|
|
18463
|
-
const approvalStatus = task.approved_by ?
|
|
18464
|
-
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}`);
|
|
18465
18805
|
}
|
|
18466
18806
|
if (task.estimated_minutes)
|
|
18467
|
-
console.log(` ${
|
|
18807
|
+
console.log(` ${chalk2.dim("Estimate:")} ${task.estimated_minutes} minutes`);
|
|
18468
18808
|
if (task.project_id)
|
|
18469
|
-
console.log(` ${
|
|
18809
|
+
console.log(` ${chalk2.dim("Project:")} ${task.project_id}`);
|
|
18470
18810
|
if (task.plan_id)
|
|
18471
|
-
console.log(` ${
|
|
18811
|
+
console.log(` ${chalk2.dim("Plan:")} ${task.plan_id}`);
|
|
18472
18812
|
if (task.working_dir)
|
|
18473
|
-
console.log(` ${
|
|
18813
|
+
console.log(` ${chalk2.dim("WorkDir:")} ${task.working_dir}`);
|
|
18474
18814
|
if (task.parent)
|
|
18475
|
-
console.log(` ${
|
|
18815
|
+
console.log(` ${chalk2.dim("Parent:")} ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
18476
18816
|
if (task.tags.length > 0)
|
|
18477
|
-
console.log(` ${
|
|
18478
|
-
console.log(` ${
|
|
18479
|
-
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}`);
|
|
18480
18820
|
if (task.started_at)
|
|
18481
|
-
console.log(` ${
|
|
18821
|
+
console.log(` ${chalk2.dim("Started:")} ${task.started_at}`);
|
|
18482
18822
|
if (task.completed_at) {
|
|
18483
|
-
console.log(` ${
|
|
18823
|
+
console.log(` ${chalk2.dim("Done:")} ${task.completed_at}`);
|
|
18484
18824
|
if (task.started_at) {
|
|
18485
18825
|
const dur = Math.round((new Date(task.completed_at).getTime() - new Date(task.started_at).getTime()) / 60000);
|
|
18486
|
-
console.log(` ${
|
|
18826
|
+
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
18487
18827
|
}
|
|
18488
18828
|
}
|
|
18489
18829
|
if (task.subtasks.length > 0) {
|
|
18490
|
-
console.log(
|
|
18830
|
+
console.log(chalk2.bold(`
|
|
18491
18831
|
Subtasks (${task.subtasks.length}):`));
|
|
18492
18832
|
for (const st of task.subtasks) {
|
|
18493
18833
|
console.log(` ${formatTaskLine(st)}`);
|
|
18494
18834
|
}
|
|
18495
18835
|
}
|
|
18496
18836
|
if (task.dependencies.length > 0) {
|
|
18497
|
-
console.log(
|
|
18837
|
+
console.log(chalk2.bold(`
|
|
18498
18838
|
Depends on (${task.dependencies.length}):`));
|
|
18499
18839
|
for (const dep of task.dependencies) {
|
|
18500
18840
|
console.log(` ${formatTaskLine(dep)}`);
|
|
18501
18841
|
}
|
|
18502
18842
|
}
|
|
18503
18843
|
if (task.blocked_by.length > 0) {
|
|
18504
|
-
console.log(
|
|
18844
|
+
console.log(chalk2.bold(`
|
|
18505
18845
|
Blocks (${task.blocked_by.length}):`));
|
|
18506
18846
|
for (const b of task.blocked_by) {
|
|
18507
18847
|
console.log(` ${formatTaskLine(b)}`);
|
|
18508
18848
|
}
|
|
18509
18849
|
}
|
|
18510
18850
|
if (task.comments.length > 0) {
|
|
18511
|
-
console.log(
|
|
18851
|
+
console.log(chalk2.bold(`
|
|
18512
18852
|
Comments (${task.comments.length}):`));
|
|
18513
18853
|
for (const c of task.comments) {
|
|
18514
|
-
const agent = c.agent_id ?
|
|
18515
|
-
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}`);
|
|
18516
18856
|
}
|
|
18517
18857
|
}
|
|
18518
18858
|
});
|
|
@@ -18526,12 +18866,12 @@ program2.command("inspect [id]").description("Full orientation for a task \u2014
|
|
|
18526
18866
|
resolvedId = active[0].id;
|
|
18527
18867
|
}
|
|
18528
18868
|
if (!resolvedId) {
|
|
18529
|
-
console.error(
|
|
18869
|
+
console.error(chalk2.red("No task ID given and no active task found. Pass an ID or use --agent."));
|
|
18530
18870
|
process.exit(1);
|
|
18531
18871
|
}
|
|
18532
18872
|
const task = getTaskWithRelations(resolvedId);
|
|
18533
18873
|
if (!task) {
|
|
18534
|
-
console.error(
|
|
18874
|
+
console.error(chalk2.red(`Task not found: ${id || resolvedId}`));
|
|
18535
18875
|
process.exit(1);
|
|
18536
18876
|
}
|
|
18537
18877
|
if (globalOpts.json) {
|
|
@@ -18551,59 +18891,59 @@ program2.command("inspect [id]").description("Full orientation for a task \u2014
|
|
|
18551
18891
|
return;
|
|
18552
18892
|
}
|
|
18553
18893
|
const sid = task.short_id || task.id.slice(0, 8);
|
|
18554
|
-
const statusColor = statusColors4[task.status] ||
|
|
18555
|
-
const prioColor = priorityColors2[task.priority] ||
|
|
18556
|
-
console.log(
|
|
18557
|
-
${
|
|
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}
|
|
18558
18898
|
`));
|
|
18559
18899
|
if (task.description) {
|
|
18560
|
-
console.log(
|
|
18900
|
+
console.log(chalk2.dim("Description:"));
|
|
18561
18901
|
console.log(` ${task.description}
|
|
18562
18902
|
`);
|
|
18563
18903
|
}
|
|
18564
18904
|
if (task.assigned_to)
|
|
18565
|
-
console.log(` ${
|
|
18905
|
+
console.log(` ${chalk2.dim("Assigned:")} ${task.assigned_to}`);
|
|
18566
18906
|
if (task.locked_by)
|
|
18567
|
-
console.log(` ${
|
|
18907
|
+
console.log(` ${chalk2.dim("Locked by:")} ${task.locked_by}`);
|
|
18568
18908
|
if (task.project_id)
|
|
18569
|
-
console.log(` ${
|
|
18909
|
+
console.log(` ${chalk2.dim("Project:")} ${task.project_id}`);
|
|
18570
18910
|
if (task.plan_id)
|
|
18571
|
-
console.log(` ${
|
|
18911
|
+
console.log(` ${chalk2.dim("Plan:")} ${task.plan_id}`);
|
|
18572
18912
|
if (task.started_at)
|
|
18573
|
-
console.log(` ${
|
|
18913
|
+
console.log(` ${chalk2.dim("Started:")} ${task.started_at}`);
|
|
18574
18914
|
if (task.completed_at) {
|
|
18575
|
-
console.log(` ${
|
|
18915
|
+
console.log(` ${chalk2.dim("Completed:")} ${task.completed_at}`);
|
|
18576
18916
|
if (task.started_at) {
|
|
18577
18917
|
const dur = Math.round((new Date(task.completed_at).getTime() - new Date(task.started_at).getTime()) / 60000);
|
|
18578
|
-
console.log(` ${
|
|
18918
|
+
console.log(` ${chalk2.dim("Duration:")} ${dur}m`);
|
|
18579
18919
|
}
|
|
18580
18920
|
}
|
|
18581
18921
|
if (task.estimated_minutes)
|
|
18582
|
-
console.log(` ${
|
|
18922
|
+
console.log(` ${chalk2.dim("Estimate:")} ${task.estimated_minutes}m`);
|
|
18583
18923
|
if (task.tags.length > 0)
|
|
18584
|
-
console.log(` ${
|
|
18924
|
+
console.log(` ${chalk2.dim("Tags:")} ${task.tags.join(", ")}`);
|
|
18585
18925
|
const unfinishedDeps = task.dependencies.filter((d) => d.status !== "completed" && d.status !== "cancelled");
|
|
18586
18926
|
if (task.dependencies.length > 0) {
|
|
18587
|
-
console.log(
|
|
18927
|
+
console.log(chalk2.bold(`
|
|
18588
18928
|
Depends on (${task.dependencies.length}):`));
|
|
18589
18929
|
for (const dep of task.dependencies) {
|
|
18590
18930
|
const blocked = dep.status !== "completed" && dep.status !== "cancelled";
|
|
18591
|
-
const icon = blocked ?
|
|
18931
|
+
const icon = blocked ? chalk2.red("\u2717") : chalk2.green("\u2713");
|
|
18592
18932
|
console.log(` ${icon} ${formatTaskLine(dep)}`);
|
|
18593
18933
|
}
|
|
18594
18934
|
}
|
|
18595
18935
|
if (unfinishedDeps.length > 0) {
|
|
18596
|
-
console.log(
|
|
18936
|
+
console.log(chalk2.red(`
|
|
18597
18937
|
BLOCKED by ${unfinishedDeps.length} unfinished dep(s)`));
|
|
18598
18938
|
}
|
|
18599
18939
|
if (task.blocked_by.length > 0) {
|
|
18600
|
-
console.log(
|
|
18940
|
+
console.log(chalk2.bold(`
|
|
18601
18941
|
Blocks (${task.blocked_by.length}):`));
|
|
18602
18942
|
for (const b of task.blocked_by)
|
|
18603
18943
|
console.log(` ${formatTaskLine(b)}`);
|
|
18604
18944
|
}
|
|
18605
18945
|
if (task.subtasks.length > 0) {
|
|
18606
|
-
console.log(
|
|
18946
|
+
console.log(chalk2.bold(`
|
|
18607
18947
|
Subtasks (${task.subtasks.length}):`));
|
|
18608
18948
|
for (const st of task.subtasks)
|
|
18609
18949
|
console.log(` ${formatTaskLine(st)}`);
|
|
@@ -18612,36 +18952,36 @@ ${chalk.cyan(sid)} ${statusColor(task.status)} ${prioColor(task.priority)} ${tas
|
|
|
18612
18952
|
const { listTaskFiles: listTaskFiles2 } = (init_task_files(), __toCommonJS(exports_task_files));
|
|
18613
18953
|
const files = listTaskFiles2(task.id);
|
|
18614
18954
|
if (files.length > 0) {
|
|
18615
|
-
console.log(
|
|
18955
|
+
console.log(chalk2.bold(`
|
|
18616
18956
|
Files (${files.length}):`));
|
|
18617
18957
|
for (const f of files)
|
|
18618
|
-
console.log(` ${
|
|
18958
|
+
console.log(` ${chalk2.dim(f.role || "file")} ${f.path}`);
|
|
18619
18959
|
}
|
|
18620
18960
|
} catch {}
|
|
18621
18961
|
try {
|
|
18622
18962
|
const { getTaskCommits: getTaskCommits2 } = (init_task_commits(), __toCommonJS(exports_task_commits));
|
|
18623
18963
|
const commits = getTaskCommits2(task.id);
|
|
18624
18964
|
if (commits.length > 0) {
|
|
18625
|
-
console.log(
|
|
18965
|
+
console.log(chalk2.bold(`
|
|
18626
18966
|
Commits (${commits.length}):`));
|
|
18627
18967
|
for (const c of commits)
|
|
18628
|
-
console.log(` ${
|
|
18968
|
+
console.log(` ${chalk2.yellow(c.commit_hash.slice(0, 7))} ${c.message || ""}`);
|
|
18629
18969
|
}
|
|
18630
18970
|
} catch {}
|
|
18631
18971
|
if (task.comments.length > 0) {
|
|
18632
|
-
console.log(
|
|
18972
|
+
console.log(chalk2.bold(`
|
|
18633
18973
|
Comments (${task.comments.length}):`));
|
|
18634
18974
|
for (const c of task.comments) {
|
|
18635
|
-
const agent = c.agent_id ?
|
|
18636
|
-
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}`);
|
|
18637
18977
|
}
|
|
18638
18978
|
}
|
|
18639
18979
|
if (task.checklist && task.checklist.length > 0) {
|
|
18640
18980
|
const done = task.checklist.filter((c) => c.checked).length;
|
|
18641
|
-
console.log(
|
|
18981
|
+
console.log(chalk2.bold(`
|
|
18642
18982
|
Checklist (${done}/${task.checklist.length}):`));
|
|
18643
18983
|
for (const item of task.checklist) {
|
|
18644
|
-
const icon = item.checked ?
|
|
18984
|
+
const icon = item.checked ? chalk2.green("\u2611") : chalk2.dim("\u2610");
|
|
18645
18985
|
console.log(` ${icon} ${item.text || item.title}`);
|
|
18646
18986
|
}
|
|
18647
18987
|
}
|
|
@@ -18657,16 +18997,16 @@ program2.command("history <id>").description("Show change history for a task (au
|
|
|
18657
18997
|
return;
|
|
18658
18998
|
}
|
|
18659
18999
|
if (history.length === 0) {
|
|
18660
|
-
console.log(
|
|
19000
|
+
console.log(chalk2.dim("No history for this task."));
|
|
18661
19001
|
return;
|
|
18662
19002
|
}
|
|
18663
|
-
console.log(
|
|
19003
|
+
console.log(chalk2.bold(`${history.length} change(s):
|
|
18664
19004
|
`));
|
|
18665
19005
|
for (const h of history) {
|
|
18666
|
-
const agent = h.agent_id ?
|
|
18667
|
-
const field = h.field ?
|
|
18668
|
-
const change = h.old_value && h.new_value ? ` ${
|
|
18669
|
-
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}`);
|
|
18670
19010
|
}
|
|
18671
19011
|
});
|
|
18672
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) => {
|
|
@@ -18676,14 +19016,14 @@ program2.command("update <id>").description("Update a task").option("--title <te
|
|
|
18676
19016
|
const resolvedId = resolveTaskId(id);
|
|
18677
19017
|
const current = getTask(resolvedId);
|
|
18678
19018
|
if (!current) {
|
|
18679
|
-
console.error(
|
|
19019
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
18680
19020
|
process.exit(1);
|
|
18681
19021
|
}
|
|
18682
19022
|
const taskListId = opts.list ? (() => {
|
|
18683
19023
|
const db = getDatabase();
|
|
18684
19024
|
const resolved = resolvePartialId(db, "task_lists", opts.list);
|
|
18685
19025
|
if (!resolved) {
|
|
18686
|
-
console.error(
|
|
19026
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
18687
19027
|
process.exit(1);
|
|
18688
19028
|
}
|
|
18689
19029
|
return resolved;
|
|
@@ -18708,7 +19048,7 @@ program2.command("update <id>").description("Update a task").option("--title <te
|
|
|
18708
19048
|
if (globalOpts.json) {
|
|
18709
19049
|
output(task, true);
|
|
18710
19050
|
} else {
|
|
18711
|
-
console.log(
|
|
19051
|
+
console.log(chalk2.green("Task updated:"));
|
|
18712
19052
|
console.log(formatTaskLine(task));
|
|
18713
19053
|
}
|
|
18714
19054
|
});
|
|
@@ -18728,7 +19068,7 @@ program2.command("done <id>").description("Mark a task as completed").option("--
|
|
|
18728
19068
|
if (globalOpts.json) {
|
|
18729
19069
|
output(task, true);
|
|
18730
19070
|
} else {
|
|
18731
|
-
console.log(
|
|
19071
|
+
console.log(chalk2.green("Task completed:"));
|
|
18732
19072
|
console.log(formatTaskLine(task));
|
|
18733
19073
|
}
|
|
18734
19074
|
});
|
|
@@ -18737,15 +19077,15 @@ program2.command("approve <id>").description("Approve a task that requires appro
|
|
|
18737
19077
|
const resolvedId = resolveTaskId(id);
|
|
18738
19078
|
const task = getTask(resolvedId);
|
|
18739
19079
|
if (!task) {
|
|
18740
|
-
console.error(
|
|
19080
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
18741
19081
|
process.exit(1);
|
|
18742
19082
|
}
|
|
18743
19083
|
if (!task.requires_approval) {
|
|
18744
|
-
console.log(
|
|
19084
|
+
console.log(chalk2.yellow("This task does not require approval."));
|
|
18745
19085
|
return;
|
|
18746
19086
|
}
|
|
18747
19087
|
if (task.approved_by) {
|
|
18748
|
-
console.log(
|
|
19088
|
+
console.log(chalk2.yellow(`Already approved by ${task.approved_by}.`));
|
|
18749
19089
|
return;
|
|
18750
19090
|
}
|
|
18751
19091
|
try {
|
|
@@ -18753,7 +19093,7 @@ program2.command("approve <id>").description("Approve a task that requires appro
|
|
|
18753
19093
|
if (globalOpts.json) {
|
|
18754
19094
|
output(updated, true);
|
|
18755
19095
|
} else {
|
|
18756
|
-
console.log(
|
|
19096
|
+
console.log(chalk2.green(`Task approved by ${globalOpts.agent || "cli"}:`));
|
|
18757
19097
|
console.log(formatTaskLine(updated));
|
|
18758
19098
|
}
|
|
18759
19099
|
} catch (e) {
|
|
@@ -18773,7 +19113,7 @@ program2.command("start <id>").description("Claim, lock, and start a task").acti
|
|
|
18773
19113
|
if (globalOpts.json) {
|
|
18774
19114
|
output(task, true);
|
|
18775
19115
|
} else {
|
|
18776
|
-
console.log(
|
|
19116
|
+
console.log(chalk2.green(`Task started by ${agentId}:`));
|
|
18777
19117
|
console.log(formatTaskLine(task));
|
|
18778
19118
|
}
|
|
18779
19119
|
});
|
|
@@ -18790,9 +19130,9 @@ program2.command("lock <id>").description("Acquire exclusive lock on a task").ac
|
|
|
18790
19130
|
if (globalOpts.json) {
|
|
18791
19131
|
output(result, true);
|
|
18792
19132
|
} else if (result.success) {
|
|
18793
|
-
console.log(
|
|
19133
|
+
console.log(chalk2.green(`Lock acquired by ${agentId}`));
|
|
18794
19134
|
} else {
|
|
18795
|
-
console.error(
|
|
19135
|
+
console.error(chalk2.red(`Lock failed: ${result.error}`));
|
|
18796
19136
|
process.exit(1);
|
|
18797
19137
|
}
|
|
18798
19138
|
});
|
|
@@ -18807,7 +19147,7 @@ program2.command("unlock <id>").description("Release lock on a task").action((id
|
|
|
18807
19147
|
if (globalOpts.json) {
|
|
18808
19148
|
output({ success: true }, true);
|
|
18809
19149
|
} else {
|
|
18810
|
-
console.log(
|
|
19150
|
+
console.log(chalk2.green("Lock released."));
|
|
18811
19151
|
}
|
|
18812
19152
|
});
|
|
18813
19153
|
program2.command("delete <id>").description("Delete a task").action((id) => {
|
|
@@ -18817,9 +19157,9 @@ program2.command("delete <id>").description("Delete a task").action((id) => {
|
|
|
18817
19157
|
if (globalOpts.json) {
|
|
18818
19158
|
output({ deleted }, true);
|
|
18819
19159
|
} else if (deleted) {
|
|
18820
|
-
console.log(
|
|
19160
|
+
console.log(chalk2.green("Task deleted."));
|
|
18821
19161
|
} else {
|
|
18822
|
-
console.error(
|
|
19162
|
+
console.error(chalk2.red("Task not found."));
|
|
18823
19163
|
process.exit(1);
|
|
18824
19164
|
}
|
|
18825
19165
|
});
|
|
@@ -18830,9 +19170,9 @@ program2.command("remove <id>").description("Remove/delete a task (alias for del
|
|
|
18830
19170
|
if (globalOpts.json) {
|
|
18831
19171
|
output({ deleted }, true);
|
|
18832
19172
|
} else if (deleted) {
|
|
18833
|
-
console.log(
|
|
19173
|
+
console.log(chalk2.green("Task removed."));
|
|
18834
19174
|
} else {
|
|
18835
|
-
console.error(
|
|
19175
|
+
console.error(chalk2.red("Task not found."));
|
|
18836
19176
|
process.exit(1);
|
|
18837
19177
|
}
|
|
18838
19178
|
});
|
|
@@ -18852,7 +19192,7 @@ program2.command("bulk <action> <ids...>").description("Bulk operation on multip
|
|
|
18852
19192
|
deleteTask(resolvedId);
|
|
18853
19193
|
results.push({ id: resolvedId, success: true });
|
|
18854
19194
|
} else {
|
|
18855
|
-
console.error(
|
|
19195
|
+
console.error(chalk2.red(`Unknown action: ${action}. Use: done, start, delete`));
|
|
18856
19196
|
process.exit(1);
|
|
18857
19197
|
}
|
|
18858
19198
|
} catch (e) {
|
|
@@ -18864,9 +19204,9 @@ program2.command("bulk <action> <ids...>").description("Bulk operation on multip
|
|
|
18864
19204
|
if (globalOpts.json) {
|
|
18865
19205
|
output({ results, succeeded, failed }, true);
|
|
18866
19206
|
} else {
|
|
18867
|
-
console.log(
|
|
19207
|
+
console.log(chalk2.green(`${action}: ${succeeded} succeeded, ${failed} failed`));
|
|
18868
19208
|
for (const r of results.filter((r2) => !r2.success)) {
|
|
18869
|
-
console.log(
|
|
19209
|
+
console.log(chalk2.red(` ${r.id}: ${r.error}`));
|
|
18870
19210
|
}
|
|
18871
19211
|
}
|
|
18872
19212
|
});
|
|
@@ -18882,8 +19222,8 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18882
19222
|
if (globalOpts.json) {
|
|
18883
19223
|
output(plan, true);
|
|
18884
19224
|
} else {
|
|
18885
|
-
console.log(
|
|
18886
|
-
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}]`)}`);
|
|
18887
19227
|
}
|
|
18888
19228
|
return;
|
|
18889
19229
|
}
|
|
@@ -18891,12 +19231,12 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18891
19231
|
const db = getDatabase();
|
|
18892
19232
|
const resolvedId = resolvePartialId(db, "plans", opts.show);
|
|
18893
19233
|
if (!resolvedId) {
|
|
18894
|
-
console.error(
|
|
19234
|
+
console.error(chalk2.red(`Could not resolve plan ID: ${opts.show}`));
|
|
18895
19235
|
process.exit(1);
|
|
18896
19236
|
}
|
|
18897
19237
|
const plan = getPlan(resolvedId);
|
|
18898
19238
|
if (!plan) {
|
|
18899
|
-
console.error(
|
|
19239
|
+
console.error(chalk2.red(`Plan not found: ${opts.show}`));
|
|
18900
19240
|
process.exit(1);
|
|
18901
19241
|
}
|
|
18902
19242
|
const tasks = listTasks({ plan_id: resolvedId });
|
|
@@ -18904,24 +19244,24 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18904
19244
|
output({ plan, tasks }, true);
|
|
18905
19245
|
return;
|
|
18906
19246
|
}
|
|
18907
|
-
console.log(
|
|
19247
|
+
console.log(chalk2.bold(`Plan Details:
|
|
18908
19248
|
`));
|
|
18909
|
-
console.log(` ${
|
|
18910
|
-
console.log(` ${
|
|
18911
|
-
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)}`);
|
|
18912
19252
|
if (plan.description)
|
|
18913
|
-
console.log(` ${
|
|
19253
|
+
console.log(` ${chalk2.dim("Desc:")} ${plan.description}`);
|
|
18914
19254
|
if (plan.project_id)
|
|
18915
|
-
console.log(` ${
|
|
18916
|
-
console.log(` ${
|
|
19255
|
+
console.log(` ${chalk2.dim("Project:")} ${plan.project_id}`);
|
|
19256
|
+
console.log(` ${chalk2.dim("Created:")} ${plan.created_at}`);
|
|
18917
19257
|
if (tasks.length > 0) {
|
|
18918
|
-
console.log(
|
|
19258
|
+
console.log(chalk2.bold(`
|
|
18919
19259
|
Tasks (${tasks.length}):`));
|
|
18920
19260
|
for (const t of tasks) {
|
|
18921
19261
|
console.log(` ${formatTaskLine(t)}`);
|
|
18922
19262
|
}
|
|
18923
19263
|
} else {
|
|
18924
|
-
console.log(
|
|
19264
|
+
console.log(chalk2.dim(`
|
|
18925
19265
|
No tasks in this plan.`));
|
|
18926
19266
|
}
|
|
18927
19267
|
return;
|
|
@@ -18930,16 +19270,16 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18930
19270
|
const db = getDatabase();
|
|
18931
19271
|
const resolvedId = resolvePartialId(db, "plans", opts.delete);
|
|
18932
19272
|
if (!resolvedId) {
|
|
18933
|
-
console.error(
|
|
19273
|
+
console.error(chalk2.red(`Could not resolve plan ID: ${opts.delete}`));
|
|
18934
19274
|
process.exit(1);
|
|
18935
19275
|
}
|
|
18936
19276
|
const deleted = deletePlan(resolvedId);
|
|
18937
19277
|
if (globalOpts.json) {
|
|
18938
19278
|
output({ deleted }, true);
|
|
18939
19279
|
} else if (deleted) {
|
|
18940
|
-
console.log(
|
|
19280
|
+
console.log(chalk2.green("Plan deleted."));
|
|
18941
19281
|
} else {
|
|
18942
|
-
console.error(
|
|
19282
|
+
console.error(chalk2.red("Plan not found."));
|
|
18943
19283
|
process.exit(1);
|
|
18944
19284
|
}
|
|
18945
19285
|
return;
|
|
@@ -18948,7 +19288,7 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18948
19288
|
const db = getDatabase();
|
|
18949
19289
|
const resolvedId = resolvePartialId(db, "plans", opts.complete);
|
|
18950
19290
|
if (!resolvedId) {
|
|
18951
|
-
console.error(
|
|
19291
|
+
console.error(chalk2.red(`Could not resolve plan ID: ${opts.complete}`));
|
|
18952
19292
|
process.exit(1);
|
|
18953
19293
|
}
|
|
18954
19294
|
try {
|
|
@@ -18956,8 +19296,8 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18956
19296
|
if (globalOpts.json) {
|
|
18957
19297
|
output(plan, true);
|
|
18958
19298
|
} else {
|
|
18959
|
-
console.log(
|
|
18960
|
-
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}]`)}`);
|
|
18961
19301
|
}
|
|
18962
19302
|
} catch (e) {
|
|
18963
19303
|
handleError(e);
|
|
@@ -18970,14 +19310,14 @@ program2.command("plans").description("List and manage plans").option("--add <na
|
|
|
18970
19310
|
return;
|
|
18971
19311
|
}
|
|
18972
19312
|
if (plans.length === 0) {
|
|
18973
|
-
console.log(
|
|
19313
|
+
console.log(chalk2.dim("No plans found."));
|
|
18974
19314
|
return;
|
|
18975
19315
|
}
|
|
18976
|
-
console.log(
|
|
19316
|
+
console.log(chalk2.bold(`${plans.length} plan(s):
|
|
18977
19317
|
`));
|
|
18978
19318
|
for (const p of plans) {
|
|
18979
|
-
const desc = p.description ?
|
|
18980
|
-
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}`);
|
|
18981
19321
|
}
|
|
18982
19322
|
});
|
|
18983
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) => {
|
|
@@ -18985,7 +19325,7 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
18985
19325
|
const { createTemplate: createTemplate2, listTemplates: listTemplates2, deleteTemplate: deleteTemplate2, taskFromTemplate: taskFromTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
|
|
18986
19326
|
if (opts.add) {
|
|
18987
19327
|
if (!opts.title) {
|
|
18988
|
-
console.error(
|
|
19328
|
+
console.error(chalk2.red("--title is required with --add"));
|
|
18989
19329
|
process.exit(1);
|
|
18990
19330
|
}
|
|
18991
19331
|
const projectId = autoProject(globalOpts);
|
|
@@ -19000,7 +19340,7 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
19000
19340
|
if (globalOpts.json) {
|
|
19001
19341
|
output(template, true);
|
|
19002
19342
|
} else {
|
|
19003
|
-
console.log(
|
|
19343
|
+
console.log(chalk2.green(`Template created: ${template.id.slice(0, 8)} | ${template.name} | "${template.title_pattern}"`));
|
|
19004
19344
|
}
|
|
19005
19345
|
return;
|
|
19006
19346
|
}
|
|
@@ -19009,9 +19349,9 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
19009
19349
|
if (globalOpts.json) {
|
|
19010
19350
|
output({ deleted }, true);
|
|
19011
19351
|
} else if (deleted) {
|
|
19012
|
-
console.log(
|
|
19352
|
+
console.log(chalk2.green("Template deleted."));
|
|
19013
19353
|
} else {
|
|
19014
|
-
console.error(
|
|
19354
|
+
console.error(chalk2.red("Template not found."));
|
|
19015
19355
|
process.exit(1);
|
|
19016
19356
|
}
|
|
19017
19357
|
return;
|
|
@@ -19027,7 +19367,7 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
19027
19367
|
if (globalOpts.json) {
|
|
19028
19368
|
output(task, true);
|
|
19029
19369
|
} else {
|
|
19030
|
-
console.log(
|
|
19370
|
+
console.log(chalk2.green("Task created from template:"));
|
|
19031
19371
|
console.log(formatTaskLine(task));
|
|
19032
19372
|
}
|
|
19033
19373
|
} catch (e) {
|
|
@@ -19041,13 +19381,13 @@ program2.command("templates").description("List and manage task templates").opti
|
|
|
19041
19381
|
return;
|
|
19042
19382
|
}
|
|
19043
19383
|
if (templates.length === 0) {
|
|
19044
|
-
console.log(
|
|
19384
|
+
console.log(chalk2.dim("No templates."));
|
|
19045
19385
|
return;
|
|
19046
19386
|
}
|
|
19047
|
-
console.log(
|
|
19387
|
+
console.log(chalk2.bold(`${templates.length} template(s):
|
|
19048
19388
|
`));
|
|
19049
19389
|
for (const t of templates) {
|
|
19050
|
-
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)}`);
|
|
19051
19391
|
}
|
|
19052
19392
|
});
|
|
19053
19393
|
program2.command("comment <id> <text>").description("Add a comment to a task").action((id, text) => {
|
|
@@ -19062,7 +19402,7 @@ program2.command("comment <id> <text>").description("Add a comment to a task").a
|
|
|
19062
19402
|
if (globalOpts.json) {
|
|
19063
19403
|
output(comment, true);
|
|
19064
19404
|
} else {
|
|
19065
|
-
console.log(
|
|
19405
|
+
console.log(chalk2.green("Comment added."));
|
|
19066
19406
|
}
|
|
19067
19407
|
});
|
|
19068
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) => {
|
|
@@ -19087,10 +19427,10 @@ program2.command("search <query>").description("Search tasks").option("--status
|
|
|
19087
19427
|
return;
|
|
19088
19428
|
}
|
|
19089
19429
|
if (tasks.length === 0) {
|
|
19090
|
-
console.log(
|
|
19430
|
+
console.log(chalk2.dim(`No tasks matching "${query}".`));
|
|
19091
19431
|
return;
|
|
19092
19432
|
}
|
|
19093
|
-
console.log(
|
|
19433
|
+
console.log(chalk2.bold(`${tasks.length} result(s) for "${query}":
|
|
19094
19434
|
`));
|
|
19095
19435
|
for (const t of tasks) {
|
|
19096
19436
|
console.log(formatTaskLine(t));
|
|
@@ -19104,13 +19444,13 @@ program2.command("deps <id>").description("Manage task dependencies").option("--
|
|
|
19104
19444
|
try {
|
|
19105
19445
|
addDependency(resolvedId, depId);
|
|
19106
19446
|
} catch (e) {
|
|
19107
|
-
console.error(
|
|
19447
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
19108
19448
|
process.exit(1);
|
|
19109
19449
|
}
|
|
19110
19450
|
if (globalOpts.json) {
|
|
19111
19451
|
output({ task_id: resolvedId, depends_on: depId }, true);
|
|
19112
19452
|
} else {
|
|
19113
|
-
console.log(
|
|
19453
|
+
console.log(chalk2.green("Dependency added."));
|
|
19114
19454
|
}
|
|
19115
19455
|
} else if (opts.remove) {
|
|
19116
19456
|
const depId = resolveTaskId(opts.remove);
|
|
@@ -19118,12 +19458,12 @@ program2.command("deps <id>").description("Manage task dependencies").option("--
|
|
|
19118
19458
|
if (globalOpts.json) {
|
|
19119
19459
|
output({ removed }, true);
|
|
19120
19460
|
} else {
|
|
19121
|
-
console.log(removed ?
|
|
19461
|
+
console.log(removed ? chalk2.green("Dependency removed.") : chalk2.red("Dependency not found."));
|
|
19122
19462
|
}
|
|
19123
19463
|
} else {
|
|
19124
19464
|
const task = getTaskWithRelations(resolvedId);
|
|
19125
19465
|
if (!task) {
|
|
19126
|
-
console.error(
|
|
19466
|
+
console.error(chalk2.red("Task not found."));
|
|
19127
19467
|
process.exit(1);
|
|
19128
19468
|
}
|
|
19129
19469
|
if (globalOpts.json) {
|
|
@@ -19131,19 +19471,19 @@ program2.command("deps <id>").description("Manage task dependencies").option("--
|
|
|
19131
19471
|
return;
|
|
19132
19472
|
}
|
|
19133
19473
|
if (task.dependencies.length > 0) {
|
|
19134
|
-
console.log(
|
|
19474
|
+
console.log(chalk2.bold("Depends on:"));
|
|
19135
19475
|
for (const dep of task.dependencies) {
|
|
19136
19476
|
console.log(` ${formatTaskLine(dep)}`);
|
|
19137
19477
|
}
|
|
19138
19478
|
}
|
|
19139
19479
|
if (task.blocked_by.length > 0) {
|
|
19140
|
-
console.log(
|
|
19480
|
+
console.log(chalk2.bold("Blocks:"));
|
|
19141
19481
|
for (const b of task.blocked_by) {
|
|
19142
19482
|
console.log(` ${formatTaskLine(b)}`);
|
|
19143
19483
|
}
|
|
19144
19484
|
}
|
|
19145
19485
|
if (task.dependencies.length === 0 && task.blocked_by.length === 0) {
|
|
19146
|
-
console.log(
|
|
19486
|
+
console.log(chalk2.dim("No dependencies."));
|
|
19147
19487
|
}
|
|
19148
19488
|
}
|
|
19149
19489
|
});
|
|
@@ -19165,9 +19505,9 @@ program2.command("projects").description("List and manage projects").option("--a
|
|
|
19165
19505
|
if (globalOpts.json) {
|
|
19166
19506
|
output(project, true);
|
|
19167
19507
|
} else {
|
|
19168
|
-
console.log(
|
|
19508
|
+
console.log(chalk2.green(`Project registered: ${project.name} (${project.path})`));
|
|
19169
19509
|
if (project.task_list_id)
|
|
19170
|
-
console.log(
|
|
19510
|
+
console.log(chalk2.dim(` Task list: ${project.task_list_id}`));
|
|
19171
19511
|
}
|
|
19172
19512
|
return;
|
|
19173
19513
|
}
|
|
@@ -19177,14 +19517,14 @@ program2.command("projects").description("List and manage projects").option("--a
|
|
|
19177
19517
|
return;
|
|
19178
19518
|
}
|
|
19179
19519
|
if (projects.length === 0) {
|
|
19180
|
-
console.log(
|
|
19520
|
+
console.log(chalk2.dim("No projects registered."));
|
|
19181
19521
|
return;
|
|
19182
19522
|
}
|
|
19183
|
-
console.log(
|
|
19523
|
+
console.log(chalk2.bold(`${projects.length} project(s):
|
|
19184
19524
|
`));
|
|
19185
19525
|
for (const p of projects) {
|
|
19186
|
-
const taskList = p.task_list_id ?
|
|
19187
|
-
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}` : ""}`);
|
|
19188
19528
|
}
|
|
19189
19529
|
});
|
|
19190
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) => {
|
|
@@ -19197,7 +19537,7 @@ program2.command("extract <path>").description("Extract TODO/FIXME/HACK/BUG/XXX/
|
|
|
19197
19537
|
const db = getDatabase();
|
|
19198
19538
|
const id = resolvePartialId(db, "task_lists", opts.list);
|
|
19199
19539
|
if (!id) {
|
|
19200
|
-
console.error(
|
|
19540
|
+
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
19201
19541
|
process.exit(1);
|
|
19202
19542
|
}
|
|
19203
19543
|
return id;
|
|
@@ -19216,18 +19556,18 @@ program2.command("extract <path>").description("Extract TODO/FIXME/HACK/BUG/XXX/
|
|
|
19216
19556
|
if (globalOpts.json) {
|
|
19217
19557
|
console.log(JSON.stringify(opts.dryRun ? { comments: result.comments } : { tasks_created: result.tasks.length, skipped: result.skipped, comments: result.comments.length }, null, 2));
|
|
19218
19558
|
} else if (opts.dryRun) {
|
|
19219
|
-
console.log(
|
|
19559
|
+
console.log(chalk2.cyan(`Found ${result.comments.length} comment(s):
|
|
19220
19560
|
`));
|
|
19221
19561
|
for (const c of result.comments) {
|
|
19222
|
-
console.log(` ${
|
|
19223
|
-
console.log(` ${
|
|
19562
|
+
console.log(` ${chalk2.yellow(`[${c.tag}]`)} ${c.message}`);
|
|
19563
|
+
console.log(` ${chalk2.gray(`${c.file}:${c.line}`)}`);
|
|
19224
19564
|
}
|
|
19225
19565
|
} else {
|
|
19226
|
-
console.log(
|
|
19566
|
+
console.log(chalk2.green(`Created ${result.tasks.length} task(s)`));
|
|
19227
19567
|
if (result.skipped > 0) {
|
|
19228
|
-
console.log(
|
|
19568
|
+
console.log(chalk2.gray(`Skipped ${result.skipped} duplicate(s)`));
|
|
19229
19569
|
}
|
|
19230
|
-
console.log(
|
|
19570
|
+
console.log(chalk2.gray(`Total comments found: ${result.comments.length}`));
|
|
19231
19571
|
for (const t of result.tasks) {
|
|
19232
19572
|
console.log(formatTaskLine(t));
|
|
19233
19573
|
}
|
|
@@ -19277,7 +19617,7 @@ program2.command("sync").description("Sync tasks with an agent task list (Claude
|
|
|
19277
19617
|
const agent = opts.agent || "claude";
|
|
19278
19618
|
const taskListId = resolveTaskListId2(agent, opts.taskList, project?.task_list_id);
|
|
19279
19619
|
if (!taskListId) {
|
|
19280
|
-
console.error(
|
|
19620
|
+
console.error(chalk2.red(`Could not detect task list ID for ${agent}. Use --task-list <id> or set appropriate env vars.`));
|
|
19281
19621
|
process.exit(1);
|
|
19282
19622
|
}
|
|
19283
19623
|
result = syncWithAgent(agent, taskListId, projectId, direction, { prefer });
|
|
@@ -19287,14 +19627,14 @@ program2.command("sync").description("Sync tasks with an agent task list (Claude
|
|
|
19287
19627
|
return;
|
|
19288
19628
|
}
|
|
19289
19629
|
if (result.pulled > 0)
|
|
19290
|
-
console.log(
|
|
19630
|
+
console.log(chalk2.green(`Pulled ${result.pulled} task(s).`));
|
|
19291
19631
|
if (result.pushed > 0)
|
|
19292
|
-
console.log(
|
|
19632
|
+
console.log(chalk2.green(`Pushed ${result.pushed} task(s).`));
|
|
19293
19633
|
if (result.pulled === 0 && result.pushed === 0 && result.errors.length === 0) {
|
|
19294
|
-
console.log(
|
|
19634
|
+
console.log(chalk2.dim("Nothing to sync."));
|
|
19295
19635
|
}
|
|
19296
19636
|
for (const err of result.errors) {
|
|
19297
|
-
console.error(
|
|
19637
|
+
console.error(chalk2.red(` Error: ${err}`));
|
|
19298
19638
|
}
|
|
19299
19639
|
});
|
|
19300
19640
|
var hooks = program2.command("hooks").description("Manage Claude Code hook integration");
|
|
@@ -19305,9 +19645,9 @@ hooks.command("install").description("Install Claude Code hooks for auto-sync").
|
|
|
19305
19645
|
if (p)
|
|
19306
19646
|
todosBin = p;
|
|
19307
19647
|
} catch {}
|
|
19308
|
-
const hooksDir =
|
|
19309
|
-
if (!
|
|
19310
|
-
|
|
19648
|
+
const hooksDir = join11(process.cwd(), ".claude", "hooks");
|
|
19649
|
+
if (!existsSync9(hooksDir))
|
|
19650
|
+
mkdirSync5(hooksDir, { recursive: true });
|
|
19311
19651
|
const hookScript = `#!/usr/bin/env bash
|
|
19312
19652
|
# Auto-generated by: todos hooks install
|
|
19313
19653
|
# Syncs todos with Claude Code task list on tool use events.
|
|
@@ -19331,11 +19671,11 @@ esac
|
|
|
19331
19671
|
|
|
19332
19672
|
exit 0
|
|
19333
19673
|
`;
|
|
19334
|
-
const hookPath =
|
|
19335
|
-
|
|
19674
|
+
const hookPath = join11(hooksDir, "todos-sync.sh");
|
|
19675
|
+
writeFileSync5(hookPath, hookScript);
|
|
19336
19676
|
execSync2(`chmod +x "${hookPath}"`);
|
|
19337
|
-
console.log(
|
|
19338
|
-
const settingsPath =
|
|
19677
|
+
console.log(chalk2.green(`Hook script created: ${hookPath}`));
|
|
19678
|
+
const settingsPath = join11(process.cwd(), ".claude", "settings.json");
|
|
19339
19679
|
const settings = readJsonFile2(settingsPath);
|
|
19340
19680
|
if (!settings["hooks"]) {
|
|
19341
19681
|
settings["hooks"] = {};
|
|
@@ -19361,8 +19701,8 @@ exit 0
|
|
|
19361
19701
|
});
|
|
19362
19702
|
hooksConfig["PostToolUse"] = filtered;
|
|
19363
19703
|
writeJsonFile2(settingsPath, settings);
|
|
19364
|
-
console.log(
|
|
19365
|
-
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."));
|
|
19366
19706
|
});
|
|
19367
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) => {
|
|
19368
19708
|
if (opts.register) {
|
|
@@ -19382,37 +19722,37 @@ function getMcpBinaryPath() {
|
|
|
19382
19722
|
if (p)
|
|
19383
19723
|
return p;
|
|
19384
19724
|
} catch {}
|
|
19385
|
-
const bunBin =
|
|
19386
|
-
if (
|
|
19725
|
+
const bunBin = join11(HOME2, ".bun", "bin", "todos-mcp");
|
|
19726
|
+
if (existsSync9(bunBin))
|
|
19387
19727
|
return bunBin;
|
|
19388
19728
|
return "todos-mcp";
|
|
19389
19729
|
}
|
|
19390
19730
|
function readJsonFile2(path) {
|
|
19391
|
-
if (!
|
|
19731
|
+
if (!existsSync9(path))
|
|
19392
19732
|
return {};
|
|
19393
19733
|
try {
|
|
19394
|
-
return JSON.parse(
|
|
19734
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
19395
19735
|
} catch {
|
|
19396
19736
|
return {};
|
|
19397
19737
|
}
|
|
19398
19738
|
}
|
|
19399
19739
|
function writeJsonFile2(path, data) {
|
|
19400
19740
|
const dir = dirname4(path);
|
|
19401
|
-
if (!
|
|
19402
|
-
|
|
19403
|
-
|
|
19741
|
+
if (!existsSync9(dir))
|
|
19742
|
+
mkdirSync5(dir, { recursive: true });
|
|
19743
|
+
writeFileSync5(path, JSON.stringify(data, null, 2) + `
|
|
19404
19744
|
`);
|
|
19405
19745
|
}
|
|
19406
19746
|
function readTomlFile(path) {
|
|
19407
|
-
if (!
|
|
19747
|
+
if (!existsSync9(path))
|
|
19408
19748
|
return "";
|
|
19409
|
-
return
|
|
19749
|
+
return readFileSync6(path, "utf-8");
|
|
19410
19750
|
}
|
|
19411
19751
|
function writeTomlFile(path, content) {
|
|
19412
19752
|
const dir = dirname4(path);
|
|
19413
|
-
if (!
|
|
19414
|
-
|
|
19415
|
-
|
|
19753
|
+
if (!existsSync9(dir))
|
|
19754
|
+
mkdirSync5(dir, { recursive: true });
|
|
19755
|
+
writeFileSync5(path, content);
|
|
19416
19756
|
}
|
|
19417
19757
|
function registerClaude(binPath, global) {
|
|
19418
19758
|
const scope = global ? "user" : "project";
|
|
@@ -19420,24 +19760,24 @@ function registerClaude(binPath, global) {
|
|
|
19420
19760
|
try {
|
|
19421
19761
|
const { execSync: execSync3 } = __require("child_process");
|
|
19422
19762
|
execSync3(cmd, { stdio: "pipe" });
|
|
19423
|
-
console.log(
|
|
19763
|
+
console.log(chalk2.green(`Claude Code (${scope}): registered via 'claude mcp add'`));
|
|
19424
19764
|
} catch {
|
|
19425
|
-
console.log(
|
|
19426
|
-
console.log(
|
|
19765
|
+
console.log(chalk2.yellow(`Claude Code: could not auto-register. Run this command manually:`));
|
|
19766
|
+
console.log(chalk2.cyan(` ${cmd}`));
|
|
19427
19767
|
}
|
|
19428
19768
|
}
|
|
19429
19769
|
function unregisterClaude(_global) {
|
|
19430
19770
|
try {
|
|
19431
19771
|
const { execSync: execSync3 } = __require("child_process");
|
|
19432
19772
|
execSync3("claude mcp remove todos", { stdio: "pipe" });
|
|
19433
|
-
console.log(
|
|
19773
|
+
console.log(chalk2.green(`Claude Code: removed todos MCP server`));
|
|
19434
19774
|
} catch {
|
|
19435
|
-
console.log(
|
|
19436
|
-
console.log(
|
|
19775
|
+
console.log(chalk2.yellow(`Claude Code: could not auto-remove. Run manually:`));
|
|
19776
|
+
console.log(chalk2.cyan(" claude mcp remove todos"));
|
|
19437
19777
|
}
|
|
19438
19778
|
}
|
|
19439
19779
|
function registerCodex(binPath) {
|
|
19440
|
-
const configPath =
|
|
19780
|
+
const configPath = join11(HOME2, ".codex", "config.toml");
|
|
19441
19781
|
let content = readTomlFile(configPath);
|
|
19442
19782
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
19443
19783
|
const block = `
|
|
@@ -19448,19 +19788,19 @@ args = []
|
|
|
19448
19788
|
content = content.trimEnd() + `
|
|
19449
19789
|
` + block;
|
|
19450
19790
|
writeTomlFile(configPath, content);
|
|
19451
|
-
console.log(
|
|
19791
|
+
console.log(chalk2.green(`Codex CLI: registered in ${configPath}`));
|
|
19452
19792
|
}
|
|
19453
19793
|
function unregisterCodex() {
|
|
19454
|
-
const configPath =
|
|
19794
|
+
const configPath = join11(HOME2, ".codex", "config.toml");
|
|
19455
19795
|
let content = readTomlFile(configPath);
|
|
19456
19796
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
19457
|
-
console.log(
|
|
19797
|
+
console.log(chalk2.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
19458
19798
|
return;
|
|
19459
19799
|
}
|
|
19460
19800
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
19461
19801
|
writeTomlFile(configPath, content.trimEnd() + `
|
|
19462
19802
|
`);
|
|
19463
|
-
console.log(
|
|
19803
|
+
console.log(chalk2.green(`Codex CLI: unregistered from ${configPath}`));
|
|
19464
19804
|
}
|
|
19465
19805
|
function removeTomlBlock(content, blockName) {
|
|
19466
19806
|
const lines = content.split(`
|
|
@@ -19484,7 +19824,7 @@ function removeTomlBlock(content, blockName) {
|
|
|
19484
19824
|
`);
|
|
19485
19825
|
}
|
|
19486
19826
|
function registerGemini(binPath) {
|
|
19487
|
-
const configPath =
|
|
19827
|
+
const configPath = join11(HOME2, ".gemini", "settings.json");
|
|
19488
19828
|
const config = readJsonFile2(configPath);
|
|
19489
19829
|
if (!config["mcpServers"]) {
|
|
19490
19830
|
config["mcpServers"] = {};
|
|
@@ -19495,19 +19835,19 @@ function registerGemini(binPath) {
|
|
|
19495
19835
|
args: []
|
|
19496
19836
|
};
|
|
19497
19837
|
writeJsonFile2(configPath, config);
|
|
19498
|
-
console.log(
|
|
19838
|
+
console.log(chalk2.green(`Gemini CLI: registered in ${configPath}`));
|
|
19499
19839
|
}
|
|
19500
19840
|
function unregisterGemini() {
|
|
19501
|
-
const configPath =
|
|
19841
|
+
const configPath = join11(HOME2, ".gemini", "settings.json");
|
|
19502
19842
|
const config = readJsonFile2(configPath);
|
|
19503
19843
|
const servers = config["mcpServers"];
|
|
19504
19844
|
if (!servers || !("todos" in servers)) {
|
|
19505
|
-
console.log(
|
|
19845
|
+
console.log(chalk2.dim(`Gemini CLI: todos not found in ${configPath}`));
|
|
19506
19846
|
return;
|
|
19507
19847
|
}
|
|
19508
19848
|
delete servers["todos"];
|
|
19509
19849
|
writeJsonFile2(configPath, config);
|
|
19510
|
-
console.log(
|
|
19850
|
+
console.log(chalk2.green(`Gemini CLI: unregistered from ${configPath}`));
|
|
19511
19851
|
}
|
|
19512
19852
|
function registerMcp(agent, global) {
|
|
19513
19853
|
const agents = agent === "all" ? ["claude", "codex", "gemini"] : [agent];
|
|
@@ -19524,7 +19864,7 @@ function registerMcp(agent, global) {
|
|
|
19524
19864
|
registerGemini(binPath);
|
|
19525
19865
|
break;
|
|
19526
19866
|
default:
|
|
19527
|
-
console.error(
|
|
19867
|
+
console.error(chalk2.red(`Unknown agent: ${a}. Use: claude, codex, gemini, all`));
|
|
19528
19868
|
}
|
|
19529
19869
|
}
|
|
19530
19870
|
}
|
|
@@ -19542,7 +19882,7 @@ function unregisterMcp(agent, global) {
|
|
|
19542
19882
|
unregisterGemini();
|
|
19543
19883
|
break;
|
|
19544
19884
|
default:
|
|
19545
|
-
console.error(
|
|
19885
|
+
console.error(chalk2.red(`Unknown agent: ${a}. Use: claude, codex, gemini, all`));
|
|
19546
19886
|
}
|
|
19547
19887
|
}
|
|
19548
19888
|
}
|
|
@@ -19551,7 +19891,7 @@ program2.command("import <url>").description("Import a GitHub issue as a task").
|
|
|
19551
19891
|
const { parseGitHubUrl: parseGitHubUrl2, fetchGitHubIssue: fetchGitHubIssue2, issueToTask: issueToTask2 } = (init_github(), __toCommonJS(exports_github));
|
|
19552
19892
|
const parsed = parseGitHubUrl2(url);
|
|
19553
19893
|
if (!parsed) {
|
|
19554
|
-
console.error(
|
|
19894
|
+
console.error(chalk2.red("Invalid GitHub issue URL. Expected: https://github.com/owner/repo/issues/123"));
|
|
19555
19895
|
process.exit(1);
|
|
19556
19896
|
}
|
|
19557
19897
|
try {
|
|
@@ -19563,15 +19903,15 @@ program2.command("import <url>").description("Import a GitHub issue as a task").
|
|
|
19563
19903
|
output(task, true);
|
|
19564
19904
|
return;
|
|
19565
19905
|
}
|
|
19566
|
-
console.log(
|
|
19567
|
-
console.log(` ${
|
|
19568
|
-
console.log(` ${
|
|
19569
|
-
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}`);
|
|
19570
19910
|
} catch (e) {
|
|
19571
19911
|
if (e.message?.includes("gh")) {
|
|
19572
|
-
console.error(
|
|
19912
|
+
console.error(chalk2.red("GitHub CLI (gh) not found or not authenticated. Install: https://cli.github.com"));
|
|
19573
19913
|
} else {
|
|
19574
|
-
console.error(
|
|
19914
|
+
console.error(chalk2.red(`Import failed: ${e.message}`));
|
|
19575
19915
|
}
|
|
19576
19916
|
process.exit(1);
|
|
19577
19917
|
}
|
|
@@ -19591,7 +19931,7 @@ program2.command("link-commit <task-id> <sha>").description("Link a git commit t
|
|
|
19591
19931
|
output(commit, true);
|
|
19592
19932
|
return;
|
|
19593
19933
|
}
|
|
19594
|
-
console.log(
|
|
19934
|
+
console.log(chalk2.green(`Linked commit ${sha.slice(0, 7)} to task ${taskId}`));
|
|
19595
19935
|
});
|
|
19596
19936
|
var hookCmd = program2.command("hook").description("Manage git hooks for auto-linking commits to tasks");
|
|
19597
19937
|
hookCmd.command("install").description("Install post-commit hook that auto-links commits to tasks").action(() => {
|
|
@@ -19599,28 +19939,28 @@ hookCmd.command("install").description("Install post-commit hook that auto-links
|
|
|
19599
19939
|
try {
|
|
19600
19940
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
19601
19941
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
19602
|
-
const { existsSync:
|
|
19942
|
+
const { existsSync: existsSync10, readFileSync: readFileSync7, writeFileSync: writeFileSync6, chmodSync } = __require("fs");
|
|
19603
19943
|
const marker = "# todos-auto-link";
|
|
19604
|
-
if (
|
|
19605
|
-
const existing =
|
|
19944
|
+
if (existsSync10(hookPath)) {
|
|
19945
|
+
const existing = readFileSync7(hookPath, "utf-8");
|
|
19606
19946
|
if (existing.includes(marker)) {
|
|
19607
|
-
console.log(
|
|
19947
|
+
console.log(chalk2.yellow("Hook already installed."));
|
|
19608
19948
|
return;
|
|
19609
19949
|
}
|
|
19610
|
-
|
|
19950
|
+
writeFileSync6(hookPath, existing + `
|
|
19611
19951
|
${marker}
|
|
19612
19952
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
19613
19953
|
`);
|
|
19614
19954
|
} else {
|
|
19615
|
-
|
|
19955
|
+
writeFileSync6(hookPath, `#!/usr/bin/env bash
|
|
19616
19956
|
${marker}
|
|
19617
19957
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
19618
19958
|
`);
|
|
19619
19959
|
chmodSync(hookPath, 493);
|
|
19620
19960
|
}
|
|
19621
|
-
console.log(
|
|
19961
|
+
console.log(chalk2.green("Post-commit hook installed. Commits with task IDs (e.g. OPE-00042) will auto-link."));
|
|
19622
19962
|
} catch (e) {
|
|
19623
|
-
console.error(
|
|
19963
|
+
console.error(chalk2.red("Not in a git repository or hook install failed."));
|
|
19624
19964
|
process.exit(1);
|
|
19625
19965
|
}
|
|
19626
19966
|
});
|
|
@@ -19629,15 +19969,15 @@ hookCmd.command("uninstall").description("Remove the todos post-commit hook").ac
|
|
|
19629
19969
|
try {
|
|
19630
19970
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
19631
19971
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
19632
|
-
const { existsSync:
|
|
19972
|
+
const { existsSync: existsSync10, readFileSync: readFileSync7, writeFileSync: writeFileSync6 } = __require("fs");
|
|
19633
19973
|
const marker = "# todos-auto-link";
|
|
19634
|
-
if (!
|
|
19635
|
-
console.log(
|
|
19974
|
+
if (!existsSync10(hookPath)) {
|
|
19975
|
+
console.log(chalk2.dim("No post-commit hook found."));
|
|
19636
19976
|
return;
|
|
19637
19977
|
}
|
|
19638
|
-
const content =
|
|
19978
|
+
const content = readFileSync7(hookPath, "utf-8");
|
|
19639
19979
|
if (!content.includes(marker)) {
|
|
19640
|
-
console.log(
|
|
19980
|
+
console.log(chalk2.dim("Hook not managed by todos."));
|
|
19641
19981
|
return;
|
|
19642
19982
|
}
|
|
19643
19983
|
const cleaned = content.split(`
|
|
@@ -19646,12 +19986,12 @@ hookCmd.command("uninstall").description("Remove the todos post-commit hook").ac
|
|
|
19646
19986
|
if (cleaned === "#!/usr/bin/env bash" || cleaned === "") {
|
|
19647
19987
|
__require("fs").unlinkSync(hookPath);
|
|
19648
19988
|
} else {
|
|
19649
|
-
|
|
19989
|
+
writeFileSync6(hookPath, cleaned + `
|
|
19650
19990
|
`);
|
|
19651
19991
|
}
|
|
19652
|
-
console.log(
|
|
19992
|
+
console.log(chalk2.green("Post-commit hook removed."));
|
|
19653
19993
|
} catch (e) {
|
|
19654
|
-
console.error(
|
|
19994
|
+
console.error(chalk2.red("Not in a git repository or hook removal failed."));
|
|
19655
19995
|
process.exit(1);
|
|
19656
19996
|
}
|
|
19657
19997
|
});
|
|
@@ -19660,17 +20000,17 @@ program2.command("init <name>").description("Register an agent and get a short U
|
|
|
19660
20000
|
try {
|
|
19661
20001
|
const result = registerAgent({ name, description: opts.description });
|
|
19662
20002
|
if (isAgentConflict(result)) {
|
|
19663
|
-
console.error(
|
|
20003
|
+
console.error(chalk2.red("CONFLICT:"), result.message);
|
|
19664
20004
|
process.exit(1);
|
|
19665
20005
|
}
|
|
19666
20006
|
if (globalOpts.json) {
|
|
19667
20007
|
output(result, true);
|
|
19668
20008
|
} else {
|
|
19669
|
-
console.log(
|
|
19670
|
-
console.log(` ${
|
|
19671
|
-
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}`);
|
|
19672
20012
|
console.log(`
|
|
19673
|
-
Use ${
|
|
20013
|
+
Use ${chalk2.cyan(`--agent ${result.id}`)} on future commands.`);
|
|
19674
20014
|
}
|
|
19675
20015
|
} catch (e) {
|
|
19676
20016
|
handleError(e);
|
|
@@ -19680,51 +20020,51 @@ program2.command("heartbeat [agent]").description("Update last_seen_at to signal
|
|
|
19680
20020
|
const globalOpts = program2.opts();
|
|
19681
20021
|
const agentId = agent || globalOpts.agent;
|
|
19682
20022
|
if (!agentId) {
|
|
19683
|
-
console.error(
|
|
20023
|
+
console.error(chalk2.red("Agent ID required. Use --agent or pass as argument."));
|
|
19684
20024
|
process.exit(1);
|
|
19685
20025
|
}
|
|
19686
20026
|
const { updateAgentActivity: updateAgentActivity2, getAgent: getAgent2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
19687
20027
|
const a = getAgent2(agentId) || (init_agents(), __toCommonJS(exports_agents)).getAgentByName(agentId);
|
|
19688
20028
|
if (!a) {
|
|
19689
|
-
console.error(
|
|
20029
|
+
console.error(chalk2.red(`Agent not found: ${agentId}`));
|
|
19690
20030
|
process.exit(1);
|
|
19691
20031
|
}
|
|
19692
20032
|
updateAgentActivity2(a.id);
|
|
19693
20033
|
if (globalOpts.json) {
|
|
19694
20034
|
console.log(JSON.stringify({ agent_id: a.id, name: a.name, last_seen_at: new Date().toISOString() }));
|
|
19695
20035
|
} else {
|
|
19696
|
-
console.log(
|
|
20036
|
+
console.log(chalk2.green(`\u2665 ${a.name} (${a.id.slice(0, 8)}) \u2014 heartbeat sent`));
|
|
19697
20037
|
}
|
|
19698
20038
|
});
|
|
19699
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) => {
|
|
19700
20040
|
const globalOpts = program2.opts();
|
|
19701
20041
|
const agentId = agent || globalOpts.agent;
|
|
19702
20042
|
if (!agentId) {
|
|
19703
|
-
console.error(
|
|
20043
|
+
console.error(chalk2.red("Agent ID or name required. Use --agent or pass as argument."));
|
|
19704
20044
|
process.exit(1);
|
|
19705
20045
|
}
|
|
19706
20046
|
const { getAgent: getAgent2, getAgentByName: getAgentByName2 } = (init_agents(), __toCommonJS(exports_agents));
|
|
19707
20047
|
const a = getAgent2(agentId) || getAgentByName2(agentId);
|
|
19708
20048
|
if (!a) {
|
|
19709
|
-
console.error(
|
|
20049
|
+
console.error(chalk2.red(`Agent not found: ${agentId}`));
|
|
19710
20050
|
process.exit(1);
|
|
19711
20051
|
}
|
|
19712
20052
|
const released = releaseAgent(a.id, opts?.sessionId);
|
|
19713
20053
|
if (!released) {
|
|
19714
|
-
console.error(
|
|
20054
|
+
console.error(chalk2.red("Release denied: session_id does not match agent's current session."));
|
|
19715
20055
|
process.exit(1);
|
|
19716
20056
|
}
|
|
19717
20057
|
if (globalOpts.json) {
|
|
19718
20058
|
console.log(JSON.stringify({ agent_id: a.id, name: a.name, released: true }));
|
|
19719
20059
|
} else {
|
|
19720
|
-
console.log(
|
|
20060
|
+
console.log(chalk2.green(`\u2713 ${a.name} (${a.id}) released \u2014 name is now available.`));
|
|
19721
20061
|
}
|
|
19722
20062
|
});
|
|
19723
20063
|
program2.command("focus [project]").description("Focus on a project (or clear focus if no project given)").action((project) => {
|
|
19724
20064
|
const globalOpts = program2.opts();
|
|
19725
20065
|
const agentId = globalOpts.agent;
|
|
19726
20066
|
if (!agentId) {
|
|
19727
|
-
console.error(
|
|
20067
|
+
console.error(chalk2.red("Agent ID required. Use --agent."));
|
|
19728
20068
|
process.exit(1);
|
|
19729
20069
|
}
|
|
19730
20070
|
const db = getDatabase();
|
|
@@ -19733,10 +20073,10 @@ program2.command("focus [project]").description("Focus on a project (or clear fo
|
|
|
19733
20073
|
const p = getProjectByPath2(process.cwd(), db) || getProjectByName(project, db);
|
|
19734
20074
|
const projectId = p?.id || project;
|
|
19735
20075
|
db.run("UPDATE agents SET active_project_id = ? WHERE id = ? OR name = ?", [projectId, agentId, agentId]);
|
|
19736
|
-
console.log(
|
|
20076
|
+
console.log(chalk2.green(`Focused on: ${p?.name || projectId}`));
|
|
19737
20077
|
} else {
|
|
19738
20078
|
db.run("UPDATE agents SET active_project_id = NULL WHERE id = ? OR name = ?", [agentId, agentId]);
|
|
19739
|
-
console.log(
|
|
20079
|
+
console.log(chalk2.dim("Focus cleared."));
|
|
19740
20080
|
}
|
|
19741
20081
|
});
|
|
19742
20082
|
program2.command("agents").description("List registered agents").action(() => {
|
|
@@ -19748,11 +20088,11 @@ program2.command("agents").description("List registered agents").action(() => {
|
|
|
19748
20088
|
return;
|
|
19749
20089
|
}
|
|
19750
20090
|
if (agents.length === 0) {
|
|
19751
|
-
console.log(
|
|
20091
|
+
console.log(chalk2.dim("No agents registered. Use 'todos init <name>' to register."));
|
|
19752
20092
|
return;
|
|
19753
20093
|
}
|
|
19754
20094
|
for (const a of agents) {
|
|
19755
|
-
console.log(` ${
|
|
20095
|
+
console.log(` ${chalk2.cyan(a.id)} ${chalk2.bold(a.name)} ${chalk2.dim(a.last_seen_at)}`);
|
|
19756
20096
|
}
|
|
19757
20097
|
} catch (e) {
|
|
19758
20098
|
handleError(e);
|
|
@@ -19764,7 +20104,7 @@ program2.command("agent-update <name>").alias("agents-update").description("Upda
|
|
|
19764
20104
|
const { getAgentByName: findByName, updateAgent: doUpdate } = (init_agents(), __toCommonJS(exports_agents));
|
|
19765
20105
|
const agent = findByName(name);
|
|
19766
20106
|
if (!agent) {
|
|
19767
|
-
console.error(
|
|
20107
|
+
console.error(chalk2.red(`Agent not found: ${name}`));
|
|
19768
20108
|
process.exit(1);
|
|
19769
20109
|
}
|
|
19770
20110
|
const updates = {};
|
|
@@ -19778,11 +20118,11 @@ program2.command("agent-update <name>").alias("agents-update").description("Upda
|
|
|
19778
20118
|
if (globalOpts.json) {
|
|
19779
20119
|
output(updated, true);
|
|
19780
20120
|
} else {
|
|
19781
|
-
console.log(
|
|
20121
|
+
console.log(chalk2.green(`Updated agent: ${updated.name} (${updated.id.slice(0, 8)})`));
|
|
19782
20122
|
if (updated.description)
|
|
19783
|
-
console.log(
|
|
20123
|
+
console.log(chalk2.dim(` Description: ${updated.description}`));
|
|
19784
20124
|
if (updated.role)
|
|
19785
|
-
console.log(
|
|
20125
|
+
console.log(chalk2.dim(` Role: ${updated.role}`));
|
|
19786
20126
|
}
|
|
19787
20127
|
} catch (e) {
|
|
19788
20128
|
handleError(e);
|
|
@@ -19793,7 +20133,7 @@ program2.command("agent <name>").description("Show all info about an agent: task
|
|
|
19793
20133
|
const { getAgentByName: findByName } = (init_agents(), __toCommonJS(exports_agents));
|
|
19794
20134
|
const agent = findByName(name);
|
|
19795
20135
|
if (!agent) {
|
|
19796
|
-
console.error(
|
|
20136
|
+
console.error(chalk2.red(`Agent not found: ${name}`));
|
|
19797
20137
|
process.exit(1);
|
|
19798
20138
|
}
|
|
19799
20139
|
const byAssigned = listTasks({ assigned_to: agent.name });
|
|
@@ -19812,53 +20152,53 @@ program2.command("agent <name>").description("Show all info about an agent: task
|
|
|
19812
20152
|
const rate = allTasks.length > 0 ? Math.round(completed.length / allTasks.length * 100) : 0;
|
|
19813
20153
|
const lastSeenMs = Date.now() - new Date(agent.last_seen_at).getTime();
|
|
19814
20154
|
const lastSeenMins = Math.floor(lastSeenMs / 60000);
|
|
19815
|
-
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`);
|
|
19816
20156
|
const isOnline = lastSeenMins < 5;
|
|
19817
20157
|
if (opts.json || globalOpts.json) {
|
|
19818
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));
|
|
19819
20159
|
return;
|
|
19820
20160
|
}
|
|
19821
20161
|
console.log(`
|
|
19822
|
-
${isOnline ?
|
|
20162
|
+
${isOnline ? chalk2.green("\u25CF") : chalk2.dim("\u25CB")} ${chalk2.bold(agent.name)} ${chalk2.dim(`(${agent.id})`)} ${lastSeenStr}`);
|
|
19823
20163
|
if (agent.description)
|
|
19824
|
-
console.log(
|
|
20164
|
+
console.log(chalk2.dim(` ${agent.description}`));
|
|
19825
20165
|
if (agent.role)
|
|
19826
|
-
console.log(
|
|
20166
|
+
console.log(chalk2.dim(` Role: ${agent.role}`));
|
|
19827
20167
|
console.log();
|
|
19828
|
-
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`)}`);
|
|
19829
20169
|
console.log();
|
|
19830
20170
|
if (inProgress.length > 0) {
|
|
19831
|
-
console.log(
|
|
20171
|
+
console.log(chalk2.bold(" In progress:"));
|
|
19832
20172
|
for (const t of inProgress) {
|
|
19833
20173
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19834
|
-
const staleFlag = new Date(t.updated_at).getTime() < Date.now() - 30 * 60 * 1000 ?
|
|
19835
|
-
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}`);
|
|
19836
20176
|
}
|
|
19837
20177
|
console.log();
|
|
19838
20178
|
}
|
|
19839
20179
|
if (pending.length > 0) {
|
|
19840
|
-
console.log(
|
|
20180
|
+
console.log(chalk2.bold(` Pending (${pending.length}):`));
|
|
19841
20181
|
for (const t of pending.slice(0, 5)) {
|
|
19842
20182
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19843
|
-
const due = t.due_at ?
|
|
19844
|
-
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}`);
|
|
19845
20185
|
}
|
|
19846
20186
|
if (pending.length > 5)
|
|
19847
|
-
console.log(
|
|
20187
|
+
console.log(chalk2.dim(` ... and ${pending.length - 5} more`));
|
|
19848
20188
|
console.log();
|
|
19849
20189
|
}
|
|
19850
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);
|
|
19851
20191
|
if (recentDone.length > 0) {
|
|
19852
|
-
console.log(
|
|
20192
|
+
console.log(chalk2.bold(" Recently completed:"));
|
|
19853
20193
|
for (const t of recentDone) {
|
|
19854
20194
|
const id = t.short_id || t.id.slice(0, 8);
|
|
19855
|
-
const when = t.completed_at ?
|
|
19856
|
-
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}`);
|
|
19857
20197
|
}
|
|
19858
20198
|
console.log();
|
|
19859
20199
|
}
|
|
19860
20200
|
if (allTasks.length === 0) {
|
|
19861
|
-
console.log(
|
|
20201
|
+
console.log(chalk2.dim(" No tasks assigned to this agent."));
|
|
19862
20202
|
}
|
|
19863
20203
|
});
|
|
19864
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) => {
|
|
@@ -19868,14 +20208,14 @@ program2.command("org").description("Show agent org chart \u2014 who reports to
|
|
|
19868
20208
|
const [agentName, managerName] = opts.set.split("=");
|
|
19869
20209
|
const agent = getByName(agentName);
|
|
19870
20210
|
if (!agent) {
|
|
19871
|
-
console.error(
|
|
20211
|
+
console.error(chalk2.red(`Agent not found: ${agentName}`));
|
|
19872
20212
|
process.exit(1);
|
|
19873
20213
|
}
|
|
19874
20214
|
let managerId = null;
|
|
19875
20215
|
if (managerName) {
|
|
19876
20216
|
const manager = getByName(managerName);
|
|
19877
20217
|
if (!manager) {
|
|
19878
|
-
console.error(
|
|
20218
|
+
console.error(chalk2.red(`Manager not found: ${managerName}`));
|
|
19879
20219
|
process.exit(1);
|
|
19880
20220
|
}
|
|
19881
20221
|
managerId = manager.id;
|
|
@@ -19884,7 +20224,7 @@ program2.command("org").description("Show agent org chart \u2014 who reports to
|
|
|
19884
20224
|
if (globalOpts.json) {
|
|
19885
20225
|
output({ agent: agentName, reports_to: managerName || null }, true);
|
|
19886
20226
|
} else {
|
|
19887
|
-
console.log(
|
|
20227
|
+
console.log(chalk2.green(managerId ? `${agentName} \u2192 ${managerName}` : `${agentName} \u2192 (top-level)`));
|
|
19888
20228
|
}
|
|
19889
20229
|
return;
|
|
19890
20230
|
}
|
|
@@ -19894,15 +20234,15 @@ program2.command("org").description("Show agent org chart \u2014 who reports to
|
|
|
19894
20234
|
return;
|
|
19895
20235
|
}
|
|
19896
20236
|
if (tree.length === 0) {
|
|
19897
|
-
console.log(
|
|
20237
|
+
console.log(chalk2.dim("No agents registered."));
|
|
19898
20238
|
return;
|
|
19899
20239
|
}
|
|
19900
20240
|
function render2(nodes, indent = 0) {
|
|
19901
20241
|
for (const n of nodes) {
|
|
19902
20242
|
const prefix = " ".repeat(indent);
|
|
19903
|
-
const title = n.agent.title ?
|
|
19904
|
-
const level = n.agent.level ?
|
|
19905
|
-
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}`);
|
|
19906
20246
|
render2(n.reports, indent + 1);
|
|
19907
20247
|
}
|
|
19908
20248
|
}
|
|
@@ -19918,21 +20258,21 @@ program2.command("lists").aliases(["task-lists", "tl"]).description("List and ma
|
|
|
19918
20258
|
output(list, true);
|
|
19919
20259
|
return;
|
|
19920
20260
|
}
|
|
19921
|
-
console.log(
|
|
19922
|
-
console.log(` ${
|
|
19923
|
-
console.log(` ${
|
|
19924
|
-
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}`);
|
|
19925
20265
|
return;
|
|
19926
20266
|
}
|
|
19927
20267
|
if (opts.delete) {
|
|
19928
20268
|
const db = getDatabase();
|
|
19929
20269
|
const resolved = resolvePartialId(db, "task_lists", opts.delete);
|
|
19930
20270
|
if (!resolved) {
|
|
19931
|
-
console.error(
|
|
20271
|
+
console.error(chalk2.red("Task list not found"));
|
|
19932
20272
|
process.exit(1);
|
|
19933
20273
|
}
|
|
19934
20274
|
deleteTaskList(resolved);
|
|
19935
|
-
console.log(
|
|
20275
|
+
console.log(chalk2.green("Task list deleted."));
|
|
19936
20276
|
return;
|
|
19937
20277
|
}
|
|
19938
20278
|
const lists = listTaskLists(projectId);
|
|
@@ -19941,11 +20281,11 @@ program2.command("lists").aliases(["task-lists", "tl"]).description("List and ma
|
|
|
19941
20281
|
return;
|
|
19942
20282
|
}
|
|
19943
20283
|
if (lists.length === 0) {
|
|
19944
|
-
console.log(
|
|
20284
|
+
console.log(chalk2.dim("No task lists. Use 'todos lists --add <name>' to create one."));
|
|
19945
20285
|
return;
|
|
19946
20286
|
}
|
|
19947
20287
|
for (const l of lists) {
|
|
19948
|
-
console.log(` ${
|
|
20288
|
+
console.log(` ${chalk2.dim(l.id.slice(0, 8))} ${chalk2.bold(l.name)} ${chalk2.dim(`(${l.slug})`)}`);
|
|
19949
20289
|
}
|
|
19950
20290
|
} catch (e) {
|
|
19951
20291
|
handleError(e);
|
|
@@ -19956,20 +20296,20 @@ program2.command("upgrade").alias("self-update").description("Update todos to th
|
|
|
19956
20296
|
const currentVersion = getPackageVersion();
|
|
19957
20297
|
const res = await fetch("https://registry.npmjs.org/@hasna/todos/latest");
|
|
19958
20298
|
if (!res.ok) {
|
|
19959
|
-
console.error(
|
|
20299
|
+
console.error(chalk2.red("Failed to check for updates."));
|
|
19960
20300
|
process.exit(1);
|
|
19961
20301
|
}
|
|
19962
20302
|
const data = await res.json();
|
|
19963
20303
|
const latestVersion = data.version;
|
|
19964
|
-
console.log(` Current: ${
|
|
19965
|
-
console.log(` Latest: ${
|
|
20304
|
+
console.log(` Current: ${chalk2.dim(currentVersion)}`);
|
|
20305
|
+
console.log(` Latest: ${chalk2.green(latestVersion)}`);
|
|
19966
20306
|
if (currentVersion === latestVersion) {
|
|
19967
|
-
console.log(
|
|
20307
|
+
console.log(chalk2.green(`
|
|
19968
20308
|
Already up to date!`));
|
|
19969
20309
|
return;
|
|
19970
20310
|
}
|
|
19971
20311
|
if (opts.check) {
|
|
19972
|
-
console.log(
|
|
20312
|
+
console.log(chalk2.yellow(`
|
|
19973
20313
|
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
19974
20314
|
return;
|
|
19975
20315
|
}
|
|
@@ -19979,10 +20319,10 @@ Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
|
19979
20319
|
useBun = true;
|
|
19980
20320
|
} catch {}
|
|
19981
20321
|
const cmd = useBun ? "bun add -g @hasna/todos@latest" : "npm install -g @hasna/todos@latest";
|
|
19982
|
-
console.log(
|
|
20322
|
+
console.log(chalk2.dim(`
|
|
19983
20323
|
Running: ${cmd}`));
|
|
19984
20324
|
execSync2(cmd, { stdio: "inherit" });
|
|
19985
|
-
console.log(
|
|
20325
|
+
console.log(chalk2.green(`
|
|
19986
20326
|
Updated to ${latestVersion}!`));
|
|
19987
20327
|
} catch (e) {
|
|
19988
20328
|
handleError(e);
|
|
@@ -19990,7 +20330,7 @@ Updated to ${latestVersion}!`));
|
|
|
19990
20330
|
});
|
|
19991
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) => {
|
|
19992
20332
|
const globalOpts = program2.opts();
|
|
19993
|
-
const configPath =
|
|
20333
|
+
const configPath = join11(process.env["HOME"] || "~", ".todos", "config.json");
|
|
19994
20334
|
if (opts.get) {
|
|
19995
20335
|
const config2 = loadConfig();
|
|
19996
20336
|
const keys = opts.get.split(".");
|
|
@@ -20001,7 +20341,7 @@ program2.command("config").description("View or update configuration").option("-
|
|
|
20001
20341
|
if (globalOpts.json) {
|
|
20002
20342
|
output({ key: opts.get, value }, true);
|
|
20003
20343
|
} else {
|
|
20004
|
-
console.log(value !== undefined ? JSON.stringify(value, null, 2) :
|
|
20344
|
+
console.log(value !== undefined ? JSON.stringify(value, null, 2) : chalk2.dim("(not set)"));
|
|
20005
20345
|
}
|
|
20006
20346
|
return;
|
|
20007
20347
|
}
|
|
@@ -20016,7 +20356,7 @@ program2.command("config").description("View or update configuration").option("-
|
|
|
20016
20356
|
}
|
|
20017
20357
|
let config2 = {};
|
|
20018
20358
|
try {
|
|
20019
|
-
config2 = JSON.parse(
|
|
20359
|
+
config2 = JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
20020
20360
|
} catch {}
|
|
20021
20361
|
const keys = key.split(".");
|
|
20022
20362
|
let obj = config2;
|
|
@@ -20027,13 +20367,13 @@ program2.command("config").description("View or update configuration").option("-
|
|
|
20027
20367
|
}
|
|
20028
20368
|
obj[keys[keys.length - 1]] = parsedValue;
|
|
20029
20369
|
const dir = dirname4(configPath);
|
|
20030
|
-
if (!
|
|
20031
|
-
|
|
20032
|
-
|
|
20370
|
+
if (!existsSync9(dir))
|
|
20371
|
+
mkdirSync5(dir, { recursive: true });
|
|
20372
|
+
writeFileSync5(configPath, JSON.stringify(config2, null, 2));
|
|
20033
20373
|
if (globalOpts.json) {
|
|
20034
20374
|
output({ key, value: parsedValue }, true);
|
|
20035
20375
|
} else {
|
|
20036
|
-
console.log(
|
|
20376
|
+
console.log(chalk2.green(`Set ${key} = ${JSON.stringify(parsedValue)}`));
|
|
20037
20377
|
}
|
|
20038
20378
|
return;
|
|
20039
20379
|
}
|
|
@@ -20074,25 +20414,25 @@ program2.command("watch").description("Live-updating task list (refreshes every
|
|
|
20074
20414
|
counts[t.status] = (counts[t.status] || 0) + 1;
|
|
20075
20415
|
process.stdout.write("\x1B[2J\x1B[0f");
|
|
20076
20416
|
const now2 = new Date().toLocaleTimeString();
|
|
20077
|
-
console.log(
|
|
20417
|
+
console.log(chalk2.bold(`todos watch`) + chalk2.dim(` \u2014 ${now2} \u2014 refreshing every ${opts.interval}s \u2014 Ctrl+C to stop
|
|
20078
20418
|
`));
|
|
20079
20419
|
const parts = [
|
|
20080
|
-
`total: ${
|
|
20081
|
-
`pending: ${
|
|
20082
|
-
`in_progress: ${
|
|
20083
|
-
`completed: ${
|
|
20084
|
-
`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))}`
|
|
20085
20425
|
];
|
|
20086
20426
|
console.log(parts.join(" ") + `
|
|
20087
20427
|
`);
|
|
20088
20428
|
if (tasks.length === 0) {
|
|
20089
|
-
console.log(
|
|
20429
|
+
console.log(chalk2.dim("No matching tasks."));
|
|
20090
20430
|
return;
|
|
20091
20431
|
}
|
|
20092
20432
|
for (const t of tasks) {
|
|
20093
20433
|
console.log(formatTaskLine(t));
|
|
20094
20434
|
}
|
|
20095
|
-
console.log(
|
|
20435
|
+
console.log(chalk2.dim(`
|
|
20096
20436
|
${tasks.length} task(s) shown`));
|
|
20097
20437
|
}
|
|
20098
20438
|
render2();
|
|
@@ -20112,19 +20452,19 @@ program2.command("stream").description("Subscribe to real-time task events via S
|
|
|
20112
20452
|
params.set("events", opts.events);
|
|
20113
20453
|
const url = `${baseUrl}/api/tasks/stream?${params}`;
|
|
20114
20454
|
const eventColors = {
|
|
20115
|
-
"task.created":
|
|
20116
|
-
"task.started":
|
|
20117
|
-
"task.completed":
|
|
20118
|
-
"task.failed":
|
|
20119
|
-
"task.assigned":
|
|
20120
|
-
"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
|
|
20121
20461
|
};
|
|
20122
|
-
console.log(
|
|
20462
|
+
console.log(chalk2.dim(`Connecting to ${url} \u2014 Ctrl+C to stop
|
|
20123
20463
|
`));
|
|
20124
20464
|
try {
|
|
20125
20465
|
const resp = await fetch(url);
|
|
20126
20466
|
if (!resp.ok || !resp.body) {
|
|
20127
|
-
console.error(
|
|
20467
|
+
console.error(chalk2.red(`Failed to connect: ${resp.status}`));
|
|
20128
20468
|
process.exit(1);
|
|
20129
20469
|
}
|
|
20130
20470
|
const reader = resp.body.getReader();
|
|
@@ -20150,11 +20490,11 @@ program2.command("stream").description("Subscribe to real-time task events via S
|
|
|
20150
20490
|
if (opts.json) {
|
|
20151
20491
|
console.log(JSON.stringify({ event: eventName, ...data }));
|
|
20152
20492
|
} else {
|
|
20153
|
-
const colorFn = eventColors[eventName] ||
|
|
20493
|
+
const colorFn = eventColors[eventName] || chalk2.white;
|
|
20154
20494
|
const ts = new Date(data.timestamp || Date.now()).toLocaleTimeString();
|
|
20155
20495
|
const taskId = data.task_id ? data.task_id.slice(0, 8) : "";
|
|
20156
20496
|
const agentInfo = data.agent_id ? ` [${data.agent_id}]` : "";
|
|
20157
|
-
console.log(`${
|
|
20497
|
+
console.log(`${chalk2.dim(ts)} ${colorFn(eventName.padEnd(25))} ${taskId}${agentInfo}`);
|
|
20158
20498
|
}
|
|
20159
20499
|
} catch {}
|
|
20160
20500
|
eventName = "";
|
|
@@ -20162,8 +20502,8 @@ program2.command("stream").description("Subscribe to real-time task events via S
|
|
|
20162
20502
|
}
|
|
20163
20503
|
}
|
|
20164
20504
|
} catch (e) {
|
|
20165
|
-
console.error(
|
|
20166
|
-
console.error(
|
|
20505
|
+
console.error(chalk2.red(`Connection error: ${e instanceof Error ? e.message : e}`));
|
|
20506
|
+
console.error(chalk2.dim("Is `todos serve` running?"));
|
|
20167
20507
|
process.exit(1);
|
|
20168
20508
|
}
|
|
20169
20509
|
});
|
|
@@ -20184,29 +20524,29 @@ program2.command("blame <file>").description("Show which tasks/agents touched a
|
|
|
20184
20524
|
output({ file: filePath, task_files: taskFiles, commits: commitRows }, true);
|
|
20185
20525
|
return;
|
|
20186
20526
|
}
|
|
20187
|
-
console.log(
|
|
20527
|
+
console.log(chalk2.bold(`
|
|
20188
20528
|
Blame: ${filePath}
|
|
20189
20529
|
`));
|
|
20190
20530
|
if (taskFiles.length > 0) {
|
|
20191
|
-
console.log(
|
|
20531
|
+
console.log(chalk2.bold("Task File Links:"));
|
|
20192
20532
|
for (const tf of taskFiles) {
|
|
20193
20533
|
const task = getTask2(tf.task_id, db);
|
|
20194
20534
|
const title = task ? task.title : "unknown";
|
|
20195
20535
|
const sid = task?.short_id || tf.task_id.slice(0, 8);
|
|
20196
|
-
console.log(` ${
|
|
20536
|
+
console.log(` ${chalk2.cyan(sid)} ${title} \u2014 ${chalk2.dim(tf.role || "file")} ${chalk2.dim(tf.updated_at)}`);
|
|
20197
20537
|
}
|
|
20198
20538
|
}
|
|
20199
20539
|
if (commitRows.length > 0) {
|
|
20200
|
-
console.log(
|
|
20540
|
+
console.log(chalk2.bold(`
|
|
20201
20541
|
Commit Links (${commitRows.length}):`));
|
|
20202
20542
|
for (const c of commitRows) {
|
|
20203
20543
|
const sid = c.short_id || c.task_id.slice(0, 8);
|
|
20204
|
-
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 || "")}`);
|
|
20205
20545
|
}
|
|
20206
20546
|
}
|
|
20207
20547
|
if (taskFiles.length === 0 && commitRows.length === 0) {
|
|
20208
|
-
console.log(
|
|
20209
|
-
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."));
|
|
20210
20550
|
}
|
|
20211
20551
|
console.log();
|
|
20212
20552
|
});
|
|
@@ -20225,17 +20565,17 @@ program2.command("next").description("Show the best pending task to work on next
|
|
|
20225
20565
|
filters.project_id = opts.project;
|
|
20226
20566
|
const task = getNextTask(opts.agent, Object.keys(filters).length ? filters : undefined, db);
|
|
20227
20567
|
if (!task) {
|
|
20228
|
-
console.log(
|
|
20568
|
+
console.log(chalk2.dim("No tasks available."));
|
|
20229
20569
|
return;
|
|
20230
20570
|
}
|
|
20231
20571
|
if (opts.json) {
|
|
20232
20572
|
console.log(JSON.stringify(task, null, 2));
|
|
20233
20573
|
return;
|
|
20234
20574
|
}
|
|
20235
|
-
console.log(
|
|
20236
|
-
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}`);
|
|
20237
20577
|
if (task.description)
|
|
20238
|
-
console.log(
|
|
20578
|
+
console.log(chalk2.dim(` ${task.description.slice(0, 100)}`));
|
|
20239
20579
|
});
|
|
20240
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) => {
|
|
20241
20581
|
const db = getDatabase();
|
|
@@ -20244,28 +20584,28 @@ program2.command("claim <agent>").description("Atomically claim the best pending
|
|
|
20244
20584
|
filters.project_id = opts.project;
|
|
20245
20585
|
const task = claimNextTask(agent, Object.keys(filters).length ? filters : undefined, db);
|
|
20246
20586
|
if (!task) {
|
|
20247
|
-
console.log(
|
|
20587
|
+
console.log(chalk2.dim("No tasks available to claim."));
|
|
20248
20588
|
return;
|
|
20249
20589
|
}
|
|
20250
20590
|
if (opts.json) {
|
|
20251
20591
|
console.log(JSON.stringify(task, null, 2));
|
|
20252
20592
|
return;
|
|
20253
20593
|
}
|
|
20254
|
-
console.log(
|
|
20594
|
+
console.log(chalk2.green(`Claimed: ${task.short_id || task.id.slice(0, 8)} | ${task.priority} | ${task.title}`));
|
|
20255
20595
|
});
|
|
20256
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) => {
|
|
20257
20597
|
const globalOpts = program2.opts();
|
|
20258
20598
|
const { stealTask: stealTask2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
20259
20599
|
const task = stealTask2(agent, { stale_minutes: parseInt(opts.staleMinutes, 10), project_id: opts.project });
|
|
20260
20600
|
if (!task) {
|
|
20261
|
-
console.log(
|
|
20601
|
+
console.log(chalk2.dim("No stale tasks available to steal."));
|
|
20262
20602
|
return;
|
|
20263
20603
|
}
|
|
20264
20604
|
if (globalOpts.json) {
|
|
20265
20605
|
output(task, true);
|
|
20266
20606
|
return;
|
|
20267
20607
|
}
|
|
20268
|
-
console.log(
|
|
20608
|
+
console.log(chalk2.green(`Stolen: ${task.short_id || task.id.slice(0, 8)} | ${task.priority} | ${task.title}`));
|
|
20269
20609
|
});
|
|
20270
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) => {
|
|
20271
20611
|
const db = getDatabase();
|
|
@@ -20277,24 +20617,24 @@ program2.command("status").description("Show full project health snapshot").opti
|
|
|
20277
20617
|
console.log(JSON.stringify(s, null, 2));
|
|
20278
20618
|
return;
|
|
20279
20619
|
}
|
|
20280
|
-
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`);
|
|
20281
20621
|
if (s.stale_count > 0)
|
|
20282
|
-
console.log(
|
|
20622
|
+
console.log(chalk2.red(`\u26A0\uFE0F ${s.stale_count} stale tasks (stuck in_progress)`));
|
|
20283
20623
|
if (s.overdue_recurring > 0)
|
|
20284
|
-
console.log(
|
|
20624
|
+
console.log(chalk2.yellow(`\uD83D\uDD01 ${s.overdue_recurring} overdue recurring`));
|
|
20285
20625
|
if (s.active_work.length > 0) {
|
|
20286
|
-
console.log(
|
|
20626
|
+
console.log(chalk2.bold(`
|
|
20287
20627
|
Active:`));
|
|
20288
20628
|
for (const w of s.active_work.slice(0, 5)) {
|
|
20289
20629
|
const id = w.short_id || w.id.slice(0, 8);
|
|
20290
|
-
console.log(` ${
|
|
20630
|
+
console.log(` ${chalk2.cyan(id)} | ${w.assigned_to || w.locked_by || "?"} | ${w.title}`);
|
|
20291
20631
|
}
|
|
20292
20632
|
}
|
|
20293
20633
|
if (s.next_task) {
|
|
20294
|
-
console.log(
|
|
20634
|
+
console.log(chalk2.bold(`
|
|
20295
20635
|
Next up:`));
|
|
20296
20636
|
const t = s.next_task;
|
|
20297
|
-
console.log(` ${
|
|
20637
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${chalk2.yellow(t.priority)} ${t.title}`);
|
|
20298
20638
|
}
|
|
20299
20639
|
});
|
|
20300
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) => {
|
|
@@ -20305,60 +20645,60 @@ program2.command("recap").description("Show what happened in the last N hours \u
|
|
|
20305
20645
|
output(recap, true);
|
|
20306
20646
|
return;
|
|
20307
20647
|
}
|
|
20308
|
-
console.log(
|
|
20648
|
+
console.log(chalk2.bold(`
|
|
20309
20649
|
Recap \u2014 last ${recap.hours} hours (since ${new Date(recap.since).toLocaleString()})
|
|
20310
20650
|
`));
|
|
20311
20651
|
if (recap.completed.length > 0) {
|
|
20312
|
-
console.log(
|
|
20652
|
+
console.log(chalk2.green.bold(`Completed (${recap.completed.length}):`));
|
|
20313
20653
|
for (const t of recap.completed) {
|
|
20314
20654
|
const id = t.short_id || t.id.slice(0, 8);
|
|
20315
20655
|
const dur = t.duration_minutes != null ? ` (${t.duration_minutes}m)` : "";
|
|
20316
|
-
console.log(` ${
|
|
20656
|
+
console.log(` ${chalk2.green("\u2713")} ${chalk2.cyan(id)} ${t.title}${dur}${t.assigned_to ? ` \u2014 ${chalk2.dim(t.assigned_to)}` : ""}`);
|
|
20317
20657
|
}
|
|
20318
20658
|
} else {
|
|
20319
|
-
console.log(
|
|
20659
|
+
console.log(chalk2.dim("No tasks completed in this period."));
|
|
20320
20660
|
}
|
|
20321
20661
|
if (recap.in_progress.length > 0) {
|
|
20322
|
-
console.log(
|
|
20662
|
+
console.log(chalk2.blue.bold(`
|
|
20323
20663
|
In Progress (${recap.in_progress.length}):`));
|
|
20324
20664
|
for (const t of recap.in_progress) {
|
|
20325
20665
|
const id = t.short_id || t.id.slice(0, 8);
|
|
20326
|
-
console.log(` ${
|
|
20666
|
+
console.log(` ${chalk2.blue("\u2192")} ${chalk2.cyan(id)} ${t.title}${t.assigned_to ? ` \u2014 ${chalk2.dim(t.assigned_to)}` : ""}`);
|
|
20327
20667
|
}
|
|
20328
20668
|
}
|
|
20329
20669
|
if (recap.blocked.length > 0) {
|
|
20330
|
-
console.log(
|
|
20670
|
+
console.log(chalk2.red.bold(`
|
|
20331
20671
|
Blocked (${recap.blocked.length}):`));
|
|
20332
20672
|
for (const t of recap.blocked) {
|
|
20333
20673
|
const id = t.short_id || t.id.slice(0, 8);
|
|
20334
|
-
console.log(` ${
|
|
20674
|
+
console.log(` ${chalk2.red("\u2717")} ${chalk2.cyan(id)} ${t.title}`);
|
|
20335
20675
|
}
|
|
20336
20676
|
}
|
|
20337
20677
|
if (recap.stale.length > 0) {
|
|
20338
|
-
console.log(
|
|
20678
|
+
console.log(chalk2.yellow.bold(`
|
|
20339
20679
|
Stale (${recap.stale.length}):`));
|
|
20340
20680
|
for (const t of recap.stale) {
|
|
20341
20681
|
const id = t.short_id || t.id.slice(0, 8);
|
|
20342
20682
|
const ago = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
20343
|
-
console.log(` ${
|
|
20683
|
+
console.log(` ${chalk2.yellow("!")} ${chalk2.cyan(id)} ${t.title} \u2014 last update ${ago}m ago`);
|
|
20344
20684
|
}
|
|
20345
20685
|
}
|
|
20346
20686
|
if (recap.created.length > 0) {
|
|
20347
|
-
console.log(
|
|
20687
|
+
console.log(chalk2.dim.bold(`
|
|
20348
20688
|
Created (${recap.created.length}):`));
|
|
20349
20689
|
for (const t of recap.created.slice(0, 10)) {
|
|
20350
20690
|
const id = t.short_id || t.id.slice(0, 8);
|
|
20351
|
-
console.log(` ${
|
|
20691
|
+
console.log(` ${chalk2.dim("+")} ${chalk2.cyan(id)} ${t.title}`);
|
|
20352
20692
|
}
|
|
20353
20693
|
if (recap.created.length > 10)
|
|
20354
|
-
console.log(
|
|
20694
|
+
console.log(chalk2.dim(` ... and ${recap.created.length - 10} more`));
|
|
20355
20695
|
}
|
|
20356
20696
|
if (recap.agents.length > 0) {
|
|
20357
|
-
console.log(
|
|
20697
|
+
console.log(chalk2.bold(`
|
|
20358
20698
|
Agents:`));
|
|
20359
20699
|
for (const a of recap.agents) {
|
|
20360
20700
|
const seen = Math.round((Date.now() - new Date(a.last_seen_at).getTime()) / 60000);
|
|
20361
|
-
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`);
|
|
20362
20702
|
}
|
|
20363
20703
|
}
|
|
20364
20704
|
console.log();
|
|
@@ -20373,7 +20713,7 @@ program2.command("standup").description("Generate standup notes \u2014 completed
|
|
|
20373
20713
|
output(recap, true);
|
|
20374
20714
|
return;
|
|
20375
20715
|
}
|
|
20376
|
-
console.log(
|
|
20716
|
+
console.log(chalk2.bold(`
|
|
20377
20717
|
Standup \u2014 since ${sinceDate.toLocaleDateString()}
|
|
20378
20718
|
`));
|
|
20379
20719
|
const byAgent = new Map;
|
|
@@ -20384,29 +20724,29 @@ Standup \u2014 since ${sinceDate.toLocaleDateString()}
|
|
|
20384
20724
|
byAgent.get(agent).push(t);
|
|
20385
20725
|
}
|
|
20386
20726
|
if (byAgent.size > 0) {
|
|
20387
|
-
console.log(
|
|
20727
|
+
console.log(chalk2.green.bold("Done:"));
|
|
20388
20728
|
for (const [agent, tasks] of byAgent) {
|
|
20389
|
-
console.log(` ${
|
|
20729
|
+
console.log(` ${chalk2.cyan(agent)}:`);
|
|
20390
20730
|
for (const t of tasks) {
|
|
20391
20731
|
const dur = t.duration_minutes != null ? ` (${t.duration_minutes}m)` : "";
|
|
20392
|
-
console.log(` ${
|
|
20732
|
+
console.log(` ${chalk2.green("\u2713")} ${t.short_id || t.id.slice(0, 8)} ${t.title}${dur}`);
|
|
20393
20733
|
}
|
|
20394
20734
|
}
|
|
20395
20735
|
} else {
|
|
20396
|
-
console.log(
|
|
20736
|
+
console.log(chalk2.dim("Nothing completed."));
|
|
20397
20737
|
}
|
|
20398
20738
|
if (recap.in_progress.length > 0) {
|
|
20399
|
-
console.log(
|
|
20739
|
+
console.log(chalk2.blue.bold(`
|
|
20400
20740
|
In Progress:`));
|
|
20401
20741
|
for (const t of recap.in_progress) {
|
|
20402
|
-
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)}` : ""}`);
|
|
20403
20743
|
}
|
|
20404
20744
|
}
|
|
20405
20745
|
if (recap.blocked.length > 0) {
|
|
20406
|
-
console.log(
|
|
20746
|
+
console.log(chalk2.red.bold(`
|
|
20407
20747
|
Blocked:`));
|
|
20408
20748
|
for (const t of recap.blocked) {
|
|
20409
|
-
console.log(` ${
|
|
20749
|
+
console.log(` ${chalk2.red("\u2717")} ${t.short_id || t.id.slice(0, 8)} ${t.title}`);
|
|
20410
20750
|
}
|
|
20411
20751
|
}
|
|
20412
20752
|
console.log();
|
|
@@ -20415,7 +20755,7 @@ program2.command("fail <id>").description("Mark a task as failed with optional r
|
|
|
20415
20755
|
const db = getDatabase();
|
|
20416
20756
|
const resolvedId = resolvePartialId(db, "tasks", id);
|
|
20417
20757
|
if (!resolvedId) {
|
|
20418
|
-
console.error(
|
|
20758
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
20419
20759
|
process.exit(1);
|
|
20420
20760
|
}
|
|
20421
20761
|
const result = failTask(resolvedId, opts.agent, opts.reason, { retry: opts.retry }, db);
|
|
@@ -20423,11 +20763,11 @@ program2.command("fail <id>").description("Mark a task as failed with optional r
|
|
|
20423
20763
|
console.log(JSON.stringify(result, null, 2));
|
|
20424
20764
|
return;
|
|
20425
20765
|
}
|
|
20426
|
-
console.log(
|
|
20766
|
+
console.log(chalk2.red(`Failed: ${result.task.short_id || result.task.id.slice(0, 8)} | ${result.task.title}`));
|
|
20427
20767
|
if (opts.reason)
|
|
20428
|
-
console.log(
|
|
20768
|
+
console.log(chalk2.dim(`Reason: ${opts.reason}`));
|
|
20429
20769
|
if (result.retryTask)
|
|
20430
|
-
console.log(
|
|
20770
|
+
console.log(chalk2.yellow(`Retry created: ${result.retryTask.short_id || result.retryTask.id.slice(0, 8)} | ${result.retryTask.title}`));
|
|
20431
20771
|
});
|
|
20432
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) => {
|
|
20433
20773
|
const db = getDatabase();
|
|
@@ -20440,14 +20780,14 @@ program2.command("active").description("Show all currently in-progress tasks").o
|
|
|
20440
20780
|
return;
|
|
20441
20781
|
}
|
|
20442
20782
|
if (work.length === 0) {
|
|
20443
|
-
console.log(
|
|
20783
|
+
console.log(chalk2.dim("No active work."));
|
|
20444
20784
|
return;
|
|
20445
20785
|
}
|
|
20446
|
-
console.log(
|
|
20786
|
+
console.log(chalk2.bold(`Active work (${work.length}):`));
|
|
20447
20787
|
for (const w of work) {
|
|
20448
20788
|
const id = w.short_id || w.id.slice(0, 8);
|
|
20449
20789
|
const agent = w.assigned_to || w.locked_by || "unassigned";
|
|
20450
|
-
console.log(` ${
|
|
20790
|
+
console.log(` ${chalk2.cyan(id)} | ${chalk2.yellow(w.priority)} | ${agent.padEnd(12)} | ${w.title}`);
|
|
20451
20791
|
}
|
|
20452
20792
|
});
|
|
20453
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) => {
|
|
@@ -20461,14 +20801,14 @@ program2.command("stale").description("Find tasks stuck in_progress with no rece
|
|
|
20461
20801
|
return;
|
|
20462
20802
|
}
|
|
20463
20803
|
if (tasks.length === 0) {
|
|
20464
|
-
console.log(
|
|
20804
|
+
console.log(chalk2.dim("No stale tasks."));
|
|
20465
20805
|
return;
|
|
20466
20806
|
}
|
|
20467
|
-
console.log(
|
|
20807
|
+
console.log(chalk2.bold(`Stale tasks (${tasks.length}):`));
|
|
20468
20808
|
for (const t of tasks) {
|
|
20469
20809
|
const id = t.short_id || t.id.slice(0, 8);
|
|
20470
20810
|
const staleMin = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
|
|
20471
|
-
console.log(` ${
|
|
20811
|
+
console.log(` ${chalk2.cyan(id)} | ${t.locked_by || t.assigned_to || "?"} | ${t.title} ${chalk2.dim(`(${staleMin}min stale)`)}`);
|
|
20472
20812
|
}
|
|
20473
20813
|
});
|
|
20474
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) => {
|
|
@@ -20484,17 +20824,17 @@ program2.command("redistribute <agent>").description("Release stale in-progress
|
|
|
20484
20824
|
console.log(JSON.stringify(result, null, 2));
|
|
20485
20825
|
return;
|
|
20486
20826
|
}
|
|
20487
|
-
console.log(
|
|
20827
|
+
console.log(chalk2.bold(`Released ${result.released.length} stale task(s).`));
|
|
20488
20828
|
for (const t of result.released) {
|
|
20489
20829
|
const id = t.short_id || t.id.slice(0, 8);
|
|
20490
|
-
console.log(` ${
|
|
20830
|
+
console.log(` ${chalk2.yellow("released")} ${chalk2.cyan(id)} ${t.title}`);
|
|
20491
20831
|
}
|
|
20492
20832
|
if (result.claimed) {
|
|
20493
20833
|
const id = result.claimed.short_id || result.claimed.id.slice(0, 8);
|
|
20494
|
-
console.log(
|
|
20495
|
-
Claimed: ${
|
|
20834
|
+
console.log(chalk2.green(`
|
|
20835
|
+
Claimed: ${chalk2.cyan(id)} ${result.claimed.title}`));
|
|
20496
20836
|
} else {
|
|
20497
|
-
console.log(
|
|
20837
|
+
console.log(chalk2.dim(`
|
|
20498
20838
|
No task claimed (nothing available).`));
|
|
20499
20839
|
}
|
|
20500
20840
|
});
|
|
@@ -20504,7 +20844,7 @@ program2.command("assign <id> <agent>").description("Assign a task to an agent")
|
|
|
20504
20844
|
const db = getDatabase();
|
|
20505
20845
|
const task = getTask(resolvedId, db);
|
|
20506
20846
|
if (!task) {
|
|
20507
|
-
console.error(
|
|
20847
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
20508
20848
|
process.exit(1);
|
|
20509
20849
|
}
|
|
20510
20850
|
try {
|
|
@@ -20513,7 +20853,7 @@ program2.command("assign <id> <agent>").description("Assign a task to an agent")
|
|
|
20513
20853
|
console.log(JSON.stringify(updated));
|
|
20514
20854
|
return;
|
|
20515
20855
|
}
|
|
20516
|
-
console.log(
|
|
20856
|
+
console.log(chalk2.green(`Assigned to ${agent}: ${formatTaskLine(updated)}`));
|
|
20517
20857
|
} catch {
|
|
20518
20858
|
handleError(new Error("Failed to assign"));
|
|
20519
20859
|
}
|
|
@@ -20524,7 +20864,7 @@ program2.command("unassign <id>").description("Remove task assignment").option("
|
|
|
20524
20864
|
const db = getDatabase();
|
|
20525
20865
|
const task = getTask(resolvedId, db);
|
|
20526
20866
|
if (!task) {
|
|
20527
|
-
console.error(
|
|
20867
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
20528
20868
|
process.exit(1);
|
|
20529
20869
|
}
|
|
20530
20870
|
try {
|
|
@@ -20533,7 +20873,7 @@ program2.command("unassign <id>").description("Remove task assignment").option("
|
|
|
20533
20873
|
console.log(JSON.stringify(updated));
|
|
20534
20874
|
return;
|
|
20535
20875
|
}
|
|
20536
|
-
console.log(
|
|
20876
|
+
console.log(chalk2.green(`Unassigned: ${formatTaskLine(updated)}`));
|
|
20537
20877
|
} catch {
|
|
20538
20878
|
handleError(new Error("Failed to unassign"));
|
|
20539
20879
|
}
|
|
@@ -20544,7 +20884,7 @@ program2.command("tag <id> <tag>").description("Add a tag to a task").option("--
|
|
|
20544
20884
|
const db = getDatabase();
|
|
20545
20885
|
const task = getTask(resolvedId, db);
|
|
20546
20886
|
if (!task) {
|
|
20547
|
-
console.error(
|
|
20887
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
20548
20888
|
process.exit(1);
|
|
20549
20889
|
}
|
|
20550
20890
|
const newTags = [...new Set([...task.tags, tag])];
|
|
@@ -20554,7 +20894,7 @@ program2.command("tag <id> <tag>").description("Add a tag to a task").option("--
|
|
|
20554
20894
|
console.log(JSON.stringify(updated));
|
|
20555
20895
|
return;
|
|
20556
20896
|
}
|
|
20557
|
-
console.log(
|
|
20897
|
+
console.log(chalk2.green(`Tagged [${tag}]: ${formatTaskLine(updated)}`));
|
|
20558
20898
|
} catch {
|
|
20559
20899
|
handleError(new Error("Failed to tag"));
|
|
20560
20900
|
}
|
|
@@ -20565,7 +20905,7 @@ program2.command("untag <id> <tag>").description("Remove a tag from a task").opt
|
|
|
20565
20905
|
const db = getDatabase();
|
|
20566
20906
|
const task = getTask(resolvedId, db);
|
|
20567
20907
|
if (!task) {
|
|
20568
|
-
console.error(
|
|
20908
|
+
console.error(chalk2.red(`Task not found: ${id}`));
|
|
20569
20909
|
process.exit(1);
|
|
20570
20910
|
}
|
|
20571
20911
|
const newTags = task.tags.filter((t) => t !== tag);
|
|
@@ -20575,7 +20915,7 @@ program2.command("untag <id> <tag>").description("Remove a tag from a task").opt
|
|
|
20575
20915
|
console.log(JSON.stringify(updated));
|
|
20576
20916
|
return;
|
|
20577
20917
|
}
|
|
20578
|
-
console.log(
|
|
20918
|
+
console.log(chalk2.green(`Untagged [${tag}]: ${formatTaskLine(updated)}`));
|
|
20579
20919
|
} catch {
|
|
20580
20920
|
handleError(new Error("Failed to untag"));
|
|
20581
20921
|
}
|
|
@@ -20591,7 +20931,7 @@ program2.command("pin <id>").description("Escalate task to critical priority").o
|
|
|
20591
20931
|
console.log(JSON.stringify(updated));
|
|
20592
20932
|
return;
|
|
20593
20933
|
}
|
|
20594
|
-
console.log(
|
|
20934
|
+
console.log(chalk2.red(`\uD83D\uDCCC Pinned (critical): ${formatTaskLine(updated)}`));
|
|
20595
20935
|
} catch {
|
|
20596
20936
|
handleError(new Error("Failed to pin"));
|
|
20597
20937
|
}
|
|
@@ -20678,19 +21018,19 @@ program2.command("doctor").description("Diagnose common task data issues").optio
|
|
|
20678
21018
|
console.log(JSON.stringify({ issues, ok: !issues.some((i) => i.severity === "error") }));
|
|
20679
21019
|
return;
|
|
20680
21020
|
}
|
|
20681
|
-
console.log(
|
|
21021
|
+
console.log(chalk2.bold(`todos doctor
|
|
20682
21022
|
`));
|
|
20683
21023
|
for (const issue of issues) {
|
|
20684
|
-
const icon = issue.severity === "error" ?
|
|
21024
|
+
const icon = issue.severity === "error" ? chalk2.red("\u2717") : issue.severity === "warn" ? chalk2.yellow("\u26A0") : chalk2.green("\u2713");
|
|
20685
21025
|
console.log(` ${icon} ${issue.message}`);
|
|
20686
21026
|
}
|
|
20687
21027
|
const errors2 = issues.filter((i) => i.severity === "error").length;
|
|
20688
21028
|
const warns = issues.filter((i) => i.severity === "warn").length;
|
|
20689
21029
|
if (errors2 === 0 && warns === 0)
|
|
20690
|
-
console.log(
|
|
21030
|
+
console.log(chalk2.green(`
|
|
20691
21031
|
All clear.`));
|
|
20692
21032
|
else
|
|
20693
|
-
console.log(
|
|
21033
|
+
console.log(chalk2[errors2 > 0 ? "red" : "yellow"](`
|
|
20694
21034
|
${errors2} error(s), ${warns} warning(s). Run with --fix to auto-resolve where possible.`));
|
|
20695
21035
|
});
|
|
20696
21036
|
program2.command("health").description("Check todos system health \u2014 database, config, connectivity").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -20705,7 +21045,7 @@ program2.command("health").description("Check todos system health \u2014 databas
|
|
|
20705
21045
|
try {
|
|
20706
21046
|
size = `${(statSync3(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
20707
21047
|
} catch {}
|
|
20708
|
-
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)}` });
|
|
20709
21049
|
} catch (e) {
|
|
20710
21050
|
checks.push({ name: "Database", ok: false, message: e instanceof Error ? e.message : "Failed" });
|
|
20711
21051
|
}
|
|
@@ -20738,15 +21078,15 @@ program2.command("health").description("Check todos system health \u2014 databas
|
|
|
20738
21078
|
console.log(JSON.stringify({ ok, checks }));
|
|
20739
21079
|
return;
|
|
20740
21080
|
}
|
|
20741
|
-
console.log(
|
|
21081
|
+
console.log(chalk2.bold(`todos health
|
|
20742
21082
|
`));
|
|
20743
21083
|
for (const c of checks) {
|
|
20744
|
-
const icon = c.ok ?
|
|
21084
|
+
const icon = c.ok ? chalk2.green("\u2713") : chalk2.yellow("\u26A0");
|
|
20745
21085
|
console.log(` ${icon} ${c.name.padEnd(14)} ${c.message}`);
|
|
20746
21086
|
}
|
|
20747
21087
|
const allOk = checks.every((c) => c.ok);
|
|
20748
21088
|
console.log(`
|
|
20749
|
-
${allOk ?
|
|
21089
|
+
${allOk ? chalk2.green("All checks passed.") : chalk2.yellow("Some checks need attention.")}`);
|
|
20750
21090
|
});
|
|
20751
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) => {
|
|
20752
21092
|
const globalOpts = program2.opts();
|
|
@@ -20795,15 +21135,15 @@ program2.command("report").description("Analytics report: task activity, complet
|
|
|
20795
21135
|
if (sparkline)
|
|
20796
21136
|
lines.push(`| Activity | \`${sparkline}\` |`);
|
|
20797
21137
|
} else {
|
|
20798
|
-
lines.push(
|
|
21138
|
+
lines.push(chalk2.bold(`todos report \u2014 last ${days} day${days !== 1 ? "s" : ""}`));
|
|
20799
21139
|
lines.push("");
|
|
20800
|
-
lines.push(` Total: ${
|
|
20801
|
-
lines.push(` Changed: ${
|
|
20802
|
-
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)`);
|
|
20803
21143
|
if (failed.length > 0)
|
|
20804
|
-
lines.push(` Failed: ${
|
|
21144
|
+
lines.push(` Failed: ${chalk2.red(String(failed.length))}`);
|
|
20805
21145
|
if (sparkline)
|
|
20806
|
-
lines.push(` Activity: ${
|
|
21146
|
+
lines.push(` Activity: ${chalk2.dim(sparkline)}`);
|
|
20807
21147
|
if (Object.keys(byAgent).length > 0) {
|
|
20808
21148
|
lines.push(` By agent: ${Object.entries(byAgent).map(([a, n]) => `${a}=${n}`).join(" ")}`);
|
|
20809
21149
|
}
|
|
@@ -20827,21 +21167,21 @@ program2.command("today").description("Show task activity from today").option("-
|
|
|
20827
21167
|
console.log(JSON.stringify({ date: start.toISOString().slice(0, 10), completed, started, changed: other }));
|
|
20828
21168
|
return;
|
|
20829
21169
|
}
|
|
20830
|
-
console.log(
|
|
21170
|
+
console.log(chalk2.bold(`Today \u2014 ${start.toISOString().slice(0, 10)}
|
|
20831
21171
|
`));
|
|
20832
21172
|
if (completed.length > 0) {
|
|
20833
|
-
console.log(
|
|
21173
|
+
console.log(chalk2.green(` \u2713 Completed (${completed.length}):`));
|
|
20834
21174
|
for (const t of completed)
|
|
20835
|
-
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}`) : ""}`);
|
|
20836
21176
|
}
|
|
20837
21177
|
if (started.length > 0) {
|
|
20838
|
-
console.log(
|
|
21178
|
+
console.log(chalk2.blue(`
|
|
20839
21179
|
\u25B6 Started (${started.length}):`));
|
|
20840
21180
|
for (const t of started)
|
|
20841
|
-
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}`) : ""}`);
|
|
20842
21182
|
}
|
|
20843
21183
|
if (completed.length === 0 && started.length === 0)
|
|
20844
|
-
console.log(
|
|
21184
|
+
console.log(chalk2.dim(" No activity today."));
|
|
20845
21185
|
});
|
|
20846
21186
|
program2.command("yesterday").description("Show task activity from yesterday").option("--json", "Output as JSON").action(async (opts) => {
|
|
20847
21187
|
const globalOpts = program2.opts();
|
|
@@ -20860,21 +21200,21 @@ program2.command("yesterday").description("Show task activity from yesterday").o
|
|
|
20860
21200
|
console.log(JSON.stringify({ date: start.toISOString().slice(0, 10), completed, started }));
|
|
20861
21201
|
return;
|
|
20862
21202
|
}
|
|
20863
|
-
console.log(
|
|
21203
|
+
console.log(chalk2.bold(`Yesterday \u2014 ${start.toISOString().slice(0, 10)}
|
|
20864
21204
|
`));
|
|
20865
21205
|
if (completed.length > 0) {
|
|
20866
|
-
console.log(
|
|
21206
|
+
console.log(chalk2.green(` \u2713 Completed (${completed.length}):`));
|
|
20867
21207
|
for (const t of completed)
|
|
20868
|
-
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}`) : ""}`);
|
|
20869
21209
|
}
|
|
20870
21210
|
if (started.length > 0) {
|
|
20871
|
-
console.log(
|
|
21211
|
+
console.log(chalk2.blue(`
|
|
20872
21212
|
\u25B6 Started (${started.length}):`));
|
|
20873
21213
|
for (const t of started)
|
|
20874
|
-
console.log(` ${
|
|
21214
|
+
console.log(` ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}`);
|
|
20875
21215
|
}
|
|
20876
21216
|
if (completed.length === 0 && started.length === 0)
|
|
20877
|
-
console.log(
|
|
21217
|
+
console.log(chalk2.dim(" No activity yesterday."));
|
|
20878
21218
|
});
|
|
20879
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) => {
|
|
20880
21220
|
const globalOpts = program2.opts();
|
|
@@ -20909,23 +21249,23 @@ program2.command("mine").description("Show tasks assigned to you, grouped by sta
|
|
|
20909
21249
|
}
|
|
20910
21250
|
const statusOrder = ["in_progress", "pending", "blocked", "completed", "failed", "cancelled"];
|
|
20911
21251
|
const statusIcons2 = { in_progress: "\u25B6", pending: "\u25CB", blocked: "\u2298", completed: "\u2713", failed: "\u2717", cancelled: "\u2014" };
|
|
20912
|
-
const statusColors5 = { in_progress:
|
|
20913
|
-
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):
|
|
20914
21254
|
`));
|
|
20915
21255
|
for (const status of statusOrder) {
|
|
20916
21256
|
const group = groups[status];
|
|
20917
21257
|
if (!group || group.length === 0)
|
|
20918
21258
|
continue;
|
|
20919
|
-
const color = statusColors5[status] ||
|
|
21259
|
+
const color = statusColors5[status] || chalk2.white;
|
|
20920
21260
|
const icon = statusIcons2[status] || "?";
|
|
20921
21261
|
console.log(color(` ${icon} ${status.replace("_", " ")} (${group.length}):`));
|
|
20922
21262
|
for (const t of group) {
|
|
20923
|
-
const priority = t.priority === "critical" || t.priority === "high" ?
|
|
20924
|
-
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}`);
|
|
20925
21265
|
}
|
|
20926
21266
|
}
|
|
20927
21267
|
if (tasks.length === 0)
|
|
20928
|
-
console.log(
|
|
21268
|
+
console.log(chalk2.dim(` No tasks assigned to ${agent}.`));
|
|
20929
21269
|
});
|
|
20930
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) => {
|
|
20931
21271
|
const globalOpts = program2.opts();
|
|
@@ -20947,15 +21287,15 @@ program2.command("blocked").description("Show tasks blocked by incomplete depend
|
|
|
20947
21287
|
return;
|
|
20948
21288
|
}
|
|
20949
21289
|
if (blockedTasks.length === 0) {
|
|
20950
|
-
console.log(
|
|
21290
|
+
console.log(chalk2.green(" No blocked tasks!"));
|
|
20951
21291
|
return;
|
|
20952
21292
|
}
|
|
20953
|
-
console.log(
|
|
21293
|
+
console.log(chalk2.bold(`Blocked (${blockedTasks.length}):
|
|
20954
21294
|
`));
|
|
20955
21295
|
for (const { task, blockers } of blockedTasks) {
|
|
20956
|
-
console.log(` ${
|
|
21296
|
+
console.log(` ${chalk2.cyan(task.short_id || task.id.slice(0, 8))} ${task.title}`);
|
|
20957
21297
|
for (const bl of blockers) {
|
|
20958
|
-
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}]`)}`);
|
|
20959
21299
|
}
|
|
20960
21300
|
}
|
|
20961
21301
|
});
|
|
@@ -20969,16 +21309,16 @@ program2.command("overdue").description("Show tasks past their due date").option
|
|
|
20969
21309
|
return;
|
|
20970
21310
|
}
|
|
20971
21311
|
if (tasks.length === 0) {
|
|
20972
|
-
console.log(
|
|
21312
|
+
console.log(chalk2.green(" No overdue tasks!"));
|
|
20973
21313
|
return;
|
|
20974
21314
|
}
|
|
20975
|
-
console.log(
|
|
21315
|
+
console.log(chalk2.bold.red(`Overdue (${tasks.length}):
|
|
20976
21316
|
`));
|
|
20977
21317
|
for (const t of tasks) {
|
|
20978
21318
|
const dueDate = t.due_at.slice(0, 10);
|
|
20979
21319
|
const daysOverdue = Math.floor((Date.now() - new Date(t.due_at).getTime()) / 86400000);
|
|
20980
|
-
const urgency = daysOverdue > 7 ?
|
|
20981
|
-
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})`)}`);
|
|
20982
21322
|
}
|
|
20983
21323
|
});
|
|
20984
21324
|
program2.command("week").description("Show task activity from the past 7 days").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -21013,8 +21353,8 @@ program2.command("week").description("Show task activity from the past 7 days").
|
|
|
21013
21353
|
}
|
|
21014
21354
|
const totalCompleted = tasks.filter((t) => t.status === "completed").length;
|
|
21015
21355
|
const totalStarted = tasks.filter((t) => t.status === "in_progress").length;
|
|
21016
|
-
console.log(
|
|
21017
|
-
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
|
|
21018
21358
|
`));
|
|
21019
21359
|
const sortedDays = Object.keys(days).sort().reverse();
|
|
21020
21360
|
for (const day of sortedDays) {
|
|
@@ -21025,14 +21365,14 @@ program2.command("week").description("Show task activity from the past 7 days").
|
|
|
21025
21365
|
if (completed.length === 0 && started.length === 0)
|
|
21026
21366
|
continue;
|
|
21027
21367
|
const weekday = new Date(day + "T12:00:00").toLocaleDateString("en-US", { weekday: "short" });
|
|
21028
|
-
console.log(
|
|
21368
|
+
console.log(chalk2.bold(` ${weekday} ${day}`));
|
|
21029
21369
|
for (const t of completed)
|
|
21030
|
-
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}`) : ""}`);
|
|
21031
21371
|
for (const t of started)
|
|
21032
|
-
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}`) : ""}`);
|
|
21033
21373
|
}
|
|
21034
21374
|
if (tasks.length === 0)
|
|
21035
|
-
console.log(
|
|
21375
|
+
console.log(chalk2.dim(" No activity this week."));
|
|
21036
21376
|
});
|
|
21037
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) => {
|
|
21038
21378
|
const globalOpts = program2.opts();
|
|
@@ -21060,20 +21400,20 @@ program2.command("burndown").description("Show task completion velocity over the
|
|
|
21060
21400
|
}
|
|
21061
21401
|
const maxVal = Math.max(...dayStats.map((d) => Math.max(d.completed, d.created)), 1);
|
|
21062
21402
|
const barWidth = 30;
|
|
21063
|
-
console.log(
|
|
21403
|
+
console.log(chalk2.bold("Burndown (last " + numDays + ` days):
|
|
21064
21404
|
`));
|
|
21065
|
-
console.log(
|
|
21405
|
+
console.log(chalk2.dim(" Date Done New Failed Chart"));
|
|
21066
21406
|
for (const day of dayStats) {
|
|
21067
21407
|
const weekday = new Date(day.date + "T12:00:00").toLocaleDateString("en-US", { weekday: "short" });
|
|
21068
|
-
const completedBar =
|
|
21069
|
-
const createdBar =
|
|
21070
|
-
const failed = day.failed > 0 ?
|
|
21071
|
-
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}`);
|
|
21072
21412
|
}
|
|
21073
21413
|
const totalCompleted = dayStats.reduce((s, d) => s + d.completed, 0);
|
|
21074
21414
|
const totalCreated = dayStats.reduce((s, d) => s + d.created, 0);
|
|
21075
21415
|
const velocity = (totalCompleted / numDays).toFixed(1);
|
|
21076
|
-
console.log(
|
|
21416
|
+
console.log(chalk2.dim(`
|
|
21077
21417
|
Velocity: ${velocity}/day \xB7 ${totalCompleted} done \xB7 ${totalCreated} created`));
|
|
21078
21418
|
});
|
|
21079
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) => {
|
|
@@ -21086,38 +21426,38 @@ program2.command("log").description("Show recent task activity log (git-log styl
|
|
|
21086
21426
|
return;
|
|
21087
21427
|
}
|
|
21088
21428
|
if (entries.length === 0) {
|
|
21089
|
-
console.log(
|
|
21429
|
+
console.log(chalk2.dim(" No activity yet."));
|
|
21090
21430
|
return;
|
|
21091
21431
|
}
|
|
21092
21432
|
const actionIcons = {
|
|
21093
|
-
create:
|
|
21094
|
-
start:
|
|
21095
|
-
complete:
|
|
21096
|
-
fail:
|
|
21097
|
-
update:
|
|
21098
|
-
approve:
|
|
21099
|
-
lock:
|
|
21100
|
-
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")
|
|
21101
21441
|
};
|
|
21102
21442
|
let lastDate = "";
|
|
21103
21443
|
for (const e of entries) {
|
|
21104
21444
|
const date = e.created_at.slice(0, 10);
|
|
21105
21445
|
const time = e.created_at.slice(11, 16);
|
|
21106
21446
|
if (date !== lastDate) {
|
|
21107
|
-
console.log(
|
|
21447
|
+
console.log(chalk2.bold(`
|
|
21108
21448
|
${date}`));
|
|
21109
21449
|
lastDate = date;
|
|
21110
21450
|
}
|
|
21111
|
-
const icon = actionIcons[e.action] ||
|
|
21112
|
-
const agent = e.agent_id ?
|
|
21113
|
-
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));
|
|
21114
21454
|
let detail = "";
|
|
21115
21455
|
if (e.field && e.old_value && e.new_value) {
|
|
21116
|
-
detail =
|
|
21456
|
+
detail = chalk2.dim(` ${e.field}: ${e.old_value} \u2192 ${e.new_value}`);
|
|
21117
21457
|
} else if (e.field && e.new_value) {
|
|
21118
|
-
detail =
|
|
21458
|
+
detail = chalk2.dim(` ${e.field}: ${e.new_value}`);
|
|
21119
21459
|
}
|
|
21120
|
-
console.log(` ${
|
|
21460
|
+
console.log(` ${chalk2.dim(time)} ${icon} ${e.action.padEnd(8)} ${taskRef}${detail}${agent}`);
|
|
21121
21461
|
}
|
|
21122
21462
|
});
|
|
21123
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) => {
|
|
@@ -21142,15 +21482,15 @@ program2.command("ready").description("Show all tasks ready to be claimed (pendi
|
|
|
21142
21482
|
return;
|
|
21143
21483
|
}
|
|
21144
21484
|
if (limited.length === 0) {
|
|
21145
|
-
console.log(
|
|
21485
|
+
console.log(chalk2.dim(" No tasks ready to claim."));
|
|
21146
21486
|
return;
|
|
21147
21487
|
}
|
|
21148
|
-
console.log(
|
|
21488
|
+
console.log(chalk2.bold(`Ready to claim (${ready.length}${ready.length > limited.length ? `, showing ${limited.length}` : ""}):
|
|
21149
21489
|
`));
|
|
21150
21490
|
for (const t of limited) {
|
|
21151
|
-
const pri = t.priority === "critical" ?
|
|
21152
|
-
const due = t.due_at ?
|
|
21153
|
-
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}`);
|
|
21154
21494
|
}
|
|
21155
21495
|
});
|
|
21156
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) => {
|
|
@@ -21177,41 +21517,41 @@ program2.command("sprint").description("Sprint dashboard: in-progress, next up,
|
|
|
21177
21517
|
console.log(JSON.stringify({ in_progress: inProgress, next_up: nextUp, blocked, overdue }));
|
|
21178
21518
|
return;
|
|
21179
21519
|
}
|
|
21180
|
-
console.log(
|
|
21520
|
+
console.log(chalk2.bold(`Sprint Dashboard
|
|
21181
21521
|
`));
|
|
21182
|
-
console.log(
|
|
21522
|
+
console.log(chalk2.blue(` \u25B6 In Progress (${inProgress.length}):`));
|
|
21183
21523
|
if (inProgress.length === 0)
|
|
21184
|
-
console.log(
|
|
21524
|
+
console.log(chalk2.dim(" (none)"));
|
|
21185
21525
|
for (const t of inProgress) {
|
|
21186
|
-
const agent = t.assigned_to ?
|
|
21187
|
-
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}`);
|
|
21188
21528
|
}
|
|
21189
|
-
console.log(
|
|
21529
|
+
console.log(chalk2.white(`
|
|
21190
21530
|
\u25CB Next Up (${nextUp.length}):`));
|
|
21191
21531
|
if (nextUp.length === 0)
|
|
21192
|
-
console.log(
|
|
21532
|
+
console.log(chalk2.dim(" (none)"));
|
|
21193
21533
|
for (const t of nextUp) {
|
|
21194
|
-
const pri = t.priority === "critical" ?
|
|
21195
|
-
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}`);
|
|
21196
21536
|
}
|
|
21197
21537
|
if (blocked.length > 0) {
|
|
21198
|
-
console.log(
|
|
21538
|
+
console.log(chalk2.red(`
|
|
21199
21539
|
\u2298 Blocked (${blocked.length}):`));
|
|
21200
21540
|
for (const { task, blockers } of blocked) {
|
|
21201
|
-
console.log(` ${
|
|
21541
|
+
console.log(` ${chalk2.cyan(task.short_id || task.id.slice(0, 8))} ${task.title}`);
|
|
21202
21542
|
for (const bl of blockers)
|
|
21203
|
-
console.log(` ${
|
|
21543
|
+
console.log(` ${chalk2.dim("\u2190 " + (bl.short_id || bl.id.slice(0, 8)) + " " + bl.title)} ${chalk2.yellow(`[${bl.status}]`)}`);
|
|
21204
21544
|
}
|
|
21205
21545
|
}
|
|
21206
21546
|
if (overdue.length > 0) {
|
|
21207
|
-
console.log(
|
|
21547
|
+
console.log(chalk2.red(`
|
|
21208
21548
|
\u26A0 Overdue (${overdue.length}):`));
|
|
21209
21549
|
for (const t of overdue) {
|
|
21210
21550
|
const daysOver = Math.floor((Date.now() - new Date(t.due_at).getTime()) / 86400000);
|
|
21211
|
-
console.log(` ${
|
|
21551
|
+
console.log(` ${chalk2.red(`${daysOver}d`)} ${chalk2.cyan(t.short_id || t.id.slice(0, 8))} ${t.title}`);
|
|
21212
21552
|
}
|
|
21213
21553
|
}
|
|
21214
|
-
console.log(
|
|
21554
|
+
console.log(chalk2.dim(`
|
|
21215
21555
|
${inProgress.length} active \xB7 ${pending.length} pending \xB7 ${blocked.length} blocked \xB7 ${overdue.length} overdue`));
|
|
21216
21556
|
});
|
|
21217
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) => {
|
|
@@ -21221,7 +21561,7 @@ program2.command("handoff").description("Create or view agent session handoffs")
|
|
|
21221
21561
|
const projectId = autoProject(globalOpts) || undefined;
|
|
21222
21562
|
if (opts.create || opts.summary) {
|
|
21223
21563
|
if (!opts.summary) {
|
|
21224
|
-
console.error(
|
|
21564
|
+
console.error(chalk2.red(" --summary is required for creating a handoff"));
|
|
21225
21565
|
process.exit(1);
|
|
21226
21566
|
}
|
|
21227
21567
|
const handoff = createHandoff2({
|
|
@@ -21237,7 +21577,7 @@ program2.command("handoff").description("Create or view agent session handoffs")
|
|
|
21237
21577
|
console.log(JSON.stringify(handoff));
|
|
21238
21578
|
return;
|
|
21239
21579
|
}
|
|
21240
|
-
console.log(
|
|
21580
|
+
console.log(chalk2.green(` \u2713 Handoff created by ${handoff.agent_id || "unknown"}`));
|
|
21241
21581
|
return;
|
|
21242
21582
|
}
|
|
21243
21583
|
const handoffs = listHandoffs2(projectId, parseInt(opts.limit, 10), db);
|
|
@@ -21246,31 +21586,31 @@ program2.command("handoff").description("Create or view agent session handoffs")
|
|
|
21246
21586
|
return;
|
|
21247
21587
|
}
|
|
21248
21588
|
if (handoffs.length === 0) {
|
|
21249
|
-
console.log(
|
|
21589
|
+
console.log(chalk2.dim(" No handoffs yet."));
|
|
21250
21590
|
return;
|
|
21251
21591
|
}
|
|
21252
21592
|
for (const h of handoffs) {
|
|
21253
21593
|
const time = h.created_at.slice(0, 16).replace("T", " ");
|
|
21254
|
-
console.log(
|
|
21594
|
+
console.log(chalk2.bold(`
|
|
21255
21595
|
${time} ${h.agent_id || "unknown"}`));
|
|
21256
21596
|
console.log(` ${h.summary}`);
|
|
21257
21597
|
if (h.completed?.length) {
|
|
21258
|
-
console.log(
|
|
21598
|
+
console.log(chalk2.green(` \u2713 Completed:`));
|
|
21259
21599
|
for (const c of h.completed)
|
|
21260
21600
|
console.log(` - ${c}`);
|
|
21261
21601
|
}
|
|
21262
21602
|
if (h.in_progress?.length) {
|
|
21263
|
-
console.log(
|
|
21603
|
+
console.log(chalk2.blue(` \u25B6 In progress:`));
|
|
21264
21604
|
for (const c of h.in_progress)
|
|
21265
21605
|
console.log(` - ${c}`);
|
|
21266
21606
|
}
|
|
21267
21607
|
if (h.blockers?.length) {
|
|
21268
|
-
console.log(
|
|
21608
|
+
console.log(chalk2.red(` \u2298 Blockers:`));
|
|
21269
21609
|
for (const c of h.blockers)
|
|
21270
21610
|
console.log(` - ${c}`);
|
|
21271
21611
|
}
|
|
21272
21612
|
if (h.next_steps?.length) {
|
|
21273
|
-
console.log(
|
|
21613
|
+
console.log(chalk2.cyan(` \u2192 Next steps:`));
|
|
21274
21614
|
for (const c of h.next_steps)
|
|
21275
21615
|
console.log(` - ${c}`);
|
|
21276
21616
|
}
|
|
@@ -21296,16 +21636,16 @@ program2.command("priorities").description("Show task counts grouped by priority
|
|
|
21296
21636
|
console.log(JSON.stringify(counts));
|
|
21297
21637
|
return;
|
|
21298
21638
|
}
|
|
21299
|
-
console.log(
|
|
21639
|
+
console.log(chalk2.bold(`Priority Breakdown:
|
|
21300
21640
|
`));
|
|
21301
|
-
const priColors = { critical:
|
|
21641
|
+
const priColors = { critical: chalk2.bgRed.white, high: chalk2.red, medium: chalk2.yellow, low: chalk2.blue, none: chalk2.dim };
|
|
21302
21642
|
for (const p of priorities) {
|
|
21303
21643
|
const c = counts[p];
|
|
21304
21644
|
if (!c || c.total === 0)
|
|
21305
21645
|
continue;
|
|
21306
|
-
const color = priColors[p] ||
|
|
21307
|
-
const bar =
|
|
21308
|
-
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}`);
|
|
21309
21649
|
}
|
|
21310
21650
|
});
|
|
21311
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) => {
|
|
@@ -21325,21 +21665,21 @@ program2.command("context").description("Session start context: status, latest h
|
|
|
21325
21665
|
console.log(JSON.stringify({ status, next_task: nextTask, overdue_count: overdue.length, latest_handoff: handoff, as_of: new Date().toISOString() }));
|
|
21326
21666
|
return;
|
|
21327
21667
|
}
|
|
21328
|
-
console.log(
|
|
21668
|
+
console.log(chalk2.bold(`Session Context
|
|
21329
21669
|
`));
|
|
21330
21670
|
console.log(` ${status.pending} pending \xB7 ${status.in_progress} active \xB7 ${status.completed} done \xB7 ${status.total} total`);
|
|
21331
21671
|
if (status.stale_count > 0)
|
|
21332
|
-
console.log(
|
|
21672
|
+
console.log(chalk2.yellow(` \u26A0 ${status.stale_count} stale tasks`));
|
|
21333
21673
|
if (overdue.length > 0)
|
|
21334
|
-
console.log(
|
|
21674
|
+
console.log(chalk2.red(` \u26A0 ${overdue.length} overdue tasks`));
|
|
21335
21675
|
if (nextTask) {
|
|
21336
|
-
const pri = nextTask.priority === "critical" || nextTask.priority === "high" ?
|
|
21337
|
-
console.log(
|
|
21676
|
+
const pri = nextTask.priority === "critical" || nextTask.priority === "high" ? chalk2.red(` [${nextTask.priority}]`) : "";
|
|
21677
|
+
console.log(chalk2.bold(`
|
|
21338
21678
|
Next up:`));
|
|
21339
|
-
console.log(` ${
|
|
21679
|
+
console.log(` ${chalk2.cyan(nextTask.short_id || nextTask.id.slice(0, 8))} ${nextTask.title}${pri}`);
|
|
21340
21680
|
}
|
|
21341
21681
|
if (handoff) {
|
|
21342
|
-
console.log(
|
|
21682
|
+
console.log(chalk2.bold(`
|
|
21343
21683
|
Last handoff (${handoff.agent_id || "unknown"}, ${handoff.created_at.slice(0, 16).replace("T", " ")}):`));
|
|
21344
21684
|
console.log(` ${handoff.summary}`);
|
|
21345
21685
|
if (handoff.next_steps?.length) {
|
|
@@ -21347,7 +21687,7 @@ program2.command("context").description("Session start context: status, latest h
|
|
|
21347
21687
|
console.log(` \u2192 ${s}`);
|
|
21348
21688
|
}
|
|
21349
21689
|
}
|
|
21350
|
-
console.log(
|
|
21690
|
+
console.log(chalk2.dim(`
|
|
21351
21691
|
as_of: ${new Date().toISOString()}`));
|
|
21352
21692
|
});
|
|
21353
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) => {
|
|
@@ -21386,11 +21726,11 @@ ${opts.stack.slice(0, 1500)}
|
|
|
21386
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 }));
|
|
21387
21727
|
return;
|
|
21388
21728
|
}
|
|
21389
|
-
console.log(
|
|
21729
|
+
console.log(chalk2.green(`\u2713 Created task ${task.short_id || task.id.slice(0, 8)}: ${task.title}`));
|
|
21390
21730
|
if (assignResult.agent_name) {
|
|
21391
|
-
console.log(
|
|
21731
|
+
console.log(chalk2.cyan(` Assigned to: ${assignResult.agent_name} (via ${assignResult.method})`));
|
|
21392
21732
|
if (assignResult.reason)
|
|
21393
|
-
console.log(
|
|
21733
|
+
console.log(chalk2.dim(` Reason: ${assignResult.reason}`));
|
|
21394
21734
|
}
|
|
21395
21735
|
});
|
|
21396
21736
|
program2.action(async () => {
|
|
@@ -21407,4 +21747,5 @@ program2.action(async () => {
|
|
|
21407
21747
|
program2.help();
|
|
21408
21748
|
}
|
|
21409
21749
|
});
|
|
21750
|
+
program2.addCommand(makeBrainsCommand());
|
|
21410
21751
|
program2.parse();
|