Python SDK for CMDOP agent interaction
Project description
cmdop
Turn any machine into an API endpoint.
from cmdop import CMDOPClient
# Server in another country. Behind NAT. Behind firewall. Don't care.
with CMDOPClient.remote(api_key="cmd_xxx") as server:
server.terminal.execute("docker restart app")
server.files.write("/etc/nginx/sites/new.conf", config)
logs = server.files.read("/var/log/app/error.log")
No SSH. No VPN. No open ports. No bullshit.
The Problem
You need to run a command on a remote server. What do you do?
Option A: SSH
→ Generate keys
→ Copy public key to server
→ Configure sshd
→ Open port 22
→ Hope firewall allows it
→ Pray NAT doesn't break it
→ paramiko/fabric in Python
→ Debug why it doesn't work
Option B: Build an API
→ Write FastAPI endpoints
→ Implement authentication
→ Get SSL certificate
→ Open ports
→ Deploy to every server
→ Maintain all of this
→ Write client code
→ Repeat for each new operation
Option C: CMDOP
server.terminal.execute("your command")
That's it.
How It Works
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Your Code │ ──── │ Cloud Relay │ ──── │ Agent │
│ (SDK) │ │grpc.cmdop.com│ │ (on server)│
└─────────────┘ └─────────────┘ └─────────────┘
│
Outbound only ──────┘
No open ports
Works through any NAT/firewall
- Install agent on target machine
- Agent connects outbound to cloud relay
- Your code connects to same relay with API key
- Commands flow through, results come back
- Zero network configuration required
What Can You Do?
Terminal — full shell access:
session = server.terminal.create(shell="/bin/bash")
server.terminal.send_input(session.session_id, "kubectl get pods\n")
output = server.terminal.get_history(session.session_id)
Files — complete filesystem control:
server.files.list("/home/user")
server.files.read("/etc/nginx/nginx.conf")
server.files.write("/tmp/config.json", b'{"key": "value"}')
server.files.delete("/tmp/garbage", recursive=True)
server.files.copy("/backup/db.sql", "/restore/db.sql")
Browser — direct programmatic control (no LLM):
with server.browser.create_session() as browser:
browser.navigate("https://example.com/login")
browser.type("input[name='email']", "user@example.com")
browser.type("input[name='password']", "secret")
browser.click("button[type='submit']")
# Extract data with CSS selectors
prices = browser.extract(".product-price")
# Or regex
emails = browser.extract_regex(r"[\w.-]+@[\w.-]+\.\w+")
# Or run any JavaScript
data = browser.execute_script("return window.__APP_STATE__")
AI Agents — run LLM agents with type-safe responses:
from pydantic import BaseModel
class ServerHealth(BaseModel):
status: str
cpu_percent: float
memory_used_gb: float
disk_free_gb: float
issues: list[str]
# Run agent, get typed response
result = server.agent.run(
prompt="Check server health and report issues",
output_schema=ServerHealth
)
health: ServerHealth = result.output # Fully typed!
if health.issues:
alert(health.issues)
Real World
Give AI Agents Hands
from pydantic import BaseModel
class DeployResult(BaseModel):
success: bool
version: str
containers_running: int
errors: list[str]
# Agent executes commands, parses output, returns structured data
with CMDOPClient.remote(api_key=KEY) as server:
result = server.agent.run(
prompt="Deploy myapp:v2.1 and verify all containers are healthy",
agent_type="terminal",
output_schema=DeployResult
)
if not result.output.success:
rollback(result.output.errors)
Deploy Without The Circus
# No Ansible. No Terraform. No YAML hell.
def deploy(version: str):
with CMDOPClient.remote(api_key=PROD_KEY) as prod:
prod.terminal.execute(f"docker pull myapp:{version}")
prod.terminal.execute("docker-compose up -d")
# Verify
health = prod.files.read("/var/log/myapp/health.log")
assert b"healthy" in health
Fleet Management
# Update 1000 edge devices
async def update_fleet(device_keys: list[str], new_config: bytes):
async with asyncio.TaskGroup() as tg:
for key in device_keys:
tg.create_task(update_device(key, new_config))
async def update_device(key: str, config: bytes):
async with AsyncCMDOPClient.remote(api_key=key) as device:
await device.files.write("/etc/app/config.yml", config)
await device.terminal.execute("systemctl restart app")
Debug Customer Issues
# Support engineer gets temporary access
def diagnose(customer_key: str):
with CMDOPClient.remote(api_key=customer_key) as machine:
# See what's running
session = machine.terminal.create()
machine.terminal.send_input(session.session_id, "ps aux\n")
# Read their logs
logs = machine.files.read("~/Library/Logs/MyApp/error.log")
# Check disk space
machine.terminal.send_input(session.session_id, "df -h\n")
Scrape Without The LLM Tax
# Direct browser control - fast, predictable, cheap
def scrape_products(url: str) -> list[dict]:
with CMDOPClient.local() as client:
with client.browser.create_session(headless=True) as browser:
browser.navigate(url)
# Wait for content to load
browser.wait_for(".product-card", timeout_ms=10000)
# Extract with CSS selectors
names = browser.extract(".product-name")
prices = browser.extract(".product-price")
links = browser.extract(".product-link", attr="href")
return [
{"name": n, "price": p, "url": l}
for n, p, l in zip(names, prices, links)
]
Installation
pip install cmdop
Quick Start
from cmdop import CMDOPClient
# Remote server via cloud relay
with CMDOPClient.remote(api_key="cmd_xxx") as client:
result = client.files.list("/home")
print(result.entries)
# Local agent on same machine
with CMDOPClient.local() as client:
result = client.files.list("/home")
Async
from cmdop import AsyncCMDOPClient
async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
await client.terminal.execute("echo hello")
content = await client.files.read("/etc/hostname")
API
Terminal
| Method | What it does |
|---|---|
create(shell, cols, rows) |
Start terminal session |
send_input(id, data) |
Send commands/keystrokes |
get_history(id) |
Get output |
resize(id, cols, rows) |
Resize terminal |
send_signal(id, signal) |
Send SIGINT/SIGTERM/etc |
close(id) |
End session |
Files
| Method | What it does |
|---|---|
list(path) |
List directory |
read(path) |
Read file |
write(path, content) |
Write file |
delete(path) |
Delete file/dir |
copy(src, dst) |
Copy |
move(src, dst) |
Move/rename |
mkdir(path) |
Create directory |
info(path) |
Get metadata |
Agent
| Method | What it does |
|---|---|
run(prompt, output_schema=None) |
Run AI agent, optionally with Pydantic model for structured output |
Supported agent types: chat, terminal, command, router, planner
Structured output with Pydantic:
class MyResult(BaseModel):
answer: str
confidence: float
result = client.agent.run("...", output_schema=MyResult)
typed_data: MyResult = result.output # Validated against schema
Browser
Direct browser control without LLM. Fast, predictable, cheap.
| Method | What it does |
|---|---|
create_session(headless=True) |
Start browser session (returns context manager) |
navigate(url) |
Go to URL, returns final URL |
click(selector) |
Click element by CSS selector |
type(selector, text) |
Type into input/textarea |
wait_for(selector, timeout_ms) |
Wait for element to appear |
extract(selector, attr=None) |
Get text or attribute from elements |
extract_regex(pattern) |
Extract regex matches from page |
get_html() |
Get page HTML |
get_text() |
Get page text content |
execute_script(js) |
Run JavaScript, return result |
screenshot() |
Capture PNG screenshot |
get_state() |
Get current URL and title |
set_cookies(cookies) |
Set browser cookies |
get_cookies() |
Get all cookies |
Session as context manager:
with client.browser.create_session() as browser:
browser.navigate("https://example.com")
data = browser.extract("h1")
# Session auto-closes
Security
- TLS everywhere — all traffic encrypted
- Outbound only — agent never accepts incoming connections
- API key scoping — keys bound to specific agents/teams
- No credentials on wire — no SSH keys, no passwords
- Audit logs — every action tracked
Requirements
- Python 3.10+
- CMDOP agent running on target machine
Links
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cmdop-0.1.9.tar.gz.
File metadata
- Download URL: cmdop-0.1.9.tar.gz
- Upload date:
- Size: 139.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.18
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98014891be211bc4f292cad7930153ea01227a464554a3e125436442641669ed
|
|
| MD5 |
59e4c285131b4f508df44139f49c4c0b
|
|
| BLAKE2b-256 |
57fe1d1b96833bd535098ee9c68897acf06e3651bfc18944f7363d7f350bc2c6
|
File details
Details for the file cmdop-0.1.9-py3-none-any.whl.
File metadata
- Download URL: cmdop-0.1.9-py3-none-any.whl
- Upload date:
- Size: 239.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.18
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0d21365c3424bd18ec707fbb26b6e0ddbb1bd1d4816404a451a918fd50c9d35
|
|
| MD5 |
82eeb018268451eaf7fb27105115ebf0
|
|
| BLAKE2b-256 |
a21b1e919cb5fe57d9f529d3d9e50989f7ad4aef1a17ecd2d7208c9e779ffe0f
|