diff --git a/README.md b/README.md
index b3125fb0..b4861825 100644
--- a/README.md
+++ b/README.md
@@ -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
+```
+
## Configuration
diff --git a/package.json b/package.json
index 4a73a123..74a1dbaa 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
- "version": "7.43.0-dev.15",
+ "version": "7.43.0-dev.17",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",
diff --git a/src/cliproxy/services/proxy-lifecycle-service.ts b/src/cliproxy/services/proxy-lifecycle-service.ts
index 4c140262..2d1388f2 100644
--- a/src/cliproxy/services/proxy-lifecycle-service.ts
+++ b/src/cliproxy/services/proxy-lifecycle-service.ts
@@ -62,6 +62,16 @@ export async function startProxy(
return ensureCliproxyService(port, verbose);
}
+/**
+ * Start CLIProxy service (or reuse existing running instance)
+ */
+export async function startProxy(
+ port: number = CLIPROXY_DEFAULT_PORT,
+ verbose: boolean = false
+): Promise {
+ return ensureCliproxyService(port, verbose);
+}
+
/**
* Check if proxy is currently running
*/
diff --git a/src/commands/cliproxy/proxy-lifecycle-subcommand.ts b/src/commands/cliproxy/proxy-lifecycle-subcommand.ts
index 1dad5b5a..f9b52cad 100644
--- a/src/commands/cliproxy/proxy-lifecycle-subcommand.ts
+++ b/src/commands/cliproxy/proxy-lifecycle-subcommand.ts
@@ -76,6 +76,57 @@ export async function handleRestart(verbose = false): Promise {
console.log('');
}
+export async function handleStart(verbose = false): Promise {
+ 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 {
+ 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 {
await initUI();
console.log(header('CLIProxy Status'));