devops-bot-sdk 1.7.46__tar.gz → 1.7.48__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 (306) hide show
  1. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/devops_bot_sdk.egg-info/SOURCES.txt +3 -1
  4. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agentd.py +6 -0
  6. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/cli.py +29 -8
  7. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/config.py +21 -83
  8. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/faqs.py +10 -13
  9. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/firewall.py +112 -47
  10. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/help_data/areas.json +13 -13
  11. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/help_data/commands.json +12 -2
  12. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/help_data/help.json +1 -1
  13. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/help_data/pages.json +1 -1
  14. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/help_data/releases.json +20 -0
  15. devops_bot_sdk-1.7.48/sdk/help_data/snapshot.json +17 -0
  16. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/helpdesk_kb.md +30 -33
  17. devops_bot_sdk-1.7.48/sdk/install_marker.py +175 -0
  18. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/run_auto.py +6 -0
  19. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/api.py +28 -52
  20. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/server.py +6 -0
  21. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/app.js +19 -37
  22. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/index.html +7 -15
  23. devops_bot_sdk-1.7.48/tests/test_backend_only_allowlist.py +155 -0
  24. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_firewall_open_on_update.py +44 -1
  25. devops_bot_sdk-1.7.48/tests/test_install_marker.py +169 -0
  26. devops_bot_sdk-1.7.46/sdk/help_data/snapshot.json +0 -17
  27. devops_bot_sdk-1.7.46/tests/test_one_allowed_ip.py +0 -153
  28. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/README.md +0 -0
  29. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  30. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  31. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/devops_bot_sdk.egg-info/requires.txt +0 -0
  32. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  33. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/pyproject.toml +0 -0
  34. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/__init__.py +0 -0
  35. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/classifier.py +0 -0
  36. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/collectors/__init__.py +0 -0
  37. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/collectors/common.py +0 -0
  38. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/collectors/linux_auditd.py +0 -0
  39. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/collectors/macos_eslogger.py +0 -0
  40. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/collectors/windows_eventlog.py +0 -0
  41. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/models.py +0 -0
  42. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/os_setup.py +0 -0
  43. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/recorder.py +0 -0
  44. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/redact.py +0 -0
  45. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/settings.py +0 -0
  46. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/shipper.py +0 -0
  47. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/activity/spool.py +0 -0
  48. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agent_sharing.py +0 -0
  49. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/__init__.py +0 -0
  50. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/activity.py +0 -0
  51. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/builder.py +0 -0
  52. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/chat.py +0 -0
  53. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/connectors.py +0 -0
  54. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/engines/__init__.py +0 -0
  55. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/engines/claude_code.py +0 -0
  56. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/engines/http.py +0 -0
  57. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/engines/mcp.py +0 -0
  58. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/engines/n8n.py +0 -0
  59. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/engines/shell.py +0 -0
  60. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/integrations.py +0 -0
  61. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/inventory.py +0 -0
  62. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/library/__init__.py +0 -0
  63. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/manifest.py +0 -0
  64. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/prompt_studio.py +0 -0
  65. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/prompt_template.py +0 -0
  66. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/prompt_vars.py +0 -0
  67. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/run_log.py +0 -0
  68. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/schedules.py +0 -0
  69. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/secrets.py +0 -0
  70. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/ticket_run.py +0 -0
  71. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/usage.py +0 -0
  72. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/agents/varstore.py +0 -0
  73. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/browser_profile.py +0 -0
  74. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/claude_usage.py +0 -0
  75. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/client.py +0 -0
  76. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/collectors/__init__.py +0 -0
  77. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/collectors/files.py +0 -0
  78. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/collectors/process.py +0 -0
  79. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/collectors/screenshot.py +0 -0
  80. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/crucial.py +0 -0
  81. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/custom_agents.py +0 -0
  82. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/deferred_tickets.py +0 -0
  83. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/departments.py +0 -0
  84. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/design_verify/__init__.py +0 -0
  85. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/design_verify/assets.py +0 -0
  86. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/design_verify/bootstrap.py +0 -0
  87. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/design_verify/browser.py +0 -0
  88. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/design_verify/compare.py +0 -0
  89. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/design_verify/loop.py +0 -0
  90. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/estimator.py +0 -0
  91. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/exceptions.py +0 -0
  92. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/fallback_handoff.py +0 -0
  93. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/formats.py +0 -0
  94. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/git_ops.py +0 -0
  95. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/github_fetch.py +0 -0
  96. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/github_ingest.py +0 -0
  97. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/graph_recon.py +0 -0
  98. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/graphify.py +0 -0
  99. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/help_actions.py +0 -0
  100. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/helpdesk.py +0 -0
  101. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/hooks/__init__.py +0 -0
  102. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/hooks/crucial_guard.py +0 -0
  103. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/instructions_template.py +0 -0
  104. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/intake.py +0 -0
  105. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ipc/__init__.py +0 -0
  106. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ipc/electron_bridge.py +0 -0
  107. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ipc/handlers.py +0 -0
  108. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/journal.py +0 -0
  109. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/local_exec.py +0 -0
  110. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/log_colors.py +0 -0
  111. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/memory_guard.py +0 -0
  112. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/model_policy.py +0 -0
  113. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/models/__init__.py +0 -0
  114. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/models/envelope.py +0 -0
  115. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/models/requests.py +0 -0
  116. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/models/responses.py +0 -0
  117. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/models/snapshots.py +0 -0
  118. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/phase_timer.py +0 -0
  119. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/plan_run.py +0 -0
  120. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/platform_compat.py +0 -0
  121. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/prd_progress.py +0 -0
  122. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/proof_readers.py +0 -0
  123. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/providers.py +0 -0
  124. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/py.typed +0 -0
  125. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/resume.py +0 -0
  126. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/router.py +0 -0
  127. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/schedule_tickets.py +0 -0
  128. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/schedule_timeline.py +0 -0
  129. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/scheduler.py +0 -0
  130. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/scope_plan.py +0 -0
  131. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/self_report.py +0 -0
  132. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/self_report_local.py +0 -0
  133. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/sse.py +0 -0
  134. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/swagger.py +0 -0
  135. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/task_file.py +0 -0
  136. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/task_types.py +0 -0
  137. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/test.py +0 -0
  138. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/test_env.py +0 -0
  139. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/test_pipeline.py +0 -0
  140. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ticket_board.py +0 -0
  141. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ticket_bulk.py +0 -0
  142. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ticket_fields.py +0 -0
  143. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ticket_plan.py +0 -0
  144. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ticket_pools.py +0 -0
  145. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/tickets.py +0 -0
  146. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/tiers/__init__.py +0 -0
  147. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/tiers/mini_agent.py +0 -0
  148. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/__init__.py +0 -0
  149. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/dashboard.py +0 -0
  150. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/health.py +0 -0
  151. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/ip_allowlist.py +0 -0
  152. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/jobs.py +0 -0
  153. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/models.py +0 -0
  154. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/runner.py +0 -0
  155. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/security.py +0 -0
  156. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/service.py +0 -0
  157. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/session.py +0 -0
  158. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/setup_manager.py +0 -0
  159. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/agents.html +0 -0
  160. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/chat.html +0 -0
  161. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/custom-agents.html +0 -0
  162. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/default-agents.html +0 -0
  163. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/faqs.html +0 -0
  164. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/help.html +0 -0
  165. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/report.html +0 -0
  166. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/runs.html +0 -0
  167. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/schedules.html +0 -0
  168. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/setup.html +0 -0
  169. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/styles.css +0 -0
  170. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/static/tickets.html +0 -0
  171. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/ui/swagger.py +0 -0
  172. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/updater.py +0 -0
  173. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/url_access.py +0 -0
  174. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/vps_health.py +0 -0
  175. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/vps_identity.py +0 -0
  176. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/sdk/write_hook.py +0 -0
  177. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/setup.cfg +0 -0
  178. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_activity_claude_runs.py +0 -0
  179. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_activity_collectors.py +0 -0
  180. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_activity_core.py +0 -0
  181. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_activity_live_tracking.py +0 -0
  182. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_activity_macos.py +0 -0
  183. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_activity_os_setup.py +0 -0
  184. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_activity_pipeline.py +0 -0
  185. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_activity.py +0 -0
  186. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_activity_tracking.py +0 -0
  187. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_chat_features.py +0 -0
  188. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_crud_cli.py +0 -0
  189. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_listing.py +0 -0
  190. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_prompt_async.py +0 -0
  191. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_prompt_models.py +0 -0
  192. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_run_ticket.py +0 -0
  193. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_schedules.py +0 -0
  194. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_sharing.py +0 -0
  195. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agent_workspace_settings.py +0 -0
  196. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_agents_connectors_builder.py +0 -0
  197. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_allowed_tools_config.py +0 -0
  198. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_approval_park.py +0 -0
  199. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_auto_agent_dispatch.py +0 -0
  200. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_auto_ticket_queue.py +0 -0
  201. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_browser_profile.py +0 -0
  202. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_cheap_tier.py +0 -0
  203. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_claude_usage_gate.py +0 -0
  204. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_client_jira_projects.py +0 -0
  205. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_config_token_diagnostics.py +0 -0
  206. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_configure_token_owner.py +0 -0
  207. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_console_encoding.py +0 -0
  208. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_crash_recovery.py +0 -0
  209. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_create_safe_retry.py +0 -0
  210. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_deferred_tickets.py +0 -0
  211. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_departments.py +0 -0
  212. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_done_transition.py +0 -0
  213. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_epic_decompose_run.py +0 -0
  214. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_epic_inherited_agent_id.py +0 -0
  215. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_epic_session_isolation.py +0 -0
  216. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_estimator.py +0 -0
  217. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_fallback_cli.py +0 -0
  218. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_fallback_cli_upgrade.py +0 -0
  219. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_fallback_rate_limit.py +0 -0
  220. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_faqs.py +0 -0
  221. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_formats.py +0 -0
  222. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_git_action.py +0 -0
  223. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_github_fetch.py +0 -0
  224. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_github_ingest.py +0 -0
  225. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_graph_off_by_default.py +0 -0
  226. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_graph_recon.py +0 -0
  227. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_graphify_build.py +0 -0
  228. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_graphify_hook_install.py +0 -0
  229. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_health.py +0 -0
  230. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_help_actions.py +0 -0
  231. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_help_attachments.py +0 -0
  232. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_help_questions.py +0 -0
  233. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_helpdesk.py +0 -0
  234. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_infographic_data.py +0 -0
  235. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_instructions_template.py +0 -0
  236. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_intake_job_outcome.py +0 -0
  237. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_intake_plan_review.py +0 -0
  238. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_journal.py +0 -0
  239. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_limit_fallback_handoff.py +0 -0
  240. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_local_exec_provider.py +0 -0
  241. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_log_colors_backlog.py +0 -0
  242. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_mcp_route_contract.py +0 -0
  243. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_memory_guard.py +0 -0
  244. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_model_policy.py +0 -0
  245. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_panel_help.py +0 -0
  246. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_panel_ticket_schedules.py +0 -0
  247. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_parallel_tickets.py +0 -0
  248. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_phase_timer.py +0 -0
  249. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_plan_ahead.py +0 -0
  250. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_plan_new_command.py +0 -0
  251. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_plan_run_activity.py +0 -0
  252. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_pool_pr_modes.py +0 -0
  253. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_pr_identity.py +0 -0
  254. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_prd_artifact.py +0 -0
  255. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_project_path_resolution.py +0 -0
  256. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_proof_readers.py +0 -0
  257. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_providers.py +0 -0
  258. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_quiet_run_log.py +0 -0
  259. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_recon_session_isolation.py +0 -0
  260. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_resume.py +0 -0
  261. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_router.py +0 -0
  262. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_run_auto_agent_only.py +0 -0
  263. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_run_outputs.py +0 -0
  264. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_run_verdict_is_honest.py +0 -0
  265. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_schedule_api.py +0 -0
  266. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_schedule_outputs_and_plans.py +0 -0
  267. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_schedule_tickets.py +0 -0
  268. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_schedule_timeline.py +0 -0
  269. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_scheduler.py +0 -0
  270. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_scheduler_transient_retry.py +0 -0
  271. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_self_report.py +0 -0
  272. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_service_token_warning.py +0 -0
  273. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_stale_panel_restart.py +0 -0
  274. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_swagger_command.py +0 -0
  275. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_task_file_and_plan_run.py +0 -0
  276. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_task_file_format_doc.py +0 -0
  277. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_theme_switch.py +0 -0
  278. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_board.py +0 -0
  279. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_bulk.py +0 -0
  280. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_cli.py +0 -0
  281. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_detail_view.py +0 -0
  282. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_fields.py +0 -0
  283. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_plan.py +0 -0
  284. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_pools.py +0 -0
  285. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_run_resolve_by_id.py +0 -0
  286. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_schedule_and_agent.py +0 -0
  287. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ticket_speed_fixes.py +0 -0
  288. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_tier_flag_parity.py +0 -0
  289. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ui_bruteforce_guard.py +0 -0
  290. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ui_gate.py +0 -0
  291. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ui_lifespan.py +0 -0
  292. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_ui_no_native_dialogs.py +0 -0
  293. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_update_timer.py +0 -0
  294. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_updater_extras.py +0 -0
  295. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_updater_ignores_cwd.py +0 -0
  296. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_updater_pep668.py +0 -0
  297. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_updater_reports_the_truth.py +0 -0
  298. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_url_access.py +0 -0
  299. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_version_flag.py +0 -0
  300. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_vps_health.py +0 -0
  301. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_vps_only_reporting.py +0 -0
  302. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_windows_loop_identity.py +0 -0
  303. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_windows_panel_task.py +0 -0
  304. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_windows_stop_and_pidfile.py +0 -0
  305. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/tests/test_windows_task_paths.py +0 -0
  306. {devops_bot_sdk-1.7.46 → devops_bot_sdk-1.7.48}/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.48
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.48
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
@@ -32,6 +32,7 @@ sdk/graphify.py
32
32
  sdk/help_actions.py
