mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
302 lines
8.9 KiB
TypeScript
302 lines
8.9 KiB
TypeScript
/**
|
|
* CLIProxy Command Dispatcher
|
|
*
|
|
* Routes cliproxy subcommands to their respective handlers.
|
|
* This is the main entry point for all `ccs cliproxy` commands.
|
|
*/
|
|
|
|
import { CLIProxyBackend } from '../../cliproxy/types';
|
|
import { DEFAULT_BACKEND } from '../../cliproxy/platform-detector';
|
|
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
|
import { handleSync } from '../cliproxy-sync-handler';
|
|
|
|
// Import subcommand handlers
|
|
import { handleList } from './auth-subcommand';
|
|
import {
|
|
handleQuotaStatus,
|
|
handleDoctor,
|
|
handleSetDefault,
|
|
handlePauseAccount,
|
|
handleResumeAccount,
|
|
} from './quota-subcommand';
|
|
import { handleCreate, handleRemove, handleEdit } from './variant-subcommand';
|
|
import {
|
|
handleProxyStatus,
|
|
handleStart,
|
|
handleStop,
|
|
handleRestart,
|
|
} from './proxy-lifecycle-subcommand';
|
|
import { showStatus, handleInstallVersion, handleInstallLatest } from './install-subcommand';
|
|
import { showHelp } from './help-subcommand';
|
|
import {
|
|
handleCatalogStatus,
|
|
handleCatalogRefresh,
|
|
handleCatalogReset,
|
|
} from './catalog-subcommand';
|
|
|
|
/**
|
|
* Parse --backend flag from args
|
|
* Returns the backend value and remaining args without --backend flag
|
|
*/
|
|
function parseBackendArg(args: string[]): {
|
|
backend: CLIProxyBackend | undefined;
|
|
remainingArgs: string[];
|
|
} {
|
|
const backendIdx = args.indexOf('--backend');
|
|
if (backendIdx === -1) {
|
|
// Also check for --backend=value format
|
|
const backendEqualsIdx = args.findIndex((a) => a.startsWith('--backend='));
|
|
if (backendEqualsIdx !== -1) {
|
|
const value = args[backendEqualsIdx].split('=')[1] as CLIProxyBackend;
|
|
if (value !== 'original' && value !== 'plus') {
|
|
console.warn(`Invalid backend '${value}'. Valid options: original, plus`);
|
|
return { backend: undefined, remainingArgs: args };
|
|
}
|
|
const remainingArgs = [...args];
|
|
remainingArgs.splice(backendEqualsIdx, 1);
|
|
return { backend: value, remainingArgs };
|
|
}
|
|
return { backend: undefined, remainingArgs: args };
|
|
}
|
|
const value = args[backendIdx + 1];
|
|
if (value !== 'original' && value !== 'plus') {
|
|
console.warn(`Invalid backend '${value}'. Valid options: original, plus`);
|
|
return { backend: undefined, remainingArgs: args };
|
|
}
|
|
const remainingArgs = [...args];
|
|
remainingArgs.splice(backendIdx, 2);
|
|
return { backend: value, remainingArgs };
|
|
}
|
|
|
|
/**
|
|
* Get effective backend (CLI flag > config.yaml > default)
|
|
*/
|
|
function getEffectiveBackend(cliBackend?: CLIProxyBackend): CLIProxyBackend {
|
|
if (cliBackend) return cliBackend;
|
|
const config = loadOrCreateUnifiedConfig();
|
|
return config.cliproxy?.backend ?? DEFAULT_BACKEND;
|
|
}
|
|
|
|
/**
|
|
* Parse --provider flag from args for quota command
|
|
* Returns the provider filter value and remaining args
|
|
* Accepts: agy, codex, claude, anthropic, gemini, gemini-cli, ghcp, github-copilot, all
|
|
*/
|
|
function parseProviderArg(args: string[]): {
|
|
provider: 'agy' | 'codex' | 'claude' | 'gemini' | 'ghcp' | 'all';
|
|
remainingArgs: string[];
|
|
} {
|
|
const providerIdx = args.indexOf('--provider');
|
|
if (providerIdx === -1) {
|
|
// Also check for --provider=value format
|
|
const providerEqualsIdx = args.findIndex((a) => a.startsWith('--provider='));
|
|
if (providerEqualsIdx !== -1) {
|
|
const value = args[providerEqualsIdx].split('=')[1]?.toLowerCase() || '';
|
|
const remainingArgs = [...args];
|
|
remainingArgs.splice(providerEqualsIdx, 1);
|
|
// Handle empty value
|
|
if (!value) {
|
|
console.error(
|
|
'Warning: --provider requires a value. Valid options: agy, codex, claude, anthropic, gemini, gemini-cli, ghcp, github-copilot, all'
|
|
);
|
|
return { provider: 'all', remainingArgs };
|
|
}
|
|
// Normalize aliases
|
|
const normalized =
|
|
value === 'gemini-cli'
|
|
? 'gemini'
|
|
: value === 'github-copilot'
|
|
? 'ghcp'
|
|
: value === 'anthropic'
|
|
? 'claude'
|
|
: value;
|
|
if (
|
|
normalized !== 'agy' &&
|
|
normalized !== 'codex' &&
|
|
normalized !== 'claude' &&
|
|
normalized !== 'gemini' &&
|
|
normalized !== 'ghcp' &&
|
|
normalized !== 'all'
|
|
) {
|
|
console.error(
|
|
`Invalid provider '${value}'. Valid options: agy, codex, claude, anthropic, gemini, gemini-cli, ghcp, github-copilot, all`
|
|
);
|
|
return { provider: 'all', remainingArgs };
|
|
}
|
|
return {
|
|
provider: normalized as 'agy' | 'codex' | 'claude' | 'gemini' | 'ghcp' | 'all',
|
|
remainingArgs,
|
|
};
|
|
}
|
|
return { provider: 'all', remainingArgs: args };
|
|
}
|
|
const rawValue = args[providerIdx + 1];
|
|
// Warn if no value or value looks like another flag
|
|
if (!rawValue || rawValue.startsWith('-')) {
|
|
console.error(
|
|
'Warning: --provider requires a value. Valid options: agy, codex, claude, anthropic, gemini, gemini-cli, ghcp, github-copilot, all'
|
|
);
|
|
}
|
|
const value = rawValue?.toLowerCase() || 'all';
|
|
const remainingArgs = [...args];
|
|
remainingArgs.splice(providerIdx, 2);
|
|
// Normalize aliases
|
|
const normalized =
|
|
value === 'gemini-cli'
|
|
? 'gemini'
|
|
: value === 'github-copilot'
|
|
? 'ghcp'
|
|
: value === 'anthropic'
|
|
? 'claude'
|
|
: value;
|
|
if (
|
|
normalized !== 'agy' &&
|
|
normalized !== 'codex' &&
|
|
normalized !== 'claude' &&
|
|
normalized !== 'gemini' &&
|
|
normalized !== 'ghcp' &&
|
|
normalized !== 'all'
|
|
) {
|
|
console.error(
|
|
`Invalid provider '${value}'. Valid options: agy, codex, claude, anthropic, gemini, gemini-cli, ghcp, github-copilot, all`
|
|
);
|
|
return { provider: 'all', remainingArgs };
|
|
}
|
|
return {
|
|
provider: normalized as 'agy' | 'codex' | 'claude' | 'gemini' | 'ghcp' | 'all',
|
|
remainingArgs,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Main router for cliproxy commands
|
|
*/
|
|
export async function handleCliproxyCommand(args: string[]): Promise<void> {
|
|
// Parse --backend flag first (before other processing)
|
|
const { backend: cliBackend, remainingArgs } = parseBackendArg(args);
|
|
const effectiveBackend = getEffectiveBackend(cliBackend);
|
|
|
|
const verbose = remainingArgs.includes('--verbose') || remainingArgs.includes('-v');
|
|
const command = remainingArgs[0];
|
|
|
|
if (remainingArgs.includes('--help') || remainingArgs.includes('-h')) {
|
|
await showHelp();
|
|
return;
|
|
}
|
|
|
|
// Profile commands
|
|
if (command === 'create') {
|
|
await handleCreate(remainingArgs.slice(1), effectiveBackend);
|
|
return;
|
|
}
|
|
|
|
if (command === 'edit') {
|
|
await handleEdit(remainingArgs.slice(1), effectiveBackend);
|
|
return;
|
|
}
|
|
|
|
if (command === 'list' || command === 'ls') {
|
|
await handleList();
|
|
return;
|
|
}
|
|
|
|
if (command === 'remove' || command === 'delete' || command === 'rm') {
|
|
await handleRemove(remainingArgs.slice(1));
|
|
return;
|
|
}
|
|
|
|
// Catalog commands
|
|
if (command === 'catalog') {
|
|
const subcommand = remainingArgs[1];
|
|
if (subcommand === 'refresh') {
|
|
await handleCatalogRefresh(verbose);
|
|
return;
|
|
}
|
|
if (subcommand === 'reset') {
|
|
await handleCatalogReset();
|
|
return;
|
|
}
|
|
await handleCatalogStatus(verbose);
|
|
return;
|
|
}
|
|
|
|
// Sync command
|
|
if (command === 'sync') {
|
|
await handleSync(remainingArgs.slice(1));
|
|
return;
|
|
}
|
|
|
|
// Proxy lifecycle commands
|
|
if (command === 'start') {
|
|
await handleStart(verbose);
|
|
return;
|
|
}
|
|
|
|
if (command === 'stop') {
|
|
await handleStop();
|
|
return;
|
|
}
|
|
|
|
if (command === 'restart') {
|
|
await handleRestart(verbose);
|
|
return;
|
|
}
|
|
|
|
if (command === 'status') {
|
|
await handleProxyStatus();
|
|
return;
|
|
}
|
|
|
|
// Diagnostics
|
|
if (command === 'doctor' || command === 'diag') {
|
|
await handleDoctor(verbose);
|
|
return;
|
|
}
|
|
|
|
// Quota management commands
|
|
if (command === 'default') {
|
|
await handleSetDefault(remainingArgs.slice(1));
|
|
return;
|
|
}
|
|
|
|
if (command === 'pause') {
|
|
await handlePauseAccount(remainingArgs.slice(1));
|
|
return;
|
|
}
|
|
|
|
if (command === 'resume') {
|
|
await handleResumeAccount(remainingArgs.slice(1));
|
|
return;
|
|
}
|
|
|
|
if (command === 'quota') {
|
|
const { provider: providerFilter } = parseProviderArg(remainingArgs.slice(1));
|
|
await handleQuotaStatus(verbose, providerFilter);
|
|
return;
|
|
}
|
|
|
|
// Binary installation commands
|
|
const installIdx = remainingArgs.indexOf('--install');
|
|
if (installIdx !== -1) {
|
|
let version = remainingArgs[installIdx + 1];
|
|
if (!version || version.startsWith('-')) {
|
|
console.error('Missing version argument for --install');
|
|
console.error(' Usage: ccs cliproxy --install <version>');
|
|
console.error(' Example: ccs cliproxy --install 6.6.80-0');
|
|
process.exit(1);
|
|
}
|
|
// Strip leading 'v' prefix and whitespace (user may type " v6.6.80-0 ")
|
|
version = version.trim().replace(/^v/, '');
|
|
await handleInstallVersion(version, verbose, effectiveBackend);
|
|
return;
|
|
}
|
|
|
|
if (remainingArgs.includes('--latest') || remainingArgs.includes('--update')) {
|
|
await handleInstallLatest(verbose, effectiveBackend);
|
|
return;
|
|
}
|
|
|
|
// Default: show status
|
|
await showStatus(verbose, effectiveBackend);
|
|
}
|