From 1c3374f6a7e4440e299d49b58808c6454b4547c2 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Mon, 1 Dec 2025 05:22:33 -0500 Subject: [PATCH 1/2] 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 --- CHANGELOG.md | 21 ++++++++ VERSION | 2 +- installers/install.ps1 | 2 +- installers/install.sh | 2 +- package.json | 2 +- src/cliproxy/auth-handler.ts | 83 +++++++++++++++++++++++++++++-- src/cliproxy/platform-detector.ts | 2 +- 7 files changed, 106 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f22290ae..6eceee31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/VERSION b/VERSION index 91ff5727..05cda494 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.2.0 +5.3.0-beta.2 diff --git a/installers/install.ps1 b/installers/install.ps1 index 5ffc1688..406e7f4b 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -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) { diff --git a/installers/install.sh b/installers/install.sh index a2682cda..2c859b10 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -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 diff --git a/package.json b/package.json index c749cb3a..cb9cee24 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/cliproxy/auth-handler.ts b/src/cliproxy/auth-handler.ts index 22cbabe6..eb027268 100644 --- a/src/cliproxy/auth-handler.ts +++ b/src/cliproxy/auth-handler.ts @@ -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> = { + 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 { diff --git a/src/cliproxy/platform-detector.ts b/src/cliproxy/platform-detector.ts index 7a5d87ef..28b5ad3b 100644 --- a/src/cliproxy/platform-detector.ts +++ b/src/cliproxy/platform-detector.ts @@ -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; From 7f3f1fc34ac0e8f5afc7b386f6608b1e3aa7d2bd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 1 Dec 2025 10:23:00 +0000 Subject: [PATCH 2/2] chore(release): 5.2.1 [skip ci] ## [5.2.1](https://github.com/kaitranntt/ccs/compare/v5.2.0...v5.2.1) (2025-12-01) ### Bug Fixes * **cliproxy:** improve qwen oauth error handling ([#33](https://github.com/kaitranntt/ccs/issues/33)) ([1c3374f](https://github.com/kaitranntt/ccs/commit/1c3374f6a7e4440e299d49b58808c6454b4547c2)), closes [#29](https://github.com/kaitranntt/ccs/issues/29) --- CHANGELOG.md | 7 +++++++ VERSION | 2 +- installers/install.ps1 | 2 +- installers/install.sh | 2 +- package.json | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eceee31..1ef7fd1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [5.2.1](https://github.com/kaitranntt/ccs/compare/v5.2.0...v5.2.1) (2025-12-01) + + +### Bug Fixes + +* **cliproxy:** improve qwen oauth error handling ([#33](https://github.com/kaitranntt/ccs/issues/33)) ([1c3374f](https://github.com/kaitranntt/ccs/commit/1c3374f6a7e4440e299d49b58808c6454b4547c2)), closes [#29](https://github.com/kaitranntt/ccs/issues/29) + # [5.3.0-beta.2](https://github.com/kaitranntt/ccs/compare/v5.3.0-beta.1...v5.3.0-beta.2) (2025-12-01) diff --git a/VERSION b/VERSION index 05cda494..26d99a28 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.3.0-beta.2 +5.2.1 diff --git a/installers/install.ps1 b/installers/install.ps1 index 406e7f4b..be14a02a 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -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.3.0-beta.2" +$CcsVersion = "5.2.1" # Try to read VERSION file for git installations if ($ScriptDir) { diff --git a/installers/install.sh b/installers/install.sh index 2c859b10..6861ac20 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -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.3.0-beta.2" +CCS_VERSION="5.2.1" # Try to read VERSION file for git installations if [[ -f "$SCRIPT_DIR/VERSION" ]]; then diff --git a/package.json b/package.json index cb9cee24..0b4060b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "5.3.0-beta.2", + "version": "5.2.1", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli",