Metadata-Version: 2.3
Name: bulletins-sdk
Version: 0.2.0
Summary: Python SDK for Bulletins
Author: Fredrik Angelsen
Author-email: Fredrik Angelsen <fredrikangelsen@gmail.com>
Requires-Dist: httpx>=0.28.1
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# bulletins

Python SDK for the [Bulletins](https://bulletins.no) API. Async client, SSE streaming, and a declarative bot framework.

## Install

```bash
uv add bulletins-sdk
```

## Quick start

### Client

```python
import asyncio
from bulletins import BulletinsClient

async def main():
    client = BulletinsClient("bk_...", base_url="https://bulletins.no")

    async with client:
        await client.resolve()  # auto-resolves party + integration from key
        threads = await client.list_threads()
        for t in threads:
            print(f"{t.name} ({t.status})")

        await client.send_message(threads[0].id, "Hello from Python")

asyncio.run(main())
```

### SSE streaming

```python
async with client.stream_party() as stream:
    async for event in stream:
        print(f"[{event['type']}] {event.get('senderName')}: {event['content']}")
```

### Bot

Declarative bot with auto-registration of custom event types:

```python
import asyncio
from bulletins import Bot, EventTypeSpec

bot = Bot(
    "Ping Bot",
    event_types=[
        EventTypeSpec("bot:pong", content_schema={"message": {"type": "string"}}),
    ],
)

@bot.on("message")
async def handle(event, client):
    body = (event.content or {}).get("body", "")
    if body.startswith("!ping"):
        await client.send_message(event.thread_id, "pong")

asyncio.run(bot.start(api_key="bk_...", base_url="https://bulletins.no"))
```

Create the integration and API key in the Bulletins settings UI. The bot auto-resolves its party and integration from the API key and registers event types on startup.

## API

### `BulletinsClient`

| Method | Description |
|---|---|
| `resolve()` | Auto-resolve party + integration from API key |
| `list_threads()` | List threads for the active party |
| `get_thread(id)` | Thread detail with events and participants |
| `create_thread(name, to_party_id, body)` | Create a thread with initial message |
| `list_events(thread_id, before, limit)` | Paginated event history |
| `create_event(thread_id, type, content)` | Create any event type |
| `send_message(thread_id, body)` | Send a text message |
| `stream_party()` | SSE stream for all party events |
| `stream_thread(thread_id)` | SSE stream for a single thread |
| `list_integrations()` | List integrations |
| `get_integration_detail(id)` | Integration with all child resources |
| `create_event_type(integration_id, ...)` | Register a custom event type |
| `create_action(integration_id, ...)` | Create an action |
| `create_webhook(integration_id, ...)` | Create a webhook |
| `create_api_key(integration_id, ...)` | Create an API key |
| `get_agent_token(thread_id)` | LiveKit token for joining calls |

### `Bot`

| Method | Description |
|---|---|
| `on(event_type)` | Decorator to register an event handler |
| `start(api_key, base_url)` | Resolve identity, register event types, dispatch loop |
| `stop()` | Stop the dispatch loop |

## Requirements

Python 3.12+, httpx.
