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.
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.
- 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.
- 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.
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.
- 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.
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.
The frontend map of session placeholder names missed
"Antigravity (Session)", so the provider column showed the raw English
name and truncated it. Add the entry and a test that every placeholder
name in usage_stats.rs is translated.
No other app shows a pricing note above the usage tables, although
Codex, Gemini and the rest also estimate session-log amounts from API
prices. Remove the note, its locale key and the test that pinned it.
The card keeps one line per group (Gemini, Claude/GPT) to fit, so the
5-hour and weekly windows could not both be seen anywhere on it. On
Antigravity cards the quota column is now one dropdown, like the resets
dropdown on Codex official cards, listing every window with its own
reset time. Refreshing moves to the ↻ button that appears on hover.
Other cards keep click-to-refresh.
An official Antigravity card bound to an Auth Center account showed the
quota of whatever account agy was signed in with, which is a different
account until the card's terminal is opened. It now shows the bound
account's quota, shared with that account's row in the Auth Center, and
like Codex account cards it shows even when the card is not current.
Cards that follow agy's sign-in, and platforms that cannot switch
accounts, keep the signed-in quota.
Official Antigravity cards asked agy's own /usage command for quota, which
starts agy and takes seven to nine seconds. On macOS the card now takes the
account agy is signed in with from the keychain, adds it to the Auth Center
if it is new, and asks Google directly with CC Switch's copy of the token,
the same way the Auth Center does (about one second).
agy's /usage is still used when the keychain holds no recognizable login,
when the user removed that account from CC Switch, and on platforms that
cannot switch accounts.
Every Antigravity row in the Auth Center now has a quota column, the
same one Copilot, ChatGPT and xAI rows use: Gemini and Claude/GPT, each
with a 5-hour and a weekly window. It is queried once when the page
opens and on the refresh button, through agy_account_quota, so accounts
agy is not signed in with show quota too. Accounts that need to sign in
again get no quota column.
Accounts that Google does not allow to use Antigravity, such as those
restricted by age, get the same 403 "You do not have a valid license,
contact your administrator" from the quota endpoint. That reads like a
CC Switch or licensing problem. On a 403 the quota query now asks
loadCodeAssist once, and when the account only has ineligible tiers it
shows Google's own reason instead.
The Auth Center can now show quota for every Antigravity account, not
only the one agy is signed in with. The new agy_account_quota command
calls Google's retrieveUserQuotaSummary with the account's own token, so
no switch is needed. The response carries the same buckets as agy's
/usage, and both now go through one tier builder.
The endpoint only answers clients that identify as Antigravity, so the
User-Agent is "cc-switch/<version> (antigravity)": it names CC Switch
and adds the word the server checks for. It only reads quota; it never
calls a model, warms up accounts or switches on low quota.
Tokens that expire within two minutes are refreshed first. The refreshed
token is saved only to CC Switch's copy, never to the keychain: agy keeps
refreshing its own login, and Google does not rotate the refresh token,
so both access tokens stay valid. For the account agy is using, the
keychain login is absorbed first so its newer token is used. A rejected
token triggers one forced refresh; invalid_grant marks the account as
needing sign-in again.
Third-party upstreams such as OpenCode Go, OpenRouter (deepseek/
namespace) and the Hermes presets call DeepSeek V4.1 Flash
deepseek-v4.1-flash. Pricing had no row for that id: none of the lookup
fallbacks turn it into deepseek-flash, and the prefix fallback only
matches longer ids. These requests showed "Unpriced" and were counted
at $0.
Add a deepseek-v4.1-flash row at the same official peak price as
deepseek-flash (0.3 / 1.2 / 0.006 per million). Default prices are added
again on every start, so existing installs get the row on next launch.
Requests already logged are not repriced.
An official Antigravity card can now pick one of the Auth Center accounts
(macOS only). With an account picked, Open Terminal first switches agy's
global sign-in to it; when it is already signed in, nothing is written.
Without one, the card keeps following whatever agy is signed in with.
The binding is stored as authBinding with authProvider "agy_oauth". The
account id is the Google subject, so a card synced to another computer
works once the same account is added there; if it is missing, opening the
terminal stops with a message instead of launching under another account.
Platforms that cannot switch ignore the binding and keep it on the card.
The Open Terminal dialog says when opening will switch agy's sign-in, and
warns when agy is already running. The Auth Center counts bound cards as
users of an account, so the removal dialog lists them.
On macOS the Auth Center now lists Antigravity accounts next to Copilot,
ChatGPT and xAI. The account agy is signed in with is tagged "In use by
agy", and "Switch agy to this account" in the row menu replaces agy's
sign-in. After switching, CC Switch checks whether agy is running and
warns that open sessions keep the old account and may write it back.
Adding an account opens Google's sign-in page in the browser. The shared
login hook treats a response without a user code as this browser flow: it
leaves the clipboard alone, polls the local result every 1.5 seconds and
cancels the backend flow so the callback port is released. The pending
block shows a "reopen sign-in page" button instead of a device code.
Removing an Antigravity account says that agy stays signed in.
The backend now records the sign-in time in seconds, which is what the
Auth Center formats.
agy keeps a single global login in the system keychain (service "gemini",
account "antigravity"). CC Switch now keeps its own copy of each account
and switches agy by writing the chosen one back into that entry.
- Sign in through the browser with agy's own OAuth client (PKCE, loopback
callback on 127.0.0.1), requesting the same scopes agy does, so agy can
keep refreshing the token on its own.
- Read and write the keychain through /usr/bin/security, the program the
entry already trusts: the value goes over `security -i` stdin, `-U`
updates in place and keeps the entry's access control, and the write is
read back and checked.
- Before overwriting, take back the current login: refresh tokens agy
renewed go to their account, an unknown account is added, and an
unreadable value is stashed to agy-login-stash.json.
- Removing an account only drops CC Switch's copy; agy is never signed out.
- Exposed as the "agy_oauth" provider of the generic auth_* commands, where
the default account is the one agy is signed in with. macOS only for now.
The usage dashboard listed every known app as a filter chip regardless of
the visibility switches on the Apps page. Filter the chips the same way the
session manager does: Claude Code stays while Claude Desktop is shown, and
the currently selected app is kept even if it has been hidden.
Since route and aggregation mode keep the ChatGPT login (#8431), the
route table writes requires_openai_auth = true whenever a login is on
disk, so making a third-party card the aggregation default no longer
keeps Codex from locking the send button to Luna Reserve once the
official 5-hour quota runs out.
The guide and the 4.6 manual pages now describe the method that still
works: switch to Direct, keep "Keep official login for direct switches"
off, switch to a third-party card and fully restart Codex; switch back
to Aggregation once the quota resets. The old method is marked as no
longer working.
Refs #7962
Add a "Create a desktop shortcut" checkbox to the MSI install-directory page and a public CREATEDESKTOPSHORTCUT property for silent installs. Upgrades keep the current state of the desktop shortcut, and clearing the option during an upgrade removes the old one.
The desktop shortcut is now copied from the Start Menu shortcut by custom actions with Return="ignore", so a Desktop that cannot be written no longer blocks or rolls back the install.
Fixes#5667
Co-authored-by: thefool <thefoolyuzhe@gmail.com>
The MSI now carries REBOOT=ReallySuppress, so Windows Installer never restarts the machine when a file is in use during an in-app update, including updates driven by older updaters. The updater also passes REBOOT=ReallySuppress, a leftover instance is closed on the updater path, and the release workflow fails if the built MSI lacks the property.
Fixes#8534
The long paragraph above the Antigravity form explained a case that should
not exist: a non-official card without a key is just the official login
card again. Drop the paragraph and warn on save when a custom card has no
API key, as Gemini CLI already does. A key without an endpoint stays valid
and uses Google's default API.
The endpoint and model rules move into "?" tips next to their fields. The
card's terminal button now reads "Open Terminal with This Provider"; the
shorter "Open Terminal" label stays for the Claude Code card menu.
CC Switch can only reach the agy it starts itself: a third-party key
travels in that process's environment, so "enabling" a key card never
changed what a plain `agy` used. Antigravity cards now only launch.
- Every card's primary action is Open Terminal; there is no "in use"
state, the official quota shows on official cards at all times, and
any card can be deleted.
- Official cards start `agy --model=<display name>`, which applies to
that session only. Key cards keep their private --gemini_dir.
- The real ~/.gemini/antigravity-cli/settings.json is never written any
more: switching is refused, adding the first card, full syncs and
deep links with enabled=true leave it alone. The only remaining write
removes the modelProvider left behind by the #8268 preview.
- The terminal dialog and form hints no longer talk about switching.
agy writes a zero-byte ~/.gemini/config/mcp_config.json the first time it
starts. Parsing it as JSON failed, so enabling any MCP server for
Antigravity showed "Invalid Antigravity MCP JSON" and importing from it
failed too. Treat a blank file as having no servers.
- Use the Antigravity logo for the app instead of reusing Gemini's.
- List Antigravity CLI on the Apps page: install with the official script,
update with `agy update` (also for the Homebrew cask, which is
auto_updates), read the latest version from the release manifest, and
uninstall official-script and cask installs.
- Find install.ps1's %LOCALAPPDATA%\agy\bin on Windows.
- agy stores every prompt wrapped in <USER_REQUEST>, followed by context it
adds itself (<ADDITIONAL_METADATA>, <USER_SETTINGS_CHANGE>). Session titles
and user messages now show only the text inside <USER_REQUEST>; an empty
request keeps the original content.
- The official-quota hint said CC Switch reads the CLI's OAuth credentials.
For agy it runs agy's own /usage command instead, so the usage dialog now
says that (and that a check takes about 10 seconds).
- Ask agy's own /usage command (agy -p /usage --output-format json) instead of
reading credentials; it makes no model call and opens no conversation.
- Keep the Gemini group and the Claude/GPT group, each with its 5-hour and
weekly window, as four tiers; unknown buckets are skipped.
- On the card each group shows only its window with the least left, since
four tiers do not fit; the expanded view lists all four.
- A missing agy is treated as no login (nothing shown); a timeout propagates
so the last good value is kept; other failures do not echo agy's output.
- Store the agy prompt in ~/.gemini/config/AGENTS.md, which agy reads as a
standalone customization root and every private key profile links to.
- List sessions recorded under each key profile and resume them with the
matching --gemini_dir and the card's key in a launched terminal.
- Fetch the model list for API-key cards from agy's own offline catalog in a
throwaway profile, so the form offers display names agy actually accepts.
- Record agy session usage under priced model IDs (display name kept as the
request model) and estimate cost from Gemini API prices.
- Treat an imported settings file with a model as the official login card.
Ported from #8608:
- Model discovery works before agy has written a settings.json; the
official login lives outside that file.
- Configurations imported from live are marked official: the key is never
on disk, so an import is always agy's own login.
- Antigravity provider cards show the configured model.
- Session usage rows take their model from the `Model Selection` step agy
injects at the start of a conversation, since responses carry no model.
Not ported: running `agy models` while importing on startup or when the form
opens. It takes several seconds on a signed-in machine and may prompt for
sign-in on one that never logged in; the Fetch Models button stays explicit.
Co-authored-by: ajia1206 <491387123@qq.com>
agy only reads GEMINI_API_KEY and GOOGLE_GEMINI_BASE_URL from the process
environment, and modelProvider is global. The previous approach wrote the key
to a file, installed a wrapper script and edited .zshenv/.zprofile/.zshrc so
every agy picked it up.
Now the native settings.json always stays in login mode: login cards set the
model (display name) and drop modelProvider; key cards only drop
modelProvider. A key card applies to agy started from the card's "Open
Terminal": `agy --gemini_dir=<private dir>` under ~/.cc-switch/agy/<id>, whose
settings carry modelProvider and the model and whose config links back to
~/.gemini/config so MCP, Skills and prompts stay shared. The key and endpoint
reach that one process through the environment only.
The terminal launcher now takes a per-app launch description (command, env,
temp files) so Claude Code and agy share the terminal layer. On macOS/Linux
env values are read from a 0600 file that is deleted right away and passed
via `env`, so values exported by the user's rc files cannot override them;
on Windows they live in a setlocal block. Claude's temporary settings file is
now written 0600 as well, and temp files are removed before handing over to
the interactive shell (exec skipped the EXIT trap).
On startup, wrappers, env files and PATH blocks left by earlier test builds
are removed (only CC Switch-marked content) and the current provider is
reapplied so modelProvider returns to login mode.
Add Antigravity CLI (agy) as a managed app: provider switching for the native login and API-key modes, MCP and Skills sync through ~/.gemini/config, session browsing and session-log usage import.
Co-authored-by: peterLwk888 <lwk1124@outlook.com>