bt-cli 0.4.13__tar.gz → 0.4.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. {bt_cli-0.4.13 → bt_cli-0.4.15}/PKG-INFO +1 -1
  2. {bt_cli-0.4.13 → bt_cli-0.4.15}/pyproject.toml +1 -1
  3. bt_cli-0.4.15/scripts/sync-package-data.sh +21 -0
  4. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/config_file.py +14 -3
  5. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/policies.py +17 -3
  6. bt_cli-0.4.15/src/bt_cli/pws/commands/import_export.py +627 -0
  7. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/secrets.py +5 -5
  8. bt_cli-0.4.13/src/bt_cli/pws/commands/import_export.py +0 -452
  9. {bt_cli-0.4.13 → bt_cli-0.4.15}/.claude/skills/bt/SKILL.md +0 -0
  10. {bt_cli-0.4.13 → bt_cli-0.4.15}/.claude/skills/entitle/SKILL.md +0 -0
  11. {bt_cli-0.4.13 → bt_cli-0.4.15}/.claude/skills/epmw/SKILL.md +0 -0
  12. {bt_cli-0.4.13 → bt_cli-0.4.15}/.claude/skills/pra/SKILL.md +0 -0
  13. {bt_cli-0.4.13 → bt_cli-0.4.15}/.claude/skills/pws/SKILL.md +0 -0
  14. {bt_cli-0.4.13 → bt_cli-0.4.15}/.env.example +0 -0
  15. {bt_cli-0.4.13 → bt_cli-0.4.15}/.github/workflows/ci.yml +0 -0
  16. {bt_cli-0.4.13 → bt_cli-0.4.15}/.github/workflows/release.yml +0 -0
  17. {bt_cli-0.4.13 → bt_cli-0.4.15}/.gitignore +0 -0
  18. {bt_cli-0.4.13 → bt_cli-0.4.15}/CLAUDE.md +0 -0
  19. {bt_cli-0.4.13 → bt_cli-0.4.15}/README.md +0 -0
  20. {bt_cli-0.4.13 → bt_cli-0.4.15}/assets/cli-help.png +0 -0
  21. {bt_cli-0.4.13 → bt_cli-0.4.15}/assets/cli-output.png +0 -0
  22. {bt_cli-0.4.13 → bt_cli-0.4.15}/bt-cli.spec +0 -0
  23. {bt_cli-0.4.13 → bt_cli-0.4.15}/bt_entry.py +0 -0
  24. {bt_cli-0.4.13 → bt_cli-0.4.15}/scripts/bt_entry.py +0 -0
  25. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/__init__.py +0 -0
  26. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/cli.py +0 -0
  27. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/commands/__init__.py +0 -0
  28. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/commands/configure.py +0 -0
  29. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/commands/learn.py +0 -0
  30. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/commands/quick.py +0 -0
  31. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/__init__.py +0 -0
  32. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/auth.py +0 -0
  33. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/client.py +0 -0
  34. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/config.py +0 -0
  35. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/csv_utils.py +0 -0
  36. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/errors.py +0 -0
  37. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/output.py +0 -0
  38. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/prompts.py +0 -0
  39. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/core/rest_debug.py +0 -0
  40. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/data/CLAUDE.md +0 -0
  41. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/data/__init__.py +0 -0
  42. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  43. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
  44. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  45. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
  46. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
  47. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/__init__.py +0 -0
  48. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/client/__init__.py +0 -0
  49. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/client/base.py +0 -0
  50. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/__init__.py +0 -0
  51. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/accounts.py +0 -0
  52. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/applications.py +0 -0
  53. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/auth.py +0 -0
  54. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/bundles.py +0 -0
  55. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/integrations.py +0 -0
  56. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/permissions.py +0 -0
  57. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/policies.py +0 -0
  58. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/resources.py +0 -0
  59. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/roles.py +0 -0
  60. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/users.py +0 -0
  61. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/commands/workflows.py +0 -0
  62. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/__init__.py +0 -0
  63. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/bundle.py +0 -0
  64. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/common.py +0 -0
  65. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/integration.py +0 -0
  66. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/permission.py +0 -0
  67. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/policy.py +0 -0
  68. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/resource.py +0 -0
  69. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/role.py +0 -0
  70. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/user.py +0 -0
  71. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/entitle/models/workflow.py +0 -0
  72. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/__init__.py +0 -0
  73. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/client/__init__.py +0 -0
  74. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/client/base.py +0 -0
  75. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/__init__.py +0 -0
  76. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/audits.py +0 -0
  77. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/auth.py +0 -0
  78. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/computers.py +0 -0
  79. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/events.py +0 -0
  80. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/groups.py +0 -0
  81. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/quick.py +0 -0
  82. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/requests.py +0 -0
  83. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/roles.py +0 -0
  84. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/tasks.py +0 -0
  85. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/commands/users.py +0 -0
  86. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/epmw/models/__init__.py +0 -0
  87. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/__init__.py +0 -0
  88. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/client/__init__.py +0 -0
  89. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/client/base.py +0 -0
  90. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/__init__.py +0 -0
  91. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/auth.py +0 -0
  92. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/import_export.py +0 -0
  93. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/jump_clients.py +0 -0
  94. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  95. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/jump_items.py +0 -0
  96. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  97. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/policies.py +0 -0
  98. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/quick.py +0 -0
  99. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/teams.py +0 -0
  100. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/users.py +0 -0
  101. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/commands/vault.py +0 -0
  102. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/__init__.py +0 -0
  103. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/common.py +0 -0
  104. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/jump_client.py +0 -0
  105. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/jump_group.py +0 -0
  106. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/jump_item.py +0 -0
  107. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/jumpoint.py +0 -0
  108. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/team.py +0 -0
  109. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/user.py +0 -0
  110. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pra/models/vault.py +0 -0
  111. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/__init__.py +0 -0
  112. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/client/__init__.py +0 -0
  113. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/client/base.py +0 -0
  114. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  115. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  116. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/__init__.py +0 -0
  117. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/accounts.py +0 -0
  118. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/assets.py +0 -0
  119. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/auth.py +0 -0
  120. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/clouds.py +0 -0
  121. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/config.py +0 -0
  122. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/credentials.py +0 -0
  123. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/databases.py +0 -0
  124. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/directories.py +0 -0
  125. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/functional.py +0 -0
  126. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/platforms.py +0 -0
  127. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/quick.py +0 -0
  128. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/search.py +0 -0
  129. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/systems.py +0 -0
  130. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/users.py +0 -0
  131. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/commands/workgroups.py +0 -0
  132. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/config.py +0 -0
  133. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/models/__init__.py +0 -0
  134. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/models/account.py +0 -0
  135. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/models/asset.py +0 -0
  136. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/models/common.py +0 -0
  137. {bt_cli-0.4.13 → bt_cli-0.4.15}/src/bt_cli/pws/models/system.py +0 -0
  138. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/__init__.py +0 -0
  139. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/conftest.py +0 -0
  140. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/core/__init__.py +0 -0
  141. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/core/test_auth.py +0 -0
  142. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/core/test_config.py +0 -0
  143. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/core/test_errors.py +0 -0
  144. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/core/test_rest_debug.py +0 -0
  145. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/entitle/__init__.py +0 -0
  146. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/entitle/test_client.py +0 -0
  147. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/entitle/test_commands.py +0 -0
  148. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/entitle-smoke-test.sh +0 -0
  149. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/epmw/__init__.py +0 -0
  150. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/epmw/test_client.py +0 -0
  151. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/epmw/test_commands.py +0 -0
  152. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/epmw-quick-test-plan.md +0 -0
  153. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/fixtures/__init__.py +0 -0
  154. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/fixtures/responses.py +0 -0
  155. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/__init__.py +0 -0
  156. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/conftest.py +0 -0
  157. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/helpers.py +0 -0
  158. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/test_entitle_integration.py +0 -0
  159. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/test_epmw_integration.py +0 -0
  160. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/test_epmw_lifecycle.py +0 -0
  161. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/test_pra_integration.py +0 -0
  162. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/test_pra_lifecycle.py +0 -0
  163. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/test_pws_integration.py +0 -0
  164. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/integration/test_pws_lifecycle.py +0 -0
  165. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pra/__init__.py +0 -0
  166. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pra/test_client.py +0 -0
  167. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pra/test_commands.py +0 -0
  168. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pra-smoke-test.sh +0 -0
  169. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pra-test-plan.md +0 -0
  170. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pws/__init__.py +0 -0
  171. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pws/test_client.py +0 -0
  172. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pws/test_commands.py +0 -0
  173. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pws-quick-test-plan.md +0 -0
  174. {bt_cli-0.4.13 → bt_cli-0.4.15}/tests/pws-smoke-test.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bt-cli
3
- Version: 0.4.13
3
+ Version: 0.4.15
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.13"
7
+ version = "0.4.15"
8
8
  description = "BeyondTrust Platform CLI (unofficial) - Password Safe, Entitle, PRA, EPM"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ # Sync skills and CLAUDE.md to package data directory before building
3
+ # Run this before `python -m build` to ensure package has latest content
4
+
5
+ set -e
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
9
+
10
+ echo "Syncing package data..."
11
+
12
+ # Sync skills
13
+ rm -rf "$PROJECT_ROOT/src/bt_cli/data/skills"
14
+ cp -r "$PROJECT_ROOT/.claude/skills" "$PROJECT_ROOT/src/bt_cli/data/skills"
15
+ echo " ✓ Synced .claude/skills -> src/bt_cli/data/skills"
16
+
17
+ # Sync CLAUDE.md
18
+ cp "$PROJECT_ROOT/CLAUDE.md" "$PROJECT_ROOT/src/bt_cli/data/CLAUDE.md"
19
+ echo " ✓ Synced CLAUDE.md -> src/bt_cli/data/CLAUDE.md"
20
+
21
+ echo "Done. Ready to build."
@@ -205,6 +205,7 @@ def save_config_file(config: ConfigFile, path: Optional[Path] = None) -> None:
205
205
  # This prevents TOCTOU race where file could be readable between
