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 +16 -0
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/bin/prodboard.ts +4 -0
- package/config.schema.json +100 -0
- package/package.json +47 -6
- package/src/commands/comments.ts +86 -0
- package/src/commands/daemon.ts +112 -0
- package/src/commands/init.ts +83 -0
- package/src/commands/issues.ts +268 -0
- package/src/commands/schedules.ts +276 -0
- package/src/config.ts +155 -0
- package/src/confirm.ts +14 -0
- package/src/cron.ts +121 -0
- package/src/db.ts +157 -0
- package/src/format.ts +99 -0
- package/src/ids.ts +5 -0
- package/src/index.ts +214 -0
- package/src/invocation.ts +102 -0
- package/src/logger.ts +84 -0
- package/src/mcp.ts +543 -0
- package/src/queries/comments.ts +31 -0
- package/src/queries/issues.ts +155 -0
- package/src/queries/runs.ts +159 -0
- package/src/queries/schedules.ts +115 -0
- package/src/scheduler.ts +411 -0
- package/src/templates.ts +43 -0
- package/src/types.ts +82 -0
- package/templates/CLAUDE.md +12 -0
- package/templates/config.jsonc +38 -0
- package/templates/mcp.json +8 -0
- package/templates/system-prompt-nogit.md +33 -0
- package/templates/system-prompt.md +31 -0
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
|
package/bin/prodboard.ts
ADDED
|
@@ -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.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
10
|
-
|
|
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
|
+
}
|