Metadata-Version: 2.4
Name: ruos
Version: 0.1.0
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Typing :: Typed
Summary: Ultra-lite Python client for ruOS: desktops, ruOS Lite and the hosted MCP tools
Keywords: ruos,mcp,remote-desktop,computer-use,agents
Author-email: Cognitum <ruv@ruv.net>
License: MIT OR Apache-2.0
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://ruos.cognitum.one
Project-URL: Repository, https://github.com/cognitum-one/ruos-desktop

# ruos

Ultra-lite Python client for [ruOS](https://ruos.cognitum.one): your cloud
desktops, ruOS Lite browsers, and the hosted ruOS MCP tools, from Python.

The library is a single compiled Rust extension (PyO3, abi3 wheels for
Python 3.8+). It has no Python dependencies, uses rustls (no OpenSSL), and
releases the GIL during every network call.

```bash
pip install ruos
```

## Sign in

The Python package has no login flow of its own. Use one of these:

1. `npx ruos login`, which stores tokens in `~/.config/ruos/credentials.json`
   (mode 0600; `$RUOS_CONFIG_DIR` or `$XDG_CONFIG_HOME/ruos` if set). The
   Python client reads the same file and refreshes the access token
   automatically. On refresh it writes the file back in the format
   `npx ruos` expects (`version: 1`, `expires_at` in epoch milliseconds, all
   other fields kept), so one login covers both tools.
2. Set `RUOS_TOKEN` to an access token or a tenant API token (`ruos_mcp_…`,
   minted in the dashboard).
3. Pass `Client(token="...")`.

Precedence is `token=`, then `RUOS_TOKEN`, then the credentials file. Only
file-based credentials are refreshed.

## Use

```python
import ruos

c = ruos.Client()

c.whoami()          # {"tier": "pro", "token_source": "credentials_file", "plan": {...}, ...}
c.desktops()        # [{"machine_id": "...", "name": "dev", "state": "started", ...}, ...]
c.lite()            # saved ruOS Lite browsers ([] if none)

c.start("dev")      # by machine id, Fly id, name or display name; or "lite"
c.stop("dev")

c.tools()           # hosted MCP tools/list
c.call("desktop_status")                       # hosted MCP tools/call
c.call("desktop_exec", machine_id="...", command="uname -a")

png_or_jpeg = c.screenshot()                   # machine="lite" by default
open("screen.jpg", "wb").write(png_or_jpeg)

c.act("lite", "click", x=640, y=360)
c.act("lite", "type", text="hello")
c.act("lite", "key", keys="ctrl+l")

print(ruos.__version__)
```

`act` takes these actions: `mouse_move`, `click`, `double_click`, `drag`,
`scroll`, `type`, `key`, `cursor_position`, `screen_size`. Its keyword
arguments are `x`, `y`, `to_x`, `to_y`, `button`, `direction`, `amount`,
`text` and `keys`. Anything else raises `ValueError` before a request is sent.

### Options

```python
ruos.Client(
    token=None,              # explicit bearer
    base_url=None,           # default: $RUOS_BASE_URL or https://ruos.cognitum.one
    credentials_path=None,   # default: ~/.config/ruos/credentials.json
    timeout=None,            # seconds, default 60
)
```

## Errors

Every API error is a `ruos.RuosError`:

| Exception           | When                                                     |
|---------------------|----------------------------------------------------------|
| `ruos.AuthError`    | no credentials, HTTP 401/403, or a failed token refresh  |
| `ruos.NotFound`     | HTTP 404, or no desktop matches the name you gave        |
| `ruos.RateLimited`  | HTTP 429; `retry_after` holds the server's hint in seconds |
| `ruos.RuosError`    | anything else (5xx, transport, MCP tool errors)          |

Invalid arguments raise `ValueError` or `TypeError`. Tokens never appear in
exception messages or `repr(Client)`.

## Notes

- `whoami()` reports the tenant's plan and where the token came from. ruOS
  does not yet have a route that returns your identity.
- The hosted MCP endpoint (`/mcp`) is stateless Streamable HTTP. The client
  sends each JSON-RPC request on its own and accepts both SSE and JSON replies.
- Source: [`packages/ruos-py`](https://github.com/cognitum-one/ruos-desktop/tree/main/packages/ruos-py).
  Design: ADR-100.

License: MIT OR Apache-2.0.