206
206
  # creation and chmod.
207
207
  import os
208
+ import sys
208
209
  import tempfile
209
210
 
210
211
  # Write to temp file in same directory, then atomic rename
@@ -213,12 +214,22 @@ def save_config_file(config: ConfigFile, path: Optional[Path] = None) -> None:
213
214
  # Create temp file with secure permissions
214
215
  fd, tmp_path = tempfile.mkstemp(dir=dir_path, prefix=".config_", suffix=".tmp")
215
216
  try:
216
- # Set permissions on file descriptor before writing
217
- os.fchmod(fd, 0o600)
217
+ # Set permissions on file descriptor before writing (POSIX only)
218
+ # Windows doesn't support fchmod - permissions work differently there
219
+ if sys.platform != "win32" and hasattr(os, "fchmod"):
220
+ os.fchmod(fd, 0o600)
218
221
  with os.fdopen(fd, "w") as f:
219
222
  yaml.dump(data, f, default_flow_style=False, sort_keys=False)
220
- # Atomic rename
223
+ # Atomic rename (on Windows, need to remove target first if exists)
224
+ if sys.platform == "win32" and path.exists():
225
+ os.unlink(path)
221
226
  os.rename(tmp_path, path)
227
+ # On Windows, set permissions after the fact using chmod
228
+ if sys.platform == "win32":
229
+ try:
230
+ os.chmod(path, 0o600)
231
+ except OSError:
232
+ pass # Best effort on Windows
222
233
  except Exception:
223
234
  # Clean up temp file on error
224
235
  try:
@@ -288,15 +288,29 @@ def revert_policy(
288
288
  @app.command("download")
289
289
  def download_policy(
290
290
  policy_id: str = typer.Argument(..., help="Policy ID (UUID)"),
291
+ file: Optional[str] = typer.Option(None, "--file", "-f", help="Save to file instead of stdout"),
291
292
  ):
292
- """Download policy content (XML format)."""
293
+ """Download policy content (XML format).
294
+
295
+ Examples:
296
+ bt epmw policies download <policy_id>
297
+ bt epmw policies download <policy_id> --file policy.xml
298
+ bt epmw policies download <policy_id> > policy.xml
299
+ """
293
300
  from bt_cli.epmw.client import get_client
294
301
 
295
302
  try:
296
303
  client = get_client()
297
304
  content = client.download_policy(policy_id)
298
- # Policy content is XML
299
- typer.echo(content)
305
+
306
+ if file:
307
+ # Write to file
308
+ with open(file, "w", encoding="utf-8") as f:
309
+ f.write(content)
310
+ print_success(f"Policy saved to: {file}")
311
+ else:
312
+ # Output to stdout
313
+ typer.echo(content)
300
314
  except httpx.HTTPStatusError as e:
301
315
  print_api_error(e, "download policy")
302
316
  raise typer.Exit(1)