33
33
  sdk/helpdesk.py
34
34
  sdk/helpdesk_kb.md
35
+ sdk/install_marker.py
35
36
  sdk/instructions_template.py
36
37
  sdk/intake.py
37
38
  sdk/journal.py
@@ -190,6 +191,7 @@ tests/test_allowed_tools_config.py
190
191
  tests/test_approval_park.py
191
192
  tests/test_auto_agent_dispatch.py
192
193
  tests/test_auto_ticket_queue.py
194
+ tests/test_backend_only_allowlist.py
193
195
  tests/test_browser_profile.py
194
196
  tests/test_cheap_tier.py
195
197
  tests/test_claude_usage_gate.py
@@ -225,6 +227,7 @@ tests/test_help_attachments.py
225
227
  tests/test_help_questions.py
226
228
  tests/test_helpdesk.py
227
229
  tests/test_infographic_data.py
230
+ tests/test_install_marker.py
228
231
  tests/test_instructions_template.py
229
232
  tests/test_intake_job_outcome.py
230
233
  tests/test_intake_plan_review.py
@@ -235,7 +238,6 @@ tests/test_log_colors_backlog.py
235
238
  tests/test_mcp_route_contract.py
236
239
  tests/test_memory_guard.py
237
240
  tests/test_model_policy.py
