See and message your other live Claude sessions (/live, /peers, /tell), confirm risky shell commands, chime after long turns.

A shelf of small, independent mods for Claude Code. Install only the ones you want. Each has its own README with install steps and a sample of what it looks like.
Browse the demo site for a visual walkthrough of every mod, with copy-paste install commands.
15 mods in five groups: watch what is happening, stay safe, quality and flow, multi-session, and fun. They are independent: each is its own plugin, installs on its own, and removes cleanly.
/plugin marketplace add cloudcruncher/claude-mod-garage `` /plugin install agent-radar@mod-garage ``/reload-plugins (or restart). Then use the mod's command (see tables) or just let it work.Try one without installing: git clone https://github.com/cloudcruncher/claude-mod-garage, then claude --plugin-dir ./claude-mod-garage/dev-mods/agent-radar.
hooks/hooks.json with a "modules" list). If /plugin install succeeds but nothing happens, your build is probably too old.verify-gate and git-band shell out to your own tools (npm, uv, make, git). Those need to be on your PATH.See your agents, git state and spend without leaving the terminal.
| Mod | What it does | Activate |
|---|---|---|
| agent-radar | A live side window showing what your agents are doing, in parallel. | /radar opens the pane (Esc closes it). |
| git-band | Branch, dirty count and ahead/behind always visible above the prompt. | Automatic once installed. Nothing to run. |
| turn-recap | A toast after each long turn: how long, how many tools, which files. | Automatic once installed. |
| cost-router | Usage in the status line, spend warnings, cheaper models for subagents. | Automatic once installed. |
Guardrails that stop mistakes before they cost you.
| Mod | What it does | Activate |
|---|---|---|
| secret-guard | Stops secrets being written, committed or pushed. | Automatic once installed. |
| loop-breaker | Cuts off a model that keeps repeating the same tool call. | Automatic once installed. |
| read-only-mode | Look but don't touch: explore a codebase with edits disabled. | /readonly on to enable, /readonly off to disable, /readonly status to check. |
| auto-checkpoint | Automatic snapshots before edits, so any change is undoable. | Snapshots are automatic. |
| context-guardian | Warns before the context window fills up. | Automatic once installed. |
Faster prompts, automatic checks, and a record of what happened.
| Mod | What it does | Activate |
|---|---|---|
| verify-gate | Runs your tests, lint and type-check after edits, and feeds failures back. | Automatic once installed. Needs a recognisable project (package.json, pyproject.toml or Makefile). |
| prompt-snippets | Type ;review or ;tests and it expands into a full instruction. | Type ;name anywhere at the start of a word in a prompt. |
| session-journal | One command turns the session into a shareable markdown timeline. | /journal any time; run it again to refresh the same file. |
Run several Claude sessions and keep them aware of each other.
| Mod | What it does | Activate |
|---|---|---|
| session-deck (repo root) | See and message your other live Claude sessions. | /live opens the pane, /peers lists sessions, /tell <id-prefix> <text> messages one. |
For the long runs.
| Mod | What it does | Activate |
|---|---|---|
| break-time | A coffee-break reminder with an animated pane. | Automatic reminders. |
| arcade | Doom-ish raycaster and Snake in a pane, for waiting on long runs. | /doom: WASD/arrows move, space shoots, q quits. |
Every mod in full. Click one to expand.
Opens a pane beside the conversation that updates in real time. It shows what each loop is doing right now (thinking, writing its next message, starting a tool call) and the tool running (with a ticking timer) and what the model last said in its visible messages, every subagent with its recent calls and latest message, an activity log with a one-line result under each call, the files edited this session, and in the header a working/idle state, turn time, a context bar with the window split by category (system prompt, tools, skills, messages) and a ranked list of what is filling the conversation (which tools' results, your prompts, the model's replies, and the biggest single results; token figures there are estimates), and 5h usage. Ideal when you fan out several subagents and want to see who is doing what.
Install
/plugin install agent-radar@mod-garage
Use it
/radar opens the pane (Esc closes it).autoOpen to open it at every session start.What you'll see (illustrative)
/radar pane
● working (2 tools running) · turn 1m12s · 14 calls · 5h 8%
ctx ███░░░░░░░ 34% (+6% this turn)
System prompt 3.9k · System tools 9.2k · Skills 7.5k · Messages 29k
Now
● main writing: “Running the suite before I touch the parser…” 3s
▶ Bash npm test -- --watch=false 8s
▶ [a3f1] Grep validateToken 2s
“Running the suite before I touch the parser, then I'll fix whatever fails.”
Agents (3 spawned)
▶ Explore · map auth flow
✓ Read src/auth.ts 0s
✓ Grep validateToken 1s
“Token checks live in src/auth.ts and middleware/session.ts.”
· Plan · outline migration
Building up in context (estimate)
████████ Read ×6 11k
███░░░░░ Bash ×3 4.1k
█░░░░░░░ Replies 1.2k
biggest:
7.0k Read src/auth.ts
2.5k Bash npm test
Activity
✓ Bash npm test 14s
→ 12 passed
✗ Bash npx tsc --noEmit 6s
→ src/auth.ts(41,5): error TS2322: Type 'string' is not assignable…
✓ Read src/auth.ts 0s
Files touched
auth/token.test.ts ×2
src/auth.ts
Options
| Option | Default | What it does |
|---|---|---|
autoOpen | false | Open the pane automatically at session start |
Set options when installing, or later in /plugin → the mod → configure. Notes
[a3f1] is the first 4 characters of a subagent id; calls with no tag are the main session's.Adds one dim line above the prompt with the current git branch, how many files changed, and commits ahead or behind upstream. It refreshes after every turn and every 15 seconds, so you notice at a glance when Claude has modified files or you are on the wrong branch.
Install
/plugin install git-band@mod-garage
Use it
What you'll see (illustrative)
Above the prompt
⎇ feature/login ● 3 changed ↑1
> _
After committing
⎇ feature/login ✓ clean ↑2
> _
Notes
When a turn runs longer than a threshold and used tools, shows a one-line toast so you can tab away during long runs and come back to a summary.
Install
/plugin install turn-recap@mod-garage
Use it
What you'll see (illustrative)
Toast
Turn done in 1m 35s · 12 tool calls · 3 files edited
Options
| Option | Default | What it does |
|---|---|---|
minSeconds | 20 | Turns shorter than this show no recap |
Set options when installing, or later in /plugin → the mod → configure.
Shows 5h NN% | ctx NN% in the status line, toasts once when your 5-hour usage passes 70% and 85%, and picks haiku/sonnet/opus for subagents that were given no model, based on keywords in their task. Explicit models are never overridden.
Install
/plugin install cost-router@mod-garage
Use it
What you'll see (illustrative)
Status line
5h 62% | ctx 34%
Toast
5h usage passed 70% (now 71%)
Notes
n/a and no toasts fire.Refuses to write .env, .pem, SSH keys and credentials files, refuses commands that contain API keys or high-entropy tokens, blocks git add/commit of staged secrets, and blocks pushes to main/master and force pushes. .env.example-style files are allowed.
Install
/plugin install secret-guard@mod-garage
Use it
What you'll see (illustrative)
Model tries to write .env
secret-guard: refusing to write .env. .env is an environment file that usually holds secrets. Use a .env.example with placeholder values, or ask the user to edit it.
Model tries to push
secret-guard: git push blocked: pushing to main is not allowed.
Notes
If the same tool is called with identical arguments more than N times in a row, the next call is refused with a message telling the model to change approach, and you get a toast. Each subagent is judged on its own calls. If the hook errors it fails open.
Install
/plugin install loop-breaker@mod-garage
Use it
What you'll see (illustrative)
Fourth identical call
This exact Bash call has already run 3 times in a row with the same arguments. Repeating it will not change the result. Re-read the output, change the arguments, or try a different approach.
Toast
loop-breaker: refused a Bash call repeated 4 times
Options
| Option | Default | What it does |
|---|---|---|
maxRepeats | 3 | Identical calls allowed in a row |
Set options when installing, or later in /plugin → the mod → configure.
/readonly on refuses every file edit and every shell command that is not clearly read-only (ls, cat, grep, git status/log/diff, find without -delete...). Redirects, command substitution and unknown commands are refused. Great for onboarding to unfamiliar code or inspecting a production checkout.
Install
/plugin install read-only-mode@mod-garage
Use it
/readonly on to enable, /readonly off to disable, /readonly status to check.What you'll see (illustrative)
Enable
> /readonly on
Read-only mode ON: edits and mutating commands are refused.
Model tries to edit
Read-only mode is on (the user ran /readonly on). Do not edit files; describe the change instead.
Status line
🔒 read-only
Notes
Snapshots files before the model edits them or runs risky shell commands. /rewind-to lists the checkpoints and restores one.
Install
/plugin install auto-checkpoint@mod-garage
Use it
/rewind-to list shows checkpoints; /rewind-to 2 restores checkpoint 2.What you'll see (illustrative)
/rewind-to list
1 14:02 Edit src/auth.ts
2 14:05 Edit src/auth.ts
3 14:09 Bash git checkout -- .
/rewind-to 2
Rewound to checkpoint 2:
restored src/auth.ts
Notes
Toasts at 70% context. At 85% it also adds an instruction telling the model to wrap up the current step and run /compact, keeping the task, changed files, failing output, decisions and branch state.
Install
/plugin install context-guardian@mod-garage
Use it
What you'll see (illustrative)
At 70%
Context is 70% full. Consider /compact or delegating.
At 85% (sent to the model)
Context is nearly full (85% or more). Wrap up the current step, then run /compact.
Notes
After a turn that edited code, detects your project's fast checks (npm/pnpm/yarn/bun scripts for test, lint, typecheck; pytest, ruff, mypy/pyright via uv; make test) and runs them. Failures go back to the model so it fixes them, up to 3 retries. Docs, images and config edits are ignored.
Install
/plugin install verify-gate@mod-garage
Use it
What you'll see (illustrative)
All green
verified
Gives up
verify-gate: still failing after 3 retries, giving up this turn
Short ;name shortcuts in a prompt expand to reusable instructions before Claude sees them. Ships with ;review ;tests ;plan ;explain ;short ;fix, and you can add your own.
Install
/plugin install prompt-snippets@mod-garage
Use it
;name anywhere at the start of a word in a prompt./snippets lists everything available.extraSnippets option (a JSON object).What you'll see (illustrative)
You type
refactor the parser ;plan ;short
Claude receives
refactor the parser Before editing anything, outline a short plan: the goal, the files you will touch and how you will verify it. Then proceed. Keep the answer under 5 lines. Results first, no preamble.
/snippets
;review Review the current changes for correctness bugs, security issues and missed…
;tests Write focused tests for the behaviour above, run them, and report the result…
;plan Before editing anything, outline a short plan: the goal, the files you will touc…
Options
| Option | Default | What it does |
|---|---|---|
extraSnippets | (empty) | JSON object of name -> text, e.g. {"ship":"Run the checks, then commit and push."} |
Set options when installing, or later in /plugin → the mod → configure. Notes
;foo are left untouched.Quietly records your prompts, shell commands run and files edited. /journal writes them to .claude/journal/<date-time>.md, grouped under each prompt with a files-changed summary. Good for handoffs, standups and PR descriptions.
Install
/plugin install session-journal@mod-garage
Use it
/journal any time; run it again to refresh the same file.What you'll see (illustrative)
/journal
Journal written to .claude/journal/2026-10-14-09-30.md (23 entries).
The file
# Session 2026-10-14 09:30
## 09:30 · fix the login redirect
- ran `npm test`
- edited `src/auth.ts`
- ran `npm test`
## Files changed
- `src/auth.ts` (×1)
Notes
.claude/journal/ to .gitignore if you don't want to commit journals.Keeps a shared roster of your running sessions (project, model, one-line AI summary of what each is doing). /live opens a pane with the roster, usage bars and a small pet; /peers lists other sessions; /tell sends one a message. Also asks before risky shell commands (force-delete, force-push, publishing) and chimes after turns longer than 30s.
Install
/plugin install session-deck@mod-garage
Use it
/live opens the pane, /peers lists sessions, /tell <id-prefix> <text> messages one.What you'll see (illustrative)
/peers
a1b2c3d4 sonnet /work/api fixing token refresh bug
9f8e7d6c opus /work/web reviewing checkout redesign
/tell 9f8e Please rebase on main
Sent to 9f8e7d6c
Risky command prompt
DESTRUCTIVE: force/recursive delete | run it? rm -rf src
[Run it] [Refuse]
Notes
session-deck (the repo root is itself a plugin).Tracks active time and, after a while, toasts and opens an animated ASCII coffee-break pane with a countdown and stretch tips.
Install
/plugin install break-time@mod-garage
Use it
/break starts a break now, /break-skip snoozes 15 minutes, /break-done resets the timer.What you'll see (illustrative)
Toast
Time for a coffee break. Run /break (or /break-skip to snooze).
Options
| Option | Default | What it does |
|---|---|---|
intervalMinutes | 150 | Active minutes between reminders |
breakMinutes | 5 | Countdown length |
Set options when installing, or later in /plugin → the mod → configure.
Tiny text-mode games that run inside Claude Code. /doom is a raycaster with wandering enemies and a minimap; /snake is classic snake.
Install
/plugin install arcade@mod-garage
Use it
/doom: WASD/arrows move, space shoots, q quits./snake: WASD/arrows steer, r restarts, q quits.What you'll see (illustrative)
/snake
┌──────────────┐
│ │
│ ████► ● │
│ │
└──────────────┘ score 3
Notes
| Task | Command |
|---|---|
| See what is installed | /plugin |
| Update a mod | /plugin marketplace update mod-garage then /reload-plugins |
| Turn a mod off without removing it | /plugin disable <name>@mod-garage |
| Remove a mod | /plugin uninstall <name>@mod-garage |
| Set a mod's options | /plugin → pick the mod → configure |
secret-guard fails closed: if the guard itself errors, the write or git command is blocked. loop-breaker fails open so a bug in it never stops your work.read-only-mode is a guardrail on the model's tool calls, not a sandbox.session-journal and auto-checkpoint write inside your project (.claude/journal/, checkpoints). Add them to .gitignore if you do not want them committed./reload-plugins, then /plugin and confirm it is enabled.claude plugin validate <path> reports problems with a mod's manifest or hooks, and claude --debug logs hook failures.claude plugin validate dev-mods/<name>
claude plugin test dev-mods/<name>
python3 scripts/gen_docs.py # regenerate these docs from scripts/gen_docs.py
Each mod keeps pure logic in lib.ts with unit tests and the hook wiring in register.ts(x). Test mods one folder at a time: running claude plugin test at the repo root also loads the root plugin's hooks into every nested test.
MIT
hooks/register.js 155 lines1// Each rule: [label, regex]. First match wins; the label is shown in the prompt so you know why it flagged.
2const SAFE_RM = /^(\.\/)?(node_modules|dist|build|out|coverage|__pycache__|\.cache|\.next|\.turbo|\.pytest_cache|\.venv|target|tmp)\/?$/
3const RULES = [
4 ['DESTRUCTIVE: force/recursive delete', /\brm\s+(-[a-z]*\s+)*-[a-z]*(r[a-z]*f|f[a-z]*r)[a-z]*\b/i],
5 ['DESTRUCTIVE: discards uncommitted work', /\bgit\s+(reset\s+--hard|clean\s+-[a-z]*f|checkout\s+--\s+\.|restore\s+\.)/i],
6 ['DESTRUCTIVE: force push', /\bgit\s+push\b.*(--force|\s-f\b)/i],
7 ['DESTRUCTIVE: drops data', /\bdrop\s+(table|database|schema)\b|\btruncate\s+table\b/i],
8 ['PROD: dbt against prod', /\bdbt\s+(run|build|seed)\b.*--target\s+prod/i],
9 ['PUBLISH: pushes code to a remote', /\bgit\s+push\b/i],
10 ['PUBLISH: releases a package or image', /\b(npm|pnpm|yarn)\s+publish\b|\buv\s+publish\b|\btwine\s+upload\b|\bdocker\s+push\b|\bcargo\s+publish\b|\bgh\s+release\s+(create|upload)\b/i],
11 ['PUBLISH: makes content public', /\bgh\s+(gist\s+create\b.*--public|repo\s+(create\b.*--public|edit\b.*--visibility\s+public))|\bgh\s+pr\s+(create|merge)\b/i],
12 ['PUBLISH: deploys or uploads a site', /\b(vercel|netlify|wrangler|firebase|surge|gh-pages)\b.*\b(deploy|publish)\b|\baws\s+s3\s+(sync|cp|mv)\b.*(--acl\s+public|s3:\/\/)|\bscp\b|\brsync\b.*:/i],
13]
14// Heredoc bodies are data (a doc, a script written to disk), not commands, unless piped into a shell.
15function stripHeredocs(cmd) {
16 return cmd.replace(/^(.*?)<<-?\s*(['"]?)(\w+)\2[^\n]*\n[\s\S]*?\n\s*\3[ \t]*$/gm, (m, head) =>
17 /\b(ba|z|da)?sh\b\s*(-s|-)?\s*$/.test(head.trim()) ? m : head)
18}
19function classify(cmd) {
20 cmd = stripHeredocs(cmd)
21 const rm = cmd.match(/\brm\s+(.*)/)
22 if (rm && /-[a-z]*r/i.test(rm[1])) {
23 const targets = rm[1].split(/\s+/).filter(t => t && !t.startsWith('-') && !/^[;&|]/.test(t))
24 if (targets.length && targets.every(t => SAFE_RM.test(t))) cmd = cmd.replace(/\brm\b/g, 'rm-safe')
25 }
26 for (const [label, re] of RULES) if (re.test(cmd)) return label
27 return null
28}
29const PEERS = 'peers'
30let me = ''
31let working = false
32let usage = { ctx: 0, five: 0 }
33let tick = 0
34let peerCache = {}
35const PANE = 'live'
36const STALE_MS = 6 * 3600 * 1000
37
38async function heartbeat($) {
39 const id = await $.session.id()
40 const all = (await $.store.get(PEERS)) || {}
41 const now = await $.clock.now()
42 for (const k of Object.keys(all)) if (now - all[k].t > STALE_MS) delete all[k]
43 all[id] = { t: now, cwd: await $.session.cwd(), model: String(await $.session.model()), summary: (all[id] || {}).summary || '' }
44 await $.store.set(PEERS, all)
45 return all
46}
47
48function pet() {
49 const f = tick % 2
50 if (usage.five >= 90 || usage.ctx >= 90) return f ? '(x_x)' : '(>_<)'
51 if (usage.five >= 80 || usage.ctx >= 80) return f ? '(;°o°)' : '(;°O°)'
52 if (working) return ['(ง •̀_•́)ง', '(ง •̀o•́)ง'][f]
53 return ['(=^・ω・^=)', '(=^-ω-^=)'][f]
54}
55
56async function summarise($) {
57 try {
58 const msgs = (await $.session.messages()).slice(-8).map((m) => m.role + ': ' + String(m.text || '').slice(0, 300)).join('\n')
59 if (!msgs.trim()) return
60 const r = await $.model.complete({ model: 'haiku', system: 'Summarise what this coding session is working on in under 12 words. No preamble.', prompt: msgs, maxTokens: 40, timeoutMs: 15000 })
61 if (!r.isAnswered) return
62 const all = (await $.store.get(PEERS)) || {}
63 const id = await $.session.id()
64 all[id] = { ...(all[id] || { t: await $.clock.now(), cwd: await $.session.cwd(), model: '' }), summary: r.text.trim().replace(/\s+/g, ' ').slice(0, 100) }
65 await $.store.set(PEERS, all)
66 } catch {}
67}
68
69export function register(on) {
70 on('command.run', { command: 'live' }, async ($) => {
71 await $.ui.open({ id: PANE, title: 'Live', closeOnEscape: true })
72 return {}
73 })
74
75 on('ui.render', { component: 'Pane' }, async ($, e, next) => {
76 if (e.requestId !== PANE) return next(e)
77 const { Box, Text } = $.ui.resolve(e)
78 const bar = (p) => '█'.repeat(Math.round(Math.min(100, p) / 12.5)).padEnd(8, '░') + ' ' + Math.round(p) + '%'
79 const rows = Object.entries(peerCache).map(([k, v]) =>
80 Text({ children: [(k === me ? '★ ' : ' ') + k.slice(0, 6) + ' ' + String(v.cwd).split('/').slice(-2).join('/') + ' · ' + (v.summary || '…')], wrap: 'truncate' }))
81 return Box({ flexDirection: 'column', children: [
82 Text({ children: [pet() + ' 5h ' + bar(usage.five) + ' ctx ' + bar(usage.ctx)], bold: true }),
83 Text({ children: [' '] }),
84 ...(rows.length ? rows : [Text({ children: ['No sessions yet.'] })]),
85 ] })
86 })
87
88 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
89 working = !!(e.props && e.props.isWorking)
90 const { Box, Text } = $.ui.resolve(e)
91 const theirs = await next(e)
92 return Box({ flexDirection: 'column', children: [theirs, Text({ children: [pet()], dimColor: true })] })
93 })
94
95 on('session.start', async ($, e, next) => {
96 $.clock.every(60_000, () => heartbeat($))
97 me = await $.session.id()
98 peerCache = await heartbeat($)
99 $.clock.every(2000, async () => {
100 tick++
101 try {
102 const u = await $.session.usage()
103 usage.ctx = (u.context && u.context.percent) || 0
104 const r = (u.rateLimits || []).find((x) => /five|5/.test(String(x.kind))) || (u.rateLimits || [])[0]
105 usage.five = r ? r.percentUsed || 0 : 0
106 peerCache = (await $.store.get(PEERS)) || {}
107 } catch {}
108 $.ui.invalidate('ui.render')
109 })
110 try { await $.command.register({ name: 'live', description: 'Open the live pane: peers, usage, pet' }) } catch {}
111 try { await $.command.register({ name: 'peers', description: 'List your other live Claude sessions' }) } catch {}
112 try { await $.command.register({ name: 'tell', description: 'Message another session: /tell <id-prefix> <text>', argumentHint: '<id-prefix> <text>' }) } catch {}
113 return next(e)
114 })
115
116 on('command.run', { command: 'peers' }, async ($) => {
117 const me = await $.session.id()
118 const all = await heartbeat($)
119 const rows = Object.entries(all).filter(([k]) => k !== me).map(([k, v]) => `${k.slice(0, 8)} ${v.model} ${v.cwd} ${v.summary || ''}`)
120 return { text: rows.length ? rows.join('\n') : 'No other live sessions.' }
121 })
122
123 on('command.run', { command: 'tell' }, async ($, e) => {
124 const [prefix, ...rest] = (e.args || '').trim().split(/\s+/)
125 const all = (await $.store.get(PEERS)) || {}
126 const hits = Object.keys(all).filter((k) => prefix && k.startsWith(prefix))
127 if (hits.length !== 1 || !rest.length) return { text: 'Usage: /tell <id-prefix> <text> (prefix must match exactly one peer; see /peers)' }
128 const r = await $.session.send({ to: { sessionId: hits[0] }, text: rest.join(' ') })
129 return { text: r.isDelivered ? 'Sent to ' + hits[0].slice(0, 8) : 'Not delivered: ' + r.reason }
130 })
131
132 on('session.receive', async ($, e, next) => {
133 $.ui.toast('Message from another session: ' + String(e.text).slice(0, 120))
134 return next(e)
135 })
136
137 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
138 const why = classify(e.command)
139 if (!why) return next(e)
140 let answer = 'Refuse'
141 try { answer = await $.ui.ask(why + ' | run it? ' + e.command, ['Run it', 'Refuse']) } catch {}
142 if (answer !== 'Run it') return { deny: 'The user declined this command. Ask before trying a different approach.' }
143 return next(e)
144 }).catch(async ($, e, next) => {
145 if (next.called) return next(e)
146 return { deny: 'Risky-command guard failed (' + next.error.kind + '), command not run.' }
147 })
148
149 on('turn.complete', async ($, e, next) => {
150 if (!e.agentId) $.clock.after(0, () => summarise($))
151 if (!e.agentId && (e.durationMs || 0) > 30_000) { try { await $.audio.play('done') } catch {} }
152 return next(e)
153 })
154}
155