When models have thinking enabled (e.g. codex with thinking: auto), the
API returns content as [{ type: 'thinking', thinking: '...' }, { type:
'text', text: '...' }]. The hook reads content[0].text which is
undefined for thinking blocks, causing 'No text content in response'
errors that block the Read tool (exit 2).
Also fix cross-provider model fallback: getModelsToTry() unconditionally
appends DEFAULT_MODEL (gemini-2.5-flash) as retry, which 502s on
non-gemini provider routes (e.g. codex). Only use DEFAULT_MODEL when no
provider-specific model is configured.
Fixes: content[0].text -> content.find(b => b.type === 'text')
Fixes: cross-provider retry causing 'unknown provider' 502
Ref: #511
* fix(cliproxy): migrate deprecated gemini-claude-* model names to upstream claude-* names (#515)
* 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
* fix(hooks): isolate image type check before error-prone processing (#514)
* 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.
* chore(release): 7.41.0-dev.1 [skip ci]
* fix(cliproxy): add fork:true for Claude model aliases in config generator (#523)
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
* chore(release): 7.41.0-dev.2 [skip ci]
* feat(cliproxy): add account safety guards to prevent Google account bans (#516)
* 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
* chore(release): 7.41.0-dev.3 [skip ci]
* feat(cliproxy): runtime quota monitoring during active sessions (#529)
* feat(cliproxy): add runtime quota monitoring during active sessions
Adds adaptive background quota polling to detect and respond to quota
exhaustion during active CLIProxy sessions. Prevents rate-limit-driven
account bans by auto-cooling exhausted accounts and switching defaults.
- Adaptive polling: 300s normal, 60s at 20% threshold, stops at 0%
- Stderr warnings at 20%, boxed exhaustion alerts at 0%
- Cooldown + default switch on exhaustion (existing patterns)
- Configurable via quota_management.runtime_monitor in config.yaml
- Timer.unref() prevents blocking process exit
- monitorStopped guard for in-flight poll safety
Closes#524
* fix: address code review feedback (attempt 1/5)
- M1: Round quotaPercent display with Math.round() to avoid ugly floats
- M2: Rename exhaust_threshold -> exhaustion_threshold for consistency
with existing auto.exhaustion_threshold config field
- M3: Replace async not.toThrow() with direct await assertion pattern
* fix: address code review feedback (attempt 2/5)
- Remove .claude/agent-memory/ from tracking and add to .gitignore
- Unify cooldown_minutes default to 5 (was 10 in runtime_monitor, 5 in auto)
- Add threshold validation in startQuotaMonitor (warn > exhaustion)
- Document intentional post-switch monitoring gap in code comment
* chore(release): 7.41.0-dev.4 [skip ci]
* fix(cliproxy): mask email in ban detection and fix JSDoc default
- Use maskEmail() in handleBanDetection output for consistency
- Fix cooldown_minutes JSDoc: default is 5, not 10
* chore(release): 7.41.0-dev.5 [skip ci]
* fix(cliproxy): address all review feedback (Low + informational)
- Add sync constraint comment on process.exit handler (executor)
- Add TOCTOU race acceptability comment (account-safety)
- Mask email in handleQuotaExhaustion reason string
- Use realistic exhaustion_threshold (5) in test configs
* chore(release): 7.41.0-dev.6 [skip ci]
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Add ENOTFOUND, ENETUNREACH, EAI_AGAIN to noRetryPatterns to prevent
infinite retries on network errors
- Update E2E test to expect exit code 2 (block) when CLIProxy unavailable
- Fix debug message expectation in test
Model resolution priority:
1. provider_models[current_provider]
2. ANTHROPIC_MODEL from profile settings
3. DEFAULT_MODEL (gemini-2.5-flash)
Allows users to override vision model via profile's ANTHROPIC_MODEL.
- Empty model validation: check `model.trim()` before using
- 10MB boundary: use `>=` instead of `>` for consistent messaging
- Timeout clamping: ensure timeout is between 1-600 seconds
- Response stream error: add error handler for network failures
- Empty response validation: check before JSON.parse to prevent crashes
Changes ImageAnalysisConfig from providers array to provider_models
mapping for granular vision model control per CLIProxy provider.
Breaking change: config.yaml image_analysis section now uses
provider_models instead of providers/model fields.
Provider-to-model mappings:
- agy → gemini-2.5-flash
- gemini → gemini-2.5-flash
- codex → gpt-5.1-codex-mini
- kiro → kiro-claude-haiku-4-5
- ghcp → claude-haiku-4.5
- claude → claude-haiku-4-5-20251001
Hook checks CCS_CURRENT_PROVIDER against provider_models and skips
if no vision model configured for that provider.
Match websearch transformer format:
- Header: [Image Analysis via CLIProxy]
- Metadata: File name, size in KB, model used
- Separator lines for better readability
- Footer instruction for Claude
Intercept Read tool calls for image/PDF files and route through CLIProxy
with gemini-2.5-flash for vision analysis. Returns text descriptions
instead of blocking, enabling Claude to "see" images via proxy.
Key changes:
- Add image-analyzer-transformer.cjs hook script
- Add ImageAnalysisConfig type and loader
- Add hook installer and profile injector
- Add prompt templates for analysis customization
- Add e2e test suite (excluded from normal CI runs)
- Configure test:e2e script for manual testing
Environment variables:
- CCS_IMAGE_ANALYSIS_ENABLED: Enable/disable (default: 1)
- CCS_IMAGE_ANALYSIS_MODEL: Vision model (default: gemini-2.5-flash)
- CCS_IMAGE_ANALYSIS_TIMEOUT: Timeout in seconds (default: 60)
- CCS_CLIPROXY_API_KEY: API key for CLIProxy auth
- CCS_CLIPROXY_PORT: CLIProxy port (default: 8317)
Closes#426
Match WebSearch hook pattern:
- ENABLED by default for settings/cliproxy profiles
- DISABLED for native Claude accounts (account/default)
- User can override via config: hooks.block_image_read.enabled: false
This ensures CCS CLI users get context protection out-of-the-box
while native Claude subscription users are unaffected.
Add PreToolUse hook that intercepts Read tool calls on image files
(.png, .jpg, .webp, etc.) and blocks them with helpful message.
This prevents context exhaustion when image generation skills
produce multiple files and the agent tries to read them (each
image can consume 100K+ tokens).
Configuration:
- Enable via config.yaml: hooks.block_image_read.enabled: true
- Or env var: CCS_BLOCK_IMAGE_READ=1
Hook integration:
- lib/hooks/block-image-read.cjs - the hook script
- src/utils/hooks/image-read-block-hook-env.ts - config loader
- Integrated into all spawn locations (ccs.ts, shell-executor,
cliproxy-executor)
Closes#426
On Windows, globally installed CLI tools via npm/pnpm are .cmd/.bat
batch files, not real .exe executables. Node.js spawnSync() without
the shell option cannot execute these files and returns ENOENT error.
Changes:
- Extract isWindows as a global constant at file top
- Add shell: isWindows to all CLI execution spawnSync calls
- Remove duplicate isWindows declaration in isCliAvailable()
Fixes#378
Co-authored-by: Cursor <cursoragent@cursor.com>
Fixes#273
The websearch-transformer hook used hardcoded 'which' command which doesn't
exist on Windows. Now uses process.platform detection to choose 'where' on
Windows and 'which' on Unix systems.
Previously, WebSearch status showed "Ready (Gemini)" when the CLI binary
was installed, misleading users who hadn't authenticated yet.
Changes:
- Add isGeminiAuthenticated() to check ~/.gemini/oauth_creds.json
- Add 'needs_auth' state to WebSearchReadiness type
- Show warning "[!] Gemini: run 'gemini' to login" when not authenticated
- Fix incorrect 'gemini auth login' references (no such command exists)
- Update docs with correct npm install and authentication instructions
WebSearch hook was blocking native Claude accounts (ccs ck, default)
with "No Providers Enabled" message instead of passing through to
server-side WebSearch.
Changes:
- Add shouldSkipHook() detection for account/default profile types
- Fix blocking bug: exit(0) when no providers enabled instead of blocking
- Pass CCS_PROFILE_TYPE and CCS_WEBSEARCH_SKIP env vars for profile-aware behavior
- Add profile type signals to settings, cliproxy, account, and default execution paths
- Add model field to Gemini/OpenCode WebSearch providers
- Config.yaml now explicitly stores model (gemini-2.5-flash, opencode/grok-code)
- UI: blur-to-save for model inputs (no save on every keystroke)
- UI: collapsible install hints inside provider cards
- UI: left accent border for enabled providers (better light theme contrast)
- Hook: use systemMessage for info-styled confirmation
- Hook: cleaner result format without defensive "NOT an error" language
- Hook timeout now computed from max provider timeout + 30s buffer
- Removes hardcoded HOOK_TIMEOUT_SECONDS constant
- ensureHookConfig() now updates timeout when it changes
- Default OpenCode model changed from gpt-5-nano to grok-code
- Default OpenCode timeout changed from 60s to 90s
- Single source of truth: config.yaml controls all timeouts
- Add SHARED_INSTRUCTIONS constant with 7 quality guidelines applied to ALL providers
- Refactor PROVIDER_CONFIG to use toolInstruction (provider-specific) and quirks (optional)
- Add buildPrompt() function to combine shared + provider-specific instructions
- Gemini: google_web_search tool instruction
- OpenCode: built-in capabilities instruction
- Grok: web search + X/Twitter quirk for real-time events
DRY improvement: shared guidelines now maintained in one place
Major refactor of WebSearch hook to:
1. **Respect config.yaml settings**: Hook now reads CCS_WEBSEARCH_GEMINI,
CCS_WEBSEARCH_OPENCODE, CCS_WEBSEARCH_GROK env vars to determine which
providers to use. Only enabled AND installed providers are tried.
2. **Consolidate prompts/models**: Added PROVIDER_CONFIG section at top of
hook file for easy prompt engineering:
- gemini: model + prompt template
- opencode: model (overridable via env) + prompt template
- grok: model + prompt template
3. **Pass provider states via env**: Updated getWebSearchHookEnv() to pass
individual provider enabled states as env vars.
Breaking change: Hook no longer falls back to all installed CLIs. It now
strictly respects config.yaml settings.
Add automatic fallback chain (Gemini -> OpenCode -> Grok) when multiple
WebSearch CLI providers are available. The hook now tries each provider
in order until one succeeds.
Changes:
- Update websearch-transformer.cjs with tryOpenCodeSearch() and tryGrokSearch()
- Implement fallback chain logic in processHook()
- Add outputAllFailedMessage() for when all providers fail
- Update outputNoToolsMessage() with all three install options
- Update config.yaml comments to document OpenCode and fallback behavior
- Add Grok CLI detection (grok-4-cli by lalomorales22) alongside Gemini CLI
- Add WebSearch health check group to health-service.ts
- Update settings UI to show both Gemini and Grok CLI providers
- Add detailed install hints when no WebSearch CLI is installed
- Update API routes to return grokCli status
- Both CLI and dashboard users now see clear installation guidance
Providers:
- Gemini CLI: FREE tier (1000 req/day), no API key needed
- Grok CLI: Requires xAI API key (XAI_API_KEY), web + X search
* fix: add missing commands/ccs.md symlink during npm install
Previously, the ClaudeSymlinkManager only created symlinks for the
commands/ccs directory and skills/ccs-delegation directory, but missed
the commands/ccs.md file. This resulted in an incomplete symlink structure
where users would see the ccs folder symlinked but not the ccs.md file.
Added commands/ccs.md to the ccsItems array to ensure all CCS components
are properly symlinked during installation.
Fixes: npm install now creates complete symlink structure for all CCS items
* chore: bump version to 4.3.9
* fix(postinstall): make ora dependency optional to fix missing ~/.ccs/.claude/ directory
The root cause was that `ora` module was not available during `npm install`
when the postinstall script runs, causing both:
- .claude/ directory copy to fail (ClaudeDirInstaller)
- Symlink creation to fail (ClaudeSymlinkManager)
This resulted in the ~/.ccs/.claude/ directory not being created.
Changes:
- Made ora import optional in ClaudeDirInstaller
- Made ora import optional in ClaudeSymlinkManager
- Both classes now gracefully fallback to console.log when ora is unavailable
- Postinstall now successfully creates ~/.ccs/.claude/ and symlinks
Tested: Clean install now properly creates all directories and symlinks
* chore: bump version to 4.3.7
Version 4.3.7 - Postinstall Fix Release
Changes:
- Fixed missing ~/.ccs/.claude/ directory during npm install
- Made ora dependency optional in installer utilities
- Added CHANGELOG entry documenting the fix
Files updated:
- VERSION: 4.3.6 -> 4.3.7
- package.json: version updated
- lib/ccs: version string updated
- lib/ccs.ps1: version string updated
- installers/install.sh: version string updated
- installers/install.ps1: version string updated
- CHANGELOG.md: added 4.3.7 release notes
* fix: handle ora v9 ES module compatibility
ora v9.0.0 is now an ES module, which requires using .default when
importing with CommonJS require(). This was causing "ora is not a
function" errors in doctor.js and installer utilities.
Changes:
- Updated ora import to use oraModule.default || oraModule
- Added fallback spinner implementation for when ora is unavailable
- Ensures compatibility with both ES and CommonJS module systems
- Fixes ccs doctor command and postinstall script
Tested:
- ccs doctor now works correctly with spinners
- postinstall successfully creates ~/.ccs/.claude/ and symlinks
- Fallback console.log works when ora is unavailable
* chore: update package-lock.json
* chore: bump version to 4.3.8
Version 4.3.8 - ora v9 Compatibility Release
Changes:
- Fixed ora v9 ES module compatibility issues
- Updated CHANGELOG with 4.3.8 release notes
Files updated:
- VERSION: 4.3.7 -> 4.3.8
- package.json: version updated
- lib/ccs: version string updated
- lib/ccs.ps1: version string updated
- installers/install.sh: version string updated
- installers/install.ps1: version string updated
- CHANGELOG.md: added 4.3.8 release notes
* fix(postinstall): make ora dependency optional to fix missing ~/.ccs/.claude/ directory
The root cause was that `ora` module was not available during `npm install`
when the postinstall script runs, causing both:
- .claude/ directory copy to fail (ClaudeDirInstaller)
- Symlink creation to fail (ClaudeSymlinkManager)
This resulted in the ~/.ccs/.claude/ directory not being created.
Changes:
- Made ora import optional in ClaudeDirInstaller
- Made ora import optional in ClaudeSymlinkManager
- Both classes now gracefully fallback to console.log when ora is unavailable
- Postinstall now successfully creates ~/.ccs/.claude/ and symlinks
Tested: Clean install now properly creates all directories and symlinks
* chore: bump version to 4.3.7
Version 4.3.7 - Postinstall Fix Release
Changes:
- Fixed missing ~/.ccs/.claude/ directory during npm install
- Made ora dependency optional in installer utilities
- Added CHANGELOG entry documenting the fix
Files updated:
- VERSION: 4.3.6 -> 4.3.7
- package.json: version updated
- lib/ccs: version string updated
- lib/ccs.ps1: version string updated
- installers/install.sh: version string updated
- installers/install.ps1: version string updated
- CHANGELOG.md: added 4.3.7 release notes
* feat(shared): add plugin support to shared directories
Add Claude Code plugins to the .ccs/shared/ symlink architecture,
enabling plugin sharing across all CCS profiles (GLM, GLMT, Kimi, etc).
Changes:
- SharedManager: Add 'plugins' to sharedDirs array
- Help text: Update all three implementations (Node.js, bash, PowerShell)
- Postinstall: Create ~/.ccs/shared/plugins/ directory on install
Architecture:
~/.claude/plugins/ <- ~/.ccs/shared/plugins/ <- instance/plugins/
This follows the existing pattern for commands/skills/agents and
maintains cross-platform compatibility with Windows fallback support.
* chore: bump version to 4.3.6
* docs: update CHANGELOG for v4.3.6
* feat(completion): improve fish shell completion with category prefixes
- Remove problematic inline set_color commands that prevented descriptions from displaying
- Add [cmd], [model], and [account] prefixes to visually group completions
- Update file header to document categorization features
This brings fish completion quality closer to zsh by making categories
clear and ensuring descriptions actually display to users.
Fixes issue where fish completions showed no descriptions.
* chore: bump version to 4.3.1
* feat: enhance UX for 'ccs sync' and 'ccs doctor' commands
- Add ora spinners for real-time progress feedback
- Add cli-table3 for structured health check summary table
- Display detailed component information in doctor report
- Show file/directory counts during sync operations
- Improve visual hierarchy and readability
- Fix Unicode checkmark violation (replaced with [OK])
Breaking changes: None
Dependencies added: ora@^5.4.1, cli-table3@^0.6.5
Version bumped to 4.3.0
* fix: match doctor summary header width to table width
* feat: improve settings validation to check API keys, not just endpoints
- Check if API keys are configured or still placeholders
- Show 'Key configured' vs 'Placeholder key (not configured)'
- Previous message 'Valid (API: api.z.ai)' was misleading
- Now users can clearly see which profiles need API key configuration
- Warnings shown for placeholder keys (yellow [!])
* feat: add category grouping and alignment to doctor checks
- Group checks into logical categories: System, Configuration, Profiles & Delegation, System Health
- Add consistent padding (26 chars) for aligned columns
- Add 2-space indent for better visual hierarchy
- Separate categories with blank lines for better readability
- Much cleaner and easier to scan than before
* fix: perfect alignment for all doctor check output
- Use .padEnd(26) consistently for all component names
- Remove extra space before status icons
- Now all status icons [OK]/[X]/[!]/[i] align perfectly
- All component names pad to exactly 26 characters
Add comprehensive update functionality with intelligent notification system.
Supports manual update checking, automatic notifications every 24h, and works
across npm and direct installations with proper error handling.
Closes#12