This commit is contained in:
f-trycua
2025-11-12 19:16:00 +01:00
parent 6f04b80b11
commit 8cc273e0e8
12 changed files with 86 additions and 59 deletions

View File

@@ -1,7 +1,4 @@
{
"title": "Integrations",
"pages": [
"hud",
"observability"
]
}
"pages": ["hud", "observability"]
}

View File

@@ -1,9 +1,5 @@
{
"title": "CLI",
"description": "Command-line interface for CUA",
"pages": [
"index",
"installation",
"commands"
]
}
"pages": ["index", "installation", "commands"]
}

View File

@@ -15,4 +15,4 @@
"---[CodeXml]API Reference---",
"...libraries"
]
}
}

View File

@@ -9,12 +9,7 @@ export function Hero({ children }: { children: React.ReactNode }) {
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<pattern
id="hero-grid"
width="40"
height="40"
patternUnits="userSpaceOnUse"
>
<pattern id="hero-grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path
d="M 40 0 L 0 0 0 40"
fill="none"
@@ -33,9 +28,7 @@ export function Hero({ children }: { children: React.ReactNode }) {
</div>
{/* Content */}
<div className="relative z-10">
{children}
</div>
<div className="relative z-10">{children}</div>
</div>
);
}

View File

@@ -2,11 +2,7 @@
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -20,12 +16,8 @@
"jsx": "react-jsx",
"incremental": true,
"paths": {
"@/.source": [
"./.source/index.ts"
],
"@/*": [
"./src/*"
]
"@/.source": ["./.source/index.ts"],
"@/*": ["./src/*"]
},
"plugins": [
{
@@ -40,7 +32,5 @@
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
"exclude": ["node_modules"]
}

View File

@@ -24,7 +24,8 @@ export async function loginViaBrowser(): Promise<string> {
port: 0,
fetch(req) {
const u = new URL(req.url);
if (u.pathname !== '/callback') return new Response('Not found', { status: 404 });
if (u.pathname !== '/callback')
return new Response('Not found', { status: 404 });
const token = u.searchParams.get('token');
if (!token) return new Response('Missing token', { status: 400 });
resolveToken(token);
@@ -38,8 +39,12 @@ export async function loginViaBrowser(): Promise<string> {
const callbackURL = `http://${CALLBACK_HOST}:${server.port}/callback`;
const url = `${AUTH_PAGE}?callback_url=${encodeURIComponent(callbackURL)}`;
console.log(`${c.cyan}${c.bold}Opening your default browser to authorize the CLI...${c.reset}`);
console.log(`${c.dim}If the browser does not open automatically, copy/paste this URL:${c.reset}`);
console.log(
`${c.cyan}${c.bold}Opening your default browser to authorize the CLI...${c.reset}`
);
console.log(
`${c.dim}If the browser does not open automatically, copy/paste this URL:${c.reset}`
);
console.log(`${c.yellow}${c.underline}${url}${c.reset}`);
await openInBrowser(url);

View File

@@ -9,7 +9,11 @@ export function registerAuthCommands(y: Argv) {
.command(
'login',
'Open browser to authorize and store API key',
(y) => y.option('api-key', { type: 'string', describe: 'API key to store directly' }),
(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']));

View File

@@ -48,7 +48,12 @@ export function registerVmCommands(y: Argv) {
})
.option('region', {
type: 'string',
choices: ['north-america', 'europe', 'asia-pacific', 'south-america'],
choices: [
'north-america',
'europe',
'asia-pacific',
'south-america',
],
demandOption: true,
describe: 'VM region',
}),
@@ -98,7 +103,11 @@ export function registerVmCommands(y: Argv) {
if (res.status === 202) {
// VM provisioning started
const data = (await res.json()) as { status: string; name: string; job_id: string };
const data = (await res.json()) as {
status: string;
name: string;
job_id: string;
};
console.log(`VM provisioning started: ${data.name}`);
console.log(`Job ID: ${data.job_id}`);
console.log("Use 'cua vm list' to monitor provisioning progress");
@@ -122,7 +131,9 @@ export function registerVmCommands(y: Argv) {
});
if (res.status === 202) {
const body = (await res.json().catch(() => ({}))) as { status?: string };
const body = (await res.json().catch(() => ({}))) as {
status?: string;
};
console.log(`VM deletion initiated: ${body.status ?? 'deleting'}`);
return;
}
@@ -182,7 +193,9 @@ export function registerVmCommands(y: Argv) {
method: 'POST',
});
if (res.status === 202) {
const body = (await res.json().catch(() => ({}))) as { status?: string };
const body = (await res.json().catch(() => ({}))) as {
status?: string;
};
console.log(body.status ?? 'stopping');
return;
}
@@ -206,12 +219,17 @@ export function registerVmCommands(y: Argv) {
async (argv: Record<string, unknown>) => {
const token = await ensureApiKeyInteractive();
const name = String((argv as any).name);
const res = await http(`/v1/vms/${encodeURIComponent(name)}/restart`, {
token,
method: 'POST',
});
const res = await http(
`/v1/vms/${encodeURIComponent(name)}/restart`,
{
token,
method: 'POST',
}
);
if (res.status === 202) {
const body = (await res.json().catch(() => ({}))) as { status?: string };
const body = (await res.json().catch(() => ({}))) as {
status?: string;
};
console.log(body.status ?? 'restarting');
return;
}
@@ -252,7 +270,9 @@ export function registerVmCommands(y: Argv) {
process.exit(1);
}
const host =
vm.host && vm.host.length ? vm.host : `${vm.name}.containers.cloud.trycua.com`;
vm.host && vm.host.length
? vm.host
: `${vm.name}.containers.cloud.trycua.com`;
const url = `https://${host}/vnc.html?autoconnect=true&password=${encodeURIComponent(vm.password)}`;
console.log(`Opening NoVNC: ${url}`);
await openInBrowser(url);
@@ -282,7 +302,9 @@ export function registerVmCommands(y: Argv) {
process.exit(1);
}
const host =
vm.host && vm.host.length ? vm.host : `${vm.name}.containers.cloud.trycua.com`;
vm.host && vm.host.length
? vm.host
: `${vm.name}.containers.cloud.trycua.com`;
const base = WEBSITE_URL.replace(/\/$/, '');
const url = `${base}/dashboard/playground?host=${encodeURIComponent(host)}&id=${encodeURIComponent(vm.name)}&name=${encodeURIComponent(vm.name)}&vnc_password=${encodeURIComponent(vm.password)}&fullscreen=true`;
console.log(`Opening Playground: ${url}`);

View File

@@ -1,5 +1,7 @@
export const WEBSITE_URL = Bun.env.CUA_WEBSITE_URL?.replace(/\/$/, '') || 'https://cua.ai';
export const API_BASE = Bun.env.CUA_API_BASE?.replace(/\/$/, '') || 'https://api.cua.ai';
export const WEBSITE_URL =
Bun.env.CUA_WEBSITE_URL?.replace(/\/$/, '') || 'https://cua.ai';
export const API_BASE =
Bun.env.CUA_API_BASE?.replace(/\/$/, '') || 'https://api.cua.ai';
export const AUTH_PAGE = `${WEBSITE_URL}/cli-auth`;
export const CALLBACK_HOST = '127.0.0.1';

View File

@@ -5,7 +5,9 @@ export async function http(
opts: { method?: string; token: string; body?: any }
): Promise<Response> {
const url = `${API_BASE}${path}`;
const headers: Record<string, string> = { Authorization: `Bearer ${opts.token}` };
const headers: Record<string, string> = {
Authorization: `Bearer ${opts.token}`,
};
if (opts.body) headers['content-type'] = 'application/json';
return fetch(url, {
method: opts.method || 'GET',

View File

@@ -4,7 +4,9 @@ import { getDbPath } from './config';
function getDb(): Database {
const db = new Database(getDbPath());
db.exec('PRAGMA journal_mode = WAL;');
db.exec('CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT NOT NULL);');
db.exec(
'CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT NOT NULL);'
);
return db;
}
@@ -23,7 +25,9 @@ export function setApiKey(token: string) {
export function getApiKey(): string | null {
const db = getDb();
try {
const row = db.query("SELECT v FROM kv WHERE k='api_key'").get() as { v: string } | undefined;
const row = db.query("SELECT v FROM kv WHERE k='api_key'").get() as
| { v: string }
| undefined;
return row?.v ?? null;
} finally {
db.close();

View File

@@ -12,8 +12,18 @@ export async function writeEnvFile(cwd: string, key: string) {
return path;
}
export type VmStatus = 'pending' | 'running' | 'stopped' | 'terminated' | 'failed';
export type VmItem = { name: string; password: string; status: VmStatus; host?: string };
export type VmStatus =
| 'pending'
| 'running'
| 'stopped'
| 'terminated'
| 'failed';
export type VmItem = {
name: string;
password: string;
status: VmStatus;
host?: string;
};
export function printVmList(items: VmItem[]) {
const rows: string[][] = [
@@ -22,8 +32,10 @@ export function printVmList(items: VmItem[]) {
];
const widths: number[] = [0, 0, 0, 0];
for (const r of rows)
for (let i = 0; i < 4; 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(' '));
for (let i = 0; i < 4; 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');
}