From ebe174645938c8f86f92eafaaaa57a0db94482a6 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Sun, 26 Jul 2026 09:35:00 -0400 Subject: [PATCH] docs(architecture): reconcile provider and target contracts --- docs/system-architecture/index.md | 641 +++++-------- docs/system-architecture/provider-flows.md | 776 ++++------------ docs/system-architecture/target-adapters.md | 960 ++++---------------- 3 files changed, 571 insertions(+), 1806 deletions(-) diff --git a/docs/system-architecture/index.md b/docs/system-architecture/index.md index 280f9c94..21d0b0e9 100644 --- a/docs/system-architecture/index.md +++ b/docs/system-architecture/index.md @@ -1,465 +1,246 @@ # CCS System Architecture -Last Updated: 2026-04-14 +CCS separates profile resolution, provider routing, and target execution. This +document describes stable boundaries; source registries and tests own mutable +provider and command inventories. -High-level architecture overview for the CCS (Claude Codex Switch) system. +## System context ---- - -## System Overview - -CCS is a multi-provider profile and runtime manager that enables seamless switching between multiple Claude accounts, alternative AI providers, and multiple CLI targets (Claude Code, Factory Droid, Codex CLI) for credential delivery. - -The system consists of two main components: - -1. **CLI Application** (`src/`) - Node.js TypeScript CLI -2. **Dashboard UI** (`ui/`) - React web application served by Express - -Dashboard localization (i18n) architecture and contributor workflow are documented in [Dashboard i18n Guide](../i18n-dashboard.md). - -CCS v7.34 adds Image Analysis Hook for vision model proxying through CLIProxy with automatic injection for all profile types. -CCS v7.67 adds a native structured logging lane for CCS-owned runtime events, backed by `src/services/logging/`, bounded JSONL files under `~/.ccs/logs/`, and a dedicated dashboard `/logs` route. -CCS PR review now uses PR-Agent in GitHub Actions, with reviews running on the self-hosted `cliproxy` runner, existing `AI_REVIEW_*` workflow variables and secrets preserved as the runtime contract, and repo-level guidance stored in `.pr_agent.toml`. - -``` -+===========================================================================+ -| CCS System | -+===========================================================================+ -| | -| +------------------+ +-----------------+ +----------------+ | -| | User Terminal | ---> | CCS CLI | ---> | Target CLI | | -| | (ccs command) | | (src/ccs.ts) | | (claude/droid/codex) | | -| +------------------+ +-----------------+ +----------------+ | -| | | | -| v v | -| +------------------+ +-----------------+ +----------------+ | -| | Dashboard UI | <--> | Express | ---> | Provider APIs | | -| | (React SPA) | | Web Server | | (Claude/GLM/ | | -| +------------------+ +-----------------+ | Gemini/etc) | | -| | +----------------+ | -| v | -| +---------------------+ | -| | CLIProxyAPI | | -| | (Local or Remote) | | -| +---------------------+ | -| | -+===========================================================================+ -``` - ---- - -## Component Architecture - -### Multi-Target Adapter System - -CCS v7.45 introduces the Target Adapter pattern, enabling seamless integration with different CLI implementations. - -**Key architecture:** - -``` -Profile Resolution (CLIProxy, Settings/API, Account-based) +```text +User or automation | v -Target Resolution (--target flag > runtime entrypoint / argv[0] > config > default) - | - v -Get Target Adapter (Claude, Droid, or Codex) - | - +---> detectBinary() (find CLI on system) - | - +---> prepareCredentials() (write config or set env) - | - +---> buildArgs() (construct CLI arguments) - | - +---> buildEnv() (prepare environment variables) - | - v -Spawn Target Process +CCS CLI ------------------------> Target CLI + | | + | profile/provider state | provider protocol + v v +CCS local server <-----------> CLIProxy or direct API + | + v +React dashboard ``` -**Each target adapter implements different credential delivery:** +The main implementation surfaces are: -- **Claude Adapter**: Env var delivery (existing behavior) - - `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_MODEL` - - No config files needed +| Surface | Ownership | +| --- | --- | +| `src/` | CLI, dispatch, server, provider integration, target adapters | +| `ui/src/` | Dashboard application | +| `dist/` and `dist/ui/` | Build outputs | +| `docker/` | Integrated and legacy container definitions | +| `tests/` | Unit, integration, end-to-end, native, and Docker contracts | -- **Droid Adapter**: Config file delivery to `~/.factory/settings.json` - - Writes custom model entry: `custom:ccs-` - - Spawns: `droid -m custom:ccs- ` - - Model config includes baseUrl, apiKey, provider +Dashboard localization is documented in the +[Dashboard i18n Guide](../i18n-dashboard.md). -- **Codex Adapter**: Transient runtime overrides plus user-layer dashboard inspection - - Uses `codex -c key=value` only for CCS-routed launches - - Preserves native `~/.codex/config.toml` ownership - - Dashboard page reads/writes only the user config layer with explicit runtime-vs-provider warnings +## Execution pipeline -**Runtime entrypoints (built-in bins) and argv[0]-style aliases:** - -``` -ccs → Target: claude (default) -ccs-droid → Target: droid (explicit alias) -ccsd → Target: droid (legacy shortcut) -ccs-codex → Target: codex (explicit alias) -ccsx → Target: codex (short alias) -ccsxp → Target: codex (native cliproxy shortcut; prepends `--config model_provider="cliproxy"`) -``` - -For details on the adapter architecture, see [Target Adapters](./target-adapters.md). - -### CLI Layer - -``` -+===========================================================================+ -| CLI Architecture | -+===========================================================================+ - - User Input (ccs [--target ] [args]) - | - v - +-------------+ - | ccs.ts | Entry point, command routing - +-------------+ - | - +---> [Version/Help/Doctor/etc.] ---> Exit - | - v - +------------------+ - | Target Resolution | Determine which CLI to use - +------------------+ - | - v - +-------------+ - | Profile | Determines execution path - | Detection | - +-------------+ - | - +---> [Native Claude Account] ---> execClaude() - | | - +---> [CLIProxy Provider] ---> execClaudeWithCLIProxy() - | | - +---> [Settings/API Profile] ---> normalize legacy glmt if needed - | - v - +------------------+ - | Target Adapter | Get appropriate adapter - +------------------+ - | - v - +------------------+ - | Prepare Creds | Deliver credentials - +------------------+ - | - v - +------------------+ - | Target CLI | Claude Code or Droid - +------------------+ -``` - ---- - -## Data Flow Architecture - -### CLI Execution Flow - -``` -+===========================================================================+ -| CLI Execution Flow | -+===========================================================================+ - - 1. Parse Arguments - | - v - 2. Resolve Target Type - | - v - 3. Detect Profile Type - | - +---> Native Claude ---> 3a. Load Account Settings - | | - | v - | 4a. Set CLAUDE_CONFIG_DIR - | | - | v - | 5a. Get Claude Target Adapter - | - +---> CLIProxy -------> 3b. Ensure Binary Installed - | | - | v - | 4b. Generate Config - | | - | v - | 5b. Resolve Target Adapter - | | - | v - | 6b. Prepare Credentials - | | - | v - | 7b. Spawn via Adapter - | - +---> Settings/API ---> 3c. Load settings env - | - v - 4c. Normalize legacy glmt if needed - | - v - 5c. Resolve Target Adapter - | - v - 6c. Spawn via Adapter -``` - ---- - -## Provider Integration Architecture - -For detailed provider flows (CLIProxyAPI, legacy GLMT compatibility, quota management), see [Provider Flows](./provider-flows.md). - ---- - -## Configuration Architecture - -### CCS Logging Architecture - -- Shared logging contract lives in `src/services/logging/` and is used for CCS-owned runtime diagnostics, request tracing, and bounded recent-entry reads. -- Config lives at top-level `logging.*` in `~/.ccs/config.yaml`; `cliproxy.logging.*` still controls upstream CLIProxy runtime files only. -- CCS-owned runtime logs write to `~/.ccs/logs/current.jsonl` and rotate into `~/.ccs/logs/archive/` based on policy. -- Dashboard exposure uses native `/api/logs/config`, `/api/logs/sources`, and `/api/logs/entries` endpoints plus the `System -> Logs` React page. -- Request logging explicitly skips `/api/logs` reads so the log viewer does not recursively log itself. - -### Config File Hierarchy - -``` -+===========================================================================+ -| Configuration Hierarchy | -+===========================================================================+ - - ~/.ccs/ +```text +Parse command | - +---> config.yaml # Main CCS config (unified) +Resolve command vs launch | - +---> profiles.json # Claude account registry +Resolve target | - +---> .settings.json # Per-profile settings +Resolve profile type + +-- account profile ------> isolated target config root + +-- settings profile -----> profile environment + +-- CLIProxy provider ----> local or remote proxy route | - +---> cliproxy/ - | | - | +---> config.yaml # CLIProxy configuration - | +---> auth/ # OAuth tokens - | +---> bin/ # CLIProxy binary +Prepare target credentials | - +---> shared/ # Symlinked resources - | - +---> commands/ # Claude Code commands - +---> skills/ # Custom skills - +---> agents/ # Agent configurations - +---> plugins/ - | - +---> cache/ # Shared plugin payload/cache data - +---> marketplaces/ # Shared marketplace payload directories - +---> installed_plugins.json - - ~/.ccs/instances// - | - +---> plugins/ - | - +---> known_marketplaces.json # Instance-local registry for active CLAUDE_CONFIG_DIR validation - - ~/.factory/ (Droid CLI) - | - +---> settings.json # Droid config (custom models) +Spawn target and forward lifecycle signals ``` -Plugin ownership note: -- `commands/`, `skills/`, `agents/`, and `settings.json` remain shared through the existing symlink/copy flow. -- Marketplace payload directories stay shared, but `known_marketplaces.json` is reconciled per instance so Claude Code can validate `installLocation` against that instance's `CLAUDE_CONFIG_DIR/plugins/marketplaces`. +Command routing stops before profile execution for management commands such as +configuration, diagnostics, proxy management, and environment export. -### Config Loading Order +### Profile resolution -``` - 1. Environment Variables (highest priority) - | - v - 2. CLI Arguments (including --target) - | - v - 3. Profile-specific settings (~/.ccs/.settings.json) - | - v - 4. Main config (~/.ccs/config.yaml) - | - v - 5. Default values (lowest priority) +Profile resolution distinguishes: + +1. built-in CLIProxy provider shortcuts; +2. user-defined CLIProxy profiles; +3. settings/API profiles; and +4. registered account profiles. + +Canonical provider IDs and aliases come from +[`src/cliproxy/provider-capabilities.ts`](../../src/cliproxy/provider-capabilities.ts). +The detector and dispatcher consume those registries; documentation must not +maintain a second provider list. + +### Target resolution + +Provider and target are independent axes. The selected target is resolved from +explicit flags and runtime entry points before falling back to configuration +and the default target. + +Each adapter owns credential delivery: + +- **Claude Code:** launch environment and optional isolated + `CLAUDE_CONFIG_DIR`; +- **Factory Droid:** CCS-managed custom-model entries in + `~/.factory/settings.json`; and +- **Codex CLI:** transient `-c` overrides for CCS-routed launches while native + user configuration remains separately owned. + +See [Target Adapters](./target-adapters.md) for the detailed compatibility +contract. + +## Provider routing + +CCS supports three routing boundaries: + +| Route | Credential and transport owner | +| --- | --- | +| Direct settings/API profile | Target receives the selected provider's environment | +| Local CLIProxy | CCS manages a local proxy binary, config, and auth directory | +| Remote CLIProxy | CCS connects to the configured remote service and applies the selected fallback policy | + +Local backend choice is explicit. `original` is the default. `plus` is an +opt-in backend for provider capabilities unavailable in the original backend. +Compatibility restrictions are defined in +[`src/cliproxy/types/provider-types.ts`](../../src/cliproxy/types/provider-types.ts) +and enforced before local execution. + +See [Provider Flows](./provider-flows.md). + +## Configuration ownership + +The effective CCS directory is resolved by +[`src/utils/config-manager.ts`](../../src/utils/config-manager.ts). The normal +default is `~/.ccs`; tests and scoped workflows can override it. + +```text +CCS directory +├── config.yaml +├── profiles.json +├── .settings.json +├── instances/ +├── logs/ +└── cliproxy/ + ├── config.yaml + ├── auth/ + └── bin/ ``` ---- +### Settings-write contract -## WebSocket Architecture +Normal launches do not rewrite shared Claude settings. API profiles store +string-valued launch environment in CCS-owned per-profile settings. -### Real-time Communication +Persistent shared configuration is explicit: -``` -+===========================================================================+ -| WebSocket Communication | -+===========================================================================+ +- `ccs persist` reads and validates `~/.claude/settings.json`; +- it refuses unsafe symlink targets; +- it preserves unrelated settings while updating the requested managed fields; +- it creates a backup when an existing file is present; and +- it writes the replacement atomically under a settings-directory lock. - Dashboard (React) Server (Express) - | | - |<------ Connection Established ------>| - | | - |<------ health:update ----------------| Health status - | | - |<------ auth:status ------------------| Auth changes - | | - |<------ usage:update -----------------| Usage stats - | | - |------- action:refresh -------------->| User requests - | | +Target-owned writers follow their own boundary. For example, the Droid adapter +manages CCS custom-model entries in `~/.factory/settings.json`, not arbitrary +user settings. + +## Local server and dashboard + +The Express server exposes APIs used by the React dashboard for supported +configuration, auth, usage, health, and logging workflows. The dashboard is a +management surface over shared services; it must not implement a competing +configuration model. + +Real-time updates use server-owned WebSocket messages. Event names and payloads +are code contracts and should be read from the server and UI implementations +rather than copied into this overview. + +## Logging + +CCS-owned structured runtime logging lives under `src/services/logging/`. +Top-level `logging.*` configuration controls CCS JSONL logs under the CCS +directory. `cliproxy.logging.*` controls upstream CLIProxy files and is a +separate contract. + +The dashboard log reader excludes its own log-read requests from request +logging to prevent recursive noise. See [Logging Contract](../logging-contract.md). + +## Managed tool preparation + +WebSearch and image analysis are prepared before the target launch when the +selected profile needs CCS-managed tooling. They use provider-aware routes but +have different failure contracts: enabled third-party WebSearch fails closed if +its managed MCP replacement cannot be prepared, while image analysis can use a +compatible native path when available. + +- [WebSearch](../websearch.md) +- [Provider Flows](./provider-flows.md) + +## Security and trust boundaries + +### Local host + +CCS reads and writes user-authorized configuration and starts target processes. +That local filesystem access is more privileged than a provider API request. +Sensitive values must not enter logs or dashboard responses. + +### Local proxy + +The host CLI uses loopback for locally managed CLIProxy traffic. Local auth +files and the management API remain sensitive even when the transport never +leaves the machine. + +### Remote proxy + +A remote CLIProxy crosses a network and administrative boundary. TLS, +authentication, certificate policy, reachability, and local fallback are +explicit configuration choices. Remote-only mode must not silently start a +local proxy. + +### Container deployment + +The integrated container exposes dashboard and proxy ports through the +operator's port mappings. The image therefore does not inherit the host +installation's loopback-only assumption. Network exposure and access control +belong to the deployment operator. + +## Build and distribution + +```text +src/ -------- TypeScript --------> dist/ +ui/src/ ----- Vite --------------> dist/ui/ + | + v + npm package + | + v + integrated Docker image ``` ---- +The package requires Node.js 18 or newer. Repository development supports Bun +1.0 or newer. CI can pin newer tool versions independently; those workflow pins +are not the minimum consumer runtime contract. -## Security Architecture +The integrated Docker image is built from +[`docker/Dockerfile.integrated`](../../docker/Dockerfile.integrated). It layers +CCS onto a digest-pinned CLIProxy base, runs CLIProxy and the dashboard under +supervision, and health-checks both services. It does not bundle Claude Code, +Gemini CLI, Codex CLI, Droid, or other target CLIs. -### Authentication Flow +Release lane details are in [Release Process](../release-process.md). -See [Provider Flows](./provider-flows.md) → Authentication Flow section. +## Architecture invariants -### Security Boundaries +- Provider identity comes from the provider registry. +- Target compatibility is enforced at the adapter boundary. +- Environment values persisted in settings are strings. +- Shared target configuration changes require an explicit workflow. +- Local and remote proxy modes do not silently cross trust boundaries. +- Dashboard and CLI use the same domain services and configuration schema. +- Volatile capability details remain source-owned. -``` - +------------------+ - | User Terminal | - +------------------+ - | - | Local only (no network exposure) - v - +------------------+ - | CCS CLI | - +------------------+ - | - | Localhost only (127.0.0.1) - v - +------------------+ - | CLIProxy/Legacy | Binds to localhost only - +------------------+ - | - | TLS encrypted - v - +------------------+ - | Target CLI | Spawned locally (claude/droid) - +------------------+ - | - | TLS encrypted - v - +------------------+ - | Provider APIs | External endpoints - +------------------+ -``` +## Related documentation ---- - -## Build and Distribution - -### Build Pipeline - -``` -+===========================================================================+ -| Build Pipeline | -+===========================================================================+ - - src/ (TypeScript) ui/src/ (React TSX) - | | - v v - TypeScript Compiler Vite Build - | | - v v - dist/ (JavaScript) dist/ui/ (Static assets) - | | - +---------------+---------------------+ - | - v - npm package (@kaitranntt/ccs) - | - v - npm registry / GitHub releases -``` - -### Package Contents - -``` - @kaitranntt/ccs - | - +---> dist/ # Compiled CLI - +---> dist/ui/ # Built dashboard - +---> lib/ # Native scripts - | +---> ccs # Bash bootstrap - | +---> ccs.ps1 # PowerShell bootstrap - +---> package.json -``` - ---- - -## Deployment Architecture - -### Local Installation - -``` - npm install -g @kaitranntt/ccs - | - v - Global node_modules - | - +---> Creates symlink: ccs --> dist/ccs.js - | - +---> Runtime aliases: ccs-droid / ccsd → ccs (auto-select droid target) - | - +---> First run creates: ~/.ccs/ -``` - -### PR Review Lane - -Automated pull request review stays in `.github/workflows/ai-review.yml`, but the workflow now runs PR-Agent instead of the old Claude action. Reviews run on the existing self-hosted `cliproxy` runner, while the workflow preserves the existing `AI_REVIEW_BASE_URL`, `AI_REVIEW_MODEL`, and `AI_REVIEW_API_KEY` contract by mapping those values into PR-Agent env keys such as `OPENAI.*`, `config.*`, and `github_action_config.*`. Repo-specific reviewer guidance lives in `.pr_agent.toml`. - -``` -GitHub Actions `ai-review.yml` - | - v -Self-hosted `cliproxy` runner - | - v -PR-Agent action - | - v -CLIProxy - | - v -Configured model from `.pr_agent.toml` -``` - -- `ai-review.yml` owns automation wiring such as runner selection, PR-Agent action usage, and runtime values mapped from `AI_REVIEW_*` into `OPENAI.*`, `config.*`, and `github_action_config.*`. -- `.pr_agent.toml` in the repo root owns review instructions for this repository. -- Contributors should treat PR-Agent comments and trusted `/review` reruns as the primary AI review path for PRs targeting CCS. - -### Runtime Dependencies - -``` - +------------------+ +------------------+ - | Node.js 14+ | | Claude CLI | - | (required) | | (required) | - +------------------+ +------------------+ - - +------------------+ +------------------+ - | CLIProxyAPI | | Droid CLI | - | (auto-managed) | | (optional) | - +------------------+ +------------------+ -``` - ---- - -## Related Documentation - -- [Codebase Summary](../codebase-summary.md) - Detailed directory structure -- [Code Standards](../code-standards.md) - Coding conventions & patterns -- [Target Adapters](./target-adapters.md) - Multi-CLI adapter architecture -- [Provider Flows](./provider-flows.md) - CLIProxy, legacy GLMT compatibility, authentication flows -- [Project Roadmap](../project-roadmap.md) - Development phases +- [Codebase Summary](../codebase-summary.md) +- [Code Standards](../code-standards.md) +- [Target Adapters](./target-adapters.md) +- [Provider Flows](./provider-flows.md) +- [Release Process](../release-process.md) +- [Project Roadmap](../project-roadmap.md) diff --git a/docs/system-architecture/provider-flows.md b/docs/system-architecture/provider-flows.md index 39b516c3..c7a7a4d5 100644 --- a/docs/system-architecture/provider-flows.md +++ b/docs/system-architecture/provider-flows.md @@ -1,641 +1,247 @@ # Provider Integration Flows -Last Updated: 2026-07-16 +CCS routes profiles through direct target configuration or CLIProxy. This +document describes the durable flow and trust boundaries. Mutable provider +capabilities are linked to their source registries. -Detailed provider integration flows including CLIProxyAPI, legacy GLMT compatibility transforms, remote CLIProxy, quota management, and authentication. +## Sources of truth ---- +| Detail | Authoritative source | +| --- | --- | +| Canonical provider IDs, aliases, auth-flow type, callback ports, refresh ownership | [`src/cliproxy/provider-capabilities.ts`](../../src/cliproxy/provider-capabilities.ts) | +| Original vs Plus backend restrictions | [`src/cliproxy/types/provider-types.ts`](../../src/cliproxy/types/provider-types.ts) | +| Provider/backend enforcement | [`src/cliproxy/services/variant-service.ts`](../../src/cliproxy/services/variant-service.ts) | +| Remote proxy precedence and fallback | [`src/cliproxy/proxy/proxy-config-resolver.ts`](../../src/cliproxy/proxy/proxy-config-resolver.ts) | +| Provider capability regression tests | [`src/cliproxy/__tests__/provider-capabilities.test.ts`](../../src/cliproxy/__tests__/provider-capabilities.test.ts) | -## CLIProxyAPI Flow +Do not copy provider inventories, callback ports, or quota-supported lists into +this document. They change independently of the architecture. -### Overview +## Route selection -CLIProxyAPI is a local OAuth proxy binary that enables seamless integration with multiple AI providers. CCS manages the binary and configuration automatically. - -### Local Backend Choice - -CCS defaults to the original `router-for-me/CLIProxyAPI` backend because it is the stable MIT upstream. The `plus` backend is an explicit opt-in path that downloads the community-maintained `kaitranntt/CLIProxyAPIPlus` fork for providers that still require Plus-only support, such as Kiro, Cursor, GitLab, CodeBuddy, Kilo, and deprecated GitHub Copilot compatibility. CCS does not silently downgrade `backend: plus` to `original`; users choose that backend deliberately when they need those providers. - -Generated local CLIProxy configs also keep the management dashboard aligned with the selected backend. `backend: original` uses upstream CPAMC (`router-for-me/Cli-Proxy-API-Management-Center`), while `backend: plus` uses the CCS-maintained dashboard fork (`kaitranntt/Cli-Proxy-API-Management-Center`). Advanced users can override the generated `remote-management.panel-github-repository` value by setting `cliproxy.management_panel_repository` in `~/.ccs/config.yaml`; CCS will regenerate stale local CLIProxy configs when the expected dashboard repository changes. - -``` -+===========================================================================+ -| CLIProxyAPI Integration | -+===========================================================================+ - - Claude CLI - | - | ANTHROPIC_BASE_URL = localhost:XXXX - v - +------------------+ - | CLIProxyAPI | Local proxy binary (Plus fork opt-in for plus-only providers) - | (binary) | - +------------------+ - | - +---> OAuth Authentication - | | - | +---> Authorization Code Flow (port-based) - | | - Gemini, Codex, Antigravity, iFlow, Claude, GitLab - | | - Opens browser for user auth - | | - Callback to localhost:PORT - | | - | +---> Device Code Flow (no port needed) - | - xAI/Grok, Kimi, Kiro, Copilot, CodeBuddy, Kilo, and Qoder - | - User enters a code on the provider verification page - | - Polls for token completion - | | - | +---> Browser URL Polling (no callback port) - | - Cursor - | - Opens provider login URL returned by CLIProxyAPIPlus - | - Polls auth state until token is saved - | | - | v - | +------------------+ - | | OAuth Server | Browser-based auth - | +------------------+ - | - +---> Request Transformation - | | - | v - | Anthropic Format --> Provider Format - | - +---> Image Analysis Hook (v7.34) - | | - | v - | Vision Model Proxying (gemini, codex, agy, clipproxy) - | - Auto-injected via claude-hooks - | - Skip for Claude Sub accounts (native vision) - | - Fallback with deprecated block-image-read - | - +---> Provider APIs +```text +Resolved profile + | + +-- account profile + | └── target-native authenticated context + | + +-- settings/API profile + | └── direct or compatible upstream URL and credential environment + | + └-- CLIProxy provider + | + +-- reachable configured remote proxy + | + └-- local proxy, when selected or fallback is allowed | - +---> Google (Gemini) - +---> OpenAI (Codex) - +---> xAI (Grok) - +---> Antigravity (AGY) - +---> AWS Kiro (Claude-powered) - +---> GitHub Copilot (ghcp, deprecated compatibility) - +---> OpenAI-compatible endpoints + └── original or Plus backend ``` -### Supported Built-In Providers +The provider route is resolved before the target adapter prepares credentials. +This keeps provider selection independent from whether Claude Code, Droid, or +Codex receives the route. -| Provider | ID | Auth Method | Callback Port | Backend | -|----------|----|----|------|--------| -| Gemini | `gemini` | Authorization Code | 8085 | Original | -| Codex | `codex` | Authorization Code | 1455 | Original | -| xAI (Grok) | `xai` (`grok` CLI alias) | Device Code | none | Original | -| Antigravity | `agy` | Authorization Code | 51121 | Original | -| Qwen | `qwen` | Account linking unavailable in bundled runtime | none | Original | -| iFlow | `iflow` | Authorization Code | 11451 | Original | -| Claude | `claude` | Authorization Code | 54545 | Original | -| Kimi | `kimi` | Device Code | none | Original | -| Kiro (AWS) | `kiro` | Method-aware (default: Device Code) | none by default | Plus | -| GitHub Copilot (deprecated) | `ghcp` | Device Code | none | Plus | -| Cursor | `cursor` | Browser URL polling | none | Plus | -| GitLab Duo | `gitlab` | Authorization Code or PAT | 17171 | Plus | -| CodeBuddy | `codebuddy` | Device-style polling | none | Plus | -| Kilo AI | `kilo` | Device Code | none | Plus | -| Qoder | `qoder` | Device Code | none | Plus | +## Local CLIProxy -xAI uses CLIProxyAPI's `--xai-login` device flow. CCS stores and discovers the resulting -`xai-*.json` credentials under the canonical `xai` provider. `ccs grok` is a command alias only, -so it shares the same accounts, settings, live model catalog, and routing as `ccs xai`. +CCS manages the local binary, generated configuration, auth files, lifecycle, +and provider-specific launch environment. -### Codex Duplicate-Email Account Identity +The local backend contract is: -Codex can legitimately produce multiple auth files for the same email when the user has both a team/business login and a personal/free login. CCS now treats those as separate accounts instead of collapsing them by email. +- `original` is the default upstream CLIProxy backend; +- `plus` is an explicit opt-in community-maintained distribution; +- providers declared Plus-only fail clearly on the original backend; and +- CCS does not silently downgrade a configured Plus backend. -- Internal account IDs stay duplicate-aware for Codex only: `email#variant` -- Variant keys are derived from the auth filename, for example `kaidu.kd@gmail.com#04a0f049-team` and `kaidu.kd@gmail.com#free` -- Dashboard surfaces continue to show the canonical email, with a compact variant badge such as `Team` or `Free` -- Quota fetch resolves the exact registry `tokenFile` for the selected account instead of scanning by email and taking the first match -- Live usage/account monitor stats key by `provider + account identity`, so duplicate Codex emails no longer merge into one runtime bucket +The management-panel repository is generated to match the selected backend +unless the user supplies the supported override. -This preserves the user-visible distinction between business and personal Codex sessions while keeping other providers on their existing email-backed identity model. +Local target traffic uses a provider-compatible endpoint and an internal +credential. CLIProxy owns upstream OAuth token use and request translation +according to the selected provider. -### Built-In Provider Detection +## Remote CLIProxy -CCS derives canonical providers from `provider-capabilities.ts`. `profile-detector.ts` resolves only -explicit CLI aliases before routing through the CLIProxy execution flow. +Remote mode delegates proxy lifecycle and stored authentication to another +CLIProxy installation. -```typescript -const provider = resolveCLIProxyProviderShortcut(profileName); - -if (provider) { - return execClaudeWithCLIProxy(claudeCli, provider, args); -} +```text +CLI flags and environment + | + v +CCS proxy configuration + | + v +Reachability check + +-----+-----+ + | | +reachable unreachable + | | + remote remote-only? ---- yes ---> fail + | + no + | + fallback enabled? -- no ----> fail + | + yes + | + local proxy ``` ---- +Configuration precedence and accepted environment names are implemented in +[`proxy-config-resolver.ts`](../../src/cliproxy/proxy/proxy-config-resolver.ts). +The stable behavioral contract is: -## Legacy GLMT Compatibility Flow +- CLI flags override other sources where supported; +- environment can select a remote host; +- configuration supplies persistent remote settings; +- HTTPS and HTTP defaults remain protocol-aware; +- `--remote-only` disables local fallback; and +- an unreachable remote proxy falls back only when fallback is enabled. -### Overview +A remote proxy is a separate trust boundary. Its operator can receive provider +traffic and owns its stored OAuth state. Use TLS and authentication appropriate +to that boundary. -GLMT is no longer a marketed runtime surface in CCS. Existing `glmt` profiles are kept as a compatibility path and normalized at launch to the direct GLM endpoint. The `src/glmt/` module remains because Cursor response translation still imports its transformer pipeline. +## Authentication -``` -+===========================================================================+ -| Legacy GLMT Compatibility + Internal Transforms | -+===========================================================================+ +CCS asks the selected CLIProxy backend to start the provider's supported auth +flow. The provider registry identifies whether that is an authorization-code +flow, device-code flow, browser polling flow, or currently unsupported account +linking. - Claude CLI +```text +Select canonical provider | - | legacy glmt settings detected - v - +------------------+ - | Compatibility | normalizeDeprecatedGlmtEnv() - | Layer | (src/utils/glmt-deprecation.ts) - +------------------+ +Validate backend and auth-start support | - v - +------------------+ - | Direct GLM API | https://api.z.ai/api/anthropic - +------------------+ +Start backend-owned auth flow | - v - +------------------+ - | src/glmt/* | retained for Cursor translation - +------------------+ +User completes provider interaction + | +Backend writes provider auth file + | +CCS reconciles account identity and readiness + | +Launch through provider route ``` -### Supported Migration Targets +Provider tokens live under the configured CLIProxy auth directory. Documentation +and examples must use neutral account labels; raw email addresses, token +filenames, access tokens, and refresh tokens are not architecture data. -| Provider | Config Key | Endpoint | Auth | -|----------|------------|----------|------| -| Z.AI (GLM) | `glm` | https://api.z.ai/api/anthropic | API key | -| Kimi API | `km` | https://api.kimi.com/coding/ | API key | -| Legacy compatibility | `glmt` | normalized to direct GLM at runtime | existing profile only | +Some providers can produce more than one account with the same display email. +CCS keeps runtime identity tied to the exact registered auth file rather than +collapsing accounts by display text. The dashboard can show a safe variant +label without exposing the internal identifier. -Use `ccs glm` for Z.AI profiles and `ccs km` for reasoning-first Kimi API profiles. Keep `glmt` only when migrating an existing settings file. +## API-key profiles -### Runtime Handling +API-key profiles store string-valued environment under the CCS profile +settings file. -CCS detects the deprecated `glmt` profile name and normalizes legacy proxy-only settings before dispatching through the normal settings-profile flow: - -```typescript -if (isDeprecatedGlmtProfileName(profileName)) { - const normalized = normalizeDeprecatedGlmtEnv(settingsEnv); - // warn user, validate against direct GLM endpoint, continue through settings flow -} +```text +Create or edit API profile + | +Write CCS-owned .settings.json + | +Resolve direct/native vs compatible proxy-style auth + | +Target adapter prepares credentials + | +Spawn target ``` ---- +Native Anthropic profiles use `ANTHROPIC_API_KEY` without forcing a proxy base +URL. Compatible providers typically use `ANTHROPIC_BASE_URL` and +`ANTHROPIC_AUTH_TOKEN`. The profile writer owns that distinction; callers +should not infer it from documentation examples. -## Remote CLIProxy Flow (v7.1) +Normal launches do not write `~/.claude/settings.json`. Users who want shared +Claude settings must select the explicit `ccs persist` workflow. -### Overview +## Legacy GLMT compatibility -Remote CLIProxy enables CCS to delegate authentication to a central proxy server instead of spawning a local binary. +`glmt` is a compatibility input, not a current provider architecture. Existing +legacy settings are normalized to the direct GLM path before normal +settings-profile dispatch. Internal GLMT transformer modules can remain in use +by other compatibility surfaces without making GLMT a supported standalone +runtime. -``` -+===========================================================================+ -| Remote CLIProxy Architecture (v7.1) | -+===========================================================================+ +New configuration should use current API profile commands and provider names. - Config Resolution (proxy-config-resolver.ts) +## Quota and account-pool flow + +Quota support is provider-specific and intentionally sourced from +[`provider-capabilities.ts`](../../src/cliproxy/provider-capabilities.ts). + +The stable pool contract is: + +1. Reconcile registered accounts with live auth files. +2. Exclude manually paused accounts from rotation. +3. Fetch quota only for providers with an implemented quota fetcher. +4. Distinguish an exhausted account from a provider-wide or transient failure. +5. When a healthy fallback exists, CCS can create a temporary quota pause for + an exhausted account. +6. Persist the cooldown so later launches observe the same state. +7. Auto-resume only pauses created by CCS quota management; never override a + user's manual pause. + +Routing strategy remains an explicit user choice. CCS must not infer +round-robin or fill-first from account count, plan tier, or quota state. + +## Image analysis + +For profiles that need managed vision support, CCS prepares the image-analysis +route before launch: + +```text +Resolve profile and provider | - +---> Priority: CLI flags > ENV vars > config.yaml > defaults +Resolve supported provider backend | - v - +------------------+ - | ResolvedProxyConfig | - | mode: local|remote | - +------------------+ +Provision managed MCP/runtime configuration | - +---> [mode = local] ---> Spawn local CLIProxyAPI binary - | | - | v - | localhost:8317 +Target invokes ImageAnalysis | - +---> [mode = remote] ---> Connect to remote server - | - v - +------------------+ - | Health Check | remote-proxy-client.ts - | /v1/models | 2s timeout - +------------------+ - | - +---> [reachable] ---> Use remote - | | - | v - | protocol://host:port - | - +---> [unreachable] ---> Fallback decision - | - +-----------------------------+ - | - +---> [fallbackEnabled] ---> Start local - | - +---> [remoteOnly] ---> Fail with error - - CLI Flags: - --proxy-host Remote hostname/IP - --proxy-port Port (default: 8317 HTTP, 443 HTTPS) - --proxy-protocol http or https - --proxy-auth-token Bearer authentication - --local-proxy Force local mode - --remote-only Fail if remote unreachable - - Environment Variables: - CCS_PROXY_HOST Remote hostname - CCS_PROXY_PORT Remote port - CCS_PROXY_PROTOCOL Protocol (http/https) - CCS_PROXY_AUTH_TOKEN Auth token - CCS_PROXY_FALLBACK_ENABLED Enable fallback (true/false) +CCS sends provider-scoped request + | +Return text result ``` -### Configuration Resolution +The managed route must not expose its internal credential in logs or user +settings. If managed preparation, authentication, or proxy readiness is +unavailable, CCS falls back to compatible native behavior where possible +instead of failing the entire target launch. -```typescript -// proxy-config-resolver.ts: Priority order -const resolved = { - ...DEFAULT_CONFIG, // 4. Defaults (lowest) - ...yamlConfig, // 3. config.yaml - ...envConfig, // 2. Environment variables - ...cliFlags, // 1. CLI flags (highest) -}; -``` +Provider-specific vision mappings are implementation data and should be read +from the image-analysis routing services and tests. -### Health Check +## Session and observability boundary -```typescript -// remote-proxy-client.ts -async function checkRemoteProxyHealth(config: ResolvedProxyConfig): Promise { - try { - const url = `${config.protocol}://${config.host}:${config.port}/v1/models`; - const response = await fetch(url, { - headers: config.authToken ? { Authorization: `Bearer ${config.authToken}` } : {}, - timeout: 2000, - }); - return response.ok; - } catch { - return false; - } -} -``` +Execution paths can record non-secret metadata such as profile identity, +profile type, canonical provider, target type, timestamps, duration, and +numeric process exit status. Logs must not contain provider tokens, API keys, +raw auth payloads, or personal account identifiers. ---- +Textual CCS log codes and numeric child-process exit statuses are different +contracts. See [Logging Contract](../logging-contract.md). -## Quota Management Flow (v7.14) +## Invariants -### Overview +- Canonical provider identity is registry-driven. +- Provider aliases normalize before execution. +- Backend restrictions are validated before starting local auth or routing. +- Remote-only mode never starts a local fallback. +- Persistent environment values are strings. +- Account display text is not a unique runtime identity. +- Manual pauses are never auto-resumed by quota management. +- Provider credentials never appear in architecture examples. -Hybrid quota management enables automatic detection of exhausted accounts and failover to next available account. -Before local CLIProxy startup, CCS reconciles the whole active account pool for the provider, not only the default account. When CCS detects any quota-exhausted account and a healthy fallback exists, it temporarily pauses the exhausted account out of CLIProxy rotation and automatically resumes that pause after the configured cooldown expires. This durable self-pause uses the same account registry and token movement path as dashboard/manual pause, so the dashboard shows the account as paused and CLIProxy cannot rediscover its token from the live `auth/` folder. +## Related documentation -``` -+===========================================================================+ -| Quota Management Architecture (v7.14) | -+===========================================================================+ - - Pre-Flight Check (before session start) - | - v - +------------------+ - | quota-manager.ts | Hybrid quota management - +------------------+ - | - +---> Get all active accounts for provider - | - +---> For each account: - | | - | v - | +------------------+ - | | quota-fetcher.ts | Provider-specific API calls - | +------------------+ - | | - | +---> Check isPaused flag --> Skip if paused - | | - | +---> Fetch quota from provider API - | | - Antigravity: fetchAvailableModels - | | - Claude: policy limits endpoint - | | - Codex: ChatGPT usage windows - | | - Gemini CLI: Code Assist quota buckets - | | - GitHub Copilot: copilot_internal/user snapshots (deprecated compatibility) - | | - | +---> Detect tier (free/paid/unknown) - | | - | +---> Check exhaustion status - | - +---> Pause exhausted non-default accounts when another healthy account exists - | - +---> Select best account (not paused, not exhausted) - | - +---> Auto-failover to next account if current exhausted - | - +---> Temporarily pause exhausted account when fallback exists - | - move token out of live auth discovery - | - persist cooldown expiry across launches - | - auto-resume only CCS-created quota pauses - - CLI Commands: - ccs cliproxy pause --> Set isPaused=true in account-manager - ccs cliproxy resume --> Set isPaused=false - ccs cliproxy status [account] --> Display quota + tier info - - Dashboard UI: - - Pause/Resume toggle per account - - Tier badge (free/paid/unknown) - - Quota usage display -``` - -### Account Selection Algorithm - -```typescript -// quota-manager.ts: Best account selection -function selectBestAccount(accounts: AccountInfo[]): AccountInfo | null { - // Priority: - // 1. Not paused - // 2. Not exhausted - // 3. Paid tier over free tier - // 4. Highest remaining quota - - return accounts - .filter(acc => !acc.isPaused && !acc.isExhausted) - .sort((a, b) => { - if (a.tier !== b.tier) return (a.tier === 'paid' ? -1 : 1); - return (b.remainingQuota || 0) - (a.remainingQuota || 0); - })[0] || null; -} -``` - ---- - -## Authentication Flow - -### OAuth Providers - Authorization Code Flow - -**Providers**: Gemini, Codex, Antigravity, Kiro (aws method) - -``` -+===========================================================================+ -| OAuth - Authorization Code Flow (Port-based) | -+===========================================================================+ - - 1. User runs: ccs codex - | - v - 2. Check token cache (~/.ccs/cliproxy/auth/) - | - +---> [Valid token] ---> Use cached token - | - +---> [No/Expired token] - | - v - 3. Start local OAuth server (localhost:9876) - | - v - 4. Open browser with OAuth request - | https://oauth-provider/authorize?redirect_uri=http://localhost:9876/callback - v - 5. User authorizes in browser - | - v - 6. OAuth provider redirects to localhost:9876/callback?code=XXXX - | - v - 7. Exchange auth code for access token - | - v - 8. Cache token locally (~/.ccs/cliproxy/auth/gemini.json) - | - v - 9. Proceed with Claude CLI -``` - -### OAuth Providers - Device Code Flow - -**Providers**: GitHub Copilot (ghcp, deprecated compatibility) - -Provider identity note: -- Providers that do not expose a reliable email no longer require a manual nickname during first auth. -- CCS derives a stable internal account identifier from the token/cache context and still allows the user to rename the account later. - -``` -+===========================================================================+ -| OAuth - Device Code Flow (No Port Needed) | -+===========================================================================+ - - 1. User runs: ccs ghcp - | - v - 2. Check token cache (~/.ccs/cliproxy/auth/) - | - +---> [Valid token] ---> Use cached token - | - +---> [No/Expired token] - | - v - 3. Request device code from GitHub - | - v - 4. Display user code + verification URL - | "Enter code XXXX-XXXX at github.com/login/device" - v - 5. User opens URL in browser and enters code - | - v - 6. Poll GitHub for token completion - | - v - 7. Receive and cache token locally - | - v - 8. Proceed with Claude CLI -``` - -### Kiro OAuth - Method-Aware Flow - -**Supported methods**: -- `aws`: Device Code (default, AWS org friendly) -- `aws-authcode`: Authorization Code via CLI flow -- `google`: Social OAuth via management API -- `github`: Social OAuth via management API (Dashboard flow) - -``` -+===========================================================================+ -| Kiro OAuth - Method-Aware Flow | -+===========================================================================+ - - Configuration: - ccs_profile: - target: claude - cliproxy: - provider: kiro - kiro_method: aws # or aws-authcode, google, github - - Flow: - Device Code (aws) - → /start endpoint (no callback port) - → Opens browser - → User enters code - → Poll /status - - Authorization Code (aws-authcode, google, github) - → /start-url endpoint - → Returns auth_url - → User visits URL - → Callback handled - → Poll /status for completion - - Key behavior: - - Device Code method uses /start route (no callback port) - - Callback/social methods use /start-url + status polling - - Some management flows return state first, auth_url later - - Manual nicknames are optional when the upstream provider does not return an email - - Account storage uses a stable internal identifier so reauth/update flows do not depend on dashboard list order -``` - -### API Key Profiles (GLM, Kimi) - -``` -+===========================================================================+ -| API Key Profile (Non-OAuth) | -+===========================================================================+ - - 1. User configures API key in settings - | - v - 2. Key stored in ~/.ccs/.settings.json - | - v - 3. Profile detection: APIKeyProfile - | - v - 4. Key passed via ANTHROPIC_AUTH_TOKEN env var - | - v - 5. Target adapter (Claude/Droid) handles delivery - | - └─ Claude: env var - └─ Droid: config file (~/.factory/settings.json) -``` - -### Anthropic Direct API Key - -``` -+===========================================================================+ -| Anthropic Direct API Key (Native Auth) | -+===========================================================================+ - - 1. User creates profile: ccs api create --preset anthropic - | - v - 2. Key stored in ~/.ccs/.settings.json - | env: { ANTHROPIC_API_KEY: "sk-ant-..." } - | (NO ANTHROPIC_BASE_URL, NO ANTHROPIC_AUTH_TOKEN) - v - 3. Profile detection: settings-based - | - v - 4. Key passed via ANTHROPIC_API_KEY env var - | Claude CLI uses native endpoint (api.anthropic.com) - v - 5. Claude CLI authenticates with x-api-key header - - Detection logic (profile-writer.ts): - - apiKey.startsWith('sk-ant-') -> native mode - - baseUrl.includes('api.anthropic.com') -> native mode - - Otherwise -> proxy mode (existing behavior) -``` - ---- - -## Image Analysis Hook Flow (v7.34) - -### Overview - -Image Analysis Hook enables vision model proxying through CLIProxy with automatic injection for all profile types. - -``` -+===========================================================================+ -| Image Analysis Hook Flow (v7.34) | -+===========================================================================+ - - Claude CLI with image input - | - v - Hook Installer (ensureProfileHooks) - | - +---> Check ~/.claude/hooks/openai-vision-hook.cjs exists - | - +---> If missing: auto-install via image-analyzer-hook-installer - | - v - Hook Configuration - | - +---> Set ANTHROPIC_IMAGE_HOOK_URL - | (proxy endpoint URL) - | - v - Claude CLI processes image request - | - v - Claude prefers ImageAnalysis MCP tool - | - v - CCS provider-backed image analysis - | - +---> Provider route resolved before launch - | - +---> Direct request to /api/provider//v1/messages - | - +---> Native Read fallback if runtime/auth/proxy is unavailable - | - v - Text description returned to Claude CLI -``` - -### Runtime Environment - -```typescript -// getImageAnalysisHookEnv() -{ - CCS_IMAGE_ANALYSIS_RUNTIME_BASE_URL: 'http://127.0.0.1:8317', - CCS_IMAGE_ANALYSIS_RUNTIME_PATH: '/api/provider/agy', - CCS_IMAGE_ANALYSIS_RUNTIME_API_KEY: 'ccs-internal-managed', -} -``` - -### Provider Support - -| Provider | Vision Support | Notes | -|----------|---|---| -| Gemini | ✓ | Via CCS ImageAnalysis provider route | -| Codex | ✓ | Via CCS ImageAnalysis provider route | -| Antigravity | ✓ | Via CCS ImageAnalysis provider route | -| Kiro | ✓ | Via mapped CCS provider route when configured | -| Copilot | ✓ | Deprecated compatibility route via mapped ghcp provider | -| GLM/Kimi | ✓ | Via explicit or fallback backend mapping | - ---- - -## Session Tracking - -All execution paths record session metadata including target CLI used: - -```typescript -{ - profileName: 'gemini', - profileType: 'clipproxy', - provider: 'google-gemini', - targetCli: 'claude', // NEW: which target was used - timestamp: '2026-02-16T10:40:00Z', - duration: 12345, - exitCode: 0, - model: 'claude-opus-4-6', -} -``` - -This enables analytics on target CLI usage and adoption. - ---- - -## Related Documentation - -- [System Architecture Index](./index.md) — Overall system design -- [Target Adapters](./target-adapters.md) — Multi-CLI adapter pattern -- [Codebase Summary](../codebase-summary.md) — Module structure -- [Code Standards](../code-standards.md) — Implementation guidelines +- [System Architecture](./index.md) +- [Target Adapters](./target-adapters.md) +- [Logging Contract](../logging-contract.md) +- [Codebase Summary](../codebase-summary.md) +- [Code Standards](../code-standards.md) diff --git a/docs/system-architecture/target-adapters.md b/docs/system-architecture/target-adapters.md index a2b11eb4..6218f087 100644 --- a/docs/system-architecture/target-adapters.md +++ b/docs/system-architecture/target-adapters.md @@ -1,803 +1,181 @@ # Target Adapters -Last Updated: 2026-05-18 +Target adapters are the last-mile boundary between CCS profile resolution and a +supported CLI runtime. Profile discovery and credential resolution happen +before this boundary; the selected adapter owns binary detection, target-native +credential delivery, argument and environment construction, and child-process +execution. -Detailed documentation of the target adapter pattern and implementations. +Related architecture: ---- +- [System architecture](./index.md) +- [Provider flows](./provider-flows.md) -## Overview +## Contract -The target adapter system enables CCS to dispatch credential-resolved profiles to different CLI implementations while maintaining a unified configuration and profile system. +The canonical interface and data types live in +[`src/targets/target-adapter.ts`](../../src/targets/target-adapter.ts). Each +adapter must: -**Key insight**: Profile resolution (detecting provider, loading auth, building credentials) is target-agnostic. Only the final credential delivery and process spawning differ per target. +1. detect its runtime binary without changing user configuration; +2. reject unsupported profile types before launch; +3. prepare only the target-owned configuration needed for the launch; +4. construct an argument vector and environment without exposing credentials in + arguments; +5. spawn the target with inherited stdio and forward process signals. ---- +The registry in +[`src/targets/target-registry.ts`](../../src/targets/target-registry.ts) maps a +target type to its adapter. Target names, built-in aliases, legacy alias +environment variables, and persistence eligibility are centralized in +[`src/targets/target-metadata.ts`](../../src/targets/target-metadata.ts). -## Target Adapter Interface - -Each CLI target implements the `TargetAdapter` contract: - -```typescript -export interface TargetAdapter { - readonly type: TargetType; // 'claude' | 'droid' | 'codex' - readonly displayName: string; // "Claude Code" | "Factory Droid" | "Codex CLI" - - /** Detect if the target CLI binary exists on system */ - detectBinary(): TargetBinaryInfo | null; - - /** Prepare credentials for delivery to target CLI */ - prepareCredentials(creds: TargetCredentials): Promise; - - /** Build spawn arguments for the target CLI */ - buildArgs( - profile: string, - userArgs: string[], - options?: { - creds?: TargetCredentials; - profileType?: ProfileType; - binaryInfo?: TargetBinaryInfo; - } - ): string[]; - - /** Build environment variables for the target CLI */ - buildEnv(creds: TargetCredentials, profileType: string): NodeJS.ProcessEnv; - - /** Spawn the target CLI process (replaces current process flow) */ - exec(args: string[], env: NodeJS.ProcessEnv, options?: { cwd?: string }): void; - - /** Check if a profile type is supported by this target */ - supportsProfileType(profileType: string): boolean; -} -``` - -### Type Definitions - -```typescript -export type TargetType = 'claude' | 'droid' | 'codex'; - -export interface TargetCredentials { - baseUrl: string; // API endpoint - apiKey: string; // Auth token - model?: string; // Model ID - provider?: 'anthropic' | 'openai' | 'generic-chat-completion-api'; - envVars?: NodeJS.ProcessEnv; // Additional env vars -} - -export interface TargetBinaryInfo { - path: string; // Full path to binary - needsShell: boolean; // Windows .cmd/.bat/.ps1? - version?: string; // Optional version string - features?: readonly string[]; // Capability probes -} -``` - ---- +Do not duplicate the TypeScript interface in this guide. Interface signatures +and credential fields change with runtime requirements; the source files are +the contract checked by the compiler. ## Target Resolution -CCS resolves which adapter to use via priority-ordered checks: - -### Resolution Priority - -``` -1. --target flag (CLI argument) — highest priority - └─ ccs --target droid glm - └─ ccs --target codex - -2. Explicit runtime entrypoint (`CCS_INTERNAL_ENTRY_TARGET`) — dedicated bin shims - └─ ccs-droid / ccsd → droid - └─ ccs-codex / ccsx → codex - └─ ccsxp → codex, then prepends `--config model_provider="cliproxy"` - -3. argv[0] detection (runtime alias pattern) — binary name mapping for same-binary/custom aliases - └─ ccs-droid (explicit alias) → droid - └─ ccsd (legacy shortcut) → droid - └─ ccs-codex (explicit alias) → codex - └─ ccsx (short alias) → codex - └─ ccs (regular command) → default - -4. Per-profile config (from ~/.ccs/config.yaml or settings.json) - └─ persisted targets are currently only `claude` and `droid` - └─ profiles: - glm: - target: droid - -5. Fallback: 'claude' — lowest priority -``` - -### Implementation - -```typescript -// src/targets/target-resolver.ts - -export function resolveTargetType( - args: string[], - profileConfig?: { target?: TargetType } -): TargetType { - // 1. Parse --target flags (supports --target value and --target=value) - // Repeated flags: last one wins. - const parsed = parseTargetFlags(args); - if (parsed.targetOverride) { - return parsed.targetOverride; - } - - // 2. Check explicit runtime entrypoint shim - const entrypointTarget = resolveEntrypointTarget(); - if (entrypointTarget) { - return entrypointTarget; - } - - // 3. Check argv[0] (binary name / custom alias map) - const binName = path.basename(process.argv[1] || process.argv0 || '').replace(/\.(cmd|bat|ps1|exe)$/i, ''); - if (ARGV0_TARGET_MAP[binName]) { - return ARGV0_TARGET_MAP[binName]; - } - - // 4. Check profile config - if (profileConfig?.target) { - // Persisted targets are validated before profile configuration is saved. - return profileConfig.target; - } - - // 5. Default to claude - return 'claude'; -} -``` - ---- - -## Claude Adapter - -### Implementation - -```typescript -// src/targets/claude-adapter.ts - -export class ClaudeAdapter implements TargetAdapter { - readonly type: TargetType = 'claude'; - readonly displayName = 'Claude Code'; - - detectBinary(): TargetBinaryInfo | null { - const info = getClaudeCliInfo(); - if (!info) return null; - return { path: info.path, needsShell: info.needsShell }; - } - - async prepareCredentials(_creds: TargetCredentials): Promise { - // No-op: Claude receives credentials via environment variables - } - - buildArgs(_profile: string, userArgs: string[]): string[] { - return userArgs; // Pass through user arguments unchanged - } - - buildEnv(creds: TargetCredentials, profileType: string): NodeJS.ProcessEnv { - const webSearchEnv = getWebSearchHookEnv(); - - // For native profiles, strip stale proxy env to prevent interference - const baseEnv = - profileType === 'account' || profileType === 'default' - ? stripAnthropicEnv(process.env) - : process.env; - - const env: NodeJS.ProcessEnv = { ...baseEnv, ...webSearchEnv }; - - if (creds.envVars) { - Object.assign(env, creds.envVars); - } - - // Deliver credentials via environment variables - if (creds.baseUrl) env['ANTHROPIC_BASE_URL'] = creds.baseUrl; - if (creds.apiKey) env['ANTHROPIC_AUTH_TOKEN'] = creds.apiKey; - if (creds.model) env['ANTHROPIC_MODEL'] = creds.model; - - return env; - } - - exec(args: string[], env: NodeJS.ProcessEnv, _options?: { cwd?: string }): void { - const claudeCli = detectClaudeCli(); - if (!claudeCli) { - void ErrorManager.showClaudeNotFound(); - process.exit(1); - return; - } - - // Handle Windows shell requirements - const isWindows = process.platform === 'win32'; - const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(claudeCli); - - let child: ChildProcess; - if (needsShell) { - const cmdString = [claudeCli, ...args].map(escapeShellArg).join(' '); - child = spawn(cmdString, { shell: true, stdio: 'inherit', env }); - } else { - child = spawn(claudeCli, args, { stdio: 'inherit', env }); - } - - // Handle process termination - const onSigInt = () => child.kill('SIGINT'); - const onSigTerm = () => child.kill('SIGTERM'); - process.once('SIGINT', onSigInt); - process.once('SIGTERM', onSigTerm); - child.on('exit', () => { - process.removeListener('SIGINT', onSigInt); - process.removeListener('SIGTERM', onSigTerm); - }); - } - - supportsProfileType(profileType: string): boolean { - // Claude supports all profile types - return true; - } -} -``` - -Native Claude launches keep user arguments session-scoped. The launch layer validates and normalizes -`--effort low|medium|high|xhigh|max` before spawning Claude, then passes it through without writing -to Claude or CCS configuration. CLIProxy-backed Claude launches still treat `--effort` as the CCS -thinking alias handled by CLIProxy. - -### Credential Delivery - -**Method**: Environment variables - -```bash -export ANTHROPIC_BASE_URL=https://api.anthropic.com -export ANTHROPIC_AUTH_TOKEN=sk-ant-... -export ANTHROPIC_MODEL=claude-opus-4-6 -export WEBSEARCH_HOOK_ENV=... # Image analysis, websearch -``` - -### Execution - -```bash -# Direct invocation -ccs codex -→ claude "args..." - with ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN set - -# With --target override -ccs --target claude glm -→ claude "args..." - with ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN set -``` - ---- - -## Droid Adapter - -### Implementation - -```typescript -// src/targets/droid-adapter.ts - -export class DroidAdapter implements TargetAdapter { - readonly type: TargetType = 'droid'; - readonly displayName = 'Factory Droid'; - - detectBinary(): TargetBinaryInfo | null { - const info = getDroidBinaryInfo(); - if (!info) return null; - - // Non-blocking version compatibility check - checkDroidVersion(info.path); - return info; - } - - async prepareCredentials(creds: TargetCredentials): Promise { - // Write custom model entry to ~/.factory/settings.json - await upsertCcsModel(creds.profile, { - model: creds.model || 'claude-opus-4-6', - displayName: `CCS ${creds.profile}`, - baseUrl: creds.baseUrl, - apiKey: creds.apiKey, - provider: creds.provider || 'anthropic', - }); - } - - buildArgs(profile: string, userArgs: string[]): string[] { - // Droid uses -m syntax for model selection - return ['-m', `custom:ccs-${profile}`, ...userArgs]; - } - - buildEnv(_creds: TargetCredentials, _profileType: string): NodeJS.ProcessEnv { - // Droid reads from config file — minimal env needed - return { ...process.env }; - } - - exec(args: string[], env: NodeJS.ProcessEnv, _options?: { cwd?: string }): void { - const droidPath = detectDroidCli(); - if (!droidPath) { - console.error('[X] Droid CLI not found. Install: npm i -g @factory/cli'); - process.exit(1); - return; - } - - // Handle Windows shell requirements - const isWindows = process.platform === 'win32'; - const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(droidPath); - - let child: ChildProcess; - if (needsShell) { - const cmdString = [droidPath, ...args].map(escapeShellArg).join(' '); - child = spawn(cmdString, { shell: true, stdio: 'inherit', env }); - } else { - child = spawn(droidPath, args, { stdio: 'inherit', env }); - } - - // Handle process termination - const onSigInt = () => child.kill('SIGINT'); - const onSigTerm = () => child.kill('SIGTERM'); - process.once('SIGINT', onSigInt); - process.once('SIGTERM', onSigTerm); - child.on('exit', () => { - process.removeListener('SIGINT', onSigInt); - process.removeListener('SIGTERM', onSigTerm); - }); - } - - supportsProfileType(profileType: string): boolean { - // Droid currently supports direct settings/default paths only - return profileType === 'settings' || profileType === 'default'; - } -} -``` - -### Credential Delivery - -**Method**: Config file (`~/.factory/settings.json`) - -```json -{ - "customModels": [ - { - "model": "claude-opus-4-6", - "displayName": "CCS gemini", - "baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai/", - "apiKey": "AIza...", - "provider": "openai" - }, - { - "model": "glm-4", - "displayName": "CCS glm", - "baseUrl": "https://open.bigmodel.cn/api/paas/v4/", - "apiKey": "your-glm-key", - "provider": "openai" - } - ] -} -``` - -### Execution - -```bash -# Direct invocation -ccs codex -→ droid -m custom:ccs-codex "args..." - (credentials loaded from ~/.factory/settings.json) - -# With --target override -ccs --target droid glm -→ droid -m custom:ccs-glm "args..." - (credentials loaded from ~/.factory/settings.json) -``` - -### Runtime Alias Pattern - -```bash -# Built-in package bin aliases -ccs-droid glm -→ Target: droid (forced by runtime alias) -→ droid -m custom:ccs-glm "args..." - -# Legacy shortcut still works -ccsd glm -→ Target: droid (forced by runtime alias) -→ droid -m custom:ccs-glm "args..." -``` - -On Windows, `ccs-droid.cmd`, `ccsd.cmd`, `ccsd.bat`, `ccsd.ps1`, and `ccsd.exe` wrappers are also recognized. - -Additional alias names can be configured at runtime after you create a matching -symlink or another launcher that preserves the invoked basename. Use `CCS_TARGET_ALIASES` (preferred, -`target=alias1,alias2;...`) or legacy `CCS_DROID_ALIASES` (comma-separated). -Example: - -```bash -ln -s /path/to/ccs /path/to/mydroid -CCS_TARGET_ALIASES=droid=mydroid -``` - ---- - -## Codex Adapter - -### Implementation - -The Codex adapter keeps CCS-backed Codex launches transient. It does not rewrite -`~/.codex/config.toml`. Instead it: - -- passes through native default Codex sessions unchanged -- probes the installed Codex binary for `--config ` support -- injects CCS-backed provider credentials through temporary `-c` overrides -- stores the routed API key only in process env via `CCS_CODEX_API_KEY` - -```typescript -// src/targets/codex-adapter.ts - -export class CodexAdapter implements TargetAdapter { - readonly type: TargetType = 'codex'; - readonly displayName = 'Codex CLI'; - - detectBinary(): TargetBinaryInfo | null { - return getCodexBinaryInfo(); - } - - async prepareCredentials(_creds: TargetCredentials): Promise { - // No file writes. Codex uses transient -c overrides plus env_key injection. - } - - buildArgs(profile: string, userArgs: string[], options?: BuildOptions): string[] { - if ((options?.profileType || 'default') === 'default') { - return userArgs; - } - - if (!codexBinarySupportsConfigOverrides(options?.binaryInfo)) { - throw new Error('Upgrade Codex before using CCS-backed Codex profiles.'); - } - - return [ - '-c', - 'model_provider=\"ccs_runtime\"', - '-c', - 'model_providers.ccs_runtime.base_url=\"http://127.0.0.1:8317/api/provider/codex\"', - '-c', - 'model_providers.ccs_runtime.env_key=\"CCS_CODEX_API_KEY\"', - '-c', - 'model_providers.ccs_runtime.wire_api=\"responses\"', - ...userArgs, - ]; - } - - buildEnv(creds: TargetCredentials, profileType: string): NodeJS.ProcessEnv { - const env = { ...stripAnthropicEnv(process.env) }; - if (profileType !== 'default') { - env['CCS_CODEX_API_KEY'] = creds.apiKey; - } - return env; - } -} -``` - -### Support Matrix - -Codex is a real runtime target, but it is intentionally narrower than Claude or Droid in v1: - -| Profile Type | Codex Target | Notes | -|--------------|--------------|-------| -| `default` | Yes | Uses existing native Codex auth/config | -| `cliproxy` provider=`codex` | Yes | Routed through CLIProxy Codex Responses bridge | -| `cliproxy` composite | No | Not proven native-Codex-safe | -| `settings` with Codex bridge metadata | Yes | Only when the API profile resolves to a Codex CLIProxy bridge | -| `settings` generic API profile | No | Claude/Droid only | -| `account` | No | Claude-only account isolation concept | -| `copilot` | No | Not a native Codex provider path | - -### Codex Dashboard Surface - -CCS also exposes a dedicated dashboard route at `ccs config` -> `Compatible` -> `Codex CLI`. -That page is intentionally narrower than the Droid dashboard in overall scope, but it is no -longer read-mostly: - -- reads and writes only the user config layer: `~/.codex/config.toml` or `$CODEX_HOME/config.toml` -- provides guided controls for top-level settings, project trust, profiles, model providers, - MCP servers, and supported feature flags -- keeps a raw `config.toml` editor as the escape hatch for unsupported or fidelity-sensitive edits -- shows binary detection, user-layer config summaries, support-matrix guidance, and upstream docs -- normalizes TOML formatting and drops comments on structured saves -- keeps structured controls disabled while raw TOML is dirty or invalid, validates project trust - paths as absolute or `~/...`, and lets feature flags reset back to Codex defaults -- warns that transient CCS runtime overrides such as `codex -c key=value` and - `CCS_CODEX_API_KEY` can change the effective runtime without persisting into the file editor - -This keeps the dashboard honest about Codex's merged configuration model while still giving users -one place to inspect and manage the user-owned layer safely. - -### Runtime Entrypoints and argv[0] Fallback - -```bash -# Built-in package bin entrypoints -ccs-codex -→ dist/bin/codex-runtime.js -→ CCS_INTERNAL_ENTRY_TARGET=codex - -ccsx -→ dist/bin/codex-runtime.js -→ CCS_INTERNAL_ENTRY_TARGET=codex -→ passes native Codex diagnostics plus known upstream Codex subcommands and aliases through before CCS profile detection -→ reserves CCS-owned `auth`, `doctor`, and `update` - -ccsxp -→ dist/bin/ccsxp-runtime.js -→ CCS_INTERNAL_ENTRY_TARGET=codex -→ injects native `model_provider="cliproxy"` override -→ pins CODEX_HOME to native `~/.codex` unless `CCSXP_CODEX_HOME` is set -→ repairs `[model_providers.cliproxy]` in the active Codex `config.toml` -→ preserves valid custom `base_url` values for remote or non-default CLIProxy endpoints -→ injects the effective CCS CLIProxy auth token into the provider's configured `env_key` -→ ignores the configured CCS default account/profile and stays in native Codex default mode -``` - -If a user launches CCS through a custom shim instead of the built-in package bins, target -resolution falls back to `argv[0]` aliases from `CCS_TARGET_ALIASES` or legacy -`CCS_CODEX_ALIASES`: - -```bash -ln -s /path/to/ccs /path/to/mycodex -CCS_TARGET_ALIASES='codex=mycodex' -# Legacy fallback: -CCS_CODEX_ALIASES='mycodex' -``` - ---- - -## Registry and Lookup - -The target registry is a simple map-based store for adapters: - -```typescript -// src/targets/target-registry.ts - -const adapters = new Map(); - -export function registerTarget(adapter: TargetAdapter): void { - adapters.set(adapter.type, adapter); -} - -export function getTarget(type: TargetType): TargetAdapter { - const adapter = adapters.get(type); - if (!adapter) { - throw new Error(`Unknown target "${type}"`); - } - return adapter; -} - -export function getDefaultTarget(): TargetAdapter { - return getTarget('claude'); -} -``` - -### Adapter Registration - -At startup, adapters self-register: - -```typescript -// src/ccs.ts (initialization) - -registerTarget(new ClaudeAdapter()); -registerTarget(new DroidAdapter()); -registerTarget(new CodexAdapter()); -``` - ---- - -## Execution Flow - -### Step-by-Step - -``` -1. Parse command-line arguments - └─ args: ['--target', 'droid', 'glm'] - -2. Resolve target type - └─ resolveTargetType(args) → 'droid' - └─ stripTargetFlag(args) → ['glm'] - -3. Detect and resolve profile - └─ detectProfile(['glm']) → { profile: 'glm', ... } - └─ Load credentials from config/CLIProxy/env - -4. Build credentials object - └─ TargetCredentials { - baseUrl: '...', - apiKey: '...', - model: 'claude-opus-4-6', - envVars: { CCS_PROFILE_NAME: 'glm', ... } - } - -5. Get target adapter - └─ getTarget('droid') → DroidAdapter instance - -6. Prepare credentials - └─ adapter.prepareCredentials(creds) - └─ DroidAdapter: writes to ~/.factory/settings.json - -7. Build spawn arguments - └─ adapter.buildArgs('glm', []) → ['-m', 'custom:ccs-glm'] - -8. Build environment - └─ adapter.buildEnv(creds, profileType) → process.env - -9. Spawn target CLI - └─ adapter.exec(spawnArgs, env) - └─ exec spawn('droid', ['-m', 'custom:ccs-glm', ...]) - -10. Replace current process - └─ Child process inherits stdio - └─ Signal handlers propagate to child -``` - ---- - -## Adding a New Target - -To support a new CLI (e.g., MyAI CLI), follow this pattern: - -### 1. Create Adapter Class - -```typescript -// src/targets/myai-adapter.ts - -export class MyAiAdapter implements TargetAdapter { - readonly type: TargetType = 'myai'; - readonly displayName = 'MyAI CLI'; - - detectBinary(): TargetBinaryInfo | null { - const path = which.sync('myai', { nothrow: true }); - if (!path) return null; - return { path, needsShell: process.platform === 'win32' }; - } - - async prepareCredentials(creds: TargetCredentials): Promise { - // Write to ~/.myai/config or similar - } - - buildArgs(profile: string, userArgs: string[]): string[] { - return ['-p', profile, ...userArgs]; - } - - buildEnv(creds: TargetCredentials, _profileType: string): NodeJS.ProcessEnv { - return { - ...process.env, - MYAI_API_KEY: creds.apiKey, - MYAI_API_URL: creds.baseUrl, - }; - } - - exec(args: string[], env: NodeJS.ProcessEnv): void { - const myaiPath = this.detectBinary()?.path; - if (!myaiPath) { - console.error('[X] MyAI CLI not found'); - process.exit(1); - } - spawn(myaiPath, args, { stdio: 'inherit', env }); - } - - supportsProfileType(profileType: string): boolean { - return true; // or implement specific logic - } -} -``` - -### 2. Update Type Definition - -```typescript -// src/targets/target-adapter.ts - -export type TargetType = 'claude' | 'droid' | 'codex' | 'myai'; -``` - -### 3. Register in ccs.ts - -```typescript -registerTarget(new MyAiAdapter()); -``` - -### 4. Update Documentation - -- Add to [Codebase Summary](../codebase-summary.md) -- Update Code Standards adapter examples -- Document CLI-specific behavior - ---- - -## Cross-Platform Considerations - -### Windows Shell Detection - -Both adapters check for shell-requiring binaries: - -```typescript -const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(binaryPath); - -if (needsShell) { - const cmdString = [binaryPath, ...args].map(escapeShellArg).join(' '); - spawn(cmdString, { shell: true, stdio: 'inherit' }); -} else { - spawn(binaryPath, args, { stdio: 'inherit' }); -} -``` - -### Environment Variable Escaping - -Arguments passed to shell are escaped to prevent injection: - -```typescript -export function escapeShellArg(arg: string): string { - // Wrap in quotes and escape internal quotes - return `"${arg.replace(/"/g, '\\"')}"`; -} -``` - -### Signal Handling - -Both adapters propagate signals from parent to child: - -```typescript -const onSigInt = () => child.kill('SIGINT'); -const onSigTerm = () => child.kill('SIGTERM'); -process.once('SIGINT', onSigInt); -process.once('SIGTERM', onSigTerm); - -child.on('exit', () => { - process.removeListener('SIGINT', onSigInt); - process.removeListener('SIGTERM', onSigTerm); -}); -``` - -This ensures CTRL+C and graceful shutdowns work correctly. - ---- - -## Testing Target Adapters - -### Unit Tests - -```typescript -describe('ClaudeAdapter', () => { - it('detects Claude CLI', () => { - const adapter = new ClaudeAdapter(); - const binary = adapter.detectBinary(); - expect(binary).not.toBeNull(); - }); - - it('builds env with credentials', () => { - const adapter = new ClaudeAdapter(); - const env = adapter.buildEnv({ - baseUrl: 'https://api.anthropic.com', - apiKey: 'sk-ant-...', - model: 'claude-opus-4-6', - }, 'cliproxy'); - - expect(env['ANTHROPIC_AUTH_TOKEN']).toBe('sk-ant-...'); - }); -}); -``` - -### Integration Tests - -```bash -# Test Claude adapter -ccs --target claude help - -# Test Droid adapter (if installed) -ccs --target droid help - -# Test Codex adapter (if installed) -ccs --target codex -ccs-codex -ccsxp - -# Test argv[0] detection -ccs-droid help -ccsx -``` - ---- - -## Related Documentation - -- [Codebase Summary](../codebase-summary.md) — Module structure -- [Code Standards](../code-standards.md) — Adapter pattern guidelines -- [System Architecture Index](./index.md) — Overall system design +[`src/targets/target-resolver.ts`](../../src/targets/target-resolver.ts) selects +the runtime in this order: + +1. `--target ` or `--target=`; if repeated, the last flag wins. +2. A trusted package runtime entrypoint identified by + `CCS_INTERNAL_ENTRY_TARGET`. +3. The invoked binary name, including built-in or configured aliases. +4. A persisted per-profile target. +5. `claude`. + +All `--target` flags are removed before the remaining arguments reach the +runtime. Parsing stops at the `--` option terminator. + +The built-in runtime aliases are derived from target metadata: + +| Target | Built-in aliases | +| --- | --- | +| Claude Code | Base `ccs` command | +| Factory Droid | `ccs-droid`, `ccsd` | +| Codex CLI | `ccs-codex`, `ccsx`, `ccsxp` | + +Custom aliases use `CCS_TARGET_ALIASES` with entries such as +`droid=team-droid;codex=team-codex`. The target-specific legacy environment +variables remain compatibility inputs. Alias values are validated and cannot +replace reserved package binary names. + +## Runtime Compatibility + +Adapter-level checks are deliberately conservative. Flow-specific compatibility +is evaluated in +[`src/targets/target-runtime-compatibility.ts`](../../src/targets/target-runtime-compatibility.ts), +which has the provider and bridge context needed for an accurate decision. + +| Profile flow | Claude | Droid | Codex | +| --- | --- | --- | --- | +| Native default | Supported | Conditional: requires resolved `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN` | Supported | +| Settings/API profile | Supported | Supported | Codex CLIProxy bridge only | +| CLIProxy profile | Supported | Supported | Non-composite `codex` provider only | +| Claude account | Supported | Not supported | Not supported | +| Copilot | Supported | Not supported | Not supported | +| Cursor local proxy | Supported | Not supported | Not supported | + +When changing compatibility, update both the runtime evaluator and its focused +tests. The authoritative coverage is in +[`tests/unit/targets/target-runtime-compatibility.test.ts`](../../tests/unit/targets/target-runtime-compatibility.test.ts). + +The compatibility evaluator accepts Droid's native-default flow, but adapter +preparation still validates the resolved credentials. A default Droid launch +cannot proceed without both a non-empty base URL and auth token. + +## Credential and Configuration Boundaries + +### Claude Code + +[`src/targets/claude-adapter.ts`](../../src/targets/claude-adapter.ts) delivers +resolved provider values through the child environment. Native account and +default launches remove stale Anthropic routing variables before execution. +Browser and WebSearch launch preparation may add runtime-specific arguments and +environment values. + +The adapter does not persist provider credentials to Claude settings. + +### Factory Droid + +[`src/targets/droid-adapter.ts`](../../src/targets/droid-adapter.ts) validates +the resolved base URL and token, then delegates the target-owned write to +[`src/targets/droid-config-manager.ts`](../../src/targets/droid-config-manager.ts). +That manager updates the CCS custom model and active model in Factory settings. +The adapter passes user arguments through; it does not inject a `-m` selector. + +Factory settings are a persistent user-owned surface. Writes must preserve +unrelated settings and use the configuration manager rather than direct JSON +replacement. + +### Codex CLI + +[`src/targets/codex-adapter.ts`](../../src/targets/codex-adapter.ts) keeps normal +CCS-backed launches transient: + +- native default sessions keep native Codex authentication and configuration; +- CCS-backed sessions use `-c key=value` overrides for a Responses-compatible + runtime provider; +- the resolved API key is supplied through the provider's environment key, not + on the command line; +- stale Anthropic and nested Codex-session variables are removed before spawn. + +The `ccsxp` shortcut is the explicit exception. It may repair the dedicated +`cliproxy` provider block in the active Codex configuration through +[`src/targets/codex-cliproxy-provider-config.ts`](../../src/targets/codex-cliproxy-provider-config.ts). +That repair preserves a valid custom base URL and the configured environment-key +name. General Codex launches must not rewrite `config.toml`. + +## Execution Invariants + +The dispatcher owns the order of operations: + +1. parse CCS-owned arguments and resolve the target; +2. resolve the profile and provider credentials; +3. evaluate target/profile/provider compatibility; +4. detect the target binary; +5. call `prepareCredentials`; +6. build target arguments and environment; +7. execute the child runtime. + +Flow implementations live under +[`src/dispatcher/flows/`](../../src/dispatcher/flows/). Shared target execution +logic lives in +[`src/dispatcher/target-executor.ts`](../../src/dispatcher/target-executor.ts). + +All adapters must preserve these invariants: + +- user credentials never appear in documented examples, logs, or spawn + arguments; +- target-owned persistent writes are explicit and scoped; +- stale routing variables from another runtime do not leak into the child; +- Windows wrapper handling does not use a shell unless the wrapper format + requires one; +- child signals and exit behavior propagate to the CCS process; +- binary and launch failures run registered cleanup before exit. + +## Adding or Changing a Target + +Change the smallest complete set: + +1. update `TargetType` and the adapter contract only when required; +2. add target metadata and aliases in `target-metadata.ts`; +3. implement and register the adapter through `src/targets/index.ts`; +4. add flow-aware compatibility rules; +5. add focused resolver, adapter, compatibility, and integration tests; +6. update CLI help and dashboard controls if the target becomes user + configurable. + +Start with these test suites: + +- [`tests/unit/targets/target-resolver.test.ts`](../../tests/unit/targets/target-resolver.test.ts) +- [`tests/unit/targets/target-registry.test.ts`](../../tests/unit/targets/target-registry.test.ts) +- [`tests/unit/targets/target-runtime-compatibility.test.ts`](../../tests/unit/targets/target-runtime-compatibility.test.ts) +- target-specific tests under + [`tests/unit/targets/`](../../tests/unit/targets/) + +Do not describe a target as supported from adapter registration alone. Support +requires a compatible profile flow, safe credential delivery, binary detection, +execution behavior, and tests for the full combination.