bt-cli 0.4.71__tar.gz → 0.4.72__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 (252) hide show
  1. {bt_cli-0.4.71 → bt_cli-0.4.72}/CLAUDE.md +2 -2
  2. {bt_cli-0.4.71 → bt_cli-0.4.72}/PKG-INFO +1 -1
  3. {bt_cli-0.4.71 → bt_cli-0.4.72}/pyproject.toml +1 -1
  4. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/__init__.py +1 -1
  5. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/commands/learn.py +1 -1
  6. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/commands/quick.py +31 -31
  7. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/output.py +3 -1
  8. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/prompts.py +5 -5
  9. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/bundles.py +1 -1
  10. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/permissions.py +1 -1
  11. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/requests.py +2 -2
  12. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/resources.py +2 -2
  13. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_cmdgrps.py +1 -1
  14. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_hostgrps.py +1 -1
  15. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_tmdategrps.py +1 -1
  16. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_usergrps.py +1 -1
  17. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/policies.py +3 -3
  18. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/quick.py +4 -4
  19. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/group_policies.py +1 -1
  20. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/jump_clients.py +1 -1
  21. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/jump_items.py +4 -4
  22. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/quick.py +16 -16
  23. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/users.py +2 -2
  24. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/vault.py +3 -3
  25. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/accounts.py +3 -3
  26. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/assets.py +1 -1
  27. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/attributes.py +2 -2
  28. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/clouds.py +2 -2
  29. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/databases.py +1 -1
  30. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/functional.py +4 -4
  31. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/quick.py +65 -65
  32. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/search.py +2 -2
  33. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/secrets.py +10 -10
  34. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/sessions.py +3 -3
  35. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/systems.py +4 -4
  36. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/workgroups.py +1 -1
  37. bt_cli-0.4.72/tests/core/test_json_stdout_purity.py +304 -0
  38. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pws/test_commands.py +24 -13
  39. {bt_cli-0.4.71 → bt_cli-0.4.72}/.claude/skills/bt/SKILL.md +0 -0
  40. {bt_cli-0.4.71 → bt_cli-0.4.72}/.claude/skills/entitle/SKILL.md +0 -0
  41. {bt_cli-0.4.71 → bt_cli-0.4.72}/.claude/skills/epml/SKILL.md +0 -0
  42. {bt_cli-0.4.71 → bt_cli-0.4.72}/.claude/skills/epmw/SKILL.md +0 -0
  43. {bt_cli-0.4.71 → bt_cli-0.4.72}/.claude/skills/pra/SKILL.md +0 -0
  44. {bt_cli-0.4.71 → bt_cli-0.4.72}/.claude/skills/pws/SKILL.md +0 -0
  45. {bt_cli-0.4.71 → bt_cli-0.4.72}/.env.example +0 -0
  46. {bt_cli-0.4.71 → bt_cli-0.4.72}/.github/workflows/ci.yml +0 -0
  47. {bt_cli-0.4.71 → bt_cli-0.4.72}/.github/workflows/release.yml +0 -0
  48. {bt_cli-0.4.71 → bt_cli-0.4.72}/.gitignore +0 -0
  49. {bt_cli-0.4.71 → bt_cli-0.4.72}/README.md +0 -0
  50. {bt_cli-0.4.71 → bt_cli-0.4.72}/assets/cli-help.png +0 -0
  51. {bt_cli-0.4.71 → bt_cli-0.4.72}/assets/cli-output.png +0 -0
  52. {bt_cli-0.4.71 → bt_cli-0.4.72}/bt-cli-cheatsheet.html +0 -0
  53. {bt_cli-0.4.71 → bt_cli-0.4.72}/bt-cli.spec +0 -0
  54. {bt_cli-0.4.71 → bt_cli-0.4.72}/bt_entry.py +0 -0
  55. {bt_cli-0.4.71 → bt_cli-0.4.72}/epml-clients-server-side-filters-plan.md +0 -0
  56. {bt_cli-0.4.71 → bt_cli-0.4.72}/epml-implementation-plan.md +0 -0
  57. {bt_cli-0.4.71 → bt_cli-0.4.72}/pf-implementation-plan.md +0 -0
  58. {bt_cli-0.4.71 → bt_cli-0.4.72}/scripts/bt_entry.py +0 -0
  59. {bt_cli-0.4.71 → bt_cli-0.4.72}/scripts/pf_onboard.py +0 -0
  60. {bt_cli-0.4.71 → bt_cli-0.4.72}/scripts/sync-package-data.sh +0 -0
  61. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/cli.py +0 -0
  62. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/commands/__init__.py +0 -0
  63. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/commands/configure.py +0 -0
  64. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/__init__.py +0 -0
  65. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/auth.py +0 -0
  66. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/client.py +0 -0
  67. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/config.py +0 -0
  68. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/config_file.py +0 -0
  69. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/csv_utils.py +0 -0
  70. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/errors.py +0 -0
  71. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/core/rest_debug.py +0 -0
  72. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/CLAUDE.md +0 -0
  73. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/__init__.py +0 -0
  74. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  75. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
  76. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/epml/SKILL.md +0 -0
  77. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  78. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/pf/SKILL.md +0 -0
  79. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
  80. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
  81. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/secrets/SKILL.md +0 -0
  82. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/data/skills/session-analysis/SKILL.md +0 -0
  83. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/__init__.py +0 -0
  84. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/client/__init__.py +0 -0
  85. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/client/base.py +0 -0
  86. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/__init__.py +0 -0
  87. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/accounts.py +0 -0
  88. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/applications.py +0 -0
  89. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/audit.py +0 -0
  90. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/auth.py +0 -0
  91. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/integrations.py +0 -0
  92. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/jit.py +0 -0
  93. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/policies.py +0 -0
  94. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/roles.py +0 -0
  95. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/users.py +0 -0
  96. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/workflows.py +0 -0
  97. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/__init__.py +0 -0
  98. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/bundle.py +0 -0
  99. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/common.py +0 -0
  100. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/integration.py +0 -0
  101. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/permission.py +0 -0
  102. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/policy.py +0 -0
  103. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/resource.py +0 -0
  104. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/role.py +0 -0
  105. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/user.py +0 -0
  106. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/entitle/models/workflow.py +0 -0
  107. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/__init__.py +0 -0
  108. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/client/__init__.py +0 -0
  109. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/client/base.py +0 -0
  110. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/__init__.py +0 -0
  111. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/audit.py +0 -0
  112. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/auth.py +0 -0
  113. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/client_pkg.py +0 -0
  114. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/clients.py +0 -0
  115. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/external_apis.py +0 -0
  116. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/hosts.py +0 -0
  117. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/iolog.py +0 -0
  118. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/license.py +0 -0
  119. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/quick.py +0 -0
  120. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
  121. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
  122. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
  123. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
  124. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
  125. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/settings.py +0 -0
  126. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/siems.py +0 -0
  127. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/commands/users.py +0 -0
  128. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epml/models/__init__.py +0 -0
  129. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/__init__.py +0 -0
  130. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/client/__init__.py +0 -0
  131. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/client/base.py +0 -0
  132. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/__init__.py +0 -0
  133. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/audits.py +0 -0
  134. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/auth.py +0 -0
  135. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/computers.py +0 -0
  136. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/events.py +0 -0
  137. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/groups.py +0 -0
  138. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/requests.py +0 -0
  139. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/roles.py +0 -0
  140. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/tasks.py +0 -0
  141. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/users.py +0 -0
  142. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/epmw/models/__init__.py +0 -0
  143. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/__init__.py +0 -0
  144. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/client/__init__.py +0 -0
  145. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/client/base.py +0 -0
  146. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/commands/__init__.py +0 -0
  147. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/commands/auth.py +0 -0
  148. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/commands/machines.py +0 -0
  149. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/commands/tokens.py +0 -0
  150. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/commands/user.py +0 -0
  151. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pf/commands/users.py +0 -0
  152. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/__init__.py +0 -0
  153. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/client/__init__.py +0 -0
  154. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/client/base.py +0 -0
  155. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/__init__.py +0 -0
  156. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/auth.py +0 -0
  157. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/import_export.py +0 -0
  158. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  159. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  160. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/policies.py +0 -0
  161. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/commands/teams.py +0 -0
  162. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/__init__.py +0 -0
  163. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/common.py +0 -0
  164. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/group_policy.py +0 -0
  165. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/jump_client.py +0 -0
  166. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/jump_group.py +0 -0
  167. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/jump_item.py +0 -0
  168. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/jumpoint.py +0 -0
  169. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/team.py +0 -0
  170. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/user.py +0 -0
  171. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pra/models/vault.py +0 -0
  172. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/__init__.py +0 -0
  173. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/client/__init__.py +0 -0
  174. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/client/base.py +0 -0
  175. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  176. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  177. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/__init__.py +0 -0
  178. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/audit.py +0 -0
  179. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/auth.py +0 -0
  180. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/config.py +0 -0
  181. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/credentials.py +0 -0
  182. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/directories.py +0 -0
  183. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/import_export.py +0 -0
  184. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/platforms.py +0 -0
  185. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/commands/users.py +0 -0
  186. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/config.py +0 -0
  187. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/models/__init__.py +0 -0
  188. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/models/account.py +0 -0
  189. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/models/asset.py +0 -0
  190. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/models/common.py +0 -0
  191. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/pws/models/system.py +0 -0
  192. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/__init__.py +0 -0
  193. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/client/__init__.py +0 -0
  194. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/client/base.py +0 -0
  195. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/__init__.py +0 -0
  196. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/_hints.py +0 -0
  197. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/auth.py +0 -0
  198. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/dynamic.py +0 -0
  199. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/folders.py +0 -0
  200. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/integrations.py +0 -0
  201. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/leases.py +0 -0
  202. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/static.py +0 -0
  203. {bt_cli-0.4.71 → bt_cli-0.4.72}/src/bt_cli/secrets/models/__init__.py +0 -0
  204. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/__init__.py +0 -0
  205. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/conftest.py +0 -0
  206. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/core/__init__.py +0 -0
  207. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/core/test_auth.py +0 -0
  208. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/core/test_config.py +0 -0
  209. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/core/test_config_file.py +0 -0
  210. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/core/test_errors.py +0 -0
  211. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/core/test_output.py +0 -0
  212. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/core/test_rest_debug.py +0 -0
  213. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/entitle/__init__.py +0 -0
  214. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/entitle/test_client.py +0 -0
  215. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/entitle/test_commands.py +0 -0
  216. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/entitle-smoke-test.sh +0 -0
  217. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/epml/__init__.py +0 -0
  218. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/epml/test_client.py +0 -0
  219. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/epml/test_commands.py +0 -0
  220. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/epmw/__init__.py +0 -0
  221. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/epmw/test_client.py +0 -0
  222. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/epmw/test_commands.py +0 -0
  223. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/epmw-quick-test-plan.md +0 -0
  224. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/fixtures/__init__.py +0 -0
  225. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/fixtures/responses.py +0 -0
  226. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/__init__.py +0 -0
  227. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/conftest.py +0 -0
  228. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/helpers.py +0 -0
  229. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/test_entitle_integration.py +0 -0
  230. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/test_epmw_integration.py +0 -0
  231. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/test_epmw_lifecycle.py +0 -0
  232. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/test_pra_integration.py +0 -0
  233. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/test_pra_lifecycle.py +0 -0
  234. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/test_pws_integration.py +0 -0
  235. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/integration/test_pws_lifecycle.py +0 -0
  236. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pf/__init__.py +0 -0
  237. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pf/test_client.py +0 -0
  238. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pf/test_commands.py +0 -0
  239. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pra/__init__.py +0 -0
  240. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pra/test_client.py +0 -0
  241. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pra/test_commands.py +0 -0
  242. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pra/test_jump_item_fixes.py +0 -0
  243. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pra-smoke-test.sh +0 -0
  244. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pra-test-plan.md +0 -0
  245. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pws/__init__.py +0 -0
  246. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pws/test_client.py +0 -0
  247. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pws/test_quick_sessions.py +0 -0
  248. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pws-quick-test-plan.md +0 -0
  249. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/pws-smoke-test.sh +0 -0
  250. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/secrets/__init__.py +0 -0
  251. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/secrets/test_client.py +0 -0
  252. {bt_cli-0.4.71 → bt_cli-0.4.72}/tests/secrets/test_commands.py +0 -0
@@ -1,6 +1,6 @@
1
1
  # BT-CLI
2
2
 
3
- BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.71**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.72**
4
4
 
5
5
  ## Setup
6
6
 
@@ -82,7 +82,7 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
82
82
  - PRA K8s tunnels: Require Linux jumpoint
83
83
  - PWS assets: Must create via workgroup endpoint
84
84
  - ECM integration: PWS system name must match PRA jump item name
85
- - **PRA `-o json` is pure JSON on stdout** — all human status/error banners go to **stderr** (`core/output.py` `err_console`), so `... -o json | jq` is safe. API 4xx `errors` objects are surfaced field-by-field, not collapsed to "Validation Failed".
85
+ - **`-o json` is pure JSON on stdout — in every product.** All human status/progress banners *and interactive prompts* go to **stderr** (`core/output.py` `err_console`; `typer.confirm`/`typer.prompt` are called with `err=True`), so `... -o json | jq` and `json.load(stdin)` are safe even for `quick` workflow commands that narrate their steps. Enforced by static guards in `tests/core/test_json_stdout_purity.py` — when adding a command, use `print_success`/`print_warning`/`print_info` or `err_console.print` for anything that isn't the command's data. Consequence: piping *table* output to a file drops the narration (it's on stderr) — that's intended. PRA API 4xx `errors` objects are surfaced field-by-field, not collapsed to "Validation Failed".
86
86
  - **PRA tunnel create gates fields by `tunnel_type`**: tcp→`--ports`, mssql→`--username`/`--database`, k8s→`--url`/`--ca-cert`. **psql/mysql are created BARE** — the public Config API has no write path for their username/database (POST rejects, PATCH no-ops, PUT 405s) though GET shows them; CLI warns + ignores those flags. Set in `/login` UI.
87
87
  - **PRA `export jump-items` defaults to a REAL export** (all types: shell/rdp/vnc/web/tunnel, full type-specific columns) that round-trips through `import jump-items`; `--sample` gives the blank template. `bt pra jump-items web create` exists.
88
88
  - **PRA vault `associate -t protocol_tunnel` is MSSQL-only** (live-verified): server rejects psql/mysql/tcp/k8s with "Only SQL Server Tunnels are allowed" — use tag/criteria association for those. **TCP `--ports` needs listen;destination *pairs*** (even count; odd → 400 "number of parameters is invalid"). Jumpoint listing is `bt pra jumpoints list` (plural).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bt-cli
3
- Version: 0.4.71
3
+ Version: 0.4.72
4
4
  Summary: BeyondTrust Platform CLI (unofficial) - Password Safe, Entitle, PRA, EPM
5
5
  Author-email: Dave Grendysz <dgrendysz@beyondtrust.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bt-cli"
7
- version = "0.4.71"
7
+ version = "0.4.72"
8
8
  description = "BeyondTrust Platform CLI (unofficial) - Password Safe, Entitle, PRA, EPM"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """BeyondTrust Unified Admin CLI."""
2
2
 
3
- __version__ = "0.4.71"
3
+ __version__ = "0.4.72"
@@ -221,7 +221,7 @@ def clear_learnings(
221
221
  bt learn clear --force
222
222
  """
223
223
  if not force:
224
- if not typer.confirm("Clear all learnings?"):
224
+ if not typer.confirm("Clear all learnings?", err=True):
225
225
  console.print("[yellow]Cancelled[/yellow]")
226
226
  raise typer.Exit(0)
227
227
 
@@ -11,7 +11,7 @@ from rich.console import Console
11
11
  from rich.panel import Panel
12
12
  from rich.table import Table
13
13
 
14
- from ..core.output import print_api_error, print_error, print_success, print_warning
14
+ from ..core.output import err_console, print_api_error, print_error, print_success, print_warning
15
15
  from ..core.prompts import prompt_if_missing, prompt_from_list
16
16
 
17
17
 
@@ -128,7 +128,7 @@ def pasm_onboard(
128
128
  for i, row in enumerate(rows, 1):
129
129
  console.print(f" {i}. {row.get('name', '?')} ({row.get('ip', '?')})")
130
130
 
131
- if not typer.confirm("\nProceed with bulk onboarding?"):
131
+ if not typer.confirm("\nProceed with bulk onboarding?", err=True):
132
132
  console.print("[yellow]Cancelled.[/yellow]")
133
133
  raise typer.Exit(0)
134
134
 
@@ -158,19 +158,19 @@ def pasm_onboard(
158
158
  )
159
159
  results.append({"name": row.get('name'), "status": "success"})
160
160
  except Exception as e:
161
- console.print(f"[red]Failed: {e}[/red]")
161
+ err_console.print(f"[red]Failed: {e}[/red]")
162
162
  results.append({"name": row.get('name'), "status": "failed", "error": str(e)})
163
163
 
164
164
  # Summary
165
165
  console.print(f"\n[bold]Bulk Onboarding Summary:[/bold]")
166
166
  success = sum(1 for r in results if r["status"] == "success")
167
167
  failed = sum(1 for r in results if r["status"] == "failed")
168
- console.print(f" [green]Success: {success}[/green]")
168
+ err_console.print(f" [green]Success: {success}[/green]")
169
169
  if failed:
170
- console.print(f" [red]Failed: {failed}[/red]")
170
+ err_console.print(f" [red]Failed: {failed}[/red]")
171
171
  for r in results:
172
172
  if r["status"] == "failed":
173
- console.print(f" - {r['name']}: {r.get('error', 'Unknown error')}")
173
+ err_console.print(f" - {r['name']}: {r.get('error', 'Unknown error')}")
174
174
 
175
175
  if output == "json":
176
176
  console.print_json(json.dumps(results, default=str))
@@ -273,7 +273,7 @@ def _onboard_single_host(
273
273
  pws.authenticate()
274
274
 
275
275
  # Create asset
276
- console.print(f"[dim]PWS: Creating asset '{name}'...[/dim]")
276
+ err_console.print(f"[dim]PWS: Creating asset '{name}'...[/dim]")
277
277
  asset = pws.create_asset(
278
278
  workgroup_id=workgroup,
279
279
  ip_address=ip,
@@ -281,10 +281,10 @@ def _onboard_single_host(
281
281
  dns_name=dns,
282
282
  )
283
283
  asset_id = asset.get("AssetID")
284
- console.print(f" [green]Created asset ID: {asset_id}[/green]")
284
+ err_console.print(f" [green]Created asset ID: {asset_id}[/green]")
285
285
 
286
286
  # Create managed system
287
- console.print(f"[dim]PWS: Creating managed system '{name}'...[/dim]")
287
+ err_console.print(f"[dim]PWS: Creating managed system '{name}'...[/dim]")
288
288
  system = pws.create_managed_system(
289
289
  system_name=name,
290
290
  platform_id=platform,
@@ -295,7 +295,7 @@ def _onboard_single_host(
295
295
  elevation_command=elevation,
296
296
  )
297
297
  system_id = system.get("ManagedSystemID")
298
- console.print(f" [green]Created managed system ID: {system_id}[/green]")
298
+ err_console.print(f" [green]Created managed system ID: {system_id}[/green]")
299
299
 
300
300
  result["pws"] = {
301
301
  "created": True,
@@ -314,7 +314,7 @@ def _onboard_single_host(
314
314
  account_password = generate_password()
315
315
  generated_password = True
316
316
 
317
- console.print(f"[dim]PWS: Creating managed account '{account}'...[/dim]")
317
+ err_console.print(f"[dim]PWS: Creating managed account '{account}'...[/dim]")
318
318
  account_obj = pws.create_managed_account(
319
319
  system_id=system_id,
320
320
  account_name=account,
@@ -322,7 +322,7 @@ def _onboard_single_host(
322
322
  auto_management_flag=True if functional_account else False,
323
323
  )
324
324
  account_id = account_obj.get("ManagedAccountID")
325
- console.print(f" [green]Created managed account ID: {account_id}[/green]")
325
+ err_console.print(f" [green]Created managed account ID: {account_id}[/green]")
326
326
 
327
327
  if generated_password:
328
328
  console.print(f" [yellow]Generated password (save this!):[/yellow] [bold]{account_password}[/bold]")
@@ -339,7 +339,7 @@ def _onboard_single_host(
339
339
  pra_user = pra_username or ("ec2-admin" if jump_type == "shell" else None)
340
340
 
341
341
  if jump_type == "shell":
342
- console.print(f"[dim]PRA: Creating shell jump item '{name}'...[/dim]")
342
+ err_console.print(f"[dim]PRA: Creating shell jump item '{name}'...[/dim]")
343
343
  jump_item = pra.create_shell_jump(
344
344
  name=name,
345
345
  hostname=ip,
@@ -350,9 +350,9 @@ def _onboard_single_host(
350
350
  username=pra_user,
351
351
  )
352
352
  jump_id = jump_item.get("id")
353
- console.print(f" [green]Created shell jump ID: {jump_id}[/green]")
353
+ err_console.print(f" [green]Created shell jump ID: {jump_id}[/green]")
354
354
  else:
355
- console.print(f"[dim]PRA: Creating RDP jump item '{name}'...[/dim]")
355
+ err_console.print(f"[dim]PRA: Creating RDP jump item '{name}'...[/dim]")
356
356
  jump_item = pra.create_rdp_jump(
357
357
  name=name,
358
358
  hostname=ip,
@@ -361,7 +361,7 @@ def _onboard_single_host(
361
361
  rdp_port=port if port != 22 else 3389,
362
362
  )
363
363
  jump_id = jump_item.get("id")
364
- console.print(f" [green]Created RDP jump ID: {jump_id}[/green]")
364
+ err_console.print(f" [green]Created RDP jump ID: {jump_id}[/green]")
365
365
 
366
366
  result["pra"] = {
367
367
  "created": True,
@@ -489,7 +489,7 @@ def pasm_offboard(
489
489
  else:
490
490
  pws_asset_id = pws_system.get("AssetID")
491
491
  except Exception as e:
492
- console.print(f"[yellow]PWS search error: {e}[/yellow]")
492
+ err_console.print(f"[yellow]PWS search error: {e}[/yellow]")
493
493
 
494
494
  # Search PRA
495
495
  if not skip_pra:
@@ -512,7 +512,7 @@ def pasm_offboard(
512
512
  pra_jump_type = "rdp"
513
513
  break
514
514
  except Exception as e:
515
- console.print(f"[yellow]PRA search error: {e}[/yellow]")
515
+ err_console.print(f"[yellow]PRA search error: {e}[/yellow]")
516
516
 
517
517
  # Show what will be deleted
518
518
  console.print(f"\n[bold]Will delete '{name}':[/bold]")
@@ -541,7 +541,7 @@ def pasm_offboard(
541
541
 
542
542
  # Confirm
543
543
  if not force:
544
- confirm = typer.confirm("\nProceed with deletion?")
544
+ confirm = typer.confirm("\nProceed with deletion?", err=True)
545
545
  if not confirm:
546
546
  console.print("[yellow]Cancelled.[/yellow]")
547
547
  raise typer.Exit(0)
@@ -557,21 +557,21 @@ def pasm_offboard(
557
557
  for acc in pws_accounts:
558
558
  acc_id = acc.get("ManagedAccountID", acc.get("AccountId"))
559
559
  acc_name = acc.get("AccountName")
560
- console.print(f"[dim]PWS: Deleting account {acc_name}...[/dim]")
560
+ err_console.print(f"[dim]PWS: Deleting account {acc_name}...[/dim]")
561
561
  pws.delete_managed_account(acc_id)
562
562
  deleted_accounts.append({"id": acc_id, "name": acc_name})
563
- console.print(f" [green]Deleted account: {acc_name}[/green]")
563
+ err_console.print(f" [green]Deleted account: {acc_name}[/green]")
564
564
 
565
565
  # Delete system
566
- console.print(f"[dim]PWS: Deleting system {pws_system.get('SystemName')}...[/dim]")
566
+ err_console.print(f"[dim]PWS: Deleting system {pws_system.get('SystemName')}...[/dim]")
567
567
  pws.delete_managed_system(system_id)
568
- console.print(f" [green]Deleted system: {pws_system.get('SystemName')}[/green]")
568
+ err_console.print(f" [green]Deleted system: {pws_system.get('SystemName')}[/green]")
569
569
 
570
570
  # Delete asset
571
571
  if pws_asset_id:
572
- console.print(f"[dim]PWS: Deleting asset {pws_asset_id}...[/dim]")
572
+ err_console.print(f"[dim]PWS: Deleting asset {pws_asset_id}...[/dim]")
573
573
  pws.delete_asset(pws_asset_id)
574
- console.print(f" [green]Deleted asset ID: {pws_asset_id}[/green]")
574
+ err_console.print(f" [green]Deleted asset ID: {pws_asset_id}[/green]")
575
575
 
576
576
  result["pws"] = {
577
577
  "deleted": True,
@@ -585,12 +585,12 @@ def pasm_offboard(
585
585
  pra = get_pra_client()
586
586
  jump_id = pra_jump.get("id")
587
587
 
588
- console.print(f"[dim]PRA: Deleting {pra_jump_type} jump item {pra_jump.get('name')}...[/dim]")
588
+ err_console.print(f"[dim]PRA: Deleting {pra_jump_type} jump item {pra_jump.get('name')}...[/dim]")
589
589
  if pra_jump_type == "shell":
590
590
  pra.delete_shell_jump(jump_id)
591
591
  else:
592
592
  pra.delete_rdp_jump(jump_id)
593
- console.print(f" [green]Deleted {pra_jump_type} jump: {pra_jump.get('name')}[/green]")
593
+ err_console.print(f" [green]Deleted {pra_jump_type} jump: {pra_jump.get('name')}[/green]")
594
594
 
595
595
  result["pra"] = {
596
596
  "deleted": True,
@@ -650,7 +650,7 @@ def pasm_search(
650
650
  try:
651
651
  with get_pws_client() as pws:
652
652
  pws.authenticate()
653
- console.print(f"[dim]Searching PWS for '{query}'...[/dim]")
653
+ err_console.print(f"[dim]Searching PWS for '{query}'...[/dim]")
654
654
 
655
655
  systems = pws.list_managed_systems()
656
656
  result["pws"]["systems"] = [
@@ -661,12 +661,12 @@ def pasm_search(
661
661
  accounts = pws.list_managed_accounts(account_name=query)
662
662
  result["pws"]["accounts"] = accounts[:20]
663
663
  except Exception as e:
664
- console.print(f"[yellow]PWS search error: {e}[/yellow]")
664
+ err_console.print(f"[yellow]PWS search error: {e}[/yellow]")
665
665
 
666
666
  # Search PRA
667
667
  try:
668
668
  pra = get_pra_client()
669
- console.print(f"[dim]Searching PRA for '{query}'...[/dim]")
669
+ err_console.print(f"[dim]Searching PRA for '{query}'...[/dim]")
670
670
 
671
671
  shell_jumps = pra.list_shell_jumps()
672
672
  result["pra"]["shell_jumps"] = [
@@ -682,7 +682,7 @@ def pasm_search(
682
682
  or query_lower in j.get("hostname", "").lower()
683
683
  ][:20]
684
684
  except Exception as e:
685
- console.print(f"[yellow]PRA search error: {e}[/yellow]")
685
+ err_console.print(f"[yellow]PRA search error: {e}[/yellow]")
686
686
 
687
687
  if output == "json":
688
688
  console.print_json(json.dumps(result, default=str))
@@ -195,7 +195,9 @@ def confirm_action(message: str, default: bool = False) -> bool:
195
195
  True if confirmed, False otherwise
196
196
  """
197
197
  suffix = " [Y/n]" if default else " [y/N]"
198
- response = console.input(f"{message}{suffix} ").strip().lower()
198
+ # Prompt on stderr (input is still read from stdin) so a confirmation in a
199
+ # `-o json` flow doesn't land in the JSON document on stdout.
200
+ response = err_console.input(f"{message}{suffix} ").strip().lower()
199
201
 
200
202
  if not response:
201
203
  return default
@@ -35,7 +35,7 @@ def prompt_if_missing(
35
35
  The value (either original or prompted)
36
36
  """
37
37
  if value is None or value == "":
38
- raw = typer.prompt(prompt_text, type=value_type)
38
+ raw = typer.prompt(prompt_text, type=value_type, err=True)
39
39
  if value_type is str:
40
40
  return _clean_str(raw) # type: ignore[return-value]
41
41
  return raw
@@ -69,7 +69,7 @@ def prompt_from_list(
69
69
  item_id = item.get(id_key, "")
70
70
  item_name = item.get(name_key, "Unknown")
71
71
  console.print(f" {str(item_id):>{id_width}}: {item_name}")
72
- raw = typer.prompt(prompt_text, type=value_type)
72
+ raw = typer.prompt(prompt_text, type=value_type, err=True)
73
73
  if value_type is str:
74
74
  return _clean_str(raw) # type: ignore[return-value]
75
75
  return raw
@@ -95,11 +95,11 @@ def prompt_choice(
95
95
  marker = " (default)" if value == default else ""
96
96
  console.print(f" {value}: {desc}{marker}")
97
97
 
98
- result = typer.prompt("Choice", default=default or choices[0][0])
98
+ result = typer.prompt("Choice", default=default or choices[0][0], err=True)
99
99
  valid_values = [v for v, _ in choices]
100
100
  while result not in valid_values:
101
101
  console.print(f"[red]Invalid choice. Options: {', '.join(valid_values)}[/red]")
102
- result = typer.prompt("Choice", default=default or choices[0][0])
102
+ result = typer.prompt("Choice", default=default or choices[0][0], err=True)
103
103
  return result
104
104
 
105
105
 
@@ -148,7 +148,7 @@ def prompt_filtered_pick(
148
148
  "[dim]Type a number to pick, text to filter, n/p for next/prev page, blank to cancel[/dim]"
149
149
  )
150
150
 
151
- ans = typer.prompt(">", default="", show_default=False).strip()
151
+ ans = typer.prompt(">", default="", show_default=False, err=True).strip()
152
152
  if ans == "":
153
153
  raise typer.Abort()
154
154
  low = ans.lower()
@@ -201,7 +201,7 @@ def delete_bundle(
201
201
  ) -> None:
202
202
  """Delete a bundle."""
203
203
  if not confirm:
204
- typer.confirm(f"Are you sure you want to delete bundle {bundle_id}?", abort=True)
204
+ typer.confirm(f"Are you sure you want to delete bundle {bundle_id}?", abort=True, err=True)
205
205
 
206
206
  try:
207
207
  with get_client() as client:
@@ -53,7 +53,7 @@ def revoke_permission(
53
53
  ) -> None:
54
54
  """Revoke a permission."""
55
55
  if not confirm:
56
- typer.confirm(f"Are you sure you want to revoke permission {permission_id}?", abort=True)
56
+ typer.confirm(f"Are you sure you want to revoke permission {permission_id}?", abort=True, err=True)
57
57
 
58
58
  try:
59
59
  with get_client() as client:
@@ -141,7 +141,7 @@ def _pick_duration_interactive(
141
141
 
142
142
  def _prompt_justification() -> str:
143
143
  while True:
144
- text = typer.prompt("Justification (business reason)").strip()
144
+ text = typer.prompt("Justification (business reason)", err=True).strip()
145
145
  if 1 <= len(text) <= 2048:
146
146
  return text
147
147
  console.print("[red]Justification must be 1..2048 characters[/red]")
@@ -252,7 +252,7 @@ def create_access_request(
252
252
  )
253
253
 
254
254
  if not yes:
255
- typer.confirm("Submit this request?", default=True, abort=True)
255
+ typer.confirm("Submit this request?", default=True, abort=True, err=True)
256
256
 
257
257
  with get_client(user_token=True) as writer:
258
258
  result = writer.create_access_request(
@@ -6,7 +6,7 @@ import httpx
6
6
  import typer
7
7
 
8
8
  from ..client.base import get_client
9
- from ...core.output import console, print_table, print_json, print_error, print_api_error
9
+ from ...core.output import err_console, console, print_table, print_json, print_error, print_api_error
10
10
 
11
11
  app = typer.Typer(no_args_is_help=True, help="Manage resources")
12
12
 
@@ -98,7 +98,7 @@ def create_virtual_resource(
98
98
  role_name=role_name,
99
99
  )
100
100
  resource = result.get("result", result)
101
- console.print(f"Created virtual resource: {resource.get('name')} (ID: {resource.get('id')})")
101
+ err_console.print(f"Created virtual resource: {resource.get('name')} (ID: {resource.get('id')})")
102
102
  print_json(result)
103
103
  except httpx.HTTPStatusError as e:
104
104
  print_api_error(e, "create virtual resource")
@@ -135,7 +135,7 @@ def clear_commands(
135
135
  """
136
136
  from bt_cli.epml.client import get_client
137
137
  if not yes:
138
- typer.confirm(f"Clear all commands in cmdgrp {cmdgrp_id}?", abort=True)
138
+ typer.confirm(f"Clear all commands in cmdgrp {cmdgrp_id}?", abort=True, err=True)
139
139
  try:
140
140
  with get_client() as c:
141
141
  c.clear_cmdgrp_commands(cmdgrp_id, host_id=host)
@@ -139,7 +139,7 @@ def clear_hosts(
139
139
  """
140
140
  from bt_cli.epml.client import get_client
141
141
  if not yes:
142
- typer.confirm(f"Clear all hosts in hostgrp {hostgrp_id}?", abort=True)
142
+ typer.confirm(f"Clear all hosts in hostgrp {hostgrp_id}?", abort=True, err=True)
143
143
  try:
144
144
  with get_client() as c:
145
145
  c.clear_hostgrp_hosts(hostgrp_id, host_id=host)
@@ -218,7 +218,7 @@ def clear_tmdates(
218
218
  """
219
219
  from bt_cli.epml.client import get_client
220
220
  if not yes:
221
- typer.confirm(f"Clear all tmdates in tmdategrp {tmdategrp_id}?", abort=True)
221
+ typer.confirm(f"Clear all tmdates in tmdategrp {tmdategrp_id}?", abort=True, err=True)
222
222
  try:
223
223
  with get_client() as c:
224
224
  c.clear_tmdategrp_tmdates(tmdategrp_id, host_id=host)
@@ -139,7 +139,7 @@ def clear_users(
139
139
  """
140
140
  from bt_cli.epml.client import get_client
141
141
  if not yes:
142
- typer.confirm(f"Clear all users in usergrp {usergrp_id}?", abort=True)
142
+ typer.confirm(f"Clear all users in usergrp {usergrp_id}?", abort=True, err=True)
143
143
  try:
144
144
  with get_client() as c:
145
145
  c.clear_usergrp_users(usergrp_id, host_id=host)
@@ -199,7 +199,7 @@ def delete_policy(
199
199
  if not force:
200
200
  policy = client.get_policy(policy_id)
201
201
  policy_name = policy.get("name", policy_id)
202
- if not typer.confirm(f"Delete policy '{policy_name}'?"):
202
+ if not typer.confirm(f"Delete policy '{policy_name}'?", err=True):
203
203
  print_warning("Cancelled.")
204
204
  raise typer.Exit(0)
205
205
 
@@ -266,7 +266,7 @@ def revert_policy(
266
266
  if not policy.get("hasOpenDraft"):
267
267
  print_warning(f"Policy '{policy_name}' has no draft to revert.")
268
268
  raise typer.Exit(0)
269
- if not typer.confirm(f"Revert draft changes for policy '{policy_name}'?"):
269
+ if not typer.confirm(f"Revert draft changes for policy '{policy_name}'?", err=True):
270
270
  print_warning("Cancelled.")
271
271
  raise typer.Exit(0)
272
272
 
@@ -668,7 +668,7 @@ def delete_app_group(
668
668
  if not force:
669
669
  group = client.get_application_group(policy_id, app_group_id)
670
670
  group_name = group.get("name", app_group_id)
671
- if not typer.confirm(f"Delete application group '{group_name}'?"):
671
+ if not typer.confirm(f"Delete application group '{group_name}'?", err=True):
672
672
  print_warning("Cancelled.")
673
673
  raise typer.Exit(0)
674
674
 
@@ -127,7 +127,7 @@ def stale_computers(
127
127
  console.print()
128
128
  if not force:
129
129
  confirm = typer.confirm(
130
- f"Delete {len(stale_computers)} stale computer(s)?"
130
+ f"Delete {len(stale_computers)} stale computer(s)?", err=True
131
131
  )
132
132
  if not confirm:
133
133
  console.print("[yellow]Deletion cancelled.[/yellow]")
@@ -417,7 +417,7 @@ def move_computer(
417
417
  )
418
418
  console.print(table)
419
419
 
420
- selection = typer.prompt("Select computer number", type=int)
420
+ selection = typer.prompt("Select computer number", type=int, err=True)
421
421
  if selection < 1 or selection > len(computers):
422
422
  print_error(f"Invalid selection. Choose 1-{len(computers)}")
423
423
  raise typer.Exit(1)
@@ -469,7 +469,7 @@ def move_computer(
469
469
  )
470
470
  console.print(table)
471
471
 
472
- selection = typer.prompt("Select target group number", type=int)
472
+ selection = typer.prompt("Select target group number", type=int, err=True)
473
473
  if selection < 1 or selection > len(groups):
474
474
  print_error(f"Invalid selection. Choose 1-{len(groups)}")
475
475
  raise typer.Exit(1)
@@ -490,7 +490,7 @@ def move_computer(
490
490
  console.print(f"[bold]From group:[/bold] {current_group}")
491
491
  console.print(f"[bold]To group:[/bold] {group_name}")
492
492
  console.print()
493
- confirm = typer.confirm("Proceed with move?")
493
+ confirm = typer.confirm("Proceed with move?", err=True)
494
494
  if not confirm:
495
495
  console.print("[yellow]Cancelled.[/yellow]")
496
496
  raise typer.Exit(0)
@@ -132,7 +132,7 @@ def delete_group_policy(
132
132
  from rich.console import Console
133
133
 
134
134
  if not yes:
135
- if not typer.confirm(f"Delete Group Policy {policy_id}?"):
135
+ if not typer.confirm(f"Delete Group Policy {policy_id}?", err=True):
136
136
  raise typer.Exit(0)
137
137
  try:
138
138
  get_client().delete_group_policy(policy_id)
@@ -122,7 +122,7 @@ def delete_jump_client(
122
122
  from bt_cli.pra.client import get_client
123
123
 
124
124
  if not force:
125
- typer.confirm(f"Delete jump client {client_id}?", abort=True)
125
+ typer.confirm(f"Delete jump client {client_id}?", abort=True, err=True)
126
126
 
127
127
  try:
128
128
  client = get_client()
@@ -262,7 +262,7 @@ def delete_shell_jump(
262
262
  return
263
263
 
264
264
  if not force:
265
- typer.confirm(f"Delete shell jump '{item.get('name')}' (ID: {item_id})?", abort=True)
265
+ typer.confirm(f"Delete shell jump '{item.get('name')}' (ID: {item_id})?", abort=True, err=True)
266
266
 
267
267
  client.delete_shell_jump(item_id)
268
268
  print_success(f"Deleted shell jump '{item.get('name')}' (ID: {item_id})")
@@ -458,7 +458,7 @@ def delete_rdp_jump(
458
458
  return
459
459
 
460
460
  if not force:
461
- typer.confirm(f"Delete RDP jump '{item.get('name')}' (ID: {item_id})?", abort=True)
461
+ typer.confirm(f"Delete RDP jump '{item.get('name')}' (ID: {item_id})?", abort=True, err=True)
462
462
 
463
463
  client.delete_rdp_jump(item_id)
464
464
  print_success(f"Deleted RDP jump '{item.get('name')}' (ID: {item_id})")
@@ -705,7 +705,7 @@ def delete_web_jump(
705
705
  return
706
706
 
707
707
  if not force:
708
- typer.confirm(f"Delete web jump '{item.get('name')}' (ID: {item_id})?", abort=True)
708
+ typer.confirm(f"Delete web jump '{item.get('name')}' (ID: {item_id})?", abort=True, err=True)
709
709
 
710
710
  client.delete_web_jump(item_id)
711
711
  print_success(f"Deleted web jump '{item.get('name')}' (ID: {item_id})")
@@ -941,7 +941,7 @@ def delete_protocol_tunnel(
941
941
  return
942
942
 
943
943
  if not force:
944
- typer.confirm(f"Delete protocol tunnel '{item.get('name')}' (ID: {item_id})?", abort=True)
944
+ typer.confirm(f"Delete protocol tunnel '{item.get('name')}' (ID: {item_id})?", abort=True, err=True)
945
945
 
946
946
  client.delete_protocol_tunnel(item_id)
947
947
  print_success(f"Deleted protocol tunnel '{item.get('name')}' (ID: {item_id})")