minitest-cli 0.36.0__tar.gz → 0.37.1__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 (227) hide show
  1. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/.env.example +6 -0
  2. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/PKG-INFO +28 -3
  3. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/README.md +27 -2
  4. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/pyproject.toml +4 -1
  5. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/api/client.py +9 -2
  6. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/_response_errors.py +29 -7
  7. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps.py +4 -0
  8. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch_helpers.py +22 -0
  9. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build.py +26 -8
  10. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_helpers.py +3 -1
  11. minitest_cli-0.37.1/src/minitest_cli/commands/draft.py +111 -0
  12. minitest_cli-0.37.1/src/minitest_cli/commands/draft_asks.py +148 -0
  13. minitest_cli-0.37.1/src/minitest_cli/commands/draft_conversation.py +84 -0
  14. minitest_cli-0.37.1/src/minitest_cli/commands/draft_create.py +66 -0
  15. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature.py +9 -6
  16. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_apply.py +1 -1
  17. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_conflicts.py +1 -1
  18. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_helpers.py +1 -3
  19. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_show.py +1 -1
  20. minitest_cli-0.37.1/src/minitest_cli/commands/draft_helpers.py +131 -0
  21. minitest_cli-0.37.1/src/minitest_cli/commands/draft_legacy.py +40 -0
  22. minitest_cli-0.37.1/src/minitest_cli/commands/draft_lifecycle.py +103 -0
  23. minitest_cli-0.37.1/src/minitest_cli/commands/draft_pr.py +49 -0
  24. minitest_cli-0.37.1/src/minitest_cli/commands/draft_render.py +110 -0
  25. minitest_cli-0.37.1/src/minitest_cli/commands/draft_update.py +45 -0
  26. minitest_cli-0.37.1/src/minitest_cli/commands/draft_watch.py +90 -0
  27. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/env_helpers.py +4 -0
  28. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run.py +26 -47
  29. minitest_cli-0.37.1/src/minitest_cli/commands/run_list.py +55 -0
  30. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_targets.py +61 -11
  31. minitest_cli-0.37.1/src/minitest_cli/core/app_context.py +68 -0
  32. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/core/config.py +5 -0
  33. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/main.py +2 -1
  34. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/targets.py +9 -1
  35. minitest_cli-0.37.1/tests/conftest.py +26 -0
  36. minitest_cli-0.37.1/tests/draft_backend.py +98 -0
  37. minitest_cli-0.37.1/tests/test_app_context.py +84 -0
  38. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_build_commands.py +1 -1
  39. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_deprecated_commands.py +18 -3
  40. minitest_cli-0.37.1/tests/test_draft_ask_commands.py +120 -0
  41. minitest_cli-0.37.1/tests/test_draft_commands.py +167 -0
  42. minitest_cli-0.37.1/tests/test_draft_conversation_commands.py +133 -0
  43. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_draft_feature_commands.py +1 -1
  44. minitest_cli-0.37.1/tests/test_draft_pr_commands.py +135 -0
  45. minitest_cli-0.37.1/tests/test_physical_devices.py +227 -0
  46. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_run_commands.py +7 -7
  47. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/uv.lock +1 -1
  48. minitest_cli-0.36.0/src/minitest_cli/core/app_context.py +0 -31
  49. minitest_cli-0.36.0/tests/conftest.py +0 -11
  50. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/.github/workflows/ci.yml +0 -0
  51. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/.github/workflows/install-scripts.yml +0 -0
  52. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/.github/workflows/release.yml +0 -0
  53. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/.gitignore +0 -0
  54. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/.opencode/skill/release/SKILL.md +0 -0
  55. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/AGENTS.md +0 -0
  56. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/RELEASE.md +0 -0
  57. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/install.ps1 +0 -0
  58. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/install.sh +0 -0
  59. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/pyrightconfig.json +0 -0
  60. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/renovate.json +0 -0
  61. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/__init__.py +0 -0
  62. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/api/__init__.py +0 -0
  63. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/api/apps_manager_client.py +0 -0
  64. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/api/errors.py +0 -0
  65. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/assets/__init__.py +0 -0
  66. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/assets/callback.html +0 -0
  67. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/__init__.py +0 -0
  68. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_knowledge.py +0 -0
  69. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
  70. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_network_helpers.py +0 -0
  71. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill.py +0 -0
  72. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_helpers.py +0 -0
  73. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_secret.py +0 -0
  74. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_write.py +0 -0
  75. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_dependencies.py +0 -0
  76. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_get.py +0 -0
  77. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_helpers.py +0 -0
  78. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_table.py +0 -0
  79. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/auth.py +0 -0
  80. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/auth_api_key.py +0 -0
  81. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch.py +0 -0
  82. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch_warnings.py +0 -0
  83. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_commit.py +0 -0
  84. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_fix_prompts.py +0 -0
  85. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_get.py +0 -0
  86. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/capabilities.py +0 -0
  87. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/commit_helpers.py +0 -0
  88. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/deprecated_alias.py +0 -0
  89. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/env.py +0 -0
  90. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types.py +0 -0
  91. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types_helpers.py +0 -0
  92. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types_write.py +0 -0
  93. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init.py +0 -0
  94. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init_helpers.py +0 -0
  95. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init_playbook.py +0 -0
  96. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues.py +0 -0
  97. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_fix.py +0 -0
  98. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_get.py +0 -0
  99. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_helpers.py +0 -0
  100. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_projection.py +0 -0
  101. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_service.py +0 -0
  102. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/private_network.py +0 -0
  103. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/private_network_helpers.py +0 -0
  104. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/recording_timeline.py +0 -0
  105. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/residential_proxy.py +0 -0
  106. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_cancel.py +0 -0
  107. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_commit.py +0 -0
  108. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_commit_helpers.py +0 -0
  109. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_display.py +0 -0
  110. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_feedback.py +0 -0
  111. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_helpers.py +0 -0
  112. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_recording.py +0 -0
  113. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_recording_helpers.py +0 -0
  114. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_tagged.py +0 -0
  115. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens.py +0 -0
  116. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_detail.py +0 -0
  117. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_format.py +0 -0
  118. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_helpers.py +0 -0
  119. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_tree.py +0 -0
  120. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/skill.py +0 -0
  121. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/tags.py +0 -0
  122. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/tags_helpers.py +0 -0
  123. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file.py +0 -0
  124. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file_helpers.py +0 -0
  125. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file_list.py +0 -0
  126. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile.py +0 -0
  127. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_card.py +0 -0
  128. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_default.py +0 -0
  129. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_helpers.py +0 -0
  130. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_list.py +0 -0
  131. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_update.py +0 -0
  132. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/upgrade.py +0 -0
  133. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story.py +0 -0
  134. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_app_skills.py +0 -0
  135. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_bindings.py +0 -0
  136. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_camera.py +0 -0
  137. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_create.py +0 -0
  138. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_criteria.py +0 -0
  139. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_criterion_edits.py +0 -0
  140. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_delete.py +0 -0
  141. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_device_count.py +0 -0
  142. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_helpers.py +0 -0
  143. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_modify.py +0 -0
  144. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_overrides.py +0 -0
  145. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_profiles.py +0 -0
  146. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_setup_commit.py +0 -0
  147. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_update.py +0 -0
  148. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts.py +0 -0
  149. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts_helpers.py +0 -0
  150. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts_projection.py +0 -0
  151. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/core/__init__.py +0 -0
  152. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/core/auth.py +0 -0
  153. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/core/credentials.py +0 -0
  154. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/core/oauth.py +0 -0
  155. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/core/tenants.py +0 -0
  156. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/core/token_exchange.py +0 -0
  157. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/__init__.py +0 -0
  158. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app.py +0 -0
  159. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_env_vars.py +0 -0
  160. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_failure.py +0 -0
  161. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_screen.py +0 -0
  162. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/base.py +0 -0
  163. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/batch.py +0 -0
  164. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/build.py +0 -0
  165. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/commit_build.py +0 -0
  166. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/draft_feature.py +0 -0
  167. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/flow_type.py +0 -0
  168. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/issues.py +0 -0
  169. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/recording.py +0 -0
  170. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/story_run.py +0 -0
  171. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/tag.py +0 -0
  172. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/models/user_story.py +0 -0
  173. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/__init__.py +0 -0
  174. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/confirm.py +0 -0
  175. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/dependency_sim.py +0 -0
  176. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/mermaid.py +0 -0
  177. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/output.py +0 -0
  178. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/skill_refresh.py +0 -0
  179. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/update_check.py +0 -0
  180. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/video_frames.py +0 -0
  181. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/__init__.py +0 -0
  182. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/_commit_transport.py +0 -0
  183. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/_network_settings_server.py +0 -0
  184. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_api_client_auth.py +0 -0
  185. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_app_knowledge_commands.py +0 -0
  186. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_app_skill_commands.py +0 -0
  187. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_apps_commands.py +0 -0
  188. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_apps_dependencies.py +0 -0
  189. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_auth.py +0 -0
  190. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_auth_api_key.py +0 -0
  191. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_auth_commands.py +0 -0
  192. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_batch_commands.py +0 -0
  193. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_build_fix_prompts.py +0 -0
  194. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_build_get_command.py +0 -0
  195. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_capabilities_command.py +0 -0
  196. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_channel_header.py +0 -0
  197. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_code_quality.py +0 -0
  198. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_commit_commands.py +0 -0
  199. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_deprecated_flags.py +0 -0
  200. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_env.py +0 -0
  201. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_flow_types_commands.py +0 -0
  202. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_init_command.py +0 -0
  203. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_issues.py +0 -0
  204. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_issues_commands.py +0 -0
  205. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_issues_get_command.py +0 -0
  206. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_mermaid.py +0 -0
  207. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_native_device_types.py +0 -0
  208. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_network_errors.py +0 -0
  209. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_private_network_commands.py +0 -0
  210. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_recording_timeline.py +0 -0
  211. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_residential_proxy_commands.py +0 -0
  212. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_response_errors.py +0 -0
  213. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_run_cancel.py +0 -0
  214. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_run_frames_command.py +0 -0
  215. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_screens_commands.py +0 -0
  216. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_skill_command.py +0 -0
  217. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_suite_change_provenance.py +0 -0
  218. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_tags_commands.py +0 -0
  219. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_test_file_commands.py +0 -0
  220. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_test_profile_commands.py +0 -0
  221. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_upgrade_command.py +0 -0
  222. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_user_story_bindings_commands.py +0 -0
  223. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_user_story_commands.py +0 -0
  224. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_user_story_setup_commit.py +0 -0
  225. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_verdicts.py +0 -0
  226. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_verdicts_actionable.py +0 -0
  227. {minitest_cli-0.36.0 → minitest_cli-0.37.1}/tests/test_version.py +0 -0
