README down to what a user needs; the rest into docs/

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>
This commit is contained in:
smoido
2026-09-15 01:52:03 +03:00
co-authored by Claude Opus 5
parent 51e42823d1
commit b514e00745
12 changed files with 1210 additions and 945 deletions
+119
View File
@@ -0,0 +1,119 @@
# Development
How to change claude-mode without breaking it. How the pieces fit is in
[architecture.md](architecture.md); why they are shaped that way, in
[design.md](design.md).
## Running the tests
```bash
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](providers.md#adding-a-provider).
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. 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.