SDKsJavaScript / TypeScript

JavaScript / TypeScript SDK

Full reference for @ciderstack/fleet-sdk.

npm install @ciderstack/fleet-sdk

Requires Node.js 18+, Bun, or Deno.


Client configuration

import { FleetClient } from "@ciderstack/fleet-sdk";
 
const client = new FleetClient({
  host: "192.168.1.100",        // Required — IP or hostname
  apiToken: "csk_abc123...",    // Auth: API token (or use nodeId)
  port: 9473,                   // Optional — default 9473
  timeout: 30000,               // Optional — request timeout in ms
});
OptionTypeDefaultDescription
hoststringrequiredIP address or hostname of the Fleet node
portnumber9473Fleet HTTP server port
timeoutnumber30000Request timeout in milliseconds
nodeIdstringTrusted node ID from pairing
apiTokenstringAPI token string

Provide exactly one of nodeId or apiToken.

Static constants:

ConstantValue
FleetClient.DEFAULT_PORT9473
FleetClient.DEFAULT_TIMEOUT30000 (ms)
FleetClient.DEFAULT_EXEC_TIMEOUT_SECONDS300 (5 min)

Pairing

const creds = await FleetClient.pair(host, code, name?, port?);
// Returns PairingCredentials

See Authentication for full details.


Node information

// Get node hardware and software info
const info = await client.getNodeInfo();
console.log(`${info.name} (${info.machineModel})`);
console.log(`macOS ${info.osVersion}, Fleet v${info.fleetVersion}`);
console.log(`${info.cpuCores} cores, ${info.totalMemoryGB} GB RAM`);
 
// Get real-time resource stats
const stats = await client.getNodeStats();
console.log(`CPU: ${stats.cpuUsagePercent.toFixed(1)}%`);
console.log(`Memory: ${stats.memoryUsedGB.toFixed(1)}/${stats.memoryTotalGB} GB`);
console.log(`Disk: ${stats.diskUsedGB.toFixed(1)}/${stats.diskTotalGB} GB`);
console.log(`VMs: ${stats.runningVMCount}/${stats.totalVMCount}`);
MethodReturnsRPC
getNodeInfo()Promise<NodeInfo>GetNodeInfo
getNodeStats()Promise<NodeStats>GetNodeStats

VM management

// List and find VMs
const vms = await client.listVMs();
const vm = await client.getVM("vm-uuid");  // null if not found
 
// Lifecycle
await client.startVM(vm.id);
await client.suspendVM(vm.id);      // Pause (stays in memory)
await client.startVM(vm.id);        // Resume from suspend
await client.stopVM(vm.id);
await client.startVMRecovery(vm.id); // Boot into macOS Recovery
 
// Clone, rename, delete
const clone = await client.cloneVM(vm.id, "my-clone");
await client.renameVM(clone.id, "new-name");
await client.deleteVM(clone.id);
 
// Create a new VM from an OCI image
const vmId = await client.createVM({
  name: "ci-runner-01",
  cpuCount: 4,
  memoryMB: 8192,
  diskGB: 64,
  ociImage: "ghcr.io/myorg/macos-base:latest",
});
MethodReturnsRPCMutating
listVMs()Promise<VM[]>ListVMsNo
getVM(vmId)Promise<VM | null>No
startVM(vmId)Promise<boolean>StartVMYes
stopVM(vmId)Promise<boolean>StopVMYes
suspendVM(vmId)Promise<boolean>SuspendVMYes
startVMRecovery(vmId)Promise<boolean>StartVMRecoveryYes
cloneVM(vmId, newName)Promise<VM>CloneVMYes
renameVM(vmId, newName)Promise<boolean>RenameVMYes
deleteVM(vmId)Promise<boolean>DeleteVMYes
createVM(options)Promise<string>CreateVMOnNodeYes

createVM options:

OptionTypeDefaultDescription
namestringrequiredVM name
cpuCountnumber4Virtual CPUs
memoryMBnumber8192Memory in MB
diskGBnumber64Disk size in GB
ipswPathstringPath to IPSW on the node
ociImagestringOCI image reference

VM settings

// Get current settings
const settings = await client.getVMSettings(vmId);
 
// Update settings (only provided fields are changed)
await client.updateVMSettings(vmId, {
  cpuCount: 8,
  memorySize: 16384,
});
 
// Set display resolution
await client.updateVMSettings(vmId, {
  displayWidth: 1920,
  displayHeight: 1080,
});
 
// Mark as disposable with 1-hour TTL
await client.updateVMSettings(vmId, {
  intent: "disposable",
  ttlSeconds: 3600,
});
 
// Clear the TTL
await client.updateVMSettings(vmId, { ttlSeconds: -1 });
MethodReturnsRPCMutating
getVMSettings(vmId)Promise<Record<string, unknown>>GetVMSettingsNo
updateVMSettings(vmId, settings)Promise<boolean>UpdateVMSettingsYes

Settings fields:

FieldTypeDescription
cpuCountnumberNumber of virtual CPUs
memorySizenumberMemory in MB
displayWidthnumberDisplay width in pixels
displayHeightnumberDisplay height in pixels
intentstring"disposable", "persistent", "interactive", or "ci"
ttlSecondsnumberTime-to-live in seconds. -1 to clear.

Snapshots

// Create a snapshot
const snapshot = await client.createSnapshot(vmId, "pre-update", "Before system update");
 
// List snapshots
const snapshots = await client.listSnapshots(vmId);
for (const s of snapshots) {
  console.log(`${s.name}: ${s.createdAt.toISOString()} (${s.sizeBytes} bytes)`);
}
 
// Restore (VM must be stopped)
await client.restoreSnapshot(vmId, snapshot.id);
 
// Delete
await client.deleteSnapshot(vmId, snapshot.id);
MethodReturnsRPCMutating
listSnapshots(vmId)Promise<Snapshot[]>ListSnapshotsNo
createSnapshot(vmId, name, description?)Promise<Snapshot>CreateSnapshotYes
restoreSnapshot(vmId, snapshotId)Promise<boolean>RestoreSnapshotYes
deleteSnapshot(vmId, snapshotId)Promise<boolean>DeleteSnapshotYes

Command execution

Execute shell commands on running VMs via SSH. The HTTP timeout is automatically extended for long-running commands.

const result = await client.execCommand(vmId, "uname -a", {
  sshUser: "admin",
  sshPassword: "password",
});
 
if (result.success) {
  console.log(result.stdout);
  console.log(`Completed in ${result.duration}s`);
} else {
  console.error(`Exit ${result.exitCode}: ${result.stderr}`);
}
 
// Long-running command with extended timeout
const build = await client.execCommand(
  vmId,
  "xcodebuild -scheme MyApp build",
  { sshUser: "admin", sshPassword: "password", timeout: 600 }
);
MethodReturnsRPCMutating
execCommand(vmId, command, options?)Promise<ExecResult>ExecCommandYes

Options:

OptionTypeDefaultDescription
sshUserstring"cideradmin"SSH username
sshPasswordstringSSH password
timeoutnumber300Command timeout in seconds

Templates

// List templates
const templates = await client.listTemplates();
for (const t of templates) {
  console.log(`${t.name} (${t.category}) — ${t.sourceType}`);
  console.log(`  Defaults: ${t.defaultCPU} CPU, ${t.defaultMemoryMB} MB, ${t.defaultDiskGB} GB`);
}
 
// Look up by ID or name
const template = await client.getTemplate("template-uuid");
const byName = await client.getTemplateByName("CI Runner Base");
 
// Create from a stopped VM
const newTemplate = await client.createTemplateFromVM({
  vmId: "abc-123-...",
  name: "Clean macOS 15 Base",
  description: "Fresh macOS Sequoia with Xcode CLI tools",
  category: "base",
  keepOriginalVM: true,
});
 
// Delete
await client.deleteTemplate(newTemplate.id);
MethodReturnsRPCMutating
listTemplates()Promise<Template[]>ListTemplatesNo
getTemplate(templateId)Promise<Template | null>GetTemplateNo
getTemplateByName(name)Promise<Template | null>GetTemplateNo
createTemplateFromVM(options)Promise<Template>CreateTemplateFromVMYes
deleteTemplate(templateId)Promise<boolean>DeleteTemplateYes

createTemplateFromVM options:

OptionTypeDefaultDescription
vmIdstringrequiredSource VM (must be stopped)
namestringrequiredTemplate name
descriptionstringDescription
categorystring"custom""base", "dev", "cicd", "testing", "custom"
keepOriginalVMbooleantrueKeep the source VM after template creation

Shared folders

Share directories between the host and VM using VirtioFS.

// List shared folders
const folders = await client.listSharedFolders(vmId);
for (const f of folders) {
  const mode = f.readOnly ? "ro" : "rw";
  const status = f.isEnabled ? "enabled" : "disabled";
  console.log(`${f.name}: ${f.hostPath} (${mode}, ${status})`);
}
 
// Add a shared folder
await client.addSharedFolder({
  vmId: vmId,
  name: "project-src",
  hostPath: "/Users/dev/project",
  readOnly: false,
});
 
// Toggle on/off
await client.setSharedFolderEnabled(vmId, "project-src", false);
await client.setSharedFolderEnabled(vmId, "project-src", true);
 
// Remove
await client.removeSharedFolder(vmId, "project-src");
MethodReturnsRPCMutating
listSharedFolders(vmId)Promise<SharedFolder[]>ListSharedFoldersNo
addSharedFolder(options)Promise<boolean>AddSharedFolderYes
removeSharedFolder(vmId, name)Promise<boolean>RemoveSharedFolderYes
setSharedFolderEnabled(vmId, name, enabled)Promise<boolean>SetSharedFolderEnabledYes

addSharedFolder options:

OptionTypeDefaultDescription
vmIdstringrequiredTarget VM
namestringrequiredDisplay name
hostPathstringrequiredAbsolute path on the host
readOnlybooleanfalseMount as read-only
mountTagstring"CiderStackShare"VirtioFS mount tag

Image management

// List available IPSWs and OCI images
const ipsws = await client.listIPSWs();
const images = await client.listOCIImages();
 
// Download an IPSW to the node
await client.downloadIPSW("https://updates.cdn-apple.com/.../restore.ipsw", "macOS 15.1", "15.1");
 
// Get IPSW metadata
const info = await client.getIPSWInfo("/path/to/restore.ipsw");
 
// Pull an OCI image (public)
await client.pullOCIImage("ghcr.io/myorg/macos-base:latest");
 
// Pull an OCI image (private registry)
await client.pullOCIImage("ghcr.io/myorg/private:v1", {
  username: "user",
  password: "ghp_token...",
});
 
// Push a VM as an OCI image (returns task ID)
const taskId = await client.pushImage(vmId, "ghcr.io/myorg/my-vm:latest");
MethodReturnsRPCMutating
listIPSWs()Promise<Record<string, unknown>[]>ListRemoteIPSWsNo
listOCIImages()Promise<Record<string, unknown>[]>ListRemoteOCIImagesNo
downloadIPSW(url, name, version)Promise<boolean>DownloadIPSWOnNodeYes
getIPSWInfo(path)Promise<Record<string, unknown>>GetIPSWInfoNo
pullOCIImage(imageRef, credentials?)Promise<boolean>PullOCIImageOnNodeYes
pushImage(vmId, imageName, insecure?)Promise<string>PushImageYes

Tasks

Monitor background tasks like VM creation, image pulls, and IPSW downloads.

const tasks = await client.getTasks();
for (const task of tasks) {
  const pct = (task.progress * 100).toFixed(0);
  console.log(`[${task.status}] ${task.title}: ${pct}%`);
  if (task.progressText) console.log(`  ${task.progressText}`);
  if (task.errorMessage) console.log(`  ERROR: ${task.errorMessage}`);
}
 
// Include completed tasks
const all = await client.getTasks(true);
MethodReturnsRPC
getTasks(includeCompleted?)Promise<Task[]>GetTasks

Fleet overview

Get aggregated fleet status. Only available on the manager node.

const overview = await client.getFleetOverview();
 
// Fleet-wide stats
const s = overview.stats;
console.log(`Nodes: ${s.connectedNodes}/${s.totalNodes}`);
console.log(`VMs: ${s.runningVMs}/${s.totalVMs} running`);
console.log(`CPU: ${s.aggregatedCPUUsage.toFixed(1)}%`);
 
// Per-node breakdown
const allNodes = [overview.managerNode, ...overview.workerNodes];
for (const node of allNodes) {
  console.log(`${node.name} (${node.connectionState}): ${node.runningVMCount} VMs`);
}
 
// All VMs across the fleet
for (const vm of overview.vms) {
  console.log(`${vm.name} on ${vm.hostName}: ${vm.state}`);
}
 
// Activity log
const events = await client.getFleetEvents(50);
for (const e of events) {
  console.log(`[${e.eventType}] ${e.nodeName}: ${e.message}`);
}
 
// Filter by event type
const startEvents = await client.getFleetEvents(20, "vm_started");
 
// Node resource availability
const resources = await client.getRemoteResources();
MethodReturnsRPC
getFleetOverview()Promise<FleetOverview>GetFleetOverview
getFleetEvents(limit?, eventType?)Promise<FleetEvent[]>GetFleetEvents
getRemoteResources()Promise<Record<string, unknown>>GetRemoteResources

API token management

See Authentication for full details.

MethodReturnsRPCMutating
generateAPIToken(name, permissions?)Promise<APIToken>GenerateAPITokenYes
revokeAPIToken(tokenId)Promise<boolean>RevokeAPITokenYes
listAPITokens()Promise<APITokenSummary[]>ListAPITokensNo

Fleet utilities

// Unpair a node
await client.unpair("node-uuid");
 
// Clean up VMs with expired TTL
const cleaned = await client.cleanupVMs();
console.log(`Cleaned ${cleaned} VMs`);
 
// Force cleanup (includes persistent VMs)
const forceClean = await client.cleanupVMs(true);
MethodReturnsRPCMutating
unpair(nodeId)Promise<boolean>UnpairYes
cleanupVMs(force?)Promise<number>CleanupVMsYes
getIPSWInfo(path)Promise<Record<string, unknown>>GetIPSWInfoNo

Error handling

All API errors throw FleetError:

import { FleetClient, FleetError } from "@ciderstack/fleet-sdk";
 
try {
  await client.startVM("non-existent-id");
} catch (error) {
  if (error instanceof FleetError) {
    console.error(`Fleet error: ${error.message}`);
    console.error("Response:", error.response);
  }
}

FleetError has a response property containing the raw API response for debugging.


Types and enums

Enums

enum VMState {
  Stopped = "stopped",
  Starting = "starting",
  Running = "running",
  Paused = "paused",
  Stopping = "stopping",
  Error = "error",
}
 
enum TaskStatus {
  Pending = "pending",
  Running = "running",
  Completed = "completed",
  Failed = "failed",
  Cancelled = "cancelled",
}
 
enum NodeRole {
  Worker = "worker",
  Manager = "manager",
  Operator = "operator",
}
 
type APITokenPermissions = "readOnly" | "fullAccess";

Interfaces

interface NodeInfo {
  nodeId: string;
  name: string;
  hostname: string;
  ipAddress: string;
  port: number;
  machineModel: string;
  osVersion: string;
  cpuCores: number;
  totalMemoryGB: number;
  fleetVersion: string;
}
 
interface NodeStats {
  nodeId: string;
  timestamp: Date;
  cpuUsagePercent: number;
  memoryUsedGB: number;
  memoryTotalGB: number;
  diskUsedGB: number;
  diskTotalGB: number;
  runningVMCount: number;
  totalVMCount: number;
}
 
interface VM {
  id: string;
  nodeID?: string;
  nodeName?: string;
  isLocal?: boolean;
  name: string;
  state: VMState;
  cpuCount: number;
  memoryMB: number;
  diskSizeGB?: number;
  osVersion?: string;
  ipAddress?: string;
}
 
