cmdop 0.1.19__tar.gz → 0.1.21__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.
Files changed (201) hide show
  1. {cmdop-0.1.19 → cmdop-0.1.21}/PKG-INFO +21 -1
  2. {cmdop-0.1.19 → cmdop-0.1.21}/README.md +20 -0
  3. {cmdop-0.1.19 → cmdop-0.1.21}/pyproject.toml +1 -1
  4. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/__init__.py +1 -1
  5. cmdop-0.1.21/src/cmdop/_generated/rpc_messages/browser_pb2.py +133 -0
  6. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/browser_pb2.pyi +4 -2
  7. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/aio/service.py +7 -2
  8. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/aio/session.py +74 -7
  9. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/sync/service.py +7 -2
  10. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/sync/session.py +91 -9
  11. cmdop-0.1.19/src/cmdop/_generated/rpc_messages/browser_pb2.py +0 -133
  12. {cmdop-0.1.19 → cmdop-0.1.21}/.gitignore +0 -0
  13. {cmdop-0.1.19 → cmdop-0.1.21}/LICENSE +0 -0
  14. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/__init__.py +0 -0
  15. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/agent_messages_pb2.py +0 -0
  16. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/agent_messages_pb2.pyi +0 -0
  17. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/agent_messages_pb2_grpc.py +0 -0
  18. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/common_types_pb2.py +0 -0
  19. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/common_types_pb2.pyi +0 -0
  20. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/common_types_pb2_grpc.py +0 -0
  21. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/control_messages_pb2.py +0 -0
  22. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/control_messages_pb2.pyi +0 -0
  23. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/control_messages_pb2_grpc.py +0 -0
  24. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/__init__.py +0 -0
  25. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/archive_pb2.py +0 -0
  26. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/archive_pb2.pyi +0 -0
  27. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/archive_pb2_grpc.py +0 -0
  28. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/changes_pb2.py +0 -0
  29. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/changes_pb2.pyi +0 -0
  30. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/changes_pb2_grpc.py +0 -0
  31. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/common_pb2.py +0 -0
  32. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/common_pb2.pyi +0 -0
  33. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/common_pb2_grpc.py +0 -0
  34. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/directory_pb2.py +0 -0
  35. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/directory_pb2.pyi +0 -0
  36. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/directory_pb2_grpc.py +0 -0
  37. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/file_crud_pb2.py +0 -0
  38. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/file_crud_pb2.pyi +0 -0
  39. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/file_crud_pb2_grpc.py +0 -0
  40. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/hls_pb2.py +0 -0
  41. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/hls_pb2.pyi +0 -0
  42. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/hls_pb2_grpc.py +0 -0
  43. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/requests_pb2.py +0 -0
  44. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/requests_pb2.pyi +0 -0
  45. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/requests_pb2_grpc.py +0 -0
  46. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/search_pb2.py +0 -0
  47. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/search_pb2.pyi +0 -0
  48. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/search_pb2_grpc.py +0 -0
  49. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/transfer_pb2.py +0 -0
  50. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/transfer_pb2.pyi +0 -0
  51. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations/transfer_pb2_grpc.py +0 -0
  52. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations_pb2.py +0 -0
  53. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations_pb2.pyi +0 -0
  54. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_operations_pb2_grpc.py +0 -0
  55. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/__init__.py +0 -0
  56. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/archive_pb2.py +0 -0
  57. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/archive_pb2.pyi +0 -0
  58. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/archive_pb2_grpc.py +0 -0
  59. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/directory_pb2.py +0 -0
  60. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/directory_pb2.pyi +0 -0
  61. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/directory_pb2_grpc.py +0 -0
  62. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/file_crud_pb2.py +0 -0
  63. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/file_crud_pb2.pyi +0 -0
  64. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/file_crud_pb2_grpc.py +0 -0
  65. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/hls_pb2.py +0 -0
  66. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/hls_pb2.pyi +0 -0
  67. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/hls_pb2_grpc.py +0 -0
  68. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/search_pb2.py +0 -0
  69. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/search_pb2.pyi +0 -0
  70. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc/search_pb2_grpc.py +0 -0
  71. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc_pb2.py +0 -0
  72. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc_pb2.pyi +0 -0
  73. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/file_rpc_pb2_grpc.py +0 -0
  74. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/message_pool.py +0 -0
  75. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/py.typed +0 -0
  76. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/__init__.py +0 -0
  77. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/agent_pb2.py +0 -0
  78. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/agent_pb2.pyi +0 -0
  79. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/agent_pb2_grpc.py +0 -0
  80. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/browser_pb2_grpc.py +0 -0
  81. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/device_pb2.py +0 -0
  82. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/device_pb2.pyi +0 -0
  83. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/device_pb2_grpc.py +0 -0
  84. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/extract_pb2.py +0 -0
  85. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/extract_pb2.pyi +0 -0
  86. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/extract_pb2_grpc.py +0 -0
  87. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/health_pb2.py +0 -0
  88. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/health_pb2.pyi +0 -0
  89. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/health_pb2_grpc.py +0 -0
  90. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/history_pb2.py +0 -0
  91. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/history_pb2.pyi +0 -0
  92. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/history_pb2_grpc.py +0 -0
  93. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.py +0 -0
  94. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.pyi +0 -0
  95. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/lifecycle_pb2_grpc.py +0 -0
  96. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/push_pb2.py +0 -0
  97. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/push_pb2.pyi +0 -0
  98. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/push_pb2_grpc.py +0 -0
  99. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/session_pb2.py +0 -0
  100. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/session_pb2.pyi +0 -0
  101. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/session_pb2_grpc.py +0 -0
  102. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/terminal_pb2.py +0 -0
  103. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/terminal_pb2.pyi +0 -0
  104. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages/terminal_pb2_grpc.py +0 -0
  105. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages_pb2.py +0 -0
  106. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages_pb2.pyi +0 -0
  107. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/rpc_messages_pb2_grpc.py +0 -0
  108. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/service_pb2.py +0 -0
  109. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/service_pb2.pyi +0 -0
  110. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/service_pb2_grpc.py +0 -0
  111. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/tunnel_pb2.py +0 -0
  112. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/tunnel_pb2.pyi +0 -0
  113. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/_generated/tunnel_pb2_grpc.py +0 -0
  114. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/__init__.py +0 -0
  115. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/__init__.py +0 -0
  116. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/__init__.py +0 -0
  117. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/client.py +0 -0
  118. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/enums.py +0 -0
  119. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/logger.py +0 -0
  120. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/machines__api__machine_sharing/__init__.py +0 -0
  121. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/machines__api__machine_sharing/client.py +0 -0
  122. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/machines__api__machine_sharing/models.py +0 -0
  123. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/machines__api__machine_sharing/sync_client.py +0 -0
  124. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/machines__api__machines/__init__.py +0 -0
  125. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/machines__api__machines/client.py +0 -0
  126. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/machines__api__machines/models.py +0 -0
  127. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/machines__api__machines/sync_client.py +0 -0
  128. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/retry.py +0 -0
  129. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/schema.json +0 -0
  130. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/machines/sync_client.py +0 -0
  131. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/__init__.py +0 -0
  132. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/client.py +0 -0
  133. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/enums.py +0 -0
  134. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/logger.py +0 -0
  135. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/retry.py +0 -0
  136. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/schema.json +0 -0
  137. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/sync_client.py +0 -0
  138. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/system__api__oauth/__init__.py +0 -0
  139. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/system__api__oauth/client.py +0 -0
  140. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/system__api__oauth/models.py +0 -0
  141. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/system__api__oauth/sync_client.py +0 -0
  142. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/system__api__system/__init__.py +0 -0
  143. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/system__api__system/client.py +0 -0
  144. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/system__api__system/models.py +0 -0
  145. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/system/system__api__system/sync_client.py +0 -0
  146. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/__init__.py +0 -0
  147. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/client.py +0 -0
  148. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/enums.py +0 -0
  149. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/logger.py +0 -0
  150. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/retry.py +0 -0
  151. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/schema.json +0 -0
  152. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/sync_client.py +0 -0
  153. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/__init__.py +0 -0
  154. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/client.py +0 -0
  155. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/models.py +0 -0
  156. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/sync_client.py +0 -0
  157. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/client.py +0 -0
  158. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/config.py +0 -0
  159. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/discovery.py +0 -0
  160. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/exceptions.py +0 -0
  161. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/helpers/__init__.py +0 -0
  162. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/helpers/cleaner.py +0 -0
  163. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/helpers/formatting.py +0 -0
  164. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/logging.py +0 -0
  165. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/models/__init__.py +0 -0
  166. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/models/agent.py +0 -0
  167. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/models/base.py +0 -0
  168. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/models/config.py +0 -0
  169. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/models/extract.py +0 -0
  170. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/models/files.py +0 -0
  171. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/models/terminal.py +0 -0
  172. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/py.typed +0 -0
  173. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/__init__.py +0 -0
  174. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/agent.py +0 -0
  175. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/base.py +0 -0
  176. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/__init__.py +0 -0
  177. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/aio/__init__.py +0 -0
  178. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/base/__init__.py +0 -0
  179. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/base/service.py +0 -0
  180. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/base/session.py +0 -0
  181. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/js/__init__.py +0 -0
  182. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/js/core.py +0 -0
  183. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/js/fetch.py +0 -0
  184. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/js/interaction.py +0 -0
  185. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/js/scroll.py +0 -0
  186. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/models.py +0 -0
  187. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/parsing.py +0 -0
  188. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/browser/sync/__init__.py +0 -0
  189. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/extract.py +0 -0
  190. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/files.py +0 -0
  191. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/services/terminal.py +0 -0
  192. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/streaming/__init__.py +0 -0
  193. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/streaming/base.py +0 -0
  194. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/streaming/handlers.py +0 -0
  195. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/streaming/terminal.py +0 -0
  196. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/transport/__init__.py +0 -0
  197. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/transport/auth.py +0 -0
  198. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/transport/base.py +0 -0
  199. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/transport/discovery.py +0 -0
  200. {cmdop-0.1.19 → cmdop-0.1.21}/src/cmdop/transport/local.py +0 -0
  201. {cmdop-0.1.19 → cmdop-0.1.21}/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.19
3
+ Version: 0.1.21
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
@@ -204,6 +204,26 @@ with client.browser.create_session() as b:
204
204
  | `close_modal()` | Close dialogs |
205
205
  | `press_key(key, selector)` | Press keyboard key |
206
206
  | `get/set_cookies()` | Cookie management |
207
+ | `with_timeout(fn, sec, cleanup)` | Run with timeout, skip if hangs |
208
+
209
+ **with_timeout() - Smart timeout for long operations:**
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")
219
+
220
+ # Async
221
+ result, ok = await browser.with_timeout(
222
+ process_item(browser, item), # coroutine
223
+ timeout_sec=60,
224
+ on_timeout=lambda: browser.press_key('Escape'),
225
+ )
226
+ ```
207
227
 
208
228
  **scroll() parameters:**
209
229
  - `direction`: "up", "down", "left", "right"
@@ -163,6 +163,26 @@ with client.browser.create_session() as b:
163
163
  | `close_modal()` | Close dialogs |
164
164
  | `press_key(key, selector)` | Press keyboard key |
165
165
  | `get/set_cookies()` | Cookie management |
166
+ | `with_timeout(fn, sec, cleanup)` | Run with timeout, skip if hangs |
167
+
168
+ **with_timeout() - Smart timeout for long operations:**
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")
178
+
179
+ # Async
180
+ result, ok = await browser.with_timeout(
181
+ process_item(browser, item), # coroutine
182
+ timeout_sec=60,
183
+ on_timeout=lambda: browser.press_key('Escape'),
184
+ )
185
+ ```
166
186
 
167
187
  **scroll() parameters:**
168
188
  - `direction`: "up", "down", "left", "right"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cmdop"
3
- version = "0.1.19"
3
+ version = "0.1.21"
4
4
  description = "Python SDK for CMDOP agent interaction"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -124,7 +124,7 @@ from cmdop.logging import (
124
124
  get_log_dir,
125
125
  )
126
126
 
127
- __version__ = "0.1.19"
127
+ __version__ = "0.1.21"
128
128
 
129
129
  __all__ = [
130
130
  # Version
@@ -0,0 +1,133 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: rpc_messages/browser.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'rpc_messages/browser.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1arpc_messages/browser.proto\x12\x08terminal\"\x9b\x01\n\x1b\x42rowserCreateSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x10\n\x08provider\x18\x02 \x01(\t\x12\x12\n\nprofile_id\x18\x03 \x01(\t\x12\x11\n\tstart_url\x18\x04 \x01(\t\x12\x10\n\x08headless\x18\x05 \x01(\x08\x12\r\n\x05width\x18\x06 \x01(\x05\x12\x0e\n\x06height\x18\x07 \x01(\x05\"Z\n\x1c\x42rowserCreateSessionResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"L\n\x1a\x42rowserCloseSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\"=\n\x1b\x42rowserCloseSessionResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"i\n\x16\x42rowserNavigateRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x0b\n\x03url\x18\x03 \x01(\t\x12\x12\n\ntimeout_ms\x18\x04 \x01(\x05\"L\n\x17\x42rowserNavigateResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x11\n\tfinal_url\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"\x80\x01\n\x13\x42rowserClickRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\x12\x12\n\ntimeout_ms\x18\x04 \x01(\x05\x12\x13\n\x0bmove_cursor\x18\x05 \x01(\x08\"6\n\x14\x42rowserClickResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x8d\x01\n\x12\x42rowserTypeRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\x12\x0c\n\x04text\x18\x04 \x01(\t\x12\x12\n\nhuman_like\x18\x05 \x01(\x08\x12\x13\n\x0b\x63lear_first\x18\x06 \x01(\x08\"5\n\x13\x42rowserTypeResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"j\n\x12\x42rowserWaitRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\x12\x12\n\ntimeout_ms\x18\x04 \x01(\x05\"D\n\x13\x42rowserWaitResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x66ound\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"{\n\x15\x42rowserExtractRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\x12\x11\n\tattribute\x18\x04 \x01(\t\x12\r\n\x05limit\x18\x05 \x01(\x05\"W\n\x16\x42rowserExtractResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0e\n\x06values\x18\x02 \x03(\t\x12\r\n\x05\x63ount\x18\x03 \x01(\x05\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"\x7f\n\x1a\x42rowserExtractRegexRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x0f\n\x07pattern\x18\x03 \x01(\t\x12\x11\n\tfrom_html\x18\x04 \x01(\x08\x12\r\n\x05limit\x18\x05 \x01(\x05\"]\n\x1b\x42rowserExtractRegexResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07matches\x18\x02 \x03(\t\x12\r\n\x05\x63ount\x18\x03 \x01(\x05\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"Y\n\x15\x42rowserGetHTMLRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\"F\n\x16\x42rowserGetHTMLResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0c\n\x04html\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"Y\n\x15\x42rowserGetTextRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\"F\n\x16\x42rowserGetTextResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"]\n\x1b\x42rowserExecuteScriptRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x0e\n\x06script\x18\x03 \x01(\t\"N\n\x1c\x42rowserExecuteScriptResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"~\n\x18\x42rowserScreenshotRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x11\n\tfull_page\x18\x03 \x01(\x08\x12\x0e\n\x06\x66ormat\x18\x04 \x01(\t\x12\x0f\n\x07quality\x18\x05 \x01(\x05\"I\n\x19\x42rowserScreenshotResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"H\n\x16\x42rowserGetStateRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\"U\n\x17\x42rowserGetStateResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\r\n\x05title\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"K\n\x19\x42rowserGetPageInfoRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\"\xbb\x03\n\x1a\x42rowserGetPageInfoResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0b\n\x03url\x18\x03 \x01(\t\x12\r\n\x05title\x18\x04 \x01(\t\x12\x13\n\x0bpage_height\x18\x05 \x01(\x05\x12\x17\n\x0fviewport_height\x18\x06 \x01(\x05\x12\x16\n\x0eviewport_width\x18\x07 \x01(\x05\x12\x10\n\x08scroll_x\x18\x08 \x01(\x05\x12\x10\n\x08scroll_y\x18\t \x01(\x05\x12\x0e\n\x06\x61t_top\x18\n \x01(\x08\x12\x11\n\tat_bottom\x18\x0b \x01(\x08\x12\x14\n\x0cload_time_ms\x18\x0c \x01(\x05\x12\x15\n\rcookies_count\x18\r \x01(\x05\x12\x10\n\x08is_https\x18\x0e \x01(\x08\x12\x13\n\x0bhas_iframes\x18\x0f \x01(\x08\x12\x15\n\rdom_nodes_raw\x18\x10 \x01(\x05\x12\x19\n\x11\x64om_nodes_cleaned\x18\x11 \x01(\x05\x12\x17\n\x0ftokens_estimate\x18\x12 \x01(\x05\x12\x1b\n\x13\x63loudflare_detected\x18\x13 \x01(\x08\x12\x18\n\x10\x63\x61ptcha_detected\x18\x14 \x01(\x08\"\x91\x01\n\rBrowserCookie\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\x12\x0e\n\x06\x64omain\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0e\n\x06secure\x18\x05 \x01(\x08\x12\x11\n\thttp_only\x18\x06 \x01(\x08\x12\x11\n\tsame_site\x18\x07 \x01(\t\x12\x0f\n\x07\x65xpires\x18\x08 \x01(\x03\"t\n\x18\x42rowserSetCookiesRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12(\n\x07\x63ookies\x18\x03 \x03(\x0b\x32\x17.terminal.BrowserCookie\";\n\x19\x42rowserSetCookiesResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"Z\n\x18\x42rowserGetCookiesRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x0e\n\x06\x64omain\x18\x03 \x01(\t\"e\n\x19\x42rowserGetCookiesResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12(\n\x07\x63ookies\x18\x02 \x03(\x0b\x32\x17.terminal.BrowserCookie\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"n\n\x17\x42rowserMouseMoveRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\t\n\x01x\x18\x03 \x01(\x05\x12\t\n\x01y\x18\x04 \x01(\x05\x12\r\n\x05steps\x18\x05 \x01(\x05\":\n\x18\x42rowserMouseMoveResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"k\n\x13\x42rowserHoverRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\x12\x12\n\ntimeout_ms\x18\x04 \x01(\x05\"6\n\x14\x42rowserHoverResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x9e\x01\n\x14\x42rowserScrollRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x11\n\tdirection\x18\x03 \x01(\t\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x05\x12\x10\n\x08selector\x18\x05 \x01(\t\x12\x11\n\tcontainer\x18\x06 \x01(\t\x12\x0e\n\x06smooth\x18\x07 \x01(\x08\"\xb1\x01\n\x15\x42rowserScrollResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x10\n\x08scroll_x\x18\x02 \x01(\x05\x12\x10\n\x08scroll_y\x18\x03 \x01(\x05\x12\x13\n\x0bscrolled_by\x18\x04 \x01(\x05\x12\x13\n\x0bpage_height\x18\x05 \x01(\x05\x12\x17\n\x0fviewport_height\x18\x06 \x01(\x05\x12\x11\n\tat_bottom\x18\x07 \x01(\x08\x12\r\n\x05\x65rror\x18\x08 \x01(\t\"\xd5\x01\n\x1f\x42rowserValidateSelectorsRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x0c\n\x04item\x18\x03 \x01(\t\x12\x45\n\x06\x66ields\x18\x04 \x03(\x0b\x32\x35.terminal.BrowserValidateSelectorsRequest.FieldsEntry\x1a-\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd2\x02\n BrowserValidateSelectorsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05valid\x18\x02 \x01(\x08\x12\x46\n\x06\x63ounts\x18\x03 \x03(\x0b\x32\x36.terminal.BrowserValidateSelectorsResponse.CountsEntry\x12H\n\x07samples\x18\x04 \x03(\x0b\x32\x37.terminal.BrowserValidateSelectorsResponse.SamplesEntry\x12\x0e\n\x06\x65rrors\x18\x05 \x03(\t\x12\r\n\x05\x65rror\x18\x06 \x01(\t\x1a-\n\x0b\x43ountsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a.\n\x0cSamplesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"}\n\x19\x42rowserExtractDataRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1a\n\x12\x62rowser_session_id\x18\x02 \x01(\t\x12\x0c\n\x04item\x18\x03 \x01(\t\x12\x13\n\x0b\x66ields_json\x18\x04 \x01(\t\x12\r\n\x05limit\x18\x05 \x01(\x05\"_\n\x1a\x42rowserExtractDataResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x12\n\nitems_json\x18\x02 \x01(\t\x12\r\n\x05\x63ount\x18\x03 \x01(\x05\x12\r\n\x05\x65rror\x18\x04 \x01(\tB\x10Z\x0eterminal/protob\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'rpc_messages.browser_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ _globals['DESCRIPTOR']._loaded_options = None
34
+ _globals['DESCRIPTOR']._serialized_options = b'Z\016terminal/proto'
35
+ _globals['_BROWSERVALIDATESELECTORSREQUEST_FIELDSENTRY']._loaded_options = None
36
+ _globals['_BROWSERVALIDATESELECTORSREQUEST_FIELDSENTRY']._serialized_options = b'8\001'
37
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE_COUNTSENTRY']._loaded_options = None
38
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE_COUNTSENTRY']._serialized_options = b'8\001'
39
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE_SAMPLESENTRY']._loaded_options = None
40
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE_SAMPLESENTRY']._serialized_options = b'8\001'
41
+ _globals['_BROWSERCREATESESSIONREQUEST']._serialized_start=41
42
+ _globals['_BROWSERCREATESESSIONREQUEST']._serialized_end=196
43
+ _globals['_BROWSERCREATESESSIONRESPONSE']._serialized_start=198
44
+ _globals['_BROWSERCREATESESSIONRESPONSE']._serialized_end=288
45
+ _globals['_BROWSERCLOSESESSIONREQUEST']._serialized_start=290
46
+ _globals['_BROWSERCLOSESESSIONREQUEST']._serialized_end=366
47
+ _globals['_BROWSERCLOSESESSIONRESPONSE']._serialized_start=368
48
+ _globals['_BROWSERCLOSESESSIONRESPONSE']._serialized_end=429
49
+ _globals['_BROWSERNAVIGATEREQUEST']._serialized_start=431
50
+ _globals['_BROWSERNAVIGATEREQUEST']._serialized_end=536
51
+ _globals['_BROWSERNAVIGATERESPONSE']._serialized_start=538
52
+ _globals['_BROWSERNAVIGATERESPONSE']._serialized_end=614
53
+ _globals['_BROWSERCLICKREQUEST']._serialized_start=617
54
+ _globals['_BROWSERCLICKREQUEST']._serialized_end=745
55
+ _globals['_BROWSERCLICKRESPONSE']._serialized_start=747
56
+ _globals['_BROWSERCLICKRESPONSE']._serialized_end=801
57
+ _globals['_BROWSERTYPEREQUEST']._serialized_start=804
58
+ _globals['_BROWSERTYPEREQUEST']._serialized_end=945
59
+ _globals['_BROWSERTYPERESPONSE']._serialized_start=947
60
+ _globals['_BROWSERTYPERESPONSE']._serialized_end=1000
61
+ _globals['_BROWSERWAITREQUEST']._serialized_start=1002
62
+ _globals['_BROWSERWAITREQUEST']._serialized_end=1108
63
+ _globals['_BROWSERWAITRESPONSE']._serialized_start=1110
64
+ _globals['_BROWSERWAITRESPONSE']._serialized_end=1178
65
+ _globals['_BROWSEREXTRACTREQUEST']._serialized_start=1180
66
+ _globals['_BROWSEREXTRACTREQUEST']._serialized_end=1303
67
+ _globals['_BROWSEREXTRACTRESPONSE']._serialized_start=1305
68
+ _globals['_BROWSEREXTRACTRESPONSE']._serialized_end=1392
69
+ _globals['_BROWSEREXTRACTREGEXREQUEST']._serialized_start=1394
70
+ _globals['_BROWSEREXTRACTREGEXREQUEST']._serialized_end=1521
71
+ _globals['_BROWSEREXTRACTREGEXRESPONSE']._serialized_start=1523
72
+ _globals['_BROWSEREXTRACTREGEXRESPONSE']._serialized_end=1616
73
+ _globals['_BROWSERGETHTMLREQUEST']._serialized_start=1618
74
+ _globals['_BROWSERGETHTMLREQUEST']._serialized_end=1707
75
+ _globals['_BROWSERGETHTMLRESPONSE']._serialized_start=1709
76
+ _globals['_BROWSERGETHTMLRESPONSE']._serialized_end=1779
77
+ _globals['_BROWSERGETTEXTREQUEST']._serialized_start=1781
78
+ _globals['_BROWSERGETTEXTREQUEST']._serialized_end=1870
79
+ _globals['_BROWSERGETTEXTRESPONSE']._serialized_start=1872
80
+ _globals['_BROWSERGETTEXTRESPONSE']._serialized_end=1942
81
+ _globals['_BROWSEREXECUTESCRIPTREQUEST']._serialized_start=1944
82
+ _globals['_BROWSEREXECUTESCRIPTREQUEST']._serialized_end=2037
83
+ _globals['_BROWSEREXECUTESCRIPTRESPONSE']._serialized_start=2039
84
+ _globals['_BROWSEREXECUTESCRIPTRESPONSE']._serialized_end=2117
85
+ _globals['_BROWSERSCREENSHOTREQUEST']._serialized_start=2119
86
+ _globals['_BROWSERSCREENSHOTREQUEST']._serialized_end=2245
87
+ _globals['_BROWSERSCREENSHOTRESPONSE']._serialized_start=2247
88
+ _globals['_BROWSERSCREENSHOTRESPONSE']._serialized_end=2320
89
+ _globals['_BROWSERGETSTATEREQUEST']._serialized_start=2322
90
+ _globals['_BROWSERGETSTATEREQUEST']._serialized_end=2394
91
+ _globals['_BROWSERGETSTATERESPONSE']._serialized_start=2396
92
+ _globals['_BROWSERGETSTATERESPONSE']._serialized_end=2481
93
+ _globals['_BROWSERGETPAGEINFOREQUEST']._serialized_start=2483
94
+ _globals['_BROWSERGETPAGEINFOREQUEST']._serialized_end=2558
95
+ _globals['_BROWSERGETPAGEINFORESPONSE']._serialized_start=2561
96
+ _globals['_BROWSERGETPAGEINFORESPONSE']._serialized_end=3004
97
+ _globals['_BROWSERCOOKIE']._serialized_start=3007
98
+ _globals['_BROWSERCOOKIE']._serialized_end=3152
99
+ _globals['_BROWSERSETCOOKIESREQUEST']._serialized_start=3154
100
+ _globals['_BROWSERSETCOOKIESREQUEST']._serialized_end=3270
101
+ _globals['_BROWSERSETCOOKIESRESPONSE']._serialized_start=3272
102
+ _globals['_BROWSERSETCOOKIESRESPONSE']._serialized_end=3331
103
+ _globals['_BROWSERGETCOOKIESREQUEST']._serialized_start=3333
104
+ _globals['_BROWSERGETCOOKIESREQUEST']._serialized_end=3423
105
+ _globals['_BROWSERGETCOOKIESRESPONSE']._serialized_start=3425
106
+ _globals['_BROWSERGETCOOKIESRESPONSE']._serialized_end=3526
107
+ _globals['_BROWSERMOUSEMOVEREQUEST']._serialized_start=3528
108
+ _globals['_BROWSERMOUSEMOVEREQUEST']._serialized_end=3638
109
+ _globals['_BROWSERMOUSEMOVERESPONSE']._serialized_start=3640
110
+ _globals['_BROWSERMOUSEMOVERESPONSE']._serialized_end=3698
111
+ _globals['_BROWSERHOVERREQUEST']._serialized_start=3700
112
+ _globals['_BROWSERHOVERREQUEST']._serialized_end=3807
113
+ _globals['_BROWSERHOVERRESPONSE']._serialized_start=3809
114
+ _globals['_BROWSERHOVERRESPONSE']._serialized_end=3863
115
+ _globals['_BROWSERSCROLLREQUEST']._serialized_start=3866
116
+ _globals['_BROWSERSCROLLREQUEST']._serialized_end=4024
117
+ _globals['_BROWSERSCROLLRESPONSE']._serialized_start=4027
118
+ _globals['_BROWSERSCROLLRESPONSE']._serialized_end=4204
119
+ _globals['_BROWSERVALIDATESELECTORSREQUEST']._serialized_start=4207
120
+ _globals['_BROWSERVALIDATESELECTORSREQUEST']._serialized_end=4420
121
+ _globals['_BROWSERVALIDATESELECTORSREQUEST_FIELDSENTRY']._serialized_start=4375
122
+ _globals['_BROWSERVALIDATESELECTORSREQUEST_FIELDSENTRY']._serialized_end=4420
123
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE']._serialized_start=4423
124
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE']._serialized_end=4761
125
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE_COUNTSENTRY']._serialized_start=4668
126
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE_COUNTSENTRY']._serialized_end=4713
127
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE_SAMPLESENTRY']._serialized_start=4715
128
+ _globals['_BROWSERVALIDATESELECTORSRESPONSE_SAMPLESENTRY']._serialized_end=4761
129
+ _globals['_BROWSEREXTRACTDATAREQUEST']._serialized_start=4763
130
+ _globals['_BROWSEREXTRACTDATAREQUEST']._serialized_end=4888
131
+ _globals['_BROWSEREXTRACTDATARESPONSE']._serialized_start=4890
132
+ _globals['_BROWSEREXTRACTDATARESPONSE']._serialized_end=4985
133
+ # @@protoc_insertion_point(module_scope)
@@ -73,16 +73,18 @@ class BrowserNavigateResponse(_message.Message):
73
73
  def __init__(self, success: bool = ..., final_url: _Optional[str] = ..., error: _Optional[str] = ...) -> None: ...
74
74
 
75
75
  class BrowserClickRequest(_message.Message):
76
- __slots__ = ("session_id", "browser_session_id", "selector", "timeout_ms")
76
+ __slots__ = ("session_id", "browser_session_id", "selector", "timeout_ms", "move_cursor")
77
77
  SESSION_ID_FIELD_NUMBER: _ClassVar[int]
78
78
  BROWSER_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
79
79
  SELECTOR_FIELD_NUMBER: _ClassVar[int]
80
80
  TIMEOUT_MS_FIELD_NUMBER: _ClassVar[int]
81
+ MOVE_CURSOR_FIELD_NUMBER: _ClassVar[int]
81
82
  session_id: str
82
83
  browser_session_id: str
83
84
  selector: str
84
85
  timeout_ms: int
85
- def __init__(self, session_id: _Optional[str] = ..., browser_session_id: _Optional[str] = ..., selector: _Optional[str] = ..., timeout_ms: _Optional[int] = ...) -> None: ...
86
+ move_cursor: bool
87
+ def __init__(self, session_id: _Optional[str] = ..., browser_session_id: _Optional[str] = ..., selector: _Optional[str] = ..., timeout_ms: _Optional[int] = ..., move_cursor: bool = ...) -> None: ...
86
88
 
87
89
  class BrowserClickResponse(_message.Message):
88
90
  __slots__ = ("success", "error")
@@ -88,11 +88,16 @@ class AsyncBrowserService(BaseService, BaseServiceMixin):
88
88
 
89
89
  return response.final_url
90
90
 
91
- async def click(self, session_id: str, selector: str, timeout_ms: int = 5000) -> None:
91
+ async def click(
92
+ self, session_id: str, selector: str, timeout_ms: int = 5000, move_cursor: bool = False
93
+ ) -> None:
92
94
  from cmdop._generated.rpc_messages.browser_pb2 import BrowserClickRequest
93
95
 
94
96
  request = BrowserClickRequest(
95
- browser_session_id=session_id, selector=selector, timeout_ms=timeout_ms
97
+ browser_session_id=session_id,
98
+ selector=selector,
99
+ timeout_ms=timeout_ms,
100
+ move_cursor=move_cursor,
96
101
  )
97
102
  response = await self._call_async(self._get_stub.BrowserClick, request)
98
103
 
@@ -2,10 +2,16 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any
5
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, TypeVar
6
6
 
7
7
  import asyncio
8
8
 
9
+ from cmdop.logging import get_logger
10
+
11
+ _log = get_logger("cmdop.browser")
12
+
13
+ T = TypeVar("T")
14
+
9
15
  from cmdop.services.browser.base.session import BaseSession
10
16
  from cmdop.services.browser.models import (
11
17
  BrowserCookie,
@@ -58,9 +64,7 @@ class AsyncBrowserSession(BaseSession):
58
64
  timeout_ms: Timeout in milliseconds
59
65
  move_cursor: If True, move cursor to element before clicking (human-like)
60
66
  """
61
- if move_cursor:
62
- await self.hover(selector, timeout_ms)
63
- await self._service.click(self._session_id, selector, timeout_ms)
67
+ await self._service.click(self._session_id, selector, timeout_ms, move_cursor)
64
68
 
65
69
  async def type(
66
70
  self,
@@ -295,15 +299,78 @@ class AsyncBrowserSession(BaseSession):
295
299
  data = parse_json_result(result) or {}
296
300
  return data.get("success", False)
297
301
 
298
- async def wait_seconds(self, seconds: float) -> None:
299
- """Wait for specified seconds."""
300
- await asyncio.sleep(seconds)
302
+ async def wait(self, ms: int, jitter: float = 0.1) -> None:
303
+ """
304
+ Wait for specified milliseconds with optional jitter.
305
+
306
+ Args:
307
+ ms: Wait time in milliseconds
308
+ jitter: Random variation ±jitter (default 10%, so 1000ms becomes 900-1100ms)
309
+ """
310
+ import random
311
+ actual = (ms / 1000) * (1 + random.uniform(-jitter, jitter))
312
+ await asyncio.sleep(actual)
313
+
314
+ async def wait_seconds(self, seconds: float, jitter: float = 0.1) -> None:
315
+ """
316
+ Wait for specified seconds with optional jitter.
317
+
318
+ Args:
319
+ seconds: Base wait time in seconds
320
+ jitter: Random variation ±jitter (default 10%, so 1.0s becomes 0.9-1.1s)
321
+ """
322
+ await self.wait(int(seconds * 1000), jitter)
301
323
 
302
324
  async def wait_random(self, min_sec: float = 0.5, max_sec: float = 1.5) -> None:
303
325
  """Wait for random time between min and max seconds."""
304
326
  import random
305
327
  await asyncio.sleep(min_sec + random.random() * (max_sec - min_sec))
306
328
 
329
+ async def with_timeout(
330
+ self,
331
+ coro: Awaitable[T],
332
+ timeout_sec: float = 60.0,
333
+ on_timeout: Callable[[], Awaitable[None]] | None = None,
334
+ ) -> tuple[T | None, bool]:
335
+ """
336
+ Run a coroutine with a timeout. Skips if it hangs.
337
+
338
+ Args:
339
+ coro: Coroutine to run
340
+ timeout_sec: Timeout in seconds (default 60)
341
+ on_timeout: Optional async cleanup function to call on timeout
342
+
343
+ Returns:
344
+ Tuple of (result, success). If timeout, returns (None, False).
345
+
346
+ Example:
347
+ # Simple usage
348
+ result, ok = await browser.with_timeout(
349
+ process_listing(browser, listing),
350
+ timeout_sec=30,
351
+ )
352
+ if not ok:
353
+ print("Skipped due to timeout")
354
+
355
+ # With cleanup
356
+ result, ok = await browser.with_timeout(
357
+ enrich_listing(browser, item),
358
+ timeout_sec=60,
359
+ on_timeout=lambda: browser.press_key('Escape'),
360
+ )
361
+ """
362
+ try:
363
+ result = await asyncio.wait_for(coro, timeout=timeout_sec)
364
+ return result, True
365
+ except asyncio.TimeoutError:
366
+ _log.warning("[timeout] Coroutine timed out after %.1fs", timeout_sec)
367
+ if on_timeout:
368
+ try:
369
+ await on_timeout()
370
+ except Exception as e:
371
+ _log.debug("[timeout] Cleanup failed: %s", e)
372
+ return None, False
373
+
307
374
  async def click_all_by_text(self, text: str, role: str = "button") -> int:
308
375
  """Click all elements containing specific text."""
309
376
  js = self._build_click_all_by_text(text, role)
@@ -88,11 +88,16 @@ class BrowserService(BaseService, BaseServiceMixin):
88
88
 
89
89
  return response.final_url
90
90
 
91
- def click(self, session_id: str, selector: str, timeout_ms: int = 5000) -> None:
91
+ def click(
92
+ self, session_id: str, selector: str, timeout_ms: int = 5000, move_cursor: bool = False
93
+ ) -> None:
92
94
  from cmdop._generated.rpc_messages.browser_pb2 import BrowserClickRequest
93
95
 
94
96
  request = BrowserClickRequest(
95
- browser_session_id=session_id, selector=selector, timeout_ms=timeout_ms
97
+ browser_session_id=session_id,
98
+ selector=selector,
99
+ timeout_ms=timeout_ms,
100
+ move_cursor=move_cursor,
96
101
  )
97
102
  response = self._call_sync(self._get_stub.BrowserClick, request)
98
103
 
@@ -3,7 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import time
6
- from typing import TYPE_CHECKING, Any, Callable
6
+ import threading
7
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
8
+
9
+ T = TypeVar("T")
7
10
 
8
11
  from cmdop.logging import get_logger
9
12
  from cmdop.services.browser.base.session import BaseSession
@@ -72,11 +75,8 @@ class BrowserSession(BaseSession):
72
75
  timeout_ms: Timeout in milliseconds
73
76
  move_cursor: If True, move cursor to element before clicking (human-like)
74
77
  """
75
- if move_cursor:
76
- _log.debug("[click] moving cursor to %s", selector[:60])
77
- self.hover(selector, timeout_ms)
78
- _log.debug("[click] %s", selector[:60])
79
- self._service.click(self._session_id, selector, timeout_ms)
78
+ _log.debug("[click] %s%s", selector[:60], " (move_cursor)" if move_cursor else "")
79
+ self._service.click(self._session_id, selector, timeout_ms, move_cursor)
80
80
 
81
81
  def type(
82
82
  self,
@@ -494,15 +494,97 @@ class BrowserSession(BaseSession):
494
494
  data = parse_json_result(result) or {}
495
495
  return data.get("success", False)
496
496
 
497
- def wait_seconds(self, seconds: float) -> None:
498
- """Wait for specified seconds."""
499
- time.sleep(seconds)
497
+ def wait(self, ms: int, jitter: float = 0.1) -> None:
498
+ """
499
+ Wait for specified milliseconds with optional jitter.
500
+
501
+ Args:
502
+ ms: Wait time in milliseconds
503
+ jitter: Random variation ±jitter (default 10%, so 1000ms becomes 900-1100ms)
504
+ """
505
+ import random
506
+ actual = (ms / 1000) * (1 + random.uniform(-jitter, jitter))
507
+ time.sleep(actual)
508
+
509
+ def wait_seconds(self, seconds: float, jitter: float = 0.1) -> None:
510
+ """
511
+ Wait for specified seconds with optional jitter.
512
+
513
+ Args:
514
+ seconds: Base wait time in seconds
515
+ jitter: Random variation ±jitter (default 10%, so 1.0s becomes 0.9-1.1s)
516
+ """
517
+ self.wait(int(seconds * 1000), jitter)
500
518
 
501
519
  def wait_random(self, min_sec: float = 0.5, max_sec: float = 1.5) -> None:
502
520
  """Wait for random time between min and max seconds."""
503
521
  import random
504
522
  time.sleep(min_sec + random.random() * (max_sec - min_sec))
505
523
 
524
+ def with_timeout(
525
+ self,
526
+ fn: Callable[[], T],
527
+ timeout_sec: float = 60.0,
528
+ on_timeout: Callable[[], None] | None = None,
529
+ ) -> tuple[T | None, bool]:
530
+ """
531
+ Run a function with a timeout. Skips if it hangs.
532
+
533
+ Args:
534
+ fn: Function to run (no arguments, use lambda/closure for args)
535
+ timeout_sec: Timeout in seconds (default 60)
536
+ on_timeout: Optional cleanup function to call on timeout
537
+
538
+ Returns:
539
+ Tuple of (result, success). If timeout, returns (None, False).
540
+
541
+ Example:
542
+ # Simple usage
543
+ result, ok = browser.with_timeout(
544
+ lambda: process_listing(browser, listing),
545
+ timeout_sec=30,
546
+ )
547
+ if not ok:
548
+ print("Skipped due to timeout")
549
+ continue
550
+
551
+ # With cleanup
552
+ result, ok = browser.with_timeout(
553
+ lambda: enrich_listing(browser, item),
554
+ timeout_sec=60,
555
+ on_timeout=lambda: browser.press_key('Escape'),
556
+ )
557
+ """
558
+ result_container: list[T | None] = [None]
559
+ exception_container: list[Exception | None] = [None]
560
+ completed = threading.Event()
561
+
562
+ def target() -> None:
563
+ try:
564
+ result_container[0] = fn()
565
+ except Exception as e:
566
+ exception_container[0] = e
567
+ finally:
568
+ completed.set()
569
+
570
+ thread = threading.Thread(target=target, daemon=True)
571
+ thread.start()
572
+
573
+ if completed.wait(timeout=timeout_sec):
574
+ # Completed in time
575
+ if exception_container[0]:
576
+ raise exception_container[0]
577
+ return result_container[0], True
578
+ else:
579
+ # Timeout - run cleanup if provided
580
+ _log.warning("[timeout] Function timed out after %.1fs", timeout_sec)
581
+ if on_timeout:
582
+ try:
583
+ on_timeout()
584
+ except Exception as e:
585
+ _log.debug("[timeout] Cleanup failed: %s", e)
586
+ return None, False
587
+
506
588
  def click_all_by_text(self, text: str, role: str = "button") -> int:
507
589
  """
508
590
  Click all elements containing specific text.