Metadata-Version: 2.1
Name: perplexity-remotebox
Author: Perplexity AI
License: Proprietary
Description-Content-Type: text/markdown
Summary: Connect your machine as a remotebox for Perplexity Computer.
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.12
Version: 0.1.0

# remotebox-connect

The remotebox outbound connector — the sole connector implementation. It
started as a dependency-free native port of the Python connector CLI that
lived in `pplx/python/sdks/remotebox` (published as `perplexity-remotebox`);
that CLI has since been deleted. One static binary replaces the Python 3.12+
runtime and its dependency floor for BYOC dogfooding; see ASI-6627. Run it
from the repo with `bin/remotebox-connect`.

One documented exception to "dependency-free": if Cloudflare blocks the
built-in HTTP client's TLS fingerprint during the browser OAuth token
exchange (seen on devboxes), the flow falls back to the system `curl` binary
(`src/auth.rs`). Steady-state connector operation — connect, exec, file
transfer, reconnect — never shells out to external tools.

The wire protocol, reconnect behavior, auth flows, token-cache paths, and CLI
flag surface are 1:1 ports of the retired Python connector — except the
experimental sidecar flags below, which are new — and must stay compatible
with the fs-ws-mux broker, whose protocol implementation lives in
`pplx/python/sdks/remotebox/protocol.py`. When changing that protocol, mirror
the change here (and vice versa) — the loopback tests in `tests/loopback.rs`
encode the shared contract, and the fs-ws-mux loopback suite
(`pplx/python/apps/asi/src/workers/fs_ws_mux/tests/test_remotebox_loopback.py`)
runs this binary against the real broker.

Known wire divergence, kept deliberately: pydantic parses arbitrary-precision
integers, so the Python protocol implementation accepts integer fields that
overflow the Rust field's storage (e.g. a `max_output_bytes` above
`i64::MAX`); the Rust connector rejects such frames at parse time with an
error frame. No real client emits these values — the broker and SDKs stay
within i64 — and every case is pinned by the `DIVERGES` rows in
`tests/python_golden.rs`.

System proxies are honored like the Python connector was (it inherited them
from `websockets`' `proxy=True` default): `wss_proxy`/`https_proxy` (or
`ws_proxy`/`http_proxy` for `ws://` brokers) with `no_proxy` bypass, tunneled
via HTTP CONNECT with optional Basic auth. `https://` (TLS-to-proxy) and
SOCKS proxies are rejected with a clear error instead of dialing directly
(`src/proxy.rs`).

## Experimental EnvD-compatible sidecar (opt-in)

`--envd-binary <path>` makes the connector spawn a compatible EnvD binary
as its child process and proxy v2 `envd_request` frames to it over loopback
(`--envd-port`, default 49983); without the flag the connector serves legacy
frames only. After successful startup the connector advertises EnvD executor
eligibility during registration. Sidecar mode is Linux-only (supervision relies on PDEATHSIG);
on other platforms `--envd-binary` fails at startup while the legacy path
keeps working. Exactly one EnvD may exist per box, and it must be the one this
connector spawned, because initialization hands it the generated access
token.

Startup enforces that invariant rather than assuming it: it refuses to start
if anything already listens on the port (an EnvD orphaned by an earlier
`kill -9`, or an unrelated squatter — either would answer the health check
in place of a child that lost the bind race and exited), and it re-verifies
the spawned child is alive after initialization. On exit the child dies with
the connector, including SIGTERM/SIGKILL (PDEATHSIG), so no stale EnvD can
survive to shadow the next run. The sidecar's HTTP client never uses system
proxies.

The supported sidecar is the `:envd_host` Bazel target in this package. It
builds a pinned EnvD 0.6.13 source revision with the small downstream
`envd-host.patch`: `--host` allows the connector to bind it to `127.0.0.1`,
commands may run as the current user when EnvD is unprivileged, and self-upgrade
is disabled in host mode. The upstream defaults are unchanged. The connector
still verifies `/proc/net/tcp` before initialization and fails closed unless
the listener is exclusively loopback.

