devops-bot-sdk 1.6.28__tar.gz → 1.6.32__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 (163) hide show
  1. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/SOURCES.txt +4 -0
  4. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/__init__.py +2 -2
  5. devops_bot_sdk-1.6.32/sdk/browser_profile.py +362 -0
  6. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/cli.py +145 -3
  7. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/self_report.py +130 -0
  8. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/self_report_local.py +21 -119
  9. devops_bot_sdk-1.6.32/sdk/url_access.py +647 -0
  10. devops_bot_sdk-1.6.32/tests/test_browser_profile.py +223 -0
  11. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_self_report.py +136 -2
  12. devops_bot_sdk-1.6.32/tests/test_url_access.py +480 -0
  13. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/README.md +0 -0
  14. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  15. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  16. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/requires.txt +0 -0
  17. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  18. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/pyproject.toml +0 -0
  19. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agentd.py +0 -0
  20. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/__init__.py +0 -0
  21. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/activity.py +0 -0
  22. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/builder.py +0 -0
  23. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/chat.py +0 -0
  24. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/connectors.py +0 -0
  25. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/engines/__init__.py +0 -0
  26. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/engines/claude_code.py +0 -0
  27. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/engines/http.py +0 -0
  28. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/engines/mcp.py +0 -0
  29. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/engines/n8n.py +0 -0
  30. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/engines/shell.py +0 -0
  31. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/integrations.py +0 -0
  32. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/inventory.py +0 -0
  33. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/library/__init__.py +0 -0
  34. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/manifest.py +0 -0
  35. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/prompt_studio.py +0 -0
  36. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/prompt_template.py +0 -0
  37. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/prompt_vars.py +0 -0
  38. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/run_log.py +0 -0
  39. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/schedules.py +0 -0
  40. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/secrets.py +0 -0
  41. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/ticket_run.py +0 -0
  42. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/usage.py +0 -0
  43. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/agents/varstore.py +0 -0
  44. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/client.py +0 -0
  45. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/collectors/__init__.py +0 -0
  46. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/collectors/files.py +0 -0
  47. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/collectors/process.py +0 -0
  48. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/collectors/screenshot.py +0 -0
  49. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/config.py +0 -0
  50. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/crucial.py +0 -0
  51. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/departments.py +0 -0
  52. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/design_verify/__init__.py +0 -0
  53. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/design_verify/assets.py +0 -0
  54. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/design_verify/bootstrap.py +0 -0
  55. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/design_verify/browser.py +0 -0
  56. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/design_verify/compare.py +0 -0
  57. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/design_verify/loop.py +0 -0
  58. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/exceptions.py +0 -0
  59. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/formats.py +0 -0
  60. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/git_ops.py +0 -0
  61. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/github_ingest.py +0 -0
  62. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/graph_recon.py +0 -0
  63. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/graphify.py +0 -0
  64. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/hooks/__init__.py +0 -0
  65. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/hooks/crucial_guard.py +0 -0
  66. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/intake.py +0 -0
  67. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ipc/__init__.py +0 -0
  68. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ipc/electron_bridge.py +0 -0
  69. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ipc/handlers.py +0 -0
  70. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/local_exec.py +0 -0
  71. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/models/__init__.py +0 -0
  72. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/models/envelope.py +0 -0
  73. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/models/requests.py +0 -0
  74. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/models/responses.py +0 -0
  75. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/models/snapshots.py +0 -0
  76. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/phase_timer.py +0 -0
  77. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/plan_run.py +0 -0
  78. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/platform_compat.py +0 -0
  79. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/proof_readers.py +0 -0
  80. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/py.typed +0 -0
  81. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/run_auto.py +0 -0
  82. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/scheduler.py +0 -0
  83. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/sse.py +0 -0
  84. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/task_file.py +0 -0
  85. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/test.py +0 -0
  86. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/test_pipeline.py +0 -0
  87. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ticket_fields.py +0 -0
  88. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/__init__.py +0 -0
  89. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/api.py +0 -0
  90. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/dashboard.py +0 -0
  91. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/health.py +0 -0
  92. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/ip_allowlist.py +0 -0
  93. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/jobs.py +0 -0
  94. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/models.py +0 -0
  95. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/runner.py +0 -0
  96. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/security.py +0 -0
  97. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/server.py +0 -0
  98. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/service.py +0 -0
  99. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/session.py +0 -0
  100. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/setup_manager.py +0 -0
  101. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/agents.html +0 -0
  102. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/app.js +0 -0
  103. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/chat.html +0 -0
  104. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/custom-agents.html +0 -0
  105. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/default-agents.html +0 -0
  106. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/index.html +0 -0
  107. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/login.html +0 -0
  108. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/report.html +0 -0
  109. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/runs.html +0 -0
  110. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/schedules.html +0 -0
  111. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/setup.html +0 -0
  112. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/styles.css +0 -0
  113. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/ui/static/tickets.html +0 -0
  114. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/updater.py +0 -0
  115. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/sdk/vps_health.py +0 -0
  116. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/setup.cfg +0 -0
  117. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_activity.py +0 -0
  118. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_activity_tracking.py +0 -0
  119. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_chat_features.py +0 -0
  120. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_listing.py +0 -0
  121. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_prompt_async.py +0 -0
  122. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_prompt_models.py +0 -0
  123. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_run_ticket.py +0 -0
  124. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_schedules.py +0 -0
  125. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agent_workspace_settings.py +0 -0
  126. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_agents_connectors_builder.py +0 -0
  127. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_allowed_tools_config.py +0 -0
  128. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_auto_agent_dispatch.py +0 -0
  129. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_client_jira_projects.py +0 -0
  130. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_config_token_diagnostics.py +0 -0
  131. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_configure_token_owner.py +0 -0
  132. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_console_encoding.py +0 -0
  133. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_departments.py +0 -0
  134. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_epic_decompose_run.py +0 -0
  135. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_epic_inherited_agent_id.py +0 -0
  136. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_epic_session_isolation.py +0 -0
  137. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_formats.py +0 -0
  138. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_git_action.py +0 -0
  139. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_github_ingest.py +0 -0
  140. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_graph_recon.py +0 -0
  141. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_graphify_build.py +0 -0
  142. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_graphify_hook_install.py +0 -0
  143. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_health.py +0 -0
  144. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_intake_plan_review.py +0 -0
  145. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_otp_login.py +0 -0
  146. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_phase_timer.py +0 -0
  147. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_plan_new_command.py +0 -0
  148. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_plan_run_activity.py +0 -0
  149. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_proof_readers.py +0 -0
  150. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_recon_session_isolation.py +0 -0
  151. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_run_auto_agent_only.py +0 -0
  152. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_run_outputs.py +0 -0
  153. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_schedule_api.py +0 -0
  154. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_scheduler.py +0 -0
  155. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_service_token_warning.py +0 -0
  156. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_task_file_and_plan_run.py +0 -0
  157. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_task_file_format_doc.py +0 -0
  158. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_ticket_fields.py +0 -0
  159. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_ticket_run_resolve_by_id.py +0 -0
  160. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_ticket_speed_fixes.py +0 -0
  161. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_update_timer.py +0 -0
  162. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_updater_extras.py +0 -0
  163. {devops_bot_sdk-1.6.28 → devops_bot_sdk-1.6.32}/tests/test_vps_health.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.28
