SLOPSHOPPER

session-deck

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

newpanebandguardcommandtoast
v0.1.0MITupdated 2026-10-14cloudcruncher/claude-mod-garage
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · session-deck
│ ┃ Live ✕ › fix the failing auth test and add an audit log call │ ┃ (=^・ω・^=) 5h ░░░░░░░░ 0% ctx ░░░░░░░░ 0% │ ┃ ⏺ Read(src/auth.ts) │ ┃ ★ previe work/app · … ⎿ Read 6 lines │ ⏺ Update(src/auth.ts) │ ⎿ Added 2 lines, removed 1 line │ ⏺ Bash(rm -rf build && git push --force origin main) │ ⎿ Denied by session-deck: The user declined this command. A │ │ ● Done. refresh now rejects expired claims and logs an audit event. │ │ ✻ Worked for 42s · done 4:20 PM │ │ › /live │ │ ⟨Claude Code's own drawing⟩ (=^・ω・^=) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
⟨Claude Code's own drawing⟩ (=^・ω・^=)
Pane · Live
(=^・ω・^=) 5h ░░░░░░░░ 0% ctx ░░░░░░░░ 0% ★ previe work/app · …
README

Claude Mod Garage

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.

Quick start

  1. Add the marketplace once: `` /plugin marketplace add cloudcruncher/claude-mod-garage ``
  2. Install any mod by name: `` /plugin install agent-radar@mod-garage ``
  3. Run /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.

Requirements

  • A Claude Code build that supports plugin hook modules (hooks/hooks.json with a "modules" list). If /plugin install succeeds but nothing happens, your build is probably too old.
  • The mods are TypeScript hook modules that Claude Code loads itself. There is nothing to compile and no runtime to install.
  • Built and tested on macOS. Side panes dock beside the conversation from about 110 terminal columns; on narrower terminals they may not be shown.
  • verify-gate and git-band shell out to your own tools (npm, uv, make, git). Those need to be on your PATH.

Pick a mod

Watch what's happening

See your agents, git state and spend without leaving the terminal.

ModWhat it doesActivate
agent-radarA live side window showing what your agents are doing, in parallel./radar opens the pane (Esc closes it).
git-bandBranch, dirty count and ahead/behind always visible above the prompt.Automatic once installed. Nothing to run.
turn-recapA toast after each long turn: how long, how many tools, which files.Automatic once installed.
cost-routerUsage in the status line, spend warnings, cheaper models for subagents.Automatic once installed.

Stay safe

Guardrails that stop mistakes before they cost you.

ModWhat it doesActivate
secret-guardStops secrets being written, committed or pushed.Automatic once installed.
loop-breakerCuts off a model that keeps repeating the same tool call.Automatic once installed.
read-only-modeLook but don't touch: explore a codebase with edits disabled./readonly on to enable, /readonly off to disable, /readonly status to check.
auto-checkpointAutomatic snapshots before edits, so any change is undoable.Snapshots are automatic.
context-guardianWarns before the context window fills up.Automatic once installed.

Quality & flow

Faster prompts, automatic checks, and a record of what happened.

ModWhat it doesActivate
verify-gateRuns 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-snippetsType ;review or ;tests and it expands into a full instruction.Type ;name anywhere at the start of a word in a prompt.
session-journalOne command turns the session into a shareable markdown timeline./journal any time; run it again to refresh the same file.

Multi-session

Run several Claude sessions and keep them aware of each other.

ModWhat it doesActivate
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.

Wellbeing & fun

For the long runs.

ModWhat it doesActivate
break-timeA coffee-break reminder with an animated pane.Automatic reminders.
arcadeDoom-ish raycaster and Snake in a pane, for waiting on long runs./doom: WASD/arrows move, space shoots, q quits.

Mod details

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).
  • Set option 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

OptionDefaultWhat it does
autoOpenfalseOpen the pane automatically at session start

Set options when installing, or later in /plugin → the mod → configure. Notes

  • Panes dock beside the transcript from about 110 terminal columns; narrower terminals show it above the prompt.
  • [a3f1] is the first 4 characters of a subagent id; calls with no tag are the main session's.
  • Shows the model's visible messages only. Its private reasoning is never read or displayed.
  • Redraws once a second while the pane is open.

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

  • Automatic once installed. Nothing to run.

What you'll see (illustrative)

Above the prompt

⎇ feature/login ● 3 changed ↑1
> _

After committing

⎇ feature/login ✓ clean ↑2
> _

Notes

  • Shows nothing outside a git repository.

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

  • Automatic once installed.

What you'll see (illustrative)

Toast

Turn done in 1m 35s · 12 tool calls · 3 files edited

Options

OptionDefaultWhat it does
minSeconds20Turns 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

  • Automatic once installed.

What you'll see (illustrative)

Status line

5h 62% | ctx 34%

Toast

5h usage passed 70% (now 71%)

Notes

  • With no subscription rate-limit data, values show as 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

  • Automatic once installed.

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

  • Fails closed: if the guard itself errors, the write or git command is blocked.

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

  • Automatic once installed.

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

OptionDefaultWhat it does
maxRepeats3Identical 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

  • It is a guardrail on the model's tool calls, not a sandbox: it does not stop you from running commands yourself.

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

  • Snapshots are automatic.
  • /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

  • Output format shown is illustrative.

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

  • Automatic once installed.

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

  • Toast wording is illustrative.

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

  • Automatic once installed. Needs a recognisable project (package.json, pyproject.toml or Makefile).

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

  • Type ;name anywhere at the start of a word in a prompt.
  • /snippets lists everything available.
  • Add your own with the 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

OptionDefaultWhat 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

  • Unknown names such as ;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

  • Add .claude/journal/ to .gitignore if you don't want to commit journals.
  • Records are kept in memory (last 1000 entries), so a restart starts a fresh journal.

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.
  • The risky-command prompt and the chime are automatic.

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

  • Sessions only see each other on the same machine and user.
  • Install name: 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

  • Automatic reminders.
  • /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

OptionDefaultWhat it does
intervalMinutes150Active minutes between reminders
breakMinutes5Countdown 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

  • Doom needs a pane of at least 40x16; 80x30 or larger looks best.
  • Keys only reach a game while its pane has focus. Esc closes it.
  • Key presses move in steps (terminals give no key-up), so tap rather than hold.

Managing mods

TaskCommand
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

Safety notes

  • 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.
  • Nothing in this repo makes network calls of its own, and no mod sends your prompts anywhere.

Troubleshooting

  • Installed but nothing shows: run /reload-plugins, then /plugin and confirm it is enabled.
  • Pane does not open: the terminal may be too narrow (panes dock from about 110 columns). Widen it and retry.
  • Command not found: the slash command only exists once the mod is enabled and reloaded.
  • Something looks wrong: claude plugin validate <path> reports problems with a mod's manifest or hooks, and claude --debug logs hook failures.

Develop

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.

License

MIT

Source 1 files
hooks/register.js 155 lines
1// 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