praetorian-cli 2.6.0__tar.gz → 2.6.2__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 (225) hide show
  1. {praetorian_cli-2.6.0/praetorian_cli.egg-info → praetorian_cli-2.6.2}/PKG-INFO +1 -1
  2. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/delete.py +4 -2
  3. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/get.py +5 -4
  4. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/list.py +8 -6
  5. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/chariot.py +3 -1
  6. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/account_discovery.py +91 -50
  7. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/aegis.py +59 -55
  8. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/files.py +29 -14
  9. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/search.py +10 -6
  10. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/globals.py +1 -0
  11. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_endpoint_live.py +98 -1
  12. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/menu.py +58 -27
  13. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2/praetorian_cli.egg-info}/PKG-INFO +1 -1
  14. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/setup.cfg +1 -1
  15. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/LICENSE +0 -0
  16. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/MANIFEST.in +0 -0
  17. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/README.md +0 -0
  18. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/__init__.py +0 -0
  19. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/__init__.py +0 -0
  20. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/access.py +0 -0
  21. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/add.py +0 -0
  22. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/aegis.py +0 -0
  23. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/agent.py +0 -0
  24. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/chariot.py +0 -0
  25. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/cli_decorators.py +0 -0
  26. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/configure.py +0 -0
  27. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/critfinder.py +0 -0
  28. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/engagement.py +0 -0
  29. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/enrich.py +0 -0
  30. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/export.py +0 -0
  31. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/find.py +0 -0
  32. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/hunt.py +0 -0
  33. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/imports.py +0 -0
  34. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/link.py +0 -0
  35. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/query.py +0 -0
  36. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/report.py +0 -0
  37. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/run.py +0 -0
  38. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/script.py +0 -0
  39. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/search.py +0 -0
  40. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/ssh_utils.py +0 -0
  41. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/test.py +0 -0
  42. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/unlink.py +0 -0
  43. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/update.py +0 -0
  44. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/utils.py +0 -0
  45. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/vm.py +0 -0
  46. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/vm_proxy.py +0 -0
  47. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/main.py +0 -0
  48. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/runners/__init__.py +0 -0
  49. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/runners/local.py +0 -0
  50. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/scripts/__init__.py +0 -0
  51. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/scripts/commands/__init__.py +0 -0
  52. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
  53. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/scripts/utils.py +0 -0
  54. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/__init__.py +0 -0
  55. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/__init__.py +0 -0
  56. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/accounts.py +0 -0
  57. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/ad.py +0 -0
  58. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/agents.py +0 -0
  59. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/assets.py +0 -0
  60. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/attributes.py +0 -0
  61. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/capabilities.py +0 -0
  62. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/configurations.py +0 -0
  63. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/conversations.py +0 -0
  64. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/credentials.py +0 -0
  65. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/definitions.py +0 -0
  66. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/endpoint_executions.py +0 -0
  67. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/engineer_vm.py +0 -0
  68. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/hunts.py +0 -0
  69. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/integrations.py +0 -0
  70. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/jobs.py +0 -0
  71. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/keys.py +0 -0
  72. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/preseeds.py +0 -0
  73. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/reports.py +0 -0
  74. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/risks.py +0 -0
  75. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/scanners.py +0 -0
  76. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/schedules.py +0 -0
  77. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/schema.py +0 -0
  78. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/seeds.py +0 -0
  79. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/settings.py +0 -0
  80. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/statistics.py +0 -0
  81. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/webhook.py +0 -0
  82. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/webpage.py +0 -0
  83. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/exceptions.py +0 -0
  84. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/guard.py +0 -0
  85. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/keychain.py +0 -0
  86. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/mcp_server.py +0 -0
  87. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/__init__.py +0 -0
  88. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/aegis.py +0 -0
  89. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/query.py +0 -0
  90. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/utils.py +0 -0
  91. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/vm.py +0 -0
  92. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/__init__.py +0 -0
  93. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/pytest.ini +0 -0
  94. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_access_aws.py +0 -0
  95. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_access_github.py +0 -0
  96. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_account.py +0 -0
  97. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_add_file_cli.py +0 -0
  98. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_management_users.py +0 -0
  99. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_network_policy.py +0 -0
  100. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_tunnel.py +0 -0
  101. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_v2_enrollment.py +0 -0
  102. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_agent.py +0 -0
  103. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_agent_interactions.py +0 -0
  104. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_asset.py +0 -0
  105. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_async_http.py +0 -0
  106. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_attribute.py +0 -0
  107. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_bulk.py +0 -0
  108. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
  109. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_cli_errors.py +0 -0
  110. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_configuration.py +0 -0
  111. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_conversation.py +0 -0
  112. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_conversation_approval_surfaces.py +0 -0
  113. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_conversation_interactions.py +0 -0
  114. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_credential_process.py +0 -0
  115. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_credentials.py +0 -0
  116. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_definition.py +0 -0
  117. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_approvals.py +0 -0
  118. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_execution_cli.py +0 -0
  119. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_executions.py +0 -0
  120. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_network_policy.py +0 -0
  121. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_status.py +0 -0
  122. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_engineer_vm.py +0 -0
  123. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_entity_resolution_cli.py +0 -0
  124. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_entity_resolver.py +0 -0
  125. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_export_cli.py +0 -0
  126. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_extend.py +0 -0
  127. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_file.py +0 -0
  128. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_file_verified_download.py +0 -0
  129. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_all_surface_cli.py +0 -0
  130. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_chat.py +0 -0
  131. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_chat_live.py +0 -0
  132. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_data.py +0 -0
  133. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_internal_cli.py +0 -0
  134. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_launch.py +0 -0
  135. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_memory_browser.py +0 -0
  136. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_open.py +0 -0
  137. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_overview.py +0 -0
  138. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_scope_selector.py +0 -0
  139. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_workflows.py +0 -0
  140. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunts_internal.py +0 -0
  141. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_job.py +0 -0
  142. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_key.py +0 -0
  143. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_keychain.py +0 -0
  144. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_keychain_cache.py +0 -0
  145. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_local_runner.py +0 -0
  146. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_mcp.py +0 -0
  147. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_my_by_raw_query.py +0 -0
  148. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_preseed.py +0 -0
  149. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_report.py +0 -0
  150. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_risk.py +0 -0
  151. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_run_cli.py +0 -0
  152. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_sdk_exceptions.py +0 -0
  153. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_search.py +0 -0
  154. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_seed.py +0 -0
  155. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_setting.py +0 -0
  156. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_terminal.py +0 -0
  157. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_test_handler.py +0 -0
  158. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_test_target.py +0 -0
  159. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_update_check.py +0 -0
  160. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_webhook.py +0 -0
  161. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_webpage.py +0 -0
  162. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_z_cli.py +0 -0
  163. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/ui_mocks.py +0 -0
  164. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/utils.py +0 -0
  165. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/__init__.py +0 -0
  166. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/__init__.py +0 -0
  167. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/account_selector.py +0 -0
  168. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
  169. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/cp.py +0 -0
  170. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/enrollment.py +0 -0
  171. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/help.py +0 -0
  172. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/hunt.py +0 -0
  173. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/info.py +0 -0
  174. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/job.py +0 -0
  175. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/job_helpers.py +0 -0
  176. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/job_v2.py +0 -0
  177. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/list.py +0 -0
  178. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/network_policy.py +0 -0
  179. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/proxy.py +0 -0
  180. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/schedule.py +0 -0
  181. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/schedule_helpers.py +0 -0
  182. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/set.py +0 -0
  183. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
  184. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/tunnel.py +0 -0
  185. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/user.py +0 -0
  186. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/constants.py +0 -0
  187. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/theme.py +0 -0
  188. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/utils.py +0 -0
  189. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/async_http.py +0 -0
  190. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/__init__.py +0 -0
  191. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/__init__.py +0 -0
  192. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/accounts.py +0 -0
  193. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/context.py +0 -0
  194. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/data.py +0 -0
  195. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/marcus.py +0 -0
  196. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/reporting.py +0 -0
  197. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/search.py +0 -0
  198. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/tools.py +0 -0
  199. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/console.py +0 -0
  200. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/context.py +0 -0
  201. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/renderer.py +0 -0
  202. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/conversation/__init__.py +0 -0
  203. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/conversation/approvals.py +0 -0
  204. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/conversation/endpoint_status.py +0 -0
  205. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/conversation/textual_chat.py +0 -0
  206. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/entity_resolver.py +0 -0
  207. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/entity_selector.py +0 -0
  208. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_chat.py +0 -0
  209. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_chat_live.py +0 -0
  210. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_data.py +0 -0
  211. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_defaults.py +0 -0
  212. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_format.py +0 -0
  213. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_launch.py +0 -0
  214. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_memory.py +0 -0
  215. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_open.py +0 -0
  216. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_overview.py +0 -0
  217. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_workflows.py +0 -0
  218. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/terminal.py +0 -0
  219. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/textual_refresh.py +0 -0
  220. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/SOURCES.txt +0 -0
  221. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/dependency_links.txt +0 -0
  222. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/entry_points.txt +0 -0
  223. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/requires.txt +0 -0
  224. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/top_level.txt +0 -0
  225. {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praetorian-cli
3
- Version: 2.6.0
3
+ Version: 2.6.2
4
4
  Summary: For interacting with the Guard API
5
5
  Home-page: https://github.com/praetorian-inc/praetorian-cli
6
6
  Author: Praetorian
@@ -103,8 +103,9 @@ def seed(chariot, key):
103
103
 
104
104
  @delete.command()
105
105
  @click.argument('filepath', required=True)
106
+ @click.option('--praetorian', is_flag=True, default=False, help='Delete from the praetorian partition')
106
107
  @cli_handler
107
- def file(chariot, filepath):
108
+ def file(chariot, filepath, praetorian):
108
109
  """ Delete a file
109
110
 
110
111
  \b
@@ -114,8 +115,9 @@ def file(chariot, filepath):
114
115
  \b
115
116
  Example usage:
116
117
  - guard delete file "home/report-dec-2024.pdf"
118
+ - guard delete file "home/internal/repo.zip" --praetorian
117
119
  """
118
- chariot.files.delete(filepath)
120
+ chariot.files.delete(filepath, praetorian=praetorian)
119
121
 
120
122
 
121
123
  @delete.command()
@@ -131,7 +131,8 @@ def job(chariot, key):
131
131
  @cli_handler
132
132
  @click.argument('name')
133
133
  @click.option('-p', '--path', default=os.getcwd(), help='Download path. Default: save to current directory')
134
- def file(chariot, name, path):
134
+ @click.option('--praetorian', is_flag=True, default=False, help='Download from the praetorian partition')
135
+ def file(chariot, name, path, praetorian):
135
136
  """ Download a file using key or name
136
137
 
137
138
  \b
@@ -143,11 +144,11 @@ def file(chariot, name, path):
143
144
  - guard get file "#file#proofs/example.azurewebsites.net/jira-unauthenticated-user-picker"
144
145
  - guard get file "proofs/example.azurewebsites.net/jira-unauthenticated-user-picker"
145
146
  - guard get file "proofs/example.azurewebsites.net/jira-unauthenticated-user-picker" --path ~/Downloads
147
+ - guard get file "home/internal/repo.zip" --praetorian
146
148
  """
147
149
  if name.startswith('#'):
148
- downloaded_filepath = chariot.files.save(name.split('#')[-1], path)
149
- else:
150
- downloaded_filepath = chariot.files.save(name, path)
150
+ name = name.split('#')[-1]
151
+ downloaded_filepath = chariot.files.save(name, path, praetorian=praetorian)
151
152
  click.echo(f'Saved file at {downloaded_filepath}')
152
153
 
153
154
 
@@ -69,8 +69,9 @@ def accounts(chariot, filter, details, offset, page):
69
69
 
70
70
 
71
71
  @list.command()
72
- @list_params('Aegis ID', has_filter=False)
73
- def aegis(chariot, details, offset, page):
72
+ @cli_handler
73
+ @click.option('-d', '--details', is_flag=True, help='Show detailed agent information')
74
+ def aegis(chariot, details):
74
75
  """ List Aegis
75
76
 
76
77
  Retrieve and display a list of Aegis instances.
@@ -79,9 +80,8 @@ def aegis(chariot, details, offset, page):
79
80
  Example usages:
80
81
  - guard list aegis
81
82
  - guard list aegis --details
82
- - guard list aegis --page all
83
83
  """
84
- render_list_results(chariot.aegis.list(offset, pagination_size(page)), details)
84
+ click.echo(chariot.aegis.format_agents_list(details=details))
85
85
 
86
86
 
87
87
  @list.command()
@@ -141,7 +141,8 @@ def jobs(chariot, capability, status, target, summary, filter, details, offset,
141
141
 
142
142
  @list.command()
143
143
  @list_params('file path')
144
- def files(chariot, filter, details, offset, page):
144
+ @click.option('--praetorian', is_flag=True, default=False, help='List the praetorian partition')
145
+ def files(chariot, praetorian, filter, details, offset, page):
145
146
  """ List files
146
147
 
147
148
  Retrieve and display a list of files in the Guard file system.
@@ -152,8 +153,9 @@ def files(chariot, filter, details, offset, page):
152
153
  - guard list files --filter "home/reports/cloud-assessment-2024-"
153
154
  - guard list files --details
154
155
  - guard list files --page all
156
+ - guard list files --praetorian --filter "home/internal/"
155
157
  """
156
- render_list_results(chariot.files.list(filter, offset, pagination_size(page)), details)
158
+ render_list_results(chariot.files.list(filter, offset, pagination_size(page), praetorian=praetorian), details)
157
159
 
158
160
 
159
161
  @list.command()
@@ -250,7 +250,7 @@ class Chariot:
250
250
  process_failure(resp)
251
251
  return resp
252
252
 
253
- def download(self, name: str, global_=False) -> bytes:
253
+ def download(self, name: str, global_=False, praetorian=False) -> bytes:
254
254
  params = dict(name=name)
255
255
  # Encrypted files have _encrypted/ prefix in the path. Encrypted files do not use presigned URLs.
256
256
  # Instead, they use the /encrypted-file endpoint that directly gets and puts content.
@@ -263,6 +263,8 @@ class Chariot:
263
263
  # Regular files, use presigned URLs
264
264
  if global_:
265
265
  params |= GLOBAL_FLAG
266
+ if praetorian:
267
+ params['praetorian'] = 'true'
266
268
 
267
269
  resp = self.chariot_request('GET', self.url('/file'), params=params)
268
270
  process_failure(resp)
@@ -97,7 +97,7 @@ def discover_aegis_accounts(sdk, on_progress=None) -> List[dict]:
97
97
  metadata,
98
98
  )
99
99
  except Exception as e:
100
- logger.debug('Agent check failed for %s: %s', account_email, e)
100
+ logger.warning('Agent inventory failed for %s: %s', account_email, e)
101
101
  return _PROBE_FAILED
102
102
  finally:
103
103
  with lock:
@@ -184,18 +184,29 @@ def _fetch_account_agents(base_url: str, headers: dict) -> Optional[List[dict]]:
184
184
  timeout=30,
185
185
  )
