SLOPSHOPPER

spinner-pack

Themed spinner packs: moon, braille or coffee verbs and glyphs that follow each phase of a turn.

newspinner
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · spinner-pack
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ✻ Waxing ◓… ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

spinner-pack

Themed spinner packs: moon, braille or coffee verbs and glyphs that follow each phase of a turn.

What it draws

The pack swaps the spinner's word for a themed verb with a themed glyph beside it. Both step as the turn moves from requesting to thinking, tool use and responding. With the moon pack, one turn in the terminal reads:

✻ Orbiting ◐…
✻ Waxing ◓… (4s · ↓ 310 tokens)
✻ Eclipsing ◒… (12s · ↓ 1.2k tokens)
✻ Waning ◐… (19s · ↓ 2.4k tokens)

The glyph in front, the ellipsis and everything in brackets are Claude Code's own. The mod only rewrites the word.

Phasemoonbraillecoffee
requestingOrbiting ◐Computing ⠋Grinding ∴
thinkingWaxing ◓Computing ⠹Steeping ∵
tool inputEclipsing ◑Threading ⠼Brewing ∷
tool useEclipsing ◒Threading ⠦Brewing ⁂
respondingWaning ◐Weaving ⠇Pouring ∴

In the desktop app, a row that names its step (Creating notes.md ◒…) keeps those words and gains the glyph. Only the generic Working takes the pack's verb.

Options

OptionTypeDefaultWhat it does
packstringmoonThe pack: moon, braille, coffee, or classic to leave the spinner untouched.

Set it with /config, or in settings under pluginConfigs["spinner-pack@mod-showroom"].options.

Install

  • Terminal: /plugin install spinner-pack --marketplace KROU4/claude-code-mod-showroom
  • Shell: claude plugin install spinner-pack --marketplace KROU4/claude-code-mod-showroom
  • Desktop app: + next to the prompt box → Plugins → Add marketplace KROU4/claude-code-mod-showroom → Add plugin

Limits

  • The animated glyph in front of the verb stays Claude Code's. Replacing it would mean drawing the whole line, elapsed time and tokens included, in place of Claude Code's.
  • The pack's glyph steps with the turn's phase, not on every animation frame.
  • While Claude Code shows a state message of its own (compacting, for one), or a phase the pack does not know, the spinner is left as it is.

License

MIT. See LICENSE.

Source 1 files
hooks/register.ts 55 lines
1import type { Register, RenderPropsOf } from 'claude-code'
2
3type Phase = RenderPropsOf['Spinner']['mode']
4type Pack = Record<Phase, readonly [verb: string, glyph: string]>
5
6// The Spinner's props carry no glyph: the engine animates its own in front of
7// the word. A pack's glyph therefore joins the verb in `word`, ahead of the
8// engine's ellipsis, and steps with it each time the turn changes phase.
9const PACKS: Record<string, Pack | undefined> = {
10  moon: {
11    requesting: ['Orbiting', '◐'],
12    thinking: ['Waxing', '◓'],
13    'tool-input': ['Eclipsing', '◑'],
14    'tool-use': ['Eclipsing', '◒'],
15    responding: ['Waning', '◐'],
16  },
17  braille: {
18    requesting: ['Computing', '⠋'],
19    thinking: ['Computing', '⠹'],
20    'tool-input': ['Threading', '⠼'],
21    'tool-use': ['Threading', '⠦'],
22    responding: ['Weaving', '⠇'],
23  },
24  coffee: {
25    requesting: ['Grinding', '∴'],
26    thinking: ['Steeping', '∵'],
27    'tool-input': ['Brewing', '∷'],
28    'tool-use': ['Brewing', '⁂'],
29    responding: ['Pouring', '∴'],
30  },
31}
32
33export const register: Register = (on, options) => {
34  const pack = PACKS[String(options.pack)]
35  if (pack === undefined) {
36    return // `classic`: the engine's spinner as it is
37  }
38
39  on('ui.render', { component: 'Spinner' }, ($, e, next) => {
40    // A later build may raise a phase this pack does not know yet.
41    const entry: Pack[Phase] | undefined = pack[e.props.mode]
42    if (entry === undefined || e.props.message !== null) {
43      return next(e)
44    }
45
46    const [verb, glyph] = entry
47    // On the desktop the word names the step being taken (`Creating
48    // notes.md`), worth keeping; only its stand-in `Working` gives way.
49    const isStep = e.surface === 'desktop' && e.props.word !== 'Working'
50    const word = isStep ? e.props.word : verb
51
52    return next({ ...e, props: { ...e.props, word: `${word} ${glyph}` } })
53  })
54}
55