bt-cli 0.4.67__tar.gz → 0.4.69__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 (249) hide show
  1. {bt_cli-0.4.67 → bt_cli-0.4.69}/CLAUDE.md +1 -1
  2. {bt_cli-0.4.67 → bt_cli-0.4.69}/PKG-INFO +1 -1
  3. {bt_cli-0.4.67 → bt_cli-0.4.69}/pyproject.toml +1 -1
  4. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/__init__.py +1 -1
  5. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/skills/pws/SKILL.md +26 -0
  6. bt_cli-0.4.69/src/bt_cli/data/skills/session-analysis/SKILL.md +72 -0
  7. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/sessions.py +78 -2
  8. bt_cli-0.4.67/src/bt_cli/data/skills/session-analysis/SKILL.md +0 -64
  9. {bt_cli-0.4.67 → bt_cli-0.4.69}/.claude/skills/bt/SKILL.md +0 -0
  10. {bt_cli-0.4.67 → bt_cli-0.4.69}/.claude/skills/entitle/SKILL.md +0 -0
  11. {bt_cli-0.4.67 → bt_cli-0.4.69}/.claude/skills/epml/SKILL.md +0 -0
  12. {bt_cli-0.4.67 → bt_cli-0.4.69}/.claude/skills/epmw/SKILL.md +0 -0
  13. {bt_cli-0.4.67 → bt_cli-0.4.69}/.claude/skills/pra/SKILL.md +0 -0
  14. {bt_cli-0.4.67 → bt_cli-0.4.69}/.claude/skills/pws/SKILL.md +0 -0
  15. {bt_cli-0.4.67 → bt_cli-0.4.69}/.env.example +0 -0
  16. {bt_cli-0.4.67 → bt_cli-0.4.69}/.github/workflows/ci.yml +0 -0
  17. {bt_cli-0.4.67 → bt_cli-0.4.69}/.github/workflows/release.yml +0 -0
  18. {bt_cli-0.4.67 → bt_cli-0.4.69}/.gitignore +0 -0
  19. {bt_cli-0.4.67 → bt_cli-0.4.69}/README.md +0 -0
  20. {bt_cli-0.4.67 → bt_cli-0.4.69}/assets/cli-help.png +0 -0
  21. {bt_cli-0.4.67 → bt_cli-0.4.69}/assets/cli-output.png +0 -0
  22. {bt_cli-0.4.67 → bt_cli-0.4.69}/bt-cli.spec +0 -0
  23. {bt_cli-0.4.67 → bt_cli-0.4.69}/bt_entry.py +0 -0
  24. {bt_cli-0.4.67 → bt_cli-0.4.69}/epml-clients-server-side-filters-plan.md +0 -0
  25. {bt_cli-0.4.67 → bt_cli-0.4.69}/epml-implementation-plan.md +0 -0
  26. {bt_cli-0.4.67 → bt_cli-0.4.69}/pf-implementation-plan.md +0 -0
  27. {bt_cli-0.4.67 → bt_cli-0.4.69}/scripts/bt_entry.py +0 -0
  28. {bt_cli-0.4.67 → bt_cli-0.4.69}/scripts/pf_onboard.py +0 -0
  29. {bt_cli-0.4.67 → bt_cli-0.4.69}/scripts/sync-package-data.sh +0 -0
  30. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/cli.py +0 -0
  31. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/commands/__init__.py +0 -0
  32. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/commands/configure.py +0 -0
  33. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/commands/learn.py +0 -0
  34. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/commands/quick.py +0 -0
  35. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/__init__.py +0 -0
  36. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/auth.py +0 -0
  37. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/client.py +0 -0
  38. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/config.py +0 -0
  39. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/config_file.py +0 -0
  40. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/csv_utils.py +0 -0
  41. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/errors.py +0 -0
  42. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/output.py +0 -0
  43. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/prompts.py +0 -0
  44. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/core/rest_debug.py +0 -0
  45. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/CLAUDE.md +0 -0
  46. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/__init__.py +0 -0
  47. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
  48. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
  49. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/skills/epml/SKILL.md +0 -0
  50. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
  51. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/skills/pf/SKILL.md +0 -0
  52. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
  53. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/data/skills/secrets/SKILL.md +0 -0
  54. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/__init__.py +0 -0
  55. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/client/__init__.py +0 -0
  56. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/client/base.py +0 -0
  57. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/__init__.py +0 -0
  58. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/accounts.py +0 -0
  59. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/applications.py +0 -0
  60. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/auth.py +0 -0
  61. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/bundles.py +0 -0
  62. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/integrations.py +0 -0
  63. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/permissions.py +0 -0
  64. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/policies.py +0 -0
  65. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/requests.py +0 -0
  66. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/resources.py +0 -0
  67. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/roles.py +0 -0
  68. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/users.py +0 -0
  69. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/commands/workflows.py +0 -0
  70. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/__init__.py +0 -0
  71. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/bundle.py +0 -0
  72. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/common.py +0 -0
  73. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/integration.py +0 -0
  74. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/permission.py +0 -0
  75. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/policy.py +0 -0
  76. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/resource.py +0 -0
  77. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/role.py +0 -0
  78. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/user.py +0 -0
  79. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/entitle/models/workflow.py +0 -0
  80. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/__init__.py +0 -0
  81. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/client/__init__.py +0 -0
  82. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/client/base.py +0 -0
  83. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/__init__.py +0 -0
  84. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/audit.py +0 -0
  85. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/auth.py +0 -0
  86. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/client_pkg.py +0 -0
  87. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/clients.py +0 -0
  88. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/external_apis.py +0 -0
  89. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/hosts.py +0 -0
  90. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/iolog.py +0 -0
  91. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/license.py +0 -0
  92. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/quick.py +0 -0
  93. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
  94. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
  95. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
  96. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
  97. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
  98. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
  99. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -0
  100. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
  101. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/rbp_usergrps.py +0 -0
  102. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/settings.py +0 -0
  103. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/siems.py +0 -0
  104. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/commands/users.py +0 -0
  105. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epml/models/__init__.py +0 -0
  106. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/__init__.py +0 -0
  107. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/client/__init__.py +0 -0
  108. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/client/base.py +0 -0
  109. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/__init__.py +0 -0
  110. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/audits.py +0 -0
  111. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/auth.py +0 -0
  112. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/computers.py +0 -0
  113. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/events.py +0 -0
  114. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/groups.py +0 -0
  115. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/policies.py +0 -0
  116. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/quick.py +0 -0
  117. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/requests.py +0 -0
  118. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/roles.py +0 -0
  119. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/tasks.py +0 -0
  120. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/commands/users.py +0 -0
  121. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/epmw/models/__init__.py +0 -0
  122. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/__init__.py +0 -0
  123. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/client/__init__.py +0 -0
  124. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/client/base.py +0 -0
  125. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/commands/__init__.py +0 -0
  126. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/commands/auth.py +0 -0
  127. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/commands/machines.py +0 -0
  128. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/commands/tokens.py +0 -0
  129. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/commands/user.py +0 -0
  130. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pf/commands/users.py +0 -0
  131. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/__init__.py +0 -0
  132. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/client/__init__.py +0 -0
  133. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/client/base.py +0 -0
  134. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/__init__.py +0 -0
  135. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/auth.py +0 -0
  136. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/group_policies.py +0 -0
  137. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/import_export.py +0 -0
  138. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/jump_clients.py +0 -0
  139. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/jump_groups.py +0 -0
  140. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/jump_items.py +0 -0
  141. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/jumpoints.py +0 -0
  142. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/policies.py +0 -0
  143. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/quick.py +0 -0
  144. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/teams.py +0 -0
  145. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/users.py +0 -0
  146. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/commands/vault.py +0 -0
  147. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/__init__.py +0 -0
  148. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/common.py +0 -0
  149. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/group_policy.py +0 -0
  150. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/jump_client.py +0 -0
  151. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/jump_group.py +0 -0
  152. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/jump_item.py +0 -0
  153. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/jumpoint.py +0 -0
  154. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/team.py +0 -0
  155. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/user.py +0 -0
  156. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pra/models/vault.py +0 -0
  157. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/__init__.py +0 -0
  158. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/client/__init__.py +0 -0
  159. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/client/base.py +0 -0
  160. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/client/beyondinsight.py +0 -0
  161. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/client/passwordsafe.py +0 -0
  162. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/__init__.py +0 -0
  163. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/accounts.py +0 -0
  164. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/assets.py +0 -0
  165. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/attributes.py +0 -0
  166. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/audit.py +0 -0
  167. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/auth.py +0 -0
  168. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/clouds.py +0 -0
  169. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/config.py +0 -0
  170. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/credentials.py +0 -0
  171. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/databases.py +0 -0
  172. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/directories.py +0 -0
  173. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/functional.py +0 -0
  174. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/import_export.py +0 -0
  175. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/platforms.py +0 -0
  176. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/quick.py +0 -0
  177. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/search.py +0 -0
  178. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/secrets.py +0 -0
  179. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/systems.py +0 -0
  180. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/users.py +0 -0
  181. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/commands/workgroups.py +0 -0
  182. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/config.py +0 -0
  183. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/models/__init__.py +0 -0
  184. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/models/account.py +0 -0
  185. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/models/asset.py +0 -0
  186. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/models/common.py +0 -0
  187. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/pws/models/system.py +0 -0
  188. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/__init__.py +0 -0
  189. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/client/__init__.py +0 -0
  190. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/client/base.py +0 -0
  191. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/commands/__init__.py +0 -0
  192. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/commands/_hints.py +0 -0
  193. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/commands/auth.py +0 -0
  194. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/commands/dynamic.py +0 -0
  195. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/commands/folders.py +0 -0
  196. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/commands/integrations.py +0 -0
  197. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/commands/leases.py +0 -0
  198. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/commands/static.py +0 -0
  199. {bt_cli-0.4.67 → bt_cli-0.4.69}/src/bt_cli/secrets/models/__init__.py +0 -0
  200. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/__init__.py +0 -0
  201. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/conftest.py +0 -0
  202. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/core/__init__.py +0 -0
  203. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/core/test_auth.py +0 -0
  204. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/core/test_config.py +0 -0
  205. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/core/test_config_file.py +0 -0
  206. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/core/test_errors.py +0 -0
  207. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/core/test_output.py +0 -0
  208. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/core/test_rest_debug.py +0 -0
  209. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/entitle/__init__.py +0 -0
  210. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/entitle/test_client.py +0 -0
  211. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/entitle/test_commands.py +0 -0
  212. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/entitle-smoke-test.sh +0 -0
  213. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/epml/__init__.py +0 -0
  214. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/epml/test_client.py +0 -0
  215. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/epml/test_commands.py +0 -0
  216. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/epmw/__init__.py +0 -0
  217. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/epmw/test_client.py +0 -0
  218. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/epmw/test_commands.py +0 -0
  219. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/epmw-quick-test-plan.md +0 -0
  220. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/fixtures/__init__.py +0 -0
  221. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/fixtures/responses.py +0 -0
  222. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/__init__.py +0 -0
  223. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/conftest.py +0 -0
  224. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/helpers.py +0 -0
  225. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/test_entitle_integration.py +0 -0
  226. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/test_epmw_integration.py +0 -0
  227. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/test_epmw_lifecycle.py +0 -0
  228. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/test_pra_integration.py +0 -0
  229. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/test_pra_lifecycle.py +0 -0
  230. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/test_pws_integration.py +0 -0
  231. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/integration/test_pws_lifecycle.py +0 -0
  232. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pf/__init__.py +0 -0
  233. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pf/test_client.py +0 -0
  234. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pf/test_commands.py +0 -0
  235. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pra/__init__.py +0 -0
  236. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pra/test_client.py +0 -0
  237. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pra/test_commands.py +0 -0
  238. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pra/test_jump_item_fixes.py +0 -0
  239. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pra-smoke-test.sh +0 -0
  240. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pra-test-plan.md +0 -0
  241. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pws/__init__.py +0 -0
  242. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pws/test_client.py +0 -0
  243. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pws/test_commands.py +0 -0
  244. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pws/test_quick_sessions.py +0 -0
  245. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pws-quick-test-plan.md +0 -0
  246. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/pws-smoke-test.sh +0 -0
  247. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/secrets/__init__.py +0 -0
  248. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/secrets/test_client.py +0 -0
  249. {bt_cli-0.4.67 → bt_cli-0.4.69}/tests/secrets/test_commands.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.67**
