minitest-cli 0.34.0__tar.gz → 0.35.0__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 (206) hide show
  1. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/PKG-INFO +30 -2
  2. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/README.md +29 -1
  3. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/pyproject.toml +1 -1
  4. minitest_cli-0.35.0/src/minitest_cli/commands/app_network_helpers.py +114 -0
  5. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/apps.py +9 -2
  6. minitest_cli-0.35.0/src/minitest_cli/commands/private_network.py +143 -0
  7. minitest_cli-0.35.0/src/minitest_cli/commands/private_network_helpers.py +93 -0
  8. minitest_cli-0.35.0/src/minitest_cli/commands/residential_proxy.py +121 -0
  9. minitest_cli-0.35.0/tests/_network_settings_server.py +106 -0
  10. minitest_cli-0.35.0/tests/test_private_network_commands.py +118 -0
  11. minitest_cli-0.35.0/tests/test_residential_proxy_commands.py +89 -0
  12. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/uv.lock +1 -1
  13. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/.env.example +0 -0
  14. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/.github/workflows/ci.yml +0 -0
  15. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/.github/workflows/install-scripts.yml +0 -0
  16. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/.github/workflows/release.yml +0 -0
  17. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/.gitignore +0 -0
  18. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/.opencode/skill/release/SKILL.md +0 -0
  19. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/AGENTS.md +0 -0
  20. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/RELEASE.md +0 -0
  21. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/install.ps1 +0 -0
  22. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/install.sh +0 -0
  23. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/pyrightconfig.json +0 -0
  24. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/renovate.json +0 -0
  25. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/__init__.py +0 -0
  26. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/api/__init__.py +0 -0
  27. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/api/apps_manager_client.py +0 -0
  28. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/api/client.py +0 -0
  29. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/api/errors.py +0 -0
  30. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/assets/__init__.py +0 -0
  31. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/assets/callback.html +0 -0
  32. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/__init__.py +0 -0
  33. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/_response_errors.py +0 -0
  34. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/app_knowledge.py +0 -0
  35. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
  36. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/app_skill.py +0 -0
  37. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/app_skill_helpers.py +0 -0
  38. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/app_skill_secret.py +0 -0
  39. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/app_skill_write.py +0 -0
  40. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/apps_dependencies.py +0 -0
  41. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/apps_get.py +0 -0
  42. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/apps_helpers.py +0 -0
  43. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/apps_table.py +0 -0
  44. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/auth.py +0 -0
  45. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/auth_api_key.py +0 -0
  46. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/batch.py +0 -0
  47. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/batch_helpers.py +0 -0
  48. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/batch_warnings.py +0 -0
  49. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/build.py +0 -0
  50. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/build_commit.py +0 -0
  51. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/build_fix_prompts.py +0 -0
  52. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/build_helpers.py +0 -0
  53. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/capabilities.py +0 -0
  54. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/commit_helpers.py +0 -0
  55. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/deprecated_alias.py +0 -0
  56. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/draft_feature.py +0 -0
  57. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/draft_feature_apply.py +0 -0
  58. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/draft_feature_conflicts.py +0 -0
  59. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/draft_feature_helpers.py +0 -0
  60. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/draft_feature_show.py +0 -0
  61. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/env.py +0 -0
  62. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/env_helpers.py +0 -0
  63. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/flow_types.py +0 -0
  64. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/flow_types_helpers.py +0 -0
  65. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/flow_types_write.py +0 -0
  66. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/init.py +0 -0
  67. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/init_helpers.py +0 -0
  68. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/init_playbook.py +0 -0
  69. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/issues.py +0 -0
  70. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/issues_fix.py +0 -0
  71. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/issues_helpers.py +0 -0
  72. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/issues_projection.py +0 -0
  73. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/issues_service.py +0 -0
  74. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/maintenance.py +0 -0
  75. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/maintenance_callbacks.py +0 -0
  76. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/maintenance_helpers.py +0 -0
  77. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/maintenance_state.py +0 -0
  78. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/recording_timeline.py +0 -0
  79. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run.py +0 -0
  80. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_cancel.py +0 -0
  81. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_commit.py +0 -0
  82. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_commit_helpers.py +0 -0
  83. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_display.py +0 -0
  84. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_feedback.py +0 -0
  85. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_helpers.py +0 -0
  86. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_recording.py +0 -0
  87. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_recording_helpers.py +0 -0
  88. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_tagged.py +0 -0
  89. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/run_targets.py +0 -0
  90. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/screens.py +0 -0
  91. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/screens_detail.py +0 -0
  92. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/screens_format.py +0 -0
  93. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/screens_helpers.py +0 -0
  94. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/screens_tree.py +0 -0
  95. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/skill.py +0 -0
  96. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/tags.py +0 -0
  97. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/tags_helpers.py +0 -0
  98. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_file.py +0 -0
  99. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_file_helpers.py +0 -0
  100. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_file_list.py +0 -0
  101. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_profile.py +0 -0
  102. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_profile_card.py +0 -0
  103. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_profile_default.py +0 -0
  104. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_profile_helpers.py +0 -0
  105. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_profile_list.py +0 -0
  106. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/test_profile_update.py +0 -0
  107. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/upgrade.py +0 -0
  108. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story.py +0 -0
  109. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_app_skills.py +0 -0
  110. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_bindings.py +0 -0
  111. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_camera.py +0 -0
  112. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_create.py +0 -0
  113. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_criteria.py +0 -0
  114. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_criterion_edits.py +0 -0
  115. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_delete.py +0 -0
  116. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_device_count.py +0 -0
  117. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_helpers.py +0 -0
  118. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_modify.py +0 -0
  119. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_overrides.py +0 -0
  120. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_profiles.py +0 -0
  121. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_setup_commit.py +0 -0
  122. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/user_story_update.py +0 -0
  123. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/verdicts.py +0 -0
  124. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/verdicts_helpers.py +0 -0
  125. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/commands/verdicts_projection.py +0 -0
  126. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/core/__init__.py +0 -0
  127. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/core/app_context.py +0 -0
  128. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/core/auth.py +0 -0
  129. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/core/config.py +0 -0
  130. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/core/credentials.py +0 -0
  131. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/core/oauth.py +0 -0
  132. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/core/tenants.py +0 -0
  133. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/core/token_exchange.py +0 -0
  134. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/main.py +0 -0
  135. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/__init__.py +0 -0
  136. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/app.py +0 -0
  137. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/app_env_vars.py +0 -0
  138. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/app_failure.py +0 -0
  139. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/app_screen.py +0 -0
  140. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/base.py +0 -0
  141. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/batch.py +0 -0
  142. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/build.py +0 -0
  143. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/commit_build.py +0 -0
  144. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/draft_feature.py +0 -0
  145. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/flow_type.py +0 -0
  146. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/issues.py +0 -0
  147. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/recording.py +0 -0
  148. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/story_run.py +0 -0
  149. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/tag.py +0 -0
  150. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/targets.py +0 -0
  151. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/models/user_story.py +0 -0
  152. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/utils/__init__.py +0 -0
  153. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/utils/confirm.py +0 -0
  154. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/utils/dependency_sim.py +0 -0
  155. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/utils/mermaid.py +0 -0
  156. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/utils/output.py +0 -0
  157. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/utils/skill_refresh.py +0 -0
  158. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/utils/update_check.py +0 -0
  159. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/src/minitest_cli/utils/video_frames.py +0 -0
  160. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/__init__.py +0 -0
  161. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/_commit_transport.py +0 -0
  162. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/conftest.py +0 -0
  163. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_api_client_auth.py +0 -0
  164. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_app_knowledge_commands.py +0 -0
  165. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_app_skill_commands.py +0 -0
  166. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_apps_commands.py +0 -0
  167. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_apps_dependencies.py +0 -0
  168. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_auth.py +0 -0
  169. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_auth_api_key.py +0 -0
  170. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_auth_commands.py +0 -0
  171. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_batch_commands.py +0 -0
  172. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_build_commands.py +0 -0
  173. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_build_fix_prompts.py +0 -0
  174. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_capabilities_command.py +0 -0
  175. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_channel_header.py +0 -0
  176. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_code_quality.py +0 -0
  177. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_commit_commands.py +0 -0
  178. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_deprecated_commands.py +0 -0
  179. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_deprecated_flags.py +0 -0
  180. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_draft_feature_commands.py +0 -0
  181. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_env.py +0 -0
  182. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_flow_types_commands.py +0 -0
  183. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_init_command.py +0 -0
  184. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_issues.py +0 -0
  185. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_issues_commands.py +0 -0
  186. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_mermaid.py +0 -0
  187. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_native_device_types.py +0 -0
  188. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_network_errors.py +0 -0
  189. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_recording_timeline.py +0 -0
  190. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_response_errors.py +0 -0
  191. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_run_cancel.py +0 -0
  192. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_run_commands.py +0 -0
  193. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_run_frames_command.py +0 -0
  194. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_screens_commands.py +0 -0
  195. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_skill_command.py +0 -0
  196. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_suite_change_provenance.py +0 -0
  197. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_tags_commands.py +0 -0
  198. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_test_file_commands.py +0 -0
  199. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_test_profile_commands.py +0 -0
  200. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_upgrade_command.py +0 -0
  201. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_user_story_bindings_commands.py +0 -0
  202. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_user_story_commands.py +0 -0
  203. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_user_story_setup_commit.py +0 -0
  204. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_verdicts.py +0 -0
  205. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_verdicts_actionable.py +0 -0
  206. {minitest_cli-0.34.0 → minitest_cli-0.35.0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: minitest-cli
3
- Version: 0.34.0
3
+ Version: 0.35.0
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
@@ -166,7 +166,7 @@ app defaults.
166
166
  | Command | Description |
167
167
  | ---------------- | ------------------------- |
168
168
  | `minitest auth` | Authentication management |
169
- | `minitest apps` | List, inspect, and create apps |
169
+ | `minitest apps` | List, inspect, and create apps; residential proxy and private network settings |
170
170
  | `minitest scenario` | Create, update, list and delete scenarios |
171
171
  | `minitest scenario-binding` | Bind test accounts, test files or app skills to scenarios |
172
172
  | `minitest draft` | Drafts — branches of the test suite |
@@ -198,6 +198,34 @@ stderr and leave `--json` stdout unchanged. Switch to the new names.
198
198
  | `run cancel --srp`, `run recording --srp` | `--target-id` |
199
199
  | `--type` on `scenario create/update/list` | `--tag` |
200
200
 
201
+ ## App network settings
202
+
203
+ Two per-app settings decide how test devices reach the network. Anyone on the
204
+ app can read them; only workspace **owners and admins** can change them (other
205
+ roles get `Only workspace owners and admins can change this setting`, exit 2).
206
+ `set` reads the current settings first and changes only what you pass.
207
+ Domains are an exact host (`api.stg.example.com`) or `*.example.com`, which
208
+ matches subdomains only, not `example.com` itself.
209
+
210
+ ```bash
211
+ # Residential proxy: reach the listed domains from a residential IP in one country
212
+ minitest --app <app-id> apps residential-proxy show
213
+ minitest --app <app-id> apps residential-proxy set [--enable|--disable] [--country tr] \
214
+ [--domain ipinfo.io ...] [--remove-domain <d> ...] [--clear-domains]
215
+
216
+ # Private network: reach internal domains through a connector running in your network
217
+ minitest --app <app-id> apps private-network show
218
+ minitest --app <app-id> apps private-network connectors
219
+ minitest --app <app-id> apps private-network set [--enable|--disable] [--connector <name-or-id>] \
220
+ [--domain api.stg.corp.internal ...] [--remove-domain <d> ...] [--clear-domains] [--port 443 ...]
221
+ ```
222
+
223
+ `--clear-domains --domain a --domain b` replaces the list; `--port` replaces the
224
+ ports (default 443). Turning the private network on needs a connector and at
225
+ least one domain. Connectors cannot be created from the CLI: a workspace owner
226
+ or admin creates them in the webapp, under **Workspace settings → Private
227
+ network**.
228
+
201
229
  ## CLI-only maintenance
202
230
 
203
231
  `minitest maintenance` lets a coding agent keep Minitest scenarios in sync
@@ -133,7 +133,7 @@ app defaults.
133
133
  | Command | Description |
134
134
  | ---------------- | ------------------------- |
135
135
  | `minitest auth` | Authentication management |
136
- | `minitest apps` | List, inspect, and create apps |
136
+ | `minitest apps` | List, inspect, and create apps; residential proxy and private network settings |
137
137
  | `minitest scenario` | Create, update, list and delete scenarios |
138
138
  | `minitest scenario-binding` | Bind test accounts, test files or app skills to scenarios |
139
139
  | `minitest draft` | Drafts — branches of the test suite |
@@ -165,6 +165,34 @@ stderr and leave `--json` stdout unchanged. Switch to the new names.
165
165
  | `run cancel --srp`, `run recording --srp` | `--target-id` |
166
166
  | `--type` on `scenario create/update/list` | `--tag` |
167
167
 
168
+ ## App network settings
169
+
170
+ Two per-app settings decide how test devices reach the network. Anyone on the
171
+ app can read them; only workspace **owners and admins** can change them (other
172
+ roles get `Only workspace owners and admins can change this setting`, exit 2).
173
+ `set` reads the current settings first and changes only what you pass.
174
+ Domains are an exact host (`api.stg.example.com`) or `*.example.com`, which
175
+ matches subdomains only, not `example.com` itself.
176
+
177
+ ```bash
178
+ # Residential proxy: reach the listed domains from a residential IP in one country
179
+ minitest --app <app-id> apps residential-proxy show
180
+ minitest --app <app-id> apps residential-proxy set [--enable|--disable] [--country tr] \
181
+ [--domain ipinfo.io ...] [--remove-domain <d> ...] [--clear-domains]
182
+
183
+ # Private network: reach internal domains through a connector running in your network
184
+ minitest --app <app-id> apps private-network show
185
+ minitest --app <app-id> apps private-network connectors
186
+ minitest --app <app-id> apps private-network set [--enable|--disable] [--connector <name-or-id>] \
187
+ [--domain api.stg.corp.internal ...] [--remove-domain <d> ...] [--clear-domains] [--port 443 ...]
188
+ ```
189
+
190
+ `--clear-domains --domain a --domain b` replaces the list; `--port` replaces the
191
+ ports (default 443). Turning the private network on needs a connector and at
192
+ least one domain. Connectors cannot be created from the CLI: a workspace owner
193
+ or admin creates them in the webapp, under **Workspace settings → Private
194
+ network**.
195
+
168
196
  ## CLI-only maintenance
169
197
 
170
198
  `minitest maintenance` lets a coding agent keep Minitest scenarios in sync
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "minitest-cli"
3
- version = "0.34.0"
3
+ version = "0.35.0"
4
4
  description = "Minitest CLI – command-line interface for the Minitest testing platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1,114 @@
1
+ """Shared plumbing for the per-app network settings (residential proxy, private network)."""
2
+
3
+ import asyncio
4
+ import re
5
+ from typing import Any
6
+
7
+ import httpx
8
+ import typer
9
+
10
+ from minitest_cli.api.client import ApiClient
11
+ from minitest_cli.api.errors import format_network_error
12
+ from minitest_cli.commands._response_errors import extract_detail
13
+ from minitest_cli.core.app_context import resolve_app_id
14
+ from minitest_cli.core.auth import require_auth
15
+ from minitest_cli.core.config import Settings
16
+ from minitest_cli.utils.output import print_error
17
+
18
+ EXIT_GENERAL_ERROR = 1
19
+ EXIT_AUTH_ERROR = 2
20
+ EXIT_NETWORK_ERROR = 3
21
+ EXIT_NOT_FOUND = 4
22
+
23
+ ADMIN_ONLY_MESSAGE = "Only workspace owners and admins can change this setting."
24
+
25
+
26
+ def is_json_mode() -> bool:
27
+ return typer.Context.json_mode # type: ignore[attr-defined]
28
+
29
+
30
+ def resolve_target() -> tuple[Settings, str]:
31
+ settings: Settings = typer.Context.settings # type: ignore[attr-defined]
32
+ require_auth(settings)
33
+ return settings, resolve_app_id(settings, typer.Context.app_flag) # type: ignore[attr-defined]
34
+
35
+
36
+ def fail(message: str, code: int = EXIT_GENERAL_ERROR) -> typer.Exit:
37
+ print_error(message)
38
+ return typer.Exit(code=code)
39
+
40
+
41
+ def _raise_for_status(resp: httpx.Response) -> None:
42
+ if resp.status_code < 400:
43
+ return
44
+ detail = extract_detail(resp)
45
+ if resp.status_code == 403:
46
+ raise fail(f"{ADMIN_ONLY_MESSAGE} ({detail})", EXIT_AUTH_ERROR)
47
+ if resp.status_code == 401:
48
+ raise fail(f"Authentication failed: {detail}", EXIT_AUTH_ERROR)
49
+ if resp.status_code == 404:
50
+ raise fail(f"Not found: {detail}", EXIT_NOT_FOUND)
51
+ if resp.status_code in (400, 409, 422):
52
+ raise fail(f"Rejected by the server ({resp.status_code}): {detail}")
53
+ raise fail(f"API error ({resp.status_code}): {detail}", EXIT_NETWORK_ERROR)
54
+
55
+
56
+ def call_api(
57
+ settings: Settings, method: str, path: str, body: dict[str, Any] | None = None
58
+ ) -> dict[str, Any]:
59
+ async def _run() -> dict[str, Any]:
60
+ async with ApiClient(settings) as client:
61
+ resp = await client.request(method, path, json=body)
62
+ _raise_for_status(resp)
63
+ try:
64
+ return resp.json()
65
+ except ValueError as exc:
66
+ raise fail(
67
+ f"Unexpected non-JSON response ({resp.status_code}): {resp.text[:200]}",
68
+ EXIT_NETWORK_ERROR,
69
+ ) from exc
70
+
71
+ try:
72
+ return asyncio.run(_run())
73
+ except httpx.HTTPError as exc:
74
+ raise fail(format_network_error(exc), EXIT_NETWORK_ERROR) from exc
75
+
76
+
77
+ def merge_domains(
78
+ current: list[str],
79
+ *,
80
+ add: list[str] | None,
81
+ remove: list[str] | None,
82
+ clear: bool,
83
+ pattern: str,
84
+ max_domains: int,
85
+ ) -> list[str]:
86
+ """Apply ``--clear-domains``, ``--remove-domain`` then ``--domain`` and validate the result."""
87
+ added = [_normalize(d) for d in add or []]
88
+ invalid = [d for d in added if not re.fullmatch(pattern, d)]
89
+ if invalid:
90
+ raise fail(
91
+ f"Invalid domain(s): {', '.join(invalid)}. Use a lowercase host like api.example.com, "
92
+ "or *.example.com to match its subdomains (not example.com itself)."
93
+ )
94
+ removed = {_normalize(d) for d in remove or []}
95
+ unknown = sorted(removed - set(current))
96
+ if unknown:
97
+ raise fail(f"Not in the current domain list: {', '.join(unknown)}.")
98
+ base = [] if clear else [d for d in current if d not in removed]
99
+ merged = list(dict.fromkeys([*base, *added]))
100
+ if len(merged) > max_domains:
101
+ raise fail(f"Too many domains: {len(merged)} (max {max_domains}).")
102
+ return merged
103
+
104
+
105
+ def _normalize(domain: str) -> str:
106
+ return domain.strip().lower().rstrip(".")
107
+
108
+
109
+ def on_off(value: bool) -> str:
110
+ return "on" if value else "off"
111
+
112
+
113
+ def domain_list(domains: list[str]) -> str:
114
+ return ", ".join(domains) if domains else "-"
@@ -1,4 +1,4 @@
1
- """App management commands: list, get, create, dependencies."""
1
+ """App management commands: list, get, create, dependencies, network settings."""
2
2
 
3
3
  import asyncio
4
4
  from pathlib import Path
@@ -10,7 +10,12 @@ import typer
10
10
  from minitest_cli.api.apps_manager_client import AppsManagerClient # noqa: F401 re-exported for tests
11
11
  from minitest_cli.api.client import ApiClient
12
12
  from minitest_cli.api.errors import format_network_error
13
- from minitest_cli.commands import apps_dependencies, apps_get
13
+ from minitest_cli.commands import (
14
+ apps_dependencies,
15
+ apps_get,
16
+ private_network,
17
+ residential_proxy,
18
+ )
14
19
  from minitest_cli.commands.apps_helpers import create_app_request
15
20
  from minitest_cli.commands.apps_table import APP_TABLE_HEADERS, app_row
16
21
  from minitest_cli.core.auth import require_auth
@@ -30,6 +35,8 @@ EXIT_NETWORK_ERROR = 3
30
35
  app = typer.Typer(name="apps", help="App management.")
31
36
  app.command(name="dependencies")(apps_dependencies.dependencies)
32
37
  app.command(name="get")(apps_get.get_app)
38
+ app.add_typer(residential_proxy.app)
39
+ app.add_typer(private_network.app)
33
40
 
34
41
 
35
42
  @app.callback()
@@ -0,0 +1,143 @@
1
+ """'apps private-network': reach a customer's internal domains through a connector."""
2
+
3
+ from typing import Annotated, Any
4
+
5
+ import typer
6
+
7
+ from minitest_cli.commands.app_network_helpers import (
8
+ call_api,
9
+ fail,
10
+ is_json_mode,
11
+ merge_domains,
12
+ resolve_target,
13
+ )
14
+ from minitest_cli.commands.private_network_helpers import (
15
+ CREATE_IN_WEBAPP_HINT,
16
+ UNAVAILABLE_MESSAGE,
17
+ render_config,
18
+ render_connectors,
19
+ resolve_connector,
20
+ )
21
+ from minitest_cli.core.config import Settings
22
+ from minitest_cli.utils.output import print_success
23
+
24
+ app = typer.Typer(
25
+ name="private-network",
26
+ help=(
27
+ "Private network of the app (--app): devices reach internal domains through a "
28
+ "connector running in your network. Changing it needs the OWNER or ADMIN role. "
29
+ f"{CREATE_IN_WEBAPP_HINT}"
30
+ ),
31
+ no_args_is_help=True,
32
+ )
33
+
34
+
35
+ def _path(app_id: str) -> str:
36
+ return f"/api/v1/apps/{app_id}/private-network"
37
+
38
+
39
+ @app.command(name="show")
40
+ def show() -> None:
41
+ """Show the app's private network settings."""
42
+ settings, app_id = resolve_target()
43
+ config = call_api(settings, "GET", _path(app_id))
44
+ render_config(config)
45
+
46
+
47
+ @app.command(name="connectors")
48
+ def connectors() -> None:
49
+ """List the workspace connectors the app can route through (read-only)."""
50
+ settings, app_id = resolve_target()
51
+ listing = call_api(settings, "GET", f"{_path(app_id)}/connectors")
52
+ render_connectors(listing)
53
+
54
+
55
+ @app.command(name="set")
56
+ def set_config(
57
+ enable: Annotated[
58
+ bool | None,
59
+ typer.Option(
60
+ "--enable/--disable",
61
+ help="Switch it on or off. On needs a connector and at least one domain.",
62
+ ),
63
+ ] = None,
64
+ connector: Annotated[
65
+ str | None,
66
+ typer.Option("--connector", help="Connector name or id (see 'connectors')."),
67
+ ] = None,
68
+ domain: Annotated[
69
+ list[str] | None,
70
+ typer.Option(
71
+ "--domain",
72
+ help="Internal domain to reach (repeatable): host, or *.host for its subdomains only.",
73
+ ),
74
+ ] = None,
75
+ remove_domain: Annotated[
76
+ list[str] | None,
77
+ typer.Option("--remove-domain", help="Domain to stop reaching (repeatable)."),
78
+ ] = None,
79
+ clear_domains: Annotated[
80
+ bool,
81
+ typer.Option("--clear-domains", help="Drop every current domain before adding --domain."),
82
+ ] = False,
83
+ port: Annotated[
84
+ list[int] | None,
85
+ typer.Option(
86
+ "--port",
87
+ min=1,
88
+ max=65535,
89
+ help="Port devices may reach (repeatable). Replaces the current ports; default 443.",
90
+ ),
91
+ ] = None,
92
+ ) -> None:
93
+ """Change the app's private network settings; options not passed stay as they are."""
94
+ if not any([enable is not None, connector, domain, remove_domain, clear_domains, port]):
95
+ raise fail(
96
+ "Nothing to change: pass --enable/--disable, --connector, a domain option or --port."
97
+ )
98
+ settings, app_id = resolve_target()
99
+ current = call_api(settings, "GET", _path(app_id))
100
+ body = _build_body(settings, app_id, current, enable, connector, port)
101
+ body["domains"] = merge_domains(
102
+ current["domains"],
103
+ add=domain,
104
+ remove=remove_domain,
105
+ clear=clear_domains,
106
+ pattern=current["domainPattern"],
107
+ max_domains=current["maxDomains"],
108
+ )
109
+ if body["enabled"]:
110
+ if not current["available"]:
111
+ raise fail(UNAVAILABLE_MESSAGE)
112
+ if not body["connectorId"]:
113
+ raise fail(
114
+ "Turning it on needs a connector: pass --connector <name-or-id> "
115
+ f"(see 'minitest apps private-network connectors'). {CREATE_IN_WEBAPP_HINT}"
116
+ )
117
+ if not body["domains"]:
118
+ raise fail("Turning it on needs at least one --domain.")
119
+
120
+ updated = call_api(settings, "PUT", _path(app_id), body)
121
+ if not is_json_mode():
122
+ print_success("Private network updated.")
123
+ render_config(updated)
124
+
125
+
126
+ def _build_body(
127
+ settings: Settings,
128
+ app_id: str,
129
+ current: dict[str, Any],
130
+ enable: bool | None,
131
+ connector: str | None,
132
+ port: list[int] | None,
133
+ ) -> dict[str, Any]:
134
+ if connector:
135
+ listing = call_api(settings, "GET", f"{_path(app_id)}/connectors")
136
+ connector_id = resolve_connector(listing["connectors"], connector)["id"]
137
+ else:
138
+ connector_id = current["connector"]["id"] if current["connector"] else None
139
+ return {
140
+ "enabled": current["enabled"] if enable is None else enable,
141
+ "connectorId": connector_id,
142
+ "ports": list(dict.fromkeys(port)) if port else current["ports"] or [443],
143
+ }
@@ -0,0 +1,93 @@
1
+ """Rendering and connector lookup for 'apps private-network'."""
2
+
3
+ from typing import Any
4
+
5
+ from minitest_cli.commands.app_network_helpers import domain_list, fail, is_json_mode, on_off
6
+ from minitest_cli.utils.output import print_info, print_json, print_table, print_warning
7
+
8
+ UNAVAILABLE_MESSAGE = "This workspace cannot use private network. Contact Minitap to enable it."
9
+ CREATE_IN_WEBAPP_HINT = (
10
+ "Connectors can only be created by a workspace owner or admin in the webapp: "
11
+ "Workspace settings → Private network."
12
+ )
13
+
14
+
15
+ def resolve_connector(connectors: list[dict[str, Any]], ref: str) -> dict[str, Any]:
16
+ """Find a connector by id, or by name (case-insensitive)."""
17
+ if not connectors:
18
+ raise fail(f"This workspace has no connector. {CREATE_IN_WEBAPP_HINT}")
19
+ by_id = [c for c in connectors if c["id"] == ref]
20
+ matches = by_id or [c for c in connectors if c["name"].casefold() == ref.casefold()]
21
+ if len(matches) == 1:
22
+ return matches[0]
23
+ if matches:
24
+ ids = ", ".join(c["id"] for c in matches)
25
+ raise fail(f"Several connectors are named '{ref}': pass one of these ids instead: {ids}.")
26
+ names = ", ".join(f"{c['name']} ({c['status']})" for c in connectors)
27
+ raise fail(f"No connector named '{ref}'. Available: {names}. {CREATE_IN_WEBAPP_HINT}")
28
+
29
+
30
+ def without_token_prefix(config: dict[str, Any]) -> dict[str, Any]:
31
+ connector = config.get("connector")
32
+ if not connector:
33
+ return config
34
+ return {**config, "connector": {k: v for k, v in connector.items() if k != "tokenPrefix"}}
35
+
36
+
37
+ def render_config(config: dict[str, Any]) -> None:
38
+ config = without_token_prefix(config)
39
+ if not config["available"]:
40
+ print_warning(UNAVAILABLE_MESSAGE)
41
+ if is_json_mode():
42
+ print_json(config)
43
+ warn_connector_state(config)
44
+ return
45
+ connector = config["connector"]
46
+ print(f"Enabled: {on_off(config['enabled'])}") # noqa: T201
47
+ if connector:
48
+ print(f"Connector: {connector['name']} ({connector['status']}) {connector['id']}") # noqa: T201
49
+ else:
50
+ print("Connector: -") # noqa: T201
51
+ print(f"Domains: {domain_list(config['domains'])}") # noqa: T201
52
+ print(f"Ports: {', '.join(str(p) for p in config['ports']) or '-'}") # noqa: T201
53
+ warn_connector_state(config)
54
+
55
+
56
+ def warn_connector_state(config: dict[str, Any]) -> None:
57
+ connector = config["connector"]
58
+ if config["enabled"] and connector and connector["status"] != "online":
59
+ print_warning(
60
+ f"Connector '{connector['name']}' is {connector['status']}: runs cannot reach "
61
+ "the internal domains until it is connected."
62
+ )
63
+ refused = config["domainsRefusedByConnector"]
64
+ if refused:
65
+ print_warning(
66
+ f"The connector's own allowlist refuses: {', '.join(refused)}. Add them on the "
67
+ "connector machine, or the devices will not reach them."
68
+ )
69
+
70
+
71
+ def render_connectors(listing: dict[str, Any]) -> None:
72
+ if not listing["available"]:
73
+ print_warning(UNAVAILABLE_MESSAGE)
74
+ connectors = listing["connectors"]
75
+ if not connectors:
76
+ print_info(f"No connector yet. {CREATE_IN_WEBAPP_HINT}")
77
+ if is_json_mode():
78
+ print_json(listing)
79
+ return
80
+ if not connectors:
81
+ return
82
+ rows = [
83
+ [
84
+ c["name"],
85
+ c["status"],
86
+ domain_list(c["domains"]),
87
+ ", ".join(str(p) for p in c["ports"]) or "-",
88
+ c.get("lastSeenAt") or "-",
89
+ c["id"],
90
+ ]
91
+ for c in connectors
92
+ ]
93
+ print_table(["Name", "Status", "Domains", "Ports", "Last seen", "ID"], rows, title="Connectors")
@@ -0,0 +1,121 @@
1
+ """'apps residential-proxy': route some of an app's device traffic through residential IPs."""
2
+
3
+ from typing import Annotated, Any
4
+
5
+ import typer
6
+
7
+ from minitest_cli.commands.app_network_helpers import (
8
+ call_api,
9
+ domain_list,
10
+ fail,
11
+ is_json_mode,
12
+ merge_domains,
13
+ on_off,
14
+ resolve_target,
15
+ )
16
+ from minitest_cli.utils.output import print_json, print_success, print_warning
17
+
18
+ app = typer.Typer(
19
+ name="residential-proxy",
20
+ help=(
21
+ "Residential proxy of the app (--app): devices reach the listed domains from a "
22
+ "residential IP in the chosen country. Changing it needs the OWNER or ADMIN role."
23
+ ),
24
+ no_args_is_help=True,
25
+ )
26
+
27
+ UNAVAILABLE_MESSAGE = (
28
+ "This workspace cannot use the residential proxy. Contact Minitap to enable it."
29
+ )
30
+
31
+
32
+ def _path(app_id: str) -> str:
33
+ return f"/api/v1/apps/{app_id}/residential-proxy"
34
+
35
+
36
+ def _render(config: dict[str, Any]) -> None:
37
+ if not config["available"]:
38
+ print_warning(UNAVAILABLE_MESSAGE)
39
+ if is_json_mode():
40
+ print_json(config)
41
+ return
42
+ print(f"Enabled: {on_off(config['enabled'])}") # noqa: T201
43
+ print(f"Country: {config['country']}") # noqa: T201
44
+ print(f"Domains: {domain_list(config['allowedDomains'])}") # noqa: T201
45
+ print(f"Countries available: {', '.join(config['availableCountries'])}") # noqa: T201
46
+ print(f"Price: {config['usdPerGb']} USD per GB") # noqa: T201
47
+
48
+
49
+ @app.command(name="show")
50
+ def show() -> None:
51
+ """Show the app's residential proxy settings."""
52
+ settings, app_id = resolve_target()
53
+ config = call_api(settings, "GET", _path(app_id))
54
+ _render(config)
55
+
56
+
57
+ @app.command(name="set")
58
+ def set_config(
59
+ enable: Annotated[
60
+ bool | None,
61
+ typer.Option("--enable/--disable", help="Switch the proxy on or off."),
62
+ ] = None,
63
+ country: Annotated[
64
+ str | None,
65
+ typer.Option("--country", help="Exit country code, e.g. tr (see 'show')."),
66
+ ] = None,
67
+ domain: Annotated[
68
+ list[str] | None,
69
+ typer.Option(
70
+ "--domain",
71
+ help="Domain to route through the proxy (repeatable): host or *.host for subdomains.",
72
+ ),
73
+ ] = None,
74
+ remove_domain: Annotated[
75
+ list[str] | None,
76
+ typer.Option("--remove-domain", help="Domain to stop routing (repeatable)."),
77
+ ] = None,
78
+ clear_domains: Annotated[
79
+ bool,
80
+ typer.Option("--clear-domains", help="Drop every current domain before adding --domain."),
81
+ ] = False,
82
+ ) -> None:
83
+ """Change the app's residential proxy settings; options not passed stay as they are."""
84
+ if (
85
+ enable is None
86
+ and country is None
87
+ and not domain
88
+ and not remove_domain
89
+ and not clear_domains
90
+ ):
91
+ raise fail("Nothing to change: pass --enable/--disable, --country or a domain option.")
92
+ settings, app_id = resolve_target()
93
+ current = call_api(settings, "GET", _path(app_id))
94
+ if not current["available"]:
95
+ raise fail(UNAVAILABLE_MESSAGE)
96
+
97
+ chosen_country = country.strip().lower() if country else current["country"]
98
+ if chosen_country not in current["availableCountries"]:
99
+ raise fail(
100
+ f"Unknown country '{chosen_country}'. Pick one of: "
101
+ f"{', '.join(current['availableCountries'])}."
102
+ )
103
+ body = {
104
+ "enabled": current["enabled"] if enable is None else enable,
105
+ "country": chosen_country,
106
+ "allowedDomains": merge_domains(
107
+ current["allowedDomains"],
108
+ add=domain,
109
+ remove=remove_domain,
110
+ clear=clear_domains,
111
+ pattern=current["domainPattern"],
112
+ max_domains=current["maxDomains"],
113
+ ),
114
+ }
115
+ if body["enabled"] and not body["allowedDomains"]:
116
+ print_warning("The proxy is on but no domain is listed, so no traffic goes through it.")
117
+
118
+ updated = call_api(settings, "PUT", _path(app_id), body)
119
+ if not is_json_mode():
120
+ print_success("Residential proxy updated.")
121
+ _render(updated)