fix(cliproxy): ensure provider-specific OAuth token detection

Fixes critical auth bug where checking any JSON file in the auth
directory would incorrectly mark providers as authenticated. Now
validates that token files match provider-specific prefixes (gemini-,
codex-, antigravity-) to prevent OAuth skipping across providers.

Updates help command examples to use placeholder syntax and updates
CHANGELOG to reflect Qwen -> Antigravity provider name change.
This commit is contained in:
kaitranntt committed 2025-11-28 20:53:20 -05:00
1 parent e54d401656
commit e0df6aa900
3 files changed
+45 -17

No files matched your search

+4 -4
View File
@@ -8,7 +8,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/)
- **CLIProxy OAuth Profiles**: Three new zero-config profiles powered by CLIProxyAPI
- `ccs gemini` - Google Gemini via OAuth (zero config)
- `ccs codex` - OpenAI Codex via OAuth (zero config)
- `ccs qwen` - Alibaba Qwen via OAuth (zero config)
- `ccs agy` - Antigravity (AGY) via OAuth (zero config)
- **Download-on-Demand Binary**: CLIProxyAPI binary (~15MB) downloads automatically on first use
- Supports 6 platforms: darwin/linux/windows × amd64/arm64
@@ -25,7 +25,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/)
- **CLIProxy Diagnostics** in `ccs doctor`:
- Binary installation status + version
- Config file validation
- OAuth status per provider (gemini/codex/qwen)
- OAuth status per provider (gemini/codex/agy)
- Port 8317 availability check
- **Enhanced Error Messages** (`src/utils/error-manager.ts`):
@@ -44,7 +44,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/)
### Changed
- **Profile Detection**: New priority order
1. CLIProxy profiles (gemini, codex, qwen)
1. CLIProxy profiles (gemini, codex, agy)
2. Settings-based profiles (glm, glmt, kimi)
3. Account-based profiles (work, personal)
4. Default Claude CLI
@@ -57,7 +57,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/)
- **Model Mappings**:
- Gemini: gemini-2.0-flash (opus: thinking-exp, haiku: flash-lite)
- Codex: gpt-4o (opus: o1, haiku: gpt-4o-mini)
- Qwen: qwen-max (sonnet: qwen-plus, haiku: qwen-turbo)
- Antigravity: agy (sonnet: agy-pro, haiku: agy-turbo)
- **Storage**:
- Binary: `~/.ccs/bin/cliproxyapi`
- Tokens: `~/.ccs/cliproxy-auth/<provider>/`
+37 -9
View File
@@ -120,10 +120,21 @@ export function getProviderTokenDir(provider: CLIProxyProvider): string {
return getProviderAuthDir(provider);
}
/**
* Provider-specific auth file prefixes
* CLIProxyAPI names auth files with provider prefix (e.g., "antigravity-user@email.json")
*/
const PROVIDER_AUTH_PREFIXES: Record<CLIProxyProvider, string[]> = {
gemini: ['gemini-', 'google-'],
codex: ['codex-', 'openai-'],
agy: ['antigravity-', 'agy-'],
};
/**
* Check if provider has valid authentication
* CLIProxyAPI stores OAuth tokens as JSON files in the auth directory.
* We check for any .json files that could contain tokens.
* Files are named with provider prefix (e.g., "antigravity-user@email.json").
* We MUST check for provider-specific files, not just any .json file.
*/
export function isAuthenticated(provider: CLIProxyProvider): boolean {
const tokenDir = getProviderTokenDir(provider);
@@ -132,13 +143,22 @@ export function isAuthenticated(provider: CLIProxyProvider): boolean {
return false;
}
// Check for any token files created by CLIProxyAPI
// Get valid prefixes for this provider
const validPrefixes = PROVIDER_AUTH_PREFIXES[provider] || [];
// Check for provider-specific token files
try {
const files = fs.readdirSync(tokenDir);
const tokenFiles = files.filter(
(f) => f.endsWith('.json') || f.endsWith('.token') || f === 'credentials'
);
return tokenFiles.length > 0;
const providerTokenFiles = files.filter((f) => {
// Must be a token-like file
if (!f.endsWith('.json') && !f.endsWith('.token') && f !== 'credentials') {
return false;
}
// Must have provider-specific prefix
const lowerFile = f.toLowerCase();
return validPrefixes.some((prefix) => lowerFile.startsWith(prefix));
});
return providerTokenFiles.length > 0;
} catch {
return false;
}
@@ -152,11 +172,19 @@ export function getAuthStatus(provider: CLIProxyProvider): AuthStatus {
let tokenFiles: string[] = [];
let lastAuth: Date | undefined;
// Get valid prefixes for this provider
const validPrefixes = PROVIDER_AUTH_PREFIXES[provider] || [];
if (fs.existsSync(tokenDir)) {
const files = fs.readdirSync(tokenDir);
tokenFiles = files.filter(
(f) => f.endsWith('.json') || f.endsWith('.token') || f === 'credentials'
);
// Only include provider-specific files
tokenFiles = files.filter((f) => {
if (!f.endsWith('.json') && !f.endsWith('.token') && f !== 'credentials') {
return false;
}
const lowerFile = f.toLowerCase();
return validPrefixes.some((prefix) => lowerFile.startsWith(prefix));
});
// Get most recent modification time
for (const file of tokenFiles) {
+4 -4
View File
@@ -55,10 +55,10 @@ export function handleHelpCommand(): void {
` ${colored('ccs agy', 'yellow')} Antigravity (gemini-3-pro-preview)`
);
console.log('');
console.log(` ${colored('ccs gemini --auth', 'yellow')} Authenticate only`);
console.log(` ${colored('ccs gemini --logout', 'yellow')} Clear authentication`);
console.log(` ${colored('ccs gemini --headless', 'yellow')} Headless auth (for SSH)`);
console.log(` ${colored('ccs gemini "explain code"', 'yellow')} Use with prompt`);
console.log(` ${colored('ccs <provider> --auth', 'yellow')} Authenticate only`);
console.log(` ${colored('ccs <provider> --logout', 'yellow')} Clear authentication`);
console.log(` ${colored('ccs <provider> --headless', 'yellow')} Headless auth (for SSH)`);
console.log(` ${colored('ccs codex "explain code"', 'yellow')} Use with prompt`);
console.log('');
// ═══════════════════════════════════════════════════════════════════════════