@@ -16,6 +16,12 @@ MINITEST_API_URL=https://testing-service.dev.minitap.ai
16
16
  # Development
17
17
  MINITEST_APPS_MANAGER_URL=https://apps-manager.dev.minitap.ai
18
18
 
19
+ # —— Conductor (minitest draft) ——
20
+ # Production
21
+ # MINITEST_CONDUCTOR_URL=https://conductor.app.minitap.ai
22
+ # Development
23
+ MINITEST_CONDUCTOR_URL=https://conductor.dev.minitap.ai
24
+
19
25
  # —— Integrations ——
20
26
  # Production
21
27
  # MINITEST_INTEGRATIONS_URL=https://integrations.minitap.ai
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: minitest-cli
3
- Version: 0.36.0
3
+ Version: 0.37.1
4
4
  Summary: Minitest CLI – command-line interface for the Minitest testing platform
5
5
  Project-URL: Homepage, https://minitap.ai/
6
6
  Project-URL: Source, https://github.com/minitap-ai/minitest-cli
@@ -131,6 +131,29 @@ targets, or Edge. V1 supports one form factor per OS per run; run phone and
131
131
  tablet suites separately. Tablet results are labelled `iOS · Tablet` or
132
132
  `Android · Tablet`; phone labels stay unchanged.
