Split the POSIX CLI into modules

linux/claude-mode was 3,035 lines. It is now 177: the paths and flags, a
loader, and the command dispatch. Everything else moved, verbatim, into
twelve files under linux/lib/, one per concern - output, core, preflight,
sessions, switch, catalogue, commands, doctor, presets, menu, setup, repair.
The move was done by line range with a check that every original line landed
in exactly one file; the only thing that changed place is the switch's
running-sessions question, which now sits with session detection.

The CLI finds lib/, cm-json.py and cm-vault.sh beside itself, following the
~/.local/bin symlink, so a checkout runs its own code rather than the
installed version's (it used to mix the two). The key helper path written
into settings.json is still the installed one.

linux/install.sh ships bin/lib/, clearing old modules first so a removed one
cannot linger. The package build copies linux/ recursively, which a flat copy
would not.

tests/cli/test_install.sh runs the real installer into a sandbox home: every
file lands, the symlink runs, a reinstall keeps edited presets and drops a
stale module. docs/architecture.md lists the modules.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
smoido
2026-09-15 02:00:10 +03:00
co-authored by Claude Opus 5
parent b514e00745
commit 441166d003
17 changed files with 3063 additions and 2905 deletions
+33 -8
View File
@@ -46,7 +46,7 @@ Under `~/.claude-mode/`:
| file | written by | read by |
|---|---|---|
| `bin/claude-mode`, `bin/cm-json.py`, `bin/cm-vault.sh`, `bin/claude-key-helper.sh` | the installer | — (Windows: `claude-mode.ps1` at the top, `bin/claude-key-helper.ps1` + `.cmd`) |
| `bin/claude-mode`, `bin/lib/*.sh`, `bin/cm-json.py`, `bin/cm-vault.sh`, `bin/claude-key-helper.sh` | the installer | — (Windows: `claude-mode.ps1` at the top, `bin/claude-key-helper.ps1` + `.cmd`) |
| `providers.json` | the installer, every time | the CLI, the key helper's engine, the widget (via `health.json`) |
| `presets/*.json` | the installer seeds them; `preset`, `setup`, the panel edit them | everything |
| `state.json` | a switch; `preset rename` of the active preset | the CLI, the key helper, the widget |
@@ -68,8 +68,9 @@ anything else), `~/.claude/projects/*/*.jsonl` (Claude Code's transcripts, which
```
claude-mode.ps1, install.ps1 the Windows build and its installer
bin/ the Windows key helper
linux/ the POSIX port: claude-mode, cm-json.py, cm-vault.sh,
claude-key-helper.sh, install.sh, bootstrap.sh
linux/ the POSIX port: claude-mode (settings + dispatch),
lib/*.sh (the rest, one file per concern), cm-json.py,
cm-vault.sh, claude-key-helper.sh, install.sh, bootstrap.sh
omarchy/ the bar widget and its installer
providers.json, presets/ shared by both builds, byte for byte
scripts/ test.sh, bump-version.sh, build-package.ps1
@@ -77,6 +78,28 @@ tests/ static, python, cli and windows suites
docs/ this
```
### The POSIX CLI's modules
`linux/claude-mode` holds the paths and flags, loads these in order, and
dispatches the command line. It finds them — and `cm-json.py` and `cm-vault.sh`
— beside itself, following the `~/.local/bin` symlink, so a checkout runs its own
code rather than the installed version's.
| `linux/lib/` | what is in it |
|---|---|
| `output.sh` | the theme-derived colour palette, `ok`/`warn`/`err`, the banner, `usage` |
| `core.sh` | state and preset helpers, the provider table (`prov_field`, `provider_resolve`), `resolve_preset` |
| `preflight.sh` | `cm_preflight`, the server probe, `claude-mode preflight` |
| `sessions.sh` | finding running sessions, `claude-mode sessions`, and the question a switch asks about them |
| `switch.sh` | `set_mode`, settings backups, stray env vars, `health.json`, the guardrail check, `claude-mode repair` |
| `catalogue.sh` | `provider_catalogue` and the model cache |
| `commands.sh` | `status`, `presets`, `models`, `set-key` |
| `doctor.sh` | `doctor` and its per-provider checks |
| `presets.sh` | `claude-mode preset …` and re-applying the preset in use |
| `menu.sh` | the interactive menu and its pickers |
| `setup.sh` | `claude-mode setup` |
| `repair.sh` | `claude-mode repair-session` and dismissals |
## Contracts between the pieces
These are the interfaces one piece relies on another to keep. Each is pinned by a
@@ -120,11 +143,13 @@ test.
| a provider's endpoint, auth template, setup, checks or look | `providers.json` | the same file |
| a shipped preset | `presets/*.json` | the same files |
| what a switch writes to `settings.json` | `cm-json.py` `cmd_apply`, `BASE_MANAGED` | `Set-ClaudeMode`, `$script:BaseManagedEnvKeys` |
| what refuses a switch | `linux/claude-mode` `cm_preflight` | `Set-ClaudeMode` guards |
| how a model list is fetched | `provider_catalogue` + a parser in `cm-json.py` | `Get-ProviderCatalogue` |
| a doctor check | `cmd_doctor`, `doctor_*` | `Invoke-Doctor`, `Test-PresetCatalogue`, `Test-OllamaContext` |
| setup | `cmd_setup`, `setup_server`, `setup_models` | — |
| session detection and repair | `cmd_sessions`, `cmd_repair_session`; `cm-json.py` `cmd_scan_sessions`, `cmd_repair_session` | — |
| a command's arguments | the dispatch at the end of `linux/claude-mode` | the dispatch at the end of `claude-mode.ps1` |
| what refuses a switch | `lib/preflight.sh` `cm_preflight` | `Set-ClaudeMode` guards |
| how a model list is fetched | `lib/catalogue.sh` `provider_catalogue` + a parser in `cm-json.py` | `Get-ProviderCatalogue` |
| a doctor check | `lib/doctor.sh` | `Invoke-Doctor`, `Test-PresetCatalogue`, `Test-OllamaContext` |
| setup | `lib/setup.sh` | — |
| session detection | `lib/sessions.sh` | — |
| session repair | `lib/repair.sh`; `cm-json.py` `cmd_scan_sessions`, `cmd_repair_session` | — |
| key storage | `cm-vault.sh` | the DPAPI functions in `claude-mode.ps1` |
| the bar icon and state | `omarchy/smoido.claude-mode/BarWidget.qml` | — |
| the panel | `omarchy/smoido.claude-mode/Panel.qml` | — |