186
186
  if resp.status_code != 200:
187
- logger.debug('Agent fetch returned status %d', resp.status_code)
188
- return None
187
+ raise RuntimeError(f'HTTP {resp.status_code} from /agent/enhanced: {resp.text}')
189
188
  return resp.json() or []
190
189
 
191
190
 
192
- def _fetch_account_endpoints(base_url: str, headers: dict) -> Optional[List[dict]]:
193
- identity_rows = _fetch_account_endpoint_identities(base_url, headers)
194
- live_rows = _fetch_account_live_endpoints(base_url, headers)
191
+ def _fetch_account_endpoints(base_url: str, headers: dict, on_warning=None) -> Optional[List[dict]]:
192
+ identity_rows = _fetch_account_source(
193
+ lambda: _fetch_account_endpoint_identities(base_url, headers, on_warning),
194
+ headers, 'v2 endpoint identities', on_warning,
195
+ )
196
+ live_rows = _fetch_account_source(
197
+ lambda: _fetch_account_live_endpoints(base_url, headers),
198
+ headers, 'v2 live endpoints', on_warning,
199
+ )
195
200
  if identity_rows is None and live_rows is None:
196
201
  return None
197
- tunnel_rows = _fetch_account_tunnel_states(base_url, headers)
198
- status_rows = _fetch_account_aegis_statuses(base_url, headers)
202
+ tunnel_rows = _fetch_account_source(
203
+ lambda: _fetch_account_tunnel_states(base_url, headers),
204
+ headers, 'v2 tunnel states', on_warning,
205
+ )
206
+ status_rows = _fetch_account_source(
207
+ lambda: _fetch_account_aegis_statuses(base_url, headers),
208
+ headers, 'v2 endpoint statuses', on_warning,
209
+ )
199
210
  return merge_aegis_endpoint_rows(
200
211
  identity_rows or [],
201
212
  live_rows or [],
@@ -204,8 +215,11 @@ def _fetch_account_endpoints(base_url: str, headers: dict) -> Optional[List[dict
204
215
  )
205
216
 
206
217
 
207
- def _fetch_account_endpoint_identities(base_url: str, headers: dict) -> Optional[List[dict]]:
208
- inventory_rows = _fetch_account_endpoint_inventory(base_url, headers)
218
+ def _fetch_account_endpoint_identities(base_url: str, headers: dict, on_warning=None) -> Optional[List[dict]]:
219
+ inventory_rows = _fetch_account_source(
220
+ lambda: _fetch_account_endpoint_inventory(base_url, headers),
221
+ headers, 'v2 durable endpoints', on_warning,
222
+ )
209
223
  if inventory_rows is not None:
210
224
  return inventory_rows
211
225
 
@@ -215,8 +229,7 @@ def _fetch_account_endpoint_identities(base_url: str, headers: dict) -> Optional
215
229
  timeout=30,
216
230
  )
217
231
  if resp.status_code != 200:
218
- logger.debug('Endpoint identity fetch returned status %d', resp.status_code)
219
- return None
232
+ raise RuntimeError(f'HTTP {resp.status_code} from /endpoint: {resp.text}')
220
233
  return [
221
234
  normalize_active_endpoint_summary(endpoint)
222
235
  for endpoint in _flatten_response(resp.json())
@@ -237,8 +250,7 @@ def _fetch_account_endpoint_inventory(base_url: str, headers: dict) -> Optional[
237
250
  timeout=30,
238
251
  )
239
252
  if resp.status_code != 200:
240
- logger.debug('Endpoint inventory fetch returned status %d', resp.status_code)
241
- return None
253
+ raise RuntimeError(f'HTTP {resp.status_code} from /endpoint/list: {resp.text}')
242
254
 
243
255
  body = resp.json()
244
256
  rows = normalize_to_list(body, ['endpoints', 'data', 'items'])
@@ -289,8 +301,7 @@ def _fetch_account_my_rows(base_url: str, headers: dict, key: str) -> Optional[L
289
301
  timeout=30,
290
302
  )
291
303
  if resp.status_code != 200:
292
- logger.debug('%s fetch returned status %d', key, resp.status_code)
293
- return None
304
+ raise RuntimeError(f'HTTP {resp.status_code} from /my ({key}): {resp.text}')
294
305
 
295
306
  body = resp.json()
296
307
  rows.extend(_flatten_response(body))
@@ -305,12 +316,36 @@ def _fetch_account_my_rows(base_url: str, headers: dict, key: str) -> Optional[L
305
316
  params = {'key': key, 'offset': serialized_offset}
306
317
 
307
318
 
308
- def _fetch_account_records(base_url: str, headers: dict) -> Optional[tuple[List[dict], List[dict]]]:
309
- agents_data = _fetch_account_agents(base_url, headers)
310
- endpoints_data = _fetch_account_endpoints(base_url, headers)
311
- if agents_data is None or endpoints_data is None:
312
- return None
313
- return agents_data, endpoints_data
319
+ def _fetch_account_source(fetch, headers: dict, source: str, on_warning=None):
320
+ try:
321
+ rows = fetch()
322
+ if rows is not None:
323
+ return rows
324
+ error = 'inventory unavailable or pagination incomplete'
325
+ except Exception as exc:
326
+ error = str(exc)
327
+ warn = on_warning if on_warning is not None else logger.warning
328
+ warn(f"Account {headers.get('account', '(current)')}: {source} failed: {error}. "
329
+ "Agent inventory may be incomplete.")
330
+ return None
331
+
332
+
333
+ def _fetch_account_records(
334
+ base_url: str, headers: dict, on_warning=None,
335
+ ) -> Optional[tuple[List[dict], List[dict]]]:
336
+ """Keep available rows; return None if empty results cannot be confirmed."""
337
+ agents_data = _fetch_account_source(
338
+ lambda: _fetch_account_agents(base_url, headers),
339
+ headers, 'legacy agents', on_warning,
340
+ )
341
+ endpoints_data = _fetch_account_source(
342
+ lambda: _fetch_account_endpoints(base_url, headers, on_warning),
343
+ headers, 'v2 endpoints', on_warning,
344
+ )
345
+ if not agents_data and not endpoints_data:
346
+ if agents_data is None or endpoints_data is None:
347
+ return None
348
+ return agents_data or [], endpoints_data or []
314
349
 
315
350
 
316
351
  def _aegis_endpoint_rows(rows: List[dict]) -> List[dict]:
@@ -444,18 +479,22 @@ def _friendly_name_from_email(email: str) -> str:
444
479
  return name.title() if name else email
445
480
 
446
481
 
447
- def load_agents_for_accounts(sdk, selected_accounts: List[dict], on_progress=None) -> List[tuple]:
482
+ def load_agents_for_accounts(
483
+ sdk, selected_accounts: List[dict], on_progress=None, on_warning=None,
484
+ ) -> tuple[List[tuple], List[str]]:
448
485
  """Load agents from multiple accounts concurrently with retry.
449
486
 
450
487
  Args:
451
488
  sdk: Chariot SDK instance
452
489
  selected_accounts: List of account info dicts from discover_aegis_accounts
453
490
  on_progress: Optional callback(checked: int, total: int, display_name: str)
491
+ on_warning: Optional callback(message: str) for incomplete or failed loads.
492
+ Defaults to logger.warning.
454
493
 
455
494
  Returns:
456
495
  Tuple of (agent_tuples, failed_account_names) where agent_tuples is
457
496
  a list of (Agent, account_info) tuples and failed_account_names is
458
- a list of display names for accounts that failed after retry.
497
+ a list of display names for failed or incomplete empty accounts after retry.
459
498
  """
460
499
  base_url = sdk.keychain.base_url()
461
500
  auth_headers = dict(sdk.keychain.headers())
@@ -463,60 +502,62 @@ def load_agents_for_accounts(sdk, selected_accounts: List[dict], on_progress=Non
463
502
  checked = 0
464
503
  failed_accounts = []
465
504
  lock = threading.Lock()
505
+ warn = on_warning if on_warning is not None else logger.warning
466
506
 
467
- def _load_one(acct, attempt=1, track_progress=True):
507
+ def _load_one(acct, attempt):
468
508
  nonlocal checked
469
509
  email = acct['account_email']
510
+ warnings = []
470
511
  try:
471
512
  headers = dict(auth_headers)
472
513
  headers['account'] = email
473
- records = _fetch_account_records(base_url, headers)
514
+ records = _fetch_account_records(base_url, headers, on_warning=warnings.append)
474
515
  if records is None:
475
516
  logger.debug('Agent load for %s failed (attempt %d)', email, attempt)
476
- return None # Signal failure (distinct from empty account)
517
+ return None, warnings
477
518
  agents_data, endpoints_data = records
478
519
  agents = [(Agent.from_dict(d), acct) for d in agents_data]
479
520
  agents.extend(
480
521
  (Agent.from_endpoint_dict(d), acct) for d in endpoints_data
481
522
  )
482
- return agents
523
+ return agents, warnings
483
524
  except Exception as e:
484
- logger.debug('Agent load failed for %s: %s (attempt %d)', email, e, attempt)
485
- return None
525
+ warnings.append(f'Account {email}: agent inventory failed (attempt {attempt}): {e}')
526
+ return None, warnings
486
527
  finally:
487
- if track_progress:
528
+ if attempt == 1:
488
529
  with lock:
489
530
  checked += 1
490
531
  if on_progress:
491
532
  on_progress(checked, total, acct.get('display_name', email))
492
533
 
493
534
  results = []
494
- retry_accounts = []
495
- with ThreadPoolExecutor(max_workers=4) as executor:
496
- futures = {executor.submit(_load_one, acct): acct for acct in selected_accounts}
497
- for future in as_completed(futures):
498
- result = future.result()
499
- if result is None:
500
- retry_accounts.append(futures[future])
501
- else:
502
- results.extend(result)
503
-
504
- # Retry failed accounts once (don't double-count progress)
505
- if retry_accounts:
506
- logger.debug('Retrying %d failed account(s)', len(retry_accounts))
535
+ pending = selected_accounts
536
+ for attempt in (1, 2):
537
+ if attempt == 2:
538
+ logger.debug('Retrying %d failed account(s)', len(pending))
539
+ retry_accounts = []
507
540
  with ThreadPoolExecutor(max_workers=4) as executor:
508
- futures = {executor.submit(_load_one, acct, 2, False): acct for acct in retry_accounts}
541
+ futures = {executor.submit(_load_one, acct, attempt): acct for acct in pending}
509
542
  for future in as_completed(futures):
510
- result = future.result()
543
+ acct = futures[future]
544
+ result, warnings = future.result()
511
545
  if result is None:
512
- acct = futures[future]
513
- with lock:
514
- failed_accounts.append(acct.get('display_name', acct['account_email']))
546
+ if attempt == 1:
547
+ retry_accounts.append(acct)
548
+ # A successful retry must not leave a partial-load warning.
549
+ continue
550
+ failed_accounts.append(acct.get('display_name', acct['account_email']))
515
551
  else:
516
552
  results.extend(result)
553
+ for message in warnings:
554
+ warn(message)
555
+ if not retry_accounts:
556
+ break
557
+ pending = retry_accounts
517
558
 
518
559
  if failed_accounts:
519
- logger.warning('Failed to load agents for: %s', ', '.join(failed_accounts))
560
+ warn(f"Failed to load agents for: {', '.join(failed_accounts)}")
520
561
 
521
562
  # Sort deterministically by account name then hostname to avoid
522
563
  # non-deterministic ordering from concurrent thread completion.
@@ -1,5 +1,6 @@
1
1
  from typing import List, Optional
2
2
  from urllib.parse import quote
3
+ import logging
3
4
  import shlex
4
5
  import shutil
5
6
  import subprocess
@@ -187,56 +188,54 @@ class Aegis:
187
188
  self.api = api
188
189
 
189
190
 
190
- def list(self) -> tuple:
191
- """
192
- List all Aegis agents.
193
-
194
- Retrieves all Aegis agents from the account, returning them as Agent
195
- objects with detailed information including system specs, network
196
- interfaces, and tunnel connectivity status.
197
-
198
- :return: A tuple containing (list of Agent objects, None for compatibility)
199
- :rtype: tuple
191
+ def list(self, *, on_warning=None) -> tuple[List[Agent], None]:
192
+ """List legacy and v2 agents independently, returning (agents, None).
200
193
 
201
- **Example Usage:**
202
- >>> # List all Aegis agents
203
- >>> agents, _ = sdk.aegis.list()
204
-
205
- >>> # Check agent properties
206
- >>> for agent in agents:
207
- >>> print(f"Agent: {agent.hostname}")
208
- >>> print(f"OS: {agent.os}")
209
- >>> print(f"Has tunnel: {agent.has_tunnel}")
210
- >>> print(f"Online: {agent.is_online}")
211
-
212
- **Agent Object Properties:**
213
- - client_id: Unique identifier for v1 agents
214
- - endpoint_id: Unique identifier for v2 endpoints
215
- - version: Aegis protocol version (v1 or v2)
216
- - hostname: Agent hostname
217
- - os: Operating system (e.g., 'linux', 'windows')
218
- - network_interfaces: List of NetworkInterface objects
219
- - has_tunnel: Boolean indicating if Cloudflare tunnel is active
220
- - is_online: Boolean indicating if agent is currently online
194
+ Send partial failures to on_warning(message), or the logger if omitted.
195
+ Raise RuntimeError if both inventories fail. A successful empty response
196
+ still counts as a loaded inventory.
221
197
  """
222
- agents = self._list_legacy_agents()
223
- agents.extend(self._list_endpoint_agents())
198
+ agents = []
199
+ errors = []
200
+ loaded = False
201
+ try:
202
+ agents.extend(self._list_legacy_agents())
203
+ loaded = True
204
+ except Exception as exc:
205
+ errors.append(f'Legacy Aegis inventory: {exc}')
206
+ try:
207
+ endpoints, endpoint_errors = self._list_endpoint_agents()
208
+ agents.extend(endpoints)
209
+ errors.extend(endpoint_errors)
210
+ loaded = True
211
+ except Exception as exc:
212
+ errors.append(f'Aegis v2 inventory: {exc}')
213
+
214
+ if not loaded:
215
+ raise RuntimeError('Failed to load Aegis inventories: ' + '; '.join(errors))
216
+ if errors:
217
+ message = 'Incomplete Aegis inventory: ' + '; '.join(errors)
218
+ warn = on_warning if on_warning is not None else logging.getLogger(__name__).warning
219
+ warn(message)
224
220
  return agents, None
225
221
 
226
222
  def _list_legacy_agents(self) -> List[Agent]:
227
223
  agents_data = self.api.get('/agent/enhanced')
228
224
  return [Agent.from_dict(agent_data) for agent_data in agents_data]
229
225
 
230
- def _list_endpoint_agents(self) -> List[Agent]:
226
+ def _list_endpoint_agents(self) -> tuple[List[Agent], List[str]]:
227
+ errors = []
231
228
  try:
232
229
  identity_rows = self._list_endpoint_inventory_rows()
233
- except Exception:
230
+ except Exception as exc:
231
+ errors.append(f'Aegis v2 durable inventory: {exc}')
234
232
  try:
235
233
  identity_rows = self._list_active_endpoint_rows()
236
- except Exception:
237
- identity_rows = []
234
+ except Exception as exc:
235
+ errors.append(f'Aegis v2 active inventory: {exc}')
236
+ identity_rows = None
238
237
 
239
- live_rows = []
238
+ live_rows = None
240
239
  tunnel_rows = []
241
240
  status_rows = []
242
241
  if hasattr(self.api, 'search'):
@@ -246,8 +245,9 @@ class Aegis:
246
245
  endpoints_data,
247
246
  ["endpoints", "endpointInfos", "endpointinfos", "data", "items"],
248
247
  )
249
- except Exception:
250
- pass
248
+ except Exception as exc:
249
+ if identity_rows is None:
250
+ errors.append(f'Aegis v2 live inventory: {exc}')
251
251
  try:
252
252
  tunnel_rows, _ = self.api.search.by_key_prefix(
253
253
  '#endpointaegistunnelstate#'
@@ -261,16 +261,15 @@ class Aegis:
261
261
  except Exception:
262
262
  pass
263
263
 
264
- try:
265
- rows = merge_aegis_endpoint_rows(
266
- identity_rows,
267
- live_rows,
268
- tunnel_rows,
269
- status_rows,
270
- )
271
- return [Agent.from_endpoint_dict(endpoint) for endpoint in rows]
272
- except Exception:
273
- return []
264
+ if identity_rows is None and live_rows is None:
265
+ raise RuntimeError('; '.join(errors))
266
+ rows = merge_aegis_endpoint_rows(
267
+ identity_rows or [],
268
+ live_rows or [],
269
+ tunnel_rows,
270
+ status_rows,
271
+ )
272
+ return [Agent.from_endpoint_dict(endpoint) for endpoint in rows], errors
274
273
 
275
274
  def list_hunt_endpoints(self) -> List[Agent]:
276
275
  """List active tenant-owned Aegis v2 identities for Internal Hunts."""
@@ -892,10 +891,15 @@ class Aegis:
892
891
  >>> result = sdk.aegis.format_agents_list(details=True, filter_text="windows")
893
892
  >>> print(result)
894
893
  """
895
- agents_data, _ = self.list()
896
-
894
+ warnings = []
895
+ agents_data, _ = self.list(on_warning=warnings.append)
896
+ warning_text = ''.join(f'Warning: {message}\n' for message in warnings)
897
+
897
898
  if not agents_data:
898
- return "No agents found."
899
+ return warning_text + (
900
+ 'No agents returned from available inventories.'
901
+ if warnings else 'No agents found.'
902
+ )
899
903
 
900
904
  if filter_text:
901
905
  filter_lower = filter_text.lower()
@@ -907,7 +911,7 @@ class Aegis:
907
911
  ]
908
912
 
909
913
  if not agents_data:
910
- return f"No agents found matching filter: {filter_text}"
914
+ return warning_text + f"No agents found matching filter: {filter_text}"
911
915
 
912
916
  if details:
913
917
  detailed_lines = []
@@ -918,9 +922,9 @@ class Aegis:
918
922
  if lines:
919
923
  lines[0] = f"[{i:2d}] {lines[0].lstrip()}"
920
924
  detailed_lines.append('\n'.join(lines))
921
- return '\n\n'.join(detailed_lines)
925
+ return warning_text + '\n\n'.join(detailed_lines)
922
926
  else:
923
927
  lines = [f"{'#':>4} {'VERSION':<7} {'HOSTNAME':<30} ID"]
924
928
  for i, agent in enumerate(agents_data, 1):
925
929
  lines.append(f"{i:>4} {agent.version:<7} {(agent.hostname or 'Unknown')[:30]:<30} {agent.display_id}")
926
- return '\n'.join(lines)
930
+ return warning_text + '\n'.join(lines)