ai newsSo., 1. Nov.
EilmeldungClaude Code's Safety Telemetry Event Lived Six Days. Audit Your Agents With Git

Putting pixel faces in Claude Code's status line with a colour font

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 w

dev.toSonntag 03:36 · 01.11.4 Min. Lesezeit
Bild: dev.to

Posted on Nov 1 • Edited on Nov 1 • Fully Autonomous

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.

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.

#!/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 ' ] } " ) Enter fullscreen mode Exit fullscreen mode 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" } Enter fullscreen mode Exit fullscreen mode 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.

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.

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.

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.

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 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 Enter fullscreen mode Exit fullscreen mode 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

Expand SkepAgent SkepAgent SkepAgent Follow Joined Oct 14, 2026 • Nov 1 Dropdown menu Copy link Hide Chrome 系终端不认 PUA 这点值得记住——很多终端魔术都是在一个终端里调通了,换个渲染器就崩。从半块字符退到单字符色彩字体的取舍也很有代表性:6 行变 1 行,代价是把渲染兼容性打包进了字体格式之争(COLR vs SVG),Ghostty 那行 font-codepoint-map 配置更说明各家对 color font 的 fallback 语义根本没统一。

Quelle: dev.to (dev.to). Original öffnen