bt-cli 0.4.21__tar.gz → 0.4.23__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 (174) hide show
  1. bt_cli-0.4.23/.env.bak +20 -0
  2. {bt_cli-0.4.21 → bt_cli-0.4.23}/CLAUDE.md +1 -1
  3. {bt_cli-0.4.21 → bt_cli-0.4.23}/PKG-INFO +1 -1
  4. {bt_cli-0.4.21 → bt_cli-0.4.23}/pyproject.toml +1 -1
  5. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/__init__.py +1 -1
  6. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/quick.py +178 -0
  7. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/vault.py +32 -4
  8. {bt_cli-0.4.21 → bt_cli-0.4.23}/.claude/skills/bt/SKILL.md +0 -0
  9. {bt_cli-0.4.21 → bt_cli-0.4.23}/.claude/skills/entitle/SKILL.md +0 -0
  10. {bt_cli-0.4.21 → bt_cli-0.4.23}/.claude/skills/epmw/SKILL.md +0 -0
  11. {bt_cli-0.4.21 → bt_cli-0.4.23}/.claude/skills/pra/SKILL.md +0 -0
  12. {bt_cli-0.4.21 → bt_cli-0.4.23}/.claude/skills/pws/SKILL.md +0 -0
  13. {bt_cli-0.4.21 → bt_cli-0.4.23}/.env.example +0 -0
  14. {bt_cli-0.4.21 → bt_cli-0.4.23}/.github/workflows/ci.yml +0 -0
  15. {bt_cli-0.4.21 → bt_cli-0.4.23}/.github/workflows/release.yml +0 -0
  16. {bt_cli-0.4.21 → bt_cli-0.4.23}/.gitignore +0 -0
  17. {bt_cli-0.4.21 → bt_cli-0.4.23}/README.md +0 -0
  18. {bt_cli-0.4.21 → bt_cli-0.4.23}/assets/cli-help.png +0 -0
  19. {bt_cli-0.4.21 → bt_cli-0.4.23}/assets/cli-output.png +0 -0
  20. {bt_cli-0.4.21 → bt_cli-0.4.23}/bt-cli.spec +0 -0
  21. {bt_cli-0.4.21 → bt_cli-0.4.23}/bt_entry.py +0 -0
  22. {bt_cli-0.4.21 → bt_cli-0.4.23}/scripts/bt_entry.py +0 -0
  23. {bt_cli-0.4.21 → bt_cli-0.4.23}/scripts/sync-package-data.sh +0 -0
  24. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/cli.py +0 -0
  25. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/commands/__init__.py +0 -0
  26. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/commands/configure.py +0 -0
  27. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/commands/learn.py +0 -0
  28. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/commands/quick.py +0 -0
  29. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/__init__.py +0 -0
  30. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/auth.py +0 -0
  31. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/client.py +0 -0
  32. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/config.py +0 -0
  33. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/config_file.py +0 -0
  34. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/csv_utils.py +0 -0
  35. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/errors.py +0 -0
  36. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/output.py +0 -0
  37. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/prompts.py +0 -0
  38. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/core/rest_debug.py +0 -0
  39. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/data/CLAUDE.md +0 -0
  40. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/data/__init__.py +0 -0
  41. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  42. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
  43. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  44. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
  45. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
  46. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/__init__.py +0 -0
  47. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/client/__init__.py +0 -0
  48. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/client/base.py +0 -0
  49. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/__init__.py +0 -0
  50. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/accounts.py +0 -0
  51. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/applications.py +0 -0
  52. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/auth.py +0 -0
  53. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/bundles.py +0 -0
  54. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/integrations.py +0 -0
  55. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/permissions.py +0 -0
  56. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/policies.py +0 -0
  57. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/resources.py +0 -0
  58. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/roles.py +0 -0
  59. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/users.py +0 -0
  60. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/commands/workflows.py +0 -0
  61. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/__init__.py +0 -0
  62. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/bundle.py +0 -0
  63. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/common.py +0 -0
  64. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/integration.py +0 -0
  65. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/permission.py +0 -0
  66. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/policy.py +0 -0
  67. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/resource.py +0 -0
  68. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/role.py +0 -0
  69. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/user.py +0 -0
  70. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/entitle/models/workflow.py +0 -0
  71. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/__init__.py +0 -0
  72. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/client/__init__.py +0 -0
  73. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/client/base.py +0 -0
  74. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/__init__.py +0 -0
  75. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/audits.py +0 -0
  76. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/auth.py +0 -0
  77. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/computers.py +0 -0
  78. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/events.py +0 -0
  79. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/groups.py +0 -0
  80. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/policies.py +0 -0
  81. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/requests.py +0 -0
  82. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/roles.py +0 -0
  83. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/tasks.py +0 -0
  84. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/commands/users.py +0 -0
  85. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/epmw/models/__init__.py +0 -0
  86. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/__init__.py +0 -0
  87. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/client/__init__.py +0 -0
  88. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/client/base.py +0 -0
  89. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/__init__.py +0 -0
  90. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/auth.py +0 -0
  91. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/import_export.py +0 -0
  92. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/jump_clients.py +0 -0
  93. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  94. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/jump_items.py +0 -0
  95. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  96. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/policies.py +0 -0
  97. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/quick.py +0 -0
  98. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/teams.py +0 -0
  99. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/commands/users.py +0 -0
  100. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/__init__.py +0 -0
  101. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/common.py +0 -0
  102. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/jump_client.py +0 -0
  103. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/jump_group.py +0 -0
  104. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/jump_item.py +0 -0
  105. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/jumpoint.py +0 -0
  106. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/team.py +0 -0
  107. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/user.py +0 -0
  108. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pra/models/vault.py +0 -0
  109. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/__init__.py +0 -0
  110. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/client/__init__.py +0 -0
  111. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/client/base.py +0 -0
  112. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  113. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  114. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/__init__.py +0 -0
  115. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/accounts.py +0 -0
  116. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/assets.py +0 -0
  117. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/auth.py +0 -0
  118. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/clouds.py +0 -0
  119. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/config.py +0 -0
  120. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/credentials.py +0 -0
  121. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/databases.py +0 -0
  122. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/directories.py +0 -0
  123. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/functional.py +0 -0
  124. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/import_export.py +0 -0
  125. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/platforms.py +0 -0
  126. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/quick.py +0 -0
  127. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/search.py +0 -0
  128. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/secrets.py +0 -0
  129. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/systems.py +0 -0
  130. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/users.py +0 -0
  131. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/commands/workgroups.py +0 -0
  132. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/config.py +0 -0
  133. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/models/__init__.py +0 -0
  134. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/models/account.py +0 -0
  135. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/models/asset.py +0 -0
  136. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/models/common.py +0 -0
  137. {bt_cli-0.4.21 → bt_cli-0.4.23}/src/bt_cli/pws/models/system.py +0 -0
  138. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/__init__.py +0 -0
  139. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/conftest.py +0 -0
  140. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/core/__init__.py +0 -0
  141. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/core/test_auth.py +0 -0
  142. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/core/test_config.py +0 -0
  143. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/core/test_errors.py +0 -0
  144. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/core/test_rest_debug.py +0 -0
  145. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/entitle/__init__.py +0 -0
  146. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/entitle/test_client.py +0 -0
  147. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/entitle/test_commands.py +0 -0
  148. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/entitle-smoke-test.sh +0 -0
  149. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/epmw/__init__.py +0 -0
  150. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/epmw/test_client.py +0 -0
  151. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/epmw/test_commands.py +0 -0
  152. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/epmw-quick-test-plan.md +0 -0
  153. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/fixtures/__init__.py +0 -0
  154. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/fixtures/responses.py +0 -0
  155. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/__init__.py +0 -0
  156. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/conftest.py +0 -0
  157. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/helpers.py +0 -0
  158. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/test_entitle_integration.py +0 -0
  159. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/test_epmw_integration.py +0 -0
  160. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/test_epmw_lifecycle.py +0 -0
  161. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/test_pra_integration.py +0 -0
  162. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/test_pra_lifecycle.py +0 -0
  163. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/test_pws_integration.py +0 -0
  164. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/integration/test_pws_lifecycle.py +0 -0
  165. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pra/__init__.py +0 -0
  166. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pra/test_client.py +0 -0
  167. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pra/test_commands.py +0 -0
  168. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pra-smoke-test.sh +0 -0
  169. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pra-test-plan.md +0 -0
  170. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pws/__init__.py +0 -0
  171. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pws/test_client.py +0 -0
  172. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pws/test_commands.py +0 -0
  173. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pws-quick-test-plan.md +0 -0
  174. {bt_cli-0.4.21 → bt_cli-0.4.23}/tests/pws-smoke-test.sh +0 -0
