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>
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>
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>
`claude-mode set-key openrouter sk-or-...` did the wrong thing on the
POSIX port: the dispatcher loop kept the *last* positional, so the key
became the ref name and the prompt asked for "the key for ref
'sk-or-...'". Storing to that ref succeeds, so nothing looks wrong until
the switch later reports no key for 'openrouter' - which is exactly what
happened on macOS.
Both ports now take `set-key [ref] [key]`: the first positional is the
ref unless it is key-shaped (sk-* or longer than a ref name would
plausibly be), in which case it is the key for the default ref, and a
second positional is always the key. An inline key warns that it is now
in the shell history, since the hidden prompt leaves no trace. Extra
arguments are refused instead of ignored. The Windows dispatch already
read the first positional correctly, so it only gains the inline form.
The shipped default preset moves to the models asked for: DeepSeek 4.1
Flash on opus, GLM Flash on sonnet. Existing installs keep their own
presets - the installer never overwrites one without --force - so an
upgrade needs `claude-mode preset set default opus ...` and the same for
sonnet, or a --force install.
Verified in the bash 3.2 container: ref+key stores under the ref, a bare
key stores under openrouter, an empty prompt aborts, a custom ref still
works, the stored value is byte-identical, and four positionals are
refused.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude Code's project slug is not "slashes become dashes". Every
non-alphanumeric character becomes one dash, nothing collapsed - verified
against 2.1.269 by running a session in a directory named `slug._test x`,
which was filed as `-tmp-cmtest-slug--test-x`: the dot, the underscore and
the space each became a dash of their own. Paths with dots are ordinary on
macOS (iCloud Drive lives under `Mobile Documents`), and the old slash-only
rule pointed at a directory that does not exist, so repair-session could
not find a single transcript there.
It is also the physical directory that gets slugged. Claude Code asks the
OS for its working directory, and symlinks come back resolved: a session
started in /tmp/x is filed under -private-tmp-x on macOS, where /tmp is a
symlink, while the shell's $PWD still reads /tmp/x. Verified the same way -
a session started in /tmp/cmtest/linkdir landed under -tmp-cmtest-realdir.
cm_project_dir now applies the real rule and falls back to the resolved
path, which also covers the walk-up search in repair-session. Both cases
were re-tested end to end afterwards: from the dotted-and-spaced directory
and from the symlinked one, the transcripts are found.
The bar widget still reverses a slug for display ("-home-smoido-Work" ->
"home/smoido/Work"), which is lossy in the same way but is only a label,
and only ever sees Linux paths.
Co-Authored-By: Claude Code <noreply@anthropic.com>
apiKeyHelper is a shell command line, not a path, so the raw value written
into settings.json was split at the first space. A Windows profile named
"Mohammed Ahmed" produced an attempt to run C:\Users\Mohammed, surfacing as
"your apiKeyHelper script is failing" with nothing to go on. Quote the value
when it contains anything a shell cares about, and leave it bare otherwise so
no existing settings.json churns on the next switch. The POSIX port had the
same bug against a /Users/First Last home; shlex.quote has exactly the wanted
"leave ordinary paths alone" behaviour.
doctor could not see any of this. It quoted the path itself before running it,
so it exercised a command line Claude Code never uses and passed while the
real one failed. It now reads the string out of settings.json, reports it when
it is not what a switch would write, and runs that string through a shell.
The helper itself exited 1 in silence on four distinct faults - no state, no
preset, no key, undecryptable key - collapsing them into one indistinguishable
message. Each now names itself on stderr, which is what /status displays. The
DPAPI case says what it actually means: a key stored by a different Windows
account than the one Claude Code runs as. Success paths stay silent, so stdout
still carries the key and nothing else.
Also make install.ps1 survive a Restricted execution policy: piped through
iex it is not subject to the policy, but invoking the installed script for the
key prompt is, which is where a fresh install died. Set Process scope for the
install, offer to set CurrentUser to RemoteSigned, and clear the
mark-of-the-web that Expand-Archive can leave on the extracted scripts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Source of truth so far has been c:\Users\smoido\projects\cli on the Windows
box, which has no git history of its own. This is that tree copied verbatim over
SSH, minus dist/ - the PowerShell build, the POSIX port under linux/, and the
presets both share.
Recorded as its own commit so that everything after it is a reviewable diff
rather than an undifferentiated first drop.