cmdop 0.1.21__tar.gz → 0.1.22__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.21 → cmdop-0.1.22}/PKG-INFO +69 -60
- {cmdop-0.1.21 → cmdop-0.1.22}/README.md +68 -59
- {cmdop-0.1.21 → cmdop-0.1.22}/pyproject.toml +1 -1
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/__init__.py +1 -1
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/client.py +2 -8
- cmdop-0.1.22/src/cmdop/services/browser/__init__.py +59 -0
- cmdop-0.1.22/src/cmdop/services/browser/capabilities/__init__.py +15 -0
- cmdop-0.1.22/src/cmdop/services/browser/capabilities/_base.py +28 -0
- cmdop-0.1.22/src/cmdop/services/browser/capabilities/_helpers.py +16 -0
- cmdop-0.1.22/src/cmdop/services/browser/capabilities/dom.py +76 -0
- cmdop-0.1.22/src/cmdop/services/browser/capabilities/fetch.py +46 -0
- cmdop-0.1.22/src/cmdop/services/browser/capabilities/input.py +49 -0
- cmdop-0.1.22/src/cmdop/services/browser/capabilities/scroll.py +147 -0
- cmdop-0.1.22/src/cmdop/services/browser/capabilities/timing.py +66 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/browser/js/__init__.py +6 -4
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/browser/js/interaction.py +34 -0
- cmdop-0.1.22/src/cmdop/services/browser/service/__init__.py +5 -0
- cmdop-0.1.22/src/cmdop/services/browser/service/aio.py +30 -0
- cmdop-0.1.21/src/cmdop/services/browser/sync/service.py → cmdop-0.1.22/src/cmdop/services/browser/service/sync.py +2 -2
- cmdop-0.1.22/src/cmdop/services/browser/session.py +166 -0
- cmdop-0.1.21/src/cmdop/services/browser/__init__.py +0 -46
- cmdop-0.1.21/src/cmdop/services/browser/aio/__init__.py +0 -6
- cmdop-0.1.21/src/cmdop/services/browser/aio/service.py +0 -420
- cmdop-0.1.21/src/cmdop/services/browser/aio/session.py +0 -407
- cmdop-0.1.21/src/cmdop/services/browser/base/__init__.py +0 -6
- cmdop-0.1.21/src/cmdop/services/browser/base/session.py +0 -124
- cmdop-0.1.21/src/cmdop/services/browser/sync/__init__.py +0 -6
- cmdop-0.1.21/src/cmdop/services/browser/sync/session.py +0 -644
- {cmdop-0.1.21 → cmdop-0.1.22}/.gitignore +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/LICENSE +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/agent_messages_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/agent_messages_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/agent_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/common_types_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/common_types_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/common_types_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/control_messages_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/control_messages_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/control_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/archive_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/archive_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/archive_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/changes_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/changes_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/changes_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/common_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/common_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/common_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/directory_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/directory_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/directory_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/file_crud_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/file_crud_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/file_crud_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/hls_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/hls_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/hls_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/requests_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/requests_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/requests_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/search_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/search_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/search_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/transfer_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/transfer_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations/transfer_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_operations_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/archive_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/archive_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/archive_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/directory_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/directory_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/directory_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/file_crud_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/file_crud_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/file_crud_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/hls_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/hls_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/hls_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/search_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/search_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc/search_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/file_rpc_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/message_pool.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/py.typed +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/agent_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/agent_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/agent_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/browser_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/browser_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/browser_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/device_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/device_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/device_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/extract_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/extract_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/extract_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/health_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/health_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/health_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/history_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/history_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/history_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/lifecycle_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/push_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/push_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/push_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/session_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/session_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/session_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/terminal_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/terminal_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages/terminal_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/rpc_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/service_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/service_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/service_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/tunnel_pb2.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/tunnel_pb2.pyi +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/_generated/tunnel_pb2_grpc.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/enums.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/logger.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/machines__api__machine_sharing/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/machines__api__machine_sharing/client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/machines__api__machine_sharing/models.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/machines__api__machine_sharing/sync_client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/machines__api__machines/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/machines__api__machines/client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/machines__api__machines/models.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/machines__api__machines/sync_client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/retry.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/schema.json +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/machines/sync_client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/enums.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/logger.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/retry.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/schema.json +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/sync_client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/system__api__oauth/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/system__api__oauth/client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/system__api__oauth/models.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/system__api__oauth/sync_client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/system__api__system/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/system__api__system/client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/system__api__system/models.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/system/system__api__system/sync_client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/enums.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/logger.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/retry.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/schema.json +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/sync_client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/models.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/sync_client.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/config.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/discovery.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/exceptions.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/helpers/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/helpers/cleaner.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/helpers/formatting.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/logging.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/models/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/models/agent.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/models/base.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/models/config.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/models/extract.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/models/files.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/models/terminal.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/py.typed +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/agent.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/base.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/browser/js/core.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/browser/js/fetch.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/browser/js/scroll.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/browser/models.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/browser/parsing.py +0 -0
- /cmdop-0.1.21/src/cmdop/services/browser/base/service.py → /cmdop-0.1.22/src/cmdop/services/browser/service/_helpers.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/extract.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/files.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/services/terminal.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/streaming/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/streaming/base.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/streaming/handlers.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/streaming/terminal.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/transport/__init__.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/transport/auth.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/transport/base.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/transport/discovery.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/transport/local.py +0 -0
- {cmdop-0.1.21 → cmdop-0.1.22}/src/cmdop/transport/remote.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cmdop
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.22
|
|
4
4
|
Summary: Python SDK for CMDOP agent interaction
|
|
5
5
|
Project-URL: Homepage, https://cmdop.com
|
|
6
6
|
Project-URL: Documentation, https://cmdop.com
|
|
@@ -145,91 +145,100 @@ health: Health = result.output # Typed!
|
|
|
145
145
|
|
|
146
146
|
## Browser
|
|
147
147
|
|
|
148
|
+
Capability-based API for browser automation.
|
|
149
|
+
|
|
148
150
|
```python
|
|
149
|
-
with client.browser.create_session() as
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
with client.browser.create_session() as s:
|
|
152
|
+
s.navigate("https://shop.com/products")
|
|
153
|
+
s.dom.close_modal() # Close popups
|
|
152
154
|
|
|
153
155
|
# BeautifulSoup parsing
|
|
154
|
-
soup =
|
|
156
|
+
soup = s.dom.soup() # SoupWrapper with chainable API
|
|
155
157
|
for item in soup.select(".product"):
|
|
156
158
|
title = item.select_one("h2").text()
|
|
157
159
|
price = item.attr("data-price")
|
|
158
160
|
|
|
159
161
|
# Scrolling with random delays
|
|
160
162
|
for _ in range(10):
|
|
161
|
-
soup =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
b.wait_random(0.8, 1.5) # Random delay
|
|
163
|
+
soup = s.dom.soup(".listings")
|
|
164
|
+
s.scroll.js("down", 700)
|
|
165
|
+
s.timing.random(0.8, 1.5)
|
|
165
166
|
|
|
166
|
-
# Click with cursor movement
|
|
167
|
-
|
|
167
|
+
# Click with cursor movement
|
|
168
|
+
s.click("button.buy", move_cursor=True)
|
|
168
169
|
|
|
169
170
|
# Click all "See more" buttons
|
|
170
|
-
|
|
171
|
+
s.input.click_all("See more")
|
|
171
172
|
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
# Mouse operations
|
|
174
|
+
s.input.mouse_move(500, 300)
|
|
175
|
+
s.input.hover(".tooltip-trigger")
|
|
175
176
|
|
|
176
177
|
# JS fetch (bypass CORS, inherit cookies)
|
|
177
|
-
data =
|
|
178
|
+
data = s.fetch.json("/api/items")
|
|
178
179
|
```
|
|
179
180
|
|
|
181
|
+
### Core Methods (on session)
|
|
182
|
+
|
|
180
183
|
| Method | Description |
|
|
181
184
|
|--------|-------------|
|
|
182
185
|
| `navigate(url)` | Go to URL |
|
|
183
|
-
| `click(selector, move_cursor)` | Click element
|
|
184
|
-
| `click_all_by_text(text, role)` | Click all matching elements |
|
|
186
|
+
| `click(selector, move_cursor)` | Click element |
|
|
185
187
|
| `type(selector, text)` | Type text |
|
|
186
|
-
| `wait_for(selector
|
|
187
|
-
| `
|
|
188
|
-
| `wait_random(min, max)` | Random sleep |
|
|
189
|
-
| `extract(selector, attr)` | Get text/attr |
|
|
190
|
-
| `get_html(selector)` | Get HTML |
|
|
191
|
-
| `soup(selector)` | → SoupWrapper |
|
|
192
|
-
| `parse_html(html)` | → BeautifulSoup |
|
|
193
|
-
| `fetch_json(url)` | JS fetch → dict |
|
|
194
|
-
| `fetch_all(urls)` | Parallel fetch |
|
|
195
|
-
| `execute_js(code)` | Run async JS |
|
|
188
|
+
| `wait_for(selector)` | Wait for element |
|
|
189
|
+
| `execute_script(js)` | Run JavaScript |
|
|
196
190
|
| `screenshot()` | PNG bytes |
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `get_scroll_info()` | Position + page size |
|
|
200
|
-
| `get_page_info()` | Comprehensive page info |
|
|
201
|
-
| `mouse_move(x, y, steps)` | Move cursor to coordinates |
|
|
202
|
-
| `hover(selector)` | Hover over element |
|
|
203
|
-
| `select(selector, value)` | Dropdown select |
|
|
204
|
-
| `close_modal()` | Close dialogs |
|
|
205
|
-
| `press_key(key, selector)` | Press keyboard key |
|
|
191
|
+
| `get_state()` | URL + title |
|
|
192
|
+
| `get_page_info()` | Full page info |
|
|
206
193
|
| `get/set_cookies()` | Cookie management |
|
|
207
|
-
| `with_timeout(fn, sec, cleanup)` | Run with timeout, skip if hangs |
|
|
208
194
|
|
|
209
|
-
|
|
210
|
-
```python
|
|
211
|
-
# Sync
|
|
212
|
-
result, ok = browser.with_timeout(
|
|
213
|
-
lambda: process_item(browser, item),
|
|
214
|
-
timeout_sec=60,
|
|
215
|
-
on_timeout=lambda: browser.press_key('Escape'),
|
|
216
|
-
)
|
|
217
|
-
if not ok:
|
|
218
|
-
print("Skipped - timed out")
|
|
195
|
+
### Capabilities
|
|
219
196
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
|
|
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 |
|
|
227
224
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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 |
|
|
233
242
|
|
|
234
243
|
## SDKBaseModel
|
|
235
244
|
|
|
@@ -104,91 +104,100 @@ health: Health = result.output # Typed!
|
|
|
104
104
|
|
|
105
105
|
## Browser
|
|
106
106
|
|
|
107
|
+
Capability-based API for browser automation.
|
|
108
|
+
|
|
107
109
|
```python
|
|
108
|
-
with client.browser.create_session() as
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
with client.browser.create_session() as s:
|
|
111
|
+
s.navigate("https://shop.com/products")
|
|
112
|
+
s.dom.close_modal() # Close popups
|
|
111
113
|
|
|
112
114
|
# BeautifulSoup parsing
|
|
113
|
-
soup =
|
|
115
|
+
soup = s.dom.soup() # SoupWrapper with chainable API
|
|
114
116
|
for item in soup.select(".product"):
|
|
115
117
|
title = item.select_one("h2").text()
|
|
116
118
|
price = item.attr("data-price")
|
|
117
119
|
|
|
118
120
|
# Scrolling with random delays
|
|
119
121
|
for _ in range(10):
|
|
120
|
-
soup =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
b.wait_random(0.8, 1.5) # Random delay
|
|
122
|
+
soup = s.dom.soup(".listings")
|
|
123
|
+
s.scroll.js("down", 700)
|
|
124
|
+
s.timing.random(0.8, 1.5)
|
|
124
125
|
|
|
125
|
-
# Click with cursor movement
|
|
126
|
-
|
|
126
|
+
# Click with cursor movement
|
|
127
|
+
s.click("button.buy", move_cursor=True)
|
|
127
128
|
|
|
128
129
|
# Click all "See more" buttons
|
|
129
|
-
|
|
130
|
+
s.input.click_all("See more")
|
|
130
131
|
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
# Mouse operations
|
|
133
|
+
s.input.mouse_move(500, 300)
|
|
134
|
+
s.input.hover(".tooltip-trigger")
|
|
134
135
|
|
|
135
136
|
# JS fetch (bypass CORS, inherit cookies)
|
|
136
|
-
data =
|
|
137
|
+
data = s.fetch.json("/api/items")
|
|
137
138
|
```
|
|
138
139
|
|
|
140
|
+
### Core Methods (on session)
|
|
141
|
+
|
|
139
142
|
| Method | Description |
|
|
140
143
|
|--------|-------------|
|
|
141
144
|
| `navigate(url)` | Go to URL |
|
|
142
|
-
| `click(selector, move_cursor)` | Click element
|
|
143
|
-
| `click_all_by_text(text, role)` | Click all matching elements |
|
|
145
|
+
| `click(selector, move_cursor)` | Click element |
|
|
144
146
|
| `type(selector, text)` | Type text |
|
|
145
|
-
| `wait_for(selector
|
|
146
|
-
| `
|
|
147
|
-
| `wait_random(min, max)` | Random sleep |
|
|
148
|
-
| `extract(selector, attr)` | Get text/attr |
|
|
149
|
-
| `get_html(selector)` | Get HTML |
|
|
150
|
-
| `soup(selector)` | → SoupWrapper |
|
|
151
|
-
| `parse_html(html)` | → BeautifulSoup |
|
|
152
|
-
| `fetch_json(url)` | JS fetch → dict |
|
|
153
|
-
| `fetch_all(urls)` | Parallel fetch |
|
|
154
|
-
| `execute_js(code)` | Run async JS |
|
|
147
|
+
| `wait_for(selector)` | Wait for element |
|
|
148
|
+
| `execute_script(js)` | Run JavaScript |
|
|
155
149
|
| `screenshot()` | PNG bytes |
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `get_scroll_info()` | Position + page size |
|
|
159
|
-
| `get_page_info()` | Comprehensive page info |
|
|
160
|
-
| `mouse_move(x, y, steps)` | Move cursor to coordinates |
|
|
161
|
-
| `hover(selector)` | Hover over element |
|
|
162
|
-
| `select(selector, value)` | Dropdown select |
|
|
163
|
-
| `close_modal()` | Close dialogs |
|
|
164
|
-
| `press_key(key, selector)` | Press keyboard key |
|
|
150
|
+
| `get_state()` | URL + title |
|
|
151
|
+
| `get_page_info()` | Full page info |
|
|
165
152
|
| `get/set_cookies()` | Cookie management |
|
|
166
|
-
| `with_timeout(fn, sec, cleanup)` | Run with timeout, skip if hangs |
|
|
167
153
|
|
|
168
|
-
|
|
169
|
-
```python
|
|
170
|
-
# Sync
|
|
171
|
-
result, ok = browser.with_timeout(
|
|
172
|
-
lambda: process_item(browser, item),
|
|
173
|
-
timeout_sec=60,
|
|
174
|
-
on_timeout=lambda: browser.press_key('Escape'),
|
|
175
|
-
)
|
|
176
|
-
if not ok:
|
|
177
|
-
print("Skipped - timed out")
|
|
154
|
+
### Capabilities
|
|
178
155
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
|
|
156
|
+
**`session.scroll`** - Scrolling
|
|
157
|
+
| Method | Description |
|
|
158
|
+
|--------|-------------|
|
|
159
|
+
| `js(dir, amount)` | JS scroll (works on complex sites) |
|
|
160
|
+
| `native(dir, amount)` | Browser API scroll |
|
|
161
|
+
| `to_bottom()` | Scroll to page bottom |
|
|
162
|
+
| `to_element(selector)` | Scroll element into view |
|
|
163
|
+
| `info()` | Get scroll position |
|
|
164
|
+
| `infinite(extract_fn)` | Smart infinite scroll with extraction |
|
|
165
|
+
|
|
166
|
+
**`session.input`** - Input operations
|
|
167
|
+
| Method | Description |
|
|
168
|
+
|--------|-------------|
|
|
169
|
+
| `click_js(selector)` | JS click (reliable) |
|
|
170
|
+
| `click_all(text, role)` | Click all matching elements |
|
|
171
|
+
| `key(key, selector)` | Press keyboard key |
|
|
172
|
+
| `hover(selector)` | Hover over element (native) |
|
|
173
|
+
| `hover_js(selector)` | Hover via JS |
|
|
174
|
+
| `mouse_move(x, y)` | Move cursor to coordinates |
|
|
175
|
+
|
|
176
|
+
**`session.timing`** - Delays
|
|
177
|
+
| Method | Description |
|
|
178
|
+
|--------|-------------|
|
|
179
|
+
| `wait(ms)` | Wait milliseconds |
|
|
180
|
+
| `seconds(n)` | Wait seconds |
|
|
181
|
+
| `random(min, max)` | Random delay |
|
|
182
|
+
| `timeout(fn, sec, cleanup)` | Run with timeout |
|
|
186
183
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
**`session.dom`** - DOM operations
|
|
185
|
+
| Method | Description |
|
|
186
|
+
|--------|-------------|
|
|
187
|
+
| `html(selector)` | Get HTML |
|
|
188
|
+
| `text(selector)` | Get text content |
|
|
189
|
+
| `soup(selector)` | → SoupWrapper |
|
|
190
|
+
| `parse(html)` | → BeautifulSoup |
|
|
191
|
+
| `extract(selector, attr)` | Get text/attr list |
|
|
192
|
+
| `select(selector, value)` | Dropdown select |
|
|
193
|
+
| `close_modal()` | Close dialogs |
|
|
194
|
+
|
|
195
|
+
**`session.fetch`** - HTTP from browser context
|
|
196
|
+
| Method | Description |
|
|
197
|
+
|--------|-------------|
|
|
198
|
+
| `json(url)` | Fetch JSON |
|
|
199
|
+
| `all(requests)` | Parallel fetch |
|
|
200
|
+
| `execute(method, url, ...)` | Custom request |
|
|
192
201
|
|
|
193
202
|
## SDKBaseModel
|
|
194
203
|
|
|
@@ -439,15 +439,9 @@ class AsyncCMDOPClient:
|
|
|
439
439
|
@property
|
|
440
440
|
def browser(self) -> AsyncBrowserService:
|
|
441
441
|
"""
|
|
442
|
-
Async browser service
|
|
442
|
+
Async browser service (stub - not implemented).
|
|
443
443
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
Example:
|
|
447
|
-
>>> async with client.browser.create_session() as session:
|
|
448
|
-
... await session.navigate("https://google.com")
|
|
449
|
-
... await session.type("input[name='q']", "Python")
|
|
450
|
-
... results = await session.extract(".result-title")
|
|
444
|
+
Use sync CMDOPClient.browser instead.
|
|
451
445
|
"""
|
|
452
446
|
if self._browser is None:
|
|
453
447
|
self._browser = AsyncBrowserService(self._transport)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Browser SDK with capability-based API.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from cmdop.services.browser import BrowserSession
|
|
5
|
+
|
|
6
|
+
with service.create_session() as session:
|
|
7
|
+
session.navigate("https://example.com")
|
|
8
|
+
|
|
9
|
+
# Capabilities
|
|
10
|
+
session.scroll.js("down", 500)
|
|
11
|
+
session.scroll.to_bottom()
|
|
12
|
+
|
|
13
|
+
session.input.click_js(".button")
|
|
14
|
+
session.input.key("Escape")
|
|
15
|
+
|
|
16
|
+
session.timing.wait(1000)
|
|
17
|
+
|
|
18
|
+
soup = session.dom.soup()
|
|
19
|
+
data = session.fetch.json("/api/data")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .session import BrowserSession
|
|
23
|
+
from .service.sync import BrowserService
|
|
24
|
+
from .service.aio import AsyncBrowserService
|
|
25
|
+
from .models import (
|
|
26
|
+
BrowserCookie,
|
|
27
|
+
BrowserState,
|
|
28
|
+
PageInfo,
|
|
29
|
+
ScrollResult,
|
|
30
|
+
ScrollInfo,
|
|
31
|
+
InfiniteScrollResult,
|
|
32
|
+
)
|
|
33
|
+
from .capabilities import (
|
|
34
|
+
ScrollCapability,
|
|
35
|
+
InputCapability,
|
|
36
|
+
TimingCapability,
|
|
37
|
+
DOMCapability,
|
|
38
|
+
FetchCapability,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
# Session & Service
|
|
43
|
+
"BrowserSession",
|
|
44
|
+
"BrowserService",
|
|
45
|
+
"AsyncBrowserService",
|
|
46
|
+
# Models
|
|
47
|
+
"BrowserCookie",
|
|
48
|
+
"BrowserState",
|
|
49
|
+
"PageInfo",
|
|
50
|
+
"ScrollResult",
|
|
51
|
+
"ScrollInfo",
|
|
52
|
+
"InfiniteScrollResult",
|
|
53
|
+
# Capabilities
|
|
54
|
+
"ScrollCapability",
|
|
55
|
+
"InputCapability",
|
|
56
|
+
"TimingCapability",
|
|
57
|
+
"DOMCapability",
|
|
58
|
+
"FetchCapability",
|
|
59
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Browser capabilities."""
|
|
2
|
+
|
|
3
|
+
from .scroll import ScrollCapability
|
|
4
|
+
from .input import InputCapability
|
|
5
|
+
from .timing import TimingCapability
|
|
6
|
+
from .dom import DOMCapability
|
|
7
|
+
from .fetch import FetchCapability
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"ScrollCapability",
|
|
11
|
+
"InputCapability",
|
|
12
|
+
"TimingCapability",
|
|
13
|
+
"DOMCapability",
|
|
14
|
+
"FetchCapability",
|
|
15
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Base capability class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..session import BrowserSession
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseCapability:
|
|
11
|
+
"""Base class for all capabilities.
|
|
12
|
+
|
|
13
|
+
Capabilities group related browser operations and delegate
|
|
14
|
+
actual execution to the session.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__slots__ = ("_s",)
|
|
18
|
+
|
|
19
|
+
def __init__(self, session: "BrowserSession") -> None:
|
|
20
|
+
self._s = session
|
|
21
|
+
|
|
22
|
+
def _js(self, script: str) -> str:
|
|
23
|
+
"""Execute JS via session."""
|
|
24
|
+
return self._s.execute_script(script)
|
|
25
|
+
|
|
26
|
+
def _call(self, method: str, *args: Any, **kwargs: Any) -> Any:
|
|
27
|
+
"""Call service method via session."""
|
|
28
|
+
return self._s._call_service(method, *args, **kwargs)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Shared parsing helpers for capabilities."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from cmdop.services.browser.js import parse_json_result
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def to_dict(raw: str) -> dict[str, Any]:
|
|
8
|
+
"""Parse JS result to dict, default empty."""
|
|
9
|
+
result = parse_json_result(raw)
|
|
10
|
+
return result if isinstance(result, dict) else {}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def to_list(raw: str) -> list[Any]:
|
|
14
|
+
"""Parse JS result to list, default empty."""
|
|
15
|
+
result = parse_json_result(raw)
|
|
16
|
+
return result if isinstance(result, list) else []
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""DOM capability."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from cmdop.services.browser.js import build_select_js, build_close_modal_js
|
|
7
|
+
from cmdop.services.browser.parsing import parse_html, SoupWrapper
|
|
8
|
+
|
|
9
|
+
from ._base import BaseCapability
|
|
10
|
+
from ._helpers import to_dict
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from bs4 import BeautifulSoup
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DOMCapability(BaseCapability):
|
|
17
|
+
"""DOM operations: extraction, forms, modals.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
html = session.dom.html()
|
|
21
|
+
soup = session.dom.soup()
|
|
22
|
+
session.dom.select("#country", value="US")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def html(self, selector: str | None = None) -> str:
|
|
26
|
+
"""Get page HTML."""
|
|
27
|
+
return self._call("get_html", selector)
|
|
28
|
+
|
|
29
|
+
def text(self, selector: str | None = None) -> str:
|
|
30
|
+
"""Get page text content."""
|
|
31
|
+
return self._call("get_text", selector)
|
|
32
|
+
|
|
33
|
+
def soup(self, selector: str | None = None) -> SoupWrapper:
|
|
34
|
+
"""Get HTML as SoupWrapper with chainable API."""
|
|
35
|
+
return SoupWrapper(html=self.html(selector))
|
|
36
|
+
|
|
37
|
+
def parse(self, html: str | None = None, selector: str | None = None) -> "BeautifulSoup":
|
|
38
|
+
"""Parse HTML with BeautifulSoup."""
|
|
39
|
+
if html is None:
|
|
40
|
+
html = self.html(selector)
|
|
41
|
+
return parse_html(html)
|
|
42
|
+
|
|
43
|
+
def select(
|
|
44
|
+
self,
|
|
45
|
+
selector: str,
|
|
46
|
+
value: str | None = None,
|
|
47
|
+
text: str | None = None,
|
|
48
|
+
) -> dict:
|
|
49
|
+
"""Select dropdown option by value or text."""
|
|
50
|
+
js = build_select_js(selector, value, text)
|
|
51
|
+
return to_dict(self._js(js))
|
|
52
|
+
|
|
53
|
+
def close_modal(self, selectors: list[str] | None = None) -> bool:
|
|
54
|
+
"""Try to close modal/dialog."""
|
|
55
|
+
js = build_close_modal_js(selectors)
|
|
56
|
+
return to_dict(self._js(js)).get("success", False)
|
|
57
|
+
|
|
58
|
+
def extract(
|
|
59
|
+
self, selector: str, attr: str | None = None, limit: int = 100
|
|
60
|
+
) -> list[str]:
|
|
61
|
+
"""Extract text or attribute from elements."""
|
|
62
|
+
return self._call("extract", selector, attr, limit)
|
|
63
|
+
|
|
64
|
+
def extract_regex(
|
|
65
|
+
self, pattern: str, from_html: bool = False, limit: int = 100
|
|
66
|
+
) -> list[str]:
|
|
67
|
+
"""Extract matches using regex pattern."""
|
|
68
|
+
return self._call("extract_regex", pattern, from_html, limit)
|
|
69
|
+
|
|
70
|
+
def validate_selectors(self, item: str, fields: dict[str, str]) -> dict:
|
|
71
|
+
"""Validate CSS selectors on page."""
|
|
72
|
+
return self._call("validate_selectors", item, fields)
|
|
73
|
+
|
|
74
|
+
def extract_data(self, item: str, fields_json: str, limit: int = 100) -> dict:
|
|
75
|
+
"""Extract structured data from page."""
|
|
76
|
+
return self._call("extract_data", item, fields_json, limit)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Fetch capability."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from cmdop.services.browser.js import (
|
|
6
|
+
build_fetch_js,
|
|
7
|
+
build_fetch_all_js,
|
|
8
|
+
build_async_js,
|
|
9
|
+
parse_json_result,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from ._base import BaseCapability
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FetchCapability(BaseCapability):
|
|
16
|
+
"""HTTP fetch operations from browser context.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
data = session.fetch.json("/api/data")
|
|
20
|
+
results = session.fetch.all({"a": "/api/a", "b": "/api/b"})
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def json(self, url: str) -> dict | list | None:
|
|
24
|
+
"""Fetch JSON from URL."""
|
|
25
|
+
js = build_fetch_js(url)
|
|
26
|
+
return parse_json_result(self._js(js))
|
|
27
|
+
|
|
28
|
+
def all(
|
|
29
|
+
self,
|
|
30
|
+
urls: dict[str, str],
|
|
31
|
+
headers: dict[str, str] | None = None,
|
|
32
|
+
credentials: bool = False,
|
|
33
|
+
) -> dict[str, Any]:
|
|
34
|
+
"""Fetch multiple URLs in parallel. Returns {id: {data, error}}."""
|
|
35
|
+
if not urls:
|
|
36
|
+
return {}
|
|
37
|
+
js = build_fetch_all_js(urls, headers, credentials)
|
|
38
|
+
# fetch_all returns via execute_js (async wrapper)
|
|
39
|
+
wrapped = build_async_js(js.replace("return ", ""))
|
|
40
|
+
result = parse_json_result(self._js(wrapped))
|
|
41
|
+
return result if isinstance(result, dict) else {}
|
|
42
|
+
|
|
43
|
+
def execute(self, code: str) -> dict | list | str | None:
|
|
44
|
+
"""Execute async JS code (can use await, fetch, etc)."""
|
|
45
|
+
js = build_async_js(code)
|
|
46
|
+
return parse_json_result(self._js(js))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Input capability."""
|
|
2
|
+
|
|
3
|
+
from cmdop.services.browser.js import (
|
|
4
|
+
build_click_js,
|
|
5
|
+
build_press_key_js,
|
|
6
|
+
build_click_all_by_text_js,
|
|
7
|
+
build_hover_js,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from ._base import BaseCapability
|
|
11
|
+
from ._helpers import to_dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InputCapability(BaseCapability):
|
|
15
|
+
"""Input operations: clicks, keyboard, hover.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
session.input.click_js(".button")
|
|
19
|
+
session.input.key("Escape")
|
|
20
|
+
session.input.click_all("See more")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def click_js(self, selector: str, scroll_into_view: bool = True) -> bool:
|
|
24
|
+
"""Click using JavaScript. More reliable than native click."""
|
|
25
|
+
js = build_click_js(selector, scroll_into_view)
|
|
26
|
+
return to_dict(self._js(js)).get("success", False)
|
|
27
|
+
|
|
28
|
+
def key(self, key: str, selector: str | None = None) -> bool:
|
|
29
|
+
"""Press keyboard key. Keys: Escape, Enter, Tab, ArrowDown, etc."""
|
|
30
|
+
js = build_press_key_js(key, selector)
|
|
31
|
+
return to_dict(self._js(js)).get("success", False)
|
|
32
|
+
|
|
33
|
+
def click_all(self, text: str, role: str = "button") -> int:
|
|
34
|
+
"""Click all elements containing text. Returns count clicked."""
|
|
35
|
+
js = build_click_all_by_text_js(text, role)
|
|
36
|
+
return to_dict(self._js(js)).get("clicked", 0)
|
|
37
|
+
|
|
38
|
+
def hover_js(self, selector: str) -> bool:
|
|
39
|
+
"""Hover using JavaScript."""
|
|
40
|
+
js = build_hover_js(selector)
|
|
41
|
+
return to_dict(self._js(js)).get("success", False)
|
|
42
|
+
|
|
43
|
+
def hover(self, selector: str, timeout_ms: int = 5000) -> None:
|
|
44
|
+
"""Hover using native browser API."""
|
|
45
|
+
self._call("hover", selector, timeout_ms)
|
|
46
|
+
|
|
47
|
+
def mouse_move(self, x: int, y: int, steps: int = 10) -> None:
|
|
48
|
+
"""Move mouse to coordinates with human-like movement."""
|
|
49
|
+
self._call("mouse_move", x, y, steps)
|