bt-cli 0.4.31__tar.gz → 0.4.33__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 (205) hide show
  1. bt_cli-0.4.33/.claude/skills/epml/SKILL.md +199 -0
  2. {bt_cli-0.4.31 → bt_cli-0.4.33}/CLAUDE.md +8 -2
  3. {bt_cli-0.4.31 → bt_cli-0.4.33}/PKG-INFO +1 -1
  4. bt_cli-0.4.33/epml-implementation-plan.md +259 -0
  5. {bt_cli-0.4.31 → bt_cli-0.4.33}/pyproject.toml +1 -1
  6. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/__init__.py +1 -1
  7. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/cli.py +154 -2
  8. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/auth.py +27 -0
  9. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/client.py +17 -12
  10. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/config.py +73 -0
  11. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/config_file.py +41 -0
  12. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/data/CLAUDE.md +14 -2
  13. bt_cli-0.4.33/src/bt_cli/data/skills/epml/SKILL.md +199 -0
  14. bt_cli-0.4.33/src/bt_cli/epml/__init__.py +1 -0
  15. bt_cli-0.4.33/src/bt_cli/epml/client/__init__.py +5 -0
  16. bt_cli-0.4.33/src/bt_cli/epml/client/base.py +595 -0
  17. bt_cli-0.4.33/src/bt_cli/epml/commands/__init__.py +58 -0
  18. bt_cli-0.4.33/src/bt_cli/epml/commands/audit.py +88 -0
  19. bt_cli-0.4.33/src/bt_cli/epml/commands/auth.py +70 -0
  20. bt_cli-0.4.33/src/bt_cli/epml/commands/client_pkg.py +99 -0
  21. bt_cli-0.4.33/src/bt_cli/epml/commands/external_apis.py +70 -0
  22. bt_cli-0.4.33/src/bt_cli/epml/commands/hosts.py +83 -0
  23. bt_cli-0.4.33/src/bt_cli/epml/commands/iolog.py +45 -0
  24. bt_cli-0.4.33/src/bt_cli/epml/commands/license.py +42 -0
  25. bt_cli-0.4.33/src/bt_cli/epml/commands/quick.py +67 -0
  26. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_cmdgrps.py +141 -0
  27. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_entitlement.py +32 -0
  28. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_hostgrps.py +141 -0
  29. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_policy.py +159 -0
  30. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_roles.py +165 -0
  31. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_tests.py +269 -0
  32. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_tmdategrps.py +77 -0
  33. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_tx.py +87 -0
  34. bt_cli-0.4.33/src/bt_cli/epml/commands/rbp_usergrps.py +141 -0
  35. bt_cli-0.4.33/src/bt_cli/epml/commands/settings.py +56 -0
  36. bt_cli-0.4.33/src/bt_cli/epml/commands/siems.py +46 -0
  37. bt_cli-0.4.33/src/bt_cli/epml/commands/users.py +90 -0
  38. bt_cli-0.4.33/src/bt_cli/epml/models/__init__.py +1 -0
  39. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/client/beyondinsight.py +36 -29
  40. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/client/passwordsafe.py +24 -0
  41. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/search.py +40 -35
  42. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/users.py +20 -6
  43. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/conftest.py +27 -1
  44. bt_cli-0.4.33/tests/epml/__init__.py +1 -0
  45. bt_cli-0.4.33/tests/epml/test_client.py +205 -0
  46. bt_cli-0.4.33/tests/epml/test_commands.py +186 -0
  47. {bt_cli-0.4.31 → bt_cli-0.4.33}/.claude/skills/bt/SKILL.md +0 -0
  48. {bt_cli-0.4.31 → bt_cli-0.4.33}/.claude/skills/entitle/SKILL.md +0 -0
  49. {bt_cli-0.4.31 → bt_cli-0.4.33}/.claude/skills/epmw/SKILL.md +0 -0
  50. {bt_cli-0.4.31 → bt_cli-0.4.33}/.claude/skills/pra/SKILL.md +0 -0
  51. {bt_cli-0.4.31 → bt_cli-0.4.33}/.claude/skills/pws/SKILL.md +0 -0
  52. {bt_cli-0.4.31 → bt_cli-0.4.33}/.env.example +0 -0
  53. {bt_cli-0.4.31 → bt_cli-0.4.33}/.github/workflows/ci.yml +0 -0
  54. {bt_cli-0.4.31 → bt_cli-0.4.33}/.github/workflows/release.yml +0 -0
  55. {bt_cli-0.4.31 → bt_cli-0.4.33}/.gitignore +0 -0
  56. {bt_cli-0.4.31 → bt_cli-0.4.33}/README.md +0 -0
  57. {bt_cli-0.4.31 → bt_cli-0.4.33}/assets/cli-help.png +0 -0
  58. {bt_cli-0.4.31 → bt_cli-0.4.33}/assets/cli-output.png +0 -0
  59. {bt_cli-0.4.31 → bt_cli-0.4.33}/bt-cli.spec +0 -0
  60. {bt_cli-0.4.31 → bt_cli-0.4.33}/bt_entry.py +0 -0
  61. {bt_cli-0.4.31 → bt_cli-0.4.33}/scripts/bt_entry.py +0 -0
  62. {bt_cli-0.4.31 → bt_cli-0.4.33}/scripts/sync-package-data.sh +0 -0
  63. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/commands/__init__.py +0 -0
  64. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/commands/configure.py +0 -0
  65. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/commands/learn.py +0 -0
  66. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/commands/quick.py +0 -0
  67. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/__init__.py +0 -0
  68. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/csv_utils.py +0 -0
  69. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/errors.py +0 -0
  70. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/output.py +0 -0
  71. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/prompts.py +0 -0
  72. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/core/rest_debug.py +0 -0
  73. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/data/__init__.py +0 -0
  74. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  75. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
  76. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  77. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
  78. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
  79. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/__init__.py +0 -0
  80. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/client/__init__.py +0 -0
  81. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/client/base.py +0 -0
  82. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/__init__.py +0 -0
  83. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/accounts.py +0 -0
  84. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/applications.py +0 -0
  85. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/auth.py +0 -0
  86. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/bundles.py +0 -0
  87. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/integrations.py +0 -0
  88. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/permissions.py +0 -0
  89. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/policies.py +0 -0
  90. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/resources.py +0 -0
  91. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/roles.py +0 -0
  92. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/users.py +0 -0
  93. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/commands/workflows.py +0 -0
  94. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/__init__.py +0 -0
  95. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/bundle.py +0 -0
  96. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/common.py +0 -0
  97. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/integration.py +0 -0
  98. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/permission.py +0 -0
  99. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/policy.py +0 -0
  100. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/resource.py +0 -0
  101. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/role.py +0 -0
  102. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/user.py +0 -0
  103. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/entitle/models/workflow.py +0 -0
  104. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/__init__.py +0 -0
  105. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/client/__init__.py +0 -0
  106. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/client/base.py +0 -0
  107. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/__init__.py +0 -0
  108. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/audits.py +0 -0
  109. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/auth.py +0 -0
  110. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/computers.py +0 -0
  111. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/events.py +0 -0
  112. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/groups.py +0 -0
  113. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/policies.py +0 -0
  114. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/quick.py +0 -0
  115. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/requests.py +0 -0
  116. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/roles.py +0 -0
  117. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/tasks.py +0 -0
  118. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/commands/users.py +0 -0
  119. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/epmw/models/__init__.py +0 -0
  120. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/__init__.py +0 -0
  121. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/client/__init__.py +0 -0
  122. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/client/base.py +0 -0
  123. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/__init__.py +0 -0
  124. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/auth.py +0 -0
  125. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/import_export.py +0 -0
  126. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/jump_clients.py +0 -0
  127. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  128. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/jump_items.py +0 -0
  129. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  130. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/policies.py +0 -0
  131. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/quick.py +0 -0
  132. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/teams.py +0 -0
  133. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/users.py +0 -0
  134. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/commands/vault.py +0 -0
  135. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/__init__.py +0 -0
  136. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/common.py +0 -0
  137. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/jump_client.py +0 -0
  138. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/jump_group.py +0 -0
  139. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/jump_item.py +0 -0
  140. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/jumpoint.py +0 -0
  141. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/team.py +0 -0
  142. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/user.py +0 -0
  143. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pra/models/vault.py +0 -0
  144. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/__init__.py +0 -0
  145. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/client/__init__.py +0 -0
  146. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/client/base.py +0 -0
  147. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/__init__.py +0 -0
  148. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/accounts.py +0 -0
  149. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/assets.py +0 -0
  150. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/attributes.py +0 -0
  151. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/auth.py +0 -0
  152. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/clouds.py +0 -0
  153. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/config.py +0 -0
  154. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/credentials.py +0 -0
  155. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/databases.py +0 -0
  156. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/directories.py +0 -0
  157. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/functional.py +0 -0
  158. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/import_export.py +0 -0
  159. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/platforms.py +0 -0
  160. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/quick.py +0 -0
  161. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/secrets.py +0 -0
  162. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/systems.py +0 -0
  163. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/commands/workgroups.py +0 -0
  164. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/config.py +0 -0
  165. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/models/__init__.py +0 -0
  166. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/models/account.py +0 -0
  167. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/models/asset.py +0 -0
  168. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/models/common.py +0 -0
  169. {bt_cli-0.4.31 → bt_cli-0.4.33}/src/bt_cli/pws/models/system.py +0 -0
  170. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/__init__.py +0 -0
  171. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/core/__init__.py +0 -0
  172. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/core/test_auth.py +0 -0
  173. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/core/test_config.py +0 -0
  174. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/core/test_errors.py +0 -0
  175. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/core/test_rest_debug.py +0 -0
  176. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/entitle/__init__.py +0 -0
  177. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/entitle/test_client.py +0 -0
  178. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/entitle/test_commands.py +0 -0
  179. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/entitle-smoke-test.sh +0 -0
  180. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/epmw/__init__.py +0 -0
  181. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/epmw/test_client.py +0 -0
  182. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/epmw/test_commands.py +0 -0
  183. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/epmw-quick-test-plan.md +0 -0
  184. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/fixtures/__init__.py +0 -0
  185. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/fixtures/responses.py +0 -0
  186. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/__init__.py +0 -0
  187. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/conftest.py +0 -0
  188. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/helpers.py +0 -0
  189. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/test_entitle_integration.py +0 -0
  190. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/test_epmw_integration.py +0 -0
  191. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/test_epmw_lifecycle.py +0 -0
  192. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/test_pra_integration.py +0 -0
  193. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/test_pra_lifecycle.py +0 -0
  194. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/test_pws_integration.py +0 -0
  195. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/integration/test_pws_lifecycle.py +0 -0
  196. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pra/__init__.py +0 -0
  197. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pra/test_client.py +0 -0
  198. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pra/test_commands.py +0 -0
  199. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pra-smoke-test.sh +0 -0
  200. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pra-test-plan.md +0 -0
  201. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pws/__init__.py +0 -0
  202. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pws/test_client.py +0 -0
  203. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pws/test_commands.py +0 -0
  204. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pws-quick-test-plan.md +0 -0
  205. {bt_cli-0.4.31 → bt_cli-0.4.33}/tests/pws-smoke-test.sh +0 -0
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: epml
3
+ description: EPM Linux commands for endpoint privilege management — RBP roles/cmdgrps/usergrps, policy import/export, test suites, transactions, audit. Use when working with PMUL/RBP, PAT auth against api.beyondtrust.io, or `/site/<id>/epm/linux/` URLs.
4
+ ---
5
+
6
+ # EPM Linux Commands (`bt epml`)
7
+
8
+ ## Configuration
9
+
10
+ EPM Linux speaks to the cloud gateway with a **Personal Access Token** (PAT).
11
+ The gateway URL takes a transform that the OpenAPI spec does NOT document — see "URL transform" below.
12
+
13
+ ```bash
14
+ export BT_EPML_API_URL=https://api.beyondtrust.io # default; usually omit
15
+ export BT_EPML_SITE_ID=<your-site-uuid> # required
16
+ export BT_EPML_PAT=PAT_xxxxxxxxxx # required (mint at app.beyondtrust.io)
17
+ export BT_EPML_DEFAULT_HOST=100 # default PMUL host id (optional)
18
+
19
+ bt epml auth test # confirms /settings is reachable
20
+ bt epml auth status # show current config (no secrets)
21
+ ```
22
+
23
+ ## URL transform — important
24
+
25
+ The OpenAPI spec lists paths starting with `/api/...` and has an empty `servers`
26
+ block. The deployed gateway requires a site/product prefix that ISN'T in the
27
+ spec. The CLI handles this in one place (`_build_url`), but you'll see this if
28
+ you debug raw curl:
29
+
30
+ ```
31
+ spec path: /api/<rest>
32
+ real URL: https://api.beyondtrust.io/site/<site-id>/epm/linux/<rest>
33
+ ```
34
+
35
+ A direct `curl https://api.beyondtrust.io/api/v1/users -H "Authorization: Bearer <PAT>"` returns
36
+ `401 {"error":"Access denied for this site"}` — that's NOT a PAT/audience problem,
37
+ it's a missing path prefix.
38
+
39
+ ## Two-authorizer reality (PAT-OK vs IAM-blocked)
40
+
41
+ The spec declares `nomine-authorizer` (PAT) globally with zero per-op overrides,
42
+ but the gateway has some routes wired to AWS IAM (SigV4) instead. PAT cannot
43
+ reach those — they return either `403 {"message":"Invalid key=value pair... SHA-256... Base64..."}`
44
+ or `403 {"Message":"User is not authorized... no identity-based policy allows
45
+ the execute-api:Invoke action"}`. Same root, different envelope.
46
+
47
+ Known IAM-only paths (PAT-blocked):
48
+ - `PUT /settings` (read is fine)
49
+ - `POST/PUT/DELETE /v4/siems` (read is fine)
50
+ - `/v1/hosts`, `/v6/users`, `/v6/auth`, `/v1/auth`, `/v2/appfeatures`
51
+ - `/v7/directoryservices/*`, `/epml/license`, `/epml/licenseinfo`
52
+ - All `/v6/pbul/rbp/*` (use the legacy `/pbul/{hostid}/rbp/*` instead)
53
+ - **Inside testsuite**: `PUT /testsuite/{id}` and `POST /testsuite/test` (per-method split — list/get/create/delete/run *suites* are fine; PUT-test and DELETE-test are fine, but POST-add-test is IAM)
54
+
55
+ ## Auth + connection
56
+
57
+ ```bash
58
+ bt epml auth test # GET /settings, reports 401 vs 403 distinctly
59
+ bt epml auth status # show env config
60
+ bt whoami # includes EPM Linux row
61
+ ```
62
+
63
+ ## Settings, users, hosts, license
64
+
65
+ ```bash
66
+ bt epml settings list # /api/settings
67
+ bt epml settings get session_timeout
68
+ bt epml users list # /api/v1/users (table)
69
+ bt epml users list --v2 # /api/v2/users
70
+ bt epml users get <user_id>
71
+ bt epml hosts list # synthesized from /pbul/features
72
+ bt epml hosts features # full feature flags per PMUL host
73
+ bt epml license get # /api/epml/enhancedlicense
74
+ ```
75
+
76
+ Note: `bt epml hosts list` is synthesized from `/api/pbul/features` because
77
+ `/api/v1/hosts` is IAM-blocked.
78
+
79
+ ## Audit, SIEMs, External APIs, Iologs, Client packages
80
+
81
+ ```bash
82
+ bt epml audit sessions list --take 50
83
+ bt epml audit sessions categories
84
+ bt epml siems list # read only — write is IAM
85
+ bt epml external-apis list
86
+ bt epml external-apis get <id>
87
+ bt epml iolog list # default host
88
+ bt epml iolog list -H 200 --start 0 --len 200
89
+ bt epml client-pkg list # signed S3 URLs (~30min TTL)
90
+ bt epml client-pkg status # build queue
91
+ bt epml client-pkg link epml-client.x86_64.rpm
92
+ ```
93
+
94
+ ## RBP — Role-Based Policy
95
+
96
+ All under `/api/pbul/{hostid}/rbp/...` (default host = `BT_EPML_DEFAULT_HOST`).
97
+ Use `-H/--host` to override.
98
+
99
+ ```bash
100
+ # Command groups
101
+ bt epml rbp cmdgrps list
102
+ bt epml rbp cmdgrps create --name "Admin Tools" --description "..."
103
+ bt epml rbp cmdgrps delete 12,13
104
+ bt epml rbp cmdgrps commands list <cmdgrp_id>
105
+ bt epml rbp cmdgrps commands add <cmdgrp_id> --commands "vi,less,cat"
106
+ bt epml rbp cmdgrps commands remove <cmdgrp_id> --ids 1,2
107
+
108
+ # Host groups
109
+ bt epml rbp hostgrps list
110
+ bt epml rbp hostgrps create --name "Admin Hosts"
111
+ bt epml rbp hostgrps hosts add <hostgrp_id> --hosts "web-*,db-*"
112
+
113
+ # User groups
114
+ bt epml rbp usergrps list
115
+ bt epml rbp usergrps create --name "Helpdesk"
116
+ bt epml rbp usergrps users add <usergrp_id> --users "alice,bob"
117
+
118
+ # Time/date groups
119
+ bt epml rbp tmdategrps list
120
+ bt epml rbp tmdategrps create --name "Working Hours"
121
+
122
+ # Roles + assignments
123
+ bt epml rbp roles list
124
+ bt epml rbp roles create --name "Helpdesk Role"
125
+ bt epml rbp roles duplicate <role_id>
126
+ bt epml rbp roles cmdgrps add <role_id> --ids 1,2
127
+ bt epml rbp roles cmdgrps remove <role_id> <cmdgrp_id>
128
+ bt epml rbp roles hostgrps list <role_id>
129
+ bt epml rbp roles usergrps add <role_id> --ids 5
130
+ bt epml rbp roles tmdategrps add <role_id> --ids 1
131
+
132
+ # Entitlement report ('who can do what')
133
+ bt epml rbp entitlement run
134
+ bt epml rbp entitlement run --raw
135
+
136
+ # Policy export/import/versions
137
+ bt epml rbp policy export --file policy.json
138
+ bt epml rbp policy import policy.json
139
+ bt epml rbp policy versions
140
+ bt epml rbp policy diff
141
+ bt epml rbp policy snapshot
142
+ bt epml rbp policy revert
143
+ bt epml rbp policy delete-all --yes-i-mean-it # destructive, gated
144
+ ```
145
+
146
+ ## Test suites + transactions (the killer workflow)
147
+
148
+ ```bash
149
+ # Suites (CRUD is mostly Bearer-OK; update-suite and add-test are IAM-only — see caveats)
150
+ bt epml rbp tests suites list
151
+ bt epml rbp tests suites create -n "production-policy" -d "..."
152
+ bt epml rbp tests suites get <suite_id>
153
+ bt epml rbp tests suites delete <suite_id>
154
+
155
+ # Tests within a suite (add is IAM-blocked → use the GUI to add tests; CLI to run)
156
+ bt epml rbp tests tests update <test_id> -f test.json
157
+ bt epml rbp tests tests delete <test_id>
158
+
159
+ # Run a suite — exits 0 on pass, 1 on any failure
160
+ bt epml rbp tests run --suite <suite_id>
161
+
162
+ # Transactions
163
+ bt epml rbp tx status
164
+ bt epml rbp tx begin
165
+ bt epml rbp tx commit
166
+ bt epml rbp tx rollback
167
+ bt epml rbp tx abort
168
+
169
+ # Quick: tests-then-deploy — open a tx, edit, then commit-or-rollback based on tests
170
+ bt epml rbp tx begin
171
+ # ... make changes via rbp cmdgrps/roles/etc ...
172
+ bt epml quick tests-then-deploy --suite <suite_id> # commits if pass; rollbacks if fail
173
+ ```
174
+
175
+ ## Destructive operations (always confirm)
176
+
177
+ - `bt epml rbp policy delete-all --yes-i-mean-it` — wipes the entire RBP DB
178
+ - `bt epml rbp <resource> delete <ids>` — bulk delete by ID list
179
+ - `bt epml rbp tx commit` — applies in-flight changes
180
+ - `bt epml client-pkg ...` POST endpoints — triggers a real installer build
181
+
182
+ ## Path-version policy (CLI internal)
183
+
184
+ Where the spec offers both legacy `/api/pbul/{hostid}/rbp/<x>` and newer
185
+ `/api/v6/pbul/rbp/<x>`, the CLI uses the **legacy** version because v6 routes
186
+ are mostly IAM-only. If the API team flips v6 to PAT later, the swap is a
187
+ single-PR change in `client/base.py`.
188
+
189
+ ## Output formats
190
+
191
+ All `list` and `get` commands accept `-o/--output table|json`. Table is the
192
+ default for list; JSON is the default for single-resource gets.
193
+
194
+ ## Troubleshooting
195
+
196
+ - **401 "Access denied for this site"** — the URL didn't include `/site/<id>/epm/linux/`. Should never happen via the CLI; if you see it, file a bug.
197
+ - **403 IAM error** — the operation is on an IAM-only route. See the IAM-blocked list above.
198
+ - **400 with detailed validation messages** — auth passed; the body shape is wrong. Cross-reference `/home/admin/epml-cloud-spec.json` schemas.
199
+ - **Build server returns "building": true** — `bt epml client-pkg status` keeps `true` while a build is queued; idempotent to re-check.
@@ -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.31**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, and EPM Linux. **Version: 0.4.33**
4
4
 
