claude-mode.ps1 was 2,407 lines. It is now 153: the help block, parameters, paths, the managed-key list, a loader, and the dispatch. The rest moved, verbatim, into eleven files under lib/ - providers, output, files, core, vault, switch, guards, health, catalogue, commands, menu - dot-sourced into the script's scope in their original order, with the same check as the bash split that every original line landed in exactly one file. Inside a module $PSScriptRoot is lib\, so the one path beside the main script (providers.json) now goes through $script:Here. install.ps1 ships lib\, clearing old modules first. The Windows suite parses every module and install.ps1 (36 checks, all green on Windows PowerShell 5.1); install.ps1 is parsed but never run, since it edits the real profile and User PATH. linux/bootstrap.sh and scripts/build-package.ps1 existed only to build and serve packages for the Arkylx Index. Both installers fetch the repository's own archive, so a push to master is the release; the two scripts, the dist/ ignore and their mentions in the docs are gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.3 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. In the
lib/*.ps1 modules $PSScriptRoot is lib\: reach anything beside
claude-mode.ps1 through $script:Here.
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. Both installers fetch the repository's own archive (
install.ps1piped throughiex,linux/install.shthroughcurl | bash), so a push tomasteris the release.
Semver: a minor version adds behaviour, a patch fixes it.