133
133
 
134
+ ## Physical devices
135
+
136
+ By default builds run on virtual devices (iOS Simulator, Android emulator). If
137
+ physical devices are enabled for your workspace, upload a device build with
138
+ `--physical` and select the physical lane when you start the run:
139
+
140
+ ```bash
141
+ # iOS: an Ad Hoc or Development .ipa signed for Minitap's iPhones
142
+ BUILD_ID=$(minitest --json --app <app-id> build upload ./MyApp.ipa --physical | jq -r .id)
143
+ minitest --app <app-id> run all --ios-build "$BUILD_ID" --ios-physical
144
+
145
+ # Android: the same works with --android-physical
146
+ minitest --app <app-id> run start --tag smoke --android-build <android-build-id> --android-physical
147
+ ```
148
+
149
+ - `build upload --physical` checks the build for physical devices instead of
150
+ virtual ones. A Simulator build is rejected (`ios.simulator_only_ipa`), and a
151
+ device build uploaded without `--physical` is rejected with a hint to add it.
152
+ - `--ios-physical` / `--android-physical` need the matching `--ios-build` /
153
+ `--android-build`, and can't be combined with `--*-device-type`.
154
+ - If physical devices aren't enabled for your workspace, the run is refused
155
+ with a message saying so.
156
+
134
157
  An iPhone-only build may run on iPad in compatibility mode; the server warns
135
158
  that the run does not establish native tablet-layout coverage. Unknown
136
159
  device-family metadata produces a different warning with the same coverage
@@ -145,6 +168,7 @@ app defaults.
145
168
  | `MINITEST_APP_ID` | Default app ID | No (can use `--app` flag) |
146
169
  | `MINITEST_API_URL` | testing-service base URL | No (defaults to production) |
147
170
  | `MINITEST_APPS_MANAGER_URL` | apps-manager base URL (used by `minitest apps create`) | No (defaults to production) |
171
+ | `MINITEST_CONDUCTOR_URL` | Conductor base URL (used by `minitest draft`) | No (defaults to production) |
148
172
  | `MINITEST_INTEGRATIONS_URL` | minihands-integrations base URL (used to list tenants) | No (defaults to production) |
149
173
  | `MINITEST_WEBAPP_URL` | Minitest webapp base URL (used for review links) | No (defaults to production) |
150
174
  | `MINITEST_SUPABASE_URL` | Supabase project URL used for OAuth login | No (defaults to production) |
@@ -169,7 +193,8 @@ app defaults.
169
193
  | `minitest apps` | List, inspect, and create apps; residential proxy and private network settings |
170
194
  | `minitest scenario` | Create, update, list and delete scenarios |
171
195
  | `minitest scenario-binding` | Bind test accounts, test files or app skills to scenarios |
172
- | `minitest draft` | Drafts — branches of the test suite |
196
+ | `minitest draft` | Ask Mini for a suite change, follow it, check and merge it (drafts in the webapp) |
197
+ | `minitest df` | Low-level suite branches Mini writes (draft features) |
173
198
  | `minitest test-profile` | Test profiles (Test accounts in the webapp) |
174
199
  | `minitest tags` | List, create, update and delete scenario tags |
175
200
  | `minitest app-skill` | App skills (Skills in the webapp): procedures Mini loads to arrange test state |
@@ -190,7 +215,7 @@ stderr and leave `--json` stdout unchanged. Switch to the new names.
190
215
  | `minitest user-story` | `minitest scenario` |
191
216
  | `minitest user-story-binding` | `minitest scenario-binding` |
192
217
  | `minitest scenario-binding set-skills` | `minitest scenario-binding set-app-skills` |
193
- | `minitest df` | `minitest draft` |
218
+ | `minitest draft show/apply/delete` (v0.34–v0.36) | `minitest df show/apply/delete` |
194
219
  | `minitest flow-types` | `minitest tags` |
195
220
  | `run from-commit --user-story` / `-u` | `run from-commit --scenario` / `-s` |
196
221
  | `batch list --user-story-id` | `batch list --scenario` |
@@ -98,6 +98,29 @@ targets, or Edge. V1 supports one form factor per OS per run; run phone and
98
98
  tablet suites separately. Tablet results are labelled `iOS · Tablet` or
99
99
  `Android · Tablet`; phone labels stay unchanged.
100
100
 
101
+ ## Physical devices
102
+
103
+ By default builds run on virtual devices (iOS Simulator, Android emulator). If
104
+ physical devices are enabled for your workspace, upload a device build with
105
+ `--physical` and select the physical lane when you start the run:
106
+
107
+ ```bash
108
+ # iOS: an Ad Hoc or Development .ipa signed for Minitap's iPhones
109
+ BUILD_ID=$(minitest --json --app <app-id> build upload ./MyApp.ipa --physical | jq -r .id)
110
+ minitest --app <app-id> run all --ios-build "$BUILD_ID" --ios-physical
111
+
112
+ # Android: the same works with --android-physical
113
+ minitest --app <app-id> run start --tag smoke --android-build <android-build-id> --android-physical
114
+ ```
115
+
116
+ - `build upload --physical` checks the build for physical devices instead of
117
+ virtual ones. A Simulator build is rejected (`ios.simulator_only_ipa`), and a
118
+ device build uploaded without `--physical` is rejected with a hint to add it.
119
+ - `--ios-physical` / `--android-physical` need the matching `--ios-build` /
120
+ `--android-build`, and can't be combined with `--*-device-type`.
121
+ - If physical devices aren't enabled for your workspace, the run is refused
122
+ with a message saying so.
123
+
101
124
  An iPhone-only build may run on iPad in compatibility mode; the server warns
102
125
  that the run does not establish native tablet-layout coverage. Unknown
103
126
  device-family metadata produces a different warning with the same coverage
@@ -112,6 +135,7 @@ app defaults.
112
135
  | `MINITEST_APP_ID` | Default app ID | No (can use `--app` flag) |
113
136
  | `MINITEST_API_URL` | testing-service base URL | No (defaults to production) |
114
137
  | `MINITEST_APPS_MANAGER_URL` | apps-manager base URL (used by `minitest apps create`) | No (defaults to production) |
138
+ | `MINITEST_CONDUCTOR_URL` | Conductor base URL (used by `minitest draft`) | No (defaults to production) |
115
139
  | `MINITEST_INTEGRATIONS_URL` | minihands-integrations base URL (used to list tenants) | No (defaults to production) |
116
140
  | `MINITEST_WEBAPP_URL` | Minitest webapp base URL (used for review links) | No (defaults to production) |
117
141
  | `MINITEST_SUPABASE_URL` | Supabase project URL used for OAuth login | No (defaults to production) |
@@ -136,7 +160,8 @@ app defaults.
136
160
  | `minitest apps` | List, inspect, and create apps; residential proxy and private network settings |
137
161
  | `minitest scenario` | Create, update, list and delete scenarios |
138
162
  | `minitest scenario-binding` | Bind test accounts, test files or app skills to scenarios |
139
- | `minitest draft` | Drafts — branches of the test suite |
163
+ | `minitest draft` | Ask Mini for a suite change, follow it, check and merge it (drafts in the webapp) |
164
+ | `minitest df` | Low-level suite branches Mini writes (draft features) |
140
165
  | `minitest test-profile` | Test profiles (Test accounts in the webapp) |
141
166
  | `minitest tags` | List, create, update and delete scenario tags |
142
167
  | `minitest app-skill` | App skills (Skills in the webapp): procedures Mini loads to arrange test state |
@@ -157,7 +182,7 @@ stderr and leave `--json` stdout unchanged. Switch to the new names.
157
182
  | `minitest user-story` | `minitest scenario` |
158
183
  | `minitest user-story-binding` | `minitest scenario-binding` |
159
184
  | `minitest scenario-binding set-skills` | `minitest scenario-binding set-app-skills` |
160
- | `minitest df` | `minitest draft` |
185
+ | `minitest draft show/apply/delete` (v0.34–v0.36) | `minitest df show/apply/delete` |
161
186
  | `minitest flow-types` | `minitest tags` |
162
187
  | `run from-commit --user-story` / `-u` | `run from-commit --scenario` / `-s` |
163
188
  | `batch list --user-story-id` | `batch list --scenario` |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "minitest-cli"
3
- version = "0.36.0"
3
+ version = "0.37.1"
4
4
  description = "Minitest CLI – command-line interface for the Minitest testing platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -128,6 +128,9 @@ python_functions = [
128
128
  python_classes = [
129
129
  "Test*"
130
130
  ]
