bob-rs 0.1.0

Unofficial Rust SDK for the bob agent CLI — detection, install, keychain, run. Not affiliated with or endorsed by the bob maintainers.
Documentation

bob-rs

Unofficial. A community Rust SDK for the bob agent CLI. Not affiliated with, sponsored by, or endorsed by the maintainers of bob.

A standalone Rust SDK for the bob agent CLI: detection and readiness probing, streaming install, OS-keychain credential storage, and spawning a bob run with its --output-format stream-json stream piped back line-by-line.

No Tauri, no HTTP server, no harness abstraction — just the bob integration logic, so it can be reused by any host. Exposing bob as a Harness lives in the agent-harness crate's bob module (which wraps this SDK); this crate stays a clean, standalone SDK.

Key surface:

  • get_readiness() → a BobReadinessSnapshot (installed? version? Node? auth configured?).
  • install_bob(cb) → streams the bundled install script's progress.
  • spawn_bob(opts, run_id, cb) / spawn_bob_raw(...) → spawn a run, streaming ProcessEvents (from the cli-stream engine) until exit; returns a ProcessHandle for cancellation.
  • resolve_api_key() → the bob API key, resolved as BOBSHELL_API_KEY from the environment first, else the OS keychain (see Authentication). read_api_key / write_api_key / delete_api_key manage the keychain entry directly.

Authentication

bob runs with a BOBSHELL_API_KEY. resolve_api_key() (used by spawn_bob) resolves it in this order:

  1. the BOBSHELL_API_KEY environment variable — shell-exported, or loaded into the process env from a .env by your host (bob-rs does not parse a .env file itself); then
  2. the OS keychain entry (write_api_key to store it there).

The env var wins when both are set. So: export BOBSHELL_API_KEY, or call bob_rs::write_api_key(&key) once to persist it in the keychain.

Example — run bob with a prompt

use std::sync::mpsc::sync_channel;
use bob_rs::{spawn_bob, BobApprovalMode, BobChatMode, BobError, ProcessEvent, RunBobOptions};

fn main() -> Result<(), BobError> {
    // Auth: `BOBSHELL_API_KEY` — read from the env if set, else the OS
    // keychain (store there once via `bob_rs::write_api_key`). `bob` on PATH.
    let (tx, rx) = sync_channel::<ProcessEvent>(256);

    let _handle = spawn_bob(
        RunBobOptions {
            prompt: "List the files in this directory.".into(),
            chat_mode: BobChatMode::Ask,
            approval_mode: BobApprovalMode::Default,
            max_coins: 30,
            cwd: None,             // defaults to the current directory
            bob_executable: None,  // defaults to `bob` on PATH
        },
        "demo".into(),
        move |ev| { let _ = tx.send(ev); }, // FnMut + Send + Sync + Clone
    )?;

    // bob-rs streams bob's stdout RAW — one JSON object per line from its
    // `--output-format stream-json`. For a *normalized* event stream (text /
    // thinking / tool calls), use the `agent-harness` crate, whose `bob`
    // adapter parses these for you.
    for ev in rx {
        match ev {
            ProcessEvent::Stdout { line, .. }      => println!("{line}"),
            ProcessEvent::Stderr { line, .. }      => eprintln!("{line}"),
            ProcessEvent::Error  { message, .. }   => eprintln!("error: {message}"),
            ProcessEvent::Exited { exit_code, .. } => { eprintln!("(exit {exit_code:?})"); break; }
            ProcessEvent::Started { .. }           => {}
        }
    }
    Ok(())
}

License

Licensed under either of MIT or Apache-2.0 at your option.