Metadata-Version: 2.4
Name: cmdop
Version: 0.1.20
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,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: beautifulsoup4>=4.12.0
Requires-Dist: grpcio>=1.60.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: lxml>=5.0.0
Requires-Dist: protobuf>=4.25.0
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: pydantic>=2.5.0
Requires-Dist: rich>=13.0.0
Requires-Dist: toon-python>=0.1.2
Provides-Extra: dev
Requires-Dist: beautifulsoup4>=4.12.0; 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

**Any machine. One API.**

```python
from cmdop import CMDOPClient

with CMDOPClient.remote(api_key="cmd_xxx") as server:
    server.terminal.execute("docker restart app")
    server.files.write("/etc/nginx/nginx.conf", new_config)
    logs = server.files.read("/var/log/app.log")
```

No SSH. No VPN. No open ports.

---

## How

```
Your Code ──── Cloud Relay ──── Agent (on server)
                    │
        Outbound only, works through any NAT/firewall
```

Agent connects out. Your code connects to relay. Done.

---

## Install

```bash
pip install cmdop
```

```python
from cmdop import CMDOPClient, AsyncCMDOPClient

# Remote (via cloud relay)
with CMDOPClient.remote(api_key="cmd_xxx") as client:
    client.files.list("/home")

# Local (direct IPC)
with CMDOPClient.local() as client:
    client.terminal.execute("ls -la")

# Async
async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
    await client.files.read("/etc/hostname")
```

---

## Terminal

```python
session = server.terminal.create()
server.terminal.send_input(session.session_id, "kubectl get pods\n")
output = server.terminal.get_history(session.session_id)
```

| Method | Description |
|--------|-------------|
| `create(shell)` | Start session |
| `send_input(id, data)` | Send commands |
| `get_history(id)` | Get output |
| `resize(id, cols, rows)` | Resize |
| `send_signal(id, signal)` | SIGINT/SIGTERM |
| `close(id)` | End session |

## Files

```python
server.files.list("/var/log")
server.files.read("/etc/nginx/nginx.conf")
server.files.write("/tmp/config.json", b'{"key": "value"}')
```

| Method | Description |
|--------|-------------|
| `list(path)` | List dir |
| `read(path)` | Read file |
| `write(path, content)` | Write file |
| `delete(path)` | Delete |
| `copy/move(src, dst)` | Copy/Move |
| `mkdir(path)` | Create dir |
| `info(path)` | Metadata |

## Agent

```python
from pydantic import BaseModel

class Health(BaseModel):
    status: str
    cpu: float
    issues: list[str]

result = server.agent.run("Check server health", output_schema=Health)
health: Health = result.output  # Typed!
```

---

## Browser

```python
with client.browser.create_session() as b:
    b.navigate("https://shop.com/products")
    b.close_modal()  # Close popups

    # BeautifulSoup parsing
    soup = b.soup()  # SoupWrapper with chainable API
    for item in soup.select(".product"):
        title = item.select_one("h2").text()
        price = item.attr("data-price")

    # Scrolling with random delays
    for _ in range(10):
        soup = b.soup(".listings")
        # ... parse ...
        b.scroll("down", 700)
        b.wait_random(0.8, 1.5)  # Random delay

    # Click with cursor movement (human-like)
    b.click("button.buy", move_cursor=True)  # Moves cursor before clicking

    # Click all "See more" buttons
    b.click_all_by_text("See more")

    # Native mouse operations
    b.mouse_move(500, 300)  # Smooth cursor movement
    b.hover(".tooltip-trigger")  # Hover to reveal tooltip

    # JS fetch (bypass CORS, inherit cookies)
    data = b.fetch_json("https://api.site.com/v1/items")
```

| Method | Description |
|--------|-------------|
| `navigate(url)` | Go to URL |
| `click(selector, move_cursor)` | Click element (optionally move cursor first) |
| `click_all_by_text(text, role)` | Click all matching elements |
| `type(selector, text)` | Type text |
| `wait_for(selector, ms)` | Wait for element |
| `wait_seconds(n)` | Sleep |
| `wait_random(min, max)` | Random sleep |
| `extract(selector, attr)` | Get text/attr |
| `get_html(selector)` | Get HTML |
| `soup(selector)` | → SoupWrapper |
| `parse_html(html)` | → BeautifulSoup |
| `fetch_json(url)` | JS fetch → dict |
| `fetch_all(urls)` | Parallel fetch |
| `execute_js(code)` | Run async JS |
| `screenshot()` | PNG bytes |
| `scroll(dir, amount, ...)` | Native scroll page |
| `scroll_to(selector)` | Scroll element into view |
| `get_scroll_info()` | Position + page size |
| `get_page_info()` | Comprehensive page info |
| `mouse_move(x, y, steps)` | Move cursor to coordinates |
| `hover(selector)` | Hover over element |
| `select(selector, value)` | Dropdown select |
| `close_modal()` | Close dialogs |
| `press_key(key, selector)` | Press keyboard key |
| `get/set_cookies()` | Cookie management |

**scroll() parameters:**
- `direction`: "up", "down", "left", "right"
- `amount`: pixels to scroll
- `smooth`: animate scroll (default True)
- `selector`: scroll element into view (alternative to direction/amount)

## SDKBaseModel

Auto-cleaning Pydantic model for scraped data:

```python
from cmdop import SDKBaseModel

class Product(SDKBaseModel):
    __base_url__ = "https://shop.com"
    name: str = ""    # "  iPhone 15  \n" → "iPhone 15"
    price: int = 0    # "$1,299.00" → 1299
    rating: float = 0 # "4.5 stars" → 4.5
    url: str = ""     # "/p/123" → "https://shop.com/p/123"

products = Product.from_list(raw["items"])  # Auto dedupe + filter
```

---

## Utilities

**Logging:**
```python
from cmdop import get_logger
log = get_logger(__name__)
log.info("Starting")  # Rich console + auto file logging
```

**TOON Format (30-50% token savings):**
```python
from cmdop import json_to_toon, JsonCleaner
toon = json_to_toon({"name": "Alice", "age": 25})
# → "name: Alice\nage: 25"
```

---

## Requirements

- Python 3.10+
- CMDOP agent on target

## Links

[cmdop.com](https://cmdop.com)

## License

MIT
