cmdop 0.1.23__tar.gz → 0.1.25__tar.gz
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.
- cmdop-0.1.25/PKG-INFO +322 -0
- cmdop-0.1.25/README.md +281 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/pyproject.toml +1 -1
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/__init__.py +1 -1
- cmdop-0.1.25/src/cmdop/helpers/__init__.py +17 -0
- cmdop-0.1.25/src/cmdop/helpers/network_analyzer.py +368 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/__init__.py +2 -0
- cmdop-0.1.25/src/cmdop/services/browser/capabilities/visual.py +100 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/session.py +10 -0
- cmdop-0.1.23/PKG-INFO +0 -330
- cmdop-0.1.23/README.md +0 -289
- cmdop-0.1.23/src/cmdop/helpers/__init__.py +0 -9
- {cmdop-0.1.23 → cmdop-0.1.25}/.gitignore +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/LICENSE +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/agent_messages_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/agent_messages_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/agent_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/common_types_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/common_types_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/common_types_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/control_messages_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/control_messages_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/control_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.23/src/cmdop/_generated/rpc_messages → cmdop-0.1.25/src/cmdop/_generated/file_operations}/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/archive_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/archive_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/archive_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/changes_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/changes_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/changes_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/common_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/common_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/common_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/directory_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/directory_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/directory_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/file_crud_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/file_crud_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/file_crud_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/hls_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/hls_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/hls_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/requests_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/requests_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/requests_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/search_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/search_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/search_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/transfer_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/transfer_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations/transfer_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_operations_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/archive_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/archive_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/archive_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/directory_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/directory_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/directory_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/file_crud_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/file_crud_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/file_crud_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/hls_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/hls_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/hls_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/search_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/search_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc/search_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/file_rpc_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/message_pool.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/py.typed +0 -0
- {cmdop-0.1.23/src/cmdop/_generated/file_operations → cmdop-0.1.25/src/cmdop/_generated/rpc_messages}/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/agent_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/agent_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/agent_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/browser_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/browser_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/browser_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/device_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/device_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/device_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/extract_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/extract_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/extract_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/health_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/health_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/health_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/history_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/history_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/history_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/lifecycle_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/push_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/push_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/push_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/session_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/session_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/session_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/terminal_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/terminal_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages/terminal_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/rpc_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/service_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/service_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/service_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/tunnel_pb2.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/tunnel_pb2.pyi +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/_generated/tunnel_pb2_grpc.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/enums.py +0 -0
- {cmdop-0.1.23/src/cmdop/api/generated/workspaces → cmdop-0.1.25/src/cmdop/api/generated/machines}/logger.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/machines__api__machine_sharing/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/machines__api__machine_sharing/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/machines__api__machine_sharing/models.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/machines__api__machine_sharing/sync_client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/machines__api__machines/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/machines__api__machines/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/machines__api__machines/models.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/machines__api__machines/sync_client.py +0 -0
- {cmdop-0.1.23/src/cmdop/api/generated/workspaces → cmdop-0.1.25/src/cmdop/api/generated/machines}/retry.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/schema.json +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/machines/sync_client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/enums.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/logger.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/retry.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/schema.json +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/sync_client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/system__api__oauth/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/system__api__oauth/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/system__api__oauth/models.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/system__api__oauth/sync_client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/system__api__system/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/system__api__system/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/system__api__system/models.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/system/system__api__system/sync_client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/enums.py +0 -0
- {cmdop-0.1.23/src/cmdop/api/generated/machines → cmdop-0.1.25/src/cmdop/api/generated/workspaces}/logger.py +0 -0
- {cmdop-0.1.23/src/cmdop/api/generated/machines → cmdop-0.1.25/src/cmdop/api/generated/workspaces}/retry.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/schema.json +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/sync_client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/models.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/sync_client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/client.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/config.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/discovery.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/exceptions.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/helpers/cleaner.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/helpers/formatting.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/logging.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/models/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/models/agent.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/models/base.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/models/config.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/models/extract.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/models/files.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/models/terminal.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/py.typed +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/agent.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/base.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/_base.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/_helpers.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/dom.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/fetch.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/input.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/network.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/scroll.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/capabilities/timing.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/js/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/js/core.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/js/fetch.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/js/interaction.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/js/scroll.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/models.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/parsing.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/service/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/service/_helpers.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/service/aio.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/browser/service/sync.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/extract.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/files.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/services/terminal.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/streaming/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/streaming/base.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/streaming/handlers.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/streaming/terminal.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/transport/__init__.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/transport/auth.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/transport/base.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/transport/discovery.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/transport/local.py +0 -0
- {cmdop-0.1.23 → cmdop-0.1.25}/src/cmdop/transport/remote.py +0 -0
cmdop-0.1.25/PKG-INFO
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cmdop
|
|
3
|
+
Version: 0.1.25
|
|
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
|
cmdop-0.1.25/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# cmdop
|
|
2
|
+
|
|
3
|
+
Python SDK for CMDOP browser automation and server control.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Your Code ──── Cloud Relay ──── Agent (on server)
|
|
9
|
+
│
|
|
10
|
+
Outbound only, works through any NAT/firewall
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install cmdop
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Connection
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from cmdop import CMDOPClient, AsyncCMDOPClient
|
|
23
|
+
|
|
24
|
+
# Local (direct IPC to running agent)
|
|
25
|
+
client = CMDOPClient.local()
|
|
26
|
+
|
|
27
|
+
# Remote (via cloud relay)
|
|
28
|
+
client = CMDOPClient.remote(api_key="cmd_xxx")
|
|
29
|
+
|
|
30
|
+
# Async
|
|
31
|
+
async with AsyncCMDOPClient.local() as client:
|
|
32
|
+
await client.files.read("/etc/hostname")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Browser
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from cmdop.services.browser.models import WaitUntil
|
|
39
|
+
|
|
40
|
+
with client.browser.create_session(headless=False) as s:
|
|
41
|
+
s.navigate("https://shop.com", wait_until=WaitUntil.NETWORKIDLE)
|
|
42
|
+
|
|
43
|
+
# Core methods
|
|
44
|
+
s.click("button.buy", move_cursor=True)
|
|
45
|
+
s.type("input[name=q]", "search term")
|
|
46
|
+
s.wait_for(".results")
|
|
47
|
+
s.execute_script("return document.title")
|
|
48
|
+
s.screenshot()
|
|
49
|
+
s.get_state() # URL + title
|
|
50
|
+
s.get_page_info() # Full page info
|
|
51
|
+
s.get_cookies()
|
|
52
|
+
s.set_cookies([...])
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**WaitUntil options:**
|
|
56
|
+
| Value | Description |
|
|
57
|
+
|-------|-------------|
|
|
58
|
+
| `LOAD` | Wait for load event (default) |
|
|
59
|
+
| `DOMCONTENTLOADED` | Wait for DOMContentLoaded |
|
|
60
|
+
| `NETWORKIDLE` | Wait until network is idle (best for SPA) |
|
|
61
|
+
| `COMMIT` | Return immediately (fastest) |
|
|
62
|
+
|
|
63
|
+
### Capabilities
|
|
64
|
+
|
|
65
|
+
**`s.scroll`** - Scrolling
|
|
66
|
+
```python
|
|
67
|
+
s.scroll.js("down", 500) # JS scroll (works on complex sites)
|
|
68
|
+
s.scroll.native("down", 500) # Browser API scroll
|
|
69
|
+
s.scroll.to_bottom() # Scroll to page bottom
|
|
70
|
+
s.scroll.to_element(".item") # Scroll element into view
|
|
71
|
+
s.scroll.info() # Get scroll position/dimensions
|
|
72
|
+
|
|
73
|
+
# Smart infinite scroll with extraction
|
|
74
|
+
items = s.scroll.infinite(
|
|
75
|
+
extract_fn=lambda: extract_new_items(),
|
|
76
|
+
limit=100,
|
|
77
|
+
max_scrolls=50,
|
|
78
|
+
scroll_amount=800,
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**`s.input`** - Input
|
|
83
|
+
```python
|
|
84
|
+
s.input.click_js(".btn") # JS click (reliable)
|
|
85
|
+
s.input.click_all("See more") # Click all matching elements
|
|
86
|
+
s.input.key("Escape") # Press key
|
|
87
|
+
s.input.key("Enter", ".input") # Press key on element
|
|
88
|
+
s.input.hover(".tooltip") # Native hover
|
|
89
|
+
s.input.hover_js(".tooltip") # JS hover
|
|
90
|
+
s.input.mouse_move(500, 300) # Move cursor to coordinates
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**`s.timing`** - Delays
|
|
94
|
+
```python
|
|
95
|
+
s.timing.wait(500) # Wait ms
|
|
96
|
+
s.timing.seconds(2) # Wait seconds
|
|
97
|
+
s.timing.random(0.5, 1.5) # Random delay
|
|
98
|
+
s.timing.timeout(fn, 10, cleanup) # Run with timeout
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**`s.dom`** - DOM operations
|
|
102
|
+
```python
|
|
103
|
+
s.dom.html(".container") # Get HTML
|
|
104
|
+
s.dom.text(".title") # Get text
|
|
105
|
+
s.dom.soup(".items") # → SoupWrapper (chainable BS4)
|
|
106
|
+
s.dom.parse(html_string) # → BeautifulSoup
|
|
107
|
+
s.dom.extract(".items", "href") # Get attr list
|
|
108
|
+
s.dom.select("#country", "US") # Dropdown select
|
|
109
|
+
s.dom.close_modal() # Close dialogs/popups
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**`s.fetch`** - HTTP from browser (bypass CORS, inherit cookies)
|
|
113
|
+
```python
|
|
114
|
+
s.fetch.json("/api/items") # Fetch JSON
|
|
115
|
+
s.fetch.all(["/api/a", "/api/b"]) # Parallel fetch
|
|
116
|
+
s.fetch.execute("return fetch(...)") # Custom JS
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**`s.network`** - Traffic capture
|
|
120
|
+
```python
|
|
121
|
+
s.network.enable(max_exchanges=1000)
|
|
122
|
+
s.navigate(url)
|
|
123
|
+
|
|
124
|
+
# Get exchanges
|
|
125
|
+
exchanges = s.network.get_all()
|
|
126
|
+
api = s.network.last("/api/data")
|
|
127
|
+
data = api.json_body()
|
|
128
|
+
|
|
129
|
+
# Filter
|
|
130
|
+
posts = s.network.filter(
|
|
131
|
+
url_pattern="/api/posts",
|
|
132
|
+
methods=["GET", "POST"],
|
|
133
|
+
status_codes=[200],
|
|
134
|
+
resource_types=["xhr", "fetch"],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Convenience
|
|
138
|
+
s.network.api_calls("/api/") # XHR/Fetch matching pattern
|
|
139
|
+
s.network.last_json("/api/data") # JSON body directly
|
|
140
|
+
s.network.wait_for("/api/", 5000) # Wait for request
|
|
141
|
+
s.network.export_har() # Export to HAR
|
|
142
|
+
s.network.stats() # Capture statistics
|
|
143
|
+
s.network.clear() # Clear captured
|
|
144
|
+
s.network.disable()
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**`s.visual`** - Browser overlay (requires CMDOP extension)
|
|
148
|
+
```python
|
|
149
|
+
s.visual.toast("Loading...") # Show toast
|
|
150
|
+
s.visual.clear_toasts() # Clear all toasts
|
|
151
|
+
s.visual.countdown(30, "Click!") # Countdown timer
|
|
152
|
+
s.visual.highlight(".element") # Highlight element
|
|
153
|
+
s.visual.hide_highlight() # Hide highlight
|
|
154
|
+
s.visual.click(100, 200) # Show click effect
|
|
155
|
+
s.visual.move(0, 0, 100, 200) # Show cursor trail
|
|
156
|
+
s.visual.set_state("busy") # idle/active/busy
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## NetworkAnalyzer
|
|
160
|
+
|
|
161
|
+
Discover API endpoints by capturing traffic while user interacts.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from cmdop import CMDOPClient
|
|
165
|
+
from cmdop.helpers import NetworkAnalyzer
|
|
166
|
+
|
|
167
|
+
client = CMDOPClient.local()
|
|
168
|
+
with client.browser.create_session(headless=False) as b:
|
|
169
|
+
analyzer = NetworkAnalyzer(b)
|
|
170
|
+
|
|
171
|
+
snapshot = analyzer.capture(
|
|
172
|
+
"https://example.com/cars",
|
|
173
|
+
wait_seconds=30,
|
|
174
|
+
countdown_message="Click pagination!",
|
|
175
|
+
min_size=100, # Ignore tracking pixels
|
|
176
|
+
max_size=500_000, # Ignore heavy assets
|
|
177
|
+
same_origin=True, # Only same domain
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Get best data API
|
|
181
|
+
if snapshot.api_requests:
|
|
182
|
+
best = snapshot.best_api()
|
|
183
|
+
print(best.url)
|
|
184
|
+
print(best.item_count)
|
|
185
|
+
print(best.data_key) # "data", "items", etc.
|
|
186
|
+
print(best.item_fields) # Field names
|
|
187
|
+
print(best.to_curl()) # curl command
|
|
188
|
+
print(best.to_httpx()) # Python httpx code
|
|
189
|
+
|
|
190
|
+
# All captured
|
|
191
|
+
for req in snapshot.api_requests:
|
|
192
|
+
print(f"{req.method} {req.url} → {req.item_count} items")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**NetworkSnapshot:**
|
|
196
|
+
- `api_requests` - Requests with data arrays
|
|
197
|
+
- `json_requests` - Other JSON responses
|
|
198
|
+
- `cookies` - Session cookies
|
|
199
|
+
- `total_requests`, `total_bytes`
|
|
200
|
+
|
|
201
|
+
**RequestSnapshot:**
|
|
202
|
+
- `url`, `method`, `headers`, `body`, `cookies`
|
|
203
|
+
- `status`, `content_type`, `size`
|
|
204
|
+
- `data_key`, `item_count`, `item_fields`, `sample_response`
|
|
205
|
+
- `to_curl()`, `to_httpx()`
|
|
206
|
+
|
|
207
|
+
## Agent
|
|
208
|
+
|
|
209
|
+
Run AI tasks with typed output:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from pydantic import BaseModel
|
|
213
|
+
|
|
214
|
+
class Health(BaseModel):
|
|
215
|
+
status: str
|
|
216
|
+
cpu: float
|
|
217
|
+
issues: list[str]
|
|
218
|
+
|
|
219
|
+
result = client.agent.run("Check server health", output_schema=Health)
|
|
220
|
+
health: Health = result.output # Typed!
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Terminal
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
session = client.terminal.create()
|
|
227
|
+
client.terminal.send_input(session.session_id, "ls -la\n")
|
|
228
|
+
output = client.terminal.get_history(session.session_id)
|
|
229
|
+
client.terminal.resize(session.session_id, 120, 40)
|
|
230
|
+
client.terminal.send_signal(session.session_id, "SIGINT")
|
|
231
|
+
client.terminal.close(session.session_id)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Files
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
client.files.list("/var/log")
|
|
238
|
+
client.files.read("/etc/nginx/nginx.conf")
|
|
239
|
+
client.files.write("/tmp/config.json", b'{"key": "value"}')
|
|
240
|
+
client.files.delete("/tmp/old.txt")
|
|
241
|
+
client.files.copy("/src", "/dst")
|
|
242
|
+
client.files.move("/old", "/new")
|
|
243
|
+
client.files.mkdir("/new/dir")
|
|
244
|
+
client.files.info("/path")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## SDKBaseModel
|
|
248
|
+
|
|
249
|
+
Auto-cleaning Pydantic model:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from cmdop import SDKBaseModel
|
|
253
|
+
|
|
254
|
+
class Product(SDKBaseModel):
|
|
255
|
+
__base_url__ = "https://shop.com"
|
|
256
|
+
name: str = "" # " iPhone 15 \n" → "iPhone 15"
|
|
257
|
+
price: int = 0 # "$1,299.00" → 1299
|
|
258
|
+
rating: float = 0 # "4.5 stars" → 4.5
|
|
259
|
+
url: str = "" # "/p/123" → "https://shop.com/p/123"
|
|
260
|
+
|
|
261
|
+
products = Product.from_list(raw["items"]) # Auto dedupe + filter
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Utilities
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from cmdop import get_logger, json_to_toon
|
|
268
|
+
|
|
269
|
+
# Logging (rich console + file)
|
|
270
|
+
log = get_logger(__name__)
|
|
271
|
+
log.info("Starting")
|
|
272
|
+
|
|
273
|
+
# TOON format (30-50% token savings)
|
|
274
|
+
toon = json_to_toon({"name": "Alice", "age": 25})
|
|
275
|
+
# → "name: Alice\nage: 25"
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Requirements
|
|
279
|
+
|
|
280
|
+
- Python 3.10+
|
|
281
|
+
- CMDOP agent running locally or API key for remote
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""CMDOP SDK helpers."""
|
|
2
|
+
|
|
3
|
+
from cmdop.helpers.formatting import json_to_toon
|
|
4
|
+
from cmdop.helpers.cleaner import JsonCleaner
|
|
5
|
+
from cmdop.helpers.network_analyzer import (
|
|
6
|
+
NetworkAnalyzer,
|
|
7
|
+
NetworkSnapshot,
|
|
8
|
+
RequestSnapshot,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"json_to_toon",
|
|
13
|
+
"JsonCleaner",
|
|
14
|
+
"NetworkAnalyzer",
|
|
15
|
+
"NetworkSnapshot",
|
|
16
|
+
"RequestSnapshot",
|
|
17
|
+
]
|