interface VMSettings {
  cpuCount?: number;
  memorySize?: number;
  displayWidth?: number;
  displayHeight?: number;
  intent?: string;
  ttlSeconds?: number;
}
 
interface CreateVMOptions {
  name: string;
  cpuCount?: number;
  memoryMB?: number;
  diskGB?: number;
  ipswPath?: string;
  ociImage?: string;
}
 
interface Snapshot {
  id: string;
  name: string;
  description?: string;
  createdAt: Date;
  sizeBytes: number;
}
 
interface ExecResult {
  success: boolean;
  exitCode: number;
  stdout: string;
  stderr: string;
  duration: number;
}
 
interface Template {
  id: string;
  name: string;
  description?: string;
  macOSVersion?: string;
  iconName: string;
  category: string;
  sourceType: string;
  createdAt: Date;
  modifiedAt: Date;
  diskSizeBytes?: number;
  defaultCPU: number;
  defaultMemoryMB: number;
  defaultDiskGB: number;
}
 
interface CreateTemplateOptions {
  vmId: string;
  name: string;
  description?: string;
  category?: string;
  keepOriginalVM?: boolean;
}
 
interface SharedFolder {
  name: string;
  hostPath: string;
  readOnly: boolean;
  isEnabled: boolean;
  mountTag: string;
}
 
interface AddSharedFolderOptions {
  vmId: string;
  name: string;
  hostPath: string;
  readOnly?: boolean;
  mountTag?: string;
}
 
interface APIToken {
  id: string;
  name: string;
  permissions: APITokenPermissions;
  token?: string;
  createdAt: number;
  lastUsed?: number;
}
 
interface APITokenSummary {
  id: string;
  name: string;
  permissions: APITokenPermissions;
  tokenPrefix: string;
  createdAt: number;
  lastUsed?: number;
}
 
interface Task {
  id: string;
  type: string;
  title: string;
  status: TaskStatus;
  progress: number;
  progressText?: string;
  startedAt: Date;
  completedAt?: Date;
  vmId?: string;
  vmName?: string;
  errorMessage?: string;
}
 
interface FleetOverview {
  managerNode: FleetNodeSummary;
  workerNodes: FleetNodeSummary[];
  vms: FleetVMSummary[];
  stats: FleetStats;
}
 
interface FleetNodeSummary {
  nodeID: string;
  name: string;
  hostname: string;
  ipAddress: string;
  port: number;
  isLocal: boolean;
  connectionState: string;
  stats: object | null;
  vmCount: number;
  runningVMCount: number;
}
 
interface FleetVMSummary {
  id: string;
  name: string;
  state: string;
  hostNodeID: string;
  hostName: string;
  cpuCount: number;
  memoryMB: number;
  diskGB?: number;
}
 
interface FleetStats {
  totalNodes: number;
  connectedNodes: number;
  totalVMs: number;
  runningVMs: number;
  maxRunningVMs: number;
  aggregatedCPUUsage: number;
  aggregatedMemoryUsage: number;
  aggregatedStorageUsage: number;
}
 
interface FleetEvent {
  id: string;
  timestamp: string;
  eventType: string;
  vmID?: string;
  vmName?: string;
  nodeName: string;
  initiatedBy: string;
  message: string;
}
 
interface PairingCredentials {
  nodeId: string;
  privateKeyHex: string;
  publicKeyHex: string;
  responderNodeId: string;
  responderName: string;
}
 
interface ClientOptions {
  host: string;
  port?: number;
  timeout?: number;
  nodeId?: string;
  apiToken?: string;
}

Exports

The SDK exports the following from @ciderstack/fleet-sdk:

Classes: FleetClient, FleetError

Enums: VMState, TaskStatus, NodeRole

Types: NodeInfo, NodeStats, VM, Snapshot, Task, ExecResult, VMSettings, CreateVMOptions, FleetOverview, FleetStats, FleetNodeSummary, FleetVMSummary, FleetEvent, ClientOptions, PairingCredentials, APIToken, APITokenSummary, APITokenPermissions, Template, CreateTemplateOptions, SharedFolder, AddSharedFolderOptions


See also