prodboard 0.1.0 → 0.1.2
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 +192 -174
- package/package.json +1 -1
- package/src/commands/install.ts +127 -0
- package/src/index.ts +13 -0
- package/src/invocation.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# prodboard
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a9f2241: Fix daemon failing immediately by adding `--verbose` flag required for `--output-format stream-json` in print mode
|
|
8
|
+
|
|
9
|
+
## 0.1.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 1aa4d25: Add `prodboard install` and `prodboard uninstall` commands to manage a user-level systemd service for the daemon
|
|
14
|
+
|
|
3
15
|
## 0.1.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,285 +1,293 @@
|
|
|
1
1
|
# prodboard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Give Claude Code a persistent task board and a cron scheduler so it can manage work across sessions.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**The problem:** Claude Code loses context between sessions. It can't remember what tasks exist, what's in progress, or what to work on next. There's no way to schedule it to run recurring jobs like daily triage or nightly CI.
|
|
6
6
|
|
|
7
|
-
prodboard
|
|
7
|
+
**The solution:** prodboard is a local issue tracker backed by SQLite that Claude Code can read and write through MCP tools. It also includes a cron daemon that spawns Claude Code on a schedule to work through tasks autonomously.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
```
|
|
10
|
+
You (CLI) ──┐
|
|
11
|
+
├──▶ SQLite DB ◀── MCP Server ◀── Claude Code
|
|
12
|
+
Cron Daemon ──┘
|
|
13
|
+
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## What You Get
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
- **An issue board Claude Code can use** — Claude reads, creates, updates, and completes issues via MCP tools during any session
|
|
18
|
+
- **Scheduled Claude Code runs** — Define cron jobs that spawn Claude Code to triage issues, run maintenance, or work through the backlog
|
|
19
|
+
- **A CLI you can use too** — Same board, human-friendly commands. Add issues, check status, review what Claude did
|
|
20
|
+
- **Everything local** — Single SQLite file at `~/.prodboard/db.sqlite`. No servers, no accounts, no cloud
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
## Quick Start
|
|
18
23
|
|
|
19
24
|
```bash
|
|
25
|
+
# Install
|
|
20
26
|
bun install -g prodboard
|
|
27
|
+
|
|
28
|
+
# Initialize
|
|
29
|
+
prodboard init
|
|
30
|
+
|
|
31
|
+
# Connect Claude Code to the board
|
|
32
|
+
claude mcp add prodboard -- bunx prodboard mcp
|
|
21
33
|
```
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
That's it. Open Claude Code and start talking to it:
|
|
36
|
+
|
|
37
|
+
## Things You Can Say to Claude Code
|
|
38
|
+
|
|
39
|
+
Once connected, you can manage your board entirely through conversation:
|
|
40
|
+
|
|
41
|
+
### Setting Up Cron Jobs
|
|
24
42
|
|
|
25
|
-
```bash
|
|
26
|
-
prodboard init
|
|
27
43
|
```
|
|
44
|
+
"Every hour, tail the nginx access and error logs. If you see
|
|
45
|
+
anything unusual — spikes in 5xx errors, suspicious request
|
|
46
|
+
patterns, or unexpected traffic — create a new issue to investigate"
|
|
28
47
|
|
|
29
|
-
|
|
48
|
+
"Every 2 hours, pick up the next todo issue and try to fix it.
|
|
49
|
+
Open a PR with your changes, comment the PR link on the issue,
|
|
50
|
+
and move the issue to review"
|
|
30
51
|
|
|
31
|
-
|
|
52
|
+
"Every 2 hours, pick up the next issue in review. Check out the
|
|
53
|
+
PR, verify the code is correct and tests pass. If everything
|
|
54
|
+
looks good, merge it. If not, add a review comment on the PR
|
|
55
|
+
explaining what needs to change and move the issue back to todo"
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
"Create a cron job that runs every 6 hours to check for
|
|
58
|
+
and fix any TypeScript type errors in the project"
|
|
59
|
+
|
|
60
|
+
"Set up a daily schedule at 9 AM on weekdays to review
|
|
61
|
+
the board and work on the highest priority task"
|
|
62
|
+
|
|
63
|
+
"Add a cron job that runs every night at midnight to
|
|
64
|
+
run the test suite and create issues for any failures"
|
|
65
|
+
|
|
66
|
+
"Schedule a weekly cleanup every Friday at 5 PM to
|
|
67
|
+
archive all done issues and summarize what was accomplished"
|
|
68
|
+
|
|
69
|
+
"Create a schedule that runs every 30 minutes to monitor
|
|
70
|
+
the API health endpoint and create an issue if it's down"
|
|
35
71
|
```
|
|
36
72
|
|
|
37
|
-
|
|
73
|
+
### Managing Tasks
|
|
38
74
|
|
|
39
|
-
```bash
|
|
40
|
-
cat ~/.prodboard/mcp.json
|
|
41
75
|
```
|
|
76
|
+
"Add a task to fix the authentication timeout bug in the API"
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
"What's on the board right now?"
|
|
44
79
|
|
|
45
|
-
|
|
46
|
-
|
|
80
|
+
"Pick up the next task and start working on it"
|
|
81
|
+
|
|
82
|
+
"Mark the login bug as done, I fixed it manually"
|
|
83
|
+
|
|
84
|
+
"Create an issue to refactor the database layer, mark it as todo"
|
|
85
|
+
|
|
86
|
+
"Show me all in-progress issues"
|
|
87
|
+
|
|
88
|
+
"Add a comment to the auth bug — we need to check the session TTL"
|
|
47
89
|
```
|
|
48
90
|
|
|
49
|
-
|
|
91
|
+
### Reviewing Activity
|
|
50
92
|
|
|
51
|
-
|
|
93
|
+
```
|
|
94
|
+
"Show me what the last cron run did"
|
|
52
95
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
96
|
+
"What tasks did you complete this week?"
|
|
97
|
+
|
|
98
|
+
"Show the schedule stats for the daily triage job"
|
|
84
99
|
```
|
|
85
100
|
|
|
86
|
-
|
|
101
|
+
Claude Code handles all the MCP tool calls behind the scenes — you just talk to it naturally.
|
|
102
|
+
|
|
103
|
+
## Adding Tasks from the CLI
|
|
104
|
+
|
|
105
|
+
You can also manage the board directly:
|
|
87
106
|
|
|
88
107
|
```bash
|
|
89
|
-
|
|
90
|
-
prodboard
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
prodboard add "Fix login bug" -d "OAuth callback URL is wrong" -s todo
|
|
109
|
+
prodboard add "Add dark mode" -s todo
|
|
110
|
+
prodboard add "Write API tests" -s todo
|
|
111
|
+
prodboard ls
|
|
112
|
+
```
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
### Starting the Scheduler
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Start the cron daemon (keeps running in foreground)
|
|
118
|
+
prodboard daemon
|
|
98
119
|
|
|
99
|
-
#
|
|
100
|
-
prodboard
|
|
120
|
+
# Or preview what's scheduled without running anything
|
|
121
|
+
prodboard daemon --dry-run
|
|
122
|
+
```
|
|
101
123
|
|
|
102
|
-
|
|
103
|
-
prodboard schedule enable <id>
|
|
104
|
-
prodboard schedule disable <id>
|
|
124
|
+
## CLI Reference
|
|
105
125
|
|
|
106
|
-
|
|
107
|
-
prodboard schedule rm <id> --force
|
|
126
|
+
### Issues
|
|
108
127
|
|
|
109
|
-
|
|
110
|
-
prodboard
|
|
128
|
+
```bash
|
|
129
|
+
prodboard add "Fix bug" -d "description" -s todo # Create
|
|
130
|
+
prodboard ls # List (table)
|
|
131
|
+
prodboard ls --status todo --status in-progress # Filter by status
|
|
132
|
+
prodboard ls --search "login" --json # Search + JSON output
|
|
133
|
+
prodboard show <id> # Details + comments
|
|
134
|
+
prodboard edit <id> --title "New title" -s review # Update fields
|
|
135
|
+
prodboard mv <id> done # Change status
|
|
136
|
+
prodboard rm <id> --force # Delete
|
|
137
|
+
```
|
|
111
138
|
|
|
112
|
-
|
|
113
|
-
prodboard schedule logs
|
|
114
|
-
prodboard schedule logs --schedule <id> --limit 10
|
|
139
|
+
### Comments
|
|
115
140
|
|
|
116
|
-
|
|
117
|
-
prodboard
|
|
118
|
-
prodboard
|
|
141
|
+
```bash
|
|
142
|
+
prodboard comment <id> "Looking into this" # Add comment
|
|
143
|
+
prodboard comment <id> "Fixed" --author claude # With author
|
|
144
|
+
prodboard comments <id> # List comments
|
|
119
145
|
```
|
|
120
146
|
|
|
121
|
-
###
|
|
147
|
+
### Schedules
|
|
122
148
|
|
|
123
149
|
```bash
|
|
124
|
-
|
|
125
|
-
prodboard
|
|
150
|
+
prodboard schedule add --name "job" --cron "0 9 * * *" --prompt "Do X"
|
|
151
|
+
prodboard schedule ls # List schedules
|
|
152
|
+
prodboard schedule edit <id> --cron "0 10 * * *" # Edit
|
|
153
|
+
prodboard schedule enable <id> # Enable
|
|
154
|
+
prodboard schedule disable <id> # Disable
|
|
155
|
+
prodboard schedule rm <id> --force # Delete
|
|
156
|
+
prodboard schedule run <id> # Run immediately
|
|
157
|
+
prodboard schedule logs # Run history
|
|
158
|
+
prodboard schedule stats --days 7 # Statistics
|
|
159
|
+
```
|
|
126
160
|
|
|
127
|
-
|
|
128
|
-
prodboard daemon --dry-run
|
|
161
|
+
### Daemon
|
|
129
162
|
|
|
130
|
-
|
|
131
|
-
prodboard daemon
|
|
163
|
+
```bash
|
|
164
|
+
prodboard daemon # Start (foreground)
|
|
165
|
+
prodboard daemon --dry-run # Preview schedules
|
|
166
|
+
prodboard daemon status # Check if running
|
|
132
167
|
```
|
|
133
168
|
|
|
134
169
|
### Other
|
|
135
170
|
|
|
136
171
|
```bash
|
|
137
|
-
prodboard config
|
|
138
|
-
prodboard version
|
|
139
|
-
prodboard help # Show help
|
|
172
|
+
prodboard config # Show configuration
|
|
173
|
+
prodboard version # Show version
|
|
140
174
|
```
|
|
141
175
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
|
149
|
-
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
| `prodboard://schedules` | Active schedules with next run times |
|
|
176
|
+
IDs support prefix matching — use `a3f9` instead of the full `a3f9b2c1d4e5f678`.
|
|
177
|
+
|
|
178
|
+
## MCP Tools
|
|
179
|
+
|
|
180
|
+
These are the tools Claude Code sees when connected to the board:
|
|
181
|
+
|
|
182
|
+
| Tool | What Claude Uses It For |
|
|
183
|
+
|------|------------------------|
|
|
184
|
+
| `board_summary` | See issue counts and recent activity |
|
|
185
|
+
| `list_issues` | Browse issues with filters |
|
|
186
|
+
| `get_issue` | Read full issue details and comments |
|
|
187
|
+
| `create_issue` | Log a new task or bug |
|
|
188
|
+
| `update_issue` | Change title, description, or status |
|
|
189
|
+
| `delete_issue` | Remove an issue |
|
|
190
|
+
| `add_comment` | Leave notes on issues (default author: "claude") |
|
|
191
|
+
| `pick_next_issue` | Claim the oldest todo, move to in-progress |
|
|
192
|
+
| `complete_issue` | Mark done with an optional summary comment |
|
|
193
|
+
| `list_schedules` | See scheduled jobs |
|
|
194
|
+
| `create_schedule` | Set up a new cron job |
|
|
195
|
+
| `update_schedule` | Modify a schedule |
|
|
196
|
+
| `delete_schedule` | Remove a schedule |
|
|
197
|
+
| `list_runs` | Check run history and results |
|
|
198
|
+
|
|
199
|
+
MCP resources: `prodboard://issues` (board summary) and `prodboard://schedules` (active schedules).
|
|
167
200
|
|
|
168
201
|
## Configuration
|
|
169
202
|
|
|
170
|
-
Config file: `~/.prodboard/config.jsonc`
|
|
203
|
+
Config file: `~/.prodboard/config.jsonc`
|
|
171
204
|
|
|
172
205
|
```jsonc
|
|
173
206
|
{
|
|
174
207
|
"general": {
|
|
175
|
-
// Issue statuses in display order
|
|
176
208
|
"statuses": ["todo", "in-progress", "review", "done", "archived"],
|
|
177
|
-
// Default status for new issues
|
|
178
209
|
"defaultStatus": "todo",
|
|
179
|
-
// Optional prefix for issue IDs
|
|
180
210
|
"idPrefix": ""
|
|
181
211
|
},
|
|
182
212
|
"daemon": {
|
|
183
|
-
// Max concurrent scheduled runs
|
|
184
213
|
"maxConcurrentRuns": 2,
|
|
185
|
-
// Default max turns for Claude
|
|
186
214
|
"maxTurns": 50,
|
|
187
|
-
// Absolute max (cannot be overridden)
|
|
188
215
|
"hardMaxTurns": 200,
|
|
189
|
-
// Run timeout in seconds
|
|
190
216
|
"runTimeoutSeconds": 1800,
|
|
191
|
-
// Days to keep run history
|
|
192
217
|
"runRetentionDays": 30,
|
|
193
|
-
// Log level: debug, info, warn, error
|
|
194
218
|
"logLevel": "info",
|
|
195
|
-
// Worktree usage: auto, always, never
|
|
196
219
|
"useWorktrees": "auto"
|
|
197
220
|
}
|
|
198
221
|
}
|
|
199
222
|
```
|
|
200
223
|
|
|
201
|
-
## Scheduler
|
|
224
|
+
## Scheduler Details
|
|
202
225
|
|
|
203
226
|
### Cron Syntax
|
|
204
227
|
|
|
205
|
-
Standard 5-field cron
|
|
228
|
+
Standard 5-field cron:
|
|
206
229
|
|
|
207
230
|
```
|
|
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
|
|
231
|
+
┌───────────── minute (0-59)
|
|
232
|
+
│ ┌─────────── hour (0-23)
|
|
233
|
+
│ │ ┌───────── day of month (1-31)
|
|
234
|
+
│ │ │ ┌─────── month (1-12)
|
|
235
|
+
│ │ │ │ ┌───── day of week (0-6, Sun=0)
|
|
236
|
+
│ │ │ │ │
|
|
237
|
+
* * * * *
|
|
213
238
|
```
|
|
214
239
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
240
|
+
| Expression | Meaning |
|
|
241
|
+
|-----------|---------|
|
|
242
|
+
| `0 9 * * 1-5` | Weekdays at 9 AM |
|
|
243
|
+
| `*/15 * * * *` | Every 15 minutes |
|
|
244
|
+
| `0 0 1 * *` | First of every month |
|
|
245
|
+
| `0 9,17 * * *` | 9 AM and 5 PM daily |
|
|
220
246
|
|
|
221
247
|
### Template Variables
|
|
222
248
|
|
|
223
|
-
Use in schedule prompts:
|
|
249
|
+
Use in schedule prompts to inject board context:
|
|
224
250
|
|
|
225
|
-
| Variable |
|
|
226
|
-
|
|
227
|
-
| `{{board_summary}}` |
|
|
251
|
+
| Variable | Value |
|
|
252
|
+
|----------|-------|
|
|
253
|
+
| `{{board_summary}}` | "3 todo, 1 in-progress, 0 review" |
|
|
228
254
|
| `{{todo_count}}` | Number of todo issues |
|
|
229
255
|
| `{{in_progress_count}}` | Number of in-progress issues |
|
|
230
256
|
| `{{datetime}}` | Current ISO 8601 timestamp |
|
|
231
257
|
| `{{schedule_name}}` | Name of the schedule |
|
|
232
258
|
|
|
233
|
-
Example:
|
|
234
|
-
|
|
235
259
|
```bash
|
|
236
260
|
prodboard schedule add \
|
|
237
261
|
--name "morning-standup" \
|
|
238
262
|
--cron "0 9 * * 1-5" \
|
|
239
|
-
--prompt "Board
|
|
263
|
+
--prompt "Board: {{board_summary}}. Pick the next todo and work on it."
|
|
240
264
|
```
|
|
241
265
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
### systemd
|
|
245
|
-
|
|
246
|
-
Create `/etc/systemd/system/prodboard.service`:
|
|
266
|
+
### Running as a systemd Service
|
|
247
267
|
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
After=network.target
|
|
268
|
+
```bash
|
|
269
|
+
# Install and start as a user-level systemd service (no sudo needed)
|
|
270
|
+
prodboard install
|
|
252
271
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
User=your-user
|
|
256
|
-
ExecStart=/usr/local/bin/bun run prodboard daemon
|
|
257
|
-
Restart=on-failure
|
|
258
|
-
RestartSec=10
|
|
272
|
+
# Check status
|
|
273
|
+
systemctl --user status prodboard
|
|
259
274
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
```
|
|
275
|
+
# Remove the service
|
|
276
|
+
prodboard uninstall
|
|
263
277
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
sudo systemctl start prodboard
|
|
267
|
-
sudo systemctl status prodboard
|
|
278
|
+
# Reinstall (e.g. after updating prodboard)
|
|
279
|
+
prodboard install --force
|
|
268
280
|
```
|
|
269
281
|
|
|
270
282
|
## Troubleshooting
|
|
271
283
|
|
|
272
|
-
**"prodboard is not initialized"**
|
|
273
|
-
Run `prodboard init` to create `~/.prodboard/`.
|
|
284
|
+
**"prodboard is not initialized"** — Run `prodboard init`.
|
|
274
285
|
|
|
275
|
-
**MCP
|
|
276
|
-
Check that `~/.prodboard/mcp.json` exists and the path to prodboard is correct.
|
|
286
|
+
**MCP not connecting** — Verify `claude mcp add prodboard -- bunx prodboard mcp` was run, or check `~/.prodboard/mcp.json`.
|
|
277
287
|
|
|
278
|
-
**Daemon not starting**
|
|
279
|
-
Check `~/.prodboard/logs/daemon.log` for errors. Ensure claude CLI is installed and accessible.
|
|
288
|
+
**Daemon not starting** — Check `~/.prodboard/logs/daemon.log`. Make sure `claude` CLI is installed and `ANTHROPIC_API_KEY` is set.
|
|
280
289
|
|
|
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.
|
|
290
|
+
**Stale PID file** — The daemon crashed. Run `prodboard daemon` to restart (auto-cleans stale PIDs).
|
|
283
291
|
|
|
284
292
|
## Development
|
|
285
293
|
|
|
@@ -289,6 +297,16 @@ bun test
|
|
|
289
297
|
bun run typecheck
|
|
290
298
|
```
|
|
291
299
|
|
|
300
|
+
## Upgrading
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Update to the latest version
|
|
304
|
+
bun install -g prodboard@latest
|
|
305
|
+
|
|
306
|
+
# If running as a systemd service, reinstall to pick up the new binary path
|
|
307
|
+
prodboard install --force
|
|
308
|
+
```
|
|
309
|
+
|
|
292
310
|
## License
|
|
293
311
|
|
|
294
312
|
MIT
|
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
|
|
5
|
+
const SERVICE_NAME = "prodboard";
|
|
6
|
+
const SERVICE_DIR = path.join(os.homedir(), ".config", "systemd", "user");
|
|
7
|
+
const SERVICE_PATH = path.join(SERVICE_DIR, `${SERVICE_NAME}.service`);
|
|
8
|
+
|
|
9
|
+
function parseArgs(args: string[]): { flags: Record<string, boolean> } {
|
|
10
|
+
const flags: Record<string, boolean> = {};
|
|
11
|
+
for (const arg of args) {
|
|
12
|
+
if (arg === "--force" || arg === "-f") {
|
|
13
|
+
flags.force = true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return { flags };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function systemctlAvailable(): Promise<boolean> {
|
|
20
|
+
try {
|
|
21
|
+
const proc = Bun.spawn(["systemctl", "--version"], {
|
|
22
|
+
stdout: "ignore",
|
|
23
|
+
stderr: "ignore",
|
|
24
|
+
});
|
|
25
|
+
const code = await proc.exited;
|
|
26
|
+
return code === 0;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function runSystemctl(...args: string[]): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
33
|
+
const proc = Bun.spawn(["systemctl", "--user", ...args], {
|
|
34
|
+
stdout: "pipe",
|
|
35
|
+
stderr: "pipe",
|
|
36
|
+
});
|
|
37
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
38
|
+
new Response(proc.stdout).text(),
|
|
39
|
+
new Response(proc.stderr).text(),
|
|
40
|
+
proc.exited,
|
|
41
|
+
]);
|
|
42
|
+
return { exitCode, stdout, stderr };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function generateServiceFile(bunPath: string, prodboardPath: string, home: string): string {
|
|
46
|
+
return `[Unit]
|
|
47
|
+
Description=prodboard scheduler daemon
|
|
48
|
+
After=network.target
|
|
49
|
+
|
|
50
|
+
[Service]
|
|
51
|
+
Type=simple
|
|
52
|
+
ExecStart=${bunPath} run ${prodboardPath} daemon
|
|
53
|
+
Restart=on-failure
|
|
54
|
+
RestartSec=10
|
|
55
|
+
Environment="HOME=${home}"
|
|
56
|
+
|
|
57
|
+
[Install]
|
|
58
|
+
WantedBy=default.target
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function install(args: string[]): Promise<void> {
|
|
63
|
+
const { flags } = parseArgs(args);
|
|
64
|
+
|
|
65
|
+
if (!(await systemctlAvailable())) {
|
|
66
|
+
console.error("systemd is not available on this system.");
|
|
67
|
+
console.error("The install command requires systemd (Linux).");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const alreadyInstalled = fs.existsSync(SERVICE_PATH);
|
|
72
|
+
|
|
73
|
+
if (alreadyInstalled && !flags.force) {
|
|
74
|
+
console.log("prodboard is already installed as a systemd service.");
|
|
75
|
+
const { stdout } = await runSystemctl("status", SERVICE_NAME);
|
|
76
|
+
console.log(stdout);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const bunPath = Bun.which("bun") ?? process.execPath;
|
|
81
|
+
const prodboardPath = Bun.which("prodboard") ?? `${bunPath} x prodboard`;
|
|
82
|
+
const home = os.homedir();
|
|
83
|
+
|
|
84
|
+
const serviceContent = generateServiceFile(bunPath, prodboardPath, home);
|
|
85
|
+
|
|
86
|
+
fs.mkdirSync(SERVICE_DIR, { recursive: true });
|
|
87
|
+
fs.writeFileSync(SERVICE_PATH, serviceContent);
|
|
88
|
+
console.log(`Service file written to ${SERVICE_PATH}`);
|
|
89
|
+
|
|
90
|
+
const reload = await runSystemctl("daemon-reload");
|
|
91
|
+
if (reload.exitCode !== 0) {
|
|
92
|
+
console.error("Failed to reload systemd:", reload.stderr);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const enable = await runSystemctl("enable", SERVICE_NAME);
|
|
97
|
+
if (enable.exitCode !== 0) {
|
|
98
|
+
console.error("Failed to enable service:", enable.stderr);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const start = await runSystemctl("start", SERVICE_NAME);
|
|
103
|
+
if (start.exitCode !== 0) {
|
|
104
|
+
console.error("Failed to start service:", start.stderr);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log("prodboard service installed, enabled, and started.");
|
|
109
|
+
const { stdout } = await runSystemctl("status", SERVICE_NAME);
|
|
110
|
+
console.log(stdout);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function uninstall(_args: string[]): Promise<void> {
|
|
114
|
+
if (!fs.existsSync(SERVICE_PATH)) {
|
|
115
|
+
console.log("prodboard is not installed as a systemd service.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await runSystemctl("stop", SERVICE_NAME);
|
|
120
|
+
await runSystemctl("disable", SERVICE_NAME);
|
|
121
|
+
|
|
122
|
+
fs.unlinkSync(SERVICE_PATH);
|
|
123
|
+
console.log(`Removed ${SERVICE_PATH}`);
|
|
124
|
+
|
|
125
|
+
await runSystemctl("daemon-reload");
|
|
126
|
+
console.log("prodboard service uninstalled.");
|
|
127
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -142,6 +142,17 @@ export async function main(): Promise<void> {
|
|
|
142
142
|
}
|
|
143
143
|
break;
|
|
144
144
|
}
|
|
145
|
+
case "install": {
|
|
146
|
+
ensureInitialized();
|
|
147
|
+
const { install } = await import("./commands/install.ts");
|
|
148
|
+
await install(args.slice(1));
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "uninstall": {
|
|
152
|
+
const { uninstall } = await import("./commands/install.ts");
|
|
153
|
+
await uninstall(args.slice(1));
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
145
156
|
case "config": {
|
|
146
157
|
ensureInitialized();
|
|
147
158
|
const { loadConfig } = await import("./config.ts");
|
|
@@ -191,6 +202,8 @@ Commands:
|
|
|
191
202
|
comments <id> List comments for an issue
|
|
192
203
|
schedule <sub> Manage scheduled tasks
|
|
193
204
|
daemon Start the scheduler daemon
|
|
205
|
+
install Install systemd service
|
|
206
|
+
uninstall Remove systemd service
|
|
194
207
|
config Show configuration
|
|
195
208
|
mcp Start MCP server (stdio)
|
|
196
209
|
version Show version
|
package/src/invocation.ts
CHANGED
|
@@ -46,7 +46,7 @@ export function buildInvocation(
|
|
|
46
46
|
args.push("--dangerously-skip-permissions");
|
|
47
47
|
|
|
48
48
|
// Output format
|
|
49
|
-
args.push("--output-format", "stream-json");
|
|
49
|
+
args.push("--verbose", "--output-format", "stream-json");
|
|
50
50
|
|
|
51
51
|
// MCP config
|
|
52
52
|
const mcpConfigPath = path.join(PRODBOARD_DIR, "mcp.json");
|