3
+ Version: 1.6.32
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.28
3
+ Version: 1.6.32
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -8,6 +8,7 @@ devops_bot_sdk.egg-info/requires.txt
8
8
  devops_bot_sdk.egg-info/top_level.txt
9
9
  sdk/__init__.py
10
10
  sdk/agentd.py
11
+ sdk/browser_profile.py
11
12
  sdk/cli.py
12
13
  sdk/client.py
13
14
  sdk/config.py
@@ -36,6 +37,7 @@ sdk/test.py
36
37
  sdk/test_pipeline.py
37
38
  sdk/ticket_fields.py
38
39
  sdk/updater.py
40
+ sdk/url_access.py
39
41
  sdk/vps_health.py
40
42
  sdk/agents/__init__.py
41
43
  sdk/agents/activity.py
@@ -119,6 +121,7 @@ tests/test_agent_workspace_settings.py
119
121
  tests/test_agents_connectors_builder.py
120
122
  tests/test_allowed_tools_config.py
121
123
  tests/test_auto_agent_dispatch.py
124
+ tests/test_browser_profile.py
122
125
  tests/test_client_jira_projects.py
123
126
  tests/test_config_token_diagnostics.py
124
127
  tests/test_configure_token_owner.py
@@ -154,4 +157,5 @@ tests/test_ticket_run_resolve_by_id.py
154
157
  tests/test_ticket_speed_fixes.py
155
158
  tests/test_update_timer.py
156
159
  tests/test_updater_extras.py
160
+ tests/test_url_access.py
157
161
  tests/test_vps_health.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.28
3
+ Version: 1.6.32
4
4
 
5
5
  Public surface:
6
6
  BackendClient.from_config() — create client from ~/.agentos/config.toml
@@ -34,7 +34,7 @@ Rules:
34
34
  - All data egress through submit_webhook only
35
35
  """
36
36
 
37
- __version__ = "1.6.28" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.32" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
38
38
  __author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
39
39
 
40
40
  from sdk.client import BackendClient
@@ -0,0 +1,362 @@
1
+ """A browser profile the SDK owns, and the operator signs in to once.
2
+
3
+ The generic answer to "read the link I am logged in to" is a browser, and the
4
+ browser has to be one with sessions in it. Neither obvious candidate is:
5
+
6
+ * **Chrome for Testing**, which ``design_verify/bootstrap`` installs for the
7
+ design loop, is a fresh binary with a blank profile. It is signed in to
8
+ nothing, so driving it answers every private link with a login page.
9
+ * **The operator's own Chrome profile** cannot be driven either. Chrome 136 and
10
+ later refuse ``--remote-debugging-port`` while using the default user-data-dir
11
+ — deliberately, because allowing it is how session cookies get stolen — and a
12
+ second Chrome cannot open a profile the running one holds. Copying the profile
13
+ would work and is precisely the cookie-theft shape that decision exists to
14
+ prevent, so it is not done here either.
15
+
16
+ So this module keeps a THIRD profile: ``~/.agentos/browser-profile``, belonging
17
+ to the SDK. ``agentos browser login <url>`` opens a normal, visible window on it;
18
+ the person signs in the way they would anywhere — password, SSO, MFA, whatever
19
+ the site asks — and closes it. The persistent cookies that login writes stay in
20
+ that directory, and every later read runs headless against the same directory and
21
+ is therefore signed in too.
22
+
23
+ Two properties make this the right trade:
24
+
25
+ * **Nothing decrypts anything.** The SDK never reads the person's own cookie
26
+ store. It has its own profile, holding only sessions they deliberately signed
27
+ into for this purpose, in a directory they can delete in one command.
28
+ * **No new dependency and no CDP.** ``chrome --headless=new --dump-dom <url>``
29
+ prints the rendered DOM to stdout. That is the entire mechanism: no WebSocket,
30
+ no Playwright, no driver to keep in step with the browser version.
31
+
32
+ It is genuinely host-agnostic. Nothing here knows what GitHub or Outlook is — a
33
+ site is readable if the person signed in to it, and the list of such sites is
34
+ whatever they chose.
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import asyncio
39
+ import json
40
+ import logging
41
+ import os
42
+ import platform
43
+ import shutil
44
+ import subprocess
45
+ import sys
46
+ from contextlib import suppress
47
+ from dataclasses import dataclass
48
+ from functools import lru_cache
49
+ from datetime import datetime, timezone
50
+ from pathlib import Path
51
+ from urllib.parse import urlparse
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+ PROFILE_ENV = "AGENTOS_BROWSER_PROFILE"
56
+ BINARY_ENV = "AGENTOS_BROWSER_BINARY"
57
+ _SANDBOX_ENV = "AGENTOS_BROWSER_NO_SANDBOX"
58
+
59
+ # Chromium family only: ``--dump-dom`` is a Chrome flag and has no Firefox
60
+ # equivalent, and the whole point is one code path rather than a driver per
61
+ # engine. Order is preference — the operator's everyday Chrome first, since a
62
+ # site that works there is the one they will expect to work here.
63
+ _CANDIDATES = (
64
+ "google-chrome", "google-chrome-stable", "chromium", "chromium-browser",
65
+ "microsoft-edge", "microsoft-edge-stable", "brave-browser",
66
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
67
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
68
+ )
69
+
70
+ # A page is evidence, not an application: give it time to render and settle, then
71
+ # take what is there. `--virtual-time-budget` fast-forwards timers so this costs
72
+ # wall-clock only for the network, not for a site's animations.
73
+ _VIRTUAL_TIME_MS = 12_000
74
+ _FETCH_TIMEOUT_S = 60.0
75
+ _REAP_TIMEOUT_S = 3.0
76
+ _VERSION_TIMEOUT_S = 10.0
77
+
78
+
79
+ def profile_dir() -> Path:
80
+ return Path(os.environ.get(PROFILE_ENV, "") or
81
+ (Path.home() / ".agentos" / "browser-profile")).expanduser()
82
+
83
+
84
+ def sites_file() -> Path:
85
+ """Hosts the operator signed in to, RECORDED BY US as they do it.
86
+
87
+ Deliberately not derived from the profile's cookie database. `browser status`
88
+ needs to answer "what is this signed in to", and reading cookies to find out
89
+ — even our own — would put cookie-parsing code in the SDK, which is the thing
90
+ this design exists to avoid. A note we wrote ourselves is enough.
91
+ """
92
+ return profile_dir().parent / "browser-sites.json"
93
+
94
+
95
+ @dataclass
96
+ class Fetched:
97
+ """One headless read. ``html`` empty means it did not happen."""
98
+ html: str = ""
99
+ reason: str = ""
100
+
101
+ @property
102
+ def ok(self) -> bool:
103
+ return bool(self.html)
104
+
105
+
106
+ def find_browser() -> str:
107
+ """Path to a Chromium-family browser, or "". Honours ``AGENTOS_BROWSER_BINARY``."""
108
+ override = os.environ.get(BINARY_ENV, "").strip()
109
+ if override:
110
+ return override if (shutil.which(override) or Path(override).exists()) else ""
111
+ for name in _CANDIDATES:
112
+ found = shutil.which(name)
113
+ if found:
114
+ return found
115
+ if name.startswith("/") and Path(name).exists():
116
+ return name
117
+ return ""
118
+
119
+
120
+ def is_available() -> bool:
121
+ return bool(find_browser())
122
+
123
+
124
+ @lru_cache(maxsize=8)
125
+ def _version_of(binary: str) -> str:
126
+ """``149.0.7827.200`` from ``chrome --version``, or "".
127
+
128
+ Cached per binary: a browser does not change version inside one process, and
129
+ this is a subprocess on the path of every read. Keyed on the path rather than
130
+ stored in a flag so an override pointing somewhere else is not answered with
131
+ the previous browser's version.
132
+ """
133
+ try:
134
+ out = subprocess.run([binary, "--version"], capture_output=True, text=True,
135
+ timeout=_VERSION_TIMEOUT_S).stdout
136
+ except (OSError, subprocess.SubprocessError):
137
+ return ""
138
+ for word in out.split():
139
+ if word[:1].isdigit() and word.count(".") >= 2:
140
+ return word.strip()
141
+ return ""
142
+
143
+
144
+ def _version() -> str:
145
+ binary = find_browser()
146
+ return _version_of(binary) if binary else ""
147
+
148
+
149
+ def user_agent() -> str:
150
+ """A non-headless UA for the browser we actually have.
151
+
152
+ New headless still announces ``HeadlessChrome``, and enough sites refuse it
153
+ that a page would come back as a block notice rather than the evidence. Built
154
+ from the installed version rather than hard-coded so it does not quietly go
155
+ stale into exactly the mismatch that gets a request flagged.
156
+ """
157
+ version = _version() or "140.0.0.0"
158
+ system = sys.platform
159
+ if system == "darwin":
160
+ os_part = "Macintosh; Intel Mac OS X 10_15_7"
161
+ elif system.startswith("win"):
162
+ os_part = "Windows NT 10.0; Win64; x64"
163
+ else:
164
+ os_part = f"X11; Linux {platform.machine() or 'x86_64'}"
165
+ return (f"Mozilla/5.0 ({os_part}) AppleWebKit/537.36 (KHTML, like Gecko) "
166
+ f"Chrome/{version} Safari/537.36")
167
+
168
+
169
+ def _base_args() -> list[str]:
170
+ args = [
171
+ f"--user-data-dir={profile_dir()}",
172
+ "--no-first-run",
173
+ "--no-default-browser-check",
174
+ "--disable-background-networking",
175
+ "--disable-sync", # this profile is not anyone's identity
176
+ "--mute-audio",
177
+ ]
178
+ if os.environ.get(_SANDBOX_ENV, "").strip() in ("1", "true", "True"):
179
+ # Some containers cannot run the sandbox at all. Off by default: the
180
+ # sandbox is the thing standing between a hostile page and this machine.
181
+ args.append("--no-sandbox")
182
+ return args
183
+
184
+
185
+ def busy_pid() -> int | None:
186
+ """The pid holding this profile, or None.
187
+
188
+ Chrome keeps one instance per user-data-dir; a second launch hands its
189
+ arguments to the first and exits, which for a headless read means an empty
190
+ answer with no error. Checking first turns that into a sentence the operator
191
+ can act on. A stale lock from a crash names a pid that is gone, and is
192
+ correctly reported as not busy.
193
+ """
194
+ lock = profile_dir() / "SingletonLock"
195
+ try:
196
+ target = os.readlink(lock)
197
+ except (OSError, ValueError):
198
+ return None
199
+ # "<hostname>-<pid>"
200
+ _, _, raw = target.rpartition("-")
201
+ try:
202
+ pid = int(raw)
203
+ except ValueError:
204
+ return None
205
+ try:
206
+ os.kill(pid, 0)
207
+ except ProcessLookupError:
208
+ return None
209
+ except PermissionError:
210
+ return pid # alive, owned by someone else
211
+ except OSError:
212
+ return None
213
+ return pid
214
+
215
+
216
+ async def fetch_dom(url: str, *, timeout: float = _FETCH_TIMEOUT_S) -> Fetched:
217
+ """Render ``url`` headless in the signed-in profile and return its DOM.
218
+
219
+ ``--dump-dom`` prints the page AFTER scripts have run, which is what makes
220
+ this work on the single-page apps most internal tools are: the HTML a plain
221
+ GET returns is an empty shell, and the content only exists once the app has
222
+ fetched it with the session this profile holds.
223
+ """
224
+ binary = find_browser()
225
+ if not binary:
226
+ return Fetched(reason=(
227
+ "no Chrome, Chromium, Edge or Brave was found on this machine, so a "
228
+ "link behind a login cannot be opened here. Install one, or set "
229
+ f"{BINARY_ENV} to its path."))
230
+ if not profile_dir().exists():
231
+ return Fetched(reason=(
232
+ "the SDK's browser profile does not exist yet — nothing has been "
233
+ "signed in to. Run `agentos browser login <url>`, sign in, close the "
234
+ "window, and this link becomes readable."))
235
+ pid = busy_pid()
236
+ if pid is not None:
237
+ return Fetched(reason=(
238
+ f"the SDK's browser profile is open in another window (pid {pid}). "
239
+ "Close the window `agentos browser login` opened and try again."))
240
+
241
+ # `user_agent` shells out for the version. Cached, but the first call per
242
+ # binary is a subprocess — off the loop, or one read stalls every other
243
+ # coroutine in the process for as long as the browser takes to answer.
244
+ agent = await asyncio.to_thread(user_agent)
245
+ cmd = [binary, "--headless=new", "--disable-gpu", *_base_args(),
246
+ f"--user-agent={agent}",
247
+ f"--virtual-time-budget={_VIRTUAL_TIME_MS}",
248
+ "--dump-dom", url]
249
+ try:
250
+ proc = await asyncio.create_subprocess_exec(
251
+ *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
252
+ except OSError as exc:
253
+ return Fetched(reason=f"could not start {Path(binary).name} — {exc}")
254
+ try:
255
+ out, err = await asyncio.wait_for(proc.communicate(), timeout=timeout)
256
+ except asyncio.TimeoutError:
257
+ proc.kill()
258
+ # Reap it. Without the wait the transport is still open when the loop
259
+ # closes, which surfaces as a "Event loop is closed" warning from
260
+ # somewhere entirely unrelated to the browser.
261
+ with suppress(Exception):
262
+ # BOUNDED. A killed process whose orphaned child still holds the
263
+ # pipes never reports exit, and an unbounded wait here would hang the
264
+ # very call the timeout above exists to bound.
265
+ await asyncio.wait_for(proc.wait(), timeout=_REAP_TIMEOUT_S)
266
+ return Fetched(reason=f"timed out rendering in the browser after {int(timeout)}s.")
267
+
268
+ html = out.decode(errors="replace").strip()
269
+ if not html:
270
+ detail = err.decode(errors="replace").strip().splitlines()
271
+ tail = detail[-1][:200] if detail else f"exit {proc.returncode}"
272
+ return Fetched(reason=f"was opened in the browser but rendered nothing — {tail}")
273
+ return Fetched(html=html)
274
+
275
+
276
+ # ── Signing in ────────────────────────────────────────────────────────────────
277
+
278
+ def _load_sites() -> dict:
279
+ try:
280
+ data = json.loads(sites_file().read_text(encoding="utf-8"))
281
+ except Exception: # noqa: BLE001 — absent until the first login
282
+ return {}
283
+ return data if isinstance(data, dict) else {}
284
+
285
+
286
+ def record_login(url: str) -> str:
287
+ """Note that a login window was opened for this URL's host. Returns the host."""
288
+ host = (urlparse(url).hostname or "").lower()
289
+ if not host:
290
+ return ""
291
+ sites = _load_sites()
292
+ sites[host] = datetime.now(timezone.utc).isoformat(timespec="seconds")
293
+ try:
294
+ sites_file().parent.mkdir(parents=True, exist_ok=True)
295
+ sites_file().write_text(json.dumps(sites, indent=2, sort_keys=True), encoding="utf-8")
296
+ except OSError as exc:
297
+ logger.warning("browser_profile.sites_write_failed: %s", exc)
298
+ return host
299
+
300
+
301
+ def logged_in_hosts() -> dict[str, str]:
302
+ """``{host: when the login window was opened}`` — what we were told, not proof.
303
+
304
+ A session can expire, and a person can open the window and never sign in. So
305
+ this is a record of intent; `agentos report check <url>` is what actually
306
+ answers whether a host is readable right now.
307
+ """
308
+ return dict(sorted(_load_sites().items()))
309
+
310
+
311
+ def open_login(url: str) -> tuple[bool, str]:
312
+ """Open a VISIBLE window on the SDK's profile at ``url``. ``(ok, message)``.
313
+
314
+ Returns as soon as the browser is launched — the point is for a person to
315
+ take it from there, and waiting for them to finish typing a password is not
316
+ this function's business.
317
+ """
318
+ binary = find_browser()
319
+ if not binary:
320
+ return False, ("no Chrome, Chromium, Edge or Brave found. Install one, or "
321
+ f"set {BINARY_ENV} to its path.")
322
+ pid = busy_pid()
323
+ if pid is not None:
324
+ return False, (f"a window on this profile is already open (pid {pid}). Use it, "
325
+ "or close it and run this again.")
326
+ try:
327
+ profile_dir().mkdir(parents=True, exist_ok=True)
328
+ except OSError as exc:
329
+ return False, f"cannot create {profile_dir()} — {exc}"
330
+
331
+ cmd = [binary, *_base_args(), "--new-window", url]
332
+ try:
333
+ subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
334
+ start_new_session=True)
335
+ except OSError as exc:
336
+ return False, f"could not start {Path(binary).name} — {exc}"
337
+ host = record_login(url)
338
+ return True, host
339
+
340
+
341
+ def forget() -> tuple[bool, str]:
342
+ """Delete the profile — every session in it. ``(ok, message)``."""
343
+ target = profile_dir()
344
+ if not target.exists():
345
+ return True, f"nothing to remove: {target} does not exist."
346
+ pid = busy_pid()
347
+ if pid is not None:
348
+ return False, (f"a window on this profile is open (pid {pid}). Close it first, "
349
+ "or the deletion leaves a half-written profile behind.")
350
+ try:
351
+ shutil.rmtree(target)
352
+ sites_file().unlink(missing_ok=True)
353
+ except OSError as exc:
354
+ return False, f"could not remove {target} — {exc}"
355
+ return True, f"removed {target} and every session in it."
356
+
357
+
358
+ __all__ = [
359
+ "BINARY_ENV", "Fetched", "PROFILE_ENV", "busy_pid", "fetch_dom", "find_browser",
360
+ "forget", "is_available", "logged_in_hosts", "open_login", "profile_dir",
361
+ "record_login", "sites_file", "user_agent",
362
+ ]
@@ -143,12 +143,16 @@ def main() -> None:
143
143
  _report_command(rest)
144
144
  return
145
145
 
146
+ if noun == "browser":
147
+ _browser_command(rest)
148
+ return
149
+
146
150
  _print_usage()
147
151
  sys.exit(1)
148
152
 
149
153
 
150
154
  def _print_usage() -> None:
151
- print("usage: agentos <configure|chat|agent|integration|default-agents|agentd|github|report> ...")
155
+ print("usage: agentos <configure|chat|agent|integration|default-agents|agentd|github|report|browser> ...")
152
156
  print()
153
157
  print(" agentos configure [--rotate]")
154
158
  print(" agentos chat [--product-id ID] [--pv-id ID] [--no-autorun] # requirements → ticket")
@@ -159,6 +163,7 @@ def _print_usage() -> None:
159
163
  print(" agentos agentd <start [--foreground]|status>")
160
164
  print(" agentos github <ingest [--days N]|status> # PR ingestion; --days N backfills N days")
161
165
  print(" agentos report <submit|list|show ID> # report work Jira/GitHub never saw")
166
+ print(" agentos browser <login URL|status|logout> # sign in once; private links become readable")
162
167
 
163
168
 
164
169
  # ── agent ────────────────────────────────────────────────────────────────
@@ -732,6 +737,7 @@ def _report_usage() -> None:
732
737
  print(" agentos report list [--limit N]")
