Reflect the seamless-launch change: the app self-starts the background
server, ccs bar runs it detached, and add the serve/stop/status commands
plus the launch.json/server.pid/serve.log files.
The dropdown height defaulted to screenCap before the content was measured,
and the ScrollView frame had no alignment, so an unmeasured panel rendered at
nearly full screen height with the content centered in it (macOS then centered
the oversized window). Default to a modest height until measured and pin the
frame to .top, so the popover anchors under the menu bar and sizes to its
content, scrolling only on genuine overflow.
The app now port-probes for a live CCS server and, if none is found,
starts the recorded detached server itself (launch.json, with a login-shell
fallback) before connecting, so double-clicking the app works without the
CLI. The offline panel gains a Start CCS action and a starting state. The
dropdown is sized to its measured content up to a screen-aware cap instead
of a fixed maxHeight, so it no longer collapses and clips rows.
Refs #1526, #1527
ccs bar now spawns the web-server as a detached background process and
exits immediately instead of hosting it in the foreground, so closing the
terminal no longer takes the bar offline. Adds 'bar serve' (the long-lived
host), 'bar stop', and 'bar status', and records ~/.ccs/bar/launch.json at
install so the app can start the server without a shell PATH.
Refs #1526
Generate AppIcon.icns from the CCS mark at package time (sips + iconutil)
and reference it via CFBundleIconFile so Finder, Spotlight, and the Dock
show the brand icon instead of a blank bundle.
Refs #1525
Use version-aware CLIProxy Plus asset names so current
no-plugin releases install without breaking older fallback releases.
Tag latest-version caches with their GitHub repo and ignore
legacy or cross-repo cache entries.
Closes#1522
Some providers (e.g. Kimi, Anthropic-API mirrors) reject OpenAI-format
chat-completions requests and/or only accept requests from a recognized
coding-agent User-Agent (e.g. Claude Code, Roo Code, Kilo Code). The
OpenAI-compat proxy previously translated every profile's request to
OpenAI format and overwrote the User-Agent with a fixed sentinel,
which made these providers unreachable.
This change adds an opt-in Anthropic passthrough mode:
- New CCS_OPENAI_PROXY_PASSTHROUGH=1 env var on a profile opts it in.
- The base URL is auto-detected as Anthropic-style for known hosts
(api.kimi.com, api.minimax.com, api.anthropic.com) or any base URL
ending in /v1.
- In passthrough mode the proxy forwards the incoming Anthropic body
verbatim to the upstream /v1/messages endpoint, preserving the
original User-Agent (or x-stainless-user-agent) so coding-agent
provider checks pass.
- The Anthropic-format response is streamed back unchanged.
Adds:
- isAnthropicPassthroughProfile() + passthrough option on
resolveOpenAIChatCompletionsUrl/resolveOpenAIModelsUrl
- CCS_OPENAI_PROXY_PASSTHROUGH env var on OpenAICompatProfileConfig
- readRawBody() helper for the passthrough path
- Preserved User-Agent (or x-stainless-user-agent) on the upstream
request, falling back to CCS-OpenAI-Compat-Proxy/1.0
- Skip SSE response transformation in passthrough mode (upstream
already returns Anthropic-format bytes)
Tests:
- 9 new tests in upstream-url.test.ts covering auto-detection and the
passthrough URL contract
- 2 new tests in profile-router.test.ts covering the env var
Verified end-to-end against api.kimi.com: a request through the
modified proxy returned a real Kimi response (model kimi-k2p7-coding)
with the original claude-cli/2.1.170 User-Agent preserved.
- create-command: show the pool onboarding hint only after profile
creation fully succeeds; the pre-create placement burned the
once-per-install dismissal when creation failed or rolled back
- remote env (claude): skip the read-level stale-pin filter once the
migration marker exists - a post-migration pin is user-intentional
(e.g. an explicit --config pick equal to a historical default)
- unified-config loader: document that loadOrCreateUnifiedConfig never
writes to disk, so read paths on legacy installs stay side-effect free
Legacy profiles.json-only installs get pool guidance exclusively from
ccs doctor (once-per-install semantics); printing a migrate notice on
every account add nagged with no dismissal available. Also document why
type === 'account' profiles are Claude by schema in the onboarding
counter, so the filter is not misread as over-counting.
- pool --enable/--disable: refuse remote targets with manual config
guidance; resolve the lifecycle port instead of assuming 8317
- enablePoolRouting: roll back the pool flag when config regeneration
fails; already-enabled path re-runs regeneration (repairable state)
- pool opt-in prompt: gate on hasUnifiedConfig (legacy installs skip);
never auto-accept consent under --yes/CCS_YES
- order show (file mode): render selector pick order incl. residual
on-disk priorities and surface drift instead of alphabetical order
- order --reset: clear residual priority fields via management-API
PATCH when proxy runs, atomic direct write when stopped
- quota pool section: classify in-proxy 429 cooldowns as cooling with
reset times (graceful degradation when proxy or endpoint is absent);
honest paused label plus resume hint
- routing state: remote targets report pool as not manageable instead
of echoing the local flag; strategy/affinity apply warns when pool
routing overrides the change (CLI, API message, and dashboard)
- claude model-neutral: --config explicit pins survive the stale-pin
migration; remote env path filters historical default pins read from
claude.settings.json without mutating the file
- cross-lane guard: also checks account-profile CLAUDE_CONFIG_DIR lanes
for email overlap, not only the ambient ~/.claude login
- onboarding hint: opt-in copy naming ccs cliproxy pool --enable
- dashboard card: pool-ON shows drain-order pointer, pool-OFF shows the
enable command, local-only note for remote proxies (i18n x5)
- ccs cliproxy quota: per-provider Pool context section with routing
mode (pool on/off, strategy, affinity, effective retry cap), drain
order resolved exactly as the selector picks (incl. residual on-disk
priorities reordering display + drift warning), and per-account state
named distinctly: available / cooling-until-<time> / paused - these
produce different client errors so they read differently
- cooling visibility degrades honestly when pool cooling is disabled
(no cooldown data exists; renderer says so)
- dashboard routing card: pool badge + retry cap + drain-order hint in
compact and full variants via additive poolRouting state block
- no Bar summary fields, no per-session pin introspection (cut per
plan); policy_quota_unsupported semantics untouched
Part of #1464 account pools (phase 6).
- ccs doctor, native account launch, and ccs auth create now surface a
one-line pool suggestion when >=2 native Claude profiles exist and no
pool is enabled; shared once-per-install dismissal; TTY-gated; hint
failures can never break a launch (guarded, single config read)
- credential import from native profiles evaluated and REJECTED:
refresh tokens are client_id-bound and not replayable cross-client;
pool setup requires fresh OAuth per account (verdict report in plan)
Part of #1464 account pools (phase 5).
- ccs cliproxy accounts order <provider>: show effective drain order
exactly as the selector resolves it (priority bucket desc, then
file-name tie-break, Windows-only case folding to match upstream)
- --by-tier derives priorities from tier metadata where present;
claude pools state tier-unknown and fall back to file order
- --set for manual order; duplicate IDs rejected; priority always >= 1
- dual write path: management-API PATCH when proxy running (avoids
MarkResult persist clobber), atomic direct write when stopped
- drift warning when stored order no longer matches auth files;
usage-attribution stability covered by regression test
Part of #1464 account pools (phase 4).
The ci-workflow meta-test pinned the literal branch list [main, dev];
the account-pools epic adds its integration branch to pull_request
triggers so phase PRs get full CI. Assert main+dev coverage via
pattern instead of exact list.
- pool opt-in writes disable-cooling: false, routing.strategy fill-first,
session-affinity on (1h TTL), max-retry-credentials 3; all emissions
pool-gated so non-pool generated config stays content-identical
- cooling re-enable is safe on current CLIProxy binaries: the v5
disable-cooling workaround targeted upstream cooldown bugs fixed by
Apr 2026 (see plan archaeology report)
- informed-consent prompt at the 1->2 account-add transition enumerates
every provider with multiple accounts (instance-global effect) and is
gated per provider on spike-verified limit signals
- disablePoolRouting restores the non-pool config including
disable-cooling: true and prints single-account rollback guidance
- cross-lane guard warns when a pool account email is also active in a
native Claude profile (concurrency is the documented ban vector)
- routing strategy and affinity subcommands warn when pool routing
manages those keys
Part of #1464 account pools (phase 3).
Phase PRs target kai/feat/1464-account-pools instead of dev; without
this the full CI matrix never runs on them. Revisit at the final
epic-to-dev promotion PR.
- claude provider launch env no longer pins ANTHROPIC_MODEL/tier defaults;
one-shot migration strips CCS-written stale pins across all historically
shipped default generations while preserving explicit user pins
- TTY-gated once-per-install warning when a user profile named claude or
anthropic is shadowed by the built-in provider, with rename guidance
- account-safety ban messaging parameterized by provider; Anthropic
patterns gated to claude accounts only
- first-run notice that ccs claude routes through the local CLIProxy
instance
Part of #1464 account pools (phase 2).
The refresh spinner rendered after the version label, pushing the
version away from the header edge while a refresh was in progress. The
spinner now sits between the Spacer and the version label.
'open -a' only activates a running app, so suggesting 'ccs bar' as an
alternative to quitting could not load the new binary. The hint now
says to quit from the menu bar first, then run 'ccs bar' to relaunch
the updated app.
A right-aligned v{CFBundleShortVersionString} label fills the unused
header space next to the CCS name, styled like the subtitle and hidden
when no bundle version is available (e.g. swift run). The display
logic lives in CCSBarCore as a pure helper with ccs-bar-check
coverage, making the on-screen build identifiable after reinstalls.
The reinstall guard deleted the existing bundle before download, so a
transient download or extraction failure left no app on disk. The
archive now extracts into a hidden staging directory inside the
Applications folder; the old bundle is removed only after the new one
is verified in staging, then renamed into place. Every failure before
the swap leaves the previous install untouched, and staging is cleaned
up on all paths.
Three review/CI corrections to the install flow:
The compat handshake now runs before the quarantine-clear and launch
block. It is a server-side check unrelated to Gatekeeper, and the
previous ordering let a failed quarantine clear (always the case where
xattr is absent, e.g. Linux CI) skip the handshake entirely.
Reinstalls remove the existing bundle before extraction so the
post-extraction existence check actually proves the fresh bundle
landed; an unremovable bundle aborts install instead of extracting
over it.
Declining the launch prompt now prints the same run-ccs-bar hint as
the non-TTY path instead of ending silently. Install tests inject the
newer deps (clearQuarantine, isBarRunning, promptLaunch) everywhere
the defaults could touch host binaries, keeping results identical on
macOS and Linux runners.
A failed quarantine clear previously fell through to the launch
handoff, so a default-yes prompt (or --launch) opened the still
quarantined app straight into the Gatekeeper block. Install now ends
after printing the manual xattr guidance, with a hint to run 'ccs bar'
once quarantine is cleared; --launch does not override a failed clear.