Files
claude-mode/docs/development.md
T
smoidoandClaude Opus 5 4091746d4e Split the Windows script into modules; drop the Arkylx Index pieces
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>
2026-09-15 02:06:45 +03:00

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, then omarchy restart shell: an edited QML file does not reliably reload in a running shell, and Modes.js, a .pragma library, never does.
  • Anything with a text field must live in a KeyboardPanel. A PopupCard is 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 from Modes.js — new providers need no QML change.
  • There are no automated UI tests. tests/static.sh catches QML syntax errors; everything else is checked by eye, on the real bar.

Releasing

  1. scripts/test.sh --windows — everything green.
  2. scripts/bump-version.sh X.Y.Z, and a CHANGELOG.md entry for it (the static check fails without one).
  3. Commit.
  4. Install on your own machine: bash linux/install.sh, bash omarchy/install.sh, omarchy restart shell, and look at the bar.
  5. Push. Both installers fetch the repository's own archive (install.ps1 piped through iex, linux/install.sh through curl | bash), so a push to master is the release.

Semver: a minor version adds behaviour, a patch fixes it.