Metadata-Version: 2.3
Name: bulletins-sdk
Version: 0.1.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():
    # API key auth (bots, services)
    client = BulletinsClient.from_api_key(
        "bk_...",
        base_url="https://bulletins.no",
        party_id="...",
    )

    # Or login auth (scripts, testing)
    client = await BulletinsClient.from_login(
        "user@example.com", "password",
        base_url="https://bulletins.no",
    )

    async with client:
        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.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",
    party_id="...",
    integration_id="...",
))
```

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

## API

### `BulletinsClient`

| Method | Description |
|---|---|
| `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, party_id, integration_id)` | Connect, register event types, dispatch loop |
| `stop()` | Stop the dispatch loop |

## Requirements

Python 3.12+, httpx.
