Skip to main content

Async-first Python SDK for CMDOP — the messenger for machines. Manage your fleet, stream each machine's resident AI agent, zero dependencies.

Project description

cmdop (Python)

cmdop — Python SDK for CMDOP

Async-first Python SDK for CMDOP — manage your machines, fleets, tunnels, and schedules, stream a machine's AI agent, and drive the skills marketplace, all from typed Python.

📚 Docs: docs.cmdop.com · SDK · Bots · Connect

How the CMDOP SDK connects your app to your machines

  • One install, zero dependenciespip install cmdop is everything. No native build step, no extra runtime, nothing fetched on first run.
  • Runs on macOS, Linux, and Windows — a single self-contained package; nothing is fetched at install or first run. (The CMDOP agent your machines run ships for macOS and Linux.)
  • Typed end to end — every resource and response is fully typed, with one clean async streaming API for live agent output.

Install

pip install cmdop        # or: uv add cmdop

Quick start

from cmdop import Client

async with Client(token="...") as c:        # or Client.from_env()
    page = await c.machines.list(presence="online")
    for m in page.items:
        print(m.hostname, m.presence)

    text = await c.machines.ask(machine_id, "uptime").collect()
    print(text)

Client is an async context manager — use async with so resources are released on exit (or call await c.aclose()).

Namespaces

The same surface as the relay, mirrored exactly by the Node SDK (snake_case here, camelCase there):

Namespace Methods
machines list · get · update · disable · info · spend · ask · messages · clear_messages · active_session
fleets list · get · create · update · disable
fleets.members list · add · set_role · remove
fleets.machines list · attach · detach
tunnels open · close · list · get · sessions
schedules list · get · create · update · delete · trigger · runs
keys list · issue · revoke
skills list · get · my · install · star · versions · reviews · create · update · delete · publish · publish_status · categories · tags

List endpoints also expose iter(...) (yield every item, following cursors) and pages(...) (yield each page).

Two planes, one client. machines / fleets / tunnels / schedules / keys use your relay token (CMDOP_TOKEN); the skills marketplace uses your platform API key (CMDOP_API_KEY). Set whichever you need — the client routes each call to the right plane for you.

Streaming: machines.ask()

ask() returns a FrameStream (aliased AskStream) — an async iterator of typed frames with pin() / confirm() / collect():

stream = c.machines.ask(machine_id, "df -h", session_id="s1")
async for frame in stream:
    if frame.type == "event":
        print(frame.payload.get("delta", ""), end="")
    elif frame.type == "pin_required":          # machine asked for its connect PIN
        await stream.pin(frame.challenge_id, "1234")
    elif frame.type == "confirm_required":       # a dangerous plan awaits approval
        await stream.confirm(frame.token, accept=True)
    elif frame.type == "pin_denied":
        print("PIN rejected:", frame.reason)
    elif frame.type == "done":
        print("\n->", frame.text)

# one-shot: drain to the final text
text = await c.machines.ask(machine_id, "uptime").collect()

Connection PIN. If a machine requires a PIN, pass it upfront — the SDK is headless, so there's no prompt to answer:

text = await c.machines.ask(machine_id, "uptime", pin="1234").collect()

A wrong PIN raises PinDeniedError; an unverified one raises PinTimeoutError (both subclass PermissionError, non-retryable). The reactive stream.pin(...) callback above is for interactive use (a human at a prompt). The relay never stores the PIN — it forwards it once to the target, which verifies it locally.

Frame types: event · done · error · confirm_required · pin_required · pin_denied. An error outcome raises AgentStreamError.

Environment variables

Var Meaning Default
CMDOP_TOKEN relay Bearer token (machines/fleets/…) — (required for relay ops)
CMDOP_BASE_URL relay REST root — your relay's address (e.g. https://<yours>.cmdop.dev) — (required for relay ops)
CMDOP_API_KEY platform UserAPIKey (for skills) — (required for skills)
CMDOP_API_BASE_URL platform REST root https://api.cmdop.com
CMDOP_FLEET_ID default fleet for fleet-scoped ops none
CMDOP_TIMEOUT_MS per-call timeout 30000

Where to get them. Create a free account at cmdop.com — it provisions your own relay domain, which becomes your CMDOP_BASE_URL (e.g. https://<yours>.cmdop.dev). CMDOP_TOKEN is a relay session token — copy it from your account's CLI-token page at my.cmdop.com (or, if you run the relay yourself, mint one from its /admin); it's the same token the desktop/CLI saves when you enroll a machine. CMDOP_API_KEY is a platform UserAPIKey — create it in your account settings at my.cmdop.com. The SDK is a client: it does not enroll machines (that's cmdop enroll <enrollment-password> on the agent), so you never paste an enrollment password here.

Precedence is always explicit arg > env var > default.

Error handling

Every failure surfaces as a typed exception carrying a stable code (all subclass CmdopError):

from cmdop import (
    AuthError, PermissionError, NotFoundError, ConflictError,
    ValidationError, RateLimitError, ServerError, ConnectionError,
    TimeoutError, UnavailableError, AgentStreamError, CmdopError,
)

try:
    await c.machines.get("missing-id")
except NotFoundError:
    ...
except CmdopError as e:
    if e.retryable:           # True on TimeoutError — safe to retry
        ...
    print(e.code, e.message)

ConnectionError covers a lost connection mid-call (pending calls reject); TimeoutError (a retryable subclass) is a deadline/handshake timeout; UnavailableError means the relay is up but the target agent/machine is offline. AgentStreamError is the streaming-ask error outcome.

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cmdop-1.1.1-py3-none-any.whl (25.0 MB view details)

Uploaded Python 3

File details

Details for the file cmdop-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: cmdop-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 25.0 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.18

File hashes

Hashes for cmdop-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9ff2c72c78b611220a8d2a5b0521eda17f2fdff3801e1caef87f24000d0959dc
MD5 6d19943f520967125a589d674b78e5e3
BLAKE2b-256 93003faa4fa2c3911eea746dec076f511fa902a256b559252f5291f19e59669a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page