Metadata-Version: 2.4
Name: menlo-robot-sdk
Version: 0.1.1
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 Menlo Robot Control Service (RCS): robot CRUD, session lifecycle, and runtime skill calls through RCS.

> Alpha: this package is in active development and may introduce breaking API changes between minor versions.

## Install

```bash
uv add menlo-robot-sdk
```

The base package covers the RCS HTTP control plane and runtime HTTP/SSE helpers. Install the optional LiveKit extra only if your Python process needs to join a LiveKit room directly:

```bash
uv add "menlo-robot-sdk[livekit]"
```

## Configure

Local development against RCS uses an unsigned development identity:

```python
from menlo_robot_sdk import DevIdentity, MenloSettings

settings = MenloSettings(
    rcs_url="http://localhost:8002",
    identity=DevIdentity(
        sub="user_dev",
        organization_id="org_dev",
        team_id="team_dev",
    ).to_header(),
)
```

You can also load settings from environment variables:

```bash
export MENLO_RCS_URL=http://localhost:8002
export MENLO_IDENTITY='{"sub":"user_dev","organization_id":"org_dev","team_id":"team_dev","roles":[],"scopes":[]}'
```

`MENLO_API_KEY` alone is not accepted by RCS yet. Hosted auth and cloud setup are separate from this local SDK path.

## Quick Example

```python
import asyncio

from menlo_robot_sdk import AsyncClient, ConnectCallbacks, DevIdentity, MenloSettings, connect, workers


async def main() -> None:
    settings = MenloSettings(
        rcs_url="http://localhost:8002",
        identity=DevIdentity(
            sub="user_dev",
            organization_id="org_dev",
            team_id="team_dev",
        ).to_header(),
    )

    events = []

    def on_action_result(event) -> None:
        events.append(event)

    async with AsyncClient(settings=settings) as client:
        created = await client.robots.create(name="sdk-demo", model="asimov-v0")
        session = None
        try:
            session = await connect(
                client,
                created.robot.id,
                workers=workers.for_rcs_stack(),
                join_livekit=False,
                callbacks=ConnectCallbacks(on_action_result=on_action_result),
            )

            skills = await session.discover_skills()
            status = await session.get_robot_status()
            action_id = await session.invoke(
                "set_velocity",
                {"wz": 0.8, "duration_s": 2.0},
            )

            print([skill.name for skill in skills], status.runtime.status, action_id)
        finally:
            if session is not None:
                await session.disconnect()
            await client.robots.delete(created.robot.id)


asyncio.run(main())
```

The runtime skill calls above require a running local RCS stack and browser viewer.
