devops-bot-sdk 1.7.46__tar.gz → 1.7.47__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 (304) hide show
  1. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/devops_bot_sdk.egg-info/SOURCES.txt +1 -1
  4. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/cli.py +7 -7
  6. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/config.py +21 -83
  7. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/faqs.py +10 -13
  8. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/firewall.py +112 -47
  9. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/help_data/areas.json +4 -4
  10. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/help_data/commands.json +2 -2
  11. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/help_data/help.json +1 -1
  12. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/help_data/pages.json +1 -1
  13. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/help_data/releases.json +10 -0
  14. devops_bot_sdk-1.7.47/sdk/help_data/snapshot.json +17 -0
  15. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/helpdesk_kb.md +30 -33
  16. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/api.py +28 -52
  17. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/app.js +19 -37
  18. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/index.html +7 -15
  19. devops_bot_sdk-1.7.47/tests/test_backend_only_allowlist.py +155 -0
  20. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_firewall_open_on_update.py +44 -1
  21. devops_bot_sdk-1.7.46/sdk/help_data/snapshot.json +0 -17
  22. devops_bot_sdk-1.7.46/tests/test_one_allowed_ip.py +0 -153
  23. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/README.md +0 -0
  24. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  25. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  26. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/devops_bot_sdk.egg-info/requires.txt +0 -0
  27. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  28. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/pyproject.toml +0 -0
  29. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/__init__.py +0 -0
  30. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/classifier.py +0 -0
  31. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/collectors/__init__.py +0 -0
  32. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/collectors/common.py +0 -0
  33. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/collectors/linux_auditd.py +0 -0
  34. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/collectors/macos_eslogger.py +0 -0
  35. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/collectors/windows_eventlog.py +0 -0
  36. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/models.py +0 -0
  37. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/os_setup.py +0 -0
  38. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/recorder.py +0 -0
  39. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/redact.py +0 -0
  40. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/settings.py +0 -0
  41. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/shipper.py +0 -0
  42. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/activity/spool.py +0 -0
  43. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agent_sharing.py +0 -0
  44. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agentd.py +0 -0
  45. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/__init__.py +0 -0
  46. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/activity.py +0 -0
  47. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/builder.py +0 -0
  48. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/chat.py +0 -0
  49. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/connectors.py +0 -0
  50. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/engines/__init__.py +0 -0
  51. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/engines/claude_code.py +0 -0
  52. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/engines/http.py +0 -0
  53. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/engines/mcp.py +0 -0
  54. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/engines/n8n.py +0 -0
  55. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/engines/shell.py +0 -0
  56. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/integrations.py +0 -0
  57. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/inventory.py +0 -0
  58. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/library/__init__.py +0 -0
  59. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/manifest.py +0 -0
  60. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/prompt_studio.py +0 -0
  61. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/prompt_template.py +0 -0
  62. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/prompt_vars.py +0 -0
  63. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/run_log.py +0 -0
  64. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/schedules.py +0 -0
  65. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/secrets.py +0 -0
  66. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/ticket_run.py +0 -0
  67. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/usage.py +0 -0
  68. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/agents/varstore.py +0 -0
  69. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/browser_profile.py +0 -0
  70. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/claude_usage.py +0 -0
  71. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/client.py +0 -0
  72. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/collectors/__init__.py +0 -0
  73. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/collectors/files.py +0 -0
  74. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/collectors/process.py +0 -0
  75. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/collectors/screenshot.py +0 -0
  76. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/crucial.py +0 -0
  77. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/custom_agents.py +0 -0
  78. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/deferred_tickets.py +0 -0
  79. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/departments.py +0 -0
  80. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/design_verify/__init__.py +0 -0
  81. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/design_verify/assets.py +0 -0
  82. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/design_verify/bootstrap.py +0 -0
  83. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/design_verify/browser.py +0 -0
  84. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/design_verify/compare.py +0 -0
  85. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/design_verify/loop.py +0 -0
  86. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/estimator.py +0 -0
  87. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/exceptions.py +0 -0
  88. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/fallback_handoff.py +0 -0
  89. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/formats.py +0 -0
  90. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/git_ops.py +0 -0
  91. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/github_fetch.py +0 -0
  92. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/github_ingest.py +0 -0
  93. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/graph_recon.py +0 -0
  94. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/graphify.py +0 -0
  95. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/help_actions.py +0 -0
  96. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/helpdesk.py +0 -0
  97. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/hooks/__init__.py +0 -0
  98. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/hooks/crucial_guard.py +0 -0
  99. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/instructions_template.py +0 -0
  100. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/intake.py +0 -0
  101. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ipc/__init__.py +0 -0
  102. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ipc/electron_bridge.py +0 -0
  103. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ipc/handlers.py +0 -0
  104. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/journal.py +0 -0
  105. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/local_exec.py +0 -0
  106. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/log_colors.py +0 -0
  107. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/memory_guard.py +0 -0
  108. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/model_policy.py +0 -0
  109. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/models/__init__.py +0 -0
  110. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/models/envelope.py +0 -0
  111. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/models/requests.py +0 -0
  112. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/models/responses.py +0 -0
  113. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/models/snapshots.py +0 -0
  114. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/phase_timer.py +0 -0
  115. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/plan_run.py +0 -0
  116. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/platform_compat.py +0 -0
  117. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/prd_progress.py +0 -0
  118. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/proof_readers.py +0 -0
  119. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/providers.py +0 -0
  120. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/py.typed +0 -0
  121. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/resume.py +0 -0
  122. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/router.py +0 -0
  123. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/run_auto.py +0 -0
  124. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/schedule_tickets.py +0 -0
  125. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/schedule_timeline.py +0 -0
  126. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/scheduler.py +0 -0
  127. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/scope_plan.py +0 -0
  128. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/self_report.py +0 -0
  129. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/self_report_local.py +0 -0
  130. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/sse.py +0 -0
  131. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/swagger.py +0 -0
  132. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/task_file.py +0 -0
  133. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/task_types.py +0 -0
  134. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/test.py +0 -0
  135. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/test_env.py +0 -0
  136. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/test_pipeline.py +0 -0
  137. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ticket_board.py +0 -0
  138. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ticket_bulk.py +0 -0
  139. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ticket_fields.py +0 -0
  140. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ticket_plan.py +0 -0
  141. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ticket_pools.py +0 -0
  142. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/tickets.py +0 -0
  143. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/tiers/__init__.py +0 -0
  144. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/tiers/mini_agent.py +0 -0
  145. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/__init__.py +0 -0
  146. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/dashboard.py +0 -0
  147. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/health.py +0 -0
  148. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/ip_allowlist.py +0 -0
  149. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/jobs.py +0 -0
  150. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/models.py +0 -0
  151. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/runner.py +0 -0
  152. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/security.py +0 -0
  153. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/server.py +0 -0
  154. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/service.py +0 -0
  155. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/session.py +0 -0
  156. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/setup_manager.py +0 -0
  157. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/agents.html +0 -0
  158. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/chat.html +0 -0
  159. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/custom-agents.html +0 -0
  160. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/default-agents.html +0 -0
  161. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/faqs.html +0 -0
  162. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/help.html +0 -0
  163. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/report.html +0 -0
  164. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/runs.html +0 -0
  165. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/schedules.html +0 -0
  166. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/setup.html +0 -0
  167. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/styles.css +0 -0
  168. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/static/tickets.html +0 -0
  169. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/ui/swagger.py +0 -0
  170. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/updater.py +0 -0
  171. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/url_access.py +0 -0
  172. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/vps_health.py +0 -0
  173. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/vps_identity.py +0 -0
  174. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/sdk/write_hook.py +0 -0
  175. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/setup.cfg +0 -0
  176. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_activity_claude_runs.py +0 -0
  177. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_activity_collectors.py +0 -0
  178. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_activity_core.py +0 -0
  179. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_activity_live_tracking.py +0 -0
  180. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_activity_macos.py +0 -0
  181. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_activity_os_setup.py +0 -0
  182. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_activity_pipeline.py +0 -0
  183. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_activity.py +0 -0
  184. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_activity_tracking.py +0 -0
  185. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_chat_features.py +0 -0
  186. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_crud_cli.py +0 -0
  187. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_listing.py +0 -0
  188. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_prompt_async.py +0 -0
  189. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_prompt_models.py +0 -0
  190. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_run_ticket.py +0 -0
  191. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_schedules.py +0 -0
  192. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_sharing.py +0 -0
  193. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agent_workspace_settings.py +0 -0
  194. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_agents_connectors_builder.py +0 -0
  195. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_allowed_tools_config.py +0 -0
  196. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_approval_park.py +0 -0
  197. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_auto_agent_dispatch.py +0 -0
  198. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_auto_ticket_queue.py +0 -0
  199. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_browser_profile.py +0 -0
  200. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_cheap_tier.py +0 -0
  201. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_claude_usage_gate.py +0 -0
  202. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_client_jira_projects.py +0 -0
  203. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_config_token_diagnostics.py +0 -0
  204. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_configure_token_owner.py +0 -0
  205. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_console_encoding.py +0 -0
  206. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_crash_recovery.py +0 -0
  207. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_create_safe_retry.py +0 -0
  208. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_deferred_tickets.py +0 -0
  209. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_departments.py +0 -0
  210. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_done_transition.py +0 -0
  211. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_epic_decompose_run.py +0 -0
  212. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_epic_inherited_agent_id.py +0 -0
  213. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_epic_session_isolation.py +0 -0
  214. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_estimator.py +0 -0
  215. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_fallback_cli.py +0 -0
  216. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_fallback_cli_upgrade.py +0 -0
  217. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_fallback_rate_limit.py +0 -0
  218. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_faqs.py +0 -0
  219. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_formats.py +0 -0
  220. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_git_action.py +0 -0
  221. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_github_fetch.py +0 -0
  222. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_github_ingest.py +0 -0
  223. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_graph_off_by_default.py +0 -0
  224. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_graph_recon.py +0 -0
  225. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_graphify_build.py +0 -0
  226. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_graphify_hook_install.py +0 -0
  227. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_health.py +0 -0
  228. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_help_actions.py +0 -0
  229. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_help_attachments.py +0 -0
  230. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_help_questions.py +0 -0
  231. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_helpdesk.py +0 -0
  232. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_infographic_data.py +0 -0
  233. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_instructions_template.py +0 -0
  234. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_intake_job_outcome.py +0 -0
  235. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_intake_plan_review.py +0 -0
  236. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_journal.py +0 -0
  237. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_limit_fallback_handoff.py +0 -0
  238. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_local_exec_provider.py +0 -0
  239. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_log_colors_backlog.py +0 -0
  240. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_mcp_route_contract.py +0 -0
  241. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_memory_guard.py +0 -0
  242. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_model_policy.py +0 -0
  243. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_panel_help.py +0 -0
  244. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_panel_ticket_schedules.py +0 -0
  245. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_parallel_tickets.py +0 -0
  246. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_phase_timer.py +0 -0
  247. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_plan_ahead.py +0 -0
  248. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_plan_new_command.py +0 -0
  249. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_plan_run_activity.py +0 -0
  250. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_pool_pr_modes.py +0 -0
  251. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_pr_identity.py +0 -0
  252. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_prd_artifact.py +0 -0
  253. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_project_path_resolution.py +0 -0
  254. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_proof_readers.py +0 -0
  255. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_providers.py +0 -0
  256. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_quiet_run_log.py +0 -0
  257. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_recon_session_isolation.py +0 -0
  258. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_resume.py +0 -0
  259. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_router.py +0 -0
  260. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_run_auto_agent_only.py +0 -0
  261. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_run_outputs.py +0 -0
  262. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_run_verdict_is_honest.py +0 -0
  263. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_schedule_api.py +0 -0
  264. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_schedule_outputs_and_plans.py +0 -0
  265. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_schedule_tickets.py +0 -0
  266. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_schedule_timeline.py +0 -0
  267. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_scheduler.py +0 -0
  268. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_scheduler_transient_retry.py +0 -0
  269. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_self_report.py +0 -0
  270. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_service_token_warning.py +0 -0
  271. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_stale_panel_restart.py +0 -0
  272. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_swagger_command.py +0 -0
  273. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_task_file_and_plan_run.py +0 -0
  274. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_task_file_format_doc.py +0 -0
  275. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_theme_switch.py +0 -0
  276. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_board.py +0 -0
  277. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_bulk.py +0 -0
  278. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_cli.py +0 -0
  279. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_detail_view.py +0 -0
  280. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_fields.py +0 -0
  281. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_plan.py +0 -0
  282. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_pools.py +0 -0
  283. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_run_resolve_by_id.py +0 -0
  284. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_schedule_and_agent.py +0 -0
  285. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ticket_speed_fixes.py +0 -0
  286. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_tier_flag_parity.py +0 -0
  287. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ui_bruteforce_guard.py +0 -0
  288. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ui_gate.py +0 -0
  289. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ui_lifespan.py +0 -0
  290. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_ui_no_native_dialogs.py +0 -0
  291. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_update_timer.py +0 -0
  292. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_updater_extras.py +0 -0
  293. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_updater_ignores_cwd.py +0 -0
  294. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_updater_pep668.py +0 -0
  295. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_updater_reports_the_truth.py +0 -0
  296. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_url_access.py +0 -0
  297. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_version_flag.py +0 -0
  298. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_vps_health.py +0 -0
  299. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_vps_only_reporting.py +0 -0
  300. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_windows_loop_identity.py +0 -0
  301. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_windows_panel_task.py +0 -0
  302. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_windows_stop_and_pidfile.py +0 -0
  303. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_windows_task_paths.py +0 -0
  304. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.47}/tests/test_write_hook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.7.46
