VMs
@solarisdk/desktop: classes DesktopClient and Desktop. A VM is a GUI desktop you drive with mouse, keyboard, and screenshots, and stream live. See the TypeScript SDK hub for install and configuration.
npm install @solarisdk/desktopContents
DesktopClient: new DesktopClient(), create(), connect(), get(), resume(), pause(), attach(), destroy(), volumesDesktop: health(), screenshot(), mouse.*, keyboard.*, display.*, clipboard.*, open(), stream.*, record.*, process.*, ports.*, pkg.install(), exec(), execStream(), fs.*, inherited members- Types:
CreateDesktopOptions,CreateDesktopResponse,GetDesktopResponse,ScreenshotOptions,HealthResult,ProcessInfo,PortInfo,RecordStartResult
DesktopClient
The VM entry point. Talks the gateway HTTP API.
Properties
volumesVolumeClient: persistent volume CRUD. Same surface asSandboxClient.volumes.
Constructors
new DesktopClient()
new DesktopClient(options: DesktopClientOptions): DesktopClientCreates a client. Does not open any connection.
Parameters:
options.apiKeystring: required.options.baseUrlstring: required, e.g.https://api.getsolari.com.options.fetch?typeof fetch: defaults to the global.options.callTimeoutMs?number: per-RPC timeout for handles this client creates.
Returns: DesktopClient
Throws: SolariError if apiKey or baseUrl is missing.
Example:
import { DesktopClient } from "@solarisdk/desktop";
const client = new DesktopClient({
apiKey: process.env.SOLARI_API_KEY!,
baseUrl: "https://api.getsolari.com",
});Methods
create()
create(opts?: CreateDesktopOptions): Promise<Desktop>Creates a VM and returns a live handle. The call sends an idempotency key, so retrying is safe.
Parameters:
opts?CreateDesktopOptions: see CreateDesktopOptions.
Returns: Promise<Desktop>
Throws: AuthError, PlanError, ConcurrencyLimitError, NoCapacityError, or GatewayError.
Example:
const vm = await client.create({
template: "office",
resolution: "1280x720",
cpu: 2,
memMb: 4096,
});connect()
connect(sessionId: string): Promise<Desktop>Re-attaches by id, resuming the session first if it is paused. The canonical way back to a VM when all you have is its id.
Parameters:
sessionIdstring: the session id.
Returns: Promise<Desktop>
Throws: GatewayError if the id is unknown.
Example:
const vm = await client.connect(sessionId); // resumes if paused
await vm.connect(); // open the control channel
await vm.keyboard.type("back again");get()
get(sessionId: string): Promise<GetDesktopResponse>Fetches a session’s current status. Returns the raw record, not a handle. The gateway does not re-issue control URLs here.
Parameters:
sessionIdstring: the session id.
Returns: Promise<GetDesktopResponse>: status, expiresAt, optional recordingUrl.
Example:
const st = await client.get(sessionId);
console.log(st.status); // "ready" | "paused" | ...pause()
pause(sessionId: string): Promise<DesktopLifecycleResponse>Saves full RAM + disk state so it resumes exactly where it left off. Billing for compute stops and it stops counting against your concurrency limit. Prefer Desktop.pause() when you hold a live handle.
Parameters:
sessionIdstring: the session id.
Returns: Promise<DesktopLifecycleResponse>: sessionId, status.
Example:
await client.pause(sessionId);resume()
resume(sessionId: string): Promise<Desktop>Restores full RAM + disk state and returns a new handle. Always resumes. Use connect() if the session may already be running.
Parameters:
sessionIdstring: the session id.
Returns: Promise<Desktop>
Example:
const vm = await client.resume(sessionId);attach()
attach(session: CreateDesktopResponse): DesktopRebuilds a handle from a saved create response. Synchronous, no network call. Use it to carry a session across processes.
Parameters:
sessionCreateDesktopResponse:sessionId,controlUrl,streamUrl,expiresAt.
Returns: Desktop
Example:
const saved = JSON.parse(await readFile("session.json", "utf8"));
const vm = client.attach(saved);destroy()
destroy(sessionId: string): Promise<DeleteDesktopResponse>Destroys a session. Idempotent.
Parameters:
sessionIdstring: the session id.
Returns: Promise<DeleteDesktopResponse>
Example:
await client.destroy(sessionId);volumes
Identical to SandboxClient.volumes: create(), list(), get(), delete().
Example:
const vol = await client.volumes.create({ name: "assets" });
const vm = await client.create({
template: "office",
volumes: [{ volumeId: vol.volumeId, path: "/data" }],
});Desktop
A live GUI session. Construct via DesktopClient.create().
Properties
idstring: the session id.sessionIdstring: alias ofid.streamUrlstring:wss://URL serving RFB (VNC) bytes for the live view.controlUrlstring:wss://JSON-RPC control channel.expiresAtstring: ISO 8601 expiry.connectedboolean: whether the control channel is open.
Methods
health()
health(): Promise<HealthResult>Readiness probe for the display, VNC server, and guest agent.
Returns: Promise<HealthResult>: ready, display, vnc.
Example:
const h = await vm.health();
if (!h.ready) throw new Error("desktop not ready");screenshot()
screenshot(opts?: ScreenshotOptions): Promise<Uint8Array>Captures the current screen as image bytes.
Parameters:
opts.format?"png" | "jpeg": default"png".opts.quality?number: JPEG quality 1 to 100; ignored for PNG.
Returns: Promise<Uint8Array>: decoded image bytes.
Example:
import { writeFile } from "node:fs/promises";
await writeFile("shot.png", await vm.screenshot());
const jpeg = await vm.screenshot({ format: "jpeg", quality: 80 });mouse.*
mouse.move(x: number, y: number, opts?: { humanize?: boolean }): Promise<void>
mouse.click(x: number, y: number, opts?: { button?: MouseButton; humanize?: boolean }): Promise<void>
mouse.doubleClick(x: number, y: number, opts?: ClickOptions): Promise<void>
mouse.down(x: number, y: number, button?: MouseButton): Promise<void>
mouse.up(x: number, y: number, button?: MouseButton): Promise<void>
mouse.scroll(x: number, y: number, opts?: { button?: MouseButton; humanize?: boolean }): Promise<void>
mouse.drag(from: { x: number; y: number }, to: { x: number; y: number }, button?: MouseButton): Promise<void>Drive the pointer at absolute screen coordinates. The origin is the top-left of the display.
Parameters:
x,ynumber: absolute coordinates.button?MouseButton:"left"(default),"middle","right".humanize?boolean: use a humanized trajectory instead of teleporting. Honored bymove,click, andscroll.from,to{ x: number; y: number }: drag endpoints.
Returns: Promise<void>
Example:
await vm.mouse.move(400, 300, { humanize: true });
await vm.mouse.click(400, 300);
await vm.mouse.doubleClick(120, 80);
await vm.mouse.scroll(400, 300);
await vm.mouse.drag({ x: 100, y: 100 }, { x: 500, y: 400 });doubleClick accepts a full ClickOptions for type parity but only forwards button; drag takes no options at all. Both always teleport.keyboard.*
keyboard.type(text: string): Promise<void>
keyboard.press(keys: string | string[]): Promise<void>
keyboard.hotkey(...keys: string[]): Promise<void>
keyboard.down(keys: string | string[]): Promise<void>
keyboard.up(keys: string | string[]): Promise<void>Type literal text, or press key chords. hotkey() is press() with variadic arguments.
Parameters:
textstring: literal text to type.keysstring | string[]: key names, e.g."Return"or["ctrl", "c"].
Returns: Promise<void>
Example:
await vm.keyboard.type("hello world");
await vm.keyboard.press("Return");
await vm.keyboard.hotkey("ctrl", "c");
await vm.keyboard.down("shift"); // hold
await vm.keyboard.press("Left");
await vm.keyboard.up("shift");display.*
display.set(w: number, h: number): Promise<void>
display.size(): Promise<{ w: number; h: number }>
display.cursor(): Promise<{ x: number; y: number }>Read or change the display resolution and read the cursor position.
Parameters:
w,hnumber: new resolution in pixels.
Returns: Promise<void> / Promise<{ w, h }> / Promise<{ x, y }>
Example:
await vm.display.set(1920, 1080);
const { w, h } = await vm.display.size();
const { x, y } = await vm.display.cursor();clipboard.*
clipboard.get(): Promise<string>
clipboard.set(text: string): Promise<void>Read or write the guest clipboard. get() returns "" when the clipboard is empty.
Parameters:
textstring: content to place on the clipboard.
Returns: Promise<string> / Promise<void>
Example:
await vm.clipboard.set("pasted from the host");
await vm.keyboard.hotkey("ctrl", "v");
console.log(await vm.clipboard.get());open()
open(name: string, args?: string[]): Promise<number>Launches a GUI application by name and returns its pid.
Parameters:
namestring: executable name, e.g."firefox".args?string[]: arguments.
Returns: Promise<number>, the pid.
Example:
const pid = await vm.open("firefox", ["https://example.com"]);stream.*
stream.start(): Promise<{ streamUrl: string; token?: string }>
stream.stop(): Promise<void>Returns the embeddable live-view URL. The stream URL is minted at create time, so start() is a getter and stop() is a no-op. The RFB socket belongs to the caller.
Returns: Promise<{ streamUrl: string; token?: string }> / Promise<void>
Example:
const { streamUrl } = await vm.stream.start();
// same value as vm.streamUrl, hand it to a noVNC client@solarisdk/desktop also exports mountDesktop(el, { streamUrl }), which wires noVNC to a DOM element for you. It needs the optional @novnc/novnc peer dependency and only runs in a browser.record.*
record.start(opts?: { fps?: number; format?: string; path?: string }): Promise<RecordStartResult>
record.stop(): Promise<RecordStopResult>Records the session to an mp4 inside the guest. Retrieve it afterwards with downloadUrl().
Parameters:
opts.fps?number: capture frame rate.opts.format?string,opts.path?string: output format and in-guest path.
Returns: Promise<RecordStartResult> (path, fps) / Promise<RecordStopResult> (path, sizeBytes).
Example:
await vm.record.start({ fps: 15 });
await vm.keyboard.type("recorded work");
const { path, sizeBytes } = await vm.record.stop();
const { url } = await vm.downloadUrl(path);process.*
process.list(): Promise<ProcessInfo[]>
process.start(cmd: string, opts?: { args?: string[]; cwd?: string }): Promise<number>
process.kill(pid: number): Promise<void>
process.signal(pid: number, signal?: number): Promise<void>Inspect and control guest processes by pid. For output capture use commands.run() instead.
Parameters:
cmdstring: program to start.opts.args?string[],opts.cwd?string.pidnumber: target process.signal?number: signal number.
Returns: Promise<ProcessInfo[]> / Promise<number> (the pid) / Promise<void>
Example:
const pid = await vm.process.start("xterm");
for (const p of await vm.process.list()) console.log(p.pid, p.name);
await vm.process.kill(pid);ports.*
ports.list(): Promise<PortInfo[]>Lists TCP sockets listening inside the guest.
Returns: Promise<PortInfo[]>: each with port, addr, optional pid.
Example:
for (const p of await vm.ports.list()) console.log(p.port, p.addr, p.pid);pkg.install()
pkg.install(manager: "apt" | "pip" | "npm", packages: string[]): Promise<PkgInstallResult>Installs packages in the guest.
Parameters:
managerPackageManager:"apt","pip", or"npm".packagesstring[]: package names.
Returns: Promise<PkgInstallResult>: exitCode, stdout, stderr. A failed install resolves with a non-zero exitCode; it does not throw.
Example:
const r = await vm.pkg.install("apt", ["imagemagick"]);
if (r.exitCode !== 0) console.error(r.stderr);exec()
exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>Runs a command to completion (a v1 convenience). Prefer commands.run(), which adds streaming callbacks and a warm HTTP fast path.
Parameters:
cmdstring: program to run.opts.args?string[],opts.cwd?string,opts.timeoutMs?number.opts.stream?boolean: reserved; use execStream(), which sets it for you.
Returns: Promise<ExecResult>: exitCode, stdout, stderr.
Example:
const r = await vm.exec("xdotool", { args: ["getactivewindow"] });execStream()
execStream(cmd: string, onChunk: ExecStreamHandler, opts?: ExecOptions): Promise<ExecResult>Runs a command, delivering output chunks as they arrive (a v1 convenience). Prefer commands.run() with onStdout.
Parameters:
cmdstring: program to run.onChunkExecStreamHandler: called with{ stream, text, bytes }.opts?ExecOptions: withoutstream.
Returns: Promise<ExecResult>
Example:
await vm.execStream("apt-get", (c) => process.stdout.write(c.text), {
args: ["update"],
});fs.*
fs.read(path) / fs.readText(path) / fs.write(path, data, mode?)
fs.list(path) / fs.stat(path) / fs.remove(path, recursive?) / fs.mkdir(path)A v1 alias for a subset of files.*, which is the canonical surface and has more methods.
Example:
await vm.fs.write("/tmp/note.txt", "hi"); // same as vm.files.write(...)Inherited members
Desktop extends the same base as Sandbox. These all work on a VM:
| Member | Purpose |
|---|---|
connect(), reconnect(), close() | Control-channel lifecycle. |
commands.run(), commands.start(), pty.create() | Run commands and open PTYs. |
runCode(), createCodeContext() | Stateful kernel execution with rich outputs. |
files.*, downloadUrl(), uploadUrl() | Filesystem access and signed transfer URLs. |
git.* | Clone, status, commit, push, pull, checkout, log. |
env(), volumes.mount() | Session env vars and volume mounts. |
metrics(), snapshot(), revert() | Usage, checkpoints, in-place restore. |
pause(), resume(), setTimeout(), kill() | Session lifecycle. |
previewUrl() | Public URL for an in-guest port. |
Types
CreateDesktopOptions
template?string: e.g."office","ubuntu-desktop". Default"default".resolution?string: initial display size, e.g."1280x720".cpu?number: vCPUs, 1 to 16. Default 2; grown on assign via vCPU hot-add.memMb?number: RAM in MiB, 2048 to 65536. Default 2048; grown via virtio-mem hotplug.record?boolean: record server-side. The201body carries a presignedrecordingUrl, butcreate()returns aDesktophandle, which does not surface it. Read it back from get().timeoutMs?number: rolling idle window; resets on every use. OverridesttlSecondsand the 30-minute default.lifecycle?SandboxLifecycle:{ onTimeout: "pause" | "kill", autoResume?: boolean }.metadata?Record<string, string>: opaque labels.volumes?VolumeAttachment[]: each{ volumeId, path }.ttlSeconds?number: legacy TTL; prefertimeoutMs.
CreateDesktopResponse
sessionIdstring: signed, opaque session capability.streamUrlstring:wss://RFB stream.controlUrlstring:wss://JSON-RPC control channel.expiresAtstring: ISO 8601 expiry.recordingUrl?string: present whenrecord: true.
GetDesktopResponse
sessionIdstring.statusDesktopStatus:starting,ready,paused,releasing,gone.expiresAtstring,recordingUrl?string.
ScreenshotOptions
format?"png" | "jpeg": default"png".quality?number: JPEG only, 1 to 100.
HealthResult
readyboolean,displayboolean,vncboolean.
ProcessInfo / PortInfo
ProcessInfo:pidnumber,namestring,cmd?string.PortInfo:portnumber,addrstring,pid?number.
RecordStartResult / RecordStopResult
RecordStartResult:pathstring (in-guest mp4),fpsnumber.RecordStopResult:pathstring,sizeBytesnumber.
Errors are shared across products. See Errors.
