bt-cli 0.4.58__tar.gz → 0.4.59__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 (248) hide show
  1. {bt_cli-0.4.58 → bt_cli-0.4.59}/CLAUDE.md +5 -1
  2. {bt_cli-0.4.58 → bt_cli-0.4.59}/PKG-INFO +1 -1
  3. {bt_cli-0.4.58 → bt_cli-0.4.59}/pyproject.toml +1 -1
  4. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/__init__.py +1 -1
  5. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/errors.py +49 -0
  6. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/output.py +23 -11
  7. bt_cli-0.4.59/src/bt_cli/data/skills/pra/SKILL.md +216 -0
  8. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/client/base.py +117 -12
  9. bt_cli-0.4.59/src/bt_cli/pra/commands/import_export.py +578 -0
  10. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/jump_groups.py +5 -3
  11. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/jump_items.py +83 -5
  12. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/vault.py +10 -2
  13. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_errors.py +20 -0
  14. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_output.py +6 -1
  15. bt_cli-0.4.59/tests/pra/test_jump_item_fixes.py +344 -0
  16. bt_cli-0.4.58/src/bt_cli/data/skills/pra/SKILL.md +0 -150
  17. bt_cli-0.4.58/src/bt_cli/pra/commands/import_export.py +0 -442
  18. {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/bt/SKILL.md +0 -0
  19. {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/entitle/SKILL.md +0 -0
  20. {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/epml/SKILL.md +0 -0
  21. {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/epmw/SKILL.md +0 -0
  22. {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/pra/SKILL.md +0 -0
  23. {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/pws/SKILL.md +0 -0
  24. {bt_cli-0.4.58 → bt_cli-0.4.59}/.env.example +0 -0
  25. {bt_cli-0.4.58 → bt_cli-0.4.59}/.github/workflows/ci.yml +0 -0
  26. {bt_cli-0.4.58 → bt_cli-0.4.59}/.github/workflows/release.yml +0 -0
  27. {bt_cli-0.4.58 → bt_cli-0.4.59}/.gitignore +0 -0
  28. {bt_cli-0.4.58 → bt_cli-0.4.59}/README.md +0 -0
  29. {bt_cli-0.4.58 → bt_cli-0.4.59}/assets/cli-help.png +0 -0
  30. {bt_cli-0.4.58 → bt_cli-0.4.59}/assets/cli-output.png +0 -0
  31. {bt_cli-0.4.58 → bt_cli-0.4.59}/bt-cli.spec +0 -0
  32. {bt_cli-0.4.58 → bt_cli-0.4.59}/bt_entry.py +0 -0
  33. {bt_cli-0.4.58 → bt_cli-0.4.59}/epml-clients-server-side-filters-plan.md +0 -0
  34. {bt_cli-0.4.58 → bt_cli-0.4.59}/epml-implementation-plan.md +0 -0
  35. {bt_cli-0.4.58 → bt_cli-0.4.59}/pf-implementation-plan.md +0 -0
  36. {bt_cli-0.4.58 → bt_cli-0.4.59}/scripts/bt_entry.py +0 -0
  37. {bt_cli-0.4.58 → bt_cli-0.4.59}/scripts/pf_onboard.py +0 -0
  38. {bt_cli-0.4.58 → bt_cli-0.4.59}/scripts/sync-package-data.sh +0 -0
  39. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/cli.py +0 -0
  40. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/commands/__init__.py +0 -0
  41. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/commands/configure.py +0 -0
  42. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/commands/learn.py +0 -0
  43. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/commands/quick.py +0 -0
  44. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/__init__.py +0 -0
  45. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/auth.py +0 -0
  46. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/client.py +0 -0
  47. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/config.py +0 -0
  48. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/config_file.py +0 -0
  49. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/csv_utils.py +0 -0
  50. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/prompts.py +0 -0
  51. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/rest_debug.py +0 -0
  52. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/CLAUDE.md +0 -0
  53. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/__init__.py +0 -0
  54. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  55. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
  56. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/epml/SKILL.md +0 -0
  57. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  58. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/pf/SKILL.md +0 -0
  59. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
  60. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/secrets/SKILL.md +0 -0
  61. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/__init__.py +0 -0
  62. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/client/__init__.py +0 -0
  63. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/client/base.py +0 -0
  64. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/__init__.py +0 -0
  65. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/accounts.py +0 -0
  66. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/applications.py +0 -0
  67. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/auth.py +0 -0
  68. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/bundles.py +0 -0
  69. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/integrations.py +0 -0
  70. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/permissions.py +0 -0
  71. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/policies.py +0 -0
  72. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/requests.py +0 -0
  73. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/resources.py +0 -0
  74. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/roles.py +0 -0
  75. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/users.py +0 -0
  76. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/workflows.py +0 -0
  77. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/__init__.py +0 -0
  78. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/bundle.py +0 -0
  79. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/common.py +0 -0
  80. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/integration.py +0 -0
  81. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/permission.py +0 -0
  82. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/policy.py +0 -0
  83. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/resource.py +0 -0
  84. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/role.py +0 -0
  85. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/user.py +0 -0
  86. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/workflow.py +0 -0
  87. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/__init__.py +0 -0
  88. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/client/__init__.py +0 -0
  89. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/client/base.py +0 -0
  90. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/__init__.py +0 -0
  91. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/audit.py +0 -0
  92. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/auth.py +0 -0
  93. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/client_pkg.py +0 -0
  94. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/clients.py +0 -0
  95. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/external_apis.py +0 -0
  96. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/hosts.py +0 -0
  97. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/iolog.py +0 -0
  98. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/license.py +0 -0
  99. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/quick.py +0 -0
  100. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
  101. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
  102. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
  103. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
  104. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
  105. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
  106. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -0
  107. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
  108. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_usergrps.py +0 -0
  109. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/settings.py +0 -0
  110. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/siems.py +0 -0
  111. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/users.py +0 -0
  112. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/models/__init__.py +0 -0
  113. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/__init__.py +0 -0
  114. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/client/__init__.py +0 -0
  115. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/client/base.py +0 -0
  116. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/__init__.py +0 -0
  117. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/audits.py +0 -0
  118. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/auth.py +0 -0
  119. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/computers.py +0 -0
  120. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/events.py +0 -0
  121. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/groups.py +0 -0
  122. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/policies.py +0 -0
  123. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/quick.py +0 -0
  124. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/requests.py +0 -0
  125. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/roles.py +0 -0
  126. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/tasks.py +0 -0
  127. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/users.py +0 -0
  128. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/models/__init__.py +0 -0
  129. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/__init__.py +0 -0
  130. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/client/__init__.py +0 -0
  131. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/client/base.py +0 -0
  132. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/__init__.py +0 -0
  133. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/auth.py +0 -0
  134. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/machines.py +0 -0
  135. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/tokens.py +0 -0
  136. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/user.py +0 -0
  137. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/users.py +0 -0
  138. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/__init__.py +0 -0
  139. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/client/__init__.py +0 -0
  140. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/__init__.py +0 -0
  141. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/auth.py +0 -0
  142. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/group_policies.py +0 -0
  143. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/jump_clients.py +0 -0
  144. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  145. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/policies.py +0 -0
  146. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/quick.py +0 -0
  147. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/teams.py +0 -0
  148. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/users.py +0 -0
  149. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/__init__.py +0 -0
  150. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/common.py +0 -0
  151. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/group_policy.py +0 -0
  152. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/jump_client.py +0 -0
  153. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/jump_group.py +0 -0
  154. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/jump_item.py +0 -0
  155. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/jumpoint.py +0 -0
  156. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/team.py +0 -0
  157. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/user.py +0 -0
  158. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/vault.py +0 -0
  159. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/__init__.py +0 -0
  160. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/client/__init__.py +0 -0
  161. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/client/base.py +0 -0
  162. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  163. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  164. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/__init__.py +0 -0
  165. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/accounts.py +0 -0
  166. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/assets.py +0 -0
  167. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/attributes.py +0 -0
  168. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/audit.py +0 -0
  169. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/auth.py +0 -0
  170. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/clouds.py +0 -0
  171. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/config.py +0 -0
  172. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/credentials.py +0 -0
  173. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/databases.py +0 -0
  174. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/directories.py +0 -0
  175. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/functional.py +0 -0
  176. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/import_export.py +0 -0
  177. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/platforms.py +0 -0
  178. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/quick.py +0 -0
  179. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/search.py +0 -0
  180. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/secrets.py +0 -0
  181. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/sessions.py +0 -0
  182. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/systems.py +0 -0
  183. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/users.py +0 -0
  184. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/workgroups.py +0 -0
  185. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/config.py +0 -0
  186. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/__init__.py +0 -0
  187. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/account.py +0 -0
  188. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/asset.py +0 -0
  189. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/common.py +0 -0
  190. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/system.py +0 -0
  191. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/__init__.py +0 -0
  192. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/client/__init__.py +0 -0
  193. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/client/base.py +0 -0
  194. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/__init__.py +0 -0
  195. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/_hints.py +0 -0
  196. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/auth.py +0 -0
  197. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/dynamic.py +0 -0
  198. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/folders.py +0 -0
  199. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/integrations.py +0 -0
  200. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/leases.py +0 -0
  201. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/static.py +0 -0
  202. {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/models/__init__.py +0 -0
  203. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/__init__.py +0 -0
  204. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/conftest.py +0 -0
  205. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/__init__.py +0 -0
  206. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_auth.py +0 -0
  207. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_config.py +0 -0
  208. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_config_file.py +0 -0
  209. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_rest_debug.py +0 -0
  210. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/entitle/__init__.py +0 -0
  211. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/entitle/test_client.py +0 -0
  212. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/entitle/test_commands.py +0 -0
  213. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/entitle-smoke-test.sh +0 -0
  214. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epml/__init__.py +0 -0
  215. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epml/test_client.py +0 -0
  216. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epml/test_commands.py +0 -0
  217. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epmw/__init__.py +0 -0
  218. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epmw/test_client.py +0 -0
  219. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epmw/test_commands.py +0 -0
  220. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epmw-quick-test-plan.md +0 -0
  221. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/fixtures/__init__.py +0 -0
  222. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/fixtures/responses.py +0 -0
  223. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/__init__.py +0 -0
  224. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/conftest.py +0 -0
  225. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/helpers.py +0 -0
  226. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_entitle_integration.py +0 -0
  227. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_epmw_integration.py +0 -0
  228. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_epmw_lifecycle.py +0 -0
  229. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_pra_integration.py +0 -0
  230. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_pra_lifecycle.py +0 -0
  231. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_pws_integration.py +0 -0
  232. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_pws_lifecycle.py +0 -0
  233. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pf/__init__.py +0 -0
  234. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pf/test_client.py +0 -0
  235. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pf/test_commands.py +0 -0
  236. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra/__init__.py +0 -0
  237. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra/test_client.py +0 -0
  238. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra/test_commands.py +0 -0
  239. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra-smoke-test.sh +0 -0
  240. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra-test-plan.md +0 -0
  241. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws/__init__.py +0 -0
  242. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws/test_client.py +0 -0
  243. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws/test_commands.py +0 -0
  244. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws-quick-test-plan.md +0 -0
  245. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws-smoke-test.sh +0 -0
  246. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/secrets/__init__.py +0 -0
  247. {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/secrets/test_client.py +0 -0
  248. {bt_cli-0.4.58 → bt_cli-0.4.59}/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.58**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.59**
4
4
 
5
5
  ## Setup
6
6
 
@@ -81,6 +81,10 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
81
81
  - PRA K8s tunnels: Require Linux jumpoint
82
82
  - PWS assets: Must create via workgroup endpoint
83
83
  - ECM integration: PWS system name must match PRA jump item name
84
+ - **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
+ - **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.
86
+ - **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.
87
+ - **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).
84
88
  - **PWS `/Sessions` ignores `limit`/`offset`** and returns the full session *history* every call — never `paginate()` it (infinite loop); `list_sessions` does a single GET and `bt pws sessions list` caps display client-side. `Status` codes (live-confirmed): 0=Not started, 1=Active, 2=Completed, 5=Locked, 7=Terminated; `--active`=Status 1. Active sessions have `EndTime`==`StartTime` (placeholder, not null) — identify live sessions by Status, not timestamps. **`terminate` = `POST /Sessions/{id}/Terminate`** (symmetric w/ `/Lock`); `/Sessions/{id}` is GET/PUT only (DELETE → 405).
85
89
  - Entitle permissions: Use server-side filters (`--resource`, `--user`, `--integration`) - dataset can be 20k+ records
86
90
  - Entitle users: Use `--all` flag only when needed - default fetches first page only
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bt-cli
3
- Version: 0.4.58
3
+ Version: 0.4.59
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.58"
7
+ version = "0.4.59"
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.58"
3
+ __version__ = "0.4.59"
@@ -121,6 +121,44 @@ HTTP_STATUS_MESSAGES = {
121
121
  }
122
122
 
123
123
 
124
+ def _format_field_errors(response: dict) -> Optional[str]:
125
+ """Flatten an API's field-level validation errors into one readable string.
126
+
127
+ Looks for the common ``errors`` / ``field_errors`` / ``validationErrors``
128
+ containers, which map a field name to either a message string or a list of
129
+ message strings. Returns e.g. ``"username: only allowed when tunnel_type is
130
+ mssql; database: only allowed when tunnel_type is mssql"`` or None if no
131
+ field errors are present.
132
+
133
+ Args:
134
+ response: The (already sanitized) parsed JSON response body
135
+
136
+ Returns:
137
+ A flattened "field: message" string, or None
138
+ """
139
+ for key in ("errors", "field_errors", "fieldErrors", "validationErrors"):
140
+ container = response.get(key)
141
+ if not container:
142
+ continue
143
+
144
+ parts: list[str] = []
145
+ if isinstance(container, dict):
146
+ for field, messages in container.items():
147
+ if isinstance(messages, (list, tuple)):
148
+ joined = ", ".join(str(m) for m in messages)
149
+ else:
150
+ joined = str(messages)
151
+ parts.append(f"{field}: {joined}" if field else joined)
152
+ elif isinstance(container, (list, tuple)):
153
+ parts.extend(str(m) for m in container)
154
+ else:
155
+ parts.append(str(container))
156
+
157
+ if parts:
158
+ return "; ".join(parts)
159
+ return None
160
+
161
+
124
162
  def get_http_error_message(error: httpx.HTTPStatusError) -> str:
125
163
  """Map HTTP status error to user-friendly message.
126
164
 
@@ -161,6 +199,17 @@ def get_http_error_message(error: httpx.HTTPStatusError) -> str:
161
199
  detail = err.get("message")
162
200
  elif isinstance(err, str):
163
201
  detail = err
202
+
203
+ # Append field-level validation details. Many APIs (e.g. PRA Config
204
+ # API) return a generic top-level "Validation Failed" message plus an
205
+ # "errors" object keyed by field name, e.g.:
206
+ # {"message": "Validation Failed",
207
+ # "errors": {"username": ["only allowed when tunnel_type is mssql"]}}
208
+ # Surfacing those field messages is what lets a user self-diagnose.
209
+ if isinstance(sanitized_response, dict):
210
+ field_errors = _format_field_errors(sanitized_response)
211
+ if field_errors:
212
+ detail = f"{detail} ({field_errors})" if detail else field_errors
164
213
  except Exception:
165
214
  # Response may not be JSON
166
215
  pass
@@ -19,12 +19,21 @@ class OutputFormat(str, Enum):
19
19
  JSON = "json"
20
20
 
21
21
 
22
- # Shared console instance
22
+ # Shared console instances.
23
+ #
24
+ # `console` is for machine-consumable output (JSON, tables, raw values) and
25
+ # writes to stdout. `err_console` is for human-facing status/diagnostics
26
+ # (success/error/warning/info banners) and writes to stderr.
27
+ #
28
+ # Keeping these separate is what lets `-o json` produce *pure* JSON on stdout:
29
+ # a "Created X (ID: N)" status line goes to stderr, so `... -o json | jq` and
30
+ # `json.load(stdin)` see only the JSON document.
23
31
  console = Console()
32
+ err_console = Console(stderr=True)
24
33
 
25
34
 
26
35
  def print_json(data: Any) -> None:
27
- """Print data as formatted JSON.
36
+ """Print data as formatted JSON to stdout.
28
37
 
29
38
  Args:
30
39
  data: Any JSON-serializable data
@@ -137,39 +146,42 @@ def _format_value(value: Any) -> str:
137
146
 
138
147
 
139
148
  def print_success(message: str) -> None:
140
- """Print a success message.
149
+ """Print a success message to stderr.
150
+
151
+ Status/diagnostic output goes to stderr so it never pollutes stdout data
152
+ (e.g. ``-o json`` output that may be piped into a parser).
141
153
 
142
154
  Args:
143
155
  message: Message to display
144
156
  """
145
- console.print(f"[green]{escape(message)}[/green]")
157
+ err_console.print(f"[green]{escape(message)}[/green]")
146
158
 
147
159
 
148
160
  def print_error(message: str) -> None:
149
- """Print an error message.
161
+ """Print an error message to stderr.
150
162
 
151
163
  Args:
152
164
  message: Error message to display
153
165
  """
154
- console.print(f"[red]Error:[/red] {escape(message)}")
166
+ err_console.print(f"[red]Error:[/red] {escape(message)}")
155
167
 
156
168
 
157
169
  def print_warning(message: str) -> None:
158
- """Print a warning message.
170
+ """Print a warning message to stderr.
159
171
 
160
172
  Args:
161
173
  message: Warning message to display
162
174
  """
163
- console.print(f"[yellow]Warning:[/yellow] {escape(message)}")
175
+ err_console.print(f"[yellow]Warning:[/yellow] {escape(message)}")
164
176
 
165
177
 
166
178
  def print_info(message: str) -> None:
167
- """Print an info message.
179
+ """Print an info message to stderr.
168
180
 
169
181
  Args:
170
182
  message: Info message to display
171
183
  """
172
- console.print(f"[blue]{escape(message)}[/blue]")
184
+ err_console.print(f"[blue]{escape(message)}[/blue]")
173
185
 
174
186
 
175
187
  def confirm_action(message: str, default: bool = False) -> bool:
@@ -203,4 +215,4 @@ def print_api_error(error: Exception, operation: str) -> None:
203
215
  operation: Description of the operation that failed (e.g., "list systems")
204
216
  """
205
217
  message = handle_api_error(error, operation)
206
- console.print(f"[red]Error:[/red] {escape(message)}")
218
+ err_console.print(f"[red]Error:[/red] {escape(message)}")
@@ -0,0 +1,216 @@
1
+ ---
2
+ name: pra
3
+ description: Privileged Remote Access commands for jump items, vault accounts, and remote sessions. Use when working with PRA shell jumps, RDP, protocol tunnels, or SSH CA authentication.
4
+ ---
5
+
6
+ # PRA Commands (`bt pra`)
7
+
8
+ ## IMPORTANT: Destructive Operations
9
+
10
+ **ALWAYS confirm with the user before:**
11
+ - `bt pra jump-items shell delete` - Deletes shell jump item
12
+ - `bt pra jump-items rdp delete` - Deletes RDP jump item
13
+ - `bt pra jump-items web delete` - Deletes web jump item
14
+ - `bt pra jump-items tunnel delete` - Deletes protocol tunnel
15
+ - `bt pra jump-groups delete` - Deletes jump group
16
+ - `bt pra vault accounts delete` - Deletes vault account
17
+
18
+ List affected resources first, then ask for explicit confirmation.
19
+
20
+ ## Quick Commands
21
+
22
+ ```bash
23
+ # Vault credential checkout
24
+ bt pra quick vault # Interactive - shows accounts, prompts
25
+ bt pra quick vault -n "server-admin"
26
+ bt pra quick vault -n postgres --raw
27
+
28
+ # Search jump items and vault
29
+ bt pra quick search axion
30
+ bt pra quick search admin -o json
31
+ ```
32
+
33
+ ## Jump Items
34
+
35
+ ### Shell Jump (SSH/Telnet)
36
+
37
+ ```bash
38
+ bt pra jump-items shell list
39
+ bt pra jump-items shell get <jump_item_id>
40
+ bt pra jump-items shell create \
41
+ --name "web-server-01" \
42
+ --hostname "10.0.1.50" \
43
+ --jumpoint <jumpoint_id> \
44
+ --jump-group <jump_group_id> \
45
+ --protocol ssh \
46
+ --port 22 \
47
+ --username "admin"
48
+ bt pra jump-items shell delete <jump_item_id>
49
+ ```
50
+
51
+ ### RDP Jump
52
+
53
+ ```bash
54
+ bt pra jump-items rdp list
55
+ bt pra jump-items rdp get <jump_item_id>
56
+ bt pra jump-items rdp create \
57
+ --name "win-server-01" \
58
+ --hostname "10.0.2.10" \
59
+ --jumpoint <jumpoint_id> \
60
+ --jump-group <jump_group_id> \
61
+ --port 3389
62
+ ```
63
+
64
+ ### Web Jump
65
+
66
+ ```bash
67
+ bt pra jump-items web list
68
+ bt pra jump-items web get <jump_item_id>
69
+ bt pra jump-items web create \
70
+ --name "Grafana" \
71
+ --url "https://grafana.example.com" \
72
+ --jumpoint <jumpoint_id> \
73
+ --jump-group <jump_group_id> \
74
+ --username-format "{username}" \
75
+ --authentication-timeout 30
76
+ # --no-verify-certificate to skip TLS verification
77
+ ```
78
+
79
+ ### Protocol Tunnels (TCP/MSSQL/PostgreSQL/MySQL/K8s)
80
+
81
+ ```bash
82
+ bt pra jump-items tunnel list
83
+
84
+ # TCP — port pairs via --ports
85
+ bt pra jump-items tunnel create -n tcp-jump -h 10.0.0.1 -j <jp> -g <jg> \
86
+ --type tcp --ports "22;24;26;28"
87
+
88
+ # MSSQL — username/database are settable
89
+ bt pra jump-items tunnel create -n sql -h 10.0.0.3 -j <jp> -g <jg> \
90
+ --type mssql --username sa --database demo
91
+
92
+ # PostgreSQL / MySQL — created BARE (see limitation below)
93
+ bt pra jump-items tunnel create -n pg -h 10.0.0.2 -j <jp> -g <jg> --type psql
94
+
95
+ # K8s — requires Linux jumpoint
96
+ bt pra jump-items tunnel create -n production-k8s -h k8s-api.example.com \
97
+ -j <jp> -g <jg> --type k8s \
98
+ --url "https://k8s-api.example.com:6443" --ca-cert "$(cat /path/to/ca.crt)"
99
+ ```
100
+
101
+ **Per-type field gating (CLI sends only what the endpoint accepts):**
102
+
103
+ | tunnel_type | type-specific fields |
104
+ |-------------|----------------------|
105
+ | tcp | `--ports` (tunnel_definitions — **listen;destination pairs**, even count: `"5432;5432"` or `"22;24;26;28"`; an odd count 400s) |
106
+ | mssql | `--username`, `--database` |
107
+ | psql/mysql | none — created bare |
108
+ | k8s | `--url`, `--ca-cert` |
109
+
110
+ **psql/mysql limitation:** the public Config API has no working write path for a
111
+ PostgreSQL/MySQL tunnel's username/database (POST rejects them, PATCH no-ops, PUT
112
+ 405s) — even though they appear populated on GET. The CLI ignores
113
+ `--username`/`--database` for those types (with a warning) and creates the tunnel
114
+ bare. Set the user/db afterward in the `/login` web UI.
115
+
116
+ ## Jump Groups
117
+
118
+ ```bash
119
+ bt pra jump-groups list
120
+ bt pra jump-groups get <group_id>
121
+ bt pra jump-groups create \
122
+ --name "Customer-05" \
123
+ --code-name customer05 \
124
+ --comments "New customer environment"
125
+ bt pra jump-groups delete <group_id>
126
+ ```
127
+
128
+ ## Vault Accounts
129
+
130
+ ```bash
131
+ bt pra vault accounts list
132
+ bt pra vault accounts get <account_id>
133
+ bt pra vault accounts checkout <account_id>
134
+ bt pra vault accounts checkin <account_id>
135
+ bt pra vault accounts rotate <account_id>
136
+
137
+ # SSH CA - get public key for provisioning
138
+ bt pra vault accounts get-public-key <ssh_ca_account_id>
139
+
140
+ # Associate a vault account with a jump item (credential injection)
141
+ bt pra vault accounts associate <account_id> -j <jump_item_id> -t shell_jump
142
+ # --type: shell_jump | remote_rdp | remote_vnc | web_jump | protocol_tunnel
143
+ ```
144
+
145
+ Direct per-item association works for shells, RDP, VNC and web jumps. For
146
+ protocol tunnels, `-t protocol_tunnel` is accepted **only for MSSQL (SQL Server)
147
+ tunnels** — the server rejects psql/mysql/tcp/k8s with *"Only SQL Server Tunnels
148
+ are allowed."* (live-verified). For those, wire the credential via tag/criteria
149
+ association on the vault account instead.
150
+
151
+ ## SSH CA Authentication
152
+
153
+ PRA supports SSH CA for ephemeral access - no static keys on hosts.
154
+
155
+ ```bash
156
+ # Find SSH CA vault accounts
157
+ bt pra vault accounts list | grep -i ssh
158
+
159
+ # Get CA public key (ready for authorized_keys)
160
+ bt pra vault accounts get-public-key <ssh_ca_account_id>
161
+ # Output: cert-authority ssh-rsa AAAAB3NzaC1yc2E...
162
+
163
+ # Provision EC2 with SSH CA
164
+ PRA_CA_KEY=$(bt pra vault accounts get-public-key <ssh_ca_account_id>)
165
+ # Embed in user-data script for EC2
166
+ ```
167
+
168
+ ## CSV Import/Export
169
+
170
+ `export jump-items` dumps your **actual** jump items of every type (shell, rdp,
171
+ vnc, web, tunnel) with all type-specific columns, so the file round-trips back
172
+ through `import jump-items` as a faithful copy. Pass `--sample` for a blank
173
+ one-row-per-type template instead.
174
+
175
+ ```bash
176
+ bt pra export jump-items --file jump-items.csv # real export (all types)
177
+ bt pra export jump-items --file template.csv --sample # blank template
178
+ bt pra import jump-items --file jump-items.csv --dry-run
179
+ bt pra import jump-items --file jump-items.csv
180
+ # Override jumpoint/jump-group for every row:
181
+ bt pra import jump-items --file jump-items.csv --jumpoint 3 --jump-group 24
182
+ ```
183
+
184
+ CSV columns (superset; blanks where N/A): `type, name, hostname, jumpoint_id,
185
+ jump_group_id, username, port, protocol, domain, tag, comments, url,
186
+ tunnel_type, tunnel_definitions, database, verify_certificate, username_format,
187
+ authentication_timeout, ca_certificates`. `type` ∈ {shell, rdp, vnc, web, tunnel}.
188
+
189
+ ## Discover IDs
190
+
191
+ ```bash
192
+ # Find jumpoint IDs
193
+ bt pra jumpoints list
194
+
195
+ # Find jump group IDs
196
+ bt pra jump-groups list
197
+
198
+ # Find vault account IDs
199
+ bt pra vault accounts list
200
+
201
+ # Find vault account group IDs
202
+ bt pra vault groups list
203
+ ```
204
+
205
+ ## API Notes
206
+
207
+ - Base path: `/api/config/v1`
208
+ - Pagination: `per_page`/`current_page` (1-indexed)
209
+ - Response: Array directly (pagination in headers)
210
+ - Jump item types have separate endpoints
211
+ - K8s tunnels require Linux jumpoint
212
+ - **`-o json` is pure JSON on stdout** — status banners ("Created ... (ID: N)")
213
+ go to **stderr**, so `... -o json | jq` and `json.load(stdin)` are safe.
214
+ - **API validation detail is surfaced**: a 4xx with an `errors` object
215
+ (e.g. `{"username": ["only allowed when tunnel_type is 'mssql'"]}`) is shown
216
+ field-by-field in the CLI error, not collapsed to "Validation Failed".
@@ -457,6 +457,38 @@ class PRAClient:
457
457
  params["hostname"] = hostname
458
458
  return self.get_paginated("/jump-item/remote-vnc", params)
459
459
 
460
+ def create_vnc_jump(
461
+ self,
462
+ name: str,
463
+ hostname: str,
464
+ jumpoint_id: int,
465
+ jump_group_id: int,
466
+ port: int = 5900,
467
+ tag: Optional[str] = None,
468
+ comments: Optional[str] = None,
469
+ ) -> Dict[str, Any]:
470
+ """Create a Remote VNC Jump item."""
471
+ data = {
472
+ "name": name,
473
+ "hostname": hostname,
474
+ "jumpoint_id": jumpoint_id,
475
+ "jump_group_id": jump_group_id,
476
+ "port": port,
477
+ }
478
+ if tag:
479
+ data["tag"] = tag
480
+ if comments:
481
+ data["comments"] = comments
482
+ return self.post("/jump-item/remote-vnc", json=data)
483
+
484
+ def get_vnc_jump(self, item_id: int) -> Dict[str, Any]:
485
+ """Get a Remote VNC Jump item."""
486
+ return self.get(f"/jump-item/remote-vnc/{item_id}")
487
+
488
+ def delete_vnc_jump(self, item_id: int) -> None:
489
+ """Delete a Remote VNC Jump item."""
490
+ self.delete(f"/jump-item/remote-vnc/{item_id}")
491
+
460
492
  def list_web_jumps(
461
493
  self,
462
494
  jump_group_id: Optional[int] = None,
@@ -473,6 +505,52 @@ class PRAClient:
473
505
  params["name"] = name
474
506
  return self.get_paginated("/jump-item/web-jump", params)
475
507
 
508
+ def create_web_jump(
509
+ self,
510
+ name: str,
511
+ url: str,
512
+ jumpoint_id: int,
513
+ jump_group_id: int,
514
+ username_format: Optional[str] = None,
515
+ verify_certificate: bool = True,
516
+ authentication_timeout: Optional[int] = None,
517
+ jump_group_type: Optional[str] = None,
518
+ tag: Optional[str] = None,
519
+ comments: Optional[str] = None,
520
+ ) -> Dict[str, Any]:
521
+ """Create a Web Jump item.
522
+
523
+ Args:
524
+ name: Jump item name
525
+ url: Target URL (https://...)
526
+ jumpoint_id: Jumpoint ID
527
+ jump_group_id: Jump Group ID
528
+ username_format: How the username field is matched on the page
529
+ verify_certificate: Verify the site's TLS certificate (default True)
530
+ authentication_timeout: Seconds before auth times out
531
+ jump_group_type: 'shared' or 'personal' (defaults server-side)
532
+ tag: Optional tag
533
+ comments: Optional comments
534
+ """
535
+ data = {
536
+ "name": name,
537
+ "url": url,
538
+ "jumpoint_id": jumpoint_id,
539
+ "jump_group_id": jump_group_id,
540
+ "verify_certificate": verify_certificate,
541
+ }
542
+ if username_format:
543
+ data["username_format"] = username_format
544
+ if authentication_timeout is not None:
545
+ data["authentication_timeout"] = authentication_timeout
546
+ if jump_group_type:
547
+ data["jump_group_type"] = jump_group_type
548
+ if tag:
549
+ data["tag"] = tag
550
+ if comments:
551
+ data["comments"] = comments
552
+ return self.post("/jump-item/web-jump", json=data)
553
+
476
554
  def get_web_jump(self, item_id: int) -> Dict[str, Any]:
477
555
  """Get a Web Jump item."""
478
556
  return self.get(f"/jump-item/web-jump/{item_id}")
@@ -532,6 +610,27 @@ class PRAClient:
532
610
  ) -> Dict[str, Any]:
533
611
  """Create a Protocol Tunnel Jump item.
534
612
 
613
+ The Config API's ``protocol-tunnel-jump`` POST endpoint rejects
614
+ type-specific fields that don't belong to the chosen ``tunnel_type``
615
+ (it 400s with e.g. "This field is only allowed when tunnel_type is
616
+ 'mssql'"). We therefore only include each field for the type that
617
+ actually accepts it:
618
+
619
+ ============= ===========================================
620
+ tunnel_type type-specific fields sent
621
+ ============= ===========================================
622
+ tcp tunnel_definitions
623
+ mssql username, database
624
+ psql / mysql (none — see note below)
625
+ k8s url, ca_certificates
626
+ ============= ===========================================
627
+
628
+ Note: psql/mysql tunnels have ``username``/``database`` in their GET
629
+ representation, but the public API has no working write path for them
630
+ (POST rejects them, PATCH no-ops, PUT 405s). A psql/mysql tunnel is
631
+ therefore created "bare"; any username/database passed here is ignored
632
+ for those types and the caller is expected to warn the user.
633
+
535
634
  Args:
536
635
  name: Jump item name
537
636
  hostname: Target hostname (auto-set for k8s from url)
@@ -539,8 +638,8 @@ class PRAClient:
539
638
  jump_group_id: Jump Group ID
540
639
  tunnel_type: 'tcp', 'mssql', 'psql', 'mysql', or 'k8s'
541
640
  tunnel_definitions: Port pairs for TCP (e.g., "22;24;26;28")
542
- username: Database username (for mssql, psql, mysql)
543
- database: Database name (for mssql, psql, mysql)
641
+ username: Database username (mssql only)
642
+ database: Database name (mssql only)
544
643
  url: K8s API URL (required for k8s)
545
644
  ca_certificates: K8s CA cert (required for k8s)
546
645
  tag: Optional tag
@@ -553,16 +652,22 @@ class PRAClient:
553
652
  "jump_group_id": jump_group_id,
554
653
  "tunnel_type": tunnel_type,
555
654
  }
556
- if tunnel_definitions:
557
- data["tunnel_definitions"] = tunnel_definitions
558
- if username:
559
- data["username"] = username
560
- if database:
561
- data["database"] = database
562
- if url:
563
- data["url"] = url
564
- if ca_certificates:
565
- data["ca_certificates"] = ca_certificates
655
+ # Only send fields the endpoint allows for this tunnel_type.
656
+ if tunnel_type == "tcp":
657
+ if tunnel_definitions:
658
+ data["tunnel_definitions"] = tunnel_definitions
659
+ elif tunnel_type == "mssql":
660
+ if username:
661
+ data["username"] = username
662
+ if database:
663
+ data["database"] = database
664
+ elif tunnel_type == "k8s":
665
+ if url:
666
+ data["url"] = url
667
+ if ca_certificates:
668
+ data["ca_certificates"] = ca_certificates
669
+ # psql / mysql: no type-specific fields are writable; create bare.
670
+
566
671
  if tag:
567
672
  data["tag"] = tag
568
673
  if comments: