---
title: Pilot JavaScript SDK
description: "PilotClient for TypeScript and Node.js — run playbooks, manage runs, and stream events programmatically."
section: Pilot
order: 4
updatedAt: 2026-06-01
slug: pilot/sdk/javascript
---
# Pilot JavaScript SDK

`@cuitty/pilot-sdk` is the TypeScript/Node.js client for the Pilot API. It provides typed methods for managing playbooks, starting runs, streaming real-time events, and listing providers.

## Install

```bash
sfw bun add @cuitty/pilot-sdk
# or
sfw npm install @cuitty/pilot-sdk
```

## Quick start

```typescript
import { PilotClient } from "@cuitty/pilot-sdk";

const pilot = new PilotClient({
  baseUrl: "http://localhost:4320",
  auth: { apiKey: process.env.PILOT_API_KEY },
});

// List all playbooks
const playbooks = await pilot.playbooks.list();

// Start a run
const { id } = await pilot.runs.start({
  playbookId: "cloudflare.dns.add-a-record",
  mode: "autonomous",
  inputs: { zone: "example.com", record_name: "app", ip_address: "1.2.3.4" },
});

// Stream events until completion
for await (const event of pilot.runs.stream(id)) {
  console.log(event.type, event.stepId, event.status);
}

// Clean up
pilot.close();
```

## PilotClient

The main entry point. Creates service instances and manages shared configuration.

```typescript
interface PilotClientConfig {
  baseUrl: string;            // Pilot server URL
  auth?: {
    token?: string;           // Bearer token
    cookie?: string;          // Raw cookie string
    apiKey?: string;          // X-Pilot-API-Key header
  };
  timeout?: number;           // Per-request timeout in ms (default 30000)
}

const pilot = new PilotClient(config);
```

| Property | Type | Description |
|----------|------|-------------|
| `pilot.playbooks` | `PlaybookService` | Playbook CRUD |
| `pilot.runs` | `RunService` | Run lifecycle + streaming |
| `pilot.providers` | `ProviderService` | Provider profile listing |

| Method | Returns | Description |
|--------|---------|-------------|
| `pilot.health()` | `Promise<HealthStatus>` | Check server health |
| `pilot.close()` | `void` | Close all active event subscriptions |

## PlaybookService

```typescript
// List all playbooks, optionally filtered by target
const all = await pilot.playbooks.list();
const cfOnly = await pilot.playbooks.list({ target: "cloudflare" });

// Get a single playbook (latest version)
const pb = await pilot.playbooks.get("cloudflare.dns.add-a-record");

// Create a new playbook
const { id, version } = await pilot.playbooks.create({
  target: "cloudflare",
  title: "Add CNAME record",
  yaml: playbookYaml,
  authoredBy: "human",
});

// Update (bumps version)
await pilot.playbooks.update("cloudflare.dns.add-cname", {
  yaml: updatedYaml,
});

// Delete all versions
await pilot.playbooks.delete("cloudflare.dns.add-cname");
```

| Method | Params | Returns |
|--------|--------|---------|
| `list(options?)` | `{ target?: string }` | `PlaybookSummary[]` |
| `get(id)` | playbook id | `Playbook` |
| `create(req)` | `CreatePlaybookRequest` | `{ id, version, created }` |
| `update(id, req)` | id + `UpdatePlaybookRequest` | `{ id, version }` |
| `delete(id)` | playbook id | `{ deleted }` |

## RunService

```typescript
// Start a run
const { id, status } = await pilot.runs.start({
  playbookId: "cloudflare.dns.add-a-record",
  mode: "interactive",         // "autonomous" | "interactive" | "review"
  inputs: { zone: "example.com", record_name: "api", ip_address: "1.2.3.4" },
  providerProfileId: "cf-prod",
});

// Get run details
const run = await pilot.runs.get(id);

// List runs with filters
const recent = await pilot.runs.list({ status: "running", limit: 10 });

// Abort a running run
await pilot.runs.abort(id);

// Approve a step awaiting review
await pilot.runs.approve(id, {
  stepId: "save",
  decision: "approve",         // "approve" | "skip" | "abort"
});
```

| Method | Returns |
|--------|---------|
| `start(req)` | `{ id, status }` |
| `get(id)` | `Run` (includes `steps` array) |
| `list(options?)` | `Run[]` |
| `abort(id)` | `{ aborted, runId }` |
| `approve(id, req)` | `{ approved, runId, stepId, decision }` |

## Event streaming

Two patterns for consuming real-time run events via SSE:

### Async iterator

```typescript
for await (const event of pilot.runs.stream(runId)) {
  switch (event.type) {
    case "step.start":
      console.log(`Step ${event.stepId} started`);
      break;
    case "step.complete":
      console.log(`Step ${event.stepId} done in ${event.durationMs}ms`);
      break;
    case "step.failed":
      console.error(`Step ${event.stepId} failed: ${event.error}`);
      break;
    case "run.completed":
      console.log("Run finished", event.outputs);
      break;
  }
}
```

The iterator yields events until the run reaches a terminal state (`run.completed`, `run.failed`, or `run.aborted`).

### Callback subscription

```typescript
const unsubscribe = pilot.runs.subscribe(runId, {
  onStep: (event) => console.log("step", event.stepId, event.status),
  onComplete: (event) => console.log("done", event.outputs),
  onError: (event) => console.error("error", event.error),
  onEvent: (event) => {}, // every event
});

// Later: close the SSE connection
unsubscribe();
```

### Event types

| Type | Fields | When |
|------|--------|------|
| `step.start` | `stepId`, `action` | Step begins executing |
| `step.complete` | `stepId`, `action`, `durationMs`, `aiUsed` | Step succeeded |
| `step.failed` | `stepId`, `action`, `error` | Step errored |
| `step.awaiting_approval` | `stepId`, `action` | Step paused at breakpoint |
| `run.completed` | `outputs` | All steps finished |
| `run.failed` | `error` | Run failed |
| `run.aborted` | | Run was aborted |

## ProviderService

```typescript
const providers = await pilot.providers.list();
// Returns: Provider[]
// { id, projectId, provider, label, baseUrl, lastAuthenticated, expiresAt }
```

## Error handling

The SDK throws typed errors:

```typescript
import { PilotApiError, PilotConnectionError } from "@cuitty/pilot-sdk";

try {
  await pilot.runs.start({ playbookId: "nonexistent" });
} catch (err) {
  if (err instanceof PilotApiError) {
    console.error(`API ${err.status}: ${err.statusText}`, err.body);
  } else if (err instanceof PilotConnectionError) {
    console.error("Cannot reach Pilot:", err.message);
  }
}
```

| Error class | Properties | When |
|-------------|-----------|------|
| `PilotApiError` | `status`, `statusText`, `body` | Non-2xx HTTP response |
| `PilotConnectionError` | `message`, `cause` | Network / timeout failure |

## See also

- [Playbook reference](/docs/pilot/playbook-reference) — YAML schema for playbook documents
- [Executors](/docs/pilot/executors) — Browser, Terraform, Git, and other executor types
- [Quickstart](/docs/pilot/quickstart) — record and replay your first workflow