bt-cli 0.4.73__tar.gz → 0.5.0__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 (300) hide show
  1. {bt_cli-0.4.73/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/entitle/SKILL.md +166 -3
  2. {bt_cli-0.4.73/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/pra/SKILL.md +3 -0
  3. {bt_cli-0.4.73 → bt_cli-0.5.0}/CLAUDE.md +5 -4
  4. {bt_cli-0.4.73 → bt_cli-0.5.0}/PKG-INFO +1 -1
  5. bt_cli-0.5.0/entitle-gaps-implementation-plan.md +378 -0
  6. {bt_cli-0.4.73 → bt_cli-0.5.0}/pyproject.toml +1 -1
  7. bt_cli-0.5.0/scripts/sync-package-data.sh +77 -0
  8. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/__init__.py +1 -1
  9. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/cli.py +20 -7
  10. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/commands/configure.py +9 -9
  11. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/commands/learn.py +2 -2
  12. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/commands/quick.py +5 -6
  13. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/output.py +57 -4
  14. bt_cli-0.5.0/src/bt_cli/data/CLAUDE.md +141 -0
  15. bt_cli-0.5.0/src/bt_cli/data/skills/entitle/SKILL.md +468 -0
  16. {bt_cli-0.4.73/.claude → bt_cli-0.5.0/src/bt_cli/data}/skills/epml/SKILL.md +24 -3
  17. bt_cli-0.5.0/src/bt_cli/data/skills/pf/SKILL.md +116 -0
  18. bt_cli-0.5.0/src/bt_cli/data/skills/pra/SKILL.md +219 -0
  19. bt_cli-0.5.0/src/bt_cli/data/skills/pws/SKILL.md +353 -0
  20. bt_cli-0.5.0/src/bt_cli/data/skills/secrets/SKILL.md +229 -0
  21. bt_cli-0.5.0/src/bt_cli/data/skills/session-analysis/SKILL.md +72 -0
  22. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/client/base.py +358 -19
  23. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/__init__.py +17 -0
  24. bt_cli-0.5.0/src/bt_cli/entitle/commands/_helpers.py +68 -0
  25. bt_cli-0.5.0/src/bt_cli/entitle/commands/agent_tokens.py +402 -0
  26. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/auth.py +4 -2
  27. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/bundles.py +65 -4
  28. bt_cli-0.5.0/src/bt_cli/entitle/commands/chats.py +162 -0
  29. bt_cli-0.5.0/src/bt_cli/entitle/commands/directory_groups.py +84 -0
  30. bt_cli-0.5.0/src/bt_cli/entitle/commands/forwards.py +392 -0
  31. bt_cli-0.5.0/src/bt_cli/entitle/commands/integrations.py +421 -0
  32. bt_cli-0.5.0/src/bt_cli/entitle/commands/permissions.py +139 -0
  33. bt_cli-0.5.0/src/bt_cli/entitle/commands/policies.py +336 -0
  34. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/requests.py +22 -10
  35. bt_cli-0.5.0/src/bt_cli/entitle/commands/resources.py +279 -0
  36. bt_cli-0.5.0/src/bt_cli/entitle/commands/roles.py +368 -0
  37. bt_cli-0.5.0/src/bt_cli/entitle/commands/tasks.py +281 -0
  38. bt_cli-0.5.0/src/bt_cli/entitle/commands/user_accounts.py +209 -0
  39. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/users.py +81 -5
  40. bt_cli-0.5.0/src/bt_cli/entitle/commands/webhooks.py +516 -0
  41. bt_cli-0.5.0/src/bt_cli/entitle/commands/workflow_webhook_writes.py +304 -0
  42. bt_cli-0.5.0/src/bt_cli/entitle/commands/workflows.py +712 -0
  43. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/client_pkg.py +5 -0
  44. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_roles.py +4 -8
  45. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_tmdategrps.py +5 -0
  46. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_usergrps.py +5 -0
  47. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/settings.py +5 -0
  48. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/groups.py +5 -0
  49. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/policies.py +16 -9
  50. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/quick.py +16 -11
  51. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/users.py +5 -0
  52. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/group_policies.py +17 -0
  53. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/import_export.py +3 -4
  54. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/jump_items.py +20 -0
  55. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/quick.py +9 -9
  56. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/vault.py +10 -0
  57. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/accounts.py +14 -5
  58. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/assets.py +13 -4
  59. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/clouds.py +8 -4
  60. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/config.py +5 -6
  61. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/credentials.py +3 -4
  62. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/databases.py +18 -4
  63. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/directories.py +8 -4
  64. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/functional.py +17 -3
  65. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/import_export.py +3 -4
  66. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/platforms.py +3 -4
  67. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/quick.py +9 -10
  68. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/search.py +7 -8
  69. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/secrets.py +51 -7
  70. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/sessions.py +7 -6
  71. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/systems.py +8 -4
  72. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/users.py +9 -9
  73. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/workgroups.py +7 -3
  74. bt_cli-0.5.0/tests/_runner.py +21 -0
  75. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/conftest.py +44 -0
  76. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/test_command_tree.py +21 -0
  77. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/test_json_stdout_purity.py +4 -10
  78. bt_cli-0.5.0/tests/core/test_package_data_sync.py +353 -0
  79. bt_cli-0.5.0/tests/data/entitle-api-reference.openapi.json +8930 -0
  80. bt_cli-0.5.0/tests/entitle/test_agent_tokens.py +322 -0
  81. bt_cli-0.5.0/tests/entitle/test_agent_tokens_writes.py +433 -0
  82. bt_cli-0.5.0/tests/entitle/test_chats.py +189 -0
  83. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/entitle/test_client.py +59 -0
  84. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/entitle/test_commands.py +326 -3
  85. bt_cli-0.5.0/tests/entitle/test_coverage.py +123 -0
  86. bt_cli-0.5.0/tests/entitle/test_directory_groups.py +222 -0
  87. bt_cli-0.5.0/tests/entitle/test_forwards.py +193 -0
  88. bt_cli-0.5.0/tests/entitle/test_forwards_writes.py +521 -0
  89. bt_cli-0.5.0/tests/entitle/test_integrations_writes.py +466 -0
  90. bt_cli-0.5.0/tests/entitle/test_policies_writes.py +413 -0
  91. bt_cli-0.5.0/tests/entitle/test_resources_writes.py +375 -0
  92. bt_cli-0.5.0/tests/entitle/test_roles_writes.py +390 -0
  93. bt_cli-0.5.0/tests/entitle/test_tasks.py +219 -0
  94. bt_cli-0.5.0/tests/entitle/test_tasks_writes.py +337 -0
  95. bt_cli-0.5.0/tests/entitle/test_user_accounts.py +260 -0
  96. bt_cli-0.5.0/tests/entitle/test_user_accounts_writes.py +419 -0
  97. bt_cli-0.5.0/tests/entitle/test_webhooks.py +279 -0
  98. bt_cli-0.5.0/tests/entitle/test_webhooks_writes.py +590 -0
  99. bt_cli-0.5.0/tests/entitle/test_workflow_webhook_writes.py +522 -0
  100. bt_cli-0.5.0/tests/entitle/test_workflows_writes.py +508 -0
  101. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pra/test_jump_item_fixes.py +4 -10
  102. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pws/test_commands.py +4 -10
  103. bt_cli-0.4.73/.claude/skills/entitle/SKILL.md +0 -231
  104. bt_cli-0.4.73/.claude/skills/pra/SKILL.md +0 -153
  105. bt_cli-0.4.73/.claude/skills/pws/SKILL.md +0 -198
  106. bt_cli-0.4.73/scripts/sync-package-data.sh +0 -21
  107. bt_cli-0.4.73/src/bt_cli/data/CLAUDE.md +0 -118
  108. bt_cli-0.4.73/src/bt_cli/entitle/commands/integrations.py +0 -60
  109. bt_cli-0.4.73/src/bt_cli/entitle/commands/permissions.py +0 -70
  110. bt_cli-0.4.73/src/bt_cli/entitle/commands/policies.py +0 -97
  111. bt_cli-0.4.73/src/bt_cli/entitle/commands/resources.py +0 -131
  112. bt_cli-0.4.73/src/bt_cli/entitle/commands/roles.py +0 -74
  113. bt_cli-0.4.73/src/bt_cli/entitle/commands/workflows.py +0 -187
  114. {bt_cli-0.4.73 → bt_cli-0.5.0}/.claude/skills/bt/SKILL.md +0 -0
  115. {bt_cli-0.4.73/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/epml/SKILL.md +0 -0
  116. {bt_cli-0.4.73 → bt_cli-0.5.0}/.claude/skills/epmw/SKILL.md +0 -0
  117. {bt_cli-0.4.73/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/pf/SKILL.md +0 -0
  118. {bt_cli-0.4.73/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/pws/SKILL.md +0 -0
  119. {bt_cli-0.4.73/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/secrets/SKILL.md +0 -0
  120. {bt_cli-0.4.73/src/bt_cli/data → bt_cli-0.5.0/.claude}/skills/session-analysis/SKILL.md +0 -0
  121. {bt_cli-0.4.73 → bt_cli-0.5.0}/.env.example +0 -0
  122. {bt_cli-0.4.73 → bt_cli-0.5.0}/.github/workflows/ci.yml +0 -0
  123. {bt_cli-0.4.73 → bt_cli-0.5.0}/.github/workflows/release.yml +0 -0
  124. {bt_cli-0.4.73 → bt_cli-0.5.0}/.gitignore +0 -0
  125. {bt_cli-0.4.73 → bt_cli-0.5.0}/README.md +0 -0
  126. {bt_cli-0.4.73 → bt_cli-0.5.0}/assets/cli-help.png +0 -0
  127. {bt_cli-0.4.73 → bt_cli-0.5.0}/assets/cli-output.png +0 -0
  128. {bt_cli-0.4.73 → bt_cli-0.5.0}/bt-cli-cheatsheet.html +0 -0
  129. {bt_cli-0.4.73 → bt_cli-0.5.0}/bt-cli.spec +0 -0
  130. {bt_cli-0.4.73 → bt_cli-0.5.0}/bt_entry.py +0 -0
  131. {bt_cli-0.4.73 → bt_cli-0.5.0}/epml-clients-server-side-filters-plan.md +0 -0
  132. {bt_cli-0.4.73 → bt_cli-0.5.0}/epml-implementation-plan.md +0 -0
  133. {bt_cli-0.4.73 → bt_cli-0.5.0}/pf-implementation-plan.md +0 -0
  134. {bt_cli-0.4.73 → bt_cli-0.5.0}/scripts/bt_entry.py +0 -0
  135. {bt_cli-0.4.73 → bt_cli-0.5.0}/scripts/pf_onboard.py +0 -0
  136. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/commands/__init__.py +0 -0
  137. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/__init__.py +0 -0
  138. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/auth.py +0 -0
  139. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/client.py +0 -0
  140. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/command_tree.py +0 -0
  141. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/config.py +0 -0
  142. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/config_file.py +0 -0
  143. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/csv_utils.py +0 -0
  144. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/errors.py +0 -0
  145. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/prompts.py +0 -0
  146. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/core/rest_debug.py +0 -0
  147. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/data/__init__.py +0 -0
  148. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  149. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  150. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/__init__.py +0 -0
  151. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/client/__init__.py +0 -0
  152. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/accounts.py +0 -0
  153. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/applications.py +0 -0
  154. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/audit.py +0 -0
  155. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/commands/jit.py +0 -0
  156. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/__init__.py +0 -0
  157. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/bundle.py +0 -0
  158. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/common.py +0 -0
  159. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/integration.py +0 -0
  160. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/permission.py +0 -0
  161. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/policy.py +0 -0
  162. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/resource.py +0 -0
  163. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/role.py +0 -0
  164. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/user.py +0 -0
  165. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/entitle/models/workflow.py +0 -0
  166. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/__init__.py +0 -0
  167. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/client/__init__.py +0 -0
  168. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/client/base.py +0 -0
  169. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/__init__.py +0 -0
  170. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/audit.py +0 -0
  171. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/auth.py +0 -0
  172. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/clients.py +0 -0
  173. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/external_apis.py +0 -0
  174. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/hosts.py +0 -0
  175. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/iolog.py +0 -0
  176. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/license.py +0 -0
  177. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/quick.py +0 -0
  178. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
  179. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
  180. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
  181. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
  182. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
  183. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
  184. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/siems.py +0 -0
  185. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/commands/users.py +0 -0
  186. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epml/models/__init__.py +0 -0
  187. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/__init__.py +0 -0
  188. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/client/__init__.py +0 -0
  189. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/client/base.py +0 -0
  190. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/__init__.py +0 -0
  191. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/audits.py +0 -0
  192. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/auth.py +0 -0
  193. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/computers.py +0 -0
  194. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/events.py +0 -0
  195. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/requests.py +0 -0
  196. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/roles.py +0 -0
  197. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/commands/tasks.py +0 -0
  198. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/epmw/models/__init__.py +0 -0
  199. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/__init__.py +0 -0
  200. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/client/__init__.py +0 -0
  201. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/client/base.py +0 -0
  202. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/commands/__init__.py +0 -0
  203. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/commands/auth.py +0 -0
  204. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/commands/machines.py +0 -0
  205. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/commands/tokens.py +0 -0
  206. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/commands/user.py +0 -0
  207. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pf/commands/users.py +0 -0
  208. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/__init__.py +0 -0
  209. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/client/__init__.py +0 -0
  210. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/client/base.py +0 -0
  211. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/__init__.py +0 -0
  212. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/auth.py +0 -0
  213. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/jump_clients.py +0 -0
  214. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  215. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  216. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/policies.py +0 -0
  217. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/teams.py +0 -0
  218. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/commands/users.py +0 -0
  219. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/__init__.py +0 -0
  220. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/common.py +0 -0
  221. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/group_policy.py +0 -0
  222. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/jump_client.py +0 -0
  223. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/jump_group.py +0 -0
  224. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/jump_item.py +0 -0
  225. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/jumpoint.py +0 -0
  226. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/team.py +0 -0
  227. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/user.py +0 -0
  228. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pra/models/vault.py +0 -0
  229. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/__init__.py +0 -0
  230. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/client/__init__.py +0 -0
  231. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/client/base.py +0 -0
  232. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  233. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  234. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/__init__.py +0 -0
  235. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/attributes.py +0 -0
  236. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/audit.py +0 -0
  237. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/commands/auth.py +0 -0
  238. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/config.py +0 -0
  239. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/models/__init__.py +0 -0
  240. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/models/account.py +0 -0
  241. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/models/asset.py +0 -0
  242. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/models/common.py +0 -0
  243. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/pws/models/system.py +0 -0
  244. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/__init__.py +0 -0
  245. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/client/__init__.py +0 -0
  246. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/client/base.py +0 -0
  247. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/__init__.py +0 -0
  248. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/_hints.py +0 -0
  249. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/auth.py +0 -0
  250. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/dynamic.py +0 -0
  251. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/folders.py +0 -0
  252. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/integrations.py +0 -0
  253. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/leases.py +0 -0
  254. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/commands/static.py +0 -0
  255. {bt_cli-0.4.73 → bt_cli-0.5.0}/src/bt_cli/secrets/models/__init__.py +0 -0
  256. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/__init__.py +0 -0
  257. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/__init__.py +0 -0
  258. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/test_auth.py +0 -0
  259. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/test_config.py +0 -0
  260. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/test_config_file.py +0 -0
  261. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/test_errors.py +0 -0
  262. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/test_output.py +0 -0
  263. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/core/test_rest_debug.py +0 -0
  264. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/entitle/__init__.py +0 -0
  265. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/entitle-smoke-test.sh +0 -0
  266. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/epml/__init__.py +0 -0
  267. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/epml/test_client.py +0 -0
  268. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/epml/test_commands.py +0 -0
  269. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/epmw/__init__.py +0 -0
  270. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/epmw/test_client.py +0 -0
  271. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/epmw/test_commands.py +0 -0
  272. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/epmw-quick-test-plan.md +0 -0
  273. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/fixtures/__init__.py +0 -0
  274. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/fixtures/responses.py +0 -0
  275. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/__init__.py +0 -0
  276. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/conftest.py +0 -0
  277. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/helpers.py +0 -0
  278. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/test_entitle_integration.py +0 -0
  279. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/test_epmw_integration.py +0 -0
  280. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/test_epmw_lifecycle.py +0 -0
  281. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/test_pra_integration.py +0 -0
  282. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/test_pra_lifecycle.py +0 -0
  283. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/test_pws_integration.py +0 -0
  284. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/integration/test_pws_lifecycle.py +0 -0
  285. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pf/__init__.py +0 -0
  286. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pf/test_client.py +0 -0
  287. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pf/test_commands.py +0 -0
  288. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pra/__init__.py +0 -0
  289. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pra/test_client.py +0 -0
  290. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pra/test_commands.py +0 -0
  291. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pra-smoke-test.sh +0 -0
  292. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pra-test-plan.md +0 -0
  293. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pws/__init__.py +0 -0
  294. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pws/test_client.py +0 -0
  295. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pws/test_quick_sessions.py +0 -0
  296. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pws-quick-test-plan.md +0 -0
  297. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/pws-smoke-test.sh +0 -0
  298. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/secrets/__init__.py +0 -0
  299. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/secrets/test_client.py +0 -0
  300. {bt_cli-0.4.73 → bt_cli-0.5.0}/tests/secrets/test_commands.py +0 -0
@@ -5,6 +5,33 @@ description: Entitle commands for JIT access, bundles, workflows, and permission
5
5
 
6
6
  # Entitle Commands (`bt entitle`)
7
7
 
8
+ ## Two API tokens — read vs. write-on-behalf-of-user
9
+
10
+ Entitle issues two distinct kinds of bearer token. The CLI uses both:
11
+
12
+ | Token | Config field | Env var | Used for |
13
+ |---|---|---|---|
14
+ | **Org / admin** | `entitle.api_key` | `BT_ENTITLE_API_KEY` | All read-only commands (`list`, `get`) and admin writes (revoke, delete) |
15
+ | **User-context** | `entitle.user_api_key` | `BT_ENTITLE_USER_API_KEY` | On-behalf-of-a-user writes — currently `bt entitle requests create` |
16
+
17
+ The org/admin token **cannot** create access requests because requests are
18
+ made *as* a person, not on behalf of the org. If only the admin token is
19
+ configured, `bt entitle requests create` exits early with a panel explaining
20
+ how to add a user token.
21
+
22
+ ```yaml
23
+ # ~/.bt-cli/config.yaml
24
+ profiles:
25
+ default:
26
+ entitle:
27
+ api_url: https://api.us.entitle.io
28
+ api_key: <ORG_TOKEN> # broad read access
29
+ user_api_key: <USER_TOKEN> # personal token, for `requests create`
30
+ ```
31
+
32
+ The user token is generated from `app.entitle.io` → user profile → API tokens
33
+ → **Create user token** (different page from the org token).
34
+
8
35
  ## IMPORTANT: Destructive Operations
9
36
 
10
37
  **ALWAYS confirm with the user before:**
@@ -296,10 +323,146 @@ bt entitle roles list --resource <resource_id>
296
323
  **Kubernetes:** EKS, AKS, GKE, Rancher
297
324
  **BeyondTrust:** Password Safe, PRA, Remote Support
298
325
 
326
+ ## Newer command families
327
+
328
+ Added in 0.5.0; the CLI now covers 75 of the API's 77 operations.
329
+
330
+ ```bash
331
+ # Grant/revoke work items behind an access request (read + status only)
332
+ bt entitle tasks list
333
+ bt entitle tasks get <task-id>
334
+ bt entitle tasks set-status <task-id> --status todo|inProgress|done
335
+ bt entitle tasks archive <task-id>
336
+
337
+ # Agent tokens — these are real credentials
338
+ bt entitle agent-tokens list
339
+ bt entitle agent-tokens create --name "collector" -o json # token shown ONCE
340
+ bt entitle agent-tokens update <id> --name "new name"
341
+ bt entitle agent-tokens delete <id>
342
+
343
+ # Outbound webhooks — two separate families
344
+ bt entitle audit-webhooks list | get | create | update | enable | delete
345
+ bt entitle workflow-webhooks list | get | create | update | delete
346
+
347
+ # Delegation: send someone's queue to someone else
348
+ bt entitle request-forwards list | get | for-user <user-id> | create | delete
349
+ bt entitle review-forwards list | get | create | delete
350
+
351
+ # Identity plumbing
352
+ bt entitle directory-groups list --search "DYN-ROLE"
353
+ bt entitle user-accounts list | create | delete
354
+ bt entitle users pats <user-id>
355
+ bt entitle permissions account <account-id>
356
+ bt entitle chats channels --integration-type slack
357
+ ```
358
+
359
+ Writes were added to the families that previously had reads only: `roles`,
360
+ `policies`, `workflows`, `integrations` all gained create/update/delete, and
361
+ `resources` and `bundles` gained `update`.
362
+
363
+ ## Update semantics: PUT merges, but lists inside it do not
364
+
365
+ Live-verified per family. **Every family merges** — a field you leave out of the
366
+ body keeps its current value, so no command does read-modify-write and a `curl`
367
+ sending one field is safe.
368
+
369
+ Two qualifications that matter more than the rule:
370
+
371
+ - **A list you *do* send replaces that list wholesale.** `--role`, `--tag`,
372
+ `--group`, `--duration` set the whole collection; they never append. Passing
373
+ `-g` alone drops every other target.
374
+ - **`workflows` merges at the top level but REPLACES its `rules` array.** A
375
+ two-rule workflow plus a PUT carrying one rule leaves **one rule**, and each
376
+ rule object must be complete (`400 "rules.0.inGroups must be an array"`).
377
+
378
+ ## Writes that return 200 and do nothing
379
+
380
+ The most dangerous behaviour in this API. All live-verified — **verify a write
381
+ with a follow-up GET**, because the response will not tell you:
382
+
383
+ | Operation | Silently ignored |
384
+ |---|---|
385
+ | `POST /roles` | drops `prerequisitePermissions` (CLI issues a follow-up PUT) |
386
+ | `PUT /roles/{id}` | ignores `name`, `resource`, `virtualizedRole` |
387
+ | `PUT /integrations/{id}` | ignores `allowChangingAccountPermissions`, `allowCreatingAccounts`, `readonly` |
388
+ | `PUT /policies/{id}` | a `sortOrder` past the last policy keeps the old value |
389
+ | workflow webhooks | `approvalAlgorithmIds` is read-only |
390
+ | both webhook families | `{"enabled": false}` on create *and* update |
391
+ | integrations update | an unknown `--owner` (but create 404s on it) |
392
+
393
+ **`POST` on both forwards families is an UPSERT keyed on the forwarder** — a
394
+ second create for the same person returns the *same id* with the target
395
+ replaced, 200, no 409. "Create a forward for Alice" therefore silently
396
+ redirects her queue away from whoever it pointed at. Check with
397
+ `request-forwards for-user <id>` first.
398
+
399
+ **`POST /usersAccounts` is idempotent** on `(user, account)`: it returns the
400
+ pre-existing row with its original `createdAt` rather than creating a duplicate.
401
+ A script that deletes "the row it just created" will be deleting a real one.
402
+
403
+ ## Pagination: the envelope tells you
404
+
405
+ `pagination` present in the response means the endpoint really pages. Absent
406
+ means `page`/`perPage` are **silently ignored** and one GET returns everything —
407
+ requesting page 2 hands back the same first row.
408
+
409
+ | Ignores paging | Really pages |
410
+ |---|---|
411
+ | `/tasks`, `/applications`, `/auditLogsWebhooks`, `/workflowsWebhooks` | `/agentTokens`, `/directoryGroups`, `/usersAccounts`, both `*Forwards` |
412
+
413
+ `perPage` caps at **100** and 400s above it. `--limit` means *maximum results*
414
+ and bounds the fetch, so a small limit is cheap; it does not mean page size.
415
+
416
+ ## Secrets and redaction
417
+
418
+ - **`agent-tokens create` returns a ~776-character token exactly once.** An
419
+ immediate GET returns only `id` and `name`. It is surfaced solely under
420
+ `-o json`, redacted in tables, with a stderr warning that it cannot be
421
+ retrieved again. Capture it at creation or delete and recreate.
422
+ - **Webhook header values are redacted by header NAME, not per hook.**
423
+ `Authorization`, `X-Api-Key`, `api-key`, `X-Secret`, `Token` read back as
424
+ `***`; `Cookie` and arbitrary names come back verbatim. **A GET-edit-PUT
425
+ round trip would store the literal `***` over a real secret**, which is why
426
+ neither webhook family offers `--file` and update sends only your flags.
427
+
428
+ ## Tenant-shaped gotchas (not CLI bugs)
429
+
430
+ - **A policy has no name or description** — the body is only
431
+ `inGroups`/`bundles`/`roles`/`sortOrder`, so policies are trackable by UUID
432
+ alone. `POST` also rejects an empty grant (`"Policy must grant at least 1
433
+ permission"`), so a policy can never be created inert: any create is a real
434
+ grant for as long as it lives.
435
+ - **Webhooks cannot be created disabled** in either family, and `/disable`
436
+ 404s. `DELETE` is the only off switch — which is why `audit-webhooks` has
437
+ `enable` and deliberately no `--disable` flag.
438
+ - **Deleted webhook names stay reserved**: recreating a just-deleted name 400s
439
+ with "Resource already exists".
440
+ - **`chats channels`** needs the *user* token (the org token 401s) and accepts
441
+ only `--integration-type slack`. It returns
442
+ `500 chatsService.missingScopes` until the tenant's Slack app is
443
+ re-authorized with `channels:read,groups:read,mpim:read,im:read`; the CLI
444
+ exits 3 with that message rather than a generic failure.
445
+ - **`orgRoles` is unreachable** — `GET /orgRoles` and
446
+ `PUT /orgRoles/identities` are 401 on *both* tokens, the same org-level
447
+ gating Pathfinder has. These are the only two API operations the CLI does not
448
+ implement, and they stay unimplemented until the gateway opens up.
449
+ - **Bundle create needs at least one role** (`"roles must contain at least 1
450
+ elements"`), and **role create** needs only `name` + `resource` +
451
+ `requestable` — the spec's required `prerequisitePermissions` is the field
452
+ the server discards. Virtual Application resources also need
453
+ `virtualizedRole`.
454
+ - **Omitting either side of a `usersAccounts` body is a 500, not a 400.**
455
+
299
456
  ## API Notes
300
457
 
301
458
  - Base path: `/public/v1`
302
- - Pagination: `page`/`perPage`
303
- - Response: `{"result": [...], "pagination": {...}}`
459
+ - Pagination: `page`/`perPage`, but only where the response carries a
460
+ `pagination` key — see the table above. `perPage` caps at 100.
461
+ - Response: `{"result": [...]}`, with `pagination` alongside it on the
462
+ endpoints that really page. Single GETs wrap the object in `result` too;
463
+ `eligibleJitAccess` is the exception, returning `{roles, bundles, pagination}`.
304
464
  - All IDs are UUIDs (strings)
305
- - Bearer token auth
465
+ - Bearer token auth; some routes need the *user* token, not the org one
466
+ - Coverage: 75 of 77 operations. The two missing are the `orgRoles` pair,
467
+ blocked by org-level gating. `tests/entitle/test_coverage.py` fails if that
468
+ drifts.
@@ -14,6 +14,9 @@ description: Privileged Remote Access commands for jump items, vault accounts, a
14
14
  - `bt pra jump-items tunnel delete` - Deletes protocol tunnel
15
15
  - `bt pra jump-groups delete` - Deletes jump group
16
16
  - `bt pra vault accounts delete` - Deletes vault account
17
+ - `bt pra users delete` - Deletes a user (non-admins only)
18
+ - `bt pra policies group delete` - Deletes a Group Policy
19
+ - `bt pra policies group {jumpoints|members|jump-groups|vault-accounts|vault-account-groups|teams} remove` - Detaches a sub-resource from a Group Policy
17
20
 
18
21
  List affected resources first, then ask for explicit confirmation.
19
22
 
@@ -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.73**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.5.0**
4
4
 
5
5
  ## Setup
6
6
 
@@ -42,7 +42,7 @@ Use these slash commands for detailed product guidance:
42
42
  | Cross-product | `bt quick` | `pasm-onboard`, `pasm-offboard`, `pasm-search` |
43
43
  | Password Safe | `bt pws` | `systems`, `accounts`, `credentials`, `sessions`, `secrets`, `audit`, `quick` |
44
44
  | PRA | `bt pra` | `jump-items`, `vault`, `jump-groups`, `quick` |
45
- | Entitle | `bt entitle` | `integrations`, `resources`, `bundles`, `permissions`, `jit`, `audit` |
45
+ | Entitle | `bt entitle` | `integrations`, `resources`, `roles`, `bundles`, `workflows`, `policies`, `permissions`, `users`, `accounts`, `requests`, `tasks`, `jit`, `audit`, `agent-tokens`, `audit-webhooks`, `workflow-webhooks`, `request-forwards`, `review-forwards`, `directory-groups`, `user-accounts`, `chats` |
46
46
  | EPM Windows | `bt epmw` | `computers`, `groups`, `policies`, `requests`, `quick` |
47
47
  | EPM Linux | `bt epml` | `rbp` (cmdgrps/hostgrps/usergrps/tmdategrps/roles/policy/tests/tx), `settings`, `users`, `audit`, `siems`, `quick` |
48
48
  | Secrets API | `bt secrets` | `folders`, `static`, `dynamic` (incl. `generate`), `leases`, `integrations` |
@@ -71,7 +71,7 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
71
71
  | Product | Pagination | Response Format |
72
72
  |---------|------------|-----------------|
73
73
  | PWS | `limit`/`offset` | `{"TotalCount": N, "Data": [...]}` |
74
- | Entitle | `page`/`perPage` | `{"result": [...], "pagination": {...}}` |
74
+ | Entitle | `page`/`perPage` (**only where `pagination` is in the response**; max 100) | `{"result": [...]}`, `pagination` alongside it when the endpoint really pages |
75
75
  | PRA | `per_page`/`current_page` (1-indexed) | Array (pagination in headers) |
76
76
  | EPMW | `pageNumber`/`pageSize` | `{"data": [...], "totalCount": N}` |
77
77
  | EPML | varies — `take`/`skip` (audit, siems), `start`/`len` (iologs), plain arrays | mostly arrays; some `{total, data}` |
@@ -92,8 +92,9 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
92
92
  - **PWS session keystroke transcript = `GET /Sessions/{id}/Keystrokes`** → events `{KeystrokeID, TimeMarker (epoch ms), Type, Data}`. **Type 1 = user input** (typed keys; special keys as glyphs `⌫`/`→`/`↑`), **Type 2 = terminal output** (SSH stdout). SSH captures a full input+output transcript; **RDP is sparse** (typed keys / window events only, off by default → often empty). Surfaced as `bt pws sessions transcript <id>` (default = time-ordered readable transcript; `--input-only`; text/json/csv). Text trail, not the visual recording. (There is **no** `bt pws sessions export` — session *metadata* evidence is `bt pws audit export`.)
93
93
  - **PWS session recording download = headless replay under `/pbsm/replay`** (there is **no** video-export API — `?video/?mp4/?webm` return JSON, not media). Flow: `GET /Sessions/{id}` for `Token`+`RecordKey`+`Protocol` → `POST /pbsm/replay` `{id:Token, record_key, protocol:"RDP"|"SSH", headless:true}` → `PUT /pbsm/replay/{rid}` `{offset:<ms>}` (seek) → `GET /pbsm/replay/{rid}?png=<scale>` (one PNG per offset) → `DELETE /pbsm/replay/{rid}`. **Warm-up:** the first `PUT` 404s (`"no such file or directory"`) until the renderer loads — retry ~0.5s. The seek response is state `{offset, next, eof, duration}`; **`next` = next recorded frame's offset**, enabling event-driven stepping. **`bt pws sessions analyze <id>` (or `--dir`)** preps a replay dump for LLM analysis without calling an LLM: reads `index.json`, selects **keyframes** (RDP frames whose PNG byte-size changed ≥`--threshold`%, dependency-free — no Pillow, UNION the frame that renders each **typed keystroke burst**, anchored via the recording `StartTime` → each keyframe tagged `screen-change`/`after-typing` with the `typed` text, so the model reads targeted spots not every frame), and prints a token/USD **cost estimate** (`--model opus|sonnet|haiku`) for focused (keyframes) vs all-frames; writes `analysis.json`. SSH → recommends the transcript over frames. Consumed by the `/session-analysis` skill (interactive; the model reads only the keyframes). Surfaced as `bt pws sessions replay <id>`. **Default = even coverage + dedup**: ~60 evenly-spaced frames across the whole session (`--frames N`) with consecutive-identical frames skipped (`--dedup`, sha1-based) — because event-driven over-samples RDP (it records many deltas as the screen paints, so `next`-stepping clusters at the login and burns the frame budget before covering the timeline). Modes are mutually exclusive: `--frames` (default) / `--fps` / `--interval` / `--all-frames` (event-driven forensic). Also `--from/--to`, `--scale`, `--max-frames`, `--no-dedup`; `--mp4` stitches via ffmpeg, else prints the command + install hint. Always `DELETE`s the replay in `finally`.
94
94
  - Entitle permissions: Use server-side filters (`--resource`, `--user`, `--integration`) - dataset can be 20k+ records
95
- - Entitle users: Use `--all` flag only when needed - default fetches first page only
95
+ - **Entitle `--limit` means *maximum results*, not page size** (fixed 0.5.0): it bounds the fetch, so a small limit is cheap. It used to be forwarded as `perPage` while `paginate()` walked every page anyway, which made a small `--limit` *more* work — `--limit 2` over `/usersAccounts` was ~978 requests. `users list --all` is uncapped; without it you get one page.
96
96
  - **Entitle tokens are region-bound**: a token minted in one region 401s `"The token received doesn't exist"` on the others. Servers: `api.entitle.io`, `api.us.entitle.io`, `api.ca.entitle.io` — set `BT_ENTITLE_API_URL`/`entitle.api_url` to the tenant's region (that misleading 401 also = wrong-region config, not just a dead token). **No `GET /users/{id}`** — `users get` resolves via `list_users(search=...)`.
97
+ - **Entitle write semantics** (all live-verified, 0.5.0; full detail in the entitle SKILL.md): **PUT merges** in every family — omitted fields survive, so no command does read-modify-write — but **a list you do send replaces that list**, and **`workflows` replaces its whole `rules` array**. Beware the **silent 200-OK no-ops**: `POST /roles` discards `prerequisitePermissions`, `PUT /roles` ignores `name`/`resource`/`virtualizedRole`, `PUT /integrations` ignores its three create-only booleans, and neither webhook family accepts `{"enabled": false}` (**DELETE is the only off switch**). **`POST` on both forwards families is an upsert keyed on the forwarder** — creating one silently retargets any existing forward for that person; **`POST /usersAccounts` is idempotent** and returns the pre-existing row, so never delete "the row you just created" without checking its id against a baseline. **Webhook header values are redacted by header NAME** (`Authorization`/`X-Api-Key`/`Token` → `***`; `Cookie` verbatim), so a GET-edit-PUT round trip would write `***` over a real secret — hence no `--file` on webhooks. **`agent-tokens create` returns a one-shot ~776-char token** (`-o json` only). **`orgRoles` (2 ops) is 401 on both tokens** and is the only gap left; `tests/entitle/test_coverage.py` guards 75/77.
97
98
  - **Entitle `jit` / `audit`** (spec-derived, live-verified): `bt entitle jit list` wraps `GET /eligibleJitAccess` — with an org token `--user` is **required** (else 400), and the response is `{roles,bundles,pagination}` not the `result` envelope. `bt entitle audit search` wraps `POST /auditLogs/search` (Experimental) — **date window must be < 24h** (server 400s on ≥24h; CLI enforces client-side, default `--hours 24`), no pagination (returns all rows in range), each row's context is one of `integration`/`idp`/`ticket`/`user`. Full spec at `/projects/reference/entitle-api-reference.openapi.json`.
98
99
  - Windows builds: Rich must be pinned to `<14.0.0` (Rich 14 has PyInstaller unicode issues)
99
100
  - **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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bt-cli
3
- Version: 0.4.73
3
+ Version: 0.5.0
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,378 @@
1
+ # Plan: Close the Entitle API coverage gap in `bt entitle`
2
+
3
+ ## Context
4
+
5
+ `bt entitle` implements **26 of the 77 operations** in the Entitle public API
6
+ spec (`/projects/reference/entitle-api-reference.openapi.json`, 43 paths). This
7
+ plan covers the **51 missing operations**.
8
+
9
+ Measured 2026-09-03 by walking the live Typer tree (`core/command_tree.py`) and
10
+ diffing call sites in `entitle/client/base.py` against the spec:
11
+
12
+ | | count |
13
+ |---|---|
14
+ | Spec paths | 43 |
15
+ | Spec operations | 77 |
16
+ | Covered by the client | 26 |
17
+ | **Missing** | **51** |
18
+ | Existing `bt entitle` leaf commands | 27 |
19
+
20
+ > **Correction (Phase 6):** the stale "43 paths vs CLI's 19" line is in the
21
+ > project *memory* file, not CLAUDE.md — this plan named the wrong file in
22
+ > Phase 0. Both are now updated. Count *operations*, not paths: a path with 5
23
+ > methods is 5 units of work.
24
+
25
+ Two families are missing entirely (no read, no write): `tasks`, `agentTokens`,
26
+ `auditLogsWebhooks`, `workflowsWebhooks`, `accessRequestForwards`,
27
+ `accessReviewForwards`, `usersAccounts`, `orgRoles`, `directoryGroups`,
28
+ `chats/channels`. Four families have reads but no writes: `roles`, `policies`,
29
+ `workflows`, `integrations`.
30
+
31
+ ### Prerequisites before any live phase
32
+
33
+ 1. **API keys.** Both tokens live in the Ninth Floor Vault:
34
+ `nfv get pws-prod-cli-workload` → `ENT-URL`, `ENT-KEY` (org/admin),
35
+ `ENT-USER-KEY` (user-context). Load them at runtime rather than writing them
36
+ to a `.env` — see `scratchpad/entitle-env.sh`.
37
+ 2. **Region: US.** `ENT-URL` is `https://api.us.entitle.io`. Entitle tokens are
38
+ region-bound — a token minted in one region returns
39
+ `401 "The token received doesn't exist"` on the others. **That 401 is also
40
+ the symptom of a wrong-region config, not just a dead token** — do not chase
41
+ it as an auth bug.
42
+ 3. **Two-token model.** `EntitleClient(config, use_user_token=...)` selects
43
+ between the org/admin token (`api_key`) and the user-context token
44
+ (`user_api_key`). Every new command must make a deliberate choice; see
45
+ Design decision 4.
46
+
47
+ ### Live reconnaissance (run 2026-09-03, both tokens, US tenant)
48
+
49
+ Auth confirmed on both tokens (76 applications). Every missing read endpoint was
50
+ probed. Three findings change this plan:
51
+
52
+ **A. `pagination` in the envelope is the reliable signal for whether an endpoint
53
+ actually paginates.** Where it is absent, `page`/`perPage` are silently ignored
54
+ and a single GET returns the whole collection — verified by requesting
55
+ `page=2&perPage=1` and getting back the same first row.
56
+
57
+ | endpoint | envelope | pages? |
58
+ |---|---|---|
59
+ | `/tasks` | `{result}` | **no — ignores paging** |
60
+ | `/auditLogsWebhooks` | `{result}` | **no — ignores paging** |
61
+ | `/workflowsWebhooks` | `{result}` | **no — ignores paging** |
62
+ | `/agentTokens` | `{pagination, result}` | yes |
63
+ | `/directoryGroups` | `{pagination, result}` | yes |
64
+ | `/usersAccounts` | `{pagination, result}` | yes |
65
+ | `/accessRequestForwards` | `{pagination, result}` | yes |
66
+ | `/accessReviewForwards` | `{pagination, result}` | yes |
67
+
68
+ The three non-paginating endpoints are the `/Sessions` pattern from Password
69
+ Safe. `paginate()` happens to terminate on them (`totalPages` defaults to 1), so
70
+ this is not a hang — but calling it implies a pagination that does not exist.
71
+ Use a single `get()` and cap display client-side, as `list_sessions` does.
72
+
73
+ **B. `/orgRoles` is unreachable — 401 on *both* tokens** (`request.unauthorized`).
74
+ `GET /orgRoles/identities` 404s, consistent with it being PUT-only. This is the
75
+ same org-level gating seen in Pathfinder. **Both orgRoles operations are struck
76
+ from the plan** and move to a documented-blocked list; `PUT
77
+ /orgRoles/identities` cannot be verified, so it must not ship blind. That
78
+ removes the riskiest operation in the original draft.
79
+
80
+ **C. `/chats/channels` is user-token-only and needs `integrationType`.** The
81
+ admin token always returns `401 "User must be logged in to use this action"`.
82
+ With the user token, the parameter is mandatory and the enum has exactly one
83
+ member — the server says so itself:
84
+ `"integrationType must be one of the following values: slack"`. On this tenant
85
+ the call then returns `500 chatsService.missingScopes`, i.e. the Slack
86
+ integration lacks scopes. Ship the command with `--integration-type slack` as
87
+ the only accepted value, and surface `missingScopes` as an actionable message
88
+ rather than a generic 500.
89
+
90
+ **D. `PUT` is a MERGE, not a full replace** (probed on a throwaway bundle,
91
+ created and deleted in the same run). Sending only `{name, description}` left
92
+ `category`, `tags`, `allowedDurations` and `roles` untouched. So **no
93
+ read-modify-write is needed** — the EPM-L upsert-on-id trap does not apply here.
94
+ A field that *is* sent replaces that field wholesale, so list-valued flags set
95
+ the list rather than appending. Re-probe per family before relying on this in
96
+ Phase 3; it is verified for `bundles` only.
97
+
98
+ **E. Every `DELETE` in the client was broken.** `_get_headers()` set
99
+ `Content-Type: application/json` on all requests including bodyless ones, and
100
+ the server answers `400 "Body cannot be empty when content-type is set to
101
+ 'application/json'"`. That silently broke all three shipped delete paths —
102
+ `bundles delete`, `resources delete`, `permissions revoke`. Fixed in Phase 0;
103
+ the header is now sent only when there is a body.
104
+
105
+ **F. Two body/query constraints the spec does not state.** `perPage` caps at
106
+ **100** (larger 400s), now clamped in `paginate()`. And bundle create requires
107
+ **at least one role** — `"roles must contain at least 1 elements"` — so
108
+ `bundles create` without `--role` could never have succeeded; it now fails
109
+ client-side with a message naming the fix.
110
+
111
+ **G. Every family MERGES on PUT — with one important exception.** Probed
112
+ per family on throwaway objects (Phase 3): bundles, roles, policies, resources
113
+ and integrations all keep fields omitted from the body, so no `update` needs
114
+ read-modify-write. **Workflows merges at the top level but replaces the `rules`
115
+ array wholesale** — a 2-rule workflow plus a PUT carrying one rule leaves one
116
+ rule, and a rule object must be complete (`400 "rules.0.inGroups must be an
117
+ array"`). In every family a list you *do* pass is replaced, not appended.
118
+
119
+ **H. Silent 200-OK no-ops are the real hazard here.** Several writes report
120
+ success and do nothing: `POST /roles` drops `prerequisitePermissions`
121
+ (so `roles create` follows up with a PUT); `PUT /roles` ignores
122
+ `name`/`resource`/`virtualizedRole`; `PUT /integrations` accepts and ignores
123
+ the three create-only fields (`allowChangingAccountPermissions`,
124
+ `allowCreatingAccounts`, `readonly`); an unknown `--owner` is a no-op on
125
+ integration update but 404s on create; and `PUT` with a raised `sortOrder`
126
+ past the last policy silently keeps the old value. A `curl` user would believe
127
+ all of these landed. Verify writes with a follow-up GET.
128
+
129
+ **I. Spec inaccuracies found live.** `POST /roles` needs only
130
+ `name` + `resource` + `requestable` (the spec's `prerequisitePermissions` is
131
+ the field the server discards). Workflow approver types include **`Webhook`**
132
+ and **`TeamsChannel`**, both absent from the published spec; the notified-type
133
+ enum is that list minus `Automatic`. Integration create does not enforce the
134
+ spec's `maintainers`/`prerequisitePermissions`/`agentToken`.
135
+
136
+ **J. Policies cannot be created inert.** A policy has no `name` or
137
+ `description` at all — the body is only `inGroups`/`bundles`/`roles`/
138
+ `sortOrder`, so it is trackable by UUID alone — and `POST` rejects an empty
139
+ grant with `400 "Policy must grant at least 1 permission"`. Any policy create
140
+ is therefore a real grant for as long as it lives; do it only against a
141
+ Virtual Application, which provisions nothing downstream.
142
+
143
+ **K. Two writes silently overwrite or ignore, in ways the response hides.**
144
+ `POST` on both forwards families is an **upsert keyed on the forwarder** — a
145
+ second create for the same person returns the same id with the target replaced,
146
+ 200, no 409. A naive "create a forward for Alice" therefore silently
147
+ redirects her queue away from whoever it pointed at. `POST /usersAccounts`, by
148
+ contrast, is **idempotent** on `(user, account)`: it returns the pre-existing
149
+ row with its original `createdAt` rather than creating a duplicate — so a
150
+ caller that deletes "the row it just made" would be deleting a real one.
151
+
152
+ **L. Webhooks cannot be created disabled, in either family.**
153
+ `{"enabled": false}` is accepted with 200 and silently discarded on create and
154
+ on `PUT`; `/disable` 404s. **DELETE is the only off switch.** The audit family
155
+ therefore exposes `enable` with no `--disable`, because a `--disable` flag
156
+ could only lie.
157
+
158
+ **M. Header redaction is by header NAME, not per hook, and it breaks
159
+ round-tripping.** `Authorization`, `X-Api-Key`, `api-key`, `X-Secret` and
160
+ `Token` read back as `***`; `Cookie` and arbitrary names come back verbatim.
161
+ Re-PUTting a body obtained from GET would therefore **store the literal `***`
162
+ over a real secret**, so neither webhook family offers `--file`, and update
163
+ sends only the flags given.
164
+
165
+ **N. `POST /agentTokens` returns a one-shot ~776-char credential** in
166
+ `{result:{id,name,token}}`; an immediate GET returns only `id`+`name`. It is
167
+ surfaced solely under `-o json`, redacted in tables, with an stderr warning
168
+ that it is unrecoverable. The spec documents this response bare, but it is
169
+ enveloped live.
170
+
171
+ **Revised scope: 49 implementable, 2 blocked.**
172
+
173
+ ---
174
+
175
+ ## Command tree (additions)
176
+
177
+ ```
178
+ bt entitle
179
+ ├── tasks list | get | set-status | archive
180
+ ├── agent-tokens list | get | create | update | delete
181
+ ├── audit-webhooks list | get | create | update | delete | enable
182
+ ├── workflow-webhooks list | get | create | update | delete
183
+ ├── request-forwards list | get | get-for-user | create | delete
184
+ ├── review-forwards list | get | create | delete
185
+ ├── user-accounts list | create | delete
186
+ ├── directory-groups list
187
+ ├── chats channels (user token; --integration-type slack)
188
+ ├── users + pats
189
+ ├── permissions + account
190
+ ├── roles + create | update | delete
191
+ ├── policies + create | update | delete
192
+ ├── workflows + create | update | delete
193
+ ├── integrations + create | update | delete
194
+ ├── resources + update
195
+ └── bundles + update ← client method already exists
196
+ ```
197
+
198
+ `bundles update` is not one of the 51: `EntitleClient.update_bundle()` already
199
+ exists at `client/base.py:359` with no command exposing it. Free win, Phase 0.
200
+
201
+ ---
202
+
203
+ ## Implementation phases
204
+
205
+ Ordered read-before-write so the models and table columns are already proven by
206
+ the time a phase starts mutating a live tenant.
207
+
208
+ ### Phase 0 — Orphan + scaffolding (DONE)
209
+
210
+ - Expose `bt entitle bundles update` over the existing `update_bundle()`.
211
+ - Fix the `Content-Type` bug breaking every DELETE (finding E).
212
+ - Clamp `perPage` to 100 and validate bundle-create roles (finding F).
213
+ - Add `tests/entitle/test_coverage.py`: a **static guard** that parses the spec
214
+ and asserts every operation is either implemented or listed in an explicit
215
+ `KNOWN_UNIMPLEMENTED` set. This is the same class of fix as the derived
216
+ command tree in 0.4.73 — it makes the gap impossible to re-open silently, and
217
+ each later phase shrinks the allowlist.
218
+ - Replace the placeholder block at `tests/entitle/test_commands.py:526`.
219
+
220
+ ### Phase 1 — Read-only, new families (11 ops) — DONE
221
+
222
+ | Ops | Commands |
223
+ |---|---|
224
+ | `GET /tasks`, `GET /tasks/{id}` | `tasks list`, `tasks get` |
225
+ | `GET /agentTokens`, `GET /agentTokens/{id}` | `agent-tokens list/get` |
226
+ | `GET /directoryGroups` | `directory-groups list` |
227
+ | `GET /chats/channels` | `chats channels` |
228
+ | `GET /usersAccounts` | `user-accounts list` |
229
+ | `GET /users/{id}/personalAccessTokens` | `users pats` |
230
+ | `GET /permissions/{accountId}` | `permissions account` |
231
+ | `GET /auditLogsWebhooks`, `GET /workflowsWebhooks` | `*-webhooks list` |
232
+
233
+ Envelopes are already captured in finding A above — use that table rather than
234
+ re-deriving it. `tasks`, `audit-webhooks`, and `workflow-webhooks` take a single
235
+ `get()`, not `paginate()`. `chats channels` takes the **user** token.
236
+
237
+ ### Phase 2 — Read-only, single-item gets + forwards (7 ops) — DONE
238
+
239
+ `GET` on `{id}` for `auditLogsWebhooks`, `workflowsWebhooks`,
240
+ `accessRequestForwards`, `accessReviewForwards`, plus
241
+ `GET /accessRequestForwards/user/{forwardUserId}`.
242
+
243
+ Split from Phase 1 only so Phase 1 can ship while list-shape questions on the
244
+ forwards families are still open.
245
+
246
+ ### Phase 3 — Complete the half-done families (15 ops) — DONE
247
+
248
+ Writes on families that already have reads. Highest user-visible value: these
249
+ groups currently look broken (`bt entitle roles list` works, `roles create`
250
+ doesn't exist).
251
+
252
+ - `roles` create / update / delete
253
+ - `policies` create / update / delete
254
+ - `workflows` create / update / delete
255
+ - `integrations` create / update / delete
256
+ - `resources` update
257
+
258
+ **Live verify (create → update → delete on objects the agent itself made).**
259
+ Record for each: required vs optional body fields, whether `PUT` is a full
260
+ replace or a merge, and the 4xx body shape on validation failure.
261
+
262
+ > **`PUT` merges — verified for `bundles` (finding D), not yet for the others.**
263
+ > Probe each family the same way before writing its `update`: create a throwaway
264
+ > object, `PUT` a partial body, and check whether the omitted fields survive. If
265
+ > any family turns out to replace instead, that one needs read-modify-write and
266
+ > a warning in the skill file for direct-`curl` users.
267
+
268
+ ### Phase 4 — Webhooks + agent tokens, write paths (10 ops) — DONE
269
+
270
+ - `auditLogsWebhooks` create / update / delete / **enable** (`PUT {id}/enable`)
271
+ - `workflowsWebhooks` create / update / delete
272
+ - `agentTokens` create / update / delete
273
+
274
+ **Handle the secret.** `POST /agentTokens` almost certainly returns a token value
275
+ exactly once. It must go to stdout as data under `-o json`, never to a log or a
276
+ status banner, and the table view should make the show-once behavior explicit.
277
+
278
+ **Webhook targets are outbound, and an enabled one receives real data.**
279
+ Creating an audit-log webhook on a live tenant means real audit events start
280
+ being POSTed to whatever URL it names. **Do not use a request-bin or
281
+ webhook.site style capture service** — that exfiltrates the tenant's audit
282
+ trail to a third party, which is worse than the bug being tested for. Point it
283
+ at `https://example.com/...` (IANA-reserved, nothing listening, nothing
284
+ recorded), leave it disabled wherever the test allows, and delete every webhook
285
+ created during verification.
286
+
287
+ ### Phase 5 — Forwards + user accounts (6 ops) — DONE
288
+
289
+ - `accessRequestForwards` create / delete
290
+ - `accessReviewForwards` create / delete
291
+ - `usersAccounts` create / delete
292
+
293
+ `orgRoles` is **excluded** — see finding B. Do not implement `org-roles list` or
294
+ `set-identities` until the tenant's org-level gating is lifted; an unverifiable
295
+ org-wide identity write is not worth shipping blind.
296
+
297
+ ### Phase 6 — Tests, skill, docs — DONE
298
+
299
+ - Drain `KNOWN_UNIMPLEMENTED` in the Phase 0 guard to empty.
300
+ - Update `src/bt_cli/data/skills/entitle/SKILL.md` with every new command plus
301
+ the envelope/body-shape findings from the live runs.
302
+ - Fix the stale "43 paths vs CLI's 19" line — it lives in the project memory
303
+ file, not `CLAUDE.md` — and add any gotcha worth a permanent warning
304
+ (region-binding is already there).
305
+ - **Do not touch `bt tree` / `bt find`** — they derive from the live Typer app.
306
+ New commands appear automatically; help text comes from the docstring's first
307
+ line, so **every new command needs a docstring** (see the 29 blank-help
308
+ commands found in the same audit that produced this plan).
309
+
310
+ ---
311
+
312
+ ## Files
313
+
314
+ **Create** (`src/bt_cli/entitle/`):
315
+
316
+ ```
317
+ commands/tasks.py commands/agent_tokens.py
318
+ commands/webhooks.py commands/forwards.py
319
+ commands/user_accounts.py commands/directory_groups.py
320
+ commands/chats.py
321
+ models/task.py models/agent_token.py
322
+ models/webhook.py models/forward.py
323
+ tests/entitle/test_coverage.py
324
+ ```
325
+
326
+ **Modify:**
327
+
328
+ ```
329
+ client/base.py +49 methods
330
+ commands/__init__.py register new sub-apps
331
+ commands/{roles,policies,workflows,integrations,resources,bundles}.py
332
+ commands/{users,permissions}.py add pats / account
333
+ src/bt_cli/data/skills/entitle/SKILL.md
334
+ CLAUDE.md
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Key design decisions
340
+
341
+ 1. **Group naming: kebab-case, mirroring the resource.** `agent-tokens`, not
342
+ `agenttokens` or `tokens`. Two webhook families stay separate
343
+ (`audit-webhooks`, `workflow-webhooks`) rather than a merged `webhooks
344
+ --type` — they are different endpoints with different bodies, and only the
345
+ audit one has `enable`.
346
+ 2. **`tasks` mutations get verbs, not `update`.** `PUT /tasks/{id}` only sets
347
+ status and `DELETE` archives rather than deletes. `tasks set-status` and
348
+ `tasks archive` say what actually happens; `tasks delete` would lie.
349
+ 3. **Pagination is chosen per endpoint from the finding-A table, not assumed.**
350
+ Endpoints whose envelope carries `pagination` go through `paginate()`; the
351
+ three that lack it take a single `get()` with client-side capping. Add a
352
+ comment at each single-`get()` call site saying why, so nobody "fixes" it
353
+ into a paginate later. If a new endpoint returns an envelope the four
354
+ existing branches don't handle, extend `paginate()` — never special-case at
355
+ the call site.
356
+ 4. **Token choice is explicit per command.** Default to the org token; use the
357
+ user token only where the API demands on-behalf-of semantics (as
358
+ `requests create` does). Where an org token 400s, surface the same
359
+ actionable error `jit list` gives, not a raw 400.
360
+ 5. **`-o json` purity is non-negotiable.** All banners, warnings, and
361
+ confirmations to stderr via `print_success`/`print_warning`/`err_console`,
362
+ and `typer.confirm(..., err=True)`. Enforced by
363
+ `tests/core/test_json_stdout_purity.py`.
364
+ 6. **Destructive commands require confirmation.** Every `delete`, `archive`,
365
+ `revoke`, and `set-identities` prompts unless `--yes`. Prompt goes to stderr.
366
+
367
+ ---
368
+
369
+ ## Verification
370
+
371
+ Per phase: unit tests green, `tests/core/test_json_stdout_purity.py` green,
372
+ `tests/core/test_command_tree.py` green, the Phase 0 coverage guard shrunk by
373
+ exactly the phase's operation count, and a live run of every new command against
374
+ the tenant with the response shape recorded in the skill file.
375
+
376
+ Full-live-verify hygiene: every object an agent creates is deleted in the same
377
+ phase. Nothing is created against a name that could collide with real tenant
378
+ data — prefix all test objects `btcli-verify-<phase>-<uuid>`.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bt-cli"
7
- version = "0.4.73"
7
+ version = "0.5.0"
8
8
  description = "BeyondTrust Platform CLI (unofficial) - Password Safe, Entitle, PRA, EPM"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"