# heyo-sdk

Rust SDK for the Heyo cloud sandbox API. Mirrors the TypeScript SDK
(`sdk-ts/` → `@heyocomputer/sdk`) so the same patterns translate across
languages.

```rust
use heyo_sdk::{Sandbox, SandboxCreateOptions, SandboxSize, HeyoClientOptions, CommandRunOptions};

#[tokio::main]
async fn main() -> Result<(), heyo_sdk::HeyoError> {
    let sandbox = Sandbox::create(
        SandboxCreateOptions {
            image: Some("ubuntu:24.04".into()),
            size_class: Some(SandboxSize::Small),
            ttl_seconds: Some(600),
            ..Default::default()
        },
        HeyoClientOptions::default(),  // reads HEYO_API_KEY
    )
    .await?;

    let out = sandbox.commands().run("uname -a", CommandRunOptions::default()).await?;
    println!("{}", out.stdout);

    sandbox.kill().await?;
    Ok(())
}
```

## Surface

- `Sandbox` — VM lifecycle (`create`, `connect`, `list`, `info`,
  `wait_for_ready`, `kill`, `stop/start/restart`, `set_ttl`, `resize`,
  `checkpoint/restore`, `replace_mount`, `bind_port`, `shell`).
- `Sandbox::commands()` — `.run(cmd, opts)` against `/sandbox/:id/exec`.
- `Sandbox::files()` — `.read` / `.write` against `/sandbox/:id/read-file` /
  `/write-file` (base64 over the wire, exposed as `Vec<u8>` / `String`).
- `ShellSession` — persistent interactive shell over the WebSocket protocol,
  with `output()` and `events()` streams, auto-reconnect, and graceful
  `close()`.
- `Database` — cloud sqlite (`create`, `connect`, `list`, `regions`, `info`,
  `exec`, `batch`, `connect_token`, `connection_info`, `list_connections`,
  `revoke_connection`, `checkout`, `checkin`, `delete`).
- `archive_dir(path, opts)` — tar+gz a local directory, presign + PUT + finalize.

## Errors

Everything returns `Result<T, HeyoError>`. Notable variants:

- `Authentication` — no API key.
- `InvalidArgument(String)` — 400/422.
- `NotFound(String)` — 404.
- `Api { status, message, body }` — 5xx and friends.
- `Timeout(Duration, String)` — `wait_for_*` budget exceeded.
- `SandboxFailed { sandbox_id, reason }` — provisioning ended in `failed`.
- `CheckinConflict { expected, current }` — optimistic concurrency on
  `Database::checkin`.
- `SessionExpired { session_id }`, `Connection(String)`, `ShellExit(i32)` —
  shell session failures.

## Configuration

`HeyoClient::new(HeyoClientOptions { .. })` (or any high-level
`Sandbox::create` / `Database::create` etc. that takes a `HeyoClientOptions`)
resolves config in this order:

1. Fields set on `HeyoClientOptions` win.
2. `HEYO_API_KEY` env var supplies `api_key` when unset.
3. `base_url` defaults to `https://server.heyo.computer`.
4. `timeout` defaults to 60s.

## Running integration tests

All tests under `tests/` are marked `#[ignore]` because they need a live
cloud API + an API key. Set up `.env` next to `Cargo.toml`:

```env
HEYO_API_KEY=heyo_api_xxxxxxxxxxxx
# optional — defaults to localhost:4445 ("local")
HEYO_ENV=local
# or HEYO_BASE_URL=http://localhost:4445
```

Then run individual tests:

```bash
cargo test --test smoke               -- --ignored --nocapture
cargo test --test shell_protocol      -- --ignored --nocapture
HEYO_DB_ID=db-xxxx cargo test --test db_select_one        -- --ignored --nocapture
HEYO_DB_ID=db-xxxx cargo test --test db_checkout_checkin  -- --ignored --nocapture
```

Tests skip cleanly with an `eprintln!` message when required env is missing,
so plain `cargo test` (no `--ignored`) is a no-op.

## Status / limitations

- `archive_dir` v0 excludes a fixed deny-list (`.git`, `node_modules`,
  `target`, …) plus any caller-supplied directory names. `.gitignore`
  parsing is a follow-up.
- `Sandbox::list_public_images` matches the TS surface but the underlying
  `/public-images` endpoint may not be wired everywhere; expect 404s on
  older clouds.
- The shell test exercises open / IO / resize / close. Forced-reconnect
  testing exists in the TS SDK by reaching into a private socket field;
  Rust integration tests rely on natural disconnects rather than poking
  internals.
