mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix(cliproxy): improve qwen oauth error handling (#33)
Fixes #29 - Update OAuth flow documentation (Qwen uses Device Code Flow, not callback-based) - Remove incorrect port cleanup for Qwen (Device Code Flow needs no port) - Fix error messages to show Qwen-specific troubleshooting tips
This commit is contained in:
@@ -1,3 +1,24 @@
|
||||
# [5.3.0-beta.2](https://github.com/kaitranntt/ccs/compare/v5.3.0-beta.1...v5.3.0-beta.2) (2025-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cliproxy:** improve qwen oauth error handling ([7e0b0fe](https://github.com/kaitranntt/ccs/commit/7e0b0feca8ce2ed5d505c5bf6c84e54c6df8839e)), closes [#29](https://github.com/kaitranntt/ccs/issues/29)
|
||||
|
||||
# [5.3.0-beta.1](https://github.com/kaitranntt/ccs/compare/v5.2.0...v5.3.0-beta.1) (2025-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cliproxy:** use double-dash flags for cliproxyapi auth ([#24](https://github.com/kaitranntt/ccs/issues/24)) ([4c81f28](https://github.com/kaitranntt/ccs/commit/4c81f28f0b67ef92cf74d0f5c13a5943ff0a7f00))
|
||||
* **glmt:** handle 401 errors and headers-already-sent exception ([#30](https://github.com/kaitranntt/ccs/issues/30)) ([c953382](https://github.com/kaitranntt/ccs/commit/c95338232a37981b95b785b47185ce18d6d94b7a)), closes [#26](https://github.com/kaitranntt/ccs/issues/26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cliproxy:** add qwen code oauth provider support ([#31](https://github.com/kaitranntt/ccs/issues/31)) ([a3f1e52](https://github.com/kaitranntt/ccs/commit/a3f1e52ac68600ba0806d67aacceb6477ffa3543)), closes [#29](https://github.com/kaitranntt/ccs/issues/29)
|
||||
* **cliproxy:** auto-update cliproxyapi to latest release on startup ([8873ccd](https://github.com/kaitranntt/ccs/commit/8873ccd981679e8acff8965accdc22215c6e4aa2))
|
||||
|
||||
# [5.2.0](https://github.com/kaitranntt/ccs/compare/v5.1.1...v5.2.0) (2025-12-01)
|
||||
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or (
|
||||
# IMPORTANT: Update this version when releasing new versions!
|
||||
# This hardcoded version is used for standalone installations (irm | iex)
|
||||
# For git installations, VERSION file is read if available
|
||||
$CcsVersion = "5.2.0"
|
||||
$CcsVersion = "5.3.0-beta.2"
|
||||
|
||||
# Try to read VERSION file for git installations
|
||||
if ($ScriptDir) {
|
||||
|
||||
@@ -84,7 +84,7 @@ fi
|
||||
# IMPORTANT: Update this version when releasing new versions!
|
||||
# This hardcoded version is used for standalone installations (curl | bash)
|
||||
# For git installations, VERSION file is read if available
|
||||
CCS_VERSION="5.2.0"
|
||||
CCS_VERSION="5.3.0-beta.2"
|
||||
|
||||
# Try to read VERSION file for git installations
|
||||
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kaitranntt/ccs",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -14,12 +14,72 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import { spawn, execSync } from 'child_process';
|
||||
import { ProgressIndicator } from '../utils/progress-indicator';
|
||||
import { getProviderAuthDir, generateConfig } from './config-generator';
|
||||
import { ensureCLIProxyBinary } from './binary-manager';
|
||||
import { CLIProxyProvider } from './types';
|
||||
|
||||
/**
|
||||
* OAuth callback ports used by CLIProxyAPI (hardcoded in binary)
|
||||
* See: https://github.com/router-for-me/CLIProxyAPI/tree/main/internal/auth
|
||||
*
|
||||
* OAuth flow types per provider:
|
||||
* - Gemini: Authorization Code Flow with local callback server on port 8085
|
||||
* - 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)
|
||||
*
|
||||
* We auto-kill processes on callback ports before OAuth to avoid conflicts.
|
||||
*/
|
||||
const OAUTH_CALLBACK_PORTS: Partial<Record<CLIProxyProvider, number>> = {
|
||||
gemini: 8085,
|
||||
// codex uses 1455
|
||||
// agy uses 51121
|
||||
// qwen uses Device Code Flow - no callback port needed
|
||||
};
|
||||
|
||||
/**
|
||||
* Kill any process using a specific port
|
||||
* Used to free OAuth callback port before authentication
|
||||
*/
|
||||
function killProcessOnPort(port: number, verbose: boolean): boolean {
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows: use netstat + taskkill
|
||||
const result = execSync(`netstat -ano | findstr :${port}`, {
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
});
|
||||
const lines = result.trim().split('\n');
|
||||
for (const line of lines) {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
const pid = parts[parts.length - 1];
|
||||
if (pid && /^\d+$/.test(pid)) {
|
||||
execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe' });
|
||||
if (verbose) console.error(`[auth] Killed process ${pid} on port ${port}`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// Unix: use lsof + kill
|
||||
const result = execSync(`lsof -ti:${port}`, { encoding: 'utf-8', stdio: 'pipe' });
|
||||
const pids = result
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((p) => p);
|
||||
for (const pid of pids) {
|
||||
execSync(`kill -9 ${pid}`, { stdio: 'pipe' });
|
||||
if (verbose) console.error(`[auth] Killed process ${pid} on port ${port}`);
|
||||
}
|
||||
return pids.length > 0;
|
||||
}
|
||||
} catch {
|
||||
// No process on port or command failed - that's fine
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if running in a headless environment (no browser available)
|
||||
*/
|
||||
@@ -328,6 +388,16 @@ export async function triggerOAuth(
|
||||
const configPath = generateConfig(provider);
|
||||
log(`Config generated: ${configPath}`);
|
||||
|
||||
// Free OAuth callback port if this provider shares it with another
|
||||
// Qwen and Gemini both use port 8085 - kill any existing process to avoid conflict
|
||||
const callbackPort = OAUTH_CALLBACK_PORTS[provider];
|
||||
if (callbackPort) {
|
||||
const killed = killProcessOnPort(callbackPort, verbose);
|
||||
if (killed) {
|
||||
log(`Freed port ${callbackPort} for OAuth callback`);
|
||||
}
|
||||
}
|
||||
|
||||
// Build args: config + auth flag + optional --no-browser for headless
|
||||
const args = ['--config', configPath, oauthConfig.authFlag];
|
||||
if (headless) {
|
||||
@@ -428,8 +498,15 @@ export async function triggerOAuth(
|
||||
} else {
|
||||
if (!headless) spinner.fail('Authentication incomplete');
|
||||
console.error('[X] Token not found after authentication');
|
||||
console.error(' The OAuth flow may have been cancelled or port 8085 was in use');
|
||||
console.error(' Try: pkill -f cli-proxy-api && ccs gemini --auth');
|
||||
// Qwen uses Device Code Flow (polling), others use Authorization Code Flow (callback)
|
||||
if (provider === 'qwen') {
|
||||
console.error(' Qwen uses Device Code Flow - ensure you completed auth in browser');
|
||||
console.error(' The polling may have timed out or been cancelled');
|
||||
console.error(' Try: ccs qwen --auth --verbose');
|
||||
} else {
|
||||
console.error(' The OAuth flow may have been cancelled or callback port was in use');
|
||||
console.error(` Try: pkill -f cli-proxy-api && ccs ${provider} --auth`);
|
||||
}
|
||||
resolve(false);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { PlatformInfo, SupportedOS, SupportedArch, ArchiveExtension } from './ty
|
||||
* CLIProxyAPI fallback version (used when GitHub API unavailable)
|
||||
* Auto-update fetches latest from GitHub; this is only a safety net
|
||||
*/
|
||||
export const CLIPROXY_FALLBACK_VERSION = '6.5.27';
|
||||
export const CLIPROXY_FALLBACK_VERSION = '6.5.31';
|
||||
|
||||
/** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */
|
||||
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
|
||||
|
||||
Reference in New Issue
Block a user