OpenClawDashboard/bridge/src/routes/tasks-file.ts
2026-06-05 19:49:23 +00:00

114 lines
No EOL
4.1 KiB
TypeScript

/**
* routes/tasks-file.ts — Read tasks and projects from Tiger's markdown files
*
* Endpoints:
* GET /tiger/file-tasks — all tasks from TASKS.md
* GET /tiger/file-tasks/active — only active/pending-action tasks
* GET /tiger/file-tasks/completed — only completed tasks
* GET /tiger/file-tasks/projects — all projects from PROJECTS.md
*/
import { Router, Request, Response } from "express";
import { execInSandbox } from "../tiger.js";
const router = Router();
function parseTasksJsonBlock(stdout: string): any[] {
const match = stdout.match(/```json\s+TASKS\s*\n([\s\S]+?)\n```/);
if (!match) {
throw new Error("TASKS.md missing TASKS_JSON block.");
}
try {
return JSON.parse(match[1]);
} catch (e: any) {
throw new Error(`TASKS_JSON block is not valid JSON: ${e.message}`);
}
}
function parseProjectsMarkdown(stdout: string) {
const projects: any[] = [];
const lines = stdout.split("\n");
let inActive = false;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith("## Active Projects")) { inActive = true; continue; }
if (trimmed.startsWith("## Completed Projects")) break;
if (inActive && trimmed.match(/^\| \d/)) {
const cols = trimmed.split("|").map((c: string) => c.trim()).filter(Boolean);
if (cols.length >= 5) {
projects.push({
id: cols[0],
name: cols[1],
description: cols[2],
created: cols[3],
tasks_count: cols[4],
status: cols[5] || "active",
});
}
}
}
return projects;
}
// GET /tiger/file-tasks — all tasks
router.get("/", async (req: Request, res: Response) => {
try {
const { stdout } = await execInSandbox("cat /home/node/.openclaw/workspace/TASKS.md");
const allTasks = parseTasksJsonBlock(stdout);
const projectFilter = (req.query.project as string || "").trim().toLowerCase();
const filtered = projectFilter
? allTasks.filter((t: any) => {
const pid = (t.project || "").toLowerCase().replace(/^p0?/, "");
return (
pid.includes(projectFilter) ||
t.project?.toLowerCase().includes(projectFilter) ||
t.title?.toLowerCase().includes(projectFilter)
);
})
: allTasks;
res.json({ ok: true, source: "TASKS.md", count: filtered.length, tasks: filtered });
} catch (err: any) {
const status = err.message?.includes("missing TASKS_JSON") ? 502 : 500;
res.status(status).json({ ok: false, error: err.message });
}
});
// GET /tiger/file-tasks/active — active + pending-action tasks only
router.get("/active", async (_req: Request, res: Response) => {
try {
const { stdout } = await execInSandbox("cat /home/node/.openclaw/workspace/TASKS.md");
const tasks = parseTasksJsonBlock(stdout).filter(
(t: any) => t.section === "in-progress" || t.section === "pending-action"
);
res.json({ ok: true, source: "TASKS.md", count: tasks.length, tasks });
} catch (err: any) {
const status = err.message?.includes("missing TASKS_JSON") ? 502 : 500;
res.status(status).json({ ok: false, error: err.message });
}
});
// GET /tiger/file-tasks/completed — completed tasks only
router.get("/completed", async (_req: Request, res: Response) => {
try {
const { stdout } = await execInSandbox("cat /home/node/.openclaw/workspace/TASKS.md");
const tasks = parseTasksJsonBlock(stdout).filter((t: any) => t.section === "completed");
res.json({ ok: true, source: "TASKS.md", count: tasks.length, tasks });
} catch (err: any) {
const status = err.message?.includes("missing TASKS_JSON") ? 502 : 500;
res.status(status).json({ ok: false, error: err.message });
}
});
// GET /tiger/file-tasks/projects — all projects
router.get("/projects", async (_req: Request, res: Response) => {
try {
const { stdout } = await execInSandbox("cat /home/node/.openclaw/workspace/PROJECTS.md");
const projects = parseProjectsMarkdown(stdout);
res.json({ ok: true, source: "PROJECTS.md", count: projects.length, projects });
} catch (err: any) {
res.status(500).json({ ok: false, error: err.message });
}
});
export default router;