Merge pull request #178 from kaitranntt/dev

feat(cliproxy): migrate to CLIProxyAPIPlus with Kiro and GitHub Copilot OAuth
This commit is contained in:
Kai (Tam Nhu) Tran
2025-12-22 01:25:06 -05:00
committed by GitHub
55 changed files with 425 additions and 143 deletions
+8 -3
View File
@@ -89,7 +89,8 @@ The dashboard provides visual management for all account types:
| **Claude** | Subscription | `ccs` | Default, strategic planning |
| **Gemini** | OAuth | `ccs gemini` | Zero-config, fast iteration |
| **Codex** | OAuth | `ccs codex` | Code generation |
| **Copilot** | OAuth | `ccs copilot` | GitHub Copilot models |
| **Copilot** | OAuth | `ccs copilot` or `ccs ghcp` | GitHub Copilot models |
| **Kiro** | OAuth | `ccs kiro` | AWS CodeWhisperer (Claude-powered) |
| **Antigravity** | OAuth | `ccs agy` | Alternative routing |
| **OpenRouter** | API Key | `ccs openrouter` | 300+ models, unified API |
| **GLM** | API Key | `ccs glm` | Cost-optimized execution |
@@ -102,7 +103,8 @@ The dashboard provides visual management for all account types:
> **OAuth providers** authenticate via browser on first run. Tokens are cached in `~/.ccs/cliproxy/auth/`.
**Powered by:**
- [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) - OAuth proxy for Gemini, Codex, Antigravity
- [CLIProxyAPIPlus](https://github.com/router-for-me/CLIProxyAPIPlus) - Extended OAuth proxy with Kiro ([@fuko2935](https://github.com/fuko2935), [@Ravens2121](https://github.com/Ravens2121)) and Copilot ([@em4go](https://github.com/em4go)) support
- [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) - Core OAuth proxy for Gemini, Codex, Antigravity
- [copilot-api](https://github.com/ericc-ch/copilot-api) - GitHub Copilot API integration
> [!TIP]
@@ -116,8 +118,11 @@ The dashboard provides visual management for all account types:
```bash
ccs # Default Claude session
ccs agy # Antigravity (OAuth)
ccs gemini # Gemini (OAuth)
ccs codex # OpenAI Codex (OAuth)
ccs kiro # Kiro/AWS CodeWhisperer (OAuth)
ccs ghcp # GitHub Copilot (OAuth device flow)
ccs agy # Antigravity (OAuth)
ccs glm # GLM (API key)
```
+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317/api/provider/copilot",
"ANTHROPIC_AUTH_TOKEN": "ccs-internal-managed",
"ANTHROPIC_MODEL": "gpt-4o",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gpt-4o",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-4o",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-4o-mini"
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317/api/provider/kiro",
"ANTHROPIC_AUTH_TOKEN": "ccs-internal-managed",
"ANTHROPIC_MODEL": "kiro",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "kiro",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "kiro",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "kiro"
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
# CCS Codebase Summary
Last Updated: 2025-12-21
Last Updated: 2025-12-22
Comprehensive overview of the modularized CCS codebase structure following the Phase 9 modularization effort (Settings, Analytics, Auth Monitor splits + Test Infrastructure) and v7.1 Remote CLIProxy feature.
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, and v7.2 Kiro + GitHub Copilot (ghcp) OAuth providers.
## Repository Structure
@@ -163,7 +163,7 @@ src/
| Core | `commands/`, `errors/` | CLI commands, error handling |
| Auth | `auth/`, `cliproxy/auth/` | Authentication across providers |
| Config | `config/`, `types/` | Configuration & type definitions |
| Providers | `cliproxy/`, `copilot/`, `glmt/` | Provider integrations |
| Providers | `cliproxy/`, `copilot/`, `glmt/` | Provider integrations (7 CLIProxy providers: gemini, codex, agy, qwen, iflow, kiro, ghcp) |
| Remote Proxy | `cliproxy/remote-*.ts`, `proxy-config-resolver.ts` | Remote CLIProxy support (v7.1) |
| Services | `web-server/`, `api/` | HTTP server, API services |
| Utilities | `utils/`, `management/` | Helpers, diagnostics |
+13 -6
View File
@@ -1,6 +1,6 @@
# CCS Product Development Requirements (PDR)
Last Updated: 2025-12-21
Last Updated: 2025-12-22
## Product Overview
@@ -10,7 +10,7 @@ Last Updated: 2025-12-21
**Description**: CLI wrapper enabling seamless switching between multiple Claude accounts and alternative AI providers (GLM, Gemini, Codex, OpenRouter) with a React-based dashboard for configuration management. Supports both local and remote CLIProxyAPI instances.
**Current Version**: v7.1.x (Remote CLIProxy routing, OpenRouter integration)
**Current Version**: v7.2.x (Kiro + GitHub Copilot OAuth providers)
---
@@ -31,7 +31,7 @@ Developers using Claude Code face these challenges:
CCS provides:
1. **Multi-Account Claude**: Isolated instances via `CLAUDE_CONFIG_DIR`
2. **OAuth Providers**: Zero-config Gemini, Codex, Antigravity, Copilot integration
2. **OAuth Providers**: Zero-config Gemini, Codex, Antigravity, Copilot, Kiro (ghcp) integration
3. **API Profiles**: GLM, Kimi, OpenRouter, any Anthropic-compatible API
4. **Visual Dashboard**: React SPA for configuration management
5. **Automatic WebSearch**: MCP fallback for third-party providers
@@ -64,8 +64,8 @@ CCS provides:
- Share commands, skills, agents across accounts
### FR-003: OAuth Provider Integration
- Support Gemini, Codex, Antigravity, Copilot OAuth flows
- Browser-based authentication
- Support Gemini, Codex, Antigravity, Copilot, Kiro (ghcp) OAuth flows
- Browser-based authentication (Authorization Code flow for most, Device Code for ghcp)
- Token caching and refresh
### FR-004: API Profile Management
@@ -210,11 +210,16 @@ CCS provides:
- [x] Fallback to local when remote unreachable
- [x] Protocol-based default ports (HTTPS:443, HTTP:8317)
### v7.2 Release (Complete)
- [x] Kiro (AWS) OAuth provider support via CLIProxyAPIPlus
- [x] GitHub Copilot (ghcp) OAuth provider via Device Code flow
- [x] Authorization Code flow for Kiro (port 9876)
- [x] Device Code flow for ghcp (no local port needed)
### v8.0 Release (Planned - Q1 2026)
- [ ] Multiple CLIProxyAPI instances (load balancing, failover)
- [ ] Native git worktree support
- [ ] Critical bug fixes (#158, #155, #124)
- [ ] Kiro auth support (#157)
### v9.0 Release (Future - Q2 2026)
- [ ] Team collaboration features
@@ -230,6 +235,8 @@ CCS provides:
- Anthropic Claude API
- Google Gemini API
- GitHub Codex/Copilot API
- GitHub Copilot (ghcp - Device Code OAuth)
- AWS Kiro (Authorization Code OAuth)
- Z.AI GLM API
- OpenRouter API
+6 -3
View File
@@ -1,6 +1,6 @@
# CCS Project Roadmap
Last Updated: 2025-12-21
Last Updated: 2025-12-22
Forward-looking roadmap documenting current priorities, GitHub issues, and future feature plans.
@@ -22,6 +22,7 @@ All major modularization work is complete. The codebase evolved from monolithic
| 8 | Auth Monitor | `monitoring/auth-monitor/` (465->8 files) |
| 9 | Test Infrastructure | 99 UI tests + 539 CLI tests, 90% coverage |
| 10 | Remote CLIProxy | `proxy-config-resolver.ts`, `remote-proxy-client.ts` |
| 11 | Kiro + ghcp Providers | OAuth support via CLIProxyAPIPlus (v7.2) |
**Metrics Achieved**:
- Files >500 lines: 12 -> 5 (-58%)
@@ -62,7 +63,7 @@ All major modularization work is complete. The codebase evolved from monolithic
| Issue | Title | Type | Status |
|-------|-------|------|--------|
| #142 | Configure with available CLIProxyAPI | enhancement | **COMPLETE** (v7.1) |
| #157 | Support for Kiro auth from CLIProxyAPIPlus | enhancement | - |
| #157 | Support for Kiro auth from CLIProxyAPIPlus | enhancement | **COMPLETE** (v7.2) |
| #123 | Add More Models | enhancement | Ongoing |
| #114 | OpenCode Zen Free model + Auto Rotation API Key | enhancement | - |
@@ -150,7 +151,8 @@ worktrees:
### Priority 5: Authentication Enhancements
- **#158**: Fix AGY OAuth flow
- **#157**: Add Kiro auth support from CLIProxyAPIPlus
- **#157**: ~~Add Kiro auth support from CLIProxyAPIPlus~~ **COMPLETE** (v7.2)
- GitHub Copilot (ghcp) Device Code flow **COMPLETE** (v7.2)
---
@@ -160,6 +162,7 @@ worktrees:
|-----------|--------|--------|
| Modularization (Phases 1-9) | COMPLETE | - |
| Remote CLIProxy Support (#142) | COMPLETE | v7.1 |
| Kiro + GitHub Copilot OAuth (#157) | COMPLETE | v7.2 |
| Critical Bug Fixes (#158, #155, #124) | PLANNED | Q1 2026 |
| Multiple CLIProxyAPI Instances | PLANNED | Q1 2026 |
| Git Worktree Support | PLANNED | Q1 2026 |
+49 -11
View File
@@ -1,6 +1,6 @@
# CCS System Architecture
Last Updated: 2025-12-21
Last Updated: 2025-12-22
High-level architecture documentation for the CCS (Claude Code Switch) system.
@@ -8,12 +8,12 @@ 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). It consists of two main components:
CCS is a CLI wrapper that enables seamless switching between multiple Claude accounts and alternative AI providers (GLM, Gemini, Codex, Kiro, GitHub Copilot). 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.1 adds support for both **local** and **remote** CLIProxyAPI instances.
CCS v7.2 adds Kiro (AWS) and GitHub Copilot (ghcp) OAuth providers via CLIProxyAPIPlus.
```
+===========================================================================+
@@ -84,8 +84,9 @@ CCS v7.1 adds support for both **local** and **remote** CLIProxyAPI instances.
Profile Resolution
|
v
1. CLIProxy Hardcoded ----+---> gemini, codex, agy
1. CLIProxy Hardcoded ----+---> gemini, codex, agy, kiro, ghcp
(OAuth-based) | Zero-config OAuth providers
| (kiro: Auth Code, ghcp: Device Code)
|
2. CLIProxy Variants -----+---> config.cliproxy section
(User-defined) | Custom provider configurations
@@ -296,11 +297,21 @@ CCS v7.1 adds support for both **local** and **remote** CLIProxyAPI instances.
| ANTHROPIC_BASE_URL = localhost:XXXX
v
+------------------+
| CLIProxyAPI | Local proxy binary
| CLIProxyAPI | Local proxy binary (CLIProxyAPIPlus for kiro/ghcp)
| (binary) |
+------------------+
|
+---> OAuth Authentication (Gemini, Codex, AGY)
+---> 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
| +------------------+
@@ -317,6 +328,8 @@ CCS v7.1 adds support for both **local** and **remote** CLIProxyAPI instances.
+---> Google (Gemini)
+---> GitHub (Codex)
+---> Antigravity
+---> AWS Kiro (Claude-powered)
+---> GitHub Copilot (ghcp)
+---> OpenAI-compatible endpoints
```
@@ -497,10 +510,10 @@ CCS v7.1 adds support for both **local** and **remote** CLIProxyAPI instances.
| Authentication Flow |
+===========================================================================+
OAuth Providers (Gemini, Codex, AGY)
-----------------------------------
OAuth Providers - Authorization Code Flow (Gemini, Codex, AGY, Kiro)
--------------------------------------------------------------------
1. User runs: ccs gemini
1. User runs: ccs gemini (or ccs kiro)
|
v
2. Check token cache (~/.ccs/cliproxy/auth/)
@@ -510,8 +523,8 @@ CCS v7.1 adds support for both **local** and **remote** CLIProxyAPI instances.
+---> [No/Expired token]
|
v
3. Open browser for OAuth
|
3. Open browser for OAuth (localhost:PORT callback)
| - Kiro uses port 9876
v
4. Callback with auth code
|
@@ -522,6 +535,31 @@ CCS v7.1 adds support for both **local** and **remote** CLIProxyAPI instances.
6. Cache token locally
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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
"version": "7.2.0",
"version": "7.2.0-dev.1",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",
+9 -1
View File
@@ -20,7 +20,15 @@ import { loadUnifiedConfig, isUnifiedMode } from '../config/unified-config-loade
export type ProfileType = 'settings' | 'account' | 'cliproxy' | 'copilot' | 'default';
/** CLIProxy profile names (OAuth-based, zero config) */
export const CLIPROXY_PROFILES = ['gemini', 'codex', 'agy', 'qwen'] as const;
export const CLIPROXY_PROFILES = [
'gemini',
'codex',
'agy',
'qwen',
'iflow',
'kiro',
'ghcp',
] as const;
export type CLIProxyProfileName = (typeof CLIPROXY_PROFILES)[number];
export interface ProfileDetectionResult {
+1 -1
View File
@@ -495,7 +495,7 @@ export function discoverExistingAccounts(): void {
* Get summary of all accounts across providers
*/
export function getAllAccountsSummary(): Record<CLIProxyProvider, AccountInfo[]> {
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow'];
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'];
const summary: Record<CLIProxyProvider, AccountInfo[]> = {} as Record<
CLIProxyProvider,
AccountInfo[]
+22
View File
@@ -16,12 +16,16 @@ import { AccountInfo } from '../account-manager';
* - Codex: Authorization Code Flow with local callback server on port 1455
* - Agy: Authorization Code Flow with local callback server on port 51121
* - Qwen: Device Code Flow (polling-based, NO callback port needed)
* - Kiro: Authorization Code Flow with local callback server on port 9876
* - GHCP: Device Code Flow (polling-based, NO callback port needed)
*/
export const OAUTH_CALLBACK_PORTS: Partial<Record<CLIProxyProvider, number>> = {
gemini: 8085,
kiro: 9876,
// codex uses 1455
// agy uses 51121
// qwen uses Device Code Flow - no callback port needed
// ghcp uses Device Code Flow - no callback port needed
};
/**
@@ -100,6 +104,20 @@ export const OAUTH_CONFIGS: Record<CLIProxyProvider, ProviderOAuthConfig> = {
scopes: ['phone', 'profile', 'email'],
authFlag: '--iflow-login',
},
kiro: {
provider: 'kiro',
displayName: 'Kiro (AWS)',
authUrl: 'https://oidc.us-east-1.amazonaws.com',
scopes: ['codewhisperer:completions', 'codewhisperer:conversations'],
authFlag: '--kiro-login',
},
ghcp: {
provider: 'ghcp',
displayName: 'GitHub Copilot (OAuth)',
authUrl: 'https://github.com/login/device/code',
scopes: ['copilot'],
authFlag: '--github-copilot-login',
},
};
/**
@@ -113,6 +131,8 @@ export const PROVIDER_AUTH_PREFIXES: Record<CLIProxyProvider, string[]> = {
agy: ['antigravity-', 'agy-'],
qwen: ['qwen-'],
iflow: ['iflow-'],
kiro: ['kiro-', 'aws-', 'codewhisperer-'],
ghcp: ['github-copilot-', 'copilot-', 'gh-'],
};
/**
@@ -125,6 +145,8 @@ export const PROVIDER_TYPE_VALUES: Record<CLIProxyProvider, string[]> = {
agy: ['antigravity'],
qwen: ['qwen'],
iflow: ['iflow'],
kiro: ['kiro', 'codewhisperer'],
ghcp: ['github-copilot', 'copilot'],
};
/**
+25 -16
View File
@@ -1,12 +1,13 @@
/**
* OAuth Handler for CLIProxyAPI
*
* Manages OAuth authentication flow for CLIProxy providers (Gemini, Codex, Antigravity).
* Manages OAuth authentication flow for CLIProxy providers (Gemini, Codex, Antigravity, Kiro, Copilot).
* CLIProxyAPI handles OAuth internally - we just need to:
* 1. Check if auth exists (token files in CCS auth directory)
* 2. Trigger OAuth flow by spawning binary with auth flag
* 3. Auto-detect headless environments (SSH, no DISPLAY)
* 4. Use --no-browser flag for headless, display OAuth URL for manual auth
* 5. Handle Device Code flows for Copilot/Qwen (no callback server)
*/
import * as fs from 'fs';
@@ -118,6 +119,7 @@ async function prepareBinary(
* Trigger OAuth flow for provider
* Auto-detects headless environment and uses --no-browser flag accordingly
* Shows real-time step-by-step progress for better user feedback
* Handles both Authorization Code (callback server) and Device Code (polling) flows
*/
export async function triggerOAuth(
provider: CLIProxyProvider,
@@ -128,6 +130,7 @@ export async function triggerOAuth(
const callbackPort = OAUTH_PORTS[provider];
const isCLI = !fromUI;
const headless = options.headless ?? isHeadlessEnvironment();
const isDeviceCodeFlow = callbackPort === null;
// Check for existing accounts
const existingAccounts = getProviderAccounts(provider);
@@ -145,8 +148,8 @@ export async function triggerOAuth(
}
}
// Pre-flight checks
if (!(await runPreflightChecks(provider, oauthConfig))) {
// Pre-flight checks (skip for device code flows which don't need callback ports)
if (!isDeviceCodeFlow && !(await runPreflightChecks(provider, oauthConfig))) {
return null;
}
@@ -158,7 +161,7 @@ export async function triggerOAuth(
const { binaryPath, tokenDir, configPath } = prepared;
// Free callback port if needed
// Free callback port if needed (only for authorization code flows)
const localCallbackPort = OAUTH_CALLBACK_PORTS[provider];
if (localCallbackPort) {
const killed = killProcessOnPort(localCallbackPort, verbose);
@@ -173,19 +176,25 @@ export async function triggerOAuth(
args.push('--no-browser');
}
// Show callback server step
showStep(2, 4, 'progress', `Starting callback server on port ${callbackPort || 'N/A'}...`);
// Show step based on flow type
if (isDeviceCodeFlow) {
showStep(2, 4, 'progress', `Starting ${oauthConfig.displayName} Device Code flow...`);
console.log('');
console.log(info('Device Code Flow - follow the instructions below'));
} else {
showStep(2, 4, 'progress', `Starting callback server on port ${callbackPort}...`);
// Show headless instructions
if (headless) {
console.log('');
console.log(warn('PORT FORWARDING REQUIRED'));
console.log(` OAuth callback uses localhost:${callbackPort} which must be reachable.`);
console.log(' Run this on your LOCAL machine:');
console.log(
` ${color(`ssh -L ${callbackPort}:localhost:${callbackPort} <USER>@<HOST>`, 'command')}`
);
console.log('');
// Show headless instructions (only for authorization code flows)
if (headless) {
console.log('');
console.log(warn('PORT FORWARDING REQUIRED'));
console.log(` OAuth callback uses localhost:${callbackPort} which must be reachable.`);
console.log(' Run this on your LOCAL machine:');
console.log(
` ${color(`ssh -L ${callbackPort}:localhost:${callbackPort} <USER>@<HOST>`, 'command')}`
);
console.log('');
}
}
// Execute OAuth process
+1 -1
View File
@@ -174,7 +174,7 @@ function handleTokenNotFound(provider: CLIProxyProvider, callbackPort: number |
/** Handle process exit with error */
function handleProcessError(code: number | null, state: ProcessState, headless: boolean): void {
console.log('');
console.log(fail(`CLIProxyAPI auth exited with code ${code}`));
console.log(fail(`CLIProxy Plus auth exited with code ${code}`));
if (state.stderrData && !state.urlDisplayed) {
console.log(` ${state.stderrData.trim().split('\n')[0]}`);
}
+1 -1
View File
@@ -145,7 +145,7 @@ export function getAuthStatus(provider: CLIProxyProvider): AuthStatus {
* Get auth status for all providers
*/
export function getAllAuthStatus(): AuthStatus[] {
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow'];
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'];
return providers.map(getAuthStatus);
}
+3 -3
View File
@@ -25,10 +25,10 @@ import {
ensureBinary,
} from './binary';
/** Default configuration */
/** Default configuration (uses CLIProxyAPIPlus fork with Kiro + Copilot support) */
const DEFAULT_CONFIG: BinaryManagerConfig = {
version: CLIPROXY_FALLBACK_VERSION,
releaseUrl: 'https://github.com/router-for-me/CLIProxyAPI/releases/download',
releaseUrl: 'https://github.com/router-for-me/CLIProxyAPIPlus/releases/download',
binPath: getBinDir(),
maxRetries: 3,
verbose: false,
@@ -110,7 +110,7 @@ export async function installCliproxyVersion(version: string, verbose = false):
const manager = new BinaryManager({ version, verbose, forceVersion: true });
if (manager.isBinaryInstalled()) {
if (verbose)
console.log(info(`Removing existing CLIProxyAPI v${getInstalledCliproxyVersion()}`));
console.log(info(`Removing existing CLIProxy Plus v${getInstalledCliproxyVersion()}`));
manager.deleteBinary();
}
await manager.ensureBinary();
+1 -1
View File
@@ -180,7 +180,7 @@ export function fetchJson(url: string, verbose = false): Promise<Record<string,
return new Promise((resolve, reject) => {
const options = {
headers: {
'User-Agent': 'CCS-CLIProxyAPI-Updater/1.0',
'User-Agent': 'CCS-CLIProxyPlus-Updater/1.0',
Accept: 'application/vnd.github.v3+json',
},
};
+3 -3
View File
@@ -32,7 +32,7 @@ export async function downloadAndInstall(
fs.mkdirSync(config.binPath, { recursive: true });
const archivePath = path.join(config.binPath, `cliproxy-archive.${platform.extension}`);
const spinner = new ProgressIndicator(`Downloading CLIProxyAPI v${config.version}`);
const spinner = new ProgressIndicator(`Downloading CLIProxy Plus v${config.version}`);
spinner.start();
try {
@@ -64,7 +64,7 @@ export async function downloadAndInstall(
spinner.update('Extracting binary');
await extractArchive(archivePath, config.binPath, platform.extension, verbose);
spinner.succeed('CLIProxyAPI ready');
spinner.succeed('CLIProxy Plus ready');
fs.unlinkSync(archivePath);
const binaryPath = path.join(config.binPath, getExecutableName());
@@ -74,7 +74,7 @@ export async function downloadAndInstall(
}
writeInstalledVersion(config.binPath, config.version);
console.log(ok(`CLIProxyAPI v${config.version} installed successfully`));
console.log(ok(`CLIProxy Plus v${config.version} installed successfully`));
} catch (error) {
spinner.fail('Installation failed');
throw error;
+3 -3
View File
@@ -22,15 +22,15 @@ async function handleAutoUpdate(config: BinaryManagerConfig, verbose: boolean):
if (!updateResult.hasUpdate) return;
const proxyRunning = await isCliproxyRunning(CLIPROXY_DEFAULT_PORT);
const updateMsg = `CLIProxyAPI update available: v${updateResult.currentVersion} -> v${updateResult.latestVersion}`;
const updateMsg = `CLIProxy Plus update available: v${updateResult.currentVersion} -> v${updateResult.latestVersion}`;
if (proxyRunning) {
console.log(info(updateMsg));
console.log(info('Run "ccs cliproxy stop" then restart to apply update'));
log('Skipping update: CLIProxyAPI is currently running', verbose);
log('Skipping update: CLIProxy Plus is currently running', verbose);
} else {
console.log(info(updateMsg));
console.log(info('Updating CLIProxyAPI...'));
console.log(info('Updating CLIProxy Plus...'));
deleteBinary(config.binPath, verbose);
config.version = updateResult.latestVersion;
await downloadAndInstall(config, verbose);
+2 -2
View File
@@ -24,6 +24,6 @@ export const VERSION_CACHE_DURATION_MS = 60 * 60 * 1000;
/** Version pin file name - stores user's explicit version choice */
export const VERSION_PIN_FILE = '.version-pin';
/** GitHub API URL for latest release */
/** GitHub API URL for latest release (CLIProxyAPIPlus fork with Kiro + Copilot support) */
export const GITHUB_API_LATEST_RELEASE =
'https://api.github.com/repos/router-for-me/CLIProxyAPI/releases/latest';
'https://api.github.com/repos/router-for-me/CLIProxyAPIPlus/releases/latest';
+7 -2
View File
@@ -9,10 +9,15 @@ import { UpdateCheckResult, GITHUB_API_LATEST_RELEASE } from './types';
/**
* Compare semver versions (true if latest > current)
* Handles CLIProxyAPIPlus versioning: strips -0 suffix before comparison
*/
export function isNewerVersion(latest: string, current: string): boolean {
const latestParts = latest.split('.').map((p) => parseInt(p, 10) || 0);
const currentParts = current.split('.').map((p) => parseInt(p, 10) || 0);
// Strip -0 suffix from CLIProxyAPIPlus versions (e.g., "6.6.40-0" -> "6.6.40")
const cleanLatest = latest.replace(/-\d+$/, '');
const cleanCurrent = current.replace(/-\d+$/, '');
const latestParts = cleanLatest.split('.').map((p) => parseInt(p, 10) || 0);
const currentParts = cleanCurrent.split('.').map((p) => parseInt(p, 10) || 0);
// Pad arrays to same length
while (latestParts.length < 3) latestParts.push(0);
+2 -2
View File
@@ -505,12 +505,12 @@ export async function execClaudeWithCLIProxy(
await waitForProxyReady(cfg.port, cfg.timeout, cfg.pollInterval);
readySpinner.succeed(`CLIProxy ready on port ${cfg.port}`);
} catch (error) {
readySpinner.fail('CLIProxy startup failed');
readySpinner.fail('CLIProxy Plus startup failed');
proxy.kill('SIGTERM');
const err = error as Error;
console.error('');
console.error(fail('CLIProxy failed to start'));
console.error(fail('CLIProxy Plus failed to start'));
console.error('');
console.error('Possible causes:');
console.error(` 1. Port ${cfg.port} already in use`);
+4 -1
View File
@@ -44,8 +44,9 @@ export function getCliproxyWritablePath(): string {
* v1: Initial config (port, auth-dir, api-keys only)
* v2: Full-featured config with dashboard, quota mgmt, simplified key
* v3: Logging disabled by default (user opt-in via ~/.ccs/config.yaml)
* v4: Added Kiro (AWS) and GitHub Copilot providers
*/
export const CLIPROXY_CONFIG_VERSION = 3;
export const CLIPROXY_CONFIG_VERSION = 4;
/** Provider display names (static metadata) */
const PROVIDER_DISPLAY_NAMES: Record<CLIProxyProvider, string> = {
@@ -54,6 +55,8 @@ const PROVIDER_DISPLAY_NAMES: Record<CLIProxyProvider, string> = {
agy: 'Antigravity',
qwen: 'Qwen Code',
iflow: 'iFlow',
kiro: 'Kiro (AWS)',
ghcp: 'GitHub Copilot (OAuth)',
};
/**
+9 -8
View File
@@ -8,10 +8,11 @@
import { PlatformInfo, SupportedOS, SupportedArch, ArchiveExtension } from './types';
/**
* CLIProxyAPI fallback version (used when GitHub API unavailable)
* CLIProxyAPIPlus fallback version (used when GitHub API unavailable)
* Auto-update fetches latest from GitHub; this is only a safety net
* Note: CLIProxyAPIPlus uses v6.6.X-0 suffix pattern
*/
export const CLIPROXY_FALLBACK_VERSION = '6.5.53';
export const CLIPROXY_FALLBACK_VERSION = '6.6.40-0';
/** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
@@ -56,7 +57,7 @@ export function detectPlatform(version: string = CLIPROXY_FALLBACK_VERSION): Pla
}
const extension: ArchiveExtension = os === 'windows' ? 'zip' : 'tar.gz';
const binaryName = `CLIProxyAPI_${version}_${os}_${arch}.${extension}`;
const binaryName = `CLIProxyAPIPlus_${version}_${os}_${arch}.${extension}`;
return {
os,
@@ -69,11 +70,11 @@ export function detectPlatform(version: string = CLIPROXY_FALLBACK_VERSION): Pla
/**
* Get executable name based on platform
* @returns Binary executable name (with .exe on Windows)
* Note: The actual binary inside the archive is named 'cli-proxy-api'
* Note: The actual binary inside the archive is named 'cli-proxy-api-plus'
*/
export function getExecutableName(): string {
const platform = detectPlatform();
return platform.os === 'windows' ? 'cli-proxy-api.exe' : 'cli-proxy-api';
return platform.os === 'windows' ? 'cli-proxy-api-plus.exe' : 'cli-proxy-api-plus';
}
/**
@@ -82,7 +83,7 @@ export function getExecutableName(): string {
*/
export function getArchiveBinaryName(): string {
const platform = detectPlatform();
return platform.os === 'windows' ? 'cli-proxy-api.exe' : 'cli-proxy-api';
return platform.os === 'windows' ? 'cli-proxy-api-plus.exe' : 'cli-proxy-api-plus';
}
/**
@@ -92,7 +93,7 @@ export function getArchiveBinaryName(): string {
*/
export function getDownloadUrl(version: string = CLIPROXY_FALLBACK_VERSION): string {
const platform = detectPlatform(version);
const baseUrl = `https://github.com/router-for-me/CLIProxyAPI/releases/download/v${version}`;
const baseUrl = `https://github.com/router-for-me/CLIProxyAPIPlus/releases/download/v${version}`;
return `${baseUrl}/${platform.binaryName}`;
}
@@ -102,7 +103,7 @@ export function getDownloadUrl(version: string = CLIPROXY_FALLBACK_VERSION): str
* @returns Full URL to checksums.txt
*/
export function getChecksumsUrl(version: string = CLIPROXY_FALLBACK_VERSION): string {
return `https://github.com/router-for-me/CLIProxyAPI/releases/download/v${version}/checksums.txt`;
return `https://github.com/router-for-me/CLIProxyAPIPlus/releases/download/v${version}/checksums.txt`;
}
/**
+1 -1
View File
@@ -242,7 +242,7 @@ export async function ensureCliproxyService(
started: false,
alreadyRunning: false,
port,
error: `CLIProxy failed to start within 5s on port ${port}`,
error: `CLIProxy Plus failed to start within 5s on port ${port}`,
};
}
@@ -80,7 +80,7 @@ export function saveVariantUnified(
if (!config.cliproxy) {
config.cliproxy = {
oauth_accounts: {},
providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow'],
providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'],
variants: {},
};
}
+3 -1
View File
@@ -114,8 +114,10 @@ export interface DownloadResult {
* - agy: Antigravity via OAuth (short name for easy usage)
* - qwen: Qwen Code via OAuth (qwen3-coder)
* - iflow: iFlow via OAuth
* - kiro: Kiro (AWS CodeWhisperer) via OAuth
* - ghcp: GitHub Copilot via Device Code (OAuth through CLIProxyAPIPlus)
*/
export type CLIProxyProvider = 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow';
export type CLIProxyProvider = 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
/**
* CLIProxy config.yaml structure (minimal)
+10 -10
View File
@@ -102,7 +102,7 @@ function formatModelOption(model: ModelEntry): string {
async function handleCreate(args: string[]): Promise<void> {
await initUI();
const parsedArgs = parseProfileArgs(args);
console.log(header('Create CLIProxy Variant'));
console.log(header('Create CLIProxy Plus Variant'));
console.log('');
// Step 1: Profile name
@@ -241,7 +241,7 @@ async function handleCreate(args: string[]): Promise<void> {
// Create variant
console.log('');
console.log(info('Creating CLIProxy variant...'));
console.log(info('Creating CLIProxy Plus variant...'));
const result = createVariant(name, provider, model, account);
if (!result.success) {
@@ -423,7 +423,7 @@ async function showStatus(verbose: boolean): Promise<void> {
const status = getBinaryStatus();
console.log('');
console.log(color('CLIProxyAPI Status', 'primary'));
console.log(color('CLIProxy Plus Status', 'primary'));
console.log('');
if (status.installed) {
@@ -471,13 +471,13 @@ async function showStatus(verbose: boolean): Promise<void> {
}
async function handleInstallVersion(version: string, verbose: boolean): Promise<void> {
console.log(info(`Installing CLIProxyAPI v${version}...`));
console.log(info(`Installing CLIProxy Plus v${version}...`));
console.log('');
const result = await installVersion(version, verbose);
if (!result.success) {
console.error('');
console.error(fail(`Failed to install CLIProxyAPI v${version}`));
console.error(fail(`Failed to install CLIProxy Plus v${version}`));
console.error(` ${result.error}`);
console.error('');
console.error('Possible causes:');
@@ -486,12 +486,12 @@ async function handleInstallVersion(version: string, verbose: boolean): Promise<
console.error(' 3. GitHub API rate limiting');
console.error('');
console.error('Check available versions at:');
console.error(' https://github.com/router-for-me/CLIProxyAPI/releases');
console.error(' https://github.com/router-for-me/CLIProxyAPIPlus/releases');
process.exit(1);
}
console.log('');
console.log(ok(`CLIProxyAPI v${version} installed (pinned)`));
console.log(ok(`CLIProxy Plus v${version} installed (pinned)`));
console.log('');
console.log(dim('This version will be used until you run:'));
console.log(
@@ -501,7 +501,7 @@ async function handleInstallVersion(version: string, verbose: boolean): Promise<
}
async function handleInstallLatest(verbose: boolean): Promise<void> {
console.log(info('Fetching latest CLIProxyAPI version...'));
console.log(info('Fetching latest CLIProxy Plus version...'));
const result = await installLatest(verbose);
if (!result.success) {
@@ -515,7 +515,7 @@ async function handleInstallLatest(verbose: boolean): Promise<void> {
}
console.log('');
console.log(ok(`CLIProxyAPI updated to v${result.version}`));
console.log(ok(`CLIProxy Plus updated to v${result.version}`));
console.log(dim('Auto-update is now enabled.'));
console.log('');
}
@@ -569,7 +569,7 @@ async function showHelp(): Promise<void> {
console.log(subheader('Notes:'));
console.log(` Default fallback version: ${color(CLIPROXY_FALLBACK_VERSION, 'info')}`);
console.log(
` Releases: ${color('https://github.com/router-for-me/CLIProxyAPI/releases', 'path')}`
` Releases: ${color('https://github.com/router-for-me/CLIProxyAPIPlus/releases', 'path')}`
);
console.log('');
}
+14 -11
View File
@@ -150,9 +150,9 @@ Claude Code Profile & Model Switcher`.trim();
// MAJOR SECTION 3: CLI Proxy (OAuth Providers)
// ═══════════════════════════════════════════════════════════════════════════
printMajorSection(
'CLI Proxy (OAuth Providers)',
'CLI Proxy Plus (OAuth Providers)',
[
'Zero-config OAuth authentication via CLIProxyAPI',
'Zero-config OAuth authentication via CLIProxy Plus',
'First run: Browser opens for authentication, then model selection',
'Settings: ~/.ccs/{provider}.settings.json (created after auth)',
],
@@ -161,6 +161,8 @@ Claude Code Profile & Model Switcher`.trim();
['ccs codex', 'OpenAI Codex (gpt-5.1-codex-max)'],
['ccs agy', 'Antigravity (Claude/Gemini models)'],
['ccs qwen', 'Qwen Code (qwen3-coder)'],
['ccs kiro', 'Kiro (AWS CodeWhisperer Claude models)'],
['ccs ghcp', 'GitHub Copilot (OAuth via CLIProxy Plus)'],
['', ''], // Spacer
['ccs <provider> --auth', 'Authenticate only'],
['ccs <provider> --auth --add', 'Add another account'],
@@ -174,16 +176,17 @@ Claude Code Profile & Model Switcher`.trim();
);
// ═══════════════════════════════════════════════════════════════════════════
// MAJOR SECTION 4: GitHub Copilot Integration
// MAJOR SECTION 4: GitHub Copilot Integration (copilot-api)
// ═══════════════════════════════════════════════════════════════════════════
printMajorSection(
'GitHub Copilot Integration',
'GitHub Copilot Integration (copilot-api)',
[
'Use your GitHub Copilot subscription with Claude Code',
'Use your GitHub Copilot subscription with Claude Code via copilot-api',
'Requires: npm install -g copilot-api',
'Note: For OAuth-based access, use ccs ghcp instead',
],
[
['ccs copilot', 'Use Copilot as API backend'],
['ccs copilot', 'Use Copilot via copilot-api daemon'],
['ccs copilot auth', 'Authenticate with GitHub'],
['ccs copilot status', 'Show integration status'],
['ccs copilot models', 'List available models'],
@@ -234,10 +237,10 @@ Claude Code Profile & Model Switcher`.trim();
]);
// CLI Proxy management
printSubSection('CLI Proxy Management', [
['ccs cliproxy', 'Show CLIProxyAPI status and version'],
['ccs cliproxy --help', 'Full CLIProxy management help'],
['ccs cliproxy --install <ver>', 'Install specific version (e.g., 6.5.53)'],
printSubSection('CLI Proxy Plus Management', [
['ccs cliproxy', 'Show CLIProxy Plus status and version'],
['ccs cliproxy --help', 'Full CLIProxy Plus management help'],
['ccs cliproxy --install <ver>', 'Install specific version (e.g., 6.6.6)'],
['ccs cliproxy --latest', 'Update to latest version'],
]);
@@ -262,7 +265,7 @@ Claude Code Profile & Model Switcher`.trim();
// CLI Proxy paths
console.log(subheader('CLI Proxy:'));
console.log(` Binary: ${color('~/.ccs/cliproxy/bin/cli-proxy-api', 'path')}`);
console.log(` Binary: ${color('~/.ccs/cliproxy/bin/cli-proxy-api-plus', 'path')}`);
console.log(` Config: ${color('~/.ccs/cliproxy/config.yaml', 'path')}`);
console.log(` Auth: ${color('~/.ccs/cliproxy/auth/', 'path')}`);
console.log(` ${dim('Port: 8317 (default)')}`);
+2 -2
View File
@@ -58,7 +58,7 @@ export type OAuthAccounts = Record<string, string>;
*/
export interface CLIProxyVariantConfig {
/** Base provider to use */
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow';
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
/** Account nickname (references oauth_accounts) */
account?: string;
/** Path to settings file (e.g., "~/.ccs/gemini-custom.settings.json") */
@@ -370,7 +370,7 @@ export function createEmptyUnifiedConfig(): UnifiedConfig {
profiles: {},
cliproxy: {
oauth_accounts: {},
providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow'],
providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'],
variants: {},
logging: {
enabled: false,
+6 -8
View File
@@ -1,5 +1,5 @@
/**
* CLIProxy Health Checks - Binary, config, auth, and port status
* CLIProxy Plus Health Checks - Binary, config, auth, and port status
*/
import * as fs from 'fs';
@@ -21,28 +21,26 @@ import { HealthCheck, IHealthChecker, createSpinner } from './types';
const ora = createSpinner();
/**
* Check CLIProxy binary installation
* Check CLIProxy Plus binary installation
*/
export class CLIProxyBinaryChecker implements IHealthChecker {
name = 'CLIProxy Binary';
name = 'CLIProxy Plus Binary';
run(results: HealthCheck): void {
const spinner = ora('Checking CLIProxy binary').start();
const spinner = ora('Checking CLIProxy Plus binary').start();
if (isCLIProxyInstalled()) {
const binaryPath = getCLIProxyPath();
const installedVersion = getInstalledCliproxyVersion();
spinner.succeed();
console.log(` ${ok('CLIProxy Binary'.padEnd(22))} v${installedVersion}`);
console.log(` ${ok('CLIProxy Plus'.padEnd(22))} v${installedVersion}`);
results.addCheck('CLIProxy Binary', 'success', undefined, undefined, {
status: 'OK',
info: `v${installedVersion} (${binaryPath})`,
});
} else {
spinner.info();
console.log(
` ${info('CLIProxy Binary'.padEnd(22))} Not installed (downloads on first use)`
);
console.log(` ${info('CLIProxy Plus'.padEnd(22))} Not installed (downloads on first use)`);
results.addCheck(
'CLIProxy Binary',
'success',
+2 -2
View File
@@ -66,8 +66,8 @@ class Doctor {
runSymlinkChecks(this.results);
console.log('');
// Group 6: CLIProxy (OAuth profiles)
console.log(header('CLIPROXY (OAUTH PROFILES)'));
// Group 6: CLIProxy Plus (OAuth profiles)
console.log(header('CLIPROXY PLUS (OAUTH PROFILES)'));
await runCLIProxyChecks(this.results);
console.log('');
+6 -2
View File
@@ -32,6 +32,8 @@ export const OAUTH_CALLBACK_PORTS: Record<CLIProxyProvider, number | null> = {
agy: 51121,
qwen: null, // Device Code Flow - no callback port
iflow: null, // Device Code Flow - no callback port
kiro: 9876, // Authorization Code Flow
ghcp: null, // Device Code Flow - no callback port
};
/**
@@ -48,6 +50,8 @@ export const OAUTH_FLOW_TYPES: Record<CLIProxyProvider, OAuthFlowType> = {
agy: 'authorization_code',
qwen: 'device_code',
iflow: 'device_code',
kiro: 'authorization_code',
ghcp: 'device_code',
};
/**
@@ -134,7 +138,7 @@ export async function checkOAuthPort(provider: CLIProxyProvider): Promise<OAuthP
* Check OAuth ports for all providers
*/
export async function checkAllOAuthPorts(): Promise<OAuthPortDiagnostic[]> {
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow'];
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'];
const results: OAuthPortDiagnostic[] = [];
for (const provider of providers) {
@@ -149,7 +153,7 @@ export async function checkAllOAuthPorts(): Promise<OAuthPortDiagnostic[]> {
* Check OAuth ports for providers that use Authorization Code flow only
*/
export async function checkAuthCodePorts(): Promise<OAuthPortDiagnostic[]> {
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy'];
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'kiro'];
const results: OAuthPortDiagnostic[] = [];
for (const provider of providers) {
+7 -7
View File
@@ -32,13 +32,13 @@ export function getCatCommand(filePath: string): string {
}
/**
* Get platform-specific command to kill CLIProxy processes
* Get platform-specific command to kill CLIProxy Plus processes
*/
export function getKillCLIProxyCommand(): string {
if (isWindows) {
return 'taskkill /F /IM cli-proxy-api.exe';
return 'taskkill /F /IM cli-proxy-api-plus.exe';
}
return 'pkill -f cli-proxy-api';
return 'pkill -f cli-proxy-api-plus';
}
/**
@@ -81,7 +81,7 @@ export function killProcessByPid(pid: number, verbose = false): boolean {
}
/**
* Kill all CLIProxy processes (cross-platform)
* Kill all CLIProxy Plus processes (cross-platform)
* @returns number of processes killed
*/
export function killAllCLIProxyProcesses(verbose = false): number {
@@ -91,12 +91,12 @@ export function killAllCLIProxyProcesses(verbose = false): number {
if (isWindows) {
// Windows: taskkill by image name
// Use /T to kill child processes too
execSync('taskkill /F /IM cli-proxy-api.exe /T 2>nul', { stdio: 'pipe' });
execSync('taskkill /F /IM cli-proxy-api-plus.exe /T 2>nul', { stdio: 'pipe' });
killed++;
} else {
// Unix: pkill with pattern matching
try {
execSync('pkill -9 -f cli-proxy-api', { stdio: 'pipe' });
execSync('pkill -9 -f cli-proxy-api-plus', { stdio: 'pipe' });
killed++;
} catch {
// pkill returns non-zero if no processes matched - that's OK
@@ -107,7 +107,7 @@ export function killAllCLIProxyProcesses(verbose = false): number {
}
if (verbose && killed > 0) {
console.error(`[cleanup] Killed ${killed} CLIProxy process(es)`);
console.error(`[cleanup] Killed ${killed} CLIProxy Plus process(es)`);
}
return killed;
+11 -1
View File
@@ -28,7 +28,15 @@ import type { CLIProxyProvider } from '../../cliproxy/types';
const router = Router();
// Valid providers list
const validProviders: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow'];
const validProviders: CLIProxyProvider[] = [
'gemini',
'codex',
'agy',
'qwen',
'iflow',
'kiro',
'ghcp',
];
/**
* GET /api/cliproxy/auth - Get auth status for built-in CLIProxy profiles
@@ -57,6 +65,8 @@ router.get('/', async (_req: Request, res: Response): Promise<void> => {
codex: 'codex',
qwen: 'qwen',
iflow: 'iflow',
kiro: 'kiro',
copilot: 'ghcp', // CLIProxyAPI returns 'copilot', we map to 'ghcp'
};
// Update lastUsedAt for providers with recent activity
@@ -32,7 +32,7 @@ const handleStatsRequest = async (_req: Request, res: Response): Promise<void> =
const running = await isCliproxyRunning();
if (!running) {
res.status(503).json({
error: 'CLIProxyAPI not running',
error: 'CLIProxy Plus not running',
message: 'Start a CLIProxy session (gemini, codex, agy) to collect stats',
});
return;
@@ -43,7 +43,7 @@ const handleStatsRequest = async (_req: Request, res: Response): Promise<void> =
if (!stats) {
res.status(503).json({
error: 'Stats unavailable',
message: 'CLIProxyAPI is running but stats endpoint not responding',
message: 'CLIProxy Plus is running but stats endpoint not responding',
});
return;
}
@@ -164,7 +164,7 @@ router.get('/models', async (_req: Request, res: Response): Promise<void> => {
const running = await isCliproxyRunning();
if (!running) {
res.status(503).json({
error: 'CLIProxyAPI not running',
error: 'CLIProxy Plus not running',
message: 'Start a CLIProxy session (gemini, codex, agy) to fetch available models',
});
return;
@@ -175,7 +175,7 @@ router.get('/models', async (_req: Request, res: Response): Promise<void> => {
if (!modelsResponse) {
res.status(503).json({
error: 'Models unavailable',
message: 'CLIProxyAPI is running but /v1/models endpoint not responding',
message: 'CLIProxy Plus is running but /v1/models endpoint not responding',
});
return;
}
@@ -197,7 +197,7 @@ router.get('/error-logs', async (_req: Request, res: Response): Promise<void> =>
const running = await isCliproxyRunning();
if (!running) {
res.status(503).json({
error: 'CLIProxyAPI not running',
error: 'CLIProxy Plus not running',
message: 'Start a CLIProxy session to view error logs',
});
return;
@@ -207,7 +207,7 @@ router.get('/error-logs', async (_req: Request, res: Response): Promise<void> =>
if (files === null) {
res.status(503).json({
error: 'Error logs unavailable',
message: 'CLIProxyAPI is running but error logs endpoint not responding',
message: 'CLIProxy Plus is running but error logs endpoint not responding',
});
return;
}
@@ -248,7 +248,7 @@ router.get('/error-logs/:name', async (req: Request, res: Response): Promise<voi
try {
const running = await isCliproxyRunning();
if (!running) {
res.status(503).json({ error: 'CLIProxyAPI not running' });
res.status(503).json({ error: 'CLIProxy Plus not running' });
return;
}
+1
View File
@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>GithubCopilot</title><path d="M19.245 5.364c1.322 1.36 1.877 3.216 2.11 5.817.622 0 1.2.135 1.592.654l.73.964c.21.278.323.61.323.955v2.62c0 .339-.173.669-.453.868C20.239 19.602 16.157 21.5 12 21.5c-4.6 0-9.205-2.583-11.547-4.258-.28-.2-.452-.53-.453-.868v-2.62c0-.345.113-.679.321-.956l.73-.963c.392-.517.974-.654 1.593-.654l.029-.297c.25-2.446.81-4.213 2.082-5.52 2.461-2.54 5.71-2.851 7.146-2.864h.198c1.436.013 4.685.323 7.146 2.864zm-7.244 4.328c-.284 0-.613.016-.962.05-.123.447-.305.85-.57 1.108-1.05 1.023-2.316 1.18-2.994 1.18-.638 0-1.306-.13-1.851-.464-.516.165-1.012.403-1.044.996a65.882 65.882 0 00-.063 2.884l-.002.48c-.002.563-.005 1.126-.013 1.69.002.326.204.63.51.765 2.482 1.102 4.83 1.657 6.99 1.657 2.156 0 4.504-.555 6.985-1.657a.854.854 0 00.51-.766c.03-1.682.006-3.372-.076-5.053-.031-.596-.528-.83-1.046-.996-.546.333-1.212.464-1.85.464-.677 0-1.942-.157-2.993-1.18-.266-.258-.447-.661-.57-1.108-.32-.032-.64-.049-.96-.05zm-2.525 4.013c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zm5 0c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zM7.635 5.087c-1.05.102-1.935.438-2.385.906-.975 1.037-.765 3.668-.21 4.224.405.394 1.17.657 1.995.657h.09c.649-.013 1.785-.176 2.73-1.11.435-.41.705-1.433.675-2.47-.03-.834-.27-1.52-.63-1.813-.39-.336-1.275-.482-2.265-.394zm6.465.394c-.36.292-.6.98-.63 1.813-.03 1.037.24 2.06.675 2.47.968.957 2.136 1.104 2.776 1.11h.044c.825 0 1.59-.263 1.995-.657.555-.556.765-3.187-.21-4.224-.45-.468-1.335-.804-2.385-.906-.99-.088-1.875.058-2.265.394zM12 7.615c-.24 0-.525.015-.84.044.03.16.045.336.06.526l-.001.159a2.94 2.94 0 01-.014.25c.225-.022.425-.027.612-.028h.366c.187 0 .387.006.612.028-.015-.146-.015-.277-.015-.409.015-.19.03-.365.06-.526a9.29 9.29 0 00-.84-.044z"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -14,7 +14,7 @@ import { Label } from '@/components/ui/label';
import { useCreateVariant, useCliproxyAuth } from '@/hooks/use-cliproxy';
import { usePrivacy } from '@/contexts/privacy-context';
const providers = ['gemini', 'codex', 'agy', 'qwen', 'iflow'] as const;
const providers = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'] as const;
const schema = z.object({
name: z
@@ -39,6 +39,8 @@ const providerOptions = [
{ value: 'agy', label: 'Antigravity' },
{ value: 'qwen', label: 'Alibaba Qwen' },
{ value: 'iflow', label: 'iFlow' },
{ value: 'kiro', label: 'Kiro (AWS)' },
{ value: 'ghcp', label: 'GitHub Copilot (OAuth)' },
];
export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
@@ -116,6 +116,10 @@ export function CliproxyHeader({
{ id: 'gemini', displayName: 'Gemini' },
{ id: 'codex', displayName: 'Codex' },
{ id: 'agy', displayName: 'Agy' },
{ id: 'qwen', displayName: 'Qwen' },
{ id: 'iflow', displayName: 'iFlow' },
{ id: 'kiro', displayName: 'Kiro' },
{ id: 'ghcp', displayName: 'GitHub Copilot' },
];
const getProviderStatus = (providerId: string) => {
@@ -131,10 +135,8 @@ export function CliproxyHeader({
{/* Top row: Title and Login Buttons */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold tracking-tight">CLIProxy</h1>
<p className="text-sm text-muted-foreground mt-1">
Manage OAuth providers and configuration
</p>
<h1 className="text-2xl font-bold tracking-tight">CLIProxy Plus</h1>
<p className="text-sm text-muted-foreground mt-1">CCS-level account management</p>
</div>
{/* Login Buttons - Wrap on mobile */}
@@ -71,7 +71,7 @@ export function CliproxyStatsOverview({ className }: CliproxyStatsOverviewProps)
Session Statistics
</h2>
<p className="text-sm text-muted-foreground">
Real-time usage metrics from CLIProxyAPI
Real-time usage metrics from CLIProxy Plus
</p>
</div>
<Badge variant="secondary" className="w-fit gap-1.5">
@@ -35,6 +35,8 @@ const providerLabels: Record<string, string> = {
agy: 'Antigravity',
qwen: 'Alibaba Qwen',
iflow: 'iFlow',
kiro: 'Kiro (AWS)',
ghcp: 'GitHub Copilot (OAuth)',
};
export function CliproxyTable({ data }: CliproxyTableProps) {
@@ -101,7 +101,9 @@ export function ModelPreferencesGrid() {
{modelsData.totalCount} total
</Badge>
</CardTitle>
<CardDescription>Models available through CLIProxyAPI, grouped by provider</CardDescription>
<CardDescription>
Models available through CLIProxy Plus, grouped by provider
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@@ -54,6 +54,8 @@ export function ProviderEditor({
codex: ['openai'],
qwen: ['alibaba', 'qwen'],
iflow: ['iflow'],
kiro: ['kiro', 'aws'],
ghcp: ['github', 'copilot'],
};
const owners = ownerMap[provider.toLowerCase()] || [provider.toLowerCase()];
return modelsData.models.filter((m) =>
@@ -18,6 +18,8 @@ const PROVIDER_IMAGES: Record<string, string> = {
agy: '/assets/providers/agy.png',
qwen: '/assets/providers/qwen-color.svg',
iflow: '/assets/providers/iflow.png',
kiro: '/assets/providers/kiro.png',
ghcp: '/assets/providers/copilot.svg',
};
/** Provider color configuration (for fallback only - no background for image logos) */
@@ -28,6 +30,8 @@ const PROVIDER_CONFIG: Record<string, { text: string; letter: string }> = {
agy: { text: 'text-violet-600', letter: 'A' },
qwen: { text: 'text-cyan-600', letter: 'Q' },
iflow: { text: 'text-indigo-600', letter: 'i' },
kiro: { text: 'text-teal-600', letter: 'K' },
ghcp: { text: 'text-green-600', letter: 'C' },
};
/** Size configuration */
+1 -1
View File
@@ -54,7 +54,7 @@ const navGroups = [
{
path: '/cliproxy',
icon: Zap,
label: 'CLIProxy',
label: 'CLIProxy Plus',
isCollapsible: true,
children: [
{ path: '/cliproxy', label: 'Overview' },
@@ -147,7 +147,7 @@ export function ProxyStatusWidget() {
isRunning ? 'bg-green-500 animate-pulse' : 'bg-muted-foreground/30'
)}
/>
<span className="text-sm font-medium">CLIProxy Service</span>
<span className="text-sm font-medium">CLIProxy Plus</span>
{hasUpdate && (
<Badge
variant="secondary"
@@ -10,6 +10,8 @@ export const PROVIDERS: ProviderOption[] = [
{ id: 'agy', name: 'Antigravity', description: 'Antigravity AI models' },
{ id: 'qwen', name: 'Alibaba Qwen', description: 'Qwen Code models' },
{ id: 'iflow', name: 'iFlow', description: 'iFlow AI models' },
{ id: 'kiro', name: 'Kiro (AWS)', description: 'AWS CodeWhisperer models' },
{ id: 'ghcp', name: 'GitHub Copilot (OAuth)', description: 'GitHub Copilot via OAuth' },
];
export const ALL_STEPS = ['provider', 'auth', 'variant', 'success'];
+4
View File
@@ -18,6 +18,10 @@ const AUTH_ENDPOINTS: Record<string, string> = {
gemini: '/gemini-cli-auth-url',
codex: '/codex-auth-url',
agy: '/antigravity-auth-url',
qwen: '/qwen-auth-url',
iflow: '/iflow-auth-url',
kiro: '/kiro-auth-url',
ghcp: '/ghcp-auth-url',
};
const AUTH_TIMEOUT_MS = 300000; // 5 minutes
+4 -4
View File
@@ -47,20 +47,20 @@ export interface UpdateProfile {
export interface Variant {
name: string;
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow';
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
settings: string;
account?: string;
}
export interface CreateVariant {
name: string;
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow';
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
model?: string;
account?: string;
}
export interface UpdateVariant {
provider?: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow';
provider?: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
model?: string;
account?: string;
}
@@ -69,7 +69,7 @@ export interface UpdateVariant {
export interface OAuthAccount {
id: string;
email?: string;
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow';
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
isDefault: boolean;
tokenFile: string;
createdAt: string;
+115
View File
@@ -151,4 +151,119 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
},
],
},
kiro: {
provider: 'kiro',
displayName: 'Kiro (AWS)',
defaultModel: 'kiro-claude-sonnet-4-5',
models: [
{
id: 'kiro-claude-opus-4-5',
name: 'Kiro Claude Opus 4.5',
description: 'Claude Opus 4.5 via Kiro (2.2x credit)',
presetMapping: {
default: 'kiro-claude-opus-4-5',
opus: 'kiro-claude-opus-4-5',
sonnet: 'kiro-claude-sonnet-4-5',
haiku: 'kiro-claude-haiku-4-5',
},
},
{
id: 'kiro-claude-sonnet-4-5',
name: 'Kiro Claude Sonnet 4.5',
description: 'Claude Sonnet 4.5 via Kiro (1.3x credit)',
presetMapping: {
default: 'kiro-claude-sonnet-4-5',
opus: 'kiro-claude-opus-4-5',
sonnet: 'kiro-claude-sonnet-4-5',
haiku: 'kiro-claude-haiku-4-5',
},
},
{
id: 'kiro-claude-sonnet-4',
name: 'Kiro Claude Sonnet 4',
description: 'Claude Sonnet 4 via Kiro (1.3x credit)',
presetMapping: {
default: 'kiro-claude-sonnet-4',
opus: 'kiro-claude-opus-4-5',
sonnet: 'kiro-claude-sonnet-4',
haiku: 'kiro-claude-haiku-4-5',
},
},
{
id: 'kiro-claude-haiku-4-5',
name: 'Kiro Claude Haiku 4.5',
description: 'Claude Haiku 4.5 via Kiro (0.4x credit)',
},
],
},
ghcp: {
provider: 'ghcp',
displayName: 'GitHub Copilot (OAuth)',
defaultModel: 'claude-sonnet-4.5',
models: [
{
id: 'claude-opus-4.5',
name: 'Claude Opus 4.5',
description: 'Anthropic Claude Opus 4.5 via GitHub Copilot',
presetMapping: {
default: 'claude-opus-4.5',
opus: 'claude-opus-4.5',
sonnet: 'claude-sonnet-4.5',
haiku: 'claude-haiku-4.5',
},
},
{
id: 'claude-sonnet-4.5',
name: 'Claude Sonnet 4.5',
description: 'Anthropic Claude Sonnet 4.5 via GitHub Copilot',
presetMapping: {
default: 'claude-sonnet-4.5',
opus: 'claude-opus-4.5',
sonnet: 'claude-sonnet-4.5',
haiku: 'claude-haiku-4.5',
},
},
{
id: 'claude-sonnet-4',
name: 'Claude Sonnet 4',
description: 'Anthropic Claude Sonnet 4 via GitHub Copilot',
},
{
id: 'claude-haiku-4.5',
name: 'Claude Haiku 4.5',
description: 'Anthropic Claude Haiku 4.5 via GitHub Copilot',
},
{
id: 'gpt-5.2',
name: 'GPT-5.2',
description: 'OpenAI GPT-5.2 via GitHub Copilot',
presetMapping: {
default: 'gpt-5.2',
opus: 'gpt-5.2',
sonnet: 'gpt-5.1',
haiku: 'gpt-5-mini',
},
},
{
id: 'gpt-5.1',
name: 'GPT-5.1',
description: 'OpenAI GPT-5.1 via GitHub Copilot',
},
{
id: 'gpt-5',
name: 'GPT-5',
description: 'OpenAI GPT-5 via GitHub Copilot',
},
{
id: 'gpt-5-mini',
name: 'GPT-5 Mini',
description: 'OpenAI GPT-5 Mini via GitHub Copilot',
},
{
id: 'gemini-3-pro',
name: 'Gemini 3 Pro',
description: 'Google Gemini 3 Pro via GitHub Copilot',
},
],
},
};
+6
View File
@@ -9,6 +9,8 @@ export const PROVIDER_ASSETS: Record<string, string> = {
agy: '/assets/providers/agy.png',
codex: '/assets/providers/openai.svg',
qwen: '/assets/providers/qwen-color.svg',
kiro: '/assets/providers/kiro.png',
ghcp: '/assets/providers/copilot.svg',
};
// Provider brand colors
@@ -19,6 +21,8 @@ export const PROVIDER_COLORS: Record<string, string> = {
vertex: '#4285F4',
iflow: '#f94144',
qwen: '#6236FF',
kiro: '#4d908e', // Dark Cyan (AWS-inspired)
ghcp: '#43aa8b', // Seaweed (GitHub-inspired)
};
// Provider display names
@@ -29,6 +33,8 @@ const PROVIDER_NAMES: Record<string, string> = {
vertex: 'Vertex AI',
iflow: 'iFlow',
qwen: 'Qwen',
kiro: 'Kiro (AWS)',
ghcp: 'GitHub Copilot (OAuth)',
};
// Map provider to display name
+2
View File
@@ -27,6 +27,8 @@ const PROVIDER_COLORS: Record<string, string> = {
vertex: '#577590', // Blue Slate
iflow: '#f94144', // Strawberry
qwen: '#f9c74f', // Tuscan
kiro: '#4d908e', // Dark Cyan (AWS-inspired)
copilot: '#43aa8b', // Seaweed (GitHub-inspired)
};
// Status colors (from Analytics Cost breakdown) - darker for light theme contrast
+1 -1
View File
@@ -235,7 +235,7 @@ export function CliproxyPage() {
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2">
<Zap className="w-5 h-5 text-primary" />
<h1 className="font-semibold">CLIProxy</h1>
<h1 className="font-semibold">CLIProxy Plus</h1>
</div>
<Button
variant="ghost"
@@ -81,7 +81,7 @@ export default function GlobalEnvSection() {
<div className="p-5 space-y-6">
<p className="text-sm text-muted-foreground">
Environment variables injected into all non-Claude subscription profiles (gemini, codex,
agy, copilot, etc.)
agy, ghcp, etc.)
</p>
{/* Enable/Disable Toggle */}
@@ -142,7 +142,7 @@ export default function ProxySection() {
<ScrollArea className="flex-1">
<div className="p-5 space-y-6">
<p className="text-sm text-muted-foreground">
Configure local or remote CLIProxyAPI connection for proxy-based profiles
Configure local or remote CLIProxy Plus connection for proxy-based profiles
</p>
{/* Mode Toggle - Card based selection */}
@@ -166,7 +166,7 @@ export default function ProxySection() {
<span className="font-medium">Local</span>
</div>
<p className="text-xs text-muted-foreground">
Run CLIProxyAPI binary on this machine
Run CLIProxy Plus binary on this machine
</p>
</button>
@@ -187,7 +187,7 @@ export default function ProxySection() {
<span className="font-medium">Remote</span>
</div>
<p className="text-xs text-muted-foreground">
Connect to a remote CLIProxyAPI server
Connect to a remote CLIProxy Plus server
</p>
</button>
</div>