TheMizeGuy/clawdagotchi/tree/main/plugins/mize-coworker
mize-coworker
一个橙色像素画 Claude 吉祥物住在状态行和转圈行旁边,会根据工具活动进行动画,照看后台 agent,并响应工作会话状态。
关于这个 mod
一个 Claude Code 插件,会在终端状态行和主循环转圈行旁边绘制小型像素画 Claude。这个精灵会呼吸、眨眼、散步、蹦跳、打哈欠、喝咖啡;上下文窗口快满时会出汗;用量窗口过热时会盯着沙漏;10 月下旬会带着南瓜出现;闲置 10 分钟后会睡着(提示缓存冷却时会结霜)。在转圈行旁边,它会按词语换成不同场景(思维气泡、记事本、地球、卷轴、插头)。主循环休息、agent 或工作流运行时,它会表演小短剧(发射、报告、计数、雷达、无线电、指挥、栖息、杂耍、口香糖、爆米花、飞机、禅、花园、灯笼、午餐)。它只负责绘制:不启动进程、不联网、不调用模型、不改变上下文,也不会修改工具调用。已在 Claude Code 2.1.288 上测试,包含 190 个插件测试、132 张 PNG 的帧契约和崩溃安全测试套件。
安装
请先查看作者 README 确认 marketplace 和插件名称;命令可能随仓库结构改变。
claude plugin marketplace add TheMizeGuy/clawdagotchi claude plugin install mize-coworker
原文 / README
mize-coworker
Claude as a small coworker in the terminal. An orange pixel-art Claude (the Claude Code mascot: wide body, two eyes, arm nubs, four legs; 8 cells by 2 rows, or 4 by 1 with the big option off) sits at the right end of the status line row (the engine's SessionMode site, after its mode labels) and lives there: he breathes, blinks, strolls a few cells, looks around, hops, yawns and sips a coffee; he sweats when the context window is nearly full, eyes an hourglass when a usage window runs hot, sits by a glowing pumpkin in the last week of October, and falls asleep after 10 idle minutes, frosted once the prompt cache has gone cold. While the main session rests and agents or workflows work in the background he minds them: at ease, with a skit every few seconds (a rocket as they start, a score paddle with how many are at work, a radar, a headset, a helper bringing a finished agent's report, juggling, bubble gum, popcorn, a paper plane that comes back, a plant that grows the longer they run). While the main loop's turn runs he moves up beside the thinking spinner, left of its whole line, in a scene of 12 cells by 2 for every spinner word: a thought bubble filling with dots while Thinking, a pen on a notepad while Writing, a book whose pages turn, a magnifying glass sweeping lines of text, the same glass over a turning globe while Searching the web, a globe alone while Browsing, a laptop, a terminal printing output, one to three small helpers for the agents in flight, a speech bubble when he needs you, a gear while Working, a scroll unrolling while Loading a skill, a plug going into its socket while Calling an MCP server. When the turn ends he comes back down with a hop, or a cheer after a long one. The main loop's spinner word also changes from the random verb to what is happening: Reading register.ts, Running npm, Browsing docs.example.com. /coworker demo plays every animation once, about two minutes, in the band above the prompt.
The mod only draws. It runs no process, keeps nothing in $.store, makes no model call and no network call, and adds nothing to the model's context. It never changes a tool call. It writes one small file per session, the cells it needs on the status row, and one machine-wide file, the default of those cells for the next session's start (The status row); it reads that default before writing it, and the session's vitals the status line writes (Vitals).
Tested on Claude Code 2.1.288: claude plugin test (190 tests: the frame table, every loop step by step and the solo frame the footer shows for it, the scene every spinner word and mode puts beside the spinner and the footer that never swaps, the footer and spinner mounted on the terminal surface through the engine, the scenes and scenes off, the done gesture's two forms, the team of one to three, the breath and the three-phase blink on a mock clock, the sleep loop and its cold form, the vitals parser, the idle moves on a mock clock and a seeded generator with each vitals- and date-gated move appearing only when its condition holds, the activity model's agents at work (the stop report by task type, the quiet, the count, the open-turn rest), the skits and their mix, twelve minutes of minding agents through the engine (the send-off, at least eight different skits, the count on the paddle, never a hop), a background shell that keeps nothing busy, the demo tour step by step through the engine and its band, the default reserve, and a crash-safety suite whose sentinel plugin reads next.trace and fails on any hook of this mod the engine skipped, the band's included), tests/test_frames.py (the table against scripts/frames.json and every PNG's size; claude plugin test has no file system, see Frames), claude plugin validate --strict, tsc, a headless claude -p "/coworker" run, and live interactive sessions driven in a pseudo-terminal (0.5.0, at 120 columns: /coworker demo drew the band above the prompt, ⢼⢿⠿⡿⡧ Writing · write3 3 s in and ⢼⢯⠿⡯⡧ Searching the web · webSearch1 9 s in, the label 13 cells in, the width of his seat; /coworker demo again took it away; the status row and the auto mode on row under it stayed adjacent throughout; the session start wrote 22 11 110 to the default reserve; 0.4.0, at 100 columns: the alt beside the spinner, 13 cells in). The pictures were checked by hand in Ghostty 1.3.1 (kitty graphics); the pty harness (scripts/tui-capture.py) has no graphics protocol and shows the alt.
What it shows
focus · reading [Claude]
The engine's own labels come first, dim and joined by & as the engine draws them. After them come a dim caption and Claude. Claude is the terminal's Image element, a PNG frame drawn over a box of 8 cells by 2 rows where the terminal speaks the kitty graphics protocol (Ghostty, kitty). The box spans the status row and the row under it, the engine's permission-mode row (auto mode on), which is short and leaves that space empty: no row is added to the footer, and the status line and the mode row keep their places. The caption, the z and the blank cells to his right sit on the first row, the status row. With the big option off the box is 0.2.0's 4 cells by 1 row. Where the picture cannot be drawn, the engine draws its alt in its place, dim: the 5-cell braille of the frame's pose (⢼⢯⠿⡽⡧). With the picture option off, Claude is that braille as text, in the same orange. Asleep, a z follows him in #7d5a50 (the braille the same color). With no labels the row is reading [Claude]. Right of Claude are the blank cells his wander has put there (below). The row is one line and stays inside the cells the mod reserves (The status row). In a terminal narrower than 110 columns the caption is left out and Claude alone is drawn.
The footer always draws a solo frame (Claude alone, 8 by 2). Beside the spinner (below) a busy activity is a scene, 12 by 2; the footer draws that scene's solo frame in its place, so the same loop reads as Claude alone there (eyes up while thinking, eyes down while reading, a scuttle while running).
| activity | when | caption | loop beside the spinner (one step per 250 ms) | in the footer |
|---|---|---|---|---|
| thinking | the main turn runs and no tool call is in flight | thinking | think1 x2, think2 x2, think3 x4: the thought bubble fills with one, two, three dots. By the spinner's mode, step for step: responding (Writing) write1-3, a pen writing on a notepad; tool-input type1-3, the laptop; tool-use (Working) work1, work2, work1, the gear | lookUp, idle |
| reading | Read, NotebookRead | reading | read1 x3, read2 x3, read1 x2, read2 x2, read3 x2: the left page, the right page, a page turning | lookDown, blink |
| searching | Grep, Glob, ToolSearch, LS | searching | search1 x2, search2 x2, search3 x2, search2 x2: the magnifying glass over the first, middle and last line | lookL, idle, lookR |
| editing | Edit, Write, NotebookEdit, MultiEdit | editing | type1, type2, type1, type2, type3, type2: at the laptop, the line of code growing | armsIn, idle |
| running | Bash, BashOutput, Monitor | running | run1 x2, run2 x2, run3 x2: a terminal printing output | stepA, stepB |
| browsing | WebFetch, WebSearch | browsing | web1 x2, web2 x2, web3 x2: the globe turning. Searching the web (WebSearch): webSearch1-3, the magnifying glass sweeping the turning globe | lookR, idle |
| delegating | Agent, Task, Workflow, SendMessage | delegating | teamNa x2, teamNb x2: N small helpers bobbing, one per delegating call in flight, 1 to 3 | lookR, idle (a look toward the helpers: the scene's own hop2 is never looped without them, on the status row or beside the spinner with scenes off) |
| asking | AskUserQuestion, or a permission dialog, until that loop's call ends or its turn does | needs you | ask1 x2, ask2 x2: a yellow speech bubble with an exclamation mark, Claude waving | wave1, wave2 |
| working | any other tool (MCP tools, Skill, TodoWrite, ...) | working | work1 x2, work2 x2: a gear turning. Loading a skill (Skill): skill1-2, a scroll unrolling; Calling <server> (an MCP tool): plug1-2, a plug going into its socket | idle, blink |
| greeting | a session opens (2 s) | hi | wave1 x2, wave2 x2, wave1 x2, wave2 x2 | the same |
| done | the main turn ends with an answer; none after an interrupt or an error | done | a turn under 2 minutes: hop1, hop2, hop3, idle (1.5 s). Two minutes or more: cheer1, cheer2, cheer3, cheer2, cheer3, idle (2 s), confetti. Each plays once and holds its last frame | the same |
| oops | one of the main loop's own tool calls fails (1.25 s, at most once in 10 s) | oops | flinch | the same |
| supervising | the main loop rests (no turn, or a turn left open with no spinner drawn) and agents or workflows are at work in the background | N agents | (footer only) at ease as when idle: the breath, the blink, and a skit every 4 to 10 s (Minding agents) | |
| idle | no turn and no agent at work in the background | none | (footer only) idle and idleUp in turn every 2 s, a blink every 6 s (blinkHalf 100 ms, blink 200 ms, blinkHalf 100 ms), and the moves (Idle life) | |
| asleep | 10 minutes idle | z after the sprite | (footer only) sleep1, sleep2, sleep3, one frame every 3 s; sleepCold1, sleepCold2 while the prompt cache is cold | |
The done gesture's length comes from turn.complete's durationMs, else from the turn start the mod saw. Delegating counts the delegating calls in flight from any loop (classic.PreToolUse carries no agent id), so a workflow agent's own Agent call adds a helper too.
What it shows, in order. A permission ask comes first. Then, while the main loop is at work (its turn runs and its spinner is drawn): the most recently started tool call still in flight, else a gesture still running, else thinking. While it rests (no turn, or a turn left open with no spinner drawn, as between a /goal's iterations): a gesture still running (the hop at the turn's end plays out), else supervising while agents are at work in the background, else the most recently started call in flight, else thinking while the turn is open, else the main loop's own last tool event if it came in the last 8 s and after its turn ended, else idle, and asleep after 10 idle minutes.
- Classic tool events fire for subagents and workflow agents too, so while the main turn runs Claude mirrors the session and its agents together. Once the main loop rests he does not mirror them: he minds them.
- Permission asks. An ask is recorded only when nothing beneath decided it (so a dialog really opens), and it belongs to the loop and tool that raised it: another loop's tool events leave it alone. The API has no event for an answered dialog, so
needs youlasts until that loop's call ends (done, failed or denied) or its turn does, which for an approved call means through its run. - Background work. When the main loop stops, the engine says what still runs in the background (
classic.Stop'sbackground_tasks), and the mod reads each task by itstypealone: a shell or a monitor only runs and keeps nothing busy (he idles, and sleeps, with a glance at a small terminal among his idle moves); any other type (a subagent, a workflow) is agent work. Agents are at work while a stop reported some or one was heard from (a tool event with itsagent_id, or a call that starts with no main turn running, which can only be an agent's) within the last 10 minutes, or a call of one is in flight; an agent's own end (turn.completewith itsagentId) and a main stop that reports none take that back at once. Until 0.6.0 any background task, a shell included, showed asdelegating, whose footer frames were a hop and a stand alternating every half second for as long as the task ran: he looked stuck, hopping in the corner.
Frames
There are 132 picture frames, named in scripts/frames.json (the frame contract): 94 solo frames (kind: "solo", 70x40 logical pixels, drawn in 8x2 cells, anywhere) and 38 scenes (kind: "scene", 105x40, 12x2 cells, Claude at the left exactly as in a solo frame and a prop at the right, drawn only beside the spinner and in the demo). Each scene names the solo frame the footer shows in its place; each solo frame names the braille pose that stands in for it. 0.5.0 added blinkHalf (the blink's half-shut lids) and the scenes the spinner words swap in: write1-3, webSearch1-3, skill1-2, plug1-2. 0.6.0 added 59 solo frames, the skits of a Claude minding background agents and the peek at a background shell: tally0-9 and tallyMany, radar1-4, radio1-3, report1-4, launch1-3, perch1-2, conduct1-3, juggle1-3, gum1-3, popcorn1-2, plane1-5, zen1-2, plant1-4, water1-4, lantern1-2, lunch1-2, term1-2. Wherever a scene cannot be drawn (the footer, or beside the spinner with scenes or big off) its stand-in is footerOf(frame): the scene's own solo frame, except for the team scenes (built on the hop), which stand in by a look to the right.
- The pictures are
assets/frames/<name>.png, drawn byscripts/make-frames.py(Pillow): one parametric renderer for Claude and one function per prop, exported at 4x as hard pixels, so the terminal's own scaling keeps the edges crisp. Edit a frame there, run it, look at the sheets it writes with--sheet <dir>, and commit the PNGs. - The code names frames by name:
hooks/lib/sprite.tsmirrors the table (SOLO_FRAMES,SCENE_FRAMES, one entry per line), and the view in$.statecarries the frame's name beside its braille. A frame is never looked up by its braille (several frames share a pose). - The braille is the twelve 0.1.0 poses as pixel rows (
PIXELS: idle, blink, lookL, lookR, focus, armsIn, stepA, stepB, hop, wave, flinch, sleep), composed when the module loads: the picture'salt, and thepicture: falsedrawing. tests/test_frames.pyholds the TS table equal toframes.jsonkind by kind (its solo frames in order, then its scenes in order; the JSON may interleave the kinds), and checks every PNG exists at its kind's size (widthandheighttimesscale). It runs outside the engine becauseclaude plugin testgives a test no file system:python3 -m unittest discover -s plugins/mize-coworker/tests -p 'test_*.py'from the repo root.tests/lib.test.tschecks the table's own sense: 132 distinct names, every scene's solo frame a solo frame, every frame drawn by some loop, swap, move, the breath or the blink.
Beside the spinner
While the main loop's turn runs (between its turn.start and turn.complete), Claude is drawn in the main loop's Spinner site instead of the footer: the frame and a space, then the engine's own spinner line, whole (glyph, word, elapsed time, tokens). The hook places await next(e), a reference to the engine's own drawing, as a child of its own row:
[ Claude + prop ]
[ Claude + prop ] ✳ Reading register.ts… (3s · ↓ 11 tokens)
A busy activity there is its scene, an Image of columns={12} rows={2}; a solo frame (the flinch) is columns={8} rows={2}. His seat is a scene's width and a space (13 cells) whatever the frame, so the spinner line keeps its place when a solo frame comes between two scenes. With the scenes option off, every frame there is the solo frame, 8 by 2, in a seat of its own width. With big off, the solo frame in 4 by 1, stepped one row down to the line.
Every spinner word has its own scene. The Spinner hook hands sceneFor(frame, word, mode) (hooks/lib/sprite.ts) the frame the loop is on, the word it narrates and the spinner's mode, and draws what comes back, step for step: the thought bubble becomes the notepad while responding (Writing), the laptop while tool-input (the model writing a tool call's input) and the gear while tool-use (Working: work1, work2, work1); the globe takes the magnifying glass for Searching the web; the gear becomes the scroll for Loading a skill and the plug for Calling <server>. Every other frame, word and mode stays as it is. The swap is the spinner's drawing alone: the state keeps the loop's own frame, the footer draws that frame's solo frame, and the alt is the braille of the drawn scene's solo frame. The word comes from the doing, written whenever Claude is drawn, so with narrate off the scene still follows what is happening while the spinner keeps the engine's word. The redraws stay paced by the loop's own frames, so the longest wait between two is unchanged (the tool-use gear holds work1 six ticks across the loop's wrap, 1.5 s, inside the 2.5 s grace).
The seat is held by the spinner drawing him, not by the turn alone: the spinner redraws him at the frame rate, and the footer, which follows the frames too, takes him back once 2.5 seconds have passed without that (SPINNER_FRESH_MS, longer than any frame a busy loop holds; a /goal keeps the turn open while the engine draws no spinner; seen 2026-10-02), then leaves him again when the spinner draws him. A turn start counts as a drawing, so the spinner gets its grace first.
The engine's spinner drawing opens with a blank row above its line. The two-row picture spans that blank row and the line, the line's text starting on the second row right of him (seen in Ghostty 1.3.1 on 2.1.288). With big off, the one-row picture steps one row down to sit on the line itself (seen in a live 2.1.287 session; without the step it sat on the blank row above the line). Meanwhile the footer draws only the engine's labels, and the status-row reserve stays as it is, so nothing reflows. He comes back to the footer when the turn ends, for the done gesture, the idle, minding background agents and sleep. Only the terminal and only the main loop's spinner: a subagent's spinner, a spinner showing a message of its own (compacting, a retry), another surface, or picture, sprite or besideSpinner off leave the spinner as before (the word narrated, nothing else). While he sits there the spinner line redraws with his frames, at most 4 times a second.
Idle life
Idle on the status row (not asleep, no turn), with animate on:
- Breath. idle and idleUp (one pixel taller, arms a pixel higher) take turns every 2 seconds, one slow timer.
- Blink. Every 6 seconds the lids close and open: blinkHalf (half shut) 100 ms, blink (shut) 200 ms, blinkHalf 100 ms, then open again; one
afterchain. It shows over the breath; a move under way shows its own frames instead. - Moves (with
gestureson too). Every 12 to 30 seconds he does one of: a stroll to another spot (one cell per 250 ms tick on alternating feet, then idle), a look around (lookL, lookL, idle, lookR, lookR, idle), a hop (hop1, hop2, hop3, idle), a yawn (yawn1 x2, yawn2 x4, yawn1 x2: easing in and out around the widest moment) or a coffee sip (sip1 x2, sip2 x4, sip1 x2). Strolls and looks are the common moves, hops less so; yawns and sips are rarer than either, and after 6 idle minutes, as his nap nears, he yawns more. - Moves the session calls for.
sweat(sweat1, sweat2, sweat1, sweat2: worried eyes toward the context gauge, a drop at his temple) only when the context window is 80% full or more;clock(clock1 x2, clock2 x2: an hourglass, its sand running) only while the 5h or 7d usage window isoverorcrit;pumpkin(pumpkin1 x3, pumpkin2 x2, pumpkin1 x3, pumpkin2 x2: a jack-o'-lantern, its glow flickering unevenly) only from 24 to 31 October, local time, with theseasonaloption on;peek(term1 x4, term2 x6: a glance at a small terminal, a line of output arriving) only while a shell or a monitor runs in the background. When one or more of them applies, about one move in three is one of them (picked evenly), the everyday mix the rest.
After a move ends the breath goes on. The choices come from a small seeded generator (mulberry32, seeded from the session start time; no Math.random), so the tests see the same walk every time. Where he stands is x, the blank cells to his right (the row is right-aligned, so a larger x is further left): 0 to 12 at full width, and in a terminal narrower than 110 columns 0 to 1 (0 to 2 with big or picture off). With a caption showing he is drawn where the caption leaves room, and his x is kept for when it goes. No move timer runs while asleep, busy, or with gestures or animate off.
Asleep after 10 idle minutes he stays where he was, the z in the first two of his blank cells, and sleeps in a slow loop: sleep1 (settled low, a small z), sleep2 (breathing in, a bigger Z drifting up), sleep3 (breathing out, the Zs fading), one frame every 3 seconds, one timer, cancelled when he wakes, when the session ends and with animate off. While the vitals say the prompt cache is cold, he sleeps frosted instead: sleepCold1, sleepCold2 (frost on his top edge, a snowflake turning). The first sleep frame already reads the vitals.
Minding agents
When the main loop rests and agents or workflows are at work in the background (a workflow launched and left to run, an Agent call in the background, a /goal waiting on either), Claude is supervising: on the status row, at ease exactly as when idle (the breath, the blink), with the caption N agents where there is room for one, and, with gestures and animate on, a skit every 4 to 10 seconds. A skit is a move like the idle ones: solo frames, one per 250 ms tick, 2.5 to 6 seconds long, then the breath again. No skit plays twice running, and none is the hop.
| skit | what he does | frames, in ticks |
|---|---|---|
| launch | a small rocket lifts off at his side. Played first, once per stretch of agent work, when the agents take over within 10 s of the main turn's end | launch1 x4, launch2 x2, launch3 x4 |
| report | a helper runs in with a page, he reads it, a green check. Played when an agent finishes (its turn.complete) while the main loop rests: at once when he is between moves, else after the move under way; at most one in 20 s | report1 x2, report2 x2, report3 x4, report4 x4 |
| tally | a judge's paddle with how many agents are at work: 1 to 9, then 9+ | tally0 x2, tallyN x8, tally0 x2 |
| radar | he watches them on a small scope, the sweep going twice round | radar1-4, two ticks each, twice |
| radio | mission control: a headset, a word into the mic, "copy that" | radio1 x4, radio2 x2, radio1 x2, radio2 x2, radio3 x4 |
| conduct | a baton and drifting notes; only while a workflow is among the background tasks (Orchestrating agents) | conduct1, 2, 3, 2, two ticks each, twice |
| perch | a helper checks in from the top of his head | perch1 x3, perch2 x2, perch1 x2, perch2 x2, perch1 x3 |
| juggle | three balls | juggle1, 2, 3, six times round |
| gum | a bubble grows and pops | gum1 x3, gum2 x5, gum3 x3 |
| popcorn | he watches the show | popcorn1 x3, popcorn2 x2, three times |
| plane | a paper plane, thrown off to the right, comes back from the left | plane1 x3, plane2 x2, plane3 x4, plane4 x2, plane5 x4 |
| zen | he meditates, floating | zen1 x4, zen2 x4, three times |
| garden | he waters a potted plant, then admires it. The plant grows with the wait: a sprout, leaves after 2 minutes of agent work, a bud after 6, a flower after 15 | waterN x4, plantN x6 |
| lantern | the night shift, 22:00 to 06:00 local time (seasonal) | lantern1 x3, lantern2 x2, twice |
| lunch | a sandwich, and a bite out of it, 12:00 to 13:00 local time (seasonal) | lunch1 x4, lunch2 x6 |
The everyday looks, strolls, sips and yawns are in the mix too, and so are the moves the session calls for (the sweat, the pumpkin, the peek; the hourglass also once the agents have run for 5 minutes). One draw of the seeded generator picks the next skit by weight (skitWeights in hooks/lib/wander.ts), the kind last played left out.
Where the caption shows (110 columns or more), it takes cells from his walk: a stroll goes only as far as the row as drawn leaves (three cells beside 3 agents, none beside a mode label and 9+ agents).
How many agents: those heard from in the last 3 minutes or with a call in flight, or the agent tasks the last stop reported when that is more. He stops minding them when the main loop's next stop reports none, when the last agent heard from ends (and no workflow is among the tasks), or after 10 minutes with no sign of any agent and no call of one in flight, which is also when he would fall asleep, so a quiet that long ends in sleep. A sign of an agent wakes him to it again, and so does an agent's end: the idle time runs from it. Only an agent that ended with an answer brings a report; one that was stopped or died on an error brings none.
Vitals
The status line script (statusline/statusline.sh in this repo, installed as ~/.claude/statusline.sh) measures the session on each run and writes it to the status bus for its session, one line in ~/.claude/state/statusline/bus/<session id>/.vitals:
<context percent, 0 to 100, or empty> <5h state> <7d state> <cache state>
A window state is calm, watch, over, crit or empty; the cache state is warm, cold or empty. The format is the bus helper's (shared/statusline-bus.ts: VITALS_FILE, vitalsPath, parseVitals). parseVitals is total: a field it does not know reads as unknown, a line without exactly four fields reads as all unknown, and nothing it is given makes it throw.
The mod reads the file with $.fs.read only when an idle Claude picks his next move and when a sleeping one draws a frame (or falls asleep), never more than once every 10 seconds (between reads the last answer stands), and treats a missing or unreadable file as unknown: no sweat, no hourglass, a warm sleep. The file follows the session id across /clear, /resume and /branch. It reads nothing while busy.
The status row
The engine draws the SessionMode site right-aligned on the same row as the status line (~/.claude/statusline.sh). When the two do not fit, the site wraps onto a row of its own, and the prompt would jump every time a caption came or went. So the mod reserves its cells on the status bus (shared/statusline-bus.ts, vendored as hooks/lib/statusline-bus.ts): at session start it writes 22<TAB>11<TAB>110 to ~/.claude/state/statusline/bus/<session id>/.reserve, and the script keeps 22 cells free at the row's right end, or 11 below 110 columns, where the mod draws Claude without the caption. Full: a space, Claude's 8 cells, 12 to wander in and one spare, which also holds a space, a 10-cell caption, a space and Claude. Compact: a space, Claude, 1 cell to wander in and one spare (asleep, the z takes the wander cell without its space). Where the picture cannot be drawn, its 5-cell alt is narrower than the box, so the 8 cells are what is counted. Only the status row is reserved: the second row of the picture lies over the mode row's empty right end, which the status line does not draw on. The footer draws solo frames only, so the 12-cell scenes never touch the reserve.
With big off (or picture off: the braille is 5 cells on one row either way) the smaller reserve holds: 18<TAB>9<TAB>110. Full: a space, Claude's 4 cells, 12 to wander in and one spare; compact: a space, Claude, 3 cells and one spare. Where the picture cannot be drawn, its alt takes 5 cells instead of 4, so every width is counted with 5: the walk uses the spare cell at full width and keeps to 2 cells in a narrow terminal, a caption shortens it, and the · before delegating with labels showing becomes a space.
tests/lib.test.ts proves the sums for both sizes, every activity, every frame of every loop in every context (the team, the cheer, the cold sleep, the breath, both blink phases), every x, the picture and the braille, with and without labels. /coworker off, the sprite option off and the session's end release the cells (an empty file); a /clear, /resume or /branch moves the reserve to the new session id. A reserve older than a day is written again at the next turn start, so a session open for days stays inside the script's three-day sweep. A headless session writes nothing.
The default reserve. The engine draws a session's first status line before the mod's session start has reserved anything, so in a window the status line fills the site wrapped onto a row of its own for up to one refresh at every start (0.4.0). The mod therefore keeps a machine-wide default beside the session folders, ~/.claude/state/statusline/bus/.reserve-default (RESERVE_DEFAULT_FILE, reserveDefaultPath), in the same format: at session start, and when a /clear, /resume or /branch moves the session, it writes there the line it wrote to its own .reserve, only when the file is missing or says something else, or is a day old (the script's daily sweep deletes bus files untouched for three days, this one included). It asks whether the file exists before reading it, so a missing one costs no failed read. A release (off, the session's end, the sprite option off) never writes the default, so one session's off does not reach the next one's start. The status line holds the default's cells for a session that has no .reserve of its own yet; once the session's file exists, it rules. So a session whose Claude reserves nothing (the sprite option off, or off adopted on a reload) still writes its own file, empty, and the default holds no cells for it.
The spinner word
Only the main loop's spinner (the terminal raises it under the session's id, the engine's own fallback is main; a subagent's spinner carries its agent id and is left alone), and only while the engine shows no message of its own (compacting, a retry). Only word is rewritten. The engine keeps its glyph, shimmer, elapsed time, tokens, effort and the trailing ellipsis.
| doing | word |
|---|---|
| Read, Edit, Write, NotebookEdit, NotebookRead | the verb and the file's basename: Reading statusline.sh, Editing register.ts |
| Bash | Running <program>: the basename of the first word after NAME=value assignments and a leading cd <dir> &&, only if it matches ^[A-Za-z0-9._+-]{1,24}$, else Running. Never an argument: wherever shell syntax makes a word boundary uncertain (an assignment that expands, an array assignment, a redirection in the program's place, a quoted =), the word is just Running. |
| Grep, Glob, ToolSearch, LS | Searching |
| WebFetch | Browsing <host> (host only, no user info or port; ^[a-z0-9.-]{1,40}$, else Browsing) |
| WebSearch | Searching the web |
| Agent, Task, SendMessage | Delegating |
| Workflow | Orchestrating agents |
| MCP tool mcp__<server>__<tool> | Calling <server> |
| Skill | Loading a skill |
| AskUserQuestion, a permission dialog | Asking you |
| any other tool | Working |
| no tool in flight | by the spinner's mode: responding is Writing, tool-use is Working, the rest Thinking |
Words are at most 40 characters. Control characters and format characters (bidi marks and overrides, zero-width and other invisible ones) are dropped.
Command
| Command | Who | What |
|---|---|---|
| /coworker | anyone | The switches, what Claude is doing and for how long, and the demo (what it does, or the act it is on). Headless, it says nothing is drawn. |
| /coworker off | you, typed at the prompt | Turns Claude and the spinner words off for this session (and ends a demo). |
| /coworker on | you, typed at the prompt | Turns them back on. |
| /coworker demo | you, typed at the prompt | Plays the demo tour in the band above the prompt; again, stops it. |
off, on and demo run only when e.origin.kind === 'composer', meaning you pressed Enter at the prompt. Any other origin gets one line and nothing changes. A headless claude -p "/coworker off" reports origin sdk. The command is registered immediate, so it answers while a turn runs. With a screen, the answer is dim transcript rows the model never reads. Headless, it is the command's text, which the engine prints as mize-coworker: ..., so claude -p "/coworker" is a quick check that the plugin loads.
The demo
/coworker demo is a tour of everything Claude does, so you can watch every animation at any width: 46 acts, 120.75 seconds in all (an act of a loop about 2 seconds, a skit its own 2.5 to 6), drawn in the band above the prompt (the AbovePrompt site), over whatever the other mods draw there. Each step is two rows: the picture (a scene in 12x2 cells, a solo frame in 8x2, padded by the spaces after it to a seat 13 cells wide so the label holds its place) and, beside its lower row, a dim label with the spinner word or the move's name and the frame (Writing · write2). Where the terminal cannot draw pictures, the band shows the picture's braille alt and the label.
- The acts, in order. Every busy scene family as the spinner shows it, the swapped ones included:
Thinking,Writing,Reading register.tsx,Searching,Searching the web,Browsing example.com,Editing register.tsx,Running npm,Delegatingwith one and with two helpers,Orchestrating agentswith three,Asking you,Working,Loading a skill,Calling github(the words are the narration's own for a sample call). Then the gestures for their moments: the greeting,done(the hop), the cheer,oops. Then idle: a breath and a blink at its own pace, and every move as the wander plays it:stroll(the walk),look,hop,yawn,sip,sweat,clock,pumpkin,peek. Then the skits of a Claude minding background agents, each once:launch,tally(for three agents),radar,radio,report,perch,conduct,juggle,gum,popcorn,plane,zen,garden(the plant through its four stages),lantern,lunch. Then the sleep loop and the cold one, sped up so a whole pass takes about 2 seconds. A loop plays whole, as often as it takes to fill its act.hooks/lib/demo.tsbuilds the tour; a frame held for several ticks is one step. - As drawn elsewhere. The band draws each frame as the spinner would: a scene with
scenesandbigon, else its solo frame (4 by 1 withbigoff); withpictureoff, the braille in orange (muted asleep). Withanimateoff, each act is its first frame alone, held for the act. - It ends by itself after the last act, or at
/coworker demoagain,/coworker off, a main turn starting, a/clear,/resumeor/branch, or the session's end. It does not start during a turn, whileoffholds, with thespriteoption off, or headless (the reply says why). - It touches nothing else. One
$.statevalue (demo: the step's frame and label), written only when the step changes, and one$.clock.afterat a time, the length of the step on screen, cancelled with the other timers. The footer's view and spot, the doing and the reserve are never written: the footer goes on breathing and wandering under the band. A hot reload ends the tour and takes its last step off the band.
Options (/config, or pluginConfigs in settings)
| Option | Default | What |
|---|---|---|
| sprite | true | Claude and its caption beside the footer labels. Off, nothing of Claude is drawn anywhere, the demo included. |
| picture | true | Claude as the orange pixel-art Image (its braille alt, dim, where the terminal has no kitty graphics). Off: the orange braille as text, and he stays in the footer through a turn. |
| big | true | The picture 8 cells by 2 rows, over the status row and the mode row under it, and beside the spinner over its blank row and its line. Off: 0.2.0's 4 cells by 1 row, no scenes, and the 18-cell reserve, for a terminal where the two-row picture does not sit well. The braille (picture off, or the alt) is one row either way. |
| besideSpinner | true | While the main loop's turn runs, Claude sits beside its spinner instead of in the footer (needs picture). |
| scenes | true | Beside the spinner (and in the demo), the 12x2 scenes (Claude and a prop for what the session is doing, one for every spinner word). Off: his solo frame there too, 8 by 2. Needs big. |
| narrate | true | The spinner words. Off, the spinner keeps the engine's verb, and the scene beside it still follows what is happening. |
| animate | true | When off (reduced motion), Claude shows the first frame of each activity, and no 250 ms, breath, blink, wander or sleep timer runs. It still changes with the activity and still falls asleep, and /coworker demo shows each act's first frame. |
| gestures | true | The wave when a session opens, the hop or cheer when a turn ends with an answer, the flinch when one of the main loop's own tool calls fails, the idle moves, and the skits while he minds background agents (off, he only breathes and blinks there). |
| seasonal | true | The date- and time-bound moves, local time: the pumpkin from 24 to 31 October while idle; minding agents, the lantern from 22:00 to 06:00 and the sandwich from 12:00 to 13:00 (needs gestures). |
How it hooks in
session.startregisters/coworker. In an interactive session it also reads what$.stateholds, so a hot reload writes nothing that is already there (and takes a tour step the module no longer plays off the band), reserves its cells on the status row, keeps the default reserve in step, and asks for a redraw ($.ui.invalidate('ui.render')): the footer is first drawn before the session is known to be interactive, and a hook that passed then never read$.state, so no later write would reach it. A headless session (isInteractive: false) registers the command and does nothing else: no timers, no state writes, no render work, no reads.turn.startandturn.completemark the main loop's turn (and its length, for the cheer);turn.startalso ends a demo. A subagent's run raises noturn.start, and itsturn.completecarriesagentId: that agent is no longer at work, and one that ends while the main loop rests leaves a report (the helper with a page). The main turn's end also drops its own calls still in flight, and every call when the last stop reported no agents in the background, since an interrupt or an API error can leave a call with no end event.classic.PreToolUse(after the chain answered, and only for a call it did not deny) starts a call.classic.PostToolUse,classic.PostToolUseFailureandclassic.PermissionDeniedend it, matched bytool_use_id. At most 200 calls are held, the oldest dropped, and a call older than 30 minutes is forgotten.classic.PermissionRequestshowsneeds youwhen the chain beneath left the ask undecided.classic.Stopandclassic.SubagentStopreport the session's background work: the main loop's stop is the word on which agents still run; an agent's own stop can only lower the count. These hooks always return what the chain returned. There is notool.callhook (docs/BUILD-SPEC.mdrule 7a).$.stateholds five values (types/index.d.ts):view(the frame's name, its braille, the caption and whether he sleeps; the SessionMode hook reads it, and the Spinner hook while Claude sits there),doing(activity and word, which the Spinner hook reads for its word and for the scene beside it; written while Claude is drawn or the words are on),spot(where:footerorspinner, andx, which both render hooks read),isOff, anddemo(the tour's step, frame and label, which the AbovePrompt hook reads; an empty frame is no tour). A value is written only when it changes. A frame that repeats in a loop, such as thinking's four think3 steps, writes nothing. A view an older version wrote (0.3.1's had no frame name) is drawn as the idle frame, or the first sleep frame asleep, until the next write.- Timers: one
$.clock.every(250)runs only while the activity is busy (not idle, supervising or asleep). At ease (idle or supervising), one$.clock.afterchain runs the blink (6 s open, then its three phases) and one$.clock.every(2000)the breath; one$.clock.afterwaits for the next move (12 to 30 s idle, 4 to 10 s supervising, under a second when a skit answers an event) and one$.clock.every(250)steps through a move, then stops. Asleep, one$.clock.every(3000)runs the sleep loop. One$.clock.aftercovers the next change no event brings (the 8 s decay, sleep, a forgotten call, the end of a gesture, the agents going quiet); a tool event that does not move that time, or moves it less than a minute later, leaves it alone. While a demo plays, one$.clock.afterchain steps through it.session.end(except/clearand/resume) cancels all of them; everysession.endends a demo.classic.SessionStartafter/clear,/resumeor/branchtakes the new session id and reads$.stateagain. After asession.endthat is final, late events record, draw and schedule nothing. ui.renderonSessionModedraws aBoxrow ofTexts and Claude'sImage(key="claude",source: { file: <plugin root>/assets/frames/<solo frame>.png, format: 'png' }, 8x2 cells or 4x1 withbigoff, the braille asalt) on theterminalsurface only. The plugin root is$.plugin.root, read at session start. On any other surface, withspriteoff, while/coworker offholds, while Claude sits beside the spinner, or headless, it returnsnext(e)untouched.ui.renderonSpinnerpassesnext({ ...e, props: { ...e.props, word } }), and while Claude sits there returns a row of his seat (the scenesceneForgives for the word and mode in 12x2, or the solo frame in 8x2; withbigoff 4x1, stepped down one row bymarginTop) and that engine drawing.ui.renderonAbovePrompt, while a demo plays, on theterminalsurface and with no survey holding the band, returns a column of the tour's row (Imagekey="demo", or the orange braille withpictureoff, and the dim label) overawait next(e), what the mods beneath draw there; otherwisenext(e)untouched.- All mods share one worker, so every hook and timer body catches what it calls and logs the failure to the debug log (
claude --debug). Nothing is thrown out of a hook or a timer.
Limits
- The engine draws
·between other footer items (a PR badge) and its mode labels, but that flag is not among SessionMode's props. This drawing therefore opens with one space instead. - The SessionMode site is raised even with no mode labels while a plugin hooks it (Claude Code 2.1.287), so idle Claude is always there. If a later build stops raising it with no labels, Claude shows only while a label does.
classic.PreToolUsecarries no agent id, so while the main turn runs the shown call is the session's or any agent's, and the delegating helpers count every loop's delegating calls. That is intended. For the same reason a call that starts with no main turn running is taken for an agent's; after a hot reload in the middle of a turn (below) the main loop's own calls are taken so too, until that turn ends.- A hot reload in the middle of a turn starts the model over: until the next tool event Claude shows idle, and the turn's end is still seen (with the engine's
durationMs, so a long turn still cheers). - A call whose end event never comes shows until the main turn ends or 30 minutes pass. An agent killed with a call in flight (no end event, no main turn after it) is minded for those 30 minutes; one killed between calls for 10. The count on the paddle and in the caption is the agents heard from in the last 3 minutes (or the tasks the last stop reported, if more), so a workflow's agents that have not yet called a tool, or have thought for longer than that, are not in it.
- While a turn is left open with no spinner (a
/goalpause), an agent's call start counts as the main loop's (no agent id) until the main loop's next stop, so agents are minded there by their end events: one silent for 10 minutes inside a single long call is shown by that call's activity instead. - A spinner showing a message of the engine's own (compacting, a retry) does not seat him, so after 2.5 s the main loop counts as resting although its turn runs: with agents at work in the background he minds them on the status row until the message goes.
needs youcannot end when the dialog is answered, only when the call it was for ends: the API raises nothing at the answer.- A terminal without the kitty graphics protocol (Terminal.app, VS Code's terminal, the pty test harness) shows the picture's braille
alt, dim, not orange: one of twelve poses, so the scenes' props and the finer frames (the breath, the yawn, the cheer's confetti) do not show. Setpictureto false there for the orange braille text. Withbigon, thealtbeside the spinner sits on the engine's blank row, one row above the spinner line, since the two-row box does not step down;bigoff puts it on the line. - The two-row picture rests on two things the engine draws today (2.1.288): a permission-mode row under the status row that leaves its right end empty, and a blank row above the spinner's line. If a later build fills that end of the mode row, the picture lies over it; if it drops the blank row, Claude hangs one row below the spinner line. Set
bigto false there. - With
bigoff, the step down to the spinner's line rests on the same blank row, as 2.1.287 draws it; if a later build drops that row, Claude sits one row below the line. - While the main turn runs, Claude's seat beside the spinner holds only while that site keeps drawing him: when the spinner shows a message of its own (compacting), or the engine draws no spinner although the turn has not ended (a
/goalpause between its iterations), the footer takes him back after 2.5 seconds, and leaves him again the moment the spinner draws him. - The vitals are as fresh as the status line's last run and the mod's last read (up to 10 s): a sweat or a frosted sleep can trail the gauge by one refresh. Until the status line writes
.vitals, every vitals-gated move stays off and he sleeps warm; the engine's debug log then shows the read'sENOENT, at most once in 10 s. - A session left asleep keeps its 3-second sleep loop running (one state write, one footer redraw and at most one small file read per 10 s) until it wakes or ends;
animateoff stops it. - The reserve assumes the engine's own mode labels are absent; with a label showing (
focus), a caption can still push the site onto its own row in a terminal where the status line fills its budget. - A session killed without
session.endleaves its reserve file; the status line script's daily sweep removes files untouched for three days. DelegatingandOrchestrating agentsshare the helpers' scene: it counts the delegating calls in flight (one to three helpers), whatever the word. In thetool-inputmode the word staysThinking(the engine says nothing more specific there) while the scene is the laptop.- The default reserve is one file for the machine: the last session start (or move) to write it wins, so a session with
bigoff writes18 9 110and the next session's first status line holds 18 cells until that session's own reserve is written. The status line decides when it reads the default (a session with no.reserveof its own); a session where the mod does not load at all (--safe-mode, the plugin disabled) writes no file of its own, so it rests on the script's rule. - The demo plays in the band, which the person can collapse (ctrl+x ctrl+a); collapsed, the tour plays on unseen until it ends. It plays between turns only, and a hot reload ends it. A host that refused the tour's timer (none does in a live session; the engine reports the refusal) would leave its first step in the band until
/coworker demoagain. - Nothing is drawn under
claude -por the SDK. The desktop's SDK sessions are not interactive, so they get neither the sprite nor the spinner words.
