Runs Explore subagents on haiku and general-purpose ones on sonnet unless a model was asked for

A few mods for Claude Code, packaged as a plugin marketplace.
| Mod | What it does |
|---|---|
| pomodoro | A pixel-art pomodoro timer in a side pane. Day and night skies, animated sun, clouds, stars and meteors, a grass field and a daily tomato tally. Toast and chime when a phase ends. |
| agent-progress | Live Powerline-style progress bars for running subagents, with a moving glint and the model each one runs on. |
| cmd-guard | Stops destructive shell commands (rm -rf on /, ~ or *, force pushes, hard resets, dropping tables, mkfs, dd…) and asks what to do: refuse, allow once, trust for this session, or use a safer alternative. |
| model-router | Runs Explore subagents on haiku and general-purpose subagents on sonnet to save cost. An explicit model on the Agent call always wins; forks and workflows are left alone. Optionally lets Jev pick the model per task. |
<img src="docs/pomodoro-focus.png" alt="Pomodoro focus mode: day sky with sun and clouds" width="280"> <img src="docs/pomodoro-break.png" alt="Pomodoro break mode: night sky with moon and stars" width="280">
claude plugin marketplace add redjackfred/claude-code-mods
claude plugin install pomodoro@claude-code-mods
claude plugin install agent-progress@claude-code-mods
claude plugin install cmd-guard@claude-code-mods
claude plugin install model-router@claude-code-mods
Install only the ones you want. Restart Claude Code afterwards.
/focus [minutes] start a focus session (default 25)
/focus skip skip to the next phase
/focus stop stop the timer
/focus show|hide show or hide the side pane
afplay, so sound is macOS only.
/agent-progress toggles the bars on and off.

