Files
claude-mode/docs/architecture.md
T
smoidoandClaude Opus 5 bb01418ce0 Split the bar panel into one QML file per stage
Panel.qml keeps the state, the CLI calls and the stage switch (889 lines,
was 2360); each stage is its own file taking the panel as a required
property. The omarchy installer copies every QML/JS file and clears stale
ones. tests/static.sh filters qmllint on [syntax], not the word error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 02:12:49 +03:00

12 KiB

Architecture

How the pieces of claude-mode fit together, what each file on disk is for, and where to go to change something. Why it is shaped this way is in design.md; how to work on it, in development.md.

The pieces

flowchart LR
    user([you]) --> cli["claude-mode<br/>(bash on Linux/macOS,<br/>PowerShell on Windows)"]
    widget["bar widget<br/>(Omarchy, QML)"] -- runs --> cli
    cli -- all JSON through --> engine["cm-json.py<br/>(POSIX only)"]
    engine --> providers[(providers.json)]
    engine --> presets[(presets/*.json)]
    cli -- a switch writes --> settings[(~/.claude/settings.json)]
    cli --> state[(state.json)]
    cli --> health[(health.json)]
    claude["Claude Code"] -- reads at startup --> settings
    claude -- runs on a timer --> helper["key helper"]
    helper --> state
    helper --> vault[(vault)]
    widget -- watches --> health
    widget -- reads --> cache[(models-cache.json)]
  • The CLI does everything that changes state. The POSIX port is bash for control flow and terminal UI, with every piece of JSON handled by cm-json.py — bash only ever sees flat lines of text. The Windows build is one PowerShell script that does the same with ConvertFrom-Json.
  • A switch rewrites the managed keys in ~/.claude/settings.json (an env block and apiKeyHelper) and records what it wrote in state.json. Claude Code reads settings.json at startup, from every launch context.
  • The key helper is what apiKeyHelper runs. It prints the active preset's key from the vault and nothing else, so no key ever sits in settings.json. Claude Code re-runs it on a timer, in every live session.
  • The bar widget never reads config itself. It watches health.json, which the CLI rewrites on every change, and runs the CLI for anything it does.
  • providers.json describes every gateway provider as data, read by all three (the widget through health.json). Behaviour that differs in kind is chosen by name from it — see providers.md.

Files on disk

Under ~/.claude-mode/:

file written by read by
bin/claude-mode, bin/lib/*.sh, bin/cm-json.py, bin/cm-vault.sh, bin/claude-key-helper.sh the installer — (Windows: claude-mode.ps1 and lib\*.ps1 at the top, bin\claude-key-helper.ps1 + .cmd)
providers.json the installer, every time the CLI, the key helper's engine, the widget (via health.json)
presets/*.json the installer seeds them; preset, setup, the panel edit them everything
state.json a switch; preset rename of the active preset the CLI, the key helper, the widget
defaults.json preset default (and rename/rm keep it right) the CLI; published in health.json
health.json a switch, status, doctor, preset edits, health the widget
models-cache.json every catalogue fetch the panel's model picker
ignored-sessions.json repair-session --ignore / --unignore the session scan
vault/ set-key (file backend; DPAPI on Windows) the key helper
backups/ a switch, before it writes settings.json (last 20 kept) you, if needed
VERSION the installer the CLI, stamped into health.json

Outside it: ~/.claude/settings.json (a switch writes the managed keys and never anything else), ~/.claude/projects/*/*.jsonl (Claude Code's transcripts, which repair-session reads and repairs), and on Omarchy ~/.config/omarchy/plugins/smoido.claude-mode/ (the widget).

The repository

claude-mode.ps1, lib/*.ps1         the Windows build: settings + dispatch, and its modules
install.ps1, profile-snippet.ps1   its installer, and the block it adds to the PowerShell profile
bin/                               the Windows key helper
linux/                             the POSIX port: claude-mode (settings + dispatch),
                                   lib/*.sh (its modules), cm-json.py, cm-vault.sh,
                                   claude-key-helper.sh, install.sh
omarchy/                           the bar widget and its installer
providers.json, presets/           shared by both builds, byte for byte
scripts/                           test.sh, bump-version.sh
tests/                             static, python, cli and windows suites
docs/                              this

The POSIX CLI's modules

linux/claude-mode holds the paths and flags, loads these in order, and dispatches the command line. It finds them — and cm-json.py and cm-vault.sh — beside itself, following the ~/.local/bin symlink, so a checkout runs its own code rather than the installed version's.

linux/lib/ what is in it
output.sh the theme-derived colour palette, ok/warn/err, the banner, usage
core.sh state and preset helpers, the provider table (prov_field, provider_resolve), resolve_preset
preflight.sh cm_preflight, the server probe, claude-mode preflight
sessions.sh finding running sessions, claude-mode sessions, and the question a switch asks about them
switch.sh set_mode, settings backups, stray env vars, health.json, the guardrail check, claude-mode repair
catalogue.sh provider_catalogue and the model cache
commands.sh status, presets, models, set-key
doctor.sh doctor and its per-provider checks
presets.sh claude-mode preset … and re-applying the preset in use
menu.sh the interactive menu and its pickers
setup.sh claude-mode setup
repair.sh claude-mode repair-session and dismissals

The Windows script's modules

claude-mode.ps1 holds the help block, the parameters, the paths and flags and the managed-key list, then dot-sources these from lib\ beside it, in order, and dispatches. Inside a dot-sourced file $PSScriptRoot is lib\, so paths beside the main script go through $script:Here.

lib/ what is in it
providers.ps1 reading providers.json; Get-Provider, Resolve-ProviderId, Test-ProviderDoctor
output.ps1 Write-Ok/Write-Warn2/Write-Err2, mode colours, the banner, Show-Usage
files.ps1 JSON read/write (5.1 has no -AsHashtable), Protect-FileAcl
core.ps1 state.json, presets, Resolve-PresetForProvider
vault.ps1 the DPAPI vault, Get-PresetAuth
switch.ps1 Set-ClaudeMode, settings backups, the apiKeyHelper command line
guards.ps1 the cost guard, the OpenRouter guardrail check, stale cached model ids, repair
health.ps1 health.json; persistent environment variables that would override a switch
catalogue.ps1 Get-ProviderCatalogue, LM Studio's model list and template check, the doctor model and context checks
commands.ps1 status, presets, preset, models, doctor
menu.ps1 the interactive menu, its pickers, interactive preset editing, New-PresetScaffold

Contracts between the pieces

These are the interfaces one piece relies on another to keep. Each is pinned by a test.

  • providers.json — its shape and the values each field may take (providers.md); tests/static.sh enforces them.
  • provider-tsvcm-json.py hands bash one tab-separated row per provider, and bash addresses the columns by number (bash 3.2 has no associative arrays). The column order is PROVIDER_TSV in cm-json.py, mirrored in a comment in linux/claude-mode: append only. Pinned by tests/python.
  • The managed env keys — the keys a switch owns and clears: BASE_MANAGED in cm-json.py and $script:BaseManagedEnvKeys in claude-mode.ps1 must list the same keys. Pinned by tests/python.
  • health.json — what the widget reads: mode, preset, version, models, contextTokens, keyBackend, presets (the catalogue), providers (id, title, blurb, logo, logoScale, serverEditable, perServerCatalogue, defaultKeyRef, defaultBaseUrl, serverHint, keyOptional), defaultPresetFor, defaultPresetChosen. No key material, ever.
  • claude-mode preflight <mode> [preset] — JSON the widget acts on: ok, code (ok, needs-setup, no-models, no-url, missing-key, helper-missing, server-auth, server-wrong, server-unreachable, no-preset, provider-mismatch), title, detail, remedy (a command), and remedyKind, which picks the panel's button (setup, set-key, edit-preset, set-url, needs-key, start-server).
  • claude-mode repair-session --json — the scan: broken[] and ignored[] (each with sessionId, project, path, dropLines, providers, mtime, and reason for the ignored), count, ignoredCount, maxAgeDays.
  • models-cache.jsonproviders.<id>fetchedAt, ok, failedAt, baseUrl (per-server providers), models[] (id, and contextTokens, priceIn, priceOut, state, note where known). No keys.
  • The key helper — prints the key and nothing else on stdout; every failure explains itself on stderr, because Claude Code shows that under /status. It reads the preset in one open and re-reads state.json once on a miss, so a rename of the active preset cannot catch it mid-lookup.

Where to change what

to change POSIX Windows
a provider's endpoint, auth template, setup, checks or look providers.json the same file
a shipped preset presets/*.json the same files
what a switch writes to settings.json cm-json.py cmd_apply, BASE_MANAGED lib/switch.ps1 Set-ClaudeMode; $script:BaseManagedEnvKeys in claude-mode.ps1
a command's arguments the dispatch at the end of linux/claude-mode the dispatch at the end of claude-mode.ps1
what refuses a switch linux/lib/preflight.sh cm_preflight the guards in Set-ClaudeMode
how a model list is fetched linux/lib/catalogue.sh provider_catalogue + a parser in cm-json.py lib/catalogue.ps1 Get-ProviderCatalogue
a doctor check linux/lib/doctor.sh lib/commands.ps1 Invoke-Doctor, lib/catalogue.ps1
setup linux/lib/setup.sh
session detection linux/lib/sessions.sh
session repair linux/lib/repair.sh; cm-json.py cmd_scan_sessions, cmd_repair_session
key storage linux/cm-vault.sh lib/vault.ps1
the bar icon and state omarchy/smoido.claude-mode/BarWidget.qml
what the panel does Panel.qml (state, actions, the processes it runs)
how one panel stage looks its own file, below

The bar widget's files

All in omarchy/smoido.claude-mode/. The panel owns every piece of state and every action; each stage file only draws, from the panel it is handed.

file what is in it
BarWidget.qml the icon, the state it watches (health.json, state.json, the model cache), the session scan, the provider lookup
Panel.qml the panel's state and actions, the CLI processes they run, the hero and the footer, and the stages in stacking order
ModeList.qml the modes to switch to, each unfolding its presets
BlockedStage.qml a refused switch, with the fix
ServerStage.qml a server provider's address and key
PresetStage.qml the preset editor
TierStage.qml picking one tier's model
NewPresetStage.qml, RenameStage.qml, DeleteStage.qml a preset's lifecycle
SessionsStage.qml the running-sessions question before a switch
BrokenSessions.qml, RepairStage.qml sessions a switch broke, and repairing one
ModelMap.qml the active tier-to-model map
PillButton.qml, BrandIcon.qml the panel's button, and a provider's mark
Modes.js fallback presentation for anthropic and an older health.json

A stage cannot reach into another file by id, so where an action has to touch a stage's field — clearing the model search, focusing a name field — Panel.qml emits a signal and the stage's Connections block does it.