5
5
  ## Setup
6
6
 
@@ -21,6 +21,7 @@ Use these slash commands for detailed product guidance:
21
21
  | `/pra` | PRA - jump items, vault, SSH CA |
22
22
  | `/entitle` | Entitle - JIT access, bundles, workflows |
23
23
  | `/epmw` | EPM Windows - computers, policies, requests |
24
+ | `/epml` | EPM Linux - RBP roles/cmdgrps/usergrps, policy, test suites, transactions |
24
25
 
25
26
  ## Command Structure
26
27
 
@@ -31,6 +32,7 @@ Use these slash commands for detailed product guidance:
31
32
  | PRA | `bt pra` | `jump-items`, `vault`, `jump-groups`, `quick` |
32
33
  | Entitle | `bt entitle` | `integrations`, `resources`, `bundles`, `permissions` |
33
34
  | EPM Windows | `bt epmw` | `computers`, `groups`, `policies`, `requests`, `quick` |
35
+ | EPM Linux | `bt epml` | `rbp` (cmdgrps/hostgrps/usergrps/tmdategrps/roles/policy/tests/tx), `settings`, `users`, `audit`, `siems`, `quick` |
34
36
 
35
37
  ## Common Patterns
36
38
 
@@ -58,6 +60,7 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
58
60
  | Entitle | `page`/`perPage` | `{"result": [...], "pagination": {...}}` |