Production `asi-prod` runs `spaced-svc` inside its Firecracker guest, not a
standalone EnvD binary. The host build therefore targets the same EnvD API
behavior, not a byte-identical Space runtime.

## Build and test

```bash
cargo build -p remotebox-connect
cargo test -p remotebox-connect

bin/bazel build //pplx/rust/apps/remotebox-connect/...
bin/bazel test //pplx/rust/apps/remotebox-connect/...

# Current connector plus pinned EnvD host build in one Linux tarball.
bin/bazel build //pplx/rust/apps/remotebox-connect:remotebox_bundle

# Exercise stock AsyncSandbox through broker -> connector -> packaged EnvD.
bin/builder execute-functional-test \
  //pplx/python/apps/asi/src/workers/fs_ws_mux:remotebox_canary_test

```

## Install

Release `0.1.0` and later of `perplexity-remotebox` ship the native Rust
connector. Install it from PyPI into an isolated tool environment:

```bash
uv tool install perplexity-remotebox
remotebox-connect --email you@perplexity.ai
```

The Linux wheels also install the pinned EnvD sidecar as
`remotebox-envd`. Opt in to the preview executor with:

```bash
remotebox-connect \
  --email you@perplexity.ai \
  --envd-binary "$(command -v remotebox-envd)"
```

macOS wheels contain only `remotebox-connect`; sidecar mode is Linux-only.
The connector itself has no Python runtime dependency after installation.

## Run

```bash
target/debug/remotebox-connect --email you@perplexity.ai
target/debug/remotebox-connect --doctor
bazel-bin/pplx/rust/apps/remotebox-connect/remotebox-connect \
  --email you@perplexity.ai \
  --remotebox-id "$DEVBOX_ID" \
  --dir /home/dev \
  --envd-binary bazel-bin/pplx/rust/apps/remotebox-connect/envd-host
```

Flags match the retired Python `remotebox-connect` CLI (`--email`,
`--doctor`, `--default-url`, `--broker-url`, `--auth-base-url`,
`--oauth-token-base-url`, `--remotebox-id`, `--dir`, `--fresh-login`,
`--verbose`).

## Versioning

Cargo and unstamped Bazel builds report `0.0.0+local`. Stamped Bazel builds
embed the workspace version (same pattern as pplx-cli):

```bash
bin/bazel build --config=stamp //pplx/rust/apps/remotebox-connect:remotebox-connect
# remotebox-connect 2026.07.14.1784060218+5d78235
```

The version is sent as `X-App-Version` on every broker connection and is what
broker-side minimum-version enforcement keys on.

## Publish to PyPI

The PyPI publisher builds the Linux amd64/arm64 and macOS amd64/arm64 wheels
before uploading any of them. Preview and publish a release with:

```bash
bin/builder publish-wheels \
  //pplx/rust/apps/remotebox-connect:remotebox_connect_package \
  --registry pypi \
  --version 0.1.0 \
  --dry-run

PYPI_TOKEN=pypi-... bin/builder publish-wheels \
  //pplx/rust/apps/remotebox-connect:remotebox_connect_package \
  --registry pypi \
  --version 0.1.0
```

PyPI versions are immutable. Confirm the version is unused and run the wheel
tests before removing `--dry-run`.

## Status / follow-ups (ASI-6627)

The Python connector CLI and its `perplexity-remotebox` PyPI publish rail are
deleted; the shared Python protocol remains at `pplx/python/sdks/remotebox`
for broker-side consumers and compatibility tests. Remaining follow-ups:

- A non-PyPI install path for machines without Python (`curl | sh` or a
  standalone archive).
- Yank the published `perplexity-remotebox` 0.1.0.dev1 from PyPI (manual,
  outside the repo).
