feat(cli): add pause, resume, status subcommands

- ccs cliproxy pause <provider> <account>

- ccs cliproxy resume <provider> <account>

- ccs cliproxy status [provider] - show quota/tier info

Refs #282
This commit is contained in:
kaitranntt
2026-01-06 12:17:48 -05:00
parent 10e3eec16f
commit cfd8dd974e
+234 -1
View File
@@ -20,8 +20,15 @@
import * as path from 'path';
import { getAllAuthStatus, getOAuthConfig, triggerOAuth } from '../cliproxy/auth-handler';
import { getProviderAccounts } from '../cliproxy/account-manager';
import {
getProviderAccounts,
setDefaultAccount,
pauseAccount,
resumeAccount,
findAccountByQuery,
} from '../cliproxy/account-manager';
import { fetchAllProviderQuotas } from '../cliproxy/quota-fetcher';
import { isOnCooldown } from '../cliproxy/quota-manager';
import { CLIPROXY_FALLBACK_VERSION } from '../cliproxy/platform-detector';
import { CLIPROXY_PROFILES, CLIProxyProfileName } from '../auth/profile-detector';
import { supportsModelConfig, getProviderCatalog, ModelEntry } from '../cliproxy/model-catalog';
@@ -544,6 +551,15 @@ async function showHelp(): Promise<void> {
['remove <name>', 'Remove a CLIProxy variant profile'],
],
],
[
'Quota Management:',
[
['default <account>', 'Set default account for rotation'],
['pause <account>', 'Pause account (skip in rotation)'],
['resume <account>', 'Resume paused account'],
['quota', 'Show quota status for all accounts'],
],
],
[
'Proxy Lifecycle:',
[
@@ -691,6 +707,202 @@ function formatQuotaBar(percentage: number): string {
return `[${filledChar.repeat(filled)}${' '.repeat(empty)}]`;
}
// ============================================================================
// QUOTA MANAGEMENT COMMANDS
// ============================================================================
async function handleSetDefault(args: string[]): Promise<void> {
await initUI();
const parsed = parseProfileArgs(args);
if (!parsed.name) {
console.log(fail('Usage: ccs cliproxy default <account> [--provider <provider>]'));
console.log('');
console.log('Examples:');
console.log(' ccs cliproxy default ultra@gmail.com');
console.log(' ccs cliproxy default john --provider agy');
process.exit(1);
}
const provider = (parsed.provider || 'agy') as CLIProxyProvider;
const account = findAccountByQuery(provider, parsed.name);
if (!account) {
console.log(fail(`Account not found: ${parsed.name}`));
console.log('');
const accounts = getProviderAccounts(provider);
if (accounts.length > 0) {
console.log('Available accounts:');
for (const acc of accounts) {
const badge = acc.isDefault ? color(' (current default)', 'info') : '';
console.log(` - ${acc.email || acc.id}${badge}`);
}
} else {
console.log(`No accounts found for provider: ${provider}`);
console.log(`Run: ccs ${provider} --auth`);
}
process.exit(1);
}
const success = setDefaultAccount(provider, account.id);
if (success) {
console.log(ok(`Default account set to: ${account.email || account.id}`));
console.log(info(`Provider: ${provider}`));
} else {
console.log(fail('Failed to set default account'));
process.exit(1);
}
}
async function handlePauseAccount(args: string[]): Promise<void> {
await initUI();
const parsed = parseProfileArgs(args);
if (!parsed.name) {
console.log(fail('Usage: ccs cliproxy pause <account> [--provider <provider>]'));
console.log('');
console.log('Pauses an account so it will be skipped in quota rotation.');
process.exit(1);
}
const provider = (parsed.provider || 'agy') as CLIProxyProvider;
const account = findAccountByQuery(provider, parsed.name);
if (!account) {
console.log(fail(`Account not found: ${parsed.name}`));
process.exit(1);
}
if (account.paused) {
console.log(warn(`Account already paused: ${account.email || account.id}`));
console.log(info(`Paused at: ${account.pausedAt || 'unknown'}`));
return;
}
const success = pauseAccount(provider, account.id);
if (success) {
console.log(ok(`Account paused: ${account.email || account.id}`));
console.log(info('Account will be skipped in quota rotation'));
} else {
console.log(fail('Failed to pause account'));
process.exit(1);
}
}
async function handleResumeAccount(args: string[]): Promise<void> {
await initUI();
const parsed = parseProfileArgs(args);
if (!parsed.name) {
console.log(fail('Usage: ccs cliproxy resume <account> [--provider <provider>]'));
console.log('');
console.log('Resumes a paused account for quota rotation.');
process.exit(1);
}
const provider = (parsed.provider || 'agy') as CLIProxyProvider;
const account = findAccountByQuery(provider, parsed.name);
if (!account) {
console.log(fail(`Account not found: ${parsed.name}`));
process.exit(1);
}
if (!account.paused) {
console.log(warn(`Account is not paused: ${account.email || account.id}`));
return;
}
const success = resumeAccount(provider, account.id);
if (success) {
console.log(ok(`Account resumed: ${account.email || account.id}`));
console.log(info('Account is now active in quota rotation'));
} else {
console.log(fail('Failed to resume account'));
process.exit(1);
}
}
async function handleQuotaStatus(): Promise<void> {
await initUI();
console.log(header('Quota Status'));
console.log('');
const provider: CLIProxyProvider = 'agy';
const accounts = getProviderAccounts(provider);
if (accounts.length === 0) {
console.log(info('No Antigravity accounts configured'));
console.log(` Run: ${color('ccs agy --auth', 'command')} to authenticate`);
return;
}
console.log(dim('Fetching quotas...'));
const quotaResult = await fetchAllProviderQuotas(provider);
// Build table rows
const rows: string[][] = [];
for (const account of accounts) {
const quotaData = quotaResult.accounts.find((q) => q.account.id === account.id);
const quota = quotaData?.quota;
// Calculate average quota
let avgQuota = 'N/A';
if (quota?.success && quota.models.length > 0) {
const avg = Math.round(
quota.models.reduce((sum, m) => sum + m.percentage, 0) / quota.models.length
);
avgQuota = `${avg}%`;
}
// Build status badges
const statusParts: string[] = [];
if (account.paused) statusParts.push(color('PAUSED', 'warning'));
if (isOnCooldown(provider, account.id)) statusParts.push(color('COOLDOWN', 'warning'));
const defaultMark = account.isDefault ? color('*', 'success') : ' ';
const tier = account.tier || 'unknown';
const status = statusParts.join(', ');
rows.push([
defaultMark,
account.nickname || account.email || account.id,
tier,
avgQuota,
status,
]);
}
console.log('');
console.log(
table(rows, {
head: ['', 'Account', 'Tier', 'Quota', 'Status'],
colWidths: [3, 30, 10, 10, 20],
})
);
console.log('');
console.log(info(`Default account marked with ${color('*', 'success')}`));
console.log('');
// Show summary of paused/cooldown accounts
const pausedCount = accounts.filter((a) => a.paused).length;
const cooldownCount = accounts.filter((a) => isOnCooldown(provider, a.id)).length;
if (pausedCount > 0) {
console.log(
warn(`${pausedCount} account(s) paused - use 'ccs cliproxy resume <account>' to re-enable`)
);
}
if (cooldownCount > 0) {
console.log(info(`${cooldownCount} account(s) on cooldown (exhausted recently)`));
}
if (pausedCount > 0 || cooldownCount > 0) {
console.log('');
}
}
// ============================================================================
// MAIN ROUTER
// ============================================================================
@@ -734,6 +946,27 @@ export async function handleCliproxyCommand(args: string[]): Promise<void> {
return;
}
// Quota management commands
if (command === 'default') {
await handleSetDefault(args.slice(1));
return;
}
if (command === 'pause') {
await handlePauseAccount(args.slice(1));
return;
}
if (command === 'resume') {
await handleResumeAccount(args.slice(1));
return;
}
if (command === 'quota') {
await handleQuotaStatus();
return;
}
const installIdx = args.indexOf('--install');
if (installIdx !== -1) {
let version = args[installIdx + 1];