mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
docs(architecture): replace volatile maintainer snapshots
This commit is contained in:
1 parent
3bb2d56778
commit
721ca5fc33
2 files changed
+349
-1399
No files matched your search
+178
-728
@@ -1,749 +1,199 @@
|
|||||||
# CCS Code Standards
|
# CCS Code Standards
|
||||||
|
|
||||||
Last Updated: 2026-04-07
|
These standards describe current repository practice. Enforced configuration
|
||||||
|
and tests take precedence over prose.
|
||||||
Code standards, modularization patterns, and conventions for the CCS codebase.
|
|
||||||
|
## Source of Truth
|
||||||
---
|
|
||||||
|
| Contract | Authoritative source |
|
||||||
## Core Principles
|
| --- | --- |
|
||||||
|
| TypeScript and CLI lint rules | [`eslint.config.mjs`](../eslint.config.mjs) |
|
||||||
### YAGNI (You Aren't Gonna Need It)
|
| Formatting | [`.prettierrc`](../.prettierrc) |
|
||||||
- No features "just in case"
|
| Root commands and test entry points | [`package.json`](../package.json) |
|
||||||
- Only implement what is currently needed
|
| UI commands and dependencies | [`ui/package.json`](../ui/package.json) |
|
||||||
- Delete unused code rather than commenting it out
|
| Commit syntax | [`commitlint.config.cjs`](../commitlint.config.cjs) |
|
||||||
|
| Release effects | [`.releaserc.cjs`](../.releaserc.cjs) |
|
||||||
### KISS (Keep It Simple, Stupid)
|
| Repository workflow | [`CLAUDE.md`](../CLAUDE.md) and [`CONTRIBUTING.md`](../CONTRIBUTING.md) |
|
||||||
- Prefer simple solutions over clever ones
|
|
||||||
- Reduce complexity at every opportunity
|
Update this document when one of those contracts changes. Do not duplicate
|
||||||
- Use established patterns over custom implementations
|
exhaustive rule, command, target, or dependency lists here.
|
||||||
|
|
||||||
### DRY (Don't Repeat Yourself)
|
## Design Priorities
|
||||||
- One source of truth for configuration
|
|
||||||
- Extract common logic into shared utilities
|
Apply these in order:
|
||||||
- Use barrel exports to centralize imports
|
|
||||||
|
1. **YAGNI**: build only behavior required by the accepted scope.
|
||||||
---
|
2. **KISS**: prefer a small local change over a new abstraction or dependency.
|
||||||
|
3. **DRY**: share stable domain behavior, not incidental similarity.
|
||||||
## File Organization
|
|
||||||
|
Preserve public contracts unless the change intentionally updates them. Error
|
||||||
### Directory Structure Rules
|
messages should explain recovery, and configuration changes should remain
|
||||||
|
CLI-complete with dashboard parity where the feature supports both surfaces.
|
||||||
1. **Domain-based organization**: Group files by business domain, not by file type
|
|
||||||
2. **Barrel exports required**: Every directory must have an `index.ts` aggregating exports
|
## Organization and Imports
|
||||||
3. **Flat within depth**: Keep nesting to 3 levels maximum
|
|
||||||
4. **Co-location**: Keep related files together (component + hooks + utils)
|
- Keep code inside the domain that owns its behavior.
|
||||||
|
- Split files at cohesive boundaries such as types, pure utilities, lifecycle
|
||||||
### File Naming Conventions
|
services, hooks, or view components.
|
||||||
|
- Follow nearby naming and import patterns. New TypeScript files normally use
|
||||||
| Convention | Example | When to Use |
|
descriptive kebab-case names.
|
||||||
|------------|---------|-------------|
|
- There is no universal maximum directory depth.
|
||||||
| kebab-case | `cliproxy-executor.ts` | All TypeScript/TSX files |
|
- Barrel exports are optional compatibility boundaries, not a repository-wide
|
||||||
| kebab-case | `profile-detector.ts` | Multi-word file names |
|
requirement. Preserve an existing barrel when consumers depend on it; use a
|
||||||
| *-adapter.ts | `claude-adapter.ts`, `droid-adapter.ts` | TargetAdapter implementations |
|
direct import when that is the local pattern or the symbol is intentionally
|
||||||
| *-detector.ts | `droid-detector.ts` | Binary detection logic |
|
private.
|
||||||
| *-manager.ts | `droid-config-manager.ts` | Config/state management |
|
- Before creating a module, check whether the runtime, standard library,
|
||||||
| PascalCase | `BinaryManager` | Class exports only |
|
installed dependencies, or an existing repository utility already owns the
|
||||||
| camelCase | `detectProfile` | Function exports |
|
behavior.
|
||||||
|
|
||||||
**File names should be descriptive**: LLMs should understand the file's purpose from its name alone without reading content.
|
## File Size
|
||||||
|
|
||||||
### Correct Examples
|
Two different thresholds serve different purposes:
|
||||||
|
|
||||||
```
|
- **200 lines is a review heuristic.** When a code file grows beyond roughly
|
||||||
src/cliproxy/binary-manager.ts # Binary management logic
|
200 lines, check whether it contains multiple responsibilities. A cohesive
|
||||||
src/commands/doctor-command.ts # Doctor CLI command handler
|
file may remain larger.
|
||||||
ui/src/components/cliproxy/provider-editor/index.tsx
|
- **400 lines is the current CLI lint warning.**
|
||||||
```
|
[`eslint.config.mjs`](../eslint.config.mjs) configures `max-lines` as a
|
||||||
|
warning for `src/**/*.ts`, excluding blank lines and comments.
|
||||||
### Incorrect Examples
|
|
||||||
|
Do not split mechanically to satisfy a number. Split when the result improves
|
||||||
```
|
ownership, testing, reuse, or reviewability. Preserve existing exports and
|
||||||
src/utils/helper.ts # Too vague
|
behavior when they are part of a consumed contract.
|
||||||
src/cliproxy/manager.ts # Which manager?
|
|
||||||
ui/src/components/Editor.tsx # Not kebab-case
|
## TypeScript and Linting
|
||||||
```
|
|
||||||
|
The CLI uses strict TypeScript. Current enforced CLI rules include:
|
||||||
---
|
|
||||||
|
- no explicit `any`
|
||||||
## File Size Limit: 200 Lines
|
- no non-null assertions
|
||||||
|
- no unused variables, except names intentionally prefixed with `_`
|
||||||
**Target**: All code files should be under 200 lines.
|
- `prefer-const`, `no-var`, and strict equality
|
||||||
|
|
||||||
**Exceptions** (with justification):
|
Use `unknown` at untrusted boundaries and narrow it before use. Keep types close
|
||||||
- Data files (model-pricing.ts, model-catalog.ts)
|
to their owning domain; export types only when another module consumes them.
|
||||||
- Entry points with routing logic (ccs.ts)
|
|
||||||
- Complex transformation logic that cannot be meaningfully split
|
New generic `throw new Error(...)` sites are blocked by the local
|
||||||
|
`ccs/no-new-throw-error` rule. Use the typed errors in
|
||||||
### Why 200 Lines?
|
[`src/errors/error-types.ts`](../src/errors/error-types.ts) so
|
||||||
|
[`src/errors/error-handler.ts`](../src/errors/error-handler.ts) can map failures
|
||||||
1. **Context efficiency**: LLMs process smaller files faster
|
consistently. The generated baseline in
|
||||||
2. **Single responsibility**: Forces focused, testable modules
|
[`eslint-rules/throw-error-baseline.json`](../eslint-rules/throw-error-baseline.json)
|
||||||
3. **Navigation**: Easier to scan and understand
|
grandfathers existing sites; do not expand it to bypass a new error design.
|
||||||
4. **Maintainability**: Reduces merge conflicts
|
|
||||||
|
## Terminal and Process Behavior
|
||||||
### When Files Exceed 200 Lines
|
|
||||||
|
- CLI terminal output is ASCII only: `[OK]`, `[!]`, `[X]`, `[i]`.
|
||||||
If a file grows beyond 200 lines:
|
- Respect `NO_COLOR` and TTY-aware output.
|
||||||
|
- Prefer argument arrays when spawning processes. When a platform wrapper
|
||||||
1. **Identify extraction candidates**:
|
requires a shell, use the existing quoting and wrapper utilities rather than
|
||||||
- Helper functions that could be utilities
|
interpolating untrusted input.
|
||||||
- Constants and type definitions
|
- Preserve target-specific stdio, signal, and exit-code behavior. The adapter
|
||||||
- Subcomponents within React components
|
contract lives in
|
||||||
- Related logic that forms a cohesive unit
|
[`src/targets/target-adapter.ts`](../src/targets/target-adapter.ts).
|
||||||
|
|
||||||
2. **Create subdirectory structure**:
|
## Target Adapters
|
||||||
```
|
|
||||||
# Before
|
Runtime target behavior is divided across:
|
||||||
provider-editor.tsx (921 lines)
|
|
||||||
|
| Concern | Source |
|
||||||
# After
|
| --- | --- |
|
||||||
provider-editor/
|
| Target names, aliases, persistence | [`src/targets/target-metadata.ts`](../src/targets/target-metadata.ts) |
|
||||||
├── index.tsx # Main component (200 lines)
|
| Selection priority and flag parsing | [`src/targets/target-resolver.ts`](../src/targets/target-resolver.ts) |
|
||||||
├── model-mapping-form.tsx
|
| Adapter contract | [`src/targets/target-adapter.ts`](../src/targets/target-adapter.ts) |
|
||||||
├── endpoint-config.tsx
|
| Registration and lookup | [`src/targets/target-registry.ts`](../src/targets/target-registry.ts) |
|
||||||
├── auth-section.tsx
|
| Startup registration | [`src/ccs.ts`](../src/ccs.ts) |
|
||||||
├── hooks.ts
|
|
||||||
├── types.ts
|
A target change should update metadata, implementation, registration, tests,
|
||||||
└── utils.ts
|
help text, and public documentation as applicable. Do not copy the target list
|
||||||
```
|
into new guides; link to metadata when maintainers need the current set.
|
||||||
|
|
||||||
3. **Preserve public API**: Main export remains the same through barrel export
|
## Configuration and Test Isolation
|
||||||
|
|
||||||
---
|
- Treat persisted environment values as strings.
|
||||||
|
- Route CCS paths through `getCcsDir()` in
|
||||||
## Barrel Export Pattern
|
[`src/utils/config-manager.ts`](../src/utils/config-manager.ts).
|
||||||
|
- Never run tests against a contributor's real `~/.ccs/` or `~/.claude/`.
|
||||||
### What is a Barrel Export?
|
Set `CCS_HOME` to a temporary directory.
|
||||||
|
- Preserve documented configuration and profile-resolution priority. Add a
|
||||||
An `index.ts` file that aggregates and re-exports module contents:
|
focused test when changing precedence or fallback behavior.
|
||||||
|
- Treat Docker service `ccs` and network `ccs-net` as public contracts; see
|
||||||
```typescript
|
[`CONTRIBUTING.md`](../CONTRIBUTING.md#if-you-change-the-docker-network-or-service-name).
|
||||||
// src/cliproxy/index.ts
|
|
||||||
|
## Dashboard Code
|
||||||
// Types (with explicit type keyword)
|
|
||||||
export type { PlatformInfo, BinaryInfo } from './types';
|
- Keep page orchestration in `ui/src/pages/`, reusable UI in
|
||||||
|
`ui/src/components/`, server-state access in `ui/src/hooks/`, and shared
|
||||||
// Functions
|
helpers in `ui/src/lib/` when that matches the owning domain.
|
||||||
export { detectPlatform } from './platform-detector';
|
- Preserve unsaved input across background refreshes. Destructive replacement
|
||||||
export { BinaryManager } from './binary-manager';
|
of dirty state requires an explicit user action or confirmation.
|
||||||
|
- Use the existing API and query helpers before adding a new data-access layer.
|
||||||
// From subdirectories
|
- Keep accessibility, keyboard interaction, responsive layout, loading,
|
||||||
export * from './auth';
|
empty, and error states in scope for user-facing changes.
|
||||||
export * from './services';
|
- Locale codes and normalization are owned by
|
||||||
```
|
[`ui/src/lib/locales.ts`](../ui/src/lib/locales.ts); translations are wired
|
||||||
|
through [`ui/src/lib/i18n.ts`](../ui/src/lib/i18n.ts).
|
||||||
### Rules for Barrel Exports
|
|
||||||
|
The UI has its own ESLint, TypeScript, Prettier, and Vitest configuration.
|
||||||
1. **Every domain directory must have `index.ts`**
|
Verify commands against [`ui/package.json`](../ui/package.json).
|
||||||
2. **Export types with `export type`** for tree-shaking
|
|
||||||
3. **Re-export subdirectories** for deep access
|
## Tests and Quality Gates
|
||||||
4. **Keep barrel exports flat** - no logic, only exports
|
|
||||||
|
The root TypeScript suites run with Bun's test runner. The dashboard uses
|
||||||
### Import Patterns
|
Vitest. Native shell and PowerShell probes cover platform-specific behavior.
|
||||||
|
See [`tests/README.md`](../tests/README.md) for ownership and commands.
|
||||||
```typescript
|
|
||||||
// CORRECT: Import from domain barrel
|
Run the smallest relevant test first, then the normal gate:
|
||||||
import { execClaudeWithCLIProxy, CLIProxyProvider } from '../cliproxy';
|
|
||||||
import { Config, Settings } from '../types';
|
|
||||||
|
|
||||||
// INCORRECT: Import from specific file (bypasses barrel)
|
|
||||||
import { execClaudeWithCLIProxy } from '../cliproxy/cliproxy-executor';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Exception: Deep Imports
|
|
||||||
|
|
||||||
Allowed when:
|
|
||||||
- Importing private utilities not exposed in barrel
|
|
||||||
- Circular dependency avoidance
|
|
||||||
- Performance-critical tree-shaking
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Target Adapter Pattern
|
|
||||||
|
|
||||||
The target adapter pattern enables pluggable support for multiple CLI implementations (Claude Code, Factory Droid, Codex CLI, etc.) while preserving a unified profile system.
|
|
||||||
|
|
||||||
### Pattern Overview
|
|
||||||
|
|
||||||
**Each CLI target implements a `TargetAdapter` interface:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface TargetAdapter {
|
|
||||||
readonly type: TargetType; // 'claude' | 'droid' | 'codex'
|
|
||||||
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 | Codex |
|
|
||||||
|--------|--------|-------|-------|
|
|
||||||
| **Credential delivery** | Environment variables | Config file (~/.factory/settings.json) | Transient `-c` overrides + `CCS_CODEX_API_KEY` |
|
|
||||||
| **Spawn args** | `claude <args>` | `droid -m custom:ccs-<profile> <args>` | `codex <args>` or `codex -c ... <args>` |
|
|
||||||
| **Config write** | None (uses env) | `upsertCcsModel()` writes to settings | None at runtime; dashboard edits user-owned `~/.codex/config.toml` only |
|
|
||||||
| **Binary detection** | `detectClaudeCli()` | `detectDroidCli()` with version check | `detectCodexCli()` plus `--config` capability probe |
|
|
||||||
|
|
||||||
### Target Resolution Priority
|
|
||||||
|
|
||||||
Resolves which adapter to use via `resolveTargetType()`:
|
|
||||||
|
|
||||||
```
|
|
||||||
1. --target <name> flag (highest priority)
|
|
||||||
↓
|
|
||||||
2. explicit runtime entrypoint (`CCS_INTERNAL_ENTRY_TARGET`):
|
|
||||||
- ccs-droid / ccsd → droid
|
|
||||||
- ccs-codex / ccsx → codex
|
|
||||||
- ccsxp → codex (native cliproxy shortcut)
|
|
||||||
↓
|
|
||||||
3. argv[0] detection (runtime alias pattern / custom alias map):
|
|
||||||
- ccs-droid → droid
|
|
||||||
- ccsd → droid
|
|
||||||
- ccs-codex → codex
|
|
||||||
- ccsx → codex
|
|
||||||
- ccs → default
|
|
||||||
↓
|
|
||||||
4. Profile config: profileConfig.target field
|
|
||||||
↓
|
|
||||||
5. 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());
|
|
||||||
registerTarget(new CodexAdapter());
|
|
||||||
|
|
||||||
// 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:
|
|
||||||
|
|
||||||
### Step 1: Analyze Structure
|
|
||||||
|
|
||||||
Identify logical boundaries:
|
|
||||||
- Render sections in React components
|
|
||||||
- Handler groups in route files
|
|
||||||
- Related utility functions
|
|
||||||
- Constants and types
|
|
||||||
|
|
||||||
### Step 2: Extract Types First
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// types.ts
|
|
||||||
export interface ProviderEditorProps {
|
|
||||||
providerId: string;
|
|
||||||
onSave: (config: ProviderConfig) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelMappingValues {
|
|
||||||
model: string;
|
|
||||||
endpoint: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Extract Utilities
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// utils.ts
|
|
||||||
export function validateEndpoint(url: string): boolean { ... }
|
|
||||||
export function formatModelName(name: string): string { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Extract Hooks
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// hooks.ts
|
|
||||||
export function useProviderConfig(providerId: string) { ... }
|
|
||||||
export function useModelValidation() { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Extract Subcomponents
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// model-mapping-form.tsx
|
|
||||||
export function ModelMappingForm({ values, onChange }: Props) { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Compose in Index
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// index.tsx
|
|
||||||
import { ModelMappingForm } from './model-mapping-form';
|
|
||||||
import { useProviderConfig } from './hooks';
|
|
||||||
import type { ProviderEditorProps } from './types';
|
|
||||||
|
|
||||||
export function ProviderEditor({ providerId, onSave }: ProviderEditorProps) {
|
|
||||||
const config = useProviderConfig(providerId);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ModelMappingForm values={config.mapping} onChange={...} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-export types for consumers
|
|
||||||
export type { ProviderEditorProps, ModelMappingValues } from './types';
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## TypeScript Standards
|
|
||||||
|
|
||||||
### Strict Mode Required
|
|
||||||
|
|
||||||
All projects use TypeScript strict mode:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type Annotations
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// CORRECT: Explicit return types for public functions
|
|
||||||
export function detectProfile(args: string[]): DetectedProfile { ... }
|
|
||||||
|
|
||||||
// CORRECT: Inferred types for internal functions
|
|
||||||
const formatName = (name: string) => name.trim().toLowerCase();
|
|
||||||
|
|
||||||
// INCORRECT: any type
|
|
||||||
function processData(data: any) { ... } // Use unknown or proper type
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type Exports
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// CORRECT: Use type keyword for type-only exports
|
|
||||||
export type { Config, Settings } from './config';
|
|
||||||
|
|
||||||
// CORRECT: Group type exports in barrel
|
|
||||||
export type {
|
|
||||||
PlatformInfo,
|
|
||||||
BinaryInfo,
|
|
||||||
DownloadProgress,
|
|
||||||
} from './types';
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ESLint Rules (Enforced)
|
|
||||||
|
|
||||||
| Rule | Level | Notes |
|
|
||||||
|------|-------|-------|
|
|
||||||
| `@typescript-eslint/no-unused-vars` | error | Ignore `_` prefix |
|
|
||||||
| `@typescript-eslint/no-explicit-any` | error | Use proper types |
|
|
||||||
| `@typescript-eslint/no-non-null-assertion` | error | No `!` assertions |
|
|
||||||
| `prefer-const` | error | Immutable by default |
|
|
||||||
| `no-var` | error | Use const/let |
|
|
||||||
| `eqeqeq` | error | Strict equality |
|
|
||||||
| `react-hooks/*` | recommended | (UI only) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Terminal Output Standards
|
|
||||||
|
|
||||||
### CCS Logging Standards
|
|
||||||
|
|
||||||
- Use the shared logger from `src/services/logging/` for CCS-owned runtime diagnostics, request tracing, and structured events.
|
|
||||||
- Keep `utils/ui` and deliberate `console.log`/`console.error` output for user-facing CLI UX only.
|
|
||||||
- Redact secrets before persistence; never write raw tokens, cookies, API keys, or password hashes into CCS-owned logs.
|
|
||||||
- Persist CCS-owned logs only under `getCcsDir()/logs`; do not invent per-feature log roots.
|
|
||||||
- When adding dashboard polling or diagnostics routes, prevent them from recursively logging the log viewer itself.
|
|
||||||
|
|
||||||
### ASCII Only
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// CORRECT
|
|
||||||
console.log('[OK] Operation successful');
|
|
||||||
console.log('[!] Warning message');
|
|
||||||
console.log('[X] Error occurred');
|
|
||||||
console.log('[i] Information');
|
|
||||||
|
|
||||||
// INCORRECT - NO EMOJIS
|
|
||||||
console.log('Operation successful'); // NO
|
|
||||||
console.log('Warning message'); // NO
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Handling
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { colors } from '../utils/ui';
|
|
||||||
|
|
||||||
// Colors are TTY-aware and respect NO_COLOR
|
|
||||||
console.log(colors.green('[OK]') + ' Operation successful');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Box Borders
|
|
||||||
|
|
||||||
Use ASCII box drawing for error displays:
|
|
||||||
|
|
||||||
```
|
|
||||||
+=====================================+
|
|
||||||
| [X] ERROR: Configuration failed |
|
|
||||||
| |
|
|
||||||
| Details: Unable to parse config |
|
|
||||||
+=====================================+
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
For all Claude child-process launches (delegation, adapters, proxies, helper spawners), sanitize env before spawn:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const cleanEnv = stripClaudeCodeEnv(mergedEnv); // case-insensitive remove of CLAUDECODE
|
|
||||||
spawn(binaryPath, args, { env: cleanEnv, stdio: 'inherit' });
|
|
||||||
```
|
|
||||||
|
|
||||||
This prevents Claude Code nested-session guard failures when CCS runs inside parent Claude sessions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## React Component Standards (UI)
|
|
||||||
|
|
||||||
### Component Structure
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// component-name.tsx
|
|
||||||
|
|
||||||
// 1. Imports (grouped: react, external, internal, relative)
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { useProfiles } from '@/hooks';
|
|
||||||
import { formatName } from './utils';
|
|
||||||
import type { ComponentProps } from './types';
|
|
||||||
|
|
||||||
// 2. Types (if not in separate file)
|
|
||||||
interface Props {
|
|
||||||
id: string;
|
|
||||||
onSave: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Component
|
|
||||||
export function ComponentName({ id, onSave }: Props) {
|
|
||||||
// Hooks first
|
|
||||||
const profiles = useProfiles();
|
|
||||||
const [state, setState] = useState(null);
|
|
||||||
|
|
||||||
// Handlers
|
|
||||||
const handleClick = () => { ... };
|
|
||||||
|
|
||||||
// Render
|
|
||||||
return ( ... );
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Naming Conventions
|
|
||||||
|
|
||||||
| Item | Convention | Example |
|
|
||||||
|------|------------|---------|
|
|
||||||
| Component files | kebab-case.tsx | `provider-editor.tsx` |
|
|
||||||
| Component exports | PascalCase | `ProviderEditor` |
|
|
||||||
| Hook files | use-*.ts | `use-profiles.ts` |
|
|
||||||
| Hook exports | useCamelCase | `useProfiles` |
|
|
||||||
| Utility files | kebab-case.ts | `path-utils.ts` |
|
|
||||||
| Utility exports | camelCase | `formatPath` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Input State Persistence Patterns
|
|
||||||
|
|
||||||
When building forms and editors that allow users to make changes, follow these patterns to prevent data loss.
|
|
||||||
|
|
||||||
### Pattern 1: Key-Based Remounting
|
|
||||||
|
|
||||||
**Use when**: Component has complex local state that should reset on prop changes.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Parent component
|
|
||||||
<ProfileEditor
|
|
||||||
key={profileId} // Forces remount when profile changes
|
|
||||||
profileId={profileId}
|
|
||||||
onSave={handleSave}
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why**: Without `key`, React reuses the component instance. Local `useState` values persist even when props change, causing stale data bugs.
|
|
||||||
|
|
||||||
### Pattern 2: Unsaved Changes Confirmation
|
|
||||||
|
|
||||||
**Use when**: User might navigate away while editing.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Parent tracks dirty state
|
|
||||||
const [editorHasChanges, setEditorHasChanges] = useState(false);
|
|
||||||
const [pendingSwitch, setPendingSwitch] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// Child notifies parent of dirty state
|
|
||||||
useEffect(() => {
|
|
||||||
onHasChangesUpdate?.(computedHasChanges);
|
|
||||||
}, [computedHasChanges, onHasChangesUpdate]);
|
|
||||||
|
|
||||||
// Intercept navigation
|
|
||||||
const handleSelect = (id: string) => {
|
|
||||||
if (editorHasChanges && currentId !== id) {
|
|
||||||
setPendingSwitch(id); // Show confirmation dialog
|
|
||||||
} else {
|
|
||||||
setCurrentId(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Flow**:
|
|
||||||
1. Child computes `hasChanges` from local state vs saved data
|
|
||||||
2. Child notifies parent via callback
|
|
||||||
3. Parent intercepts navigation when dirty
|
|
||||||
4. Show confirmation dialog: "Discard & Switch" or "Cancel"
|
|
||||||
5. On confirm: reset dirty state, then switch
|
|
||||||
|
|
||||||
### Pattern 3: Auto-Save with Visual Feedback
|
|
||||||
|
|
||||||
**Use when**: Simple inputs that should save immediately.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const [saved, setSaved] = useState(false);
|
|
||||||
|
|
||||||
const handleBlur = async () => {
|
|
||||||
if (value !== savedValue) {
|
|
||||||
await saveToBackend(value);
|
|
||||||
setSaved(true);
|
|
||||||
setTimeout(() => setSaved(false), 2000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Input value={value} onChange={...} onBlur={handleBlur} />
|
|
||||||
{saved && (
|
|
||||||
<span className="text-green-600 text-xs flex items-center gap-1">
|
|
||||||
<Check className="w-3.5 h-3.5" /> Saved
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**When to use which**:
|
|
||||||
| Scenario | Pattern |
|
|
||||||
|----------|---------|
|
|
||||||
| Complex multi-field editor | Pattern 2 (confirmation dialog) |
|
|
||||||
| Simple single input | Pattern 3 (auto-save + feedback) |
|
|
||||||
| List item selection | Pattern 1 (key-based remount) + Pattern 2 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quality Gates
|
|
||||||
|
|
||||||
### Pre-Commit Sequence
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Main project
|
|
||||||
bun run format
|
bun run format
|
||||||
bun run lint:fix
|
bun run lint:fix
|
||||||
bun run validate
|
bun run validate
|
||||||
bun run validate:ci-parity
|
```
|
||||||
|
|
||||||
# UI project (if changed)
|
Before review or merge confidence:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run validate:ci-parity
|
||||||
|
```
|
||||||
|
|
||||||
|
For dashboard changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
cd ui
|
cd ui
|
||||||
bun run format
|
bun run format
|
||||||
bun run lint:fix
|
|
||||||
bun run validate
|
bun run validate
|
||||||
|
bun run test:run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Validate Runs
|
Do not weaken or skip a failing check to make a change pass. If a full gate
|
||||||
|
cannot run, report the exact focused checks completed and the blocker.
|
||||||
|
|
||||||
| Project | Command | Checks |
|
## Commits and Releases
|
||||||
|---------|---------|--------|
|
|
||||||
| Main | `bun run validate` | typecheck + lint + format:check + test:fast |
|
|
||||||
| UI | `bun run validate` | typecheck + lint + format:check |
|
|
||||||
|
|
||||||
---
|
Commitlint accepts conventional types defined in
|
||||||
|
[`commitlint.config.cjs`](../commitlint.config.cjs), with a 100-character header
|
||||||
|
limit. Use focused subjects such as:
|
||||||
|
|
||||||
## Conventional Commits
|
```text
|
||||||
|
fix(doctor): handle missing config
|
||||||
All commits must follow conventional commit format:
|
feat(cliproxy): add provider quota check
|
||||||
|
docs(contributing): clarify validation
|
||||||
```
|
|
||||||
<type>(<scope>): <description>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Types
|
Semantic-release owns versions, changelog entries, tags, npm publishing, and
|
||||||
|
GitHub releases. Do not bump versions, tag, or publish manually. Release effects
|
||||||
|
for each branch and commit type are defined in
|
||||||
|
[`.releaserc.cjs`](../.releaserc.cjs).
|
||||||
|
|
||||||
| Type | When to Use | Version Bump |
|
## Documentation Triggers
|
||||||
|------|-------------|--------------|
|
|
||||||
| `feat` | New feature | MINOR |
|
|
||||||
| `fix` | Bug fix | PATCH |
|
|
||||||
| `perf` | Performance | PATCH |
|
|
||||||
| `docs` | Documentation | None |
|
|
||||||
| `style` | Formatting | None |
|
|
||||||
| `refactor` | Code restructure | None |
|
|
||||||
| `test` | Tests | None |
|
|
||||||
| `chore` | Maintenance | None |
|
|
||||||
|
|
||||||
### Examples
|
Update the owning guide when a change affects behavior, commands, setup,
|
||||||
|
architecture, security posture, public contracts, or future maintainer
|
||||||
|
decisions. Start at [`docs/README.md`](./README.md). Public CLI, provider,
|
||||||
|
configuration, installation, or workflow changes also require the matching page
|
||||||
|
in the separate `kaitranntt/ccs-docs` repository; see the docs index for
|
||||||
|
checkout guidance.
|
||||||
|
|
||||||
```bash
|
Prefer source links over copied trees and metrics. Remove stale sections rather
|
||||||
# Correct
|
than leaving TODO markers.
|
||||||
git commit -m "feat(cliproxy): add OAuth token refresh"
|
|
||||||
git commit -m "fix(doctor): handle missing config gracefully"
|
|
||||||
git commit -m "refactor(ui): split provider-editor into modules"
|
|
||||||
|
|
||||||
# Incorrect - REJECTED
|
|
||||||
git commit -m "added new feature"
|
|
||||||
git commit -m "Fixed bug"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Anti-Patterns to Avoid
|
|
||||||
|
|
||||||
### 1. God Files
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD: One file doing everything
|
|
||||||
// src/utils.ts (2000 lines with mixed concerns)
|
|
||||||
|
|
||||||
// GOOD: Split by domain
|
|
||||||
// src/utils/ui/colors.ts
|
|
||||||
// src/utils/ui/boxes.ts
|
|
||||||
// src/utils/shell-executor.ts
|
|
||||||
// src/utils/config-manager.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Barrel Import Bypass
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD: Direct import bypassing barrel
|
|
||||||
import { detectPlatform } from '../cliproxy/platform-detector';
|
|
||||||
|
|
||||||
// GOOD: Import from domain barrel
|
|
||||||
import { detectPlatform } from '../cliproxy';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Inline Everything
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD: Huge inline functions in components
|
|
||||||
function Component() {
|
|
||||||
const handleComplexOperation = () => {
|
|
||||||
// 100 lines of logic...
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOOD: Extract to hooks or utilities
|
|
||||||
function Component() {
|
|
||||||
const { handleComplexOperation } = useComplexOperation();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Type Duplication
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD: Same types defined in multiple files
|
|
||||||
// file1.ts
|
|
||||||
interface Config { ... }
|
|
||||||
// file2.ts
|
|
||||||
interface Config { ... }
|
|
||||||
|
|
||||||
// GOOD: Single source of truth
|
|
||||||
// types/config.ts
|
|
||||||
export interface Config { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Config Priority Pattern
|
|
||||||
|
|
||||||
When resolving configuration from multiple sources, follow this priority order:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// proxy-config-resolver.ts pattern
|
|
||||||
// Priority: CLI flags > Environment variables > config.yaml > defaults
|
|
||||||
|
|
||||||
const resolved = {
|
|
||||||
...DEFAULT_CONFIG, // 4. Defaults (lowest)
|
|
||||||
...yamlConfig, // 3. config.yaml
|
|
||||||
...envConfig, // 2. Environment variables
|
|
||||||
...cliFlags, // 1. CLI flags (highest)
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
This pattern is used in:
|
|
||||||
- `src/cliproxy/proxy-config-resolver.ts` - Remote proxy config
|
|
||||||
- `src/config/unified-config-loader.ts` - Main config loading
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Lint Enforcement Gates
|
|
||||||
|
|
||||||
Two ESLint gates (`eslint.config.mjs`) lock in the maintainability epic's gains:
|
|
||||||
|
|
||||||
- **`ccs/no-new-throw-error`** (error): flags new `throw new Error(...)`. Use a typed error from `src/errors/error-types.ts` (`AuthError`, `ConfigError`, `ProfileError`, `ProviderError`, `NetworkError`, `ProxyError`, `MigrationError`, `ValidationError`, `RetryableError`) so `handleError` emits a differentiated exit code. Existing ~340 sites are grandfathered in `eslint-rules/throw-error-baseline.json`; only **new** violations error. Regenerate the baseline when intentionally grandfathering a new site, or quarterly to prune converted entries:
|
|
||||||
```bash
|
|
||||||
node scripts/generate-throw-error-baseline.js
|
|
||||||
```
|
|
||||||
- **`max-lines`** (warn, 400): warns on source files over 400 lines (`skipBlankLines`, `skipComments`). Split via the Monster File Splitting methodology above (barrel `index.ts` preserves the public API).
|
|
||||||
|
|
||||||
When the no-throw rule blocks a change, prefer converting to the matching typed error. Only add to the baseline when the throw is genuinely out of scope to convert (and regenerate the baseline so the entry is explicit, not silent).
|
|
||||||
|
|
||||||
## Related Documentation
|
|
||||||
|
|
||||||
- [Codebase Summary](./codebase-summary.md) - Full directory structure
|
|
||||||
- [System Architecture](./system-architecture/index.md) - Architecture diagrams
|
|
||||||
- [CLAUDE.md](../CLAUDE.md) - AI-facing development guidance
|
|
||||||
+171
-671
@@ -1,673 +1,173 @@
|
|||||||
# CCS Codebase Summary
|
# CCS Codebase Summary
|
||||||
|
|
||||||
Last Updated: 2026-05-18
|
CCS is a TypeScript/Bun CLI and local React dashboard for selecting profiles,
|
||||||
|
preparing provider credentials, and launching Claude Code, Codex CLI, Factory
|
||||||
Comprehensive overview of the modularized CCS codebase structure following the Phase 9 modularization effort (Settings, Analytics, Auth Monitor splits + Test Infrastructure), v7.1 Remote CLIProxy feature, v7.2 Kiro + GitHub Copilot (ghcp) OAuth providers, v7.14 Hybrid Quota Management, v7.34 Image Analysis Hook, account-context validation hardening, Official Claude Channels runtime support, native Codex runtime target support, native Codex/Droid usage collectors, and models.dev-backed model pricing metadata.
|
Droid, and compatible proxy-backed workflows. This page maps stable ownership;
|
||||||
|
source and tests remain the implementation truth.
|
||||||
## Repository Structure
|
|
||||||
|
## Runtime Surfaces
|
||||||
```
|
|
||||||
ccs/
|
| Surface | Entry point | Responsibility |
|
||||||
├── src/ # CLI TypeScript source
|
| --- | --- | --- |
|
||||||
├── dist/ # Compiled JavaScript (npm package)
|
| `ccs` CLI | [`src/ccs.ts`](../src/ccs.ts) | Parse global input, register targets, resolve profiles, dispatch commands or runtimes |
|
||||||
├── lib/ # Native shell scripts (bash, PowerShell)
|
| Runtime aliases | [`package.json`](../package.json) | Expose packaged binaries such as `ccs`, `ccsx`, and target-specific entry points |
|
||||||
├── ui/ # React dashboard application
|
| Command handlers | [`src/commands/`](../src/commands/) | Implement CCS-owned command families and help text |
|
||||||
│ ├── src/ # UI source code
|
| Local web server | [`src/web-server/index.ts`](../src/web-server/index.ts) | Serve configuration APIs, WebSocket updates, and the built dashboard |
|
||||||
│ └── dist/ # Built UI bundle
|
| Dashboard | [`ui/src/main.tsx`](../ui/src/main.tsx) | Mount the React application used by `ccs config` |
|
||||||
├── docker/ # Docker deployment configuration
|
| Bootstrap wrappers | [`lib/`](../lib/) | Start the packaged CLI on Unix and Windows |
|
||||||
│ ├── Dockerfile # Multi-stage build (bun 1.2.21, node:20-bookworm-slim)
|
|
||||||
│ ├── docker-compose.yml # Compose setup with resource limits, healthcheck
|
Detailed user workflows and command reference live at
|
||||||
│ ├── entrypoint.sh # Entrypoint with privilege dropping, usage help
|
[docs.ccs.kaitran.ca](https://docs.ccs.kaitran.ca). Do not infer current flags
|
||||||
│ └── README.md # Docker deployment guide
|
from this overview; inspect the owning handler and its tests.
|
||||||
├── tests/ # Test suites
|
|
||||||
├── docs/ # Documentation
|
## CLI Domain Ownership
|
||||||
└── assets/ # Static assets (logos, screenshots)
|
|
||||||
```
|
| Domain | Main source | Owns |
|
||||||
|
| --- | --- | --- |
|
||||||
---
|
| Command routing | [`src/commands/`](../src/commands/) | Command parsing, command-specific help, setup, doctor, config, Docker, and management flows |
|
||||||
|
| Profile dispatch | [`src/dispatcher/`](../src/dispatcher/) | Resolve a launch into target-specific execution flows |
|
||||||
## CLI Source (`src/`)
|
| Runtime targets | [`src/targets/`](../src/targets/) | Target metadata, resolution, adapters, binary detection, and execution |
|
||||||
|
| Configuration | [`src/config/`](../src/config/) | Schema validation, loading, and normalized configuration access |
|
||||||
The main CLI is organized into domain-specific modules with barrel exports.
|
| CLIProxy | [`src/cliproxy/`](../src/cliproxy/) | Provider auth, configuration, routing, quota, lifecycle, and execution |
|
||||||
|
| Authentication | [`src/auth/`](../src/auth/) and [`src/codex-auth/`](../src/codex-auth/) | Account and OAuth-oriented authentication flows |
|
||||||
### Directory Structure
|
| Channels | [`src/channels/`](../src/channels/) | Official Claude channel readiness and configuration |
|
||||||
|
| Local proxy | [`src/proxy/`](../src/proxy/) | Anthropic-compatible proxy server and request/response transformers |
|
||||||
```
|
| Web API | [`src/api/`](../src/api/) and [`src/web-server/`](../src/web-server/) | Local dashboard services, routes, middleware, health, usage, and live updates |
|
||||||
src/
|
| Shared services | [`src/services/`](../src/services/) | Cross-cutting runtime services, including structured logging |
|
||||||
├── ccs.ts # Main entry point & profile execution flow
|
| Errors | [`src/errors/`](../src/errors/) | Typed error taxonomy, handling, and exit behavior |
|
||||||
├── bin/ # Dedicated runtime entrypoints
|
| Utilities | [`src/utils/`](../src/utils/) | CCS path handling and bounded shared helpers for browser, hooks, web search, image analysis, and UI support |
|
||||||
│ ├── droid-runtime.ts # Forces droid target for ccs-droid / ccsd package bins
|
| Compatibility | [`src/glmt/`](../src/glmt/), [`src/copilot/`](../src/copilot/), [`src/cursor/`](../src/cursor/) | Legacy translation and integration-specific behavior |
|
||||||
│ ├── codex-runtime.ts # Forces codex target for ccs-codex / ccsx package bins
|
| Packaging/runtime bins | [`src/bin/`](../src/bin/) | Target-specific packaged entry points |
|
||||||
│ └── ccsxp-runtime.ts # Forces codex target + native cliproxy override for ccsxp
|
|
||||||
├── types/ # TypeScript type definitions
|
The table is intentionally domain-level. Use repository search and nearby tests
|
||||||
│ ├── index.ts # Barrel export (aggregates all types)
|
to find the current implementation instead of relying on a recursive file tree.
|
||||||
│ ├── cli.ts # CLI types (ParsedArgs, ExitCode)
|
|
||||||
│ ├── config.ts # Config types (Settings, EnvVars)
|
## Profile and Target Dispatch
|
||||||
│ ├── delegation.ts # Delegation types (sessions, events)
|
|
||||||
│ ├── glmt.ts # Legacy transformer types (messages, transforms)
|
Profile resolution follows the repository contract in
|
||||||
│ └── utils.ts # Utility types (ErrorCode, LogLevel)
|
[`CLAUDE.md`](../CLAUDE.md):
|
||||||
│
|
|
||||||
├── commands/ # CLI command handlers
|
1. built-in CLIProxy providers
|
||||||
│ ├── api-command/ # API profile subcommands (split facade + handlers)
|
2. user-defined `config.cliproxy` providers
|
||||||
│ │ ├── index.ts # API command facade/router
|
3. settings-based `config.profiles`
|
||||||
│ │ ├── shared.ts # Shared API arg parsing helpers
|
4. account-based `profiles.json` entries with isolated `CLAUDE_CONFIG_DIR`
|
||||||
│ │ └── [subcommand files...]
|
|
||||||
│ ├── cliproxy-command.ts # CLIProxy subcommand handling
|
Target selection is a separate layer:
|
||||||
│ ├── config-command.ts # Config management commands
|
|
||||||
│ ├── config-image-analysis-command.ts # First-class ImageAnalysis config (NEW v7.34)
|
| Concern | Source |
|
||||||
│ ├── named-command-router.ts # Reusable named-command dispatcher
|
| --- | --- |
|
||||||
│ ├── doctor-command.ts # Health diagnostics
|
| Target names, aliases, and persistence | [`src/targets/target-metadata.ts`](../src/targets/target-metadata.ts) |
|
||||||
│ ├── env-command.ts # Export shell env vars for third-party tools (v7.39)
|
| Selection priority and `--target` parsing | [`src/targets/target-resolver.ts`](../src/targets/target-resolver.ts) |
|
||||||
│ ├── help-command.ts # Help text generation
|
| Adapter interface | [`src/targets/target-adapter.ts`](../src/targets/target-adapter.ts) |
|
||||||
│ ├── install-command.ts # Install/uninstall logic
|
| Adapter registry | [`src/targets/target-registry.ts`](../src/targets/target-registry.ts) |
|
||||||
│ ├── root-command-router.ts # Extracted top-level command dispatch from ccs.ts
|
| Claude implementation | [`src/targets/claude-adapter.ts`](../src/targets/claude-adapter.ts) |
|
||||||
│ ├── shell-completion-command.ts
|
| Droid implementation | [`src/targets/droid-adapter.ts`](../src/targets/droid-adapter.ts) |
|
||||||
│ ├── sync-command.ts # Symlink synchronization
|
| Codex implementation | [`src/targets/codex-adapter.ts`](../src/targets/codex-adapter.ts) |
|
||||||
│ ├── update-command.ts # Self-update logic
|
|
||||||
│ └── version-command.ts # Version display
|
All targets currently marked `persistedTarget` in target metadata are valid
|
||||||
│
|
profile targets. Runtime aliases are also derived from that metadata. Link to
|
||||||
├── targets/ # Multi-target adapter system (NEW)
|
the source instead of maintaining a second target list here.
|
||||||
│ ├── index.ts # Barrel export
|
|
||||||
│ ├── target-adapter.ts # TargetAdapter interface contract
|
At startup, [`src/ccs.ts`](../src/ccs.ts) registers adapters. The dispatcher
|
||||||
│ ├── target-registry.ts # Registry for runtime adapter lookup
|
resolves the profile and target, asks the adapter to prepare credentials and
|
||||||
│ ├── target-resolver.ts # Resolution logic (flag > runtime entrypoint / argv[0] > config)
|
arguments, then executes the selected CLI. Target-specific behavior belongs in
|
||||||
│ ├── target-metadata.ts # Runtime vs persisted target metadata and alias lists
|
the adapter or its supporting target module, not in generic command routing.
|
||||||
│ ├── target-runtime-compatibility.ts # Guardrails for target/profile combinations
|
|
||||||
│ ├── claude-adapter.ts # Claude Code CLI implementation
|
## Configuration and Local State
|
||||||
│ ├── droid-adapter.ts # Factory Droid CLI implementation
|
|
||||||
│ ├── codex-adapter.ts # Native Codex CLI implementation
|
[`src/utils/config-manager.ts`](../src/utils/config-manager.ts) owns CCS home
|
||||||
│ ├── codex-detector.ts # Codex binary detection and capability probing
|
resolution and honors `CCS_HOME`. Tests must point `CCS_HOME` at a temporary
|
||||||
│ ├── droid-detector.ts # Droid binary detection & version checks
|
directory and must not touch a contributor's real `~/.ccs/` or `~/.claude/`.
|
||||||
│ └── droid-config-manager.ts # ~/.factory/settings.json management
|
|
||||||
│
|
Configuration schemas and loaders live under [`src/config/`](../src/config/).
|
||||||
├── auth/ # Authentication module
|
Provider- and CLIProxy-specific persistence stays under
|
||||||
│ ├── index.ts # Barrel export
|
[`src/cliproxy/config/`](../src/cliproxy/config/). Values written to settings
|
||||||
│ ├── commands/ # Auth-specific CLI commands
|
environment maps must remain strings.
|
||||||
│ │ └── index.ts
|
|
||||||
│ ├── account-switcher.ts # Account switching logic
|
Some integrations intentionally write state owned by the launched runtime, such
|
||||||
│ └── profile-detector.ts # Profile detection (474 lines)
|
as Claude channel configuration or Codex configuration. Verify those boundaries
|
||||||
│
|
in the owning module and tests before changing paths, permissions, or cleanup.
|
||||||
├── config/ # Configuration management
|
|
||||||
│ ├── index.ts # Barrel export
|
## Dashboard Ownership
|
||||||
│ ├── unified-config-loader.ts # Central config loader (546 lines)
|
|
||||||
│ └── migration-manager.ts # Config migration logic
|
The dashboard is a separate TypeScript package under [`ui/`](../ui/):
|
||||||
│
|
|
||||||
├── proxy/ # OpenAI-compatible proxy runtime
|
| Area | Path | Responsibility |
|
||||||
│ ├── index.ts # Barrel export
|
| --- | --- | --- |
|
||||||
│ ├── proxy-daemon-entry.ts # Daemon entrypoint
|
| Pages | [`ui/src/pages/`](../ui/src/pages/) | Route-level orchestration and settings sections |
|
||||||
│ ├── proxy-daemon.ts # Lifecycle, health, and port binding
|
| Components | [`ui/src/components/`](../ui/src/components/) | Domain UI and shared primitives |
|
||||||
│ ├── proxy-port-resolver.ts # Adaptive per-profile port selection
|
| Hooks | [`ui/src/hooks/`](../ui/src/hooks/) | Server-state access and reusable UI behavior |
|
||||||
│ ├── request-router.ts # Request-time profile/model routing
|
| Contexts/providers | [`ui/src/contexts/`](../ui/src/contexts/) and [`ui/src/providers/`](../ui/src/providers/) | Cross-page client state |
|
||||||
│ ├── profile-router.ts # Profile resolution helpers
|
| Libraries | [`ui/src/lib/`](../ui/src/lib/) | API client, localization, catalogs, formatting, and helpers |
|
||||||
│ ├── proxy-env.ts # Local runtime env construction
|
|
||||||
│ ├── routing-config.ts # Proxy routing config parsing
|
The browser communicates with routes and services under
|
||||||
│ ├── upstream-url.ts # Upstream endpoint resolution
|
[`src/web-server/`](../src/web-server/). When a configuration feature supports
|
||||||
│ ├── proxy-daemon-state.ts # Persistent running-state metadata
|
both surfaces, keep CLI and dashboard behavior aligned.
|
||||||
│ ├── server/ # HTTP server and routes
|
|
||||||
│ └── transformers/ # Request and SSE translation
|
Localization codes, normalization, persistence, and fallback are owned by
|
||||||
│
|
[`ui/src/lib/locales.ts`](../ui/src/lib/locales.ts). Translation resources and
|
||||||
├── channels/ # Official Claude channel integration
|
i18next wiring live in [`ui/src/lib/i18n.ts`](../ui/src/lib/i18n.ts).
|
||||||
│ ├── official-channels-runtime.ts # Runtime gating, plugin specs, setup guidance
|
|
||||||
│ └── official-channels-store.ts # Claude channel token/env storage helpers
|
## Logging and Operational Data
|
||||||
│
|
|
||||||
├── cliproxy/ # CLIProxyAPI integration (heavily modularized)
|
Structured CCS logging lives in
|
||||||
│ ├── index.ts # Barrel export (137 lines, extensive)
|
[`src/services/logging/`](../src/services/logging/). Dashboard log routes and
|
||||||
│ ├── auth/ # OAuth handlers, token management
|
services live under [`src/web-server/`](../src/web-server/), with UI consumers
|
||||||
│ │ └── index.ts
|
under [`ui/src/components/logs/`](../ui/src/components/logs/) and the matching
|
||||||
│ ├── binary/ # Binary management
|
page and hooks.
|
||||||
│ │ └── index.ts
|
|
||||||
│ ├── services/ # Service layer
|
Keep secrets and raw credentials out of logs. Treat legacy CLIProxy log files as
|
||||||
│ │ └── index.ts
|
a distinct source rather than folding them into CCS-owned structured logs.
|
||||||
│ ├── cliproxy-executor.ts # Main executor (666 lines)
|
|
||||||
│ ├── config-generator.ts # Config file generation (531 lines)
|
## Tests
|
||||||
│ ├── account-manager.ts # Account management (509 lines)
|
|
||||||
│ ├── quota-manager.ts # Hybrid quota management (NEW v7.14)
|
Root TypeScript tests run with Bun's test runner. Bucket selection is implemented
|
||||||
│ ├── quota-fetcher.ts # Provider quota API integration (NEW v7.14)
|
by [`scripts/run-test-bucket.js`](../scripts/run-test-bucket.js). The dashboard
|
||||||
│ ├── platform-detector.ts # OS/arch detection
|
uses Vitest as configured in [`ui/package.json`](../ui/package.json).
|
||||||
│ ├── binary-manager.ts # Binary download/update
|
|
||||||
│ ├── auth-handler.ts # Authentication handling
|
| Coverage area | Location |
|
||||||
│ ├── model-catalog.ts # Provider model definitions
|
| --- | --- |
|
||||||
│ ├── model-config.ts # Model configuration
|
| Focused module behavior | [`tests/unit/`](../tests/unit/) and colocated `src/**/__tests__/` |
|
||||||
│ ├── codex-plan-compatibility.ts # Codex free/paid model fallback guardrails
|
| Cross-module behavior | [`tests/integration/`](../tests/integration/) |
|
||||||
│ ├── service-manager.ts # Background service
|
| CLI end-to-end behavior | [`tests/e2e/`](../tests/e2e/) |
|
||||||
│ ├── proxy-detector.ts # Running proxy detection
|
| Package installation and exports | [`tests/npm/`](../tests/npm/) |
|
||||||
│ ├── startup-lock.ts # Race condition prevention
|
| Shell and platform behavior | [`tests/native/`](../tests/native/) |
|
||||||
│ ├── remote-proxy-client.ts # Remote proxy health checks (v7.1)
|
| Docker public contracts | [`tests/docker/`](../tests/docker/) |
|
||||||
│ ├── proxy-config-resolver.ts # CLI/env/config merging (v7.1)
|
| Documentation checks | [`tests/docs/`](../tests/docs/) |
|
||||||
│ ├── types.ts # ResolvedProxyConfig for local/remote modes
|
| Dashboard behavior | [`ui/tests/`](../ui/tests/) and colocated UI tests |
|
||||||
│ └── [more files...]
|
|
||||||
│
|
Commands and bucket behavior are documented in
|
||||||
├── copilot/ # GitHub Copilot integration
|
[`tests/README.md`](../tests/README.md) and defined in
|
||||||
│ ├── index.ts # Barrel export
|
[`package.json`](../package.json). Avoid copying test counts or pass totals into
|
||||||
│ └── copilot-package-manager.ts # Package management (515 lines)
|
evergreen documentation.
|
||||||
│
|
|
||||||
├── glmt/ # Legacy transformer internals kept for compatibility
|
## Build and Release
|
||||||
│ ├── index.ts # Barrel export
|
|
||||||
│ ├── pipeline/ # Processing pipeline
|
| Output or process | Source of truth |
|
||||||
│ │ └── index.ts
|
| --- | --- |
|
||||||
│ ├── glmt-proxy.ts # Legacy proxy runtime kept for internal compatibility
|
| CLI compilation into `dist/` | root scripts in [`package.json`](../package.json) |
|
||||||
│ └── delta-accumulator.ts # Delta processing (484 lines)
|
| Dashboard build into `dist/ui/` | UI and root build scripts |
|
||||||
│
|
| Bundle verification | [`scripts/verify-bundle.js`](../scripts/verify-bundle.js) |
|
||||||
├── delegation/ # Task delegation & headless execution
|
| Local CI-equivalent gate | [`scripts/ci-parity-gate.sh`](../scripts/ci-parity-gate.sh) |
|
||||||
│ ├── index.ts # Barrel export
|
| Commit policy | [`commitlint.config.cjs`](../commitlint.config.cjs) |
|
||||||
│ ├── executor/ # Execution engine
|
| Branch-aware releases | [`.releaserc.cjs`](../.releaserc.cjs) |
|
||||||
│ └── [delegation files...]
|
| GitHub automation | [`.github/workflows/`](../.github/workflows/) |
|
||||||
│
|
|
||||||
├── errors/ # Centralized error handling
|
Semantic-release owns versions, changelog updates, tags, npm publication, and
|
||||||
│ ├── index.ts # Barrel export
|
GitHub releases. Generated `dist/` contents and release artifacts are outputs,
|
||||||
│ ├── error-handler.ts # Main error handler
|
not architectural source.
|
||||||
│ ├── exit-codes.ts # Exit code definitions
|
|
||||||
│ └── cleanup.ts # Cleanup logic
|
## Documentation Map
|
||||||
│
|
|
||||||
├── management/ # Doctor diagnostics
|
- [Maintainer docs index](./README.md)
|
||||||
│ ├── index.ts # Barrel export
|
- [Code standards](./code-standards.md)
|
||||||
│ ├── checks/ # Diagnostic checks
|
- [System architecture](./system-architecture/index.md)
|
||||||
│ │ ├── index.ts
|
- [Project roadmap](./project-roadmap.md)
|
||||||
│ │ └── image-analysis-check.ts # ImageAnalysis runtime validation (NEW v7.34)
|
- [Dashboard i18n](./i18n-dashboard.md)
|
||||||
│ └── repair/ # Auto-repair logic
|
- [OpenAI-compatible provider routing](./openai-compatible-providers.md)
|
||||||
│ └── index.ts
|
- [Image analysis user guide](https://docs.ccs.kaitran.ca/features/ai/image-analysis)
|
||||||
│
|
- [AI agent guide](../CLAUDE.md)
|
||||||
├── api/ # API utilities & services
|
- [Contributor guide](../CONTRIBUTING.md)
|
||||||
│ ├── index.ts # Barrel export
|
|
||||||
│ └── services/ # API services
|
Update this summary only when domain ownership, stable entry points, build
|
||||||
│ ├── index.ts
|
boundaries, or truth sources change.
|
||||||
│ ├── profile-reader.ts
|
|
||||||
│ └── profile-writer.ts
|
|
||||||
│
|
|
||||||
├── utils/ # Utilities (modularized into subdirs)
|
|
||||||
│ ├── index.ts # Barrel export
|
|
||||||
│ ├── ui/ # Terminal UI utilities
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ ├── boxes.ts # Box drawing
|
|
||||||
│ │ ├── colors.ts # Terminal colors
|
|
||||||
│ │ └── spinners.ts # Progress spinners
|
|
||||||
│ ├── websearch/ # Search tool integrations
|
|
||||||
│ │ └── index.ts
|
|
||||||
│ ├── hooks/ # Claude Code compatibility hooks (NEW v7.34)
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ ├── image-analyzer-hook-installer.ts
|
|
||||||
│ │ ├── image-analyzer-hook-configuration.ts
|
|
||||||
│ │ ├── image-analyzer-profile-hook-injector.ts
|
|
||||||
│ │ └── get-image-analysis-hook-env.ts
|
|
||||||
│ ├── image-analysis/ # ImageAnalysis MCP/runtime utilities (NEW v7.34)
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ ├── hook-installer.ts
|
|
||||||
│ │ ├── mcp-installer.ts
|
|
||||||
│ │ └── claude-tool-args.ts
|
|
||||||
│ └── [utility files...]
|
|
||||||
│
|
|
||||||
└── web-server/ # Express web server (heavily modularized)
|
|
||||||
├── index.ts # Server entry & barrel export
|
|
||||||
├── routes/ # 15+ route handlers
|
|
||||||
│ ├── index.ts
|
|
||||||
│ ├── accounts-route.ts
|
|
||||||
│ ├── auth-route.ts
|
|
||||||
│ ├── channels-routes.ts
|
|
||||||
│ ├── cliproxy-route.ts
|
|
||||||
│ ├── copilot-route.ts
|
|
||||||
│ ├── doctor-route.ts
|
|
||||||
│ ├── glmt-route.ts
|
|
||||||
│ ├── health-route.ts
|
|
||||||
│ ├── profiles-route.ts
|
|
||||||
│ └── [more routes...]
|
|
||||||
├── health/ # Health check system
|
|
||||||
│ └── index.ts
|
|
||||||
├── usage/ # Usage analytics module (default Claude, CCS instances, native Codex/Droid, CLIProxy snapshots)
|
|
||||||
│ ├── index.ts
|
|
||||||
│ ├── handlers.ts # Request handlers (633 lines)
|
|
||||||
│ ├── aggregator.ts # Data aggregation (538 lines)
|
|
||||||
│ ├── codex-native-usage-collector.ts # Native Codex rollout JSONL collector
|
|
||||||
│ ├── droid-native-usage-collector.ts # Native Droid SQLite collector
|
|
||||||
│ └── data-aggregator.ts
|
|
||||||
├── models-dev/ # Cached models.dev metadata/pricing registry integration
|
|
||||||
│ ├── registry-cache.ts
|
|
||||||
│ ├── pricing-resolver.ts
|
|
||||||
│ └── types.ts
|
|
||||||
├── services/ # Shared services
|
|
||||||
│ └── index.ts
|
|
||||||
└── model-pricing.ts # Static pricing fallback + models.dev resolver
|
|
||||||
```
|
|
||||||
|
|
||||||
### Module Categories
|
|
||||||
|
|
||||||
| Category | Directories | Purpose |
|
|
||||||
|----------|-------------|---------|
|
|
||||||
| Core | `commands/`, `errors/` | CLI commands, error handling |
|
|
||||||
| Targets | `bin/`, `targets/` | Multi-CLI adapter pattern (Claude Code, Factory Droid, Codex CLI, extensible) |
|
|
||||||
| Auth | `auth/`, `cliproxy/auth/` | Authentication across providers |
|
|
||||||
| Config | `config/`, `types/` | Configuration & type definitions |
|
|
||||||
| OpenAI Proxy | `proxy/` | Adaptive local OpenAI-compatible proxy runtime, profile routing, and SSE transforms |
|
|
||||||
| Providers | `cliproxy/`, `copilot/`, `glmt/` | Provider integrations plus retained legacy transformer internals |
|
|
||||||
| Quota | `cliproxy/quota-*.ts`, `account-manager.ts` | Hybrid quota management (v7.14) |
|
|
||||||
| Remote Proxy | `cliproxy/remote-*.ts`, `proxy-config-resolver.ts` | Remote CLIProxy support (v7.1) |
|
|
||||||
| Image Analysis | `utils/image-analysis/`, `utils/hooks/` | Vision model proxying (v7.34) |
|
|
||||||
| Services | `web-server/`, `api/` | HTTP server, API services |
|
|
||||||
| Utilities | `utils/`, `management/` | Helpers, diagnostics |
|
|
||||||
|
|
||||||
### Account Context Metadata Flow
|
|
||||||
|
|
||||||
- Source fields: `accounts.<name>.context_mode`, `accounts.<name>.context_group`, `accounts.<name>.continuity_mode` in `~/.ccs/config.yaml`.
|
|
||||||
- Runtime policy resolver: `src/auth/account-context.ts`.
|
|
||||||
- Metadata storage normalization: `src/auth/profile-registry.ts`.
|
|
||||||
- API write validation: `PUT /api/config` in `src/web-server/routes/config-routes.ts`.
|
|
||||||
- Rules:
|
|
||||||
- mode is isolation-first (`isolated` default, `shared` opt-in)
|
|
||||||
- shared mode requires non-empty valid `context_group`
|
|
||||||
- shared mode continuity depth is `standard` by default, optional `deeper`
|
|
||||||
- `context_group` is normalized (trim + lowercase + whitespace collapse to `-`)
|
|
||||||
- API route rejects `context_group`/`continuity_mode` when mode is not `shared`
|
|
||||||
- registry normalization drops malformed persisted `context_group` values
|
|
||||||
|
|
||||||
### Shared Plugin Layout
|
|
||||||
|
|
||||||
- Shared payload owner: `src/management/shared-manager.ts`.
|
|
||||||
- Profile entry point: `src/management/instance-manager.ts`.
|
|
||||||
- `plugins/marketplaces/`, `plugins/cache/`, and `installed_plugins.json` stay shared through the `~/.ccs/shared/` topology.
|
|
||||||
- `known_marketplaces.json` is now instance-local under `~/.ccs/instances/<profile>/plugins/` so Claude Code validates `installLocation` against the active `CLAUDE_CONFIG_DIR` instead of a last-writer-wins shared file.
|
|
||||||
|
|
||||||
### Official Claude Channels
|
|
||||||
|
|
||||||
- Runtime contract lives in `src/channels/official-channels-runtime.ts` and is consumed from `src/ccs.ts`, `src/commands/config-channels-command.ts`, and `src/web-server/routes/channels-routes.ts`.
|
|
||||||
- Canonical config lives under `channels.*` in `~/.ccs/config.yaml`; legacy `discord_channels.*` remains read-compatible only when canonical fields are absent.
|
|
||||||
|
|
||||||
### Native Codex Runtime Target
|
|
||||||
|
|
||||||
- Dedicated runtime entrypoints: `ccs-codex` and `ccsx` resolve through `src/bin/codex-runtime.ts`, while `ccsxp` resolves through `src/bin/ccsxp-runtime.ts`; all three set `CCS_INTERNAL_ENTRY_TARGET=codex` before delegating to `src/targets/target-resolver.ts`.
|
|
||||||
- Native Codex passthrough: `ccsx --help`, `ccsx --version`, and known upstream Codex subcommands such as `ccsx exec ...`, `ccsx apply ...`, `ccsx mcp ...`, `ccsx plugin ...`, `ccsx completion ...`, and `ccsx resume ...` short-circuit before CCS profile detection; upstream aliases such as `ccsx e ...` and `ccsx a ...` are included. CCS-owned `ccsx auth`, `ccsx doctor`, and `ccsx update` remain reserved for CCS.
|
|
||||||
- Provider shortcut behavior: `ccsxp` strips user-supplied `--target` overrides and prepends `--config model_provider="cliproxy"` so it behaves like native Codex plus the CLIProxy provider recipe. The stricter CCS-managed bridge remains available explicitly through `ccs codex --target codex`. It pins `CODEX_HOME` to native `~/.codex` by default so inherited launcher state does not send history/config writes to a nonstandard Codex root; `CCSXP_CODEX_HOME` is the explicit override. On launch, CCS repairs the native `[model_providers.cliproxy]` stanza in `config.toml`, preserves a valid custom `base_url`, reads that provider's configured `env_key` (default `CLIPROXY_API_KEY`), and injects the effective CLIProxy auth token into that key for the child Codex process.
|
|
||||||
- Implicit Codex launches such as `ccs --target codex` and `ccsxp` use native Codex default mode even when the CCS default profile is a Claude account. Explicit unsupported profiles such as `ccs work --target codex` still fail fast with native-vs-pool guidance.
|
|
||||||
- `argv[0]` alias mapping still exists in `src/targets/target-resolver.ts` for same-binary/custom alias scenarios, but the built-in npm bins above do not depend on that map at runtime.
|
|
||||||
- Metadata boundary: `src/targets/target-metadata.ts` keeps Codex runtime-only in v1, so persisted default targets remain `claude | droid`.
|
|
||||||
- Compatibility guardrails: `src/targets/target-runtime-compatibility.ts` centralizes which profile types can execute on Codex.
|
|
||||||
- Adapter behavior: `src/targets/codex-adapter.ts` and `src/targets/codex-detector.ts` launch native Codex without rewriting `~/.codex/config.toml`; CCS-backed routes use transient `codex -c key=value` overrides and env-key injection.
|
|
||||||
- Dashboard control center: `src/web-server/services/codex-dashboard-service.ts`, `src/web-server/routes/codex-routes.ts`, `ui/src/pages/codex.tsx`, and `ui/src/components/compatible-cli/codex-*.tsx` expose a split-view Codex dashboard with guided editors for top-level settings, trust, profiles, providers, MCP servers, and feature flags plus a raw TOML fallback.
|
|
||||||
- Structured-edit boundary: guided Codex saves intentionally reserialize the whole TOML document, so comments/formatting are normalized and the raw editor remains the fidelity-preserving escape hatch.
|
|
||||||
- Follow-up behavior: structured saves refresh the raw snapshot immediately, refresh discards stale raw drafts, structured controls stay disabled while raw TOML is dirty/invalid/unreadable, project trust paths must be absolute or `~/...`, unsupported upstream top-level shapes are preserved instead of deleted, and feature flags can be reset to default.
|
|
||||||
- Supported Codex flows in v1:
|
|
||||||
- `default`
|
|
||||||
- CLIProxy provider `codex`
|
|
||||||
- settings/API profiles only when they resolve to a Codex CLIProxy bridge
|
|
||||||
- Telegram and Discord bot tokens are intentionally written into Claude-managed machine state under `~/.claude/channels/<channel>/.env`, unless the official `*_STATE_DIR` environment override redirects that channel elsewhere.
|
|
||||||
- iMessage is tokenless, macOS-only, and still depends on Claude-side plugin install plus OS permissions.
|
|
||||||
- Auto-enable is gated on Bun availability, verified Claude Code v2.1.80+, verified `claude.ai` auth, native Claude `default/account` sessions, and per-channel setup readiness.
|
|
||||||
- The dashboard channels section surfaces Bun/version/auth/state-scope status from `/api/channels`, preserves token drafts when save-follow-up refresh fails, and keeps unsupported selected iMessage visible only so it can be turned off.
|
|
||||||
|
|
||||||
### Structured Logging Domain
|
|
||||||
|
|
||||||
- CCS-owned runtime logging now lives in `src/services/logging/`.
|
|
||||||
- The shared domain owns path resolution, redaction, rotation/pruning, buffered recent-entry reads, and the logger factory used by CLI/server/runtime code.
|
|
||||||
- Dashboard exposure lives in `src/web-server/routes/logs-routes.ts`, `src/web-server/services/logs-dashboard-service.ts`, and `src/web-server/middleware/request-logging-middleware.ts`.
|
|
||||||
- The native dashboard viewer lives at `ui/src/pages/logs.tsx` with supporting components under `ui/src/components/logs/` and hooks in `ui/src/hooks/use-logs.ts`.
|
|
||||||
- Legacy CLIProxy error files still exist under `~/.ccs/cliproxy/logs` and are surfaced as a labeled legacy source rather than the primary CCS logging model.
|
|
||||||
|
|
||||||
### 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:
|
|
||||||
- binary detection
|
|
||||||
- credential preparation
|
|
||||||
- target-specific args/env construction
|
|
||||||
- process execution
|
|
||||||
- profile compatibility checks
|
|
||||||
|
|
||||||
2. **Target Resolution** - Priority order:
|
|
||||||
- `--target <cli>` flag (CLI argument)
|
|
||||||
- Explicit runtime entrypoint via `CCS_INTERNAL_ENTRY_TARGET` (used by `src/bin/droid-runtime.ts`, `src/bin/codex-runtime.ts`, and `src/bin/ccsxp-runtime.ts`)
|
|
||||||
- `argv[0]` detection for custom/same-binary runtime aliases
|
|
||||||
- Per-profile `target` field (from config.yaml)
|
|
||||||
- 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/`)
|
|
||||||
|
|
||||||
The React dashboard organized by domain with barrel exports at every level.
|
|
||||||
|
|
||||||
### Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
ui/src/
|
|
||||||
├── components/
|
|
||||||
│ ├── index.ts # Main barrel (aggregates all domains)
|
|
||||||
│ │
|
|
||||||
│ ├── account/ # Account management
|
|
||||||
│ │ ├── index.ts # Barrel export
|
|
||||||
│ │ ├── accounts-table.tsx
|
|
||||||
│ │ ├── add-account-dialog.tsx
|
|
||||||
│ │ └── flow-viz/ # Flow visualization (split from 1,144-line file)
|
|
||||||
│ │ ├── index.tsx # Main component (200 lines)
|
|
||||||
│ │ ├── account-card.tsx
|
|
||||||
│ │ ├── account-card-stats.tsx
|
|
||||||
│ │ ├── connection-timeline.tsx
|
|
||||||
│ │ ├── flow-paths.tsx
|
|
||||||
│ │ ├── flow-viz-header.tsx
|
|
||||||
│ │ ├── provider-card.tsx
|
|
||||||
│ │ ├── hooks.ts
|
|
||||||
│ │ ├── types.ts
|
|
||||||
│ │ ├── utils.ts
|
|
||||||
│ │ ├── path-utils.ts
|
|
||||||
│ │ └── zone-utils.ts
|
|
||||||
│ │
|
|
||||||
│ ├── analytics/ # Usage charts, stats cards
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ ├── cliproxy-stats-card.tsx
|
|
||||||
│ │ └── usage-trend-chart.tsx
|
|
||||||
│ │
|
|
||||||
│ ├── cliproxy/ # CLIProxy configuration
|
|
||||||
│ │ ├── index.ts # Barrel export (30 lines)
|
|
||||||
│ │ ├── provider-editor/ # Split from 921-line file
|
|
||||||
│ │ │ ├── index.tsx # Main editor (250 lines)
|
|
||||||
│ │ │ └── [13 focused modules]
|
|
||||||
│ │ ├── config/ # YAML editor, file tree
|
|
||||||
│ │ │ ├── config-split-view.tsx
|
|
||||||
│ │ │ ├── diff-dialog.tsx
|
|
||||||
│ │ │ ├── file-tree.tsx
|
|
||||||
│ │ │ └── yaml-editor.tsx
|
|
||||||
│ │ ├── overview/ # Health lists, preferences
|
|
||||||
│ │ │ ├── credential-health-list.tsx
|
|
||||||
│ │ │ ├── model-preferences-grid.tsx
|
|
||||||
│ │ │ └── quick-stats-row.tsx
|
|
||||||
│ │ └── [7 top-level component files]
|
|
||||||
│ │
|
|
||||||
│ ├── copilot/ # Copilot settings
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ └── config-form/ # Split from 846-line file
|
|
||||||
│ │ └── [13 focused modules]
|
|
||||||
│ │
|
|
||||||
│ ├── health/ # System health gauges
|
|
||||||
│ │ └── index.ts
|
|
||||||
│ │
|
|
||||||
│ ├── layout/ # App structure
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ ├── sidebar.tsx
|
|
||||||
│ │ └── footer.tsx
|
|
||||||
│ │
|
|
||||||
│ ├── monitoring/ # Error logs, auth monitor
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ ├── proxy-status-widget.tsx
|
|
||||||
│ │ ├── auth-monitor/ # Split from 465-line file (8 files)
|
|
||||||
│ │ │ ├── index.tsx # Main component
|
|
||||||
│ │ │ ├── types.ts
|
|
||||||
│ │ │ ├── hooks.ts
|
|
||||||
│ │ │ ├── utils.ts
|
|
||||||
│ │ │ └── components/
|
|
||||||
│ │ │ ├── live-pulse.tsx
|
|
||||||
│ │ │ ├── inline-stats-badge.tsx
|
|
||||||
│ │ │ ├── provider-card.tsx
|
|
||||||
│ │ │ └── summary-card.tsx
|
|
||||||
│ │ └── error-logs/ # Split from 617-line file
|
|
||||||
│ │ └── [6 focused modules]
|
|
||||||
│ │
|
|
||||||
│ ├── profiles/ # Profile management
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ ├── profile-dialog.tsx
|
|
||||||
│ │ ├── profile-create-dialog.tsx
|
|
||||||
│ │ └── editor/ # Split from 531-line file
|
|
||||||
│ │ └── [10 focused modules]
|
|
||||||
│ │
|
|
||||||
│ ├── setup/ # Quick setup wizard
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ └── wizard/ # Step-based wizard
|
|
||||||
│ │ ├── index.tsx
|
|
||||||
│ │ └── steps/
|
|
||||||
│ │
|
|
||||||
│ ├── shared/ # Reusable components (19 components)
|
|
||||||
│ │ ├── index.ts
|
|
||||||
│ │ ├── ccs-logo.tsx
|
|
||||||
│ │ ├── code-editor.tsx
|
|
||||||
│ │ ├── confirm-dialog.tsx
|
|
||||||
│ │ ├── provider-icon.tsx
|
|
||||||
│ │ ├── settings-dialog.tsx
|
|
||||||
│ │ ├── stat-card.tsx
|
|
||||||
│ │ └── [13 more shared components]
|
|
||||||
│ │
|
|
||||||
│ └── ui/ # shadcn/ui primitives
|
|
||||||
│ ├── button.tsx
|
|
||||||
│ ├── card.tsx
|
|
||||||
│ ├── dialog.tsx
|
|
||||||
│ ├── searchable-select.tsx # Shared searchable combobox for model pickers
|
|
||||||
│ ├── sidebar.tsx # Custom sidebar (674 lines)
|
|
||||||
│ └── [UI primitives...]
|
|
||||||
│
|
|
||||||
├── contexts/ # React Contexts
|
|
||||||
│ ├── privacy-context.tsx
|
|
||||||
│ ├── theme-context.tsx
|
|
||||||
│ └── websocket-context.tsx
|
|
||||||
│
|
|
||||||
├── hooks/ # Custom hooks (domain-prefixed)
|
|
||||||
│ ├── use-accounts.ts
|
|
||||||
│ ├── use-cliproxy.ts
|
|
||||||
│ ├── use-health.ts
|
|
||||||
│ ├── use-profiles.ts
|
|
||||||
│ ├── use-websocket.ts
|
|
||||||
│ └── [more hooks...]
|
|
||||||
│
|
|
||||||
├── lib/ # Utilities
|
|
||||||
│ ├── api.ts # API client
|
|
||||||
│ ├── model-catalogs.ts # Model definitions
|
|
||||||
│ └── utils.ts # Helper functions
|
|
||||||
│
|
|
||||||
├── pages/ # Page components (lazy-loaded)
|
|
||||||
│ ├── analytics/ # Split from 420-line file (8 files)
|
|
||||||
│ │ ├── index.tsx # Main layout
|
|
||||||
│ │ ├── types.ts # Analytics types
|
|
||||||
│ │ ├── hooks.ts # Data fetching hooks
|
|
||||||
│ │ ├── utils.ts # Utility functions
|
|
||||||
│ │ └── components/
|
|
||||||
│ │ ├── analytics-header.tsx
|
|
||||||
│ │ ├── analytics-skeleton.tsx
|
|
||||||
│ │ ├── charts-grid.tsx
|
|
||||||
│ │ └── cost-by-model-card.tsx
|
|
||||||
│ ├── settings/ # Split from 1,781-line file (20 files)
|
|
||||||
│ │ ├── index.tsx # Main layout with lazy loading
|
|
||||||
│ │ ├── context.tsx # Settings provider wrapper
|
|
||||||
│ │ ├── settings-context.ts
|
|
||||||
│ │ ├── types.ts
|
|
||||||
│ │ ├── hooks.ts # Legacy re-exports
|
|
||||||
│ │ ├── hooks/
|
|
||||||
│ │ │ ├── index.ts
|
|
||||||
│ │ │ ├── context-hooks.ts
|
|
||||||
│ │ │ ├── use-official-channels-config.ts
|
|
||||||
│ │ │ ├── use-settings-tab.ts
|
|
||||||
│ │ │ ├── use-proxy-config.ts
|
|
||||||
│ │ │ ├── use-websearch-config.ts
|
|
||||||
│ │ │ ├── use-globalenv-config.ts
|
|
||||||
│ │ │ └── use-raw-config.ts
|
|
||||||
│ │ ├── components/
|
|
||||||
│ │ │ ├── section-skeleton.tsx
|
|
||||||
│ │ │ └── tab-navigation.tsx
|
|
||||||
│ │ └── sections/
|
|
||||||
│ │ ├── channels.tsx
|
|
||||||
│ │ ├── globalenv-section.tsx
|
|
||||||
│ │ ├── websearch/
|
|
||||||
│ │ │ ├── index.tsx
|
|
||||||
│ │ │ └── provider-card.tsx
|
|
||||||
│ │ └── proxy/
|
|
||||||
│ │ ├── index.tsx
|
|
||||||
│ │ ├── local-proxy-card.tsx
|
|
||||||
│ │ └── remote-proxy-card.tsx
|
|
||||||
│ ├── api.tsx # API profiles page (350 lines)
|
|
||||||
│ ├── cliproxy.tsx # CLIProxy page (405 lines)
|
|
||||||
│ ├── copilot.tsx # Copilot page (295 lines)
|
|
||||||
│ └── health.tsx # Health page (256 lines)
|
|
||||||
│
|
|
||||||
└── providers/ # Context providers
|
|
||||||
└── websocket-provider.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component Statistics
|
|
||||||
|
|
||||||
| Domain | Components | Subdirs | Split Files |
|
|
||||||
|--------|------------|---------|-------------|
|
|
||||||
| account | 3 | flow-viz (12 files) | 1 monster split |
|
|
||||||
| analytics | 3 | - | - |
|
|
||||||
| cliproxy | 10 | provider-editor, config, overview | 1 monster split |
|
|
||||||
| copilot | 2 | config-form (13 files) | 1 monster split |
|
|
||||||
| health | 2 | - | - |
|
|
||||||
| layout | 3 | - | - |
|
|
||||||
| monitoring | 3 | auth-monitor (8 files), error-logs (6 files) | 2 monster splits |
|
|
||||||
| profiles | 4 | editor (10 files) | 1 monster split |
|
|
||||||
| setup | 2 | wizard/steps | - |
|
|
||||||
| shared | 19 | - | - |
|
|
||||||
| **Total** | **51+** | **10 subdirs** | **7 splits** |
|
|
||||||
|
|
||||||
### Page Statistics
|
|
||||||
|
|
||||||
| Page | Structure | Files | Notes |
|
|
||||||
|------|-----------|-------|-------|
|
|
||||||
| analytics | Directory | 8 | Split 2025-12-21 |
|
|
||||||
| settings | Directory | 20 | Split 2025-12-21, lazy-loaded sections |
|
|
||||||
| api | Single file | 1 | 350 lines |
|
|
||||||
| cliproxy | Single file | 1 | 405 lines |
|
|
||||||
| copilot | Single file | 1 | 295 lines |
|
|
||||||
| health | Single file | 1 | 256 lines |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Key File Metrics
|
|
||||||
|
|
||||||
### Largest Files (Acceptable Exceptions)
|
|
||||||
|
|
||||||
**CLI (`src/`):**
|
|
||||||
|
|
||||||
| File | Lines | Status |
|
|
||||||
|------|-------|--------|
|
|
||||||
| model-pricing.ts | 920 | Static pricing fallback and resolver entrypoint |
|
|
||||||
| glmt-proxy.ts | 675 | Legacy internal compatibility path - acceptable for now |
|
|
||||||
| cliproxy-executor.ts | 666 | Core logic - acceptable |
|
|
||||||
| cliproxy-command.ts | 634 | Could split if needed |
|
|
||||||
| usage/handlers.ts | 633 | Could split if needed |
|
|
||||||
| ccs.ts | 596 | Entry point - acceptable |
|
|
||||||
| unified-config-loader.ts | 546 | Complex - acceptable |
|
|
||||||
|
|
||||||
**UI (`ui/src/`):**
|
|
||||||
|
|
||||||
| File | Lines | Status |
|
|
||||||
|------|-------|--------|
|
|
||||||
| components/ui/sidebar.tsx | 674 | shadcn - acceptable |
|
|
||||||
| pages/cliproxy.tsx | 405 | Acceptable |
|
|
||||||
| pages/api.tsx | 350 | Acceptable |
|
|
||||||
| pages/copilot.tsx | 295 | Acceptable |
|
|
||||||
| pages/health.tsx | 256 | Acceptable |
|
|
||||||
|
|
||||||
**Split Files (Completed):**
|
|
||||||
|
|
||||||
| Original | Lines | New Location | Files |
|
|
||||||
|----------|-------|--------------|-------|
|
|
||||||
| pages/settings.tsx | 1,781 | pages/settings/ | 20 |
|
|
||||||
| pages/analytics.tsx | 420 | pages/analytics/ | 8 |
|
|
||||||
| monitoring/auth-monitor.tsx | 465 | monitoring/auth-monitor/ | 8 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Import Patterns
|
|
||||||
|
|
||||||
### Standard Import Path
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// From any file in src/
|
|
||||||
import { Config, Settings } from '../types';
|
|
||||||
import { execClaudeWithCLIProxy } from '../cliproxy';
|
|
||||||
import { handleError } from '../errors';
|
|
||||||
|
|
||||||
// From any file in ui/src/
|
|
||||||
import { AccountsTable, ProviderIcon, StatCard } from '@/components';
|
|
||||||
import { useAccounts, useProfiles } from '@/hooks';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Barrel Export Pattern
|
|
||||||
|
|
||||||
Every domain directory has an `index.ts` that aggregates exports:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ui/src/components/cliproxy/index.ts
|
|
||||||
export { CategorizedModelSelector } from './categorized-model-selector';
|
|
||||||
export { CliproxyDialog } from './cliproxy-dialog';
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// From subdirectories
|
|
||||||
export { ProviderEditor } from './provider-editor';
|
|
||||||
export type { ProviderEditorProps } from './provider-editor';
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Test Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
tests/
|
|
||||||
├── unit/ # Unit tests (7 core test files)
|
|
||||||
│ ├── data-aggregator.test.ts
|
|
||||||
│ ├── cliproxy/
|
|
||||||
│ │ └── remote-proxy-client.test.ts
|
|
||||||
│ ├── commands/
|
|
||||||
│ │ └── env-command.test.ts
|
|
||||||
│ ├── jsonl-parser.test.ts
|
|
||||||
│ ├── model-pricing.test.ts
|
|
||||||
│ ├── unified-config.test.ts
|
|
||||||
│ └── mcp-manager.test.ts
|
|
||||||
├── integration/ # Integration tests
|
|
||||||
├── native/ # Native install tests
|
|
||||||
│ ├── linux/
|
|
||||||
│ ├── macos/
|
|
||||||
│ └── windows/
|
|
||||||
├── npm/ # npm package tests
|
|
||||||
├── shared/ # Shared test utilities
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Metrics
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| Total Tests | 1440 |
|
|
||||||
| Passing | 1440 |
|
|
||||||
| Skipped | 6 |
|
|
||||||
| Failed | 0 |
|
|
||||||
| Coverage Threshold | 90% |
|
|
||||||
| Test Files | 41 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Build Outputs
|
|
||||||
|
|
||||||
| Output | Source | Purpose |
|
|
||||||
|--------|--------|---------|
|
|
||||||
| `dist/` | `src/` | npm package (CLI) |
|
|
||||||
| `dist/ui/` | `ui/src/` | Built React app (served by Express) |
|
|
||||||
| `lib/` | N/A | Native shell scripts |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Documentation
|
|
||||||
|
|
||||||
- [Code Standards](./code-standards.md) - Modularization patterns, file size rules
|
|
||||||
- [System Architecture](./system-architecture/index.md) - High-level architecture diagrams
|
|
||||||
- [Project Roadmap](./project-roadmap.md) - Modularization phases and future work
|
|
||||||
- [WebSearch](./websearch.md) - WebSearch feature documentation
|
|
||||||
- [Image Analysis](./image-analysis.md) - First-class ImageAnalysis runtime documentation
|
|
||||||
- [CLAUDE.md](../CLAUDE.md) - AI-facing development guidance
|
|
||||||
Reference in new issue
Block a user