733
738
  print(" agentos report show <ID>")
734
739
  print(" agentos report formats # what a proof can be")
740
+ print(" agentos report check <URL> # can this box open it?")
735
741
  print()
736
742
  print(" Report work that never reached Jira or GitHub, attach proofs, and have the")
737
743
  print(" submission scored against them. It then appears in your daily and overall")
@@ -741,6 +747,11 @@ def _report_usage() -> None:
741
747
  print(" --text the report body; --file reads it from a file instead")
742
748
  print(" --proof repeatable — a local file path OR an http(s) URL")
743
749
  print(" --no-wait submit and exit without waiting for the verdict")
750
+ print()
751
+ print(" A proof URL is read HERE, with your own credentials, before it is submitted —")
752
+ print(" a private PR or an internal page the backend could only 404 on. `check` shows")
753
+ print(" which route opens a link (gh/az token, a host token, or your signed-in")
754
+ print(" browser) without submitting anything.")
744
755
 
745
756
 
746
757
  def _report_flag(args: list[str], name: str) -> list[str]:
@@ -758,6 +769,122 @@ def _report_flag(args: list[str], name: str) -> list[str]:
758
769
  return out
759
770
 
760
771
 
772
+ def _browser_usage() -> None:
773
+ print("usage: agentos browser login <URL> # sign in once; the session is kept")
774
+ print(" agentos browser status # what this profile is signed in to")
775
+ print(" agentos browser logout [--yes] # delete the profile and every session")
776
+ print()
777
+ print(" A private link is only evidence if something here can open it. Tokens cover")
778
+ print(" GitHub and Microsoft; everything else — Jira, Confluence, Notion, an internal")
779
+ print(" dashboard — is covered by a browser that is signed in.")
780
+ print()
781
+ print(" That browser is NOT your everyday Chrome profile: Chrome refuses to be driven")
782
+ print(" while using it, and copying it would be cookie theft by another name. The SDK")
783
+ print(" keeps its own profile instead, at ~/.agentos/browser-profile, holding only the")
784
+ print(" sessions you deliberately sign in to here. `logout` deletes all of it.")
785
+ print()
786
+ print(" login opens a normal window. Sign in the way you normally would — password,")
787
+ print(" SSO, MFA, all of it — then close the window. Later reads run headless against")
788
+ print(" the same profile, so they are signed in too.")
789
+
790
+
791
+ def _browser_command(args: list[str]) -> None:
792
+ """``agentos browser login|status|logout`` — the signed-in half of link access."""
793
+ from sdk import browser_profile as bp
794
+
795
+ verb = args[0] if args else None
796
+ rest = args[1:]
797
+
798
+ if verb == "login" and rest and not rest[0].startswith("-"):
799
+ url = rest[0]
800
+ if not (url.startswith("http://") or url.startswith("https://")):
801
+ print(f"error: not an http(s) URL: {url}")
802
+ sys.exit(1)
803
+ ok, message = bp.open_login(url)
804
+ if not ok:
805
+ print(f"error: {message}")
806
+ sys.exit(1)
807
+ print(f"opened a browser window on the SDK's profile at {url}")
808
+ print(f"profile : {bp.profile_dir()}")
809
+ print()
810
+ print(" 1. sign in the way you normally would (password, SSO, MFA — all of it)")
811
+ print(" 2. close the window when the page you want is visible")
812
+ print(f" 3. check it worked: agentos report check {url}")
813
+ print()
814
+ print(" Tick 'remember me' / 'stay signed in' where the site offers it — a session")
815
+ print(" cookie with no expiry is dropped when the browser closes, by design.")
816
+ return
817
+
818
+ if verb == "status":
819
+ binary = bp.find_browser()
820
+ print(f"browser : {binary or 'NONE FOUND — install Chrome, Chromium, Edge or Brave'}")
821
+ print(f"profile : {bp.profile_dir()}"
822
+ f"{'' if bp.profile_dir().exists() else ' (not created yet)'}")
823
+ pid = bp.busy_pid()
824
+ if pid is not None:
825
+ print(f"in use : yes — a window is open (pid {pid}); headless reads will fail")
826
+ hosts = bp.logged_in_hosts()
827
+ if not hosts:
828
+ print("signed in to: nothing yet — `agentos browser login <url>`")
829
+ else:
830
+ print("signed in to:")
831
+ for host, when in hosts.items():
832
+ print(f" {host:<40} (window opened {when})")
833
+ print()
834
+ print(" A session can expire, and opening a window is not proof of signing in.")
835
+ print(" `agentos report check <url>` is what actually answers 'can I read it'.")
836
+ return
837
+
838
+ if verb == "logout":
839
+ if "--yes" not in rest:
840
+ print(f"This deletes {bp.profile_dir()} and every session signed in to it.")
841
+ print("Re-run with --yes to confirm: agentos browser logout --yes")
842
+ sys.exit(1)
843
+ ok, message = bp.forget()
844
+ print(message if ok else f"error: {message}")
845
+ sys.exit(0 if ok else 1)
846
+
847
+ _browser_usage()
848
+ sys.exit(1)
849
+
850
+
851
+ def _report_check(url: str) -> None:
852
+ """``agentos report check <URL>`` — can this machine open that link?
853
+
854
+ The diagnostic for the whole point of `sdk.url_access`: a proof link is only
855
+ evidence if something here can read it, and when one comes back unverifiable
856
+ the operator needs to know WHICH door was shut — no `gh` login, a browser
857
+ signed out, a host nobody configured a token for — without filing a report to
858
+ find out. Prints the winning route and a short excerpt, or the reason.
859
+ """
860
+ import asyncio
861
+
862
+ from sdk import url_access
863
+
864
+ res = asyncio.run(url_access.resolve_url(url))
865
+ print(f"url : {url}")
866
+ print(f"tried : {' -> '.join(res.attempts) or 'nothing'}")
867
+ if res.ok:
868
+ excerpt = res.text.strip().splitlines()
869
+ print(f"readable: yes, via {res.via} ({len(res.text)} chars)")
870
+ print("excerpt :")
871
+ for line in excerpt[:8]:
872
+ print(f" {line[:110]}")
873
+ if len(excerpt) > 8:
874
+ print(f" … {len(excerpt) - 8} more line(s)")
875
+ return
876
+ print("readable: no")
877
+ print(f"reason : it {res.reason}")
878
+ print()
879
+ print(" Fixes, in the order they usually work:")
880
+ print(" · `gh auth login` — private GitHub PRs and issues")
881
+ print(" · `az login` — Outlook/SharePoint via Microsoft Graph")
882
+ print(f" · `agentos browser login {url}` — anything else: sign in once, in a")
883
+ print(" browser profile the SDK keeps, and every later read is signed in too")
884
+ print(f" · add a token for the host to {url_access.AUTH_FILE}")
885
+ sys.exit(1)
886
+
887
+
761
888
  def _report_command(args: list[str]) -> None:
