@hasna/todos 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +508 -22
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2599,6 +2599,10 @@ function getComment(id, db) {
|
|
|
2599
2599
|
const d = db || getDatabase();
|
|
2600
2600
|
return d.query("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
2601
2601
|
}
|
|
2602
|
+
function listComments(taskId, db) {
|
|
2603
|
+
const d = db || getDatabase();
|
|
2604
|
+
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
2605
|
+
}
|
|
2602
2606
|
var init_comments = __esm(() => {
|
|
2603
2607
|
init_types();
|
|
2604
2608
|
init_database();
|
|
@@ -6965,6 +6969,389 @@ ${text}` }] };
|
|
|
6965
6969
|
});
|
|
6966
6970
|
});
|
|
6967
6971
|
|
|
6972
|
+
// src/server/serve.ts
|
|
6973
|
+
var exports_serve = {};
|
|
6974
|
+
__export(exports_serve, {
|
|
6975
|
+
startServer: () => startServer
|
|
6976
|
+
});
|
|
6977
|
+
import { execSync } from "child_process";
|
|
6978
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
6979
|
+
import { join as join2, dirname as dirname2, extname } from "path";
|
|
6980
|
+
import { fileURLToPath } from "url";
|
|
6981
|
+
function resolveDashboardDir() {
|
|
6982
|
+
const candidates = [];
|
|
6983
|
+
try {
|
|
6984
|
+
const scriptDir = dirname2(fileURLToPath(import.meta.url));
|
|
6985
|
+
candidates.push(join2(scriptDir, "..", "dashboard", "dist"));
|
|
6986
|
+
candidates.push(join2(scriptDir, "..", "..", "dashboard", "dist"));
|
|
6987
|
+
} catch {}
|
|
6988
|
+
if (process.argv[1]) {
|
|
6989
|
+
const mainDir = dirname2(process.argv[1]);
|
|
6990
|
+
candidates.push(join2(mainDir, "..", "dashboard", "dist"));
|
|
6991
|
+
candidates.push(join2(mainDir, "..", "..", "dashboard", "dist"));
|
|
6992
|
+
}
|
|
6993
|
+
candidates.push(join2(process.cwd(), "dashboard", "dist"));
|
|
6994
|
+
for (const candidate of candidates) {
|
|
6995
|
+
if (existsSync2(candidate))
|
|
6996
|
+
return candidate;
|
|
6997
|
+
}
|
|
6998
|
+
return join2(process.cwd(), "dashboard", "dist");
|
|
6999
|
+
}
|
|
7000
|
+
function json(data, status = 200, port) {
|
|
7001
|
+
return new Response(JSON.stringify(data), {
|
|
7002
|
+
status,
|
|
7003
|
+
headers: {
|
|
7004
|
+
"Content-Type": "application/json",
|
|
7005
|
+
"Access-Control-Allow-Origin": port ? `http://localhost:${port}` : "*",
|
|
7006
|
+
...SECURITY_HEADERS
|
|
7007
|
+
}
|
|
7008
|
+
});
|
|
7009
|
+
}
|
|
7010
|
+
function getPackageVersion() {
|
|
7011
|
+
try {
|
|
7012
|
+
const pkgPath = join2(dirname2(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
7013
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version || "0.0.0";
|
|
7014
|
+
} catch {
|
|
7015
|
+
return "0.0.0";
|
|
7016
|
+
}
|
|
7017
|
+
}
|
|
7018
|
+
function serveStaticFile(filePath) {
|
|
7019
|
+
if (!existsSync2(filePath))
|
|
7020
|
+
return null;
|
|
7021
|
+
const ext = extname(filePath);
|
|
7022
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
7023
|
+
return new Response(Bun.file(filePath), {
|
|
7024
|
+
headers: { "Content-Type": contentType }
|
|
7025
|
+
});
|
|
7026
|
+
}
|
|
7027
|
+
async function startServer(port, options) {
|
|
7028
|
+
const shouldOpen = options?.open ?? true;
|
|
7029
|
+
const dashboardDir = resolveDashboardDir();
|
|
7030
|
+
const dashboardExists = existsSync2(dashboardDir);
|
|
7031
|
+
if (!dashboardExists) {
|
|
7032
|
+
console.error(`
|
|
7033
|
+
Dashboard not found at: ${dashboardDir}`);
|
|
7034
|
+
console.error(`Run this to build it:
|
|
7035
|
+
`);
|
|
7036
|
+
console.error(` cd dashboard && bun install && bun run build
|
|
7037
|
+
`);
|
|
7038
|
+
console.error(`Or from the project root:
|
|
7039
|
+
`);
|
|
7040
|
+
console.error(` bun run build:dashboard
|
|
7041
|
+
`);
|
|
7042
|
+
}
|
|
7043
|
+
const server2 = Bun.serve({
|
|
7044
|
+
port,
|
|
7045
|
+
async fetch(req) {
|
|
7046
|
+
const url = new URL(req.url);
|
|
7047
|
+
const path = url.pathname;
|
|
7048
|
+
const method = req.method;
|
|
7049
|
+
if (path === "/api/tasks" && method === "GET") {
|
|
7050
|
+
try {
|
|
7051
|
+
const filter = {};
|
|
7052
|
+
const status = url.searchParams.get("status");
|
|
7053
|
+
const priority = url.searchParams.get("priority");
|
|
7054
|
+
const projectId = url.searchParams.get("project_id");
|
|
7055
|
+
if (status)
|
|
7056
|
+
filter.status = status;
|
|
7057
|
+
if (priority)
|
|
7058
|
+
filter.priority = priority;
|
|
7059
|
+
if (projectId)
|
|
7060
|
+
filter.project_id = projectId;
|
|
7061
|
+
const tasks = listTasks(filter);
|
|
7062
|
+
const projectCache = new Map;
|
|
7063
|
+
const enriched = tasks.map((t) => {
|
|
7064
|
+
let projectName;
|
|
7065
|
+
if (t.project_id) {
|
|
7066
|
+
if (projectCache.has(t.project_id)) {
|
|
7067
|
+
projectName = projectCache.get(t.project_id);
|
|
7068
|
+
} else {
|
|
7069
|
+
const p = getProject(t.project_id);
|
|
7070
|
+
projectName = p?.name;
|
|
7071
|
+
if (projectName)
|
|
7072
|
+
projectCache.set(t.project_id, projectName);
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
return { ...t, project_name: projectName };
|
|
7076
|
+
});
|
|
7077
|
+
return json(enriched, 200, port);
|
|
7078
|
+
} catch (e) {
|
|
7079
|
+
return json({ error: e instanceof Error ? e.message : "Failed to list tasks" }, 500, port);
|
|
7080
|
+
}
|
|
7081
|
+
}
|
|
7082
|
+
const taskGetMatch = path.match(/^\/api\/tasks\/([^/]+)$/);
|
|
7083
|
+
if (taskGetMatch && method === "GET") {
|
|
7084
|
+
try {
|
|
7085
|
+
const id = taskGetMatch[1];
|
|
7086
|
+
const task = getTaskWithRelations(id);
|
|
7087
|
+
if (!task)
|
|
7088
|
+
return json({ error: "Task not found" }, 404, port);
|
|
7089
|
+
let projectName;
|
|
7090
|
+
if (task.project_id) {
|
|
7091
|
+
const p = getProject(task.project_id);
|
|
7092
|
+
projectName = p?.name;
|
|
7093
|
+
}
|
|
7094
|
+
return json({ ...task, project_name: projectName }, 200, port);
|
|
7095
|
+
} catch (e) {
|
|
7096
|
+
return json({ error: e instanceof Error ? e.message : "Failed to get task" }, 500, port);
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
if (path === "/api/tasks" && method === "POST") {
|
|
7100
|
+
try {
|
|
7101
|
+
const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
|
|
7102
|
+
if (contentLength > MAX_BODY_SIZE)
|
|
7103
|
+
return json({ error: "Request body too large" }, 413, port);
|
|
7104
|
+
const body = await req.json();
|
|
7105
|
+
if (!body.title || typeof body.title !== "string") {
|
|
7106
|
+
return json({ error: "Missing required field: title" }, 400, port);
|
|
7107
|
+
}
|
|
7108
|
+
const task = createTask({
|
|
7109
|
+
title: body.title,
|
|
7110
|
+
description: body.description,
|
|
7111
|
+
priority: body.priority,
|
|
7112
|
+
project_id: body.project_id,
|
|
7113
|
+
parent_id: body.parent_id,
|
|
7114
|
+
tags: body.tags,
|
|
7115
|
+
assigned_to: body.assigned_to,
|
|
7116
|
+
agent_id: body.agent_id,
|
|
7117
|
+
status: body.status
|
|
7118
|
+
});
|
|
7119
|
+
return json(task, 201, port);
|
|
7120
|
+
} catch (e) {
|
|
7121
|
+
return json({ error: e instanceof Error ? e.message : "Failed to create task" }, 500, port);
|
|
7122
|
+
}
|
|
7123
|
+
}
|
|
7124
|
+
const taskPatchMatch = path.match(/^\/api\/tasks\/([^/]+)$/);
|
|
7125
|
+
if (taskPatchMatch && method === "PATCH") {
|
|
7126
|
+
try {
|
|
7127
|
+
const id = taskPatchMatch[1];
|
|
7128
|
+
const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
|
|
7129
|
+
if (contentLength > MAX_BODY_SIZE)
|
|
7130
|
+
return json({ error: "Request body too large" }, 413, port);
|
|
7131
|
+
const body = await req.json();
|
|
7132
|
+
if (typeof body.version !== "number") {
|
|
7133
|
+
return json({ error: "Missing required field: version" }, 400, port);
|
|
7134
|
+
}
|
|
7135
|
+
const task = updateTask(id, {
|
|
7136
|
+
version: body.version,
|
|
7137
|
+
title: body.title,
|
|
7138
|
+
description: body.description,
|
|
7139
|
+
status: body.status,
|
|
7140
|
+
priority: body.priority,
|
|
7141
|
+
assigned_to: body.assigned_to,
|
|
7142
|
+
tags: body.tags,
|
|
7143
|
+
metadata: body.metadata
|
|
7144
|
+
});
|
|
7145
|
+
return json(task, 200, port);
|
|
7146
|
+
} catch (e) {
|
|
7147
|
+
const status = e instanceof Error && e.name === "VersionConflictError" ? 409 : 500;
|
|
7148
|
+
return json({ error: e instanceof Error ? e.message : "Failed to update task" }, status, port);
|
|
7149
|
+
}
|
|
7150
|
+
}
|
|
7151
|
+
const taskDeleteMatch = path.match(/^\/api\/tasks\/([^/]+)$/);
|
|
7152
|
+
if (taskDeleteMatch && method === "DELETE") {
|
|
7153
|
+
try {
|
|
7154
|
+
const id = taskDeleteMatch[1];
|
|
7155
|
+
const deleted = deleteTask(id);
|
|
7156
|
+
if (!deleted)
|
|
7157
|
+
return json({ error: "Task not found" }, 404, port);
|
|
7158
|
+
return json({ deleted: true }, 200, port);
|
|
7159
|
+
} catch (e) {
|
|
7160
|
+
return json({ error: e instanceof Error ? e.message : "Failed to delete task" }, 500, port);
|
|
7161
|
+
}
|
|
7162
|
+
}
|
|
7163
|
+
const taskStartMatch = path.match(/^\/api\/tasks\/([^/]+)\/start$/);
|
|
7164
|
+
if (taskStartMatch && method === "POST") {
|
|
7165
|
+
try {
|
|
7166
|
+
const id = taskStartMatch[1];
|
|
7167
|
+
const body = await req.json();
|
|
7168
|
+
const agentId = body.agent_id || "dashboard";
|
|
7169
|
+
const task = startTask(id, agentId);
|
|
7170
|
+
return json(task, 200, port);
|
|
7171
|
+
} catch (e) {
|
|
7172
|
+
const status = e instanceof Error && e.name === "TaskNotFoundError" ? 404 : e instanceof Error && e.name === "LockError" ? 409 : 500;
|
|
7173
|
+
return json({ error: e instanceof Error ? e.message : "Failed to start task" }, status, port);
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
const taskCompleteMatch = path.match(/^\/api\/tasks\/([^/]+)\/complete$/);
|
|
7177
|
+
if (taskCompleteMatch && method === "POST") {
|
|
7178
|
+
try {
|
|
7179
|
+
const id = taskCompleteMatch[1];
|
|
7180
|
+
const body = await req.json();
|
|
7181
|
+
const agentId = body.agent_id;
|
|
7182
|
+
const task = completeTask(id, agentId);
|
|
7183
|
+
return json(task, 200, port);
|
|
7184
|
+
} catch (e) {
|
|
7185
|
+
const status = e instanceof Error && e.name === "TaskNotFoundError" ? 404 : e instanceof Error && e.name === "LockError" ? 409 : 500;
|
|
7186
|
+
return json({ error: e instanceof Error ? e.message : "Failed to complete task" }, status, port);
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
const commentsGetMatch = path.match(/^\/api\/tasks\/([^/]+)\/comments$/);
|
|
7190
|
+
if (commentsGetMatch && method === "GET") {
|
|
7191
|
+
try {
|
|
7192
|
+
const taskId = commentsGetMatch[1];
|
|
7193
|
+
const comments = listComments(taskId);
|
|
7194
|
+
return json(comments, 200, port);
|
|
7195
|
+
} catch (e) {
|
|
7196
|
+
return json({ error: e instanceof Error ? e.message : "Failed to list comments" }, 500, port);
|
|
7197
|
+
}
|
|
7198
|
+
}
|
|
7199
|
+
const commentsPostMatch = path.match(/^\/api\/tasks\/([^/]+)\/comments$/);
|
|
7200
|
+
if (commentsPostMatch && method === "POST") {
|
|
7201
|
+
try {
|
|
7202
|
+
const taskId = commentsPostMatch[1];
|
|
7203
|
+
const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
|
|
7204
|
+
if (contentLength > MAX_BODY_SIZE)
|
|
7205
|
+
return json({ error: "Request body too large" }, 413, port);
|
|
7206
|
+
const body = await req.json();
|
|
7207
|
+
if (!body.content || typeof body.content !== "string") {
|
|
7208
|
+
return json({ error: "Missing required field: content" }, 400, port);
|
|
7209
|
+
}
|
|
7210
|
+
const comment = addComment({
|
|
7211
|
+
task_id: taskId,
|
|
7212
|
+
content: body.content,
|
|
7213
|
+
agent_id: body.agent_id,
|
|
7214
|
+
session_id: body.session_id
|
|
7215
|
+
});
|
|
7216
|
+
return json(comment, 201, port);
|
|
7217
|
+
} catch (e) {
|
|
7218
|
+
return json({ error: e instanceof Error ? e.message : "Failed to add comment" }, 500, port);
|
|
7219
|
+
}
|
|
7220
|
+
}
|
|
7221
|
+
if (path === "/api/projects" && method === "GET") {
|
|
7222
|
+
try {
|
|
7223
|
+
const projects = listProjects();
|
|
7224
|
+
return json(projects, 200, port);
|
|
7225
|
+
} catch (e) {
|
|
7226
|
+
return json({ error: e instanceof Error ? e.message : "Failed to list projects" }, 500, port);
|
|
7227
|
+
}
|
|
7228
|
+
}
|
|
7229
|
+
if (path === "/api/projects" && method === "POST") {
|
|
7230
|
+
try {
|
|
7231
|
+
const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
|
|
7232
|
+
if (contentLength > MAX_BODY_SIZE)
|
|
7233
|
+
return json({ error: "Request body too large" }, 413, port);
|
|
7234
|
+
const body = await req.json();
|
|
7235
|
+
if (!body.name || typeof body.name !== "string") {
|
|
7236
|
+
return json({ error: "Missing required field: name" }, 400, port);
|
|
7237
|
+
}
|
|
7238
|
+
const project = createProject({
|
|
7239
|
+
name: body.name,
|
|
7240
|
+
path: body.path || process.cwd(),
|
|
7241
|
+
description: body.description
|
|
7242
|
+
});
|
|
7243
|
+
return json(project, 201, port);
|
|
7244
|
+
} catch (e) {
|
|
7245
|
+
return json({ error: e instanceof Error ? e.message : "Failed to create project" }, 500, port);
|
|
7246
|
+
}
|
|
7247
|
+
}
|
|
7248
|
+
if (path === "/api/search" && method === "GET") {
|
|
7249
|
+
try {
|
|
7250
|
+
const q = url.searchParams.get("q");
|
|
7251
|
+
if (!q)
|
|
7252
|
+
return json({ error: "Missing query parameter: q" }, 400, port);
|
|
7253
|
+
const projectId = url.searchParams.get("project_id") || undefined;
|
|
7254
|
+
const results = searchTasks(q, projectId);
|
|
7255
|
+
return json(results, 200, port);
|
|
7256
|
+
} catch (e) {
|
|
7257
|
+
return json({ error: e instanceof Error ? e.message : "Search failed" }, 500, port);
|
|
7258
|
+
}
|
|
7259
|
+
}
|
|
7260
|
+
if (path === "/api/system/version" && method === "GET") {
|
|
7261
|
+
try {
|
|
7262
|
+
const current = getPackageVersion();
|
|
7263
|
+
const npmRes = await fetch("https://registry.npmjs.org/@hasna/todos/latest");
|
|
7264
|
+
if (!npmRes.ok) {
|
|
7265
|
+
return json({ current, latest: current, updateAvailable: false }, 200, port);
|
|
7266
|
+
}
|
|
7267
|
+
const data = await npmRes.json();
|
|
7268
|
+
const latest = data.version;
|
|
7269
|
+
return json({ current, latest, updateAvailable: current !== latest }, 200, port);
|
|
7270
|
+
} catch {
|
|
7271
|
+
const current = getPackageVersion();
|
|
7272
|
+
return json({ current, latest: current, updateAvailable: false }, 200, port);
|
|
7273
|
+
}
|
|
7274
|
+
}
|
|
7275
|
+
if (path === "/api/system/update" && method === "POST") {
|
|
7276
|
+
try {
|
|
7277
|
+
let useBun = false;
|
|
7278
|
+
try {
|
|
7279
|
+
execSync("which bun", { stdio: "ignore" });
|
|
7280
|
+
useBun = true;
|
|
7281
|
+
} catch {}
|
|
7282
|
+
const cmd = useBun ? "bun add -g @hasna/todos@latest" : "npm install -g @hasna/todos@latest";
|
|
7283
|
+
execSync(cmd, { stdio: "ignore", timeout: 60000 });
|
|
7284
|
+
return json({ success: true, message: "Updated! Restart the server to use the new version." }, 200, port);
|
|
7285
|
+
} catch (e) {
|
|
7286
|
+
return json({ success: false, message: e instanceof Error ? e.message : "Update failed" }, 500, port);
|
|
7287
|
+
}
|
|
7288
|
+
}
|
|
7289
|
+
if (method === "OPTIONS") {
|
|
7290
|
+
return new Response(null, {
|
|
7291
|
+
headers: {
|
|
7292
|
+
"Access-Control-Allow-Origin": `http://localhost:${port}`,
|
|
7293
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
7294
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
7295
|
+
}
|
|
7296
|
+
});
|
|
7297
|
+
}
|
|
7298
|
+
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
7299
|
+
if (path !== "/") {
|
|
7300
|
+
const filePath = join2(dashboardDir, path);
|
|
7301
|
+
const res2 = serveStaticFile(filePath);
|
|
7302
|
+
if (res2)
|
|
7303
|
+
return res2;
|
|
7304
|
+
}
|
|
7305
|
+
const indexPath = join2(dashboardDir, "index.html");
|
|
7306
|
+
const res = serveStaticFile(indexPath);
|
|
7307
|
+
if (res)
|
|
7308
|
+
return res;
|
|
7309
|
+
}
|
|
7310
|
+
return json({ error: "Not found" }, 404, port);
|
|
7311
|
+
}
|
|
7312
|
+
});
|
|
7313
|
+
const shutdown = () => {
|
|
7314
|
+
server2.stop();
|
|
7315
|
+
process.exit(0);
|
|
7316
|
+
};
|
|
7317
|
+
process.on("SIGINT", shutdown);
|
|
7318
|
+
process.on("SIGTERM", shutdown);
|
|
7319
|
+
const serverUrl = `http://localhost:${port}`;
|
|
7320
|
+
console.log(`Todos Dashboard running at ${serverUrl}`);
|
|
7321
|
+
if (shouldOpen) {
|
|
7322
|
+
try {
|
|
7323
|
+
const { exec } = await import("child_process");
|
|
7324
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
7325
|
+
exec(`${openCmd} ${serverUrl}`);
|
|
7326
|
+
} catch {}
|
|
7327
|
+
}
|
|
7328
|
+
return server2;
|
|
7329
|
+
}
|
|
7330
|
+
var MIME_TYPES, SECURITY_HEADERS, MAX_BODY_SIZE;
|
|
7331
|
+
var init_serve = __esm(() => {
|
|
7332
|
+
init_tasks();
|
|
7333
|
+
init_projects();
|
|
7334
|
+
init_comments();
|
|
7335
|
+
init_search();
|
|
7336
|
+
MIME_TYPES = {
|
|
7337
|
+
".html": "text/html; charset=utf-8",
|
|
7338
|
+
".js": "application/javascript",
|
|
7339
|
+
".css": "text/css",
|
|
7340
|
+
".json": "application/json",
|
|
7341
|
+
".png": "image/png",
|
|
7342
|
+
".jpg": "image/jpeg",
|
|
7343
|
+
".svg": "image/svg+xml",
|
|
7344
|
+
".ico": "image/x-icon",
|
|
7345
|
+
".woff": "font/woff",
|
|
7346
|
+
".woff2": "font/woff2"
|
|
7347
|
+
};
|
|
7348
|
+
SECURITY_HEADERS = {
|
|
7349
|
+
"X-Content-Type-Options": "nosniff",
|
|
7350
|
+
"X-Frame-Options": "DENY"
|
|
7351
|
+
};
|
|
7352
|
+
MAX_BODY_SIZE = 1024 * 1024;
|
|
7353
|
+
});
|
|
7354
|
+
|
|
6968
7355
|
// src/cli/components/Header.tsx
|
|
6969
7356
|
import { Box, Text } from "ink";
|
|
6970
7357
|
import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
@@ -7215,6 +7602,29 @@ function TaskDetail({ task }) {
|
|
|
7215
7602
|
}, undefined, false, undefined, this)
|
|
7216
7603
|
]
|
|
7217
7604
|
}, undefined, true, undefined, this),
|
|
7605
|
+
task.agent_id && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
7606
|
+
children: [
|
|
7607
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
7608
|
+
dimColor: true,
|
|
7609
|
+
children: "Agent: "
|
|
7610
|
+
}, undefined, false, undefined, this),
|
|
7611
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
7612
|
+
color: "cyan",
|
|
7613
|
+
children: task.agent_id
|
|
7614
|
+
}, undefined, false, undefined, this)
|
|
7615
|
+
]
|
|
7616
|
+
}, undefined, true, undefined, this),
|
|
7617
|
+
task.session_id && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
7618
|
+
children: [
|
|
7619
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
7620
|
+
dimColor: true,
|
|
7621
|
+
children: "Session: "
|
|
7622
|
+
}, undefined, false, undefined, this),
|
|
7623
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
7624
|
+
children: task.session_id
|
|
7625
|
+
}, undefined, false, undefined, this)
|
|
7626
|
+
]
|
|
7627
|
+
}, undefined, true, undefined, this),
|
|
7218
7628
|
task.assigned_to && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
7219
7629
|
children: [
|
|
7220
7630
|
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
@@ -7227,6 +7637,28 @@ function TaskDetail({ task }) {
|
|
|
7227
7637
|
}, undefined, false, undefined, this)
|
|
7228
7638
|
]
|
|
7229
7639
|
}, undefined, true, undefined, this),
|
|
7640
|
+
task.project_id && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
7641
|
+
children: [
|
|
7642
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
7643
|
+
dimColor: true,
|
|
7644
|
+
children: "Project: "
|
|
7645
|
+
}, undefined, false, undefined, this),
|
|
7646
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
7647
|
+
children: task.project_id
|
|
7648
|
+
}, undefined, false, undefined, this)
|
|
7649
|
+
]
|
|
7650
|
+
}, undefined, true, undefined, this),
|
|
7651
|
+
task.working_dir && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
7652
|
+
children: [
|
|
7653
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
7654
|
+
dimColor: true,
|
|
7655
|
+
children: "Work Dir: "
|
|
7656
|
+
}, undefined, false, undefined, this),
|
|
7657
|
+
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
7658
|
+
children: task.working_dir
|
|
7659
|
+
}, undefined, false, undefined, this)
|
|
7660
|
+
]
|
|
7661
|
+
}, undefined, true, undefined, this),
|
|
7230
7662
|
task.locked_by && /* @__PURE__ */ jsxDEV3(Box3, {
|
|
7231
7663
|
children: [
|
|
7232
7664
|
/* @__PURE__ */ jsxDEV3(Text3, {
|
|
@@ -8000,9 +8432,18 @@ init_projects();
|
|
|
8000
8432
|
init_comments();
|
|
8001
8433
|
init_search();
|
|
8002
8434
|
import chalk from "chalk";
|
|
8003
|
-
import { execSync } from "child_process";
|
|
8004
|
-
import { existsSync as
|
|
8005
|
-
import { basename, dirname as
|
|
8435
|
+
import { execSync as execSync2 } from "child_process";
|
|
8436
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
8437
|
+
import { basename, dirname as dirname3, join as join3, resolve as resolve2 } from "path";
|
|
8438
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8439
|
+
function getPackageVersion2() {
|
|
8440
|
+
try {
|
|
8441
|
+
const pkgPath = join3(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
|
|
8442
|
+
return JSON.parse(readFileSync2(pkgPath, "utf-8")).version || "0.0.0";
|
|
8443
|
+
} catch {
|
|
8444
|
+
return "0.0.0";
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8006
8447
|
var program2 = new Command;
|
|
8007
8448
|
function handleError(e) {
|
|
8008
8449
|
console.error(chalk.red(e instanceof Error ? e.message : String(e)));
|
|
@@ -8019,7 +8460,7 @@ function resolveTaskId(partialId) {
|
|
|
8019
8460
|
}
|
|
8020
8461
|
function detectGitRoot() {
|
|
8021
8462
|
try {
|
|
8022
|
-
return
|
|
8463
|
+
return execSync2("git rev-parse --show-toplevel", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8023
8464
|
} catch {
|
|
8024
8465
|
return null;
|
|
8025
8466
|
}
|
|
@@ -8064,7 +8505,7 @@ function formatTaskLine(t) {
|
|
|
8064
8505
|
const tags = t.tags.length > 0 ? chalk.dim(` [${t.tags.join(",")}]`) : "";
|
|
8065
8506
|
return `${chalk.dim(t.id.slice(0, 8))} ${statusFn(t.status.padEnd(11))} ${priorityFn(t.priority.padEnd(8))} ${t.title}${assigned}${lock}${tags}`;
|
|
8066
8507
|
}
|
|
8067
|
-
program2.name("todos").description("Universal task management for AI coding agents").version(
|
|
8508
|
+
program2.name("todos").description("Universal task management for AI coding agents").version(getPackageVersion2()).option("--project <path>", "Project path").option("--json", "Output as JSON").option("--agent <name>", "Agent name").option("--session <id>", "Session ID");
|
|
8068
8509
|
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("--tags <tags>", "Comma-separated tags").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").action((title, opts) => {
|
|
8069
8510
|
const globalOpts = program2.opts();
|
|
8070
8511
|
const projectId = autoProject(globalOpts);
|
|
@@ -8144,10 +8585,14 @@ program2.command("show <id>").description("Show full task details").action((id)
|
|
|
8144
8585
|
console.log(` ${chalk.dim("Assigned:")} ${task.assigned_to}`);
|
|
8145
8586
|
if (task.agent_id)
|
|
8146
8587
|
console.log(` ${chalk.dim("Agent:")} ${task.agent_id}`);
|
|
8588
|
+
if (task.session_id)
|
|
8589
|
+
console.log(` ${chalk.dim("Session:")} ${task.session_id}`);
|
|
8147
8590
|
if (task.locked_by)
|
|
8148
8591
|
console.log(` ${chalk.dim("Locked:")} ${task.locked_by} (at ${task.locked_at})`);
|
|
8149
8592
|
if (task.project_id)
|
|
8150
8593
|
console.log(` ${chalk.dim("Project:")} ${task.project_id}`);
|
|
8594
|
+
if (task.working_dir)
|
|
8595
|
+
console.log(` ${chalk.dim("WorkDir:")} ${task.working_dir}`);
|
|
8151
8596
|
if (task.parent)
|
|
8152
8597
|
console.log(` ${chalk.dim("Parent:")} ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
|
|
8153
8598
|
if (task.tags.length > 0)
|
|
@@ -8478,45 +8923,45 @@ program2.command("mcp").description("Start MCP server (stdio)").option("--regist
|
|
|
8478
8923
|
var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
8479
8924
|
function getMcpBinaryPath() {
|
|
8480
8925
|
try {
|
|
8481
|
-
const p =
|
|
8926
|
+
const p = execSync2("which todos-mcp", { encoding: "utf-8" }).trim();
|
|
8482
8927
|
if (p)
|
|
8483
8928
|
return p;
|
|
8484
8929
|
} catch {}
|
|
8485
|
-
const bunBin =
|
|
8486
|
-
if (
|
|
8930
|
+
const bunBin = join3(HOME, ".bun", "bin", "todos-mcp");
|
|
8931
|
+
if (existsSync3(bunBin))
|
|
8487
8932
|
return bunBin;
|
|
8488
8933
|
return "todos-mcp";
|
|
8489
8934
|
}
|
|
8490
8935
|
function readJsonFile(path) {
|
|
8491
|
-
if (!
|
|
8936
|
+
if (!existsSync3(path))
|
|
8492
8937
|
return {};
|
|
8493
8938
|
try {
|
|
8494
|
-
return JSON.parse(
|
|
8939
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
8495
8940
|
} catch {
|
|
8496
8941
|
return {};
|
|
8497
8942
|
}
|
|
8498
8943
|
}
|
|
8499
8944
|
function writeJsonFile(path, data) {
|
|
8500
|
-
const dir =
|
|
8501
|
-
if (!
|
|
8945
|
+
const dir = dirname3(path);
|
|
8946
|
+
if (!existsSync3(dir))
|
|
8502
8947
|
mkdirSync2(dir, { recursive: true });
|
|
8503
8948
|
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
8504
8949
|
`);
|
|
8505
8950
|
}
|
|
8506
8951
|
function readTomlFile(path) {
|
|
8507
|
-
if (!
|
|
8952
|
+
if (!existsSync3(path))
|
|
8508
8953
|
return "";
|
|
8509
|
-
return
|
|
8954
|
+
return readFileSync2(path, "utf-8");
|
|
8510
8955
|
}
|
|
8511
8956
|
function writeTomlFile(path, content) {
|
|
8512
|
-
const dir =
|
|
8513
|
-
if (!
|
|
8957
|
+
const dir = dirname3(path);
|
|
8958
|
+
if (!existsSync3(dir))
|
|
8514
8959
|
mkdirSync2(dir, { recursive: true });
|
|
8515
8960
|
writeFileSync(path, content);
|
|
8516
8961
|
}
|
|
8517
8962
|
function registerClaude(binPath) {
|
|
8518
8963
|
const cwd = process.cwd();
|
|
8519
|
-
const configPath =
|
|
8964
|
+
const configPath = join3(cwd, ".mcp.json");
|
|
8520
8965
|
const config = readJsonFile(configPath);
|
|
8521
8966
|
config["todos"] = {
|
|
8522
8967
|
command: binPath,
|
|
@@ -8527,7 +8972,7 @@ function registerClaude(binPath) {
|
|
|
8527
8972
|
}
|
|
8528
8973
|
function unregisterClaude() {
|
|
8529
8974
|
const cwd = process.cwd();
|
|
8530
|
-
const configPath =
|
|
8975
|
+
const configPath = join3(cwd, ".mcp.json");
|
|
8531
8976
|
const config = readJsonFile(configPath);
|
|
8532
8977
|
if (!("todos" in config)) {
|
|
8533
8978
|
console.log(chalk.dim(`Claude Code: todos not found in ${configPath}`));
|
|
@@ -8538,7 +8983,7 @@ function unregisterClaude() {
|
|
|
8538
8983
|
console.log(chalk.green(`Claude Code: unregistered from ${configPath}`));
|
|
8539
8984
|
}
|
|
8540
8985
|
function registerCodex(binPath) {
|
|
8541
|
-
const configPath =
|
|
8986
|
+
const configPath = join3(HOME, ".codex", "config.toml");
|
|
8542
8987
|
let content = readTomlFile(configPath);
|
|
8543
8988
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
8544
8989
|
const block = `
|
|
@@ -8552,7 +8997,7 @@ args = []
|
|
|
8552
8997
|
console.log(chalk.green(`Codex CLI: registered in ${configPath}`));
|
|
8553
8998
|
}
|
|
8554
8999
|
function unregisterCodex() {
|
|
8555
|
-
const configPath =
|
|
9000
|
+
const configPath = join3(HOME, ".codex", "config.toml");
|
|
8556
9001
|
let content = readTomlFile(configPath);
|
|
8557
9002
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
8558
9003
|
console.log(chalk.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -8585,7 +9030,7 @@ function removeTomlBlock(content, blockName) {
|
|
|
8585
9030
|
`);
|
|
8586
9031
|
}
|
|
8587
9032
|
function registerGemini(binPath) {
|
|
8588
|
-
const configPath =
|
|
9033
|
+
const configPath = join3(HOME, ".gemini", "settings.json");
|
|
8589
9034
|
const config = readJsonFile(configPath);
|
|
8590
9035
|
if (!config["mcpServers"]) {
|
|
8591
9036
|
config["mcpServers"] = {};
|
|
@@ -8599,7 +9044,7 @@ function registerGemini(binPath) {
|
|
|
8599
9044
|
console.log(chalk.green(`Gemini CLI: registered in ${configPath}`));
|
|
8600
9045
|
}
|
|
8601
9046
|
function unregisterGemini() {
|
|
8602
|
-
const configPath =
|
|
9047
|
+
const configPath = join3(HOME, ".gemini", "settings.json");
|
|
8603
9048
|
const config = readJsonFile(configPath);
|
|
8604
9049
|
const servers = config["mcpServers"];
|
|
8605
9050
|
if (!servers || !("todos" in servers)) {
|
|
@@ -8647,6 +9092,47 @@ function unregisterMcp(agent) {
|
|
|
8647
9092
|
}
|
|
8648
9093
|
}
|
|
8649
9094
|
}
|
|
9095
|
+
program2.command("serve").aliases(["dashboard", "open"]).description("Start the web dashboard").option("-p, --port <port>", "Port number", "19420").option("--no-open", "Don't auto-open browser").action(async (opts) => {
|
|
9096
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
9097
|
+
await startServer2(parseInt(opts.port, 10), { open: opts.open });
|
|
9098
|
+
});
|
|
9099
|
+
program2.command("upgrade").alias("self-update").description("Update todos to the latest version").option("--check", "Only check for updates, don't install").action(async (opts) => {
|
|
9100
|
+
try {
|
|
9101
|
+
const currentVersion = getPackageVersion2();
|
|
9102
|
+
const res = await fetch("https://registry.npmjs.org/@hasna/todos/latest");
|
|
9103
|
+
if (!res.ok) {
|
|
9104
|
+
console.error(chalk.red("Failed to check for updates."));
|
|
9105
|
+
process.exit(1);
|
|
9106
|
+
}
|
|
9107
|
+
const data = await res.json();
|
|
9108
|
+
const latestVersion = data.version;
|
|
9109
|
+
console.log(` Current: ${chalk.dim(currentVersion)}`);
|
|
9110
|
+
console.log(` Latest: ${chalk.green(latestVersion)}`);
|
|
9111
|
+
if (currentVersion === latestVersion) {
|
|
9112
|
+
console.log(chalk.green(`
|
|
9113
|
+
Already up to date!`));
|
|
9114
|
+
return;
|
|
9115
|
+
}
|
|
9116
|
+
if (opts.check) {
|
|
9117
|
+
console.log(chalk.yellow(`
|
|
9118
|
+
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
9119
|
+
return;
|
|
9120
|
+
}
|
|
9121
|
+
let useBun = false;
|
|
9122
|
+
try {
|
|
9123
|
+
execSync2("which bun", { stdio: "ignore" });
|
|
9124
|
+
useBun = true;
|
|
9125
|
+
} catch {}
|
|
9126
|
+
const cmd = useBun ? "bun add -g @hasna/todos@latest" : "npm install -g @hasna/todos@latest";
|
|
9127
|
+
console.log(chalk.dim(`
|
|
9128
|
+
Running: ${cmd}`));
|
|
9129
|
+
execSync2(cmd, { stdio: "inherit" });
|
|
9130
|
+
console.log(chalk.green(`
|
|
9131
|
+
Updated to ${latestVersion}!`));
|
|
9132
|
+
} catch (e) {
|
|
9133
|
+
handleError(e);
|
|
9134
|
+
}
|
|
9135
|
+
});
|
|
8650
9136
|
program2.command("interactive").description("Launch interactive TUI").action(async () => {
|
|
8651
9137
|
const { renderApp: renderApp2 } = await Promise.resolve().then(() => (init_App(), exports_App));
|
|
8652
9138
|
const globalOpts = program2.opts();
|