prodboard 0.3.0 → 0.4.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,11 @@
1
1
  # prodboard
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#21](https://github.com/G4brym/prodboard/pull/21) [`761e8fc`](https://github.com/G4brym/prodboard/commit/761e8fce4cd370424903753952835f7a399730bb) Thanks [@G4brym](https://github.com/G4brym)! - Add per-schedule and global model selection for Claude and OpenCode agents
8
+
3
9
  ## 0.3.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prodboard",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",
@@ -53,6 +53,11 @@ export class ClaudeDriver implements AgentDriver {
53
53
  args.push("--agents", schedule.agents_json);
54
54
  }
55
55
 
56
+ const model = schedule.model ?? config.daemon.model;
57
+ if (model) {
58
+ args.push("--model", model);
59
+ }
60
+
56
61
  return args;
57
62
  }
58
63
 
@@ -17,8 +17,9 @@ export class OpenCodeDriver implements AgentDriver {
17
17
  args.push("--attach", opencode.serverUrl);
18
18
  }
19
19
 
20
- if (opencode.model) {
21
- args.push("--model", opencode.model);
20
+ const model = schedule.model ?? config.daemon.opencode.model ?? config.daemon.model;
21
+ if (model) {
22
+ args.push("--model", model);
22
23
  }
23
24
 
24
25
  if (opencode.agent) {
@@ -77,6 +77,7 @@ export async function scheduleAdd(args: string[], dbOverride?: Database): Promis
77
77
  use_worktree: !flags["no-worktree"],
78
78
  inject_context: !flags["no-context"],
79
79
  persist_session: !!flags["persist-session"],
80
+ model: flags.model as string | undefined,
80
81
  });
81
82
 
82
83
  console.log(`Created schedule ${schedule.id}: ${schedule.name} [${schedule.cron}]`);
@@ -101,16 +102,16 @@ export async function scheduleLs(args: string[], dbOverride?: Database): Promise
101
102
  }
102
103
 
103
104
  const table = renderTable(
104
- ["ID", "Name", "Cron", "Enabled", "Next Fire"],
105
+ ["ID", "Name", "Cron", "Model", "Enabled", "Next Fire"],
105
106
  schedules.map((s) => {
106
107
  let nextFire = "";
107
108
  try {
108
109
  const next = getNextFire(s.cron, new Date());
109
110
  nextFire = formatDate(next.toISOString());
110
111
  } catch {}
111
- return [s.id, s.name, s.cron, s.enabled ? "yes" : "no", nextFire];
112
+ return [s.id, s.name, s.cron, s.model ?? "-", s.enabled ? "yes" : "no", nextFire];
112
113
  }),
113
- { maxWidths: [10, 30, 20, 8, 18] }
114
+ { maxWidths: [10, 30, 20, 20, 8, 18] }
114
115
  );
115
116
  console.log(table);
116
117
  console.log(`${schedules.length} schedule${schedules.length === 1 ? "" : "s"}`);
@@ -140,6 +141,7 @@ export async function scheduleEdit(args: string[], dbOverride?: Database): Promi
140
141
  }
141
142
  if (flags.prompt || flags.p) fields.prompt = flags.prompt ?? flags.p;
142
143
  if (flags["max-turns"]) fields.max_turns = parseInt(flags["max-turns"] as string, 10);
144
+ if (flags.model !== undefined) fields.model = flags.model === "" ? null : flags.model;
143
145
 
144
146
  const updated = updateSchedule(db, schedule.id, fields);
145
147
  console.log(`Updated schedule ${updated.id}: ${updated.name}`);
package/src/config.ts CHANGED
@@ -73,6 +73,7 @@ export function getDefaults(): Config {
73
73
  },
