cmdop 0.1.13__tar.gz → 0.1.16__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.13 → cmdop-0.1.16}/PKG-INFO +263 -113
- {cmdop-0.1.13 → cmdop-0.1.16}/README.md +259 -111
- {cmdop-0.1.13 → cmdop-0.1.16}/pyproject.toml +6 -2
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/__init__.py +16 -1
- cmdop-0.1.16/src/cmdop/helpers/__init__.py +9 -0
- cmdop-0.1.16/src/cmdop/helpers/cleaner.py +53 -0
- cmdop-0.1.16/src/cmdop/helpers/formatting.py +15 -0
- cmdop-0.1.16/src/cmdop/logging.py +252 -0
- cmdop-0.1.16/src/cmdop/services/browser/__init__.py +44 -0
- cmdop-0.1.16/src/cmdop/services/browser/aio/__init__.py +6 -0
- cmdop-0.1.16/src/cmdop/services/browser/aio/service.py +323 -0
- cmdop-0.1.16/src/cmdop/services/browser/aio/session.py +171 -0
- cmdop-0.1.16/src/cmdop/services/browser/base/__init__.py +6 -0
- cmdop-0.1.16/src/cmdop/services/browser/base/service.py +62 -0
- cmdop-0.1.16/src/cmdop/services/browser/base/session.py +72 -0
- cmdop-0.1.16/src/cmdop/services/browser/js.py +109 -0
- cmdop-0.1.16/src/cmdop/services/browser/models.py +50 -0
- cmdop-0.1.16/src/cmdop/services/browser/sync/__init__.py +6 -0
- cmdop-0.1.16/src/cmdop/services/browser/sync/service.py +313 -0
- cmdop-0.1.16/src/cmdop/services/browser/sync/session.py +171 -0
- cmdop-0.1.13/src/cmdop/services/browser.py +0 -1185
- {cmdop-0.1.13 → cmdop-0.1.16}/.gitignore +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/LICENSE +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/agent_messages_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/agent_messages_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/agent_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/common_types_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/common_types_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/common_types_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/control_messages_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/control_messages_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/control_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/archive_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/archive_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/archive_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/changes_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/changes_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/changes_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/common_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/common_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/common_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/directory_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/directory_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/directory_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/file_crud_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/file_crud_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/file_crud_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/hls_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/hls_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/hls_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/requests_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/requests_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/requests_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/search_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/search_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/search_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/transfer_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/transfer_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations/transfer_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_operations_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/archive_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/archive_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/archive_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/directory_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/directory_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/directory_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/file_crud_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/file_crud_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/file_crud_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/hls_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/hls_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/hls_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/search_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/search_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc/search_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/file_rpc_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/message_pool.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/py.typed +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/agent_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/agent_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/agent_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/browser_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/browser_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/browser_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/device_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/device_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/device_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/extract_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/extract_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/extract_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/health_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/health_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/health_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/history_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/history_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/history_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/lifecycle_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/push_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/push_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/push_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/session_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/session_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/session_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/terminal_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/terminal_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages/terminal_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/rpc_messages_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/service_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/service_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/service_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/tunnel_pb2.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/tunnel_pb2.pyi +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/_generated/tunnel_pb2_grpc.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/enums.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/logger.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/machines__api__machine_sharing/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/machines__api__machine_sharing/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/machines__api__machine_sharing/models.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/machines__api__machine_sharing/sync_client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/machines__api__machines/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/machines__api__machines/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/machines__api__machines/models.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/machines__api__machines/sync_client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/retry.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/schema.json +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/machines/sync_client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/enums.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/logger.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/retry.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/schema.json +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/sync_client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/system__api__oauth/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/system__api__oauth/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/system__api__oauth/models.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/system__api__oauth/sync_client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/system__api__system/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/system__api__system/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/system__api__system/models.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/system/system__api__system/sync_client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/enums.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/logger.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/retry.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/schema.json +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/sync_client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/models.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/sync_client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/client.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/config.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/discovery.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/exceptions.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/models/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/models/agent.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/models/base.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/models/config.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/models/extract.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/models/files.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/models/terminal.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/py.typed +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/services/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/services/agent.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/services/base.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/services/extract.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/services/files.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/services/terminal.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/streaming/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/streaming/base.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/streaming/handlers.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/streaming/terminal.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/transport/__init__.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/transport/auth.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/transport/base.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/transport/discovery.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/src/cmdop/transport/local.py +0 -0
- {cmdop-0.1.13 → cmdop-0.1.16}/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.16
|
|
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
|
|
@@ -8,7 +8,7 @@ Project-URL: Repository, https://github.com/markolofsen/cmdop-client
|
|
|
8
8
|
Author: CMDOP Team
|
|
9
9
|
License: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Keywords: agent,automation,cmdop,
|
|
11
|
+
Keywords: agent,automation,cmdop,terminal
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -24,6 +24,8 @@ Requires-Dist: httpx>=0.27.0
|
|
|
24
24
|
Requires-Dist: protobuf>=4.25.0
|
|
25
25
|
Requires-Dist: pydantic-settings>=2.0.0
|
|
26
26
|
Requires-Dist: pydantic>=2.5.0
|
|
27
|
+
Requires-Dist: rich>=13.0.0
|
|
28
|
+
Requires-Dist: toon-python>=0.1.2
|
|
27
29
|
Provides-Extra: dev
|
|
28
30
|
Requires-Dist: grpcio-tools>=1.60.0; extra == 'dev'
|
|
29
31
|
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
@@ -63,37 +65,70 @@ Agent connects out. Your code connects to relay. Done.
|
|
|
63
65
|
|
|
64
66
|
---
|
|
65
67
|
|
|
66
|
-
##
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install cmdop
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from cmdop import CMDOPClient, AsyncCMDOPClient
|
|
76
|
+
|
|
77
|
+
# Remote (via cloud relay)
|
|
78
|
+
with CMDOPClient.remote(api_key="cmd_xxx") as client:
|
|
79
|
+
client.files.list("/home")
|
|
80
|
+
|
|
81
|
+
# Local (direct IPC)
|
|
82
|
+
with CMDOPClient.local() as client:
|
|
83
|
+
client.terminal.execute("ls -la")
|
|
84
|
+
|
|
85
|
+
# Async
|
|
86
|
+
async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
|
|
87
|
+
await client.files.read("/etc/hostname")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
# Part 1: Remote Control
|
|
93
|
+
|
|
94
|
+
## Terminal
|
|
67
95
|
|
|
68
|
-
**Terminal:**
|
|
69
96
|
```python
|
|
70
97
|
session = server.terminal.create()
|
|
71
98
|
server.terminal.send_input(session.session_id, "kubectl get pods\n")
|
|
72
99
|
output = server.terminal.get_history(session.session_id)
|
|
73
100
|
```
|
|
74
101
|
|
|
75
|
-
|
|
102
|
+
| Method | Description |
|
|
103
|
+
|--------|-------------|
|
|
104
|
+
| `create(shell)` | Start session |
|
|
105
|
+
| `send_input(id, data)` | Send commands |
|
|
106
|
+
| `get_history(id)` | Get output |
|
|
107
|
+
| `resize(id, cols, rows)` | Resize |
|
|
108
|
+
| `send_signal(id, signal)` | SIGINT/SIGTERM |
|
|
109
|
+
| `close(id)` | End session |
|
|
110
|
+
|
|
111
|
+
## Files
|
|
112
|
+
|
|
76
113
|
```python
|
|
77
114
|
server.files.list("/var/log")
|
|
78
115
|
server.files.read("/etc/nginx/nginx.conf")
|
|
79
116
|
server.files.write("/tmp/config.json", b'{"key": "value"}')
|
|
80
117
|
```
|
|
81
118
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
| Method | Description |
|
|
120
|
+
|--------|-------------|
|
|
121
|
+
| `list(path)` | List dir |
|
|
122
|
+
| `read(path)` | Read file |
|
|
123
|
+
| `write(path, content)` | Write file |
|
|
124
|
+
| `delete(path)` | Delete |
|
|
125
|
+
| `copy(src, dst)` | Copy |
|
|
126
|
+
| `move(src, dst)` | Move |
|
|
127
|
+
| `mkdir(path)` | Create dir |
|
|
128
|
+
| `info(path)` | Metadata |
|
|
86
129
|
|
|
87
|
-
|
|
88
|
-
products = browser.extract_data(
|
|
89
|
-
".product-card",
|
|
90
|
-
'{"name": "h2", "price": ".price", "url": {"selector": "a", "attr": "href"}}',
|
|
91
|
-
limit=100
|
|
92
|
-
)["items"]
|
|
93
|
-
# → [{"name": "iPhone", "price": "$999", "url": "/p/123"}, ...]
|
|
94
|
-
```
|
|
130
|
+
## Agent
|
|
95
131
|
|
|
96
|
-
**AI Agent — typed responses:**
|
|
97
132
|
```python
|
|
98
133
|
from pydantic import BaseModel
|
|
99
134
|
|
|
@@ -106,11 +141,15 @@ result = server.agent.run("Check server health", output_schema=Health)
|
|
|
106
141
|
health: Health = result.output # Typed!
|
|
107
142
|
```
|
|
108
143
|
|
|
109
|
-
|
|
144
|
+
| Method | Description |
|
|
145
|
+
|--------|-------------|
|
|
146
|
+
| `run(prompt, output_schema)` | Run agent, get typed result |
|
|
147
|
+
|
|
148
|
+
Types: `chat`, `terminal`, `command`, `router`, `planner`
|
|
110
149
|
|
|
111
|
-
## Real World
|
|
150
|
+
## Real World Examples
|
|
112
151
|
|
|
113
|
-
**
|
|
152
|
+
**Deploy with typed result:**
|
|
114
153
|
```python
|
|
115
154
|
class DeployResult(BaseModel):
|
|
116
155
|
success: bool
|
|
@@ -125,7 +164,7 @@ if not result.output.success:
|
|
|
125
164
|
rollback(result.output.errors)
|
|
126
165
|
```
|
|
127
166
|
|
|
128
|
-
**Fleet
|
|
167
|
+
**Fleet update (1000 devices):**
|
|
129
168
|
```python
|
|
130
169
|
async def update_fleet(keys: list[str], config: bytes):
|
|
131
170
|
async with asyncio.TaskGroup() as tg:
|
|
@@ -138,7 +177,7 @@ async def update_one(key: str, config: bytes):
|
|
|
138
177
|
await dev.terminal.execute("systemctl restart app")
|
|
139
178
|
```
|
|
140
179
|
|
|
141
|
-
**Debug
|
|
180
|
+
**Debug customer machine:**
|
|
142
181
|
```python
|
|
143
182
|
with CMDOPClient.remote(api_key=customer_key) as m:
|
|
144
183
|
m.terminal.send_input(sid, "ps aux\n")
|
|
@@ -146,75 +185,60 @@ with CMDOPClient.remote(api_key=customer_key) as m:
|
|
|
146
185
|
m.terminal.send_input(sid, "df -h\n")
|
|
147
186
|
```
|
|
148
187
|
|
|
149
|
-
**Scrape Products:**
|
|
150
|
-
```python
|
|
151
|
-
class Product(SDKBaseModel):
|
|
152
|
-
__base_url__ = "https://amazon.com"
|
|
153
|
-
title: str = ""
|
|
154
|
-
price: int = 0 # "$1,299" → 1299
|
|
155
|
-
url: str = "" # "/dp/..." → "https://amazon.com/dp/..."
|
|
156
|
-
|
|
157
|
-
with client.browser.create_session(headless=True) as b:
|
|
158
|
-
b.navigate("https://amazon.com/s?k=laptop")
|
|
159
|
-
raw = b.extract_data(".s-result-item", '{"title": "h2", "price": ".a-price-whole", "url": {"selector": "a", "attr": "href"}}', limit=50)
|
|
160
|
-
products = Product.from_list(raw["items"]) # clean + dedupe + filter
|
|
161
|
-
```
|
|
162
|
-
|
|
163
188
|
---
|
|
164
189
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
pip install cmdop
|
|
169
|
-
```
|
|
190
|
+
# Part 2: Web Parsing
|
|
170
191
|
|
|
171
|
-
##
|
|
192
|
+
## Browser
|
|
172
193
|
|
|
194
|
+
**DOM extraction:**
|
|
173
195
|
```python
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# Remote (via cloud relay)
|
|
177
|
-
with CMDOPClient.remote(api_key="cmd_xxx") as client:
|
|
178
|
-
client.files.list("/home")
|
|
179
|
-
|
|
180
|
-
# Local (direct IPC)
|
|
181
|
-
with CMDOPClient.local() as client:
|
|
182
|
-
client.terminal.execute("ls -la")
|
|
196
|
+
with server.browser.create_session() as b:
|
|
197
|
+
b.navigate("https://shop.com/products")
|
|
183
198
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
199
|
+
products = b.extract_data(
|
|
200
|
+
".product-card",
|
|
201
|
+
'{"name": "h2", "price": ".price", "url": {"selector": "a", "attr": "href"}}',
|
|
202
|
+
limit=100
|
|
203
|
+
)["items"]
|
|
204
|
+
# → [{"name": "iPhone", "price": "$999", "url": "/p/123"}, ...]
|
|
187
205
|
```
|
|
188
206
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
207
|
+
**JS fetch injection (bypass CORS):**
|
|
208
|
+
```python
|
|
209
|
+
with client.browser.create_session() as b:
|
|
210
|
+
b.navigate("https://site.com") # Get cookies/session
|
|
211
|
+
|
|
212
|
+
# Single API call
|
|
213
|
+
data = b.fetch_json("https://api.site.com/v1/items")
|
|
214
|
+
|
|
215
|
+
# Parallel fetch with headers and credentials
|
|
216
|
+
results = b.fetch_all(
|
|
217
|
+
urls={
|
|
218
|
+
"users": "https://api.site.com/v1/users",
|
|
219
|
+
"orders": "https://api.site.com/v1/orders",
|
|
220
|
+
},
|
|
221
|
+
headers={"Accept": "application/json"},
|
|
222
|
+
credentials=True, # Include cookies
|
|
223
|
+
)
|
|
224
|
+
# → {"users": {"data": [...], "error": None}, "orders": {"data": [...], "error": None}}
|
|
225
|
+
```
|
|
203
226
|
|
|
204
|
-
|
|
227
|
+
**Custom JS execution:**
|
|
228
|
+
```python
|
|
229
|
+
with client.browser.create_session() as b:
|
|
230
|
+
b.navigate("https://site.com")
|
|
205
231
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
| `copy(src, dst)` | Copy |
|
|
213
|
-
| `move(src, dst)` | Move |
|
|
214
|
-
| `mkdir(path)` | Create dir |
|
|
215
|
-
| `info(path)` | Metadata |
|
|
232
|
+
# Execute async JS with auto-wrap and JSON parsing
|
|
233
|
+
result = b.execute_js("""
|
|
234
|
+
const resp = await fetch('/api/data');
|
|
235
|
+
return await resp.json();
|
|
236
|
+
""")
|
|
237
|
+
# → {"items": [...]}
|
|
216
238
|
|
|
217
|
-
|
|
239
|
+
# Raw mode (returns JSON string)
|
|
240
|
+
raw = b.execute_js("return document.title", raw=True)
|
|
241
|
+
```
|
|
218
242
|
|
|
219
243
|
| Method | Description |
|
|
220
244
|
|--------|-------------|
|
|
@@ -226,30 +250,15 @@ async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
|
|
|
226
250
|
| `extract(selector, attr)` | Get text/attr |
|
|
227
251
|
| `extract_regex(pattern)` | Regex matches |
|
|
228
252
|
| `validate_selectors(item, fields)` | Check selectors |
|
|
229
|
-
| `extract_data(item, fields, limit)` |
|
|
230
|
-
| `
|
|
253
|
+
| `extract_data(item, fields, limit)` | Bulk extract → list[dict] |
|
|
254
|
+
| `fetch_json(url)` | JS fetch → dict |
|
|
255
|
+
| `fetch_all(urls, headers, credentials)` | Parallel fetch → {id: {data, error}} |
|
|
256
|
+
| `execute_js(code, raw)` | Async JS with auto-wrap → dict |
|
|
257
|
+
| `execute_script(js)` | Raw JS → str |
|
|
231
258
|
| `screenshot()` | PNG |
|
|
232
259
|
| `get_cookies()` / `set_cookies()` | Cookies |
|
|
233
260
|
|
|
234
|
-
|
|
235
|
-
```python
|
|
236
|
-
with client.browser.create_session() as b:
|
|
237
|
-
b.navigate("https://cars.com/listings")
|
|
238
|
-
|
|
239
|
-
# 1. Validate (fail fast if site changed)
|
|
240
|
-
v = b.validate_selectors(".item", {"title": "h2", "price": ".price"})
|
|
241
|
-
if not v["valid"]:
|
|
242
|
-
raise Exception(v["errors"]) # also has counts, samples
|
|
243
|
-
|
|
244
|
-
# 2. Extract
|
|
245
|
-
cars = b.extract_data(
|
|
246
|
-
".item",
|
|
247
|
-
'{"title": "h2", "price": {"selector": ".price", "regex": "\\\\d+"}}',
|
|
248
|
-
limit=200
|
|
249
|
-
)["items"]
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### SDKBaseModel
|
|
261
|
+
## SDKBaseModel
|
|
253
262
|
|
|
254
263
|
Auto-cleaning Pydantic model for scraped data. No more manual `.strip()`, regex, URL joining.
|
|
255
264
|
|
|
@@ -257,7 +266,7 @@ Auto-cleaning Pydantic model for scraped data. No more manual `.strip()`, regex,
|
|
|
257
266
|
from cmdop import SDKBaseModel
|
|
258
267
|
|
|
259
268
|
class Product(SDKBaseModel):
|
|
260
|
-
__base_url__ = "https://shop.com"
|
|
269
|
+
__base_url__ = "https://shop.com"
|
|
261
270
|
|
|
262
271
|
name: str = "" # " iPhone 15 \n" → "iPhone 15"
|
|
263
272
|
price: int = 0 # "$1,299.00" → 1299
|
|
@@ -268,8 +277,6 @@ class Product(SDKBaseModel):
|
|
|
268
277
|
products = Product.from_list(raw["items"])
|
|
269
278
|
```
|
|
270
279
|
|
|
271
|
-
**What it does:**
|
|
272
|
-
|
|
273
280
|
| Type | Input | Output |
|
|
274
281
|
|------|-------|--------|
|
|
275
282
|
| `str` | `" text \n\t "` | `"text"` |
|
|
@@ -277,19 +284,162 @@ products = Product.from_list(raw["items"])
|
|
|
277
284
|
| `float` | `"4.5 out of 5"` | `4.5` |
|
|
278
285
|
| `str` (url field) | `"/path"` | `"https://base.com/path"` |
|
|
279
286
|
|
|
280
|
-
|
|
287
|
+
## Parsing Examples
|
|
288
|
+
|
|
289
|
+
**Scrape with validation:**
|
|
281
290
|
```python
|
|
282
|
-
|
|
283
|
-
|
|
291
|
+
with client.browser.create_session() as b:
|
|
292
|
+
b.navigate("https://cars.com/listings")
|
|
293
|
+
|
|
294
|
+
# 1. Validate (fail fast if site changed)
|
|
295
|
+
v = b.validate_selectors(".item", {"title": "h2", "price": ".price"})
|
|
296
|
+
if not v["valid"]:
|
|
297
|
+
raise Exception(v["errors"])
|
|
298
|
+
|
|
299
|
+
# 2. Extract
|
|
300
|
+
cars = b.extract_data(".item", '{"title": "h2", "price": ".price"}', limit=200)["items"]
|
|
284
301
|
```
|
|
285
302
|
|
|
286
|
-
|
|
303
|
+
**Scrape with SDKBaseModel:**
|
|
304
|
+
```python
|
|
305
|
+
class Product(SDKBaseModel):
|
|
306
|
+
__base_url__ = "https://amazon.com"
|
|
307
|
+
title: str = ""
|
|
308
|
+
price: int = 0 # "$1,299" → 1299
|
|
309
|
+
url: str = "" # "/dp/..." → "https://amazon.com/dp/..."
|
|
287
310
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
311
|
+
with client.browser.create_session(headless=True) as b:
|
|
312
|
+
b.navigate("https://amazon.com/s?k=laptop")
|
|
313
|
+
raw = b.extract_data(".s-result-item", '{"title": "h2", "price": ".a-price-whole", "url": {"selector": "a", "attr": "href"}}', limit=50)
|
|
314
|
+
products = Product.from_list(raw["items"]) # clean + dedupe + filter
|
|
315
|
+
```
|
|
291
316
|
|
|
292
|
-
|
|
317
|
+
**Parallel API fetching:**
|
|
318
|
+
```python
|
|
319
|
+
with client.browser.create_session() as b:
|
|
320
|
+
b.navigate("https://api.example.com")
|
|
321
|
+
|
|
322
|
+
# Fetch 10 pages in parallel
|
|
323
|
+
urls = {f"page_{i}": f"https://api.example.com/items?page={i}" for i in range(10)}
|
|
324
|
+
results = b.fetch_all(urls)
|
|
325
|
+
|
|
326
|
+
items = []
|
|
327
|
+
for key, res in results.items():
|
|
328
|
+
if res["data"]: # {"data": {...}, "error": None}
|
|
329
|
+
items.extend(res["data"].get("items", []))
|
|
330
|
+
elif res["error"]:
|
|
331
|
+
print(f"{key} failed: {res['error']}")
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**JS fetch for protected APIs:**
|
|
335
|
+
```python
|
|
336
|
+
with client.browser.create_session() as b:
|
|
337
|
+
b.navigate("https://site.com") # Get session cookies
|
|
338
|
+
|
|
339
|
+
# Fetch JSON API (inherits cookies)
|
|
340
|
+
data = b.fetch_json("https://api.site.com/v1/data")
|
|
341
|
+
|
|
342
|
+
# Parallel fetch with custom headers
|
|
343
|
+
results = b.fetch_all(
|
|
344
|
+
urls={
|
|
345
|
+
"inspection": f"https://api.site.com/v1/car/{car_id}/inspection",
|
|
346
|
+
"options": f"https://api.site.com/v1/car/{car_id}/options",
|
|
347
|
+
},
|
|
348
|
+
headers={"Accept": "application/json", "Origin": "https://site.com"},
|
|
349
|
+
credentials=True,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
inspection = results["inspection"]["data"]
|
|
353
|
+
options = results["options"]["data"]
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
# Part 3: Utilities
|
|
359
|
+
|
|
360
|
+
## Logging
|
|
361
|
+
|
|
362
|
+
Rich-powered logger with automatic project root detection and file persistence.
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from cmdop import get_logger
|
|
366
|
+
|
|
367
|
+
log = get_logger(__name__)
|
|
368
|
+
log.info("Starting process")
|
|
369
|
+
log.debug("Details: %s", data)
|
|
370
|
+
log.warning("Something seems off")
|
|
371
|
+
log.error("Failed!", exc_info=True) # With traceback
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Features:**
|
|
375
|
+
- Rich console output with colors and timestamps
|
|
376
|
+
- Auto-saves to `logs/` folder in project root
|
|
377
|
+
- Finds project root by `pyproject.toml`, `requirements.txt`, `.git`
|
|
378
|
+
- Daily log rotation by filename
|
|
379
|
+
|
|
380
|
+
**Custom settings:**
|
|
381
|
+
```python
|
|
382
|
+
from cmdop import get_logger, setup_logging
|
|
383
|
+
|
|
384
|
+
# Set log level
|
|
385
|
+
log = get_logger(__name__, level="DEBUG")
|
|
386
|
+
|
|
387
|
+
# Custom app name for log files
|
|
388
|
+
log = get_logger(__name__, app_name="myparser")
|
|
389
|
+
# → logs/myparser_2024-01-17.log
|
|
390
|
+
|
|
391
|
+
# Disable file logging
|
|
392
|
+
log = get_logger(__name__, log_to_file=False)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Log file format:**
|
|
396
|
+
```
|
|
397
|
+
2024-01-17 14:30:25 | INFO | myapp.parser:42 - Starting process
|
|
398
|
+
2024-01-17 14:30:26 | ERROR | myapp.parser:58 - Failed to fetch
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
| Function | Description |
|
|
402
|
+
|----------|-------------|
|
|
403
|
+
| `get_logger(name, level, app_name)` | Get configured logger |
|
|
404
|
+
| `setup_logging(level, log_to_file)` | Configure root logger |
|
|
405
|
+
| `find_project_root()` | Find Python project root |
|
|
406
|
+
| `get_log_dir(app_name)` | Get/create logs directory |
|
|
407
|
+
|
|
408
|
+
## TOON Format (Token Optimization)
|
|
409
|
+
|
|
410
|
+
Convert JSON to TOON format — saves 30-50% tokens for LLM consumption.
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
from cmdop import json_to_toon, JsonCleaner
|
|
414
|
+
|
|
415
|
+
# Simple conversion
|
|
416
|
+
data = {"name": "Alice", "age": 25, "city": "Seoul"}
|
|
417
|
+
toon = json_to_toon(data)
|
|
418
|
+
# → "name: Alice\nage: 25\ncity: Seoul"
|
|
419
|
+
|
|
420
|
+
# With cleaning (removes nulls, empty values, noise keys)
|
|
421
|
+
cleaner = JsonCleaner(noise_keys={"internal_id", "metadata"})
|
|
422
|
+
clean_toon = cleaner.to_toon({
|
|
423
|
+
"name": "BMW X5",
|
|
424
|
+
"price": 50000,
|
|
425
|
+
"internal_id": "abc123", # removed
|
|
426
|
+
"specs": None, # removed
|
|
427
|
+
"options": [], # removed
|
|
428
|
+
})
|
|
429
|
+
# → "name: BMW X5\nprice: 50000"
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**JsonCleaner features:**
|
|
433
|
+
- Removes `None` values and empty `[]`, `{}`, `""`
|
|
434
|
+
- Simplifies `{"code": "1", "title": "Good"}` → `"Good"`
|
|
435
|
+
- Custom noise keys per domain
|
|
436
|
+
|
|
437
|
+
| Function | Description |
|
|
438
|
+
|----------|-------------|
|
|
439
|
+
| `json_to_toon(data)` | Convert dict/list to TOON string |
|
|
440
|
+
| `JsonCleaner(noise_keys)` | Cleaner with custom noise keys |
|
|
441
|
+
| `cleaner.compact(data)` | Clean without converting |
|
|
442
|
+
| `cleaner.to_toon(data)` | Clean + convert to TOON |
|
|
293
443
|
|
|
294
444
|
---
|
|
295
445
|
|