mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 02:19:39 +00:00
refactor(cliproxy): extract service layer from cliproxy-command
- create cliproxy/services/ with 6 modules (1179 → 634 lines) - variant-service, variant-settings, variant-config-adapter - proxy-lifecycle-service, binary-service - separate CLI parsing from business logic Phase 5: Command Refactor (cliproxy-command.ts)
This commit is contained in:
@@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* CLIProxy Binary Service
|
||||||
|
*
|
||||||
|
* Handles CLIProxyAPI binary version management:
|
||||||
|
* - Get current version status
|
||||||
|
* - Install specific versions
|
||||||
|
* - Install latest version
|
||||||
|
* - Version pinning
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
getInstalledCliproxyVersion,
|
||||||
|
installCliproxyVersion,
|
||||||
|
fetchLatestCliproxyVersion,
|
||||||
|
isCLIProxyInstalled,
|
||||||
|
getCLIProxyPath,
|
||||||
|
getPinnedVersion,
|
||||||
|
savePinnedVersion,
|
||||||
|
clearPinnedVersion,
|
||||||
|
isVersionPinned,
|
||||||
|
} from '../binary-manager';
|
||||||
|
import { CLIPROXY_FALLBACK_VERSION } from '../platform-detector';
|
||||||
|
|
||||||
|
/** Binary status result */
|
||||||
|
export interface BinaryStatusResult {
|
||||||
|
installed: boolean;
|
||||||
|
currentVersion: string | null;
|
||||||
|
pinnedVersion: string | null;
|
||||||
|
binaryPath: string;
|
||||||
|
fallbackVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Install result */
|
||||||
|
export interface InstallResult {
|
||||||
|
success: boolean;
|
||||||
|
version: string;
|
||||||
|
error?: string;
|
||||||
|
wasPinned?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Latest version check result */
|
||||||
|
export interface LatestVersionResult {
|
||||||
|
success: boolean;
|
||||||
|
latestVersion?: string;
|
||||||
|
currentVersion?: string;
|
||||||
|
updateAvailable?: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current binary status
|
||||||
|
*/
|
||||||
|
export function getBinaryStatus(): BinaryStatusResult {
|
||||||
|
return {
|
||||||
|
installed: isCLIProxyInstalled(),
|
||||||
|
currentVersion: getInstalledCliproxyVersion(),
|
||||||
|
pinnedVersion: getPinnedVersion(),
|
||||||
|
binaryPath: getCLIProxyPath(),
|
||||||
|
fallbackVersion: CLIPROXY_FALLBACK_VERSION,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for latest version
|
||||||
|
*/
|
||||||
|
export async function checkLatestVersion(): Promise<LatestVersionResult> {
|
||||||
|
try {
|
||||||
|
const latestVersion = await fetchLatestCliproxyVersion();
|
||||||
|
const currentVersion = getInstalledCliproxyVersion();
|
||||||
|
const updateAvailable = latestVersion !== currentVersion;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
latestVersion,
|
||||||
|
currentVersion: currentVersion || undefined,
|
||||||
|
updateAvailable,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: (error as Error).message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate version format
|
||||||
|
*/
|
||||||
|
export function isValidVersionFormat(version: string): boolean {
|
||||||
|
return /^\d+\.\d+\.\d+$/.test(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a specific version and pin it
|
||||||
|
*/
|
||||||
|
export async function installVersion(version: string, verbose = false): Promise<InstallResult> {
|
||||||
|
if (!isValidVersionFormat(version)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
version,
|
||||||
|
error: 'Invalid version format. Expected format: X.Y.Z (e.g., 6.5.53)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await installCliproxyVersion(version, verbose);
|
||||||
|
savePinnedVersion(version);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
version,
|
||||||
|
wasPinned: true,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
version,
|
||||||
|
error: (error as Error).message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install latest version and clear any pin
|
||||||
|
*/
|
||||||
|
export async function installLatest(verbose = false): Promise<InstallResult> {
|
||||||
|
try {
|
||||||
|
const latestVersion = await fetchLatestCliproxyVersion();
|
||||||
|
const currentVersion = getInstalledCliproxyVersion();
|
||||||
|
const wasPinned = isVersionPinned();
|
||||||
|
|
||||||
|
if (isCLIProxyInstalled() && latestVersion === currentVersion && !wasPinned) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
version: latestVersion,
|
||||||
|
error: `Already running latest version: v${latestVersion}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await installCliproxyVersion(latestVersion, verbose);
|
||||||
|
clearPinnedVersion();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
version: latestVersion,
|
||||||
|
wasPinned,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
version: '',
|
||||||
|
error: (error as Error).message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a version is pinned
|
||||||
|
*/
|
||||||
|
export function isPinned(): boolean {
|
||||||
|
return isVersionPinned();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pinned version if any
|
||||||
|
*/
|
||||||
|
export function getPinned(): string | null {
|
||||||
|
return getPinnedVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear version pin
|
||||||
|
*/
|
||||||
|
export function clearPin(): void {
|
||||||
|
clearPinnedVersion();
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* CLIProxy Services
|
||||||
|
* Central export point for cliproxy service layer
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Variant management
|
||||||
|
export {
|
||||||
|
validateProfileName,
|
||||||
|
variantExists,
|
||||||
|
listVariants,
|
||||||
|
createVariant,
|
||||||
|
removeVariant,
|
||||||
|
type VariantConfig,
|
||||||
|
type VariantOperationResult,
|
||||||
|
} from './variant-service';
|
||||||
|
|
||||||
|
// Proxy lifecycle
|
||||||
|
export {
|
||||||
|
getProxyStatus,
|
||||||
|
stopProxy,
|
||||||
|
isProxyRunning,
|
||||||
|
getActiveSessionCount,
|
||||||
|
type ProxyStatusResult,
|
||||||
|
type StopProxyResult,
|
||||||
|
} from './proxy-lifecycle-service';
|
||||||
|
|
||||||
|
// Binary management
|
||||||
|
export {
|
||||||
|
getBinaryStatus,
|
||||||
|
checkLatestVersion,
|
||||||
|
isValidVersionFormat,
|
||||||
|
installVersion,
|
||||||
|
installLatest,
|
||||||
|
isPinned,
|
||||||
|
getPinned,
|
||||||
|
clearPin,
|
||||||
|
type BinaryStatusResult,
|
||||||
|
type InstallResult,
|
||||||
|
type LatestVersionResult,
|
||||||
|
} from './binary-service';
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* CLIProxy Proxy Lifecycle Service
|
||||||
|
*
|
||||||
|
* Handles start/stop/status operations for CLIProxy instances.
|
||||||
|
* Delegates to session-tracker for actual process management.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
stopProxy as stopProxySession,
|
||||||
|
getProxyStatus as getProxyStatusSession,
|
||||||
|
} from '../session-tracker';
|
||||||
|
|
||||||
|
/** Proxy status result */
|
||||||
|
export interface ProxyStatusResult {
|
||||||
|
running: boolean;
|
||||||
|
port?: number;
|
||||||
|
pid?: number;
|
||||||
|
sessionCount?: number;
|
||||||
|
startedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stop proxy result */
|
||||||
|
export interface StopProxyResult {
|
||||||
|
stopped: boolean;
|
||||||
|
pid?: number;
|
||||||
|
sessionCount?: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current proxy status
|
||||||
|
*/
|
||||||
|
export function getProxyStatus(): ProxyStatusResult {
|
||||||
|
return getProxyStatusSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the running CLIProxy instance
|
||||||
|
*/
|
||||||
|
export async function stopProxy(): Promise<StopProxyResult> {
|
||||||
|
return stopProxySession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if proxy is currently running
|
||||||
|
*/
|
||||||
|
export function isProxyRunning(): boolean {
|
||||||
|
const status = getProxyStatusSession();
|
||||||
|
return status.running;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active session count
|
||||||
|
*/
|
||||||
|
export function getActiveSessionCount(): number {
|
||||||
|
const status = getProxyStatusSession();
|
||||||
|
return status.sessionCount ?? 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* CLIProxy Variant Config Adapters
|
||||||
|
*
|
||||||
|
* Handles reading/writing variant config in both unified and legacy formats.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { getConfigPath, loadConfig } from '../../utils/config-manager';
|
||||||
|
import { CLIProxyProvider } from '../types';
|
||||||
|
import {
|
||||||
|
loadOrCreateUnifiedConfig,
|
||||||
|
saveUnifiedConfig,
|
||||||
|
isUnifiedMode,
|
||||||
|
} from '../../config/unified-config-loader';
|
||||||
|
|
||||||
|
/** Variant configuration structure */
|
||||||
|
export interface VariantConfig {
|
||||||
|
provider: string;
|
||||||
|
settings?: string;
|
||||||
|
account?: string;
|
||||||
|
model?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if variant exists in config
|
||||||
|
*/
|
||||||
|
export function variantExistsInConfig(name: string): boolean {
|
||||||
|
try {
|
||||||
|
if (isUnifiedMode()) {
|
||||||
|
const config = loadOrCreateUnifiedConfig();
|
||||||
|
return !!(config.cliproxy?.variants && name in config.cliproxy.variants);
|
||||||
|
}
|
||||||
|
const config = loadConfig();
|
||||||
|
return !!(config.cliproxy && name in config.cliproxy);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List variants from config
|
||||||
|
*/
|
||||||
|
export function listVariantsFromConfig(): Record<string, VariantConfig> {
|
||||||
|
try {
|
||||||
|
if (isUnifiedMode()) {
|
||||||
|
const unifiedConfig = loadOrCreateUnifiedConfig();
|
||||||
|
const variants = unifiedConfig.cliproxy?.variants || {};
|
||||||
|
const result: Record<string, VariantConfig> = {};
|
||||||
|
for (const name of Object.keys(variants)) {
|
||||||
|
const v = variants[name];
|
||||||
|
result[name] = { provider: v.provider, settings: v.settings, account: v.account };
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
const variants = config.cliproxy || {};
|
||||||
|
const result: Record<string, VariantConfig> = {};
|
||||||
|
for (const name of Object.keys(variants)) {
|
||||||
|
const v = variants[name] as { provider: string; settings: string; account?: string };
|
||||||
|
result[name] = { provider: v.provider, settings: v.settings, account: v.account };
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save variant to unified config
|
||||||
|
*/
|
||||||
|
export function saveVariantUnified(
|
||||||
|
name: string,
|
||||||
|
provider: CLIProxyProvider,
|
||||||
|
settingsPath: string,
|
||||||
|
account?: string
|
||||||
|
): void {
|
||||||
|
const config = loadOrCreateUnifiedConfig();
|
||||||
|
|
||||||
|
if (!config.cliproxy) {
|
||||||
|
config.cliproxy = {
|
||||||
|
oauth_accounts: {},
|
||||||
|
providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow'],
|
||||||
|
variants: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!config.cliproxy.variants) {
|
||||||
|
config.cliproxy.variants = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
config.cliproxy.variants[name] = {
|
||||||
|
provider,
|
||||||
|
account,
|
||||||
|
settings: settingsPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
saveUnifiedConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save variant to legacy JSON config
|
||||||
|
*/
|
||||||
|
export function saveVariantLegacy(
|
||||||
|
name: string,
|
||||||
|
provider: string,
|
||||||
|
settingsPath: string,
|
||||||
|
account?: string
|
||||||
|
): void {
|
||||||
|
const configPath = getConfigPath();
|
||||||
|
|
||||||
|
let config: { profiles: Record<string, string>; cliproxy?: Record<string, unknown> };
|
||||||
|
try {
|
||||||
|
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
|
} catch {
|
||||||
|
config = { profiles: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.cliproxy) {
|
||||||
|
config.cliproxy = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantConfig: { provider: string; settings: string; account?: string } = {
|
||||||
|
provider,
|
||||||
|
settings: settingsPath,
|
||||||
|
};
|
||||||
|
if (account) {
|
||||||
|
variantConfig.account = account;
|
||||||
|
}
|
||||||
|
config.cliproxy[name] = variantConfig;
|
||||||
|
|
||||||
|
const tempPath = configPath + '.tmp';
|
||||||
|
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
||||||
|
fs.renameSync(tempPath, configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove variant from unified config
|
||||||
|
*/
|
||||||
|
export function removeVariantFromUnifiedConfig(name: string): VariantConfig | null {
|
||||||
|
const config = loadOrCreateUnifiedConfig();
|
||||||
|
|
||||||
|
if (!config.cliproxy?.variants || !(name in config.cliproxy.variants)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variant = config.cliproxy.variants[name];
|
||||||
|
delete config.cliproxy.variants[name];
|
||||||
|
saveUnifiedConfig(config);
|
||||||
|
|
||||||
|
return { provider: variant.provider, settings: variant.settings };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove variant from legacy JSON config
|
||||||
|
*/
|
||||||
|
export function removeVariantFromLegacyConfig(name: string): VariantConfig | null {
|
||||||
|
const configPath = getConfigPath();
|
||||||
|
|
||||||
|
let config: { profiles: Record<string, string>; cliproxy?: Record<string, unknown> };
|
||||||
|
try {
|
||||||
|
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.cliproxy || !(name in config.cliproxy)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variant = config.cliproxy[name] as { provider: string; settings: string };
|
||||||
|
delete config.cliproxy[name];
|
||||||
|
|
||||||
|
if (Object.keys(config.cliproxy).length === 0) {
|
||||||
|
delete config.cliproxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempPath = configPath + '.tmp';
|
||||||
|
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
||||||
|
fs.renameSync(tempPath, configPath);
|
||||||
|
|
||||||
|
return variant;
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* CLIProxy Variant Service
|
||||||
|
*
|
||||||
|
* Handles CRUD operations for CLIProxy variant profiles.
|
||||||
|
* Supports both unified config (config.yaml) and legacy JSON format.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as path from 'path';
|
||||||
|
import { CLIProxyProfileName } from '../../auth/profile-detector';
|
||||||
|
import { CLIProxyProvider } from '../types';
|
||||||
|
import { isReservedName } from '../../config/reserved-names';
|
||||||
|
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||||
|
import {
|
||||||
|
createSettingsFile,
|
||||||
|
createSettingsFileUnified,
|
||||||
|
deleteSettingsFile,
|
||||||
|
getRelativeSettingsPath,
|
||||||
|
} from './variant-settings';
|
||||||
|
import {
|
||||||
|
VariantConfig,
|
||||||
|
variantExistsInConfig,
|
||||||
|
listVariantsFromConfig,
|
||||||
|
saveVariantUnified,
|
||||||
|
saveVariantLegacy,
|
||||||
|
removeVariantFromUnifiedConfig,
|
||||||
|
removeVariantFromLegacyConfig,
|
||||||
|
} from './variant-config-adapter';
|
||||||
|
|
||||||
|
// Re-export VariantConfig from adapter
|
||||||
|
export type { VariantConfig } from './variant-config-adapter';
|
||||||
|
|
||||||
|
/** Result of variant operations */
|
||||||
|
export interface VariantOperationResult {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
variant?: VariantConfig;
|
||||||
|
settingsPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate CLIProxy profile name
|
||||||
|
*/
|
||||||
|
export function validateProfileName(name: string): string | null {
|
||||||
|
if (!name) {
|
||||||
|
return 'Profile name is required';
|
||||||
|
}
|
||||||
|
if (!/^[a-zA-Z][a-zA-Z0-9._-]*$/.test(name)) {
|
||||||
|
return 'Name must start with letter, contain only letters, numbers, dot, dash, underscore';
|
||||||
|
}
|
||||||
|
if (name.length > 32) {
|
||||||
|
return 'Name must be 32 characters or less';
|
||||||
|
}
|
||||||
|
if (isReservedName(name)) {
|
||||||
|
return `'${name}' is a reserved name`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if CLIProxy variant profile exists
|
||||||
|
*/
|
||||||
|
export function variantExists(name: string): boolean {
|
||||||
|
return variantExistsInConfig(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all CLIProxy variants
|
||||||
|
*/
|
||||||
|
export function listVariants(): Record<string, VariantConfig> {
|
||||||
|
return listVariantsFromConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new CLIProxy variant
|
||||||
|
*/
|
||||||
|
export function createVariant(
|
||||||
|
name: string,
|
||||||
|
provider: CLIProxyProfileName,
|
||||||
|
model: string,
|
||||||
|
account?: string
|
||||||
|
): VariantOperationResult {
|
||||||
|
try {
|
||||||
|
let settingsPath: string;
|
||||||
|
|
||||||
|
if (isUnifiedMode()) {
|
||||||
|
settingsPath = createSettingsFileUnified(name, provider, model);
|
||||||
|
saveVariantUnified(
|
||||||
|
name,
|
||||||
|
provider as CLIProxyProvider,
|
||||||
|
getRelativeSettingsPath(provider, name),
|
||||||
|
account
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
settingsPath = createSettingsFile(name, provider, model);
|
||||||
|
saveVariantLegacy(name, provider, `~/.ccs/${path.basename(settingsPath)}`, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
settingsPath,
|
||||||
|
variant: { provider, model, account },
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: (error as Error).message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a CLIProxy variant
|
||||||
|
*/
|
||||||
|
export function removeVariant(name: string): VariantOperationResult {
|
||||||
|
try {
|
||||||
|
let variant: VariantConfig | null;
|
||||||
|
|
||||||
|
if (isUnifiedMode()) {
|
||||||
|
const unifiedVariant = removeVariantFromUnifiedConfig(name);
|
||||||
|
if (unifiedVariant?.settings) {
|
||||||
|
deleteSettingsFile(unifiedVariant.settings);
|
||||||
|
}
|
||||||
|
variant = unifiedVariant;
|
||||||
|
} else {
|
||||||
|
variant = removeVariantFromLegacyConfig(name);
|
||||||
|
if (variant?.settings) {
|
||||||
|
deleteSettingsFile(variant.settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!variant) {
|
||||||
|
return { success: false, error: `Variant '${name}' not found` };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, variant };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: (error as Error).message };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* CLIProxy Variant Settings File Manager
|
||||||
|
*
|
||||||
|
* Handles creation of settings.json files for CLIProxy variants.
|
||||||
|
* These files contain environment variables for Claude CLI integration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as os from 'os';
|
||||||
|
import { CLIProxyProfileName } from '../../auth/profile-detector';
|
||||||
|
import { getCcsDir } from '../../utils/config-manager';
|
||||||
|
import { getClaudeEnvVars, CLIPROXY_DEFAULT_PORT } from '../config-generator';
|
||||||
|
import { CLIProxyProvider } from '../types';
|
||||||
|
|
||||||
|
/** Environment settings structure */
|
||||||
|
interface SettingsEnv {
|
||||||
|
ANTHROPIC_BASE_URL: string;
|
||||||
|
ANTHROPIC_AUTH_TOKEN: string;
|
||||||
|
ANTHROPIC_MODEL: string;
|
||||||
|
ANTHROPIC_DEFAULT_OPUS_MODEL: string;
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL: string;
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SettingsFile {
|
||||||
|
env: SettingsEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build settings env object for a variant
|
||||||
|
*/
|
||||||
|
function buildSettingsEnv(provider: CLIProxyProfileName, model: string): SettingsEnv {
|
||||||
|
const baseEnv = getClaudeEnvVars(provider as CLIProxyProvider, CLIPROXY_DEFAULT_PORT);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ANTHROPIC_BASE_URL: baseEnv.ANTHROPIC_BASE_URL || '',
|
||||||
|
ANTHROPIC_AUTH_TOKEN: baseEnv.ANTHROPIC_AUTH_TOKEN || '',
|
||||||
|
ANTHROPIC_MODEL: model,
|
||||||
|
ANTHROPIC_DEFAULT_OPUS_MODEL: model,
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL: model,
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: baseEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL || model,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure directory exists
|
||||||
|
*/
|
||||||
|
function ensureDir(dir: string): void {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write settings file atomically
|
||||||
|
*/
|
||||||
|
function writeSettings(filePath: string, settings: SettingsFile): void {
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get settings file path for a variant
|
||||||
|
*/
|
||||||
|
export function getSettingsFilePath(provider: CLIProxyProfileName, name: string): string {
|
||||||
|
const ccsDir = getCcsDir();
|
||||||
|
return path.join(ccsDir, `${provider}-${name}.settings.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get settings file name (without path)
|
||||||
|
*/
|
||||||
|
export function getSettingsFileName(provider: CLIProxyProfileName, name: string): string {
|
||||||
|
return `${provider}-${name}.settings.json`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get relative settings path with ~ prefix
|
||||||
|
*/
|
||||||
|
export function getRelativeSettingsPath(provider: CLIProxyProfileName, name: string): string {
|
||||||
|
return `~/.ccs/${getSettingsFileName(provider, name)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create settings.json file for CLIProxy variant (legacy mode)
|
||||||
|
*/
|
||||||
|
export function createSettingsFile(
|
||||||
|
name: string,
|
||||||
|
provider: CLIProxyProfileName,
|
||||||
|
model: string
|
||||||
|
): string {
|
||||||
|
const ccsDir = getCcsDir();
|
||||||
|
const settingsPath = getSettingsFilePath(provider, name);
|
||||||
|
|
||||||
|
const settings: SettingsFile = {
|
||||||
|
env: buildSettingsEnv(provider, model),
|
||||||
|
};
|
||||||
|
|
||||||
|
ensureDir(ccsDir);
|
||||||
|
writeSettings(settingsPath, settings);
|
||||||
|
|
||||||
|
return settingsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create settings.json file for CLIProxy variant (unified mode)
|
||||||
|
*/
|
||||||
|
export function createSettingsFileUnified(
|
||||||
|
name: string,
|
||||||
|
provider: CLIProxyProfileName,
|
||||||
|
model: string
|
||||||
|
): string {
|
||||||
|
const ccsDir = path.join(os.homedir(), '.ccs');
|
||||||
|
const settingsPath = path.join(ccsDir, getSettingsFileName(provider, name));
|
||||||
|
|
||||||
|
const settings: SettingsFile = {
|
||||||
|
env: buildSettingsEnv(provider, model),
|
||||||
|
};
|
||||||
|
|
||||||
|
ensureDir(ccsDir);
|
||||||
|
writeSettings(settingsPath, settings);
|
||||||
|
|
||||||
|
return settingsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete settings file if it exists
|
||||||
|
*/
|
||||||
|
export function deleteSettingsFile(settingsPath: string): boolean {
|
||||||
|
const resolvedPath = settingsPath.replace(/^~/, os.homedir());
|
||||||
|
if (fs.existsSync(resolvedPath)) {
|
||||||
|
fs.unlinkSync(resolvedPath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
+259
-812
File diff suppressed because it is too large
Load Diff
@@ -449,6 +449,16 @@ export function updateUnifiedConfig(updates: Partial<UnifiedConfig>): UnifiedCon
|
|||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if unified config mode is active.
|
||||||
|
* Returns true if config.yaml exists OR CCS_UNIFIED_CONFIG=1.
|
||||||
|
*
|
||||||
|
* Use this centralized function instead of duplicating the logic.
|
||||||
|
*/
|
||||||
|
export function isUnifiedMode(): boolean {
|
||||||
|
return hasUnifiedConfig() || isUnifiedConfigEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or set default profile name.
|
* Get or set default profile name.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user