74
74
  daemon: {
75
75
  agent: "claude",
76
+ model: null,
76
77
  basePath: null,
77
78
  useTmux: true,
78
79
  opencode: {
package/src/db.ts CHANGED
@@ -108,6 +108,10 @@ const MIGRATIONS: Migration[] = [
108
108
  ALTER TABLE runs ADD COLUMN agent TEXT NOT NULL DEFAULT 'claude';
109
109
  `,
110
110
  },
111
+ {
112
+ version: 3,
113
+ sql: `ALTER TABLE schedules ADD COLUMN model TEXT;`,
114
+ },
111
115
  ];
112
116
 
113
117
  export { MIGRATIONS };
package/src/mcp.ts CHANGED
@@ -168,6 +168,7 @@ const TOOLS = [
168
168
  prompt: { type: "string" as const, description: "Prompt to send to Claude" },
169
169
  workdir: { type: "string" as const, description: "Working directory" },
170
170
  max_turns: { type: "number" as const, description: "Max turns per run" },
171
+ model: { type: "string" as const, description: "Model to use for this schedule (e.g. claude-sonnet-4-6)" },
171
172
  },
172
173
  required: ["name", "cron", "prompt"],
173
174
  },
@@ -183,6 +184,7 @@ const TOOLS = [
183
184
  cron: { type: "string" as const },
184
185
  prompt: { type: "string" as const },
185
186
  enabled: { type: "boolean" as const },
187
+ model: { type: "string" as const, description: "Model override (empty string to clear)" },
186
188
  },
187
189
  required: ["id"],
188
190
  },
@@ -401,6 +403,7 @@ export async function handleCreateSchedule(db: Database, params: any) {
401
403
  prompt: params.prompt,
402
404
  workdir: params.workdir,
403
405
  max_turns: params.max_turns,
406
+ model: params.model,
404
407
  source: "mcp",
405
408
  });
406
409
  }
@@ -420,6 +423,7 @@ export async function handleUpdateSchedule(db: Database, params: any) {
420
423
  }
421
424
  if (params.prompt !== undefined) fields.prompt = params.prompt;
422
425
  if (params.enabled !== undefined) fields.enabled = params.enabled ? 1 : 0;
426
+ if (params.model !== undefined) fields.model = params.model === "" ? null : params.model;
423
427
 
424
428
  return sq.updateSchedule(db, schedule.id, fields);
425
429
  }
@@ -16,6 +16,7 @@ export function createSchedule(
16
16
  persist_session?: boolean;
17
17
  agents_json?: string;
18
18
  source?: string;
19
+ model?: string;
19
20
  }
20
21
  ): Schedule {
21
22
  const id = generateId();
@@ -23,8 +24,8 @@ export function createSchedule(
23
24
 
24
25
  db.query(`
25
26
  INSERT INTO schedules (id, name, cron, prompt, workdir, max_turns, allowed_tools,
26
- use_worktree, inject_context, persist_session, agents_json, source, created_at, updated_at)
27
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
27
+ use_worktree, inject_context, persist_session, agents_json, source, model, created_at, updated_at)
28
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
28
29
  `).run(
29
30
  id, opts.name, opts.cron, opts.prompt,
30
31
  opts.workdir ?? ".",
@@ -35,6 +36,7 @@ export function createSchedule(
35
36
  opts.persist_session ? 1 : 0,
36
37
  opts.agents_json ?? null,
37
38
  opts.source ?? "cli",
39
+ opts.model ?? null,
38
40
  now, now
39
41
  );
40
42
 
@@ -83,6 +85,7 @@ export function updateSchedule(
83
85
  enabled: "enabled", max_turns: "max_turns", allowed_tools: "allowed_tools",
84
86
  use_worktree: "use_worktree", inject_context: "inject_context",
85
87
  persist_session: "persist_session", agents_json: "agents_json",
88
+ model: "model",
86
89
  };
87
90
 
88
91
  let hasRealFields = false;
package/src/types.ts CHANGED
@@ -6,6 +6,7 @@ export interface Config {
6
6
  };
7
7
  daemon: {
8
8
  agent: "claude" | "opencode";
9
+ model: string | null;
9
10
  basePath: string | null;
10
11
  useTmux: boolean;
11
12
  opencode: {
@@ -62,6 +63,7 @@ export interface Schedule {
62
63
  use_worktree: number;
63
64
  inject_context: number;
64
65
  persist_session: number;
66
+ model: string | null;
65
67
  agents_json: string | null;
66
68
  source: string;
67
69
  created_at: string;
@@ -17,6 +17,12 @@
17
17
  // Agent to use: "claude" or "opencode"
18
18
  // "agent": "claude",
19
19
 
20
+ // Default model for agent runs (null = agent's default)
21
+ // For Claude: "claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5-20251001"
22
+ // For OpenCode: "anthropic/claude-sonnet-4-20250514", etc.
23
+ // Can be overridden per-schedule with --model
24
+ // "model": null,
25
+
20
26
  // Base path for worktrees and agent runs (null = use schedule workdir)
21
27
  // "basePath": null,
22
28