mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 10:19:37 +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;
|
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)
|
// Special case: headless delegation (-p flag)
|
||||||
if (args.includes('-p') || args.includes('--prompt')) {
|
if (args.includes('-p') || args.includes('--prompt')) {
|
||||||
const { DelegationHandler } = await import('./delegation/delegation-handler');
|
const { DelegationHandler } = await import('./delegation/delegation-handler');
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const DEFAULT_CONFIG: BinaryManagerConfig = {
|
|||||||
binPath: getBinDir(),
|
binPath: getBinDir(),
|
||||||
maxRetries: 3,
|
maxRetries: 3,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
forceVersion: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +89,12 @@ export class BinaryManager {
|
|||||||
if (fs.existsSync(binaryPath)) {
|
if (fs.existsSync(binaryPath)) {
|
||||||
this.log(`Binary exists: ${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)
|
// Check for updates in background (non-blocking for UX)
|
||||||
try {
|
try {
|
||||||
const updateResult = await this.checkForUpdates();
|
const updateResult = await this.checkForUpdates();
|
||||||
@@ -114,16 +121,21 @@ export class BinaryManager {
|
|||||||
// Download, verify, extract
|
// Download, verify, extract
|
||||||
this.log('Binary not found, downloading...');
|
this.log('Binary not found, downloading...');
|
||||||
|
|
||||||
// Check latest version before first download
|
// Skip auto-upgrade to latest if forceVersion is set
|
||||||
try {
|
if (!this.config.forceVersion) {
|
||||||
const latestVersion = await this.fetchLatestVersion();
|
// Check latest version before first download
|
||||||
if (latestVersion && this.isNewerVersion(latestVersion, this.config.version)) {
|
try {
|
||||||
this.log(`Using latest version: ${latestVersion} (instead of ${this.config.version})`);
|
const latestVersion = await this.fetchLatestVersion();
|
||||||
this.config.version = latestVersion;
|
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 {
|
} else {
|
||||||
// Use pinned version if API fails
|
this.log(`Force version mode: using specified version ${this.config.version}`);
|
||||||
this.log(`Using pinned version: ${this.config.version}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.downloadAndInstall();
|
await this.downloadAndInstall();
|
||||||
@@ -916,4 +928,38 @@ export function getInstalledCliproxyVersion(): string {
|
|||||||
return CLIPROXY_FALLBACK_VERSION;
|
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;
|
export default BinaryManager;
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export {
|
|||||||
isCLIProxyInstalled,
|
isCLIProxyInstalled,
|
||||||
getCLIProxyPath,
|
getCLIProxyPath,
|
||||||
getInstalledCliproxyVersion,
|
getInstalledCliproxyVersion,
|
||||||
|
installCliproxyVersion,
|
||||||
|
fetchLatestCliproxyVersion,
|
||||||
} from './binary-manager';
|
} from './binary-manager';
|
||||||
|
|
||||||
// Config generation
|
// Config generation
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export interface BinaryManagerConfig {
|
|||||||
maxRetries: number;
|
maxRetries: number;
|
||||||
/** Enable verbose logging */
|
/** Enable verbose logging */
|
||||||
verbose: boolean;
|
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'],
|
['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
|
// CLI Proxy paths
|
||||||
console.log(subheader('CLI Proxy:'));
|
console.log(subheader('CLI Proxy:'));
|
||||||
console.log(` Binary: ${color('~/.ccs/cliproxy/bin/cli-proxy-api', 'path')}`);
|
console.log(` Binary: ${color('~/.ccs/cliproxy/bin/cli-proxy-api', 'path')}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user