238
- tests/test_one_allowed_ip.py
239
241
  tests/test_panel_help.py
240
242
  tests/test_panel_ticket_schedules.py
241
243
  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.48
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.48" # 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
@@ -854,6 +854,12 @@ def main() -> None:
854
854
  from sdk.platform_compat import ensure_macos_fork_safety, ensure_utf8_stdio
855
855
  ensure_utf8_stdio()
856
856
 
857
+ # Record where this installation's scripts live (~/.agentos/install.json) so
858
+ # external tools (e.g. over a minimal-PATH SSH session) can find the CLI.
859
+ # Never raises, never prints; rewrites only when something changed.
860
+ from sdk.install_marker import record_install_location
861
+ record_install_location()
862
+
857
863
  # macOS: keep the background daemon from aborting ("Python quit unexpectedly")
858
864
  # when it does networking after the double-fork. May re-exec once; must run
859
865
  # before any fork or objc-linked import.
@@ -8,6 +8,14 @@ the "verb".
8
8
 
9
9
  Command surface:
10
10
 
11
+ agentos where [--json]
12
+ Where THIS installation lives: the directory holding the agentos /
13
+ agentos-auto scripts (bin_dir), the Python running it, the SDK version,
14
+ and the path of ~/.agentos/install.json — which every entry point keeps
15
+ current and this command refreshes. For external tools (the AI Draftsman
16
+ DevOps Bot SDK connector) that reach the machine over a minimal-PATH SSH
17
+ session. See sdk.install_marker.
18
+
11
19
  agentos configure [--rotate]
