A toast when a shell command that ran over 45 seconds finishes

hooks/register.ts 19 lines1import type { Register } from 'claude-code'
2
3import { label } from './label'
4
5const LONG_MS = 45_000
6
7export const register: Register = on => {
8 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
9 const started = await $.clock.now()
10 const ran = await next(e)
11 const ms = (await $.clock.now()) - started
12 if (ms >= LONG_MS && ran.deny === undefined) {
13 const mark = ran.isError ? '✗' : '✓'
14 $.ui.toast(`${mark} ${label(e.command)} — ${Math.round(ms / 1000)}s`, { timeoutMs: 10_000 })
15 }
16 return ran
17 })
18}
19hooks/label.ts 7 lines1/** A short name for a command: the part after the last `cd … &&`, cut to 40 chars. */
2export function label(command: string): string {
3 const parts = command.split(/&&|;/).map(s => s.trim()).filter(s => s && !s.startsWith('cd '))
4 const last = (parts[parts.length - 1] ?? command).split('\n')[0]
5 return last.length > 40 ? `${last.slice(0, 39)}…` : last
6}
7