chore: import upstream snapshot with attribution
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:03:19 +08:00
commit 91e75e620b
3227 changed files with 1307078 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
[bumpversion]
current_version = 0.1.2
commit = True
tag = True
tag_name = train-v{new_version}
message = Bump cua-train to v{new_version}
[bumpversion:file:pyproject.toml]
search = version = "{current_version}"
replace = version = "{new_version}"
+23
View File
@@ -0,0 +1,23 @@
# cua-train
Python client for the [Cua Cloud](https://run.cua.ai) API.
```bash
pip install cua-train
```
```python
from cua_train import TrainClient
client = TrainClient.from_key(
client_id="ukey-...", # from your Cua Cloud account
client_secret="...",
)
# Control plane — claim a VM
http = client.get_async_httpx_client()
await http.post("/api/k8s/apis/osgym.cua.ai/v1alpha1/namespaces/my-pool/osgymsandboxclaims", json={...})
# Data plane — exec in the VM
await http.post("/api/svc/my-pool/my-sandbox-server/execute", json={"command": "echo hi"})
```
+38
View File
@@ -0,0 +1,38 @@
[project]
name = "cua-train"
version = "0.1.2"
description = "Cua Cloud Python client — control and data plane for Cua Cloud desktop VM pools"
readme = "README.md"
license = "MIT"
authors = [
{ name = "TryCua", email = "hello@trycua.com" }
]
keywords = [
"cua",
"computer-use",
"cloud",
"vm",
"automation",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
requires-python = ">=3.10"
dependencies = ["httpx>=0.27.0"]
[project.urls]
Homepage = "https://run.cua.ai"
Repository = "https://github.com/trycua/cua"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/cua_train"]
@@ -0,0 +1,5 @@
"""cua-train — Python client for the Cua Cloud API."""
from .client import TrainClient
__all__ = ["TrainClient"]
@@ -0,0 +1,117 @@
"""TrainClient — authenticated httpx client for the Cua Cloud API."""
from __future__ import annotations
import threading
import time
import httpx
class TrainClient:
"""Authenticated client for the Cua Cloud API.
Obtain via :meth:`from_key` using a per-user key (``ukey-…``) from
your Cua Cloud account. The client transparently refreshes its bearer
token before it expires.
Both the control plane (VM claim lifecycle via ``/api/k8s``) and the
data plane (exec/upload/screenshot via ``/api/svc``) are reached through
the same base URL (default: ``https://run.cua.ai``).
"""
_DEFAULT_TOKEN_URL = "https://auth.cua.ai/realms/cyclops-cs/protocol/openid-connect/token"
_DEFAULT_BASE_URL = "https://run.cua.ai"
def __init__(
self,
token: str,
base_url: str = _DEFAULT_BASE_URL,
*,
token_url: str = _DEFAULT_TOKEN_URL,
client_id: str = "",
client_secret: str = "",
token_deadline: float = 0.0,
) -> None:
self._token = token
self._base_url = base_url.rstrip("/")
self._token_url = token_url
self._client_id = client_id
self._client_secret = client_secret
self._token_deadline = token_deadline
self._refresh_lock = threading.Lock()
self._async_client: httpx.AsyncClient | None = None
@classmethod
def from_key(
cls,
*,
token_url: str = _DEFAULT_TOKEN_URL,
client_id: str,
client_secret: str,
base_url: str = _DEFAULT_BASE_URL,
) -> "TrainClient":
"""Exchange client credentials for a bearer token.
Args:
token_url: Keycloak token endpoint.
client_id: Per-user key client ID (``ukey-…``).
client_secret: Client secret.
base_url: Cua Cloud API base URL (default: ``https://run.cua.ai``).
"""
token, ttl = cls._exchange(token_url, client_id, client_secret)
return cls(
token=token,
base_url=base_url,
token_url=token_url,
client_id=client_id,
client_secret=client_secret,
token_deadline=time.monotonic() + ttl,
)
@staticmethod
def _exchange(token_url: str, client_id: str, client_secret: str) -> tuple[str, float]:
resp = httpx.post(
token_url,
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
},
)
resp.raise_for_status()
body = resp.json()
return body["access_token"], float(body.get("expires_in", 300))
def reset_token(self) -> None:
"""Force re-exchange on the next :meth:`get_async_httpx_client` call."""
self._token_deadline = 0
def _refresh_token(self) -> None:
if not self._client_id:
return
if time.monotonic() < self._token_deadline - 30:
return
with self._refresh_lock:
if time.monotonic() < self._token_deadline - 30:
return
token, ttl = self._exchange(self._token_url, self._client_id, self._client_secret)
self._token = token
self._token_deadline = time.monotonic() + ttl
auth_header = f"Bearer {token}"
if self._async_client is not None:
self._async_client.headers["Authorization"] = auth_header
def get_async_httpx_client(self) -> httpx.AsyncClient:
"""Return an async httpx client with the current bearer token.
Re-mints the token transparently when it is about to expire.
"""
self._refresh_token()
if self._async_client is None:
self._async_client = httpx.AsyncClient(
base_url=self._base_url,
headers={"Authorization": f"Bearer {self._token}"},
follow_redirects=False,
)
return self._async_client