12
20
  Store (or replace) the co_<uuid>_<hex> backend token. See
13
21
  sdk.config.configure_cli.
@@ -109,12 +117,12 @@ Command surface:
109
117
  `--any` opens the port to every source instead (not recommended: every
110
118
  scanner then reaches the panel). See sdk.firewall.
111
119
 
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.
120
+ agentos allow-ip [--clear]
121
+ Show who may reach the panel: this machine and the DevOps Bot backend only
122
+ — no outside IP (since 1.7.47). `agentos allow-ip <IP>` is refused (exit 2)
123
+ with the SSH-tunnel alternative (ssh -L 8765:127.0.0.1:8765 <user>@<server>).
124
+ `--clear` removes outside IPs an older SDK left in config.toml and syncs the
125
+ host firewall (that half needs root). See sdk.firewall.
118
126
 
119
127
  agentos swagger [--open] [--json [PATH]] [--port N]
120
128
  Where the panel's Swagger UI is and whether it can actually be reached:
@@ -173,6 +181,12 @@ def main() -> None:
173
181
  from sdk.platform_compat import ensure_utf8_stdio
174
182
  ensure_utf8_stdio()
175
183
 
184
+ # Record where this installation's scripts live (~/.agentos/install.json) so
185
+ # external tools (e.g. over a minimal-PATH SSH session) can find the CLI.
186
+ # Never raises, never prints; rewrites only when something changed.
187
+ from sdk.install_marker import record_install_location
188
+ record_install_location()
189
+
176
190
  argv = sys.argv[1:]
