feat(cliproxy): add explicit start and restart commands

This commit is contained in:
Tam Nhu Tran
2026-02-14 09:41:40 +07:00
parent 2fe8f2b4ca
commit 8395364970
7 changed files with 118 additions and 4 deletions
+9
View File
@@ -229,6 +229,15 @@ ccs cliproxy doctor # Check quota status for all agy accounts
**Auto-Failover**: When an Antigravity account runs out of quota, CCS automatically switches to another account with remaining capacity. Shared GCP project accounts are excluded (pooled quota).
### CLIProxy Lifecycle
```bash
ccs cliproxy start # Start CLIProxy background service
ccs cliproxy status # Check running status
ccs cliproxy restart # Restart CLIProxy service
ccs cliproxy stop # Stop running CLIProxy service
```
<br>
## Configuration
+2
View File
@@ -22,10 +22,12 @@ export {
export {
getProxyStatus,
stopProxy,
startProxy,
isProxyRunning,
getActiveSessionCount,
type ProxyStatusResult,
type StopProxyResult,
type StartProxyResult,
} from './proxy-lifecycle-service';
// Binary management
@@ -2,13 +2,15 @@
* CLIProxy Proxy Lifecycle Service
*
* Handles start/stop/status operations for CLIProxy instances.
* Delegates to session-tracker for actual process management.
* Delegates to session-tracker and service-manager for actual process management.
*/
import {
stopProxy as stopProxySession,
getProxyStatus as getProxyStatusSession,
} from '../session-tracker';
import { ensureCliproxyService } from '../service-manager';
import { CLIPROXY_DEFAULT_PORT } from '../config-generator';
/** Proxy status result */
export interface ProxyStatusResult {
@@ -27,6 +29,15 @@ export interface StopProxyResult {
error?: string;
}
/** Start proxy result */
export interface StartProxyResult {
started: boolean;
alreadyRunning: boolean;
port: number;
configRegenerated?: boolean;
error?: string;
}
/**
* Get current proxy status
*/
@@ -41,6 +52,16 @@ export async function stopProxy(): Promise<StopProxyResult> {
return stopProxySession();
}
/**
* Start CLIProxy service (or reuse existing running instance)
*/
export async function startProxy(
port: number = CLIPROXY_DEFAULT_PORT,
verbose: boolean = false
): Promise<StartProxyResult> {
return ensureCliproxyService(port, verbose);
}
/**
* Check if proxy is currently running
*/
+2
View File
@@ -61,6 +61,8 @@ export async function showHelp(): Promise<void> {
[
'Proxy Lifecycle:',
[
['start', 'Start CLIProxy instance in background'],
['restart', 'Restart CLIProxy instance'],
['status', 'Show running CLIProxy status'],
['stop', 'Stop running CLIProxy instance'],
['doctor', 'Quota diagnostics and shared project detection'],
+16 -1
View File
@@ -20,7 +20,12 @@ import {
handleResumeAccount,
} from './quota-subcommand';
import { handleCreate, handleRemove, handleEdit } from './variant-subcommand';
import { handleProxyStatus, handleStop } from './proxy-lifecycle-subcommand';
import {
handleProxyStatus,
handleStart,
handleStop,
handleRestart,
} from './proxy-lifecycle-subcommand';
import { showStatus, handleInstallVersion, handleInstallLatest } from './install-subcommand';
import { showHelp } from './help-subcommand';
import {
@@ -198,11 +203,21 @@ export async function handleCliproxyCommand(args: string[]): Promise<void> {
}
// 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;
@@ -2,15 +2,68 @@
* CLIProxy Lifecycle Management
*
* Handles:
* - ccs cliproxy start
* - ccs cliproxy restart
* - ccs cliproxy status
* - ccs cliproxy stop
*/
import { initUI, header, color, dim, ok, warn, info } from '../../utils/ui';
import { getProxyStatus, stopProxy } from '../../cliproxy/services';
import { getProxyStatus, startProxy, stopProxy } from '../../cliproxy/services';
import { detectRunningProxy } from '../../cliproxy/proxy-detector';
import { CLIPROXY_DEFAULT_PORT } from '../../cliproxy/config-generator';
export async function handleStart(verbose = false): Promise<void> {
await initUI();
console.log(header('Start CLIProxy'));
console.log('');
const result = await startProxy(CLIPROXY_DEFAULT_PORT, verbose);
if (result.started) {
if (result.alreadyRunning) {
console.log(info(`CLIProxy already running on port ${result.port}`));
if (result.configRegenerated) {
console.log(warn('Config updated - restart CLIProxy to apply changes'));
}
} else {
console.log(ok(`CLIProxy started on port ${result.port}`));
}
console.log(dim('To stop: ccs cliproxy stop'));
} else {
console.log(warn(result.error || 'Failed to start CLIProxy'));
}
console.log('');
}
export async function handleRestart(verbose = false): Promise<void> {
await initUI();
console.log(header('Restart CLIProxy'));
console.log('');
const stopResult = await stopProxy();
if (stopResult.stopped) {
console.log(ok(`CLIProxy stopped (PID ${stopResult.pid})`));
} else if (stopResult.error === 'No active CLIProxy session found') {
console.log(info('No active CLIProxy session found, starting a new instance'));
} else {
console.log(warn(stopResult.error || 'Failed to stop existing CLIProxy'));
console.log(info('Attempting to start a fresh instance...'));
}
const startResult = await startProxy(CLIPROXY_DEFAULT_PORT, verbose);
if (startResult.started) {
if (startResult.alreadyRunning) {
console.log(info(`CLIProxy already running on port ${startResult.port}`));
} else {
console.log(ok(`CLIProxy started on port ${startResult.port}`));
}
} else {
console.log(warn(startResult.error || 'Failed to restart CLIProxy'));
}
console.log('');
}
export async function handleProxyStatus(): Promise<void> {
await initUI();
console.log(header('CLIProxy Status'));
+13 -1
View File
@@ -491,6 +491,18 @@ describe('CLIProxy Command - Proxy Lifecycle', () => {
});
describe('Command Routing', () => {
it('routes "start" subcommand correctly', () => {
const args = ['cliproxy', 'start'];
const subcommand = args[1];
assert.strictEqual(subcommand, 'start');
});
it('routes "restart" subcommand correctly', () => {
const args = ['cliproxy', 'restart'];
const subcommand = args[1];
assert.strictEqual(subcommand, 'restart');
});
it('routes "stop" subcommand correctly', () => {
const args = ['cliproxy', 'stop'];
const subcommand = args[1];
@@ -504,7 +516,7 @@ describe('CLIProxy Command - Proxy Lifecycle', () => {
});
it('handles unknown subcommand', () => {
const validSubcommands = ['stop', 'status', 'create', 'list', 'remove'];
const validSubcommands = ['start', 'restart', 'stop', 'status', 'create', 'list', 'remove'];
const unknownCommand = 'invalid';
assert.strictEqual(validSubcommands.includes(unknownCommand), false);
});