如何用彩色字體在 Claude Code 狀態列顯示像素角色
Putting pixel faces in Claude Code's status line with a colour font
AgentValley 透過 Claude Code 的 statusLine 和 subagentStatusLine,在終端機狀態列及子智能體列顯示像素角色,並以彩色字體字形代替圖片。
I make a small Mac app that shows Claude Code sessions in the notch as a pixel crew, one little face per agent. I wanted the same faces in the terminal too, in Claude Code's status line and in the rows it shows for sub-agents. This is how it works now, and what didn't work on the way.
Claude Code wrote most of the code for this. Claude also wrote this post, since English isn't my first language, and I read it in Thai before posting. The numbers and file names come from the repo.
The status line is the easy part
Claude Code runs any command you give it as a status line. It sends JSON on stdin (model, context use, plan limits, session id, transcript path) and shows whatever the command prints. There is also subagentStatusLine for the rows under ● main.
A tiny one looks like this:
#!/usr/bin/env python3
import json, sys
data = json.load(sys.stdin)
face = chr(0x3F000) # one wide character from the font, more on that below
print(f"{face} {data['model']['display_name']}")
In AgentValley it's a subcommand of the app itself, wired in ~/.claude/settings.json:
"statusLine": {
"type": "command",
"command": "'/Applications/AgentValley.app/Contents/MacOS/AgentValley' crew status",
"padding": 1,
"refreshInterval": 30
},
"subagentStatusLine": {
"type": "command",
"command": "'/Applications/AgentValley.app/Contents/MacOS/AgentValley' crew agents"
}
Two things matter here. It runs on every new message, so keep it fast. Ours reads only the last 1 MB of the transcript and aims for under 50 ms. And always exit 0, because a status line that fails just shows nothing and you don't find out why.
Pictures are the hard part
A status line is text. Claude Code strips kitty image codes out of it, so real images are out.
Half-block characters came first (▀ ▄ in truecolor, two pixels per text row). That works in every terminal, but a 12x12 face is 6 lines tall, way too big for a status line.
So each face became one character in a font. The terminal draws a character, and the font has the colour picture inside it.
Which code points
The first pick was the Private Use Area, since that is what it's for. It broke in the Chrome-based terminals, Cursor and Paradev (formerly Superset). They never look in another font for a private use character, so you get a box unless your main terminal font has it.
Unassigned code points behave differently there. Those terminals do fall back to any installed font that has the glyph. So the faces live at the unassigned end of Plane 3, U+3F000 to U+3F7FF. Characters there count as East Asian Wide, so every terminal and Claude Code agree one of them takes two cells. That part matters. If Claude Code measures the line one way and the terminal draws it another, everything after the face shifts over.
If Unicode ever assigns that range, the faces have to move.
One wide character, not two halves
Before that, each face was split into two normal-width characters. Ghostty fits every colour glyph to 95% of its cell and centres it, so the two halves always had a thin gap between them. One wide character fixed it.
COLR and SVG in the same font
Terminals don't agree on colour font formats either. Chrome-based ones draw COLR and never SVG. Ghostty draws colour only from SVG or sbix. So every face is in the font twice, as COLR v0 + CPAL and as OpenType-SVG, plus plain outlines as a fallback.
The font is built with fontTools in Python. The pixels come from the same 12x12 grids the Mac app draws, dumped to JSON by a Swift test, so the terminal and the notch can't drift apart. A test fails if the committed font is older than the faces. Each pixel is a square in a 1200-unit em, and runs of the same colour in a row are merged into one rectangle to keep the file small. Right now it's about 2.2 MB with 1,490 characters (faces holding different things, plus name tags).
Ghostty needs one more line
Ghostty won't use a colour font as fallback for these characters on its own. You map the range in its config and restart it:
font-codepoint-map = U+3F000-U+3F7FF=AgentValley Crew
Small gotchas
- Terminals keep the fonts they've loaded until they quit. After a font update, reopen the terminal or the new faces show as boxes.
- tmux is the same, the server can outlive the font update.
- Copy text with a face in it to somewhere without the font and you get boxes. Our name tags have a switch to turn them off for that.
- No animation. Claude Code redraws the sub-agent rows about every 5 seconds, so a face only changes when the agent opens a different tool.
It's tested in Terminal, iTerm2, Ghostty 1.3.1, Paradev and Cursor. Not yet in VS Code, WezTerm, kitty, Warp or Zed. If you try one of those I'd like to hear what you see.
The font (OFL) and the code (MIT) are in AgentValley, a free Mac app: https://github.com/morsechimwai/agent-valley
來源:DEV Community · dev.to
