devops-bot-sdk 1.6.56__tar.gz → 1.6.57__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 (225) hide show
  1. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/__init__.py +1 -1
  4. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/cli.py +94 -1
  5. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ipc/handlers.py +4 -6
  6. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/local_exec.py +159 -13
  7. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/plan_run.py +2 -3
  8. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/run_auto.py +2 -3
  9. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/setup_manager.py +14 -0
  10. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_fallback_cli.py +126 -0
  11. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_task_file_and_plan_run.py +14 -0
  12. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/README.md +0 -0
  13. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  14. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  15. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  16. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/devops_bot_sdk.egg-info/requires.txt +0 -0
  17. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  18. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/pyproject.toml +0 -0
  19. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/__init__.py +0 -0
  20. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/classifier.py +0 -0
  21. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/collectors/__init__.py +0 -0
  22. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/collectors/common.py +0 -0
  23. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/collectors/linux_auditd.py +0 -0
  24. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/collectors/macos_eslogger.py +0 -0
  25. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/collectors/windows_eventlog.py +0 -0
  26. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/models.py +0 -0
  27. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/os_setup.py +0 -0
  28. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/recorder.py +0 -0
  29. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/redact.py +0 -0
  30. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/settings.py +0 -0
  31. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/shipper.py +0 -0
  32. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/activity/spool.py +0 -0
  33. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agentd.py +0 -0
  34. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/__init__.py +0 -0
  35. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/activity.py +0 -0
  36. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/builder.py +0 -0
  37. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/chat.py +0 -0
  38. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/connectors.py +0 -0
  39. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/engines/__init__.py +0 -0
  40. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/engines/claude_code.py +0 -0
  41. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/engines/http.py +0 -0
  42. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/engines/mcp.py +0 -0
  43. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/engines/n8n.py +0 -0
  44. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/engines/shell.py +0 -0
  45. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/integrations.py +0 -0
  46. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/inventory.py +0 -0
  47. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/library/__init__.py +0 -0
  48. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/manifest.py +0 -0
  49. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/prompt_studio.py +0 -0
  50. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/prompt_template.py +0 -0
  51. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/prompt_vars.py +0 -0
  52. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/run_log.py +0 -0
  53. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/schedules.py +0 -0
  54. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/secrets.py +0 -0
  55. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/ticket_run.py +0 -0
  56. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/usage.py +0 -0
  57. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/agents/varstore.py +0 -0
  58. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/browser_profile.py +0 -0
  59. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/client.py +0 -0
  60. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/collectors/__init__.py +0 -0
  61. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/collectors/files.py +0 -0
  62. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/collectors/process.py +0 -0
  63. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/collectors/screenshot.py +0 -0
  64. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/config.py +0 -0
  65. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/crucial.py +0 -0
  66. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/departments.py +0 -0
  67. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/design_verify/__init__.py +0 -0
  68. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/design_verify/assets.py +0 -0
  69. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/design_verify/bootstrap.py +0 -0
  70. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/design_verify/browser.py +0 -0
  71. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/design_verify/compare.py +0 -0
  72. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/design_verify/loop.py +0 -0
  73. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/estimator.py +0 -0
  74. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/exceptions.py +0 -0
  75. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/firewall.py +0 -0
  76. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/formats.py +0 -0
  77. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/git_ops.py +0 -0
  78. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/github_fetch.py +0 -0
  79. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/github_ingest.py +0 -0
  80. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/graph_recon.py +0 -0
  81. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/graphify.py +0 -0
  82. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/hooks/__init__.py +0 -0
  83. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/hooks/crucial_guard.py +0 -0
  84. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/intake.py +0 -0
  85. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ipc/__init__.py +0 -0
  86. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ipc/electron_bridge.py +0 -0
  87. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/journal.py +0 -0
  88. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/memory_guard.py +0 -0
  89. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/models/__init__.py +0 -0
  90. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/models/envelope.py +0 -0
  91. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/models/requests.py +0 -0
  92. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/models/responses.py +0 -0
  93. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/models/snapshots.py +0 -0
  94. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/phase_timer.py +0 -0
  95. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/platform_compat.py +0 -0
  96. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/prd_progress.py +0 -0
  97. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/proof_readers.py +0 -0
  98. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/providers.py +0 -0
  99. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/py.typed +0 -0
  100. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/resume.py +0 -0
  101. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/router.py +0 -0
  102. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/scheduler.py +0 -0
  103. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/self_report.py +0 -0
  104. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/self_report_local.py +0 -0
  105. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/sse.py +0 -0
  106. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/swagger.py +0 -0
  107. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/task_file.py +0 -0
  108. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/task_types.py +0 -0
  109. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/test.py +0 -0
  110. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/test_pipeline.py +0 -0
  111. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ticket_fields.py +0 -0
  112. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/tiers/__init__.py +0 -0
  113. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/tiers/mini_agent.py +0 -0
  114. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/__init__.py +0 -0
  115. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/api.py +0 -0
  116. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/dashboard.py +0 -0
  117. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/health.py +0 -0
  118. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/ip_allowlist.py +0 -0
  119. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/jobs.py +0 -0
  120. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/models.py +0 -0
  121. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/runner.py +0 -0
  122. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/security.py +0 -0
  123. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/server.py +0 -0
  124. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/service.py +0 -0
  125. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/session.py +0 -0
  126. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/agents.html +0 -0
  127. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/app.js +0 -0
  128. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/chat.html +0 -0
  129. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/custom-agents.html +0 -0
  130. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/default-agents.html +0 -0
  131. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/index.html +0 -0
  132. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/report.html +0 -0
  133. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/runs.html +0 -0
  134. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/schedules.html +0 -0
  135. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/setup.html +0 -0
  136. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/styles.css +0 -0
  137. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/static/tickets.html +0 -0
  138. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/ui/swagger.py +0 -0
  139. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/updater.py +0 -0
  140. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/url_access.py +0 -0
  141. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/vendor/aidraftsman-cli-0.1.0.tgz +0 -0
  142. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/vps_health.py +0 -0
  143. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/sdk/write_hook.py +0 -0
  144. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/setup.cfg +0 -0
  145. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_activity_collectors.py +0 -0
  146. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_activity_core.py +0 -0
  147. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_activity_macos.py +0 -0
  148. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_activity_os_setup.py +0 -0
  149. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_activity_pipeline.py +0 -0
  150. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_activity.py +0 -0
  151. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_activity_tracking.py +0 -0
  152. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_chat_features.py +0 -0
  153. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_listing.py +0 -0
  154. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_prompt_async.py +0 -0
  155. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_prompt_models.py +0 -0
  156. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_run_ticket.py +0 -0
  157. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_schedules.py +0 -0
  158. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agent_workspace_settings.py +0 -0
  159. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_agents_connectors_builder.py +0 -0
  160. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_allowed_tools_config.py +0 -0
  161. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_auto_agent_dispatch.py +0 -0
  162. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_browser_profile.py +0 -0
  163. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_cheap_tier.py +0 -0
  164. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_client_jira_projects.py +0 -0
  165. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_config_token_diagnostics.py +0 -0
  166. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_configure_token_owner.py +0 -0
  167. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_console_encoding.py +0 -0
  168. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_crash_recovery.py +0 -0
  169. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_departments.py +0 -0
  170. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_done_transition.py +0 -0
  171. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_epic_decompose_run.py +0 -0
  172. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_epic_inherited_agent_id.py +0 -0
  173. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_epic_session_isolation.py +0 -0
  174. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_estimator.py +0 -0
  175. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_firewall_open_on_update.py +0 -0
  176. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_formats.py +0 -0
  177. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_git_action.py +0 -0
  178. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_github_fetch.py +0 -0
  179. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_github_ingest.py +0 -0
  180. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_graph_recon.py +0 -0
  181. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_graphify_build.py +0 -0
  182. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_graphify_hook_install.py +0 -0
  183. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_health.py +0 -0
  184. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_intake_job_outcome.py +0 -0
  185. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_intake_plan_review.py +0 -0
  186. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_journal.py +0 -0
  187. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_local_exec_provider.py +0 -0
  188. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_mcp_route_contract.py +0 -0
  189. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_memory_guard.py +0 -0
  190. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_phase_timer.py +0 -0
  191. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_plan_new_command.py +0 -0
  192. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_plan_run_activity.py +0 -0
  193. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_pr_identity.py +0 -0
  194. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_prd_artifact.py +0 -0
  195. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_project_path_resolution.py +0 -0
  196. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_proof_readers.py +0 -0
  197. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_providers.py +0 -0
  198. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_recon_session_isolation.py +0 -0
  199. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_resume.py +0 -0
  200. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_router.py +0 -0
  201. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_run_auto_agent_only.py +0 -0
  202. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_run_outputs.py +0 -0
  203. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_run_verdict_is_honest.py +0 -0
  204. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_schedule_api.py +0 -0
  205. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_schedule_outputs_and_plans.py +0 -0
  206. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_scheduler.py +0 -0
  207. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_self_report.py +0 -0
  208. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_service_token_warning.py +0 -0
  209. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_stale_panel_restart.py +0 -0
  210. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_swagger_command.py +0 -0
  211. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_task_file_format_doc.py +0 -0
  212. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_ticket_fields.py +0 -0
  213. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_ticket_run_resolve_by_id.py +0 -0
  214. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_ticket_speed_fixes.py +0 -0
  215. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_tier_flag_parity.py +0 -0
  216. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_ui_gate.py +0 -0
  217. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_ui_lifespan.py +0 -0
  218. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_update_timer.py +0 -0
  219. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_updater_extras.py +0 -0
  220. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_updater_pep668.py +0 -0
  221. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_updater_reports_the_truth.py +0 -0
  222. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_url_access.py +0 -0
  223. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_vps_health.py +0 -0
  224. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/tests/test_windows_loop_identity.py +0 -0
  225. {devops_bot_sdk-1.6.56 → devops_bot_sdk-1.6.57}/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.6.56
