# Bot SDK

The `quicproquo-bot` crate provides a high-level SDK for building automated
agents on the quicproquo network. Bots authenticate with OPAQUE, send and
receive E2E encrypted messages through MLS, and can be driven programmatically
or via a JSON pipe interface for shell integration.

---

## Adding the dependency

```toml
[dependencies]
quicproquo-bot = { path = "../crates/quicproquo-bot" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
anyhow = "1"
```

---

## Quick start

```rust,no_run
use quicproquo_bot::{Bot, BotConfig};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = BotConfig::new("127.0.0.1:7000", "bot-user", "bot-password")
        .ca_cert("server-cert.der")
        .state_path("bot-state.bin");

    let bot = Bot::connect(config).await?;

    // Send a DM
    bot.send_dm("alice", "Hello from bot!").await?;

    // Poll for messages
    loop {
        for msg in bot.receive(5000).await? {
            println!("{}: {}", msg.sender, msg.text);
            if msg.text.starts_with("!echo ") {
                bot.send_dm(&msg.sender, &msg.text[6..]).await?;
            }
        }
    }
}
```

---

## Configuration

`BotConfig` uses a builder pattern. The only required arguments are the server
address, username, and password:

```rust,no_run
# use quicproquo_bot::BotConfig;
let config = BotConfig::new("127.0.0.1:7000", "my-bot", "secret123")
    .ca_cert("certs/server-cert.der")   // TLS CA certificate (DER format)
    .server_name("my-server.example")   // TLS SNI (default: "localhost")
    .state_path("my-bot-state.bin")     // Persistent state file
    .state_password("encrypt-me")       // State file encryption password
    .device_id("bot-device-1");         // Device identifier
```

| Method            | Default               | Description |
|-------------------|-----------------------|-------------|
| `ca_cert()`       | `"server-cert.der"`   | Path to the server's CA certificate in DER format. |
| `server_name()`   | `"localhost"`         | TLS server name for certificate validation. |
| `state_path()`    | `"bot-state.bin"`     | Path to the bot's encrypted state file. |
| `state_password()` | None (unencrypted)   | Password for encrypting the state file at rest. |
| `device_id()`     | None                  | Device ID reported to the server in auth tokens. |

---

## Sending messages

```rust,no_run
# use quicproquo_bot::Bot;
# async fn example(bot: &Bot) -> anyhow::Result<()> {
// Send a plaintext DM — encryption is handled internally via MLS
bot.send_dm("alice", "Hello!").await?;
# Ok(())
# }
```

`send_dm` resolves the username, establishes or joins the MLS group for the DM
channel, encrypts the plaintext, and delivers it through the server. Each call
opens a fresh QUIC connection (stateless reconnect pattern).

---

## Receiving messages

```rust,no_run
# use quicproquo_bot::Bot;
# async fn example(bot: &Bot) -> anyhow::Result<()> {
// Wait up to 5 seconds for pending messages
let messages = bot.receive(5000).await?;
for msg in &messages {
    println!("[seq={}] {}: {}", msg.seq, msg.sender, msg.text);
}

// For binary/non-UTF-8 content, use receive_raw
let raw_messages = bot.receive_raw(5000).await?;
for payload in &raw_messages {
    println!("received {} bytes", payload.len());
}
# Ok(())
# }
```

The `Message` struct contains:

| Field    | Type     | Description |
|----------|----------|-------------|
| `sender` | `String` | The sender's username. |
| `text`   | `String` | Decrypted plaintext content (UTF-8). |
| `seq`    | `u64`    | Sequence number. |

---

## Resolving users

```rust,no_run
# use quicproquo_bot::Bot;
# async fn example(bot: &Bot) -> anyhow::Result<()> {
let identity_key = bot.resolve_user("alice").await?;
println!("alice's identity key: {} bytes", identity_key.len());
# Ok(())
# }
```

---

## Identity inspection

```rust,no_run
# use quicproquo_bot::Bot;
# fn example(bot: &Bot) {
println!("username: {}", bot.username());
println!("identity key (hex): {}", bot.identity_key_hex());
let raw_key: [u8; 32] = bot.identity_key();
# }
```

---

## Pipe mode (stdin/stdout JSON lines)

For shell integration, the bot SDK supports a JSON-lines pipe interface. Each
line on stdin is a JSON command; results are written to stdout as JSON lines.

### Supported actions

**Send a message:**

```json
{"action": "send", "to": "alice", "text": "hello from pipe"}
```

Response:

```json
{"status": "ok", "action": "send"}
```

**Receive pending messages:**

```json
{"action": "recv", "timeout_ms": 5000}
```

Response:

```json
{"status": "ok", "messages": [{"sender": "peer", "text": "hi", "seq": 0}]}
```

**Resolve a username:**

```json
{"action": "resolve", "username": "alice"}
```

Response:

```json
{"status": "ok", "identity_key": "ab12cd34..."}
```

### Error responses

All actions return an error object on failure:

```json
{"error": "OPAQUE login: connection refused"}
```

### Shell examples

```bash
# Send via pipe
echo '{"action":"send","to":"alice","text":"hello"}' | my-bot-binary

# Receive via pipe
echo '{"action":"recv","timeout_ms":5000}' | my-bot-binary

# Use with jq for pretty output
echo '{"action":"recv","timeout_ms":3000}' | my-bot-binary | jq .
```

---

## Architecture notes

- **Stateless reconnect**: Each `send_dm` and `receive` call opens a fresh QUIC
  connection. There is no persistent connection to manage.
- **MLS encryption**: All messages are end-to-end encrypted via MLS (RFC 9420).
  The bot SDK wraps the client library's `cmd_send` and
  `receive_pending_plaintexts` functions.
- **State persistence**: The bot's identity seed and MLS group state are stored
  in the state file. Losing this file means losing the bot's identity.
- **Cap'n Proto !Send**: RPC calls run on a `tokio::task::LocalSet` because
  `capnp-rpc` is `!Send`.

---

## Next steps

- [Running the Client](running-the-client.md) -- CLI subcommands and REPL
- [Server Hooks](../internals/server-hooks.md) -- extend the server with plugins
- [Demo Walkthrough](demo-walkthrough.md) -- step-by-step messaging scenario
