prodboard 0.4.0 → 0.6.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # prodboard
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#25](https://github.com/G4brym/prodboard/pull/25) [`90edbb4`](https://github.com/G4brym/prodboard/commit/90edbb43b83ef38c797c1c692ace1d9a3996787b) Thanks [@G4brym](https://github.com/G4brym)! - Optimize MCP list handlers: trim prompt from list_schedules, add get_schedule tool, exclude stderr_tail from list_runs defaults
8
+
9
+ ## 0.5.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#23](https://github.com/G4brym/prodboard/pull/23) [`146b3b5`](https://github.com/G4brym/prodboard/commit/146b3b5105ad127c3eb31f4e8c6ea19918329380) Thanks [@G4brym](https://github.com/G4brym)! - Optimize MCP list handlers: trim prompt from list_schedules, add get_schedule tool, exclude stderr_tail from list_runs defaults
14
+
3
15
  ## 0.4.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -150,8 +150,11 @@ prodboard comments <id> # List comments
150
150
 
151
151
  ```bash
152
152
  prodboard schedule add --name "job" --cron "0 9 * * *" --prompt "Do X"
153
+ prodboard schedule add --name "fast" --cron "*/30 * * * *" --prompt "Do Y" --model claude-sonnet-4-6
153
154
  prodboard schedule ls # List schedules
154
155
  prodboard schedule edit <id> --cron "0 10 * * *" # Edit
156
+ prodboard schedule edit <id> --model claude-opus-4-6 # Set model
157
+ prodboard schedule edit <id> --model "" # Clear model override
155
158
  prodboard schedule enable <id> # Enable
156
159
  prodboard schedule disable <id> # Disable
157
160
  prodboard schedule rm <id> --force # Delete
@@ -193,7 +196,8 @@ These are the tools Claude Code sees when connected to the board:
193
196
  | `add_comment` | Leave notes on issues (default author: "claude") |
194
197
  | `pick_next_issue` | Claim the oldest todo, move to in-progress |
195
198
  | `complete_issue` | Mark done with an optional summary comment |
196
- | `list_schedules` | See scheduled jobs |
199
+ | `list_schedules` | See scheduled jobs (compact, excludes prompt) |
200
+ | `get_schedule` | Read full schedule details including prompt |
197
201
  | `create_schedule` | Set up a new cron job |
198
202
  | `update_schedule` | Modify a schedule |
199
203
  | `delete_schedule` | Remove a schedule |
@@ -226,6 +230,34 @@ OpenCode-specific settings:
226
230
  }
227
231
  ```
228
232
 
233
+ ## Model Selection
234
+
235
+ You can control which model is used for scheduled runs at two levels:
236
+
237
+ **Global default** — Set `daemon.model` in your config to apply to all schedules:
238
+
239
+ ```jsonc
240
+ {
241
+ "daemon": {
242
+ "model": "claude-sonnet-4-6"
243
+ }
244
+ }
245
+ ```
246
+
247
+ **Per-schedule override** — Set `--model` when creating or editing a schedule:
248
+
249
+ ```bash
250
+ prodboard schedule add --name "triage" --cron "0 9 * * *" --prompt "Triage the board" --model claude-opus-4-6
251
+ prodboard schedule edit <id> --model claude-haiku-4-5-20251001
252
+ prodboard schedule edit <id> --model "" # clear override, fall back to global
253
+ ```
254
+
255
+ **Resolution order:** schedule `--model` > `daemon.model` > agent's built-in default. For OpenCode, `daemon.opencode.model` sits between the global `daemon.model` and the agent default.
256
+
257
+ Example model IDs:
258
+ - Claude Code: `claude-sonnet-4-6`, `claude-opus-4-6`, `claude-haiku-4-5-20251001`
259
+ - OpenCode: `anthropic/claude-sonnet-4-20250514`, etc.
260
+
229
261
  ## Configuration
230
262
 
231
263
  Config file: `~/.prodboard/config.jsonc`
@@ -239,6 +271,7 @@ Config file: `~/.prodboard/config.jsonc`
239
271
  },