177
191
  if not argv:
178
192
  _print_usage()
@@ -185,6 +199,12 @@ def main() -> None:
185
199
  print(version_line())
186
200
  return
187
201
 
202
+ if noun == "where":
203
+ # Where this installation lives (bin_dir, python, version) + the path of
204
+ # ~/.agentos/install.json, which it refreshes. `--json` for machines.
205
+ from sdk.install_marker import where_cli
206
+ sys.exit(where_cli(rest))
207
+
188
208
  if noun == "configure":
189
209
  from sdk.config import configure_cli
190
210
  configure_cli(rotate="--rotate" in sys.argv)
@@ -274,9 +294,10 @@ def main() -> None:
274
294
 
275
295
 
276
296
  def _print_usage() -> None:
277
- print("usage: agentos <configure|chat|agent|ticket|integration|default-agents|agentd|github|report|browser|fallback|help|doctor|firewall|allow-ip|activity-setup|swagger> ...")
297
+ print("usage: agentos <where|configure|chat|agent|ticket|integration|default-agents|agentd|github|report|browser|fallback|help|doctor|firewall|allow-ip|activity-setup|swagger> ...")
278
298
  print()
279
299
  print(' agentos version (or --version) # installed SDK version, Python and install path')
300
+ print(' agentos where [--json] # bin dir of agentos/agentos-auto, python, version; refreshes ~/.agentos/install.json')
280
301
  print(' agentos help "<your problem>" [--file PATH]… # DevOps Bot Help: reads your logs + the SDK (+ screenshots/logs you attach)')
281
302
  print(" agentos doctor # collect a diagnostics bundle to send to your admin (no AI)")
282
303
  print(" agentos configure [--rotate]")
@@ -294,7 +315,7 @@ def _print_usage() -> None:
294
315
  print(" agentos report <submit|list|show ID> # report work Jira/GitHub never saw")
295
316
  print(" agentos browser <login URL|status|logout> # sign in once; private links become readable")
296
317
  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")
318
+ print(" agentos allow-ip [--clear] # who may reach the panel (no outside IP; use an SSH tunnel)")
298
319
  print(" agentos firewall [--apply] [--revoke IP] [--port N] # limit the UI port to the allowlisted IPs")
299
320
  print(" agentos firewall --any [--apply] # open the UI port to any source (allowlist is the gate)")
300
321
  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",
@@ -53,6 +53,15 @@
53
53
  "latest_pr": 164,
54
54
  "latest_date": "2026-10-24"
55
55
  },
56
+ {
57
+ "area": "Agents",
58
+ "changes_30d": 2,
59
+ "features_30d": 1,
60
+ "fixes_30d": 0,
61
+ "latest": "record install location in ~/.agentos/install.json + agentos where",
62
+ "latest_pr": 191,
63
+ "latest_date": "2026-11-03"
64
+ },
56
65
  {
57
66
  "area": "Schedules",
58
67
  "changes_30d": 2,
@@ -62,15 +71,6 @@
62
71
  "latest_pr": 163,
63
72
  "latest_date": "2026-10-23"
64
73
  },
65
- {
66
- "area": "Agents",
67
- "changes_30d": 1,
68
- "features_30d": 0,
69
- "fixes_30d": 0,
70
- "latest": "APPR-539: [ST0135] Early years educator - Course Orchestrator + 69 item agents",
71
- "latest_pr": 146,
72
- "latest_date": "2026-10-20"
73
- },
74
74
  {
75
75
  "area": "Daemon",
76
76
  "changes_30d": 1,