3
+ Version: 1.7.47
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.7.46
3
+ Version: 1.7.47
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
@@ -190,6 +190,7 @@ tests/test_allowed_tools_config.py
190
190
  tests/test_approval_park.py
191
191
  tests/test_auto_agent_dispatch.py
192
192
  tests/test_auto_ticket_queue.py
193
+ tests/test_backend_only_allowlist.py
193
194
  tests/test_browser_profile.py
194
195
  tests/test_cheap_tier.py
195
196
  tests/test_claude_usage_gate.py
@@ -235,7 +236,6 @@ tests/test_log_colors_backlog.py
235
236
  tests/test_mcp_route_contract.py
236
237
  tests/test_memory_guard.py
237
238
  tests/test_model_policy.py
238
- tests/test_one_allowed_ip.py
239
239
  tests/test_panel_help.py
240
240
  tests/test_panel_ticket_schedules.py
241
241
  tests/test_parallel_tickets.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.7.46
3
+ Version: 1.7.47
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.7.46" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.7.47" # 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
@@ -109,12 +109,12 @@ Command surface:
109
109
  `--any` opens the port to every source instead (not recommended: every
110
110
  scanner then reaches the panel). See sdk.firewall.
111
111
 
112
- agentos allow-ip [IP | --clear]
113
- Show, set or clear THE one outside IP allowed to reach the panel (besides
114
- this machine and the backend). Only one at a time: a new IP replaces the
115
- old one in the allowlist and the host firewall. Same-machine only — this
116
- command over SSH, or the panel opened at http://127.0.0.1:8765 on the box;
117
- the remote API refuses. The firewall half needs root. See sdk.firewall.
112
+ agentos allow-ip [--clear]
113
+ Show who may reach the panel: this machine and the DevOps Bot backend only
114
+ — no outside IP (since 1.7.47). `agentos allow-ip <IP>` is refused (exit 2)
115
+ with the SSH-tunnel alternative (ssh -L 8765:127.0.0.1:8765 <user>@<server>).
116
+ `--clear` removes outside IPs an older SDK left in config.toml and syncs the
117
+ host firewall (that half needs root). See sdk.firewall.
118
118
 
