cmdop 0.1.22__py3-none-any.whl → 0.1.24__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmdop
3
+ Version: 0.1.24
4
+ Summary: Python SDK for CMDOP agent interaction
5
+ Project-URL: Homepage, https://cmdop.com
6
+ Project-URL: Documentation, https://cmdop.com
7
+ Project-URL: Repository, https://github.com/markolofsen/cmdop-client
8
+ Author: CMDOP Team
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,automation,cmdop,terminal
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: beautifulsoup4>=4.12.0
23
+ Requires-Dist: grpcio>=1.60.0
24
+ Requires-Dist: httpx>=0.27.0
25
+ Requires-Dist: lxml>=5.0.0
26
+ Requires-Dist: protobuf>=4.25.0
27
+ Requires-Dist: pydantic-settings>=2.0.0
28
+ Requires-Dist: pydantic>=2.5.0
29
+ Requires-Dist: rich>=13.0.0
30
+ Requires-Dist: toon-python>=0.1.2
31
+ Provides-Extra: dev
32
+ Requires-Dist: beautifulsoup4>=4.12.0; extra == 'dev'
33
+ Requires-Dist: grpcio-tools>=1.60.0; extra == 'dev'
34
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
35
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
36
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
37
+ Requires-Dist: pytest-grpc-aio>=0.3.0; extra == 'dev'
38
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
39
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # cmdop
43
+
44
+ Python SDK for CMDOP browser automation and server control.
45
+
46
+ ## Architecture
47
+
48
+ ```
49
+ Your Code ──── Cloud Relay ──── Agent (on server)
50
+
51
+ Outbound only, works through any NAT/firewall
52
+ ```
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install cmdop
58
+ ```
59
+
60
+ ## Connection
61
+
62
+ ```python
63
+ from cmdop import CMDOPClient, AsyncCMDOPClient
64
+
65
+ # Local (direct IPC to running agent)
66
+ client = CMDOPClient.local()
67
+
68
+ # Remote (via cloud relay)
69
+ client = CMDOPClient.remote(api_key="cmd_xxx")
70
+
71
+ # Async
72
+ async with AsyncCMDOPClient.local() as client:
73
+ await client.files.read("/etc/hostname")
74
+ ```
75
+
76
+ ## Browser
77
+
78
+ ```python
79
+ from cmdop.services.browser.models import WaitUntil
80
+
81
+ with client.browser.create_session(headless=False) as s:
82
+ s.navigate("https://shop.com", wait_until=WaitUntil.NETWORKIDLE)
83
+
84
+ # Core methods
85
+ s.click("button.buy", move_cursor=True)
86
+ s.type("input[name=q]", "search term")
87
+ s.wait_for(".results")
88
+ s.execute_script("return document.title")
89
+ s.screenshot()
90
+ s.get_state() # URL + title
91
+ s.get_page_info() # Full page info
92
+ s.get_cookies()
93
+ s.set_cookies([...])
94
+ ```
95
+
96
+ **WaitUntil options:**
97
+ | Value | Description |
98
+ |-------|-------------|
99
+ | `LOAD` | Wait for load event (default) |
100
+ | `DOMCONTENTLOADED` | Wait for DOMContentLoaded |
101
+ | `NETWORKIDLE` | Wait until network is idle (best for SPA) |
102
+ | `COMMIT` | Return immediately (fastest) |
103
+
104
+ ### Capabilities
105
+
106
+ **`s.scroll`** - Scrolling
107
+ ```python
108
+ s.scroll.js("down", 500) # JS scroll (works on complex sites)
109
+ s.scroll.native("down", 500) # Browser API scroll
110
+ s.scroll.to_bottom() # Scroll to page bottom
111
+ s.scroll.to_element(".item") # Scroll element into view
112
+ s.scroll.info() # Get scroll position/dimensions
113
+
114
+ # Smart infinite scroll with extraction
115
+ items = s.scroll.infinite(
116
+ extract_fn=lambda: extract_new_items(),
117
+ limit=100,
118
+ max_scrolls=50,
119
+ scroll_amount=800,
120
+ )
121
+ ```
122
+
123
+ **`s.input`** - Input
124
+ ```python
125
+ s.input.click_js(".btn") # JS click (reliable)
126
+ s.input.click_all("See more") # Click all matching elements
127
+ s.input.key("Escape") # Press key
128
+ s.input.key("Enter", ".input") # Press key on element
129
+ s.input.hover(".tooltip") # Native hover
130
+ s.input.hover_js(".tooltip") # JS hover
131
+ s.input.mouse_move(500, 300) # Move cursor to coordinates
132
+ ```
133
+
134
+ **`s.timing`** - Delays
135
+ ```python
136
+ s.timing.wait(500) # Wait ms
137
+ s.timing.seconds(2) # Wait seconds
138
+ s.timing.random(0.5, 1.5) # Random delay
139
+ s.timing.timeout(fn, 10, cleanup) # Run with timeout
140
+ ```
141
+
142
+ **`s.dom`** - DOM operations
143
+ ```python
144
+ s.dom.html(".container") # Get HTML
145
+ s.dom.text(".title") # Get text
146
+ s.dom.soup(".items") # → SoupWrapper (chainable BS4)
147
+ s.dom.parse(html_string) # → BeautifulSoup
148
+ s.dom.extract(".items", "href") # Get attr list
149
+ s.dom.select("#country", "US") # Dropdown select
150
+ s.dom.close_modal() # Close dialogs/popups
151
+ ```
152
+
153
+ **`s.fetch`** - HTTP from browser (bypass CORS, inherit cookies)
154
+ ```python
155
+ s.fetch.json("/api/items") # Fetch JSON
156
+ s.fetch.all(["/api/a", "/api/b"]) # Parallel fetch
157
+ s.fetch.execute("return fetch(...)") # Custom JS
158
+ ```
159
+
160
+ **`s.network`** - Traffic capture
161
+ ```python
162
+ s.network.enable(max_exchanges=1000)
163
+ s.navigate(url)
164
+
165
+ # Get exchanges
166
+ exchanges = s.network.get_all()
167
+ api = s.network.last("/api/data")
168
+ data = api.json_body()
169
+
170
+ # Filter
171
+ posts = s.network.filter(
172
+ url_pattern="/api/posts",
173
+ methods=["GET", "POST"],
174
+ status_codes=[200],
175
+ resource_types=["xhr", "fetch"],
176
+ )
177
+
178
+ # Convenience
179
+ s.network.api_calls("/api/") # XHR/Fetch matching pattern
180
+ s.network.last_json("/api/data") # JSON body directly
181
+ s.network.wait_for("/api/", 5000) # Wait for request
182
+ s.network.export_har() # Export to HAR
183
+ s.network.stats() # Capture statistics
184
+ s.network.clear() # Clear captured
185
+ s.network.disable()
186
+ ```
187
+
188
+ **`s.visual`** - Browser overlay (requires CMDOP extension)
189
+ ```python
190
+ s.visual.toast("Loading...") # Show toast
191
+ s.visual.clear_toasts() # Clear all toasts
192
+ s.visual.countdown(30, "Click!") # Countdown timer
193
+ s.visual.highlight(".element") # Highlight element
194
+ s.visual.hide_highlight() # Hide highlight
195
+ s.visual.click(100, 200) # Show click effect
196
+ s.visual.move(0, 0, 100, 200) # Show cursor trail
197
+ s.visual.set_state("busy") # idle/active/busy
198
+ ```
199
+
200
+ ## NetworkAnalyzer
201
+
202
+ Discover API endpoints by capturing traffic while user interacts.
203
+
204
+ ```python
205
+ from cmdop import CMDOPClient
206
+ from cmdop.helpers import NetworkAnalyzer
207
+
208
+ client = CMDOPClient.local()
209
+ with client.browser.create_session(headless=False) as b:
210
+ analyzer = NetworkAnalyzer(b)
211
+
212
+ snapshot = analyzer.capture(
213
+ "https://example.com/cars",
214
+ wait_seconds=30,
215
+ countdown_message="Click pagination!",
216
+ min_size=100, # Ignore tracking pixels
217
+ max_size=500_000, # Ignore heavy assets
218
+ same_origin=True, # Only same domain
219
+ )
220
+
221
+ # Get best data API
222
+ if snapshot.api_requests:
223
+ best = snapshot.best_api()
224
+ print(best.url)
225
+ print(best.item_count)
226
+ print(best.data_key) # "data", "items", etc.
227
+ print(best.item_fields) # Field names
228
+ print(best.to_curl()) # curl command
229
+ print(best.to_httpx()) # Python httpx code
230
+
231
+ # All captured
232
+ for req in snapshot.api_requests:
233
+ print(f"{req.method} {req.url} → {req.item_count} items")
234
+ ```
235
+
236
+ **NetworkSnapshot:**
237
+ - `api_requests` - Requests with data arrays
238
+ - `json_requests` - Other JSON responses
239
+ - `cookies` - Session cookies
240
+ - `total_requests`, `total_bytes`
241
+
242
+ **RequestSnapshot:**
243
+ - `url`, `method`, `headers`, `body`, `cookies`
244
+ - `status`, `content_type`, `size`
245
+ - `data_key`, `item_count`, `item_fields`, `sample_response`
246
+ - `to_curl()`, `to_httpx()`
247
+
248
+ ## Agent
249
+
250
+ Run AI tasks with typed output:
251
+
252
+ ```python
253
+ from pydantic import BaseModel
254
+
255
+ class Health(BaseModel):
256
+ status: str
257
+ cpu: float
258
+ issues: list[str]
259
+
260
+ result = client.agent.run("Check server health", output_schema=Health)
261
+ health: Health = result.output # Typed!
262
+ ```
263
+
264
+ ## Terminal
265
+
266
+ ```python
267
+ session = client.terminal.create()
268
+ client.terminal.send_input(session.session_id, "ls -la\n")
269
+ output = client.terminal.get_history(session.session_id)
270
+ client.terminal.resize(session.session_id, 120, 40)
271
+ client.terminal.send_signal(session.session_id, "SIGINT")
272
+ client.terminal.close(session.session_id)
273
+ ```
274
+
275
+ ## Files
276
+
277
+ ```python
278
+ client.files.list("/var/log")
279
+ client.files.read("/etc/nginx/nginx.conf")
280
+ client.files.write("/tmp/config.json", b'{"key": "value"}')
281
+ client.files.delete("/tmp/old.txt")
282
+ client.files.copy("/src", "/dst")
283
+ client.files.move("/old", "/new")
284
+ client.files.mkdir("/new/dir")
285
+ client.files.info("/path")
286
+ ```
287
+
288
+ ## SDKBaseModel
289
+
290
+ Auto-cleaning Pydantic model:
291
+
292
+ ```python
293
+ from cmdop import SDKBaseModel
294
+
295
+ class Product(SDKBaseModel):
296
+ __base_url__ = "https://shop.com"
297
+ name: str = "" # " iPhone 15 \n" → "iPhone 15"
298
+ price: int = 0 # "$1,299.00" → 1299
299
+ rating: float = 0 # "4.5 stars" → 4.5
300
+ url: str = "" # "/p/123" → "https://shop.com/p/123"
301
+
302
+ products = Product.from_list(raw["items"]) # Auto dedupe + filter
303
+ ```
304
+
305
+ ## Utilities
306
+
307
+ ```python
308
+ from cmdop import get_logger, json_to_toon
309
+
310
+ # Logging (rich console + file)
311
+ log = get_logger(__name__)
312
+ log.info("Starting")
313
+
314
+ # TOON format (30-50% token savings)
315
+ toon = json_to_toon({"name": "Alice", "age": 25})
316
+ # → "name: Alice\nage: 25"
317
+ ```
318
+
319
+ ## Requirements
320
+
321
+ - Python 3.10+
322
+ - CMDOP agent running locally or API key for remote
@@ -1,4 +1,4 @@
1
- cmdop/__init__.py,sha256=te8zdIJesHnMNTru-7YrTOnoUObhAm972gTFbhoHW5g,5200
1
+ cmdop/__init__.py,sha256=IfTweCqyW_xC3SeQM5p0uD5UlYaIy1rg-CHUzUBUFJI,5200
2
2
  cmdop/client.py,sha256=nTotStZPBfYN3TrHH-OlEJMSVAXskYMQRkocsFmyaBY,14601
3
3
  cmdop/config.py,sha256=vpw1aGCyS4NKlZyzVur81Lt06QmN3FnscZji0bypUi0,4398
4
4
  cmdop/discovery.py,sha256=HNxSOa5tSuG7ppfFs21XdviW5ucjpRswVPguhX5j8Dg,7479
@@ -24,11 +24,11 @@ cmdop/_generated/file_rpc_pb2_grpc.py,sha256=HddeBZYoBOUkx4yPq2qdpwv2QmQouSbwCkf
24
24
  cmdop/_generated/message_pool.py,sha256=ncIr4Z2KeinOVGyzmR-Drc76DZnDTwWl4-bQYcIl8uM,97
25
25
  cmdop/_generated/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  cmdop/_generated/rpc_messages_pb2.py,sha256=X3iog1k2vUDna-hBpEXy0E9-D05mkDWlmBSy0L_yhmQ,2734
27
- cmdop/_generated/rpc_messages_pb2.pyi,sha256=URIFseWilxDjTnLr-INgNLabALY85QgHwPW8yxIPQFI,8400
27
+ cmdop/_generated/rpc_messages_pb2.pyi,sha256=ddIPYj1axgsGwSc0KO1lTnCknJexEqeRz3VEwsLzG0E,10507
28
28
  cmdop/_generated/rpc_messages_pb2_grpc.py,sha256=WHRqOkW1r_9wP5Sd1Bc20jDVErtDN_xHvhsv8r8xyss,892
29
- cmdop/_generated/service_pb2.py,sha256=qMyy-hlP7pUotqDdsb0gt9Gg6oYYrWa9feQdB4T0s8Y,12310
29
+ cmdop/_generated/service_pb2.py,sha256=WRgcxz-wh7RWXTXcBTgw85V5uAgsc68UGaD0gAGwAxE,13315
30
30
  cmdop/_generated/service_pb2.pyi,sha256=-q-RU8TbkwpnKuBQGIhEsjGXj8ltD87sdzgC3B6zKYY,1649
31
- cmdop/_generated/service_pb2_grpc.py,sha256=cysF2oZthsUqaA8kqgNEhHbWFHNhAVDqVO1tveNSzJ4,108911
31
+ cmdop/_generated/service_pb2_grpc.py,sha256=ulLybECN-LLMMYlUG3XJw7Fmfz8PD0jRWwVHmhlYPjs,124703
32
32
  cmdop/_generated/tunnel_pb2.py,sha256=oF5qOSw_9faeXtVFELfEw1dq3TtPOUWl1v6DzUqguGo,3928
33
33
  cmdop/_generated/tunnel_pb2.pyi,sha256=ipcoNwnTLY16IfA95SbTP7REypE65ZVpf6akF7h4FCw,4925
34
34
  cmdop/_generated/tunnel_pb2_grpc.py,sha256=yutTebujge5cHC3h84FKWSdvGBuPVYOkRDy0UGtHZ6c,886
@@ -80,8 +80,8 @@ cmdop/_generated/rpc_messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
80
80
  cmdop/_generated/rpc_messages/agent_pb2.py,sha256=1KCs4kwuuTwZm9Y8ql7t6Jjsutuepbq85E6DGVDWa84,3393
81
81
  cmdop/_generated/rpc_messages/agent_pb2.pyi,sha256=F7gy0PQlJ4cRQCli7U4v5A87-Lsw2_x0nV4hFXG1qj0,3866
82
82
  cmdop/_generated/rpc_messages/agent_pb2_grpc.py,sha256=ABbaG1eMcaWPZKdPoom2F-IjjVqjYNMDOjMp8X6QLHE,898
83
- cmdop/_generated/rpc_messages/browser_pb2.py,sha256=VAmjY4WUDXkpYOkS-q6w5qplbpIQGLmqDiS6b3O6UYE,16875
84
- cmdop/_generated/rpc_messages/browser_pb2.pyi,sha256=wA51sejGuiCfj9nEpAeymZwkvv1saQyIbAH5Ntlz0fc,25225
83
+ cmdop/_generated/rpc_messages/browser_pb2.py,sha256=2yuoDnRYtAq34NOg4xFPVo0jz0Tr1ulatT8Qe6Ibty8,24851
84
+ cmdop/_generated/rpc_messages/browser_pb2.pyi,sha256=HXQxtegycSDNrHXaxCnMKqukb2Na4dJTCUAg413c31k,37988
85
85
  cmdop/_generated/rpc_messages/browser_pb2_grpc.py,sha256=P8F8QYLT_kedGq7AXj37Us2hFQOaevdfYMHl0tTdY48,900
86
86
  cmdop/_generated/rpc_messages/device_pb2.py,sha256=nKj7axhR-FVSQoQUd4ENXpc5PU_UCjSlExajLIoeZY8,2435
87
87
  cmdop/_generated/rpc_messages/device_pb2.pyi,sha256=u5oLfTHuOSpYwMiVe2tw0vVQLLFvYgK3lxw-Vky1ZkA,1974
@@ -150,9 +150,10 @@ cmdop/api/generated/workspaces/workspaces__api__workspaces/__init__.py,sha256=Wo
150
150
  cmdop/api/generated/workspaces/workspaces__api__workspaces/client.py,sha256=A1c5s8FJQ7vJRjReO7QQ8D6tPN-6PudxQXLYP6EGE2I,9861
151
151
  cmdop/api/generated/workspaces/workspaces__api__workspaces/models.py,sha256=3UFJFLWWQs_jzSmFstJ5T1lsaVp9TYSb1u6YFFq5RSk,9501
152
152
  cmdop/api/generated/workspaces/workspaces__api__workspaces/sync_client.py,sha256=KfWxpSjZyfZLjonHRFwk0a1eUmOZHuky4ZySo11wTWg,9639
153
- cmdop/helpers/__init__.py,sha256=a1GXe7vxVYsfOTR-n_m9NkxoqZYn1fiFuEVe0bMTV2g,176
153
+ cmdop/helpers/__init__.py,sha256=fxzHT-O9D4HbvSU4qcQ9Ce3Q0Y5dZV-nQHVuoMpahCw,355
154
154
  cmdop/helpers/cleaner.py,sha256=3XWB3KpRSMgJ1Go0G7rE9bgDGy1bshk9PtWWB6pL7AU,1792
155
155
  cmdop/helpers/formatting.py,sha256=lLYUEyzZLUSXq2xHB2w3i72QoP_baHjtFpP-7mb-V-s,341
156
+ cmdop/helpers/network_analyzer.py,sha256=6ee-1J2JLKl9gHvwMfPDE6k54QL8tiLtZ2G3MwpWfsE,12018
156
157
  cmdop/models/__init__.py,sha256=W6P1oo6JkUAeVEV59HzFT646hXM0pk_obXHfHbX4tAc,1594
157
158
  cmdop/models/agent.py,sha256=Z1QDfr1-DTFVl5oPvbH2ZUBLXPHUlitiCRfG7y_OzYg,5495
158
159
  cmdop/models/base.py,sha256=1SR1ka5p-rHHkk4k9pPwbraxX_CsTG830CosGNPn1JA,7425
@@ -167,17 +168,19 @@ cmdop/services/extract.py,sha256=zwzikEKH4T4OnrprmJg3wqBWQJr-DYsSZgJGK_2yIHU,111
167
168
  cmdop/services/files.py,sha256=RGhfo7tW6diuUWC_EQQ-Y9zO1btm6mBTji0SWWa0fdo,12548
168
169
  cmdop/services/terminal.py,sha256=9SSWBexe2rWgMd-hGBEs9mcax3l7x_U84VHZpMC4xK8,17512
169
170
  cmdop/services/browser/__init__.py,sha256=31Ofu9RCYTAedPKLvnor8J7oGDgTjbqJ58OkxxHYwdk,1270
170
- cmdop/services/browser/models.py,sha256=qJ3da8hPvQn7Vtiqg3xg49G-GioidQGPPuLk2c_IBC4,2656
171
+ cmdop/services/browser/models.py,sha256=9MpNFgSgZDIznmTmsCUByEN31t_iQ6kAza1BsPSsuJs,5320
171
172
  cmdop/services/browser/parsing.py,sha256=0hQAy-0ZwJqtmhEqHO3EEdVB3iYmyhXRdouN_dCbig8,3820
172
- cmdop/services/browser/session.py,sha256=ZFZuzxEUEpwDenigBZdxr5aYePlo69LWre9YIK6LRSM,5363
173
- cmdop/services/browser/capabilities/__init__.py,sha256=SvaRxAiB8pq671bWtTThVe5BgVg0G-4wAPcgOvu9ab4,334
173
+ cmdop/services/browser/session.py,sha256=4_g-vPiiFBTiz5pbaOSxEKsjgkjEPU949jby2B15eWQ,6771
174
+ cmdop/services/browser/capabilities/__init__.py,sha256=GfLhrQ_z-g22OwlZQt8KpQltA61SLSgV4cSsY284DUI,459
174
175
  cmdop/services/browser/capabilities/_base.py,sha256=mW0jKa2CyvK-8cjenv5JYvuCKiO3rpt5F7WtWFXBitA,749
175
176
  cmdop/services/browser/capabilities/_helpers.py,sha256=jXqYbeDocAHec2GwF2_BNnJ78vTyUnHteQoS-RSG00k,488
176
177
  cmdop/services/browser/capabilities/dom.py,sha256=DuXfildga23wGBNJWtNzx-t2Cq553HC48o9KAWAlyC0,2612
177
- cmdop/services/browser/capabilities/fetch.py,sha256=GAjOjk9XkCrMgUzRUrdMmVgOmIClwBWPUnmfA_OqITY,1392
178
+ cmdop/services/browser/capabilities/fetch.py,sha256=IpUrcUAOEanrN4TrB-JVtn3TtJ8GdkyN5qbTbpBWvLc,1310
178
179
  cmdop/services/browser/capabilities/input.py,sha256=uYmWGqturMDent44Us80oT_nk4kFNGyPJ0P5I35ulew,1749
180
+ cmdop/services/browser/capabilities/network.py,sha256=tZV4Oh_J5zUjEe9GBLQBDXEVh9EVTecPbyjE7lIUrd0,7775
179
181
  cmdop/services/browser/capabilities/scroll.py,sha256=sh0VuOPOv81BZg80-n8TABOj5RpshJT12qJwm4F_OY0,4808
180
182
  cmdop/services/browser/capabilities/timing.py,sha256=NH34G_4Kfukh6JCdhLRGoouA-uNTbx9ly7ybP9Kh558,1868
183
+ cmdop/services/browser/capabilities/visual.py,sha256=ETmeN6r5JG58VYC868ntVUvm2MM3xxqkVkYaA-nUwGk,3659
181
184
  cmdop/services/browser/js/__init__.py,sha256=gTiZguikKfztDtggZTux2FqhT8YTjyHCzQR4TEnT7z4,1177
182
185
  cmdop/services/browser/js/core.py,sha256=QXCCX_al5tMgz7aCwMqhIs1aRe_IdG8teOJniaumA5Q,995
183
186
  cmdop/services/browser/js/fetch.py,sha256=WPy_H4LLkneSx06wpfnx4Sx_0Okf2ENXi6bveCd9ZCg,2188
@@ -186,7 +189,7 @@ cmdop/services/browser/js/scroll.py,sha256=yiOMAaR8ac8jCiWgNCVIt1pUGxdvktMUtzv-A
186
189
  cmdop/services/browser/service/__init__.py,sha256=AZH_r2FsxLfJGCVBaaAPw3dTGaUlgIFdlYd_RR8KxSg,100
187
190
  cmdop/services/browser/service/_helpers.py,sha256=w8foDUGZcD4HyF5eyLZUFxbx_fctAFsYRovvsksi3l4,1584
188
191
  cmdop/services/browser/service/aio.py,sha256=0E2D4igQb3YzAakdXsRMt8PEd0rFxI2gXfimwq_6nzk,767
189
- cmdop/services/browser/service/sync.py,sha256=meSoRacS5WVBTABiXopWvTIItzvR7nUiyaxnDuUMoUQ,15466
192
+ cmdop/services/browser/service/sync.py,sha256=40CIqdUAR-ZVFfQJVgUWVgGq3j7mgcpGs1SRe8zXjmI,23096
190
193
  cmdop/streaming/__init__.py,sha256=kG9UlJRqv8ndcwKMzWUddPlZT61pFO_Uf_c08A_8TxA,877
191
194
  cmdop/streaming/base.py,sha256=r7Q2QlRxgULzs9vlSGcOC_fwAQ_cF3Z3M7WsPQtxG5I,2990
192
195
  cmdop/streaming/handlers.py,sha256=FDEhADmCEFRbifvr9dU1X3C-K_96noz89Bl3tuDa_rQ,2616
@@ -197,7 +200,7 @@ cmdop/transport/base.py,sha256=2pkV8i9epgp_21dyReCfX47abRUrnALm0W5BXb-Fuz0,5571
197
200
  cmdop/transport/discovery.py,sha256=rcGAuVrR1l6jwcP0dqZxVhX1NsFK7sRHygFMCLmmUbA,10673
198
201
  cmdop/transport/local.py,sha256=ob6tWVxSdKwblHSMK8CkgjyuSdQoAeWgy5OAUd5ZNuE,7411
199
202
  cmdop/transport/remote.py,sha256=FNVqus9wOv7LlxKarXjLmSyvJiHwhvPbNDOPv1IQkmE,4329
200
- cmdop-0.1.22.dist-info/METADATA,sha256=OTt5H55-dBCDKBLpTJSiNtbsJAz5Wxy9jKTrIb7HTxE,7574
201
- cmdop-0.1.22.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
202
- cmdop-0.1.22.dist-info/licenses/LICENSE,sha256=6hyzbI1QVXW6B-XT7PaQ6UG9lns11Y_nnap8uUKGUqo,1062
203
- cmdop-0.1.22.dist-info/RECORD,,
203
+ cmdop-0.1.24.dist-info/METADATA,sha256=4MQKOd6aJUKQ3rrdKC4el7HnFaxczKIc2maFQKND8LM,9284
204
+ cmdop-0.1.24.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
205
+ cmdop-0.1.24.dist-info/licenses/LICENSE,sha256=6hyzbI1QVXW6B-XT7PaQ6UG9lns11Y_nnap8uUKGUqo,1062
206
+ cmdop-0.1.24.dist-info/RECORD,,
@@ -1,291 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: cmdop
3
- Version: 0.1.22
4
- Summary: Python SDK for CMDOP agent interaction
5
- Project-URL: Homepage, https://cmdop.com
6
- Project-URL: Documentation, https://cmdop.com
7
- Project-URL: Repository, https://github.com/markolofsen/cmdop-client
8
- Author: CMDOP Team
9
- License: MIT
10
- License-File: LICENSE
11
- Keywords: agent,automation,cmdop,terminal
12
- Classifier: Development Status :: 3 - Alpha
13
- Classifier: Intended Audience :: Developers
14
- Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
- Classifier: Typing :: Typed
21
- Requires-Python: >=3.10
22
- Requires-Dist: beautifulsoup4>=4.12.0
23
- Requires-Dist: grpcio>=1.60.0
24
- Requires-Dist: httpx>=0.27.0
25
- Requires-Dist: lxml>=5.0.0
26
- Requires-Dist: protobuf>=4.25.0
27
- Requires-Dist: pydantic-settings>=2.0.0
28
- Requires-Dist: pydantic>=2.5.0
29
- Requires-Dist: rich>=13.0.0
30
- Requires-Dist: toon-python>=0.1.2
31
- Provides-Extra: dev
32
- Requires-Dist: beautifulsoup4>=4.12.0; extra == 'dev'
33
- Requires-Dist: grpcio-tools>=1.60.0; extra == 'dev'
34
- Requires-Dist: mypy>=1.8.0; extra == 'dev'
35
- Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
36
- Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
37
- Requires-Dist: pytest-grpc-aio>=0.3.0; extra == 'dev'
38
- Requires-Dist: pytest>=8.0.0; extra == 'dev'
39
- Requires-Dist: ruff>=0.1.0; extra == 'dev'
40
- Description-Content-Type: text/markdown
41
-
42
- # cmdop
43
-
44
- **Any machine. One API.**
45
-
46
- ```python
47
- from cmdop import CMDOPClient
48
-
49
- with CMDOPClient.remote(api_key="cmd_xxx") as server:
50
- server.terminal.execute("docker restart app")
51
- server.files.write("/etc/nginx/nginx.conf", new_config)
52
- logs = server.files.read("/var/log/app.log")
53
- ```
54
-
55
- No SSH. No VPN. No open ports.
56
-
57
- ---
58
-
59
- ## How
60
-
61
- ```
62
- Your Code ──── Cloud Relay ──── Agent (on server)
63
-
64
- Outbound only, works through any NAT/firewall
65
- ```
66
-
67
- Agent connects out. Your code connects to relay. Done.
68
-
69
- ---
70
-
71
- ## Install
72
-
73
- ```bash
74
- pip install cmdop
75
- ```
76
-
77
- ```python
78
- from cmdop import CMDOPClient, AsyncCMDOPClient
79
-
80
- # Remote (via cloud relay)
81
- with CMDOPClient.remote(api_key="cmd_xxx") as client:
82
- client.files.list("/home")
83
-
84
- # Local (direct IPC)
85
- with CMDOPClient.local() as client:
86
- client.terminal.execute("ls -la")
87
-
88
- # Async
89
- async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
90
- await client.files.read("/etc/hostname")
91
- ```
92
-
93
- ---
94
-
95
- ## Terminal
96
-
97
- ```python
98
- session = server.terminal.create()
99
- server.terminal.send_input(session.session_id, "kubectl get pods\n")
100
- output = server.terminal.get_history(session.session_id)
101
- ```
102
-
103
- | Method | Description |
104
- |--------|-------------|
105
- | `create(shell)` | Start session |
106
- | `send_input(id, data)` | Send commands |
107
- | `get_history(id)` | Get output |
108
- | `resize(id, cols, rows)` | Resize |
109
- | `send_signal(id, signal)` | SIGINT/SIGTERM |
110
- | `close(id)` | End session |
111
-
112
- ## Files
113
-
114
- ```python
115
- server.files.list("/var/log")
116
- server.files.read("/etc/nginx/nginx.conf")
117
- server.files.write("/tmp/config.json", b'{"key": "value"}')
118
- ```
119
-
120
- | Method | Description |
121
- |--------|-------------|
122
- | `list(path)` | List dir |
123
- | `read(path)` | Read file |
124
- | `write(path, content)` | Write file |
125
- | `delete(path)` | Delete |
126
- | `copy/move(src, dst)` | Copy/Move |
127
- | `mkdir(path)` | Create dir |
128
- | `info(path)` | Metadata |
129
-
130
- ## Agent
131
-
132
- ```python
133
- from pydantic import BaseModel
134
-
135
- class Health(BaseModel):
136
- status: str
137
- cpu: float
138
- issues: list[str]
139
-
140
- result = server.agent.run("Check server health", output_schema=Health)
141
- health: Health = result.output # Typed!
142
- ```
143
-
144
- ---
145
-
146
- ## Browser
147
-
148
- Capability-based API for browser automation.
149
-
150
- ```python
151
- with client.browser.create_session() as s:
152
- s.navigate("https://shop.com/products")
153
- s.dom.close_modal() # Close popups
154
-
155
- # BeautifulSoup parsing
156
- soup = s.dom.soup() # SoupWrapper with chainable API
157
- for item in soup.select(".product"):
158
- title = item.select_one("h2").text()
159
- price = item.attr("data-price")
160
-
161
- # Scrolling with random delays
162
- for _ in range(10):
163
- soup = s.dom.soup(".listings")
164
- s.scroll.js("down", 700)
165
- s.timing.random(0.8, 1.5)
166
-
167
- # Click with cursor movement
168
- s.click("button.buy", move_cursor=True)
169
-
170
- # Click all "See more" buttons
171
- s.input.click_all("See more")
172
-
173
- # Mouse operations
174
- s.input.mouse_move(500, 300)
175
- s.input.hover(".tooltip-trigger")
176
-
177
- # JS fetch (bypass CORS, inherit cookies)
178
- data = s.fetch.json("/api/items")
179
- ```
180
-
181
- ### Core Methods (on session)
182
-
183
- | Method | Description |
184
- |--------|-------------|
185
- | `navigate(url)` | Go to URL |
186
- | `click(selector, move_cursor)` | Click element |
187
- | `type(selector, text)` | Type text |
188
- | `wait_for(selector)` | Wait for element |
189
- | `execute_script(js)` | Run JavaScript |
190
- | `screenshot()` | PNG bytes |
191
- | `get_state()` | URL + title |
192
- | `get_page_info()` | Full page info |
193
- | `get/set_cookies()` | Cookie management |
194
-
195
- ### Capabilities
196
-
197
- **`session.scroll`** - Scrolling
198
- | Method | Description |
199
- |--------|-------------|
200
- | `js(dir, amount)` | JS scroll (works on complex sites) |
201
- | `native(dir, amount)` | Browser API scroll |
202
- | `to_bottom()` | Scroll to page bottom |
203
- | `to_element(selector)` | Scroll element into view |
204
- | `info()` | Get scroll position |
205
- | `infinite(extract_fn)` | Smart infinite scroll with extraction |
206
-
207
- **`session.input`** - Input operations
208
- | Method | Description |
209
- |--------|-------------|
210
- | `click_js(selector)` | JS click (reliable) |
211
- | `click_all(text, role)` | Click all matching elements |
212
- | `key(key, selector)` | Press keyboard key |
213
- | `hover(selector)` | Hover over element (native) |
214
- | `hover_js(selector)` | Hover via JS |
215
- | `mouse_move(x, y)` | Move cursor to coordinates |
216
-
217
- **`session.timing`** - Delays
218
- | Method | Description |
219
- |--------|-------------|
220
- | `wait(ms)` | Wait milliseconds |
221
- | `seconds(n)` | Wait seconds |
222
- | `random(min, max)` | Random delay |
223
- | `timeout(fn, sec, cleanup)` | Run with timeout |
224
-
225
- **`session.dom`** - DOM operations
226
- | Method | Description |
227
- |--------|-------------|
228
- | `html(selector)` | Get HTML |
229
- | `text(selector)` | Get text content |
230
- | `soup(selector)` | → SoupWrapper |
231
- | `parse(html)` | → BeautifulSoup |
232
- | `extract(selector, attr)` | Get text/attr list |
233
- | `select(selector, value)` | Dropdown select |
234
- | `close_modal()` | Close dialogs |
235
-
236
- **`session.fetch`** - HTTP from browser context
237
- | Method | Description |
238
- |--------|-------------|
239
- | `json(url)` | Fetch JSON |
240
- | `all(requests)` | Parallel fetch |
241
- | `execute(method, url, ...)` | Custom request |
242
-
243
- ## SDKBaseModel
244
-
245
- Auto-cleaning Pydantic model for scraped data:
246
-
247
- ```python
248
- from cmdop import SDKBaseModel
249
-
250
- class Product(SDKBaseModel):
251
- __base_url__ = "https://shop.com"
252
- name: str = "" # " iPhone 15 \n" → "iPhone 15"
253
- price: int = 0 # "$1,299.00" → 1299
254
- rating: float = 0 # "4.5 stars" → 4.5
255
- url: str = "" # "/p/123" → "https://shop.com/p/123"
256
-
257
- products = Product.from_list(raw["items"]) # Auto dedupe + filter
258
- ```
259
-
260
- ---
261
-
262
- ## Utilities
263
-
264
- **Logging:**
265
- ```python
266
- from cmdop import get_logger
267
- log = get_logger(__name__)
268
- log.info("Starting") # Rich console + auto file logging
269
- ```
270
-
271
- **TOON Format (30-50% token savings):**
272
- ```python
273
- from cmdop import json_to_toon, JsonCleaner
274
- toon = json_to_toon({"name": "Alice", "age": 25})
275
- # → "name: Alice\nage: 25"
276
- ```
277
-
278
- ---
279
-
280
- ## Requirements
281
-
282
- - Python 3.10+
283
- - CMDOP agent on target
284
-
285
- ## Links
286
-
287
- [cmdop.com](https://cmdop.com)
288
-
289
- ## License
290
-
291
- MIT