mirror of
https://github.com/trycua/computer.git
synced 2026-01-01 19:10:30 -06:00
Add "cua get" cmd to CLI
This commit is contained in:
@@ -35,7 +35,7 @@ Both styles work identically - use whichever you prefer!
|
||||
### Available Commands
|
||||
|
||||
- **Authentication** - `cua auth login`, `cua auth env`, `cua auth logout` (also available as flat commands: `cua login`, `cua env`, `cua logout`)
|
||||
- **Sandbox Management** - `cua list`, `cua create`, `cua start`, `cua stop`, `cua restart`, `cua delete`, `cua vnc`
|
||||
- **Sandbox Management** - `cua list`, `cua create`, `cua get`, `cua start`, `cua stop`, `cua restart`, `cua delete`, `cua vnc`
|
||||
|
||||
## Authentication Commands
|
||||
|
||||
@@ -188,6 +188,77 @@ Job ID: job-xyz789
|
||||
Use 'cua list' to monitor provisioning progress
|
||||
```
|
||||
|
||||
### `cua get`
|
||||
|
||||
Get detailed information about a specific sandbox, including computer-server health status.
|
||||
|
||||
```bash
|
||||
cua get <name>
|
||||
|
||||
# With additional options
|
||||
cua get <name> --json
|
||||
cua get <name> --show-passwords
|
||||
cua get <name> --show-vnc-url
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--json` - Output all details in JSON format
|
||||
- `--show-passwords` - Include password in output
|
||||
- `--show-vnc-url` - Include computed NoVNC URL
|
||||
|
||||
**Example Output (default):**
|
||||
|
||||
```bash
|
||||
$ cua get my-dev-sandbox
|
||||
Name: my-dev-sandbox
|
||||
Status: running
|
||||
Host: my-dev-sandbox.containers.cloud.trycua.com
|
||||
OS Type: linux
|
||||
Computer Server Version: 0.1.30
|
||||
Computer Server Status: healthy
|
||||
```
|
||||
|
||||
**Example Output (with --show-passwords and --show-vnc-url):**
|
||||
|
||||
```bash
|
||||
$ cua get my-dev-sandbox --show-passwords --show-vnc-url
|
||||
Name: my-dev-sandbox
|
||||
Status: running
|
||||
Host: my-dev-sandbox.containers.cloud.trycua.com
|
||||
Password: secure-pass-123
|
||||
OS Type: linux
|
||||
Computer Server Version: 0.1.30
|
||||
Computer Server Status: healthy
|
||||
VNC URL: https://my-dev-sandbox.containers.cloud.trycua.com/vnc.html?autoconnect=true&password=secure-pass-123
|
||||
```
|
||||
|
||||
**Example Output (JSON format):**
|
||||
|
||||
```bash
|
||||
$ cua get my-dev-sandbox --json
|
||||
{
|
||||
"name": "my-dev-sandbox",
|
||||
"status": "running",
|
||||
"host": "my-dev-sandbox.containers.cloud.trycua.com",
|
||||
"os_type": "linux",
|
||||
"computer_server_version": "0.1.30",
|
||||
"computer_server_status": "healthy"
|
||||
}
|
||||
```
|
||||
|
||||
**Computer Server Health Check:**
|
||||
|
||||
The `cua get` command automatically probes the computer-server when the sandbox is running:
|
||||
- Checks OS type via `https://{host}:8443/status`
|
||||
- Checks version via `https://{host}:8443/cmd`
|
||||
- Shows "Computer Server Status: healthy" when both probes succeed
|
||||
- Uses a 3-second timeout for each probe
|
||||
|
||||
<Callout type="info">
|
||||
The computer server status is only checked for running sandboxes. Stopped or suspended sandboxes will not show computer server information.
|
||||
</Callout>
|
||||
|
||||
### `cua start`
|
||||
|
||||
Start a stopped sandbox.
|
||||
|
||||
@@ -58,14 +58,20 @@ bun run ./index.ts -- --help
|
||||
|
||||
**Available Commands:**
|
||||
- `list` (aliases: `ls`, `ps`) – list all sandboxes
|
||||
- `--show-passwords` – include passwords in output
|
||||
- `create` – create a new sandbox
|
||||
- `--os`: `linux`, `windows`, `macos`
|
||||
- `--size`: `small`, `medium`, `large`
|
||||
- `--region`: `north-america`, `europe`, `asia-pacific`, `south-america`
|
||||
- `get <name>` – get detailed information about a specific sandbox
|
||||
- `--json` – output in JSON format
|
||||
- `--show-passwords` – include password in output
|
||||
- `--show-vnc-url` – include computed NoVNC URL
|
||||
- `delete <name>` – delete a sandbox
|
||||
- `start <name>` – start a stopped sandbox
|
||||
- `stop <name>` – stop a running sandbox
|
||||
- `restart <name>` – restart a sandbox
|
||||
- `suspend <name>` – suspend a sandbox (preserves memory state)
|
||||
- `vnc <name>` (alias: `open`) – open VNC desktop in your browser
|
||||
|
||||
## Auth Flow (Dynamic Callback Port)
|
||||
|
||||
@@ -14,9 +14,10 @@ export async function runCli() {
|
||||
' env Export API key to .env file\n' +
|
||||
' logout Clear stored credentials\n' +
|
||||
'\n' +
|
||||
' cua sb <command> Create and manage cloud sandboxes\n' +
|
||||
' cua sb <command> Create and manage cloud sandboxes\n' +
|
||||
' list View all your sandboxes\n' +
|
||||
' create Provision a new sandbox\n' +
|
||||
' get Get detailed info about a sandbox\n' +
|
||||
' start Start or resume a sandbox\n' +
|
||||
' stop Stop a sandbox (preserves disk)\n' +
|
||||
' suspend Suspend a sandbox (preserves memory)\n' +
|
||||
|
||||
@@ -1,11 +1,131 @@
|
||||
import type { Argv } from 'yargs';
|
||||
import { ensureApiKeyInteractive } from '../auth';
|
||||
import { WEBSITE_URL } from '../config';
|
||||
import { http } from '../http';
|
||||
import { clearApiKey } from '../storage';
|
||||
import type { SandboxItem } from '../util';
|
||||
import { openInBrowser, printSandboxList } from '../util';
|
||||
|
||||
// Helper function to fetch sandbox details with computer-server probes
|
||||
async function fetchSandboxDetails(
|
||||
name: string,
|
||||
token: string,
|
||||
options: {
|
||||
showPasswords?: boolean;
|
||||
showVncUrl?: boolean;
|
||||
probeComputerServer?: boolean;
|
||||
} = {}
|
||||
) {
|
||||
// Fetch sandbox list
|
||||
const listRes = await http('/v1/vms', { token });
|
||||
if (listRes.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!listRes.ok) {
|
||||
console.error(`Request failed: ${listRes.status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sandboxes = (await listRes.json()) as SandboxItem[];
|
||||
const sandbox = sandboxes.find((s) => s.name === name);
|
||||
|
||||
if (!sandbox) {
|
||||
console.error('Sandbox not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Build result object
|
||||
const result: any = {
|
||||
name: sandbox.name,
|
||||
status: sandbox.status,
|
||||
host: sandbox.host || `${sandbox.name}.sandbox.cua.ai`,
|
||||
};
|
||||
|
||||
if (options.showPasswords) {
|
||||
result.password = sandbox.password;
|
||||
}
|
||||
|
||||
// Compute VNC URL if requested
|
||||
if (options.showVncUrl) {
|
||||
const host = sandbox.host || `${sandbox.name}.sandbox.cua.ai`;
|
||||
result.vnc_url = `https://${host}/vnc.html?autoconnect=true&password=${encodeURIComponent(sandbox.password)}`;
|
||||
}
|
||||
|
||||
// Probe computer-server if requested and sandbox is running
|
||||
if (options.probeComputerServer && sandbox.status === 'running' && sandbox.host) {
|
||||
let statusProbeSuccess = false;
|
||||
let versionProbeSuccess = false;
|
||||
|
||||
try {
|
||||
// Probe OS type
|
||||
const statusUrl = `https://${sandbox.host}:8443/status`;
|
||||
const statusController = new AbortController();
|
||||
const statusTimeout = setTimeout(() => statusController.abort(), 3000);
|
||||
|
||||
try {
|
||||
const statusRes = await fetch(statusUrl, {
|
||||
signal: statusController.signal,
|
||||
});
|
||||
clearTimeout(statusTimeout);
|
||||
|
||||
if (statusRes.ok) {
|
||||
const statusData = await statusRes.json() as { status: string; os_type: string; features?: string[] };
|
||||
result.os_type = statusData.os_type;
|
||||
statusProbeSuccess = true;
|
||||
}
|
||||
} catch (err) {
|
||||
// Timeout or connection error - skip
|
||||
}
|
||||
|
||||
// Probe computer-server version
|
||||
const versionUrl = `https://${sandbox.host}:8443/cmd`;
|
||||
const versionController = new AbortController();
|
||||
const versionTimeout = setTimeout(() => versionController.abort(), 3000);
|
||||
|
||||
try {
|
||||
const versionRes = await fetch(versionUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Container-Name': sandbox.name,
|
||||
'X-API-Key': token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
command: 'version',
|
||||
params: {},
|
||||
}),
|
||||
signal: versionController.signal,
|
||||
});
|
||||
clearTimeout(versionTimeout);
|
||||
|
||||
if (versionRes.ok) {
|
||||
const versionDataRaw = await versionRes.text();
|
||||
if (versionDataRaw.startsWith('data: ')) {
|
||||
const jsonStr = versionDataRaw.slice(6);
|
||||
const versionData = JSON.parse(jsonStr) as { success: boolean; protocol: number; package: string };
|
||||
if (versionData.package) {
|
||||
result.computer_server_version = versionData.package;
|
||||
versionProbeSuccess = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Timeout or connection error - skip
|
||||
}
|
||||
} catch (err) {
|
||||
// General error - skip probing
|
||||
}
|
||||
|
||||
// Set computer server status based on probe results
|
||||
if (statusProbeSuccess && versionProbeSuccess) {
|
||||
result.computer_server_status = 'healthy';
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Command handlers
|
||||
const listHandler = async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
@@ -254,6 +374,49 @@ const openHandler = async (argv: Record<string, unknown>) => {
|
||||
await openInBrowser(url);
|
||||
};
|
||||
|
||||
const getHandler = async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const name = String((argv as any).name);
|
||||
const showPasswords = Boolean(argv['show-passwords']);
|
||||
const showVncUrl = Boolean(argv['show-vnc-url']);
|
||||
const json = Boolean(argv.json);
|
||||
|
||||
const details = await fetchSandboxDetails(name, token, {
|
||||
showPasswords,
|
||||
showVncUrl,
|
||||
probeComputerServer: true,
|
||||
});
|
||||
|
||||
if (json) {
|
||||
console.log(JSON.stringify(details, null, 2));
|
||||
} else {
|
||||
// Pretty print the details
|
||||
console.log(`Name: ${details.name}`);
|
||||
console.log(`Status: ${details.status}`);
|
||||
console.log(`Host: ${details.host}`);
|
||||
|
||||
if (showPasswords) {
|
||||
console.log(`Password: ${details.password}`);
|
||||
}
|
||||
|
||||
if (details.os_type) {
|
||||
console.log(`OS Type: ${details.os_type}`);
|
||||
}
|
||||
|
||||
if (details.computer_server_version) {
|
||||
console.log(`Computer Server Version: ${details.computer_server_version}`);
|
||||
}
|
||||
|
||||
if (details.computer_server_status) {
|
||||
console.log(`Computer Server Status: ${details.computer_server_status}`);
|
||||
}
|
||||
|
||||
if (showVncUrl) {
|
||||
console.log(`VNC URL: ${details.vnc_url}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Register commands in both flat and grouped structures
|
||||
export function registerSandboxCommands(y: Argv) {
|
||||
// Grouped structure: cua sandbox <command> or cua sb <command> (register first to appear first in help)
|
||||
@@ -345,6 +508,29 @@ export function registerSandboxCommands(y: Argv) {
|
||||
y.positional('name', { type: 'string', describe: 'Sandbox name' }),
|
||||
openHandler
|
||||
)
|
||||
.command(
|
||||
'get <name>',
|
||||
'Get detailed information about a specific sandbox',
|
||||
(y) =>
|
||||
y
|
||||
.positional('name', { type: 'string', describe: 'Sandbox name' })
|
||||
.option('json', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Output in JSON format',
|
||||
})
|
||||
.option('show-passwords', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Include password in output',
|
||||
})
|
||||
.option('show-vnc-url', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Include computed NoVNC URL in output',
|
||||
}),
|
||||
getHandler
|
||||
)
|
||||
.demandCommand(1, 'You must provide a sandbox command');
|
||||
},
|
||||
() => {}
|
||||
@@ -433,6 +619,29 @@ export function registerSandboxCommands(y: Argv) {
|
||||
builder: (y: Argv) =>
|
||||
y.positional('name', { type: 'string', describe: 'Sandbox name' }),
|
||||
handler: openHandler,
|
||||
} as any)
|
||||
.command({
|
||||
command: 'get <name>',
|
||||
describe: false as any, // Hide from help
|
||||
builder: (y: Argv) =>
|
||||
y
|
||||
.positional('name', { type: 'string', describe: 'Sandbox name' })
|
||||
.option('json', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Output in JSON format',
|
||||
})
|
||||
.option('show-passwords', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Include password in output',
|
||||
})
|
||||
.option('show-vnc-url', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Include computed NoVNC URL in output',
|
||||
}),
|
||||
handler: getHandler,
|
||||
} as any);
|
||||
|
||||
return y;
|
||||
|
||||
Reference in New Issue
Block a user