131
+ markers = [
132
+ "real_app_lookup: resolve --app names through the API instead of the test shortcut",
133
+ ]
131
134
  addopts = [
132
135
  "-v",
133
136
  "--tb=short",
@@ -39,9 +39,16 @@ class ApiClient:
39
39
  response = await client.get("/api/v1/apps")
40
40
  """
41
41
 
42
- def __init__(self, settings: Settings, *, token_override: str | None = None) -> None:
42
+ def __init__(
43
+ self,
44
+ settings: Settings,
45
+ *,
46
+ token_override: str | None = None,
47
+ base_url: str | None = None,
48
+ ) -> None:
43
49
  self._settings = settings
44
50
  self._token_override = token_override
51
+ self._base_url = base_url or settings.api_url
45
52
  self._client: httpx.AsyncClient | None = None
46
53
 
47
54
  async def __aenter__(self) -> "ApiClient":
@@ -53,7 +60,7 @@ class ApiClient:
53
60
  if self._settings.cause is not None:
54
61
  headers[CAUSE_HEADER] = self._settings.cause
55
62
  self._client = httpx.AsyncClient(
56
- base_url=self._settings.api_url,
63
+ base_url=self._base_url,
57
64
  headers=headers,
58
65
  auth=_BearerAuth(self._settings, self._token_override),
59
66
  timeout=DEFAULT_TIMEOUT,
@@ -24,6 +24,11 @@ BUILD_INVALID_ERROR_CODE = "build_invalid"
24
24
  # rollout: both).
25
25
  _SPLIT_APKS_BACKEND_UNSUPPORTED_MARKER = "not yet supported on this execution backend"
26
26
 
27
+ # Rejection codes that mean "right build, wrong kind of device": the hint
28
+ # points at the upload flag that matches the build.
29
+ _DEVICE_BUILD_CODES = {"ios.device_only_ipa", "ios.device_only_sdk", "ios.no_simulator_slice"}
30
+ _SIMULATOR_BUILD_CODES = {"ios.simulator_only_ipa"}
31
+
27
32
  # Generic FastAPI ``loc`` prefixes that add noise to a field path (they're
28
33
  # implied by "this is a validation error" and repeat across every field).
29
34
  _LOC_PREFIXES_TO_DROP = {"body", "query", "path"}
@@ -86,14 +91,16 @@ def extract_detail(resp: httpx.Response) -> str:
86
91
  return resp.text
87
92
 
88
93
 
89
- def handle_response_error(resp: httpx.Response, *, resource: str = "Build") -> None:
94
+ def handle_response_error(
95
+ resp: httpx.Response, *, resource: str = "Build", physical: bool = False
96
+ ) -> None:
90
97
  """Check response status; exit with proper code on errors."""
91
98
  if resp.status_code == 404:
92
99
  print_error(f"{resource} not found: {extract_detail(resp)}")
93
100
  raise typer.Exit(code=EXIT_NOT_FOUND)
94
101
  if resp.status_code == 422 and _try_handle_split_apks_backend_unsupported(resp):
95
102
  raise typer.Exit(code=EXIT_BUILD_INVALID)
96
- if resp.status_code == 422 and _try_handle_build_invalid(resp):
103
+ if resp.status_code == 422 and _try_handle_build_invalid(resp, physical=physical):
97
104
  raise typer.Exit(code=EXIT_BUILD_INVALID)
98
105
  if resp.status_code >= 400:
99
106
  print_error(f"API error ({resp.status_code}): {extract_detail(resp)}")
@@ -122,7 +129,7 @@ def _try_handle_split_apks_backend_unsupported(resp: httpx.Response) -> bool:
122
129
  return True
123
130
 
124
131
 
125
- def _try_handle_build_invalid(resp: httpx.Response) -> bool:
132
+ def _try_handle_build_invalid(resp: httpx.Response, *, physical: bool = False) -> bool:
126
133
  """If response is a build-invalid error, render issues and return True."""
127
134
  try:
128
135
  body = resp.json()
@@ -130,17 +137,32 @@ def _try_handle_build_invalid(resp: httpx.Response) -> bool:
130
137
  return False
131
138
  if not isinstance(body, dict) or body.get("error_code") != BUILD_INVALID_ERROR_CODE:
132
139
  return False
133
- issues = body.get("issues") or []
134
- print_error("Build rejected: failed validation for virtual-device execution.")
140
+ issues = [issue for issue in body.get("issues") or [] if isinstance(issue, dict)]
141
+ devices = "physical-device" if physical else "virtual-device"
142
+ print_error(f"Build rejected: failed validation for {devices} execution.")
135
143
  for issue in issues:
136
- if not isinstance(issue, dict):
137
- continue
138
144
  code = issue.get("code", "unknown")
139
145
  message = issue.get("message", "")
140
146
  err_console.print(f" [red]✖[/red] {code}: {message}")
147
+ _print_device_kind_hint({str(issue.get("code")) for issue in issues}, physical=physical)
141
148
  return True
142
149
 
143
150
 
151
+ def _print_device_kind_hint(codes: set[str], *, physical: bool) -> None:
152
+ if not physical and codes & _DEVICE_BUILD_CODES:
153
+ err_console.print(
154
+ " [yellow]Hint:[/yellow] this is a device build. To run it on physical iPhones,"
155
+ " upload it with [bold]minitest build upload <file> --physical[/bold];"
156
+ " for virtual devices, build for the iOS Simulator instead."
157
+ )
158
+ elif physical and codes & _SIMULATOR_BUILD_CODES:
159
+ err_console.print(
160
+ " [yellow]Hint:[/yellow] this is a Simulator build. Upload it without"
161
+ " [bold]--physical[/bold] to run it on virtual devices, or export a device"
162
+ " build (Ad Hoc or Development) for physical iPhones."
163
+ )
164
+
165
+
144
166
  def print_validation_warnings(warnings: list[dict] | None) -> None:
145
167
  """Print non-fatal validation warnings to stderr."""
146
168
  if not warnings:
@@ -30,6 +30,7 @@ from minitest_cli.utils.output import (
30
30
  print_table,
31
31
  )
32
32
 
33
+ EXIT_GENERAL_ERROR = 1
33
34
  EXIT_NETWORK_ERROR = 3
34
35
 
35
36
  app = typer.Typer(name="apps", help="App management.")
@@ -70,6 +71,9 @@ def list_apps() -> None:
70
71
  detail = body.get("detail") or body.get("message") or detail
71
72
  except Exception: # noqa: BLE001
72
73
  pass
74
+ if resp.status_code in (401, 403):
75
+ print_error(f"Authentication failed ({resp.status_code}): {detail}")
76
+ raise typer.Exit(code=EXIT_GENERAL_ERROR)
73
77
  print_error(f"API error ({resp.status_code}): {detail}")
74
78
  raise typer.Exit(code=EXIT_NETWORK_ERROR)
75
79
  return AppListResponse.model_validate(resp.json())
@@ -2,11 +2,16 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
+ import httpx
6
+ import typer
7
+
5
8
  from minitest_cli.api.client import BATCH_CREATE_TIMEOUT, ApiClient
6
9
  from minitest_cli.commands.run_display import _derive_run_status
10
+ from minitest_cli.commands._response_errors import EXIT_NETWORK_ERROR, extract_detail
7
11
  from minitest_cli.commands.run_helpers import handle_response_error
8
12
  from minitest_cli.commands.batch_warnings import print_compatibility_warnings
9
13
  from minitest_cli.models.batch import BatchResponse, CreateBatchRequest
14
+ from minitest_cli.utils.output import err_console, print_error
10
15
 
11
16
 
12
17
  def batches_base_path(app_id: str) -> str:
@@ -21,12 +26,29 @@ async def post_batch(client: ApiClient, app_id: str, body: CreateBatchRequest) -
21
26
  json=body.model_dump(by_alias=True, exclude_none=True),
22
27
  timeout=BATCH_CREATE_TIMEOUT,
23
28
  )
29
+ if resp.status_code == 403 and _requests_physical(body):
30
+ _exit_physical_forbidden(resp)
24
31
  handle_response_error(resp, resource="Batch")
25
32
  batch = BatchResponse.model_validate(resp.json())
26
33
  print_compatibility_warnings(batch)
27
34
  return batch
28
35
 
29
36
 
37
+ def _requests_physical(body: CreateBatchRequest) -> bool:
38
+ return any(target.backend == "physical" for target in body.targets or [])
39
+
40
+
41
+ def _exit_physical_forbidden(resp: httpx.Response) -> None:
42
+ """A physical lane the workspace is not entitled to: say so, and how to proceed."""
43
+ print_error(extract_detail(resp))
44
+ err_console.print(
45
+ " [yellow]Fix:[/yellow] ask Minitap to enable physical devices for this workspace,"
46
+ " or drop [bold]--ios-physical[/bold] / [bold]--android-physical[/bold]"
47
+ " to run on virtual devices."
48
+ )
49
+ raise typer.Exit(code=EXIT_NETWORK_ERROR)
50
+
51
+
30
52
  def batch_summary_payload(batch: BatchResponse) -> dict[str, Any]:
31
53
  """Serialise a batch to a compact JSON payload for --json output (camelCase)."""
32
54
  return {
@@ -42,8 +42,8 @@ def upload(
42
42
  Path,
43
43
  typer.Argument(
44
44
  help=(
45
- "Path to the build file (.apk, .apks, or .ipa). Must be a Simulator"
46
- " build for iOS or an emulator-compatible Android build."
45
+ "Path to the build file (.apk, .apks, .ipa or .app.zip). A Simulator build"
46
+ " for iOS or an emulator-compatible Android build, unless --physical."
47
47
  ),
48
48
  exists=True,
49
49
  readable=True,
@@ -53,17 +53,34 @@ def upload(
53
53
  Platform | None,
54
54
  typer.Option(help="Target platform. Auto-detected from file extension."),
55
55
  ] = None,
56
+ physical: Annotated[
57
+ bool,
58
+ typer.Option(
59
+ "--physical",
60
+ help=(
61
+ "Upload a build for physical devices: a signed iOS device .ipa (Ad Hoc or"
62
+ " Development) or an Android .apk for real phones."
63
+ ),
64
+ ),
65
+ ] = False,
56
66
  ) -> None:
57
- """Upload a build file (.apk or .apks for Android, .ipa for iOS).
67
+ """Upload a build file (.apk or .apks for Android, .ipa or .app.zip for iOS).
58
68
 
59
- Builds must be compatible with virtual devices:
69
+ By default builds are checked for virtual devices:
60
70
 
61
71
  \b
62
- - iOS: provide a Simulator build (.ipa built for the iOS Simulator, not a physical device).
72
+ - iOS: provide a Simulator build (.ipa or .app.zip built for the iOS Simulator).
63
73
  - Android: provide an emulator-compatible .apk or .apks archive.
74
+
75
+ With --physical, builds are checked for physical devices instead: an iOS
76
+ device .ipa signed for Minitap's iPhones, or an Android .apk. Start runs on
77
+ them with --ios-physical / --android-physical.
64
78
  """
65
79
  settings, app_id, json_mode = resolve_app()
66
80
  resolved_platform = platform.value if platform else detect_platform(file)
81
+ form = {"platform": resolved_platform}
82
+ if physical:
83
+ form["backend"] = "physical"
67
84
 
68
85
  async def _run() -> BuildResponse:
69
86
  async with ApiClient(settings) as client:
@@ -74,9 +91,9 @@ def upload(
74
91
  resp = await client.upload_file(
75
92
  f"{base_path(app_id)}/build",
76
93
  files={"file": (file.name, f, "application/octet-stream")},
77
- data={"platform": resolved_platform},
94
+ data=form,
78
95
  )
79
- handle_response_error(resp)
96
+ handle_response_error(resp, physical=physical)
80
97
  return BuildResponse.model_validate(resp.json())
81
98
 
82
99
  result = run_api_call(_run())
@@ -86,7 +103,8 @@ def upload(
86
103
  output(result_dict, json_mode=True)
87
104
  else:
88
105
  print_validation_warnings(result.validation_warnings)
89
- print_success(f"Build uploaded: {file.name} ({resolved_platform})")
106
+ devices = ", physical devices" if physical else ""
107
+ print_success(f"Build uploaded: {file.name} ({resolved_platform}{devices})")
90
108
  output(result_dict, json_mode=False)
91
109
 
92
110
 
@@ -76,6 +76,7 @@ PLATFORM_EXTENSIONS: dict[str, Platform] = {
76
76
  ".ipa": Platform.ios,
77
77
  ".apk": Platform.android,
78
78
  ".apks": Platform.android,
79
+ ".app.zip": Platform.ios,
79
80
  }
80
81
 
81
82
 
@@ -97,7 +98,8 @@ class BuildKindFilter(StrEnum):
97
98
 
98
99
  def detect_platform(file_path: Path) -> str:
99
100
  """Auto-detect platform from file extension."""
100
- suffix = file_path.suffix.lower()
101
+ name = file_path.name.lower()
102
+ suffix = ".app.zip" if name.endswith(".app.zip") else file_path.suffix.lower()
101
103
  platform = PLATFORM_EXTENSIONS.get(suffix)
102
104
  if platform is None:
103
105
  print_error(
@@ -0,0 +1,111 @@
1
+ """`minitest draft`: ask Mini for a suite change and drive it to merge.
2
+
3
+ A draft is the workbench the webapp shows under Drafts: a branch of the suite plus
4
+ Mini's conversation about it. Mini is the only author of the branch; never `df apply`
5
+ into a draft's branch yourself.
6
+ """
7
+
8
+ from typing import Annotated
9
+
10
+ import typer
11
+
12
+ from minitest_cli.commands import (
13
+ draft_asks,
14
+ draft_conversation,
15
+ draft_create,
16
+ draft_legacy,
17
+ draft_lifecycle,
18
+ draft_pr,
19
+ draft_update,
20
+ )
21
+ from minitest_cli.commands.draft_helpers import (
22
+ DRAFTS_PATH,
23
+ call,
24
+ draft_path,
25
+ resolve_target,
26
+ run_conductor,
27
+ )
28
+ from minitest_cli.commands.draft_render import (
29
+ DRAFT_TABLE_HEADERS,
30
+ draft_row,
31
+ show_draft,
32
+ )
33
+ from minitest_cli.commands.draft_watch import WatchUntil, watch
34
+ from minitest_cli.utils.output import (
35
+ print_info,
36
+ print_json,
37
+ print_table,
38
+ )
39
+
40
+ app = typer.Typer(
41
+ name="draft",
42
+ help="Ask Mini for a suite change, follow the conversation, and check/merge the result.",
43
+ no_args_is_help=True,
44
+ )
45
+ EXIT_WATCH_TIMEOUT = 1
46
+
47
+
48
+ @app.command(name="list")
49
+ def list_drafts(
50
+ history: Annotated[
51
+ bool, typer.Option("--history", help="List merged and discarded drafts instead.")
52
+ ] = False,
53
+ ) -> None:
54
+ """List the active app's drafts (open ones by default)."""
55
+ target = resolve_target()
56
+ params = {"appId": target.app_id, "scope": "history" if history else "open"}
57
+ body = call(target, "GET", DRAFTS_PATH, retry=True, params=params)
58
+ drafts = body.get("drafts", [])
59
+ if target.json_mode:
60
+ print_json(drafts)
61
+ return
62
+ if not drafts:
63
+ print_info("No drafts found.")
64
+ return
65
+ print_table(
66
+ DRAFT_TABLE_HEADERS, [draft_row(d) for d in drafts], title=f"Drafts ({len(drafts)})"
67
+ )
68
+
69
+
70
+ @app.command(name="get")
71
+ def get_draft(
72
+ draft_id: Annotated[str, typer.Argument(help="Draft ID.")],
73
+ watch_flag: Annotated[
74
+ bool, typer.Option("--watch", help="Poll until the draft is stable, then print it.")
75
+ ] = False,
76
+ until: Annotated[
77
+ WatchUntil,
78
+ typer.Option(
79
+ "--until",
80
+ help="idle: Mini has nothing queued. settled: also no check or draft build running.",
81
+ ),
82
+ ] = WatchUntil.IDLE,
83
+ interval: Annotated[
84
+ float, typer.Option("--interval", min=1.0, help="Seconds between polls.")
85
+ ] = 5.0,
86
+ timeout: Annotated[
87
+ float, typer.Option("--timeout", min=1.0, help="Give up after this many seconds (exit 1).")
88
+ ] = 900.0,
89
+ ) -> None:
90
+ """Show a draft: state, Mini's status, scenarios and open asks."""
91
+ target = resolve_target()
92
+ path = draft_path(draft_id)
93
+ if not watch_flag:
94
+ show_draft(call(target, "GET", path, retry=True), json_mode=target.json_mode)
95
+ return
96
+ draft, stable = run_conductor(
97
+ watch(target, path, until=until, interval=interval, timeout=timeout)
98
+ )
99
+ show_draft(draft, json_mode=target.json_mode)
100
+ if not stable:
101
+ print_info(f"Still not {until.value} after {timeout:.0f}s; run the same command again.")
102
+ raise typer.Exit(code=EXIT_WATCH_TIMEOUT)
103
+
104
+
105
+ draft_create.register(app)
106
+ draft_update.register(app)
107
+ draft_conversation.register(app)
108
+ draft_lifecycle.register(app)
109
+ draft_pr.register(app)
110
+ app.add_typer(draft_asks.app)
111
+ draft_legacy.register(app)
@@ -0,0 +1,148 @@
1
+ """`minitest draft ask list|answer|decline`: answer what Mini asked on a draft.
2
+
3
+ Secret fields (``secret: true`` in an ask's answerFields) are only read from a file or
4
+ stdin (``--field-file KEY=PATH``, ``-`` for stdin) — never from the command line, where
5
+ shells and process lists keep them — and are never printed back.
6
+ """
7
+
8
+ from pathlib import Path
9
+ from typing import Annotated, Any
10
+
11
+ import typer
12
+
13
+ from minitest_cli.commands.draft_helpers import (
14
+ EXIT_GENERAL_ERROR,
15
+ EXIT_NOT_FOUND,
16
+ DraftTarget,
17
+ call,
18
+ draft_path,
19
+ resolve_target,
20
+ )
21
+ from minitest_cli.commands.draft_render import (
22
+ ASK_TABLE_HEADERS,
23
+ ask_fields,
24
+ read_file,
25
+ read_text,
26
+ show_draft,
27
+ )
28
+ from minitest_cli.commands.run_helpers import ensure_uuid
29
+ from minitest_cli.utils.output import (
30
+ print_error,
31
+ print_info,
32
+ print_json,
33
+ print_success,
34
+ print_table,
35
+ )
36
+
37
+ app = typer.Typer(
38
+ name="ask", help="Read and answer Mini's questions on a draft.", no_args_is_help=True
39
+ )
40
+ DraftIdArg = Annotated[str, typer.Argument(help="Draft ID.")]
41
+ AskIdArg = Annotated[str, typer.Argument(help="Ask ID (from `draft ask list`).")]
42
+
43
+
44
+ def _open_asks(target: DraftTarget, draft_id: str) -> list[dict[str, Any]]:
45
+ return call(target, "GET", draft_path(draft_id), retry=True).get("asks") or []
46
+
47
+
48
+ def _find_ask(target: DraftTarget, draft_id: str, ask_id: str) -> dict[str, Any]:
49
+ ensure_uuid(ask_id, kind="ask id")
50
+ for ask in _open_asks(target, draft_id):
51
+ if ask.get("id") == ask_id:
52
+ return ask
53
+ print_error(f"No open ask {ask_id} on draft {draft_id}; see `minitest draft ask list`.")
54
+ raise typer.Exit(code=EXIT_NOT_FOUND)
55
+
56
+
57
+ def _pair(raw: str, flag: str) -> tuple[str, str]:
58
+ key, sep, value = raw.partition("=")
59
+ if not sep or not key:
60
+ print_error(f"{flag} expects KEY=VALUE.")
61
+ raise typer.Exit(code=EXIT_GENERAL_ERROR)
62
+ return key, value
63
+
64
+
65
+ def _fields(ask: dict[str, Any], inline: list[str], files: list[str]) -> dict[str, str]:
66
+ known = {f["key"]: f for f in ask.get("answerFields") or []}
67
+ values: dict[str, str] = {}
68
+ for raw in inline:
69
+ key, value = _pair(raw, "--field")
70
+ if known.get(key, {}).get("secret"):
71
+ print_error(f"'{key}' is secret: pass it with --field-file {key}=PATH (- = stdin).")
72
+ raise typer.Exit(code=EXIT_GENERAL_ERROR)
73
+ values[key] = value
74
+ if sum(1 for raw in files if _pair(raw, "--field-file")[1] == "-") > 1:
75
+ print_error("Only one --field-file can read stdin.")
76
+ raise typer.Exit(code=EXIT_GENERAL_ERROR)
77
+ for raw in files:
78
+ key, path = _pair(raw, "--field-file")
79
+ values[key] = read_file(Path(path)).removesuffix("\n")
80
+ unknown = sorted(set(values) - set(known))
81
+ if unknown:
82
+ print_error(f"Unknown field(s) {', '.join(unknown)}; this ask takes: {ask_fields(ask)}.")
83
+ raise typer.Exit(code=EXIT_GENERAL_ERROR)
84
+ return values
85
+
86
+
87
+ @app.command(name="list")
88
+ def list_asks(draft_id: DraftIdArg) -> None:
89
+ """List Mini's open questions on a draft, with the fields each answer takes."""
90
+ target = resolve_target()
91
+ asks = _open_asks(target, draft_id)
92
+ if target.json_mode:
93
+ print_json(asks)
94
+ return
95
+ if not asks:
96
+ print_info("Mini is not asking anything.")
97
+ return
98
+ rows = [[a["id"], a.get("askType", "-"), a["question"], ask_fields(a)] for a in asks]
99
+ print_table(ASK_TABLE_HEADERS, rows)
100
+
101
+
102
+ @app.command(name="answer")
103
+ def answer_ask(
104
+ draft_id: DraftIdArg,
105
+ ask_id: AskIdArg,
106
+ answer: Annotated[str | None, typer.Option("--answer", help="Free-text answer.")] = None,
107
+ answer_file: Annotated[
108
+ Path | None, typer.Option("--answer-file", help="Read the answer from a file (- = stdin).")
109
+ ] = None,
110
+ field: Annotated[
111
+ list[str] | None, typer.Option("--field", help="KEY=VALUE for a non-secret field.")
112
+ ] = None,
113
+ field_file: Annotated[
114
+ list[str] | None,
115
+ typer.Option("--field-file", help="KEY=PATH, value read from a file (- = stdin)."),
116
+ ] = None,
117
+ ) -> None:
118
+ """Answer an ask; Mini resumes the draft with it. Turns auto-merge off."""
119
+ text = read_text(answer, answer_file, flag="answer", required=False)
120
+ target = resolve_target()
121
+ ask = _find_ask(target, draft_id, ask_id)
122
+ values = _fields(ask, field or [], field_file or [])
123
+ if text is None and not values:
124
+ print_error("Pass --answer/--answer-file, or the ask's fields with --field/--field-file.")
125
+ raise typer.Exit(code=EXIT_GENERAL_ERROR)
126
+ body: dict[str, object] = {}
127
+ if text is not None:
128
+ body["answer"] = text
129
+ if values:
130
+ body["fields"] = values
131
+ draft = call(target, "POST", draft_path(draft_id, f"/asks/{ask_id}/answer"), json=body)
132
+ print_success("Answered; Mini picks it up next.")
133
+ show_draft(draft, json_mode=target.json_mode)
134
+
135
+
136
+ @app.command(name="decline")
137
+ def decline_ask(
138
+ draft_id: DraftIdArg,
139
+ ask_id: AskIdArg,
140
+ reason: Annotated[str | None, typer.Option("--reason", help="Why, so Mini can adapt.")] = None,
141
+ ) -> None:
142
+ """Decline an ask; Mini carries on without it."""
143
+ target = resolve_target()
144
+ ensure_uuid(ask_id, kind="ask id")
145
+ body = {"reason": reason} if reason else None
146
+ draft = call(target, "POST", draft_path(draft_id, f"/asks/{ask_id}/decline"), json=body)
147
+ print_success("Declined.")
148
+ show_draft(draft, json_mode=target.json_mode)