b514e0074597bcc78f674ddce22073b40d124157
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b5824c6611 |
Providers as data; add Ollama and Custom endpoints
Every gateway provider is now an entry in providers.json - endpoint and auth template, how its model list is read, how it is probed before a switch, what setup asks, which doctor checks apply, and its title, colour and logo - installed next to the presets and read by all three consumers: the bash CLI (through cm-json.py), the Windows script, and the bar widget (through health.json). anthropic stays built in; it is the native login, not a gateway. Behaviour that differs in kind stays in code, chosen by name from the entry: catalogue parsers (openrouter, lmstudio, ollama, openai, static), probe rules (always, lenient, local), and named doctor checks. A provider that reuses them is an entry and a default preset, with no code. The widget draws providers from health.json, so a new one needs no QML change and no shell restart. The existing three are unchanged in behaviour: their blank presets come out byte-identical from the file, and setup, doctor, models and the picker run the same checks through the generic paths. Ollama: local server on :11434, placeholder token, one model for every tier, models from /api/tags. doctor reads the context each loaded model actually runs with (/api/ps) and its maximum (/api/show), because Ollama defaults to 4096 tokens unless OLLAMA_CONTEXT_LENGTH is set and silently truncates past it. A bare model name matches its :latest tag. Custom: any Anthropic-compatible endpoint. Ships with no address and is refused until it has one; key optional; models from /v1/models when the endpoint has a list, and a lenient probe so a proxy without one is not blocked. Also: preflight (and Set-ClaudeMode on Windows) refuses a preset with no server address; the server form, setup and set-auth use each provider's own default URL, key name and placeholder token instead of LM Studio's; the Windows build gains the no-models and no-address guards it never had. Tested on Linux against fake Ollama/Custom servers, and on Windows 5.1 in a USERPROFILE sandbox on winbox. 1.12.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a54f7bcb55 |
1.11.0: preset editor in the bar panel
Tier editing with the cached model catalogue, preset create / duplicate / rename / delete, and a choosable default preset per provider. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c5c3fc57d9 |
Choose which preset claude-mode <provider> picks
Omitting the preset used a fixed name per provider (default, zai, lmstudio). It can now be chosen: claude-mode preset default what each picks, and why claude-mode preset default openrouter cheap claude-mode preset default openrouter --clear The choice is stored in ~/.claude-mode/defaults.json and read first by default_preset_for, while its file still exists. A choice whose file is gone falls back to the built-in name, and if that is gone too, to the first preset by name, as before. Renaming a chosen preset moves the choice with it, and deleting it clears the choice. The terminal menu marks what resolve_preset would actually pick. health.json publishes defaultPresetFor (what each provider resolves to, in the CLI's order) and defaultPresetChosen (the explicit choices only). The panel sorts and tags from it, and the editor gains Make default / Clear default. preset new, rename and rm now refresh health.json, so the bar follows edits made in a terminal too. This changes existing CLI behaviour only once a default is chosen. The Windows build does not read defaults.json yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
de9f10a8c2 |
Create, duplicate, rename and delete presets from the panel
Each provider's preset list ends in "New ... preset...", and the preset editor gains Duplicate, Rename and Delete. New asks for a name and what to start from: a copy of one of that provider's presets, or a blank template. Delete is greyed out, with the reason, on the preset in use, and warns when it would leave a provider with no preset at all. CLI: - preset rename <name> <new>: the new name is hard-linked in, state.json is repointed, and only then does the old name go. - preset new <name> --provider <p> [--blank]: with a provider and no source it copies that provider's own default instead of the OpenRouter-only `default`; --blank starts from the scaffold. - valid_preset_name on every preset subcommand: no slash, no leading dot. `preset show ../../etc/passwd` used to print the file. - preset rm warns when it removes a provider's last preset. - preflight refuses a preset with every tier empty. Otherwise a blank preset would switch cleanly and Claude Code would ask the gateway for its default Anthropic models, billed at full price on OpenRouter. The panel's blocked card offers "Edit preset..." for it. The key helper now reads the active preset in a single open (new cm-json auth-of) and re-reads state.json once on a miss. Renaming the active preset could otherwise catch a running session between reading the old name and opening the file: 1 failure in 51 key fetches in a race test before, 0 in 118 across 60 renames after. It could also briefly hand out the openrouter key for a preset that uses another. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
83b59f34a1 |
Edit a preset's tiers from the bar panel
The gear on every preset row now opens an editor: the four tiers with the model each maps to, and for each one field that filters the provider's catalogue as you type (every word must match; arrows and Enter work), listed inline with context length and price, and that also takes any id typed by hand. LM Studio's server form moves one click inside the editor. The panel card moves from PopupCard to KeyboardPanel. PopupCard is an xdg-popup, which only receives keys after focus is routed through its parent surface, so no text field in it could ever be typed into - the existing server URL form included. KeyboardPanel primes layer-shell keyboard focus on open, which is why every shell panel with a text field uses it. Esc now closes the panel. The picker reads ~/.claude-mode/models-cache.json, which or_catalogue and lms_catalogue now write as a side effect, so models, doctor, setup and the menu's picker all keep it fresh and the panel never hits the network itself. One node per provider with its own fetchedAt/ok; a failed fetch keeps the old list, an LM Studio list is tied to its server, and no key or key name is ever stored. `models` gains --preset, --refresh and --json, and the Z.AI list now lives in one place. A tier edit to the active preset re-applies without the running-sessions prompt: that prompt guards against the endpoint or key moving, and a tier edit moves neither (preset url/auth still ask). A failed re-apply now says the edit was saved. `preset set` on an unknown name no longer creates it. Panel edits run through one chain that stops on the first failure and refreshes health.json at the end. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
cbd3b1d8a9 |
Dismiss broken sessions, and hide ones untouched for a week
A broken session that will never be repaired kept the bar's warning dot lit forever. repair-session now takes --ignore/--unignore/--unignore-all/--ignored, recorded in ~/.claude-mode/ignored-sessions.json, and the scan hides broken transcripts older than --max-age days (default 7, 0 disables, CM_IGNORE_AGE_DAYS sets it). Hidden sessions move to a separate ignored[] list with a reason, and --all always names how many it held back. A repair clears the session's dismissal, and entries whose transcript is gone are pruned, so a session that breaks again is never silently hidden. The panel gets an Ignore button beside Repair and a collapsed "hidden (N)" section with Restore. The dot still counts broken sessions only. Bumps to 1.10.0 and fixes the widget manifest, which still said 1.8.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4c36f4d00b |
Notice broken sessions in the bar, and offer to repair them there
Nothing tells you a session is unresumable until you try to resume it, and by then you have usually forgotten which one it was. The widget now scans every project on a timer and whenever the panel opens, marks its icon when something needs fixing, and lists the affected sessions with a repair button that says what it will drop and what it will keep before doing anything. The scan had to get roughly eighty times cheaper first. Classifying a transcript needs two facts - what its last message id is, and whether any Anthropic id exists at all - and the first settles the common case alone. Reading the tail of each file and only opening the whole thing when the tail already looks wrong takes the sweep from ~5s to ~60ms across 59 transcripts, which is the difference between something that can sit on a timer and something that cannot. `--all` uses the same path, and `--json` exposes it. The badge is a dot beside the mark rather than a recolouring of it: this widget's job is to report which provider is active, and tinting it red to mean something else entirely would be a lie about that. Verified by planting a genuinely corrupted transcript, watching the scan find it and the dot appear, then removing it and watching both clear. |
||
|
|
a4afa55580 |
Ask before switching while sessions are live, and recover the ones already broken
Acts on docs/incident-mode-switch-corrupts-live-sessions.md, which is added here as the record of why. The report identifies a consequence that was not modelled. A failed call is recoverable; a *successful* one may not be. If a running session takes even one completion from the provider being switched to - which happens when that mode matches the base URL it already had cached - that provider's message-id format lands in its transcript. OpenRouter issues `gen-<epoch>-<rand>` where Anthropic issues `msg_...`, and native Anthropic then refuses to resume the session at all, with a 400 naming previous_message_id. The only way back is to truncate the transcript, losing every turn after the cut. That happened here, and was fixed by hand. Two changes follow. Sessions are now settled before the write, not reported after it. A switch with anything running stops, names the sessions, explains what is about to happen to them, and offers restart (the only answer that ends with everything on the mode the bar now claims), close, proceed anyway, or abort - defaulting to abort. Non-interactively it refuses outright unless given --yes. The old after-the-fact reporter is deleted rather than left as a second, contradictory account. `claude-mode repair-session` replaces the hand surgery: it finds a project's transcripts, reports which are resumable, and on --apply backs the file up and truncates to the last Anthropic-issued message. Verified against the real corrupted transcript from the incident - it reproduces the manual cut exactly, 1921 lines to 1813, dropping the two `gen-` completions and the error placeholders after them, leaving a transcript that ends on a genuine msg_ id. It refuses a transcript written to in the last 90 seconds, since that one belongs to a session still running. The panel passes --yes, having already asked in its own card, and that card now names the transcript risk rather than only the inconvenient one. Requirement 4 of the report - documenting the mechanism - landed in 9c301e1; the README now carries the unrecoverable half as well. |
||
|
|
9ff9b82c25 |
One preset per mode, and a first-run setup that fills it in
The openrouter default moves its hot tiers: opus to z-ai/glm-5.3-flash and sonnet to deepseek/deepseek-v4-flash-0731. haiku and fable are unchanged. `cheap` and `lmstudio-qwen` are gone, leaving exactly one preset per mode so `claude-mode <mode>` is never ambiguous and there is no menu to read before the thing you asked for happens. The surviving lmstudio preset keeps the Qwen3.6 model rather than KAT-Coder: the two differed mainly in that KAT's chat template carries the message-order assertion this README already warns about, so between two presets that had to become one, the one that is known to work won. More presets are still a `preset new` away; the shipped set is a starting point, not a ceiling. Which is the other half of this. A shipped preset was never a working configuration - OpenRouter and Z.AI have no key stored, and lmstudio's model ids were whatever happened to be installed on the machine this was packaged on. That was left for the user to discover through a failure. Now the shipped presets carry `configured: false`, preflight blocks on it, and `claude-mode setup <mode>` walks through what is actually needed: key, server URL and auth for LM Studio, then models chosen from the provider's own catalogue rather than typed from memory. A switch that trips this in a terminal offers to run setup there and then instead of printing a command to type next. Absent means configured, deliberately: presets that predate this and any built by hand with `preset new` do not suddenly start demanding a wizard. The panel gets a "Set up <mode>…" button that hands the whole flow to a terminal, since a bar popup can host neither a hidden key prompt nor a filter-select list. Two bugs found while testing it, both real: ask_value printed its prompt to stdout while being called inside $( ), so the prompt text came back glued to the front of the answer and set-url rejected the result. Moved to stderr, which is why warn and err already go there. lms_catalogue never sent the API key. On a server with authentication switched on - the case just added support for - /api/v0/models answers 401 like anything else, so the catalogue came back empty and every caller silently concluded the server had no models installed. It now sends the preset's credential, as do the three other call sites that read it. |
||
|
|
9c301e10a1 |
Correct what a switch actually does to a running session
Every one of these said a switch leaves running sessions alone - that they keep talking to the old provider until restarted. That is wrong, and reassuring in exactly the wrong direction: a switch breaks them. The static half of the config does behave as described. Base URL, model ids and the env block are read once at startup and a running session keeps what it started with. The credential is not. It comes from running apiKeyHelper, which Claude Code re-invokes on a timer - CLAUDE_CODE_API_KEY_HELPER_TTL_MS is present in the 2.1.251 binary - and claude-key-helper.sh answers for whatever state.json says at that moment. So the switch reaches into a live session through the one part that was never cached. Into anthropic, the helper returns nothing by design and the next refresh comes back with no credential; into another provider, it hands over the new key while the session is still pointed at the old base URL, which rejects it. Either way the session starts failing calls whenever the TTL happens to expire, mid-turn as easily as between turns. One case does survive, and is now stated rather than glossed: switching between two presets of the same provider that share a keyRef keeps the same key and the same endpoint, so the session carries on with the model ids it started with. The claim in "Restarting sessions" predates this work - it came in with the Windows build - but it is wrong for the same reason and is corrected too. |
||
|
|
22cadaebc5 |
Wrap the panel's button rows instead of running past the card edge
The session-confirmation card lays out four buttons, which come to roughly 440px of content in a card 332px wide (360 less 14px of padding each side). A Row has no notion of how wide its parent is and no wrap, so it put them in one line and the last two ran off the right edge of the popup. Flow bound to the parent width instead, so the buttons take a second line when they need one. Applied to every button row rather than just the one that overflowed - the failure card can show four at once for the same reason, and the server form's rows would do it at a narrower card or a larger font. Labels are left as they are: "Switch and restart" says what the button does to the sessions listed above it, and two tidy rows read better than four terse ones crammed into a single line. |
||
|
|
f68ca08009 |
Omarchy bar widget: show the active provider, and switch from it
A Quickshell plugin for the Omarchy shell (v4's bar is Quickshell, not Waybar). The icon is the mode; clicking it opens a panel that switches. State comes from watching health.json rather than polling the CLI, so the widget costs nothing while idle and a switch made in a terminal shows up in the bar on its own. state.json is the fallback, because it exists from install onward where health.json only appears once claude-mode has run. The marks are the providers' real logos, drawn as vector paths through QtQuick.Shapes rather than set as font glyphs - three of the four have no Nerd Font pictograph at all, and paths take the bar's foreground colour and follow the theme. Claude, OpenRouter and LM Studio from simple-icons, Z.AI from lobe-icons; trademarks belong to their owners. Zhipu's mark was rejected for Z.AI: it is a dense lattice that is unreadable below ~40px. Rendering them at 13px took three fixes. A layer rasterises the Shape at its own size and then scales the texture, so a 24px buffer minified to 13 resampled two pixels into one and the Claude burst lost rays; without the layer the scale is a transform on the geometry and rasterisation happens once, at final resolution. CurveRenderer replaces the tessellating default. And the size is forced odd, because a radially symmetric mark puts its vertical and horizontal arms on the centre line, which is a pixel centre at odd sizes and the seam between two pixels at even ones, where each arm splits its coverage and greys out. Measured against a cairo render at the same size, the result is now identical. Sizing is measured rather than guessed. Every stock glyph in this bar paints 11px of ink; the marks fill their box instead of carrying a font's padding, so the box is the smaller number. Each mark also carries an optical scale from two measurements of a 200px render - LM Studio's filled container covers 69% of its box against ~38% for the others, and Z.AI and OpenRouter are wide-but-short marks spanning ~84% of the box height. Choosing a target does not switch immediately. It runs the CLI's own preflight, and if that refuses, the panel names what is missing and offers the fix - a terminal for the hidden key prompt, a re-check once a server is up, or the server form. If sessions are running it lists them by terminal and directory, marks any mid-request, and asks whether to restart them, close them, or leave them. The session action is applied strictly after the write, since restarting first would only bring them back up on the provider just left. The server form edits an LM Studio preset's base URL and whether it needs an API key, reachable from the gear on any LM Studio preset row - per row, because two presets can point at two different machines - and from the failure card. The shipped local default is untouched unless it is changed. |