Speaks and toasts when a long turn finishes

Mods for Claude Code, one folder each. A mod is a plugin of function hooks: panes, status lines, slash commands, tool guards.
| Mod | What it does |
|---|---|
| cost-pane | Token and USD spend per session and per day, in the status line and a /cost-pane pane |
| context-guard | Context window fill in the status line; a toast once it passes 80% |
| cache-watch | A toast when a turn rebuilt the prompt cache: tokens written, turn cost, likely cause (idle, model change) |
| turn-timer | Speaks (say on macOS) and toasts when a turn longer than 2 minutes finishes |
git clone git@github.com:anthonyhungnguyen/claude-code-mods.git ~/.claude/mods
List each mod folder you want in ~/.claude/settings.json, separated by ::
{ "env": { "CLAUDE_CODE_PLUGIN_DIRS": "~/.claude/mods/cost-pane:~/.claude/mods/context-guard:~/.claude/mods/cache-watch:~/.claude/mods/turn-timer" } }
New sessions load them. Check a mod with claude plugin validate ~/.claude/mods/<mod>.
hooks/register.ts 18 lines1import type { Register } from 'claude-code'
2
3const LONG_MS = 120_000
4
5export const register: Register = on => {
6 on('turn.complete', async ($, e, next) => {
7 const done = await next(e)
8 if (e.agentId || e.isAborted || e.durationMs < LONG_MS) return done
9
10 const min = Math.round(e.durationMs / 60_000)
11 const ok = e.reason === 'answer'
12 $.ui.toast(`${ok ? 'Done' : 'Stopped'} after ${min} min`)
13 // no synthesizer (Linux, a remote surface): the toast still shows
14 void $.audio.speak(ok ? `Claude finished, ${min} minutes` : 'Claude stopped with an error').catch(() => {})
15 return done
16 })
17}
18