KilimcininKorOglu/claude-code-mods/tree/main/plugins/idle-art
idle-art
一个 Claude Code 插件,在模型工作时于提示上方绘制 ASCII 动画(矩阵雨、火焰、水族箱、猫或导入的 GIF);仅显示,不产生费用。
关于这个 mod
idle-art 会在长时间运行期间用 ASCII 动画填充提示上方的空间。它提供四个内置场景(矩阵雨、火焰、水族箱、猫),还可通过 /idle-art import 将用户导入的 GIF 转换为字符片段。它使用 AbovePrompt ui.render 钩子,以约 60fps 在绘制线程上运行,并且仅用于显示:不会有任何内容传给模型,不消耗 token,也不会触碰提示缓存。设置和片段会持久保存于 mod store 中,并在 2 秒内跨窗口同步。通过市场仓库安装,需要 Claude Code 2.1.288+ 才能使用 function hooks。
安装
请先查看作者 README 确认 marketplace 和插件名称;命令可能随仓库结构改变。
claude plugin marketplace add KilimcininKorOglu/claude-code-mods claude plugin install idle-art
原文 / README
idle-art
While you wait out a long turn, all that moves above the prompt is a spinner. This mod fills that space with an ASCII animation while the model works. Four scenes are built in: matrix rain, fire, an aquarium and a cat. You can add your own: /idle-art import turns a GIF into a character clip and keeps it for every project. Display only: nothing reaches the model, so the mod costs no tokens and does not touch the prompt cache.
What it shows
The picture appears 3 seconds into a turn, so a short turn shows nothing, and it goes away when the turn ends. By default each turn draws a scene at random from the built-in scenes and your saved clips, never the one shown before. A long turn moves on to another at random: a built-in scene after 20 seconds, the cat and a clip at the end of their first round or loop that ends after 20 seconds, so a long clip plays through once and a short one repeats until then. A scene you chose by name stays for the whole turn, and choosing one while a turn draws changes the picture at once.
● Brewing… (5s)
.: . ,
, ::,,,
::;;:; ;;::; .
, ;:+ :*;++++ , ,,
:::*oO;*:;*:+: :+++,::**o ++:, :
;,, ;,;+;+::+;,;+:**+**,; ; ,
: +;*:+**o*;;;;*+**:,*:;;***o:,::;;
:,::;oOoO*ooOOO#oO*o*O*o**OOOoOo;+;,
| Scene | What moves |
|---|---|
| matrix | Streams of half-width katakana and digits fall in green, a bright head over a fading trail; glyphs under a trail flicker. |
| fire | Heat rises from a hidden row under the band and cools on the way up, drawn from . to @ and from dark red to pale yellow. |
| aquarium | A school of fish of four shapes crosses both ways, bubbles rise from them and from the sand and grow, tall seaweed sways on the sand, and the surface ripples on the top row. |
| cat | A garden: two kittens play back and forth on the grass, one on each side, a ball of yarn rolls at their feet, and a butterfly drifts over them. A big cat walks in slowly to the middle, head first, sits and blinks, and says meow; then its tricks in a new order each round: it rolls on the ground and back, jumps three times, purrs with a heart floating up, and looks left and right. It says MEOW!, walks out slowly to the right and comes round again. Each word stands in a speech bubble over its head. A round takes about 38 seconds, and random waits for the cat to walk out before it moves on. |
● Brewing… (8s)
.------.
( meow )
'------'
/
/\_/\
( o.o )
> ^ <
(_|_)~
The band takes at most 8 rows, fewer when the terminal has less room, and the terminal's whole width; a clip stands in its middle. It draws nothing in a band under 3 rows. It draws on the terminal only, and gives way to a survey. The scenes draw about 60 frames a second, and each moves by the time that passed, so the rain, the fish and the cat glide a step on every frame. The fire's heat changes ten times a second, and a clip changes at its own frame delays.
Your own GIFs
/idle-art import ~/Downloads/kitty.gif kitty
The mod reads the GIF, decodes every frame with its own delay and transparency, and turns each frame into characters: every cell takes the mean colour of its pixels, rounded to six levels a channel so neighbouring cells share a colour, and a glyph from .:-=+*#%@ by its brightness, spread over the clip's own darkest to brightest cell. A cell mostly transparent stays blank. The picture keeps its shape and fills the 8 rows of the band, at most 100 columns wide; a cell counts as twice as tall as wide. The clip then plays in the middle of the band, each frame for its own delay, in a loop.
● Brewing… (6s)
....=*******++=+***+====-....
....=******=----=*#=====-....
....-+++++*==+----===---:....
....:--=###++*=-:-=-:---:....
....-+#%#%*--==-:-:-==--:....
....+**#*++-:-:---::::--:....
....-====+-=:--:::::.:-+-....
....:-=*++*+++=-:-=--=-=-....
A clip is kept under 90,000 characters, because that is what one drawing may hand the drawing thread. A longer clip keeps every other frame, each kept frame showing for the time of both, and does so again until it fits; the answer says how many frames stayed. The clips live in the mod's store, which holds 4 MiB in all, so about forty clips fit; a clip that does not fit is refused with the reason.
A name is lowercase letters, digits and dashes, starts with a letter or digit, is up to 24 characters (a capital letter is lowered), and cannot be a built-in scene or a word the command reads. Importing under a saved name replaces that clip. A path starting with ~ is under your home directory, and a relative path is under the session's directory. A path may hold spaces: the last word is the name.
Command
/idle-art the state: on or off, the style, the delay
/idle-art on | off draw or stop drawing
/idle-art <scene or clip> always draw that one: matrix, fire, aquarium, cat, or a saved clip
/idle-art random a new scene or clip each turn and every 20 seconds or so (the default)
/idle-art delay <n> wait n seconds into a turn, 0 to 60 (default 3)
/idle-art import <gif path> <name> turn a GIF into a clip and keep it under that name
/idle-art list the built-in scenes and the saved clips
/idle-art remove <name> delete a saved clip; a style set to it goes back to random
/idle-art help
The settings and the clips stay in the mod's store, shared by every project and every window, and survive updates. Each window reads them again at each /idle-art command, at each turn's start, and every 2 seconds while a turn runs, so an on, an off, a style, a delay or a clip set in another window shows here within 2 seconds in a running turn, and at the next turn otherwise. Two windows that import at the same time keep both clips.
How it draws
An AbovePrompt ui.render hook mounts a Client element while isWorking is true. The Client runs hooks/scene.tsx on the drawing thread: a 16 ms surface.every tick advances the scene by 16 ms and asks for the next frame, so no hook runs per frame. Measured on 2.1.283, the frame clock keeps that rate: the cat, which walks 5 cells a second, moved 5 cells in each second of a live band. Each built-in scene is a pure module under hooks/art/ that answers a grid of cells; a saved clip reaches the drawing thread in the Client's props and plays from hooks/clip.ts. A row draws as one Text per run of one colour. The hooks module picks the scene and a random seed when the band first shows a working turn, a $.clock.after timer redraws the band once the delay has passed, and the main loop's turn.complete ends the turn, so the next one picks again. While a turn runs, a $.clock.every timer reads the settings and the clip names again every 2 seconds, and a turn.start hook reads them at each turn's start, so the draw itself never reads the store. The clips load again only when the stored names or the stamp an import or a remove writes changed, because one read of the store takes longer as the whole store grows: 0.28 ms for a small store and 3.1 ms for one of 3.5 MB, measured on 2.1.283. Under random the drawing thread counts a scene's time itself, and once it has run it posts the scene's name with surface.post; the ui.message hook picks the next scene and answers with its props, which the running instance takes in place. A message that names a scene no longer showing changes nothing, so a late or repeated post cannot skip one. No clip travels to the drawing thread before its turn to show, because one clip may take most of the 100,000 characters a Client's props hold. The GIF decoder in hooks/gif.ts is written for this mod and needs no tool on the machine.
Install
claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install idle-art@kilimcininkoroglu-mods
Function hooks are early access. Claude Code 2.1.288 and later load them by default, so there is nothing to switch on.
Load it from a local checkout for one session:
claude --plugin-dir plugins/idle-art
After installing
Restart Claude Code, or run /reload-plugins in an open session. The mod is on after an install; /idle-art off turns it off.
What it can reach
Validated with claude plugin validate on Claude Code 2.1.283:
❯ ./register.tsx hooks: session.start, ui.render{component=AbovePrompt}, ui.message, turn.start, turn.complete, command.run{command=idle-art}
❯ ./register.tsx calls: $.clock.after (via beginTurn), $.clock.every (via beginTurn), $.clock.now (via sceneFor), $.command.register, $.env.get (via resolvePath), $.fs.exists (via readGifBytes), $.fs.read (via readGifBytes), $.fs.stat (via readGifBytes), $.process.spawn (via streamedStdout), $.session.cwd (via resolvePath), $.store.delete (via removeClip), $.store.get (via clipsMark, loadClips, loadConfig, writeClipNames), $.store.set (via importGif, removeClip, setting, writeClipNames), $.ui.invalidate (via beginTurn, setting, sync), $.ui.log (via rereadOrStop), $.ui.resolve
❯ ./register.tsx env writes: nothing
❯ ./register.tsx env reads: HOME
❯ ./register.tsx surface modules: hooks/scene.tsx
Reach L2, runs base64 to read a GIF over 4 MiB.
1. Reads: the band's props (working, survey, rows, columns) and the clock; its settings and clips from the store, at each command, at each turn's start and every 2 seconds while a turn runs; the GIF a /idle-art import names, once, when it is 32 MiB or smaller; HOME and the session's directory to resolve that path
2. Runs: base64 -i <path> for a GIF over 4 MiB, once per import; no fork; one timer per turn for the delay, one that reads the settings every 2 seconds while a turn runs, and the drawing thread's 16 ms tick while the band shows
3. Sends: nothing; no network call and nothing to the model
4. Persists: the on/off state, the style, the delay, each imported clip, and a stamp of the last import or remove in the mod's store
5. Hostile input: a GIF is untrusted bytes: the decoder bounds every read by the file's length, refuses a broken code stream or a missing color table, stops at 500 frames, and an import that fails keeps nothing; a stored clip of the wrong shape is skipped at load; the command takes a fixed word list, a whole number from 0 to 60, and a clip name of lowercase letters, digits and dashes
Limits
- 8 rows is a small canvas: a GIF becomes about 30 columns by 8 rows at the usual 2:1 shape, enough for a silhouette or a motion, not for detail.
- A GIF of up to 4 MiB is read with
$.fs.read. A larger one, up to 32 MiB, is read throughbase64 -i <path>piece by piece, because$.fs.readrefuses a file over 4 MiB and$.process.runcuts its output at 4 MiB (measured on 2.1.283). The bytes that come back must match the file's size, or the import is refused. - Each frame is reduced to its cells as it is decoded, so a large GIF holds one frame of pixels at a time. A GIF stops at 500 frames.
- A long GIF loses frames to the 90,000-character limit; its motion stays as long, but steps more coarsely.
- The terminal draws the colours with its own palette; a terminal without true colour shows the nearest of its 256 colours.
matrixdraws half-width katakana. A font without those glyphs draws a replacement character.
Development
make install # eslint, typescript-eslint, typescript
make lint # complexity limit 10, the build fails above it
make typecheck # needs .claude/types/ from /plugin-types
make validate
make test # claude plugin test