3
+ BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.69**
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.67
3
+ Version: 0.4.69
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.67"
7
+ version = "0.4.69"
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.67"
3
+ __version__ = "0.4.69"
@@ -189,6 +189,11 @@ bt pws sessions list --active # only live sessions (started, not ended)
189
189
  bt pws sessions list --all -o json # full history
190
190
  bt pws sessions get <session_id>
191
191
 
192
+ # "The session from today" — date filters match on StartTime (client-side)
193
+ bt pws sessions list --today
194
+ bt pws sessions list --on 2026-07-02
195
+ bt pws sessions list --since 2026-06-01 --until 2026-06-30 # a range (e.g. "this week")
196
+
192
197
  # Suspend = lock (user disconnected, session can be resumed)
193
198
  bt pws sessions suspend <session_id>
194
199
 
@@ -241,6 +246,27 @@ prints the exact command + install instructions and leaves the frames. Works for
241
246
  RDP (screen) and SSH (rendered terminal). Gotcha: the renderer needs ~0.5s
242
247
  warm-up (first seek 404s), handled automatically.
243
248
 
249
+ ### "What happened in this session?" — summarize efficiently
250
+
251
+ `replay` only *downloads* frames. To turn a session into a **timeline summary of
252
+ what the user did**, don't read every frame — use the analysis pipeline:
253
+
254
+ ```bash
255
+ bt pws sessions replay <session_id> # 1. download the frames
256
+ bt pws sessions analyze <session_id> # 2. pick keyframes + anchor keystrokes + estimate cost
257
+ ```
258
+
259
+ `analyze` is deterministic prep (no LLM): it selects the handful of frames worth
260
+ reading — screen-change frames **plus** the frames that render what the user
261
+ typed (keystroke-anchored, tagged `after-typing` with the `typed` text) — pulls
262
+ the transcript, and prints a token/USD cost estimate. Then **invoke the
263
+ `/session-analysis` skill**, which tells you (the agent) to read only those
264
+ keyframes (after-typing first) and produce a timeline + auditor highlights (EPM
265
+ elevation prompts, privileged tools, service/account changes) + a secrets-exposure
266
+ note. For **SSH**, skip frames entirely — `analyze` points you at the transcript,
267
+ which is the full input+output and far cheaper. This is the intended path for
268
+ "show me / summarize the session from today".
269
+
244
270
  **Gotchas:**