Works automatically; /guard off turns it off for the session and /guard on back on. When nobody answers the prompt (or it fails), the command is blocked.
The dialog speaks the language you mostly write in during the session: English, or Traditional Chinese (繁體中文).
It matches commands with regexes, not a shell parser, so treat it as a seatbelt against slips rather than a sandbox: a determined command can still get past it. It also can't tell running a command from mentioning one, so a commit message or heredoc that only quotes a risky command gets stopped too. Reword the text, or /guard off for a moment.
<img src="docs/model-router.png" alt="model-router toasts: Explore routed to haiku, general-purpose to sonnet" width="360">
Works automatically and shows a toast for each subagent it reroutes. To steer the main agent, you can add this to your ~/.claude/CLAUDE.md:
A model-router mod runs Explore subagents on haiku and general-purpose ones on sonnet
unless the Agent call names a model. For complex reasoning, large refactors, or
hard-to-find bugs, pass `model: "opus"` on the Agent call.
Jev is a fast "System One" decision model. With it on, each general-purpose subagent's task is classified as haiku, sonnet or opus work before it starts. In a quick test it took about 0.7 s per call and costs well under a cent.
export TYPESAFE_API_KEY=... # then restart Claude Code
/router jev on # /router jev off to stop
general-purpose → haiku · jev 1.00.Each mod is a Claude Code plugin with TypeScript hooks in hooks/.
claude plugin validate . # the marketplace
claude plugin validate pomodoro # one plugin
claude plugin test pomodoro # its tests
MIT
hooks/register.ts 82 lines1import type { AgentSpawnInput, Register } from 'claude-code'
2
3// subagent type → model; a call that names its own model is left alone
4const ROUTES: Record<string, string> = { Explore: 'haiku', 'general-purpose': 'sonnet' }
5
6export const route = (e: Pick<AgentSpawnInput, 'subagentType' | 'model' | 'fork' | 'workflow'>) =>
7 e.model || e.fork || e.workflow ? undefined : ROUTES[e.subagentType]
8
9// With `/router jev on` and TYPESAFE_API_KEY set, Jev (a System One model) reads a general-purpose
10// task and picks its tier. Its pick wins only when it is confident; a slow,
11// failed or unsure answer keeps the rule's model.
12const JEV_URL = 'https://api.typesafe.ai/v1/systemone'
13const JEV_TIMEOUT_MS = 1500
14export const MIN_CONFIDENCE = 0.8
15// ponytail: the task is cut to its head, so a long prompt is judged by its start
16const MAX_TASK_CHARS = 4000
17
18export const TIERS = {
19 haiku: 'Simple, mechanical work: find or list files, read and summarize a little code, run a command and report, small obvious edits.',
20 sonnet: 'Ordinary engineering: implement a feature, write tests, review code, fix a bug with a clear cause, research across a few files.',
21 opus: 'Hard reasoning: subtle or hard-to-find bugs, large refactors, architecture or design decisions, tricky concurrency, security analysis.',
22} as const
23type Tier = keyof typeof TIERS
24type JevChoice = { choice?: string; confidence?: number }
25
26export const pick = (answer: JevChoice | undefined): { model: Tier; confidence: number } | undefined =>
27 answer?.choice && answer.choice in TIERS && (answer.confidence ?? 0) >= MIN_CONFIDENCE
28 ? { model: answer.choice as Tier, confidence: answer.confidence! }
29 : undefined
30
31export const register: Register = on => {
32 on('session.start', async ($, e, next) => {
33 await $.command.register({ name: 'router', description: 'Let Jev pick general-purpose subagent models (jev on | jev off); sends task text to TypeSafe' })
34 return next(e)
35 })
36
37 // off by default: turning it on sends every general-purpose task to TypeSafe
38 on('command.run', { command: 'router' }, async ($, e) => {
39 const arg = e.args.trim()
40 if (arg !== 'jev on' && arg !== 'jev off') {
41 return { text: `🪙 Jev routing is ${(await $.store.get('jev')) === true ? 'on' : 'off'}. Use /router jev on | jev off.` }
42 }
43 const enable = arg === 'jev on'
44 if (enable && !(await $.env.get('TYPESAFE_API_KEY'))) return { text: '🪙 Set TYPESAFE_API_KEY first, then restart Claude Code.' }
45 await $.store.set('jev', enable)
46 return { text: enable ? '🪙 Jev routing on: general-purpose tasks are sent to TypeSafe to pick a model.' : '🪙 Jev routing off.' }
47 })
48
49 on('agent.spawn', async ($, e, next) => {
50 const rule = route(e)
51 if (!rule) return next(e)
52
53 const useJev = e.subagentType === 'general-purpose' && (await $.store.get('jev')) === true
54 const key = useJev ? await $.env.get('TYPESAFE_API_KEY') : undefined
55 const judged = key ? await ask($, key, e) : undefined
56 const model = judged?.model ?? rule
57 $.ui.toast(`🪙 ${e.subagentType} → ${model}${judged ? ` · jev ${judged.confidence.toFixed(2)}` : ''}`)
58 return next({ ...e, model })
59 })
60}
61
62const ask = async ($: Parameters<Parameters<Register>[0]>[2] extends never ? never : any, key: string, e: AgentSpawnInput) => {
63 const request = $.http.fetch(JEV_URL, {
64 method: 'POST',
65 headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
66 body: JSON.stringify({
67 model: 'jev-latest',
68 state: { description: e.description, task: e.prompt.slice(0, MAX_TASK_CHARS) },
69 questions: {
70 tier: {
71 type: 'choice',
72 instructions: 'Which model tier does a coding agent need to do `task` well? Pick the cheapest tier that will still do it well.',
73 criteria: TIERS,
74 },
75 },
76 }),
77 })
78 .then((r: { ok: boolean; text: string }) => (r.ok ? pick(JSON.parse(r.text).answers?.tier) : undefined))
79 .catch(() => undefined) // network, auth or parse failure: the rule decides
80 return Promise.race([request, $.clock.sleep(JEV_TIMEOUT_MS).then(() => undefined)])
81}
82