feat(cliproxy): add version management UI with install/restart controls

Implements comprehensive version management for CLIProxyAPI Plus:

Backend APIs:
- GET /versions: fetch available versions from GitHub releases
- POST /install: install specific version with force flag for unstable
- POST /restart: restart proxy without version change

Frontend:
- 4-button layout: Restart, Update/Downgrade, Stop, Settings gear
- Collapsible version picker with dropdown + manual input
- Confirmation dialog for unstable versions (>6.6.80)
- Progressive disclosure (version picker hidden by default)

Ref: plans/260105-1250-cliproxy-version-management/
This commit is contained in:
kaitranntt
2026-01-05 13:12:46 -05:00
parent 8a56a43989
commit a69b2e9d10
7 changed files with 569 additions and 15 deletions
+55 -1
View File
@@ -6,7 +6,12 @@
import * as fs from 'fs';
import * as path from 'path';
import { getCliproxyDir, getBinDir } from '../config-generator';
import { VersionCache, VERSION_CACHE_DURATION_MS, VERSION_PIN_FILE } from './types';
import {
VersionCache,
VERSION_CACHE_DURATION_MS,
VERSION_PIN_FILE,
VersionListCache,
} from './types';
/**
* Get path to version cache file
@@ -140,3 +145,52 @@ export function clearPinnedVersion(): void {
export function isVersionPinned(): boolean {
return getPinnedVersion() !== null;
}
// ==================== Version List Cache ====================
const VERSION_LIST_CACHE_FILE = '.version-list-cache.json';
/**
* Get path to version list cache file
*/
export function getVersionListCachePath(): string {
return path.join(getCliproxyDir(), VERSION_LIST_CACHE_FILE);
}
/**
* Read version list cache if still valid
*/
export function readVersionListCache(): VersionListCache | null {
const cachePath = getVersionListCachePath();
if (!fs.existsSync(cachePath)) {
return null;
}
try {
const content = fs.readFileSync(cachePath, 'utf8');
const cache: VersionListCache = JSON.parse(content);
// Check if cache is still valid (1 hour)
if (Date.now() - cache.checkedAt < VERSION_CACHE_DURATION_MS) {
return cache;
}
return null;
} catch {
return null;
}
}
/**
* Write version list to cache
*/
export function writeVersionListCache(cache: VersionListCache): void {
const cachePath = getVersionListCachePath();
try {
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
fs.writeFileSync(cachePath, JSON.stringify(cache), 'utf8');
} catch {
// Silent fail - caching is optional
}
}