feat(cliproxy): add version management command

Allow users to install specific versions or update to latest CLIProxy
via 'ccs cliproxy' command with install/update subcommands.
This commit is contained in:
kaitranntt
2025-12-04 06:36:06 -05:00
parent 8cadf4d7fa
commit 7e07615eed
6 changed files with 271 additions and 9 deletions
+7
View File
@@ -282,6 +282,13 @@ async function main(): Promise<void> {
return;
}
// Special case: cliproxy command (manages CLIProxyAPI binary)
if (firstArg === 'cliproxy') {
const { handleCliproxyCommand } = await import('./commands/cliproxy-command');
await handleCliproxyCommand(args.slice(1));
return;
}
// Special case: headless delegation (-p flag)
if (args.includes('-p') || args.includes('--prompt')) {
const { DelegationHandler } = await import('./delegation/delegation-handler');
+55 -9
View File
@@ -63,6 +63,7 @@ const DEFAULT_CONFIG: BinaryManagerConfig = {
binPath: getBinDir(),
maxRetries: 3,
verbose: false,
forceVersion: false,
};
/**
@@ -88,6 +89,12 @@ export class BinaryManager {
if (fs.existsSync(binaryPath)) {
this.log(`Binary exists: ${binaryPath}`);
// Skip auto-update if forceVersion is set (user requested specific version)
if (this.config.forceVersion) {
this.log(`Force version mode: skipping auto-update`);
return this.getBinaryPath();
}
// Check for updates in background (non-blocking for UX)
try {
const updateResult = await this.checkForUpdates();
@@ -114,16 +121,21 @@ export class BinaryManager {
// Download, verify, extract
this.log('Binary not found, downloading...');
// Check latest version before first download
try {
const latestVersion = await this.fetchLatestVersion();
if (latestVersion && this.isNewerVersion(latestVersion, this.config.version)) {
this.log(`Using latest version: ${latestVersion} (instead of ${this.config.version})`);
this.config.version = latestVersion;
// Skip auto-upgrade to latest if forceVersion is set
if (!this.config.forceVersion) {
// Check latest version before first download
try {
const latestVersion = await this.fetchLatestVersion();
if (latestVersion && this.isNewerVersion(latestVersion, this.config.version)) {
this.log(`Using latest version: ${latestVersion} (instead of ${this.config.version})`);
this.config.version = latestVersion;
}
} catch {
// Use pinned version if API fails
this.log(`Using pinned version: ${this.config.version}`);
}
} catch {
// Use pinned version if API fails
this.log(`Using pinned version: ${this.config.version}`);
} else {
this.log(`Force version mode: using specified version ${this.config.version}`);
}
await this.downloadAndInstall();
@@ -916,4 +928,38 @@ export function getInstalledCliproxyVersion(): string {
return CLIPROXY_FALLBACK_VERSION;
}
/**
* Install a specific version of CLIProxyAPI
* Deletes existing binary and downloads the specified version
*
* @param version Version to install (e.g., "6.5.40")
* @param verbose Enable verbose logging
*/
export async function installCliproxyVersion(version: string, verbose = false): Promise<void> {
// Use forceVersion to prevent auto-upgrade to latest
const manager = new BinaryManager({ version, verbose, forceVersion: true });
// Delete existing binary if present
if (manager.isBinaryInstalled()) {
const currentVersion = getInstalledCliproxyVersion();
if (verbose) {
console.log(`[i] Removing existing CLIProxyAPI v${currentVersion}`);
}
manager.deleteBinary();
}
// Install specified version (forceVersion prevents auto-upgrade)
await manager.ensureBinary();
}
/**
* Fetch the latest CLIProxyAPI version from GitHub API
* @returns Latest version string (e.g., "6.5.40")
*/
export async function fetchLatestCliproxyVersion(): Promise<string> {
const manager = new BinaryManager();
const result = await manager.checkForUpdates();
return result.latestVersion;
}
export default BinaryManager;
+2
View File
@@ -41,6 +41,8 @@ export {
isCLIProxyInstalled,
getCLIProxyPath,
getInstalledCliproxyVersion,
installCliproxyVersion,
fetchLatestCliproxyVersion,
} from './binary-manager';
// Config generation
+2
View File
@@ -46,6 +46,8 @@ export interface BinaryManagerConfig {
maxRetries: number;
/** Enable verbose logging */
verbose: boolean;
/** Force specific version (skip auto-upgrade to latest) */
forceVersion?: boolean;
}
/**
+198
View File
@@ -0,0 +1,198 @@
/**
* CLIProxy Command Handler
*
* Manages CLIProxyAPI binary installation and version control.
* Allows users to install specific versions or update to latest.
*
* Usage:
* ccs cliproxy Show current version
* ccs cliproxy --install <ver> Install specific version
* ccs cliproxy --latest Install latest version
* ccs cliproxy --help Show help
*/
import {
getInstalledCliproxyVersion,
installCliproxyVersion,
fetchLatestCliproxyVersion,
isCLIProxyInstalled,
getCLIProxyPath,
} from '../cliproxy';
import { CLIPROXY_FALLBACK_VERSION } from '../cliproxy/platform-detector';
import { color, dim, initUI } from '../utils/ui';
/**
* Show cliproxy command help
*/
function showHelp(): void {
console.log('');
console.log('Usage: ccs cliproxy [options]');
console.log('');
console.log('Manage CLIProxyAPI binary installation.');
console.log('');
console.log('Options:');
console.log(' --install <version> Install a specific version (e.g., 6.5.40)');
console.log(' --latest Install the latest version from GitHub');
console.log(' --verbose, -v Enable verbose output');
console.log(' --help, -h Show this help message');
console.log('');
console.log('Examples:');
console.log(' ccs cliproxy Show current installed version');
console.log(' ccs cliproxy --install 6.5.38 Install version 6.5.38');
console.log(' ccs cliproxy --latest Update to latest version');
console.log('');
console.log('Notes:');
console.log(` Default fallback version: ${CLIPROXY_FALLBACK_VERSION}`);
console.log(' Releases: https://github.com/router-for-me/CLIProxyAPI/releases');
console.log('');
}
/**
* Show current cliproxy status
*/
async function showStatus(verbose: boolean): Promise<void> {
await initUI();
const installed = isCLIProxyInstalled();
const currentVersion = getInstalledCliproxyVersion();
const binaryPath = getCLIProxyPath();
console.log('');
console.log(color('CLIProxyAPI Status', 'primary'));
console.log('');
if (installed) {
console.log(` Installed: ${color('Yes', 'success')}`);
console.log(` Version: ${color(`v${currentVersion}`, 'info')}`);
console.log(` Binary: ${dim(binaryPath)}`);
} else {
console.log(` Installed: ${color('No', 'error')}`);
console.log(` Fallback: ${color(`v${CLIPROXY_FALLBACK_VERSION}`, 'info')}`);
console.log(` ${dim('Run "ccs gemini" or any provider to auto-install')}`);
}
// Try to fetch latest version
try {
console.log('');
console.log(` ${dim('Checking for updates...')}`);
const latestVersion = await fetchLatestCliproxyVersion();
if (latestVersion !== currentVersion) {
console.log(
` Latest: ${color(`v${latestVersion}`, 'success')} ${dim('(update available)')}`
);
console.log('');
console.log(` ${dim(`Run "ccs cliproxy --latest" to update`)}`);
} else {
console.log(` Latest: ${color(`v${latestVersion}`, 'success')} ${dim('(up to date)')}`);
}
} catch (error) {
if (verbose) {
const err = error as Error;
console.log(` Latest: ${dim(`Could not fetch (${err.message})`)}`);
}
}
console.log('');
}
/**
* Install a specific version
*/
async function installVersion(version: string, verbose: boolean): Promise<void> {
// Validate version format (basic semver check)
if (!/^\d+\.\d+\.\d+$/.test(version)) {
console.error('[X] Invalid version format. Expected format: X.Y.Z (e.g., 6.5.40)');
process.exit(1);
}
console.log(`[i] Installing CLIProxyAPI v${version}...`);
console.log('');
try {
await installCliproxyVersion(version, verbose);
console.log('');
console.log(`[OK] CLIProxyAPI v${version} installed successfully`);
} catch (error) {
const err = error as Error;
console.error('');
console.error(`[X] Failed to install CLIProxyAPI v${version}`);
console.error(` ${err.message}`);
console.error('');
console.error('Possible causes:');
console.error(' 1. Version does not exist on GitHub');
console.error(' 2. Network connectivity issues');
console.error(' 3. GitHub API rate limiting');
console.error('');
console.error('Check available versions at:');
console.error(' https://github.com/router-for-me/CLIProxyAPI/releases');
process.exit(1);
}
}
/**
* Install latest version
*/
async function installLatest(verbose: boolean): Promise<void> {
console.log('[i] Fetching latest CLIProxyAPI version...');
try {
const latestVersion = await fetchLatestCliproxyVersion();
const currentVersion = getInstalledCliproxyVersion();
if (isCLIProxyInstalled() && latestVersion === currentVersion) {
console.log(`[OK] Already running latest version: v${latestVersion}`);
return;
}
console.log(`[i] Latest version: v${latestVersion}`);
if (isCLIProxyInstalled()) {
console.log(`[i] Current version: v${currentVersion}`);
}
console.log('');
await installCliproxyVersion(latestVersion, verbose);
console.log('');
console.log(`[OK] CLIProxyAPI updated to v${latestVersion}`);
} catch (error) {
const err = error as Error;
console.error(`[X] Failed to install latest version: ${err.message}`);
process.exit(1);
}
}
/**
* Main cliproxy command handler
*/
export async function handleCliproxyCommand(args: string[]): Promise<void> {
const verbose = args.includes('--verbose') || args.includes('-v');
// Handle --help
if (args.includes('--help') || args.includes('-h')) {
showHelp();
return;
}
// Handle --install <version>
const installIdx = args.indexOf('--install');
if (installIdx !== -1) {
const version = args[installIdx + 1];
if (!version || version.startsWith('-')) {
console.error('[X] Missing version argument for --install');
console.error(' Usage: ccs cliproxy --install <version>');
console.error(' Example: ccs cliproxy --install 6.5.40');
process.exit(1);
}
await installVersion(version, verbose);
return;
}
// Handle --latest
if (args.includes('--latest')) {
await installLatest(verbose);
return;
}
// Default: show status
await showStatus(verbose);
}
+7
View File
@@ -204,6 +204,13 @@ Claude Code Profile & Model Switcher`.trim();
['Settings:', '~/.ccs/*.settings.json'],
]);
// CLI Proxy management
printSubSection('CLI Proxy Management', [
['ccs cliproxy', 'Show CLIProxyAPI status and version'],
['ccs cliproxy --install <ver>', 'Install specific version (e.g., 6.5.40)'],
['ccs cliproxy --latest', 'Update to latest version'],
]);
// CLI Proxy paths
console.log(subheader('CLI Proxy:'));
console.log(` Binary: ${color('~/.ccs/cliproxy/bin/cli-proxy-api', 'path')}`);