bt_cli-0.4.23/.env.bak ADDED
@@ -0,0 +1,20 @@
1
+ # Password Safe
2
+ BT_PWS_API_URL=https://semcp.ps-dev.beyondtrustcloud.com/BeyondTrust/api/public/v3
3
+ BT_PWS_CLIENT_ID=d3b50b99-1650-4ddb-b00b-7fc27b963976
4
+ BT_PWS_CLIENT_SECRET=/tUEmXpUrfy4K71Cqx29XemZomNCkWogYvLs5r0w9m8=
5
+ BT_PWS_VERIFY_SSL=true
6
+ BT_PWS_TIMEOUT=30
7
+
8
+ # Entitle
9
+ BT_ENTITLE_API_KEY=Y2MxOTZkYmUtYTNhMy00OWY2LWFiMjAtYmY4ZmY1NWQ4YzYyOmM2ZWExYzJmNzA3YTUwNTBhNTdiYTcwNzEwN2I3NWExYTUxNGRiMDQ2ODEwZjU0ZjUyNDYzOTk4ODQ2NzI5ZGQ=
10
+ BT_ENTITLE_API_URL=https://api.us.entitle.io
11
+
12
+ # PRA
13
+ BT_PRA_API_URL=https://pffd51d9.beyondtrustcloud.com
14
+ BT_PRA_CLIENT_ID=2273892cd25ede1a4b3e80a40b39edc04385ddd0
15
+ BT_PRA_CLIENT_SECRET=FVy0tNYZXDbLyQCRPTPSSWkrksWsflVj2wG7aCceirU=
16
+
17
+ # EPM Windows
18
+ BT_EPM_API_URL=https://pfsedemo-services.epm.bt3ng.com
19
+ BT_EPM_CLIENT_ID=19f4e85faae14c5399479a87d23a258a
20
+ BT_EPM_CLIENT_SECRET=00DndV2PrHllPwQySgRZqyvQY9jbIDLjEuXrG8I0TcA=
@@ -1,6 +1,6 @@
1
1
  # BT-CLI
2
2
 
3
- BeyondTrust Platform CLI for Password Safe, Entitle, PRA, and EPM Windows. **Version: 0.4.21**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, and EPM Windows. **Version: 0.4.23**
4
4
 
5
5
  ## Setup
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bt-cli
3
- Version: 0.4.21
3
+ Version: 0.4.23
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.21"
7
+ version = "0.4.23"
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.21"
3
+ __version__ = "0.4.23"
@@ -10,6 +10,7 @@ from rich.console import Console
10
10
  from rich.table import Table
11
11
 
12
12
  from ...core.output import print_api_error, print_error, print_warning, print_success
13
+ from ...core.prompts import prompt_from_list
13
14
 
14
15
  app = typer.Typer(no_args_is_help=True, help="Quick commands - common multi-step operations in one command")
15
16
  console = Console()
@@ -346,3 +347,180 @@ def group_status(
346
347
  except Exception as e:
347
348
  print_api_error(e, "quick status")
348
349
  raise typer.Exit(1)
350
+
351
+
352
+ @app.command("move-computer")
353
+ def move_computer(
354
+ computer: Optional[str] = typer.Option(None, "--computer", "-c", help="Computer name or ID (partial match for name)"),
355
+ group: Optional[str] = typer.Option(None, "--group", "-g", help="Target group name or ID"),
356
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
357
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
358
+ ) -> None:
359
+ """Move a computer to a different group.
360
+
361
+ Interactive workflow to change a computer's group membership.
362
+ Lists computers (optionally filtered), then lists available groups,
363
+ and moves the selected computer to the selected group.
364
+
365
+ Examples:
366
+ bt epmw quick move-computer # Interactive selection
367
+ bt epmw quick move-computer -c "CorpWS01" # Filter by computer name
368
+ bt epmw quick move-computer -c "CorpWS01" -g "Servers" # Specify both
369
+ bt epmw quick move-computer -c "CorpWS01" -g "Servers" -f # Skip confirmation
370
+ """
371
+ from ..client import get_client
372
+
373
+ try:
374
+ client = get_client()
375
+
376
+ # Step 1: Get and filter computers
377
+ console.print("[dim]Fetching computers...[/dim]")
378
+ computers = client.list_computers()
379
+
380
+ if not computers:
381
+ print_error("No computers found")
382
+ raise typer.Exit(1)
383
+
384
+ # Filter by computer name/ID if provided
385
+ if computer:
386
+ computer_lower = computer.lower()
387
+ filtered = [
388
+ c for c in computers
389
+ if computer_lower in (c.get("host") or "").lower()
390
+ or computer_lower in (c.get("id") or "").lower()
391
+ ]
392
+ if not filtered:
393
+ print_error(f"No computers matching '{computer}'")
394
+ raise typer.Exit(1)
395
+ computers = filtered
396
+
397
+ # Show computers and select one
398
+ if len(computers) == 1:
399
+ selected_computer = computers[0]
400
+ console.print(f"[dim]Found computer: {selected_computer.get('host')}[/dim]")
401
+ else:
402
+ # Show list and prompt for selection
403
+ table = Table(title="Available Computers")
404
+ table.add_column("#", style="dim", justify="right")
405
+ table.add_column("Host", style="cyan")
406
+ table.add_column("Domain", style="yellow")
407
+ table.add_column("Current Group", style="green")
408
+ table.add_column("Status", style="magenta")
409
+
410
+ for idx, comp in enumerate(computers, 1):
411
+ table.add_row(
412
+ str(idx),
413
+ comp.get("host", ""),
414
+ comp.get("domain", "") or "-",
415
+ comp.get("groupName", "") or "-",
416
+ comp.get("connectionStatus", ""),
417
+ )
418
+ console.print(table)
419
+
420
+ selection = typer.prompt("Select computer number", type=int)
421
+ if selection < 1 or selection > len(computers):
422
+ print_error(f"Invalid selection. Choose 1-{len(computers)}")
423
+ raise typer.Exit(1)
424
+ selected_computer = computers[selection - 1]
425
+
426
+ computer_id = selected_computer.get("id")
427
+ computer_host = selected_computer.get("host")
428
+ current_group = selected_computer.get("groupName") or "(No Group)"
429
+
430
+ # Step 2: Get and filter groups
431
+ console.print("[dim]Fetching groups...[/dim]")
432
+ groups = client.list_groups()
433
+
434
+ if not groups:
435
+ print_error("No groups found")
436
+ raise typer.Exit(1)
437
+
438
+ # Filter by group name/ID if provided
439
+ if group:
440
+ group_lower = group.lower()
441
+ filtered = [
442
+ g for g in groups
443
+ if group_lower in (g.get("name") or "").lower()
444
+ or group_lower in (g.get("id") or "").lower()
445
+ ]
446
+ if not filtered:
447
+ print_error(f"No groups matching '{group}'")
448
+ raise typer.Exit(1)
449
+ groups = filtered
450
+
451
+ # Show groups and select one
452
+ if len(groups) == 1:
453
+ selected_group = groups[0]
454
+ console.print(f"[dim]Target group: {selected_group.get('name')}[/dim]")
455
+ else:
456
+ # Show list and prompt for selection
457
+ table = Table(title="Available Groups")
458
+ table.add_column("#", style="dim", justify="right")
459
+ table.add_column("Name", style="cyan")
460
+ table.add_column("Computers", style="green", justify="right")
461
+ table.add_column("Policy", style="yellow")
462
+
463
+ for idx, grp in enumerate(groups, 1):
464
+ table.add_row(
465
+ str(idx),
466
+ grp.get("name", ""),
467
+ str(grp.get("computerCount", 0)),
468
+ grp.get("policyName", "") or "-",
469
+ )
470
+ console.print(table)
471
+
472
+ selection = typer.prompt("Select target group number", type=int)
473
+ if selection < 1 or selection > len(groups):
474
+ print_error(f"Invalid selection. Choose 1-{len(groups)}")
475
+ raise typer.Exit(1)
476
+ selected_group = groups[selection - 1]
477
+
478
+ group_id = selected_group.get("id")
479
+ group_name = selected_group.get("name")
480
+
481
+ # Check if already in target group
482
+ if current_group == group_name:
483
+ console.print(f"[yellow]Computer '{computer_host}' is already in group '{group_name}'[/yellow]")
484
+ raise typer.Exit(0)
485
+
486
+ # Step 3: Confirm and move
487
+ if not force:
488
+ console.print()
489
+ console.print(f"[bold]Move computer:[/bold] {computer_host}")
490
+ console.print(f"[bold]From group:[/bold] {current_group}")
491
+ console.print(f"[bold]To group:[/bold] {group_name}")
492
+ console.print()
493
+ confirm = typer.confirm("Proceed with move?")
494
+ if not confirm:
495
+ console.print("[yellow]Cancelled.[/yellow]")
496
+ raise typer.Exit(0)
497
+
498
+ # Perform the move
499
+ console.print("[dim]Moving computer to group...[/dim]")
500
+ client.assign_computers_to_group(group_id, [computer_id])
501
+
502
+ if output == "json":
503
+ result = {
504
+ "computer": {
505
+ "id": computer_id,
506
+ "host": computer_host,
507
+ },
508
+ "previousGroup": current_group,
509
+ "newGroup": group_name,
510
+ "success": True,
511
+ }
512
+ console.print_json(json.dumps(result, default=str))
513
+ else:
514
+ print_success(f"Moved '{computer_host}' from '{current_group}' to '{group_name}'")
515
+
516
+ except httpx.HTTPStatusError as e:
517
+ print_api_error(e, "quick move-computer")
518
+ raise typer.Exit(1)
519
+ except httpx.RequestError as e:
520
+ print_api_error(e, "quick move-computer")
521
+ raise typer.Exit(1)
522
+ except typer.Exit:
523
+ raise
524
+ except Exception as e:
525
+ print_api_error(e, "quick move-computer")
526
+ raise typer.Exit(1)
@@ -137,13 +137,16 @@ def create_vault_account(
137
137
  name: str = typer.Option(..., "--name", "-n", help="Account name"),
138
138
  account_type: str = typer.Option(
139
139
  ..., "--type", "-t",
140
- help="Account type: username_password, ssh, or ssh_ca"
140
+ help="Account type: username_password, ssh, ssh_ca, or token"
141
141
  ),
142
142
  username: Optional[str] = typer.Option(None, "--username", "-u", help="Username (for username_password)"),
143
143
  password: Optional[str] = typer.Option(None, "--password", "-p", help="Password (for username_password)"),
144
+ token: Optional[str] = typer.Option(None, "--token", help="Token value (for token type)"),
145
+ token_file: Optional[str] = typer.Option(None, "--token-file", help="Path to file containing token"),
144
146
  private_key: Optional[str] = typer.Option(None, "--private-key", help="SSH private key (for ssh type)"),
145
147
  private_key_file: Optional[str] = typer.Option(None, "--private-key-file", help="Path to SSH private key file"),
146
148
  description: Optional[str] = typer.Option(None, "--description", "-d", help="Account description"),
149
+ account_group_id: Optional[int] = typer.Option(None, "--group-id", "-g", help="Vault Account Group ID"),
147
150
  personal: bool = typer.Option(False, "--personal", help="Mark as personal account"),
148
151
  output: OutputFormat = typer.Option(OutputFormat.JSON, "--output", "-o"),
149
152
  ):
@@ -158,20 +161,29 @@ def create_vault_account(
158
161
 
159
162
  # Create SSH CA account (for certificate-based auth)
160
163
  bt pra vault accounts create -n "ssh-ca" -t ssh_ca --private-key-file /path/to/ca_key
164
+
165
+ # Create token account (API keys, bearer tokens, etc.)
166
+ bt pra vault accounts create -n "api-key" -t token --token "sk-abc123..."
167
+
168
+ # Create token account from file
169
+ bt pra vault accounts create -n "service-token" -t token --token-file /path/to/token.txt
161
170
  """
162
171
  from bt_cli.pra.client import get_client
163
172
  from pathlib import Path
164
173
 
165
- # Validate account type
166
- valid_types = ["username_password", "ssh", "ssh_ca"]
174
+ # Validate account type (token maps to opaque_token in API)
175
+ valid_types = ["username_password", "ssh", "ssh_ca", "token"]
167
176
  if account_type not in valid_types:
168
177
  print_error(f"Invalid account type '{account_type}'. Must be one of: {', '.join(valid_types)}")
169
178
  raise typer.Exit(1)
170
179
 
180
+ # Map 'token' to API type 'opaque_token'
181
+ api_type = "opaque_token" if account_type == "token" else account_type
182
+
171
183
  # Build account data
172
184
  data = {
173
185
  "name": name,
174
- "type": account_type,
186
+ "type": api_type,
175
187
  }
176
188
 
177
189
  if username:
@@ -180,9 +192,21 @@ def create_vault_account(
180
192
  data["password"] = password
181
193
  if description:
182
194
  data["description"] = description
195
+ if account_group_id:
196
+ data["account_group_id"] = account_group_id
183
197
  if personal:
184
198
  data["personal"] = True
185
199
 
200
+ # Handle token (from file or direct)
201
+ if token_file:
202
+ token_path = Path(token_file).expanduser()
203
+ if not token_path.exists():
204
+ print_error(f"Token file not found: {token_file}")
205
+ raise typer.Exit(1)
206
+ data["token"] = token_path.read_text().strip()
207
+ elif token:
208
+ data["token"] = token
209
+
186
210
  # Handle private key (from file or direct)
187
211
  if private_key_file:
188
212
  key_path = Path(private_key_file).expanduser()
@@ -202,6 +226,10 @@ def create_vault_account(
202
226
  if not data.get("private_key"):
203
227
  print_error(f"Private key is required for {account_type} type (use --private-key or --private-key-file)")
204
228
  raise typer.Exit(1)
229
+ elif account_type == "token":
230
+ if not data.get("token"):
231
+ print_error("Token is required for token type (use --token or --token-file)")
232
+ raise typer.Exit(1)
205
233
 
206
234
  try:
207
235
  client = get_client()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes