Claude Code native UI surfaces
Which parts of the Claude Code terminal UI a composed identity can drive. Verified against the installed Claude Code binary at version 2.1.221 by reading the shipped settings schema and theme loader, not from documentation. The build order lives in claude-native-ui-plan.md.
What is available
- Custom themes -
~/.claude/themes/<slug>.json, selected as"theme": "custom:<slug>". Carries the role color into the prompt border, the skill and permission accents, and the Clawd mascot body. - Status line -
statusLineruns a command that receives session JSON on stdin. Already implemented asagent-compose statusline. - Subagent status line -
subagentStatusLine.commandruns once per tick with every agent-panel row on stdin and returns{id, content}per line. Shipped asagent-compose statusline --subagent. - Spinner verbs -
spinnerVerbswithmodeofappendorreplace, plus averbsarray. The single cheapest per-role voice signal. - Spinner tips -
spinnerTipsOverridewithexcludeDefaultandtips, gated byspinnerTipsEnabled. - Session display name - the
--namelaunch flag, shown in the prompt box, the/resumepicker, and the terminal title. Pure launch argument, no host file is touched. Shipped, with--settings, as launch identity. - Output styles and syntax highlighting - both are plugin components, so a generated plugin can carry them alongside a theme.
What is not available
- The ASCII logo and the welcome banner. No override string exists in the
binary. The mascot is recolorable through the
clawd_bodyandclaudetokens, never redrawn. - Borders, box drawing, and component layout. Themes carry color tokens only.
- Free per-subagent color. The subagent palette is a fixed set of eight tokens
suffixed
_FOR_SUBAGENTS_ONLY. A role can recolor a slot but cannot add one.
Theme file contract
The loader accepts three keys and silently drops anything else.
name- display string, falls back to the file slug.base- one ofdark,light,light-daltonized,dark-daltonized,light-ansi,dark-ansi. Anything else falls back todark.overrides- a flat map. A key is kept only when the chosen base theme already defines that token, and a value is kept only when it matches#rgb,#rrggbb,rgb(r,g,b),ansi256(n), oransi:<name>.
Silent dropping is the trap worth designing around. A misspelled token does not error, it just does nothing, so a generator needs to validate token names against the base theme rather than trusting its own output.
Files over 256KB are skipped with a warning. The whole directory is watched, so a rewritten theme file is picked up without a restart.
Cluster CLI deny
The Claude settings fragment a native launch passes as --settings refuses bare
kubectl and helm, so seats change the cluster through aosguard ops kubectl. The owner of the modify-live-backend boundary keeps bare kubectl exec alone, until teable:coilyco-flight-deck/agentic-os#225 puts exec on
aosguard. Kai's decisions: teable:coilyco/agentic-os#8282 and
teable:coilyco/agent-compose#8288.
Two layers
permissions.denyforkubectlandhelm, by name and by path, on every role but the owner. A deny in any tier beats every allow, so the host'sBash(*)cannot reopen it, and for the same reason it cannot carve out exec.- A
PreToolUseBash hook,agent-compose hook bash-guard, on every role, with--allow-kubectl execfor the owner. It parses the command withmvdan.cc/sh, so a flag before the verb,sudo,env,xargs,bash -c,eval, a heredoc, a pipe into a shell, andsshorpython -ctext are all judged by the verb they would run. A computed command on a line naming a cluster CLI is refused. Exit 2 blocks and shows the reason to the model.
The owner comes from the roster's boundary table, and a roster naming none
denies every role. The launch resolves agent-compose on PATH into the hook,
because a hook that cannot start does not block. Observed on Claude Code
2.1.283: exec ran, while kubectl apply, helm upgrade, and the bash -c form
were refused.
What it does not cover
A just verb or a script file runs its own kubectl unseen. Those call sites move
to aosguard instead (#8288). Anything that writes a file and runs it later, or
builds the name at runtime from pieces, gets past a guard on ordinary use. A
caller-supplied --settings replaces the fragment whole, per
caller precedence.
Safe mode caveat
--safe-mode disables custom themes, keybindings, output styles, and plugins
together. Every surface above except the status line command and the session
name vanishes in a troubleshooting session. Identity that must survive safe mode
belongs in the composed context, never in the theme.
Generated output
agent-compose native-ui emits a theme and settings fragment per role.
examples/claude-native-ui/ is the
checked-in result for roster:core, and its README explains how a boundary becomes
a theme.