SLOPSHOPPER

tool-timing-badge

Times every tool call and wraps the engine's ToolUse rendering with a colored duration badge from the surface's own element table, on the terminal, Desktop and…

newrowsguard
A shopper browsing a rack in a slop shop
README

tool-timing-badge

Measures how long every tool call takes and draws a colored duration badge beside the engine's own ToolUse rendering, on the terminal, Desktop and mobile alike. Two hooks: tool.call (timing, "after": it awaits next) and ui.render on the ToolUse component (drawing: wraps what next drew).

Elements come from the surface's table ($.ui.resolve(e)), never from globals; JSX compiles against the environment's h. A ToolUse is drawn once per props change, so the badge appears on the redraw that follows the call settling (isRunning flips to false).

Options

  slowMs: number  calls at or above this are red, above a quarter of it yellow (default 5000)

Declared in .claude-plugin/plugin.json (userConfig). Set them in /config, in user settings (~/.claude/settings.json, not project settings), with --settings <file> or in managed settings:

{ "pluginConfigs": { "tool-timing-badge@skills-dir": { "options": { } } } }

Install

npx claude-code-templates@latest --mod ui/tool-timing-badge
claude

It is written to .claude/skills/tool-timing-badge/, which Claude Code auto-loads as tool-timing-badge@skills-dir. For one session with hot reload: claude --plugin-dir .claude/skills/tool-timing-badge. claude plugin validate .claude/skills/tool-timing-badge prints every event it hooks and every $ call it makes.

Requirements. Mods are on by default in Claude Code 2.1.287+. Typed against Anthropic's declarations: https://github.com/anthropics/claude-code/tree/main/mods

Source 1 files
hooks/tool-timing-badge.tsx 65 lines
1/**
2 * tool-timing-badge — Claude Mod
3 *
4 * Measures how long every tool call takes and draws a colored duration badge
5 * beside the engine's own ToolUse rendering, on the terminal, Desktop and
6 * mobile alike. Two hooks: `tool.call` (timing, "after": it awaits `next`) and
7 * `ui.render` on the ToolUse component (drawing: wraps what `next` drew).
8 *
9 * Elements come from the surface's table (`$.ui.resolve(e)`), never from
10 * globals; JSX compiles against the environment's `h`. A ToolUse is drawn
11 * once per props change, so the badge appears on the redraw that follows the
12 * call settling (`isRunning` flips to false).
13 *
14 * Needs Claude Code >= 2.1.287. Typed
15 * against Anthropic's declarations: https://github.com/anthropics/claude-code/tree/main/mods
16 *
17 * Options:
18 *   slowMs: number  calls at or above this are red, above a quarter of it yellow (default 5000)
19 */
20import type { Register } from 'claude-code'
21
22const durations = new Map<string, number>()
23
24function badgeColor(ms: number, slowMs: number): string {
25  if (ms >= slowMs) return 'red'
26  if (ms >= slowMs / 4) return 'yellow'
27  return 'green'
28}
29
30function format(ms: number): string {
31  return ms < 1000 ? `${ms} ms` : `${(ms / 1000).toFixed(1)} s`
32}
33
34export const register: Register = (on, options) => {
35  const slowMs = typeof options.slowMs === 'number' ? options.slowMs : 5000
36
37  // 1. Time every tool call, the hooks beneath and the tool itself included.
38  on('tool.call', async ($, e, next) => {
39    const startedAt = Date.now()
40    try {
41      return await next(e)
42    } finally {
43      const ms = Date.now() - startedAt
44      if (e.tool_use_id) durations.set(e.tool_use_id, ms)
45      if (ms >= slowMs) $.ui.log(`[tool-timing-badge] slow ${e.tool}: ${format(ms)}`)
46    }
47  })
48
49  // 2. Wrap the engine's ToolUse drawing with a badge.
50  on('ui.render', { component: 'ToolUse' }, async ($, e, next) => {
51    const { Box, Text } = $.ui.resolve(e)
52    const drawn = await next(e)
53
54    const ms = durations.get(e.props.tool_use_id)
55    if (e.props.isRunning || ms === undefined) return drawn
56
57    return (
58      <Box flexDirection="row" gap={1}>
59        {drawn}
60        <Text color={badgeColor(ms, slowMs)}>{format(ms)}</Text>
61      </Box>
62    )
63  })
64}
65