devops-bot-sdk 1.7.6__tar.gz → 1.7.20__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 (283) hide show
  1. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/devops_bot_sdk.egg-info/SOURCES.txt +12 -0
  4. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/cli.py +530 -0
  6. devops_bot_sdk-1.7.20/sdk/faqs.py +62 -0
  7. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/helpdesk_kb.md +59 -2
  8. devops_bot_sdk-1.7.20/sdk/instructions_template.py +374 -0
  9. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ipc/handlers.py +20 -4
  10. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/log_colors.py +47 -6
  11. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/platform_compat.py +5 -4
  12. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/run_auto.py +59 -5
  13. devops_bot_sdk-1.7.20/sdk/scope_plan.py +148 -0
  14. devops_bot_sdk-1.7.20/sdk/ticket_bulk.py +214 -0
  15. devops_bot_sdk-1.7.20/sdk/ticket_plan.py +571 -0
  16. devops_bot_sdk-1.7.20/sdk/ticket_pools.py +272 -0
  17. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/tickets.py +36 -5
  18. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/api.py +184 -0
  19. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/models.py +78 -0
  20. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/service.py +173 -23
  21. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/app.js +597 -2
  22. devops_bot_sdk-1.7.20/sdk/ui/static/faqs.html +37 -0
  23. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/help.html +2 -0
  24. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/styles.css +87 -2
  25. devops_bot_sdk-1.7.20/sdk/ui/static/tickets.html +331 -0
  26. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/updater.py +22 -0
  27. devops_bot_sdk-1.7.20/tests/test_faqs.py +46 -0
  28. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_infographic_data.py +1 -1
  29. devops_bot_sdk-1.7.20/tests/test_instructions_template.py +158 -0
  30. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_log_colors_backlog.py +55 -0
  31. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_mcp_route_contract.py +9 -0
  32. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_parallel_tickets.py +1 -1
  33. devops_bot_sdk-1.7.20/tests/test_ticket_bulk.py +194 -0
  34. devops_bot_sdk-1.7.20/tests/test_ticket_plan.py +262 -0
  35. devops_bot_sdk-1.7.20/tests/test_ticket_pools.py +252 -0
  36. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_update_timer.py +121 -3
  37. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_windows_task_paths.py +3 -0
  38. devops_bot_sdk-1.7.6/sdk/ui/static/tickets.html +0 -120
  39. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/README.md +0 -0
  40. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  41. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  42. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/devops_bot_sdk.egg-info/requires.txt +0 -0
  43. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  44. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/pyproject.toml +0 -0
  45. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/__init__.py +0 -0
  46. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/classifier.py +0 -0
  47. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/collectors/__init__.py +0 -0
  48. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/collectors/common.py +0 -0
  49. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/collectors/linux_auditd.py +0 -0
  50. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/collectors/macos_eslogger.py +0 -0
  51. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/collectors/windows_eventlog.py +0 -0
  52. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/models.py +0 -0
  53. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/os_setup.py +0 -0
  54. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/recorder.py +0 -0
  55. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/redact.py +0 -0
  56. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/settings.py +0 -0
  57. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/shipper.py +0 -0
  58. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/activity/spool.py +0 -0
  59. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agent_sharing.py +0 -0
  60. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agentd.py +0 -0
  61. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/__init__.py +0 -0
  62. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/activity.py +0 -0
  63. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/builder.py +0 -0
  64. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/chat.py +0 -0
  65. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/connectors.py +0 -0
  66. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/engines/__init__.py +0 -0
  67. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/engines/claude_code.py +0 -0
  68. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/engines/http.py +0 -0
  69. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/engines/mcp.py +0 -0
  70. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/engines/n8n.py +0 -0
  71. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/engines/shell.py +0 -0
  72. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/integrations.py +0 -0
  73. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/inventory.py +0 -0
  74. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/library/__init__.py +0 -0
  75. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/manifest.py +0 -0
  76. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/prompt_studio.py +0 -0
  77. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/prompt_template.py +0 -0
  78. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/prompt_vars.py +0 -0
  79. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/run_log.py +0 -0
  80. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/schedules.py +0 -0
  81. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/secrets.py +0 -0
  82. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/ticket_run.py +0 -0
  83. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/usage.py +0 -0
  84. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/agents/varstore.py +0 -0
  85. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/browser_profile.py +0 -0
  86. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/claude_usage.py +0 -0
  87. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/client.py +0 -0
  88. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/collectors/__init__.py +0 -0
  89. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/collectors/files.py +0 -0
  90. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/collectors/process.py +0 -0
  91. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/collectors/screenshot.py +0 -0
  92. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/config.py +0 -0
  93. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/crucial.py +0 -0
  94. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/custom_agents.py +0 -0
  95. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/deferred_tickets.py +0 -0
  96. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/departments.py +0 -0
  97. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/design_verify/__init__.py +0 -0
  98. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/design_verify/assets.py +0 -0
  99. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/design_verify/bootstrap.py +0 -0
  100. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/design_verify/browser.py +0 -0
  101. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/design_verify/compare.py +0 -0
  102. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/design_verify/loop.py +0 -0
  103. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/estimator.py +0 -0
  104. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/exceptions.py +0 -0
  105. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/fallback_handoff.py +0 -0
  106. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/firewall.py +0 -0
  107. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/formats.py +0 -0
  108. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/git_ops.py +0 -0
  109. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/github_fetch.py +0 -0
  110. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/github_ingest.py +0 -0
  111. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/graph_recon.py +0 -0
  112. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/graphify.py +0 -0
  113. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/help_actions.py +0 -0
  114. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/helpdesk.py +0 -0
  115. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/hooks/__init__.py +0 -0
  116. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/hooks/crucial_guard.py +0 -0
  117. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/intake.py +0 -0
  118. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ipc/__init__.py +0 -0
  119. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ipc/electron_bridge.py +0 -0
  120. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/journal.py +0 -0
  121. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/local_exec.py +0 -0
  122. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/memory_guard.py +0 -0
  123. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/model_policy.py +0 -0
  124. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/models/__init__.py +0 -0
  125. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/models/envelope.py +0 -0
  126. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/models/requests.py +0 -0
  127. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/models/responses.py +0 -0
  128. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/models/snapshots.py +0 -0
  129. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/phase_timer.py +0 -0
  130. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/plan_run.py +0 -0
  131. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/prd_progress.py +0 -0
  132. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/proof_readers.py +0 -0
  133. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/providers.py +0 -0
  134. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/py.typed +0 -0
  135. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/resume.py +0 -0
  136. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/router.py +0 -0
  137. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/schedule_tickets.py +0 -0
  138. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/scheduler.py +0 -0
  139. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/self_report.py +0 -0
  140. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/self_report_local.py +0 -0
  141. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/sse.py +0 -0
  142. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/swagger.py +0 -0
  143. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/task_file.py +0 -0
  144. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/task_types.py +0 -0
  145. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/test.py +0 -0
  146. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/test_pipeline.py +0 -0
  147. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ticket_board.py +0 -0
  148. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ticket_fields.py +0 -0
  149. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/tiers/__init__.py +0 -0
  150. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/tiers/mini_agent.py +0 -0
  151. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/__init__.py +0 -0
  152. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/dashboard.py +0 -0
  153. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/health.py +0 -0
  154. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/ip_allowlist.py +0 -0
  155. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/jobs.py +0 -0
  156. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/runner.py +0 -0
  157. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/security.py +0 -0
  158. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/server.py +0 -0
  159. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/session.py +0 -0
  160. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/setup_manager.py +0 -0
  161. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/agents.html +0 -0
  162. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/chat.html +0 -0
  163. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/custom-agents.html +0 -0
  164. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/default-agents.html +0 -0
  165. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/index.html +0 -0
  166. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/report.html +0 -0
  167. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/runs.html +0 -0
  168. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/schedules.html +0 -0
  169. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/static/setup.html +0 -0
  170. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/ui/swagger.py +0 -0
  171. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/url_access.py +0 -0
  172. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/vps_health.py +0 -0
  173. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/vps_identity.py +0 -0
  174. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/sdk/write_hook.py +0 -0
  175. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/setup.cfg +0 -0
  176. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_activity_claude_runs.py +0 -0
  177. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_activity_collectors.py +0 -0
  178. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_activity_core.py +0 -0
  179. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_activity_live_tracking.py +0 -0
  180. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_activity_macos.py +0 -0
  181. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_activity_os_setup.py +0 -0
  182. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_activity_pipeline.py +0 -0
  183. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_activity.py +0 -0
  184. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_activity_tracking.py +0 -0
  185. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_chat_features.py +0 -0
  186. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_crud_cli.py +0 -0
  187. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_listing.py +0 -0
  188. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_prompt_async.py +0 -0
  189. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_prompt_models.py +0 -0
  190. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_run_ticket.py +0 -0
  191. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_schedules.py +0 -0
  192. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_sharing.py +0 -0
  193. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agent_workspace_settings.py +0 -0
  194. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_agents_connectors_builder.py +0 -0
  195. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_allowed_tools_config.py +0 -0
  196. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_approval_park.py +0 -0
  197. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_auto_agent_dispatch.py +0 -0
  198. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_auto_ticket_queue.py +0 -0
  199. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_browser_profile.py +0 -0
  200. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_cheap_tier.py +0 -0
  201. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_claude_usage_gate.py +0 -0
  202. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_client_jira_projects.py +0 -0
  203. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_config_token_diagnostics.py +0 -0
  204. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_configure_token_owner.py +0 -0
  205. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_console_encoding.py +0 -0
  206. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_crash_recovery.py +0 -0
  207. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_create_safe_retry.py +0 -0
  208. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_deferred_tickets.py +0 -0
  209. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_departments.py +0 -0
  210. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_done_transition.py +0 -0
  211. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_epic_decompose_run.py +0 -0
  212. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_epic_inherited_agent_id.py +0 -0
  213. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_epic_session_isolation.py +0 -0
  214. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_estimator.py +0 -0
  215. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_fallback_cli.py +0 -0
  216. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_fallback_cli_upgrade.py +0 -0
  217. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_firewall_open_on_update.py +0 -0
  218. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_formats.py +0 -0
  219. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_git_action.py +0 -0
  220. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_github_fetch.py +0 -0
  221. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_github_ingest.py +0 -0
  222. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_graph_recon.py +0 -0
  223. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_graphify_build.py +0 -0
  224. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_graphify_hook_install.py +0 -0
  225. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_health.py +0 -0
  226. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_help_actions.py +0 -0
  227. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_help_attachments.py +0 -0
  228. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_help_questions.py +0 -0
  229. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_helpdesk.py +0 -0
  230. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_intake_job_outcome.py +0 -0
  231. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_intake_plan_review.py +0 -0
  232. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_journal.py +0 -0
  233. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_limit_fallback_handoff.py +0 -0
  234. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_local_exec_provider.py +0 -0
  235. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_memory_guard.py +0 -0
  236. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_model_policy.py +0 -0
  237. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_panel_help.py +0 -0
  238. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_panel_ticket_schedules.py +0 -0
  239. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_phase_timer.py +0 -0
  240. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_plan_new_command.py +0 -0
  241. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_plan_run_activity.py +0 -0
  242. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_pr_identity.py +0 -0
  243. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_prd_artifact.py +0 -0
  244. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_project_path_resolution.py +0 -0
  245. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_proof_readers.py +0 -0
  246. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_providers.py +0 -0
  247. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_recon_session_isolation.py +0 -0
  248. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_resume.py +0 -0
  249. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_router.py +0 -0
  250. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_run_auto_agent_only.py +0 -0
  251. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_run_outputs.py +0 -0
  252. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_run_verdict_is_honest.py +0 -0
  253. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_schedule_api.py +0 -0
  254. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_schedule_outputs_and_plans.py +0 -0
  255. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_schedule_tickets.py +0 -0
  256. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_scheduler.py +0 -0
  257. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_scheduler_transient_retry.py +0 -0
  258. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_self_report.py +0 -0
  259. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_service_token_warning.py +0 -0
  260. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_stale_panel_restart.py +0 -0
  261. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_swagger_command.py +0 -0
  262. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_task_file_and_plan_run.py +0 -0
  263. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_task_file_format_doc.py +0 -0
  264. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_ticket_board.py +0 -0
  265. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_ticket_cli.py +0 -0
  266. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_ticket_fields.py +0 -0
  267. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_ticket_run_resolve_by_id.py +0 -0
  268. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_ticket_schedule_and_agent.py +0 -0
  269. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_ticket_speed_fixes.py +0 -0
  270. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_tier_flag_parity.py +0 -0
  271. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_ui_gate.py +0 -0
  272. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_ui_lifespan.py +0 -0
  273. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_updater_extras.py +0 -0
  274. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_updater_ignores_cwd.py +0 -0
  275. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_updater_pep668.py +0 -0
  276. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_updater_reports_the_truth.py +0 -0
  277. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_url_access.py +0 -0
  278. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_vps_health.py +0 -0
  279. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_vps_only_reporting.py +0 -0
  280. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_windows_loop_identity.py +0 -0
  281. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_windows_panel_task.py +0 -0
  282. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/tests/test_windows_stop_and_pidfile.py +0 -0
  283. {devops_bot_sdk-1.7.6 → devops_bot_sdk-1.7.20}/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.6
3
+ Version: 1.7.20
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.6
3
+ Version: 1.7.20
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
@@ -21,6 +21,7 @@ sdk/departments.py
21
21
  sdk/estimator.py
22
22
  sdk/exceptions.py
23
23
  sdk/fallback_handoff.py
24
+ sdk/faqs.py
24
25
  sdk/firewall.py
25
26
  sdk/formats.py
26
27
  sdk/git_ops.py
@@ -31,6 +32,7 @@ sdk/graphify.py
31
32
  sdk/help_actions.py
32
33
  sdk/helpdesk.py
33
34
  sdk/helpdesk_kb.md
35
+ sdk/instructions_template.py
34
36
  sdk/intake.py
35
37
  sdk/journal.py
36
38
  sdk/local_exec.py
@@ -49,6 +51,7 @@ sdk/router.py
49
51
  sdk/run_auto.py
50
52
  sdk/schedule_tickets.py
51
53
  sdk/scheduler.py
54
+ sdk/scope_plan.py
52
55
  sdk/self_report.py
53
56
  sdk/self_report_local.py
54
57
  sdk/sse.py
@@ -58,7 +61,10 @@ sdk/task_types.py
58
61
  sdk/test.py
59
62
  sdk/test_pipeline.py
60
63
  sdk/ticket_board.py
64
+ sdk/ticket_bulk.py
61
65
  sdk/ticket_fields.py
66
+ sdk/ticket_plan.py
67
+ sdk/ticket_pools.py
62
68
  sdk/tickets.py
63
69
  sdk/updater.py
64
70
  sdk/url_access.py
@@ -144,6 +150,7 @@ sdk/ui/static/app.js
144
150
  sdk/ui/static/chat.html
145
151
  sdk/ui/static/custom-agents.html
146
152
  sdk/ui/static/default-agents.html
153
+ sdk/ui/static/faqs.html
147
154
  sdk/ui/static/help.html
148
155
  sdk/ui/static/index.html
149
156
  sdk/ui/static/report.html
@@ -193,6 +200,7 @@ tests/test_epic_session_isolation.py
193
200
  tests/test_estimator.py
194
201
  tests/test_fallback_cli.py
195
202
  tests/test_fallback_cli_upgrade.py
203
+ tests/test_faqs.py
196
204
  tests/test_firewall_open_on_update.py
197
205
  tests/test_formats.py
198
206
  tests/test_git_action.py
@@ -207,6 +215,7 @@ tests/test_help_attachments.py
207
215
  tests/test_help_questions.py
208
216
  tests/test_helpdesk.py
209
217
  tests/test_infographic_data.py
218
+ tests/test_instructions_template.py
210
219
  tests/test_intake_job_outcome.py
211
220
  tests/test_intake_plan_review.py
212
221
  tests/test_journal.py
@@ -245,8 +254,11 @@ tests/test_swagger_command.py
245
254
  tests/test_task_file_and_plan_run.py
246
255
  tests/test_task_file_format_doc.py
247
256
  tests/test_ticket_board.py
257
+ tests/test_ticket_bulk.py
248
258
  tests/test_ticket_cli.py
249
259
  tests/test_ticket_fields.py
260
+ tests/test_ticket_plan.py
261
+ tests/test_ticket_pools.py
250
262
  tests/test_ticket_run_resolve_by_id.py
251
263
  tests/test_ticket_schedule_and_agent.py
252
264
  tests/test_ticket_speed_fixes.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.7.6
3
+ Version: 1.7.20
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.6" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.7.20" # 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
@@ -202,6 +202,9 @@ def main() -> None:
202
202
  if noun == "tickets":
203
203
  _ticket_list(rest)
204
204
  return
205
+ if noun == "pool" or noun == "pools":
206
+ _pool_command(rest)
207
+ return
205
208
 
206
209
  if noun == "agentd":
207
210
  _agentd_command(rest)
@@ -267,6 +270,7 @@ def _print_usage() -> None:
267
270
  print(" agentos agent <set-key NAME KEY[=VALUE]…|keys NAME|rm-key NAME KEY…> # persistent API keys (custom agents)")
268
271
  print(" agentos ticket <list|create|update KEY_OR_ID|delete KEY_OR_ID|help>")
269
272
  print(" agentos tickets [--status S] [--project KEY] [--all] [--json] # how many To Do / In Progress / … and which")
273
+ print(" agentos pool <list|show|create|add|remove|move|order|set|run|delete|help> # ticket pools: up to 50 tickets run together, in order")
270
274
  print(" agentos integration <add PATH|test NAME|list|rm NAME>")
271
275
  print(" agentos default-agents list [--department-id UUID] # read-only; agents are created in the web app")
272
276
  print(" agentos agentd <start [--foreground]|status>")
@@ -1203,6 +1207,20 @@ def _ticket_usage() -> None:
1203
1207
  print(" [--instructions TEXT | --instructions-file PATH] [--priority P] [--status S]")
1204
1208
  print(" [--points N] [--assignee EMAIL] [--type TYPE]")
1205
1209
  print(" agentos ticket show KEY_OR_ID [--json] # summary, status, description, humanInstructions")
1210
+ print(" agentos ticket bulk-update KEY… | --pool NAME [--status S] [--priority P] [--type T] [--points N]")
1211
+ print(" [--assignee EMAIL] [--description TEXT|--description-file PATH] [--instructions TEXT|--instructions-file PATH]")
1212
+ print(" [--description-mode M] [--instructions-mode M] [--clear-description] [--clear-instructions]")
1213
+ print(" # one or a few fields on up to 50 tickets; M: append (default) | prepend | replace")
1214
+ print(" agentos ticket instructions KEY… | --pool NAME (--text TEXT | --file PATH)")
1215
+ print(" [--replace | --prepend | --clear] # Human Instructions on up to 50 tickets at once (default: append)")
1216
+ print(" agentos ticket bulk-create PLAN.md|PLAN.json [--project KEY] [--instructions TEXT | --instructions-file PATH]")
1217
+ print(" [--type T] [--priority P] [--pool NAME | --module-pools] [--dry-run] [--example]")
1218
+ print(" # many tickets from one plan: modules, cross-module, Epics → Stories, Tasks, Bugs")
1219
+ print(" agentos ticket placeholders [--template KIND] # {ticket_key} {summary} {slug}… filled per ticket; KIND: development|content|research|design|general")
1220
+ print(" agentos ticket gen-instructions [KEY… | --pool NAME] [--kind KIND] [--notes TEXT] [--from-file PATH] [-o FILE]")
1221
+ print(" # the AI writes Human Instructions for any kind of work (picks it, or --kind), using the placeholders")
1222
+ print(" agentos ticket scope-to-plan SCOPE.md [--project KEY] [--modules \"A, B\"] [-o PLAN.md]")
1223
+ print(" # the AI drafts that plan from a detailed scope document (nothing is created)")
1206
1224
  print(" agentos ticket delete KEY_OR_ID [--yes] # asks to confirm unless --yes")
1207
1225
  print()
1208
1226
  print(" TYPE: Epic|Story|Task|Bug|Sub-task (default Task) P: Lowest|Low|Medium|High|Highest (default Medium)")
@@ -1242,6 +1260,500 @@ def _parse_ticket_flags(args: list[str], *, creating: bool) -> dict[str, str]:
1242
1260
  return fields
1243
1261
 
1244
1262
 
1263
+ _POOL_USAGE = """usage: agentos pool <command> — up to 50 tickets you run together, in your order
1264
+
1265
+ agentos pool list # every pool, its size and last run
1266
+ agentos pool show NAME # run order, each ticket's summary/status, instructions
1267
+ agentos pool create NAME KEY… [--instructions TEXT] [--parallel N] # new pool (keys in run order)
1268
+ agentos pool add NAME KEY… [--at POS] # add tickets (at the end, or at position POS)
1269
+ agentos pool remove NAME KEY… # take tickets out
1270
+ agentos pool move NAME KEY POS # move one ticket to position POS (1 = first)
1271
+ agentos pool order NAME KEY… # these first, in this order; the rest follow
1272
+ agentos pool set NAME [--instructions TEXT | --clear-instructions] [--parallel N] [--rename NEW]
1273
+ agentos pool run NAME [--parallel N] # run it now (= agentos-auto pool NAME)
1274
+ agentos pool delete NAME # remove the pool (tickets are untouched)
1275
+
1276
+ --instructions: applies to EVERY ticket in the pool, ahead of each ticket's own
1277
+ Human Instructions. --parallel: tickets at once, 1–8 (default 3) — never two on
1278
+ the same repo, so same-repo tickets still run in your order.
1279
+ """
1280
+
1281
+
1282
+ def _pool_opts(args: list[str], valued: tuple[str, ...], bare: tuple[str, ...] = ()) -> tuple[list[str], dict]:
1283
+ """Split ``args`` into positionals and ``{flag: value}`` (bare flags → True)."""
1284
+ pos: list[str] = []
1285
+ opts: dict = {}
1286
+ i = 0
1287
+ while i < len(args):
1288
+ a = args[i]
1289
+ if a in valued:
1290
+ if i + 1 >= len(args):
1291
+ print(f"error: expected a value after {a}")
1292
+ sys.exit(1)
1293
+ opts[a] = args[i + 1]
1294
+ i += 2
1295
+ continue
1296
+ if a in bare:
1297
+ opts[a] = True
1298
+ elif a.startswith("--"):
1299
+ print(f"error: unknown option {a} — see --help")
1300
+ sys.exit(1)
1301
+ else:
1302
+ pos.append(a)
1303
+ i += 1
1304
+ return pos, opts
1305
+
1306
+
1307
+ def _pool_print(pool: dict, info: dict | None = None) -> None:
1308
+ from sdk import log_colors
1309
+ paint = log_colors.colorize if log_colors.enabled() else (lambda x: x)
1310
+ print(f"pool {pool['name']} — {len(pool['keys'])} ticket(s), up to {pool.get('parallel')} at once"
1311
+ + (f", last run {pool['last_run_at']}" if pool.get("last_run_at") else ""))
1312
+ width = max(len(k) for k in pool["keys"]) if pool["keys"] else 0
1313
+ for n, k in enumerate(pool["keys"], 1):
1314
+ row = (info or {}).get(k)
1315
+ tail = f" {row['summary'][:70]} → {row['status']}" if row else (" (not found on the board)" if info else "")
1316
+ print(paint(f" {n:>2}. {k.ljust(width)}{tail}"))
1317
+ if pool.get("instructions"):
1318
+ print(" instructions (every ticket):")
1319
+ for line in pool["instructions"].splitlines():
1320
+ print(f" {line}")
1321
+
1322
+
1323
+ def _pool_command(args: list[str]) -> None:
1324
+ """`agentos pool …` — ticket pools (sdk/ticket_pools.py)."""
1325
+ from sdk import ticket_pools as TP
1326
+
1327
+ sub, rest = (args[0], args[1:]) if args else ("list", [])
1328
+ if sub in ("-h", "--help", "help"):
1329
+ print(_POOL_USAGE, end="")
1330
+ return
1331
+
1332
+ def _parallel(opts: dict) -> int | None:
1333
+ return int(opts["--parallel"]) if str(opts.get("--parallel", "")).isdigit() else (
1334
+ None if "--parallel" not in opts else -1)
1335
+
1336
+ try:
1337
+ if sub in ("list", "ls"):
1338
+ pools = TP.list_pools()
1339
+ if not pools:
1340
+ print("no pools yet — `agentos pool create NAME KEY… [--instructions TEXT]`")
1341
+ return
1342
+ for p in pools:
1343
+ print(f" {p['name']:<24} {len(p['keys']):>2} ticket(s) ×{p.get('parallel')}"
1344
+ f" {'instructions' if p.get('instructions') else '':<12}"
1345
+ f" {('last run ' + p['last_run_at']) if p.get('last_run_at') else 'never run'}")
1346
+ return
1347
+ if sub == "show":
1348
+ if not rest:
1349
+ print("usage: agentos pool show NAME")
1350
+ sys.exit(1)
1351
+ pool = TP.get(" ".join(rest))
1352
+ info = None
1353
+ try:
1354
+ from sdk.client import BackendClient
1355
+ info = asyncio.run(TP.describe(BackendClient.from_config(), pool["keys"])) or None
1356
+ except Exception: # noqa: BLE001 — no token / no backend: show the keys alone
1357
+ info = None
1358
+ _pool_print(pool, info)
1359
+ return
1360
+ if sub == "create":
1361
+ pos, opts = _pool_opts(rest, ("--instructions", "--parallel"))
1362
+ if len(pos) < 2:
1363
+ print("usage: agentos pool create NAME KEY… [--instructions TEXT] [--parallel N]")
1364
+ sys.exit(1)
1365
+ pool = TP.create(pos[0], pos[1:], opts.get("--instructions", ""), _parallel(opts))
1366
+ shown = f'"{pool["name"]}"' if " " in pool["name"] else pool["name"]
1367
+ print(f"✓ created pool {pool['name']} with {len(pool['keys'])} ticket(s) — "
1368
+ f"run it: agentos pool run {shown}")
1369
+ return
1370
+ if sub == "add":
1371
+ pos, opts = _pool_opts(rest, ("--at",))
1372
+ if len(pos) < 2:
1373
+ print("usage: agentos pool add NAME KEY… [--at POS]")
1374
+ sys.exit(1)
1375
+ pool = TP.add(pos[0], pos[1:], int(opts["--at"]) if "--at" in opts else None)
1376
+ elif sub in ("remove", "rm"):
1377
+ if len(rest) < 2:
1378
+ print("usage: agentos pool remove NAME KEY…")
1379
+ sys.exit(1)
1380
+ pool = TP.remove(rest[0], rest[1:])
1381
+ elif sub == "move":
1382
+ if len(rest) != 3 or not rest[2].isdigit():
1383
+ print("usage: agentos pool move NAME KEY POS")
1384
+ sys.exit(1)
1385
+ pool = TP.move(rest[0], rest[1], int(rest[2]))
1386
+ elif sub == "order":
1387
+ if len(rest) < 2:
1388
+ print("usage: agentos pool order NAME KEY…")
1389
+ sys.exit(1)
1390
+ pool = TP.order(rest[0], rest[1:])
1391
+ elif sub == "set":
1392
+ pos, opts = _pool_opts(rest, ("--instructions", "--parallel", "--rename"),
1393
+ ("--clear-instructions",))
1394
+ if len(pos) != 1 or not opts:
1395
+ print("usage: agentos pool set NAME [--instructions TEXT | --clear-instructions] "
1396
+ "[--parallel N] [--rename NEW]")
1397
+ sys.exit(1)
1398
+ instr = "" if opts.get("--clear-instructions") else opts.get("--instructions")
1399
+ pool = TP.update(pos[0], instructions=instr, parallel=_parallel(opts),
1400
+ rename=opts.get("--rename"))
1401
+ elif sub == "delete":
1402
+ if not rest:
1403
+ print("usage: agentos pool delete NAME")
1404
+ sys.exit(1)
1405
+ pool = TP.delete(" ".join(rest))
1406
+ print(f"✓ deleted pool {pool['name']} (its {len(pool['keys'])} tickets are untouched)")
1407
+ return
1408
+ elif sub == "run":
1409
+ pos, opts = _pool_opts(rest, ("--parallel", "--workspace-root"), ("--foreground", "-f"))
1410
+ if not pos:
1411
+ print("usage: agentos pool run NAME [--parallel N]")
1412
+ sys.exit(1)
1413
+ from sdk import run_auto
1414
+ argv = ["agentos-auto", "pool", " ".join(pos)]
1415
+ for f in ("--parallel", "--workspace-root"):
1416
+ if f in opts:
1417
+ argv += [f, opts[f]]
1418
+ argv += [f for f in ("--foreground", "-f") if opts.get(f)]
1419
+ sys.argv = argv
1420
+ run_auto.main()
1421
+ return
1422
+ else:
1423
+ print(f"unknown pool command {sub!r}\n")
1424
+ print(_POOL_USAGE, end="")
1425
+ sys.exit(1)
1426
+ except TP.PoolError as exc:
1427
+ print(f"error: {exc}")
1428
+ sys.exit(1)
1429
+ _pool_print(pool)
1430
+
1431
+
1432
+ def _read_text_arg(opts: dict, text_flag: str, file_flag: str) -> str | None:
1433
+ """--text / --file twins; exits 1 on both or an unreadable file."""
1434
+ if text_flag in opts and file_flag in opts:
1435
+ print(f"error: pass {text_flag} or {file_flag}, not both")
1436
+ sys.exit(1)
1437
+ if file_flag in opts:
1438
+ try:
1439
+ return Path(opts[file_flag]).expanduser().read_text(encoding="utf-8")
1440
+ except OSError as exc:
1441
+ print(f"error: cannot read {opts[file_flag]}: {exc}")
1442
+ sys.exit(1)
1443
+ return opts.get(text_flag)
1444
+
1445
+
1446
+ def _ticket_bulk_instructions(args: list[str]) -> None:
1447
+ """`agentos ticket instructions KEY… (--text T | --file P) [--replace|--prepend|--clear]`."""
1448
+ from sdk import ticket_bulk
1449
+ from sdk.client import BackendClient
1450
+
1451
+ if not args or args[0] in ("-h", "--help", "help"):
1452
+ print("usage: agentos ticket instructions KEY… | --pool NAME (--text TEXT | --file PATH)")
1453
+ print(" [--replace | --prepend | --clear]")
1454
+ print(" Writes Human Instructions on every ticket given (at most 50). Default: APPEND to")
1455
+ print(" what each ticket already has (a block it already contains is not added twice).")
1456
+ sys.exit(0 if args else 1)
1457
+ keys, opts = _pool_opts(args, ("--text", "--file", "--pool"), ("--replace", "--prepend", "--clear", "--append"))
1458
+ modes = [m for m in ("replace", "prepend", "clear", "append") if opts.get("--" + m)]
1459
+ if len(modes) > 1:
1460
+ print("error: choose one of --append, --prepend, --replace, --clear")
1461
+ sys.exit(1)
1462
+ mode = modes[0] if modes else "append"
1463
+ text = _read_text_arg(opts, "--text", "--file") or ""
1464
+ if mode != "clear" and not text.strip():
1465
+ print("error: give the instructions with --text TEXT or --file PATH (or --clear)")
1466
+ sys.exit(1)
1467
+ keys = _bulk_keys(keys, opts.get("--pool"))
1468
+ res = asyncio.run(ticket_bulk.set_instructions(BackendClient.from_config(), keys, text, mode))
1469
+ if "updated" not in res:
1470
+ print(f"error: {res['error']}")
1471
+ sys.exit(1)
1472
+ verb = {"append": "appended to", "prepend": "prepended to", "replace": "replaced on",
1473
+ "clear": "cleared on"}[mode]
1474
+ _print_bulk_result(res, f"instructions {verb}")
1475
+ sys.exit(0 if res["ok"] else 1)
1476
+
1477
+
1478
+ def _bulk_keys(keys: list[str], pool: str | None) -> list[str]:
1479
+ from sdk import ticket_pools
1480
+ if pool:
1481
+ try:
1482
+ keys = keys + [k for k in ticket_pools.get(pool)["keys"] if k not in keys]
1483
+ except ticket_pools.PoolError as exc:
1484
+ print(f"error: {exc}")
1485
+ sys.exit(1)
1486
+ if not keys:
1487
+ print("error: which tickets? — give keys, or --pool NAME")
1488
+ sys.exit(1)
1489
+ return keys
1490
+
1491
+
1492
+ def _print_bulk_result(res: dict, verb: str) -> None:
1493
+ if res["updated"]:
1494
+ print(f"✓ {verb} {len(res['updated'])} ticket(s): {', '.join(res['updated'])}")
1495
+ if res["unchanged"]:
1496
+ print(f" unchanged (already had it): {', '.join(res['unchanged'])}")
1497
+ for p in res.get("partial") or []:
1498
+ print(f"⚠ {p['key']}: the backend accepted but did NOT apply {', '.join(p['not_applied'])}")
1499
+ for f in res["failed"]:
1500
+ print(f"✗ {f['key']}: {f['error']}")
1501
+
1502
+
1503
+ def _ticket_bulk_update(args: list[str]) -> None:
1504
+ """`agentos ticket bulk-update KEY…|--pool NAME --status S --priority P …`."""
1505
+ from sdk import ticket_bulk
1506
+ from sdk.client import BackendClient
1507
+
1508
+ if not args or args[0] in ("-h", "--help", "help"):
1509
+ print("usage: agentos ticket bulk-update KEY… | --pool NAME [one or more fields]")
1510
+ print(" --status S --priority P --type T --points N --assignee EMAIL")
1511
+ print(" --description TEXT | --description-file PATH [--description-mode append|prepend|replace]")
1512
+ print(" --instructions TEXT | --instructions-file PATH [--instructions-mode append|prepend|replace]")
1513
+ print(" --clear-description --clear-instructions")
1514
+ print(" The same change on every ticket given (at most 50); tickets that already match are")
1515
+ print(" left alone. Text fields APPEND by default.")
1516
+ sys.exit(0 if args else 1)
1517
+ valued = ("--pool", "--status", "--priority", "--type", "--points", "--assignee", "--description",
1518
+ "--description-file", "--instructions", "--instructions-file", "--description-mode",
1519
+ "--instructions-mode")
1520
+ keys, opts = _pool_opts(args, valued, ("--clear-description", "--clear-instructions"))
1521
+ keys = _bulk_keys(keys, opts.get("--pool"))
1522
+ fields: dict = {k: opts[f] for f, k in (("--status", "status"), ("--priority", "priority"),
1523
+ ("--type", "type"), ("--points", "points"),
1524
+ ("--assignee", "assigneeEmail")) if f in opts}
1525
+ modes: dict = {}
1526
+ for name, wire in (("description", "description"), ("instructions", "humanInstructions")):
1527
+ text = _read_text_arg(opts, f"--{name}", f"--{name}-file")
1528
+ if opts.get(f"--clear-{name}"):
1529
+ if text is not None:
1530
+ print(f"error: --clear-{name} and --{name} together make no sense")
1531
+ sys.exit(1)
1532
+ modes[wire] = "clear"
1533
+ elif text is not None:
1534
+ fields[wire] = text
1535
+ modes[wire] = opts.get(f"--{name}-mode", "append")
1536
+ if modes[wire] == "clear":
1537
+ print(f"error: use --clear-{name} to clear it")
1538
+ sys.exit(1)
1539
+ if not fields and not modes:
1540
+ print("error: nothing to update — pass at least one field (see --help)")
1541
+ sys.exit(1)
1542
+ res = asyncio.run(ticket_bulk.update_many(BackendClient.from_config(), keys, fields, modes))
1543
+ if "updated" not in res:
1544
+ print(f"error: {res['error']}")
1545
+ sys.exit(1)
1546
+ _print_bulk_result(res, "updated " + ", ".join(res["fields"]) + " on")
1547
+ sys.exit(0 if res["ok"] else 1)
1548
+
1549
+
1550
+ def _pname(name: str) -> str:
1551
+ return f'"{name}"' if " " in name else name
1552
+
1553
+
1554
+ def _print_plan_tree(res: dict) -> None:
1555
+ """Dry-run view: modules → Epics → Stories, with types, statuses and instructions."""
1556
+ rows = res["tickets"]
1557
+ c = res.get("counts") or {}
1558
+ print(f"would create {len(rows)} ticket(s) in {res['project']}: {c.get('epic', 0)} epic(s), "
1559
+ f"{c.get('story', 0)} story/child ticket(s), {c.get('ticket', 0)} standalone")
1560
+ current = object()
1561
+ for i, t in enumerate(rows):
1562
+ if t["module"] != current:
1563
+ current = t["module"]
1564
+ m = next((x for x in res.get("modules") or [] if x["name"] == current), None)
1565
+ print(f"\n{current or '(no module)'}" + (" — instructions: " + m["instructions"].splitlines()[0][:70]
1566
+ if m and m.get("instructions") else ""))
1567
+ indent = " " if t["parent"] is not None else " "
1568
+ kind = t.get("type") or "Task"
1569
+ extra = [x for x in (t.get("priority"), f"{t['points']} pts" if t.get("points") else "",
1570
+ f"→ {t['status']}" if t.get("status") else "") if x]
1571
+ note = f" ({t['children']} children; set Done so the loop won't re-decompose it)" if t["kind"] == "epic" and t["children"] \
1572
+ else " (no children: the loop will decompose it)" if t["kind"] == "epic" else ""
1573
+ print(f"{indent}{i + 1:>3}. {kind:<8} {t['summary']}" + (f" [{' · '.join(extra)}]" if extra else "") + note)
1574
+ for w in res.get("warnings") or []:
1575
+ print(f"warning: {w}")
1576
+ for p in res.get("pools") or []:
1577
+ print(f"pool {p['name']}: {p['count']} ticket(s)")
1578
+
1579
+
1580
+ def _ticket_bulk_create(args: list[str]) -> None:
1581
+ """`agentos ticket bulk-create PLAN [--project KEY] [--instructions …] [--pool NAME|--module-pools] [--dry-run]`."""
1582
+ from sdk import ticket_plan
1583
+
1584
+ if args and args[0] == "--example":
1585
+ print(ticket_plan.EXAMPLE_PLAN, end="")
1586
+ return
1587
+ if not args or args[0] in ("-h", "--help", "help"):
1588
+ print("usage: agentos ticket bulk-create PLAN.md|PLAN.json [--project KEY]")
1589
+ print(" [--instructions TEXT | --instructions-file PATH] [--type T] [--priority P]")
1590
+ print(" [--pool NAME] [--module-pools [--pool-prefix TEXT]] [--no-module-prefix] [--dry-run]")
1591
+ print(" Sections: `# Module: NAME` (its Instructions: apply to its tickets — e.g. the repo),")
1592
+ print(" `# Cross-module: A, B` (inherits every named module's instructions), `## Epic: T` with")
1593
+ print(" `### Story: T` children, standalone `## Task:` / `## Bug:`. Simple plans: one ticket per `## Summary`.")
1594
+ print(" An Epic created with children is set Done (the loop won't decompose it again).")
1595
+ print(f" At most {ticket_plan.MAX_PLAN} tickets. --pool: one pool of every runnable ticket; --module-pools:")
1596
+ print(" one pool per module (+ cross-module). Summaries get a [Module] prefix unless --no-module-prefix.")
1597
+ print(" Example: agentos ticket bulk-create --example > plan.md From a scope document: agentos ticket scope-to-plan")
1598
+ sys.exit(0 if args else 1)
1599
+ pos, opts = _pool_opts(args, ("--project", "--instructions", "--instructions-file", "--type",
1600
+ "--priority", "--pool", "--pool-prefix"),
1601
+ ("--dry-run", "--yes", "--module-pools", "--no-module-prefix"))
1602
+ if len(pos) != 1:
1603
+ print("error: give exactly one plan file (use - to read stdin)")
1604
+ sys.exit(1)
1605
+ try:
1606
+ text = sys.stdin.read() if pos[0] == "-" else Path(pos[0]).expanduser().read_text(encoding="utf-8")
1607
+ except OSError as exc:
1608
+ print(f"error: cannot read {pos[0]}: {exc}")
1609
+ sys.exit(1)
1610
+ shared = _read_text_arg(opts, "--instructions", "--instructions-file") or ""
1611
+ defaults = {k: opts[f] for f, k in (("--type", "type"), ("--priority", "priority")) if opts.get(f)}
1612
+
1613
+ from sdk.client import BackendClient
1614
+ client = BackendClient.from_config() if not opts.get("--dry-run") else None
1615
+ res = asyncio.run(ticket_plan.create_from_plan(
1616
+ client, text, project=opts.get("--project", ""), instructions=shared, defaults=defaults,
1617
+ pool=opts.get("--pool", ""), module_pools=bool(opts.get("--module-pools")),
1618
+ pool_prefix=opts.get("--pool-prefix", ""), module_prefix=not opts.get("--no-module-prefix"),
1619
+ dry_run=bool(opts.get("--dry-run"))))
1620
+ if res.get("errors") or ("created" not in res and not res.get("dry_run")):
1621
+ for e in res.get("errors") or [res.get("error", "failed")]:
1622
+ print(f"error: {e}")
1623
+ sys.exit(1)
1624
+ if res.get("dry_run"):
1625
+ _print_plan_tree(res)
1626
+ print("nothing created (--dry-run).")
1627
+ return
1628
+ for c in res["created"]:
1629
+ under = (f" (parent {c['parent_key']} named in its description — not linked)" if c.get("parent_unlinked")
1630
+ else f" (under {c['parent_key']})" if c.get("parent_key") else "")
1631
+ print(f"✓ {c['key'] or c['id']} {c['summary']}{under}")
1632
+ for n in res.get("notes") or []:
1633
+ print(f"note: {n}")
1634
+ for f in res["failed"]:
1635
+ print(f"✗ {f['summary']}: {f['error']}")
1636
+ for p in res.get("pools") or []:
1637
+ print(f"✓ pool {p['name']} — {len(p['keys'])} ticket(s), in plan order (run it: agentos pool run {_pname(p['name'])})")
1638
+ for e in res.get("pool_errors") or []:
1639
+ print(f"warning: pool not created: {e}")
1640
+ sys.exit(0 if res["ok"] else 1)
1641
+
1642
+
1643
+ def _ticket_placeholders(args: list[str]) -> None:
1644
+ """`agentos ticket placeholders [--template]`."""
1645
+ from sdk import instructions_template as itpl
1646
+ if "--template" in args:
1647
+ rest = [a for a in args if a != "--template"]
1648
+ kind = (rest[0] if rest else "development").lower()
1649
+ if kind not in itpl.TEMPLATES:
1650
+ print(f"error: kind must be one of {', '.join(itpl.TEMPLATES)}")
1651
+ sys.exit(1)
1652
+ print(itpl.TEMPLATES[kind][1], end="")
1653
+ return
1654
+ print("Placeholders in Human Instructions — written once, filled for each ticket:")
1655
+ for n, d in itpl.PLACEHOLDERS.items():
1656
+ print(f" {{{n}}}".ljust(18) + d)
1657
+ print(" ({{name}} works too; other braces are left alone. Filled on ticket create/update,")
1658
+ print(" bulk-update, instructions, bulk-create and at pool run time.)")
1659
+ print("Standard formats with placeholders — for every kind of work, not only code:")
1660
+ for k, (label, _) in itpl.TEMPLATES.items():
1661
+ print(f" agentos ticket placeholders --template {k}".ljust(58) + f"# {label}")
1662
+ print("({branch} is for code work; content, research, design and general tasks use an Output Location folder.)")
1663
+
1664
+
1665
+ def _ticket_gen_instructions(args: list[str]) -> None:
1666
+ """`agentos ticket gen-instructions [KEY…|--pool NAME] [--notes T] [--from-file P] [-o FILE]`."""
1667
+ from sdk import instructions_template as itpl
1668
+ if args and args[0] in ("-h", "--help", "help"):
1669
+ print("usage: agentos ticket gen-instructions [KEY… | --pool NAME] [--kind KIND] [--notes TEXT] [--from-file PATH] [-o FILE]")
1670
+ print(" The local AI writes Human Instructions for these tickets — development, content,")
1671
+ print(" research, design or general work (it picks from the tickets, or --kind) — using")
1672
+ print(" placeholders ({ticket_key}, {summary}, {slug}…) so ONE text fits them all.")
1673
+ print(" --from-file: improve existing instructions. Apply with:")
1674
+ print(" agentos ticket instructions KEY… --file OUT --replace")
1675
+ return
1676
+ keys, opts = _pool_opts(args, ("--notes", "--from-file", "--pool", "--kind", "-o", "--output"), ())
1677
+ kind = (opts.get("--kind") or "auto").lower()
1678
+ if kind != "auto" and kind not in itpl.TEMPLATES:
1679
+ print(f"error: --kind must be auto or one of {', '.join(itpl.TEMPLATES)}")
1680
+ sys.exit(1)
1681
+ keys = [k.upper() for k in keys]
1682
+ if opts.get("--pool"):
1683
+ keys = _bulk_keys(keys, opts["--pool"])
1684
+ existing = _read_text_arg(opts, "--existing", "--from-file") or ""
1685
+ rows: list[dict] = []
1686
+ if keys:
1687
+ from sdk.client import BackendClient
1688
+ recs = asyncio.run(BackendClient.from_config().list_tasks_by_keys(keys)) or []
1689
+ by = {str(r.get("jiraIssueKey") or "").upper(): r for r in recs}
1690
+ rows = [{"key": k, "summary": str(by.get(k, {}).get("summary") or ""),
1691
+ "description": str(by.get(k, {}).get("description") or "")} for k in keys]
1692
+ print("generating with the local AI…", flush=True)
1693
+ res = asyncio.run(itpl.generate(tickets=rows, notes=opts.get("--notes", ""), existing=existing, kind=kind))
1694
+ if not res.get("ok"):
1695
+ print(f"error: {res.get('error')}")
1696
+ sys.exit(1)
1697
+ out = opts.get("-o") or opts.get("--output")
1698
+ if out:
1699
+ Path(out).expanduser().write_text(res["instructions"], encoding="utf-8")
1700
+ print(f"✓ written to {out}")
1701
+ if keys:
1702
+ print(f"apply: agentos ticket instructions {' '.join(keys)} --file {out} --replace")
1703
+ else:
1704
+ print(res["instructions"], end="")
1705
+ if res.get("unknown"):
1706
+ print(f"warning: not placeholders: {', '.join('{' + n + '}' for n in res['unknown'])}")
1707
+
1708
+
1709
+ def _ticket_scope_to_plan(args: list[str]) -> None:
1710
+ """`agentos ticket scope-to-plan SCOPE [--project KEY] [--modules "A,B"] [--notes TEXT] [-o PLAN.md]`."""
1711
+ from sdk import scope_plan
1712
+
1713
+ if not args or args[0] in ("-h", "--help", "help"):
1714
+ print("usage: agentos ticket scope-to-plan SCOPE.md|SCOPE.txt [--project KEY] [--modules \"A, B\"]")
1715
+ print(" [--notes TEXT] [-o PLAN.md]")
1716
+ print(" The local AI reads a detailed scope document and drafts a ticket plan: a `# Module:` per")
1717
+ print(" module (with its repo when the scope names one), `# Cross-module:` for work spanning")
1718
+ print(" modules, Epics with User Stories (User Story format), Tasks and Bugs. Nothing is created:")
1719
+ print(" review/edit the plan, then `agentos ticket bulk-create PLAN.md --dry-run` and create.")
1720
+ sys.exit(0 if args else 1)
1721
+ pos, opts = _pool_opts(args, ("--project", "--modules", "--notes", "-o", "--output"), ())
1722
+ if len(pos) != 1:
1723
+ print("error: give exactly one scope document (use - to read stdin)")
1724
+ sys.exit(1)
1725
+ try:
1726
+ src = None if pos[0] == "-" else Path(pos[0]).expanduser()
1727
+ scope = sys.stdin.read() if src is None else src.read_text(encoding="utf-8")
1728
+ except (OSError, UnicodeDecodeError) as exc:
1729
+ print(f"error: cannot read {pos[0]} as text: {exc} (save it as .md or .txt)")
1730
+ sys.exit(1)
1731
+ # Default: next to the scope document, `<name>.plan.md`.
1732
+ out = Path(opts.get("-o") or opts.get("--output") or
1733
+ (src.with_name(src.stem + ".plan.md") if src else Path("scope.plan.md"))).expanduser()
1734
+ if out.exists():
1735
+ print(f"error: {out} already exists — pass -o NEW.md")
1736
+ sys.exit(1)
1737
+ modules = [m.strip() for m in (opts.get("--modules") or "").split(",") if m.strip()]
1738
+ print("drafting the plan with the local AI — a large scope can take several minutes…", flush=True)
1739
+ res = asyncio.run(scope_plan.draft_plan(scope, project=opts.get("--project", ""), modules=modules,
1740
+ notes=opts.get("--notes", ""),
1741
+ scope_name=src.name if src else "scope.md"))
1742
+ if not res.get("plan"):
1743
+ print(f"error: {res.get('error')}")
1744
+ sys.exit(1)
1745
+ out.write_text(res["plan"], encoding="utf-8")
1746
+ s = res["summary"]
1747
+ print(f"✓ plan written to {out}: {s['modules']} module(s), {s['cross_module']} cross-module section(s), "
1748
+ f"{s['epic']} epic(s), {s['story']} stories, {s['ticket']} standalone ticket(s)")
1749
+ for e in res.get("errors") or []:
1750
+ print(f" needs fixing: {e}")
1751
+ for w in res.get("warnings") or []:
1752
+ print(f" warning: {w}")
1753
+ print(f"next: review/edit it, then agentos ticket bulk-create {_pname(str(out))} --dry-run")
1754
+ sys.exit(0 if res.get("ok") and not res.get("errors") else 1)
1755
+
1756
+
1245
1757
  def _ticket_list(args: list[str]) -> None:
1246
1758
  """`agentos ticket list` / `agentos tickets` — how many in each status, and which."""
1247
1759
  import json as _json
@@ -1309,6 +1821,24 @@ def _ticket_command(args: list[str]) -> None:
1309
1821
  if verb == "list":
1310
1822
  _ticket_list(args[1:])
1311
1823
  return
1824
+ if verb == "instructions":
1825
+ _ticket_bulk_instructions(args[1:])
1826
+ return
1827
+ if verb == "bulk-update":
1828
+ _ticket_bulk_update(args[1:])
1829
+ return
1830
+ if verb in ("bulk-create", "plan"):
1831
+ _ticket_bulk_create(args[1:])
1832
+ return
1833
+ if verb == "scope-to-plan":
1834
+ _ticket_scope_to_plan(args[1:])
1835
+ return
1836
+ if verb == "placeholders":
1837
+ _ticket_placeholders(args[1:])
1838
+ return
1839
+ if verb == "gen-instructions":
1840
+ _ticket_gen_instructions(args[1:])
1841
+ return
1312
1842
  if verb not in {"create", "update", "show", "delete"}:
1313
1843
  _ticket_usage()
1314
1844
  sys.exit(1)