Shows how long each reply took, above the SkillOS line

The mods I run in Claude Code, each in its own folder. Clone this repo and run one script to get the same setup on any machine.
Mods are small plugins that add live panes, status bands and slash commands to Claude Code. They hot-reload when you edit them.
git clone https://github.com/wpvip-awesomeface/claude-mods.git ~/.claude/mods
~/.claude/mods/install.sh
Then start a new Claude Code session. Add --all to also turn on the personal mods in fun/ (that's the full backup restore).
The script:
CLAUDE_CODE_PLUGIN_DIRS in ~/.claude/settings.json)CLAUDE_CODE_PLUGIN_DIR_WATCH=1)mods command into ~/.local/binmods # list mods and whether they're on
mods on bento # load one in every new session
mods off workbench # stop loading it
mods on all # the core mods
mods on fun # the personal mods in fun/
| Mod | What you see | Saves tokens? |
|---|---|---|
| bento | A band above the prompt showing context use, turns and subagents | Yes, directly |
| turn-timer | "Speed Radar": how long each reply took | Indirectly |
| recipe-card | A card under each answer showing the route line, with /recipe to save a solve | Yes, over time |
workbench (in fun/) | A side pane with your open PRs, next meeting, pizza and a dino | No, it's for focus and fun |
What: a lunchbox band above the prompt. The box fills as context grows and shows context %, tokens, turn count and how many subagents ("helpers") the session has started.
Why: cost is roughly turns × context size, and both are invisible by default. Bento makes them visible, and warns you once at 70% context or 60 turns that it's time to start fresh.
Commands:
/doggybag or /handoff: writes a 5–10 line handoff (goal, state, links, next step) and copies it to your clipboard. It runs as one tool-less fork over the cached transcript, so it's cheap. Paste it into a new session./hush: hides every mod at once, for screenshares and recordings./bento: shows or hides the band.What: shows how long each reply took (Speed Radar: ⏱ 2m 15s), drawn above the route line.
Why: slow replies are usually long, tool-heavy turns. Seeing the time makes it easy to spot when a session is dragging and should be split up. It stays visible during /hush, since it's safe to show in demos.
What: reads the route line at the end of each answer (◎ shape · † graveyard · ◫ library · ≡ rung · ✦ extract) and draws it as a card. When an answer was a fresh solve worth keeping, it offers /recipe to save it as a reusable tool.
Why: the cheapest task is the one you've already solved. Saving solves to a library, and dead ends to a graveyard, keeps future sessions from paying to re-solve them.
Needs: a skills library that uses the route line. Without one, the card has nothing to read.
Heads up: this is a personal mod, built around the author's own workflow (their demo sites, calendar and sense of humor). Most people won't need or want it. It's here so the author's full setup is backed up, and as an example if you want to build your own.
It lives in fun/, and the installer leaves it off unless you pass --all or run mods on workbench.
What: a side pane with:
/prep to get ready for it/fun, off by default): a quote of the day, pizza and mochi the dinoWhy: keeps PR status and your calendar in view, so you don't spend turns asking Claude to check them.
Needs: the gh CLI signed in, and a Google Calendar connector for birdy. The demo links and the company email domain are set at the top of fun/workbench/hooks/register.tsx, so change those for your own setup. Edit the quote list with /quotes.
Commands: /workbench, /greg-check, /prep, /quotes, /fun.
Not a mod, but it lives here too: token-kit/ has the CLAUDE.md rules and a read guard hook that blocks whole-file reads over 400 lines once and returns an outline. To have an agent set all of it up, point it at AGENT_SETUP.md.
<mod>/hooks/register.tsx.*/.claude-plugin/types/ is generated by Claude Code on each machine and is not committed. Your editor may show type errors until a session has loaded the mods once.hooks/register.tsx 63 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Timing } from '../types'
5
6// Dracula cyan for the label, bento grey for the value.
7const RADAR = '#8BE9FD'
8const DATA = '#A3A3A3'
9const KEEP = 50
10
11const times = atom({ plugin: 'turn-timer', key: 'times' } as const, [] as Timing[])
12
13// The route line recipe-card draws; it draws the time above it itself.
14const ROUTE = /^◎\s*\S+\s*·\s*†/u
15
16// Matches a reply to its turn by the end of its text.
17export const tailOf = (text: string) => text.replace(/\s+/g, ' ').trim().slice(-160)
18
19// 42s, 2m 15s, 1h 4m 2s. No milliseconds.
20export const format = (ms: number) => {
21 const total = Math.max(1, Math.round(ms / 1000))
22 const h = Math.floor(total / 3600)
23 const m = Math.floor((total % 3600) / 60)
24 const s = total % 60
25 return [h && `${h}h`, (h || m) && `${m}m`, `${s}s`].filter(Boolean).join(' ')
26}
27
28// Deliberately ignores /hush (bento): the speed row is fine to show in demos.
29export const register: Register = on => {
30 on('turn.complete', async ($, e, next) => {
31 const result = await next(e)
32 if (e.agentId || !e.answer) return result
33
34 const entry = { tail: tailOf(e.answer), ms: e.durationMs }
35 await update($, times, list => [...(list ?? []), entry].slice(-KEEP))
36
37 return result
38 })
39
40 // Replies without a route line get the time row here.
41 on('ui.render', { component: 'AssistantMessage' }, async ($, e, next) => {
42 const text = e.props.text
43 const last = text.trimEnd().split('\n').pop()?.trim() ?? ''
44 if (ROUTE.test(last)) return next(e)
45
46 const tail = tailOf(text)
47 const hit = [...(await read($, times))].reverse().find(t => t.tail === tail)
48 if (!hit) return next(e)
49
50 const drawn = await next(e)
51 const { Box, Text } = $.ui.resolve(e)
52
53 return (
54 <Box flexDirection="column">
55 {drawn}
56 <Box marginTop={1} paddingX={1}>
57 <Text><Text bold color={RADAR}>Speed Radar: </Text><Text color={DATA}>⏱ {format(hit.ms)}</Text></Text>
58 </Box>
59 </Box>
60 )
61 })
62}
63types/index.d.ts 8 lines1export type Timing = { tail: string; ms: number }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'turn-timer': { times: Timing[] }
6 }
7}
8