119
119
  agentos swagger [--open] [--json [PATH]] [--port N]
120
120
  Where the panel's Swagger UI is and whether it can actually be reached:
@@ -294,7 +294,7 @@ def _print_usage() -> None:
294
294
  print(" agentos report <submit|list|show ID> # report work Jira/GitHub never saw")
295
295
  print(" agentos browser <login URL|status|logout> # sign in once; private links become readable")
296
296
  print(" agentos fallback <install|login|status|logout> # the AI Draftsman CLI used when claude is not working")
297
- print(" agentos allow-ip [IP | --clear] # the ONE outside IP allowed to reach the panel")
297
+ print(" agentos allow-ip [--clear] # who may reach the panel (no outside IP; use an SSH tunnel)")
298
298
  print(" agentos firewall [--apply] [--revoke IP] [--port N] # limit the UI port to the allowlisted IPs")
299
299
  print(" agentos firewall --any [--apply] # open the UI port to any source (allowlist is the gate)")
300
300
  print(" agentos swagger [--open] [--json [PATH]] # where /docs is, and can you reach it")
@@ -228,30 +228,18 @@ def _parse_ip_list(raw: str) -> list[str]:
228
228
 
229
229
  def get_allowed_ips() -> list[str]:
230
230
  """Client IPs permitted to reach the ``agentos-ui`` server: the built-in backend
231
- IP(s) plus AT MOST ONE user IP.
232
-
233
- ONE user IP at a time is the rule — the panel is reachable from a single
234
- outside address, so a leaked token is useless anywhere else and the host
235
- firewall (`sdk.firewall.ensure_restricted`) admits exactly that address.
236
- Loopback and this host's own IPs are always trusted separately.
237
-
238
- The user IP is chosen by :func:`user_allowed_ip`; anything else configured is
239
- ignored and reported by :func:`ignored_allowed_ips`.
231
+ IP(s) and nothing else.
232
+
233
+ No outside user IP is allowed (since 1.7.47 — 1.7.46 allowed one). The panel is
234
+ for this machine and the DevOps Bot backend: open it on the server, or from
235
+ another computer through an SSH tunnel (``ssh -L 8765:127.0.0.1:8765 …``),
236
+ which arrives as loopback. Loopback and this host's own IPs are always trusted
237
+ separately. Anything still configured in ``AGENTOS_UI_ALLOWED_IPS`` or
238
+ config.toml ``allowed_ips`` is ignored and reported by
239
+ :func:`ignored_allowed_ips`; the host firewall (`sdk.firewall.ensure_restricted`)
240
+ removes its rule.
240
241
  """
241
- out = list(_DEFAULT_ALLOWED_IPS)
242
- user = user_allowed_ip()
243
- if user and user not in out:
244
- out.append(user)
245
- return out
246
-
247
-
248
- def _single_address(value: str) -> bool:
249
- """True for one host address (a bare IP, or a /32 / /128) — not a range."""
250
- import ipaddress
251
- try:
252
- return ipaddress.ip_network(value.strip(), strict=False).num_addresses == 1
253
- except ValueError:
254
- return False
242
+ return list(_DEFAULT_ALLOWED_IPS)
255
243
 
256
244
 
257
245
  def _configured_user_ips() -> list[str]:
@@ -267,22 +255,9 @@ def _configured_user_ips() -> list[str]:
267
255
  return out
268
256
 
269
257
 
270
- def user_allowed_ip() -> str | None:
271
- """The ONE user IP allowed to reach the panel, or None.
272
-
273
- The most recent wins: the runtime entry (config.toml — set by
274
- ``POST /api/ui/allowlist``) over ``.env``, and within a source the LAST entry,
275
- since the documented way to add an IP is to append it. CIDR ranges are never
276
- chosen — a range is many IPs.
277
- """
278
- candidates = [ip for ip in _configured_user_ips() if _single_address(ip)]
279
- return candidates[-1] if candidates else None
280
-
281
-
282
258
  def ignored_allowed_ips() -> list[str]:
283
- """Configured user IPs/ranges that are NOT allowed, because only one may be."""
284
- keep = user_allowed_ip()
285
- return [ip for ip in _configured_user_ips() if ip != keep]
259
+ """Configured outside IPs/ranges that are NOT allowed — none ever is."""
260
+ return _configured_user_ips()
286
261
 
287
262
 
288
263
  # Built-in baseline of tools a headless agent run may use. Kept here (not in
@@ -339,26 +314,14 @@ class PermanentAllowlistEntry(ValueError):
339
314
 
340
315
 
341
316
  def permanent_allowed_ips() -> list[str]:
342
- """IPs/CIDRs whitelisted **permanently** — from ``.env`` (``~/.agentos/.env`` or
343
- CWD) or the ``AGENTOS_UI_ALLOWED_IPS`` process env var.
344
-
345
- These are the fixed baseline: they are ALWAYS allowed and **cannot be disabled**
346
- through the API — change them by editing ``.env`` and restarting.
347
- """
348
- out: list[str] = []
349
- for src in (",".join(_DEFAULT_ALLOWED_IPS), # built-in backend IP(s) — always permanent
350
- _dotenv_value("AGENTOS_UI_ALLOWED_IPS"),
351
- os.environ.get("AGENTOS_UI_ALLOWED_IPS", "")):
352
- for ip in _parse_ip_list(src):
353
- if ip not in out:
354
- out.append(ip)
355
- return out
317
+ """The IPs that are always allowed and cannot be disabled — the built-in
318
+ backend IP(s). ``AGENTOS_UI_ALLOWED_IPS`` no longer adds to them (1.7.47)."""
319
+ return list(_DEFAULT_ALLOWED_IPS)
356
320
 
357
321
 
358
322
  def runtime_allowed_ips() -> list[str]:
359
- """IPs/CIDRs whitelisted **at runtime** (config.toml ``allowed_ips``) — added by
360
- the whitelist endpoint and removable by the disable endpoint. They stay in the
361
- allowlist until disabled."""
323
+ """IPs/CIDRs left in config.toml ``allowed_ips`` by an older SDK. Ignored since
324
+ 1.7.47 (see :func:`get_allowed_ips`); ``agentos allow-ip --clear`` removes them."""
362
325
  return _parse_ip_list(_load_raw().get("allowed_ips", ""))
363
326
 
364
327
 
@@ -461,40 +424,15 @@ def journal_enabled() -> bool:
461
424
  return flag(FLAG_JOURNAL) or tiered_routing_enabled()
462
425
 
463
426
 
464
- def add_allowed_ip(ip: str) -> list[str]:
465
- """Make ``ip`` THE user IP at runtime (persisted to config.toml ``allowed_ips``).
466
-
467
- Only one user IP may reach the panel at a time, so this REPLACES whichever was
468
- there before — including one from ``.env``, which it outranks while set.
469
- Idempotent. The middleware sees it on the next request; the caller updates the
470
- host firewall. A backend IP is already allowed and is left alone. Returns the
471
- effective allowlist.
472
- """
473
- ip = (ip or "").strip()
474
- if not ip:
475
- raise ValueError("ip is required")
476
- if not _single_address(ip):
477
- raise ValueError(f"{ip} is a range — only one IP may be allowed at a time")
478
- if ip in _DEFAULT_ALLOWED_IPS:
479
- return get_allowed_ips()
480
- raw = _load_raw() # preserves token_enc / base_url / etc.
481
- raw["allowed_ips"] = ip
482
- _save_raw(raw)
483
- return get_allowed_ips()
484
-
485
-
486
427
  def remove_allowed_ip(ip: str) -> list[str]:
487
- """Disable a RUNTIME-whitelisted IP/CIDR (remove from config.toml ``allowed_ips``).
428
+ """Remove a leftover entry from config.toml ``allowed_ips`` (ignored anyway).
488
429
 
489
- Raises :class:`PermanentAllowlistEntry` if the IP is permanently whitelisted via
490
- ``.env`` — those are never removable through the API (edit ``.env`` instead).
491
- Returns the merged allowlist.
430
+ Raises :class:`PermanentAllowlistEntry` for a backend IP. Returns the allowlist.
492
431
  """
493
432
  ip = (ip or "").strip()
494
433
  if ip in permanent_allowed_ips():
495
434
  raise PermanentAllowlistEntry(
496
- f"{ip} is permanently whitelisted via .env (AGENTOS_UI_ALLOWED_IPS) — "
497
- "it cannot be disabled through the API; edit .env and restart to change it."
435
+ f"{ip} is a DevOps Bot backend IP — it is always allowed and cannot be removed."
498
436
  )
499
437
  raw = _load_raw()
500
438
  entries = [x for x in _parse_ip_list(raw.get("allowed_ips", "")) if x != ip]
@@ -19,19 +19,16 @@ FAQS: list[tuple[str, str, str]] = [
19
19
  "7. **Your co_ API token** — `agentos configure`\n"
20
20
  "Then `agentos doctor` checks the lot."),
21
21
  ("Basics", "How do I open the control panel from another machine?",
22
- "**Easiest — an SSH tunnel (nothing to open):** on your machine run "
23
- "`ssh -L 8765:127.0.0.1:8765 you@SERVER`, then browse `http://127.0.0.1:8765`.\n"
24
- "**Directly by IP** — only ONE outside IP can open the panel at a time, and only someone on the "
25
- "server can set it:\n"
26
- "1. On YOUR machine, find your public IP: `curl -s https://api.ipify.org; echo` → prints e.g. `203.0.113.7` "
27
- "(Windows PowerShell: `Invoke-RestMethod https://api.ipify.org`)\n"
28
- "2. On the SERVER (over SSH): `agentos allow-ip 203.0.113.7` — this REPLACES the previous IP, which loses access. "
29
- "Or open the panel on the server itself (`http://127.0.0.1:8765`) → Dashboard → **Remote access**.\n"
30
- "3. If it says the firewall needs root: `sudo agentos firewall --apply` (Windows: `agentos firewall --apply` "
31
- "in an Administrator PowerShell).\n"
32
- "4. Browse `http://SERVER-IP:8765` and paste your co_ token. `agentos allow-ip` shows the allowed IP; "
33
- "`agentos allow-ip --clear` removes it. The remote API cannot change it, so a leaked token cannot "
34
- "open the panel to another address."),
22
+ "**Use an SSH tunnel** — no outside IP can be allowed; only the server itself and the DevOps Bot "
23
+ "backend reach the panel.\n"
24
+ "1. **Linux / macOS:** on your machine run `ssh -L 8765:127.0.0.1:8765 you@SERVER` and leave it open.\n"
25
+ "2. **Windows:** the same command in PowerShell (built-in OpenSSH client): "
26
+ "`ssh -L 8765:127.0.0.1:8765 you@SERVER`.\n"
27
+ "3. Browse `http://127.0.0.1:8765` on your machine — it arrives as loopback, so no IP to allow. "
28
+ "(Port 8765 busy locally? `ssh -L 9000:127.0.0.1:8765 …` and use `http://127.0.0.1:9000`.)\n"
29
+ "Or open `http://127.0.0.1:8765` on the server itself. `agentos allow-ip <IP>` is refused; "
30
+ "`agentos allow-ip` shows who may connect and `agentos allow-ip --clear` removes outside IPs an older "
31
+ "SDK left behind. A leaked token cannot open the panel to another address."),
35
32
  ("Basics", "How do I use devops-bot-sdk on another machine?",
36
33
  "Install it there too — the same checklist (Python, git, SSH key, repo access, claude or ad), then "
37
34
  "`pip install \"git+ssh://git@github.com/consultancy-outfit/DevOpsBot-SDK.git\"` and `agentos configure` "
@@ -158,6 +158,41 @@ def revoke_any_rule(port: int, system: str | None = None) -> str:
158
158
  return f"# unsupported platform {sysname!r}"
159
159
 
160
160
 
161
+ def revoke_any_rules(port: int, system: str | None = None,
162
+ forms: list[str] | None = None) -> list[str]:
163
+ """Every command needed to remove the open-to-any rule(s) for ``port``.
164
+
165
+ ufw keeps ``8765/tcp`` and a bare ``8765`` (tcp+udp, what a hand-typed
166
+ ``ufw allow 8765`` makes) as DIFFERENT rules, and deleting one leaves the other
167
+ — the port then stays open to everyone after ``ufw delete allow 8765/tcp``.
168
+ ``forms`` are the ``To`` spellings ufw shows for the Anywhere rows; unknown
169
+ (None) → both, since deleting a rule that does not exist is harmless.
170
+ """
171
+ sysname = (system or _system()).lower()
172
+ if sysname != "linux":
173
+ return [revoke_any_rule(port, sysname)]
174
+ if forms is None:
175
+ forms = [f"{port}/tcp", str(port)]
176
+ return [f"sudo ufw delete allow {f}" for f in forms]
177
+
178
+
179
+ def _ufw_any_forms(port: int) -> list[str] | None:
180
+ """The ``To`` spellings of the IPv4 open-to-any rows for ``port`` (e.g.
181
+ ``["8765/tcp", "8765"]``), or None when ufw cannot be read."""
182
+ text = _run(_ufw_argv() + ["status"])
183
+ if text is None:
184
+ return None
185
+ out: list[str] = []
186
+ for line in text.splitlines():
187
+ parts = line.split("#", 1)[0].split()
188
+ if (len(parts) >= 3 and "(v6)" not in line and parts[1].upper() == "ALLOW"
189
+ and parts[-1] == "Anywhere"
190
+ and (parts[0] == str(port) or parts[0].startswith(f"{port}/"))
191
+ and parts[0] not in out):
192
+ out.append(parts[0])
193
+ return out
194
+
195
+
161
196
  def ensure_open(port: int | None = None, *, apply: bool = True) -> tuple[bool, str]:
162
197
  """Make sure ``port`` is reachable. Returns ``(changed, message)``. Never raises.
