diff --git a/libs/typescript/cua-cli/src/cli.ts b/libs/typescript/cua-cli/src/cli.ts index 9c086379..7ee7d080 100644 --- a/libs/typescript/cua-cli/src/cli.ts +++ b/libs/typescript/cua-cli/src/cli.ts @@ -17,7 +17,9 @@ export async function runCli() { ' cua sb Create and manage cloud sandboxes\n' + ' list View all your sandboxes\n' + ' create Provision a new sandbox\n' + - ' start/stop Control sandbox state\n' + + ' start Start or resume a sandbox\n' + + ' stop Stop a sandbox (preserves disk)\n' + + ' suspend Suspend a sandbox (preserves memory)\n' + ' vnc Open remote desktop\n' + '\n' + 'Documentation: https://docs.cua.ai/libraries/cua-cli/commands' diff --git a/libs/typescript/cua-cli/src/commands/sandbox.ts b/libs/typescript/cua-cli/src/commands/sandbox.ts index 36e66bf5..5d2bde93 100644 --- a/libs/typescript/cua-cli/src/commands/sandbox.ts +++ b/libs/typescript/cua-cli/src/commands/sandbox.ts @@ -191,6 +191,41 @@ const restartHandler = async (argv: Record) => { process.exit(1); }; +const suspendHandler = async (argv: Record) => { + const token = await ensureApiKeyInteractive(); + const name = String((argv as any).name); + const res = await http(`/v1/vms/${encodeURIComponent(name)}/suspend`, { + token, + method: 'POST', + }); + if (res.status === 202) { + const body = (await res.json().catch(() => ({}))) as { + status?: string; + }; + console.log(body.status ?? 'suspending'); + return; + } + if (res.status === 404) { + console.error('Sandbox not found'); + process.exit(1); + } + if (res.status === 401) { + clearApiKey(); + console.error("Unauthorized. Try 'cua login' again."); + process.exit(1); + } + if (res.status === 400 || res.status === 500) { + const body = (await res.json().catch(() => ({}))) as { error?: string }; + console.error( + body.error ?? + "Suspend not supported for this VM. Use 'cua sb stop' instead." + ); + process.exit(1); + } + console.error(`Unexpected status: ${res.status}`); + process.exit(1); +}; + const openHandler = async (argv: Record) => { const token = await ensureApiKeyInteractive(); const name = String((argv as any).name); @@ -296,6 +331,13 @@ export function registerSandboxCommands(y: Argv) { y.positional('name', { type: 'string', describe: 'Sandbox name' }), restartHandler ) + .command( + 'suspend ', + 'Suspend a sandbox, preserving memory state (use start to resume)', + (y) => + y.positional('name', { type: 'string', describe: 'Sandbox name' }), + suspendHandler + ) .command( ['vnc ', 'open '], 'Open remote desktop (VNC) connection in your browser', @@ -378,6 +420,13 @@ export function registerSandboxCommands(y: Argv) { y.positional('name', { type: 'string', describe: 'Sandbox name' }), handler: restartHandler, } as any) + .command({ + command: 'suspend ', + describe: false as any, // Hide from help + builder: (y: Argv) => + y.positional('name', { type: 'string', describe: 'Sandbox name' }), + handler: suspendHandler, + } as any) .command({ command: ['vnc ', 'open '], describe: false as any, // Hide from help diff --git a/libs/typescript/cua-cli/src/util.ts b/libs/typescript/cua-cli/src/util.ts index 36c6e0d6..60147049 100644 --- a/libs/typescript/cua-cli/src/util.ts +++ b/libs/typescript/cua-cli/src/util.ts @@ -16,6 +16,8 @@ export type SandboxStatus = | 'pending' | 'running' | 'stopped' + | 'suspended' + | 'suspending' | 'terminated' | 'failed'; export type SandboxItem = {