Files
claude-mode/docs/architecture.md
T
smoidoandClaude Opus 5 441166d003 Split the POSIX CLI into modules
linux/claude-mode was 3,035 lines. It is now 177: the paths and flags, a
loader, and the command dispatch. Everything else moved, verbatim, into
twelve files under linux/lib/, one per concern - output, core, preflight,
sessions, switch, catalogue, commands, doctor, presets, menu, setup, repair.
The move was done by line range with a check that every original line landed
in exactly one file; the only thing that changed place is the switch's
running-sessions question, which now sits with session detection.

The CLI finds lib/, 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 (it used to mix the two). The key helper path written
into settings.json is still the installed one.

linux/install.sh ships bin/lib/, clearing old modules first so a removed one
cannot linger. The package build copies linux/ recursively, which a flat copy
would not.

tests/cli/test_install.sh runs the real installer into a sandbox home: every
file lands, the symlink runs, a reinstall keeps edited presets and drops a
stale module. docs/architecture.md lists the modules.

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

8.9 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 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 (settings + dispatch),
                                   lib/*.sh (the rest, one file per concern), 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

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

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 Set-ClaudeMode, $script:BaseManagedEnvKeys
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 lib/preflight.sh cm_preflight Set-ClaudeMode guards
how a model list is fetched lib/catalogue.sh provider_catalogue + a parser in cm-json.py Get-ProviderCatalogue
a doctor check lib/doctor.sh Invoke-Doctor, Test-PresetCatalogue, Test-OllamaContext
setup lib/setup.sh
session detection lib/sessions.sh
session repair lib/repair.sh; 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