bt-cli 0.4.70__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.70 → bt_cli-0.4.72}/.github/workflows/ci.yml +3 -1
  2. {bt_cli-0.4.70 → bt_cli-0.4.72}/CLAUDE.md +5 -3
  3. {bt_cli-0.4.70 → bt_cli-0.4.72}/PKG-INFO +2 -2
  4. bt_cli-0.4.72/bt-cli-cheatsheet.html +229 -0
  5. {bt_cli-0.4.70 → bt_cli-0.4.72}/pyproject.toml +5 -1
  6. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/__init__.py +1 -1
  7. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/commands/learn.py +1 -1
  8. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/commands/quick.py +31 -31
  9. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/config.py +40 -11
  10. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/output.py +3 -1
  11. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/prompts.py +5 -5
  12. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/entitle/SKILL.md +52 -0
  13. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/client/base.py +75 -3
  14. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/__init__.py +3 -0
  15. bt_cli-0.4.72/src/bt_cli/entitle/commands/audit.py +139 -0
  16. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/bundles.py +1 -1
  17. bt_cli-0.4.72/src/bt_cli/entitle/commands/jit.py +137 -0
  18. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/permissions.py +1 -1
  19. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/requests.py +2 -2
  20. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/resources.py +2 -2
  21. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_cmdgrps.py +1 -1
  22. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_hostgrps.py +1 -1
  23. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_tmdategrps.py +1 -1
  24. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_usergrps.py +1 -1
  25. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/policies.py +3 -3
  26. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/quick.py +4 -4
  27. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/group_policies.py +1 -1
  28. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/jump_clients.py +1 -1
  29. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/jump_items.py +4 -4
  30. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/quick.py +16 -16
  31. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/users.py +2 -2
  32. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/vault.py +3 -3
  33. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/accounts.py +3 -3
  34. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/assets.py +1 -1
  35. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/attributes.py +2 -2
  36. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/clouds.py +2 -2
  37. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/databases.py +1 -1
  38. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/functional.py +4 -4
  39. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/quick.py +65 -65
  40. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/search.py +2 -2
  41. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/secrets.py +10 -10
  42. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/sessions.py +3 -3
  43. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/systems.py +4 -4
  44. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/workgroups.py +1 -1
  45. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/conftest.py +40 -0
  46. bt_cli-0.4.72/tests/core/test_json_stdout_purity.py +304 -0
  47. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/entitle/test_client.py +59 -8
  48. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/entitle/test_commands.py +113 -0
  49. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/epml/test_commands.py +20 -20
  50. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/conftest.py +5 -1
  51. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pf/test_commands.py +15 -15
  52. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pra/test_jump_item_fixes.py +10 -2
  53. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pws/test_commands.py +24 -13
  54. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/secrets/test_commands.py +40 -40
  55. {bt_cli-0.4.70 → bt_cli-0.4.72}/.claude/skills/bt/SKILL.md +0 -0
  56. {bt_cli-0.4.70 → bt_cli-0.4.72}/.claude/skills/entitle/SKILL.md +0 -0
  57. {bt_cli-0.4.70 → bt_cli-0.4.72}/.claude/skills/epml/SKILL.md +0 -0
  58. {bt_cli-0.4.70 → bt_cli-0.4.72}/.claude/skills/epmw/SKILL.md +0 -0
  59. {bt_cli-0.4.70 → bt_cli-0.4.72}/.claude/skills/pra/SKILL.md +0 -0
  60. {bt_cli-0.4.70 → bt_cli-0.4.72}/.claude/skills/pws/SKILL.md +0 -0
  61. {bt_cli-0.4.70 → bt_cli-0.4.72}/.env.example +0 -0
  62. {bt_cli-0.4.70 → bt_cli-0.4.72}/.github/workflows/release.yml +0 -0
  63. {bt_cli-0.4.70 → bt_cli-0.4.72}/.gitignore +0 -0
  64. {bt_cli-0.4.70 → bt_cli-0.4.72}/README.md +0 -0
  65. {bt_cli-0.4.70 → bt_cli-0.4.72}/assets/cli-help.png +0 -0
  66. {bt_cli-0.4.70 → bt_cli-0.4.72}/assets/cli-output.png +0 -0
  67. {bt_cli-0.4.70 → bt_cli-0.4.72}/bt-cli.spec +0 -0
  68. {bt_cli-0.4.70 → bt_cli-0.4.72}/bt_entry.py +0 -0
  69. {bt_cli-0.4.70 → bt_cli-0.4.72}/epml-clients-server-side-filters-plan.md +0 -0
  70. {bt_cli-0.4.70 → bt_cli-0.4.72}/epml-implementation-plan.md +0 -0
  71. {bt_cli-0.4.70 → bt_cli-0.4.72}/pf-implementation-plan.md +0 -0
  72. {bt_cli-0.4.70 → bt_cli-0.4.72}/scripts/bt_entry.py +0 -0
  73. {bt_cli-0.4.70 → bt_cli-0.4.72}/scripts/pf_onboard.py +0 -0
  74. {bt_cli-0.4.70 → bt_cli-0.4.72}/scripts/sync-package-data.sh +0 -0
  75. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/cli.py +0 -0
  76. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/commands/__init__.py +0 -0
  77. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/commands/configure.py +0 -0
  78. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/__init__.py +0 -0
  79. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/auth.py +0 -0
  80. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/client.py +0 -0
  81. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/config_file.py +0 -0
  82. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/csv_utils.py +0 -0
  83. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/errors.py +0 -0
  84. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/core/rest_debug.py +0 -0
  85. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/CLAUDE.md +0 -0
  86. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/__init__.py +0 -0
  87. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  88. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/epml/SKILL.md +0 -0
  89. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  90. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/pf/SKILL.md +0 -0
  91. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
  92. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
  93. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/secrets/SKILL.md +0 -0
  94. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/data/skills/session-analysis/SKILL.md +0 -0
  95. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/__init__.py +0 -0
  96. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/client/__init__.py +0 -0
  97. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/accounts.py +0 -0
  98. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/applications.py +0 -0
  99. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/auth.py +0 -0
  100. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/integrations.py +0 -0
  101. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/policies.py +0 -0
  102. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/roles.py +0 -0
  103. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/users.py +0 -0
  104. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/commands/workflows.py +0 -0
  105. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/__init__.py +0 -0
  106. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/bundle.py +0 -0
  107. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/common.py +0 -0
  108. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/integration.py +0 -0
  109. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/permission.py +0 -0
  110. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/policy.py +0 -0
  111. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/resource.py +0 -0
  112. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/role.py +0 -0
  113. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/user.py +0 -0
  114. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/entitle/models/workflow.py +0 -0
  115. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/__init__.py +0 -0
  116. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/client/__init__.py +0 -0
  117. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/client/base.py +0 -0
  118. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/__init__.py +0 -0
  119. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/audit.py +0 -0
  120. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/auth.py +0 -0
  121. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/client_pkg.py +0 -0
  122. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/clients.py +0 -0
  123. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/external_apis.py +0 -0
  124. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/hosts.py +0 -0
  125. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/iolog.py +0 -0
  126. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/license.py +0 -0
  127. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/quick.py +0 -0
  128. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
  129. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
  130. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
  131. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
  132. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
  133. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/settings.py +0 -0
  134. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/siems.py +0 -0
  135. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/commands/users.py +0 -0
  136. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epml/models/__init__.py +0 -0
  137. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/__init__.py +0 -0
  138. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/client/__init__.py +0 -0
  139. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/client/base.py +0 -0
  140. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/__init__.py +0 -0
  141. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/audits.py +0 -0
  142. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/auth.py +0 -0
  143. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/computers.py +0 -0
  144. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/events.py +0 -0
  145. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/groups.py +0 -0
  146. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/requests.py +0 -0
  147. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/roles.py +0 -0
  148. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/tasks.py +0 -0
  149. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/commands/users.py +0 -0
  150. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/epmw/models/__init__.py +0 -0
  151. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/__init__.py +0 -0
  152. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/client/__init__.py +0 -0
  153. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/client/base.py +0 -0
  154. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/commands/__init__.py +0 -0
  155. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/commands/auth.py +0 -0
  156. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/commands/machines.py +0 -0
  157. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/commands/tokens.py +0 -0
  158. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/commands/user.py +0 -0
  159. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pf/commands/users.py +0 -0
  160. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/__init__.py +0 -0
  161. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/client/__init__.py +0 -0
  162. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/client/base.py +0 -0
  163. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/__init__.py +0 -0
  164. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/auth.py +0 -0
  165. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/import_export.py +0 -0
  166. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  167. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  168. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/policies.py +0 -0
  169. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/commands/teams.py +0 -0
  170. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/__init__.py +0 -0
  171. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/common.py +0 -0
  172. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/group_policy.py +0 -0
  173. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/jump_client.py +0 -0
  174. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/jump_group.py +0 -0
  175. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/jump_item.py +0 -0
  176. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/jumpoint.py +0 -0
  177. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/team.py +0 -0
  178. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/user.py +0 -0
  179. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pra/models/vault.py +0 -0
  180. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/__init__.py +0 -0
  181. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/client/__init__.py +0 -0
  182. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/client/base.py +0 -0
  183. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  184. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  185. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/__init__.py +0 -0
  186. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/audit.py +0 -0
  187. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/auth.py +0 -0
  188. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/config.py +0 -0
  189. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/credentials.py +0 -0
  190. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/directories.py +0 -0
  191. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/import_export.py +0 -0
  192. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/platforms.py +0 -0
  193. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/commands/users.py +0 -0
  194. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/config.py +0 -0
  195. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/models/__init__.py +0 -0
  196. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/models/account.py +0 -0
  197. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/models/asset.py +0 -0
  198. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/models/common.py +0 -0
  199. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/pws/models/system.py +0 -0
  200. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/__init__.py +0 -0
  201. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/client/__init__.py +0 -0
  202. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/client/base.py +0 -0
  203. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/__init__.py +0 -0
  204. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/_hints.py +0 -0
  205. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/auth.py +0 -0
  206. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/dynamic.py +0 -0
  207. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/folders.py +0 -0
  208. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/integrations.py +0 -0
  209. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/leases.py +0 -0
  210. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/commands/static.py +0 -0
  211. {bt_cli-0.4.70 → bt_cli-0.4.72}/src/bt_cli/secrets/models/__init__.py +0 -0
  212. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/__init__.py +0 -0
  213. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/core/__init__.py +0 -0
  214. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/core/test_auth.py +0 -0
  215. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/core/test_config.py +0 -0
  216. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/core/test_config_file.py +0 -0
  217. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/core/test_errors.py +0 -0
  218. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/core/test_output.py +0 -0
  219. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/core/test_rest_debug.py +0 -0
  220. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/entitle/__init__.py +0 -0
  221. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/entitle-smoke-test.sh +0 -0
  222. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/epml/__init__.py +0 -0
  223. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/epml/test_client.py +0 -0
  224. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/epmw/__init__.py +0 -0
  225. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/epmw/test_client.py +0 -0
  226. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/epmw/test_commands.py +0 -0
  227. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/epmw-quick-test-plan.md +0 -0
  228. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/fixtures/__init__.py +0 -0
  229. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/fixtures/responses.py +0 -0
  230. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/__init__.py +0 -0
  231. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/helpers.py +0 -0
  232. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/test_entitle_integration.py +0 -0
  233. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/test_epmw_integration.py +0 -0
  234. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/test_epmw_lifecycle.py +0 -0
  235. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/test_pra_integration.py +0 -0
  236. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/test_pra_lifecycle.py +0 -0
  237. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/test_pws_integration.py +0 -0
  238. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/integration/test_pws_lifecycle.py +0 -0
  239. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pf/__init__.py +0 -0
  240. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pf/test_client.py +0 -0
  241. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pra/__init__.py +0 -0
  242. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pra/test_client.py +0 -0
  243. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pra/test_commands.py +0 -0
  244. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pra-smoke-test.sh +0 -0
  245. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pra-test-plan.md +0 -0
  246. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pws/__init__.py +0 -0
  247. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pws/test_client.py +0 -0
  248. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pws/test_quick_sessions.py +0 -0
  249. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pws-quick-test-plan.md +0 -0
  250. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/pws-smoke-test.sh +0 -0
  251. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/secrets/__init__.py +0 -0
  252. {bt_cli-0.4.70 → bt_cli-0.4.72}/tests/secrets/test_client.py +0 -0
