prodboard 0.1.3 → 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/CHANGELOG.md +12 -0
- package/README.md +119 -9
- package/package.json +4 -2
- package/src/agents/claude.ts +107 -0
- package/src/agents/index.ts +18 -0
- package/src/agents/opencode.ts +108 -0
- package/src/agents/types.ts +41 -0
- package/src/commands/daemon.ts +57 -1
- package/src/commands/install.ts +21 -2
- package/src/commands/schedules.ts +1 -0
- package/src/config.ts +102 -3
- package/src/db.ts +7 -0
- package/src/index.ts +4 -0
- package/src/invocation.ts +13 -66
- package/src/opencode-server.ts +54 -0
- package/src/queries/runs.ts +5 -4
- package/src/scheduler.ts +240 -155
- package/src/tmux.ts +68 -0
- package/src/types.ts +17 -0
- package/src/webui/components/board.tsx +32 -0
- package/src/webui/components/layout.tsx +28 -0
- package/src/webui/components/status-badge.tsx +23 -0
- package/src/webui/index.ts +85 -0
- package/src/webui/routes/auth.tsx +57 -0
- package/src/webui/routes/issues.tsx +162 -0
- package/src/webui/routes/runs.tsx +103 -0
- package/src/webui/routes/schedules.tsx +144 -0
- package/src/webui/static/style.ts +47 -0
- package/src/worktree.ts +75 -0
- package/templates/CLAUDE.md +3 -0
- package/templates/config.jsonc +43 -1
package/src/worktree.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
|
|
4
|
+
export class WorktreeManager {
|
|
5
|
+
constructor(private basePath: string) {}
|
|
6
|
+
|
|
7
|
+
async create(runId: string, sourceDir: string): Promise<string> {
|
|
8
|
+
const worktreesDir = path.join(this.basePath, ".worktrees");
|
|
9
|
+
const worktreePath = path.join(worktreesDir, runId);
|
|
10
|
+
const branchName = `prodboard/${runId}`;
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(worktreesDir)) {
|
|
13
|
+
fs.mkdirSync(worktreesDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = Bun.spawnSync(
|
|
17
|
+
["git", "worktree", "add", worktreePath, "-b", branchName],
|
|
18
|
+
{ cwd: sourceDir, stdout: "pipe", stderr: "pipe" }
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
if (result.exitCode !== 0) {
|
|
22
|
+
const stderr = new TextDecoder().decode(result.stderr);
|
|
23
|
+
throw new Error(`Failed to create worktree: ${stderr}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return path.resolve(worktreePath);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async remove(runId: string): Promise<void> {
|
|
30
|
+
const worktreePath = path.join(this.basePath, ".worktrees", runId);
|
|
31
|
+
const branchName = `prodboard/${runId}`;
|
|
32
|
+
|
|
33
|
+
// Remove worktree
|
|
34
|
+
try {
|
|
35
|
+
Bun.spawnSync(
|
|
36
|
+
["git", "worktree", "remove", worktreePath, "--force"],
|
|
37
|
+
{ cwd: this.basePath, stdout: "pipe", stderr: "pipe" }
|
|
38
|
+
);
|
|
39
|
+
} catch {}
|
|
40
|
+
|
|
41
|
+
// Clean up the directory if it still exists
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(worktreePath)) {
|
|
44
|
+
fs.rmSync(worktreePath, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
|
|
48
|
+
// Delete the branch (try safe delete first, force-delete as fallback)
|
|
49
|
+
try {
|
|
50
|
+
const result = Bun.spawnSync(
|
|
51
|
+
["git", "branch", "-d", branchName],
|
|
52
|
+
{ cwd: this.basePath, stdout: "pipe", stderr: "pipe" }
|
|
53
|
+
);
|
|
54
|
+
if (result.exitCode !== 0) {
|
|
55
|
+
console.error(`[prodboard] Warning: Branch ${branchName} has unmerged commits, force-deleting`);
|
|
56
|
+
Bun.spawnSync(
|
|
57
|
+
["git", "branch", "-D", branchName],
|
|
58
|
+
{ cwd: this.basePath, stdout: "pipe", stderr: "pipe" }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isGitRepo(dir: string): boolean {
|
|
65
|
+
try {
|
|
66
|
+
const result = Bun.spawnSync(
|
|
67
|
+
["git", "rev-parse", "--git-dir"],
|
|
68
|
+
{ cwd: dir, stdout: "pipe", stderr: "pipe" }
|
|
69
|
+
);
|
|
70
|
+
return result.exitCode === 0;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
package/templates/CLAUDE.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
This project uses prodboard for issue tracking. The prodboard MCP server is configured and available.
|
|
4
4
|
|
|
5
|
+
## Supported Agents
|
|
6
|
+
prodboard supports multiple AI coding agents (Claude Code, OpenCode). The daemon is configured to use one agent at a time.
|
|
7
|
+
|
|
5
8
|
## Issue Workflow
|
|
6
9
|
- Check `board_summary` at the start of each session
|
|
7
10
|
- Use `pick_next_issue` to claim work
|
package/templates/config.jsonc
CHANGED
|
@@ -14,10 +14,26 @@
|
|
|
14
14
|
},
|
|
15
15
|
|
|
16
16
|
"daemon": {
|
|
17
|
+
// Agent to use: "claude" or "opencode"
|
|
18
|
+
// "agent": "claude",
|
|
19
|
+
|
|
20
|
+
// Base path for worktrees and agent runs (null = use schedule workdir)
|
|
21
|
+
// "basePath": null,
|
|
22
|
+
|
|
23
|
+
// Wrap agent runs in tmux sessions (allows attaching to running agents)
|
|
24
|
+
// "useTmux": true,
|
|
25
|
+
|
|
26
|
+
// OpenCode-specific settings (only used when agent is "opencode")
|
|
27
|
+
// "opencode": {
|
|
28
|
+
// "serverUrl": null, // auto-detect or override (e.g., "http://localhost:4096")
|
|
29
|
+
// "model": null, // e.g., "anthropic/claude-sonnet-4-20250514"
|
|
30
|
+
// "agent": null // opencode agent name
|
|
31
|
+
// },
|
|
32
|
+
|
|
17
33
|
// Maximum concurrent scheduled runs
|
|
18
34
|
// "maxConcurrentRuns": 2,
|
|
19
35
|
|
|
20
|
-
// Default max turns for
|
|
36
|
+
// Default max turns for agent invocations
|
|
21
37
|
// "maxTurns": 50,
|
|
22
38
|
|
|
23
39
|
// Absolute maximum turns (cannot be overridden by schedules)
|
|
@@ -32,7 +48,33 @@
|
|
|
32
48
|
// Log level: "debug", "info", "warn", "error"
|
|
33
49
|
// "logLevel": "info",
|
|
34
50
|
|
|
51
|
+
// Max size per log file in MB
|
|
52
|
+
// "logMaxSizeMb": 10,
|
|
53
|
+
|
|
54
|
+
// Max number of rotated log files
|
|
55
|
+
// "logMaxFiles": 5,
|
|
56
|
+
|
|
57
|
+
// Tools allowed for scheduled runs in git repos
|
|
58
|
+
// "defaultAllowedTools": ["Read", "Edit", "Write", "Glob", "Grep", "Bash", ...],
|
|
59
|
+
|
|
60
|
+
// Tools allowed for scheduled runs in non-git directories
|
|
61
|
+
// "nonGitDefaultAllowedTools": ["Read", "Edit", "Write", "Glob", "Grep", "Bash", ...],
|
|
62
|
+
|
|
35
63
|
// Worktree usage: "auto", "always", "never"
|
|
36
64
|
// "useWorktrees": "auto"
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
"webui": {
|
|
68
|
+
// Enable the web UI (served by the daemon)
|
|
69
|
+
// "enabled": false,
|
|
70
|
+
|
|
71
|
+
// Port for the web UI
|
|
72
|
+
// "port": 3838,
|
|
73
|
+
|
|
74
|
+
// Hostname to bind to
|
|
75
|
+
// "hostname": "127.0.0.1",
|
|
76
|
+
|
|
77
|
+
// Password for web UI access (null = no auth)
|
|
78
|
+
// "password": null
|
|
37
79
|
}
|
|
38
80
|
}
|