Repository navigation
How It Works
Clayo is one WPF project, CcxShell.csproj, with its files flat in the repo root. The namespace is still CcxShell and some names still say ccx, from the project's first draft.
| File | What it does |
|---|---|
Program.cs |
Entry point. Answers clayo --statusline before any WPF assembly loads, otherwise starts the app. |
App.xaml(.cs) |
Start-up: clears inherited Claude Code session variables, picks the folder, takes the single instance, opens the window and the island, keeps the login entry. Logs crashes. App.xaml holds the palette and shared styles. |
SingleInstance.cs |
Mutex plus named pipe: a second launch hands its folder to the running window. |
LoginStartup.cs |
The per-user Run key entry for Start at login, and its opt-out marker. |
MainWindow.xaml(.cs) |
The window: sidebar, session rows (SessionRow), folder picker, pane switching, header buttons, Settings, close-to-hide. |
SessionStore.cs |
Scans and watches ~/.claude/projects, reads each transcript's head and tail. Read only. |
SessionLauncher.cs |
Builds the claude command for new, resume and fork, and picks the shell. |
SessionNames.cs |
Names you typed, in names.json. |
SessionParents.cs |
Which session each branch came from, in parents.json. |
ConPty.cs |
P/Invoke for pipes, the pseudoconsole and CreateProcessW; PtyProcess runs one child with a read thread raising raw bytes. |
TerminalPane.xaml(.cs) |
One pane: WebView2 plus xterm.js, bridged to a PtyProcess. Infers the pane's status from its output. |
Assets/terminal.html |
The xterm.js page: rendering, input, resize coalescing, copy and paste. xterm.js and its fit and WebGL addons are vendored in Assets/xterm/. |
StatusRelay.cs |
The --settings file each pane gets, and clayo --statusline, which passes the status line JSON on and saves a copy. |
StatusStore.cs |
Watches the saved status files; works out the account's limits, including the fallback cache. |
GitInfo.cs |
Branch and uncommitted line counts for open panes' folders. |
StatusStrip.cs |
Turns status, git and settings into the header strip and footer meters; colour bands; ReserveWatch for the near-limit warning. |
StatusStripView.cs |
Draws the strip (StatusStripView) and footer (LimitsView) in each theme. |
ClayoSettings.cs |
The settings record, its defaults, and settings.json load and save. |
SettingsPane.xaml(.cs) |
The Settings screen. |
IslandWindow.xaml(.cs) |
The island window: polls the cursor, places and animates the pill, handles clicks and drops. |
IslandTrigger.cs |
Decides when the island peeks, notifies, greets or shows the drop target. No WPF, so checks/island.cs can drive it. |
IslandGlow.cs |
The coloured light under the island and the dwell bar at the top edge, in a click-through window of their own. |
ClayoMascot.cs |
The voxel mascot and its moves, drawn natively so the island needs no WebView2. |
checks/*.cs |
Runnable self-checks. See Building and Checks. |
Each pane starts a shell under ConPTY (pwsh.exe -NoLogo if it is on your PATH, else powershell.exe -NoLogo) and types the claude command into it 400 ms later. When Claude exits you land on a prompt instead of the pane dying.
Before any pane starts, App.OnStartup clears CLAUDECODE, CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_SESSION_ID, CLAUDE_CODE_MESSAGING_SOCKET, CLAUDE_CODE_MESSAGING_TOKEN and CLAUDE_PID from Clayo's own environment. Panes inherit it, and if Clayo was started from inside a Claude Code session, CLAUDE_CODE_CHILD_SESSION alone would switch transcript saving off, hiding the new session from the sidebar and from --resume. Configuration variables (ANTHROPIC_* and the like) are left alone.
Bytes cross between C# and JavaScript base64-encoded, and are never decoded to text on the way. A pipe read can split a UTF-8 character or an escape sequence in half; xterm.js reassembles them. Decoding to a string in C# would corrupt both.
-
Out: the ConPTY read thread hands raw chunks to
TerminalPane. Output is held for about a frame (8 ms) and posted as one{t:"o", d:<base64>}message, because ConPTY delivers one redraw in several reads and posting them one by one let xterm paint half-drawn screens. -
In: xterm's
onDataandonBinaryare encoded to base64 and posted as{t:"i"}; C# writes the bytes to the pty. -
Resize:
{t:"r", cols, rows}, coalesced to 60 ms in the page. -
Paste: Ctrl+V posts
{t:"paste"}. If the clipboard holds files and no text, C# types their paths; otherwise the browser does a normal paste.
Status is read off the same raw bytes. "API Error:" and "Do you want to" are plain ASCII, and ASCII bytes never occur inside a multi-byte UTF-8 sequence, so the stream can be searched without decoding. A short carry from the previous chunk catches a marker split across two reads.
Each pane's WebView2 is disposed when the pane closes. Removing it from the window alone doesn't free the renderer.
SessionStore reads *.jsonl under ~/.claude/projects, skipping subagents folders and empty files. For each file it reads:
-
The head (up to 40 lines): the
cwd, thesessionIdand the first human prompt. -
The tail (the last 96 KB): the newest
ai-titleandlast-promptlines, which Claude Code rewrites as the conversation grows.
A file is re-read only when its size or modified time changes, and the folder watcher is debounced by 600 ms.
Working directory from the transcript, not the folder name. Claude Code names each project folder by replacing path separators with dashes, so a path that contains a real dash can't be recovered from it. Clayo uses the cwd recorded in the transcript. The folder name is a last resort, for display only, never used as a working directory.
claude --resume <id> --fork-session copies the parent's messages into the child and rewrites sessionId on every copied line, so a forked transcript holds no reference to its origin. Clayo allocates the child's id itself, so it knows the link the moment it forks, and writes it to parents.json keyed by the child's id (SessionParents.cs). Because the id is known up front, the branch can be drawn in the sidebar immediately instead of scraping the new id from the output.
The sidebar walks each row's parents to work out its indent (at most 8 hops, which also stops a cycle in a hand-edited file), and builds a sort key from the chain so each branch sorts directly under its parent.
Clayo reads ~/.claude/projects and never writes there. Its own files:
| Path | What |
|---|---|
%LOCALAPPDATA%\Clayo\settings.json |
Your settings. See Settings. |
%LOCALAPPDATA%\Clayo\names.json |
Names you gave sessions |
%LOCALAPPDATA%\Clayo\parents.json |
Branch links |
%LOCALAPPDATA%\Clayo\statusline.json |
The --settings file every pane gets (statusline.dev.json for a Debug build) |
%LOCALAPPDATA%\Clayo\status\<session id>.json |
The last status line payload of each session |
%LOCALAPPDATA%\Clayo\no-login-start |
Present when you turned Start at login off |
%LOCALAPPDATA%\Clayo\crash.log |
Crash details |
%LOCALAPPDATA%\CcxShell\WebView2\ |
The terminal panes' shared WebView2 data |
HKCU\Software\Microsoft\Windows\CurrentVersion\Run, value Clayo
|
Start at login |
The JSON files are written beside themselves and swapped in, so a crash mid-write can't truncate them. A corrupt file is treated as empty rather than stopping Clayo from starting.
Clayo also reads, never writes: settings.json in CLAUDE_CONFIG_DIR or ~/.claude (your status line and effort), and %TEMP%\claude\statusline-usage-cache.json if your own status line script saves one.
Clayo · MIT · Not affiliated with Anthropic
Using Clayo
Under the hood