On Windows, pointing the Hermes config directory at a WSL path
(\\wsl$\<distro>\... or \\wsl.localhost\<distro>\...) only redirected
config, sessions and the version probe. When the Web UI was offline, the
"launch dashboard" action still opened a Windows terminal running
`hermes dashboard` natively, so it started the Windows hermes (or failed
with command not found) instead of the instance inside the distribution.
Build the dashboard command line from the existing per-tool WSL distro
detection: for a WSL override run it as
`wsl.exe -d <distro> -- sh -c "\"${SHELL:-sh}\" -lic 'hermes dashboard'"`
so the user's login shell PATH (e.g. ~/.local/bin) applies. Only one
shell flag is used, since a Ctrl+C exit is non-zero and a fallback retry
would relaunch the dashboard. The Web UI probe on 127.0.0.1 is unchanged;
WSL2 forwards localhost to the distribution. Other platforms keep the
plain `hermes dashboard`.
Refs #8689
Official Antigravity cards on macOS often showed only the Claude and GPT
quota. Google's retrieveUserQuotaSummary decides what to return from the
User-Agent: "cc-switch/<version> (antigravity)" passes the license check
but gets only the Claude and GPT group, and the Gemini group is returned
only to "antigravity/cli/<version>" from 1.2.0 on. agy's own /usage sends
that, which is why the fallback path always showed all four tiers.
The User-Agent is now "cc-switch/<version> (antigravity/cli/<agy
version>)". The version comes from the local "agy --version", probed once
per process, and falls back to 1.4.6 when agy cannot be found or read.
List V-API's mirror lines (api.vveai.com, run.v3.cm, api1.v3.cm,
api2.v3.cm, us.gpt.ge) as endpoint candidates after the main
api.gpt.ge in the Claude Code, Claude Desktop, Gemini CLI, Codex and
Grok Build presets, with /v1 on the OpenAI-format apps.
Qianwen / Bailian Token Plan Anthropic endpoints (e.g.
https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic) return 404 for
/v1/models and /models; the model list is only served under the
OpenAI-compatible path {root}/compatible-mode/v1/models. Append it as the last
candidate when the base URL ends with /apps/anthropic.
Co-authored-by: Claude Code <noreply@anthropic.com>
Move FluxA TokenPlan up to Shengsuanyun's former slot after V-API and
Shengsuanyun down to FluxA's former slot after SubRouter, in the four
README sponsor tables and the seven preset files that carry both.
Add V-API (gpt.ge) as a full sponsor placed after BuyToken: README rows
and banner in all four languages, the partner promotion copy, an inline
tile icon, and presets for Claude Code, Claude Desktop, Codex, Gemini
CLI, Grok Build, OpenCode, OpenClaw, Hermes and Pi. The gateway prices
and gates models per token group, so each preset notes which group it
needs. It has no undated claude-haiku-4-5, so the Desktop Haiku tier is
mapped to claude-haiku-5-5.
Add BuyToken as a full sponsor placed after DIT.ai: README rows and
banner in all four languages, the partner promotion copy, an inline tile
icon, and presets for Claude Code, Claude Desktop, Codex, Grok Build,
OpenCode, OpenClaw, Hermes and Pi. Keys are bound to a model group, so
each preset notes which group's key it needs. The gateway only accepts
the dated claude-haiku-4-5-20251001 ID, so the Haiku tier is mapped to
it. There is no Gemini preset because /v1beta is not served.
The provider key, the model ID and display name column headings, and
the context window and max output labels now use REQUIRED_LABEL like
the rest of the provider forms, instead of a hand-written "*" span.
The asterisk is drawn by CSS, so it no longer becomes part of the
label text that screen readers and tests read.
The provider key names the provider node in models.json, so it is now
shown for presets too (prefilled, editable) instead of only for custom
and edited providers, matching Kimi Code. A key that is already used
by another Pi card is flagged under the field and blocks saving, so
adding the same preset twice no longer fails only at the backend.
Models are now one row each (expand arrow, model ID, display name,
delete) with capabilities and the context window in the expanded
details, instead of one large card per model. Fields follow the same
order as Pi and OpenCode (API format, API key, base URL), the API
format menu shows readable protocol names, request headers are shown
inline, and required fields use the shared asterisk style.
The new display name column writes the model's display_name, which
Kimi Code shows in /model; clearing it removes the field. The model
selection hint moves into a "?" beside the models heading, and the
Kimi-only labels that duplicated OpenCode's are removed from all
locales.
Small buttons shrink slightly while pressed. On the full-width,
left-aligned "Advanced options" toggle the shrink is centered, so the
label visibly shifts. Keep these toggles at full size in the Claude,
Codex and Kimi Code forms.
Custom headers are rarely needed for Kimi Code providers, so keep them
under an "Advanced options" section like the Claude and Codex forms.
It opens by itself when headers are already set, from an existing card
or a preset that carries them.
"Always thinking" and "Load tools on demand" don't say what they do.
Add a "?" help tip next to each in all four locales: the first is for
models whose thinking can't be turned off, the second only works with
Kimi's own models and needs KIMI_CODE_EXPERIMENTAL_TOOL_SELECT. Move
"Always thinking" next to the last one so the common three come first.
models.dev moved OpenCode Zen and Go to /inference/... endpoints, while
presets and existing providers still use /zen/... (both work and bill
the same). The endpoint lookup no longer found them, so models only
listed there, such as deepseek-v4.1-flash, filled nothing. Map the old
normalized addresses to the new ones; this applies to every app's fill.
The Kimi Code form only let users type model IDs and context windows by
hand, unlike the other apps' forms. Add the same "Fetch models" picker
and per-row dropdown: picking models adds them with tool use enabled,
then fills the context window, thinking and image input from the
matching preset or models.dev entry (the endpoint decides which). A
window the user typed is never overwritten; rows still on the
placeholder window take the looked-up value. Anthropic and Gemini
providers fetch with their own auth header.
The version column already says the tool is not installed, so the extra
"not installed or not executable" caption repeated it in English. A plain
not-found probe now reports no error; WSL failures with real diagnostics
(invalid shell, exec failed) still surface.
Kimi Code saves every /model pick as default_model, so the default shown
in CC Switch can go stale while the page stays open. The backend rereads
config.toml and refuses to remove the provider Kimi Code is using, but
the page kept showing the old default and an untranslated English reason.
Reread the additive app's live state after a failed removal as well as a
successful one (now including the OpenCode, OpenClaw and Hermes default
model queries), reread Kimi providers after failed edits and deletions,
and explain the two Kimi "model in use" refusals in all four locales.
Point --chart-usage back at --chart-1 and restore the heatmap's darkest
light-mode step (#0e7490), so the usage charts look as they did before
#8711. The separate token stays, so a later color change does not touch
the quota bars that also use --chart-1.
When the user sat on page 2+ and the total dropped (the "today" window
rolled over, Codex usage was rebuilt), the page index stayed out of
range. With a single page the pagination bar is hidden, leaving an
empty table with no way back. Pull the page back to the last valid one
once real (non-placeholder) data arrives.
Each FullScreenPanel adds its own keydown listener on window, and
stopPropagation does not stop other listeners on the same target, so
stacked panels (pricing -> pricing editor, edit provider -> endpoint
speed test) all closed on a single Escape. Track open panels in a
module-level stack and only let the top one react.
The default provider in OpenCode, OpenClaw, Hermes and Kimi used a gray
card and dot. Use the same blue as an in-use switch-mode card instead,
and drop the now-unused neutral card tone.
Kimi is additive, but both card groups only offered "Switch", which
wrote the provider into config.toml and changed default_model at once,
so the Added / Available split did nothing.
- Available cards get "Add": a new add_provider_to_live_config command
writes the provider and its models into config.toml without changing
default_model, so they show up in Kimi's /model list. A node already in
config.toml belongs to Kimi and is never overwritten from the archive.
- Added cards get a "Remove" button; "Set as default" (select the
provider's model) moves into the more menu, as for OpenCode/OpenClaw.
- The provider owning default_model is marked with the gray "Default"
status like OpenCode, and cannot be removed.
Kimi Code shows a provider's key as its name in the model picker, so
separate accounts appeared as "kimi-code-ccs-<hash>". New separate
accounts are now keyed "Kimi 订阅 <nickname>" ("Kimi Subscription" when
CC Switch is not in Chinese), matching Kimi's own label for its default
login and limited to the characters Kimi accepts, numbered on clashes.
The credential slot and model aliases keep the stable
"kimi-code-ccs-<hash>" name, so default_model stays an ASCII key and
ownership checks are unchanged. A returning account is also recognised
by its credential slot, not only by the local identity cache. Card names
fall back to the readable key when no nickname is cached.
Kimi's account list drew its own layout (inline description, a permanent
region picker, highlighted current row, extra badges). It now uses the
same ManagedAccountsGroup as Copilot, ChatGPT, xAI and Antigravity:
help behind "?", region chosen when adding an account, "In use by Kimi"
badge, nickname and avatar, shared pending/error blocks, and a
"needs re-login" state for signed-out accounts.
The shared group gains optional per-row remove label and disabled
reason, an optional "remove all" action, and a looser device-code type.
Account action failures now toast instead of reusing the login error
block. Unused Kimi strings are removed from all locales.
The icon-only star on added OpenCode/OpenClaw cards was hard to read and
had no selected state. "Set as default" now sits at the top of the card's
more menu, like the aggregation page; providers with several models open
a submenu to pick the default model. The card keeps only the remove
button, and the primary button no longer needs its own dropdown.
Follow-up to #8726, which lets the Authorization Center hold several Kimi
official subscriptions, each with its own provider and credential slot.
Identity: after sign-in (and for logins found on disk) read /me to show
the account's nickname on its row and card. Signing in to an account that
is already listed writes back to its slot instead of adding a duplicate,
and the slot is chosen when the login completes, so a native Kimi login
made meanwhile is never overwritten. New separate slots are named from a
hash of region and user id, stable across devices. Opening the Kimi page
reads identities too, so card names carry the nickname without a visit to
the Authorization Center first.
Remove instead of sign out: a separate account is deleted together with
its provider node, models, credential and card (refused while in use);
web search/fetch pointing at it moves to Kimi's own default login. Kimi's
own login is only hidden from the list - CC Switch never logs Kimi out -
and stays hidden across Kimi's token rotations until the user signs in to
it again here. An empty list caused by that says so instead of "signed
out".
Quota: each account row and official card shows its quota. Tokens close
to expiry are refreshed under the same proper-lockfile-compatible lock
Kimi uses (mkdir <slot>.lock, 5 s stale takeover), with Kimi's revoked
token recovery. Windows, where Kimi takes no lock, only reads.
Also: re-login keeps custom fields such as custom_headers on the node;
deleting a card whose node was removed elsewhere deletes the leftover
CC Switch-owned credential; the card's switch button works for official
accounts after checking they are signed in on this machine; account rows
use the same compact layout as the other Authorization Center groups.
The parent_rollout_* tests asserted on the total size of the process-wide
CODEX_REPLAY_CACHES.parent_timelines map. #[serial_test::serial] only
excludes other serial tests, while non-serial tests such as
test_parse_codex_file_recognizes_branch_thread_paginated_continuation
reach load_parent_timeline (via resolve_continuation_baseline) and insert
their own entries concurrently. Under multi-threaded `cargo test` that
occasionally turned an expected count of 1 into 2; nextest's
process-per-test model hid it in CI.
Count only entries under the test's temp dir instead. Production code is
unchanged.
Picking a model from the fetched list fills its empty catalog fields from
known metadata (same-endpoint preset, models.dev). For a model the lookup
reported as text-only, the Codex fill wrote inputModalities ["text"].
For Codex an absent inputModalities is not "unknown = text": the backend
checks the exact text-only registry and otherwise fails open to
["text", "image"]. Writing ["text"] therefore switched image input off,
against the fill rule that modalities are only ever turned toward
"supported", and the Codex form has no control to switch it back, so
Codex kept refusing image attachments for that model.
Only fill inputModalities when the metadata includes image; otherwise
leave it absent and let the registry and fail-open default decide.
Refs #8682
try_get_version still ran `$SHELL -lic "<tool> --version"` via a bare
output(), so when CC Switch was launched from a terminal on Linux/WSL the
interactive shell kept the controlling tty, got stopped by SIGTTIN, and
opening Settings -> About froze the whole app.
Spawn it like resolve_path_default / login_shell_path already do: stdin
null, isolate_child_process_group (setsid) and the 10s probe deadline.
-lic is kept so PATH entries from .bashrc are still picked up; a timeout
falls back to the existing directory scan.
Fixes#3241
In the merged (stack) catalog every third-party row shared one comp_hash,
"cc-switch", so Codex never compacted when switching between them (#8485).
Third-party GPT rows mirror the official entry and carry
`tool_mode: "code_mode_only"`, where shell calls are recorded as the
custom tool `exec`; DeepSeek, Kimi and other rows are direct and only have
`exec_command`. After switching from a GPT row to a direct row without
compaction, the direct model copied the `exec` calls from history and
every turn failed with `unsupported call: exec`.
Group rows by tool protocol instead: a row with a tool_mode gets
"cc-switch-<tool_mode>", direct rows keep "cc-switch" so existing
sessions see no change. Switching within a group still skips compaction;
switching across groups compacts with the previous model first, which
folds the other protocol's calls into the summary. tool_mode and
supports_search_tool keep the template values, so third-party GPT rows
keep code mode. The cost is that a cross-group switch away from a
provider that is no longer usable fails to compact, as before #8485;
switching within the group or starting a new session avoids it.
Fixes#8676
* feat(usage): restructure the usage page layout
- Header: one sync button that shows when it last synced, next to a settings
button; the settings sheet now holds data sources, display and pricing.
- Scope bar: date first, app chips, and one cascading provider / model menu.
- Overview: a selectable metric strip drives the trend chart and heatmap; the
four token parts fold into an expandable second row. Hit-rate axis starts
near the lowest value; hourly labels are short; new terracotta chart color.
- Detail panel: tabs with counts; tables scroll sideways at natural column
widths, pin the time / app columns, group columns with wider gaps, and
animate rows that arrive on refresh.
- Request detail drawer: no per-row dividers, no duplicate session rows.
- Empty values show an em dash; dark-mode surfaces use one token per role.
* feat(usage): let people pick how many request logs fit on a page
The pagination bar gets a rows-per-page menu (10 / 20 / 50, default 20),
remembered on this device. It stays visible on a single page as long as
there are more rows than the smallest size, so a larger choice can still be
undone. Changing the size keeps the first visible row on screen, and the
rows it adds are not animated as newly arrived requests.
* fix(usage): polish details found in review
- New-row highlight keeps a pinned column's edge shadow instead of replacing it.
- Pinned cells only follow row hover on rows that can be clicked.
- Update a stale comment about the metric strip spacing.
* fix(usage): fix issues found in branch review
- Heatmap in hit-rate mode: a day with traffic but a 0% hit rate is now
shaded instead of looking idle, and the tooltip shows the hit rate.
The heatmap reuses the trend chart's hitRateOf instead of its own copy.
- Turkish: add the request log column labels and the columns menu title.
- Sync button: when auto scan is off, the tooltip says so.
- Columns menu: concurrent saves no longer drop later toggles; a failed
save reverts only once the last save returns.
- Tab counts query only the open tab; other tabs show their cached count.
- UsageDashboard uses the shared useElementWidth instead of its own copy.
- Note the two-column limit of the pinned-column offset.
* fix(usage): keep heatmap tooltip rows on one line
The added hit-rate row made the label column wider, so the card near
the right edge wrapped values like "7640.5 万" and the label itself.
* fix(usage): shorten the auto-scan-off sync tooltip to one line
The longer sentence wrapped inside the pill-shaped tooltip and broke
mid-word.
* fix(usage): open pricing as a sub-page so its editor can be used
Pricing lived in a modal Dialog, but the add/edit pricing editor is a
FullScreenPanel portaled into #content-area. The dialog's overlay sat
above it and Radix set pointer-events: none on body while the dialog was
open, so the editor's fields and save button could not be used. Pricing
is now a FullScreenPanel too, the same pattern as other nested panels.
* fix(usage): show exact metric values in the app tooltip
The metric strip used native title attributes for the exact numbers.
Use HoverTip instead, anchored to the number and opening to the right,
so it does not cover the expanded token row. No tip while the cell
shows a placeholder.
* feat(kimi): manage multiple official accounts in auth center
Isolate account credentials, providers and model aliases; preserve native logins and protect official OAuth nodes. Fix session image access, drop unpublished usage repair paths and the kimi-cli alias, and synchronize all four READMEs.
* fix(kimi): address account compatibility review feedback
Restore the native default credential slot for the first account, stop rewriting tool services on account switches, allow deleting profiles removed from native config, and fix official-account test fixtures and documentation.
* fix(kimi): pin OAuth endpoints to the login region
Use fixed official hosts for mainland and global sign-ins, keep existing account regions and credential slots during reauthentication, and preserve sanitized transport error causes. Add regional routing and network-error regression coverage.
Add Kimi Code as the 12th managed app (additive mode): native multi-provider config in ~/.kimi-code/config.toml (honors KIMI_CODE_HOME), presets derived from the Pi catalog, MCP (stdio/HTTP/SSE), Skills with native-directory protection, global prompts, session browsing/resume, and session-log usage import. Local routing and aggregation do not apply; official OAuth stays managed by Kimi Code. Schema v21 -> v22 adds enabled_kimi columns.
macOS GUI apps started from Finder or a login item inherit launchd's
soft RLIMIT_NOFILE of 256. The local router holds a client socket and an
upstream connection per in-flight request on top of the log, database
and config files, so a burst of parallel requests exhausted the
descriptors: accept() failed with EMFILE, upstream connects failed,
reading live-state.json failed and SQLite could not open the database.
Right after the logger is initialized, lift the soft limit to the hard
limit capped at 10240 (macOS OPEN_MAX; setrlimit rejects larger values
there). The hard limit is never touched and a limit that is already high
enough is left alone; failures are only logged.
Refs #8668
When CC Switch is launched from Finder, its PATH is launchd's narrow
/usr/bin:/bin:/usr/sbin:/sbin. run_detected_tool_command_with_timeout
located the tool through a login shell but then spawned it with only the
tool's own directory prepended to that narrow PATH. An npm-installed CLI
is a `#!/usr/bin/env node` script, so when node lives in a different
directory (codex in /opt/homebrew/bin, node in /usr/local/bin) the
"Restart Codex daemon" action failed with `env: node: No such file or
directory`. The same helper runs `opencode models`.
Merge the login-shell PATH in front of the inherited PATH, as tool
install/update already does. login_shell_path now honours the caller's
deadline and spawns like resolve_path_default (stdin null, own session);
when it fails or times out the previous PATH is used unchanged.
Refs #8653
Opening a terminal on Windows ran `cmd /C start ...` through
Command::output(). The console that `start` opens inherits the wrapper's
stdout/stderr pipe, so the pipe only reached EOF once the user closed
that window, and the command (and the open-terminal dialog) kept
spinning until then.
Point stdio at NUL and wait only for the wrapper's exit status. Launch
failures still surface through the exit code. Same approach as the
community PR #6716 by @umutcankurt, applied to the current code path.
Refs #6712
Add hao.ai as a full sponsor placed above Fenno: README rows and banner
in all four languages, the partner promotion copy, an inline tile icon,
and presets for Claude Code, Claude Desktop, Codex, Grok Build, OpenCode,
OpenClaw, Hermes and Pi. hao.ai serves Anthropic Messages under
/anthropic, OpenAI-compatible APIs under /v1 and xAI under /x-ai/v1, and
its catalog IDs carry a provider prefix. There is no Gemini preset since
the catalog has no Gemini models.
DIT.ai (dit.ai), an AI token exchange routing each request across
competing providers, joins as a sponsor, placed directly below GrooRoute
Pro in the sponsor order (OpenModel in the Gemini file, which has no
GrooRoute preset).
- One key on api.dit.ai serves Chat Completions, Responses, Anthropic
Messages and native Gemini (protocol docs at dit.ai/docs, catalog at
dit.ai/models, checked 2026-10-21).
- The gateway has no Haiku 5.5 and only the dated
claude-haiku-4-5-20251001, so Claude Code overrides the Haiku tier with
it and Claude Desktop maps Sonnet 5.5 / Opus 5.5 / that Haiku.
- OpenCode, OpenClaw, Hermes and Pi use Anthropic Messages with Claude
Opus 5.5 and Sonnet 5.5; Codex uses Responses with gpt-5.6-sol, Grok
Build grok-4.7, Gemini CLI gemini-3.8-flash.
- Four-locale README sponsor rows with a banner, four-locale promo copy
with the "ccswitch" coupon, and an inline icon from the official logo.
- Claude preset rows in the selector test go from 105 to 106.
ZetaAPI is no longer a sponsor; its presets go too.
- Drop the four-locale README sponsor rows and the banner.
- Remove the ZetaAPI presets from Claude Code, Claude Desktop, Codex,
Grok Build, OpenCode, OpenClaw, Hermes and Pi.
- Remove partnerPromotion.zetaapi from every locale, Turkish included.
- Keep the zetaapi icon: existing providers saved from the preset still
reference that icon key.
EveryAPI moves from the basic tier (in-app presets and website only) to
a full sponsor, placed directly below OpenModel in the sponsor order.
- Four-locale README sponsor rows with the new copy and a 1200px banner.
- The EveryAPI preset blocks in the seven preset files move below
OpenModel so file order matches the README tables; the presets
themselves are unchanged.
- The in-app promo copy adds "GPT from 15% of the official price" and
folds the first top-up bonus and the "ccswitch" code into one perk,
since they are the same offer.