- Updated websearch.md to include SearXNG in the configuration guide.
- Implemented SearXNG JSON API integration in websearch-transformer.cjs.
- Enhanced unified-config-loader.ts to support SearXNG settings.
- Defined SearXNG configuration types in unified-config-types.ts.
- Updated hook-env.ts to manage SearXNG environment variables.
- Modified status.ts to include SearXNG in web search provider checks.
- Added SearXNG routes and validation in websearch-routes.ts.
- Implemented SearXNG provider tests in websearch-transformer.test.ts.
- Updated readiness tests to account for SearXNG in status.test.ts.
- Enhanced websearch routes tests to validate SearXNG settings.
- Added SearXNG configuration fields in the UI settings.
- switch ccs-websearch MCP to stdio-first framing and keep legacy compatibility
- add cooldown persistence and bounded retries for provider failures
- surface cooldown status in readiness output and regression tests
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