bt-cli 0.4.46__tar.gz → 0.4.48__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 (228) hide show
  1. {bt_cli-0.4.46 → bt_cli-0.4.48}/CLAUDE.md +1 -1
  2. {bt_cli-0.4.46 → bt_cli-0.4.48}/PKG-INFO +3 -3
  3. {bt_cli-0.4.46 → bt_cli-0.4.48}/README.md +2 -2
  4. {bt_cli-0.4.46 → bt_cli-0.4.48}/pyproject.toml +1 -1
  5. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/__init__.py +1 -1
  6. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/secrets/SKILL.md +34 -11
  7. bt_cli-0.4.48/src/bt_cli/secrets/commands/_hints.py +30 -0
  8. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/dynamic.py +48 -12
  9. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/folders.py +8 -2
  10. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/static.py +8 -2
  11. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/secrets/test_commands.py +89 -0
  12. {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/bt/SKILL.md +0 -0
  13. {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/entitle/SKILL.md +0 -0
  14. {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/epml/SKILL.md +0 -0
  15. {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/epmw/SKILL.md +0 -0
  16. {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/pra/SKILL.md +0 -0
  17. {bt_cli-0.4.46 → bt_cli-0.4.48}/.claude/skills/pws/SKILL.md +0 -0
  18. {bt_cli-0.4.46 → bt_cli-0.4.48}/.env.example +0 -0
  19. {bt_cli-0.4.46 → bt_cli-0.4.48}/.github/workflows/ci.yml +0 -0
  20. {bt_cli-0.4.46 → bt_cli-0.4.48}/.github/workflows/release.yml +0 -0
  21. {bt_cli-0.4.46 → bt_cli-0.4.48}/.gitignore +0 -0
  22. {bt_cli-0.4.46 → bt_cli-0.4.48}/assets/cli-help.png +0 -0
  23. {bt_cli-0.4.46 → bt_cli-0.4.48}/assets/cli-output.png +0 -0
  24. {bt_cli-0.4.46 → bt_cli-0.4.48}/bt-cli.spec +0 -0
  25. {bt_cli-0.4.46 → bt_cli-0.4.48}/bt_entry.py +0 -0
  26. {bt_cli-0.4.46 → bt_cli-0.4.48}/epml-clients-server-side-filters-plan.md +0 -0
  27. {bt_cli-0.4.46 → bt_cli-0.4.48}/epml-implementation-plan.md +0 -0
  28. {bt_cli-0.4.46 → bt_cli-0.4.48}/pf-implementation-plan.md +0 -0
  29. {bt_cli-0.4.46 → bt_cli-0.4.48}/scripts/bt_entry.py +0 -0
  30. {bt_cli-0.4.46 → bt_cli-0.4.48}/scripts/pf_onboard.py +0 -0
  31. {bt_cli-0.4.46 → bt_cli-0.4.48}/scripts/sync-package-data.sh +0 -0
  32. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/cli.py +0 -0
  33. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/commands/__init__.py +0 -0
  34. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/commands/configure.py +0 -0
  35. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/commands/learn.py +0 -0
  36. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/commands/quick.py +0 -0
  37. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/__init__.py +0 -0
  38. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/auth.py +0 -0
  39. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/client.py +0 -0
  40. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/config.py +0 -0
  41. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/config_file.py +0 -0
  42. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/csv_utils.py +0 -0
  43. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/errors.py +0 -0
  44. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/output.py +0 -0
  45. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/prompts.py +0 -0
  46. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/core/rest_debug.py +0 -0
  47. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/CLAUDE.md +0 -0
  48. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/__init__.py +0 -0
  49. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  50. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
  51. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/epml/SKILL.md +0 -0
  52. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  53. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
  54. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
  55. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/__init__.py +0 -0
  56. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/client/__init__.py +0 -0
  57. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/client/base.py +0 -0
  58. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/__init__.py +0 -0
  59. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/accounts.py +0 -0
  60. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/applications.py +0 -0
  61. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/auth.py +0 -0
  62. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/bundles.py +0 -0
  63. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/integrations.py +0 -0
  64. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/permissions.py +0 -0
  65. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/policies.py +0 -0
  66. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/requests.py +0 -0
  67. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/resources.py +0 -0
  68. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/roles.py +0 -0
  69. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/users.py +0 -0
  70. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/commands/workflows.py +0 -0
  71. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/__init__.py +0 -0
  72. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/bundle.py +0 -0
  73. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/common.py +0 -0
  74. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/integration.py +0 -0
  75. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/permission.py +0 -0
  76. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/policy.py +0 -0
  77. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/resource.py +0 -0
  78. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/role.py +0 -0
  79. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/user.py +0 -0
  80. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/entitle/models/workflow.py +0 -0
  81. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/__init__.py +0 -0
  82. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/client/__init__.py +0 -0
  83. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/client/base.py +0 -0
  84. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/__init__.py +0 -0
  85. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/audit.py +0 -0
  86. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/auth.py +0 -0
  87. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/client_pkg.py +0 -0
  88. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/clients.py +0 -0
  89. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/external_apis.py +0 -0
  90. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/hosts.py +0 -0
  91. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/iolog.py +0 -0
  92. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/license.py +0 -0
  93. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/quick.py +0 -0
  94. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
  95. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
  96. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
  97. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
  98. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
  99. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
  100. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -0
  101. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
  102. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/rbp_usergrps.py +0 -0
  103. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/settings.py +0 -0
  104. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/siems.py +0 -0
  105. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/commands/users.py +0 -0
  106. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epml/models/__init__.py +0 -0
  107. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/__init__.py +0 -0
  108. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/client/__init__.py +0 -0
  109. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/client/base.py +0 -0
  110. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/__init__.py +0 -0
  111. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/audits.py +0 -0
  112. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/auth.py +0 -0
  113. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/computers.py +0 -0
  114. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/events.py +0 -0
  115. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/groups.py +0 -0
  116. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/policies.py +0 -0
  117. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/quick.py +0 -0
  118. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/requests.py +0 -0
  119. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/roles.py +0 -0
  120. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/tasks.py +0 -0
  121. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/commands/users.py +0 -0
  122. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/epmw/models/__init__.py +0 -0
  123. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/__init__.py +0 -0
  124. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/client/__init__.py +0 -0
  125. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/client/base.py +0 -0
  126. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/__init__.py +0 -0
  127. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/auth.py +0 -0
  128. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/group_policies.py +0 -0
  129. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/import_export.py +0 -0
  130. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/jump_clients.py +0 -0
  131. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  132. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/jump_items.py +0 -0
  133. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  134. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/policies.py +0 -0
  135. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/quick.py +0 -0
  136. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/teams.py +0 -0
  137. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/users.py +0 -0
  138. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/commands/vault.py +0 -0
  139. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/__init__.py +0 -0
  140. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/common.py +0 -0
  141. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/group_policy.py +0 -0
  142. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/jump_client.py +0 -0
  143. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/jump_group.py +0 -0
  144. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/jump_item.py +0 -0
  145. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/jumpoint.py +0 -0
  146. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/team.py +0 -0
  147. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/user.py +0 -0
  148. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pra/models/vault.py +0 -0
  149. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/__init__.py +0 -0
  150. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/client/__init__.py +0 -0
  151. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/client/base.py +0 -0
  152. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  153. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  154. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/__init__.py +0 -0
  155. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/accounts.py +0 -0
  156. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/assets.py +0 -0
  157. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/attributes.py +0 -0
  158. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/auth.py +0 -0
  159. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/clouds.py +0 -0
  160. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/config.py +0 -0
  161. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/credentials.py +0 -0
  162. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/databases.py +0 -0
  163. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/directories.py +0 -0
  164. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/functional.py +0 -0
  165. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/import_export.py +0 -0
  166. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/platforms.py +0 -0
  167. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/quick.py +0 -0
  168. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/search.py +0 -0
  169. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/secrets.py +0 -0
  170. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/systems.py +0 -0
  171. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/users.py +0 -0
  172. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/commands/workgroups.py +0 -0
  173. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/config.py +0 -0
  174. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/__init__.py +0 -0
  175. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/account.py +0 -0
  176. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/asset.py +0 -0
  177. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/common.py +0 -0
  178. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/pws/models/system.py +0 -0
  179. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/__init__.py +0 -0
  180. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/client/__init__.py +0 -0
  181. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/client/base.py +0 -0
  182. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/__init__.py +0 -0
  183. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/auth.py +0 -0
  184. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/integrations.py +0 -0
  185. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/commands/leases.py +0 -0
  186. {bt_cli-0.4.46 → bt_cli-0.4.48}/src/bt_cli/secrets/models/__init__.py +0 -0
  187. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/__init__.py +0 -0
  188. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/conftest.py +0 -0
  189. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/__init__.py +0 -0
  190. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/test_auth.py +0 -0
  191. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/test_config.py +0 -0
  192. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/test_errors.py +0 -0
  193. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/core/test_rest_debug.py +0 -0
  194. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/entitle/__init__.py +0 -0
  195. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/entitle/test_client.py +0 -0
  196. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/entitle/test_commands.py +0 -0
  197. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/entitle-smoke-test.sh +0 -0
  198. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epml/__init__.py +0 -0
  199. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epml/test_client.py +0 -0
  200. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epml/test_commands.py +0 -0
  201. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epmw/__init__.py +0 -0
  202. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epmw/test_client.py +0 -0
  203. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epmw/test_commands.py +0 -0
  204. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/epmw-quick-test-plan.md +0 -0
  205. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/fixtures/__init__.py +0 -0
  206. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/fixtures/responses.py +0 -0
  207. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/__init__.py +0 -0
  208. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/conftest.py +0 -0
  209. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/helpers.py +0 -0
  210. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_entitle_integration.py +0 -0
  211. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_epmw_integration.py +0 -0
  212. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_epmw_lifecycle.py +0 -0
  213. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_pra_integration.py +0 -0
  214. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_pra_lifecycle.py +0 -0
  215. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_pws_integration.py +0 -0
  216. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/integration/test_pws_lifecycle.py +0 -0
  217. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra/__init__.py +0 -0
  218. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra/test_client.py +0 -0
  219. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra/test_commands.py +0 -0
  220. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra-smoke-test.sh +0 -0
  221. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pra-test-plan.md +0 -0
  222. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws/__init__.py +0 -0
  223. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws/test_client.py +0 -0
  224. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws/test_commands.py +0 -0
  225. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws-quick-test-plan.md +0 -0
  226. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/pws-smoke-test.sh +0 -0
  227. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/secrets/__init__.py +0 -0
  228. {bt_cli-0.4.46 → bt_cli-0.4.48}/tests/secrets/test_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  # BT-CLI
2
2
 
3
- BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.46**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.48**
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.46
3
+ Version: 0.4.48
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
@@ -121,8 +121,8 @@ export BT_SECRETS_API_VERSION=2026-02-16 # default (doc page is
121
121
  Common commands:
122
122
 
123
123
  ```bash
124
- bt secrets folders list -p lab # filter server-side
125
- bt secrets dynamic list -p lab/aws -r # browse dynamic-secret configs
124
+ bt secrets folders list -p lab # filter server-side (recursive default)
125
+ bt secrets dynamic list -p lab # all dynamic-secret configs under lab/
126
126
  bt secrets dynamic generate lab/aws/ec2-readonly # mint shell `export AWS_*` lines
127
127
  eval $(bt secrets dynamic generate lab/aws/ec2-readonly) # load into current shell
128
128
  bt secrets leases list lab/aws/ec2-readonly # who has active leases
@@ -74,8 +74,8 @@ export BT_SECRETS_API_VERSION=2026-02-16 # default (doc page is
74
74
  Common commands:
75
75
 
76
76
  ```bash
77
- bt secrets folders list -p lab # filter server-side
78
- bt secrets dynamic list -p lab/aws -r # browse dynamic-secret configs
77
+ bt secrets folders list -p lab # filter server-side (recursive default)
78
+ bt secrets dynamic list -p lab # all dynamic-secret configs under lab/
79
79
  bt secrets dynamic generate lab/aws/ec2-readonly # mint shell `export AWS_*` lines
80
80
  eval $(bt secrets dynamic generate lab/aws/ec2-readonly) # load into current shell
81
81
  bt secrets leases list lab/aws/ec2-readonly # who has active leases
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bt-cli"
7
- version = "0.4.46"
7
+ version = "0.4.48"
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.46"
3
+ __version__ = "0.4.48"
@@ -56,10 +56,13 @@ bt secrets folders get lab/aws # folder=lab, name=aws
56
56
 
57
57
  ## Folders
58
58
 
59
+ `list` is **recursive by default** (mirrors how users browse the UI tree).
60
+ Pass `--shallow` / `-S` for direct children only.
61
+
59
62
  ```bash
60
- bt secrets folders list # all root folders
61
- bt secrets folders list -p lab # filter server-side by path
62
- bt secrets folders list -p lab -r # recursive
63
+ bt secrets folders list # all folders, anywhere in the site
64
+ bt secrets folders list -p lab # everything under lab/ recursively
65
+ bt secrets folders list -p lab --shallow # only direct children of lab/
63
66
  bt secrets folders get lab/aws # metadata
64
67
  bt secrets folders create lab/aws/staging
65
68
  bt secrets folders delete lab/aws/staging
@@ -72,7 +75,9 @@ Versioned key/value secrets. The body is a JSON object — pass it inline via
72
75
  `--data` or from disk via `--data-file`.
73
76
 
74
77
  ```bash
75
- bt secrets static list -p lab # server-side filter
78
+ bt secrets static list # everything in the site (recursive default)
79
+ bt secrets static list -p lab # everything under lab/
80
+ bt secrets static list -p lab --shallow # direct children of lab/ only
76
81
  bt secrets static get lab/db-pass # default JSON output (values are structured)
77
82
  bt secrets static get lab/db-pass --version 2 # specific historical version
78
83
  bt secrets static create lab/db-pass -d '{"password":"hunter2","username":"admin"}'
@@ -85,17 +90,31 @@ bt secrets static delete lab/db-pass --force
85
90
  Configurations that mint short-lived credentials on demand (AWS STS today).
86
91
 
87
92
  ```bash
88
- bt secrets dynamic list -p lab/aws -r
93
+ bt secrets dynamic list # all dynamic-secret configs in the site
94
+ bt secrets dynamic list -p lab # everything under lab/
95
+ bt secrets dynamic list -p lab --shallow # only items directly at lab/ (usually none — they nest)
89
96
  bt secrets dynamic get lab/aws/ec2-readonly
90
97
 
91
98
  # Mint fresh credentials — default prints shell `export AWS_*` lines.
92
99
  bt secrets dynamic generate lab/aws/ec2-readonly
93
- eval $(bt secrets dynamic generate lab/aws/ec2-readonly) # one-liner load into shell
94
- aws sts get-caller-identity # verify
100
+
101
+ # Apply to the current session (a child process can't mutate its parent's env,
102
+ # so the CLI emits shell-native syntax that you eval into your shell):
103
+
104
+ # bash / zsh:
105
+ eval $(bt secrets dynamic generate lab/aws/ec2-readonly)
106
+
107
+ # PowerShell:
108
+ bt secrets dynamic generate lab/aws/ec2-readonly --format powershell | iex
109
+
110
+ # cmd.exe:
111
+ for /f "delims=" %i in ('bt secrets dynamic generate lab/aws/ec2-readonly --format cmd') do %i
112
+
113
+ aws sts get-caller-identity # verify the creds are active
95
114
 
96
115
  # Other formats:
97
116
  bt secrets dynamic generate lab/aws/ec2-readonly --format json # full payload w/ leaseId
98
- bt secrets dynamic generate lab/aws/ec2-readonly --format env # KEY=value (no `export`)
117
+ bt secrets dynamic generate lab/aws/ec2-readonly --format env # bare KEY=value
99
118
  bt secrets dynamic generate lab/aws/ec2-readonly --format dotenv # KEY="value" for .env files
100
119
  ```
101
120
 
@@ -188,11 +207,15 @@ bt secrets leases list lab/aws/full-admin -o json \
188
207
  ### Browse the tree
189
208
 
190
209
  ```bash
191
- bt secrets folders list -p lab -r
192
- bt secrets static list -p lab -r
193
- bt secrets dynamic list -p lab -r
210
+ bt secrets folders list -p lab
211
+ bt secrets static list -p lab
212
+ bt secrets dynamic list -p lab
194
213
  ```
195
214
 
215
+ `list` is recursive by default; the API otherwise only returns items
216
+ directly at the requested path (this trips up newcomers — passing
217
+ `-p lab` returns 0 in shallow mode because the items live in `lab/aws`).
218
+
196
219
  ## API Notes
197
220
 
198
221
  - Base URL: `https://api.beyondtrust.io/site/<site-id>/secrets`
@@ -0,0 +1,30 @@
1
+ """Shared empty-result hint for `bt secrets <X> list` commands.
2
+
3
+ When server-side filtering returns 0 rows it's almost always one of:
4
+ * --path points one level too high (items live in subfolders)
5
+ * --shallow was passed but the user thought it was recursive
6
+ * the path simply doesn't exist
7
+
8
+ The default "No results found" output gives no clue which. This helper
9
+ prints a one-liner that nudges the user toward the next thing to try.
10
+ """
11
+
12
+ from typing import Optional
13
+
14
+ from bt_cli.core.output import console
15
+
16
+
17
+ def empty_hint(path: Optional[str], recursive: bool, kind: str) -> None:
18
+ """Print a tip when a list call returns no rows."""
19
+ if recursive:
20
+ where = f" under {path!r}" if path else ""
21
+ console.print(f"[dim]No {kind} found{where}.[/dim]")
22
+ console.print(
23
+ "[dim]Tip: pass --shallow for direct children only, or check the path.[/dim]"
24
+ )
25
+ else:
26
+ at = f" directly at {path!r}" if path else " at the root"
27
+ console.print(f"[dim]No {kind}{at} (--shallow mode).[/dim]")
28
+ console.print(
29
+ "[dim]Tip: drop --shallow (default is recursive) to search subfolders.[/dim]"
30
+ )
@@ -20,6 +20,7 @@ from bt_cli.core.output import (
20
20
  print_table,
21
21
  )
22
22
  from bt_cli.secrets.client import split_path
23
+ from bt_cli.secrets.commands._hints import empty_hint
23
24
 
24
25
  app = typer.Typer(no_args_is_help=True, help="Dynamic secrets (e.g. AWS STS, mint on demand)")
25
26
 
@@ -27,10 +28,12 @@ app = typer.Typer(no_args_is_help=True, help="Dynamic secrets (e.g. AWS STS, min
27
28
  class GenerateFormat(str, Enum):
28
29
  """Output formats for `dynamic generate`."""
29
30
 
30
- EXPORT = "export" # default — shell `export AWS_*` lines
31
- ENV = "env" # KEY=value lines (no `export` prefix)
32
- JSON = "json" # raw API payload
33
- DOTENV = "dotenv" # KEY="value" lines suitable for .env files
31
+ EXPORT = "export" # default — bash/zsh `export AWS_*` lines
32
+ POWERSHELL = "powershell" # PowerShell `$env:AWS_* = "..."` lines
33
+ CMD = "cmd" # Windows cmd.exe `set AWS_*=...` lines
34
+ ENV = "env" # bare KEY=value lines (no `export`)
35
+ JSON = "json" # raw API payload
36
+ DOTENV = "dotenv" # KEY="value" lines suitable for .env files
34
37
 
35
38
 
36
39
  def _dynamic_row(item: dict) -> dict:
@@ -50,6 +53,13 @@ def _format_credentials(secret: dict, fmt: GenerateFormat) -> str:
50
53
  (``accessKeyId``, ``secretAccessKey``, ``sessionToken``). For shell
51
54
  consumption we map those to the standard ``AWS_*`` env-var names that
52
55
  boto3, aws-cli, and the EC2 SDK all read.
56
+
57
+ A subprocess can't mutate its parent shell's environment, so each
58
+ format is designed to be piped into the shell's eval mechanism:
59
+
60
+ * EXPORT → `eval $(bt secrets dynamic generate <path>)`
61
+ * POWERSHELL → `bt secrets dynamic generate <path> --format powershell | iex`
62
+ * CMD → cmd.exe needs a temp .bat or a `for /f` loop — see SKILL.md
53
63
  """
54
64
  aws_map = [
55
65
  ("AWS_ACCESS_KEY_ID", "accessKeyId"),
@@ -62,6 +72,16 @@ def _format_credentials(secret: dict, fmt: GenerateFormat) -> str:
62
72
  value = secret[payload_key]
63
73
  if fmt is GenerateFormat.EXPORT:
64
74
  lines.append(f"export {env_name}={value}")
75
+ elif fmt is GenerateFormat.POWERSHELL:
76
+ # `Invoke-Expression` parses these as statements that set the
77
+ # process env. Quote the value so PowerShell doesn't try to
78
+ # interpret embedded characters.
79
+ lines.append(f'$env:{env_name} = "{value}"')
80
+ elif fmt is GenerateFormat.CMD:
81
+ # `set` in cmd.exe doesn't quote and doesn't tolerate spaces
82
+ # around `=`. Values from STS are alphanumeric-plus-/+=_-, so
83
+ # bare assignment is safe.
84
+ lines.append(f"set {env_name}={value}")
65
85
  elif fmt is GenerateFormat.ENV:
66
86
  lines.append(f"{env_name}={value}")
67
87
  elif fmt is GenerateFormat.DOTENV:
@@ -69,19 +89,27 @@ def _format_credentials(secret: dict, fmt: GenerateFormat) -> str:
69
89
  if not lines:
70
90
  return ""
71
91
  if "expiration" in secret:
72
- # Trailing comment is purely informational — doesn't affect `eval $(...)`.
73
- lines.append(f"# expires: {secret['expiration']}")
92
+ # Trailing comment is informational — all four script-y formats
93
+ # tolerate `#` (bash/PowerShell) or treat the line as a label-style
94
+ # noop for cmd's set, which we replace with REM below.
95
+ if fmt is GenerateFormat.CMD:
96
+ lines.append(f"REM expires: {secret['expiration']}")
97
+ else:
98
+ lines.append(f"# expires: {secret['expiration']}")
74
99
  return "\n".join(lines)
75
100
 
76
101
 
77
102
  @app.command("list")
78
103
  def list_dynamic(
79
104
  path: Optional[str] = typer.Option(None, "--path", "-p", help="Path prefix to filter"),
80
- recursive: bool = typer.Option(False, "--recursive", "-r", help="List recursively"),
105
+ recursive: bool = typer.Option(
106
+ True, "--recursive/--shallow", "-r/-S",
107
+ help="Recurse into subfolders (default). Use --shallow / -S for direct children only.",
108
+ ),
81
109
  deleted_only: bool = typer.Option(False, "--deleted-only", help="Only list soft-deleted configs"),
82
110
  output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o", help="Output format"),
83
111
  ) -> None:
84
- """List dynamic-secret configs (server-side filter by --path)."""
112
+ """List dynamic-secret configs. Recursive by default — pass --shallow for direct children only."""
85
113
  from bt_cli.secrets.client import get_client
86
114
 
87
115
  try:
@@ -90,6 +118,8 @@ def list_dynamic(
90
118
 
91
119
  if output == OutputFormat.JSON:
92
120
  print_json(data)
121
+ elif not data:
122
+ empty_hint(path, recursive, "dynamic secrets")
93
123
  else:
94
124
  print_table(
95
125
  [_dynamic_row(r) for r in data],
@@ -144,15 +174,21 @@ def generate(
144
174
  full_path: str = typer.Argument(..., help="Full dynamic-secret path"),
145
175
  format_: GenerateFormat = typer.Option(
146
176
  GenerateFormat.EXPORT, "--format", "-F",
147
- help="Output format: export | env | json | dotenv",
177
+ help="Output format: export | powershell | cmd | env | json | dotenv",
148
178
  case_sensitive=False,
149
179
  ),
150
180
  ) -> None:
151
181
  """Mint fresh credentials from a dynamic-secret config.
152
182
 
153
- Default ``--format export`` prints shell ``export AWS_*`` lines suitable
154
- for ``eval $(bt secrets dynamic generate <path>)``. Use ``--format json``
155
- to see the full payload (lease id, expiration, etc.).
183
+ Default ``--format export`` prints shell ``export AWS_*`` lines.
184
+
185
+ \b
186
+ Apply in your current session:
187
+ bash / zsh: eval $(bt secrets dynamic generate <path>)
188
+ PowerShell: bt secrets dynamic generate <path> --format powershell | iex
189
+ cmd.exe: for /f "delims=" %i in ('bt secrets dynamic generate <path> --format cmd') do %i
190
+
191
+ Use ``--format json`` to see the full payload (lease id, expiration).
156
192
 
157
193
  Heads up: every call mints real STS credentials and shows up in the
158
194
  audit trail. Don't loop this in development.
@@ -14,6 +14,7 @@ from bt_cli.core.output import (
14
14
  print_table,
15
15
  )
16
16
  from bt_cli.secrets.client import split_path
17
+ from bt_cli.secrets.commands._hints import empty_hint
17
18
 
18
19
  app = typer.Typer(no_args_is_help=True, help="Folders for organizing secrets")
19
20
 
@@ -30,11 +31,14 @@ def _folder_row(item: dict) -> dict:
30
31
  @app.command("list")
31
32
  def list_folders(
32
33
  path: Optional[str] = typer.Option(None, "--path", "-p", help="Path prefix to filter folders"),
33
- recursive: bool = typer.Option(False, "--recursive", "-r", help="List recursively"),
34
+ recursive: bool = typer.Option(
35
+ True, "--recursive/--shallow", "-r/-S",
36
+ help="Recurse into subfolders (default). Use --shallow / -S for direct children only.",
37
+ ),
34
38
  deleted_only: bool = typer.Option(False, "--deleted-only", help="Only list soft-deleted folders"),
35
39
  output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o", help="Output format"),
36
40
  ) -> None:
37
- """List folders (server-side filtering by --path)."""
41
+ """List folders. Recursive by default — pass --shallow for direct children only."""
38
42
  from bt_cli.secrets.client import get_client
39
43
 
40
44
  try:
@@ -43,6 +47,8 @@ def list_folders(
43
47
 
44
48
  if output == OutputFormat.JSON:
45
49
  print_json(data)
50
+ elif not data:
51
+ empty_hint(path, recursive, "folders")
46
52
  else:
47
53
  print_table(
48
54
  [_folder_row(r) for r in data],
@@ -16,6 +16,7 @@ from bt_cli.core.output import (
16
16
  print_table,
17
17
  )
18
18
  from bt_cli.secrets.client import split_path
19
+ from bt_cli.secrets.commands._hints import empty_hint
19
20
 
20
21
  app = typer.Typer(no_args_is_help=True, help="Static (key/value, versioned) secrets")
21
22
 
@@ -34,12 +35,15 @@ def _static_row(item: dict) -> dict:
34
35
  @app.command("list")
35
36
  def list_static(
36
37
  path: Optional[str] = typer.Option(None, "--path", "-p", help="Path prefix to filter (server-side)"),
37
- recursive: bool = typer.Option(False, "--recursive", "-r", help="List recursively"),
38
+ recursive: bool = typer.Option(
39
+ True, "--recursive/--shallow", "-r/-S",
40
+ help="Recurse into subfolders (default). Use --shallow / -S for direct children only.",
41
+ ),
38
42
  versions: bool = typer.Option(False, "--versions", help="Include version history"),
39
43
  deleted_only: bool = typer.Option(False, "--deleted-only", help="Only list soft-deleted secrets"),
40
44
  output: OutputFormat = typer.Option(OutputFormat.TABLE, "--output", "-o", help="Output format"),
41
45
  ) -> None:
42
- """List static secrets (filtered server-side by --path)."""
46
+ """List static secrets. Recursive by default — pass --shallow for direct children only."""
43
47
  from bt_cli.secrets.client import get_client
44
48
 
45
49
  try:
@@ -50,6 +54,8 @@ def list_static(
50
54
 
51
55
  if output == OutputFormat.JSON:
52
56
  print_json(data)
57
+ elif not data:
58
+ empty_hint(path, recursive, "static secrets")
53
59
  else:
54
60
  print_table(
55
61
  [_static_row(r) for r in data],
@@ -133,6 +133,49 @@ class TestDynamic:
133
133
  assert "ec2-readonly" in result.stdout
134
134
  assert route.called
135
135
 
136
+ @respx.mock
137
+ def test_dynamic_list_recursive_is_default(self, env_overrides):
138
+ """No -r / --recursive flag → still sends recursive=true (new default)."""
139
+ route = respx.get(_url("/dynamic")).mock(
140
+ return_value=httpx.Response(200, json={"data": []})
141
+ )
142
+ result = CliRunner().invoke(secrets_app, ["dynamic", "list", "-p", "lab"])
143
+ assert result.exit_code == 0
144
+ assert "recursive=true" in str(route.calls[0].request.url)
145
+
146
+ @respx.mock
147
+ def test_dynamic_list_shallow_opts_out(self, env_overrides):
148
+ """--shallow turns off recursion."""
149
+ route = respx.get(_url("/dynamic")).mock(
150
+ return_value=httpx.Response(200, json={"data": []})
151
+ )
152
+ result = CliRunner().invoke(secrets_app, ["dynamic", "list", "-p", "lab", "--shallow"])
153
+ assert result.exit_code == 0
154
+ assert "recursive=true" not in str(route.calls[0].request.url)
155
+
156
+ @respx.mock
157
+ def test_dynamic_list_empty_recursive_hint(self, env_overrides):
158
+ """Empty + recursive → hint nudges toward path check."""
159
+ respx.get(_url("/dynamic")).mock(
160
+ return_value=httpx.Response(200, json={"data": []})
161
+ )
162
+ result = CliRunner().invoke(secrets_app, ["dynamic", "list", "-p", "nowhere"])
163
+ assert result.exit_code == 0
164
+ assert "No dynamic secrets" in result.stdout
165
+ assert "'nowhere'" in result.stdout
166
+ assert "--shallow" in result.stdout # hint mentions the alternative
167
+
168
+ @respx.mock
169
+ def test_dynamic_list_empty_shallow_hint(self, env_overrides):
170
+ """Empty + --shallow → hint nudges toward dropping --shallow."""
171
+ respx.get(_url("/dynamic")).mock(
172
+ return_value=httpx.Response(200, json={"data": []})
173
+ )
174
+ result = CliRunner().invoke(secrets_app, ["dynamic", "list", "-p", "lab", "--shallow"])
175
+ assert result.exit_code == 0
176
+ assert "--shallow mode" in result.stdout
177
+ assert "drop --shallow" in result.stdout
178
+
136
179
  @respx.mock
137
180
  def test_dynamic_generate_export_shape(self, env_overrides):
138
181
  respx.post(_url("/dynamic/ec2-readonly/generate")).mock(
@@ -183,6 +226,52 @@ class TestDynamic:
183
226
  assert "AWS_ACCESS_KEY_ID=AKIA-FAKE" in result.stdout
184
227
  assert "export" not in result.stdout.split("\n")[0]
185
228
 
229
+ @respx.mock
230
+ def test_dynamic_generate_powershell_shape(self, env_overrides):
231
+ """PowerShell format: $env:VAR = "val" — pipe to Invoke-Expression."""
232
+ respx.post(_url("/dynamic/ec2-readonly/generate")).mock(
233
+ return_value=httpx.Response(201, json={"secret": {
234
+ "accessKeyId": "ASIA-FAKE",
235
+ "secretAccessKey": "SAK-FAKE",
236
+ "sessionToken": "TOK-FAKE",
237
+ "expiration": "2026-01-01T00:00:00Z",
238
+ }})
239
+ )
240
+ result = CliRunner().invoke(
241
+ secrets_app,
242
+ ["dynamic", "generate", "lab/aws/ec2-readonly", "--format", "powershell"],
243
+ )
244
+ assert result.exit_code == 0
245
+ assert '$env:AWS_ACCESS_KEY_ID = "ASIA-FAKE"' in result.stdout
246
+ assert '$env:AWS_SECRET_ACCESS_KEY = "SAK-FAKE"' in result.stdout
247
+ assert '$env:AWS_SESSION_TOKEN = "TOK-FAKE"' in result.stdout
248
+ # Comment must use `#` (valid PowerShell line comment)
249
+ assert "# expires:" in result.stdout
250
+ # Must NOT contain bash `export` syntax
251
+ assert "export AWS_" not in result.stdout
252
+
253
+ @respx.mock
254
+ def test_dynamic_generate_cmd_shape(self, env_overrides):
255
+ """cmd.exe format: set VAR=val — for `for /f` extraction."""
256
+ respx.post(_url("/dynamic/ec2-readonly/generate")).mock(
257
+ return_value=httpx.Response(201, json={"secret": {
258
+ "accessKeyId": "ASIA-FAKE",
259
+ "secretAccessKey": "SAK-FAKE",
260
+ "sessionToken": "TOK-FAKE",
261
+ "expiration": "2026-01-01T00:00:00Z",
262
+ }})
263
+ )
264
+ result = CliRunner().invoke(
265
+ secrets_app,
266
+ ["dynamic", "generate", "lab/aws/ec2-readonly", "--format", "cmd"],
267
+ )
268
+ assert result.exit_code == 0
269
+ assert "set AWS_ACCESS_KEY_ID=ASIA-FAKE" in result.stdout
270
+ assert "set AWS_SECRET_ACCESS_KEY=SAK-FAKE" in result.stdout
271
+ # cmd.exe uses REM, not #, for comments
272
+ assert "REM expires:" in result.stdout
273
+ assert "# expires" not in result.stdout
274
+
186
275
 
187
276
  class TestLeases:
188
277
  @respx.mock
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes