cmdop 0.1.16__tar.gz → 0.1.17__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 (203) hide show
  1. cmdop-0.1.17/PKG-INFO +249 -0
  2. cmdop-0.1.17/README.md +208 -0
  3. {cmdop-0.1.16 → cmdop-0.1.17}/pyproject.toml +6 -1
  4. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/__init__.py +1 -1
  5. cmdop-0.1.17/src/cmdop/services/browser/js/__init__.py +49 -0
  6. cmdop-0.1.17/src/cmdop/services/browser/js/core.py +38 -0
  7. cmdop-0.1.16/src/cmdop/services/browser/js.py → cmdop-0.1.17/src/cmdop/services/browser/js/fetch.py +1 -34
  8. cmdop-0.1.17/src/cmdop/services/browser/js/interaction.py +109 -0
  9. cmdop-0.1.17/src/cmdop/services/browser/js/scroll.py +133 -0
  10. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/models.py +32 -0
  11. cmdop-0.1.17/src/cmdop/services/browser/parsing.py +104 -0
  12. cmdop-0.1.17/src/cmdop/services/browser/sync/session.py +432 -0
  13. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/transport/discovery.py +25 -1
  14. cmdop-0.1.16/PKG-INFO +0 -464
  15. cmdop-0.1.16/README.md +0 -426
  16. cmdop-0.1.16/src/cmdop/services/browser/sync/session.py +0 -171
  17. {cmdop-0.1.16 → cmdop-0.1.17}/.gitignore +0 -0
  18. {cmdop-0.1.16 → cmdop-0.1.17}/LICENSE +0 -0
  19. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/__init__.py +0 -0
  20. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/agent_messages_pb2.py +0 -0
  21. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/agent_messages_pb2.pyi +0 -0
  22. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/agent_messages_pb2_grpc.py +0 -0
  23. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/common_types_pb2.py +0 -0
  24. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/common_types_pb2.pyi +0 -0
  25. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/common_types_pb2_grpc.py +0 -0
  26. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/control_messages_pb2.py +0 -0
  27. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/control_messages_pb2.pyi +0 -0
  28. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/control_messages_pb2_grpc.py +0 -0
  29. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/__init__.py +0 -0
  30. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/archive_pb2.py +0 -0
  31. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/archive_pb2.pyi +0 -0
  32. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/archive_pb2_grpc.py +0 -0
  33. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/changes_pb2.py +0 -0
  34. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/changes_pb2.pyi +0 -0
  35. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/changes_pb2_grpc.py +0 -0
  36. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/common_pb2.py +0 -0
  37. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/common_pb2.pyi +0 -0
  38. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/common_pb2_grpc.py +0 -0
  39. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/directory_pb2.py +0 -0
  40. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/directory_pb2.pyi +0 -0
  41. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/directory_pb2_grpc.py +0 -0
  42. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/file_crud_pb2.py +0 -0
  43. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/file_crud_pb2.pyi +0 -0
  44. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/file_crud_pb2_grpc.py +0 -0
  45. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/hls_pb2.py +0 -0
  46. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/hls_pb2.pyi +0 -0
  47. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/hls_pb2_grpc.py +0 -0
  48. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/requests_pb2.py +0 -0
  49. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/requests_pb2.pyi +0 -0
  50. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/requests_pb2_grpc.py +0 -0
  51. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/search_pb2.py +0 -0
  52. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/search_pb2.pyi +0 -0
  53. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/search_pb2_grpc.py +0 -0
  54. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/transfer_pb2.py +0 -0
  55. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/transfer_pb2.pyi +0 -0
  56. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations/transfer_pb2_grpc.py +0 -0
  57. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations_pb2.py +0 -0
  58. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations_pb2.pyi +0 -0
  59. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_operations_pb2_grpc.py +0 -0
  60. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/__init__.py +0 -0
  61. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/archive_pb2.py +0 -0
  62. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/archive_pb2.pyi +0 -0
  63. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/archive_pb2_grpc.py +0 -0
  64. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/directory_pb2.py +0 -0
  65. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/directory_pb2.pyi +0 -0
  66. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/directory_pb2_grpc.py +0 -0
  67. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/file_crud_pb2.py +0 -0
  68. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/file_crud_pb2.pyi +0 -0
  69. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/file_crud_pb2_grpc.py +0 -0
  70. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/hls_pb2.py +0 -0
  71. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/hls_pb2.pyi +0 -0
  72. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/hls_pb2_grpc.py +0 -0
  73. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/search_pb2.py +0 -0
  74. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/search_pb2.pyi +0 -0
  75. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc/search_pb2_grpc.py +0 -0
  76. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc_pb2.py +0 -0
  77. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc_pb2.pyi +0 -0
  78. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/file_rpc_pb2_grpc.py +0 -0
  79. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/message_pool.py +0 -0
  80. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/py.typed +0 -0
  81. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/__init__.py +0 -0
  82. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/agent_pb2.py +0 -0
  83. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/agent_pb2.pyi +0 -0
  84. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/agent_pb2_grpc.py +0 -0
  85. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/browser_pb2.py +0 -0
  86. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/browser_pb2.pyi +0 -0
  87. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/browser_pb2_grpc.py +0 -0
  88. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/device_pb2.py +0 -0
  89. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/device_pb2.pyi +0 -0
  90. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/device_pb2_grpc.py +0 -0
  91. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/extract_pb2.py +0 -0
  92. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/extract_pb2.pyi +0 -0
  93. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/extract_pb2_grpc.py +0 -0
  94. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/health_pb2.py +0 -0
  95. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/health_pb2.pyi +0 -0
  96. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/health_pb2_grpc.py +0 -0
  97. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/history_pb2.py +0 -0
  98. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/history_pb2.pyi +0 -0
  99. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/history_pb2_grpc.py +0 -0
  100. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.py +0 -0
  101. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/lifecycle_pb2.pyi +0 -0
  102. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/lifecycle_pb2_grpc.py +0 -0
  103. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/push_pb2.py +0 -0
  104. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/push_pb2.pyi +0 -0
  105. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/push_pb2_grpc.py +0 -0
  106. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/session_pb2.py +0 -0
  107. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/session_pb2.pyi +0 -0
  108. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/session_pb2_grpc.py +0 -0
  109. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/terminal_pb2.py +0 -0
  110. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/terminal_pb2.pyi +0 -0
  111. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages/terminal_pb2_grpc.py +0 -0
  112. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages_pb2.py +0 -0
  113. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages_pb2.pyi +0 -0
  114. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/rpc_messages_pb2_grpc.py +0 -0
  115. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/service_pb2.py +0 -0
  116. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/service_pb2.pyi +0 -0
  117. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/service_pb2_grpc.py +0 -0
  118. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/tunnel_pb2.py +0 -0
  119. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/tunnel_pb2.pyi +0 -0
  120. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/_generated/tunnel_pb2_grpc.py +0 -0
  121. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/__init__.py +0 -0
  122. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/__init__.py +0 -0
  123. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/__init__.py +0 -0
  124. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/client.py +0 -0
  125. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/enums.py +0 -0
  126. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/logger.py +0 -0
  127. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/machines__api__machine_sharing/__init__.py +0 -0
  128. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/machines__api__machine_sharing/client.py +0 -0
  129. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/machines__api__machine_sharing/models.py +0 -0
  130. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/machines__api__machine_sharing/sync_client.py +0 -0
  131. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/machines__api__machines/__init__.py +0 -0
  132. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/machines__api__machines/client.py +0 -0
  133. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/machines__api__machines/models.py +0 -0
  134. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/machines__api__machines/sync_client.py +0 -0
  135. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/retry.py +0 -0
  136. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/schema.json +0 -0
  137. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/machines/sync_client.py +0 -0
  138. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/__init__.py +0 -0
  139. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/client.py +0 -0
  140. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/enums.py +0 -0
  141. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/logger.py +0 -0
  142. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/retry.py +0 -0
  143. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/schema.json +0 -0
  144. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/sync_client.py +0 -0
  145. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/system__api__oauth/__init__.py +0 -0
  146. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/system__api__oauth/client.py +0 -0
  147. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/system__api__oauth/models.py +0 -0
  148. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/system__api__oauth/sync_client.py +0 -0
  149. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/system__api__system/__init__.py +0 -0
  150. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/system__api__system/client.py +0 -0
  151. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/system__api__system/models.py +0 -0
  152. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/system/system__api__system/sync_client.py +0 -0
  153. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/__init__.py +0 -0
  154. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/client.py +0 -0
  155. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/enums.py +0 -0
  156. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/logger.py +0 -0
  157. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/retry.py +0 -0
  158. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/schema.json +0 -0
  159. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/sync_client.py +0 -0
  160. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/__init__.py +0 -0
  161. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/client.py +0 -0
  162. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/models.py +0 -0
  163. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/api/generated/workspaces/workspaces__api__workspaces/sync_client.py +0 -0
  164. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/client.py +0 -0
  165. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/config.py +0 -0
  166. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/discovery.py +0 -0
  167. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/exceptions.py +0 -0
  168. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/helpers/__init__.py +0 -0
  169. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/helpers/cleaner.py +0 -0
  170. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/helpers/formatting.py +0 -0
  171. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/logging.py +0 -0
  172. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/models/__init__.py +0 -0
  173. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/models/agent.py +0 -0
  174. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/models/base.py +0 -0
  175. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/models/config.py +0 -0
  176. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/models/extract.py +0 -0
  177. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/models/files.py +0 -0
  178. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/models/terminal.py +0 -0
  179. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/py.typed +0 -0
  180. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/__init__.py +0 -0
  181. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/agent.py +0 -0
  182. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/base.py +0 -0
  183. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/__init__.py +0 -0
  184. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/aio/__init__.py +0 -0
  185. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/aio/service.py +0 -0
  186. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/aio/session.py +0 -0
  187. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/base/__init__.py +0 -0
  188. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/base/service.py +0 -0
  189. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/base/session.py +0 -0
  190. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/sync/__init__.py +0 -0
  191. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/browser/sync/service.py +0 -0
  192. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/extract.py +0 -0
  193. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/files.py +0 -0
  194. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/services/terminal.py +0 -0
  195. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/streaming/__init__.py +0 -0
  196. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/streaming/base.py +0 -0
  197. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/streaming/handlers.py +0 -0
  198. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/streaming/terminal.py +0 -0
  199. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/transport/__init__.py +0 -0
  200. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/transport/auth.py +0 -0
  201. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/transport/base.py +0 -0
  202. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/transport/local.py +0 -0
  203. {cmdop-0.1.16 → cmdop-0.1.17}/src/cmdop/transport/remote.py +0 -0
cmdop-0.1.17/PKG-INFO ADDED
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmdop
3
+ Version: 0.1.17
4
+ Summary: Python SDK for CMDOP agent interaction
5
+ Project-URL: Homepage, https://cmdop.com
6
+ Project-URL: Documentation, https://cmdop.com
7
+ Project-URL: Repository, https://github.com/markolofsen/cmdop-client
8
+ Author: CMDOP Team
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,automation,cmdop,terminal
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: beautifulsoup4>=4.12.0
23
+ Requires-Dist: grpcio>=1.60.0
24
+ Requires-Dist: httpx>=0.27.0
25
+ Requires-Dist: lxml>=5.0.0
26
+ Requires-Dist: protobuf>=4.25.0
27
+ Requires-Dist: pydantic-settings>=2.0.0
28
+ Requires-Dist: pydantic>=2.5.0
29
+ Requires-Dist: rich>=13.0.0
30
+ Requires-Dist: toon-python>=0.1.2
31
+ Provides-Extra: dev
32
+ Requires-Dist: beautifulsoup4>=4.12.0; extra == 'dev'
33
+ Requires-Dist: grpcio-tools>=1.60.0; extra == 'dev'
34
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
35
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
36
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
37
+ Requires-Dist: pytest-grpc-aio>=0.3.0; extra == 'dev'
38
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
39
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # cmdop
43
+
44
+ **Any machine. One API.**
45
+
46
+ ```python
47
+ from cmdop import CMDOPClient
48
+
49
+ with CMDOPClient.remote(api_key="cmd_xxx") as server:
50
+ server.terminal.execute("docker restart app")
51
+ server.files.write("/etc/nginx/nginx.conf", new_config)
52
+ logs = server.files.read("/var/log/app.log")
53
+ ```
54
+
55
+ No SSH. No VPN. No open ports.
56
+
57
+ ---
58
+
59
+ ## How
60
+
61
+ ```
62
+ Your Code ──── Cloud Relay ──── Agent (on server)
63
+
64
+ Outbound only, works through any NAT/firewall
65
+ ```
66
+
67
+ Agent connects out. Your code connects to relay. Done.
68
+
69
+ ---
70
+
71
+ ## Install
72
+
73
+ ```bash
74
+ pip install cmdop
75
+ ```
76
+
77
+ ```python
78
+ from cmdop import CMDOPClient, AsyncCMDOPClient
79
+
80
+ # Remote (via cloud relay)
81
+ with CMDOPClient.remote(api_key="cmd_xxx") as client:
82
+ client.files.list("/home")
83
+
84
+ # Local (direct IPC)
85
+ with CMDOPClient.local() as client:
86
+ client.terminal.execute("ls -la")
87
+
88
+ # Async
89
+ async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
90
+ await client.files.read("/etc/hostname")
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Terminal
96
+
97
+ ```python
98
+ session = server.terminal.create()
99
+ server.terminal.send_input(session.session_id, "kubectl get pods\n")
100
+ output = server.terminal.get_history(session.session_id)
101
+ ```
102
+
103
+ | Method | Description |
104
+ |--------|-------------|
105
+ | `create(shell)` | Start session |
106
+ | `send_input(id, data)` | Send commands |
107
+ | `get_history(id)` | Get output |
108
+ | `resize(id, cols, rows)` | Resize |
109
+ | `send_signal(id, signal)` | SIGINT/SIGTERM |
110
+ | `close(id)` | End session |
111
+
112
+ ## Files
113
+
114
+ ```python
115
+ server.files.list("/var/log")
116
+ server.files.read("/etc/nginx/nginx.conf")
117
+ server.files.write("/tmp/config.json", b'{"key": "value"}')
118
+ ```
119
+
120
+ | Method | Description |
121
+ |--------|-------------|
122
+ | `list(path)` | List dir |
123
+ | `read(path)` | Read file |
124
+ | `write(path, content)` | Write file |
125
+ | `delete(path)` | Delete |
126
+ | `copy/move(src, dst)` | Copy/Move |
127
+ | `mkdir(path)` | Create dir |
128
+ | `info(path)` | Metadata |
129
+
130
+ ## Agent
131
+
132
+ ```python
133
+ from pydantic import BaseModel
134
+
135
+ class Health(BaseModel):
136
+ status: str
137
+ cpu: float
138
+ issues: list[str]
139
+
140
+ result = server.agent.run("Check server health", output_schema=Health)
141
+ health: Health = result.output # Typed!
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Browser
147
+
148
+ ```python
149
+ with client.browser.create_session() as b:
150
+ b.navigate("https://shop.com/products")
151
+
152
+ # DOM extraction
153
+ products = b.extract_data(".product-card", '{"name": "h2", "price": ".price"}', limit=100)
154
+
155
+ # Get HTML for BeautifulSoup parsing
156
+ html = b.get_html("[role='feed']")
157
+ soup = b.parse_html(html) # Returns BeautifulSoup object
158
+
159
+ # Scroll & extract pattern
160
+ for _ in range(10):
161
+ html = b.get_html(".listings")
162
+ # ... parse with soup ...
163
+ b.scroll("down", 800)
164
+ b.wait_seconds(1.0)
165
+
166
+ # JS fetch (bypass CORS, inherit cookies)
167
+ data = b.fetch_json("https://api.site.com/v1/items")
168
+
169
+ # Parallel fetch
170
+ results = b.fetch_all({
171
+ "users": "https://api.site.com/v1/users",
172
+ "orders": "https://api.site.com/v1/orders",
173
+ }, credentials=True)
174
+ ```
175
+
176
+ | Method | Description |
177
+ |--------|-------------|
178
+ | `navigate(url)` | Go to URL |
179
+ | `click(selector)` | Click element |
180
+ | `type(selector, text)` | Type text |
181
+ | `wait_for(selector, ms)` | Wait for element |
182
+ | `wait_seconds(n)` | Sleep |
183
+ | `extract(selector, attr)` | Get text/attr |
184
+ | `get_html(selector)` | Get HTML |
185
+ | `get_text(selector)` | Get text |
186
+ | `parse_html(html)` | → BeautifulSoup |
187
+ | `extract_data(item, fields, limit)` | Bulk extract |
188
+ | `fetch_json(url)` | JS fetch → dict |
189
+ | `fetch_all(urls, credentials)` | Parallel fetch |
190
+ | `execute_js(code)` | Run async JS |
191
+ | `screenshot()` | PNG bytes |
192
+ | `scroll(dir, amount)` | Scroll page |
193
+ | `scroll_to(selector)` | Scroll to element |
194
+ | `get_scroll_info()` | Position + page size |
195
+ | `infinite_scroll(fn, limit)` | Smart scroll loop |
196
+ | `hover(selector)` | Hover |
197
+ | `select(selector, value)` | Dropdown select |
198
+ | `close_modal()` | Close dialogs |
199
+ | `get/set_cookies()` | Cookie management |
200
+
201
+ ## SDKBaseModel
202
+
203
+ Auto-cleaning Pydantic model for scraped data:
204
+
205
+ ```python
206
+ from cmdop import SDKBaseModel
207
+
208
+ class Product(SDKBaseModel):
209
+ __base_url__ = "https://shop.com"
210
+ name: str = "" # " iPhone 15 \n" → "iPhone 15"
211
+ price: int = 0 # "$1,299.00" → 1299
212
+ rating: float = 0 # "4.5 stars" → 4.5
213
+ url: str = "" # "/p/123" → "https://shop.com/p/123"
214
+
215
+ products = Product.from_list(raw["items"]) # Auto dedupe + filter
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Utilities
221
+
222
+ **Logging:**
223
+ ```python
224
+ from cmdop import get_logger
225
+ log = get_logger(__name__)
226
+ log.info("Starting") # Rich console + auto file logging
227
+ ```
228
+
229
+ **TOON Format (30-50% token savings):**
230
+ ```python
231
+ from cmdop import json_to_toon, JsonCleaner
232
+ toon = json_to_toon({"name": "Alice", "age": 25})
233
+ # → "name: Alice\nage: 25"
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Requirements
239
+
240
+ - Python 3.10+
241
+ - CMDOP agent on target
242
+
243
+ ## Links
244
+
245
+ [cmdop.com](https://cmdop.com)
246
+
247
+ ## License
248
+
249
+ MIT
cmdop-0.1.17/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # cmdop
2
+
3
+ **Any machine. One API.**
4
+
5
+ ```python
6
+ from cmdop import CMDOPClient
7
+
8
+ with CMDOPClient.remote(api_key="cmd_xxx") as server:
9
+ server.terminal.execute("docker restart app")
10
+ server.files.write("/etc/nginx/nginx.conf", new_config)
11
+ logs = server.files.read("/var/log/app.log")
12
+ ```
13
+
14
+ No SSH. No VPN. No open ports.
15
+
16
+ ---
17
+
18
+ ## How
19
+
20
+ ```
21
+ Your Code ──── Cloud Relay ──── Agent (on server)
22
+
23
+ Outbound only, works through any NAT/firewall
24
+ ```
25
+
26
+ Agent connects out. Your code connects to relay. Done.
27
+
28
+ ---
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install cmdop
34
+ ```
35
+
36
+ ```python
37
+ from cmdop import CMDOPClient, AsyncCMDOPClient
38
+
39
+ # Remote (via cloud relay)
40
+ with CMDOPClient.remote(api_key="cmd_xxx") as client:
41
+ client.files.list("/home")
42
+
43
+ # Local (direct IPC)
44
+ with CMDOPClient.local() as client:
45
+ client.terminal.execute("ls -la")
46
+
47
+ # Async
48
+ async with AsyncCMDOPClient.remote(api_key="cmd_xxx") as client:
49
+ await client.files.read("/etc/hostname")
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Terminal
55
+
56
+ ```python
57
+ session = server.terminal.create()
58
+ server.terminal.send_input(session.session_id, "kubectl get pods\n")
59
+ output = server.terminal.get_history(session.session_id)
60
+ ```
61
+
62
+ | Method | Description |
63
+ |--------|-------------|
64
+ | `create(shell)` | Start session |
65
+ | `send_input(id, data)` | Send commands |
66
+ | `get_history(id)` | Get output |
67
+ | `resize(id, cols, rows)` | Resize |
68
+ | `send_signal(id, signal)` | SIGINT/SIGTERM |
69
+ | `close(id)` | End session |
70
+
71
+ ## Files
72
+
73
+ ```python
74
+ server.files.list("/var/log")
75
+ server.files.read("/etc/nginx/nginx.conf")
76
+ server.files.write("/tmp/config.json", b'{"key": "value"}')
77
+ ```
78
+
79
+ | Method | Description |
80
+ |--------|-------------|
81
+ | `list(path)` | List dir |
82
+ | `read(path)` | Read file |
83
+ | `write(path, content)` | Write file |
84
+ | `delete(path)` | Delete |
85
+ | `copy/move(src, dst)` | Copy/Move |
86
+ | `mkdir(path)` | Create dir |
87
+ | `info(path)` | Metadata |
88
+
89
+ ## Agent
90
+
91
+ ```python
92
+ from pydantic import BaseModel
93
+
94
+ class Health(BaseModel):
95
+ status: str
96
+ cpu: float
97
+ issues: list[str]
98
+
99
+ result = server.agent.run("Check server health", output_schema=Health)
100
+ health: Health = result.output # Typed!
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Browser
106
+
107
+ ```python
108
+ with client.browser.create_session() as b:
109
+ b.navigate("https://shop.com/products")
110
+
111
+ # DOM extraction
112
+ products = b.extract_data(".product-card", '{"name": "h2", "price": ".price"}', limit=100)
113
+
114
+ # Get HTML for BeautifulSoup parsing
115
+ html = b.get_html("[role='feed']")
116
+ soup = b.parse_html(html) # Returns BeautifulSoup object
117
+
118
+ # Scroll & extract pattern
119
+ for _ in range(10):
120
+ html = b.get_html(".listings")
121
+ # ... parse with soup ...
122
+ b.scroll("down", 800)
123
+ b.wait_seconds(1.0)
124
+
125
+ # JS fetch (bypass CORS, inherit cookies)
126
+ data = b.fetch_json("https://api.site.com/v1/items")
127
+
128
+ # Parallel fetch
129
+ results = b.fetch_all({
130
+ "users": "https://api.site.com/v1/users",
131
+ "orders": "https://api.site.com/v1/orders",
132
+ }, credentials=True)
133
+ ```
134
+
135
+ | Method | Description |
136
+ |--------|-------------|
137
+ | `navigate(url)` | Go to URL |
138
+ | `click(selector)` | Click element |
139
+ | `type(selector, text)` | Type text |
140
+ | `wait_for(selector, ms)` | Wait for element |
141
+ | `wait_seconds(n)` | Sleep |
142
+ | `extract(selector, attr)` | Get text/attr |
143
+ | `get_html(selector)` | Get HTML |
144
+ | `get_text(selector)` | Get text |
145
+ | `parse_html(html)` | → BeautifulSoup |
146
+ | `extract_data(item, fields, limit)` | Bulk extract |
147
+ | `fetch_json(url)` | JS fetch → dict |
148
+ | `fetch_all(urls, credentials)` | Parallel fetch |
149
+ | `execute_js(code)` | Run async JS |
150
+ | `screenshot()` | PNG bytes |
151
+ | `scroll(dir, amount)` | Scroll page |
152
+ | `scroll_to(selector)` | Scroll to element |
153
+ | `get_scroll_info()` | Position + page size |
154
+ | `infinite_scroll(fn, limit)` | Smart scroll loop |
155
+ | `hover(selector)` | Hover |
156
+ | `select(selector, value)` | Dropdown select |
157
+ | `close_modal()` | Close dialogs |
158
+ | `get/set_cookies()` | Cookie management |
159
+
160
+ ## SDKBaseModel
161
+
162
+ Auto-cleaning Pydantic model for scraped data:
163
+
164
+ ```python
165
+ from cmdop import SDKBaseModel
166
+
167
+ class Product(SDKBaseModel):
168
+ __base_url__ = "https://shop.com"
169
+ name: str = "" # " iPhone 15 \n" → "iPhone 15"
170
+ price: int = 0 # "$1,299.00" → 1299
171
+ rating: float = 0 # "4.5 stars" → 4.5
172
+ url: str = "" # "/p/123" → "https://shop.com/p/123"
173
+
174
+ products = Product.from_list(raw["items"]) # Auto dedupe + filter
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Utilities
180
+
181
+ **Logging:**
182
+ ```python
183
+ from cmdop import get_logger
184
+ log = get_logger(__name__)
185
+ log.info("Starting") # Rich console + auto file logging
186
+ ```
187
+
188
+ **TOON Format (30-50% token savings):**
189
+ ```python
190
+ from cmdop import json_to_toon, JsonCleaner
191
+ toon = json_to_toon({"name": "Alice", "age": 25})
192
+ # → "name: Alice\nage: 25"
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Requirements
198
+
199
+ - Python 3.10+
200
+ - CMDOP agent on target
201
+
202
+ ## Links
203
+
204
+ [cmdop.com](https://cmdop.com)
205
+
206
+ ## License
207
+
208
+ MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cmdop"
3
- version = "0.1.16"
3
+ version = "0.1.17"
4
4
  description = "Python SDK for CMDOP agent interaction"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -32,6 +32,9 @@ dependencies = [
32
32
  "toon-python>=0.1.2",
33
33
  # Logging
34
34
  "rich>=13.0.0",
35
+ # HTML parsing (browser SDK)
36
+ "beautifulsoup4>=4.12.0",
37
+ "lxml>=5.0.0",
35
38
  ]
36
39
 
37
40
  [project.optional-dependencies]
@@ -44,6 +47,8 @@ dev = [
44
47
  "ruff>=0.1.0",
45
48
  # For proto generation (still uses grpcio-tools with betterproto plugin)
46
49
  "grpcio-tools>=1.60.0",
50
+ # HTML parsing for tests
51
+ "beautifulsoup4>=4.12.0",
47
52
  ]
48
53
 
49
54
  [project.urls]
@@ -124,7 +124,7 @@ from cmdop.logging import (
124
124
  get_log_dir,
125
125
  )
126
126
 
127
- __version__ = "0.1.16"
127
+ __version__ = "0.1.17"
128
128
 
129
129
  __all__ = [
130
130
  # Version
@@ -0,0 +1,49 @@
1
+ """JavaScript builders for browser automation.
2
+
3
+ This module provides JavaScript code generators for common browser operations:
4
+ - Core: JSON parsing, async wrappers
5
+ - Fetch: HTTP requests from browser context
6
+ - Scroll: Page scrolling and infinite scroll
7
+ - Interaction: Hover, select, modals
8
+ """
9
+
10
+ from cmdop.services.browser.js.core import (
11
+ parse_json_result,
12
+ build_async_js,
13
+ )
14
+
15
+ from cmdop.services.browser.js.fetch import (
16
+ build_fetch_js,
17
+ build_fetch_all_js,
18
+ )
19
+
20
+ from cmdop.services.browser.js.scroll import (
21
+ build_scroll_js,
22
+ build_scroll_to_bottom_js,
23
+ build_infinite_scroll_js,
24
+ build_get_scroll_info_js,
25
+ )
26
+
27
+ from cmdop.services.browser.js.interaction import (
28
+ build_hover_js,
29
+ build_select_js,
30
+ build_close_modal_js,
31
+ )
32
+
33
+ __all__ = [
34
+ # Core
35
+ "parse_json_result",
36
+ "build_async_js",
37
+ # Fetch
38
+ "build_fetch_js",
39
+ "build_fetch_all_js",
40
+ # Scroll
41
+ "build_scroll_js",
42
+ "build_scroll_to_bottom_js",
43
+ "build_infinite_scroll_js",
44
+ "build_get_scroll_info_js",
45
+ # Interaction
46
+ "build_hover_js",
47
+ "build_select_js",
48
+ "build_close_modal_js",
49
+ ]
@@ -0,0 +1,38 @@
1
+ """Core JavaScript utilities for browser automation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+
9
+ def parse_json_result(result: str) -> dict | list | None:
10
+ """Parse JSON result from JS execution."""
11
+ if result:
12
+ try:
13
+ data = json.loads(result)
14
+ if isinstance(data, dict) and "__error" in data:
15
+ return None
16
+ return data
17
+ except json.JSONDecodeError:
18
+ return None
19
+ return None
20
+
21
+
22
+ def build_async_js(code: str) -> str:
23
+ """
24
+ Wrap JS code in async IIFE with error handling.
25
+
26
+ The code should return a value (will be JSON.stringify'd).
27
+ Use for async operations like fetch, Promise.all, etc.
28
+ """
29
+ return f"""
30
+ (async function() {{
31
+ try {{
32
+ const result = await (async () => {{ {code} }})();
33
+ return JSON.stringify(result);
34
+ }} catch(e) {{
35
+ return JSON.stringify({{__error: e.message}});
36
+ }}
37
+ }})()
38
+ """
@@ -1,28 +1,8 @@
1
- """JavaScript code builders for browser automation."""
1
+ """Fetch JavaScript builders for browser automation."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
- from typing import Any
7
-
8
-
9
- def build_async_js(code: str) -> str:
10
- """
11
- Wrap JS code in async IIFE with error handling.
12
-
13
- The code should return a value (will be JSON.stringify'd).
14
- Use for async operations like fetch, Promise.all, etc.
15
- """
16
- return f"""
17
- (async function() {{
18
- try {{
19
- const result = await (async () => {{ {code} }})();
20
- return JSON.stringify(result);
21
- }} catch(e) {{
22
- return JSON.stringify({{__error: e.message}});
23
- }}
24
- }})()
25
- """
26
6
 
27
7
 
28
8
  def build_fetch_js(url: str) -> str:
@@ -94,16 +74,3 @@ def build_fetch_all_js(
94
74
 
95
75
  return results;
96
76
  """
97
-
98
-
99
- def parse_json_result(result: str) -> dict | list | None:
100
- """Parse JSON result from JS execution."""
101
- if result:
102
- try:
103
- data = json.loads(result)
104
- if isinstance(data, dict) and "__error" in data:
105
- return None
106
- return data
107
- except json.JSONDecodeError:
108
- return None
109
- return None