bt-cli 0.4.34__tar.gz → 0.4.35__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 (206) hide show
  1. {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/epml/SKILL.md +32 -6
  2. {bt_cli-0.4.34 → bt_cli-0.4.35}/CLAUDE.md +1 -1
  3. {bt_cli-0.4.34 → bt_cli-0.4.35}/PKG-INFO +1 -1
  4. {bt_cli-0.4.34 → bt_cli-0.4.35}/pyproject.toml +1 -1
  5. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/__init__.py +1 -1
  6. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/cli.py +15 -7
  7. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/data/CLAUDE.md +1 -1
  8. {bt_cli-0.4.34/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/epml/SKILL.md +32 -6
  9. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/client/base.py +79 -9
  10. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_cmdgrps.py +34 -10
  11. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_hostgrps.py +34 -10
  12. bt_cli-0.4.35/src/bt_cli/epml/commands/rbp_tmdategrps.py +198 -0
  13. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_usergrps.py +67 -10
  14. bt_cli-0.4.34/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -77
  15. {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/bt/SKILL.md +0 -0
  16. {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/entitle/SKILL.md +0 -0
  17. {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/epmw/SKILL.md +0 -0
  18. {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/pra/SKILL.md +0 -0
  19. {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.35/.claude}/skills/pws/SKILL.md +0 -0
  20. {bt_cli-0.4.34 → bt_cli-0.4.35}/.env.example +0 -0
  21. {bt_cli-0.4.34 → bt_cli-0.4.35}/.github/workflows/ci.yml +0 -0
  22. {bt_cli-0.4.34 → bt_cli-0.4.35}/.github/workflows/release.yml +0 -0
  23. {bt_cli-0.4.34 → bt_cli-0.4.35}/.gitignore +0 -0
  24. {bt_cli-0.4.34 → bt_cli-0.4.35}/README.md +0 -0
  25. {bt_cli-0.4.34 → bt_cli-0.4.35}/assets/cli-help.png +0 -0
  26. {bt_cli-0.4.34 → bt_cli-0.4.35}/assets/cli-output.png +0 -0
  27. {bt_cli-0.4.34 → bt_cli-0.4.35}/bt-cli.spec +0 -0
  28. {bt_cli-0.4.34 → bt_cli-0.4.35}/bt_entry.py +0 -0
  29. {bt_cli-0.4.34 → bt_cli-0.4.35}/epml-implementation-plan.md +0 -0
  30. {bt_cli-0.4.34 → bt_cli-0.4.35}/scripts/bt_entry.py +0 -0
  31. {bt_cli-0.4.34 → bt_cli-0.4.35}/scripts/sync-package-data.sh +0 -0
  32. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/commands/__init__.py +0 -0
  33. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/commands/configure.py +0 -0
  34. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/commands/learn.py +0 -0
  35. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/commands/quick.py +0 -0
  36. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/__init__.py +0 -0
  37. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/auth.py +0 -0
  38. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/client.py +0 -0
  39. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/config.py +0 -0
  40. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/config_file.py +0 -0
  41. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/csv_utils.py +0 -0
  42. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/errors.py +0 -0
  43. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/output.py +0 -0
  44. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/prompts.py +0 -0
  45. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/core/rest_debug.py +0 -0
  46. {bt_cli-0.4.34/tests/pws → bt_cli-0.4.35/src/bt_cli/data}/__init__.py +0 -0
  47. {bt_cli-0.4.34/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/bt/SKILL.md +0 -0
  48. {bt_cli-0.4.34/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/entitle/SKILL.md +0 -0
  49. {bt_cli-0.4.34/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/epmw/SKILL.md +0 -0
  50. {bt_cli-0.4.34/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/pra/SKILL.md +0 -0
  51. {bt_cli-0.4.34/.claude → bt_cli-0.4.35/src/bt_cli/data}/skills/pws/SKILL.md +0 -0
  52. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/__init__.py +0 -0
  53. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/client/__init__.py +0 -0
  54. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/client/base.py +0 -0
  55. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/__init__.py +0 -0
  56. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/accounts.py +0 -0
  57. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/applications.py +0 -0
  58. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/auth.py +0 -0
  59. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/bundles.py +0 -0
  60. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/integrations.py +0 -0
  61. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/permissions.py +0 -0
  62. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/policies.py +0 -0
  63. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/resources.py +0 -0
  64. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/roles.py +0 -0
  65. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/users.py +0 -0
  66. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/commands/workflows.py +0 -0
  67. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/__init__.py +0 -0
  68. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/bundle.py +0 -0
  69. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/common.py +0 -0
  70. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/integration.py +0 -0
  71. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/permission.py +0 -0
  72. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/policy.py +0 -0
  73. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/resource.py +0 -0
  74. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/role.py +0 -0
  75. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/user.py +0 -0
  76. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/entitle/models/workflow.py +0 -0
  77. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/__init__.py +0 -0
  78. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/client/__init__.py +0 -0
  79. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/__init__.py +0 -0
  80. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/audit.py +0 -0
  81. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/auth.py +0 -0
  82. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/client_pkg.py +0 -0
  83. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/external_apis.py +0 -0
  84. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/hosts.py +0 -0
  85. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/iolog.py +0 -0
  86. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/license.py +0 -0
  87. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/quick.py +0 -0
  88. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
  89. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
  90. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
  91. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
  92. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
  93. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/settings.py +0 -0
  94. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/siems.py +0 -0
  95. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/commands/users.py +0 -0
  96. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epml/models/__init__.py +0 -0
  97. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/__init__.py +0 -0
  98. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/client/__init__.py +0 -0
  99. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/client/base.py +0 -0
  100. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/__init__.py +0 -0
  101. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/audits.py +0 -0
  102. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/auth.py +0 -0
  103. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/computers.py +0 -0
  104. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/events.py +0 -0
  105. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/groups.py +0 -0
  106. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/policies.py +0 -0
  107. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/quick.py +0 -0
  108. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/requests.py +0 -0
  109. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/roles.py +0 -0
  110. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/tasks.py +0 -0
  111. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/commands/users.py +0 -0
  112. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/epmw/models/__init__.py +0 -0
  113. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/__init__.py +0 -0
  114. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/client/__init__.py +0 -0
  115. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/client/base.py +0 -0
  116. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/__init__.py +0 -0
  117. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/auth.py +0 -0
  118. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/import_export.py +0 -0
  119. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/jump_clients.py +0 -0
  120. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  121. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/jump_items.py +0 -0
  122. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  123. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/policies.py +0 -0
  124. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/quick.py +0 -0
  125. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/teams.py +0 -0
  126. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/users.py +0 -0
  127. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/commands/vault.py +0 -0
  128. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/__init__.py +0 -0
  129. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/common.py +0 -0
  130. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/jump_client.py +0 -0
  131. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/jump_group.py +0 -0
  132. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/jump_item.py +0 -0
  133. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/jumpoint.py +0 -0
  134. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/team.py +0 -0
  135. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/user.py +0 -0
  136. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pra/models/vault.py +0 -0
  137. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/__init__.py +0 -0
  138. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/client/__init__.py +0 -0
  139. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/client/base.py +0 -0
  140. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  141. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  142. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/__init__.py +0 -0
  143. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/accounts.py +0 -0
  144. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/assets.py +0 -0
  145. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/attributes.py +0 -0
  146. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/auth.py +0 -0
  147. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/clouds.py +0 -0
  148. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/config.py +0 -0
  149. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/credentials.py +0 -0
  150. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/databases.py +0 -0
  151. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/directories.py +0 -0
  152. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/functional.py +0 -0
  153. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/import_export.py +0 -0
  154. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/platforms.py +0 -0
  155. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/quick.py +0 -0
  156. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/search.py +0 -0
  157. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/secrets.py +0 -0
  158. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/systems.py +0 -0
  159. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/users.py +0 -0
  160. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/commands/workgroups.py +0 -0
  161. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/config.py +0 -0
  162. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/models/__init__.py +0 -0
  163. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/models/account.py +0 -0
  164. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/models/asset.py +0 -0
  165. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/models/common.py +0 -0
  166. {bt_cli-0.4.34 → bt_cli-0.4.35}/src/bt_cli/pws/models/system.py +0 -0
  167. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/__init__.py +0 -0
  168. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/conftest.py +0 -0
  169. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/core/__init__.py +0 -0
  170. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/core/test_auth.py +0 -0
  171. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/core/test_config.py +0 -0
  172. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/core/test_errors.py +0 -0
  173. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/core/test_rest_debug.py +0 -0
  174. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/entitle/__init__.py +0 -0
  175. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/entitle/test_client.py +0 -0
  176. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/entitle/test_commands.py +0 -0
  177. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/entitle-smoke-test.sh +0 -0
  178. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/epml/__init__.py +0 -0
  179. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/epml/test_client.py +0 -0
  180. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/epml/test_commands.py +0 -0
  181. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/epmw/__init__.py +0 -0
  182. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/epmw/test_client.py +0 -0
  183. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/epmw/test_commands.py +0 -0
  184. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/epmw-quick-test-plan.md +0 -0
  185. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/fixtures/__init__.py +0 -0
  186. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/fixtures/responses.py +0 -0
  187. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/__init__.py +0 -0
  188. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/conftest.py +0 -0
  189. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/helpers.py +0 -0
  190. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/test_entitle_integration.py +0 -0
  191. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/test_epmw_integration.py +0 -0
  192. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/test_epmw_lifecycle.py +0 -0
  193. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/test_pra_integration.py +0 -0
  194. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/test_pra_lifecycle.py +0 -0
  195. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/test_pws_integration.py +0 -0
  196. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/integration/test_pws_lifecycle.py +0 -0
  197. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pra/__init__.py +0 -0
  198. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pra/test_client.py +0 -0
  199. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pra/test_commands.py +0 -0
  200. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pra-smoke-test.sh +0 -0
  201. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pra-test-plan.md +0 -0
  202. {bt_cli-0.4.34/src/bt_cli/data → bt_cli-0.4.35/tests/pws}/__init__.py +0 -0
  203. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pws/test_client.py +0 -0
  204. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pws/test_commands.py +0 -0
  205. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pws-quick-test-plan.md +0 -0
  206. {bt_cli-0.4.34 → bt_cli-0.4.35}/tests/pws-smoke-test.sh +0 -0
@@ -103,21 +103,32 @@ bt epml rbp cmdgrps create --name "Admin Tools" --description "..."
103
103
  bt epml rbp cmdgrps delete 12,13
104
104
  bt epml rbp cmdgrps commands list <cmdgrp_id>
105
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
106
+ bt epml rbp cmdgrps commands clear <cmdgrp_id> # NUKE all (no per-item delete)
107
+ bt epml rbp cmdgrps commands replace <cmdgrp_id> --commands "vi,more"
107
108
 
108
- # Host groups
109
+ # Host groups (`type` defaults to I=Internal)
109
110
  bt epml rbp hostgrps list
110
- bt epml rbp hostgrps create --name "Admin Hosts"
111
+ bt epml rbp hostgrps create --name "Admin Hosts" --type I
111
112
  bt epml rbp hostgrps hosts add <hostgrp_id> --hosts "web-*,db-*"
113
+ bt epml rbp hostgrps hosts clear <hostgrp_id>
114
+ bt epml rbp hostgrps hosts replace <hostgrp_id> --hosts "web-01,web-02"
112
115
 
113
- # User groups
116
+ # User groups (`type` defaults to I=Internal)
114
117
  bt epml rbp usergrps list
115
- bt epml rbp usergrps create --name "Helpdesk"
118
+ bt epml rbp usergrps create --name "Helpdesk" --type I
119
+ bt epml rbp usergrps create-multiple groups.json # bulk create from JSON array
116
120
  bt epml rbp usergrps users add <usergrp_id> --users "alice,bob"
121
+ bt epml rbp usergrps users clear <usergrp_id>
122
+ bt epml rbp usergrps users replace <usergrp_id> --users "alice"
117
123
 
118
- # Time/date groups
124
+ # Time/date groups + tmdates
119
125
  bt epml rbp tmdategrps list
120
126
  bt epml rbp tmdategrps create --name "Working Hours"
127
+ bt epml rbp tmdategrps tmdates list <tmdategrp_id>
128
+ bt epml rbp tmdategrps tmdates add <tmdategrp_id> --from 09:00 --to 17:00 -d "weekday"
129
+ bt epml rbp tmdategrps tmdates add <tmdategrp_id> --file tmdates.json
130
+ bt epml rbp tmdategrps tmdates clear <tmdategrp_id>
131
+ bt epml rbp tmdategrps tmdates replace <tmdategrp_id> --file tmdates.json
121
132
 
122
133
  # Roles + assignments
123
134
  bt epml rbp roles list
@@ -179,6 +190,21 @@ bt epml quick tests-then-deploy --suite <suite_id> # commits if pass; rollb
179
190
  - `bt epml rbp tx commit` — applies in-flight changes
180
191
  - `bt epml client-pkg ...` POST endpoints — triggers a real installer build
181
192
 
193
+ ## API quirks worth knowing (caught while building, not in the spec)
194
+
195
+ - **Create-role `action`**: required despite spec saying optional. Must be exactly `"A"` (Allow) or `"R"` (Reject). CLI defaults to `A`.
196
+ - **Create-hostgrp / create-usergrp `type`**: required. Must be `"I"` (Internal — static list) or `"E"` (External — directory-resolved). CLI defaults to `I`.
197
+ - **Collection-level deletes** (cmdgrps/hostgrps/usergrps/tmdategrps/roles): the spec shows `id` as a query parameter, NOT a JSON body. Repeated `?id=A&id=B` is accepted at the HTTP layer but only the first value is honored — the CLI loops single-id requests under the hood.
198
+ - **Child-collection deletes** (commands inside cmdgrp, hosts inside hostgrp, users inside usergrp, tmdates inside tmdategrp): there is **no per-item delete**. `DELETE /<parent>/{id}/<children>` clears the whole list. To remove one entry, use `replace` (CLI does GET + DELETE-all + POST under the hood).
199
+ - **Add-children body shape**: each child item must be wrapped: commands → `[{"cmd": x}]`, hosts → `[{"host": x}]`, users → `[{"user": x}]`, tmdates → `{"tmdates": [...]}` (note the wrapper for tmdates; the spec doesn't document any of these wrappers).
200
+ - **`POST /usergrps/multiple`** (bulk create): body is `{"usergroups": [...]}` — undocumented wrapper key.
201
+ - **POST on child collections is additive**, not replacing. Calling `commands add` twice with the same command will get you a duplicate.
202
+ - **Children share the parent's `id`** in GET responses — the listed `id` field is the cmdgrp/hostgrp/usergrp ID, not unique per child. The actual identifier is the `cmd`/`host`/`user` text.
203
+
204
+ ## Known gaps (TODO)
205
+
206
+ - **`bt epml rbp roles get <id>`** — there is no GET-single-role in the v1 API. The detailed view (role with all child relations resolved) only exists at `/api/v6/pbul/{hostid}/rbp/roledetail/{id}`, which is on the IAM-only authorizer. Not callable with a PAT today. Workaround: use `bt epml rbp roles list` for the role row, then `bt epml rbp roles {cmdgrps,hostgrps,usergrps,tmdategrps} list <id>` to fetch each child relation.
207
+
182
208
  ## Path-version policy (CLI internal)
183
209
 
184
210
  Where the spec offers both legacy `/api/pbul/{hostid}/rbp/<x>` and newer
@@ -1,6 +1,6 @@
1
1
  # BT-CLI
2
2
 
3
- BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, and EPM Linux. **Version: 0.4.34**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, and EPM Linux. **Version: 0.4.35**
4
4
 
5
5
  ## Setup
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bt-cli
3
- Version: 0.4.34
3
+ Version: 0.4.35
4
4
  Summary: BeyondTrust Platform CLI (unofficial) - Password Safe, Entitle, PRA, EPM
5
5
  Author-email: Dave Grendysz <dgrendysz@beyondtrust.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bt-cli"
7
- version = "0.4.34"
7
+ version = "0.4.35"
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.34"
3
+ __version__ = "0.4.35"
@@ -382,10 +382,10 @@ def tree_command(
382
382
  epml.add("[green]external-apis[/green] list|get")
383
383
  epml.add("[green]iolog[/green] list")
384
384
  rbp = epml.add("[green]rbp[/green] - Role-Based Policy")
385
- rbp.add("cmdgrps list|create|delete + commands list|add|remove")
386
- rbp.add("hostgrps list|create|delete + hosts list|add|remove")
387
- rbp.add("usergrps list|create|delete + users list|add|remove")
388
- rbp.add("tmdategrps list|create|delete")
385
+ rbp.add("cmdgrps list|create|delete + commands list|add|clear|replace")
386
+ rbp.add("hostgrps list|create|delete + hosts list|add|clear|replace")
387
+ rbp.add("usergrps list|create|create-multiple|delete + users list|add|clear|replace")
388
+ rbp.add("tmdategrps list|create|delete + tmdates list|add|clear|replace")
389
389
  rbp.add("roles list|create|delete|duplicate")
390
390
  rbp.add(" + cmdgrps|hostgrps|usergrps|tmdategrps list|add|remove")
391
391
  rbp.add("entitlement run [--raw]")
@@ -515,22 +515,30 @@ def _get_all_commands() -> list[tuple[str, str]]:
515
515
  ("bt epml rbp cmdgrps delete", "Delete RBP command groups"),
516
516
  ("bt epml rbp cmdgrps commands list", "List commands within a cmdgrp"),
517
517
  ("bt epml rbp cmdgrps commands add", "Add commands to a cmdgrp"),
518
- ("bt epml rbp cmdgrps commands remove", "Remove commands from a cmdgrp"),
518
+ ("bt epml rbp cmdgrps commands clear", "Clear ALL commands in a cmdgrp"),
519
+ ("bt epml rbp cmdgrps commands replace", "Replace cmdgrp's full command list"),
519
520
  ("bt epml rbp hostgrps list", "List RBP host groups"),
520
521
  ("bt epml rbp hostgrps create", "Create an RBP host group"),
521
522
  ("bt epml rbp hostgrps delete", "Delete RBP host groups"),
522
523
  ("bt epml rbp hostgrps hosts list", "List hosts within a hostgrp"),
523
524
  ("bt epml rbp hostgrps hosts add", "Add hosts to a hostgrp"),
524
- ("bt epml rbp hostgrps hosts remove", "Remove hosts from a hostgrp"),
525
+ ("bt epml rbp hostgrps hosts clear", "Clear ALL hosts in a hostgrp"),
526
+ ("bt epml rbp hostgrps hosts replace", "Replace hostgrp's full host list"),
525
527
  ("bt epml rbp usergrps list", "List RBP user groups"),
526
528
  ("bt epml rbp usergrps create", "Create an RBP user group"),
529
+ ("bt epml rbp usergrps create-multiple", "Bulk-create user groups from a JSON file"),
527
530
  ("bt epml rbp usergrps delete", "Delete RBP user groups"),
528
531
  ("bt epml rbp usergrps users list", "List users within a usergrp"),
529
532
  ("bt epml rbp usergrps users add", "Add users to a usergrp"),
530
- ("bt epml rbp usergrps users remove", "Remove users from a usergrp"),
533
+ ("bt epml rbp usergrps users clear", "Clear ALL users in a usergrp"),
534
+ ("bt epml rbp usergrps users replace", "Replace usergrp's full user list"),
531
535
  ("bt epml rbp tmdategrps list", "List RBP time/date groups"),
532
536
  ("bt epml rbp tmdategrps create", "Create an RBP time/date group"),
533
537
  ("bt epml rbp tmdategrps delete", "Delete RBP time/date groups"),
538
+ ("bt epml rbp tmdategrps tmdates list", "List time/date entries in a group"),
539
+ ("bt epml rbp tmdategrps tmdates add", "Add time/date entries"),
540
+ ("bt epml rbp tmdategrps tmdates clear", "Clear ALL time/dates in a group"),
541
+ ("bt epml rbp tmdategrps tmdates replace", "Replace the group's full tmdate list"),
534
542
  ("bt epml rbp roles list", "List RBP roles"),
535
543
  ("bt epml rbp roles create", "Create an RBP role"),
536
544
  ("bt epml rbp roles delete", "Delete RBP roles"),
@@ -1,6 +1,6 @@
1
1
  # BT-CLI
2
2
 
3
- BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, and EPM Linux. **Version: 0.4.34**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, and EPM Linux. **Version: 0.4.35**
4
4
 
5
5
  ## Setup
6
6
 
@@ -103,21 +103,32 @@ bt epml rbp cmdgrps create --name "Admin Tools" --description "..."
103
103
  bt epml rbp cmdgrps delete 12,13
104
104
  bt epml rbp cmdgrps commands list <cmdgrp_id>
105
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
106
+ bt epml rbp cmdgrps commands clear <cmdgrp_id> # NUKE all (no per-item delete)
107
+ bt epml rbp cmdgrps commands replace <cmdgrp_id> --commands "vi,more"
107
108
 
108
- # Host groups
109
+ # Host groups (`type` defaults to I=Internal)
109
110
  bt epml rbp hostgrps list
110
- bt epml rbp hostgrps create --name "Admin Hosts"
111
+ bt epml rbp hostgrps create --name "Admin Hosts" --type I
111
112
  bt epml rbp hostgrps hosts add <hostgrp_id> --hosts "web-*,db-*"
113
+ bt epml rbp hostgrps hosts clear <hostgrp_id>
114
+ bt epml rbp hostgrps hosts replace <hostgrp_id> --hosts "web-01,web-02"
112
115
 
113
- # User groups
116
+ # User groups (`type` defaults to I=Internal)
114
117
  bt epml rbp usergrps list
115
- bt epml rbp usergrps create --name "Helpdesk"
118
+ bt epml rbp usergrps create --name "Helpdesk" --type I
119
+ bt epml rbp usergrps create-multiple groups.json # bulk create from JSON array
116
120
  bt epml rbp usergrps users add <usergrp_id> --users "alice,bob"
121
+ bt epml rbp usergrps users clear <usergrp_id>
122
+ bt epml rbp usergrps users replace <usergrp_id> --users "alice"
117
123
 
118
- # Time/date groups
124
+ # Time/date groups + tmdates
119
125
  bt epml rbp tmdategrps list
120
126
  bt epml rbp tmdategrps create --name "Working Hours"
127
+ bt epml rbp tmdategrps tmdates list <tmdategrp_id>
128
+ bt epml rbp tmdategrps tmdates add <tmdategrp_id> --from 09:00 --to 17:00 -d "weekday"
129
+ bt epml rbp tmdategrps tmdates add <tmdategrp_id> --file tmdates.json
130
+ bt epml rbp tmdategrps tmdates clear <tmdategrp_id>
131
+ bt epml rbp tmdategrps tmdates replace <tmdategrp_id> --file tmdates.json
121
132
 
122
133
  # Roles + assignments
123
134
  bt epml rbp roles list
@@ -179,6 +190,21 @@ bt epml quick tests-then-deploy --suite <suite_id> # commits if pass; rollb
179
190
  - `bt epml rbp tx commit` — applies in-flight changes
180
191
  - `bt epml client-pkg ...` POST endpoints — triggers a real installer build
181
192
 
193
+ ## API quirks worth knowing (caught while building, not in the spec)
194
+
195
+ - **Create-role `action`**: required despite spec saying optional. Must be exactly `"A"` (Allow) or `"R"` (Reject). CLI defaults to `A`.
196
+ - **Create-hostgrp / create-usergrp `type`**: required. Must be `"I"` (Internal — static list) or `"E"` (External — directory-resolved). CLI defaults to `I`.
197
+ - **Collection-level deletes** (cmdgrps/hostgrps/usergrps/tmdategrps/roles): the spec shows `id` as a query parameter, NOT a JSON body. Repeated `?id=A&id=B` is accepted at the HTTP layer but only the first value is honored — the CLI loops single-id requests under the hood.
198
+ - **Child-collection deletes** (commands inside cmdgrp, hosts inside hostgrp, users inside usergrp, tmdates inside tmdategrp): there is **no per-item delete**. `DELETE /<parent>/{id}/<children>` clears the whole list. To remove one entry, use `replace` (CLI does GET + DELETE-all + POST under the hood).
199
+ - **Add-children body shape**: each child item must be wrapped: commands → `[{"cmd": x}]`, hosts → `[{"host": x}]`, users → `[{"user": x}]`, tmdates → `{"tmdates": [...]}` (note the wrapper for tmdates; the spec doesn't document any of these wrappers).
200
+ - **`POST /usergrps/multiple`** (bulk create): body is `{"usergroups": [...]}` — undocumented wrapper key.
201
+ - **POST on child collections is additive**, not replacing. Calling `commands add` twice with the same command will get you a duplicate.
202
+ - **Children share the parent's `id`** in GET responses — the listed `id` field is the cmdgrp/hostgrp/usergrp ID, not unique per child. The actual identifier is the `cmd`/`host`/`user` text.
203
+
204
+ ## Known gaps (TODO)
205
+
206
+ - **`bt epml rbp roles get <id>`** — there is no GET-single-role in the v1 API. The detailed view (role with all child relations resolved) only exists at `/api/v6/pbul/{hostid}/rbp/roledetail/{id}`, which is on the IAM-only authorizer. Not callable with a PAT today. Workaround: use `bt epml rbp roles list` for the role row, then `bt epml rbp roles {cmdgrps,hostgrps,usergrps,tmdategrps} list <id>` to fetch each child relation.
207
+
182
208
  ## Path-version policy (CLI internal)
183
209
 
184
210
  Where the spec offers both legacy `/api/pbul/{hostid}/rbp/<x>` and newer
@@ -323,12 +323,20 @@ class EPMLClient:
323
323
  return self.get(f"/api/pbul/{h}/rbp/cmdgrps/{cmdgrp_id}/commands")
324
324
 
325
325
  def add_cmdgrp_commands(self, cmdgrp_id: int, commands: List[str], host_id: Optional[int] = None) -> Any:
326
+ """Append commands to a cmdgrp. POST is additive, not replacing."""
326
327
  h = self.host(host_id)
327
- return self.post(f"/api/pbul/{h}/rbp/cmdgrps/{cmdgrp_id}/commands", json=commands)
328
+ body = [{"cmd": c} for c in commands]
329
+ return self.post(f"/api/pbul/{h}/rbp/cmdgrps/{cmdgrp_id}/commands", json=body)
328
330
 
329
- def delete_cmdgrp_commands(self, cmdgrp_id: int, command_ids: List[int], host_id: Optional[int] = None) -> Any:
331
+ def clear_cmdgrp_commands(self, cmdgrp_id: int, host_id: Optional[int] = None) -> None:
332
+ """DELETE all commands in a cmdgrp. The API has no per-item delete."""
330
333
  h = self.host(host_id)
331
- return self._delete_with_body(f"/api/pbul/{h}/rbp/cmdgrps/{cmdgrp_id}/commands", command_ids)
334
+ self._delete_no_body(f"/api/pbul/{h}/rbp/cmdgrps/{cmdgrp_id}/commands")
335
+
336
+ def replace_cmdgrp_commands(self, cmdgrp_id: int, commands: List[str], host_id: Optional[int] = None) -> Any:
337
+ """Atomic-ish replace: clear then add. Two requests; not server-atomic."""
338
+ self.clear_cmdgrp_commands(cmdgrp_id, host_id=host_id)
339
+ return self.add_cmdgrp_commands(cmdgrp_id, commands, host_id=host_id)
332
340
 
333
341
  # ----- RBP: host groups ---------------------------------------------
334
342
 
@@ -353,12 +361,19 @@ class EPMLClient:
353
361
  return self.get(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts")
354
362
 
355
363
  def add_hostgrp_hosts(self, hostgrp_id: int, hosts: List[str], host_id: Optional[int] = None) -> Any:
364
+ """Append hostnames/patterns to a hostgrp. POST is additive."""
356
365
  h = self.host(host_id)
357
- return self.post(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts", json=hosts)
366
+ body = [{"host": x} for x in hosts]
367
+ return self.post(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts", json=body)
358
368
 
359
- def delete_hostgrp_hosts(self, hostgrp_id: int, host_ids: List[int], host_id: Optional[int] = None) -> Any:
369
+ def clear_hostgrp_hosts(self, hostgrp_id: int, host_id: Optional[int] = None) -> None:
370
+ """DELETE all hosts in a hostgrp. The API has no per-item delete."""
360
371
  h = self.host(host_id)
361
- return self._delete_with_body(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts", host_ids)
372
+ self._delete_no_body(f"/api/pbul/{h}/rbp/hostgrps/{hostgrp_id}/hosts")
373
+
374
+ def replace_hostgrp_hosts(self, hostgrp_id: int, hosts: List[str], host_id: Optional[int] = None) -> Any:
375
+ self.clear_hostgrp_hosts(hostgrp_id, host_id=host_id)
376
+ return self.add_hostgrp_hosts(hostgrp_id, hosts, host_id=host_id)
362
377
 
363
378
  # ----- RBP: user groups ---------------------------------------------
364
379
 
@@ -383,12 +398,35 @@ class EPMLClient:
383
398
  return self.get(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users")
384
399
 
385
400
  def add_usergrp_users(self, usergrp_id: int, users: List[str], host_id: Optional[int] = None) -> Any:
401
+ """Append usernames to a usergrp. POST is additive."""
386
402
  h = self.host(host_id)
387
- return self.post(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users", json=users)
403
+ body = [{"user": u} for u in users]
404
+ return self.post(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users", json=body)
405
+
406
+ def clear_usergrp_users(self, usergrp_id: int, host_id: Optional[int] = None) -> None:
407
+ """DELETE all users in a usergrp. The API has no per-item delete."""
408
+ h = self.host(host_id)
409
+ self._delete_no_body(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users")
410
+
411
+ def replace_usergrp_users(self, usergrp_id: int, users: List[str], host_id: Optional[int] = None) -> Any:
412
+ self.clear_usergrp_users(usergrp_id, host_id=host_id)
413
+ return self.add_usergrp_users(usergrp_id, users, host_id=host_id)
388
414
 
389
- def delete_usergrp_users(self, usergrp_id: int, user_ids: List[int], host_id: Optional[int] = None) -> Any:
415
+ def create_multiple_usergrps(self, groups: List[Dict[str, Any]], host_id: Optional[int] = None) -> Any:
416
+ """Bulk create user groups via /usergrps/multiple.
417
+
418
+ Each group dict needs at least `name` and `type` (`I` or `E`).
419
+ Body is wrapped: `{"usergroups": [...]}` — the spec doesn't document
420
+ the wrapper key.
421
+ """
390
422
  h = self.host(host_id)
391
- return self._delete_with_body(f"/api/pbul/{h}/rbp/usergrps/{usergrp_id}/users", user_ids)
423
+ # Default any missing types to "I"
424
+ normalized = []
425
+ for g in groups:
426
+ entry = dict(g)
427
+ entry.setdefault("type", "I")
428
+ normalized.append(entry)
429
+ return self.post(f"/api/pbul/{h}/rbp/usergrps/multiple", json={"usergroups": normalized})
392
430
 
393
431
  # ----- RBP: time/date groups ----------------------------------------
394
432
 
@@ -405,6 +443,26 @@ class EPMLClient:
405
443
  for tid in ids:
406
444
  self._delete_with_query(f"/api/pbul/{h}/rbp/tmdategrps", {"id": tid})
407
445
 
446
+ # --- tmdates (children of tmdategrps) ---
447
+
448
+ def list_tmdategrp_tmdates(self, tmdategrp_id: int, host_id: Optional[int] = None) -> Any:
449
+ h = self.host(host_id)
450
+ return self.get(f"/api/pbul/{h}/rbp/tmdategrps/{tmdategrp_id}/tmdates")
451
+
452
+ def add_tmdategrp_tmdates(self, tmdategrp_id: int, tmdates: List[Dict[str, Any]], host_id: Optional[int] = None) -> Any:
453
+ """Append time/date entries to a tmdategrp. Body is wrapped: `{tmdates: [...]}`."""
454
+ h = self.host(host_id)
455
+ return self.post(f"/api/pbul/{h}/rbp/tmdategrps/{tmdategrp_id}/tmdates", json={"tmdates": tmdates})
456
+
457
+ def clear_tmdategrp_tmdates(self, tmdategrp_id: int, host_id: Optional[int] = None) -> None:
458
+ """DELETE all tmdates in a tmdategrp."""
459
+ h = self.host(host_id)
460
+ self._delete_no_body(f"/api/pbul/{h}/rbp/tmdategrps/{tmdategrp_id}/tmdates")
461
+
462
+ def replace_tmdategrp_tmdates(self, tmdategrp_id: int, tmdates: List[Dict[str, Any]], host_id: Optional[int] = None) -> Any:
463
+ self.clear_tmdategrp_tmdates(tmdategrp_id, host_id=host_id)
464
+ return self.add_tmdategrp_tmdates(tmdategrp_id, tmdates, host_id=host_id)
465
+
408
466
  # ----- RBP: roles ---------------------------------------------------
409
467
 
410
468
  def list_roles(self, host_id: Optional[int] = None) -> Any:
@@ -582,6 +640,18 @@ class EPMLClient:
582
640
  except ValueError:
583
641
  return response.text
584
642
 
643
+ def _delete_no_body(self, spec_path: str) -> None:
644
+ """DELETE with no params and no body.
645
+
646
+ RBP child collections (e.g. cmdgrps/{id}/commands, hostgrps/{id}/hosts,
647
+ usergrps/{id}/users, tmdategrps/{id}/tmdates) all behave as
648
+ "delete all" when this is called — there's no per-item delete in the
649
+ v1 API. To remove a single child, GET the list, clear, then re-add the
650
+ ones you want to keep.
651
+ """
652
+ response = self._client.delete(self._build_url(spec_path), headers=self._headers())
653
+ response.raise_for_status()
654
+
585
655
  def _delete_with_query(self, spec_path: str, params: Dict[str, Any]) -> None:
586
656
  """DELETE with query parameters (no body).
587
657
 
@@ -96,7 +96,7 @@ def list_commands(
96
96
  print_json(data)
97
97
  else:
98
98
  rows = data if isinstance(data, list) else (data.get("data", []) if isinstance(data, dict) else [])
99
- print_table(rows, [("ID", "id"), ("Command", "command")], title=f"Commands in cmdgrp {cmdgrp_id}")
99
+ print_table(rows, [("Command", "cmd"), ("Description", "description"), ("Disabled", "disabled")], title=f"Commands in cmdgrp {cmdgrp_id}")
100
100
  except httpx.HTTPStatusError as e:
101
101
  print_api_error(e, "list commands"); raise typer.Exit(1)
102
102
  except Exception as e:
@@ -122,20 +122,44 @@ def add_commands(
122
122
  print_api_error(e, "add commands"); raise typer.Exit(1)
123
123
 
124
124
 
125
- @commands_app.command("remove")
126
- def remove_commands(
125
+ @commands_app.command("clear")
126
+ def clear_commands(
127
127
  cmdgrp_id: int = typer.Argument(..., help="Command group ID"),
128
- ids: str = typer.Option(..., "--ids", help="Comma-separated command IDs"),
129
128
  host: Optional[int] = _host_opt(),
129
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
130
130
  ):
131
- """Remove commands from a command group."""
131
+ """Remove ALL commands from a command group.
132
+
133
+ The API has no per-item delete — DELETE on the children collection clears
134
+ everything. To selectively remove, use `replace`.
135
+ """
132
136
  from bt_cli.epml.client import get_client
137
+ if not yes:
138
+ typer.confirm(f"Clear all commands in cmdgrp {cmdgrp_id}?", abort=True)
133
139
  try:
134
- id_list = [int(x.strip()) for x in ids.split(",") if x.strip()]
135
140
  with get_client() as c:
136
- c.delete_cmdgrp_commands(cmdgrp_id, id_list, host_id=host)
137
- typer.echo(f"Removed {len(id_list)} command(s) from cmdgrp {cmdgrp_id}")
141
+ c.clear_cmdgrp_commands(cmdgrp_id, host_id=host)
142
+ typer.echo(f"Cleared all commands from cmdgrp {cmdgrp_id}")
143
+ except httpx.HTTPStatusError as e:
144
+ print_api_error(e, "clear commands"); raise typer.Exit(1)
145
+ except Exception as e:
146
+ print_api_error(e, "clear commands"); raise typer.Exit(1)
147
+
148
+
149
+ @commands_app.command("replace")
150
+ def replace_commands(
151
+ cmdgrp_id: int = typer.Argument(..., help="Command group ID"),
152
+ commands: str = typer.Option(..., "--commands", "-c", help="Comma-separated command strings — final list"),
153
+ host: Optional[int] = _host_opt(),
154
+ ):
155
+ """Replace the entire command list (clear + add). Two requests; not server-atomic."""
156
+ from bt_cli.epml.client import get_client
157
+ try:
158
+ cmds = [x.strip() for x in commands.split(",") if x.strip()]
159
+ with get_client() as c:
160
+ result = c.replace_cmdgrp_commands(cmdgrp_id, cmds, host_id=host)
161
+ print_json(result)
138
162
  except httpx.HTTPStatusError as e:
139
- print_api_error(e, "remove commands"); raise typer.Exit(1)
163
+ print_api_error(e, "replace commands"); raise typer.Exit(1)
140
164
  except Exception as e:
141
- print_api_error(e, "remove commands"); raise typer.Exit(1)
165
+ print_api_error(e, "replace commands"); raise typer.Exit(1)
@@ -100,7 +100,7 @@ def list_hosts(
100
100
  print_json(data)
101
101
  else:
102
102
  rows = data if isinstance(data, list) else (data.get("data", []) if isinstance(data, dict) else [])
103
- print_table(rows, [("ID", "id"), ("Host", "host")], title=f"Hosts in hostgrp {hostgrp_id}")
103
+ print_table(rows, [("Host", "host"), ("Description", "description"), ("Disabled", "disabled")], title=f"Hosts in hostgrp {hostgrp_id}")
104
104
  except httpx.HTTPStatusError as e:
105
105
  print_api_error(e, "list hostgrp hosts"); raise typer.Exit(1)
106
106
  except Exception as e:
@@ -126,20 +126,44 @@ def add_hosts(
126
126
  print_api_error(e, "add hostgrp hosts"); raise typer.Exit(1)
127
127
 
128
128
 
129
- @hosts_app.command("remove")
130
- def remove_hosts(
129
+ @hosts_app.command("clear")
130
+ def clear_hosts(
131
131
  hostgrp_id: int = typer.Argument(..., help="Host group ID"),
132
- ids: str = typer.Option(..., "--ids", help="Comma-separated host IDs"),
133
132
  host: Optional[int] = _host_opt(),
133
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
134
134
  ):
135
- """Remove hosts from a host group."""
135
+ """Remove ALL hosts from a host group.
136
+
137
+ The API has no per-item delete — DELETE clears everything. Use `replace`
138
+ for selective removal.
139
+ """
136
140
  from bt_cli.epml.client import get_client
141
+ if not yes:
142
+ typer.confirm(f"Clear all hosts in hostgrp {hostgrp_id}?", abort=True)
137
143
  try:
138
- id_list = [int(x.strip()) for x in ids.split(",") if x.strip()]
139
144
  with get_client() as c:
140
- c.delete_hostgrp_hosts(hostgrp_id, id_list, host_id=host)
141
- typer.echo(f"Removed {len(id_list)} host(s) from hostgrp {hostgrp_id}")
145
+ c.clear_hostgrp_hosts(hostgrp_id, host_id=host)
146
+ typer.echo(f"Cleared all hosts from hostgrp {hostgrp_id}")
147
+ except httpx.HTTPStatusError as e:
148
+ print_api_error(e, "clear hostgrp hosts"); raise typer.Exit(1)
149
+ except Exception as e:
150
+ print_api_error(e, "clear hostgrp hosts"); raise typer.Exit(1)
151
+
152
+
153
+ @hosts_app.command("replace")
154
+ def replace_hosts(
155
+ hostgrp_id: int = typer.Argument(..., help="Host group ID"),
156
+ hosts: str = typer.Option(..., "--hosts", help="Comma-separated host name/glob expressions — final list"),
157
+ host: Optional[int] = _host_opt(),
158
+ ):
159
+ """Replace the entire host list (clear + add). Two requests; not server-atomic."""
160
+ from bt_cli.epml.client import get_client
161
+ try:
162
+ host_list = [x.strip() for x in hosts.split(",") if x.strip()]
163
+ with get_client() as c:
164
+ result = c.replace_hostgrp_hosts(hostgrp_id, host_list, host_id=host)
165
+ print_json(result)
142
166
  except httpx.HTTPStatusError as e:
143
- print_api_error(e, "remove hostgrp hosts"); raise typer.Exit(1)
167
+ print_api_error(e, "replace hostgrp hosts"); raise typer.Exit(1)
144
168
  except Exception as e:
145
- print_api_error(e, "remove hostgrp hosts"); raise typer.Exit(1)
169
+ print_api_error(e, "replace hostgrp hosts"); raise typer.Exit(1)