3
+ Version: 1.6.57
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.56
3
+ Version: 1.6.57
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
@@ -34,7 +34,7 @@ Rules:
34
34
  - All data egress through submit_webhook only
35
35
  """
36
36
 
37
- __version__ = "1.6.56" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.57" # 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
@@ -104,6 +104,11 @@ Command surface:
104
104
  One submission in full — its text, its proofs and the evaluator's
105
105
  per-criterion reasoning.
106
106
 
107
+ agentos fallback install | login [--browser | --api-key [KEY]] | status [--verify] | logout
108
+ The AI Draftsman CLI a ticket re-runs on when `claude` is not working:
109
+ install the bundled copy, log it in (a sign-in code approved on the
110
+ frontend, or an API key), and check it. See sdk.local_exec.fallback_status.
111
+
107
112
  agentos agentd start [--foreground]
108
113
  Start the local agent daemon (background by default).
109
114
  agentos agentd status
@@ -188,12 +193,16 @@ def main() -> None:
188
193
  _browser_command(rest)
189
194
  return
190
195
 
196
+ if noun == "fallback":
197
+ _fallback_command(rest)
198
+ return
199
+
191
200
  _print_usage()
192
201
  sys.exit(1)
193
202
 
194
203
 
195
204
  def _print_usage() -> None:
196
- print("usage: agentos <configure|chat|agent|integration|default-agents|agentd|github|report|browser|firewall|activity-setup|swagger> ...")
205
+ print("usage: agentos <configure|chat|agent|integration|default-agents|agentd|github|report|browser|fallback|firewall|activity-setup|swagger> ...")
197
206
  print()
198
207
  print(" agentos configure [--rotate]")
199
208
  print(" agentos chat [--product-id ID] [--pv-id ID] [--no-autorun] # requirements → ticket")
@@ -205,6 +214,7 @@ def _print_usage() -> None:
205
214
  print(" agentos github <ingest [--days N]|status> # PR ingestion; --days N backfills N days")
206
215
  print(" agentos report <submit|list|show ID> # report work Jira/GitHub never saw")
207
216
  print(" agentos browser <login URL|status|logout> # sign in once; private links become readable")
217
+ print(" agentos fallback <install|login|status|logout> # the AI Draftsman CLI used when claude is not working")
208
218
  print(" agentos firewall [--apply] [--revoke IP] [--port N] # open the UI port to the allowlisted IPs")
209
219
  print(" agentos firewall --any [--apply] # open the UI port to any source (allowlist is the gate)")
210
220
  print(" agentos swagger [--open] [--json [PATH]] # where /docs is, and can you reach it")
@@ -892,6 +902,89 @@ def _browser_command(args: list[str]) -> None:
892
902
  sys.exit(1)
893
903
 
894
904
 
905
+ # ── fallback ─────────────────────────────────────────────────────────────
906
+
907
+ def _fallback_usage() -> None:
908
+ print("usage: agentos fallback install # npm-install the bundled CLI now")
909
+ print(" agentos fallback login [--browser | --api-key [KEY]] [--base-url URL] [--no-browser]")
910
+ print(" agentos fallback status [--verify] # --verify asks the server too")
911
+ print(" agentos fallback logout")
912
+ print()
913
+ print(" When the `claude` CLI is not working, tickets re-run on the AI Draftsman CLI")
914
+ print(" bundled with the SDK. It needs its own login, made as the user the SDK runs as.")
915
+ print()
916
+ print(" --browser prints a sign-in code and a link; approve it on the AI Draftsman")
917
+ print(" frontend from any machine — works on a headless VPS")
918
+ print(" --api-key paste a key from Settings → API keys (hidden prompt), or pass")
919
+ print(" KEY after the flag")
920
+ print(" (neither) the CLI's own menu")
921
+
922
+
923
+ def _fallback_command(args: list[str]) -> None:
924
+ """``agentos fallback install|login|status|logout`` — the AI Draftsman fallback CLI."""
925
+ from sdk import local_exec as le
926
+
927
+ verb = args[0] if args else None
928
+ rest = args[1:]
929
+
930
+ if verb == "install":
931
+ if le._fallback_disabled():
932
+ print("error: the fallback is disabled (AGENTOS_FALLBACK_CLI=off)")
933
+ sys.exit(1)
934
+ cli = asyncio.run(le.ensure_fallback_cli())
935
+ if not cli:
936
+ print("error: could not install the AI Draftsman CLI (Node.js 20+ and npm are needed)")
937
+ sys.exit(1)
938
+ print(f"installed: {cli}")
939
+ ad = Path(cli).with_name("ad.cmd" if cli.lower().endswith(".cmd") else "ad")
940
+ if ad.exists():
941
+ print(f"also as : {ad} (the same CLI under its short name)")
942
+ print("next : agentos fallback login")
943
+ return
944
+
945
+ if verb == "login":
946
+ method = api_key = base_url = None
947
+ no_browser = True if "--no-browser" in rest else None
948
+ if "--browser" in rest:
949
+ method = "browser"
950
+ if "--api-key" in rest:
951
+ method = "api-key"
952
+ i = rest.index("--api-key") + 1
953
+ if i < len(rest) and not rest[i].startswith("-"):
954
+ api_key = rest[i]
955
+ if "--base-url" in rest:
956
+ try:
957
+ base_url = rest[rest.index("--base-url") + 1]
958
+ except IndexError:
959
+ print("error: --base-url needs a URL")
960
+ sys.exit(1)
961
+ rc = le.fallback_login(method=method, api_key=api_key, base_url=base_url,
962
+ no_browser=no_browser)
963
+ if rc == 0:
964
+ print("check it : agentos fallback status --verify")
965
+ sys.exit(rc)
966
+
967
+ if verb == "status":
968
+ st = le.fallback_status(verify="--verify" in rest)
969
+ print(f"installed : {'yes' if st['installed'] else 'no'}")
970
+ print(f"path : {st['path'] or '-'}")
971
+ print(f"logged in : {'yes' if st['logged_in'] else 'no'}")
972
+ if st["verified"] is not None:
973
+ print(f"server : {'key accepted' if st['verified'] else 'NOT accepted'}")
974
+ print(f"account : {st['account'] or '-'}")
975
+ print(f"server url: {st['base_url'] or '-'}")
976
+ if st["error"]:
977
+ print(f"problem : {st['error']}")
978
+ ready = st["logged_in"] and st["verified"] is not False
979
+ sys.exit(0 if ready else 1)
980
+
981
+ if verb == "logout":
982
+ sys.exit(le.fallback_logout())
983
+
984
+ _fallback_usage()
985
+ sys.exit(1)
986
+
987
+
895
988
  def _report_check(url: str) -> None:
896
989
  """``agentos report check <URL>`` — can this machine open that link?
