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
+56 -2
View File
@@ -4,8 +4,20 @@
*/
import { fetchJson } from './downloader';
import { readVersionCache, writeVersionCache, readInstalledVersion } from './version-cache';
import { UpdateCheckResult, GITHUB_API_LATEST_RELEASE } from './types';
import {
readVersionCache,
writeVersionCache,
readInstalledVersion,
readVersionListCache,
writeVersionListCache,
} from './version-cache';
import {
UpdateCheckResult,
GITHUB_API_LATEST_RELEASE,
GITHUB_API_ALL_RELEASES,
VersionListResult,
} from './types';
import { CLIPROXY_MAX_STABLE_VERSION } from '../platform-detector';
/**
* Compare semver versions (true if latest > current)
@@ -85,3 +97,45 @@ export async function checkForUpdates(
checkedAt: now,
};
}
/**
* Fetch all available versions from GitHub releases
* Caches result for 1 hour to avoid rate limiting
*/
export async function fetchAllVersions(verbose = false): Promise<VersionListResult> {
// Try cache first
const cache = readVersionListCache();
if (cache) {
if (verbose) {
console.error(`[cliproxy] Using cached version list (${cache.versions.length} versions)`);
}
return { ...cache, fromCache: true };
}
// Fetch from GitHub API
const response = await fetchJson(GITHUB_API_ALL_RELEASES, verbose);
// Extract and normalize versions
const releases = response as unknown as Array<{ tag_name: string }>;
const versions = releases
.map((r) => r.tag_name.replace(/^v/, ''))
.filter((v) => /^\d+\.\d+\.\d+(-\d+)?$/.test(v)); // Valid semver only
const latest = versions[0] || '';
// Find latest stable (not newer than max stable)
const latestStable =
versions.find((v) => !isNewerVersion(v, CLIPROXY_MAX_STABLE_VERSION)) ||
CLIPROXY_MAX_STABLE_VERSION;
const result: VersionListResult = {
versions,
latestStable,
latest,
fromCache: false,
checkedAt: Date.now(),
};
writeVersionListCache(result);
return result;
}