mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 04:14:57 +00:00
feat(targets): add multi-target CLI adapter system (Droid support)
Implement target adapter pattern enabling CCS CLI to support multiple backend targets (Claude, Droid) via pluggable adapters. Core additions: - TargetAdapter interface for pluggable target implementations - ClaudeAdapter and DroidAdapter concrete implementations - Target registry (singleton Map-based storage) - Target resolver with precedence: --target flag > per-profile config > busybox detection - Droid config manager with atomic writes and file locking to ~/.factory/settings.json - Droid binary detector to validate runtime environment - Adapter dispatch integrated into ccs.ts main execution flow - ccsd busybox alias for seamless Droid invocation - --target flag documentation in help - Session tracking enriched with target metadata - Dashboard target badge for visual identification Testing: - 43 unit tests covering resolver, registry, config manager, and adapters - Full coverage of target detection logic and edge cases Documentation: - Refactored system-architecture.md into modular docs/system-architecture/ subdirectory - Updated code-standards.md with target adapter guidelines - Updated codebase-summary.md with architecture overview - Updated maintainability baseline (33.8% → 35.2%) This establishes extensible foundation for multi-target support without breaking existing Claude workflows. Droid adapter is production-ready but defaults to Claude for backward compatibility.
This commit is contained in:
1 parent
8ab78f039c
commit
7d7054e2c0
27 files changed
+3157
-766
No files matched your search
@@ -40,6 +40,9 @@ Code standards, modularization patterns, and conventions for the CCS codebase.
|
||||
|------------|---------|-------------|
|
||||
| kebab-case | `cliproxy-executor.ts` | All TypeScript/TSX files |
|
||||
| kebab-case | `profile-detector.ts` | Multi-word file names |
|
||||
| *-adapter.ts | `claude-adapter.ts`, `droid-adapter.ts` | TargetAdapter implementations |
|
||||
| *-detector.ts | `droid-detector.ts` | Binary detection logic |
|
||||
| *-manager.ts | `droid-config-manager.ts` | Config/state management |
|
||||
| PascalCase | `BinaryManager` | Class exports only |
|
||||
| camelCase | `detectProfile` | Function exports |
|
||||
|
||||
@@ -157,6 +160,84 @@ Allowed when:
|
||||
|
||||
---
|
||||
|
||||
## Target Adapter Pattern
|
||||
|
||||
The target adapter pattern enables pluggable support for multiple CLI implementations (Claude Code, Factory Droid, etc.) while preserving a unified profile system.
|
||||
|
||||
### Pattern Overview
|
||||
|
||||
**Each CLI target implements a `TargetAdapter` interface:**
|
||||
|
||||
```typescript
|
||||
interface TargetAdapter {
|
||||
readonly type: TargetType; // 'claude' | 'droid'
|
||||
readonly displayName: string; // Human-readable name
|
||||
|
||||
detectBinary(): TargetBinaryInfo | null; // Find CLI on system
|
||||
prepareCredentials(creds: TargetCredentials): Promise<void>; // Deliver credentials
|
||||
buildArgs(profile: string, userArgs: string[]): string[]; // Build CLI args
|
||||
buildEnv(creds: TargetCredentials, type: string): Env; // Build env vars
|
||||
exec(args: string[], env: Env): void; // Spawn CLI process
|
||||
supportsProfileType(type: string): boolean; // Validate profile
|
||||
}
|
||||
```
|
||||
|
||||
### Key Differences Per Target
|
||||
|
||||
| Aspect | Claude | Droid |
|
||||
|--------|--------|-------|
|
||||
| **Credential delivery** | Environment variables | Config file (~/.factory/settings.json) |
|
||||
| **Spawn args** | `claude <args>` | `droid -m custom:ccs-<profile> <args>` |
|
||||
| **Config write** | None (uses env) | `upsertCcsModel()` writes to settings |
|
||||
| **Binary detection** | `detectClaudeCli()` | `detectDroidCli()` with version check |
|
||||
|
||||
### Target Resolution Priority
|
||||
|
||||
Resolves which adapter to use via `resolveTargetType()`:
|
||||
|
||||
```
|
||||
1. --target <name> flag (highest priority)
|
||||
↓
|
||||
2. Profile config: profileConfig.target field
|
||||
↓
|
||||
3. argv[0] detection (busybox pattern):
|
||||
- ccsd → droid
|
||||
- ccs → default
|
||||
↓
|
||||
4. Fallback: 'claude' (lowest priority)
|
||||
```
|
||||
|
||||
### Registration Pattern
|
||||
|
||||
At startup, adapters self-register into the runtime registry:
|
||||
|
||||
```typescript
|
||||
// In ccs.ts or initialization
|
||||
registerTarget(new ClaudeAdapter());
|
||||
registerTarget(new DroidAdapter());
|
||||
|
||||
// Later, when executing
|
||||
const targetType = resolveTargetType(args, profileConfig);
|
||||
const adapter = getTarget(targetType);
|
||||
|
||||
await adapter.prepareCredentials(credentials);
|
||||
const spawnArgs = adapter.buildArgs(profile, userArgs);
|
||||
adapter.exec(spawnArgs, adapter.buildEnv(credentials, profileType));
|
||||
```
|
||||
|
||||
### Adding a New Target
|
||||
|
||||
To add support for a new CLI (e.g., `newcli`):
|
||||
|
||||
1. Create `src/targets/newcli-adapter.ts` implementing `TargetAdapter`
|
||||
2. Implement each required method (detection, credential delivery, spawning)
|
||||
3. Create `src/targets/newcli-detector.ts` for binary detection logic
|
||||
4. Export from `src/targets/index.ts`
|
||||
5. Register in `ccs.ts`: `registerTarget(new NewCliAdapter())`
|
||||
6. Update `TargetType` union to include `'newcli'`
|
||||
|
||||
---
|
||||
|
||||
## Monster File Splitting Methodology
|
||||
|
||||
When splitting large files (500+ lines), follow this process:
|
||||
@@ -328,6 +409,26 @@ Use ASCII box drawing for error displays:
|
||||
+=====================================+
|
||||
```
|
||||
|
||||
### Cross-Platform Adapter Spawning
|
||||
|
||||
When implementing target adapters, handle platform differences for binary spawning:
|
||||
|
||||
```typescript
|
||||
// Window shell detection (.cmd, .bat, .ps1 require shell)
|
||||
const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(binaryPath);
|
||||
|
||||
if (needsShell) {
|
||||
// Escape arguments and use shell: true
|
||||
const cmdString = [binaryPath, ...args].map(escapeShellArg).join(' ');
|
||||
spawn(cmdString, { shell: true, stdio: 'inherit' });
|
||||
} else {
|
||||
// Direct spawn (Unix-like, unshelled Windows executables)
|
||||
spawn(binaryPath, args, { stdio: 'inherit' });
|
||||
}
|
||||
```
|
||||
|
||||
This pattern is used in both `ClaudeAdapter` and `DroidAdapter` to ensure cross-platform consistency.
|
||||
|
||||
---
|
||||
|
||||
## React Component Standards (UI)
|
||||
|
||||
@@ -56,6 +56,16 @@ src/
|
||||
│ ├── update-command.ts # Self-update logic
|
||||
│ └── version-command.ts # Version display
|
||||
│
|
||||
├── targets/ # Multi-target adapter system (NEW)
|
||||
│ ├── index.ts # Barrel export
|
||||
│ ├── target-adapter.ts # TargetAdapter interface contract
|
||||
│ ├── target-registry.ts # Registry for runtime adapter lookup
|
||||
│ ├── target-resolver.ts # Resolution logic (flag > config > argv[0])
|
||||
│ ├── claude-adapter.ts # Claude Code CLI implementation
|
||||
│ ├── droid-adapter.ts # Factory Droid CLI implementation
|
||||
│ ├── droid-detector.ts # Droid binary detection & version checks
|
||||
│ └── droid-config-manager.ts # ~/.factory/settings.json management
|
||||
│
|
||||
├── auth/ # Authentication module
|
||||
│ ├── index.ts # Barrel export
|
||||
│ ├── commands/ # Auth-specific CLI commands
|
||||
@@ -181,6 +191,7 @@ src/
|
||||
| Category | Directories | Purpose |
|
||||
|----------|-------------|---------|
|
||||
| Core | `commands/`, `errors/` | CLI commands, error handling |
|
||||
| Targets | `targets/` | Multi-CLI adapter pattern (Claude Code, Factory Droid, extensible) |
|
||||
| Auth | `auth/`, `cliproxy/auth/` | Authentication across providers |
|
||||
| Config | `config/`, `types/` | Configuration & type definitions |
|
||||
| Providers | `cliproxy/`, `copilot/`, `glmt/` | Provider integrations (7 CLIProxy providers: gemini, codex, agy, qwen, iflow, kiro, ghcp) |
|
||||
@@ -190,6 +201,47 @@ src/
|
||||
| Services | `web-server/`, `api/` | HTTP server, API services |
|
||||
| Utilities | `utils/`, `management/` | Helpers, diagnostics |
|
||||
|
||||
### Target Adapter Module
|
||||
|
||||
The targets module provides an extensible interface for dispatching profiles to different CLI implementations.
|
||||
|
||||
**Key components:**
|
||||
|
||||
1. **TargetAdapter Interface** - Contract that each CLI implementation must fulfill:
|
||||
- `detectBinary()` - Find CLI binary on system (platform-specific)
|
||||
- `prepareCredentials()` - Deliver credentials (env vars vs config file writes)
|
||||
- `buildArgs()` - Construct target-specific argument list
|
||||
- `buildEnv()` - Construct environment for target CLI
|
||||
- `exec()` - Spawn target process (cross-platform)
|
||||
- `supportsProfileType()` - Verify profile compatibility
|
||||
|
||||
2. **Target Resolution** - Priority order:
|
||||
- `--target <cli>` flag (CLI argument)
|
||||
- Per-profile `target` field (from config.yaml)
|
||||
- `argv[0]` detection (busybox pattern: `ccsd` → droid)
|
||||
- Default: `claude`
|
||||
|
||||
3. **Implementations:**
|
||||
- **ClaudeAdapter** - Wraps existing behavior; delivers credentials via environment variables
|
||||
- **DroidAdapter** - New; writes to ~/.factory/settings.json and spawns with `-m custom:ccs-<profile>` flag
|
||||
|
||||
4. **Registry** - Map-based lookup (O(1)) for registered adapters at runtime
|
||||
|
||||
**Usage flow:**
|
||||
```
|
||||
Profile resolution (existing)
|
||||
↓
|
||||
Target resolution (via resolver.ts)
|
||||
↓
|
||||
Get adapter from registry
|
||||
↓
|
||||
Prepare credentials (adapter.prepareCredentials)
|
||||
↓
|
||||
Build args & env (adapter.buildArgs, buildEnv)
|
||||
↓
|
||||
Spawn target CLI (adapter.exec)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI Source (`ui/src/`)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"sourceDirectory": "src",
|
||||
"largeFileThresholdLoc": 350,
|
||||
"typeScriptFileCount": 341,
|
||||
"locInSrc": 67708,
|
||||
"processExitReferenceCount": 164,
|
||||
"typeScriptFileCount": 347,
|
||||
"locInSrc": 69998,
|
||||
"processExitReferenceCount": 168,
|
||||
"synchronousFsApiReferenceCount": 850,
|
||||
"largeFileCountOver350Loc": 55
|
||||
}
|
||||
@@ -1,754 +0,0 @@
|
||||
# CCS System Architecture
|
||||
|
||||
Last Updated: 2026-02-04
|
||||
|
||||
High-level architecture documentation for the CCS (Claude Code Switch) system.
|
||||
|
||||
---
|
||||
|
||||
## System Overview
|
||||
|
||||
CCS is a CLI wrapper that enables seamless switching between multiple Claude accounts and alternative AI providers (GLM, Gemini, Codex, Kiro, GitHub Copilot, OpenRouter, Qwen, Kimi, DeepSeek). It consists of two main components:
|
||||
|
||||
1. **CLI Application** (`src/`) - Node.js TypeScript CLI
|
||||
2. **Dashboard UI** (`ui/`) - React web application served by Express
|
||||
|
||||
CCS v7.34 adds Image Analysis Hook for vision model proxying through CLIProxy with automatic injection for all profile types.
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| CCS System |
|
||||
+===========================================================================+
|
||||
| |
|
||||
| +------------------+ +-----------------+ +----------------+ |
|
||||
| | User Terminal | ---> | CCS CLI | ---> | Claude Code | |
|
||||
| | (ccs command) | | (src/ccs.ts) | | CLI | |
|
||||
| +------------------+ +-----------------+ +----------------+ |
|
||||
| | | |
|
||||
| v v |
|
||||
| +------------------+ +-----------------+ +----------------+ |
|
||||
| | Dashboard UI | <--> | Express | ---> | Provider APIs | |
|
||||
| | (React SPA) | | Web Server | | (Claude/GLM/ | |
|
||||
| +------------------+ +-----------------+ | Gemini/etc) | |
|
||||
| | +----------------+ |
|
||||
| v |
|
||||
| +---------------------+ |
|
||||
| | CLIProxyAPI | |
|
||||
| | (Local or Remote) | |
|
||||
| +---------------------+ |
|
||||
| |
|
||||
+===========================================================================+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### CLI Layer
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| CLI Architecture |
|
||||
+===========================================================================+
|
||||
|
||||
User Input (ccs <profile> [args])
|
||||
|
|
||||
v
|
||||
+-------------+
|
||||
| ccs.ts | Entry point, command routing
|
||||
+-------------+
|
||||
|
|
||||
+---> [Version/Help/Doctor/etc.] ---> Exit
|
||||
|
|
||||
v
|
||||
+-------------+
|
||||
| Profile | Determines execution path
|
||||
| Detection |
|
||||
+-------------+
|
||||
|
|
||||
+---> [Native Claude Account] ---> execClaude()
|
||||
| |
|
||||
+---> [CLIProxy Provider] ---> execClaudeWithCLIProxy()
|
||||
| |
|
||||
+---> [GLMT Profile] ---> execClaudeWithProxy()
|
||||
|
|
||||
v
|
||||
+-------------+
|
||||
| Claude CLI | Underlying Anthropic CLI
|
||||
+-------------+
|
||||
```
|
||||
|
||||
### Profile Mechanisms (Priority Order)
|
||||
|
||||
```
|
||||
Profile Resolution
|
||||
|
|
||||
v
|
||||
1. CLIProxy Hardcoded ----+---> gemini, codex, agy, kiro, ghcp
|
||||
(OAuth-based) | Zero-config OAuth providers
|
||||
| (kiro: method-aware, ghcp: Device Code)
|
||||
|
|
||||
2. CLIProxy Variants -----+---> config.cliproxy section
|
||||
(User-defined) | Custom provider configurations
|
||||
|
|
||||
3. Settings-based --------+---> config.profiles section
|
||||
(API key profiles) | GLM, GLMT, Kimi, custom
|
||||
|
|
||||
4. Account-based ---------+---> profiles.json
|
||||
(Claude instances) | Isolated via CLAUDE_CONFIG_DIR
|
||||
|
|
||||
v
|
||||
Profile Config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Architecture
|
||||
|
||||
### CLI Modules (`src/`)
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| CLI Module Structure |
|
||||
+===========================================================================+
|
||||
|
||||
+------------------+ +------------------+ +------------------+
|
||||
| commands/ | | auth/ | | config/ |
|
||||
|------------------| |------------------| |------------------|
|
||||
| doctor-command | | account-switcher | | unified-config- |
|
||||
| env-command | | profile-detector | | loader |
|
||||
| help-command | | commands/ | | migration-manager|
|
||||
| install-command | +------------------+ +------------------+
|
||||
| sync-command |
|
||||
| update-command |
|
||||
+------------------+
|
||||
| | |
|
||||
+-----------------------+------------------------+
|
||||
|
|
||||
v
|
||||
+------------------+ +------------------+ +------------------+
|
||||
| cliproxy/ | | copilot/ | | glmt/ |
|
||||
|------------------| |------------------| |------------------|
|
||||
| cliproxy-executor| | copilot-package- | | glmt-proxy |
|
||||
| config-generator | | manager | | delta-accumulator|
|
||||
| account-manager | | [copilot logic] | | pipeline/ |
|
||||
| quota-manager | +------------------+ +------------------+
|
||||
| quota-fetcher |
|
||||
| auth/ |
|
||||
| binary/ |
|
||||
| services/ |
|
||||
+------------------+
|
||||
| | |
|
||||
+-----------------------+------------------------+
|
||||
|
|
||||
v
|
||||
+------------------+ +------------------+ +------------------+
|
||||
| web-server/ | | utils/ | | errors/ |
|
||||
|------------------| |------------------| |------------------|
|
||||
| routes/ (15+) | | ui/ (boxes, | | error-handler |
|
||||
| health/ | | colors, | | exit-codes |
|
||||
| usage/ | | spinners) | | cleanup |
|
||||
| services/ | | websearch/ | +------------------+
|
||||
| model-pricing | | shell-executor |
|
||||
+------------------+ +------------------+
|
||||
|
||||
|
|
||||
v
|
||||
+------------------+ +------------------+
|
||||
| types/ | | management/ |
|
||||
|------------------| |------------------|
|
||||
| config.ts | | checks/ |
|
||||
| cli.ts | | repair/ |
|
||||
| delegation.ts | | [diagnostics] |
|
||||
| glmt.ts | +------------------+
|
||||
+------------------+
|
||||
```
|
||||
|
||||
### UI Modules (`ui/src/`)
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| UI Module Structure |
|
||||
+===========================================================================+
|
||||
|
||||
+------------------+
|
||||
| pages/ | Route-level components (modular directories)
|
||||
|------------------|
|
||||
| analytics/ | 8 files - usage charts, cost tracking
|
||||
| settings/ | 20 files - lazy-loaded tab sections
|
||||
| api.tsx |
|
||||
| cliproxy.tsx |
|
||||
| copilot.tsx |
|
||||
| health.tsx |
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+ +------------------+ +------------------+
|
||||
| components/ | | contexts/ | | hooks/ |
|
||||
|------------------| |------------------| |------------------|
|
||||
| account/ | | privacy-context | | use-accounts |
|
||||
| analytics/ | | theme-context | | use-cliproxy |
|
||||
| cliproxy/ | | websocket-context| | use-health |
|
||||
| copilot/ | +------------------+ | use-profiles |
|
||||
| health/ | | use-websocket |
|
||||
| layout/ | +------------------+
|
||||
| monitoring/ | <-- auth-monitor/ (8 files), error-logs/ (6 files)
|
||||
| profiles/ |
|
||||
| setup/ |
|
||||
| shared/ |
|
||||
| ui/ (shadcn) |
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+ +------------------+
|
||||
| lib/ | | providers/ |
|
||||
|------------------| |------------------|
|
||||
| api.ts | | websocket- |
|
||||
| model-catalogs | | provider |
|
||||
| utils.ts | +------------------+
|
||||
+------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Architecture
|
||||
|
||||
### CLI Execution Flow
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| CLI Execution Flow |
|
||||
+===========================================================================+
|
||||
|
||||
1. Parse Arguments
|
||||
|
|
||||
v
|
||||
2. Detect Profile Type
|
||||
|
|
||||
+---> Native Claude ---> 3a. Load Account Settings
|
||||
| |
|
||||
| v
|
||||
| 4a. Set CLAUDE_CONFIG_DIR
|
||||
| |
|
||||
| v
|
||||
| 5a. Spawn Claude CLI
|
||||
|
|
||||
+---> CLIProxy -------> 3b. Ensure Binary Installed
|
||||
| |
|
||||
| v
|
||||
| 4b. Generate Config
|
||||
| |
|
||||
| v
|
||||
| 5b. Start CLIProxyAPI
|
||||
| |
|
||||
| v
|
||||
| 6b. Set Proxy Env Vars
|
||||
| |
|
||||
| v
|
||||
| 7b. Spawn Claude CLI
|
||||
|
|
||||
+---> GLMT -----------> 3c. Start Embedded Proxy
|
||||
|
|
||||
v
|
||||
4c. Spawn Claude CLI
|
||||
```
|
||||
|
||||
### Dashboard Data Flow
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| Dashboard Data Flow |
|
||||
+===========================================================================+
|
||||
|
||||
Browser (React SPA)
|
||||
|
|
||||
| HTTP Requests + WebSocket
|
||||
v
|
||||
Express Server (src/web-server/)
|
||||
|
|
||||
+---> /api/accounts ---> auth/account-manager
|
||||
|
|
||||
+---> /api/profiles ---> config/unified-config-loader
|
||||
|
|
||||
+---> /api/cliproxy ---> cliproxy/
|
||||
|
|
||||
+---> /api/health ----> management/checks/
|
||||
|
|
||||
+---> /api/usage -----> usage/aggregator
|
||||
|
|
||||
v
|
||||
WebSocket (Real-time)
|
||||
|
|
||||
+---> Health status updates
|
||||
+---> Auth state changes
|
||||
+---> Usage analytics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider Integration Architecture
|
||||
|
||||
### CLIProxyAPI Flow
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| CLIProxyAPI Integration |
|
||||
+===========================================================================+
|
||||
|
||||
Claude CLI
|
||||
|
|
||||
| ANTHROPIC_BASE_URL = localhost:XXXX
|
||||
v
|
||||
+------------------+
|
||||
| CLIProxyAPI | Local proxy binary (CLIProxyAPIPlus for kiro/ghcp)
|
||||
| (binary) |
|
||||
+------------------+
|
||||
|
|
||||
+---> OAuth Authentication
|
||||
| |
|
||||
| +---> Authorization Code Flow (port-based)
|
||||
| | - Gemini, Codex, Antigravity, Kiro (port 9876)
|
||||
| | - Opens browser for user auth
|
||||
| | - Callback to localhost:PORT
|
||||
| |
|
||||
| +---> Device Code Flow (no port needed)
|
||||
| - GitHub Copilot (ghcp)
|
||||
| - User enters code at github.com/login/device
|
||||
| - Polls for token completion
|
||||
| |
|
||||
| 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, cliproxy)
|
||||
| - Auto-injected via claude-hooks
|
||||
| - Skip for Claude Sub accounts (native vision)
|
||||
| - Fallback with deprecated block-image-read
|
||||
|
|
||||
+---> Provider APIs
|
||||
|
|
||||
+---> Google (Gemini)
|
||||
+---> GitHub (Codex)
|
||||
+---> Antigravity
|
||||
+---> AWS Kiro (Claude-powered)
|
||||
+---> GitHub Copilot (ghcp)
|
||||
+---> OpenAI-compatible endpoints
|
||||
```
|
||||
|
||||
### GLMT Proxy Flow
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| GLMT Proxy Integration |
|
||||
+===========================================================================+
|
||||
|
||||
Claude CLI
|
||||
|
|
||||
| ANTHROPIC_BASE_URL = localhost:XXXX
|
||||
v
|
||||
+------------------+
|
||||
| GLMT Proxy | Embedded Node.js proxy (src/glmt/)
|
||||
| (glmt-proxy.ts)|
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| Delta Accumulator| Stream transformation
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| Pipeline | Request/Response transformation
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| GLM API | Z.AI / Kimi API
|
||||
+------------------+
|
||||
```
|
||||
|
||||
### Remote CLIProxy Flow (v7.1)
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| Remote CLIProxy Architecture |
|
||||
+===========================================================================+
|
||||
|
||||
Config Resolution (proxy-config-resolver.ts)
|
||||
|
|
||||
+---> Priority: CLI flags > ENV vars > config.yaml > defaults
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| ResolvedProxyConfig |
|
||||
| mode: local|remote |
|
||||
+------------------+
|
||||
|
|
||||
+---> [mode = local] ---> Spawn local CLIProxyAPI binary
|
||||
| |
|
||||
| v
|
||||
| localhost:8317
|
||||
|
|
||||
+---> [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)
|
||||
```
|
||||
|
||||
### Quota Management Flow (v7.14)
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| Quota Management Architecture |
|
||||
+===========================================================================+
|
||||
|
||||
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
|
||||
| | - Gemini: /models endpoint
|
||||
| | - Codex: /api/v1/account
|
||||
| | - Kiro: /api/usage
|
||||
| |
|
||||
| +---> Detect tier (free/paid/unknown)
|
||||
| |
|
||||
| +---> Check exhaustion status
|
||||
|
|
||||
+---> Select best account (not paused, not exhausted)
|
||||
|
|
||||
+---> Auto-failover to next account if current exhausted
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Architecture
|
||||
|
||||
### Config File Hierarchy
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| Configuration Hierarchy |
|
||||
+===========================================================================+
|
||||
|
||||
~/.ccs/
|
||||
|
|
||||
+---> config.yaml # Main CCS config (unified)
|
||||
|
|
||||
+---> profiles.json # Claude account registry
|
||||
|
|
||||
+---> <profile>.settings.json # Per-profile settings
|
||||
|
|
||||
+---> cliproxy/
|
||||
| |
|
||||
| +---> config.yaml # CLIProxy configuration
|
||||
| +---> auth/ # OAuth tokens
|
||||
| +---> bin/ # CLIProxy binary
|
||||
|
|
||||
+---> shared/ # Symlinked resources
|
||||
|
|
||||
+---> commands/ # Claude Code commands
|
||||
+---> skills/ # Custom skills
|
||||
+---> agents/ # Agent configurations
|
||||
```
|
||||
|
||||
### Config Loading Order
|
||||
|
||||
```
|
||||
1. Environment Variables (highest priority)
|
||||
|
|
||||
v
|
||||
2. CLI Arguments
|
||||
|
|
||||
v
|
||||
3. Profile-specific settings (~/.ccs/<profile>.settings.json)
|
||||
|
|
||||
v
|
||||
4. Main config (~/.ccs/config.yaml)
|
||||
|
|
||||
v
|
||||
5. Default values (lowest priority)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WebSocket Architecture
|
||||
|
||||
### Real-time Communication
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| WebSocket Communication |
|
||||
+===========================================================================+
|
||||
|
||||
Dashboard (React) Server (Express)
|
||||
| |
|
||||
|<------ Connection Established ------>|
|
||||
| |
|
||||
|<------ health:update ----------------| Health status
|
||||
| |
|
||||
|<------ auth:status ------------------| Auth changes
|
||||
| |
|
||||
|<------ usage:update -----------------| Usage stats
|
||||
| |
|
||||
|------- action:refresh -------------->| User requests
|
||||
| |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| Authentication Flow |
|
||||
+===========================================================================+
|
||||
|
||||
OAuth Providers - Authorization Code Flow (Gemini, Codex, AGY)
|
||||
--------------------------------------------------------------
|
||||
|
||||
1. User runs: ccs gemini
|
||||
|
|
||||
v
|
||||
2. Check token cache (~/.ccs/cliproxy/auth/)
|
||||
|
|
||||
+---> [Valid token] ---> Use cached token
|
||||
|
|
||||
+---> [No/Expired token]
|
||||
|
|
||||
v
|
||||
3. Open browser for OAuth (localhost:PORT callback)
|
||||
v
|
||||
4. Callback with auth code
|
||||
|
|
||||
v
|
||||
5. Exchange for access token
|
||||
|
|
||||
v
|
||||
6. Cache token locally
|
||||
|
||||
|
||||
Kiro OAuth - Method-Aware Flow (CLI + Dashboard parity)
|
||||
-------------------------------------------------------
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
OAuth Providers - Device Code Flow (GitHub Copilot/ghcp)
|
||||
--------------------------------------------------------
|
||||
|
||||
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. Poll for token (user completes auth in browser)
|
||||
|
|
||||
v
|
||||
6. Receive and cache token locally
|
||||
|
||||
|
||||
API Key Profiles (GLM, Kimi)
|
||||
----------------------------
|
||||
|
||||
1. User configures API key in settings
|
||||
|
|
||||
v
|
||||
2. Key stored in ~/.ccs/<profile>.settings.json
|
||||
|
|
||||
v
|
||||
3. Key passed via ANTHROPIC_AUTH_TOKEN env var
|
||||
```
|
||||
|
||||
### Security Boundaries
|
||||
|
||||
```
|
||||
+------------------+
|
||||
| User Terminal |
|
||||
+------------------+
|
||||
|
|
||||
| Local only (no network exposure)
|
||||
v
|
||||
+------------------+
|
||||
| CCS CLI |
|
||||
+------------------+
|
||||
|
|
||||
| Localhost only (127.0.0.1)
|
||||
v
|
||||
+------------------+
|
||||
| CLIProxy/GLMT | Binds to localhost only
|
||||
+------------------+
|
||||
|
|
||||
| TLS encrypted
|
||||
v
|
||||
+------------------+
|
||||
| Provider APIs | External endpoints
|
||||
+------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
|
||||
+---> First run creates: ~/.ccs/
|
||||
```
|
||||
|
||||
### Runtime Dependencies
|
||||
|
||||
```
|
||||
+------------------+ +------------------+
|
||||
| Node.js 14+ | | Claude CLI |
|
||||
| (required) | | (required) |
|
||||
+------------------+ +------------------+
|
||||
|
||||
+------------------+ +------------------+
|
||||
| CLIProxyAPI | | Gemini CLI |
|
||||
| (auto-managed) | | (optional) |
|
||||
+------------------+ +------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Codebase Summary](./codebase-summary.md) - Detailed directory structure
|
||||
- [Code Standards](./code-standards.md) - Coding conventions
|
||||
- [Project Roadmap](./project-roadmap.md) - Development phases
|
||||
- [WebSearch](./websearch.md) - WebSearch feature details
|
||||
@@ -0,0 +1,402 @@
|
||||
# CCS System Architecture
|
||||
|
||||
Last Updated: 2026-02-16
|
||||
|
||||
High-level architecture overview for the CCS (Claude Code Switch) system.
|
||||
|
||||
---
|
||||
|
||||
## System Overview
|
||||
|
||||
CCS is a CLI wrapper that enables seamless switching between multiple Claude accounts and alternative AI providers (GLM, Gemini, Codex, Kiro, GitHub Copilot, OpenRouter, Qwen, Kimi, DeepSeek). It now supports multiple CLI targets (Claude Code, Factory Droid) 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
|
||||
|
||||
CCS v7.34 adds Image Analysis Hook for vision model proxying through CLIProxy with automatic injection for all profile types.
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| CCS System |
|
||||
+===========================================================================+
|
||||
| |
|
||||
| +------------------+ +-----------------+ +----------------+ |
|
||||
| | User Terminal | ---> | CCS CLI | ---> | Target CLI | |
|
||||
| | (ccs command) | | (src/ccs.ts) | | (claude/droid) | |
|
||||
| +------------------+ +-----------------+ +----------------+ |
|
||||
| | | |
|
||||
| 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, GLMT, Account-based)
|
||||
|
|
||||
v
|
||||
Target Resolution (--target flag > config > argv[0] > default)
|
||||
|
|
||||
v
|
||||
Get Target Adapter (Claude or Droid)
|
||||
|
|
||||
+---> detectBinary() (find CLI on system)
|
||||
|
|
||||
+---> prepareCredentials() (write config or set env)
|
||||
|
|
||||
+---> buildArgs() (construct CLI arguments)
|
||||
|
|
||||
+---> buildEnv() (prepare environment variables)
|
||||
|
|
||||
v
|
||||
Spawn Target Process
|
||||
```
|
||||
|
||||
**Each target adapter implements different credential delivery:**
|
||||
|
||||
- **Claude Adapter**: Env var delivery (existing behavior)
|
||||
- `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_MODEL`
|
||||
- No config files needed
|
||||
|
||||
- **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
|
||||
|
||||
**Binary alias pattern (busybox-style):**
|
||||
|
||||
```
|
||||
ccs → Target: claude (default)
|
||||
ccsd → Target: droid (auto-selected via argv[0])
|
||||
```
|
||||
|
||||
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()
|
||||
| |
|
||||
+---> [GLMT Profile] ---> execClaudeWithProxy()
|
||||
|
|
||||
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
|
||||
|
|
||||
+---> GLMT -----------> 3c. Start Embedded Proxy
|
||||
|
|
||||
v
|
||||
4c. Resolve Target Adapter
|
||||
|
|
||||
v
|
||||
5c. Spawn via Adapter
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider Integration Architecture
|
||||
|
||||
For detailed provider flows (CLIProxyAPI, GLMT, quota management), see [Provider Flows](./provider-flows.md).
|
||||
|
||||
---
|
||||
|
||||
## Configuration Architecture
|
||||
|
||||
### Config File Hierarchy
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| Configuration Hierarchy |
|
||||
+===========================================================================+
|
||||
|
||||
~/.ccs/
|
||||
|
|
||||
+---> config.yaml # Main CCS config (unified)
|
||||
|
|
||||
+---> profiles.json # Claude account registry
|
||||
|
|
||||
+---> <profile>.settings.json # Per-profile settings
|
||||
|
|
||||
+---> cliproxy/
|
||||
| |
|
||||
| +---> config.yaml # CLIProxy configuration
|
||||
| +---> auth/ # OAuth tokens
|
||||
| +---> bin/ # CLIProxy binary
|
||||
|
|
||||
+---> shared/ # Symlinked resources
|
||||
|
|
||||
+---> commands/ # Claude Code commands
|
||||
+---> skills/ # Custom skills
|
||||
+---> agents/ # Agent configurations
|
||||
|
||||
~/.factory/ (Droid CLI)
|
||||
|
|
||||
+---> settings.json # Droid config (custom models)
|
||||
```
|
||||
|
||||
### Config Loading Order
|
||||
|
||||
```
|
||||
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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WebSocket Architecture
|
||||
|
||||
### Real-time Communication
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| WebSocket Communication |
|
||||
+===========================================================================+
|
||||
|
||||
Dashboard (React) Server (Express)
|
||||
| |
|
||||
|<------ Connection Established ------>|
|
||||
| |
|
||||
|<------ health:update ----------------| Health status
|
||||
| |
|
||||
|<------ auth:status ------------------| Auth changes
|
||||
| |
|
||||
|<------ usage:update -----------------| Usage stats
|
||||
| |
|
||||
|------- action:refresh -------------->| User requests
|
||||
| |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
See [Provider Flows](./provider-flows.md) → Authentication Flow section.
|
||||
|
||||
### Security Boundaries
|
||||
|
||||
```
|
||||
+------------------+
|
||||
| User Terminal |
|
||||
+------------------+
|
||||
|
|
||||
| Local only (no network exposure)
|
||||
v
|
||||
+------------------+
|
||||
| CCS CLI |
|
||||
+------------------+
|
||||
|
|
||||
| Localhost only (127.0.0.1)
|
||||
v
|
||||
+------------------+
|
||||
| CLIProxy/GLMT | Binds to localhost only
|
||||
+------------------+
|
||||
|
|
||||
| TLS encrypted
|
||||
v
|
||||
+------------------+
|
||||
| Target CLI | Spawned locally (claude/droid)
|
||||
+------------------+
|
||||
|
|
||||
| TLS encrypted
|
||||
v
|
||||
+------------------+
|
||||
| Provider APIs | External endpoints
|
||||
+------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
|
||||
+---> Binary alias: ccsd → ccs (auto-selects droid target)
|
||||
|
|
||||
+---> First run creates: ~/.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, GLMT, authentication flows
|
||||
- [Project Roadmap](../project-roadmap.md) - Development phases
|
||||
@@ -0,0 +1,565 @@
|
||||
# Provider Integration Flows
|
||||
|
||||
Last Updated: 2026-02-16
|
||||
|
||||
Detailed provider integration flows including CLIProxyAPI, GLMT proxy, remote CLIProxy, quota management, and authentication.
|
||||
|
||||
---
|
||||
|
||||
## CLIProxyAPI Flow
|
||||
|
||||
### Overview
|
||||
|
||||
CLIProxyAPI is a local OAuth proxy binary that enables seamless integration with multiple AI providers. CCS manages the binary and configuration automatically.
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| CLIProxyAPI Integration |
|
||||
+===========================================================================+
|
||||
|
||||
Claude CLI
|
||||
|
|
||||
| ANTHROPIC_BASE_URL = localhost:XXXX
|
||||
v
|
||||
+------------------+
|
||||
| CLIProxyAPI | Local proxy binary (CLIProxyAPIPlus for kiro/ghcp)
|
||||
| (binary) |
|
||||
+------------------+
|
||||
|
|
||||
+---> OAuth Authentication
|
||||
| |
|
||||
| +---> Authorization Code Flow (port-based)
|
||||
| | - Gemini, Codex, Antigravity, Kiro (port 9876)
|
||||
| | - Opens browser for user auth
|
||||
| | - Callback to localhost:PORT
|
||||
| |
|
||||
| +---> Device Code Flow (no port needed)
|
||||
| - GitHub Copilot (ghcp)
|
||||
| - User enters code at github.com/login/device
|
||||
| - Polls for token completion
|
||||
| |
|
||||
| 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
|
||||
|
|
||||
+---> Google (Gemini)
|
||||
+---> GitHub (Codex)
|
||||
+---> Antigravity (AGY)
|
||||
+---> AWS Kiro (Claude-powered)
|
||||
+---> GitHub Copilot (ghcp)
|
||||
+---> OpenAI-compatible endpoints
|
||||
```
|
||||
|
||||
### Supported Hardcoded Providers
|
||||
|
||||
| Provider | ID | Auth Method | Port | Binary |
|
||||
|----------|----|----|------|--------|
|
||||
| Gemini | `gemini` | Authorization Code | 9876 | CLIProxyAPI |
|
||||
| Codex | `codex` | Authorization Code | 9876 | CLIProxyAPI |
|
||||
| Antigravity | `agy` | Authorization Code | 9876 | CLIProxyAPI |
|
||||
| Kiro (AWS) | `kiro` | Method-aware (default: Device Code) | 9876 | CLIProxyAPIPlus |
|
||||
| GitHub Copilot | `ghcp` | Device Code | none | CLIProxyAPIPlus |
|
||||
|
||||
### Hardcoded Provider Detection
|
||||
|
||||
CCS detects hardcoded providers via `profile-detector.ts` and routes through `execClaudeWithCLIProxy()`.
|
||||
|
||||
```typescript
|
||||
// Profile name matching
|
||||
const hardcodedProviders = ['gemini', 'codex', 'agy', 'kiro', 'ghcp'];
|
||||
|
||||
if (hardcodedProviders.includes(profileName)) {
|
||||
return execClaudeWithCLIProxy(claudeCli, profileName, args);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GLMT Proxy Flow
|
||||
|
||||
### Overview
|
||||
|
||||
GLMT proxy enables seamless integration with GLM-compatible APIs (Z.AI, Kimi, OpenRouter, etc.) using a Node.js-based embedded proxy.
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| GLMT Proxy Integration |
|
||||
+===========================================================================+
|
||||
|
||||
Claude CLI
|
||||
|
|
||||
| ANTHROPIC_BASE_URL = localhost:XXXX
|
||||
v
|
||||
+------------------+
|
||||
| GLMT Proxy | Embedded Node.js proxy (src/glmt/)
|
||||
| (glmt-proxy.ts)|
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| Delta Accumulator| Stream transformation
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| Pipeline | Request/Response transformation
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| GLM API | Z.AI / Kimi API
|
||||
+------------------+
|
||||
```
|
||||
|
||||
### Supported GLM Providers
|
||||
|
||||
| Provider | Config Key | Endpoint | Auth |
|
||||
|----------|------------|----------|------|
|
||||
| Z.AI (GLM) | `glmt` | https://open.bigmodel.cn/api/paas/v4/ | API key |
|
||||
| Kimi | `kimi` | https://api.moonshot.cn/v1/ | API key |
|
||||
| OpenRouter | `openrouter` | https://openrouter.ai/api/v1/ | API key |
|
||||
|
||||
### GLMT Profile Detection
|
||||
|
||||
CCS detects GLMT profiles and routes through `execClaudeWithProxy()`:
|
||||
|
||||
```typescript
|
||||
// Settings-based profile detection
|
||||
const settings = loadSettings(profileName);
|
||||
if (settings.env?.ANTHROPIC_BASE_URL?.includes('glm') ||
|
||||
settings.env?.ANTHROPIC_BASE_URL?.includes('moonshot') ||
|
||||
settings.env?.ANTHROPIC_BASE_URL?.includes('openrouter')) {
|
||||
return execClaudeWithProxy(claudeCli, profileName, args);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Remote CLIProxy Flow (v7.1)
|
||||
|
||||
### Overview
|
||||
|
||||
Remote CLIProxy enables CCS to delegate authentication to a central proxy server instead of spawning a local binary.
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| Remote CLIProxy Architecture (v7.1) |
|
||||
+===========================================================================+
|
||||
|
||||
Config Resolution (proxy-config-resolver.ts)
|
||||
|
|
||||
+---> Priority: CLI flags > ENV vars > config.yaml > defaults
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| ResolvedProxyConfig |
|
||||
| mode: local|remote |
|
||||
+------------------+
|
||||
|
|
||||
+---> [mode = local] ---> Spawn local CLIProxyAPI binary
|
||||
| |
|
||||
| v
|
||||
| localhost:8317
|
||||
|
|
||||
+---> [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)
|
||||
```
|
||||
|
||||
### Configuration Resolution
|
||||
|
||||
```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)
|
||||
};
|
||||
```
|
||||
|
||||
### Health Check
|
||||
|
||||
```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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quota Management Flow (v7.14)
|
||||
|
||||
### Overview
|
||||
|
||||
Hybrid quota management enables automatic detection of exhausted accounts and failover to next available account.
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| 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
|
||||
| | - Gemini: /models endpoint
|
||||
| | - Codex: /api/v1/account
|
||||
| | - Kiro: /api/usage
|
||||
| |
|
||||
| +---> Detect tier (free/paid/unknown)
|
||||
| |
|
||||
| +---> Check exhaustion status
|
||||
|
|
||||
+---> Select best account (not paused, not exhausted)
|
||||
|
|
||||
+---> Auto-failover to next account if current exhausted
|
||||
|
||||
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 gemini
|
||||
|
|
||||
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)
|
||||
|
||||
```
|
||||
+===========================================================================+
|
||||
| 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
|
||||
```
|
||||
|
||||
### 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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
Hook intercepts image request
|
||||
|
|
||||
v
|
||||
Vision Model Proxying (via CLIProxyAPI)
|
||||
|
|
||||
+---> Gemini, Codex, AGY support vision
|
||||
|
|
||||
+---> Kiro (Claude native vision)
|
||||
|
|
||||
+---> Skip for Claude Sub accounts (native vision)
|
||||
|
|
||||
v
|
||||
Vision response returned to Claude CLI
|
||||
```
|
||||
|
||||
### Hook Environment
|
||||
|
||||
```typescript
|
||||
// getImageAnalysisHookEnv()
|
||||
{
|
||||
ANTHROPIC_IMAGE_HOOK_URL: 'http://localhost:8317/api/image-analysis',
|
||||
// or for remote proxy:
|
||||
ANTHROPIC_IMAGE_HOOK_URL: 'https://proxy.example.com:8317/api/image-analysis',
|
||||
}
|
||||
```
|
||||
|
||||
### Provider Support
|
||||
|
||||
| Provider | Vision Support | Notes |
|
||||
|----------|---|---|
|
||||
| Gemini | ✓ | Via CLIProxy image analysis |
|
||||
| Codex | ✓ | Via CLIProxy image analysis |
|
||||
| Antigravity | ✓ | Via CLIProxy image analysis |
|
||||
| Kiro | ✓ | Native Claude vision (no proxy needed) |
|
||||
| Copilot | ✗ | Not supported |
|
||||
| GLM/Kimi | ✗ | Requires direct API implementation |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,607 @@
|
||||
# Target Adapters
|
||||
|
||||
Last Updated: 2026-02-16
|
||||
|
||||
Detailed documentation of the target adapter pattern and implementations.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The target adapter system enables CCS to dispatch credential-resolved profiles to different CLI implementations while maintaining a unified configuration and profile system.
|
||||
|
||||
**Key insight**: Profile resolution (detecting provider, loading auth, building credentials) is target-agnostic. Only the final credential delivery and process spawning differ per target.
|
||||
|
||||
---
|
||||
|
||||
## Target Adapter Interface
|
||||
|
||||
Each CLI target implements the `TargetAdapter` contract:
|
||||
|
||||
```typescript
|
||||
export interface TargetAdapter {
|
||||
readonly type: TargetType; // 'claude' | 'droid'
|
||||
readonly displayName: string; // "Claude Code" | "Factory Droid"
|
||||
|
||||
/** 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[]): 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';
|
||||
|
||||
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?
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
2. Per-profile config (from ~/.ccs/config.yaml or settings.json)
|
||||
└─ profiles:
|
||||
glm:
|
||||
target: droid
|
||||
|
||||
3. argv[0] detection (busybox pattern) — binary name mapping
|
||||
└─ ccsd (symlink/batch file) → droid
|
||||
└─ ccs (regular command) → default
|
||||
|
||||
4. Fallback: 'claude' — lowest priority
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
```typescript
|
||||
// src/targets/target-resolver.ts
|
||||
|
||||
export function resolveTargetType(
|
||||
args: string[],
|
||||
profileConfig?: { target?: TargetType }
|
||||
): TargetType {
|
||||
// 1. Check --target flag
|
||||
const targetIdx = args.indexOf('--target');
|
||||
if (targetIdx !== -1 && args[targetIdx + 1]) {
|
||||
const flagValue = args[targetIdx + 1];
|
||||
if (VALID_TARGETS.has(flagValue)) {
|
||||
return flagValue as TargetType;
|
||||
}
|
||||
// Invalid target → error
|
||||
console.error(`[X] Unknown target "${flagValue}". Available: claude, droid`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 2. Check profile config
|
||||
if (profileConfig?.target) {
|
||||
return profileConfig.target;
|
||||
}
|
||||
|
||||
// 3. Check argv[0] (binary name)
|
||||
const binName = path.basename(process.argv[1] || '').replace(/\.(cmd|bat)$/i, '');
|
||||
if (ARGV0_TARGET_MAP[binName]) {
|
||||
return ARGV0_TARGET_MAP[binName];
|
||||
}
|
||||
|
||||
// 4. 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
|
||||
process.on('SIGINT', () => child.kill('SIGINT'));
|
||||
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
||||
}
|
||||
|
||||
supportsProfileType(profileType: string): boolean {
|
||||
// Claude supports all profile types
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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 gemini
|
||||
→ 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> {
|
||||
const profile = creds.envVars?.['CCS_PROFILE_NAME'] || 'default';
|
||||
|
||||
// Write custom model entry to ~/.factory/settings.json
|
||||
await upsertCcsModel(profile, {
|
||||
model: creds.model || 'claude-opus-4-6',
|
||||
displayName: `CCS ${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
|
||||
process.on('SIGINT', () => child.kill('SIGINT'));
|
||||
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
||||
}
|
||||
|
||||
supportsProfileType(profileType: string): boolean {
|
||||
// Droid supports all profile types (like Claude)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Credential Delivery
|
||||
|
||||
**Method**: Config file (`~/.factory/settings.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"customModels": {
|
||||
"ccs-gemini": {
|
||||
"model": "claude-opus-4-6",
|
||||
"displayName": "CCS gemini",
|
||||
"baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai/",
|
||||
"apiKey": "AIza...",
|
||||
"provider": "openai"
|
||||
},
|
||||
"ccs-glm": {
|
||||
"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 gemini
|
||||
→ droid -m custom:ccs-gemini "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)
|
||||
```
|
||||
|
||||
### Binary Alias Pattern
|
||||
|
||||
```bash
|
||||
# Create symlink to auto-select droid target
|
||||
ln -s /path/to/ccs /path/to/ccsd
|
||||
|
||||
# Usage
|
||||
ccsd glm
|
||||
→ Target: droid (detected from argv[0])
|
||||
→ droid -m custom:ccs-glm "args..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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' | '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
|
||||
process.on('SIGINT', () => child.kill('SIGINT'));
|
||||
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
||||
```
|
||||
|
||||
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',
|
||||
}, 'clipproxy');
|
||||
|
||||
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 argv[0] detection
|
||||
ccsd help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
Reference in new issue
Block a user