mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +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 parent
773225d180
commit
1c3374f6a7
7 files changed
+106
-8
No files matched your search
@@ -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)
|
# [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!
|
# IMPORTANT: Update this version when releasing new versions!
|
||||||
# This hardcoded version is used for standalone installations (irm | iex)
|
# This hardcoded version is used for standalone installations (irm | iex)
|
||||||
# For git installations, VERSION file is read if available
|
# 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
|
# Try to read VERSION file for git installations
|
||||||
if ($ScriptDir) {
|
if ($ScriptDir) {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ fi
|
|||||||
# IMPORTANT: Update this version when releasing new versions!
|
# IMPORTANT: Update this version when releasing new versions!
|
||||||
# This hardcoded version is used for standalone installations (curl | bash)
|
# This hardcoded version is used for standalone installations (curl | bash)
|
||||||
# For git installations, VERSION file is read if available
|
# 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
|
# Try to read VERSION file for git installations
|
||||||
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kaitranntt/ccs",
|
"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",
|
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
|
|||||||
@@ -14,12 +14,72 @@
|
|||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { spawn } from 'child_process';
|
import { spawn, execSync } from 'child_process';
|
||||||
import { ProgressIndicator } from '../utils/progress-indicator';
|
import { ProgressIndicator } from '../utils/progress-indicator';
|
||||||
import { getProviderAuthDir, generateConfig } from './config-generator';
|
import { getProviderAuthDir, generateConfig } from './config-generator';
|
||||||
import { ensureCLIProxyBinary } from './binary-manager';
|
import { ensureCLIProxyBinary } from './binary-manager';
|
||||||
import { CLIProxyProvider } from './types';
|
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)
|
* Detect if running in a headless environment (no browser available)
|
||||||
*/
|
*/
|
||||||
@@ -328,6 +388,16 @@ export async function triggerOAuth(
|
|||||||
const configPath = generateConfig(provider);
|
const configPath = generateConfig(provider);
|
||||||
log(`Config generated: ${configPath}`);
|
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
|
// Build args: config + auth flag + optional --no-browser for headless
|
||||||
const args = ['--config', configPath, oauthConfig.authFlag];
|
const args = ['--config', configPath, oauthConfig.authFlag];
|
||||||
if (headless) {
|
if (headless) {
|
||||||
@@ -428,8 +498,15 @@ export async function triggerOAuth(
|
|||||||
} else {
|
} else {
|
||||||
if (!headless) spinner.fail('Authentication incomplete');
|
if (!headless) spinner.fail('Authentication incomplete');
|
||||||
console.error('[X] Token not found after authentication');
|
console.error('[X] Token not found after authentication');
|
||||||
console.error(' The OAuth flow may have been cancelled or port 8085 was in use');
|
// Qwen uses Device Code Flow (polling), others use Authorization Code Flow (callback)
|
||||||
console.error(' Try: pkill -f cli-proxy-api && ccs gemini --auth');
|
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);
|
resolve(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { PlatformInfo, SupportedOS, SupportedArch, ArchiveExtension } from './ty
|
|||||||
* CLIProxyAPI fallback version (used when GitHub API unavailable)
|
* CLIProxyAPI fallback version (used when GitHub API unavailable)
|
||||||
* Auto-update fetches latest from GitHub; this is only a safety net
|
* 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 */
|
/** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */
|
||||||
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
|
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
|
||||||
|
|||||||
Reference in new issue
Block a user