Metadata-Version: 2.4
Name: menlo-robot-sdk
Version: 0.1.0
Summary: Python client for the Menlo Robot Control Service (RCS) control plane.
License: MIT
Requires-Python: >=3.12
Requires-Dist: httpx<1,>=0.28
Requires-Dist: pydantic<3,>=2.9
Provides-Extra: livekit
Requires-Dist: livekit<1,>=0.18; extra == 'livekit'
Provides-Extra: menlo-rcs-types
Requires-Dist: menlo-rcs-types; extra == 'menlo-rcs-types'
Description-Content-Type: text/markdown

# menlo-robot-sdk

Python client for the [Robot Control Service (RCS)](../../../services/rcs) control plane: robot CRUD and LiveKit session lifecycle.

> **⚠️ Alpha — Not for Production Use**
>
> This package is in active development and may have breaking changes between minor versions.
> APIs are subject to change. Use at your own risk.

> **Roadmap** — Stage 0 (control plane) is shipped; Stages 1–5 cover production hardening, command/telemetry API, identity, publishing, and observability. No fixed dates — see [docs/ROADMAP.md](./docs/ROADMAP.md) and [issue #125](https://github.com/menloresearch/mono/issues/125).
>
> **Rough product spec** (connect, skills, runtime helpers): [ADR 0002](./docs/adr/0002-menlo-robot-sdk-rough-product-spec.md) — what works today vs blocked on RCS ([#136](https://github.com/menloresearch/mono/issues/136)).

## Install

```bash
uv add menlo-robot-sdk

# With LiveKit support
uv add "menlo-robot-sdk[livekit]"
```

```bash
cd packages/py/menlo-robot-sdk
uv sync --all-extras --dev
uv run python -c "from menlo_robot_sdk import AsyncClient; print('ok')"
```

Path dependency from another uv project:

```toml
[tool.uv.sources]
menlo-robot-sdk = { path = "../../packages/py/menlo-robot-sdk", editable = true }
```

When PyPI publishing lands (ROADMAP Stage 4): `uv add menlo-robot-sdk` and `uv add "menlo-robot-sdk[livekit]"`.

## Configuration

See [`.env.example`](./.env.example) for all client env vars (placeholders only — no secrets).

| Variable | Description |
|----------|-------------|
| `MENLO_RCS_URL` | RCS base URL (e.g. `http://localhost:8002`) |
| `MENLO_IDENTITY` | Full `X-Menlo-Identity` header value (JWS or unsigned JSON) |
| `MENLO_IDENTITY_FILE` | Path to file containing identity (overrides `MENLO_IDENTITY`) |
| `MENLO_ROBOT_SDK_TIMEOUT` | Per-request timeout in seconds (default `30`) |
| `MENLO_ROBOT_SDK_MAX_RETRIES` | Max retries on transport errors / safe HTTP codes (default `2`) |
| `MENLO_ROBOT_SDK_DEBUG` | Set to `1` to log httpx request/response events |
| `MENLO_LIVEKIT_URL` | Optional override for `connect_session()` when RCS returns an internal hostname |
| `MENLO_RCS_INTEGRATION` | Set to `1` to run integration tests (see [MANUAL_TESTING.md](./MANUAL_TESTING.md)) |

`MENLO_API_KEY` alone is **not** supported for RCS; use `MENLO_IDENTITY` until token exchange ships ([#136](https://github.com/menloresearch/mono/issues/136)). Rough-spec **url** = `MENLO_RCS_URL` / `MenloSettings.url`.

Pass `MenloSettings` or omit `rcs_url` / `identity` on `Client` / `AsyncClient` to load from env. If you pass `rcs_url` and `identity` explicitly, you must pass **both**; passing only one raises `ValueError`. Optional `timeout` / `max_retries` override env defaults when using the env path.

`robots.update(robot_id, name=...)` sends only the keyword arguments you provide (omitted fields are not sent as `null`).

```python
from menlo_robot_sdk import AsyncClient, DevIdentity, MenloSettings, workers

settings = MenloSettings.from_env()
# or local unsigned JSON:
settings = MenloSettings(
    rcs_url="http://localhost:8002",
    identity=DevIdentity(sub="user_dev", organization_id="org_dev", team_id="team_dev").to_header(),
)

async with AsyncClient(settings=settings) as client:
    created = await client.robots.create(name="Lab bot", model="asimov-v0")
    print(created.pin_code)  # show-once PIN — save securely

    page = await client.robots.list(limit=20)
    async for robot in client.robots.iter(limit=50):
        print(robot.id)

    session = await client.robots.create_session(
        created.robot.id,
        workers=workers.for_rcs_stack(),
    )
```

## LiveKit (optional)

```python
from menlo_robot_sdk.livekit import connect_session

room = await connect_session(session)  # uses session.livekit_url
# room = await connect_session(session, livekit_url=<URL reachable from your host>)  # override for Docker
```

In a sync-only script (no running event loop), use ``asyncio.run(connect_session(session))``.

## Connect helper (rough spec — partial)

```python
from menlo_robot_sdk import AsyncClient, ConnectCallbacks, connect, workers

async with AsyncClient() as client:
    handle = await connect(
        client,
        "rb_myrobot",
        workers=workers.for_rcs_stack(),
        join_livekit=False,  # default True needs menlo-robot-sdk[livekit] for LiveKit + callbacks
    )
    # handle.get_robot_status(), invoke(), … raise MenloNotAvailableError until mono#136
    await handle.disconnect()
```

See [ADR 0002](./docs/adr/0002-menlo-robot-sdk-rough-product-spec.md) for the full matrix.

## Errors and retries

RCS errors use `{ "code", "message", "details" }`. The SDK raises `MenloAPIError` for HTTP status ≥ 400. Retries (default 2) apply to transport failures and **429** on safe methods; **POST /v1/robots** and **POST …/session** are not retried. Backoff is capped at **30s**. See [ADR 0001](./docs/adr/0001-menlo-robot-sdk-v1-rcs-control-plane.md).

## Examples

Runnable cookbooks (one use case per file, flat snippets): [examples/README.md](./examples/README.md). Run after local `rcs-stack` — see [MANUAL_TESTING.md](./MANUAL_TESTING.md).

## Testing

- **Unit** (mocked HTTP): `uv run pytest -q` (integration excluded by default)
- **Integration** (live RCS): see [MANUAL_TESTING.md](./MANUAL_TESTING.md)
- **QA checklist** (PR / release): [MANUAL_TESTING.md § QA checklist](./MANUAL_TESTING.md#qa-checklist)

## Development

```bash
cd packages/py/menlo-robot-sdk
uv sync --all-extras --dev
cd ../../../services/rcs && uv sync --frozen --no-dev && cd ../../packages/py/menlo-robot-sdk
uv run python scripts/export_rcs_openapi.py
uv run python scripts/regen_models.py --check
uv run ruff check .
uv run mypy src
uv run pytest -q
export MENLO_RCS_INTEGRATION=1
uv run pytest tests/integration -m integration -q   # live RCS; see MANUAL_TESTING.md
```

## Publishing

This package is published to PyPI via GitHub Actions on version tags. No API token needed — uses PyPI Trusted Publishing (OIDC).

**To release a new version:**

```bash
# 1. Bump version in pyproject.toml
cd packages/py/menlo-robot-sdk
# Edit version in pyproject.toml, then:
uv lock

# 2. Commit and merge to main
git add pyproject.toml uv.lock
git commit -m "chore: bump menlo-robot-sdk to v0.5.0"
# ... push PR, get approval, merge

# 3. Create and push the version tag (triggers publish workflow)
git checkout main && git pull
git tag menlo-robot-sdk-v0.5.0
git push origin menlo-robot-sdk-v0.5.0
```

The workflow `.github/workflows/menlo-robot-sdk-publish.yml` builds and publishes automatically. Check the Actions tab for status.
```
