Pilot
Pilot JavaScript SDK
PilotClient for TypeScript and Node.js — run playbooks, manage runs, and stream events programmatically.
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
sfw bun add @cuitty/pilot-sdk
# or
sfw npm install @cuitty/pilot-sdk
Quick start
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.
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
// 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
// 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
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
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
const providers = await pilot.providers.list();
// Returns: Provider[]
// { id, projectId, provider, label, baseUrl, lastAuthenticated, expiresAt }
Error handling
The SDK throws typed errors:
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 — YAML schema for playbook documents
- Executors — Browser, Terraform, Git, and other executor types
- Quickstart — record and replay your first workflow