@@ -100,7 +100,9 @@ jobs:
100
100
  pip install -e ".[test]"
101
101
 
102
102
  - name: Run integration tests
103
- run: pytest tests/integration -v -m integration
103
+ # --integration is the opt-in these tests now require; without it they
104
+ # skip, since credentials alone are no longer the trigger.
105
+ run: pytest tests/integration -v -m integration --integration
104
106
  env:
105
107
  BT_PWS_API_URL: ${{ secrets.BT_PWS_API_URL }}
106
108
  BT_PWS_CLIENT_ID: ${{ secrets.BT_PWS_CLIENT_ID }}
@@ -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.70**
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
 
@@ -42,7 +42,7 @@ Use these slash commands for detailed product guidance:
42
42
  | Cross-product | `bt quick` | `pasm-onboard`, `pasm-offboard`, `pasm-search` |
43
43
  | Password Safe | `bt pws` | `systems`, `accounts`, `credentials`, `sessions`, `secrets`, `audit`, `quick` |
44
44
  | PRA | `bt pra` | `jump-items`, `vault`, `jump-groups`, `quick` |
45
- | Entitle | `bt entitle` | `integrations`, `resources`, `bundles`, `permissions` |
45
+ | Entitle | `bt entitle` | `integrations`, `resources`, `bundles`, `permissions`, `jit`, `audit` |
46
46
  | EPM Windows | `bt epmw` | `computers`, `groups`, `policies`, `requests`, `quick` |
