- Route all `ccs cursor *` to handleCursorCommand (no profile-switching)
- Add --ccs-daemon marker to spawned process for stable PID validation
- Use random port in lifecycle integration test to prevent CI conflicts
- Remove unnecessary .toFixed(1) on integer tokenAge
- Validate PID belongs to cursor daemon via /proc before signaling
- Tighten detectProvider regex to avoid over-matching o-prefixed models
- Add integration test for daemon start→health→stop lifecycle
- Add void cast on discarded handleHelp() return value
- Update model catalog date comment
- Export detectProvider and formatModelName from barrel index
- Export CURSOR_SUBCOMMANDS from cursor-command.ts, import in ccs.ts
- Use getCcsDir() in tests instead of manual path construction
- Add handleCursorCommand routing test for unknown subcommand
- Add removePidFile() in safeResolve on failure to prevent stale PIDs
- Only send SIGKILL in stopDaemon if SIGTERM wait loop exhausted
- Check isDaemonRunning before model fetch to avoid 5s timeout
- Add port validation unit tests for startDaemon
- Change spawn stdio from piped to 'ignore' preventing buffer deadlock
- Replace setInterval with sequential setTimeout polling
- Fix TOCTOU in SIGKILL escalation (send directly without probing)
- Move CursorConfig interface to types.ts
- Change detectProvider default from 'openai' to 'unknown'
- Remove redundant removePidFile() when PID is null
- Add double-resolve guard in startDaemon with safeResolve wrapper
- Add port validation (1-65535) before Node.js script interpolation
- Fix misleading comment in stopDaemon (no PID file handling)
- Add getDaemonStatus test for no daemon running case
- Add stopDaemon tests for graceful non-existent PID handling
- Add comment explaining whitelist merge pattern in PUT /settings
- Add comment for bare 'ccs cursor' fallthrough (differs from copilot)
- Add unit tests for cursor settings routes validation logic
- Tests for null/non-object/array body rejection
- Tests for port validation (integer, range 1-65535)
- Tests for auto_start/ghost_mode boolean validation
- Tests for whitelist merge (ignores unknown properties)
- Tests for /settings/raw validation and file operations
- Tests for mtime conflict detection
All web-server tests pass. Pre-existing test failures unrelated.
- Add SIGKILL escalation in stopDaemon after SIGTERM timeout
- Document router difference between cursor/copilot help behavior
- Fix detectProvider to handle o1/o4 models via regex pattern
- Add fetchModelsFromDaemon fallback test for unreachable daemon
- Update CLAUDE.md help table with cursor command entry
- Wire cursor command into main router (ccs.ts) following copilot pattern
- Centralize default port (4242) and model (gpt-4.1) as constants
- Remove duplicate types already in cursor-protobuf-schema from dev
- Handle daemon exit code 0 before health check succeeds
- Export PID helpers and model utils for testability
- Add unit tests for cursor-daemon (PID file ops, isDaemonRunning)
- Add unit tests for cursor-models (detectProvider, formatModelName, defaults)
HIGH PRIORITY FIXES:
- Extract shared buffer parsing logic into parseProtobufFrames generator
method (DRY violation fix) - both JSON and SSE transformers now use
common frame parsing loop, eliminating ~60% code duplication
- Use COMPRESS_FLAG constants instead of hardcoded 0x01/0x02/0x03 in
parseConnectRPCFrame for better maintainability
MEDIUM PRIORITY FIXES:
- Return empty buffer on gzip decompression failure (prevents silent
data corruption) - ALREADY FIXED
- Add debug warning for unknown message roles in convertMessages
- Create GitHub issue #535 for FIELD namespace refactoring follow-up
- Add test coverage: transformProtobufToSSE, error paths, unknown roles
LOW PRIORITY FIXES:
- Rename checksum test to clarify timestamp granularity (~16 min)
- Fix debug log function name in SSE transformer - FIXED BY REFACTOR
- Add comment to TOOL_RESULT_RESULT field documenting future use
All tests pass (1593 pass, 0 fail)
All validation checks pass (typecheck + lint + format + tests)
- Add comprehensive unit tests for cursor-auth.test.ts
- validateToken: valid/invalid tokens, short tokens, UUID formats, empty strings
- extractUserInfo: JWT parsing, email handling, non-JWT tokens, malformed base64
- saveCredentials/loadCredentials: round-trip, invalid JSON/types, missing fields
- checkAuthStatus: authenticated/not authenticated, expired tokens, JWT exp, invalid dates
- deleteCredentials: delete existing/non-existent files, multiple deletes
- All tests use CCS_HOME env var for isolation, real file I/O, no mocks
- Fix dead try-catch around new Date() in checkAuthStatus()
- Replace try-catch with isNaN check (new Date('garbage') returns Invalid Date, not throw)
- Properly handle Invalid Date by checking isNaN(getTime())
- Fix email populated with sub claim in extractUserInfo()
- Change email: decoded.email || decoded.sub to email: decoded.email || undefined
- Prevent non-email values (UUIDs) from populating email field
- Add type guards for JSON.parse result in extractUserInfo()
- Cast to Record<string, unknown> and validate types
- Use typeof checks for email, userId, exp fields
* feat(cliproxy): add account safety guards to prevent Google account bans
Implements cross-provider isolation to prevent Google from flagging
concurrent OAuth usage across different client IDs (ref: #509, #512).
Three pillars:
1. Auto-pause enforcement at session launch — conflicting accounts in
other Google OAuth providers are paused so CLIProxyAPI can't use them,
restored on session exit with crash recovery via auto-paused.json
2. Ban/disable detection — error responses matching Google ban patterns
auto-pause the affected account to prevent further damage
3. Cross-provider conflict warnings during OAuth registration
Key design decisions:
- PID-based session tracking for crash recovery (dead PID = restore)
- Timestamp comparison prevents restoring ban-paused accounts on exit
- Schema validation on auto-paused.json prevents corrupted state
- Falls back to warn-only when another session is managing isolation
* fix(cliproxy): address code review feedback (attempt 1/5)
- Re-read auto-paused.json before write in enforceProviderIsolation to
reduce concurrent write race window
- Use actual email from registry for display instead of raw accountId
- Export maskEmail for testability
- Add 27 unit tests covering ban detection, email masking,
cross-provider duplicate detection, enforcement lifecycle,
crash recovery, and timestamp-guarded restore
* fix(cliproxy): address remaining review feedback (attempt 2/5)
- Add handleBanDetection test verifying account pause on ban error
- Add warnCrossProviderDuplicates tests (true/false/non-Google)
- Document PID reuse limitation in isPidAlive JSDoc comment
Config generator now outputs fork:true for Claude model alias entries,
ensuring both upstream (claude-*) and aliased (gemini-claude-*) model
names appear in /v1/models listings. Also preserves fork flag when
parsing user-added aliases during config regeneration.
Bumps config version to v7 to trigger regeneration on next ccs doctor.
Closes#522
* fix(hooks): isolate image type check before error-prone processing
Restructure processHook() into two phases so non-image Read calls
never see hook error messages. Phase 1 defensively checks tool name
and file extension, exiting 0 silently on any failure. Phase 2 only
runs for confirmed image/PDF files where errors are relevant.
Closes#511
* fix(hooks): sync image analyzer hook file on every profile launch
Add installImageAnalyzerHook() call to cliproxy executor, matching
the existing installWebSearchHook() pattern. This ensures the .cjs
file in ~/.ccs/hooks/ gets refreshed from the npm package on every
launch, so users receive hook updates after npm update.
* fix(cliproxy): migrate deprecated gemini-claude-* model names to upstream claude-* names
CLIProxyAPI registry no longer recognizes the gemini-claude-* prefix convention.
Model names in catalog, base config, and user settings are migrated to upstream
claude-* names. Auto-migration in env-builder rewrites existing user settings on
load and persists the change.
Closes#513
* fix: address code review feedback — sync UI layer and add migration tests
- Sync UI isNativeGeminiModel() with backend (remove gemini-claude- exclusion)
- Update UI model catalog agy entries from gemini-claude-* to claude-*
- Update CI/CD workflow and code-reviewer default model names
- Add unit tests for migrateDeprecatedModelNames() logic
- Convert 4 module-level constants to lazy-evaluated functions to avoid
import-time caching: openrouter-catalog, aggregator, disk-cache, auth-middleware
- Fix symlink-checks.ts to use ccsDir parameter instead of homedir/.ccs,
remove unused homedir parameter from checkSettingsSymlinks()
- Replace TOCTOU existsSync+statSync with single statSync in try/catch
for --config-dir validation in ccs.ts
- Switch detectCloudSyncPath from substring to path-segment-boundary matching
to prevent false positives (e.g., megauser != MEGA, Dropbox-api != Dropbox)
- Add test for false-positive protection
Allow users to relocate the entire ~/.ccs/ directory via CCS_DIR env var
or --config-dir CLI flag. Precedence: --config-dir > CCS_DIR > CCS_HOME > default.
Includes cloud sync path detection warning and doctor diagnostics.
Closes#507
- Add iflow, kiro, ghcp, claude to bash completion env block
- Add --shell flag validation matching --format pattern
- Add backtick injection test case
- P1: Fix bash completion $cliproxy_profiles scoping — inline profiles
in env block since variable is only defined at COMP_CWORD=1 scope
- P2: Detect account-based profiles and show specific error message
instead of generic "not found"
- P2: Show `ccs migrate` hint when unified mode is disabled and settings
profile resolution fails
- P2: transformToOpenAI omits empty entries at transform time instead of
relying on output filter (removes fragile coupling)
- P3: Add zsh and auto to --shell completions across all 4 shells; map
--shell zsh to bash in command handler since syntax is identical
- P3: Auto-detect PowerShell from SHELL containing pwsh on non-Windows
- Tests: 33 pass (+1 pwsh detection test, updated transform assertions)
- Fix P0: fish single-quote escaping uses '\'' (end-quote, literal, reopen)
instead of \' which fish doesn't support inside single-quoted strings
- Fix P0: profile arg parsing now skips flag values via findProfile() so
`ccs env --format openai gemini` correctly resolves to 'gemini'
- Add OPENAI_MODEL mapping from ANTHROPIC_MODEL in transformToOpenAI
- Add stderr warning when invalid env var keys are silently dropped
- Update --shell help text to mention zsh compatibility
- Add findProfile tests (6) and OPENAI_MODEL omission test
- Add settings profiles to zsh env completion (was proxy-only)
- Document intentional ANTHROPIC_MODEL omission in transformToOpenAI
- Use getCcsDir() in error hint instead of hardcoded ~/.ccs/
- Export parseFlag and add 5 unit tests for flag parsing
- Add fish and PowerShell single-quote escaping tests
Switch formatExportLine from double quotes to single quotes to prevent
shell metacharacter expansion ($(), backticks, etc.) when output is
consumed via eval. Also fix parseFlag to handle values containing =,
remove unused test imports, and add empty-output guard after format
transformation.
New `ccs env <profile>` command exports shell-evaluable environment
variables for OpenCode, Cursor, Continue, and other third-party tools.
Supports --format (openai|anthropic|raw) and --shell (auto|bash|fish|
powershell) flags. Auto-detects shell from $SHELL env var.
Closes#503
- Add 7 test cases covering SIGTERM/SIGKILL escalation, timer cleanup,
default and custom grace periods, and already-exited process edge case
- Add timer.unref() to prevent keeping event loop alive during shutdown
- Add comment explaining 10s grace period in headless-executor timeout
Replace permissive JSON Schema Draft-07 whitelist with strict
Gemini-compatible field set (22 fields). The "examples" field
in Claude Code tool schemas caused 400 errors from Gemini API.
Also strips other unsupported fields: $ref, $defs, oneOf, allOf,
additionalProperties, const, if/then/else, etc.
Safe change — sanitizer only runs for CLIProxy profiles (Gemini,
Codex, Antigravity), never for direct Anthropic API requests.
Closes#155
Track hasReceivedMessageDelta and hasReceivedMessageStop in both
streaming paths (pipe-through and SSE-processing). Conditionally
omit these events from buildSyntheticErrorResponse() when upstream
already sent them, preventing protocol violations.
Closes#491
* fix(cliproxy): disable 1M extended context for gemini-claude-opus-4-6-thinking
Antigravity backend only supports 256k context window for this model,
not 1M as previously declared. Set extendedContext: false with TODO
comment for easy re-enable when backend adds support.
Closes#490
* fix(cliproxy): strip [1m] suffix at runtime for models without extended context
Users with existing [1m] in saved agy.settings.json will now have it
automatically stripped at runtime when the model no longer supports
extended context. Prevents misleading context window claims.
* fix(cliproxy): guard against empty upstream SSE responses in agy profile
When CLIProxyAPIPlus drops unsigned thinking blocks during sub-agent
execution, the response stream can contain no content_block_start or
message_delta events. This causes Claude Code CLI to crash with
"No assistant message found".
Add empty response detection in the tool sanitization proxy's streaming
handler. Both the pipe-through and SSE-processing paths now track
whether meaningful content was received. If upstream sent data but no
content blocks on a 200 OK, a synthetic minimal valid SSE response is
injected to prevent the client crash and surface a clear error message.
Closes#350
* fix(cliproxy): improve empty response detection and add tests
Address code review findings:
- Remove message_delta from content detection (lifecycle event, not
content); only content_block_start indicates actual content
- Add try-catch in end handlers to handle client disconnects gracefully
- Add 3 integration tests: empty stream injection, normal stream
passthrough, 4xx/5xx non-injection
* fix(cliproxy): avoid duplicate message_start in synthetic response
Track whether upstream already sent a message_start event. When
injecting the synthetic error response, omit message_start if upstream
already sent one, preventing duplicate events in the SSE stream.
Addresses PR review feedback from ccs-reviewer[bot].
* test(cliproxy): add SSE processing path and real failure mode tests
Address code review observations:
- Add test exercising SSE processing path (sanitized tool names) for
empty response detection, ensuring both code paths are covered
- Add test mirroring real failure mode where upstream sends only
message_start then ends abruptly (no message_delta/message_stop)
- Document duplicate message_start assumption with inline comment
* fix(version): show active config path instead of deprecated config.json
The version command was using deprecated getConfigPath() which always
returned config.json path. Now uses getActiveConfigPath() which shows
config.yaml in unified mode or config.json in legacy mode.
* chore(release): 7.37.1-dev.1 [skip ci]
* fix(ui): use native dynamic import to fix Node 24 ESM/CJS interop
TypeScript compiles import() to require() when targeting CommonJS,
which breaks ESM packages like ora on Node 24. Use new Function()
to create native dynamic import at runtime, bypassing TS transform.
Closes#472
* chore(release): 7.37.1-dev.2 [skip ci]
* fix(env): strip ANTHROPIC_* from account/default profiles
Account and default profiles inherit process.env which may contain
stale ANTHROPIC_BASE_URL from prior CLIProxy sessions. This causes
ConnectionRefused errors when Claude tries to hit an unavailable proxy.
Settings-based profiles already handle this by explicitly injecting
their own ANTHROPIC_* values. This fix applies the same protection
to account/default profiles by stripping ANTHROPIC_* before spawn.
Closes#474
* test(env): add unit tests for stripAnthropicEnv
Address code review feedback from PR #475. Tests cover:
- Removing all ANTHROPIC_* keys
- Preserving non-ANTHROPIC keys
- Empty object handling
- Undefined value preservation
- Case sensitivity (only uppercase ANTHROPIC_)
- All ANTHROPIC_ prefixed variants stripped
* chore(release): 7.37.1-dev.3 [skip ci]
* feat(cliproxy): add extended context support for 1M token window
Add --1m and --no-1m flags to enable/disable 1M token context window.
Uses Claude Code's [1m] suffix mechanism.
Behavior:
- Gemini models: auto-enabled by default
- Claude models: opt-in with --1m flag
- New extendedContext field in model catalog
Also adds Claude Opus 4.6 to model catalog with extended context support.
Closes#103
* feat(ui): add extended context toggle in dashboard model config
- Add ExtendedContextToggle component for 1M token context window
- Add Claude Opus 4.6 (claude-opus-4-6-20260203) to model catalogs
- Mark Gemini and Claude models with extendedContext: true
- Toggle only appears when selected model supports extended context
- Auto-enabled info for native Gemini, opt-in info for Claude
Part of extended context feature implementation for issue #103.
* fix: address code review findings and CI failure
- Fix CI error: add missing 'provider' prop to ModelConfigSection
- Fix case sensitivity in applyExtendedContextSuffix
- Fix whitespace handling in stripModelSuffixes
- Handle --1m=value and --no-1m=value CLI patterns
- Add warning when --1m used on unsupported model
- Sync agy catalog: add extendedContext to gemini-3-pro-preview
- Extract isNativeGeminiModel to shared utility (DRY)
- Add 21 unit tests for extended-context-config
* fix(catalog): rename claude-opus-4-6-20260203 to claude-opus-4-6
* fix(ui): wire extended context toggle through component tree
- Add extendedContextEnabled and toggleExtendedContext to useProviderEditor hook
- Store setting as CCS_EXTENDED_CONTEXT env var in provider settings
- Pass props through provider-editor → model-config-tab → model-config-section
- Update UseProviderEditorReturn type with new properties
* fix(ui): apply [1m] suffix directly to model strings in settings
- Toggle now applies/strips [1m] suffix to all ANTHROPIC_*MODEL env vars
- Extended context detected by checking if any model has [1m] suffix
- Remove legacy CCS_EXTENDED_CONTEXT flag approach
- Add suffix utilities: applyExtendedContextSuffix, stripExtendedContextSuffix
- Raw Configuration now shows actual model values with [1m] suffix
* fix(qwen): update model catalog with correct context windows and tier mappings
- Update context window specs from official Alibaba docs:
- Qwen3 Coder Plus: 1M context (was 32K)
- Qwen3 Max: 256K context (flagship)
- Qwen3 Coder Flash: fast code generation
- Fix preset mappings for Claude tier equivalence:
- Opus → qwen3-max (flagship 256K)
- Sonnet → qwen3-coder-plus (balanced 1M)
- Haiku → qwen3-coder-flash (fast)
- Update provider descriptions to reflect 256K-1M context range
- Add all 7 Qwen models from CLIProxyAPI: qwen3-coder-plus, qwen3-max,
qwen3-max-preview, qwen3-235b, qwen3-vl-plus, qwen3-coder-flash, qwen3-32b
Closes#478
* fix(ui): only apply [1m] suffix to ANTHROPIC_MODEL, fix toggle refresh
- Only ANTHROPIC_MODEL gets [1m] suffix, not tier mappings
- Strip [1m] when looking up model in catalog to prevent toggle disappearing
- Fix odd page refresh when toggling extended context
* chore(release): 7.37.1-dev.4 [skip ci]
* fix(ui): remove duplicate import in use-provider-editor
* chore: address PR review feedback - sync comment and unused import
* chore(release): 7.37.1-dev.5 [skip ci]
* feat(cliproxy): add Opus 4.6 to Antigravity model catalog (#482)
* feat(cliproxy): add Opus 4.6 to Antigravity model catalog
- Add gemini-claude-opus-4-6-thinking as new default agy model
- Update preset mappings to route opus tier to Opus 4.6
- Bump CLIProxy fallback versions to v6.8.2
- Keep Opus 4.5 as previous flagship option
* fix(cliproxy): include oauth-model-alias in config generation
Root cause: CLIProxy config.yaml was missing Opus 4.6 alias because:
1. CLIProxyPlus startup migration is disabled (intentional)
2. CCS config generator never wrote oauth-model-alias section
3. Existing users with outdated aliases got 502 on Opus 4.6
Fix:
- Add DEFAULT_ANTIGRAVITY_ALIASES to config generator
- Generate oauth-model-alias section in config.yaml template
- Preserve claude-api-key and custom aliases during regeneration
- Bump config version to v6 to trigger auto-regeneration
- Update model catalog tests for new model count
* fix(cliproxy): preserve YAML indentation in extractYamlSection
- Replace .trim() with regex to strip only leading/trailing newlines,
preserving 2-space indent on claude-api-key children
- Skip standalone comments at col 0 in section boundary detection
- Update stale test description (4 → 5 models)
* chore(release): 7.37.1-dev.6 [skip ci]
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Skip saving registry when no new accounts discovered (prevents empty
accounts.json creation for invalid files)
- Skip merging empty provider sections to prevent empty provider entries
- Update test module cache clearing to include new modular submodules
(accounts/registry, accounts/index) for proper test isolation
The GLMT retry logic tests use dynamic imports and GlmtProxy
instantiation which are slower on CI runners than locally.
Increase default timeout from 5s to 30s to prevent flaky failures.