The README had grown to 1,010 lines of user docs and design notes in one file,
and had gone stale: it still showed the old numbered menu, set up only three
providers, and listed preflight checks and a file layout that predate the last
three releases. It now carries install, first run, the full command
reference, the providers at a glance, troubleshooting and a docs index.
docs/:
- providers.md presets, defaults, the model cache, context windows, each
provider (OpenRouter's cost guard and guardrail check are
written up for the first time), adding a provider
- live-sessions.md what a switch does to running sessions, and repair
- design.md why settings.json, why keys stay out of it (and the vault
per platform), the preflight checks as they are now
- bar-widget.md the widget as it is now: providers from health.json, every
server provider's settings, the restart after an upgrade
- architecture.md the pieces, every file on disk and who writes it, the
contracts between them, where to change what
- development.md running the tests, the conventions the code follows,
working on the widget, releasing
CONTRIBUTING.md points at it.
Also:
- The per-project session listing used awk, which the CLI avoids because it
is missing from minimal images; it uses the script's own TSV helpers now,
and tests/static.sh fails on any awk in the CLI.
- tests/static.sh checks every relative Markdown link and #anchor.
- A unit test pins the managed env keys between cm-json.py and
claude-mode.ps1, which only a comment kept in step before.
- test_sessions covers the per-project listing, which nothing ran.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7.5 KiB
7.5 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 withConvertFrom-Json. - A switch rewrites the managed keys in
~/.claude/settings.json(anenvblock andapiKeyHelper) and records what it wrote instate.json. Claude Code readssettings.jsonat startup, from every launch context. - The key helper is what
apiKeyHelperruns. It prints the active preset's key from the vault and nothing else, so no key ever sits insettings.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.jsondescribes every gateway provider as data, read by all three (the widget throughhealth.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/cm-json.py, bin/cm-vault.sh, bin/claude-key-helper.sh |
the installer | — (Windows: claude-mode.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, install.ps1 the Windows build and its installer
bin/ the Windows key helper
linux/ the POSIX port: claude-mode, cm-json.py, cm-vault.sh,
claude-key-helper.sh, install.sh, bootstrap.sh
omarchy/ the bar widget and its installer
providers.json, presets/ shared by both builds, byte for byte
scripts/ test.sh, bump-version.sh, build-package.ps1
tests/ static, python, cli and windows suites
docs/ this
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.shenforces them.provider-tsv—cm-json.pyhands bash one tab-separated row per provider, and bash addresses the columns by number (bash 3.2 has no associative arrays). The column order isPROVIDER_TSVincm-json.py, mirrored in a comment inlinux/claude-mode: append only. Pinned bytests/python.- The managed env keys — the keys a switch owns and clears:
BASE_MANAGEDincm-json.pyand$script:BaseManagedEnvKeysinclaude-mode.ps1must list the same keys. Pinned bytests/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), andremedyKind, 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[]andignored[](each withsessionId,project,path,dropLines,providers,mtime, andreasonfor the ignored),count,ignoredCount,maxAgeDays.models-cache.json—providers.<id>→fetchedAt,ok,failedAt,baseUrl(per-server providers),models[](id, andcontextTokens,priceIn,priceOut,state,notewhere 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-readsstate.jsononce 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 |
Set-ClaudeMode, $script:BaseManagedEnvKeys |
| what refuses a switch | linux/claude-mode cm_preflight |
Set-ClaudeMode guards |
| how a model list is fetched | provider_catalogue + a parser in cm-json.py |
Get-ProviderCatalogue |
| a doctor check | cmd_doctor, doctor_* |
Invoke-Doctor, Test-PresetCatalogue, Test-OllamaContext |
| setup | cmd_setup, setup_server, setup_models |
— |
| session detection and repair | cmd_sessions, cmd_repair_session; cm-json.py cmd_scan_sessions, cmd_repair_session |
— |
| key storage | cm-vault.sh |
the DPAPI functions in claude-mode.ps1 |
| the bar icon and state | omarchy/smoido.claude-mode/BarWidget.qml |
— |
| the panel | omarchy/smoido.claude-mode/Panel.qml |
— |