47
47
  | EPM Linux | `bt epml` | `rbp` (cmdgrps/hostgrps/usergrps/tmdategrps/roles/policy/tests/tx), `settings`, `users`, `audit`, `siems`, `quick` |
48
48
  | Secrets API | `bt secrets` | `folders`, `static`, `dynamic` (incl. `generate`), `leases`, `integrations` |
@@ -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).
@@ -92,6 +92,8 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
92
92
  - **PWS session recording download = headless replay under `/pbsm/replay`** (there is **no** video-export API — `?video/?mp4/?webm` return JSON, not media). Flow: `GET /Sessions/{id}` for `Token`+`RecordKey`+`Protocol` → `POST /pbsm/replay` `{id:Token, record_key, protocol:"RDP"|"SSH", headless:true}` → `PUT /pbsm/replay/{rid}` `{offset:<ms>}` (seek) → `GET /pbsm/replay/{rid}?png=<scale>` (one PNG per offset) → `DELETE /pbsm/replay/{rid}`. **Warm-up:** the first `PUT` 404s (`"no such file or directory"`) until the renderer loads — retry ~0.5s. The seek response is state `{offset, next, eof, duration}`; **`next` = next recorded frame's offset**, enabling event-driven stepping. **`bt pws sessions analyze <id>` (or `--dir`)** preps a replay dump for LLM analysis without calling an LLM: reads `index.json`, selects **keyframes** (RDP frames whose PNG byte-size changed ≥`--threshold`%, dependency-free — no Pillow, UNION the frame that renders each **typed keystroke burst**, anchored via the recording `StartTime` → each keyframe tagged `screen-change`/`after-typing` with the `typed` text, so the model reads targeted spots not every frame), and prints a token/USD **cost estimate** (`--model opus|sonnet|haiku`) for focused (keyframes) vs all-frames; writes `analysis.json`. SSH → recommends the transcript over frames. Consumed by the `/session-analysis` skill (interactive; the model reads only the keyframes). Surfaced as `bt pws sessions replay <id>`. **Default = even coverage + dedup**: ~60 evenly-spaced frames across the whole session (`--frames N`) with consecutive-identical frames skipped (`--dedup`, sha1-based) — because event-driven over-samples RDP (it records many deltas as the screen paints, so `next`-stepping clusters at the login and burns the frame budget before covering the timeline). Modes are mutually exclusive: `--frames` (default) / `--fps` / `--interval` / `--all-frames` (event-driven forensic). Also `--from/--to`, `--scale`, `--max-frames`, `--no-dedup`; `--mp4` stitches via ffmpeg, else prints the command + install hint. Always `DELETE`s the replay in `finally`.
