mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-10 00:17:17 +00:00
hotfix: preserve plus fallback state and guard variant updates
This commit is contained in:
@@ -5,10 +5,17 @@
|
|||||||
* Pattern: Mirrors npm install behavior (fast check, download only when needed)
|
* Pattern: Mirrors npm install behavior (fast check, download only when needed)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
import { info, warn } from '../utils/ui';
|
import { info, warn } from '../utils/ui';
|
||||||
import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config-generator';
|
import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config-generator';
|
||||||
import { BinaryInfo, BinaryManagerConfig } from './types';
|
import { BinaryInfo, BinaryManagerConfig } from './types';
|
||||||
import { BACKEND_CONFIG, DEFAULT_BACKEND, CLIPROXY_MAX_STABLE_VERSION } from './platform-detector';
|
import {
|
||||||
|
BACKEND_CONFIG,
|
||||||
|
DEFAULT_BACKEND,
|
||||||
|
CLIPROXY_MAX_STABLE_VERSION,
|
||||||
|
getExecutableName,
|
||||||
|
} from './platform-detector';
|
||||||
import { stopProxy } from './services/proxy-lifecycle-service';
|
import { stopProxy } from './services/proxy-lifecycle-service';
|
||||||
import { waitForPortFree } from '../utils/port-utils';
|
import { waitForPortFree } from '../utils/port-utils';
|
||||||
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||||
@@ -16,6 +23,7 @@ import {
|
|||||||
UpdateCheckResult,
|
UpdateCheckResult,
|
||||||
checkForUpdates,
|
checkForUpdates,
|
||||||
deleteBinary,
|
deleteBinary,
|
||||||
|
getVersionCachePath,
|
||||||
getBinaryPath,
|
getBinaryPath,
|
||||||
isBinaryInstalled,
|
isBinaryInstalled,
|
||||||
getBinaryInfo,
|
getBinaryInfo,
|
||||||
@@ -30,6 +38,7 @@ import {
|
|||||||
} from './binary';
|
} from './binary';
|
||||||
|
|
||||||
import type { CLIProxyBackend } from './types';
|
import type { CLIProxyBackend } from './types';
|
||||||
|
import { getVersionListCachePath } from './binary/version-cache';
|
||||||
|
|
||||||
export const CLIPROXY_PLUS_TRACKING_URL = 'https://github.com/kaitranntt/ccs/issues/1062';
|
export const CLIPROXY_PLUS_TRACKING_URL = 'https://github.com/kaitranntt/ccs/issues/1062';
|
||||||
|
|
||||||
@@ -72,6 +81,41 @@ export function resolveLocalBackend(
|
|||||||
return 'original';
|
return 'original';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyFallbackStateIfMissing(sourcePath: string, targetPath: string): void {
|
||||||
|
if (!fs.existsSync(sourcePath) || fs.existsSync(targetPath)) return;
|
||||||
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||||
|
fs.copyFileSync(sourcePath, targetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncPlusFallbackStateIfNeeded(configuredBackend: CLIProxyBackend): void {
|
||||||
|
if (configuredBackend !== 'plus') return;
|
||||||
|
|
||||||
|
const plusDir = getBackendBinDir('plus');
|
||||||
|
const originalDir = getBackendBinDir('original');
|
||||||
|
|
||||||
|
copyFallbackStateIfMissing(
|
||||||
|
path.join(plusDir, getExecutableName('plus')),
|
||||||
|
path.join(originalDir, getExecutableName('original'))
|
||||||
|
);
|
||||||
|
copyFallbackStateIfMissing(path.join(plusDir, '.version'), path.join(originalDir, '.version'));
|
||||||
|
copyFallbackStateIfMissing(getVersionPinPath('plus'), getVersionPinPath('original'));
|
||||||
|
copyFallbackStateIfMissing(getVersionCachePath('plus'), getVersionCachePath('original'));
|
||||||
|
copyFallbackStateIfMissing(getVersionListCachePath('plus'), getVersionListCachePath('original'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfiguredOrDefaultBackend(): CLIProxyBackend {
|
||||||
|
try {
|
||||||
|
const config = loadOrCreateUnifiedConfig();
|
||||||
|
return config.cliproxy?.backend || DEFAULT_BACKEND;
|
||||||
|
} catch {
|
||||||
|
return DEFAULT_BACKEND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStoredConfiguredBackend(): CLIProxyBackend {
|
||||||
|
return getConfiguredOrDefaultBackend();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get backend from config, with runtime fallback to 'original' when the user
|
* Get backend from config, with runtime fallback to 'original' when the user
|
||||||
* still has `backend: plus` saved.
|
* still has `backend: plus` saved.
|
||||||
@@ -83,15 +127,7 @@ export function resolveLocalBackend(
|
|||||||
* reconfig step, while CCS self-maintains its own Plus fork (future work).
|
* reconfig step, while CCS self-maintains its own Plus fork (future work).
|
||||||
*/
|
*/
|
||||||
export function getConfiguredBackend(options: { warnOnFallback?: boolean } = {}): CLIProxyBackend {
|
export function getConfiguredBackend(options: { warnOnFallback?: boolean } = {}): CLIProxyBackend {
|
||||||
let configured: CLIProxyBackend;
|
return resolveLocalBackend(getConfiguredOrDefaultBackend(), options);
|
||||||
try {
|
|
||||||
const config = loadOrCreateUnifiedConfig();
|
|
||||||
configured = config.cliproxy?.backend || DEFAULT_BACKEND;
|
|
||||||
} catch {
|
|
||||||
return DEFAULT_BACKEND;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolveLocalBackend(configured, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,9 +163,9 @@ export class BinaryManager {
|
|||||||
private backend: CLIProxyBackend;
|
private backend: CLIProxyBackend;
|
||||||
|
|
||||||
constructor(config: Partial<BinaryManagerConfig> = {}, backend?: CLIProxyBackend) {
|
constructor(config: Partial<BinaryManagerConfig> = {}, backend?: CLIProxyBackend) {
|
||||||
this.backend = backend
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
? resolveLocalBackend(backend, { warnOnFallback: true })
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
: getConfiguredBackend({ warnOnFallback: true });
|
this.backend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
const defaultConfig = createDefaultConfig(this.backend);
|
const defaultConfig = createDefaultConfig(this.backend);
|
||||||
this.config = { ...defaultConfig, ...config };
|
this.config = { ...defaultConfig, ...config };
|
||||||
}
|
}
|
||||||
@@ -180,7 +216,9 @@ export async function ensureCLIProxyBinary(
|
|||||||
verbose = false,
|
verbose = false,
|
||||||
options: EnsureCLIProxyBinaryOptions = {}
|
options: EnsureCLIProxyBinaryOptions = {}
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const backend = getConfiguredBackend({ warnOnFallback: true });
|
const configuredBackend = getConfiguredOrDefaultBackend();
|
||||||
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
|
const backend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
|
|
||||||
// Migrate old shared pin to backend-specific location (one-time migration)
|
// Migrate old shared pin to backend-specific location (one-time migration)
|
||||||
migrateVersionPin(backend);
|
migrateVersionPin(backend);
|
||||||
@@ -211,25 +249,25 @@ export async function ensureCLIProxyBinary(
|
|||||||
|
|
||||||
/** Check if CLIProxyAPI binary is installed */
|
/** Check if CLIProxyAPI binary is installed */
|
||||||
export function isCLIProxyInstalled(backend?: CLIProxyBackend): boolean {
|
export function isCLIProxyInstalled(backend?: CLIProxyBackend): boolean {
|
||||||
const effectiveBackend = backend
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
? resolveLocalBackend(backend, { warnOnFallback: true })
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
: getConfiguredBackend({ warnOnFallback: true });
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
return new BinaryManager({}, effectiveBackend).isBinaryInstalled();
|
return new BinaryManager({}, effectiveBackend).isBinaryInstalled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get CLIProxyAPI binary path (may not exist) */
|
/** Get CLIProxyAPI binary path (may not exist) */
|
||||||
export function getCLIProxyPath(backend?: CLIProxyBackend): string {
|
export function getCLIProxyPath(backend?: CLIProxyBackend): string {
|
||||||
const effectiveBackend = backend
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
? resolveLocalBackend(backend, { warnOnFallback: true })
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
: getConfiguredBackend({ warnOnFallback: true });
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
return new BinaryManager({}, effectiveBackend).getBinaryPath();
|
return new BinaryManager({}, effectiveBackend).getBinaryPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get installed CLIProxyAPI version from .version file */
|
/** Get installed CLIProxyAPI version from .version file */
|
||||||
export function getInstalledCliproxyVersion(backend?: CLIProxyBackend): string {
|
export function getInstalledCliproxyVersion(backend?: CLIProxyBackend): string {
|
||||||
const effectiveBackend = backend
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
? resolveLocalBackend(backend, { warnOnFallback: true })
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
: getConfiguredBackend({ warnOnFallback: true });
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
return readInstalledVersion(
|
return readInstalledVersion(
|
||||||
getBackendBinDir(effectiveBackend),
|
getBackendBinDir(effectiveBackend),
|
||||||
BACKEND_CONFIG[effectiveBackend].fallbackVersion
|
BACKEND_CONFIG[effectiveBackend].fallbackVersion
|
||||||
@@ -255,9 +293,9 @@ export async function installCliproxyVersion(
|
|||||||
backend?: CLIProxyBackend,
|
backend?: CLIProxyBackend,
|
||||||
deps: InstallCliproxyVersionDeps = {}
|
deps: InstallCliproxyVersionDeps = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const effectiveBackend = backend
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
? resolveLocalBackend(backend, { warnOnFallback: true })
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
: getConfiguredBackend({ warnOnFallback: true });
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
const manager =
|
const manager =
|
||||||
deps.createManager?.({ version, verbose, forceVersion: true }, effectiveBackend) ??
|
deps.createManager?.({ version, verbose, forceVersion: true }, effectiveBackend) ??
|
||||||
new BinaryManager({ version, verbose, forceVersion: true }, effectiveBackend);
|
new BinaryManager({ version, verbose, forceVersion: true }, effectiveBackend);
|
||||||
@@ -298,9 +336,9 @@ export async function installCliproxyVersion(
|
|||||||
|
|
||||||
/** Fetch the latest CLIProxyAPI version from GitHub API */
|
/** Fetch the latest CLIProxyAPI version from GitHub API */
|
||||||
export async function fetchLatestCliproxyVersion(backend?: CLIProxyBackend): Promise<string> {
|
export async function fetchLatestCliproxyVersion(backend?: CLIProxyBackend): Promise<string> {
|
||||||
const effectiveBackend = backend
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
? resolveLocalBackend(backend, { warnOnFallback: true })
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
: getConfiguredBackend({ warnOnFallback: true });
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
|
const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
|
||||||
return result.latestVersion;
|
return result.latestVersion;
|
||||||
}
|
}
|
||||||
@@ -325,9 +363,9 @@ export interface CliproxyUpdateCheckResult {
|
|||||||
export async function checkCliproxyUpdate(
|
export async function checkCliproxyUpdate(
|
||||||
backend?: CLIProxyBackend
|
backend?: CLIProxyBackend
|
||||||
): Promise<CliproxyUpdateCheckResult> {
|
): Promise<CliproxyUpdateCheckResult> {
|
||||||
const effectiveBackend = backend
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
? resolveLocalBackend(backend, { warnOnFallback: true })
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
: getConfiguredBackend({ warnOnFallback: true });
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
|
const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
|
||||||
|
|
||||||
// Import isNewerVersion for stability check
|
// Import isNewerVersion for stability check
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
clearPinnedVersion,
|
clearPinnedVersion,
|
||||||
isVersionPinned,
|
isVersionPinned,
|
||||||
resolveLocalBackend,
|
resolveLocalBackend,
|
||||||
|
syncPlusFallbackStateIfNeeded,
|
||||||
} from '../binary-manager';
|
} from '../binary-manager';
|
||||||
import { BACKEND_CONFIG, DEFAULT_BACKEND } from '../platform-detector';
|
import { BACKEND_CONFIG, DEFAULT_BACKEND } from '../platform-detector';
|
||||||
import { CLIProxyBackend } from '../types';
|
import { CLIProxyBackend } from '../types';
|
||||||
@@ -55,10 +56,10 @@ export interface LatestVersionResult {
|
|||||||
* Get current binary status for a specific backend
|
* Get current binary status for a specific backend
|
||||||
*/
|
*/
|
||||||
export function getBinaryStatus(backend?: CLIProxyBackend): BinaryStatusResult {
|
export function getBinaryStatus(backend?: CLIProxyBackend): BinaryStatusResult {
|
||||||
const effectiveBackend = resolveLocalBackend(
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND,
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
{ warnOnFallback: true }
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
const backendConfig = BACKEND_CONFIG[effectiveBackend];
|
const backendConfig = BACKEND_CONFIG[effectiveBackend];
|
||||||
return {
|
return {
|
||||||
installed: isCLIProxyInstalled(effectiveBackend),
|
installed: isCLIProxyInstalled(effectiveBackend),
|
||||||
@@ -74,10 +75,10 @@ export function getBinaryStatus(backend?: CLIProxyBackend): BinaryStatusResult {
|
|||||||
* Check for latest version
|
* Check for latest version
|
||||||
*/
|
*/
|
||||||
export async function checkLatestVersion(backend?: CLIProxyBackend): Promise<LatestVersionResult> {
|
export async function checkLatestVersion(backend?: CLIProxyBackend): Promise<LatestVersionResult> {
|
||||||
const effectiveBackend = resolveLocalBackend(
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND,
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
{ warnOnFallback: true }
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use checkCliproxyUpdate which is backend-aware (uses correct GitHub repo)
|
// Use checkCliproxyUpdate which is backend-aware (uses correct GitHub repo)
|
||||||
@@ -124,10 +125,10 @@ export async function installVersion(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const effectiveBackend = resolveLocalBackend(
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND,
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
{ warnOnFallback: true }
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await installCliproxyVersion(version, verbose, effectiveBackend);
|
await installCliproxyVersion(version, verbose, effectiveBackend);
|
||||||
@@ -154,10 +155,10 @@ export async function installLatest(
|
|||||||
verbose = false,
|
verbose = false,
|
||||||
backend?: CLIProxyBackend
|
backend?: CLIProxyBackend
|
||||||
): Promise<InstallResult> {
|
): Promise<InstallResult> {
|
||||||
const effectiveBackend = resolveLocalBackend(
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND,
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
{ warnOnFallback: true }
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const latestVersion = await fetchLatestCliproxyVersion(effectiveBackend);
|
const latestVersion = await fetchLatestCliproxyVersion(effectiveBackend);
|
||||||
@@ -193,10 +194,10 @@ export async function installLatest(
|
|||||||
* Check if a version is pinned
|
* Check if a version is pinned
|
||||||
*/
|
*/
|
||||||
export function isPinned(backend?: CLIProxyBackend): boolean {
|
export function isPinned(backend?: CLIProxyBackend): boolean {
|
||||||
const effectiveBackend = resolveLocalBackend(
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND,
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
{ warnOnFallback: true }
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
return isVersionPinned(effectiveBackend);
|
return isVersionPinned(effectiveBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,10 +205,10 @@ export function isPinned(backend?: CLIProxyBackend): boolean {
|
|||||||
* Get pinned version if any
|
* Get pinned version if any
|
||||||
*/
|
*/
|
||||||
export function getPinned(backend?: CLIProxyBackend): string | null {
|
export function getPinned(backend?: CLIProxyBackend): string | null {
|
||||||
const effectiveBackend = resolveLocalBackend(
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND,
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
{ warnOnFallback: true }
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
return getPinnedVersion(effectiveBackend);
|
return getPinnedVersion(effectiveBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,9 +216,9 @@ export function getPinned(backend?: CLIProxyBackend): string | null {
|
|||||||
* Clear version pin
|
* Clear version pin
|
||||||
*/
|
*/
|
||||||
export function clearPin(backend?: CLIProxyBackend): void {
|
export function clearPin(backend?: CLIProxyBackend): void {
|
||||||
const effectiveBackend = resolveLocalBackend(
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND,
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
{ warnOnFallback: true }
|
syncPlusFallbackStateIfNeeded(configuredBackend);
|
||||||
);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
||||||
clearPinnedVersion(effectiveBackend);
|
clearPinnedVersion(effectiveBackend);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,6 +278,13 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (updates.provider !== undefined) {
|
||||||
|
const backendError = validateProviderBackend(updates.provider);
|
||||||
|
if (backendError) {
|
||||||
|
return { success: false, error: backendError };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update settings file
|
// Update settings file
|
||||||
if (existing.settings) {
|
if (existing.settings) {
|
||||||
const settingsPath = existing.settings.replace(/^~/, os.homedir());
|
const settingsPath = existing.settings.replace(/^~/, os.homedir());
|
||||||
@@ -300,14 +307,6 @@ export function updateVariant(name: string, updates: UpdateVariantOptions): Vari
|
|||||||
// Update config entry if provider/account/target changed
|
// Update config entry if provider/account/target changed
|
||||||
if (updates.provider !== undefined || updates.account !== undefined || targetChanged) {
|
if (updates.provider !== undefined || updates.account !== undefined || targetChanged) {
|
||||||
const newProvider = updates.provider ?? existing.provider;
|
const newProvider = updates.provider ?? existing.provider;
|
||||||
|
|
||||||
// Validate provider/backend compatibility on provider change
|
|
||||||
if (updates.provider !== undefined) {
|
|
||||||
const backendError = validateProviderBackend(updates.provider);
|
|
||||||
if (backendError) {
|
|
||||||
return { success: false, error: backendError };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const newAccount = updates.account !== undefined ? updates.account : existing.account;
|
const newAccount = updates.account !== undefined ? updates.account : existing.account;
|
||||||
const newTarget = updates.target ?? existingTarget;
|
const newTarget = updates.target ?? existingTarget;
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,10 @@ export async function showHelp(): Promise<void> {
|
|||||||
[
|
[
|
||||||
'Options:',
|
'Options:',
|
||||||
[
|
[
|
||||||
['--backend <type>', 'Use specific backend: original | plus (default: from config)'],
|
[
|
||||||
|
'--backend <type>',
|
||||||
|
'Use specific backend: original | plus (local default: original; plus currently falls back locally)',
|
||||||
|
],
|
||||||
['--target <cli>', 'Default target for created/edited variants: claude | droid'],
|
['--target <cli>', 'Default target for created/edited variants: claude | droid'],
|
||||||
['--verbose, -v', 'Show detailed diagnostics including routing hints and quota fetches'],
|
['--verbose, -v', 'Show detailed diagnostics including routing hints and quota fetches'],
|
||||||
],
|
],
|
||||||
@@ -102,6 +105,11 @@ export async function showHelp(): Promise<void> {
|
|||||||
|
|
||||||
console.log(dim(' Note: CLIProxy now persists by default. Use "stop" to terminate.'));
|
console.log(dim(' Note: CLIProxy now persists by default. Use "stop" to terminate.'));
|
||||||
console.log(dim(' Routing: use gcli/<model> or agy/<model> to keep overlapping models pinned.'));
|
console.log(dim(' Routing: use gcli/<model> or agy/<model> to keep overlapping models pinned.'));
|
||||||
|
console.log(
|
||||||
|
dim(
|
||||||
|
' Backend: local CLIProxy currently uses original by default; saved plus configs fall back locally.'
|
||||||
|
)
|
||||||
|
);
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(subheader('Notes:'));
|
console.log(subheader('Notes:'));
|
||||||
console.log(` Default fallback version: ${color(getFallbackVersion(), 'info')}`);
|
console.log(` Default fallback version: ${color(getFallbackVersion(), 'info')}`);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CLIProxyBackend } from '../../cliproxy/types';
|
import { CLIProxyBackend } from '../../cliproxy/types';
|
||||||
import { getConfiguredBackend, resolveLocalBackend } from '../../cliproxy/binary-manager';
|
import { getStoredConfiguredBackend } from '../../cliproxy/binary-manager';
|
||||||
import {
|
import {
|
||||||
type QuotaSupportedProvider,
|
type QuotaSupportedProvider,
|
||||||
QUOTA_PROVIDER_HELP_TEXT,
|
QUOTA_PROVIDER_HELP_TEXT,
|
||||||
@@ -70,13 +70,10 @@ function parseBackendArg(args: string[]): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get effective backend (CLI flag > config.yaml > default)
|
* Get selected backend input (CLI flag > config.yaml > default)
|
||||||
*/
|
*/
|
||||||
function getEffectiveBackend(cliBackend?: CLIProxyBackend): CLIProxyBackend {
|
function getEffectiveBackend(cliBackend?: CLIProxyBackend): CLIProxyBackend {
|
||||||
if (cliBackend) {
|
return cliBackend ?? getStoredConfiguredBackend();
|
||||||
return resolveLocalBackend(cliBackend, { warnOnFallback: true });
|
|
||||||
}
|
|
||||||
return getConfiguredBackend({ warnOnFallback: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import { getProxyStatus as getProxyProcessStatus, stopProxy } from '../../clipro
|
|||||||
import { ensureCliproxyService } from '../../cliproxy/service-manager';
|
import { ensureCliproxyService } from '../../cliproxy/service-manager';
|
||||||
import {
|
import {
|
||||||
checkCliproxyUpdate,
|
checkCliproxyUpdate,
|
||||||
getConfiguredBackend,
|
|
||||||
getInstalledCliproxyVersion,
|
getInstalledCliproxyVersion,
|
||||||
|
getStoredConfiguredBackend,
|
||||||
} from '../../cliproxy/binary-manager';
|
} from '../../cliproxy/binary-manager';
|
||||||
import {
|
import {
|
||||||
fetchAllVersions,
|
fetchAllVersions,
|
||||||
@@ -146,7 +146,7 @@ export function shouldCacheQuotaResult(result: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildUpdateCheckFallback(
|
function buildUpdateCheckFallback(
|
||||||
backend: ReturnType<typeof getConfiguredBackend>,
|
backend: ReturnType<typeof getStoredConfiguredBackend>,
|
||||||
getInstalledVersionFn: typeof getInstalledCliproxyVersion = getInstalledCliproxyVersion
|
getInstalledVersionFn: typeof getInstalledCliproxyVersion = getInstalledCliproxyVersion
|
||||||
) {
|
) {
|
||||||
const currentVersion = getInstalledVersionFn(backend);
|
const currentVersion = getInstalledVersionFn(backend);
|
||||||
@@ -170,7 +170,7 @@ function buildUpdateCheckFallback(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildVersionsFallback(
|
function buildVersionsFallback(
|
||||||
backend: ReturnType<typeof getConfiguredBackend>,
|
backend: ReturnType<typeof getStoredConfiguredBackend>,
|
||||||
getInstalledVersionFn: typeof getInstalledCliproxyVersion = getInstalledCliproxyVersion
|
getInstalledVersionFn: typeof getInstalledCliproxyVersion = getInstalledCliproxyVersion
|
||||||
) {
|
) {
|
||||||
const currentVersion = getInstalledVersionFn(backend);
|
const currentVersion = getInstalledVersionFn(backend);
|
||||||
@@ -198,7 +198,7 @@ interface ResolveVersionsDeps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveCliproxyUpdateCheckPayload(
|
export async function resolveCliproxyUpdateCheckPayload(
|
||||||
backend: ReturnType<typeof getConfiguredBackend>,
|
backend: ReturnType<typeof getStoredConfiguredBackend>,
|
||||||
deps: ResolveUpdateCheckDeps = {}
|
deps: ResolveUpdateCheckDeps = {}
|
||||||
) {
|
) {
|
||||||
const checkCliproxyUpdateFn = deps.checkCliproxyUpdateFn ?? checkCliproxyUpdate;
|
const checkCliproxyUpdateFn = deps.checkCliproxyUpdateFn ?? checkCliproxyUpdate;
|
||||||
@@ -210,7 +210,7 @@ export async function resolveCliproxyUpdateCheckPayload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveCliproxyVersionsPayload(
|
export async function resolveCliproxyVersionsPayload(
|
||||||
backend: ReturnType<typeof getConfiguredBackend>,
|
backend: ReturnType<typeof getStoredConfiguredBackend>,
|
||||||
deps: ResolveVersionsDeps = {}
|
deps: ResolveVersionsDeps = {}
|
||||||
) {
|
) {
|
||||||
const fetchAllVersionsFn = deps.fetchAllVersionsFn ?? fetchAllVersions;
|
const fetchAllVersionsFn = deps.fetchAllVersionsFn ?? fetchAllVersions;
|
||||||
@@ -401,7 +401,7 @@ router.post('/proxy-stop', async (_req: Request, res: Response): Promise<void> =
|
|||||||
*/
|
*/
|
||||||
router.get('/update-check', async (_req: Request, res: Response): Promise<void> => {
|
router.get('/update-check', async (_req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const backend = getConfiguredBackend({ warnOnFallback: true });
|
const backend = getStoredConfiguredBackend();
|
||||||
const result = await resolveCliproxyUpdateCheckPayload(backend);
|
const result = await resolveCliproxyUpdateCheckPayload(backend);
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
@@ -1011,7 +1011,7 @@ router.get('/quota/:provider/:accountId', async (req: Request, res: Response): P
|
|||||||
*/
|
*/
|
||||||
router.get('/versions', async (_req: Request, res: Response): Promise<void> => {
|
router.get('/versions', async (_req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const backend = getConfiguredBackend({ warnOnFallback: true });
|
const backend = getStoredConfiguredBackend();
|
||||||
res.json(await resolveCliproxyVersionsPayload(backend));
|
res.json(await resolveCliproxyVersionsPayload(backend));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[cliproxy-stats] ${(error as Error).message}`);
|
console.error(`[cliproxy-stats] ${(error as Error).message}`);
|
||||||
@@ -1065,7 +1065,7 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const backend = getConfiguredBackend({ warnOnFallback: true });
|
const backend = getStoredConfiguredBackend();
|
||||||
const installResult = await installDashboardCliproxyVersion(version, backend);
|
const installResult = await installDashboardCliproxyVersion(version, backend);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { installCliproxyVersion, resolveLocalBackend } from '../../cliproxy/binary-manager';
|
import {
|
||||||
|
installCliproxyVersion,
|
||||||
|
resolveLocalBackend,
|
||||||
|
syncPlusFallbackStateIfNeeded,
|
||||||
|
} from '../../cliproxy/binary-manager';
|
||||||
import { ensureCliproxyService, type ServiceStartResult } from '../../cliproxy/service-manager';
|
import { ensureCliproxyService, type ServiceStartResult } from '../../cliproxy/service-manager';
|
||||||
import { getProxyStatus as getProxyProcessStatus } from '../../cliproxy/session-tracker';
|
import { getProxyStatus as getProxyProcessStatus } from '../../cliproxy/session-tracker';
|
||||||
import { isCliproxyRunning } from '../../cliproxy/stats-fetcher';
|
import { isCliproxyRunning } from '../../cliproxy/stats-fetcher';
|
||||||
@@ -52,6 +56,7 @@ export async function installDashboardCliproxyVersion(
|
|||||||
backend: CLIProxyBackend,
|
backend: CLIProxyBackend,
|
||||||
deps: InstallDashboardCliproxyVersionDeps = defaultDeps
|
deps: InstallDashboardCliproxyVersionDeps = defaultDeps
|
||||||
): Promise<DashboardCliproxyInstallResult> {
|
): Promise<DashboardCliproxyInstallResult> {
|
||||||
|
syncPlusFallbackStateIfNeeded(backend);
|
||||||
const effectiveBackend = resolveLocalBackend(backend, { warnOnFallback: true });
|
const effectiveBackend = resolveLocalBackend(backend, { warnOnFallback: true });
|
||||||
const backendLabel = effectiveBackend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
|
const backendLabel = effectiveBackend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
|
||||||
const shouldRestoreService = await wasProxyRunning(deps);
|
const shouldRestoreService = await wasProxyRunning(deps);
|
||||||
|
|||||||
@@ -73,6 +73,32 @@ describe('installCliproxyVersion', () => {
|
|||||||
expect(writes.join('')).toContain('backend: original');
|
expect(writes.join('')).toContain('backend: original');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reuses plus binary and pin state when local runtime falls back to original', async () => {
|
||||||
|
const { createEmptyUnifiedConfig } = await import('../../../src/config/unified-config-types');
|
||||||
|
const { saveUnifiedConfig } = await import('../../../src/config/unified-config-loader');
|
||||||
|
const { savePinnedVersion } = await import('../../../src/cliproxy/binary/version-cache');
|
||||||
|
const { getExecutableName } = await import('../../../src/cliproxy/platform-detector');
|
||||||
|
const binaryService = await import(
|
||||||
|
`../../../src/cliproxy/services/binary-service?binary-service-plus-migration=${Date.now()}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const config = createEmptyUnifiedConfig();
|
||||||
|
config.cliproxy = { ...config.cliproxy, backend: 'plus' };
|
||||||
|
saveUnifiedConfig(config);
|
||||||
|
|
||||||
|
const plusBinDir = path.join(tempHome, '.ccs', 'cliproxy', 'bin', 'plus');
|
||||||
|
fs.mkdirSync(plusBinDir, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(plusBinDir, getExecutableName('plus')), 'fake-binary');
|
||||||
|
fs.writeFileSync(path.join(plusBinDir, '.version'), '6.6.80-0');
|
||||||
|
savePinnedVersion('6.6.80-0', 'plus');
|
||||||
|
|
||||||
|
const status = binaryService.getBinaryStatus();
|
||||||
|
|
||||||
|
expect(status.installed).toBe(true);
|
||||||
|
expect(status.pinnedVersion).toBe('6.6.80-0');
|
||||||
|
expect(status.binaryPath).toContain('/original/');
|
||||||
|
});
|
||||||
|
|
||||||
it('attempts to stop the proxy even when there is no tracked running session', async () => {
|
it('attempts to stop the proxy even when there is no tracked running session', async () => {
|
||||||
const calls = {
|
const calls = {
|
||||||
stopProxy: 0,
|
stopProxy: 0,
|
||||||
|
|||||||
@@ -102,6 +102,19 @@ cliproxy:
|
|||||||
expect(error).toContain('issues/1062');
|
expect(error).toContain('issues/1062');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('leaves the settings file unchanged when a plus-only provider update is rejected', () => {
|
||||||
|
const settingsPath = path.join(tmpDir, 'gemini-demo.settings.json');
|
||||||
|
const before = fs.readFileSync(settingsPath, 'utf-8');
|
||||||
|
|
||||||
|
const result = updateVariant('demo', {
|
||||||
|
provider: 'ghcp',
|
||||||
|
model: 'gpt-5.4-mini',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(fs.readFileSync(settingsPath, 'utf-8')).toBe(before);
|
||||||
|
});
|
||||||
|
|
||||||
it('updates provider and regenerates provider-specific core env in same settings file', () => {
|
it('updates provider and regenerates provider-specific core env in same settings file', () => {
|
||||||
const result = updateVariant('demo', {
|
const result = updateVariant('demo', {
|
||||||
provider: 'codex',
|
provider: 'codex',
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ beforeAll(async () => {
|
|||||||
));
|
));
|
||||||
|
|
||||||
const ccsDir = path.join(tempHome, '.ccs');
|
const ccsDir = path.join(tempHome, '.ccs');
|
||||||
const originalBinDir = path.join(ccsDir, 'cliproxy', 'bin', 'original');
|
const plusBinDir = path.join(ccsDir, 'cliproxy', 'bin', 'plus');
|
||||||
fs.mkdirSync(originalBinDir, { recursive: true });
|
fs.mkdirSync(plusBinDir, { recursive: true });
|
||||||
setGlobalConfigDir(ccsDir);
|
setGlobalConfigDir(ccsDir);
|
||||||
|
|
||||||
const config = createEmptyUnifiedConfig();
|
const config = createEmptyUnifiedConfig();
|
||||||
config.cliproxy = { backend: 'plus' };
|
config.cliproxy = { backend: 'plus' };
|
||||||
saveUnifiedConfig(config);
|
saveUnifiedConfig(config);
|
||||||
|
|
||||||
writeInstalledVersion(originalBinDir, '6.6.80');
|
writeInstalledVersion(plusBinDir, '6.6.80');
|
||||||
writeVersionCache('6.6.89', 'original');
|
writeVersionCache('6.6.89', 'plus');
|
||||||
writeVersionListCache(
|
writeVersionListCache(
|
||||||
{
|
{
|
||||||
versions: ['6.6.89', '6.6.88', '6.6.81', '6.6.80'],
|
versions: ['6.6.89', '6.6.88', '6.6.81', '6.6.80'],
|
||||||
@@ -48,7 +48,7 @@ beforeAll(async () => {
|
|||||||
latest: '6.6.89',
|
latest: '6.6.89',
|
||||||
checkedAt: Date.now(),
|
checkedAt: Date.now(),
|
||||||
},
|
},
|
||||||
'original'
|
'plus'
|
||||||
);
|
);
|
||||||
|
|
||||||
({ default: cliproxyStatsRoutes } = await import(
|
({ default: cliproxyStatsRoutes } = await import(
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
const DEBUG_MODE_KEY = 'ccs_debug_mode';
|
const DEBUG_MODE_KEY = 'ccs_debug_mode';
|
||||||
|
|
||||||
/** Providers only available on CLIProxyAPIPlus */
|
/** Providers only available on CLIProxyAPIPlus */
|
||||||
const PLUS_ONLY_PROVIDERS = ['kiro', 'ghcp'];
|
const PLUS_ONLY_PROVIDERS = ['kiro', 'ghcp', 'cursor', 'gitlab', 'codebuddy', 'kilo'];
|
||||||
|
|
||||||
function normalizeRiskAckPhrase(value: string): string {
|
function normalizeRiskAckPhrase(value: string): string {
|
||||||
return value.trim().replace(/\s+/g, ' ').toUpperCase();
|
return value.trim().replace(/\s+/g, ' ').toUpperCase();
|
||||||
|
|||||||
Reference in New Issue
Block a user