762
889
  """``agentos report submit|list|show`` — the CLI half of self-reporting.
763
890
 
@@ -785,6 +912,9 @@ def _report_command(args: list[str]) -> None:
785
912
  if verb == "formats":
786
913
  _report_formats()
787
914
  return
915
+ if verb == "check" and rest and not rest[0].startswith("-"):
916
+ _report_check(rest[0])
917
+ return
788
918
  except TokenNotConfigured as exc:
789
919
  # The same failure every other verb gives on an unconfigured machine —
790
920
  # `sdk.config` composes a cause-specific message; we only add the prefix.
@@ -899,8 +1029,20 @@ def _report_formats() -> None:
899
1029
  print(" ffmpeg is NOT installed — a video proof is recorded and described, but")
900
1030
  print(" its content cannot be inspected. Install ffmpeg to change that.")
901
1031
  print(" Audio cannot be transcribed locally; it is recorded and described.")
902
- print(" URL proofs are fetched here. A GitHub PR or issue is read through the API")
903
- print(" with your linked token, so proofs in private repos are readable.")
1032
+ print()
1033
+ print(" Any other file type is still ACCEPTED — it is attached, named and sized,")
1034
+ print(" and scored as evidence that exists but could not be read. Nothing is")
1035
+ print(" refused for its type; only for size or count.")
1036
+ print()
1037
+ from sdk.self_report import MAX_PROOFS, proof_max_mb
1038
+ print(f" Limits: {proof_max_mb()} MB per proof (AGENTOS_PROOF_MAX_MB), "
1039
+ f"{MAX_PROOFS} proofs per submission.")
1040
+ print()
1041
+ print(" URL proofs are read HERE, with your credentials — a host token, your `gh`")
1042
+ print(" or `az` login, or a browser profile you signed in to once")
1043
+ print(" (`agentos browser login <url>`). `agentos report check <url>` says which.")
1044
+ print(" Media AT a URL is not downloaded as media — attach it as a file to have")
1045
+ print(" it read.")
904
1046
 
905
1047
 
906
1048
  def _report_list(limit: int) -> None: