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>
120 lines
6.2 KiB
Markdown
120 lines
6.2 KiB
Markdown
# 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.
|