93
93
  - Entitle permissions: Use server-side filters (`--resource`, `--user`, `--integration`) - dataset can be 20k+ records
94
94
  - Entitle users: Use `--all` flag only when needed - default fetches first page only
95
+ - **Entitle tokens are region-bound**: a token minted in one region 401s `"The token received doesn't exist"` on the others. Servers: `api.entitle.io`, `api.us.entitle.io`, `api.ca.entitle.io` — set `BT_ENTITLE_API_URL`/`entitle.api_url` to the tenant's region (that misleading 401 also = wrong-region config, not just a dead token). **No `GET /users/{id}`** — `users get` resolves via `list_users(search=...)`.
96
+ - **Entitle `jit` / `audit`** (spec-derived, live-verified): `bt entitle jit list` wraps `GET /eligibleJitAccess` — with an org token `--user` is **required** (else 400), and the response is `{roles,bundles,pagination}` not the `result` envelope. `bt entitle audit search` wraps `POST /auditLogs/search` (Experimental) — **date window must be < 24h** (server 400s on ≥24h; CLI enforces client-side, default `--hours 24`), no pagination (returns all rows in range), each row's context is one of `integration`/`idp`/`ticket`/`user`. Full spec at `/projects/reference/entitle-api-reference.openapi.json`.
95
97
  - Windows builds: Rich must be pinned to `<14.0.0` (Rich 14 has PyInstaller unicode issues)
96
98
  - **EPML URL transform**: spec paths start with `/api/...` but real URL is `https://api.beyondtrust.io/site/<site-id>/epm/linux/<spec-path-minus-/api/>`. Encapsulated in client `_build_url` — never bypass it.
97
99
  - **EPML two authorizers**: spec claims `nomine-authorizer` (PAT) globally but the gateway has IAM-only routes (return 403 with AWS-flavored body). PAT cannot reach those. Documented per-op in `src/bt_cli/data/skills/epml/SKILL.md`. Where `/v6/pbul/rbp/...` exists alongside legacy `/pbul/{hostid}/rbp/...`, prefer legacy — v6 is mostly IAM-only.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: bt-cli