240
272
  "daemon": {
241
273
  "agent": "claude", // "claude" or "opencode"
274
+ "model": null, // default model for runs (null = agent's default)
242
275
  "basePath": null, // base path for worktrees and runs (null = use schedule workdir)
243
276
  "useTmux": true, // wrap agent runs in tmux sessions
244
277
  "maxConcurrentRuns": 2,
package/bin/prodboard.ts CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prodboard",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Self-hosted, CLI-first issue tracker and cron scheduler for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -89,7 +89,7 @@ export async function scheduleLs(args: string[], dbOverride?: Database): Promise
89
89
  const isJson = !!flags.json;
90
90
  const all = !!(flags.all || flags.a);
91
91
 
92
- const schedules = listSchedules(db, { includeDisabled: all });
92
+ const schedules = listSchedules(db, { includeDisabled: true });
93
93
 
94
94
  if (isJson) {
95
95
  console.log(jsonOutput(schedules));
package/src/mcp.ts CHANGED
@@ -149,7 +149,7 @@ const TOOLS = [
149
149
  },
150
150
  {
151
151
  name: "list_schedules",
152
- description: "List scheduled tasks with their status and last run info.",
152
+ description: "List scheduled tasks (compact: excludes prompt, agents_json, allowed_tools). Use get_schedule for full details.",
153
153
  inputSchema: {
154
154
  type: "object" as const,
155
155
  properties: {
@@ -157,6 +157,17 @@ const TOOLS = [
157
157
  },
158
158
  },
159
159
  },
160
+ {
161
+ name: "get_schedule",
162
+ description: "Get full details of a scheduled task, including prompt text.",
163
+ inputSchema: {
164
+ type: "object" as const,
165
+ properties: {
166
+ id: { type: "string" as const, description: "Schedule ID or prefix" },
167
+ },
168
+ required: ["id"],
169
+ },
170
+ },
160
171
  {
161
172
  name: "create_schedule",
162
173
  description: "Create a new scheduled task.",
@@ -380,13 +391,29 @@ export async function handleListSchedules(db: Database, params: any) {
380
391
  lastRun = rq.getLastRun(db, s.id);
381
392
  }
382
393
  result.push({
383
- ...s,
394
+ id: s.id,
395
+ name: s.name,
396
+ cron: s.cron,
397
+ workdir: s.workdir,
398
+ enabled: s.enabled,
399
+ model: s.model,
400
+ max_turns: s.max_turns,
401
+ use_worktree: s.use_worktree,
402
+ source: s.source,
403
+ created_at: s.created_at,
404
+ updated_at: s.updated_at,
384
405
  last_run: lastRun ? { status: lastRun.status, finished_at: lastRun.finished_at } : null,
385
406
  });
386
407
  }
387
408
  return result;
388
409
  }
389
410
 
411
+ export async function handleGetSchedule(db: Database, params: any) {
412
+ const sq = await getScheduleQueries();
413
+ if (!sq) throw new Error("Schedule module not available");
414
+ return sq.getScheduleByPrefix(db, params.id);
415
+ }
416
+
390
417
  export async function handleCreateSchedule(db: Database, params: any) {
391
418
  const sq = await getScheduleQueries();
392
419
  if (!sq) throw new Error("Schedule module not available");
@@ -544,6 +571,9 @@ export async function startMcpServer(): Promise<void> {
544
571
  case "list_schedules":
545
572
  result = await handleListSchedules(db, params ?? {});
546
573
  break;
574
+ case "get_schedule":
575
+ result = await handleGetSchedule(db, params ?? {});
576
+ break;
547
577
  case "create_schedule":
548
578
  result = await handleCreateSchedule(db, params ?? {});
549
579
  break;
@@ -70,7 +70,7 @@ export function listRuns(
70
70
  const columns = opts?.include_output
71
71
  ? "r.*"
72
72
  : `r.id, r.schedule_id, r.status, r.pid, r.started_at, r.finished_at,
73
- r.exit_code, r.stderr_tail, r.session_id, r.worktree_path,
73
+ r.exit_code, r.session_id, r.worktree_path,
74
74
  r.tokens_in, r.tokens_out, r.cost_usd, r.tools_used,
75
75
  r.issues_touched, r.tmux_session, r.agent`;
76
76
 
@@ -9,7 +9,9 @@ import {
9
9
  updateSchedule, deleteSchedule, enableSchedule, disableSchedule,
10
10
  } from "../../queries/schedules.ts";
11
11
  import { validateCron } from "../../cron.ts";
12
- import { getLastRun } from "../../queries/runs.ts";
12
+ import { getLastRun, createRun } from "../../queries/runs.ts";
13
+ import { loadConfig } from "../../config.ts";
14
+ import { ExecutionManager } from "../../scheduler.ts";
13
15
 
14
16
  export function scheduleRoutes(db: Database, _config: Config) {
15
17
  const app = new Hono();
@@ -95,7 +97,10 @@ export function scheduleRoutes(db: Database, _config: Config) {
95
97
  const lastRun = getLastRun(db, s.id);
96
98
  return (
97
99
  <tr class="hover:bg-muted/30 transition-colors" key={s.id}>
98
- <td class="px-4 py-3 text-sm font-medium text-foreground">{s.name}</td>
100
+ <td class="px-4 py-3">
101
+ <div class="text-sm font-medium text-foreground">{s.name}</div>
102
+ <div class="text-xs text-muted-foreground font-mono mt-0.5">{s.id.slice(0, 8)}</div>
103
+ </td>
99
104
  <td class="px-4 py-3 text-sm text-muted-foreground font-mono">{s.cron}</td>
100
105
  <td class="px-4 py-3">
101
106
  {s.enabled
@@ -108,6 +113,11 @@ export function scheduleRoutes(db: Database, _config: Config) {
108
113
  </td>
109
114
  <td class="px-4 py-3 text-right">
110
115
  <div class="flex items-center justify-end gap-1.5">
116
+ <form method="post" action={`/schedules/${s.id}/run`}>
117
+ <button type="submit"
118
+ class="rounded-md border border-border px-2.5 py-1 text-xs font-medium text-foreground hover:bg-accent transition-colors"
119
+ >Run once</button>
120
+ </form>
111
121
  <form method="post" action={`/schedules/${s.id}/toggle`}>
112
122
  <button type="submit"
113
123
  class="rounded-md border border-border px-2.5 py-1 text-xs font-medium text-foreground hover:bg-accent transition-colors"
@@ -184,6 +194,20 @@ export function scheduleRoutes(db: Database, _config: Config) {
184
194
  return c.redirect("/schedules");
185
195
  });
186
196
 
197
+ app.post("/:id/run", async (c) => {
198
+ const id = c.req.param("id");
199
+ const schedule = getScheduleByPrefix(db, id);
200
+ const config = loadConfig();
201
+ const run = createRun(db, {
202
+ schedule_id: schedule.id,
203
+ prompt_used: schedule.prompt,
204
+ agent: config.daemon.agent,
205
+ });
206
+ const em = new ExecutionManager(db, config);
207
+ em.executeRun(schedule, run).catch(() => {});
208
+ return c.redirect("/schedules");
209
+ });
210
+
187
211
  app.post("/:id/delete", (c) => {
188
212
  const id = c.req.param("id");
189
213
  const schedule = getScheduleByPrefix(db, id);