Metadata-Version: 2.4
Name: cmdop
Version: 0.1.1
Summary: Python SDK for CMDOP agent interaction
Project-URL: Homepage, https://cmdop.com
Project-URL: Documentation, https://cmdop.com
Project-URL: Repository, https://github.com/markolofsen/cmdop-client
Author: CMDOP Team
License: MIT
License-File: LICENSE
Keywords: agent,automation,cmdop,grpc,terminal
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: grpcio>=1.60.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: protobuf>=4.25.0
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: pydantic>=2.5.0
Provides-Extra: dev
Requires-Dist: grpcio-tools>=1.60.0; extra == 'dev'
Requires-Dist: mypy>=1.8.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
Requires-Dist: pytest-grpc-aio>=0.3.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# cmdop

**Programmatic access to any machine. No SSH, no VPN, no open ports.**

[![PyPI](https://img.shields.io/pypi/v/cmdop)](https://pypi.org/project/cmdop/)
[![Python](https://img.shields.io/pypi/pyversions/cmdop)](https://pypi.org/project/cmdop/)
[![License](https://img.shields.io/pypi/l/cmdop)](https://github.com/cmdop/cmdop-python/blob/main/LICENSE)

```python
from cmdop import CMDOPClient

with CMDOPClient.remote(api_key="cmd_xxx") as client:
    # Execute commands on remote server
    session = client.terminal.create()
    client.terminal.send_input(session.session_id, "docker ps\n")

    # Read/write files
    logs = client.files.read("/var/log/app.log")
    client.files.write("/tmp/config.json", b'{"debug": true}')
```

One API key. Any machine. Full control.

## Use Cases

### AI Agents with Real Infrastructure Access

```python
# Give your AI agent actual server access
def ai_agent_tool(command: str) -> str:
    with CMDOPClient.remote(api_key=API_KEY) as client:
        session = client.terminal.create()
        client.terminal.send_input(session.session_id, f"{command}\n")
        time.sleep(2)
        history = client.terminal.get_history(session.session_id)
        return history.data.decode()

# Claude/GPT can now execute real commands
result = ai_agent_tool("kubectl get pods")
```

### CI/CD & Deployment

```python
# Deploy without Ansible, Fabric, or SSH keys
def deploy(server_key: str, version: str):
    with CMDOPClient.remote(api_key=server_key) as client:
        client.terminal.execute(f"docker pull myapp:{version}")
        client.terminal.execute("docker-compose up -d")

        # Verify deployment
        logs = client.files.read("/var/log/myapp/startup.log")
        assert b"Started successfully" in logs
```

### Remote Monitoring & Debugging

```python
# Collect logs from fleet of servers
async def collect_logs(server_keys: list[str]):
    async with asyncio.TaskGroup() as tg:
        for key in server_keys:
            tg.create_task(fetch_logs(key))

async def fetch_logs(api_key: str):
    async with AsyncCMDOPClient.remote(api_key=api_key) as client:
        return await client.files.read("/var/log/nginx/error.log")
```

### Edge/IoT Device Management

```python
# Update config on thousands of devices
for device_key in device_fleet:
    with CMDOPClient.remote(api_key=device_key) as client:
        client.files.write("/etc/myapp/config.yml", new_config)
        client.terminal.execute("systemctl restart myapp")
```

### Customer Support with Direct Access

```python
# Support agent can inspect customer's machine (with permission)
def diagnose_customer(customer_agent_key: str):
    with CMDOPClient.remote(api_key=customer_agent_key) as client:
        # Check disk space
        result = client.files.list("/")

        # Read app logs
        logs = client.files.read("~/Library/Logs/MyApp/error.log")

        # Check running processes
        session = client.terminal.create()
        client.terminal.send_input(session.session_id, "ps aux | grep myapp\n")
```

## Installation

```bash
pip install cmdop
```

## Connection Modes

```python
# Remote — via cloud relay (any machine, anywhere)
client = CMDOPClient.remote(api_key="cmd_xxx")

# Remote — specific agent
client = CMDOPClient.remote(api_key="cmd_xxx", agent_id="uuid-here")

# Local — direct IPC with agent on same machine
client = CMDOPClient.local()
```

## Async Support

```python
import asyncio
from cmdop import AsyncCMDOPClient

async def main():
    async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
        session = await client.terminal.create()
        await client.terminal.send_input(session.session_id, "whoami\n")

        files = await client.files.list("/home")
        content = await client.files.read("/etc/hostname")

asyncio.run(main())
```

## API Reference

### Terminal Service

| Method | Description |
|--------|-------------|
| `create(shell, cols, rows)` | Create terminal session |
| `send_input(session_id, data)` | Send input/commands |
| `resize(session_id, cols, rows)` | Resize terminal |
| `send_signal(session_id, signal)` | Send signal (SIGINT, SIGTERM) |
| `get_history(session_id, lines)` | Get output history |
| `close(session_id)` | Close session |

### Files Service

| Method | Description |
|--------|-------------|
| `list(path, page_size)` | List directory |
| `read(path)` | Read file contents |
| `write(path, content)` | Write file |
| `delete(path, recursive)` | Delete file/directory |
| `copy(src, dst)` | Copy file |
| `move(src, dst)` | Move/rename |
| `mkdir(path, parents)` | Create directory |
| `info(path)` | Get file metadata |

## Error Handling

```python
from cmdop.exceptions import (
    AgentOfflineError,      # Agent not connected to cloud
    AgentNotRunningError,   # Local agent not running
    AuthenticationError,    # Invalid API key
    SessionNotFoundError,   # Terminal session expired
    FileNotFoundError,      # File doesn't exist
    PermissionDeniedError,  # No access to file/command
)

try:
    with CMDOPClient.remote(api_key="cmd_xxx") as client:
        client.files.read("/etc/shadow")
except PermissionDeniedError:
    print("Access denied")
except AgentOfflineError:
    print("Agent not connected")
```

## How It Works

1. **Install CMDOP Agent** on target machine
2. **Agent connects** to cloud relay (outbound only, no open ports)
3. **SDK connects** to cloud relay with API key
4. **Commands routed** through relay to agent
5. **Results returned** through same secure channel

```
[Your Code] → [Cloud Relay] → [Agent] → [Target Machine]
    SDK          grpc.cmdop.com    Outbound only
```

## Security

- All traffic encrypted (gRPC over TLS)
- Agent only makes outbound connections
- API keys scoped per agent/team
- No SSH keys or credentials to manage
- Audit logging available

## Requirements

- Python 3.10+
- Running CMDOP agent on target machine

## Links

- [CMDOP Website](https://cmdop.com)

## License

MIT