3
- Version: 0.4.70
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
@@ -0,0 +1,229 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>BT-CLI Demo Flow — Working Commands (pf922288)</title>
6
+ <style>
7
+ :root{
8
+ --bg:#0d1117;
9
+ --panel:#151b23;
10
+ --border:#2a323d;
11
+ --border-strong:#3a4452;
12
+ --text:#e6edf3;
13
+ --text-secondary:#9aa7b5;
14
+ --text-muted:#6b7684;
15
+ --accent:#3ea6ff;
16
+ --green:#3fb950;
17
+ --amber:#d29922;
18
+ --tag-bg:#1c2733;
19
+ --mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
20
+ --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
21
+ }
22
+ *{box-sizing:border-box}
23
+ body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:17px;margin:0;padding:38px 22px 72px}
24
+ .wrap{max-width:960px;margin:0 auto}
25
+ h1{font-size:30px;font-weight:600;margin:0 0 6px}
26
+ .sub{font-size:16px;color:var(--text-muted);margin:0 0 24px;line-height:1.5}
27
+ .legend{display:flex;flex-wrap:wrap;gap:18px;font-size:15px;color:var(--text-secondary);margin:0 0 26px;padding:15px 18px;border:1px solid var(--border);border-radius:8px;background:var(--panel)}
28
+ .legend span{display:inline-flex;align-items:center;gap:7px}
29
+ .dot{width:10px;height:10px;border-radius:50%}
30
+ .dot.v{background:var(--green)} .dot.s{background:var(--amber)} .dot.r{background:var(--accent)}
31
+ .refbox{border:1px solid var(--border-strong);border-radius:8px;padding:16px 18px;margin:0 0 30px;background:#0a0e14;font-family:var(--mono);font-size:15px;line-height:1.85;color:var(--text-secondary)}
32
+ .refbox b{color:var(--text)}
33
+ .step{font-size:14px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin:32px 0 12px;font-weight:600}
34
+ .step:first-of-type{margin-top:0}
35
+ .row{border:1px solid var(--border);border-radius:8px;padding:16px 18px;margin-bottom:12px;background:var(--panel)}
36
+ .row.state{border-color:#4a3d1a}
37
+ .hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:10px}
38
+ .lbl{font-size:17px;font-weight:500;color:var(--text)}
39
+ .tags{display:flex;gap:7px;flex-shrink:0}
40
+ .tag{font-size:13px;font-weight:600;letter-spacing:.03em;color:var(--accent);background:var(--tag-bg);border:1px solid var(--border-strong);border-radius:4px;padding:3px 10px;white-space:nowrap}
41
+ .tag.v{color:var(--green);border-color:#264a30}
42
+ .tag.s{color:var(--amber);border-color:#4a3d1a}
43
+ .cmdrow{display:flex;align-items:stretch;gap:9px;margin-bottom:7px}
44
+ .cmd{flex:1;font-family:var(--mono);font-size:15.5px;line-height:1.6;color:#c9e8ff;background:#0a0e14;border:1px solid var(--border);border-radius:6px;padding:11px 14px;overflow-x:auto;white-space:pre}
45
+ .cpy{flex-shrink:0;width:44px;font-size:18px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:1px solid var(--border-strong);background:transparent;cursor:pointer;color:var(--text-secondary);transition:background .12s,color .12s}
46
+ .cpy:hover{background:var(--tag-bg);color:var(--text)}
47
+ .cpy.copied{color:var(--green);border-color:var(--green)}
48
+ .note{font-size:15px;color:var(--text-muted);margin-top:8px;line-height:1.6}
49
+ .note b{color:var(--text-secondary)}
50
+ .note code{font-family:var(--mono);font-size:14px;color:#c9e8ff}
51
+ .flag{border:1px dashed var(--border-strong);border-radius:8px;padding:16px 18px;font-size:16px;color:var(--text-secondary);line-height:1.7}
52
+ .flag b{color:var(--text)}
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <div class="wrap">
57
+ <h1>BT-CLI demo flow — working commands</h1>
58
+ <p class="sub">Verified live against tenant pf922288, 01 Jul 2026. Real IDs baked in. Ephemeral IDs (request/session) are captured at runtime, not hardcoded.</p>
59
+
60
+ <div class="legend">
61
+ <span><span class="dot r"></span>tag = docs bucket (BT-CLI / API)</span>
62
+ <span><span class="dot v"></span><b style="color:var(--green)">VERIFIED</b> — ran live, non-destructive, safe to demo</span>
63
+ <span><span class="dot s"></span><b style="color:var(--amber)">STATE-CHANGING</b> — correct syntax, changes/creates/deletes; run deliberately</span>
64
+ </div>
65
+
66
+ <div class="refbox">
67
+ <b>Real values (pf922288)</b><br>
68
+ system ad.upm.academy .......... ID <b>19</b> (Active Directory)<br>
69
+ system linhost2 ................ ID <b>6</b> (Linux — used for the create demo)<br>
70
+ account svc_app ................. ID <b>18119</b> (exists; credential test PASSED)<br>
71
+ safe GitHub - BTSolution-Engineering ... <b>9b56a95b-0a8a-4be6-de3c-08dd5118ea68</b><br>
72
+ folder IaC-Demo ............... <b>bec2694b-994a-47cd-07c2-08de5e7a5e2d</b><br>
73
+ group SecretsSafe_Access ..... ID <b>9</b> (Dev_Ansible=10, API_Users=11)<br>
74
+ secret path ................... <b>GitHub - BTSolution-Engineering/IaC-Demo/AWS</b><br>
75
+ perm flags ..................... None, Read, Create, Edit, Delete, Share, Manage
76
+ </div>
77
+
78
+ <div class="step">Setup — run once per session</div>
79
+ <div class="row">
80
+ <div class="hd"><span class="lbl">Session setup</span><span class="tags"><span class="tag v">VERIFIED</span></span></div>
81
+ <div class="cmdrow"><div class="cmd" id="c0">source .venv/bin/activate</div><button class="cpy" onclick="cp('c0',this)">⧉</button></div>
82
+ <div class="cmdrow"><div class="cmd" id="c2">bt whoami</div><button class="cpy" onclick="cp('c2',this)">⧉</button></div>
83
+ <div class="note">Credentials load from <b>~/.bt-cli/config.yaml</b> — no <code>source .env</code> needed. <code>bt whoami</code> should show Password Safe = Connected (user NH-Ansible, ID 120).</div>
84
+ </div>
85
+ <div class="row">
86
+ <div class="hd"><span class="lbl">Show the underlying REST call (demo toggle)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
87
+ <div class="cmdrow"><div class="cmd" id="cR">bt --show-rest pws accounts get 18119</div><button class="cpy" onclick="cp('cR',this)">⧉</button></div>
88
+ <div class="note">Prepend <b><code>bt --show-rest</code></b> to <b>any</b> command to print the REST request + response panels (method, URL, headers, body) — auth headers auto-redacted. Great for proving each command hits the public API. Also works as env: <code>export BT_SHOW_REST=1</code>.</div>
89
+ </div>
90
+
91
+ <div class="step">1 · Create account or secret — BT-CLI</div>
92
+ <div class="row state">
93
+ <div class="hd"><span class="lbl">Create managed account (local, on a non-directory system)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
94
+ <div class="cmdrow"><div class="cmd" id="c3">bt pws accounts create --system 6 --name "svc_demo01" --password 'PASSWORD' --change-frequency xdays --change-days 1 --change-time 02:00</div><button class="cpy" onclick="cp('c3',this)">⧉</button></div>
95
+ <div class="note"><b>--system takes the integer ID</b> (6 = linhost2, Linux). Sub a real PASSWORD. Note: directory accounts (like svc_app on ad.upm.academy) require a UserPrincipalName and are onboarded via <b>discovery</b>, not this manual create — so the create demo uses a local system.</div>
96
+ </div>
97
+ <div class="row state">
98
+ <div class="hd"><span class="lbl">Create secret (Secrets Safe)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
99
+ <div class="cmdrow"><div class="cmd" id="c4">bt pws secrets secrets create --folder bec2694b-994a-47cd-07c2-08de5e7a5e2d --title "db-admin-demo" --username admin --password 'PASSWORD'</div><button class="cpy" onclick="cp('c4',this)">⧉</button></div>
100
+ <div class="note">--folder is a folder <b>GUID</b> (IaC-Demo). Creates a real secret in that folder.</div>
101
+ </div>
102
+
103
+ <div class="step">2 · Assign policy, ownership, permissions — API</div>
104
+ <div class="row">
105
+ <div class="hd"><span class="lbl">List permission flags</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
106
+ <div class="cmdrow"><div class="cmd" id="c5">bt pws secrets safes permissions-options</div><button class="cpy" onclick="cp('c5',this)">⧉</button></div>
107
+ </div>
108
+ <div class="row">
109
+ <div class="hd"><span class="lbl">Show current grants on a safe</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
110
+ <div class="cmdrow"><div class="cmd" id="c6">bt pws secrets safes permissions 9b56a95b-0a8a-4be6-de3c-08dd5118ea68</div><button class="cpy" onclick="cp('c6',this)">⧉</button></div>
111
+ </div>
112
+ <div class="row state">
113
+ <div class="hd"><span class="lbl">Grant permissions to a group</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
114
+ <div class="cmdrow"><div class="cmd" id="c7">bt pws secrets safes grant 9b56a95b-0a8a-4be6-de3c-08dd5118ea68 --group 9 --permissions "Read, Create, Edit, Delete, Share, Manage"</div><button class="cpy" onclick="cp('c7',this)">⧉</button></div>
115
+ <div class="note"><b>Flags aren't freely combinable</b> — the server validates the whole set. Valid combos (verified live): <code>Read</code> · <code>Read, Edit</code> · <code>Read, Create, Edit, Delete, Share</code> · <code>Read, Create, Edit, Delete, Share, Manage</code> (full owner). <b>Manage only works with the full set</b> — <code>Read,Edit,Manage</code> is rejected as an "Invalid permission combination".</div>
116
+ </div>
117
+ <div class="row state">
118
+ <div class="hd"><span class="lbl">Revoke all permissions for a group</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
119
+ <div class="cmdrow"><div class="cmd" id="c8">bt pws secrets safes revoke 9b56a95b-0a8a-4be6-de3c-08dd5118ea68 --group 9</div><button class="cpy" onclick="cp('c8',this)">⧉</button></div>
120
+ </div>
121
+
122
+ <div class="step">3 · Trigger verification and rotation — BT-CLI</div>
123
+ <div class="row">
124
+ <div class="hd"><span class="lbl">Verify stored credential (non-destructive)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag v">VERIFIED</span></span></div>
125
+ <div class="cmdrow"><div class="cmd" id="c9">bt pws quick test -s "ad.upm.academy" -a svc_app</div><button class="cpy" onclick="cp('c9',this)">⧉</button></div>
126
+ <div class="note">Ran live → <b>Credential test PASSED for svc_app@ad.upm.academy</b>. Quick commands accept the system <b>name</b>.</div>
127
+ </div>
128
+ <div class="row state">
129
+ <div class="hd"><span class="lbl">Rotate now</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
130
+ <div class="cmdrow"><div class="cmd" id="c10">bt pws quick rotate -s "ad.upm.academy" -a svc_app</div><button class="cpy" onclick="cp('c10',this)">⧉</button></div>
131
+ <div class="note">Fires the platform rotation/propagation. Changes the live password.</div>
132
+ </div>
133
+
134
+ <div class="step">4 · Submit and approve access request — API</div>
135
+ <div class="row state">
136
+ <div class="hd"><span class="lbl">Submit checkout request (capture the request ID)</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
137
+ <div class="cmdrow"><div class="cmd" id="c11">bt pws credentials checkout --system "ad.upm.academy" --account "svc_app" --reason "demo" -o json</div><button class="cpy" onclick="cp('c11',this)">⧉</button></div>
138
+ <div class="note">Returns a <b>RequestID</b> in the JSON. <b>Requires the account to be under an access policy with a schedule your identity can use</b> — otherwise you get <code>"Could not find a schedule to use with this release request."</code> If so, use <b>quick checkout</b> (step 5), which lists only accounts you can actually request.</div>
139
+ </div>
140
+ <div class="row">
141
+ <div class="hd"><span class="lbl">List pending requests</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
142
+ <div class="cmdrow"><div class="cmd" id="c12">bt pws credentials list --status pending</div><button class="cpy" onclick="cp('c12',this)">⧉</button></div>
143
+ <div class="note">Ran live (currently "No requests found"). Also: <code>--all</code>, <code>--status approved</code>.</div>
144
+ </div>
145
+ <div class="row state">
146
+ <div class="hd"><span class="lbl">Approve a pending request</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
147
+ <div class="cmdrow"><div class="cmd" id="c13">bt pws credentials approve REQUEST_ID</div><button class="cpy" onclick="cp('c13',this)">⧉</button></div>
148
+ <div class="note">REQUEST_ID is the integer from the checkout above / from <code>credentials list</code>.</div>
149
+ </div>
150
+
151
+ <div class="step">5 · Retrieve or broker access — API</div>
152
+ <div class="row state">
153
+ <div class="hd"><span class="lbl">Quick checkout — the easy path (request + approve + retrieve)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
154
+ <div class="cmdrow"><div class="cmd" id="c14b">bt pws quick checkout</div><button class="cpy" onclick="cp('c14b',this)">⧉</button></div>
155
+ <div class="note"><b>Run with no args to go interactive</b> — it lists only the accounts you're actually allowed to request, you pick one, and it does request → approve → show password in one step. This sidesteps the access-policy/schedule issue above.</div>
156
+ </div>
157
+ <div class="row state">
158
+ <div class="hd"><span class="lbl">Quick checkout — scripted, to a variable</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
159
+ <div class="cmdrow"><div class="cmd" id="c14">PW=$(bt pws quick checkout -s "SYSTEM" -a "ACCOUNT" --raw)</div><button class="cpy" onclick="cp('c14',this)">⧉</button></div>
160
+ <div class="note">Same flow, non-interactive. Fill SYSTEM/ACCOUNT with one shown in the interactive list. <code>echo "$PW"</code> to view.</div>
161
+ </div>
162
+ <div class="row">
163
+ <div class="hd"><span class="lbl">Get a Secrets Safe secret value</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
164
+ <div class="cmdrow"><div class="cmd" id="c15">bt pws quick get-secret "GitHub - BTSolution-Engineering/IaC-Demo/AWS" --raw</div><button class="cpy" onclick="cp('c15',this)">⧉</button></div>
165
+ <div class="note">Path is <b>Safe/Folder/SecretName</b>. Resolved live to Secret "AWS". Drop <code>--raw</code> for a table view.</div>
166
+ </div>
167
+
168
+ <div class="step">6 · Revoke access — API</div>
169
+ <div class="row state">
170
+ <div class="hd"><span class="lbl">Check in + rotate on release</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
171
+ <div class="cmdrow"><div class="cmd" id="c16">bt pws credentials checkin REQUEST_ID --rotate</div><button class="cpy" onclick="cp('c16',this)">⧉</button></div>
172
+ <div class="note">REQUEST_ID is an integer (from step 4/5).</div>
173
+ </div>
174
+ <div class="row">
175
+ <div class="hd"><span class="lbl">List active sessions (capture the session ID)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
176
+ <div class="cmdrow"><div class="cmd" id="c17">bt pws sessions list --active</div><button class="cpy" onclick="cp('c17',this)">⧉</button></div>
177
+ <div class="note">Ran live (currently "No sessions found"). The <b>ID</b> column is the SESSION_ID (integer) below.</div>
178
+ </div>
179
+ <div class="row state">
180
+ <div class="hd"><span class="lbl">Suspend session (resumable)</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
181
+ <div class="cmdrow"><div class="cmd" id="c18">bt pws sessions suspend SESSION_ID</div><button class="cpy" onclick="cp('c18',this)">⧉</button></div>
182
+ </div>
183
+ <div class="row state">
184
+ <div class="hd"><span class="lbl">Terminate session (permanent)</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
185
+ <div class="cmdrow"><div class="cmd" id="c19">bt pws sessions terminate SESSION_ID --force</div><button class="cpy" onclick="cp('c19',this)">⧉</button></div>
186
+ </div>
187
+
188
+ <div class="step">7 · Offboard the account — BT-CLI</div>
189
+ <div class="row state">
190
+ <div class="hd"><span class="lbl">Offboard system (removes system + accounts + asset)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
191
+ <div class="cmdrow"><div class="cmd" id="c20">bt pws quick offboard -s "ad.upm.academy"</div><button class="cpy" onclick="cp('c20',this)">⧉</button></div>
192
+ <div class="note"><b>Destructive:</b> deletes ad.upm.academy (ID 19) and all its accounts incl. svc_app. Don't run unless you mean it.</div>
193
+ </div>
194
+
195
+ <div class="step">8 · Export audit evidence — API</div>
196
+ <div class="row">
197
+ <div class="hd"><span class="lbl">Full evidence export (JSON)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
198
+ <div class="cmdrow"><div class="cmd" id="c21">bt pws audit export --file ~/evidence.json</div><button class="cpy" onclick="cp('c21',this)">⧉</button></div>
199
+ <div class="note">Defaults to BOTH requests and sessions when no flag is given. <b>--file must be a writable path</b> — the CLI fetches the evidence then writes the file, so a "Permission denied" here means the directory isn't writable (use <code>~/</code> or <code>/tmp</code>, not a read-only checkout dir).</div>
200
+ </div>
201
+ <div class="row">
202
+ <div class="hd"><span class="lbl">Approved requests (CSV)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
203
+ <div class="cmdrow"><div class="cmd" id="c22">bt pws audit export --requests --status approved --file ~/svc_app-evidence.csv --format csv</div><button class="cpy" onclick="cp('c22',this)">⧉</button></div>
204
+ </div>
205
+ <div class="row">
206
+ <div class="hd"><span class="lbl">Session evidence (CSV)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
207
+ <div class="cmdrow"><div class="cmd" id="c23">bt pws audit export --sessions --file ~/sessions.csv --format csv</div><button class="cpy" onclick="cp('c23',this)">⧉</button></div>
208
+ </div>
209
+
210
+ <div class="step">9-10 · Integrations + API auth — out of scope</div>
211
+ <div class="flag">
212
+ <b>ServiceNow / SailPoint</b> and <b>scoped-token API auth</b> (certs, rate limits, bulk ops) are UI/console config, not bt-cli commands — nothing to copy/paste here.
213
+ </div>
214
+ </div>
215
+
216
+ <script>
217
+ function cp(id, btn){
218
+ const text = document.getElementById(id).innerText;
219
+ const done = () => { const o = btn.textContent; btn.textContent='✓'; btn.classList.add('copied'); setTimeout(()=>{btn.textContent=o;btn.classList.remove('copied');},1200); };
220
+ if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done).catch(()=>fallbackCopy(text,done));
221
+ else fallbackCopy(text,done);
222
+ }
223
+ function fallbackCopy(text, done){
224
+ const ta=document.createElement('textarea'); ta.value=text; ta.style.position='fixed'; ta.style.opacity='0';
225
+ document.body.appendChild(ta); ta.select(); try{document.execCommand('copy');}catch(e){} document.body.removeChild(ta); done();
226
+ }
227
+ </script>
228
+ </body>
229
+ </html>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bt-cli"
7
- version = "0.4.70"
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"
@@ -75,3 +75,7 @@ packages = ["src/bt_cli"]
75
75
  [tool.pytest.ini_options]
76
76
  asyncio_mode = "auto"
77
77
  testpaths = ["tests"]
78
+ # Test the working tree, not whatever bt_cli happens to be installed in
79
+ # site-packages. Without this a non-editable `pip install bt-cli` shadows
80
+ # src/ and the suite silently tests a stale copy at the same version number.
81
+ pythonpath = ["src"]
@@ -1,3 +1,3 @@
1
1
  """BeyondTrust Unified Admin CLI."""
2
2
 
3
- __version__ = "0.4.70"
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))