A band above the prompt with the last turn's duration and tool calls; toasts on slow turns

A small band above the prompt showing how long the last turn took and how many tools it called.
⏱ last turn 42s · 12 tools above the prompt. Durations over a minute read 1m05s.Slow turn: <duration>, N tool calls when a turn lasts more than 2 minutes.claude --plugin-dir /path/to/ModsTools/mods/turn-timer
claude plugin validate mods/turn-timer
claude plugin test mods/turn-timer # 6 testshooks/register.tsx 53 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Turn } from '../types'
5import { formatDuration } from './format'
6
7const SLOW_MS = 120_000
8const last = atom({ plugin: 'turn-timer', key: 'last' } as const, null)
9const isHidden = atom({ plugin: 'turn-timer', key: 'isHidden' } as const, false)
10
11export const register: Register = on => {
12 let startedAt: number | null = null
13 let tools = 0
14
15 on('prompt.submit', async ($, e, next) => {
16 startedAt = await $.clock.now()
17 tools = 0
18
19 return next(e)
20 })
21
22 on('tool.call', ($, e, next) => {
23 tools += 1
24
25 return next(e)
26 })
27
28 on('turn.complete', async ($, e, next) => {
29 if (e.agentId !== undefined) return next(e)
30 const ms = e.durationMs ?? (startedAt === null ? 0 : (await $.clock.now()) - startedAt)
31 const turn: Turn = { ms, tools }
32 await update($, last, () => turn)
33 await update($, isHidden, () => false)
34 if (ms > SLOW_MS) $.ui.toast(`Slow turn: ${formatDuration(ms)}, ${tools} tool calls`)
35
36 return next(e)
37 })
38
39 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
40 const turn = await read($, last)
41 if (e.props.hasSurvey || turn === null || (await read($, isHidden))) return next(e)
42 const { Box, Button, Text } = $.ui.resolve(e)
43 const label = `⏱ last turn ${formatDuration(turn.ms)} · ${turn.tools} tool${turn.tools === 1 ? '' : 's'}`
44
45 return (
46 <Box>
47 <Text dimColor>{label}</Text>
48 <Button key="hide" label="Hide" onPress={() => update($, isHidden, () => true)} />
49 </Box>
50 )
51 })
52}
53hooks/format.ts 7 lines1export function formatDuration(ms: number): string {
2 const total = Math.round(ms / 1000)
3 if (total < 60) return `${total}s`
4
5 return `${Math.floor(total / 60)}m${String(total % 60).padStart(2, '0')}s`
6}
7types/index.d.ts 8 lines1export type Turn = { ms: number; tools: number }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'turn-timer': { last: Turn | null; isHidden: boolean }
6 }
7}
8