claudeboard 1.0.0

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/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # ClaudeBoard 🤖
2
+
3
+ **Autonomous coding dashboard for Claude Code.**
4
+ Turn a PRD into tasks → let Claude work autonomously → watch progress in real time.
5
+
6
+ ---
7
+
8
+ ## How it works
9
+
10
+ ```
11
+ claudeboard init → Configure project + Supabase
12
+ claudeboard import-prd → Parse PRD → create tasks automatically
13
+ claudeboard start → Launch dashboard on localhost
14
+ → Give Claude Code the AGENT.md file
15
+ → Claude works autonomously 24/7
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install -g claudeboard
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Setup (one time per project)
29
+
30
+ ### 1. Init
31
+ ```bash
32
+ cd your-project
33
+ claudeboard init
34
+ ```
35
+ You'll be asked for:
36
+ - Project name
37
+ - Supabase URL
38
+ - Supabase anon key
39
+ - Port (default 3131)
40
+
41
+ ### 2. Run SQL in Supabase
42
+ Open `claudeboard-setup.sql` and run it in your Supabase SQL Editor.
43
+ This creates the tables `cb_epics`, `cb_tasks`, `cb_logs` with Realtime enabled.
44
+
45
+ ### 3. Import your PRD
46
+ ```bash
47
+ claudeboard import-prd ./PRD.md
48
+ ```
49
+ Claude parses your PRD and creates structured tasks grouped by epic automatically.
50
+
51
+ ### 4. Start the dashboard
52
+ ```bash
53
+ claudeboard start
54
+ ```
55
+ Opens `http://localhost:3131` in your browser.
56
+
57
+ ---
58
+
59
+ ## Running Claude Code autonomously
60
+
61
+ ```bash
62
+ claude --context AGENT.md
63
+ ```
64
+
65
+ The `AGENT.md` file (auto-generated by `claudeboard init`) tells Claude to:
66
+ 1. Fetch the next pending task from the API
67
+ 2. Start it → mark as `in_progress`
68
+ 3. Do the work (write code, run tests, fix errors)
69
+ 4. Log progress in real time
70
+ 5. Mark as `done` or `error`
71
+ 6. Repeat until all tasks are complete
72
+
73
+ ---
74
+
75
+ ## Dashboard features
76
+
77
+ - **Kanban view** — tasks grouped by epic with status colors
78
+ - **Live activity log** — every action Claude takes
79
+ - **Progress bar** — overall completion %
80
+ - **Add tasks** — add new tasks manually (Claude picks them up automatically)
81
+ - **Task detail** — click any task to see its full log
82
+ - **Real-time** — WebSocket updates, no refresh needed
83
+
84
+ ---
85
+
86
+ ## API (used by Claude Code)
87
+
88
+ | Method | Endpoint | Description |
89
+ |--------|----------|-------------|
90
+ | GET | `/api/board` | Full board state |
91
+ | GET | `/api/tasks/next` | Next pending task |
92
+ | POST | `/api/tasks/:id/start` | Mark task as in_progress |
93
+ | POST | `/api/tasks/:id/log` | Add a log entry |
94
+ | POST | `/api/tasks/:id/complete` | Mark task as done |
95
+ | POST | `/api/tasks/:id/fail` | Mark task as error |
96
+ | POST | `/api/tasks` | Add a new task |
97
+ | GET | `/api/tasks/:id/logs` | Get logs for a task |
98
+
99
+ ---
100
+
101
+ ## Remote access
102
+
103
+ To monitor from another computer:
104
+
105
+ ```bash
106
+ # On the notebook running claudeboard:
107
+ # Install Tailscale: https://tailscale.com
108
+ tailscale up
109
+
110
+ # Then from your main computer:
111
+ # Visit http://<notebook-tailscale-ip>:3131
112
+ ```
113
+
114
+ Or with SSH tunnel:
115
+ ```bash
116
+ ssh -L 3131:localhost:3131 user@notebook-ip
117
+ # Then open http://localhost:3131 locally
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Stack
123
+
124
+ - **CLI**: Node.js + Commander + Enquirer
125
+ - **Server**: Express + WebSockets
126
+ - **Database**: Supabase (Postgres + Realtime)
127
+ - **Dashboard**: Vanilla HTML/CSS/JS (no build step)
128
+ - **AI parsing**: Claude claude-sonnet-4-20250514 for PRD analysis
@@ -0,0 +1,76 @@
1
+ import { callClaudeJSON } from "./claude-api.js";
2
+ import { createEpic, createTask, addLog } from "./board-client.js";
3
+
4
+ const SYSTEM = `You are a senior mobile app architect specializing in React Native / Expo apps.
5
+ Your job is to read a PRD and produce a complete, ordered task breakdown.
6
+
7
+ Rules:
8
+ - Think like a real engineering team: setup first, core features, then polish, then QA
9
+ - Each task must be self-contained and implementable by a single developer agent
10
+ - Be specific — tasks like "implement auth" are too vague. Break into: "create login screen UI", "implement Supabase auth hook", "add protected route navigation"
11
+ - Always include: project setup, navigation, data layer, each feature screen, error handling, loading states, and final QA tasks
12
+ - Priority: high = blocking/core, medium = main features, low = polish/nice-to-have
13
+ - Types: config (setup/deps), feature (new screen or functionality), bug (fix), refactor, test (QA task)`;
14
+
15
+ export async function runArchitectAgent(prdContent, projectName) {
16
+ console.log(" 🏗️ Architect analyzing PRD...");
17
+
18
+ const result = await callClaudeJSON(SYSTEM, `
19
+ Project: ${projectName}
20
+
21
+ PRD:
22
+ ${prdContent}
23
+
24
+ Return this JSON structure:
25
+ {
26
+ "techStack": {
27
+ "framework": "expo",
28
+ "navigation": "expo-router or react-navigation",
29
+ "stateManagement": "...",
30
+ "backend": "supabase / firebase / none",
31
+ "ui": "nativewind / tamagui / stylesheet",
32
+ "otherDeps": ["list of npm packages needed"]
33
+ },
34
+ "epics": [
35
+ {
36
+ "name": "Epic name (e.g. Project Setup, Authentication, Home Screen)",
37
+ "order": 1,
38
+ "tasks": [
39
+ {
40
+ "title": "Specific task title",
41
+ "description": "Detailed description of exactly what needs to be implemented. Include: what file(s) to create/modify, what the component/function should do, any specific requirements from the PRD.",
42
+ "priority": "high|medium|low",
43
+ "type": "config|feature|test",
44
+ "acceptanceCriteria": "How to verify this task is done correctly"
45
+ }
46
+ ]
47
+ }
48
+ ]
49
+ }
50
+
51
+ Order epics from first to last in implementation order.
52
+ Include 25-50 tasks total for a complete mobile app.`);
53
+
54
+ console.log(` ✓ Architect created ${result.epics.length} epics`);
55
+
56
+ // Persist to board
57
+ let totalTasks = 0;
58
+ for (let i = 0; i < result.epics.length; i++) {
59
+ const epicData = result.epics[i];
60
+ const epic = await createEpic(epicData.name);
61
+
62
+ for (const task of epicData.tasks) {
63
+ await createTask({
64
+ epicId: epic.id,
65
+ title: task.title,
66
+ description: `${task.description}\n\nAcceptance: ${task.acceptanceCriteria || ""}`,
67
+ priority: task.priority,
68
+ type: task.type,
69
+ });
70
+ totalTasks++;
71
+ }
72
+ }
73
+
74
+ console.log(` ✓ Created ${totalTasks} tasks in board`);
75
+ return { techStack: result.techStack, epics: result.epics, totalTasks };
76
+ }
@@ -0,0 +1,91 @@
1
+ import { createClient } from "@supabase/supabase-js";
2
+
3
+ let supabase = null;
4
+ let PROJECT = "default";
5
+
6
+ export function initBoard(url, key, project) {
7
+ supabase = createClient(url, key);
8
+ PROJECT = project;
9
+ }
10
+
11
+ export async function getNextTask() {
12
+ const { data } = await supabase
13
+ .from("cb_tasks")
14
+ .select("*, cb_epics(name)")
15
+ .eq("project", PROJECT)
16
+ .eq("status", "todo")
17
+ .order("priority_order", { ascending: true })
18
+ .limit(1)
19
+ .single();
20
+ return data || null;
21
+ }
22
+
23
+ export async function getAllTasks() {
24
+ const { data } = await supabase
25
+ .from("cb_tasks")
26
+ .select("*, cb_epics(name)")
27
+ .eq("project", PROJECT)
28
+ .order("priority_order");
29
+ return data || [];
30
+ }
31
+
32
+ export async function startTask(id, log) {
33
+ await supabase.from("cb_tasks").update({ status: "in_progress", started_at: new Date().toISOString() }).eq("id", id);
34
+ if (log) await addLog(id, log, "start");
35
+ }
36
+
37
+ export async function completeTask(id, log) {
38
+ await supabase.from("cb_tasks").update({ status: "done", completed_at: new Date().toISOString() }).eq("id", id);
39
+ if (log) await addLog(id, log, "complete");
40
+ }
41
+
42
+ export async function failTask(id, log) {
43
+ await supabase.from("cb_tasks").update({ status: "error" }).eq("id", id);
44
+ if (log) await addLog(id, log, "error");
45
+ }
46
+
47
+ export async function blockTask(id, log) {
48
+ await supabase.from("cb_tasks").update({ status: "blocked" }).eq("id", id);
49
+ if (log) await addLog(id, log, "error");
50
+ }
51
+
52
+ export async function addLog(taskId, message, type = "progress") {
53
+ await supabase.from("cb_logs").insert({ project: PROJECT, task_id: taskId, message, type });
54
+ }
55
+
56
+ export async function createEpic(name) {
57
+ const { data } = await supabase.from("cb_epics").insert({ name, project: PROJECT }).select().single();
58
+ return data;
59
+ }
60
+
61
+ export async function createTask({ epicId, title, description, priority = "medium", type = "feature" }) {
62
+ const priorityOrder = { high: 1, medium: 2, low: 3 };
63
+ const { data } = await supabase
64
+ .from("cb_tasks")
65
+ .insert({
66
+ project: PROJECT,
67
+ epic_id: epicId,
68
+ title,
69
+ description,
70
+ priority,
71
+ priority_order: priorityOrder[priority] || 2,
72
+ type,
73
+ status: "todo",
74
+ })
75
+ .select()
76
+ .single();
77
+ return data;
78
+ }
79
+
80
+ export async function getStats() {
81
+ const tasks = await getAllTasks();
82
+ return {
83
+ total: tasks.length,
84
+ todo: tasks.filter((t) => t.status === "todo").length,
85
+ in_progress: tasks.filter((t) => t.status === "in_progress").length,
86
+ done: tasks.filter((t) => t.status === "done").length,
87
+ error: tasks.filter((t) => t.status === "error").length,
88
+ blocked: tasks.filter((t) => t.status === "blocked").length,
89
+ pct: tasks.length > 0 ? Math.round((tasks.filter((t) => t.status === "done").length / tasks.length) * 100) : 0,
90
+ };
91
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Core Claude API caller for all agents
3
+ * All agents use claude-sonnet-4-20250514 with specific system prompts and tools
4
+ */
5
+
6
+ const MODEL = "claude-sonnet-4-20250514";
7
+ const MAX_TOKENS = 8096;
8
+
9
+ /**
10
+ * Call Claude API and get text response
11
+ */
12
+ export async function callClaude(systemPrompt, userMessage, options = {}) {
13
+ const body = {
14
+ model: MODEL,
15
+ max_tokens: options.maxTokens || MAX_TOKENS,
16
+ system: systemPrompt,
17
+ messages: [{ role: "user", content: userMessage }],
18
+ };
19
+
20
+ if (options.tools) body.tools = options.tools;
21
+
22
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify(body),
26
+ });
27
+
28
+ if (!response.ok) {
29
+ const err = await response.text();
30
+ throw new Error(`Claude API error ${response.status}: ${err}`);
31
+ }
32
+
33
+ const data = await response.json();
34
+
35
+ // Extract text content
36
+ const text = data.content
37
+ .filter((b) => b.type === "text")
38
+ .map((b) => b.text)
39
+ .join("");
40
+
41
+ return { text, raw: data };
42
+ }
43
+
44
+ /**
45
+ * Call Claude API expecting JSON response
46
+ * Returns parsed object or throws
47
+ */
48
+ export async function callClaudeJSON(systemPrompt, userMessage, options = {}) {
49
+ const sys = systemPrompt + "\n\nYou MUST respond with valid JSON only. No markdown, no explanation, no backticks. Pure JSON.";
50
+ const { text } = await callClaude(sys, userMessage, options);
51
+
52
+ try {
53
+ const clean = text.replace(/```json|```/g, "").trim();
54
+ return JSON.parse(clean);
55
+ } catch (err) {
56
+ throw new Error(`Failed to parse JSON from Claude: ${text.slice(0, 200)}`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Call Claude with an image (for visual QA)
62
+ */
63
+ export async function callClaudeWithImage(systemPrompt, userMessage, imageBase64, mediaType = "image/png") {
64
+ const body = {
65
+ model: MODEL,
66
+ max_tokens: MAX_TOKENS,
67
+ system: systemPrompt,
68
+ messages: [
69
+ {
70
+ role: "user",
71
+ content: [
72
+ {
73
+ type: "image",
74
+ source: { type: "base64", media_type: mediaType, data: imageBase64 },
75
+ },
76
+ { type: "text", text: userMessage },
77
+ ],
78
+ },
79
+ ],
80
+ };
81
+
82
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
83
+ method: "POST",
84
+ headers: { "Content-Type": "application/json" },
85
+ body: JSON.stringify(body),
86
+ });
87
+
88
+ const data = await response.json();
89
+ const text = data.content?.filter((b) => b.type === "text").map((b) => b.text).join("") || "";
90
+ return { text, raw: data };
91
+ }
@@ -0,0 +1,161 @@
1
+ import { callClaudeJSON, callClaude } from "./claude-api.js";
2
+ import { startTask, completeTask, failTask, addLog } from "./board-client.js";
3
+ import { readFile, writeFile, listFiles, projectTree, readFilesAsContext } from "../tools/filesystem.js";
4
+ import { runCommand } from "../tools/terminal.js";
5
+ import path from "path";
6
+
7
+ const SYSTEM = `You are a senior React Native / Expo developer. You write clean, production-ready code.
8
+
9
+ Rules:
10
+ - Write complete, working code — never use placeholders or TODOs
11
+ - Always use TypeScript when the project uses it
12
+ - Follow the existing code style and patterns in the project
13
+ - Add console.log statements for key operations so QA can verify functionality
14
+ - Add error handling with meaningful error messages
15
+ - When creating screens, make them visually polished — proper spacing, colors, typography
16
+ - Use the existing navigation structure — never change routing patterns mid-project
17
+ - Import only packages that are already installed or that you explicitly install first
18
+
19
+ When you need to create or modify files, respond with a JSON array of file operations.`;
20
+
21
+ export async function runDeveloperAgent(task, projectPath, techStack, retryContext = null) {
22
+ console.log(` 💻 Developer working on: ${task.title}`);
23
+ await startTask(task.id, `Starting implementation: ${task.title}`);
24
+
25
+ const MAX_RETRIES = 3;
26
+ let attempt = 0;
27
+ let lastError = null;
28
+
29
+ while (attempt < MAX_RETRIES) {
30
+ attempt++;
31
+ if (attempt > 1) {
32
+ console.log(` 🔄 Retry ${attempt}/${MAX_RETRIES} for: ${task.title}`);
33
+ await addLog(task.id, `Retry ${attempt}: fixing — ${lastError}`, "progress");
34
+ }
35
+
36
+ try {
37
+ // Gather context
38
+ const tree = projectTree(projectPath);
39
+ const relevantFiles = getRelevantFiles(task, projectPath);
40
+ const fileContext = readFilesAsContext(relevantFiles, projectPath);
41
+
42
+ const retryNote = retryContext
43
+ ? `\n\nPREVIOUS ATTEMPT FAILED:\n${retryContext}\n\nFix these issues.`
44
+ : "";
45
+
46
+ const lastErrorNote = lastError
47
+ ? `\n\nLAST ERROR: ${lastError}\nFix this specifically.`
48
+ : "";
49
+
50
+ const prompt = `
51
+ Task: ${task.title}
52
+ Description: ${task.description}
53
+
54
+ Project structure:
55
+ ${tree}
56
+
57
+ Relevant existing files:
58
+ ${fileContext || "No existing files yet — this may be a fresh setup task."}
59
+
60
+ Tech stack: ${JSON.stringify(techStack || {}, null, 2)}
61
+ ${retryNote}${lastErrorNote}
62
+
63
+ Respond with JSON:
64
+ {
65
+ "plan": "Brief explanation of your approach",
66
+ "installPackages": ["pkg1", "pkg2"],
67
+ "files": [
68
+ {
69
+ "path": "relative/path/from/project/root.tsx",
70
+ "action": "create|modify|delete",
71
+ "content": "complete file content here"
72
+ }
73
+ ],
74
+ "commands": ["any post-file commands to run, e.g. npx expo install package"],
75
+ "verificationSteps": "What to check to verify this works"
76
+ }`;
77
+
78
+ const result = await callClaudeJSON(SYSTEM, prompt);
79
+
80
+ // Install packages first
81
+ if (result.installPackages?.length > 0) {
82
+ const pkgs = result.installPackages.join(" ");
83
+ await addLog(task.id, `Installing: ${pkgs}`, "progress");
84
+ const install = await runCommand(`npx expo install ${pkgs}`, projectPath, 120000);
85
+ if (install.exitCode !== 0) {
86
+ // Try npm install as fallback
87
+ await runCommand(`npm install ${pkgs}`, projectPath, 120000);
88
+ }
89
+ }
90
+
91
+ // Write files
92
+ for (const file of result.files || []) {
93
+ const fullPath = path.join(projectPath, file.path);
94
+ if (file.action === "delete") {
95
+ try { fs.unlinkSync(fullPath); } catch {}
96
+ } else {
97
+ writeFile(fullPath, file.content);
98
+ await addLog(task.id, `${file.action}: ${file.path}`, "progress");
99
+ }
100
+ }
101
+
102
+ // Run any additional commands
103
+ for (const cmd of result.commands || []) {
104
+ await addLog(task.id, `Running: ${cmd}`, "progress");
105
+ await runCommand(cmd, projectPath, 60000);
106
+ }
107
+
108
+ // Verify it compiles
109
+ const typecheck = await runCommand("npx tsc --noEmit 2>&1 | head -20", projectPath, 30000);
110
+ const hasTSErrors = typecheck.stdout.includes("error TS");
111
+
112
+ if (hasTSErrors) {
113
+ lastError = `TypeScript errors:\n${typecheck.stdout}`;
114
+ continue; // retry
115
+ }
116
+
117
+ // Check for obvious JS syntax errors
118
+ const buildCheck = await runCommand(
119
+ "npx expo export --platform web --output-dir /tmp/cb-check 2>&1 | tail -10",
120
+ projectPath,
121
+ 90000
122
+ );
123
+
124
+ if (buildCheck.stdout.includes("ERROR") || buildCheck.stdout.includes("Cannot find")) {
125
+ lastError = `Build error:\n${buildCheck.stdout}`;
126
+ continue; // retry
127
+ }
128
+
129
+ await completeTask(task.id, `✓ Implemented: ${result.plan}`);
130
+ console.log(` ✓ Done: ${task.title}`);
131
+ return { success: true, files: result.files, verificationSteps: result.verificationSteps };
132
+
133
+ } catch (err) {
134
+ lastError = err.message;
135
+ console.error(` ✗ Error on attempt ${attempt}:`, err.message);
136
+ }
137
+ }
138
+
139
+ await failTask(task.id, `Failed after ${MAX_RETRIES} attempts. Last error: ${lastError}`);
140
+ return { success: false, error: lastError };
141
+ }
142
+
143
+ /**
144
+ * Get files relevant to this task based on keywords
145
+ */
146
+ function getRelevantFiles(task, projectPath) {
147
+ const keywords = task.title.toLowerCase().split(" ");
148
+ const allFiles = listFiles(projectPath, [".ts", ".tsx", ".js", ".jsx", ".json"]);
149
+
150
+ // Always include key config files
151
+ const alwaysInclude = ["package.json", "app.json", "tsconfig.json", "babel.config.js"];
152
+ const result = allFiles.filter((f) => {
153
+ const rel = path.relative(projectPath, f);
154
+ if (alwaysInclude.some((name) => rel.endsWith(name))) return true;
155
+ if (rel.includes("app/") && rel.endsWith("_layout")) return true;
156
+ // Include files that match task keywords
157
+ return keywords.some((kw) => kw.length > 4 && rel.toLowerCase().includes(kw));
158
+ });
159
+
160
+ return result.slice(0, 15); // Max 15 files for context
161
+ }