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>
6.2 KiB
Development
How to change claude-mode without breaking it. How the pieces fit is in architecture.md; why they are shaped that way, in design.md.
Running the tests
scripts/test.sh # static checks, Python unit tests, CLI tests (~30s)
scripts/test.sh --windows # ...and the Windows suite on winbox over SSH
scripts/test.sh --windows host # ...on another Windows host
make test # the same, if make is installed
Nothing needs installing beyond bash, python3 and curl. qmllint and shellcheck
are used when present and skipped, out loud, when not.
| suite | what it covers |
|---|---|
tests/static.sh |
every script parses; JSON is valid; VERSION matches the widget manifest and has a CHANGELOG.md entry; providers.json uses only the kinds, probes and checks the code implements, every default preset exists, no id or alias shadows a command; Markdown links resolve; QML has no syntax errors |
tests/python/ |
linux/cm-json.py, function by function: providers and the column contract the shell depends on, the original three scaffolds byte for byte, every catalogue parser, the cache, session scan/dismiss/repair, rename, defaults, health, the cost guard |
tests/cli/ |
the POSIX CLI end to end, in a sandbox, against fake servers: providers, preflight, presets, models, doctor, a real switch, sessions, and the key helper under a rename race |
tests/windows/ |
claude-mode.ps1 on a real Windows PowerShell 5.1, in a sandbox, against the same fake servers |
The tests never touch a real install. Each CLI test gets its own HOME,
CM_ROOT and CLAUDE_CONFIG_DIR with the repository's scripts installed into it,
and the vault forced to the plain-file backend (CLAUDE_MODE_VAULT=file), so your
keyring, ~/.claude-mode and ~/.claude/settings.json are never read or written;
a switch a test performs rewrites the sandbox's settings.json. The Windows suite
points USERPROFILE at a temp folder for its process, which moves every path the
script uses. tests/fake_server.py stands in for Ollama, LM Studio, a keyed
gateway and a listing-less proxy, on ports the OS picks.
The Windows suite needs key-based SSH to the host and python on the host (for the
fake servers). It copies a payload to the host's %TEMP%, runs it, and removes it.
Do not test by switching your own machine. A switch rewrites the live
settings.json that every Claude Code session — including one you may be working
in — depends on. Test in the sandbox; tests/lib.sh shows how to get one by hand.
Writing a test
A CLI test sources tests/lib.sh, which provides the sandbox, run_cm (output in
$OUT, colour stripped; status in $RC), expect_rc / expect_out /
expect_eq / expect_file, start_fake (sets OLLAMA_URL, LMSTUDIO_URL,
KEYED_URL, PROXY_URL), ready_preset, set_state and preflight_code, and
ends with finish. Assertions count rather than stop, so one run reports every
failure. A test earns its place by failing when the thing it describes breaks —
check that it does, by breaking it.
Conventions
The code follows these already; they are written down so it keeps doing so.
Portability of the POSIX port. It runs on the bash 3.2 macOS ships: no
associative arrays, no mapfile, no fractional read -t, no GNU-only stat or
find flags. linux/claude-mode uses no awk (absent from minimal images).
python3 is the only dependency, and handles every piece of JSON — bash only ever
sees flat lines of text.
Never read TSV with IFS=$'\t' read when a column can be empty: tab counts as
whitespace to read, so empty fields merge and later columns shift. Use cut
(tsv_field), or have the producer print - for empty.
Windows PowerShell 5.1. No ?: or ??; "$id:latest" parses as a
drive-qualified variable, so write "${id}:latest". The .ps1 files are ASCII
only, because 5.1 reads a file without a BOM in the ANSI codepage.
Files are LF everywhere, .ps1 and .cmd included (.gitattributes,
.editorconfig).
Comments say why, and name the thing that went wrong when a line exists because of it. What the code does is left to the code.
Keys never leave the vault: not into settings.json, health.json, the model
cache, logs, or test output.
Providers
Adding a provider that reuses existing behaviour is an entry in providers.json
and a preset in presets/ — see providers.md.
The static check enforces the entry's shape; add a CLI test if the provider does
anything the others do not. A new kind of behaviour needs a parser in
cm-json.py, a branch in the bash CLI and in claude-mode.ps1, and its value
added to the check in tests/static.sh.
cm-json.py's PROVIDER_TSV is a contract: the shell addresses its columns by
number. Append only, and update the column list in linux/claude-mode and the
unit test that pins it.
The bar widget
- Install with
bash omarchy/install.sh, thenomarchy restart shell: an edited QML file does not reliably reload in a running shell, andModes.js, a.pragma library, never does. - Anything with a text field must live in a
KeyboardPanel. APopupCardis an xdg-popup, which never receives the keyboard, so its fields look fine and take no typing. - The panel's height clamps to the screen rather than scrolling: long content needs its own bounded, scrolling area, or it is cut off.
- Provider presentation comes from
health.json, not fromModes.js— new providers need no QML change. - There are no automated UI tests.
tests/static.shcatches QML syntax errors; everything else is checked by eye, on the real bar.
Releasing
scripts/test.sh --windows— everything green.scripts/bump-version.sh X.Y.Z, and aCHANGELOG.mdentry for it (the static check fails without one).- Commit.
- Install on your own machine:
bash linux/install.sh,bash omarchy/install.sh,omarchy restart shell, and look at the bar. - Push. The Windows package is built on a Windows machine with
scripts/build-package.ps1, which refuses to package a script that does not parse.
Semver: a minor version adds behaviour, a patch fixes it.