mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
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:
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,6 +41,8 @@ export {
|
||||
isCLIProxyInstalled,
|
||||
getCLIProxyPath,
|
||||
getInstalledCliproxyVersion,
|
||||
installCliproxyVersion,
|
||||
fetchLatestCliproxyVersion,
|
||||
} from './binary-manager';
|
||||
|
||||
// Config generation
|
||||
|
||||
@@ -46,6 +46,8 @@ export interface BinaryManagerConfig {
|
||||
maxRetries: number;
|
||||
/** Enable verbose logging */
|
||||
verbose: boolean;
|
||||
/** Force specific version (skip auto-upgrade to latest) */
|
||||
forceVersion?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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')}`);
|
||||
|
||||
Reference in New Issue
Block a user