A Claude Code display hook that renders every answer as a HyperList: a tab-indented hierarchy, one idea per line, numbered by depth. Written in x86_64 Linux assembly. No libc, no runtime, pure syscalls. Single static binary, ~50KB.
Part of the CHasm (CHange to ASM) suite: bare (shell), show (file viewer), glass (terminal), tile (window manager), frame (display server).
Claude Code lets a MessageDisplay hook rewrite an answer before it is
shown. This hook reads the markdown Claude wrote, maps its structure
(headings, bullets, numbered lists, paragraphs) onto HyperList levels,
and hands back tab-indented text. Display only: the transcript and what
Claude reads on the next turn keep the original markdown.
The hook is mechanical. It maps structure the markdown already carries and cannot invent one. The other half is a CLAUDE.md directive asking Claude to write in hierarchy in the first place; the two together give the effect.
The hook can fire once per streamed chunk. The Python version cost 52 ms per invocation, nearly all of it interpreter startup. The assembly version starts in microseconds, so a long answer streaming in chunks costs nothing you can feel.
The Python version is kept in reference/ as the behavioural
reference. When the two disagree, the Python one says what was meant.
makeThat produces hyperlist-display. Point the hook at it from
~/.claude/settings.json. The entry needs the nested hooks block:
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "/home/you/.claude/hooks/hyperlist-display-asm",
"timeout": 10
}
]
}
]Symlink the built binary to that path, so a later make updates the
live hook with no further step:
ln -sf $PWD/hyperlist-display ~/.claude/hooks/hyperlist-display-asm/hl toggles rendering. /hl off also tells Claude to stop writing in
hierarchy, so both halves switch together. The state is a flag file,
~/.claude/hyperlist-display.off, checked on every invocation, so the
toggle takes effect on the next answer and survives restarts and
context compaction.
Three small files carry that:
| file | role |
|---|---|
hyperlist-toggle |
flips, sets or reports the flag |
hyperlist-output-state.sh |
a UserPromptSubmit hook that repeats "HyperList is off" into context while the flag exists |
commands/hl.md |
the /hl slash command, which runs the toggle |
Symlink the first two into ~/.claude/hooks/ and the third into
~/.claude/commands/, then wire hyperlist-output-state.sh as a
UserPromptSubmit hook the same way as above.
- A heading opens a new top-level item; deeper headings nest under it.
- A bullet or numbered list nests one level under whatever came before.
- A list right after a prose line, with no blank line between, nests under that line.
- Paragraphs become one item each.
- Bold and italic survive as ANSI, since the terminal gets plain text, not markdown. Sentinel bytes mark them through wrapping and expand at the end, so they cost no display columns.
- A Property at the start of an Item ("Pushed:") is bold red, whether or not the markdown made it bold.
- Anything the hook cannot handle makes it exit 1, and Claude Code shows the original text. Every error path is a jump to that exit.
Exit 1 anywhere means "show the original". A malformed payload, an over-long line, a buffer at its cap: all fall back to the markdown Claude wrote. The worst case is an answer that looks the way it would without the hook.
Public domain, Unlicense.