Pilot

Pilot Rust SDK

Async Rust client for the Cuitty Pilot API — manage playbooks, runs, and providers with reqwest and serde.

Pilot Rust SDK

cuitty-pilot-sdk is an async Rust client for the Pilot API. It uses reqwest for HTTP, serde for JSON serialization, and thiserror for typed errors.

Install

Add to your Cargo.toml:

[dependencies]
cuitty-pilot-sdk = { path = "../packages/sdk-rust" }
# or when published:
# cuitty-pilot-sdk = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Quick start

use cuitty_pilot_sdk::{PilotClient, StartRunRequest, PilotError};

#[tokio::main]
async fn main() -> Result<(), PilotError> {
    let client = PilotClient::new(
        "http://localhost:4320",
        Some("your-auth-token".into()),
    );

    // List all playbooks
    let playbooks = client.list_playbooks(None).await?;

    // Start a run
    let resp = client.start_run(&StartRunRequest {
        playbook_id: "cloudflare.dns.add-a-record".into(),
        mode: Some("autonomous".into()),
        inputs: Some(serde_json::json!({
            "zone": "example.com",
            "record_name": "app",
            "ip_address": "1.2.3.4"
        })),
        ..Default::default()
    }).await?;

    println!("Run started: {} ({})", resp.id, resp.status);

    // Get run details
    let run = client.get_run(&resp.id).await?;
    println!("Run status: {}", run.status);

    Ok(())
}

PilotClient

The main entry point. All methods are async and return Result<T, PilotError>.

// Without auth
let client = PilotClient::new("http://localhost:4320", None);

// With bearer token
let client = PilotClient::new(
    "http://localhost:4320",
    Some("ey...".into()),
);
MethodReturnsDescription
health()HealthStatusCheck server health
list_playbooks(target)Vec<PlaybookSummary>List playbooks, optionally filtered
get_playbook(id)PlaybookGet playbook by ID (latest version)
create_playbook(req)CreatePlaybookResponseCreate a new playbook
update_playbook(id, req)UpdatePlaybookResponseUpdate (version-bump) a playbook
delete_playbook(id)DeletePlaybookResponseDelete all versions
start_run(req)StartRunResponseStart a new run
list_runs(status, playbook_id, limit)Vec<Run>List runs with optional filters
get_run(id)RunGet run details including steps
abort_run(id)AbortRunResponseAbort a running or queued run
approve_run(id, req)ApproveResponseSubmit approval decision for a step
list_providers()Vec<Provider>List all provider profiles

Playbooks

// List all
let playbooks = client.list_playbooks(None).await?;

// Filter by target
let cf = client.list_playbooks(Some("cloudflare")).await?;

// Get one
let pb = client.get_playbook("cloudflare.dns.add-a-record").await?;

// Create
let resp = client.create_playbook(&CreatePlaybookRequest {
    target: "cloudflare".into(),
    title: "Add CNAME record".into(),
    yaml: playbook_yaml,
    authored_by: Some("human".into()),
}).await?;

// Update
client.update_playbook("cloudflare.dns.add-cname", &UpdatePlaybookRequest {
    yaml: updated_yaml,
    target: None,
    title: None,
    authored_by: None,
}).await?;

// Delete
client.delete_playbook("cloudflare.dns.add-cname").await?;

Runs

// Start a run
let resp = client.start_run(&StartRunRequest {
    playbook_id: "cloudflare.dns.add-a-record".into(),
    mode: Some("interactive".into()),
    inputs: Some(serde_json::json!({
        "zone": "example.com",
        "record_name": "api",
        "ip_address": "1.2.3.4"
    })),
    provider_profile_id: Some("cf-prod".into()),
    ..Default::default()
}).await?;

// Get run with steps
let run = client.get_run(&resp.id).await?;
if let Some(steps) = &run.steps {
    for step in steps {
        println!("{}: {} ({}ms)", step.step_id, step.status,
            step.duration_ms.unwrap_or(0));
    }
}

// List recent running runs
let running = client.list_runs(Some("running"), None, Some(10)).await?;

// Abort
client.abort_run(&resp.id).await?;

// Approve a step
client.approve_run(&resp.id, &ApproveRequest {
    step_id: "save".into(),
    decision: Some("approve".into()),
}).await?;

Types

Core structs

StructKey fields
Playbookid, version, target, title, document_yaml, authored_by, created_at
PlaybookSummaryid, version, target, title, authored_by
Runid, project_id, playbook_id, mode, status, inputs_json, outputs_json, steps: Option<Vec<RunStep>>
RunStepid, run_id, step_id, action, status, ai_used, duration_ms, error_message
Providerid, project_id, provider, label, base_url, last_authenticated, expires_at
HealthStatusstatus, database, driver_pool, provider_sessions, last_checked

Request structs

StructFields
StartRunRequestplaybook_id, version?, mode?, inputs? (serde_json::Value), provider_profile_id?, project_id?
CreatePlaybookRequesttarget, title, yaml, authored_by?
UpdatePlaybookRequestyaml, target?, title?, authored_by?
ApproveRequeststep_id, decision?

All structs derive Debug, Clone, Serialize, and Deserialize.

Error handling

The SDK uses a single PilotError enum with three variants:

use cuitty_pilot_sdk::PilotError;

match client.start_run(&req).await {
    Ok(resp) => println!("Started: {}", resp.id),
    Err(PilotError::Api { status, message }) => {
        eprintln!("API error {}: {}", status, message);
    }
    Err(PilotError::Connection(err)) => {
        eprintln!("Network error: {}", err);
    }
    Err(PilotError::Other(msg)) => {
        eprintln!("Unexpected: {}", msg);
    }
}
VariantFieldsWhen
Apistatus: u16, message: StringNon-2xx HTTP response
Connectionwraps reqwest::ErrorNetwork or timeout failure
OtherStringAny other error

See also