163
198
 
@@ -197,7 +232,7 @@ def ensure_open(port: int | None = None, *, apply: bool = True) -> tuple[bool, s
197
232
  return (False, f"macOS: pf state is not readable and pf rules are file-based —\n{printable}")
198
233
  if sources is None:
199
234
  ok, tool = _tool_present()
200
- why = tool if not ok else "needs root to read the firewall state"
235
+ why = tool if not ok else "needs root or passwordless sudo to read the firewall state"
201
236
  return (False, f"could not read the firewall state ({why}) — to open it yourself:\n{printable}")
202
237
 
203
238
  # Readable and genuinely empty for this port.
@@ -214,13 +249,16 @@ def ensure_open(port: int | None = None, *, apply: bool = True) -> tuple[bool, s
214
249
 
215
250
 
216
251
  def _apply_rules(rules: list[str]) -> list[str]:
217
- """Run each rule (already privileged, so a printed ``sudo `` is stripped).
252
+ """Run each rule. As root a printed ``sudo `` is stripped; otherwise it becomes
253
+ ``sudo -n `` (passwordless sudo — see :func:`_sudo_n_ok`), which fails instead
254
+ of waiting for a password.
218
255
 
219
256
  Returns one ``"<cmd>: <reason>"`` per failure — empty means all applied.
220
257
  """
258
+ prefix = "sudo -n " if _sudo_n_ok() else ""
221
259
  failed: list[str] = []
222
260
  for rule in rules:
223
- cmd = rule[len("sudo "):] if rule.startswith("sudo ") else rule
261
+ cmd = prefix + rule[len("sudo "):] if rule.startswith("sudo ") else rule
224
262
  try:
225
263
  proc = subprocess.run(
226
264
  cmd, shell=True, capture_output=True, text=True, timeout=30,
@@ -293,10 +331,10 @@ def ensure_restricted(port: int | None = None, *, apply: bool = True,
293
331
  " sudo ufw allow OpenSSH && sudo ufw enable")
294
332
 
295
333
  if sources is None:
296
- rules = allow_rules(ips, port, sysname) + [revoke_any_rule(port, sysname)]
334
+ rules = allow_rules(ips, port, sysname) + revoke_any_rules(port, sysname)
297
335
  printable = " " + "\n ".join(rules)
298
336
  ok, tool = _tool_present()
299
- why = tool if not ok else "needs root to read the firewall state"
337
+ why = tool if not ok else "needs root or passwordless sudo to read the firewall state"
300
338
  return (False, f"could not read the firewall state ({why}) — to restrict TCP {port} "
301
339
  f"to the allowlist yourself:\n{printable}")
302
340
 
@@ -309,7 +347,11 @@ def ensure_restricted(port: int | None = None, *, apply: bool = True,
309
347
  if not missing and not open_any and not stale:
310
348
  return (False, f"TCP {port} is restricted to the allowlist" + inactive_note)
311
349
 
312
- removals = ([revoke_any_rule(port, sysname)] if open_any else []) + [
350
+ # Read ufw's exact spelling only when the deletes will run; a printed list
351
+ # names both forms (deleting a rule that is not there is harmless).
352
+ any_forms = (_ufw_any_forms(port) or [f"{port}/tcp"]) if (
353
+ sysname == "linux" and open_any and apply and _is_privileged()) else None
354
+ removals = (revoke_any_rules(port, sysname, any_forms) if open_any else []) + [
313
355
  revoke_rule(x, port, sysname) for x in stale]
314
356
  rules = allow_rules(missing, port, sysname) + removals
315
357
  printable = " " + "\n ".join(rules)
@@ -467,7 +509,7 @@ def port_sources(port: int, system: str | None = None, *,
467
509
  """
468
510
  sysname = (system or _system()).lower()
469
511
  if sysname == "linux":
470
- text = _run(["ufw", "status"])
512
+ text = _run(_ufw_argv() + ["status"])
471
513
  if text is None:
472
514
  return None
473
515
  got = _parse_ufw(text, port)
@@ -488,10 +530,42 @@ def _ufw_inactive() -> bool:
488
530
  Then every ufw rule is decoration and the port is open regardless — the
489
531
  "per-IP rules are applied but the flood still arrives" case.
490
532
  """
491
- text = _run(["ufw", "status"])
533
+ text = _run(_ufw_argv() + ["status"])
492
534
  return bool(text) and "status: inactive" in text.lower()
493
535
 
494
536
 
537
+ def _is_root() -> bool:
538
+ return hasattr(os, "geteuid") and os.geteuid() == 0
539
+
540
+
541
+ _SUDO_N: bool | None = None
542
+
543
+
544
+ def _sudo_n_ok() -> bool:
545
+ """True when this (non-root, Linux) user may run ufw through ``sudo -n``.
546
+
547
+ ``agentos-auto update`` runs as the panel user, not root, so without this the
548
+ post-update hook could neither read nor fix ufw and only ever printed the
549
+ commands — the port stayed open to the world between updates. ``-n`` never
550
+ prompts, so an unattended update still cannot stall waiting for a password.
551
+ Probed once per process.
552
+ """
553
+ global _SUDO_N
554
+ if _SUDO_N is None:
555
+ _SUDO_N = False
556
+ if _system() == "linux" and not _is_root() and shutil.which("sudo") and shutil.which("ufw"):
557
+ try:
558
+ _SUDO_N = subprocess.run(["sudo", "-n", "ufw", "status"], capture_output=True,
559
+ text=True, timeout=10).returncode == 0
560
+ except Exception: # noqa: BLE001
561
+ _SUDO_N = False
562
+ return _SUDO_N
563
+
564
+
565
+ def _ufw_argv() -> list[str]:
566
+ return ["sudo", "-n", "ufw"] if _sudo_n_ok() else ["ufw"]
567
+
568
+
495
569
  def _is_privileged() -> bool:
496
570
  if _system() == "windows":
497
571
  try:
@@ -499,7 +573,7 @@ def _is_privileged() -> bool:
499
573
  return bool(ctypes.windll.shell32.IsUserAnAdmin())
500
574
  except Exception: # noqa: BLE001 — not Windows, or no ctypes
501
575
  return False
502
- return hasattr(os, "geteuid") and os.geteuid() == 0
576
+ return _is_root() or _sudo_n_ok()
503
577
 
504
578
 
505
579
  def cli(argv: list[str]) -> int:
@@ -572,7 +646,8 @@ def cli(argv: list[str]) -> int:
572
646
 
573
647
  rules = allow_rules(ips, port, sysname)
574
648
  if "Anywhere" in (port_sources(port, sysname) or set()):
575
- rules.append(revoke_any_rule(port, sysname))
649
+ rules.extend(revoke_any_rules(
650
+ port, sysname, _ufw_any_forms(port) if sysname == "linux" else None))
576
651
  print(f"\nTCP {port} is currently open to ANY source — the last rule removes that.")
577
652
  print("\nFirewall rules for this host:\n")
578
653
  for r in rules:
@@ -608,55 +683,45 @@ def cli(argv: list[str]) -> int:
608
683
 
609
684
 
610
685
  def allow_ip_cli(argv: list[str]) -> int:
611
- """``agentos allow-ip [IP | --clear]`` — set, show or clear THE one user IP.
612
-
613
- Only one outside IP may reach the panel at a time; setting a new one replaces
614
- the old one in the allowlist and in the host firewall. This command is one of
615
- the two ways to change it — both need someone on the machine (this one over
616
- SSH, or the panel opened at http://127.0.0.1:8765 on the box); the remote API
617
- refuses. Run it as the user the panel runs as, so it edits that user's
618
- ~/.agentos/config.toml; the firewall half needs root and prints the command
619
- otherwise.
686
+ """``agentos allow-ip [--clear]`` — show who may reach the panel, or clear the
687
+ outside IPs an older SDK left configured.
688
+
689
+ Since 1.7.47 no outside IP is allowed — only this machine and the DevOps Bot
690
+ backend — so setting one is refused with the SSH-tunnel alternative.
691
+ ``--clear`` empties config.toml ``allowed_ips`` and syncs the host firewall
692
+ (which drops their rules); ``.env`` entries are reported for the operator to
693
+ delete.
620
694
  """
621
695
  from sdk import config
622
696
 
623
697
  def show() -> None:
624
- user = config.user_allowed_ip()
625
- print(f"Allowed user IP : {user or '(none — only this machine and the backend)'}")
626
- print(f"Backend IPs : {', '.join(config._DEFAULT_ALLOWED_IPS)}")
698
+ print(f"Allowed : this machine + DevOps Bot backend "
699
+ f"({', '.join(config._DEFAULT_ALLOWED_IPS)}) — no outside IP")
627
700
  ignored = config.ignored_allowed_ips()
628
701
  if ignored:
629
- print(f"Ignored : {', '.join(ignored)} (only one user IP is allowed at a time)")
702
+ print(f"Ignored : {', '.join(ignored)} (outside IPs are not allowed; "
703
+ "`agentos allow-ip --clear` removes them)")
630
704
 
631
705
  args = [a for a in argv if a.strip()]
632
706
  if not args or args[0] in ("--show", "show"):
633
707
  show()
634
708
  return 0
635
709
 
636
- if args[0] in ("--clear", "clear", "--remove"):
637
- user = config.user_allowed_ip()
638
- runtime = [ip for ip in config.runtime_allowed_ips() if ip == user]
639
- if not runtime:
640
- if user:
641
- print(f"{user} comes from AGENTOS_UI_ALLOWED_IPS in ~/.agentos/.env — "
642
- "remove it there.")
643
- return 1
644
- print("No user IP is allowed — nothing to clear.")
645
- return 0
646
- config.remove_allowed_ip(user)
647
- print(f"✓ removed {user}")
648
- else:
649
- ip = args[0]
650
- before = config.user_allowed_ip()
651
- try:
652
- config.add_allowed_ip(ip)
653
- except ValueError as exc:
654
- print(f"error: {exc}")
655
- return 2
656
- print(f"✓ {ip} is now the one IP allowed to reach the panel")
657
- if before and before != ip:
658
- print(f" (replaced {before} — it no longer has access)")
659
-
710
+ if args[0] not in ("--clear", "clear", "--remove"):
711
+ print("error: outside IPs can no longer be allowed — only this machine and the "
712
+ "DevOps Bot backend reach the panel.\n"
713
+ "From another computer use an SSH tunnel:\n"
714
+ " ssh -L 8765:127.0.0.1:8765 <user>@<server> then open http://127.0.0.1:8765")
715
+ return 2
716
+
717
+ for ip in config.runtime_allowed_ips():
718
+ if ip not in config._DEFAULT_ALLOWED_IPS:
719
+ config.remove_allowed_ip(ip)
720
+ print(f"✓ removed {ip} from ~/.agentos/config.toml")
721
+ left = config.ignored_allowed_ips()
722
+ if left:
723
+ print(f"Still in AGENTOS_UI_ALLOWED_IPS (~/.agentos/.env or the environment): "
724
+ f"{', '.join(left)} — ignored; delete that line to tidy up.")
660
725
  changed, msg = sync_rules()
661
726
  print(("↻ " if changed else "") + msg)
662
727
  show()
@@ -1,12 +1,12 @@
1
1
  [
2
2
  {
3
3
  "area": "Control panel",
4
- "changes_30d": 7,
4
+ "changes_30d": 8,
5
5
  "features_30d": 2,
6
6
  "fixes_30d": 1,
7
- "latest": "security: restrict panel port to the allowlist, cheap refusals, auth-failure bans, connection cap",
8
- "latest_pr": 188,
9
- "latest_date": "2026-10-29"
7
+ "latest": "security: backend-only panel allowlist; firewall removes bare 8765 rule, uses sudo -n",
8
+ "latest_pr": 190,
9
+ "latest_date": "2026-10-30"
10
10
  },
11
11
  {
12
12
  "area": "Help",
@@ -511,10 +511,10 @@
511
511
  },
512
512
  {
513
513
  "command": "agentos allow-ip",
514
- "syntax": "agentos allow-ip [IP | --clear]",
514
+ "syntax": "agentos allow-ip [--clear]",
515
515
  "program": "agentos",
516
516
  "area": "Other",
517
- "description": "the ONE outside IP allowed to reach the panel (on the server only)",
517
+ "description": "who may reach the panel (no outside IP); --clear removes leftover ones",
518
518
  "valid": true,
519
519
  "doc": "https://github.com/consultancy-outfit/DevOpsBot-SDK/blob/main/docs/COMMANDS.md",
520
520
  "id": "agentos allow-ip"
@@ -57,7 +57,7 @@
57
57
  },
58
58
  {
59
59
  "issue": "After `agentos-auto update`: \"no systemd unit 'agentos-ui' — restart the panel manually\", \"could not read the firewall state (needs root …) … sudo ufw allow 8765/tcp\", \"VPS health: not reported — not_assigned_vps\"",
60
- "fix": "Not errors. The update restarts the panel so it runs the new code (`sdk/ui/service.py`), limits the panel's port to the allowed IPs — the backend and the one outside IP; it removes any open-to-everyone rule (`sdk/firewall.py` `ensure_restricted` — it never runs sudo itself, so unprivileged it prints the commands instead), and reports VPS health only from the VPS assigned to the account (`sdk/vps_health.py`). On a laptop or any machine that is not the assigned VPS all three are expected and need no action; on the VPS, run `sudo agentos firewall --apply`, and restart `agentos-ui` if it runs by hand. \"ufw is INACTIVE\" in that output means no rule is enforced at all: `sudo ufw allow OpenSSH && sudo ufw enable`."
60
+ "fix": "Not errors. The update restarts the panel so it runs the new code (`sdk/ui/service.py`), limits the panel's port to the backend IPs; it removes any open-to-everyone rule — both `8765/tcp` and a bare `ufw allow 8765` — and the rules of ignored outside IPs (`sdk/firewall.py` `ensure_restricted` — not root, it tries passwordless `sudo -n`; without that it prints `sudo ufw delete allow 8765/tcp` / `sudo ufw delete allow 8765` instead), and reports VPS health only from the VPS assigned to the account (`sdk/vps_health.py`). On a laptop or any machine that is not the assigned VPS all three are expected and need no action; on the VPS, run `sudo agentos firewall --apply`, and restart `agentos-ui` if it runs by hand. \"ufw is INACTIVE\" in that output means no rule is enforced at all: `sudo ufw allow OpenSSH && sudo ufw enable`."
61
61
  },
62
62
  {
63
63
  "issue": "Panel at 100% CPU / slow, lots of 403s or unknown IPs in `ss -tn sport = :8765`",
@@ -3,7 +3,7 @@
3
3
  "page": "Dashboard",
4
4
  "file": "index.html",
5
5
  "heading": "Daily ticket activity",
6
- "summary": "Bot — waiting for work To Do— In Progress— Done— Backlog— Daily ticket activity 7 days 30 days To Do In Progress Done Backlog Up next0 Remote access Allowed IP: —"
6
+ "summary": "Bot — waiting for work To Do— In Progress— Done— Backlog— Daily ticket activity 7 days 30 days To Do In Progress Done Backlog Up next0 Remote access Allowed: this server + DevOps Bot backend"
7
7
  },
8
8
  {
9
9
  "page": "Run & Logs",
@@ -1,4 +1,14 @@
1
1
  [
2
+ {
3
+ "pr": 190,
4
+ "date": "2026-10-30",
5
+ "merged_at": "2026-10-30T14:48:32+05:00",
6
+ "type": "Other",
7
+ "area": "Control panel",
8
+ "title": "security: backend-only panel allowlist; firewall removes bare 8765 rule, uses sudo -n",
9
+ "version": "1.7.47",
10
+ "url": "https://github.com/consultancy-outfit/DevOpsBot-SDK/pull/190"
11
+ },
2
12
  {
3
13
  "pr": 189,
4
14
  "date": "2026-10-30",
@@ -0,0 +1,17 @@
1
+ [
2
+ {
3
+ "version": "1.7.47",
4
+ "commit": "226a619",
5
+ "pushed_at": "2026-10-30T14:48:32+05:00",
6
+ "generated_at": "2026-10-30T09:50:43+00:00",
7
+ "commands": 59,
8
+ "commands_invalid": 0,
9
+ "commands_undocumented": 0,
10
+ "panel_pages": 11,
11
+ "tests": 2600,
12
+ "prs_7d": 26,
13
+ "prs_30d": 42,
14
+ "features_30d": 12,
15
+ "fixes_30d": 18
16
+ }
17
+ ]