hotfix(cliproxy): fallback from deleted CLIProxyAPIPlus to original backend

The upstream router-for-me/CLIProxyAPIPlus repo was deleted, breaking any
install/update path that resolved backend: plus. Existing users with
`backend: plus` saved in config.yaml hit 404s on every bootstrap.

Changes:
- Flip DEFAULT_BACKEND from 'plus' to 'original' (platform-detector.ts)
- Flip default cliproxy.backend in createEmptyUnifiedConfig() to 'original'
- Runtime 404 fallback: getConfiguredBackend() degrades 'plus' -> 'original'
  with a one-time warning, so existing installations keep working without a
  manual reconfig step
- Update CLIProxyBackend docblock to document the fallback behavior
- Update tests to match new default

Retains the 'plus' type and BACKEND_CONFIG entry for forward compatibility
once CCS self-maintains its own Plus fork.

Closes #1062
This commit is contained in:
Tam Nhu Tran
2026-04-22 08:52:17 -04:00
parent c03f2ea791
commit b8b48a99ba
6 changed files with 57 additions and 17 deletions
+31 -3
View File
@@ -32,15 +32,43 @@ import {
import type { CLIProxyBackend } from './types';
/**
* Get backend from config or default to 'plus'
* Track whether we've already warned the user about the Plus fallback this
* process lifetime. Prevents spamming the warning on every command.
*/
let plusFallbackWarned = false;
/**
* Get backend from config, with runtime fallback to 'original' when the user
* still has `backend: plus` saved.
*
* Context (issue #1062): the upstream `router-for-me/CLIProxyAPIPlus` repo was
* deleted, so any `backend: plus` install/update path hits a 404. Rather than
* forcing users to manually edit config.yaml, we degrade to `original` at
* runtime and warn once. This keeps existing installations working without a
* reconfig step, while CCS self-maintains its own Plus fork (future work).
*/
function getConfiguredBackend(): CLIProxyBackend {
let configured: CLIProxyBackend;
try {
const config = loadOrCreateUnifiedConfig();
return config.cliproxy?.backend || DEFAULT_BACKEND;
configured = config.cliproxy?.backend || DEFAULT_BACKEND;
} catch {
return DEFAULT_BACKEND;
}
if (configured === 'plus') {
if (!plusFallbackWarned) {
plusFallbackWarned = true;
warn(
'CLIProxyAPIPlus upstream repo is currently unavailable; falling back to ' +
'`backend: original`. Run `ccs config` to update your saved config. ' +
'Tracking: https://github.com/kaitranntt/ccs/issues/1062'
);
}
return 'original';
}
return configured;
}
/**
@@ -52,7 +80,7 @@ function getBackendBinDir(backend: CLIProxyBackend = DEFAULT_BACKEND): string {
return `${baseDir}/${backend}`;
}
/** Default configuration (uses backend from config.yaml or defaults to 'plus') */
/** Default configuration (uses backend from config.yaml or defaults to `DEFAULT_BACKEND`) */
function createDefaultConfig(backend: CLIProxyBackend = DEFAULT_BACKEND): BinaryManagerConfig {
const backendConfig = BACKEND_CONFIG[backend];
return {
+9 -2
View File
@@ -29,8 +29,15 @@ export const BACKEND_CONFIG = {
},
} as const;
/** Default backend */
export const DEFAULT_BACKEND: CLIProxyBackend = 'plus';
/**
* Default backend
*
* Set to 'original' because upstream `router-for-me/CLIProxyAPIPlus` was
* deleted (issue #1062). The original `router-for-me/CLIProxyAPI` repo is
* still maintained. Users with existing `backend: plus` configs are migrated
* at runtime via a 404 fallback in the installer (see binary/installer.ts).
*/
export const DEFAULT_BACKEND: CLIProxyBackend = 'original';
/**
* CLIProxyAPIPlus fallback version (used when GitHub API unavailable)
+4 -2
View File
@@ -148,8 +148,10 @@ export type CLIProxyProvider =
/**
* CLIProxy backend selection
* - original: CLIProxyAPI (legacy provider subset)
* - plus: CLIProxyAPIPlus (expanded provider support, default)
* - original: CLIProxyAPI (current default; upstream still maintained)
* - plus: CLIProxyAPIPlus (expanded provider support; upstream deleted as of
* issue #1062 — runtime falls back to `original`. Retained for forward
* compatibility once CCS self-maintains its own Plus build.)
*/
export type CLIProxyBackend = 'original' | 'plus';
+1 -1
View File
@@ -1016,7 +1016,7 @@ export function createEmptyUnifiedConfig(): UnifiedConfig {
accounts: {},
profiles: {},
cliproxy: {
backend: 'plus',
backend: 'original',
oauth_accounts: {},
providers: [...CLIPROXY_SUPPORTED_PROVIDERS],
variants: {},
+11 -8
View File
@@ -28,8 +28,8 @@ describe('Backend Selection', () => {
});
describe('DEFAULT_BACKEND', () => {
it('defaults to plus backend for backward compatibility', () => {
assert.strictEqual(platformDetector.DEFAULT_BACKEND, 'plus');
it('defaults to original backend (Plus upstream deleted, issue #1062)', () => {
assert.strictEqual(platformDetector.DEFAULT_BACKEND, 'original');
});
});
@@ -45,9 +45,10 @@ describe('Backend Selection', () => {
assert(info.binaryName.startsWith('CLIProxyAPIPlus_6.6.51-0_'));
});
it('uses plus backend by default', () => {
it('uses original backend by default (Plus upstream deleted, issue #1062)', () => {
const info = platformDetector.detectPlatform();
assert(info.binaryName.includes('CLIProxyAPIPlus'));
assert(info.binaryName.startsWith('CLIProxyAPI_'));
assert(!info.binaryName.includes('CLIProxyAPIPlus'));
});
it('uses fallback version when version not specified', () => {
@@ -76,9 +77,10 @@ describe('Backend Selection', () => {
assert.strictEqual(name, expected);
});
it('defaults to plus backend', () => {
it('defaults to original backend (Plus upstream deleted, issue #1062)', () => {
const name = platformDetector.getExecutableName();
assert(name.includes('cli-proxy-api-plus'));
const expected = isWindows ? 'cli-proxy-api.exe' : 'cli-proxy-api';
assert.strictEqual(name, expected);
});
});
@@ -94,9 +96,10 @@ describe('Backend Selection', () => {
assert(url.includes('router-for-me/CLIProxyAPIPlus/releases'));
});
it('defaults to plus backend', () => {
it('defaults to original backend (Plus upstream deleted, issue #1062)', () => {
const url = platformDetector.getDownloadUrl();
assert(url.includes('CLIProxyAPIPlus'));
assert(url.includes('router-for-me/CLIProxyAPI/releases'));
assert(!url.includes('CLIProxyAPIPlus'));
});
});
@@ -78,7 +78,7 @@ describe('installCliproxyVersion', () => {
skipAutoUpdate: true,
})
).rejects.toThrow(
'CLIProxy Plus binary is not installed locally. Run "ccs cliproxy install" when you have network access.'
'CLIProxy binary is not installed locally. Run "ccs cliproxy install" when you have network access.'
);
});
});