245
271
  - `/Sessions` returns session **history**, not just active sessions, and **ignores `limit`/`offset`** (returns the full set every call - the client does a single GET, the CLI caps display client-side). Use `--active` for the live ones.
246
272
  - `Status` codes confirmed live: **0 = Not started, 1 = Active, 2 = Completed, 5 = Locked, 7 = Terminated** (3/9 are other terminal states). `--active` filters on Status == 1.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: session-analysis
3
+ description: Analyze a dumped Password Safe session (replay frames + keystrokes) into a timeline summary with a cost estimate. Use when the user points at a `session-<id>-replay` folder or asks to summarize/analyze a recorded RDP or SSH session.
4
+ ---
5
+
6
+ # Session Analysis
7
+
8
+ Turn a bt-cli session dump into a **timeline summary of what happened**, efficiently, with a **cost estimate**. Works for RDP (screen recording → frames) and SSH (keystroke transcript). This is interactive analysis — *you* (the model) read the frames/transcript; there is no LLM call inside the CLI.
9
+
10
+ ## The one rule: never read every frame
11
+
12
+ An RDP session sampled at ~1 frame per 2s is dozens of near-identical frames. Reading them all wastes tokens. Let the CLI pick the frames worth reading — the ones where the screen changed **and** the ones that render what the user typed — and read only those, guided by the per-frame `typed`/`reason` tags.
13
+
14
+ ## Recipe
15
+
16
+ ### 1. Prep with the CLI (does the heavy lifting)
17
+
18
+ ```bash
19
+ # If the user has a replay folder already:
20
+ bt pws sessions analyze --dir <folder>
21
+ # If they have the session id (reads ./session-<id>-replay):
22
+ bt pws sessions analyze <id> --model opus # or --model sonnet for a cheaper estimate
23
+ ```
24
+
25
+ `analyze` writes `analysis.json` into the folder and prints:
26
+ - **Who / what / when:** `user` (the initiator's real name + username, resolved from `UserID` via `/Users/{id}`), `user_email`, `account`@`system`, `start_time`. **Always name the human in the summary** — "UserID 219" is not an answer; "Amy Miller (amiller)" is.
27
+ - **RDP:** the **keyframes** to read. Each is tagged with a **reason** — `screen-change` (the picture moved) and/or `after-typing` (it renders the result of something the user typed). `after-typing` frames also carry the exact `typed` text.
28
+ - **SSH:** a pointer to the transcript (analyze it instead of frames).
29
+ - **`typed_bursts`** — typed input grouped into phrases with recording-relative offsets (`@14.9s "powershell"`), already anchored to the keyframes above.
30
+ - A **cost estimate**: `focused` (keyframes only) vs `all frames`.
31
+
32
+ If the user only has a folder (no live API access), `analyze --dir` still works — user name, keystrokes, and anchoring need the session metadata (`StartTime`/`UserID`), so offline it falls back to `screen-change` keyframes only, with a note. Keyframes + cost are still computed.
33
+
34
+ ### 2. Combine both signals — transcript AND frames
35
+
36
+ Don't treat this as either/or. The transcript gives you *verbatim text* the screen can't render cleanly; the frames give you *visual state* the transcript can't capture. Use each for what it's good at, and correlate them by timestamp (the CLI already did the correlation — `typed`/`typed_bursts` carry offsets, keystrokes carry `time`).
37
+
38
+ - **Always pull the transcript** when there is one: `bt pws sessions transcript <id>`. It's cheap.
39
+ - **SSH:** the transcript *is* the analysis — full input+output (typed commands `»` + terminal stdout). Read it; frames are rarely worth opening.
40
+ - **RDP:** the transcript is sparse (typed keys / window titles, often empty) but still adds color frames miss — the exact command typed, a window title, a value entered in a field. Fold it into the narrative alongside the frames.
41
+ - **Read the frames** (RDP): **read every keyframe the CLI listed — do not sub-sample them.** The CLI already culled the full recording down to the frames that matter; picking "6 of 37 to confirm the story" is how you miss events (a service restart, a dialog, a config change happen in the frames you skipped). If the full keyframe set is genuinely too expensive, raise `--threshold` and re-run so the *CLI* drops frames deliberately — never silently skip listed keyframes. Prioritize `after-typing` frames (the `typed` field tells you what to look for before you open the image), then read the `screen-change` frames for the mouse-driven actions keystrokes don't cover (menus, service start/stop, window closes).
42
+
43
+ ### 3. Anchoring is already done
44
+
45
+ The CLI has correlated keystrokes → frames for you (via the recording `StartTime`): the `typed` field on each keyframe and the `typed_bursts` list *are* the timeline anchors. Just narrate them — "at ~0:14 typed `powershell`, frame at 16s shows PowerShell 7 open". If RDP keystroke capture was off (bursts empty), fall back to reading every screen-change frame and inferring actions from the pictures.
46
+
47
+ ### 4. Write the summary
48
+
49
+ Produce:
50
+ - **Who** — the initiating user by name (from `user`/`user_email`), the account@system, and the time window. Note if the initiator is an API/service credential (e.g. `cli-<name>`) vs an interactive login — that distinction matters for audit.
51
+ - **Timeline table** — approximate time → event (app launched, window opened, command run, dialog shown, service changed, logoff).
52
+ - **Highlights an auditor cares about** — EPM/Defendpoint elevation or justification prompts, privileged tools (`dsa.msc`, `services.msc`, PowerShell), AD/account/OU changes, service start/stop, file transfers.
53
+ - **Security note** — explicitly state whether any credentials, passwords, tokens, or secret values were visible in the frames read.
54
+ - **Coverage & cost** — say how many of the listed keyframes you actually read (ideally all of them) so the reader knows the summary isn't under-sampled, and report the estimate from `analysis.json`.
55
+
56
+ ## Efficiency notes
57
+
58
+ - Two selection signals combine: PNG-size delta (`screen-change`, dependency-free) and keystroke anchoring (`after-typing`, when RDP keystroke capture is on). Anchoring is what makes the read *targeted* — it guarantees the frame that shows each typed action is included, even if its size barely moved.
59
+ - **Blind spot — mouse-driven, low-pixel-change actions.** A subtle action done with the mouse (a service going Running→Stopped is a few pixels of text, no keystroke) can slip past *both* signals: no `after-typing` anchor, and too small a PNG delta to trip `screen-change`. This is the classic "we missed the service restart." Mitigations: (a) **read all listed keyframes** — the restart's modal progress dialog usually *is* a keyframe, you just have to open it; (b) if you suspect a missed event in a quiet stretch, re-run `analyze --threshold 1.5` (or `1.0`) to surface smaller deltas; (c) for forensic certainty, `bt pws sessions replay <id> --all-frames --no-dedup` captures every recorded change. Under-sampling is usually the agent skipping listed keyframes, not the CLI mis-selecting them.
60
+ - If a session looks under-sampled (transitions missed), re-run `analyze --threshold 1.5` for more keyframes; over-sampled → raise it.
61
+ - Frame token cost ≈ `(width × height) / 750` per image (1024×768 ≈ ~1,050 tokens). The CLI's estimate already accounts for this.
62
+ - Pricing (per 1M tokens, for the estimate): Opus 4.8 $5/$25, Sonnet 4.6 $3/$15, Haiku 4.5 $1/$5. Use the `claude-api` skill for current numbers.
63
+
64
+ ## Scaling up (optional, not built)
65
+
66
+ For summarizing **many** sessions unattended, this interactive recipe doesn't scale — that would be a `bt pws sessions summarize` command calling the Claude API (vision) with the same keyframe/transcript inputs and the Batch API for volume. Mention it if the user wants automation; today, analysis is interactive.
67
+
68
+ ## Related
69
+
70
+ - `bt pws sessions replay <id>` — produce the frames (defaults to even coverage + dedup).
71
+ - `bt pws sessions transcript <id>` — the keystroke transcript (SSH gold; RDP sparse).
72
+ - `/pws` skill — Password Safe command reference.
@@ -5,7 +5,7 @@ import json
5
5
  import shutil
6
6
  import subprocess
7
7
  import time
8
- from datetime import datetime, timezone
8
+ from datetime import date, datetime, timezone
9
9
  from pathlib import Path
10
10
 
11
11
  import httpx
@@ -35,6 +35,41 @@ SESSION_STATUS = {
35
35
  }
36
36
 
37
37
 
38
+ def _norm_date(value: str) -> str:
39
+ """Validate a YYYY-MM-DD (or ISO) date arg and return its date portion."""
40
+ try:
41
+ datetime.strptime(value.strip()[:10], "%Y-%m-%d")
42
+ except (ValueError, AttributeError):
43
+ print_error(f"Invalid date '{value}'. Use YYYY-MM-DD.")
44
+ raise typer.Exit(2)
45
+ return value.strip()[:10]
46
+
47
+
48
+ def _resolve_date_window(today: bool, on: str, since: str, until: str) -> tuple:
49
+ """Collapse the date flags into an inclusive (lo, hi) date window (YYYY-MM-DD
50
+ strings, either may be None). --today/--on pin both ends to one day."""
51
+ if today:
52
+ d = date.today().isoformat()
53
+ return d, d
54
+ if on:
55
+ d = _norm_date(on)
56
+ return d, d
57
+ return (_norm_date(since) if since else None, _norm_date(until) if until else None)
58
+
59
+
60
+ def _started_in_window(s: dict, lo: str, hi: str) -> bool:
61
+ """True if the session's StartTime date falls within [lo, hi] (ISO date
62
+ strings sort lexicographically, so string compare on the date is correct)."""
63
+ d = (s.get("StartTime") or "")[:10]
64
+ if not d:
65
+ return False
66
+ if lo and d < lo:
67
+ return False
68
+ if hi and d > hi:
69
+ return False
70
+ return True
71
+
72
+
38
73
  def is_active(s: dict) -> bool:
39
74
  """A session is live (suspend/terminate-able) when Status == 1.
40
75
 
@@ -99,6 +134,10 @@ def list_sessions(
99
134
  system: int = typer.Option(None, "--system", help="Filter by managed system ID"),
100
135
  active: bool = typer.Option(False, "--active", help="Show only active (started, not ended) sessions"),
101
136
  status: str = typer.Option(None, "--status", "-s", help="Filter by raw numeric status code"),
137
+ today: bool = typer.Option(False, "--today", help="Only sessions that started today"),
138
+ on: str = typer.Option(None, "--on", help="Only sessions that started on this date (YYYY-MM-DD)"),
139
+ since: str = typer.Option(None, "--since", help="Only sessions on/after this date (YYYY-MM-DD)"),
140
+ until: str = typer.Option(None, "--until", help="Only sessions on/before this date (YYYY-MM-DD)"),
102
141
  limit: int = typer.Option(25, "--limit", "-l", help="Max rows to show (most recent first)"),
103
142
  fetch_all: bool = typer.Option(False, "--all", help="Show all sessions (the endpoint returns full history)"),
104
143
  output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
@@ -110,11 +149,17 @@ def list_sessions(
110
149
  suspend/terminate), or --all to see everything. Output is newest-first and
111
150
  capped at --limit by default.
112
151
 
152
+ Date filters (--today/--on/--since/--until) match on StartTime's date and are
153
+ applied client-side (the endpoint returns the full history every call).
154
+
113
155
  Examples:
114
156
  bt pws sessions list --active
157
+ bt pws sessions list --today
158
+ bt pws sessions list --since 2026-06-01 --until 2026-06-30
115
159
  bt pws sessions list --system 13
116
160
  bt pws sessions list --all -o json
117
161
  """
162
+ lo, hi = _resolve_date_window(today, on, since, until)
118
163
  try:
119
164
  with get_client() as client:
120
165
  client.authenticate()
@@ -122,6 +167,8 @@ def list_sessions(
122
167
 
123
168
  if active:
124
169
  sessions = [s for s in sessions if is_active(s)]
170
+ if lo or hi:
171
+ sessions = [s for s in sessions if _started_in_window(s, lo, hi)]
125
172
 
126
173
  total = len(sessions)
127
174
  shown = sessions if fetch_all else sessions[:limit]
@@ -715,6 +762,17 @@ def _build_keyframes(
715
762
  return keyframes
716
763
 
717
764
 
765
+ def _user_label(user: dict) -> str:
766
+ """Human label for a /Users record: 'First Last (username)' or best available."""
767
+ if not user:
768
+ return ""
769
+ full = " ".join(p for p in (user.get("FirstName"), user.get("LastName")) if p).strip()
770
+ handle = user.get("UserName") or user.get("EmailAddress") or ""
771
+ if full and handle:
772
+ return f"{full} ({handle})"
773
+ return full or handle or (f"UserID {user.get('UserID')}" if user.get("UserID") else "")
774
+
775
+
718
776
  @app.command("analyze")
719
777
  def session_analyze(
720
778
  session_id: int = typer.Argument(None, help="Session ID (defaults to reading ./session-<id>-replay)"),
@@ -758,18 +816,27 @@ def session_analyze(
758
816
  frames = idx.get("frames", [])
759
817
  sid = session_id or idx.get("session_id")
760
818
 
761
- # Pull keystrokes + session meta (best-effort; folder may be analyzed offline)
819
+ # Pull keystrokes + session meta + the initiating user (best-effort; folder
820
+ # may be analyzed offline with no API access).
762
821
  keystrokes: list[dict] = []
763
822
  meta: dict = {}
823
+ user: dict = {}
764
824
  if sid:
765
825
  try:
766
826
  with get_client() as client:
767
827
  client.authenticate()
768
828
  meta = client.get_session(sid)
769
829
  keystrokes = client.get_keystrokes(sid)
830
+ uid = meta.get("UserID")
831
+ if uid:
832
+ try:
833
+ user = client.get_user(uid)
834
+ except Exception:
835
+ pass # name resolution is a nicety; don't fail the analysis
770
836
  except Exception as e:
771
837
  if output != "json":
772
838
  print_warning(f"Could not pull keystrokes for session {sid} ({e}); continuing with frames only.")
839
+ user_label = _user_label(user)
773
840
 
774
841
  decoded_ks = [
775
842
  {"time": _fmt_marker(k.get("TimeMarker")), "type": _keystroke_type(k.get("Type")), "data": k.get("Data", "")}
@@ -807,6 +874,12 @@ def session_analyze(
807
874
  manifest = {
808
875
  "session_id": sid,
809
876
  "protocol": protocol,
877
+ "user_id": meta.get("UserID"),
878
+ "user": user_label,
879
+ "user_email": user.get("EmailAddress") if user else None,
880
+ "account": meta.get("ManagedAccountName"),
881
+ "system": meta.get("AssetName"),
882
+ "start_time": meta.get("StartTime"),
810
883
  "frame_dims": [w, h],
811
884
  "frame_count": len(frames),
812
885
  "keyframe_count": len(keyframes),
@@ -826,6 +899,9 @@ def session_analyze(
826
899
  acct = meta.get("ManagedAccountName", "?")
827
900
  sysn = meta.get("AssetName", "?")
828
901
  console.print(f"[bold]Session {sid}[/bold] {protocol} {acct}@{sysn}")
902
+ if user_label:
903
+ console.print(f" [green]Initiated by[/green] {rich_escape(user_label)}"
904
+ + (f" [dim]· {meta.get('StartTime')}[/dim]" if meta.get("StartTime") else ""))
829
905
  console.print(f" Frames: {len(frames)} ({w}x{h}) Keystroke events: {len(decoded_ks)}")
830
906
  if protocol.upper() == "SSH":
831
907
  console.print(" [green]SSH → analyze the transcript[/green] (cheap, full input+output):")
@@ -1,64 +0,0 @@
1
- ---
2
- name: session-analysis
3
- description: Analyze a dumped Password Safe session (replay frames + keystrokes) into a timeline summary with a cost estimate. Use when the user points at a `session-<id>-replay` folder or asks to summarize/analyze a recorded RDP or SSH session.
4
- ---
5
-
6
- # Session Analysis
7
-
8
- Turn a bt-cli session dump into a **timeline summary of what happened**, efficiently, with a **cost estimate**. Works for RDP (screen recording → frames) and SSH (keystroke transcript). This is interactive analysis — *you* (the model) read the frames/transcript; there is no LLM call inside the CLI.
9
-
10
- ## The one rule: never read every frame
11
-
12
- An RDP session sampled at ~1 frame per 2s is dozens of near-identical frames. Reading them all wastes tokens. Let the CLI pick the frames worth reading — the ones where the screen changed **and** the ones that render what the user typed — and read only those, guided by the per-frame `typed`/`reason` tags.
13
-
14
- ## Recipe
15
-
16
- ### 1. Prep with the CLI (does the heavy lifting)
17
-
18
- ```bash
19
- # If the user has a replay folder already:
20
- bt pws sessions analyze --dir <folder>
21
- # If they have the session id (reads ./session-<id>-replay):
22
- bt pws sessions analyze <id> --model opus # or --model sonnet for a cheaper estimate
23
- ```
24
-
25
- `analyze` writes `analysis.json` into the folder and prints:
26
- - **RDP:** the **keyframes** to read (a handful, not all). Each keyframe is tagged with a **reason** — `screen-change` (the picture moved) and/or `after-typing` (it renders the result of something the user typed). `after-typing` frames also carry the exact `typed` text.
27
- - **SSH:** a pointer to the transcript (analyze it instead of frames).
28
- - **`typed_bursts`** — typed input grouped into phrases with recording-relative offsets (`@14.9s "powershell"`), already anchored to the keyframes above.
29
- - A **cost estimate**: `focused` (keyframes only) vs `all frames`.
30
-
31
- If the user only has a folder (no live API access), `analyze --dir` still works — keystrokes need session metadata (`StartTime`) to anchor, so offline it falls back to `screen-change` keyframes only, with a note. Keyframes + cost are still computed.
32
-
33
- ### 2. Read the right inputs — targeted, in `analysis.json` order
34
-
35
- - **RDP:** `Read` only the keyframe PNGs the CLI listed. **Prioritize the `after-typing` frames** — they're where a user action produced a visible result (a launched app, a command's output, an EPM prompt). For each, the `typed` field tells you what to look for *before* you open the image, so you read with intent instead of scanning. The `screen-change`-only frames fill in transitions the keystrokes don't cover (mouse-driven actions: menus, service restarts, window closes). If a keyframe implies something happened between it and the next, that's fine — the summary is event-level, not frame-level.
36
- - **SSH:** run `bt pws sessions transcript <id>` and read that — it's the full input+output, far richer and cheaper than frames. Frames are rarely worth reading for SSH.
37
-
38
- ### 3. Anchoring is already done
39
-
40
- The CLI has correlated keystrokes → frames for you (via the recording `StartTime`): the `typed` field on each keyframe and the `typed_bursts` list *are* the timeline anchors. Just narrate them — "at ~0:14 typed `powershell`, frame at 16s shows PowerShell 7 open". If RDP keystroke capture was off (bursts empty), fall back to reading the screen-change frames and inferring actions from the pictures alone.
41
-
42
- ### 4. Write the summary
43
-
44
- Produce:
45
- - **Timeline table** — approximate time → event (app launched, window opened, command run, dialog shown, service changed, logoff).
46
- - **Highlights an auditor cares about** — EPM/Defendpoint elevation or justification prompts, privileged tools (`dsa.msc`, `services.msc`, PowerShell), AD/account/OU changes, service start/stop, file transfers.
47
- - **Security note** — explicitly state whether any credentials, passwords, tokens, or secret values were visible in the frames read.
48
- - **Cost** — report the estimate from `analysis.json` (and, if you want, the actual: images read × ~`(w*h)/750` tokens + output).
49
-
50
- ## Efficiency notes
51
-
52
- - Two selection signals combine: PNG-size delta (`screen-change`, dependency-free) and keystroke anchoring (`after-typing`, when RDP keystroke capture is on). Anchoring is what makes the read *targeted* — it guarantees the frame that shows each typed action is included, even if its size barely moved. If a session looks under-sampled (transitions missed), re-run `analyze --threshold 1.5` for more keyframes; over-sampled → raise it.
53
- - Frame token cost ≈ `(width × height) / 750` per image (1024×768 ≈ ~1,050 tokens). The CLI's estimate already accounts for this.
54
- - Pricing (per 1M tokens, for the estimate): Opus 4.8 $5/$25, Sonnet 4.6 $3/$15, Haiku 4.5 $1/$5. Use the `claude-api` skill for current numbers.
55
-
56
- ## Scaling up (optional, not built)
57
-
58
- For summarizing **many** sessions unattended, this interactive recipe doesn't scale — that would be a `bt pws sessions summarize` command calling the Claude API (vision) with the same keyframe/transcript inputs and the Batch API for volume. Mention it if the user wants automation; today, analysis is interactive.
59
-
60
- ## Related
61
-
62
- - `bt pws sessions replay <id>` — produce the frames (defaults to even coverage + dedup).
63
- - `bt pws sessions transcript <id>` — the keystroke transcript (SSH gold; RDP sparse).
64
- - `/pws` skill — Password Safe command reference.
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
File without changes