fix(cliproxy): make backend switching work with version pins and status

- Add backend param to isCLIProxyInstalled(), getCLIProxyPath(),
  getInstalledCliproxyVersion(), installCliproxyVersion()
- Update getBinaryStatus() to pass backend to all helper functions
- Add getBackendLabel() helper for dynamic CLI messages
- Replace hardcoded "CLIProxy Plus" strings with dynamic labels
- Pass --backend flag through install/update command handlers
- Import CLIProxyBackend type from types.ts instead of redefining

Setting `cliproxy.backend: original` in config.yaml now correctly
uses the original backend for version pins and binary operations.
This commit is contained in:
kaitranntt
2026-01-23 10:52:01 -05:00
parent 4fa519d8ae
commit 628148c359
5 changed files with 129 additions and 57 deletions
+31 -15
View File
@@ -26,9 +26,10 @@ import {
getVersionPinPath, getVersionPinPath,
readInstalledVersion, readInstalledVersion,
ensureBinary, ensureBinary,
migrateVersionPin,
} from './binary'; } from './binary';
type CLIProxyBackend = 'original' | 'plus'; import type { CLIProxyBackend } from './types';
/** /**
* Get backend from config or default to 'plus' * Get backend from config or default to 'plus'
@@ -111,7 +112,11 @@ export class BinaryManager {
/** Convenience function respecting version pin */ /** Convenience function respecting version pin */
export async function ensureCLIProxyBinary(verbose = false): Promise<string> { export async function ensureCLIProxyBinary(verbose = false): Promise<string> {
const backend = getConfiguredBackend(); const backend = getConfiguredBackend();
const pinnedVersion = getPinnedVersion();
// Migrate old shared pin to backend-specific location (one-time migration)
migrateVersionPin(backend);
const pinnedVersion = getPinnedVersion(backend);
if (pinnedVersion) { if (pinnedVersion) {
if (verbose) console.error(`[cliproxy] Using pinned version: ${pinnedVersion}`); if (verbose) console.error(`[cliproxy] Using pinned version: ${pinnedVersion}`);
return new BinaryManager( return new BinaryManager(
@@ -127,27 +132,34 @@ export async function ensureCLIProxyBinary(verbose = false): Promise<string> {
} }
/** Check if CLIProxyAPI binary is installed */ /** Check if CLIProxyAPI binary is installed */
export function isCLIProxyInstalled(): boolean { export function isCLIProxyInstalled(backend?: CLIProxyBackend): boolean {
const backend = getConfiguredBackend(); const effectiveBackend = backend ?? getConfiguredBackend();
return new BinaryManager({}, backend).isBinaryInstalled(); return new BinaryManager({}, effectiveBackend).isBinaryInstalled();
} }
/** Get CLIProxyAPI binary path (may not exist) */ /** Get CLIProxyAPI binary path (may not exist) */
export function getCLIProxyPath(): string { export function getCLIProxyPath(backend?: CLIProxyBackend): string {
const backend = getConfiguredBackend(); const effectiveBackend = backend ?? getConfiguredBackend();
return new BinaryManager({}, backend).getBinaryPath(); return new BinaryManager({}, effectiveBackend).getBinaryPath();
} }
/** Get installed CLIProxyAPI version from .version file */ /** Get installed CLIProxyAPI version from .version file */
export function getInstalledCliproxyVersion(): string { export function getInstalledCliproxyVersion(backend?: CLIProxyBackend): string {
const backend = getConfiguredBackend(); const effectiveBackend = backend ?? getConfiguredBackend();
return readInstalledVersion(getBackendBinDir(backend), BACKEND_CONFIG[backend].fallbackVersion); return readInstalledVersion(
getBackendBinDir(effectiveBackend),
BACKEND_CONFIG[effectiveBackend].fallbackVersion
);
} }
/** Install a specific version of CLIProxyAPI */ /** Install a specific version of CLIProxyAPI */
export async function installCliproxyVersion(version: string, verbose = false): Promise<void> { export async function installCliproxyVersion(
const backend = getConfiguredBackend(); version: string,
const manager = new BinaryManager({ version, verbose, forceVersion: true }, backend); verbose = false,
backend?: CLIProxyBackend
): Promise<void> {
const effectiveBackend = backend ?? getConfiguredBackend();
const manager = new BinaryManager({ version, verbose, forceVersion: true }, effectiveBackend);
// Check if proxy is running and stop it first // Check if proxy is running and stop it first
if (isProxyRunning()) { if (isProxyRunning()) {
@@ -165,8 +177,11 @@ export async function installCliproxyVersion(version: string, verbose = false):
} }
if (manager.isBinaryInstalled()) { if (manager.isBinaryInstalled()) {
const label = effectiveBackend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
if (verbose) if (verbose)
console.log(info(`Removing existing CLIProxy Plus v${getInstalledCliproxyVersion()}`)); console.log(
info(`Removing existing ${label} v${getInstalledCliproxyVersion(effectiveBackend)}`)
);
manager.deleteBinary(); manager.deleteBinary();
} }
await manager.ensureBinary(); await manager.ensureBinary();
@@ -223,6 +238,7 @@ export {
savePinnedVersion, savePinnedVersion,
clearPinnedVersion, clearPinnedVersion,
isVersionPinned, isVersionPinned,
migrateVersionPin,
}; };
export default BinaryManager; export default BinaryManager;
+1
View File
@@ -25,6 +25,7 @@ export {
savePinnedVersion, savePinnedVersion,
clearPinnedVersion, clearPinnedVersion,
isVersionPinned, isVersionPinned,
migrateVersionPin,
} from './version-cache'; } from './version-cache';
// Version Checker // Version Checker
+42 -15
View File
@@ -12,6 +12,8 @@ import {
VERSION_PIN_FILE, VERSION_PIN_FILE,
VersionListCache, VersionListCache,
} from './types'; } from './types';
import { DEFAULT_BACKEND } from '../platform-detector';
import type { CLIProxyBackend } from '../types';
/** /**
* Get path to version cache file * Get path to version cache file
@@ -21,10 +23,10 @@ export function getVersionCachePath(): string {
} }
/** /**
* Get path to version pin file * Get path to version pin file (backend-specific)
*/ */
export function getVersionPinPath(): string { export function getVersionPinPath(backend: CLIProxyBackend = DEFAULT_BACKEND): string {
return path.join(getBinDir(), VERSION_PIN_FILE); return path.join(getBinDir(), backend, VERSION_PIN_FILE);
} }
/** /**
@@ -98,10 +100,10 @@ export function writeInstalledVersion(binPath: string, version: string): void {
} }
/** /**
* Get pinned version if one exists * Get pinned version if one exists (backend-specific)
*/ */
export function getPinnedVersion(): string | null { export function getPinnedVersion(backend: CLIProxyBackend = DEFAULT_BACKEND): string | null {
const pinPath = getVersionPinPath(); const pinPath = getVersionPinPath(backend);
if (!fs.existsSync(pinPath)) { if (!fs.existsSync(pinPath)) {
return null; return null;
} }
@@ -113,10 +115,13 @@ export function getPinnedVersion(): string | null {
} }
/** /**
* Save pinned version to persist user's explicit choice * Save pinned version to persist user's explicit choice (backend-specific)
*/ */
export function savePinnedVersion(version: string): void { export function savePinnedVersion(
const pinPath = getVersionPinPath(); version: string,
backend: CLIProxyBackend = DEFAULT_BACKEND
): void {
const pinPath = getVersionPinPath(backend);
try { try {
fs.mkdirSync(path.dirname(pinPath), { recursive: true }); fs.mkdirSync(path.dirname(pinPath), { recursive: true });
fs.writeFileSync(pinPath, version, 'utf8'); fs.writeFileSync(pinPath, version, 'utf8');
@@ -126,10 +131,10 @@ export function savePinnedVersion(version: string): void {
} }
/** /**
* Clear pinned version (unpin) * Clear pinned version (unpin) - backend-specific
*/ */
export function clearPinnedVersion(): void { export function clearPinnedVersion(backend: CLIProxyBackend = DEFAULT_BACKEND): void {
const pinPath = getVersionPinPath(); const pinPath = getVersionPinPath(backend);
if (fs.existsSync(pinPath)) { if (fs.existsSync(pinPath)) {
try { try {
fs.unlinkSync(pinPath); fs.unlinkSync(pinPath);
@@ -140,10 +145,32 @@ export function clearPinnedVersion(): void {
} }
/** /**
* Check if a version is currently pinned * Check if a version is currently pinned (backend-specific)
*/ */
export function isVersionPinned(): boolean { export function isVersionPinned(backend: CLIProxyBackend = DEFAULT_BACKEND): boolean {
return getPinnedVersion() !== null; return getPinnedVersion(backend) !== null;
}
/**
* Migrate old shared version pin to backend-specific location.
* Called once on first run after update.
*/
export function migrateVersionPin(backend: CLIProxyBackend): void {
const oldPinPath = path.join(getBinDir(), VERSION_PIN_FILE);
if (!fs.existsSync(oldPinPath)) return;
try {
const oldVersion = fs.readFileSync(oldPinPath, 'utf8').trim();
if (!oldVersion) return;
// Save to new backend-specific location
savePinnedVersion(oldVersion, backend);
// Delete old shared file
fs.unlinkSync(oldPinPath);
} catch {
// Silent fail - not critical
}
} }
// ==================== Version List Cache ==================== // ==================== Version List Cache ====================
+26 -13
View File
@@ -58,10 +58,10 @@ export function getBinaryStatus(backend?: CLIProxyBackend): BinaryStatusResult {
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND; backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
const backendConfig = BACKEND_CONFIG[effectiveBackend]; const backendConfig = BACKEND_CONFIG[effectiveBackend];
return { return {
installed: isCLIProxyInstalled(), installed: isCLIProxyInstalled(effectiveBackend),
currentVersion: getInstalledCliproxyVersion(), currentVersion: getInstalledCliproxyVersion(effectiveBackend),
pinnedVersion: getPinnedVersion(), pinnedVersion: getPinnedVersion(effectiveBackend),
binaryPath: getCLIProxyPath(), binaryPath: getCLIProxyPath(effectiveBackend),
fallbackVersion: backendConfig.fallbackVersion, fallbackVersion: backendConfig.fallbackVersion,
backend: effectiveBackend, backend: effectiveBackend,
}; };
@@ -100,7 +100,11 @@ export function isValidVersionFormat(version: string): boolean {
/** /**
* Install a specific version and pin it * Install a specific version and pin it
*/ */
export async function installVersion(version: string, verbose = false): Promise<InstallResult> { export async function installVersion(
version: string,
verbose = false,
backend?: CLIProxyBackend
): Promise<InstallResult> {
if (!isValidVersionFormat(version)) { if (!isValidVersionFormat(version)) {
return { return {
success: false, success: false,
@@ -109,9 +113,12 @@ export async function installVersion(version: string, verbose = false): Promise<
}; };
} }
const effectiveBackend =
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
try { try {
await installCliproxyVersion(version, verbose); await installCliproxyVersion(version, verbose, effectiveBackend);
savePinnedVersion(version); savePinnedVersion(version, effectiveBackend);
return { return {
success: true, success: true,
@@ -130,13 +137,19 @@ export async function installVersion(version: string, verbose = false): Promise<
/** /**
* Install latest version and clear any pin * Install latest version and clear any pin
*/ */
export async function installLatest(verbose = false): Promise<InstallResult> { export async function installLatest(
verbose = false,
backend?: CLIProxyBackend
): Promise<InstallResult> {
const effectiveBackend =
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
try { try {
const latestVersion = await fetchLatestCliproxyVersion(); const latestVersion = await fetchLatestCliproxyVersion();
const currentVersion = getInstalledCliproxyVersion(); const currentVersion = getInstalledCliproxyVersion(effectiveBackend);
const wasPinned = isVersionPinned(); const wasPinned = isVersionPinned(effectiveBackend);
if (isCLIProxyInstalled() && latestVersion === currentVersion && !wasPinned) { if (isCLIProxyInstalled(effectiveBackend) && latestVersion === currentVersion && !wasPinned) {
return { return {
success: true, success: true,
version: latestVersion, version: latestVersion,
@@ -144,8 +157,8 @@ export async function installLatest(verbose = false): Promise<InstallResult> {
}; };
} }
await installCliproxyVersion(latestVersion, verbose); await installCliproxyVersion(latestVersion, verbose, effectiveBackend);
clearPinnedVersion(); clearPinnedVersion(effectiveBackend);
return { return {
success: true, success: true,
+29 -14
View File
@@ -111,6 +111,13 @@ function getEffectiveBackend(cliBackend?: CLIProxyBackend): CLIProxyBackend {
return config.cliproxy?.backend ?? DEFAULT_BACKEND; return config.cliproxy?.backend ?? DEFAULT_BACKEND;
} }
/**
* Get display label for backend
*/
function getBackendLabel(backend: CLIProxyBackend): string {
return backend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
}
interface CliproxyProfileArgs { interface CliproxyProfileArgs {
name?: string; name?: string;
provider?: CLIProxyProfileName; provider?: CLIProxyProfileName;
@@ -152,8 +159,10 @@ function formatModelOption(model: ModelEntry): string {
async function handleCreate(args: string[]): Promise<void> { async function handleCreate(args: string[]): Promise<void> {
await initUI(); await initUI();
const { backend } = parseBackendArg(args);
const effectiveBackend = getEffectiveBackend(backend);
const parsedArgs = parseProfileArgs(args); const parsedArgs = parseProfileArgs(args);
console.log(header('Create CLIProxy Plus Variant')); console.log(header(`Create ${getBackendLabel(effectiveBackend)} Variant`));
console.log(''); console.log('');
// Step 1: Profile name // Step 1: Profile name
@@ -292,7 +301,7 @@ async function handleCreate(args: string[]): Promise<void> {
// Create variant // Create variant
console.log(''); console.log('');
console.log(info('Creating CLIProxy Plus variant...')); console.log(info(`Creating ${getBackendLabel(effectiveBackend)} variant...`));
const result = createVariant(name, provider, model, account); const result = createVariant(name, provider, model, account);
if (!result.success) { if (!result.success) {
@@ -530,14 +539,19 @@ async function showStatus(verbose: boolean, backend: CLIProxyBackend): Promise<v
console.log(''); console.log('');
} }
async function handleInstallVersion(version: string, verbose: boolean): Promise<void> { async function handleInstallVersion(
console.log(info(`Installing CLIProxy Plus v${version}...`)); version: string,
verbose: boolean,
backend: CLIProxyBackend
): Promise<void> {
const label = getBackendLabel(backend);
console.log(info(`Installing ${label} v${version}...`));
console.log(''); console.log('');
const result = await installVersion(version, verbose); const result = await installVersion(version, verbose, backend);
if (!result.success) { if (!result.success) {
console.error(''); console.error('');
console.error(fail(`Failed to install CLIProxy Plus v${version}`)); console.error(fail(`Failed to install ${label} v${version}`));
console.error(` ${result.error}`); console.error(` ${result.error}`);
console.error(''); console.error('');
console.error('Possible causes:'); console.error('Possible causes:');
@@ -546,12 +560,12 @@ async function handleInstallVersion(version: string, verbose: boolean): Promise<
console.error(' 3. GitHub API rate limiting'); console.error(' 3. GitHub API rate limiting');
console.error(''); console.error('');
console.error('Check available versions at:'); console.error('Check available versions at:');
console.error(' https://github.com/router-for-me/CLIProxyAPIPlus/releases'); console.error(` https://github.com/${BACKEND_CONFIG[backend].repo}/releases`);
process.exit(1); process.exit(1);
} }
console.log(''); console.log('');
console.log(ok(`CLIProxy Plus v${version} installed (pinned)`)); console.log(ok(`${label} v${version} installed (pinned)`));
console.log(''); console.log('');
console.log(dim('This version will be used until you run:')); console.log(dim('This version will be used until you run:'));
console.log( console.log(
@@ -560,10 +574,11 @@ async function handleInstallVersion(version: string, verbose: boolean): Promise<
console.log(''); console.log('');
} }
async function handleInstallLatest(verbose: boolean): Promise<void> { async function handleInstallLatest(verbose: boolean, backend: CLIProxyBackend): Promise<void> {
console.log(info('Fetching latest CLIProxy Plus version...')); const label = getBackendLabel(backend);
console.log(info(`Fetching latest ${label} version...`));
const result = await installLatest(verbose); const result = await installLatest(verbose, backend);
if (!result.success) { if (!result.success) {
console.error(fail(`Failed to install latest version: ${result.error}`)); console.error(fail(`Failed to install latest version: ${result.error}`));
process.exit(1); process.exit(1);
@@ -575,7 +590,7 @@ async function handleInstallLatest(verbose: boolean): Promise<void> {
} }
console.log(''); console.log('');
console.log(ok(`CLIProxy Plus updated to v${result.version}`)); console.log(ok(`${label} updated to v${result.version}`));
console.log(dim('Auto-update is now enabled.')); console.log(dim('Auto-update is now enabled.'));
console.log(''); console.log('');
} }
@@ -1036,12 +1051,12 @@ export async function handleCliproxyCommand(args: string[]): Promise<void> {
} }
// Strip leading 'v' prefix and whitespace (user may type " v6.6.80-0 ") // Strip leading 'v' prefix and whitespace (user may type " v6.6.80-0 ")
version = version.trim().replace(/^v/, ''); version = version.trim().replace(/^v/, '');
await handleInstallVersion(version, verbose); await handleInstallVersion(version, verbose, effectiveBackend);
return; return;
} }
if (remainingArgs.includes('--latest') || remainingArgs.includes('--update')) { if (remainingArgs.includes('--latest') || remainingArgs.includes('--update')) {
await handleInstallLatest(verbose); await handleInstallLatest(verbose, effectiveBackend);
return; return;
} }