From 28d8bd84a5ac912b79416aeced95f74fd71876bb Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 27 Jan 2026 15:43:21 -0500 Subject: [PATCH 1/4] feat(cliproxy): add Claude (Anthropic) OAuth provider support Add Claude as a first-class OAuth provider in CCS CLI and dashboard. Backend support already exists in CLIProxyAPIPlus (port 54545). Changes: - Add 'claude' to CLIProxyProvider type and profile detector - Add Claude OAuth config (port 54545, --anthropic-login flag) - Add Claude to oauth-port-diagnostics flow types - Add Claude token discovery prefixes (claude-, anthropic-) - Add Claude model catalog (Opus 4.5, Sonnet 4.5/4, Haiku 4.5) - Add Claude logo and provider display name - Update variant config types and adapters Closes #380 --- src/auth/profile-detector.ts | 1 + src/cliproxy/auth/auth-types.ts | 10 ++++ src/cliproxy/config-generator.ts | 1 + src/cliproxy/model-catalog.ts | 49 +++++++++++++++++++ .../services/variant-config-adapter.ts | 2 +- src/cliproxy/types.ts | 11 ++++- src/config/unified-config-types.ts | 2 +- src/management/oauth-port-diagnostics.ts | 15 +++++- src/types/config.ts | 2 +- ui/public/assets/providers/claude.svg | 1 + ui/src/components/cliproxy/provider-logo.tsx | 1 + ui/src/lib/model-catalogs.ts | 45 +++++++++++++++++ 12 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 ui/public/assets/providers/claude.svg diff --git a/src/auth/profile-detector.ts b/src/auth/profile-detector.ts index bde28265..d5db7ae1 100644 --- a/src/auth/profile-detector.ts +++ b/src/auth/profile-detector.ts @@ -28,6 +28,7 @@ export const CLIPROXY_PROFILES = [ 'iflow', 'kiro', 'ghcp', + 'claude', ] as const; export type CLIProxyProfileName = (typeof CLIPROXY_PROFILES)[number]; diff --git a/src/cliproxy/auth/auth-types.ts b/src/cliproxy/auth/auth-types.ts index 42e3cc9e..d3af8c76 100644 --- a/src/cliproxy/auth/auth-types.ts +++ b/src/cliproxy/auth/auth-types.ts @@ -27,6 +27,7 @@ export const OAUTH_CALLBACK_PORTS: Partial> = { codex: 1455, agy: 51121, iflow: 11451, + claude: 54545, // qwen: Device Code Flow - no callback port // ghcp: Device Code Flow - no callback port }; @@ -121,6 +122,13 @@ export const OAUTH_CONFIGS: Record = { scopes: ['copilot'], authFlag: '--github-copilot-login', }, + claude: { + provider: 'claude', + displayName: 'Claude (Anthropic)', + authUrl: 'https://console.anthropic.com/oauth/authorize', + scopes: ['user:inference', 'user:profile'], + authFlag: '--anthropic-login', + }, }; /** @@ -136,6 +144,7 @@ export const PROVIDER_AUTH_PREFIXES: Record = { iflow: ['iflow-'], kiro: ['kiro-', 'aws-', 'codewhisperer-'], ghcp: ['github-copilot-', 'copilot-', 'gh-'], + claude: ['claude-', 'anthropic-'], }; /** @@ -150,6 +159,7 @@ export const PROVIDER_TYPE_VALUES: Record = { iflow: ['iflow'], kiro: ['kiro', 'codewhisperer'], ghcp: ['github-copilot', 'copilot'], + claude: ['claude', 'anthropic'], }; /** diff --git a/src/cliproxy/config-generator.ts b/src/cliproxy/config-generator.ts index a4652417..a5772bf1 100644 --- a/src/cliproxy/config-generator.ts +++ b/src/cliproxy/config-generator.ts @@ -299,6 +299,7 @@ const PROVIDER_DISPLAY_NAMES: Record = { iflow: 'iFlow', kiro: 'Kiro (AWS)', ghcp: 'GitHub Copilot (OAuth)', + claude: 'Claude (Anthropic)', }; /** diff --git a/src/cliproxy/model-catalog.ts b/src/cliproxy/model-catalog.ts index 78c86d38..7f035350 100644 --- a/src/cliproxy/model-catalog.ts +++ b/src/cliproxy/model-catalog.ts @@ -166,6 +166,55 @@ export const MODEL_CATALOG: Partial> = }, ], }, + claude: { + provider: 'claude', + displayName: 'Claude (Anthropic)', + defaultModel: 'claude-sonnet-4-5-20250514', + models: [ + { + id: 'claude-opus-4-5-20250220', + name: 'Claude Opus 4.5', + description: 'Most capable Claude model', + thinking: { + type: 'budget', + min: 1024, + max: 128000, + zeroAllowed: false, + dynamicAllowed: true, + }, + }, + { + id: 'claude-sonnet-4-5-20250514', + name: 'Claude Sonnet 4.5', + description: 'Balanced performance and speed', + thinking: { + type: 'budget', + min: 1024, + max: 128000, + zeroAllowed: false, + dynamicAllowed: true, + }, + }, + { + id: 'claude-sonnet-4-20250514', + name: 'Claude Sonnet 4', + description: 'Previous generation Sonnet', + thinking: { + type: 'budget', + min: 1024, + max: 128000, + zeroAllowed: false, + dynamicAllowed: true, + }, + }, + { + id: 'claude-haiku-4-5-20250514', + name: 'Claude Haiku 4.5', + description: 'Fast and efficient', + thinking: { type: 'none' }, + }, + ], + }, }; /** diff --git a/src/cliproxy/services/variant-config-adapter.ts b/src/cliproxy/services/variant-config-adapter.ts index 4294b956..1fb0ec65 100644 --- a/src/cliproxy/services/variant-config-adapter.ts +++ b/src/cliproxy/services/variant-config-adapter.ts @@ -132,7 +132,7 @@ export function saveVariantUnified( if (!config.cliproxy) { config.cliproxy = { oauth_accounts: {}, - providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'], + providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp', 'claude'], variants: {}, }; } diff --git a/src/cliproxy/types.ts b/src/cliproxy/types.ts index 9759bba2..3ecc56bb 100644 --- a/src/cliproxy/types.ts +++ b/src/cliproxy/types.ts @@ -118,8 +118,17 @@ export interface DownloadResult { * - iflow: iFlow via OAuth * - kiro: Kiro (AWS CodeWhisperer) via OAuth * - ghcp: GitHub Copilot via Device Code (OAuth through CLIProxyAPIPlus) + * - claude: Claude (Anthropic) via OAuth */ -export type CLIProxyProvider = 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp'; +export type CLIProxyProvider = + | 'gemini' + | 'codex' + | 'agy' + | 'qwen' + | 'iflow' + | 'kiro' + | 'ghcp' + | 'claude'; /** * CLIProxy backend selection diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index f2d3618e..0df03c27 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -61,7 +61,7 @@ export type OAuthAccounts = Record; */ export interface CLIProxyVariantConfig { /** Base provider to use */ - provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp'; + provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; /** Account nickname (references oauth_accounts) */ account?: string; /** Path to settings file (e.g., "~/.ccs/gemini-custom.settings.json") */ diff --git a/src/management/oauth-port-diagnostics.ts b/src/management/oauth-port-diagnostics.ts index 262d963d..48db2348 100644 --- a/src/management/oauth-port-diagnostics.ts +++ b/src/management/oauth-port-diagnostics.ts @@ -34,6 +34,7 @@ export const OAUTH_CALLBACK_PORTS: Record = { iflow: 11451, // Authorization Code Flow kiro: 9876, // Authorization Code Flow ghcp: null, // Device Code Flow - no callback port + claude: 54545, // Authorization Code Flow (Anthropic OAuth) }; /** @@ -52,6 +53,7 @@ export const OAUTH_FLOW_TYPES: Record = { iflow: 'authorization_code', kiro: 'authorization_code', ghcp: 'device_code', + claude: 'authorization_code', }; /** @@ -138,7 +140,16 @@ export async function checkOAuthPort(provider: CLIProxyProvider): Promise { - const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp']; + const providers: CLIProxyProvider[] = [ + 'gemini', + 'codex', + 'agy', + 'qwen', + 'iflow', + 'kiro', + 'ghcp', + 'claude', + ]; const results: OAuthPortDiagnostic[] = []; for (const provider of providers) { @@ -153,7 +164,7 @@ export async function checkAllOAuthPorts(): Promise { * Check OAuth ports for providers that use Authorization Code flow only */ export async function checkAuthCodePorts(): Promise { - const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'kiro']; + const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'kiro', 'claude']; const results: OAuthPortDiagnostic[] = []; for (const provider of providers) { diff --git a/src/types/config.ts b/src/types/config.ts index a00195a3..db910441 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -18,7 +18,7 @@ export interface ProfilesConfig { */ export interface CLIProxyVariantConfig { /** CLIProxy provider to use */ - provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp'; + provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp' | 'claude'; /** Path to settings.json with custom model configuration (optional) */ settings?: string; /** Account identifier for multi-account support (optional, defaults to 'default') */ diff --git a/ui/public/assets/providers/claude.svg b/ui/public/assets/providers/claude.svg new file mode 100644 index 00000000..62dc0db1 --- /dev/null +++ b/ui/public/assets/providers/claude.svg @@ -0,0 +1 @@ +Claude \ No newline at end of file diff --git a/ui/src/components/cliproxy/provider-logo.tsx b/ui/src/components/cliproxy/provider-logo.tsx index 66ad0420..ee762285 100644 --- a/ui/src/components/cliproxy/provider-logo.tsx +++ b/ui/src/components/cliproxy/provider-logo.tsx @@ -20,6 +20,7 @@ const PROVIDER_IMAGES: Record = { iflow: '/assets/providers/iflow.png', kiro: '/assets/providers/kiro.png', ghcp: '/assets/providers/copilot.svg', + claude: '/assets/providers/claude.svg', }; /** Provider color configuration (for fallback only - no background for image logos) */ diff --git a/ui/src/lib/model-catalogs.ts b/ui/src/lib/model-catalogs.ts index 09c056ba..9c6065a1 100644 --- a/ui/src/lib/model-catalogs.ts +++ b/ui/src/lib/model-catalogs.ts @@ -311,4 +311,49 @@ export const MODEL_CATALOGS: Record = { }, ], }, + claude: { + provider: 'claude', + displayName: 'Claude (Anthropic)', + defaultModel: 'claude-sonnet-4-5-20250514', + models: [ + { + id: 'claude-opus-4-5-20250220', + name: 'Claude Opus 4.5', + description: 'Most capable Claude model', + presetMapping: { + default: 'claude-opus-4-5-20250220', + opus: 'claude-opus-4-5-20250220', + sonnet: 'claude-sonnet-4-5-20250514', + haiku: 'claude-haiku-4-5-20250514', + }, + }, + { + id: 'claude-sonnet-4-5-20250514', + name: 'Claude Sonnet 4.5', + description: 'Balanced performance and speed', + presetMapping: { + default: 'claude-sonnet-4-5-20250514', + opus: 'claude-opus-4-5-20250220', + sonnet: 'claude-sonnet-4-5-20250514', + haiku: 'claude-haiku-4-5-20250514', + }, + }, + { + id: 'claude-sonnet-4-20250514', + name: 'Claude Sonnet 4', + description: 'Previous generation Sonnet', + presetMapping: { + default: 'claude-sonnet-4-20250514', + opus: 'claude-opus-4-5-20250220', + sonnet: 'claude-sonnet-4-20250514', + haiku: 'claude-haiku-4-5-20250514', + }, + }, + { + id: 'claude-haiku-4-5-20250514', + name: 'Claude Haiku 4.5', + description: 'Fast and efficient', + }, + ], + }, }; From d2129957d7e954701be973725545f475711d0468 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 27 Jan 2026 20:14:22 -0500 Subject: [PATCH 2/4] fix(cliproxy): add Claude to all provider lists for sidebar display Claude was missing from: - getAllAuthStatus() providers array in token-manager.ts - PROVIDER_ASSETS, PROVIDER_COLORS, PROVIDER_NAMES in provider-config.ts - PROVIDERS array in setup wizard constants.ts - PLUS_ONLY_PROVIDERS in proxy settings --- src/cliproxy/auth/token-manager.ts | 11 ++++++++++- ui/src/components/setup/wizard/constants.ts | 1 + ui/src/lib/provider-config.ts | 3 +++ ui/src/pages/settings/sections/proxy/index.tsx | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/cliproxy/auth/token-manager.ts b/src/cliproxy/auth/token-manager.ts index 1e31273c..fb1fa6fb 100644 --- a/src/cliproxy/auth/token-manager.ts +++ b/src/cliproxy/auth/token-manager.ts @@ -145,7 +145,16 @@ export function getAuthStatus(provider: CLIProxyProvider): AuthStatus { * Get auth status for all providers */ export function getAllAuthStatus(): AuthStatus[] { - const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp']; + const providers: CLIProxyProvider[] = [ + 'gemini', + 'codex', + 'agy', + 'qwen', + 'iflow', + 'kiro', + 'ghcp', + 'claude', + ]; return providers.map(getAuthStatus); } diff --git a/ui/src/components/setup/wizard/constants.ts b/ui/src/components/setup/wizard/constants.ts index 4c530fe7..abed372c 100644 --- a/ui/src/components/setup/wizard/constants.ts +++ b/ui/src/components/setup/wizard/constants.ts @@ -12,6 +12,7 @@ export const PROVIDERS: ProviderOption[] = [ { 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' }, + { id: 'claude', name: 'Claude (Anthropic)', description: 'Claude Opus/Sonnet models' }, ]; export const ALL_STEPS = ['provider', 'auth', 'variant', 'success']; diff --git a/ui/src/lib/provider-config.ts b/ui/src/lib/provider-config.ts index 989479a2..764adf51 100644 --- a/ui/src/lib/provider-config.ts +++ b/ui/src/lib/provider-config.ts @@ -11,6 +11,7 @@ export const PROVIDER_ASSETS: Record = { qwen: '/assets/providers/qwen-color.svg', kiro: '/assets/providers/kiro.png', ghcp: '/assets/providers/copilot.svg', + claude: '/assets/providers/claude.svg', }; // Provider brand colors @@ -23,6 +24,7 @@ export const PROVIDER_COLORS: Record = { qwen: '#6236FF', kiro: '#4d908e', // Dark Cyan (AWS-inspired) ghcp: '#43aa8b', // Seaweed (GitHub-inspired) + claude: '#D97706', // Anthropic orange }; // Provider display names @@ -35,6 +37,7 @@ const PROVIDER_NAMES: Record = { qwen: 'Qwen', kiro: 'Kiro (AWS)', ghcp: 'GitHub Copilot (OAuth)', + claude: 'Claude (Anthropic)', }; // Map provider to display name diff --git a/ui/src/pages/settings/sections/proxy/index.tsx b/ui/src/pages/settings/sections/proxy/index.tsx index 38879cc2..0009b164 100644 --- a/ui/src/pages/settings/sections/proxy/index.tsx +++ b/ui/src/pages/settings/sections/proxy/index.tsx @@ -29,7 +29,7 @@ import { api } from '@/lib/api-client'; const DEBUG_MODE_KEY = 'ccs_debug_mode'; /** Providers only available on CLIProxyAPIPlus */ -const PLUS_ONLY_PROVIDERS = ['kiro', 'ghcp']; +const PLUS_ONLY_PROVIDERS = ['kiro', 'ghcp', 'claude']; export default function ProxySection() { const { From b385ab131d2b179c7b7bd014859f9118afd6ce5c Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 27 Jan 2026 20:17:10 -0500 Subject: [PATCH 3/4] refactor(cliproxy): reorder providers - Antigravity first, then Claude --- src/cliproxy/auth/token-manager.ts | 4 ++-- ui/src/components/setup/wizard/constants.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cliproxy/auth/token-manager.ts b/src/cliproxy/auth/token-manager.ts index fb1fa6fb..665e821d 100644 --- a/src/cliproxy/auth/token-manager.ts +++ b/src/cliproxy/auth/token-manager.ts @@ -146,14 +146,14 @@ export function getAuthStatus(provider: CLIProxyProvider): AuthStatus { */ export function getAllAuthStatus(): AuthStatus[] { const providers: CLIProxyProvider[] = [ + 'agy', + 'claude', 'gemini', 'codex', - 'agy', 'qwen', 'iflow', 'kiro', 'ghcp', - 'claude', ]; return providers.map(getAuthStatus); } diff --git a/ui/src/components/setup/wizard/constants.ts b/ui/src/components/setup/wizard/constants.ts index abed372c..ba0fda30 100644 --- a/ui/src/components/setup/wizard/constants.ts +++ b/ui/src/components/setup/wizard/constants.ts @@ -5,14 +5,14 @@ import type { ProviderOption } from './types'; export const PROVIDERS: ProviderOption[] = [ + { id: 'agy', name: 'Antigravity', description: 'Antigravity AI models' }, + { id: 'claude', name: 'Claude (Anthropic)', description: 'Claude Opus/Sonnet models' }, { id: 'gemini', name: 'Google Gemini', description: 'Gemini Pro/Flash models' }, { id: 'codex', name: 'OpenAI Codex', description: 'GPT-4 and codex models' }, - { 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' }, - { id: 'claude', name: 'Claude (Anthropic)', description: 'Claude Opus/Sonnet models' }, ]; export const ALL_STEPS = ['provider', 'auth', 'variant', 'success']; From 2091a90b7710e7cb0b565577a5e659473126a541 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 27 Jan 2026 20:24:01 -0500 Subject: [PATCH 4/4] fix(cliproxy): address PR review feedback - Remove claude from PLUS_ONLY_PROVIDERS (Claude works with normal CLIProxy too) - Fix color inconsistency: use #D97757 to match SVG brand color - Add iflow to checkAuthCodePorts (pre-existing issue, fixed opportunistically) --- src/management/oauth-port-diagnostics.ts | 2 +- ui/src/lib/provider-config.ts | 2 +- ui/src/pages/settings/sections/proxy/index.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/management/oauth-port-diagnostics.ts b/src/management/oauth-port-diagnostics.ts index 48db2348..806a97cc 100644 --- a/src/management/oauth-port-diagnostics.ts +++ b/src/management/oauth-port-diagnostics.ts @@ -164,7 +164,7 @@ export async function checkAllOAuthPorts(): Promise { * Check OAuth ports for providers that use Authorization Code flow only */ export async function checkAuthCodePorts(): Promise { - const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'kiro', 'claude']; + const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'iflow', 'kiro', 'claude']; const results: OAuthPortDiagnostic[] = []; for (const provider of providers) { diff --git a/ui/src/lib/provider-config.ts b/ui/src/lib/provider-config.ts index 764adf51..72cb9808 100644 --- a/ui/src/lib/provider-config.ts +++ b/ui/src/lib/provider-config.ts @@ -24,7 +24,7 @@ export const PROVIDER_COLORS: Record = { qwen: '#6236FF', kiro: '#4d908e', // Dark Cyan (AWS-inspired) ghcp: '#43aa8b', // Seaweed (GitHub-inspired) - claude: '#D97706', // Anthropic orange + claude: '#D97757', // Anthropic brand color (matches SVG) }; // Provider display names diff --git a/ui/src/pages/settings/sections/proxy/index.tsx b/ui/src/pages/settings/sections/proxy/index.tsx index 0009b164..38879cc2 100644 --- a/ui/src/pages/settings/sections/proxy/index.tsx +++ b/ui/src/pages/settings/sections/proxy/index.tsx @@ -29,7 +29,7 @@ import { api } from '@/lib/api-client'; const DEBUG_MODE_KEY = 'ccs_debug_mode'; /** Providers only available on CLIProxyAPIPlus */ -const PLUS_ONLY_PROVIDERS = ['kiro', 'ghcp', 'claude']; +const PLUS_ONLY_PROVIDERS = ['kiro', 'ghcp']; export default function ProxySection() { const {