897
990
 
@@ -1973,12 +1973,11 @@ async def _run_local_pipeline(
1973
1973
  thread_id = str(payload.get("thread_id", "") or "")
1974
1974
  project_path = str(payload.get("project_path", "") or "").strip()
1975
1975
 
1976
- if not local_exec.is_available():
1976
+ if not local_exec.can_run_locally():
1977
1977
  await send(Envelope(
1978
1978
  type=Envelope.TYPE_ERROR, thread_id=thread_id,
1979
1979
  data={"code": "CLAUDE_CLI_MISSING",
1980
- "message": "The Claude Code CLI is not installed on this machine. "
1981
- "Install it (and run `claude login`) to use the local claude_code engine."},
1980
+ "message": local_exec.NO_LOCAL_CLI_MESSAGE},
1982
1981
  ).model_dump())
1983
1982
  return
1984
1983
  if not project_path:
@@ -4954,12 +4953,11 @@ async def handle_orchestrate_auto_local_run(
4954
4953
  # guaranteed on every exit — including early returns — via the finally below.
4955
4954
  active_flusher: ActivityFlusher | None = None
4956
4955
  try:
4957
- if not local_exec.is_available():
4956
+ if not local_exec.can_run_locally():
4958
4957
  await send(Envelope(
4959
4958
  type=Envelope.TYPE_ERROR, thread_id=thread_id,
4960
4959
  data={"code": "CLAUDE_CLI_MISSING",
4961
- "message": "The Claude Code CLI is not installed on this machine. "
4962
- "Install it and run `claude setup-token` to use local execution."},
4960
+ "message": local_exec.NO_LOCAL_CLI_MESSAGE},
4963
4961
  ).model_dump())
