JavaScript / TypeScript SDK
Full reference for @ciderstack/fleet-sdk.
npm install @ciderstack/fleet-sdkRequires 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
});| Option | Type | Default | Description |
|---|---|---|---|
host | string | required | IP address or hostname of the Fleet node |
port | number | 9473 | Fleet HTTP server port |
timeout | number | 30000 | Request timeout in milliseconds |
nodeId | string | — | Trusted node ID from pairing |
apiToken | string | — | API token string |
Provide exactly one of nodeId or apiToken.
Static constants:
| Constant | Value |
|---|---|
FleetClient.DEFAULT_PORT | 9473 |
FleetClient.DEFAULT_TIMEOUT | 30000 (ms) |
FleetClient.DEFAULT_EXEC_TIMEOUT_SECONDS | 300 (5 min) |
Pairing
const creds = await FleetClient.pair(host, code, name?, port?);
// Returns PairingCredentialsSee 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}`);| Method | Returns | RPC |
|---|---|---|
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",
});| Method | Returns | RPC | Mutating |
|---|---|---|---|
listVMs() | Promise<VM[]> | ListVMs | No |
getVM(vmId) | Promise<VM | null> | — | No |
startVM(vmId) | Promise<boolean> | StartVM | Yes |
stopVM(vmId) | Promise<boolean> | StopVM | Yes |
suspendVM(vmId) | Promise<boolean> | SuspendVM | Yes |
startVMRecovery(vmId) | Promise<boolean> | StartVMRecovery | Yes |
cloneVM(vmId, newName) | Promise<VM> | CloneVM | Yes |
renameVM(vmId, newName) | Promise<boolean> | RenameVM | Yes |
deleteVM(vmId) | Promise<boolean> | DeleteVM | Yes |
createVM(options) | Promise<string> | CreateVMOnNode | Yes |
createVM options:
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | VM name |
cpuCount | number | 4 | Virtual CPUs |
memoryMB | number | 8192 | Memory in MB |
diskGB | number | 64 | Disk size in GB |
ipswPath | string | — | Path to IPSW on the node |
ociImage | string | — | OCI 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 });| Method | Returns | RPC | Mutating |
|---|---|---|---|
getVMSettings(vmId) | Promise<Record<string, unknown>> | GetVMSettings | No |
updateVMSettings(vmId, settings) | Promise<boolean> | UpdateVMSettings | Yes |
Settings fields:
| Field | Type | Description |
|---|---|---|
cpuCount | number | Number of virtual CPUs |
memorySize | number | Memory in MB |
displayWidth | number | Display width in pixels |
displayHeight | number | Display height in pixels |
intent | string | "disposable", "persistent", "interactive", or "ci" |
ttlSeconds | number | Time-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);| Method | Returns | RPC | Mutating |
|---|---|---|---|
listSnapshots(vmId) | Promise<Snapshot[]> | ListSnapshots | No |
createSnapshot(vmId, name, description?) | Promise<Snapshot> | CreateSnapshot | Yes |
restoreSnapshot(vmId, snapshotId) | Promise<boolean> | RestoreSnapshot | Yes |
deleteSnapshot(vmId, snapshotId) | Promise<boolean> | DeleteSnapshot | Yes |
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 }
);| Method | Returns | RPC | Mutating |
|---|---|---|---|
execCommand(vmId, command, options?) | Promise<ExecResult> | ExecCommand | Yes |
Options:
| Option | Type | Default | Description |
|---|---|---|---|
sshUser | string | "cideradmin" | SSH username |
sshPassword | string | — | SSH password |
timeout | number | 300 | Command 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);| Method | Returns | RPC | Mutating |
|---|---|---|---|
listTemplates() | Promise<Template[]> | ListTemplates | No |
getTemplate(templateId) | Promise<Template | null> | GetTemplate | No |
getTemplateByName(name) | Promise<Template | null> | GetTemplate | No |
createTemplateFromVM(options) | Promise<Template> | CreateTemplateFromVM | Yes |
deleteTemplate(templateId) | Promise<boolean> | DeleteTemplate | Yes |
createTemplateFromVM options:
| Option | Type | Default | Description |
|---|---|---|---|
vmId | string | required | Source VM (must be stopped) |
name | string | required | Template name |
description | string | — | Description |
category | string | "custom" | "base", "dev", "cicd", "testing", "custom" |
keepOriginalVM | boolean | true | Keep 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");| Method | Returns | RPC | Mutating |
|---|---|---|---|
listSharedFolders(vmId) | Promise<SharedFolder[]> | ListSharedFolders | No |
addSharedFolder(options) | Promise<boolean> | AddSharedFolder | Yes |
removeSharedFolder(vmId, name) | Promise<boolean> | RemoveSharedFolder | Yes |
setSharedFolderEnabled(vmId, name, enabled) | Promise<boolean> | SetSharedFolderEnabled | Yes |
addSharedFolder options:
| Option | Type | Default | Description |
|---|---|---|---|
vmId | string | required | Target VM |
name | string | required | Display name |
hostPath | string | required | Absolute path on the host |
readOnly | boolean | false | Mount as read-only |
mountTag | string | "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");| Method | Returns | RPC | Mutating |
|---|---|---|---|
listIPSWs() | Promise<Record<string, unknown>[]> | ListRemoteIPSWs | No |
listOCIImages() | Promise<Record<string, unknown>[]> | ListRemoteOCIImages | No |
downloadIPSW(url, name, version) | Promise<boolean> | DownloadIPSWOnNode | Yes |
getIPSWInfo(path) | Promise<Record<string, unknown>> | GetIPSWInfo | No |
pullOCIImage(imageRef, credentials?) | Promise<boolean> | PullOCIImageOnNode | Yes |
pushImage(vmId, imageName, insecure?) | Promise<string> | PushImage | Yes |
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);| Method | Returns | RPC |
|---|---|---|
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();| Method | Returns | RPC |
|---|---|---|
getFleetOverview() | Promise<FleetOverview> | GetFleetOverview |
getFleetEvents(limit?, eventType?) | Promise<FleetEvent[]> | GetFleetEvents |
getRemoteResources() | Promise<Record<string, unknown>> | GetRemoteResources |
API token management
See Authentication for full details.
| Method | Returns | RPC | Mutating |
|---|---|---|---|
generateAPIToken(name, permissions?) | Promise<APIToken> | GenerateAPIToken | Yes |
revokeAPIToken(tokenId) | Promise<boolean> | RevokeAPIToken | Yes |
listAPITokens() | Promise<APITokenSummary[]> | ListAPITokens | No |
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);| Method | Returns | RPC | Mutating |
|---|---|---|---|
unpair(nodeId) | Promise<boolean> | Unpair | Yes |
cleanupVMs(force?) | Promise<number> | CleanupVMs | Yes |
getIPSWInfo(path) | Promise<Record<string, unknown>> | GetIPSWInfo | No |
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
- Python SDK — Python equivalent
- API Reference — Side-by-side comparison
- Examples & Workflows — CI/CD, monitoring, provisioning
- Authentication — Tokens and pairing