59
61
  | PRA | `per_page`/`current_page` (1-indexed) | Array (pagination in headers) |
60
62
  | EPMW | `pageNumber`/`pageSize` | `{"data": [...], "totalCount": N}` |
63
+ | EPML | varies — `take`/`skip` (audit, siems), `start`/`len` (iologs), plain arrays | mostly arrays; some `{total, data}` |
61
64
 
62
65
  **Important:**
63
66
  - EPMW computers: Use `archive` not `delete` (405 error)
@@ -67,6 +70,8 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
67
70
  - Entitle permissions: Use server-side filters (`--resource`, `--user`, `--integration`) - dataset can be 20k+ records
68
71
  - Entitle users: Use `--all` flag only when needed - default fetches first page only
69
72
  - Windows builds: Rich must be pinned to `<14.0.0` (Rich 14 has PyInstaller unicode issues)
73
+ - **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.
74
+ - **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.
70
75
 
71
76
  ## Functional vs Managed Accounts
72
77
 
@@ -94,7 +99,8 @@ src/bt_cli/
94
99
  ├── pws/ # Password Safe
95
100
  ├── pra/ # PRA
96
101
  ├── entitle/ # Entitle
97
- └── epmw/ # EPM Windows
102
+ ├── epmw/ # EPM Windows
103
+ └── epml/ # EPM Linux (PAT auth, /site/<id>/epm/linux/... URLs)
98
104
  ```
99
105
 
100
106
  Each product follows: `client/` (API), `commands/` (CLI), `models/` (Pydantic)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bt-cli
3
- Version: 0.4.31
3
+ Version: 0.4.33
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,259 @@
1
+ # Plan: Add EPM Linux (`bt epml`) to bt-cli
2
+
3
+ ## Context
4
+
5
+ `bt-cli` v0.4.32 already supports PWS, PRA, Entitle, EPMW. EPM Linux is the Linux counterpart to EPMW (Windows). API is reached via the BeyondTrust public gateway (`api.beyondtrust.io`) authorized by Personal Access Tokens (PATs) issued from `app.beyondtrust.io`.
6
+
7
+ Discovery is done. Cached spec at `/home/admin/epml-cloud-spec.json` (OpenAPI 3.0.0, version 25.1.6, 160 paths / 205 ops, 12 product-area tags). Verified live against site `c882e07c-753b-4fcd-a1e6-7d39defec5ae` on 2026-05-01.
8
+
9
+ ## Two non-obvious gateway facts the spec doesn't tell you
10
+
11
+ ### 1. URL transform
12
+
13
+ The spec lists every path with a `/api/` prefix and an empty `servers` block. The gateway does NOT route those paths at the document root. Real URL shape:
14
+
15
+ ```
16
+ https://api.beyondtrust.io/site/<site-id>/epm/linux/<spec-path-minus-leading-/api/>
17
+ ```
18
+
19
+ So spec `/api/v1/users` → real URL `/site/<site-id>/epm/linux/v1/users`. Spec `/api/pbul/{hostid}/rbp/cmdgrps` → real URL `/site/<site-id>/epm/linux/pbul/{hostid}/rbp/cmdgrps`.
20
+
21
+ ### 2. Two authorizers, one spec
22
+
23
+ Spec declares `nomine-authorizer` (PAT) globally with **zero per-op overrides**. The gateway disagrees: a chunk of routes are wired to AWS IAM (SigV4), and PATs cannot reach those.
24
+
25
+ - **PAT-Bearer routes (200 OK)** — most `/pbul/{hostid}/rbp/...`, `/pbul/features`, `/pbul/rbp/testsuite/*`, `/v1/users`, `/v2/users`, `/v4/audit/sessions/*`, `/v4/siems` (read), `/externalapis`, `/epml/enhancedlicense`, `/epml/clientpkg`, `/settings` (read), `/pbul/{hostid}/iologs`.
26
+ - **IAM-only routes (403, AWS SigV4 error)** — all `/v6/pbul/rbp/*`, `/v1/hosts`, `/v6/users`, `/v1/auth/*`, `/v6/auth/*`, `/v2/appfeatures`, `/v7/directoryservices`, `/epml/license`, `/epml/licenseinfo`, `/v4/audit` (root), `/pbul/{hostid}/rbp/usrgrps`, `/pbul/{hostid}/rbp/timedategrps`, `/pbul/{hostid}/rbp/version`, `PUT /settings`, `POST /v4/siems`, etc.
27
+
28
+ **Path-version policy**: prefer legacy `/pbul/{hostid}/rbp/...` over the newer `/v6/pbul/rbp/...` because the legacy host-scoped variants are mostly Bearer-reachable, the v6 variants mostly aren't. Opposite of what spec-versioning instinct suggests.
29
+
30
+ ### Key gap for downstream design
31
+
32
+ **[Updated 2026-05-01]** Earlier draft called out `/pbul/{hostid}/rbp/usrgrps` as IAM-blocked — that was a path-name typo on my end. The real spec name is `usergrps`, and it (along with `tmdategrps` and `versions`) is fully Bearer-reachable. **No critical gaps in the RBP family for a PAT-only CLI.** All four child entity types under a role (cmdgrps, hostgrps, usergrps, tmdategrps) plus full policy export/import/versions/transactions ship as Bearer.
33
+
34
+ ## Command tree: `bt epml`
35
+
36
+ ```
37
+ bt epml
38
+ auth test | status
39
+ settings list | get
40
+ hosts list # synthesized from /pbul/features (since /v1/hosts is IAM)
41
+ users list | get
42
+ license get # /epml/enhancedlicense
43
+ siems list | get # read only — write is IAM
44
+ audit sessions list | sessions get | sessions categories
45
+ external-apis list | get | create | update | delete
46
+ client-pkg list | status | build # build = POST /epml/clientpkg
47
+ iolog list | get | replay | playback | commands
48
+ rbp
49
+ cmdgrps list | get | create | update | delete | members list/add/remove
50
+ hostgrps list | get | create | update | delete | members ...
51
+ roles list | get | create | update | delete | duplicate
52
+ cmdgrps add/remove | hostgrps add/remove
53
+ entitlement run # /pbul/{hostid}/rbp/entitlement
54
+ policy export | import | versions # versions list / get / revert
55
+ tests suites list | suites get | suites create | suites update | suites delete
56
+ tests add | tests update | tests delete | run
57
+ tx begin | commit | rollback | status
58
+ quick pasm-import-policy | tests-then-deploy # cross-product workflows (later)
59
+ ```
60
+
61
+ Deferred behind IAM (don't implement; document in skill file):
62
+ - `bt epml rbp usrgrps`, `rbp timedategrps`, `rbp version`
63
+ - `bt epml settings update`
64
+ - `bt epml siems create|update|delete`
65
+ - `bt epml license upload|retire|clients`
66
+ - `bt epml directory ...` (entire DirectoryServices tag)
67
+ - `bt epml auth roles|users|...` (appliance RBAC mgmt)
68
+ - Everything under `/v6/pbul/rbp/*`
69
+
70
+ ## Implementation phases
71
+
72
+ ### Phase 1 — Foundation + auth smoke (one PR)
73
+
74
+ **1a. New `PATAuth` strategy** — `src/bt_cli/core/auth.py`
75
+ - Class `PATAuth(AuthStrategy)` with `pat: str` (and optional `site_id` for URL composition).
76
+ - `authenticate()` is a no-op (PAT is already a bearer credential, no token exchange).
77
+ - Sets `Authorization: Bearer <pat>` on every request.
78
+ - No `sign_out()`.
79
+
80
+ **1b. New `EPMLConfig`** — `src/bt_cli/core/config.py`
81
+ - Fields: `api_url` (default `https://api.beyondtrust.io`), `site_id`, `pat`, `default_host_id` (default `100`), `verify_ssl`, `timeout`.
82
+ - `load_epml_config()` env vars: `BT_EPML_API_URL`, `BT_EPML_SITE_ID`, `BT_EPML_PAT`, `BT_EPML_DEFAULT_HOST`, `BT_EPML_VERIFY_SSL`, `BT_EPML_TIMEOUT`.
83
+ - Wire into `load_config()` dispatcher under key `"epml"`.
84
+
85
+ **1c. Add `"epml"` entry in `src/bt_cli/core/config_file.py` PRODUCTS dict**
86
+ - Fields: `api_url`, `site_id`, `pat`, `default_host_id`, `verify_ssl`, `timeout`.
87
+
88
+ **1d. Module skeleton**
89
+ - `src/bt_cli/epml/__init__.py`
90
+ - `src/bt_cli/epml/client/__init__.py` — exports `EPMLClient, get_client`
91
+ - `src/bt_cli/epml/client/base.py` — httpx client. **Critical detail**: every request URL is built as `f"{api_url}/site/{site_id}/epm/linux{path}"` where `path` is the spec path with `/api/` stripped. Provide a single `_build_path(spec_path: str) -> str` helper used by every method.
92
+ - `src/bt_cli/epml/commands/__init__.py` — registers sub-typer apps
93
+ - `src/bt_cli/epml/commands/auth.py` — `test` calls `GET /settings` and reports 200/403/401 distinctly (the 401 vs 403 split tells us which of the two failure modes — wrong site prefix vs IAM-blocked path — happened).
94
+ - `src/bt_cli/epml/models/__init__.py`
95
+ - `src/bt_cli/epml/models/common.py`
96
+
97
+ **1e. Wire into CLI** — `src/bt_cli/cli.py`
98
+ - `_get_epml_app()` lazy loader.
99
+ - `app.add_typer(...)` registration.
100
+ - Add `_test_epml_connection()` → list settings.
101
+ - Add EPM-L line to `whoami` output.
102
+ - Add `"epml"` to the valid-product list in `tree_command` and `find` command.
103
+ - Update `main_callback` help text.
104
+
105
+ **Verify**: `bt epml auth test` connects, gets 200 from `/settings`, prints "Connected to EPM Linux at site <id>".
106
+
107
+ ### Phase 2 — Read surface (one PR)
108
+
109
+ Models + client methods + commands for everything that's purely a GET against a Bearer-reachable path:
110
+
111
+ - `settings list/get` — `/settings`
112
+ - `users list` — `/v1/users` (paginated; spec lists `page`/`pageSize`)
113
+ - `users get` — `/v1/users/{id}`
114
+ - `hosts list` — derived from `/pbul/features` JSON (extract HostID, Hostname, IP, PbulVersion, Features map). Synthesized resource since `/v1/hosts` is IAM-blocked.
115
+ - `license get` — `/epml/enhancedlicense`
116
+ - `siems list` — `/v4/siems`
117
+ - `siems get` — `/v4/siems/{id}` (verify Bearer first)
118
+ - `audit sessions list` — `/v4/audit/sessions`
119
+ - `audit sessions get` — `/v4/audit/sessions/{id}`
120
+ - `audit sessions categories` — `/v4/audit/sessions/categories`
121
+ - `client-pkg list` — `/epml/clientpkg`
122
+ - `client-pkg status` — `/epml/clientpkg/status`
123
+ - `iolog list` — `/pbul/{hostid}/iologs` (host-scoped — defaults to `default_host_id`, override `--host`)
124
+
125
+ Pydantic models in `models/` per resource. Output formatter handles JSON/table/raw consistently with PWS pattern.
126
+
127
+ ### Phase 3 — RBP CRUD (one PR, biggest)
128
+
129
+ Resources, all under `/pbul/{hostid}/rbp/...`:
130
+
131
+ **Command groups** (`rbp cmdgrps`)
132
+ - `list` — `GET /pbul/{hostid}/rbp/cmdgrps`
133
+ - `get` — `GET /pbul/{hostid}/rbp/cmdgrps/{id}`
134
+ - `create` — `POST /pbul/{hostid}/rbp/cmdgrps`
135
+ - `update` — `PUT /pbul/{hostid}/rbp/cmdgrps/{id}`
136
+ - `delete` — `DELETE /pbul/{hostid}/rbp/cmdgrps/{id}`
137
+ - `members list|add|remove` — child resource
138
+
139
+ **Host groups** (`rbp hostgrps`) — same shape
140
+
141
+ **Roles** (`rbp roles`)
142
+ - CRUD + `duplicate` (if spec has it; otherwise GET-then-POST)
143
+ - Sub-resources: cmdgrps, hostgrps add/remove (NOT usrgrps — IAM-blocked)
144
+
145
+ **Entitlement report** (`rbp entitlement run`)
146
+ - `GET /pbul/{hostid}/rbp/entitlement` — returns "who can run what" report
147
+ - `--raw` flag → `/pbul/{hostid}/rbp/entitlement/raw`
148
+
149
+ **Policy** (`rbp policy`)
150
+ - `export` — full RBP DB serialization
151
+ - `import` — file upload
152
+ - `versions list|get|revert`
153
+ - `delete-all` — gated behind `--yes-i-mean-it` confirmation
154
+
155
+ ### Phase 4 — RBP test suites + transactions (one PR)
156
+
157
+ **Test suites** (`rbp tests`)
158
+ - `suites list|get|create|update|delete`
159
+ - `tests add|update|delete` (under a suite)
160
+ - `run --suite <id>` — `GET /pbul/{hostid}/rbp/testsuite/{id}/run` returns `{total, failures}` — exit 0 on zero failures, exit 1 with failure detail otherwise. **This is the killer command.**
161
+
162
+ **Transactions** (`rbp tx`)
163
+ - `begin` — opens a transaction (returns transaction ID)
164
+ - `commit` — atomic apply
165
+ - `rollback` — discard
166
+ - `status` — current pending changes
167
+
168
+ Pair them in a `quick tests-then-deploy` workflow:
169
+ 1. `tx begin`
170
+ 2. apply user-supplied changes
171
+ 3. `tests run --suite <id>`
172
+ 4. on success → `tx commit`; on failure → `tx rollback`
173
+
174
+ ### Phase 5 — Tests + skill + docs (one PR)
175
+
176
+ - `tests/epml/__init__.py`
177
+ - `tests/epml/test_client.py` — respx-mocked unit tests for URL transform, auth header, pagination
178
+ - `tests/epml/test_commands.py` — CLI invocations with mocked client
179
+ - `tests/conftest.py` — `mock_epml_config`, `epml_env_vars` fixtures
180
+ - `tests/fixtures/responses.py` — `EPML_*` constants
181
+ - `src/bt_cli/data/skills/epml/SKILL.md` — command reference, the URL-transform gotcha, the IAM-blocked list, host-id concept, test-suite workflow
182
+ - Update `btcli/CLAUDE.md` and `src/bt_cli/data/CLAUDE.md` — add `bt epml` row to the Command Structure table
183
+
184
+ ## Files to create (≈25)
185
+
186
+ ```
187
+ src/bt_cli/epml/__init__.py
188
+ src/bt_cli/epml/client/__init__.py
189
+ src/bt_cli/epml/client/base.py
190
+ src/bt_cli/epml/commands/__init__.py
191
+ src/bt_cli/epml/commands/auth.py
192
+ src/bt_cli/epml/commands/settings.py
193
+ src/bt_cli/epml/commands/users.py
194
+ src/bt_cli/epml/commands/hosts.py
195
+ src/bt_cli/epml/commands/license.py
196
+ src/bt_cli/epml/commands/siems.py
197
+ src/bt_cli/epml/commands/audit.py
198
+ src/bt_cli/epml/commands/external_apis.py
199
+ src/bt_cli/epml/commands/client_pkg.py
200
+ src/bt_cli/epml/commands/iolog.py
201
+ src/bt_cli/epml/commands/rbp_cmdgrps.py
202
+ src/bt_cli/epml/commands/rbp_hostgrps.py
203
+ src/bt_cli/epml/commands/rbp_roles.py
204
+ src/bt_cli/epml/commands/rbp_entitlement.py
205
+ src/bt_cli/epml/commands/rbp_policy.py
206
+ src/bt_cli/epml/commands/rbp_tests.py
207
+ src/bt_cli/epml/commands/rbp_tx.py
208
+ src/bt_cli/epml/models/__init__.py
209
+ src/bt_cli/epml/models/common.py
210
+ src/bt_cli/epml/models/{user,host,license,siem,audit,clientpkg,cmdgrp,hostgrp,role,test,policy,settings,iolog}.py (one per resource as needed)
211
+ src/bt_cli/data/skills/epml/SKILL.md
212
+ tests/epml/__init__.py
213
+ tests/epml/test_client.py
214
+ tests/epml/test_commands.py
215
+ ```
216
+
217
+ ## Files to modify
218
+
219
+ ```
220
+ src/bt_cli/core/auth.py — add PATAuth (~25 lines)
221
+ src/bt_cli/core/config.py — add EPMLConfig + load_epml_config (~40 lines)
222
+ src/bt_cli/core/config_file.py — add "epml" entry (~10 lines)
223
+ src/bt_cli/cli.py — lazy loader, register, whoami test, tree, find, help text
224
+ tests/conftest.py — fixtures
225
+ tests/fixtures/responses.py — EPML_* mock constants
226
+ btcli/CLAUDE.md — add bt epml row + 1-line callout for the URL transform
227
+ src/bt_cli/data/CLAUDE.md — same
228
+ ```
229
+
230
+ ## Key design decisions
231
+
232
+ 1. **Auth: dedicated `PATAuth` strategy.** Cleaner than overloading existing OAuth2 strategies; the auth flow is genuinely different (no token exchange, just a static bearer credential).
233
+ 2. **URL transform encapsulated in `_build_path()`.** Every client method calls it. Anyone reading the spec and writing a new method needs zero awareness — they paste the spec path verbatim.
234
+ 3. **Synthesize `hosts list` from `/pbul/features`.** `/v1/hosts` is IAM-blocked, but `/pbul/features` returns the host data we need anyway. Surface as a normal-looking command; document the substitution in the skill file.
235
+ 4. **Default host-id baked into config.** Most RBP commands take `--host` but default to `BT_EPML_DEFAULT_HOST` (default `100` per the discovery). Mirrors how PWS handles workgroup IDs.
236
+ 5. **Skip everything IAM-blocked.** Don't add stub commands. Skill file documents what's intentionally absent and why, so users don't ask "why no `bt epml directory`."
237
+ 6. **Path-version pick: legacy over v6.** Where the spec has both `/pbul/{hostid}/rbp/X` (legacy) and `/v6/pbul/rbp/X` (newer), use legacy — it's Bearer, v6 is IAM. If the API team flips v6 to Bearer later, swap in a single PR.
238
+ 7. **Env var prefix `BT_EPML_`.** Consistent with `BT_PWS_`, `BT_PRA_`, `BT_EPM_` (Windows).
239
+
240
+ ## What to bring back to the API team
241
+
242
+ Capture in the skill file as known issues; track separately as upstream asks:
243
+
244
+ 1. Spec global security claims `nomine-authorizer` for every op, but the gateway has IAM-only routes. Either flip those to Bearer or override per-op `security` in the spec so it stops lying.
245
+ 2. `/pbul/{hostid}/rbp/usrgrps` IAM-only blocks core RBP workflows — highest-priority flip request.
246
+ 3. `/settings` is read-Bearer but write-IAM (same path, two authorizers per method). Unusual config.
247
+ 4. Service-account / non-human auth path. PATs are user-bound. CLI/automation needs an alternative; nothing in the EPM-L surface offers one today.
248
+ 5. `servers` is empty in the spec; the `/site/<id>/epm/linux/` prefix is invisible to anyone reading the OpenAPI doc. Add `servers: [{url: "https://api.beyondtrust.io/site/{siteId}/epm/linux", variables: {...}}]` and strip `/api/` from paths.
249
+
250
+ ## Verification
251
+
252
+ 1. `bt epml auth test` — 200 from `/settings`
253
+ 2. `bt epml hosts list` — shows HostID 100 PMULPolicy
254
+ 3. `bt epml users list` — returns user list
255
+ 4. `bt epml rbp cmdgrps list --host 100` — non-empty
256
+ 5. `bt epml rbp tests run --suite <id>` — runs and exits cleanly
257
+ 6. `bt tree epml` — full tree
258
+ 7. `bt whoami` — includes EPM-L line
259
+ 8. `pytest tests/epml/ -v` — all green
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bt-cli"
7
- version = "0.4.31"
7
+ version = "0.4.33"
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.31"
3
+ __version__ = "0.4.33"