prodboard 0.0.0 → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # prodboard
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dc917e9: Initial release of prodboard — a self-hosted, CLI-first issue tracker and cron scheduler for AI coding agents.
8
+
9
+ Features:
10
+
11
+ - Full CLI for issue tracking (add, ls, show, edit, mv, rm, comment)
12
+ - MCP server with 14 tools for AI agent integration
13
+ - Cron-based scheduler daemon for automated agent runs
14
+ - SQLite-backed persistent storage
15
+ - Configurable via JSONC config file
16
+ - Template engine for schedule prompts with board context injection
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 prodboard contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,294 @@
1
+ # prodboard
2
+
3
+ A self-hosted, CLI-first issue tracker and cron scheduler for AI coding agents.
4
+
5
+ ## Overview
6
+
7
+ prodboard provides three interfaces for managing issues and scheduled AI tasks:
8
+
9
+ - **CLI** — Human-friendly commands for issue tracking and schedule management
10
+ - **MCP Server** — Model Context Protocol server for AI agent integration
11
+ - **Scheduler Daemon** — Cron-based task scheduler that invokes Claude on a schedule
12
+
13
+ All state lives in a single SQLite database at `~/.prodboard/db.sqlite`.
14
+
15
+ ## Quick Start
16
+
17
+ ### Install
18
+
19
+ ```bash
20
+ bun install -g prodboard
21
+ ```
22
+
23
+ ### Initialize
24
+
25
+ ```bash
26
+ prodboard init
27
+ ```
28
+
29
+ This creates `~/.prodboard/` with the database, config, and generated files.
30
+
31
+ ### Connect to Claude
32
+
33
+ ```bash
34
+ claude mcp add prodboard -- bunx prodboard mcp
35
+ ```
36
+
37
+ Or copy the generated MCP config:
38
+
39
+ ```bash
40
+ cat ~/.prodboard/mcp.json
41
+ ```
42
+
43
+ ### Add the CLAUDE.md (optional)
44
+
45
+ ```bash
46
+ prodboard init --claude-md
47
+ ```
48
+
49
+ ## CLI Reference
50
+
51
+ ### Issue Management
52
+
53
+ ```bash
54
+ # Create an issue
55
+ prodboard add "Fix login bug" -d "SameSite cookie issue on Safari" -s todo
56
+
57
+ # List issues
58
+ prodboard ls # All non-archived issues
59
+ prodboard ls --status todo # Filter by status
60
+ prodboard ls --search "login" # Search title/description
61
+ prodboard ls --json # JSON output
62
+ prodboard ls --all # Include archived
63
+
64
+ # Show issue details
65
+ prodboard show <id> # Full ID or unique prefix
66
+ prodboard show a3f9 # Prefix match
67
+
68
+ # Edit an issue
69
+ prodboard edit <id> --title "New title"
70
+ prodboard edit <id> --status review
71
+ prodboard edit <id> -d "Updated description"
72
+
73
+ # Move issue status
74
+ prodboard mv <id> done
75
+
76
+ # Delete an issue
77
+ prodboard rm <id> --force
78
+
79
+ # Comments
80
+ prodboard comment <id> "Looking into this"
81
+ prodboard comment <id> "Fixed it" --author claude
82
+ prodboard comments <id>
83
+ prodboard comments <id> --json
84
+ ```
85
+
86
+ ### Schedule Management
87
+
88
+ ```bash
89
+ # Create a schedule
90
+ prodboard schedule add \
91
+ --name "daily-triage" \
92
+ --cron "0 9 * * 1-5" \
93
+ --prompt "Review the board and triage new issues"
94
+
95
+ # List schedules
96
+ prodboard schedule ls
97
+ prodboard schedule ls --all --json
98
+
99
+ # Edit a schedule
100
+ prodboard schedule edit <id> --cron "0 10 * * *"
101
+
102
+ # Enable/disable
103
+ prodboard schedule enable <id>
104
+ prodboard schedule disable <id>
105
+
106
+ # Delete
107
+ prodboard schedule rm <id> --force
108
+
109
+ # Run immediately (foreground)
110
+ prodboard schedule run <id>
111
+
112
+ # View run history
113
+ prodboard schedule logs
114
+ prodboard schedule logs --schedule <id> --limit 10
115
+
116
+ # View statistics
117
+ prodboard schedule stats
118
+ prodboard schedule stats --schedule <id> --days 7
119
+ ```
120
+
121
+ ### Daemon
122
+
123
+ ```bash
124
+ # Start daemon (foreground, for systemd)
125
+ prodboard daemon
126
+
127
+ # Dry run (show schedules without executing)
128
+ prodboard daemon --dry-run
129
+
130
+ # Check daemon status
131
+ prodboard daemon status
132
+ ```
133
+
134
+ ### Other
135
+
136
+ ```bash
137
+ prodboard config # Show current configuration
138
+ prodboard version # Show version
139
+ prodboard help # Show help
140
+ ```
141
+
142
+ ## MCP Tools Reference
143
+
144
+ | Tool | Description |
145
+ |------|-------------|
146
+ | `board_summary` | Overview of issues by status, recent issues |
147
+ | `list_issues` | List issues with optional filters |
148
+ | `get_issue` | Get full issue details with comments |
149
+ | `create_issue` | Create a new issue |
150
+ | `update_issue` | Update issue fields |
151
+ | `delete_issue` | Delete an issue |
152
+ | `add_comment` | Add a comment (default author: claude) |
153
+ | `pick_next_issue` | Claim next todo, move to in-progress |
154
+ | `complete_issue` | Mark done with optional comment |
155
+ | `list_schedules` | List scheduled tasks |
156
+ | `create_schedule` | Create a scheduled task |
157
+ | `update_schedule` | Update a scheduled task |
158
+ | `delete_schedule` | Delete a scheduled task |
159
+ | `list_runs` | View run history |
160
+
161
+ ### MCP Resources
162
+
163
+ | URI | Description |
164
+ |-----|-------------|
165
+ | `prodboard://issues` | Board summary (same as board_summary) |
166
+ | `prodboard://schedules` | Active schedules with next run times |
167
+
168
+ ## Configuration
169
+
170
+ Config file: `~/.prodboard/config.jsonc` (JSONC format — comments allowed)
171
+
172
+ ```jsonc
173
+ {
174
+ "general": {
175
+ // Issue statuses in display order
176
+ "statuses": ["todo", "in-progress", "review", "done", "archived"],
177
+ // Default status for new issues
178
+ "defaultStatus": "todo",
179
+ // Optional prefix for issue IDs
180
+ "idPrefix": ""
181
+ },
182
+ "daemon": {
183
+ // Max concurrent scheduled runs
184
+ "maxConcurrentRuns": 2,
185
+ // Default max turns for Claude
186
+ "maxTurns": 50,
187
+ // Absolute max (cannot be overridden)
188
+ "hardMaxTurns": 200,
189
+ // Run timeout in seconds
190
+ "runTimeoutSeconds": 1800,
191
+ // Days to keep run history
192
+ "runRetentionDays": 30,
193
+ // Log level: debug, info, warn, error
194
+ "logLevel": "info",
195
+ // Worktree usage: auto, always, never
196
+ "useWorktrees": "auto"
197
+ }
198
+ }
199
+ ```
200
+
201
+ ## Scheduler Guide
202
+
203
+ ### Cron Syntax
204
+
205
+ Standard 5-field cron expressions:
206
+
207
+ ```
208
+ minute (0-59)
209
+ hour (0-23)
210
+ day of month (1-31)
211
+ month (1-12)
212
+ day of week (0-6, 0=Sunday)
213
+ ```
214
+
215
+ Examples:
216
+ - `0 9 * * 1-5` — Weekdays at 9:00 AM
217
+ - `*/15 * * * *` — Every 15 minutes
218
+ - `0 0 1 * *` — First of every month at midnight
219
+ - `0 9,17 * * *` — 9 AM and 5 PM daily
220
+
221
+ ### Template Variables
222
+
223
+ Use in schedule prompts:
224
+
225
+ | Variable | Description |
226
+ |----------|-------------|
227
+ | `{{board_summary}}` | Compact summary: "3 todo, 1 in-progress, 0 review" |
228
+ | `{{todo_count}}` | Number of todo issues |
229
+ | `{{in_progress_count}}` | Number of in-progress issues |
230
+ | `{{datetime}}` | Current ISO 8601 timestamp |
231
+ | `{{schedule_name}}` | Name of the schedule |
232
+
233
+ Example:
234
+
235
+ ```bash
236
+ prodboard schedule add \
237
+ --name "morning-standup" \
238
+ --cron "0 9 * * 1-5" \
239
+ --prompt "Board status: {{board_summary}}. Pick and work on the next todo issue."
240
+ ```
241
+
242
+ ## Running as a Service
243
+
244
+ ### systemd
245
+
246
+ Create `/etc/systemd/system/prodboard.service`:
247
+
248
+ ```ini
249
+ [Unit]
250
+ Description=prodboard scheduler daemon
251
+ After=network.target
252
+
253
+ [Service]
254
+ Type=simple
255
+ User=your-user
256
+ ExecStart=/usr/local/bin/bun run prodboard daemon
257
+ Restart=on-failure
258
+ RestartSec=10
259
+
260
+ [Install]
261
+ WantedBy=multi-user.target
262
+ ```
263
+
264
+ ```bash
265
+ sudo systemctl enable prodboard
266
+ sudo systemctl start prodboard
267
+ sudo systemctl status prodboard
268
+ ```
269
+
270
+ ## Troubleshooting
271
+
272
+ **"prodboard is not initialized"**
273
+ Run `prodboard init` to create `~/.prodboard/`.
274
+
275
+ **MCP server not connecting**
276
+ Check that `~/.prodboard/mcp.json` exists and the path to prodboard is correct.
277
+
278
+ **Daemon not starting**
279
+ Check `~/.prodboard/logs/daemon.log` for errors. Ensure claude CLI is installed and accessible.
280
+
281
+ **Stale PID file**
282
+ If `prodboard daemon status` shows "stale PID file", the daemon crashed. It will auto-clean the PID file. Run `prodboard daemon` to restart.
283
+
284
+ ## Development
285
+
286
+ ```bash
287
+ bun install
288
+ bun test
289
+ bun run typecheck
290
+ ```
291
+
292
+ ## License
293
+
294
+ MIT
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ import { main } from "../src/index.ts";
3
+
4
+ main();
@@ -0,0 +1,100 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "prodboard Configuration",
4
+ "type": "object",
5
+ "properties": {
6
+ "general": {
7
+ "type": "object",
8
+ "properties": {
9
+ "statuses": {
10
+ "type": "array",
11
+ "items": { "type": "string" },
12
+ "default": ["todo", "in-progress", "review", "done", "archived"],
13
+ "description": "Issue statuses in display order"
14
+ },
15
+ "defaultStatus": {
16
+ "type": "string",
17
+ "default": "todo",
18
+ "description": "Default status for new issues"
19
+ },
20
+ "idPrefix": {
21
+ "type": "string",
22
+ "default": "",
23
+ "description": "Optional prefix for issue IDs"
24
+ }
25
+ },
26
+ "additionalProperties": false
27
+ },
28
+ "daemon": {
29
+ "type": "object",
30
+ "properties": {
31
+ "maxConcurrentRuns": {
32
+ "type": "integer",
33
+ "minimum": 1,
34
+ "default": 2,
35
+ "description": "Maximum concurrent scheduled runs"
36
+ },
37
+ "maxTurns": {
38
+ "type": "integer",
39
+ "minimum": 1,
40
+ "default": 50,
41
+ "description": "Default max turns for Claude invocations"
42
+ },
43
+ "hardMaxTurns": {
44
+ "type": "integer",
45
+ "minimum": 1,
46
+ "default": 200,
47
+ "description": "Absolute max turns (cannot be overridden)"
48
+ },
49
+ "runTimeoutSeconds": {
50
+ "type": "integer",
51
+ "minimum": 60,
52
+ "default": 1800,
53
+ "description": "Timeout per run in seconds"
54
+ },
55
+ "runRetentionDays": {
56
+ "type": "integer",
57
+ "minimum": 1,
58
+ "default": 30,
59
+ "description": "Days to retain run history"
60
+ },
61
+ "logLevel": {
62
+ "type": "string",
63
+ "enum": ["debug", "info", "warn", "error"],
64
+ "default": "info",
65
+ "description": "Daemon log level"
66
+ },
67
+ "logMaxSizeMb": {
68
+ "type": "number",
69
+ "minimum": 1,
70
+ "default": 10,
71
+ "description": "Max log file size in MB"
72
+ },
73
+ "logMaxFiles": {
74
+ "type": "integer",
75
+ "minimum": 1,
76
+ "default": 5,
77
+ "description": "Max number of rotated log files"
78
+ },
79
+ "defaultAllowedTools": {
80
+ "type": "array",
81
+ "items": { "type": "string" },
82
+ "description": "Default tools allowed for scheduled runs"
83
+ },
84
+ "nonGitDefaultAllowedTools": {
85
+ "type": "array",
86
+ "items": { "type": "string" },
87
+ "description": "Default tools for non-git environments"
88
+ },
89
+ "useWorktrees": {
90
+ "type": "string",
91
+ "enum": ["auto", "always", "never"],
92
+ "default": "auto",
93
+ "description": "Worktree usage strategy"
94
+ }
95
+ },
96
+ "additionalProperties": false
97
+ }
98
+ },
99
+ "additionalProperties": false
100
+ }
package/package.json CHANGED
@@ -1,11 +1,52 @@
1
1
  {
2
2
  "name": "prodboard",
3
- "version": "0.0.0",
4
- "description": "",
5
- "main": "index.js",
3
+ "version": "0.1.0",
4
+ "description": "Self-hosted, CLI-first issue tracker and cron scheduler for AI coding agents",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/G4brym/prodboard"
10
+ },
11
+ "homepage": "https://github.com/G4brym/prodboard#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/G4brym/prodboard/issues"
14
+ },
15
+ "keywords": [
16
+ "cli",
17
+ "issue-tracker",
18
+ "mcp",
19
+ "ai-agents",
20
+ "scheduler",
21
+ "cron",
22
+ "sqlite"
23
+ ],
24
+ "bin": {
25
+ "prodboard": "bin/prodboard.ts"
26
+ },
6
27
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
28
+ "dev": "bun run bin/prodboard.ts",
29
+ "test": "bun test",
30
+ "typecheck": "bun x tsc --noEmit",
31
+ "changeset": "changeset",
32
+ "version": "changeset version",
33
+ "release": "bun run test && bun run typecheck && npm publish"
34
+ },
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.12.1"
37
+ },
38
+ "engines": {
39
+ "bun": ">=1.0.0"
8
40
  },
9
- "author": "",
10
- "license": "MIT"
41
+ "files": [
42
+ "bin/",
43
+ "src/",
44
+ "templates/",
45
+ "config.schema.json",
46
+ "CHANGELOG.md"
47
+ ],
48
+ "devDependencies": {
49
+ "@changesets/cli": "^2.29.8",
50
+ "@types/bun": "^1.3.9"
51
+ }
11
52
  }
@@ -0,0 +1,86 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { ensureDb } from "../db.ts";
3
+ import { getIssueByPrefix } from "../queries/issues.ts";
4
+ import { createComment, listComments } from "../queries/comments.ts";
5
+ import { renderTable, formatDate, jsonOutput } from "../format.ts";
6
+
7
+ function parseArgs(args: string[]): { flags: Record<string, string | boolean>; positional: string[] } {
8
+ const flags: Record<string, string | boolean> = {};
9
+ const positional: string[] = [];
10
+
11
+ for (let i = 0; i < args.length; i++) {
12
+ const arg = args[i];
13
+ if (arg.startsWith("--")) {
14
+ const key = arg.slice(2);
15
+ if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
16
+ flags[key] = args[++i];
17
+ } else {
18
+ flags[key] = true;
19
+ }
20
+ } else if (arg.startsWith("-") && arg.length === 2) {
21
+ const key = arg.slice(1);
22
+ if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
23
+ flags[key] = args[++i];
24
+ } else {
25
+ flags[key] = true;
26
+ }
27
+ } else {
28
+ positional.push(arg);
29
+ }
30
+ }
31
+ return { flags, positional };
32
+ }
33
+
34
+ export async function comment(args: string[], dbOverride?: Database): Promise<void> {
35
+ const { flags, positional } = parseArgs(args);
36
+ const issueIdOrPrefix = positional[0];
37
+ const body = positional.slice(1).join(" ");
38
+
39
+ if (!issueIdOrPrefix || !body) {
40
+ console.error("Usage: prodboard comment <issue-id> <body> [--author/-a author]");
41
+ throw new Error("Invalid arguments");
42
+ }
43
+
44
+ const db = dbOverride ?? ensureDb();
45
+ const issue = getIssueByPrefix(db, issueIdOrPrefix);
46
+
47
+ const author = (flags.author ?? flags.a) as string | undefined;
48
+ const c = createComment(db, {
49
+ issue_id: issue.id,
50
+ body,
51
+ author: typeof author === "string" ? author : undefined,
52
+ });
53
+
54
+ console.log(`Added comment by ${c.author} on issue ${issue.id}`);
55
+ }
56
+
57
+ export async function comments(args: string[], dbOverride?: Database): Promise<void> {
58
+ const { flags, positional } = parseArgs(args);
59
+ const issueIdOrPrefix = positional[0];
60
+
61
+ if (!issueIdOrPrefix) {
62
+ console.error("Usage: prodboard comments <issue-id> [--json]");
63
+ throw new Error("Invalid arguments");
64
+ }
65
+
66
+ const db = dbOverride ?? ensureDb();
67
+ const issue = getIssueByPrefix(db, issueIdOrPrefix);
68
+ const cmts = listComments(db, issue.id);
69
+
70
+ if (flags.json) {
71
+ console.log(jsonOutput(cmts));
72
+ return;
73
+ }
74
+
75
+ if (cmts.length === 0) {
76
+ console.log("No comments.");
77
+ return;
78
+ }
79
+
80
+ const table = renderTable(
81
+ ["Author", "Date", "Comment"],
82
+ cmts.map((c) => [c.author, formatDate(c.created_at), c.body]),
83
+ { maxWidths: [12, 18, 60] }
84
+ );
85
+ console.log(table);
86
+ }
@@ -0,0 +1,112 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { ensureDb } from "../db.ts";
4
+ import { loadConfig, PRODBOARD_DIR } from "../config.ts";
5
+ import { listSchedules } from "../queries/schedules.ts";
6
+ import { getNextFire } from "../cron.ts";
7
+ import { formatDate } from "../format.ts";
8
+ import { Daemon } from "../scheduler.ts";
9
+
10
+ function parseArgs(args: string[]): { flags: Record<string, string | boolean>; positional: string[] } {
11
+ const flags: Record<string, string | boolean> = {};
12
+ const positional: string[] = [];
13
+
14
+ for (let i = 0; i < args.length; i++) {
15
+ const arg = args[i];
16
+ if (arg.startsWith("--")) {
17
+ const key = arg.slice(2);
18
+ if (key === "dry-run" || key === "foreground") {
19
+ flags[key] = true;
20
+ } else if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
21
+ flags[key] = args[++i];
22
+ } else {
23
+ flags[key] = true;
24
+ }
25
+ } else if (arg.startsWith("-") && arg.length === 2) {
26
+ const key = arg.slice(1);
27
+ if (key === "f") {
28
+ flags.foreground = true;
29
+ } else {
30
+ flags[key] = true;
31
+ }
32
+ } else {
33
+ positional.push(arg);
34
+ }
35
+ }
36
+ return { flags, positional };
37
+ }
38
+
39
+ export async function daemonStart(args: string[]): Promise<void> {
40
+ const { flags } = parseArgs(args);
41
+ const db = ensureDb();
42
+ const config = loadConfig();
43
+
44
+ if (flags["dry-run"]) {
45
+ const schedules = listSchedules(db);
46
+ if (schedules.length === 0) {
47
+ console.log("No active schedules.");
48
+ return;
49
+ }
50
+
51
+ console.log("Active schedules (dry run):\n");
52
+ for (const s of schedules) {
53
+ let nextFire = "N/A";
54
+ try {
55
+ const next = getNextFire(s.cron, new Date());
56
+ nextFire = formatDate(next.toISOString());
57
+ } catch {}
58
+ console.log(` ${s.id} ${s.name}`);
59
+ console.log(` Cron: ${s.cron}`);
60
+ console.log(` Next: ${nextFire}`);
61
+ console.log();
62
+ }
63
+ return;
64
+ }
65
+
66
+ const daemon = new Daemon(db, config);
67
+ await daemon.start();
68
+
69
+ // Keep process alive
70
+ await new Promise(() => {});
71
+ }
72
+
73
+ export async function daemonStatus(args: string[]): Promise<void> {
74
+ const pidFile = path.join(PRODBOARD_DIR, "daemon.pid");
75
+
76
+ if (!fs.existsSync(pidFile)) {
77
+ console.log("Daemon is not running (no PID file).");
78
+ return;
79
+ }
80
+
81
+ const pidStr = fs.readFileSync(pidFile, "utf-8").trim();
82
+ const pid = parseInt(pidStr, 10);
83
+
84
+ let running = false;
85
+ try {
86
+ process.kill(pid, 0);
87
+ running = true;
88
+ } catch {}
89
+
90
+ if (running) {
91
+ console.log(`Daemon is running (PID ${pid}).`);
92
+
93
+ // Show next scheduled runs
94
+ try {
95
+ const db = ensureDb();
96
+ const schedules = listSchedules(db);
97
+ if (schedules.length > 0) {
98
+ console.log("\nUpcoming runs:");
99
+ for (const s of schedules) {
100
+ try {
101
+ const next = getNextFire(s.cron, new Date());
102
+ console.log(` ${s.name}: ${formatDate(next.toISOString())}`);
103
+ } catch {}
104
+ }
105
+ }
106
+ } catch {}
107
+ } else {
108
+ console.log(`Daemon is not running (stale PID file: ${pid}).`);
109
+ // Clean up stale PID file
110
+ try { fs.unlinkSync(pidFile); } catch {}
111
+ }
112
+ }