brew tap noctaIO/tap
brew install obscene-spinner
spinThat puts spin (and obscene-spinner) on your PATH. Newer Homebrew gates third-party taps, so if it asks, run brew trust noctaio/tap first.
Or skip Homebrew — it's still one Python 3 file, no pip, no build, no config:
git clone https://github.com/noctaIO/obscene-spinner
cd obscene-spinner
./spin.pyRun it bare and you get the gallery: every pack on the left, each one animating live in its own glyphs and its own colour, a detail pane on the right showing exactly what Claude Code will draw. Arrow keys to browse, / to search, Enter to make it your real spinner. q to leave without touching anything.
Claude Code lets you swap out the verbs it shows while it's working on your prompt. The stock set is whimsical: "Noodling", "Lollygagging", "Shenaniganing". Pleasant. A little pleased with itself.
This is the other set. The one a senior dev mutters at 2am when the build is on fire: faffing, unfucking, yak-shaving, yeeting-to-prod, polishing-a-turd, crashing-out. Then twenty more packs besides — pirate, eldritch, corporate, Shakespearean, zen — and a dozen colour themes that recolour Claude Code's real spinner, plus thirty-six animations for the previews here. (Which is which.)
There's a catch with the real spinner: it only swaps a verb when a new operation starts, so a whole session shows you maybe five of the eighty and none of the good ones. The previews here run them on a timer instead, fast enough that the full pack actually goes past.
spin # the gallery (this is the one you want)
spin --list # every pack, animation and theme, as text
spin --list --json # the same, machine-readable
spin --pack eldritch # preview a pack in the terminal
spin --pack pirate --spinner moon --theme void # mix and match
spin --random # surprise me
spin --wall # every animation running at once
spin --set british # apply a pack, no menu
spin --set zen --theme ice # …and recolour Claude Code's real spinner
spin --mix pirate wizard zen # blend several packs into one spinner
spin --status # what's active right now
spin --restore # put back the spinner and colour you had before
spin --no-theme # apply verbs only, leave the colour alone
spin --theme-base light # the preset the written theme starts from
spin --print-theme void # the theme JSON, without installing it
spin --sfw # hide the rude packs (gallery, --list, --random)
spin --search dns # filter packs by name, description or verb
spin --once # one verb and quit, handy for status lines--set and --restore are the no-menu paths: wire them to a shell alias or a Claude Code /spinner command and flip packs in a keystroke. Everything that writes to your settings is reversible — see Applying and undoing.
| key | does |
|---|---|
↑ ↓ / j k |
move between packs |
enter |
apply this pack to Claude Code |
/ |
search packs and their verbs — enter accepts, esc clears |
tab |
cycle the category filter |
s |
toggle the safe-for-work filter |
f / F |
cycle the animation forwards / back |
t / T |
cycle the colour theme forwards / back |
w |
wall mode — every animation at once |
r |
randomise pack, animation and theme |
u |
restore the spinner and colour you had before |
? |
key help |
q |
quit, changing nothing |
The right-hand pane draws a box exactly 56 columns wide — the width Claude Code chops the verb line at — so you can see at a glance which verbs are living dangerously. It draws them in braille and in the pack's real colour, because that's what Claude Code will actually render; the animation you pick with f drives the list rows and the standalone previews.
| pack | verbs | rating | what it is |
|---|---|---|---|
profanity |
80 | nsfw | the original 2am verb pack |
british |
50 | nsfw | everything has gone pear-shaped, mildly |
scottish |
40 | nsfw | pure bealing, so it is |
cope |
42 | mild | the five stages, compressed into one build |
prod |
40 | mild | forty things you should never do to a live system |
sysadmin |
40 | mild | it was DNS. it is always DNS. |
honest |
49 | sfw | what the work actually is, most days |
corporate |
44 | sfw | circling back to socialise the deck |
eldritch |
44 | sfw | something in the dependency tree is awake |
pirate |
40 | sfw | the rum is gone and so is the build |
medieval |
40 | sfw | the siege is going badly |
hacker |
40 | sfw | jacked in, ice broken, logs wiped |
noir |
39 | mild | it was raining. it's always raining. |
existential |
39 | sfw | one must imagine the compiler happy |
chef |
40 | sfw | yes chef. behind. corner. hot pan. |
wizard |
39 | sfw | a wizard is never late |
commentator |
38 | sfw | absolute scenes in the terminal |
shakespeare |
39 | mild | thou art erroring, and most foully |
genz |
39 | mild | let him cook |
gymbro |
39 | mild | just one more set |
zen |
35 | sfw | the build is neither late nor early |
chaos |
live | nsfw | a shuffled sample of every pack at once |
news |
live | sfw | the latest wire headlines, refreshed in the background |
| animation | frames | animation | frames |
|---|---|---|---|
braille |
⠋⠙⠹⠸⠼⠴⠦⠧ |
braille2 |
⣾⣽⣻⢿⡿⣟⣯⣷ |
braille3 |
⠁⠂⠄⡀⢀⠠⠐⠈ |
dots |
⢹⢺⢼⣸⣇⡧⡗⡏ |
bounce |
⠁⠂⠄⠂ |
line |
` |
layer |
-=≡= |
pipe |
┤┘┴└├┌┬┐ |
arc |
◜◠◝◞◡◟ |
circle |
◐◓◑◒ |
quadrant |
◴◷◶◵ |
square |
◰◱◲◳ |
half |
◧◨◩◪ |
triangle |
◢◣◤◥ |
corner |
▖▘▝▗ |
toggle |
⊶⊷ |
switch |
▮▯ |
blocks |
▁▂▃▄▅▆▇█ |
grow |
▏▎▍▌▋▊▉█ |
noise |
░▒▓█▓▒ |
pulse |
·•●• |
star |
✶✸✹✺✹✷ |
arrows |
←↖↑↗→↘↓↙ |
arrows2 |
⇐⇖⇑⇗⇒⇘⇓⇙ |
orbit |
◜◝◞◟ |
flip |
_-`'´-_ |
dqpb |
dqpb |
binary |
0010 1010 1101 0110 |
bar |
▰▱▱▱▱ ▰▰▱▱▱ ▰▰▰▱▱ ▰▰▰▰▱ |
point |
∙∙∙ ●∙∙ ∙●∙ ∙∙● |
balloon |
.oO@* |
moon |
🌑🌒🌓🌔🌕🌖🌗🌘 |
earth |
🌍🌎🌏 |
clock |
🕐🕑🕒🕓🕔🕕🕖🕗 |
hearts |
💛💙💜💚🧡 |
static |
● |
| theme | style | spinner colour | theme | style | spinner colour |
|---|---|---|---|---|---|
acid |
solid | #b5ff00 |
ember |
gradient | #ff6b1a |
void |
gradient | #9d4edd |
ice |
gradient | #5bc0eb |
matrix |
gradient | #00ff41 |
blood |
gradient | #c4141c |
gold |
gradient | #ffc107 |
vapor |
gradient | #ff71ce |
deep |
gradient | #0091ad |
bone |
solid | #d8d2c4 |
mono |
solid | #e6edf3 |
rainbow |
rainbow | #ff0000 |
Ratings: sfw is safe to have on screen in an open-plan office, mild is cheerfully crude, nsfw is the reason the project is called this. spin --sfw filters to the first group, and so does s in the gallery.
Two packs are live rather than fixed: news pulls real headlines from a JSON feed, and chaos deals a shuffled hand from every other pack each time you apply it.
Worth being precise, because a pack has three parts and only two of them leave this program:
| reaches Claude Code | how | |
|---|---|---|
| verbs | yes | spinnerVerbs in ~/.claude/settings.json |
| colour | yes | a custom theme in ~/.claude/themes/obscene-spinner.json |
| animation | no | Claude Code draws its own glyph; there's no setting for it |
So --spinner moon gives you moon phases in the gallery, in --pack previews and in --wall — but Claude Code's own spinner stays braille. The gallery's preview box shows braille for exactly that reason: it's a preview, so it has to tell the truth. The colour beside it, though, is genuinely what you'll get.
Picking a pack writes spinnerVerbs into ~/.claude/settings.json with mode "replace", atomically, leaving every other setting exactly as it was:
{
"spinnerVerbs": { "mode": "replace", "verbs": ["faffing", "unfucking", "..."] }
}Because "replace" throws out the polite defaults, the first time you apply anything, whatever spinnerVerbs you had is copied to ~/.claude/spinner-backup.json — and only the first time, so a hundred more applies can't bury the original. spin --restore puts it back, byte for byte, and deletes the key entirely if you didn't have one to begin with.
spin --set eldritch # off you go
spin --restore # ✓ restored your previous spinnerVerbs.Fair warning either way: this is not the version you want on screen during a demo.
Claude Code's spinner colour comes from the claude theme token, so picking a pack also writes a small theme file:
{
"name": "obscene-spinner (Void)",
"base": "dark",
"overrides": {
"claude": "#9d4edd",
"claudeShimmer": "#c495eb"
}
}Only those two tokens are ever written. overrides is additive — anything not listed falls through to the base preset — so the rest of your interface is untouched. (claudeShimmer is the lighter half of the spinner's gradient; each theme's is derived so it's always genuinely lighter than claude, including the themes that shade bright→dark.)
Turn it on once: run /theme in Claude Code and pick obscene-spinner. After that you never touch it again — every spin --set rewrites the same file, and Claude Code watches that directory, so the colour changes live in a running session.
spin --set eldritch --theme void # writes the file, tells you to run /theme
spin --set zen --theme matrix # colour changes immediately, no /theme needed
spin --status # shows the active colour and where it came fromTwo caveats worth knowing:
basesets your whole UI, not just the spinner. It defaults todark; pass--theme-base light(ordark-daltonized,light-ansi, …) if that's wrong for you.spinprints which base it wrote.- If
~/.claude/themes/didn't exist when Claude Code started, restart it once after the first apply. After that, changes apply without a restart.
Don't want it? --no-theme applies the verbs alone. Want the JSON without installing anything? --print-theme void. And --restore removes the file — or hands back a theme you'd hand-written at that filename, byte for byte.
Drop a JSON file in ~/.claude/spinner-packs/ and it shows up in the gallery beside the built-ins:
{
"id": "mine",
"name": "My Pack",
"desc": "what I'm actually doing",
"category": "custom",
"rating": "sfw",
"spinner": "pulse",
"theme": "ice",
"verbs": ["fiddling", "tinkering", "poking"]
}Only id and verbs are required. Give it the id of a built-in and yours wins — that's how you override one.
spin --export pirate -o pirate.json # start from a pack you like
spin --import mine.json # install one (validates first)Bad files never stop the tool: a malformed pack is reported and skipped, and a single over-long verb or unknown theme name costs you that verb or falls back to a default rather than binning the whole pack.
Same spinner, different pack: live headlines instead of verbs. Out of the box it reads a markets wire — the top Reuters market stories, refreshed in the background — one headline at a time.
spin --pack news # ~5s per headline, refreshes as it runs
spin --pack news --interval 1.2 # faster churn
spin --pack news --news-url URL # or set SPIN_NEWS_URL
spin --set news # make the wire your Claude Code spinnerRead the story: while the ticker runs, press n to pause and read the current headline's summary, then any key to resume (q quits). That only works in this standalone ticker, not in Claude Code's own spinner, which Claude Code draws itself.
Point --news-url (or SPIN_NEWS_URL) at any small JSON endpoint shaped like {"items": ["headline", ...]} — objects with title and summary work too. No network, no feed? It says so and exits instead of hanging.
Headlines get two different treatments, deliberately. In the ticker, each is measured by real screen width — Chinese counts as two columns, accents as zero — then filled right to the edge and capped with …, recomputed every frame so it re-fits the instant you resize. But a headline applied to Claude Code can't end in a dangling fragment, so it's shortened instead: source tag stripped, stock words abbreviated, articles dropped, then trimmed to the leading clause. The result still reads as a headline.
Roughly four moods, because a bad session moves through all of them:
- British disasters — faffing, bollocksing, fannying-about, going-tits-up, cocking-it-up
- Cope — malding, seething, huffing-copium, dissociating, crashing-out
- Honest engineering — yak-shaving, bikeshedding, monkeypatching, footgunning, kludging
- Do not do this in prod — yeeting-to-prod, force-pushing-to-main, rm-rf-ing, shipping-and-praying
A single frame, if the animations above won't play in your viewer:
It degrades rather than breaks. NO_COLOR, TERM=dumb, a pipe, or --no-color all produce plain text with no escape codes at all. A 16-colour terminal gets the nearest ANSI colour; a 256-colour one gets the cube; anything advertising COLORTERM=truecolor gets the real thing. Colour pairs are allocated on demand and capped at what the terminal actually offers, so a small TERM can't crash the gallery. The layout reflows from 200 columns down to about 30, dropping the detail pane and then the key hints as room runs out, and spin --verbs | head exits like any other Unix filter.
python3 -m unittest discover -s tests -v # 140 tests, stdlib only
./spin.py --selftest # the same suite, front door
python3 make_demo.py # regenerate the SVGs
python3 tools/check_docs.py --write # regenerate the tables aboveThe tables in this README, the SVGs, and the --list output all come from the same registries in spin.py, and CI fails if any of them drift. The gallery's layout is drawn against a tiny screen interface with two backends — curses, and a plain character grid — so the whole TUI is rendered and asserted in tests without needing a terminal.
Adding a pack is one entry in PACKS. Adding an animation is one entry in SPINNERS; the tests will tell you off if its frames aren't all the same display width, because that makes the verb beside it jitter.
The stock verbs are fine. But sometimes the honest status really is bodging, and the terminal may as well say so.