mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-03 20:13:02 +00:00
docs(architecture): reconcile provider and target contracts
This commit is contained in:
1 parent
b918783293
commit
ebe1746459
3 files changed
+571
-1806
No files matched your search
+211
-430
@@ -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-<profile>`
|
||||
- Spawns: `droid -m custom:ccs-<profile> <args>`
|
||||
- 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 <cli>] <profile> [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
|
||||
|
|
||||
+---> <profile>.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/<profile>/
|
||||
|
|
||||
+---> 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/<profile>.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
|
||||
├── <profile>.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)
|
||||
@@ -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 <profile>.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 <host> Remote hostname/IP
|
||||
--proxy-port <port> Port (default: 8317 HTTP, 443 HTTPS)
|
||||
--proxy-protocol <proto> http or https
|
||||
--proxy-auth-token <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<boolean> {
|
||||
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 <account> --> Set isPaused=true in account-manager
|
||||
ccs cliproxy resume <account> --> 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/<profile>.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/<profile>.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/<backend>/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)
|
||||
@@ -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<void>;
|
||||
|
||||
/** 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<void> {
|
||||
// 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<void> {
|
||||
// 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 <model> 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 <key=value>` 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<void> {
|
||||
// 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<TargetType, TargetAdapter>();
|
||||
|
||||
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<void> {
|
||||
// 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 <name>` or `--target=<name>`; 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.
|
||||
Reference in new issue
Block a user