Claude Code skill that builds, debugs, audits and packages Claude Code mods from a plain-language description

Adds a live count of tool calls next to the word Claude shows while it is thinking.
Claude Code is Anthropic's assistant for working with code, in the terminal or in the Claude desktop app. A mod is a small add-on for it: it can add a panel, a bar above the box where you type, a new /command, or a rule that checks what Claude is about to do. This folder is one complete mod you can install and use as it is, or copy as a starting point.
While Claude works, Claude Code shows an animated word such as "Thinking". It does not tell you how much is going on behind it. This mod adds the number of tools Claude has used in the current answer, so you can tell a quick reply from a long job at a glance.
None of its own.
None. It works on its own.
Claude Code asks for these when you enable the mod. To change them later, open /config and find the mod by name.
| Setting | What it means | Default |
|---|---|---|
language (Language) | Language of the text the mod shows: en (English) or pt-BR (Brazilian Portuguese). | en |
You need Claude Code 2.1.287 or newer in the terminal, or 2.1.286 or newer in the desktop app.
_claude-plugin to .claude-plugin (with a dot at the start). It ships without the dot so the examples never load by accident.claude --plugin-dir path/to/spinner-counter
To keep it installed for good, ask the claude-mod-forge skill to package it ("package this mod").
From the skill folder (skills/claude-mod-forge), this runs the manifest check, a strict type check and the 2 automated tests of this mod:
node scripts/check-mod.mjs examples/spinner-counter
The tests cover the mod's logic and what it draws in the terminal and the desktop app. How it looks in your own window is worth a quick look in a real session.
For developers: the examples table lists the hooks and API calls this mod demonstrates.
hooks/register.ts 31 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import { strings } from './i18n'
5
6// Rewrites a native render site instead of replacing it: next() with changed props keeps
7// Claude Code's spinner (animation, word, mode) and only adds text after the word.
8const calls = atom({ plugin: 'spinner-counter', key: 'calls' } as const, 0)
9
10export const register: Register = (on, options) => {
11 const t = strings(options.language)
12
13 on('turn.start', async ($, e, next) => {
14 await update($, calls, () => 0)
15 return next(e)
16 })
17
18 on('tool.call', async ($, e, next) => {
19 if (e.agentId === undefined) await update($, calls, n => n + 1) // main conversation only
20 return next(e)
21 })
22
23 // Spinner is raised on the terminal and the desktop. Other surfaces simply never call this.
24 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
25 const n = await read($, calls)
26 if (n === 0) return next(e) // nothing to add: leave the native spinner untouched
27 const suffix = `${e.props.suffix} · ${t.toolCalls(n)}`
28 return next({ ...e, props: { ...e.props, suffix } })
29 })
30}
31hooks/i18n.ts 20 lines1// The text this mod shows, per language. Pure: no $ calls, so tests can cover it directly.
2// Anything other than a known language falls back to English.
3
4export type Language = 'en' | 'pt-BR'
5
6const en = {
7 toolCalls: (n: number) => `${n} tool call${n === 1 ? '' : 's'}`,
8}
9
10const ptBR: typeof en = {
11 toolCalls: (n: number) => `${n} ferramenta${n === 1 ? '' : 's'}`,
12}
13
14export type Strings = typeof en
15const TABLE: Record<Language, Strings> = { en, 'pt-BR': ptBR }
16
17export function strings(language: unknown): Strings {
18 return language === 'pt-BR' ? TABLE['pt-BR'] : TABLE.en
19}
20types/index.d.ts 8 lines1export type SpinnerCalls = number
2
3declare module 'claude-code' {
4 interface PluginState {
5 'spinner-counter': { calls: SpinnerCalls }
6 }
7}
8