mirror of
https://github.com/trycua/computer.git
synced 2026-01-05 21:09:58 -06:00
flatten CLI, rename VM to sandbox
This commit is contained in:
@@ -4,47 +4,44 @@ import { writeEnvFile } from '../util';
|
||||
import type { Argv } from 'yargs';
|
||||
|
||||
export function registerAuthCommands(y: Argv) {
|
||||
return y.command('auth', 'Auth commands', (ya) =>
|
||||
ya
|
||||
.command(
|
||||
'login',
|
||||
'Open browser to authorize and store API key',
|
||||
(y) =>
|
||||
y.option('api-key', {
|
||||
type: 'string',
|
||||
describe: 'API key to store directly',
|
||||
}),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
if (argv['api-key']) {
|
||||
setApiKey(String(argv['api-key']));
|
||||
console.log('API key saved');
|
||||
return;
|
||||
}
|
||||
console.log('Opening browser for CLI auth...');
|
||||
const token = await loginViaBrowser();
|
||||
setApiKey(token);
|
||||
return y
|
||||
.command(
|
||||
'login',
|
||||
'Open browser to authorize and store API key',
|
||||
(y) =>
|
||||
y.option('api-key', {
|
||||
type: 'string',
|
||||
describe: 'API key to store directly',
|
||||
}),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
if (argv['api-key']) {
|
||||
setApiKey(String(argv['api-key']));
|
||||
console.log('API key saved');
|
||||
return;
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'pull',
|
||||
'Create or update .env with CUA_API_KEY (login if needed)',
|
||||
() => {},
|
||||
async (_argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const out = await writeEnvFile(process.cwd(), token);
|
||||
console.log(`Wrote ${out}`);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'logout',
|
||||
'Remove stored API key',
|
||||
() => {},
|
||||
async (_argv: Record<string, unknown>) => {
|
||||
clearApiKey();
|
||||
console.log('Logged out');
|
||||
}
|
||||
)
|
||||
.demandCommand(1, 'Specify an auth subcommand')
|
||||
);
|
||||
console.log('Opening browser for CLI auth...');
|
||||
const token = await loginViaBrowser();
|
||||
setApiKey(token);
|
||||
console.log('API key saved');
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'env',
|
||||
'Create or update .env with CUA_API_KEY (login if needed)',
|
||||
() => {},
|
||||
async (_argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const out = await writeEnvFile(process.cwd(), token);
|
||||
console.log(`Wrote ${out}`);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'logout',
|
||||
'Remove stored API key',
|
||||
() => {},
|
||||
async (_argv: Record<string, unknown>) => {
|
||||
clearApiKey();
|
||||
console.log('Logged out');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,40 @@
|
||||
import type { Argv } from 'yargs';
|
||||
import { ensureApiKeyInteractive } from '../auth';
|
||||
import { http } from '../http';
|
||||
import { printVmList, openInBrowser } from '../util';
|
||||
import { WEBSITE_URL } from '../config';
|
||||
import type { VmItem } from '../util';
|
||||
import { http } from '../http';
|
||||
import { clearApiKey } from '../storage';
|
||||
import type { VmItem } from '../util';
|
||||
import { openInBrowser, printVmList } from '../util';
|
||||
|
||||
export function registerVmCommands(y: Argv) {
|
||||
return y.command('vm', 'VM commands', (yv) =>
|
||||
yv
|
||||
.command(
|
||||
'list',
|
||||
'List VMs',
|
||||
() => {},
|
||||
async (_argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const res = await http('/v1/vms', { token });
|
||||
if (res.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua auth login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!res.ok) {
|
||||
console.error(`Request failed: ${res.status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const data = (await res.json()) as VmItem[];
|
||||
printVmList(data);
|
||||
return y
|
||||
.command(
|
||||
['list', 'ls', 'ps'],
|
||||
'List sandboxes',
|
||||
(y) => y.option('show-passwords', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe: 'Show sandbox passwords in output'
|
||||
}),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const res = await http('/v1/vms', { token });
|
||||
if (res.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'create',
|
||||
'Create a new VM',
|
||||
if (!res.ok) {
|
||||
console.error(`Request failed: ${res.status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const data = (await res.json()) as VmItem[];
|
||||
printVmList(data, Boolean(argv['show-passwords']));
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'create',
|
||||
'Create a new sandbox',
|
||||
(y) =>
|
||||
y
|
||||
.option('os', {
|
||||
@@ -44,7 +47,7 @@ export function registerVmCommands(y: Argv) {
|
||||
type: 'string',
|
||||
choices: ['small', 'medium', 'large'],
|
||||
demandOption: true,
|
||||
describe: 'VM size configuration',
|
||||
describe: 'Sandbox size configuration',
|
||||
})
|
||||
.option('region', {
|
||||
type: 'string',
|
||||
@@ -55,7 +58,7 @@ export function registerVmCommands(y: Argv) {
|
||||
'south-america',
|
||||
],
|
||||
demandOption: true,
|
||||
describe: 'VM region',
|
||||
describe: 'Sandbox region',
|
||||
}),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
@@ -73,7 +76,7 @@ export function registerVmCommands(y: Argv) {
|
||||
|
||||
if (res.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua auth login' again.");
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -88,29 +91,29 @@ export function registerVmCommands(y: Argv) {
|
||||
}
|
||||
|
||||
if (res.status === 200) {
|
||||
// VM ready immediately
|
||||
// Sandbox ready immediately
|
||||
const data = (await res.json()) as {
|
||||
status: string;
|
||||
name: string;
|
||||
password: string;
|
||||
host: string;
|
||||
};
|
||||
console.log(`VM created and ready: ${data.name}`);
|
||||
console.log(`Sandbox created and ready: ${data.name}`);
|
||||
console.log(`Password: ${data.password}`);
|
||||
console.log(`Host: ${data.host}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === 202) {
|
||||
// VM provisioning started
|
||||
// Sandbox provisioning started
|
||||
const data = (await res.json()) as {
|
||||
status: string;
|
||||
name: string;
|
||||
job_id: string;
|
||||
};
|
||||
console.log(`VM provisioning started: ${data.name}`);
|
||||
console.log(`Sandbox provisioning started: ${data.name}`);
|
||||
console.log(`Job ID: ${data.job_id}`);
|
||||
console.log("Use 'cua vm list' to monitor provisioning progress");
|
||||
console.log("Use 'cua list' to monitor provisioning progress");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -118,10 +121,10 @@ export function registerVmCommands(y: Argv) {
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'delete <name>',
|
||||
'Delete a VM',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'VM name' }),
|
||||
.command(
|
||||
'delete <name>',
|
||||
'Delete a sandbox',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'Sandbox name' }),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const name = String((argv as any).name);
|
||||
@@ -134,18 +137,18 @@ export function registerVmCommands(y: Argv) {
|
||||
const body = (await res.json().catch(() => ({}))) as {
|
||||
status?: string;
|
||||
};
|
||||
console.log(`VM deletion initiated: ${body.status ?? 'deleting'}`);
|
||||
console.log(`Sandbox deletion initiated: ${body.status ?? 'deleting'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === 404) {
|
||||
console.error('VM not found or not owned by you');
|
||||
console.error('Sandbox not found or not owned by you');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (res.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua auth login' again.");
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -153,10 +156,10 @@ export function registerVmCommands(y: Argv) {
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'start <name>',
|
||||
'Start a VM',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'VM name' }),
|
||||
.command(
|
||||
'start <name>',
|
||||
'Start a sandbox',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'Sandbox name' }),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const name = String((argv as any).name);
|
||||
@@ -169,22 +172,22 @@ export function registerVmCommands(y: Argv) {
|
||||
return;
|
||||
}
|
||||
if (res.status === 404) {
|
||||
console.error('VM not found');
|
||||
console.error('Sandbox not found');
|
||||
process.exit(1);
|
||||
}
|
||||
if (res.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua auth login' again.");
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.error(`Unexpected status: ${res.status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'stop <name>',
|
||||
'Stop a VM',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'VM name' }),
|
||||
.command(
|
||||
'stop <name>',
|
||||
'Stop a sandbox',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'Sandbox name' }),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const name = String((argv as any).name);
|
||||
@@ -200,22 +203,22 @@ export function registerVmCommands(y: Argv) {
|
||||
return;
|
||||
}
|
||||
if (res.status === 404) {
|
||||
console.error('VM not found');
|
||||
console.error('Sandbox not found');
|
||||
process.exit(1);
|
||||
}
|
||||
if (res.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua auth login' again.");
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.error(`Unexpected status: ${res.status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'restart <name>',
|
||||
'Restart a VM',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'VM name' }),
|
||||
.command(
|
||||
'restart <name>',
|
||||
'Restart a sandbox',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'Sandbox name' }),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const name = String((argv as any).name);
|
||||
@@ -234,29 +237,29 @@ export function registerVmCommands(y: Argv) {
|
||||
return;
|
||||
}
|
||||
if (res.status === 404) {
|
||||
console.error('VM not found');
|
||||
console.error('Sandbox not found');
|
||||
process.exit(1);
|
||||
}
|
||||
if (res.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua auth login' again.");
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.error(`Unexpected status: ${res.status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'vnc <name>',
|
||||
'Open NoVNC for a VM in your browser',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'VM name' }),
|
||||
.command(
|
||||
'open <name>',
|
||||
'Open NoVNC for a sandbox in your browser',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'Sandbox name' }),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const name = String((argv as any).name);
|
||||
const listRes = await http('/v1/vms', { token });
|
||||
if (listRes.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua auth login' again.");
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!listRes.ok) {
|
||||
@@ -266,7 +269,7 @@ export function registerVmCommands(y: Argv) {
|
||||
const vms = (await listRes.json()) as VmItem[];
|
||||
const vm = vms.find((v) => v.name === name);
|
||||
if (!vm) {
|
||||
console.error('VM not found');
|
||||
console.error('Sandbox not found');
|
||||
process.exit(1);
|
||||
}
|
||||
const host =
|
||||
@@ -278,17 +281,17 @@ export function registerVmCommands(y: Argv) {
|
||||
await openInBrowser(url);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'chat <name>',
|
||||
'Open CUA dashboard playground for a VM',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'VM name' }),
|
||||
.command(
|
||||
'chat <name>',
|
||||
'Open CUA playground for a sandbox',
|
||||
(y) => y.positional('name', { type: 'string', describe: 'Sandbox name' }),
|
||||
async (argv: Record<string, unknown>) => {
|
||||
const token = await ensureApiKeyInteractive();
|
||||
const name = String((argv as any).name);
|
||||
const listRes = await http('/v1/vms', { token });
|
||||
if (listRes.status === 401) {
|
||||
clearApiKey();
|
||||
console.error("Unauthorized. Try 'cua auth login' again.");
|
||||
console.error("Unauthorized. Try 'cua login' again.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!listRes.ok) {
|
||||
@@ -298,7 +301,7 @@ export function registerVmCommands(y: Argv) {
|
||||
const vms = (await listRes.json()) as VmItem[];
|
||||
const vm = vms.find((v) => v.name === name);
|
||||
if (!vm) {
|
||||
console.error('VM not found');
|
||||
console.error('Sandbox not found');
|
||||
process.exit(1);
|
||||
}
|
||||
const host =
|
||||
@@ -310,7 +313,5 @@ export function registerVmCommands(y: Argv) {
|
||||
console.log(`Opening Playground: ${url}`);
|
||||
await openInBrowser(url);
|
||||
}
|
||||
)
|
||||
.demandCommand(1, 'Specify a vm subcommand')
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,17 +25,29 @@ export type VmItem = {
|
||||
host?: string;
|
||||
};
|
||||
|
||||
export function printVmList(items: VmItem[]) {
|
||||
export function printVmList(items: VmItem[], showPasswords: boolean = false) {
|
||||
const headers = showPasswords
|
||||
? ['NAME', 'STATUS', 'PASSWORD', 'HOST']
|
||||
: ['NAME', 'STATUS', 'HOST'];
|
||||
|
||||
const rows: string[][] = [
|
||||
['NAME', 'STATUS', 'PASSWORD', 'HOST'],
|
||||
...items.map((v) => [v.name, String(v.status), v.password, v.host || '']),
|
||||
headers,
|
||||
...items.map((v) => showPasswords
|
||||
? [v.name, String(v.status), v.password, v.host || '']
|
||||
: [v.name, String(v.status), v.host || '']
|
||||
),
|
||||
];
|
||||
const widths: number[] = [0, 0, 0, 0];
|
||||
|
||||
const numCols = headers.length;
|
||||
const widths: number[] = new Array(numCols).fill(0);
|
||||
|
||||
for (const r of rows)
|
||||
for (let i = 0; i < 4; i++)
|
||||
for (let i = 0; i < numCols; i++)
|
||||
widths[i] = Math.max(widths[i] ?? 0, (r[i] ?? '').length);
|
||||
|
||||
for (const r of rows)
|
||||
console.log(r.map((c, i) => (c ?? '').padEnd(widths[i] ?? 0)).join(' '));
|
||||
|
||||
if (items.length === 0) console.log('No VMs found');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user