4964
4962
  return
4965
4963
 
@@ -1052,7 +1052,9 @@ async def _run_claude_cli(
1052
1052
  # prompt goes in on stdin, `-p --output-format json` prints one JSON object.
1053
1053
  # The CLI is bundled in sdk/vendor/ and npm-installed into ~/.agentos/aidraftsman
1054
1054
  # the first time it is needed; one already on PATH is used instead. It needs
1055
- # its own login on the box (`ad auth login`, or AD_API_KEY in the env).
1055
+ # its own login on the box, as the user the SDK runs as: `agentos fallback
1056
+ # login` (browser sign-in code or an API key), or AD_API_KEY in the env.
1057
+ # `agentos fallback status --verify` checks it against the server.
1056
1058
  #
1057
1059
  # What it does NOT do: honour the PreToolUse approval guard, resume a Claude
1058
1060
  # session, or stream progress events. So a resumed run never falls back (the
@@ -1108,8 +1110,8 @@ def _bundled_bin(tarball: Path) -> Path:
1108
1110
  def fallback_cli_path() -> str | None:
1109
1111
  """Absolute path to an installed `aidraftsman` CLI, or None if absent or disabled.
1110
1112
 
1111
- Resolution order: ``AGENTOS_FALLBACK_CLI``, then ``PATH``, then the copy
1112
- installed from the bundled tarball. Never installs anything — see
1113
+ Resolution order: ``AGENTOS_FALLBACK_CLI``, then ``aidraftsman`` or ``ad``
1114
+ on ``PATH``, then the copy installed from the bundled tarball. Never installs anything — see
1113
1115
  :func:`ensure_fallback_cli` for that.
1114
1116
  """
1115
1117
  if _fallback_disabled():
@@ -1117,9 +1119,12 @@ def fallback_cli_path() -> str | None:
1117
1119
  override = _setting("AGENTOS_FALLBACK_CLI")
1118
1120
  if override and Path(override).exists():
1119
1121
  return override
1120
- found = shutil.which("aidraftsman")
1121
- if found:
1122
- return found
1122
+ # The package installs the same entry point under two names; a box may
1123
+ # only have `ad` on PATH.
1124
+ for name in ("aidraftsman", "ad"):
1125
+ found = shutil.which(name)
1126
+ if found:
1127
+ return found
1123
1128
  tarball = _bundled_tarball()
1124
1129
  if tarball and _bundled_bin(tarball).exists():
1125
1130
  return str(_bundled_bin(tarball))
@@ -1164,6 +1169,145 @@ async def ensure_fallback_cli() -> str | None:
1164
1169
  return await asyncio.to_thread(_install_bundled, tarball)
1165
1170
 
1166
1171
 
1172
+ def _fallback_argv(cli: str, *args: str) -> list[str]:
1173
+ # npm installs a .cmd shim on Windows — same reason as the claude spawn.
1174
+ cmd = [cli, *args]
1175
+ if sys.platform == "win32" and cli.lower().endswith((".cmd", ".bat")):
1176
+ cmd = [os.environ.get("COMSPEC", "cmd.exe"), "/c", *cmd]
1177
+ return cmd
1178
+
1179
+
1180
+ def fallback_status(*, verify: bool = False, timeout: float = 20.0) -> dict:
1181
+ """Readiness of the AI Draftsman fallback CLI: installed AND logged in.
1182
+
1183
+ The fallback counterpart of :func:`claude_status`. ``aidraftsman auth status``
1184
+ only says whether a key is STORED (in ~/.config/ai-draftsman/config.json for
1185
+ the user running this); ``verify=True`` also asks the server, by listing
1186
+ agents, so an expired or revoked key shows as not logged in. AD_API_KEY in
1187
+ the env counts as a login, as it does for the CLI itself. Never raises.
1188
+
1189
+ Returns::
1190
+
1191
+ {"installed": bool, "logged_in": bool, "verified": bool|None,
1192
+ "path": str|None, "account": str|None, "base_url": str|None,
1193
+ "disabled": bool, "error": str|None}
1194
+ """
1195
+ out: dict[str, Any] = {"installed": False, "logged_in": False, "verified": None,
1196
+ "path": None, "account": None, "base_url": None,
1197
+ "disabled": _fallback_disabled(), "error": None}
1198
+ if out["disabled"]:
1199
+ out["error"] = "disabled (AGENTOS_FALLBACK_CLI=off)"
1200
+ return out
1201
+ cli = fallback_cli_path()
1202
+ if not cli:
1203
+ out["error"] = "not installed — run `agentos fallback install`"
1204
+ return out
1205
+ out.update(installed=True, path=cli)
1206
+ try:
1207
+ proc = subprocess.run(_fallback_argv(cli, "auth", "status", "--json"),
1208
+ capture_output=True, text=True, errors="replace",
1209
+ timeout=timeout, check=False)
1210
+ data = json.loads(proc.stdout or "{}")
1211
+ except Exception as exc: # noqa: BLE001 — timeout / OSError / bad JSON
1212
+ out["error"] = f"`aidraftsman auth status` failed: {exc}"
1213
+ return out
1214
+ out["account"] = data.get("account")
1215
+ out["base_url"] = data.get("baseUrl")
1216
+ out["logged_in"] = bool(data.get("authenticated") or os.environ.get("AD_API_KEY"))
1217
+ if not out["logged_in"]:
1218
+ out["error"] = "not logged in — run `agentos fallback login`"
1219
+ return out
1220
+ if verify:
1221
+ try:
1222
+ proc = subprocess.run(_fallback_argv(cli, "agents", "list", "--json"),
1223
+ capture_output=True, text=True, errors="replace",
1224
+ timeout=timeout, check=False)
1225
+ except Exception as exc: # noqa: BLE001
1226
+ out["error"] = f"could not reach the AI Draftsman server: {exc}"
1227
+ return out
1228
+ out["verified"] = proc.returncode == 0
1229
+ if proc.returncode != 0:
1230
+ out["logged_in"] = proc.returncode != 2
1231
+ out["error"] = ((proc.stderr or proc.stdout).strip()[-300:]
1232
+ or f"aidraftsman exited with code {proc.returncode}")
1233
+ if proc.returncode == 2:
1234
+ out["error"] += " — run `agentos fallback login`"
1235
+ return out
1236
+
1237
+
1238
+ #: Shown when neither CLI can take a ticket; the entry points' "CLI missing" error.
1239
+ NO_LOCAL_CLI_MESSAGE = (
1240
+ "Neither the Claude Code CLI nor the AI Draftsman fallback is ready on this "
1241
+ "machine. Install claude and run `claude setup-token`, or run "
1242
+ "`agentos fallback install` then `agentos fallback login`."
1243
+ )
1244
+
1245
+
1246
+ def can_run_locally() -> bool:
1247
+ """True when a ticket can run here: claude is installed, or the fallback is
1248
+ installed AND logged in.
1249
+
1250
+ The entry points (plan, the auto loop, local execution) gate on this rather
1251
+ than :func:`is_available`, so a box with no claude at all still runs tickets
1252
+ on the fallback. A missing claude fails on spawn with no session id, which
1253
+ is exactly what ``run_claude_local`` falls back on.
1254
+ """
1255
+ if is_available():
1256
+ return True
1257
+ st = fallback_status()
1258
+ return bool(st["installed"] and st["logged_in"])
1259
+
1260
+
1261
+ def _headless() -> bool:
1262
+ """No browser can open here: an SSH session, or Linux with no display."""
1263
+ if os.environ.get("SSH_CONNECTION") or os.environ.get("SSH_TTY"):
1264
+ return True
1265
+ return sys.platform.startswith("linux") and not (
1266
+ os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"))
1267
+
1268
+
1269
+ def fallback_login(*, method: str | None = None, api_key: str | None = None,
1270
+ base_url: str | None = None, no_browser: bool | None = None) -> int:
1271
+ """Log the fallback CLI in, interactively, on this terminal. Returns its exit code.
1272
+
1273
+ Same two ways the CLI offers: ``method="browser"`` prints a sign-in code and
1274
+ a link to approve on the AI Draftsman frontend (works on a headless VPS: open
1275
+ the link on any machine), ``method="api-key"`` validates and stores a key
1276
+ from Settings → API keys (prompted with hidden input when ``api_key`` is not
1277
+ given). ``None`` shows the CLI's own menu. Installs the bundled CLI first.
1278
+ """
1279
+ cli = asyncio.run(ensure_fallback_cli())
1280
+ if not cli:
1281
+ print("error: the AI Draftsman CLI is not available "
1282
+ "(Node.js 20+ and npm are needed to install the bundled copy, "
1283
+ "or it is disabled with AGENTOS_FALLBACK_CLI=off)")
1284
+ return 1
1285
+ args = ["auth", "login"]
1286
+ if api_key:
1287
+ method = "api-key"
1288
+ args += ["--api-key", api_key]
1289
+ if method:
1290
+ args += ["--method", method]
1291
+ if base_url:
1292
+ args += ["--base-url", base_url]
1293
+ if no_browser is None:
1294
+ no_browser = _headless()
1295
+ if no_browser:
1296
+ args.append("--no-browser")
1297
+ try:
1298
+ return subprocess.run(_fallback_argv(cli, *args), check=False).returncode
1299
+ except KeyboardInterrupt: # the CLI already printed "Sign-in cancelled."
1300
+ return 130
1301
+
1302
+
1303
+ def fallback_logout() -> int:
1304
+ cli = fallback_cli_path()
1305
+ if not cli:
1306
+ print("nothing to do: the AI Draftsman CLI is not installed")
1307
+ return 0
1308
+ return subprocess.run(_fallback_argv(cli, "auth", "logout"), check=False).returncode
1309
+
1310
+
1167
1311
  def _claude_not_working(result: dict[str, Any]) -> bool:
1168
1312
  """True when the run failed because of the `claude` CLI, not the task.
1169
1313
 
@@ -1213,7 +1357,8 @@ async def run_fallback_cli(
1213
1357
  cli = await ensure_fallback_cli()
1214
1358
  if not cli:
1215
1359
  return _result(ok=False, error="AI Draftsman CLI not available (needs Node.js 20+ "
1216
- "and npm to install the bundled copy).",
1360
+ "and npm to install the bundled copy; "
1361
+ "`agentos fallback install`).",
1217
1362
  tier=tier, provider=provider)
1218
1363
 
1219
1364
  path = Path(project_path).expanduser()
@@ -1222,13 +1367,11 @@ async def run_fallback_cli(
1222
1367
  mode = _setting("AGENTOS_FALLBACK_PERMISSION_MODE") or "acceptEdits"
1223
1368
  if mode not in _FALLBACK_MODES:
1224
1369
  mode = "acceptEdits"
1225
- cmd = [cli, "-p", "--output-format", "json", "--permission-mode", mode]
1370
+ args = ["-p", "--output-format", "json", "--permission-mode", mode]
1226
1371
  agent = _setting("AGENTOS_FALLBACK_AGENT")
1227
1372
  if agent:
1228
- cmd += ["--agent", agent]
1229
- # npm installs a .cmd shim on Windows — same reason as the claude spawn.
1230
- if sys.platform == "win32" and cli.lower().endswith((".cmd", ".bat")):
1231
- cmd = [os.environ.get("COMSPEC", "cmd.exe"), "/c", *cmd]
1373
+ args += ["--agent", agent]
1374
+ cmd = _fallback_argv(cli, *args)
1232
1375
 
1233
1376
  env = dict(os.environ)
1234
1377
  if extra_env:
@@ -1277,8 +1420,11 @@ async def run_fallback_cli(
1277
1420
  continue
1278
1421
  if proc.returncode != 0 or not isinstance(data, dict) or "result" not in data:
1279
1422
  err = err_text[-500:] or f"aidraftsman exited with code {proc.returncode}"
1423
+ auth_expired = proc.returncode == 2 or _is_auth_error(err)
1424
+ if auth_expired:
1425
+ err += " — log the fallback in with `agentos fallback login`"
1280
1426
  return _result(ok=False, error=err, files_changed=files_changed,
1281
- auth_expired=proc.returncode == 2 or _is_auth_error(err),
1427
+ auth_expired=auth_expired,
1282
1428
  tier=tier, provider=provider, model="aidraftsman")
1283
1429
 
1284
1430
  usage = data.get("usage") or {}
@@ -224,12 +224,11 @@ async def run_task(task: dict[str, Any], *, send: Send, thread_id: str = "plan-l
224
224
  from sdk import local_exec
225
225
  from sdk.ipc.handlers import _code_one_repo
226
226
 
227
- if not local_exec.is_available():
227
+ if not local_exec.can_run_locally():
228
228
  await send(Envelope(
229
229
  type=Envelope.TYPE_ERROR, thread_id=thread_id,
230
230
  data={"code": "CLAUDE_CLI_MISSING",
231
- "message": "The Claude Code CLI is not installed on this machine. "
232
- "Install it and run `claude setup-token`."},
231
+ "message": local_exec.NO_LOCAL_CLI_MESSAGE},
233
232
  ).model_dump())
234
233
  return {"status": "failed", "reason": "claude_cli_missing", "repos": []}
235
234
 
@@ -1201,9 +1201,8 @@ def _run_loop(workspace_root: str | None, only_keys: list[str] | None = None,
1201
1201
  from sdk.client import BackendClient, register_status_observer
1202
1202
  from sdk.ipc import handlers
1203
1203
 
1204
- if not local_exec.is_available():
1205
- print("error: the `claude` CLI is not installed/authenticated on this machine.")
1206
- print(" install it and run `claude setup-token`, then retry.")
1204
+ if not local_exec.can_run_locally():
1205
+ print(f"error: {local_exec.NO_LOCAL_CLI_MESSAGE}")
1207
1206
  sys.exit(1)
1208
1207
 
1209
1208
  try:
@@ -77,6 +77,19 @@ def claude_readiness() -> dict:
77
77
  "auth_method": None, "email": None, "org": None, "error": str(exc)}
78
78
 
79
79
 
80
+ def fallback_readiness() -> dict:
81
+ """AI Draftsman fallback CLI readiness (installed + logged in) for /status.
82
+
83
+ Reads the stored login only — no server round trip on every /status.
84
+ """
85
+ try:
86
+ from sdk.local_exec import fallback_status
87
+ return fallback_status()
88
+ except Exception as exc: # noqa: BLE001
89
+ return {"installed": False, "logged_in": False, "verified": None, "path": None,
90
+ "account": None, "base_url": None, "disabled": False, "error": str(exc)}
91
+
92
+
80
93
  def environment_info() -> dict:
81
94
  """Env snapshot for the /status endpoint and setup wizard."""
82
95
  vp = running_venv()
@@ -92,6 +105,7 @@ def environment_info() -> dict:
92
105
  "agentsystem_exists": root.exists(),
93
106
  "sdk_version": sdk_version(),
94
107
  "claude": claude_readiness(),
108
+ "fallback": fallback_readiness(),
95
109
  }
96
110
 
97
111
 
@@ -173,3 +173,129 @@ def test_no_npm_reports_why(tmp_path, monkeypatch, no_claude):
173
173
  result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
174
174
  assert result["ok"] is False
175
175
  assert "Node.js 20+" in result["error"]
176
+
177
+
178
+ def _fake_auth_cli(tmp_path, *, status, agents_exit=0):
179
+ """An `aidraftsman` stand-in for the auth commands: records argv, prints `status`."""
180
+ record = tmp_path / "auth.json"
181
+ script = tmp_path / "aidraftsman"
182
+ script.write_text(f"""#!{sys.executable}
183
+ import json, sys
184
+ argv = sys.argv[1:]
185
+ log = json.load(open({str(record)!r})) if __import__("os").path.exists({str(record)!r}) else []
186
+ json.dump(log + [argv], open({str(record)!r}, "w"))
187
+ if argv[:2] == ["auth", "status"]:
188
+ print(json.dumps({status!r}))
189
+ elif argv[:2] == ["agents", "list"]:
190
+ if {agents_exit!r}:
191
+ sys.stderr.write("Request failed (401): Invalid API key")
192
+ sys.exit({agents_exit!r})
193
+ """)
194
+ script.chmod(0o755)
195
+ return script, record
196
+
197
+
198
+ def test_ad_on_path_is_found(tmp_path, monkeypatch):
199
+ monkeypatch.delenv("AGENTOS_FALLBACK_CLI")
200
+ monkeypatch.setattr(local_exec, "_setting", lambda key: "")
201
+ monkeypatch.setattr(local_exec.shutil, "which",
202
+ lambda name: "/opt/bin/ad" if name == "ad" else None)
203
+ assert local_exec.fallback_cli_path() == "/opt/bin/ad"
204
+
205
+
206
+ def test_status_reports_stored_login(tmp_path, monkeypatch):
207
+ cli, record = _fake_auth_cli(tmp_path, status={
208
+ "authenticated": True, "baseUrl": "https://dev-api.aidraftsman.ai/",
209
+ "account": "Consultancy Outfit"})
210
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
211
+ monkeypatch.delenv("AD_API_KEY", raising=False)
212
+
213
+ st = local_exec.fallback_status()
214
+ assert st["installed"] and st["logged_in"] and st["error"] is None
215
+ assert st["account"] == "Consultancy Outfit"
216
+ assert st["verified"] is None
217
+ assert json.loads(record.read_text()) == [["auth", "status", "--json"]]
218
+
219
+
220
+ def test_status_not_logged_in_says_how(tmp_path, monkeypatch):
221
+ cli, _ = _fake_auth_cli(tmp_path, status={"authenticated": False})
222
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
223
+ monkeypatch.delenv("AD_API_KEY", raising=False)
224
+
225
+ st = local_exec.fallback_status(verify=True)
226
+ assert st["logged_in"] is False
227
+ assert "agentos fallback login" in st["error"]
228
+
229
+
230
+ def test_status_verify_catches_a_rejected_key(tmp_path, monkeypatch):
231
+ cli, record = _fake_auth_cli(tmp_path, status={"authenticated": True}, agents_exit=2)
232
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
233
+
234
+ st = local_exec.fallback_status(verify=True)
235
+ assert st["verified"] is False and st["logged_in"] is False
236
+ assert "401" in st["error"]
237
+ assert json.loads(record.read_text())[-1] == ["agents", "list", "--json"]
238
+
239
+
240
+ def test_status_when_disabled(monkeypatch):
241
+ st = local_exec.fallback_status()
242
+ assert st["disabled"] and not st["installed"]
243
+
244
+
245
+ @pytest.mark.parametrize("kwargs, expected", [
246
+ ({"method": "browser", "no_browser": True},
247
+ ["auth", "login", "--method", "browser", "--no-browser"]),
248
+ ({"api_key": "k" * 20, "no_browser": False},
249
+ ["auth", "login", "--api-key", "k" * 20, "--method", "api-key"]),
250
+ ({"method": "api-key", "base_url": "https://x.example/", "no_browser": False},
251
+ ["auth", "login", "--method", "api-key", "--base-url", "https://x.example/"]),
252
+ ])
253
+ def test_login_passes_the_method_through(tmp_path, monkeypatch, kwargs, expected):
254
+ cli, record = _fake_auth_cli(tmp_path, status={})
255
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
256
+
257
+ assert local_exec.fallback_login(**kwargs) == 0
258
+ assert json.loads(record.read_text()) == [expected]
259
+
260
+
261
+ def test_login_on_a_headless_box_does_not_try_a_browser(tmp_path, monkeypatch):
262
+ cli, record = _fake_auth_cli(tmp_path, status={})
263
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
264
+ monkeypatch.setenv("SSH_CONNECTION", "1.2.3.4 5 6.7.8.9 22")
265
+
266
+ local_exec.fallback_login(method="browser")
267
+ assert json.loads(record.read_text())[0][-1] == "--no-browser"
268
+
269
+
270
+ def test_fallback_auth_failure_points_at_login(tmp_path, monkeypatch, no_claude):
271
+ cli, _ = _fake_cli(tmp_path, exit_code=2, stderr="Request failed (401)")
272
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
273
+
274
+ result = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
275
+ assert result["auth_expired"] is True
276
+ assert "agentos fallback login" in result["error"]
277
+
278
+
279
+ @pytest.mark.parametrize("claude, fallback, expected", [
280
+ (True, {"installed": False, "logged_in": False}, True),
281
+ (False, {"installed": True, "logged_in": True}, True),
282
+ (False, {"installed": True, "logged_in": False}, False),
283
+ (False, {"installed": False, "logged_in": False}, False),
284
+ ])
285
+ def test_can_run_locally(monkeypatch, claude, fallback, expected):
286
+ monkeypatch.setattr(local_exec, "is_available", lambda: claude)
287
+ monkeypatch.setattr(local_exec, "fallback_status", lambda **k: fallback)
288
+ assert local_exec.can_run_locally() is expected
289
+
290
+
291
+ def test_no_claude_at_all_runs_on_a_logged_in_fallback(tmp_path, monkeypatch, no_claude):
292
+ """The whole path with claude absent: the gate opens, the spawn falls back."""
293
+ cli, record = _fake_cli(tmp_path, payload={"result": "done", "usage": {}})
294
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
295
+ monkeypatch.setattr(local_exec, "fallback_status",
296
+ lambda **k: {"installed": True, "logged_in": True})
297
+
298
+ assert local_exec.can_run_locally() is True
299
+ result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
300
+ assert result["ok"] is True and result["model"] == "aidraftsman:auto"
301
+ assert record.exists()
@@ -292,6 +292,20 @@ class TestRunGuards:
292
292
  sent.append(e)
293
293
  out = _run(plan_run.run_task({"title": "x", "project_path": "/r"}, send=send))
294
294
  assert out["status"] == "failed" and out["reason"] == "claude_cli_missing"
295
+ assert "agentos fallback login" in sent[0]["data"]["message"]
296
+
297
+ def test_no_claude_but_a_logged_in_fallback_gets_past_the_gate(self, monkeypatch):
298
+ monkeypatch.setattr("sdk.local_exec.is_available", lambda: False)
299
+ monkeypatch.setattr("sdk.local_exec.fallback_status",
300
+ lambda **k: {"installed": True, "logged_in": True})
301
+ sent: list[dict] = []
302
+
303
+ async def send(e):
304
+ sent.append(e)
305
+ out = _run(plan_run.run_task({"title": "x", "instructions": "no path here"},
306
+ send=send))
307
+ # Past the CLI gate, so it stops at the NEXT guard instead.
308
+ assert out["reason"] == "no_target"
295
309
 
296
310
 
297
311
  class TestCli: