Metadata-Version: 2.4
Name: cmdop
Version: 0.1.25
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

Python SDK for CMDOP browser automation and server control.

## Architecture

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

## Install

```bash
pip install cmdop
```

## Connection

```python
from cmdop import CMDOPClient, AsyncCMDOPClient

# Local (direct IPC to running agent)
client = CMDOPClient.local()

# Remote (via cloud relay)
client = CMDOPClient.remote(api_key="cmd_xxx")

# Async
async with AsyncCMDOPClient.local() as client:
    await client.files.read("/etc/hostname")
```

## Browser

```python
from cmdop.services.browser.models import WaitUntil

with client.browser.create_session(headless=False) as s:
    s.navigate("https://shop.com", wait_until=WaitUntil.NETWORKIDLE)

    # Core methods
    s.click("button.buy", move_cursor=True)
    s.type("input[name=q]", "search term")
    s.wait_for(".results")
    s.execute_script("return document.title")
    s.screenshot()
    s.get_state()        # URL + title
    s.get_page_info()    # Full page info
    s.get_cookies()
    s.set_cookies([...])
```

**WaitUntil options:**
| Value | Description |
|-------|-------------|
| `LOAD` | Wait for load event (default) |
| `DOMCONTENTLOADED` | Wait for DOMContentLoaded |
| `NETWORKIDLE` | Wait until network is idle (best for SPA) |
| `COMMIT` | Return immediately (fastest) |

### Capabilities

**`s.scroll`** - Scrolling
```python
s.scroll.js("down", 500)           # JS scroll (works on complex sites)
s.scroll.native("down", 500)       # Browser API scroll
s.scroll.to_bottom()               # Scroll to page bottom
s.scroll.to_element(".item")       # Scroll element into view
s.scroll.info()                    # Get scroll position/dimensions

# Smart infinite scroll with extraction
items = s.scroll.infinite(
    extract_fn=lambda: extract_new_items(),
    limit=100,
    max_scrolls=50,
    scroll_amount=800,
)
```

**`s.input`** - Input
```python
s.input.click_js(".btn")           # JS click (reliable)
s.input.click_all("See more")      # Click all matching elements
s.input.key("Escape")              # Press key
s.input.key("Enter", ".input")     # Press key on element
s.input.hover(".tooltip")          # Native hover
s.input.hover_js(".tooltip")       # JS hover
s.input.mouse_move(500, 300)       # Move cursor to coordinates
```

**`s.timing`** - Delays
```python
s.timing.wait(500)                 # Wait ms
s.timing.seconds(2)                # Wait seconds
s.timing.random(0.5, 1.5)          # Random delay
s.timing.timeout(fn, 10, cleanup)  # Run with timeout
```

**`s.dom`** - DOM operations
```python
s.dom.html(".container")           # Get HTML
s.dom.text(".title")               # Get text
s.dom.soup(".items")               # → SoupWrapper (chainable BS4)
s.dom.parse(html_string)           # → BeautifulSoup
s.dom.extract(".items", "href")    # Get attr list
s.dom.select("#country", "US")     # Dropdown select
s.dom.close_modal()                # Close dialogs/popups
```

**`s.fetch`** - HTTP from browser (bypass CORS, inherit cookies)
```python
s.fetch.json("/api/items")               # Fetch JSON
s.fetch.all(["/api/a", "/api/b"])        # Parallel fetch
s.fetch.execute("return fetch(...)")     # Custom JS
```

**`s.network`** - Traffic capture
```python
s.network.enable(max_exchanges=1000)
s.navigate(url)

# Get exchanges
exchanges = s.network.get_all()
api = s.network.last("/api/data")
data = api.json_body()

# Filter
posts = s.network.filter(
    url_pattern="/api/posts",
    methods=["GET", "POST"],
    status_codes=[200],
    resource_types=["xhr", "fetch"],
)

# Convenience
s.network.api_calls("/api/")       # XHR/Fetch matching pattern
s.network.last_json("/api/data")   # JSON body directly
s.network.wait_for("/api/", 5000)  # Wait for request
s.network.export_har()             # Export to HAR
s.network.stats()                  # Capture statistics
s.network.clear()                  # Clear captured
s.network.disable()
```

**`s.visual`** - Browser overlay (requires CMDOP extension)
```python
s.visual.toast("Loading...")           # Show toast
s.visual.clear_toasts()                # Clear all toasts
s.visual.countdown(30, "Click!")       # Countdown timer
s.visual.highlight(".element")         # Highlight element
s.visual.hide_highlight()              # Hide highlight
s.visual.click(100, 200)               # Show click effect
s.visual.move(0, 0, 100, 200)          # Show cursor trail
s.visual.set_state("busy")             # idle/active/busy
```

## NetworkAnalyzer

Discover API endpoints by capturing traffic while user interacts.

```python
from cmdop import CMDOPClient
from cmdop.helpers import NetworkAnalyzer

client = CMDOPClient.local()
with client.browser.create_session(headless=False) as b:
    analyzer = NetworkAnalyzer(b)

    snapshot = analyzer.capture(
        "https://example.com/cars",
        wait_seconds=30,
        countdown_message="Click pagination!",
        min_size=100,       # Ignore tracking pixels
        max_size=500_000,   # Ignore heavy assets
        same_origin=True,   # Only same domain
    )

    # Get best data API
    if snapshot.api_requests:
        best = snapshot.best_api()
        print(best.url)
        print(best.item_count)
        print(best.data_key)        # "data", "items", etc.
        print(best.item_fields)     # Field names
        print(best.to_curl())       # curl command
        print(best.to_httpx())      # Python httpx code

    # All captured
    for req in snapshot.api_requests:
        print(f"{req.method} {req.url} → {req.item_count} items")
```

**NetworkSnapshot:**
- `api_requests` - Requests with data arrays
- `json_requests` - Other JSON responses
- `cookies` - Session cookies
- `total_requests`, `total_bytes`

**RequestSnapshot:**
- `url`, `method`, `headers`, `body`, `cookies`
- `status`, `content_type`, `size`
- `data_key`, `item_count`, `item_fields`, `sample_response`
- `to_curl()`, `to_httpx()`

## Agent

Run AI tasks with typed output:

```python
from pydantic import BaseModel

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

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

## Terminal

```python
session = client.terminal.create()
client.terminal.send_input(session.session_id, "ls -la\n")
output = client.terminal.get_history(session.session_id)
client.terminal.resize(session.session_id, 120, 40)
client.terminal.send_signal(session.session_id, "SIGINT")
client.terminal.close(session.session_id)
```

## Files

```python
client.files.list("/var/log")
client.files.read("/etc/nginx/nginx.conf")
client.files.write("/tmp/config.json", b'{"key": "value"}')
client.files.delete("/tmp/old.txt")
client.files.copy("/src", "/dst")
client.files.move("/old", "/new")
client.files.mkdir("/new/dir")
client.files.info("/path")
```

## SDKBaseModel

Auto-cleaning Pydantic model:

```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

```python
from cmdop import get_logger, json_to_toon

# Logging (rich console + file)
log = get_logger(__name__)
log.info("Starting")

# TOON format (30-50% token savings)
toon = json_to_toon({"name": "Alice", "age": 25})
# → "name: Alice\nage: 25"
```

## Requirements

- Python 3.10+
- CMDOP agent running locally or API key for remote
