claudeboard 1.0.0 → 1.3.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/agents/board-client.js +27 -0
- package/agents/claude-api.js +18 -2
- package/agents/orchestrator.js +22 -4
- package/bin/cli.js +19 -4
- package/dashboard/index.html +689 -595
- package/package.json +1 -1
package/agents/board-client.js
CHANGED
|
@@ -77,6 +77,33 @@ export async function createTask({ epicId, title, description, priority = "mediu
|
|
|
77
77
|
return data;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Check if this project already has tasks in the board
|
|
82
|
+
* Used to detect resume vs fresh start
|
|
83
|
+
*/
|
|
84
|
+
export async function hasTasks() {
|
|
85
|
+
const { data, error } = await supabase
|
|
86
|
+
.from("cb_tasks")
|
|
87
|
+
.select("id")
|
|
88
|
+
.eq("project", PROJECT)
|
|
89
|
+
.limit(1);
|
|
90
|
+
return Array.isArray(data) && data.length > 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reset any stuck "in_progress" tasks back to "todo"
|
|
95
|
+
* Called on resume so the agent re-picks them up
|
|
96
|
+
*/
|
|
97
|
+
export async function resetStuckTasks() {
|
|
98
|
+
const { data } = await supabase
|
|
99
|
+
.from("cb_tasks")
|
|
100
|
+
.update({ status: "todo", started_at: null })
|
|
101
|
+
.eq("project", PROJECT)
|
|
102
|
+
.eq("status", "in_progress")
|
|
103
|
+
.select();
|
|
104
|
+
return data?.length || 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
80
107
|
export async function getStats() {
|
|
81
108
|
const tasks = await getAllTasks();
|
|
82
109
|
return {
|
package/agents/claude-api.js
CHANGED
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
const MODEL = "claude-sonnet-4-20250514";
|
|
7
7
|
const MAX_TOKENS = 8096;
|
|
8
8
|
|
|
9
|
+
function getHeaders() {
|
|
10
|
+
const key = process.env.ANTHROPIC_API_KEY;
|
|
11
|
+
if (!key) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
"ANTHROPIC_API_KEY not set.\n" +
|
|
14
|
+
"Run: claudeboard init (and enter your API key)\n" +
|
|
15
|
+
"Or: export ANTHROPIC_API_KEY=sk-ant-..."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
"x-api-key": key,
|
|
21
|
+
"anthropic-version": "2023-06-01",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
/**
|
|
10
26
|
* Call Claude API and get text response
|
|
11
27
|
*/
|
|
@@ -21,7 +37,7 @@ export async function callClaude(systemPrompt, userMessage, options = {}) {
|
|
|
21
37
|
|
|
22
38
|
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
23
39
|
method: "POST",
|
|
24
|
-
headers:
|
|
40
|
+
headers: getHeaders(),
|
|
25
41
|
body: JSON.stringify(body),
|
|
26
42
|
});
|
|
27
43
|
|
|
@@ -81,7 +97,7 @@ export async function callClaudeWithImage(systemPrompt, userMessage, imageBase64
|
|
|
81
97
|
|
|
82
98
|
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
83
99
|
method: "POST",
|
|
84
|
-
headers:
|
|
100
|
+
headers: getHeaders(),
|
|
85
101
|
body: JSON.stringify(body),
|
|
86
102
|
});
|
|
87
103
|
|
package/agents/orchestrator.js
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
addLog,
|
|
14
14
|
createTask,
|
|
15
15
|
createEpic,
|
|
16
|
+
hasTasks,
|
|
17
|
+
resetStuckTasks,
|
|
16
18
|
} from "./board-client.js";
|
|
17
19
|
import { initSupabaseReader } from "../tools/supabase-reader.js";
|
|
18
20
|
import { runCommand, startProcess, waitForPort } from "../tools/terminal.js";
|
|
@@ -49,15 +51,31 @@ export async function runOrchestrator(config) {
|
|
|
49
51
|
console.log(chalk.dim(` Project: ${projectPath}\n`));
|
|
50
52
|
|
|
51
53
|
let techStack = {};
|
|
54
|
+
let isResume = false;
|
|
52
55
|
|
|
53
|
-
// ──
|
|
54
|
-
|
|
56
|
+
// ── DETECT: RESUME or FRESH START ─────────────────────────────────────────
|
|
57
|
+
const existingTasks = await hasTasks();
|
|
58
|
+
|
|
59
|
+
if (existingTasks && !config.forceRestart) {
|
|
60
|
+
isResume = true;
|
|
61
|
+
const stuck = await resetStuckTasks();
|
|
62
|
+
const stats = await getStats();
|
|
63
|
+
|
|
64
|
+
console.log(chalk.yellow(" ↩️ Resuming existing session\n"));
|
|
65
|
+
console.log(chalk.dim(` Found ${stats.total} tasks — ${stats.done} done, ${stats.todo} todo, ${stats.error} failed`));
|
|
66
|
+
if (stuck > 0) {
|
|
67
|
+
console.log(chalk.dim(` Reset ${stuck} stuck task(s) back to todo`));
|
|
68
|
+
}
|
|
69
|
+
console.log();
|
|
70
|
+
} else {
|
|
71
|
+
// ── PHASE 1: ARCHITECTURE ────────────────────────────────────────────────
|
|
72
|
+
if (config.forceRestart) {
|
|
73
|
+
console.log(chalk.dim(" Force restart — skipping existing tasks (they remain in board)\n"));
|
|
74
|
+
}
|
|
55
75
|
console.log(chalk.bold.cyan("[ PHASE 1: ARCHITECTURE ]\n"));
|
|
56
76
|
const archResult = await runArchitectAgent(prdContent, projectName);
|
|
57
77
|
techStack = archResult.techStack;
|
|
58
78
|
console.log(chalk.green(`\n ✓ ${archResult.totalTasks} tasks created across ${archResult.epics.length} epics\n`));
|
|
59
|
-
} else {
|
|
60
|
-
console.log(chalk.dim(" Skipping architect (tasks already exist)\n"));
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
// ── START EXPO ─────────────────────────────────────────────────────────────
|
package/bin/cli.js
CHANGED
|
@@ -19,7 +19,17 @@ ${chalk.cyan("╚═════════════════════
|
|
|
19
19
|
|
|
20
20
|
function loadConfig() {
|
|
21
21
|
try {
|
|
22
|
-
|
|
22
|
+
const config = JSON.parse(fs.readFileSync(path.join(process.cwd(), ".claudeboard.json"), "utf8"));
|
|
23
|
+
// Inject API key into environment automatically
|
|
24
|
+
if (config.anthropicKey) {
|
|
25
|
+
process.env.ANTHROPIC_API_KEY = config.anthropicKey;
|
|
26
|
+
}
|
|
27
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
28
|
+
console.log(chalk.yellow("⚠️ ANTHROPIC_API_KEY not found."));
|
|
29
|
+
console.log(chalk.dim(" Run claudeboard init again, or: export ANTHROPIC_API_KEY=sk-ant-..."));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
23
33
|
} catch {
|
|
24
34
|
console.log(chalk.yellow("No .claudeboard.json found. Run: claudeboard init"));
|
|
25
35
|
process.exit(1);
|
|
@@ -40,6 +50,7 @@ program
|
|
|
40
50
|
{ type: "input", name: "projectName", message: "Project name:", initial: path.basename(process.cwd()) },
|
|
41
51
|
{ type: "input", name: "supabaseUrl", message: "Supabase URL:", hint: "https://xxxx.supabase.co" },
|
|
42
52
|
{ type: "input", name: "supabaseKey", message: "Supabase anon key:" },
|
|
53
|
+
{ type: "password", name: "anthropicKey", message: "Anthropic API key:", hint: "sk-ant-..." },
|
|
43
54
|
{ type: "input", name: "port", message: "Dashboard port:", initial: "3131" },
|
|
44
55
|
]);
|
|
45
56
|
|
|
@@ -50,6 +61,7 @@ program
|
|
|
50
61
|
port: parseInt(answers.port),
|
|
51
62
|
supabaseUrl: answers.supabaseUrl,
|
|
52
63
|
supabaseKey: answers.supabaseKey,
|
|
64
|
+
anthropicKey: answers.anthropicKey,
|
|
53
65
|
createdAt: new Date().toISOString(),
|
|
54
66
|
};
|
|
55
67
|
|
|
@@ -91,6 +103,7 @@ program
|
|
|
91
103
|
...process.env,
|
|
92
104
|
SUPABASE_URL: config.supabaseUrl,
|
|
93
105
|
SUPABASE_KEY: config.supabaseKey,
|
|
106
|
+
ANTHROPIC_API_KEY: config.anthropicKey || process.env.ANTHROPIC_API_KEY || "",
|
|
94
107
|
PORT: String(port),
|
|
95
108
|
PROJECT_NAME: config.projectName,
|
|
96
109
|
},
|
|
@@ -116,7 +129,7 @@ program
|
|
|
116
129
|
.description("Run the AI engineering team on your project")
|
|
117
130
|
.requiredOption("--prd <path>", "Path to PRD markdown file")
|
|
118
131
|
.requiredOption("--project <path>", "Path to your app project directory")
|
|
119
|
-
.option("--
|
|
132
|
+
.option("--restart", "Force restart from scratch (ignore existing tasks in board)")
|
|
120
133
|
.option("--expo-port <port>", "Expo Web port for QA screenshots", "8081")
|
|
121
134
|
.action(async (opts) => {
|
|
122
135
|
console.log(LOGO);
|
|
@@ -132,7 +145,9 @@ program
|
|
|
132
145
|
console.log(chalk.bold(` AI Team starting for: ${chalk.cyan(config.projectName)}\n`));
|
|
133
146
|
console.log(chalk.dim(` Dashboard → http://localhost:${config.port}`));
|
|
134
147
|
console.log(chalk.dim(` PRD → ${opts.prd}`));
|
|
135
|
-
console.log(chalk.dim(` Project → ${opts.project}
|
|
148
|
+
console.log(chalk.dim(` Project → ${opts.project}`));
|
|
149
|
+
if (opts.restart) console.log(chalk.yellow(` Mode → FORCE RESTART\n`));
|
|
150
|
+
else console.log(chalk.dim(` Mode → auto-resume if tasks exist\n`));
|
|
136
151
|
|
|
137
152
|
const { runOrchestrator } = await import("../agents/orchestrator.js");
|
|
138
153
|
|
|
@@ -143,7 +158,7 @@ program
|
|
|
143
158
|
supabaseKey: config.supabaseKey,
|
|
144
159
|
projectName: config.projectName,
|
|
145
160
|
expoPort: parseInt(opts.expoPort),
|
|
146
|
-
|
|
161
|
+
forceRestart: !!opts.restart,
|
|
147
162
|
});
|
|
148
163
|
});
|
|
149
164
|
|