agentmetry 0.9.7__tar.gz → 0.9.8__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 (305) hide show
  1. {agentmetry-0.9.7 → agentmetry-0.9.8}/PKG-INFO +1 -1
  2. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/main.py +33 -8
  3. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/routes/audit.py +24 -1
  4. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/cli/__init__.py +22 -2
  5. agentmetry-0.9.8/agentmetry/core/audit/antigravity_trajectory.py +81 -0
  6. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/cloud_bridge.py +24 -0
  7. agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl +3 -0
  8. agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl +4 -0
  9. agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl +4 -0
  10. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/corpus.yaml +28 -0
  11. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/external.py +2 -2
  12. agentmetry-0.9.8/agentmetry/core/audit/grok_bot_transcript.py +58 -0
  13. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/hook_bootstrap.py +30 -0
  14. agentmetry-0.9.8/agentmetry/core/audit/session_tree.py +190 -0
  15. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_db.py +76 -0
  16. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/doctor.py +49 -4
  17. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/version.py +1 -1
  18. agentmetry-0.9.8/tests/test_antigravity_trajectory.py +35 -0
  19. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cloud_bridge.py +14 -0
  20. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_doctor.py +78 -0
  21. agentmetry-0.9.8/tests/test_grok_bot_transcript.py +44 -0
  22. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_bootstrap.py +59 -0
  23. agentmetry-0.9.8/tests/test_session_tree.py +188 -0
  24. agentmetry-0.9.8/tests/test_transcript_watcher.py +162 -0
  25. {agentmetry-0.9.7 → agentmetry-0.9.8}/.dockerignore +0 -0
  26. {agentmetry-0.9.7 → agentmetry-0.9.8}/.env.agentmetry-demo +0 -0
  27. {agentmetry-0.9.7 → agentmetry-0.9.8}/.env.example +0 -0
  28. {agentmetry-0.9.7 → agentmetry-0.9.8}/.gitignore +0 -0
  29. {agentmetry-0.9.7 → agentmetry-0.9.8}/Dockerfile +0 -0
  30. {agentmetry-0.9.7 → agentmetry-0.9.8}/README.md +0 -0
  31. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/__init__.py +0 -0
  32. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/__init__.py +0 -0
  33. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/routes/__init__.py +0 -0
  34. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/routes/auth.py +0 -0
  35. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/trusted_host.py +0 -0
  36. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/websocket.py +0 -0
  37. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/api/ws_bridge.py +0 -0
  38. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/cli/__main__.py +0 -0
  39. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/__init__.py +0 -0
  40. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/api_token.py +0 -0
  41. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/__init__.py +0 -0
  42. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/__init__.py +0 -0
  43. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/agt.py +0 -0
  44. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/chronicle.py +0 -0
  45. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
  46. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/ecs.py +0 -0
  47. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/sentinel.py +0 -0
  48. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/splunk.py +0 -0
  49. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/alerts.py +0 -0
  50. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/atlas.py +0 -0
  51. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/canonical.py +0 -0
  52. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/compliance_digest.py +0 -0
  53. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/__init__.py +0 -0
  54. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/benchmark.py +0 -0
  55. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
  56. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
  57. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
  58. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
  59. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
  60. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
  61. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
  62. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
  63. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
  64. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
  65. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
  66. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
  67. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
  68. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
  69. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
  70. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
  71. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
  72. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
  73. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
  74. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
  75. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
  76. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
  77. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
  78. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
  79. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
  80. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
  81. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
  82. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
  83. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
  84. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
  85. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
  86. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
  87. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
  88. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
  89. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
  90. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
  91. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
  92. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
  93. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
  94. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
  95. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
  96. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
  97. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
  98. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
  99. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
  100. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
  101. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
  102. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
  103. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
  104. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
  105. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
  106. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
  107. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
  108. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
  109. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/disposition.py +0 -0
  110. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/engine.py +0 -0
  111. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/live.py +0 -0
  112. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/live_store.py +0 -0
  113. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/models.py +0 -0
  114. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/rules.py +0 -0
  115. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/traits.py +0 -0
  116. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/yaml_config.py +0 -0
  117. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
  118. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/__init__.py +0 -0
  119. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/loader.py +0 -0
  120. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/models.py +0 -0
  121. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/scanner.py +0 -0
  122. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/dogfood.py +0 -0
  123. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/evidence_pack.py +0 -0
  124. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/forwarder.py +0 -0
  125. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/hashing.py +0 -0
  126. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/heartbeat.py +0 -0
  127. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/identity.py +0 -0
  128. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/ingest.py +0 -0
  129. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/managed_hooks.py +0 -0
  130. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
  131. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/migrate.py +0 -0
  132. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/mitre.py +0 -0
  133. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/otel_ingest.py +0 -0
  134. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/policy.py +0 -0
  135. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/policy_settings.py +0 -0
  136. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/posture_event.py +0 -0
  137. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/redaction.py +0 -0
  138. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/replay.py +0 -0
  139. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/run_context.py +0 -0
  140. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/signing.py +0 -0
  141. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/sinks.py +0 -0
  142. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/spool.py +0 -0
  143. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
  144. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/capability.py +0 -0
  145. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
  146. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/loader.py +0 -0
  147. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/models.py +0 -0
  148. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_anchor.py +0 -0
  149. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_chain.py +0 -0
  150. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_merkle.py +0 -0
  151. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/audit/trail_rotation.py +0 -0
  152. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/auth.py +0 -0
  153. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/__init__.py +0 -0
  154. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/audit_exporter.py +0 -0
  155. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/bridges.py +0 -0
  156. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/bus.py +0 -0
  157. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/events.py +0 -0
  158. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/bus/outbox.py +0 -0
  159. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/config.py +0 -0
  160. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/dashboard_session.py +0 -0
  161. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/__init__.py +0 -0
  162. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/autostart.py +0 -0
  163. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/driver_paths.py +0 -0
  164. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/env_file.py +0 -0
  165. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/hook_coverage.py +0 -0
  166. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/machine_view.py +0 -0
  167. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/mcp_inventory.py +0 -0
  168. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
  169. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/diagnostics/posture.py +0 -0
  170. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/drivers/__init__.py +0 -0
  171. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/drivers/host.py +0 -0
  172. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/drivers/permissions.py +0 -0
  173. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/drivers/spec.py +0 -0
  174. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/extensions.py +0 -0
  175. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/health.py +0 -0
  176. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/operator_identity.py +0 -0
  177. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/core/paths.py +0 -0
  178. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/demo.py +0 -0
  179. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/hooks/__init__.py +0 -0
  180. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/hooks/ingest.py +0 -0
  181. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/hooks/mcp_proxy.py +0 -0
  182. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/policies/detection/manifest.yaml +0 -0
  183. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/policies/dlp/manifest.yaml +0 -0
  184. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/policies/opa/agent_rules.rego +0 -0
  185. {agentmetry-0.9.7 → agentmetry-0.9.8}/agentmetry/policies/tool/manifest.yaml +0 -0
  186. {agentmetry-0.9.7 → agentmetry-0.9.8}/pyproject.toml +0 -0
  187. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/conftest.py +0 -0
  188. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
  189. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
  190. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/README.md +0 -0
  191. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
  192. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
  193. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/fake_mcp_server.py +0 -0
  194. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/fixtures/otel_claude_code_session.json +0 -0
  195. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_agentmetry_audit.py +0 -0
  196. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_agentmetry_ingest_client.py +0 -0
  197. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_agt_adapter.py +0 -0
  198. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_ask_gate_phase_a.py +0 -0
  199. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_atlas_detection_enrichment.py +0 -0
  200. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_atlas_mapping.py +0 -0
  201. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_audit_sinks.py +0 -0
  202. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_audit_stats.py +0 -0
  203. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_audit_tail.py +0 -0
  204. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_auth.py +0 -0
  205. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_auth_default.py +0 -0
  206. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_autostart.py +0 -0
  207. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_boot_sequence.py +0 -0
  208. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_burst_windows.py +0 -0
  209. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_chinese_agent_hooks.py +0 -0
  210. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_chinese_agent_sprint_b.py +0 -0
  211. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_chinese_agent_sprint_c.py +0 -0
  212. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_chronicle_adapter.py +0 -0
  213. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cli_backup.py +0 -0
  214. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cli_commands.py +0 -0
  215. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cli_env_var_names.py +0 -0
  216. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_cloudevents_adapter.py +0 -0
  217. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_compliance_digest.py +0 -0
  218. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_container_and_legacy_runtime.py +0 -0
  219. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_copilot_hooks.py +0 -0
  220. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_crewai_adapter.py +0 -0
  221. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_demo.py +0 -0
  222. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_adi.py +0 -0
  223. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_benchmark.py +0 -0
  224. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_default_config.py +0 -0
  225. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_disposition.py +0 -0
  226. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_disposition_api.py +0 -0
  227. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_duplicate_emission.py +0 -0
  228. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_emit_durability.py +0 -0
  229. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_engine.py +0 -0
  230. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_evasion.py +0 -0
  231. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_hf_incident.py +0 -0
  232. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_quoted_command_words.py +0 -0
  233. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_rule_identity.py +0 -0
  234. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_detection_rules_v2.py +0 -0
  235. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dlp_agent_env_override.py +0 -0
  236. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dlp_invisible_unicode.py +0 -0
  237. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dlp_markdown_exfil.py +0 -0
  238. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dlp_scanner.py +0 -0
  239. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dogfood.py +0 -0
  240. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_dogfood_freeze.py +0 -0
  241. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_driver_paths.py +0 -0
  242. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_ecs_threat.py +0 -0
  243. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_event_bus.py +0 -0
  244. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_event_signing.py +0 -0
  245. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_evidence_pack.py +0 -0
  246. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_extensions.py +0 -0
  247. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_external_ingest.py +0 -0
  248. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_forwarder.py +0 -0
  249. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_forwarding_shape.py +0 -0
  250. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_funding_manifest.py +0 -0
  251. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hash_key.py +0 -0
  252. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_heartbeat.py +0 -0
  253. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_coverage.py +0 -0
  254. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_decision_format.py +0 -0
  255. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_enforcement_timing.py +0 -0
  256. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_import_cost.py +0 -0
  257. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_real_process.py +0 -0
  258. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_hook_spool.py +0 -0
  259. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_identity_enrich.py +0 -0
  260. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_identity_provider.py +0 -0
  261. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_ingest_roundtrip.py +0 -0
  262. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_isolation.py +0 -0
  263. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_launch_targets.py +0 -0
  264. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_live_detection.py +0 -0
  265. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_live_detection_store.py +0 -0
  266. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_machine_view.py +0 -0
  267. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_managed_hooks.py +0 -0
  268. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_audit_proxy.py +0 -0
  269. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_concealed_text.py +0 -0
  270. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_inventory.py +0 -0
  271. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_inventory_event.py +0 -0
  272. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_meta_fingerprint.py +0 -0
  273. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_proxy_packaged.py +0 -0
  274. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mcp_schema.py +0 -0
  275. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_mitre_dlp.py +0 -0
  276. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_opensre_adapter.py +0 -0
  277. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_operator_attribution.py +0 -0
  278. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_otel_ingest.py +0 -0
  279. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_paths.py +0 -0
  280. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_posture.py +0 -0
  281. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_powershell_encoding.py +0 -0
  282. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_readme_claims.py +0 -0
  283. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_redaction.py +0 -0
  284. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_replay.py +0 -0
  285. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_sentinel.py +0 -0
  286. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_serve_script_binds_loopback.py +0 -0
  287. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_shared_install.py +0 -0
  288. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_sigma_pack.py +0 -0
  289. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_static_serving.py +0 -0
  290. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_subagent_lifecycle.py +0 -0
  291. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_suite_leaves_real_home_alone.py +0 -0
  292. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_tool_policy.py +0 -0
  293. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_tool_policy_agent_cli.py +0 -0
  294. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_tool_policy_git_hooks.py +0 -0
  295. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trail_anchor.py +0 -0
  296. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trail_chain.py +0 -0
  297. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trail_concurrency.py +0 -0
  298. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trail_merkle.py +0 -0
  299. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_trusted_host.py +0 -0
  300. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_unattended_agent_policy.py +0 -0
  301. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_version.py +0 -0
  302. {agentmetry-0.9.7 → agentmetry-0.9.8}/tests/test_yaml_detection_rules.py +0 -0
  303. {agentmetry-0.9.7 → agentmetry-0.9.8}/tools/generate_sigma_pack.py +0 -0
  304. {agentmetry-0.9.7 → agentmetry-0.9.8}/tools/mcp_audit_proxy.py +0 -0
  305. {agentmetry-0.9.7 → agentmetry-0.9.8}/tools/vault_fs_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentmetry
3
- Version: 0.9.7
3
+ Version: 0.9.8
4
4
  Summary: Local-first flight recorder for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
5
5
  Project-URL: Homepage, https://agentmetry.ai
6
6
  Project-URL: Source, https://github.com/blitzcrieg1/agentmetry
@@ -2,6 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import os
6
+ import subprocess
7
+ import sys
5
8
  from contextlib import asynccontextmanager
6
9
  from logging.handlers import RotatingFileHandler
7
10
  from pathlib import Path
@@ -76,6 +79,34 @@ def _start_forwarders() -> list[asyncio.Task]:
76
79
  return tasks
77
80
 
78
81
 
82
+ def start_transcript_watcher(watcher_path: Path) -> subprocess.Popen:
83
+ """Start the Antigravity transcript watcher, tied to this process's life.
84
+
85
+ Its stdin is a pipe only this process holds, and the lifeline variable
86
+ tells it to exit at end-of-file. When this process dies, however it dies,
87
+ the pipe closes and the watcher follows. Without it each restart left a
88
+ watcher behind, and every one of them recorded every Antigravity call.
89
+ """
90
+ return subprocess.Popen(
91
+ [sys.executable, str(watcher_path)],
92
+ stdin=subprocess.PIPE,
93
+ stdout=subprocess.DEVNULL,
94
+ stderr=subprocess.DEVNULL,
95
+ env={**os.environ, "AGENTMETRY_WATCHER_LIFELINE": "1"},
96
+ )
97
+
98
+
99
+ def stop_transcript_watcher(process: subprocess.Popen) -> None:
100
+ """Close the lifeline, then terminate. Closing alone is enough; terminate is
101
+ for a watcher that predates the lifeline."""
102
+ try:
103
+ if process.stdin:
104
+ process.stdin.close()
105
+ except OSError:
106
+ pass
107
+ process.terminate()
108
+
109
+
79
110
  @asynccontextmanager
80
111
  async def lifespan(app: FastAPI):
81
112
  from agentmetry.core.audit.migrate import backfill_db_from_jsonl
@@ -177,17 +208,11 @@ async def lifespan(app: FastAPI):
177
208
  )
178
209
 
179
210
  # Launch transcript watcher for Antigravity in the background
180
- import subprocess
181
- import sys
182
211
  watcher_process = None
183
212
  try:
184
213
  watcher_path = Path(__file__).resolve().parents[4] / "scripts" / "antigravity_transcript_watcher.py"
185
214
  if watcher_path.exists():
186
- watcher_process = subprocess.Popen(
187
- [sys.executable, str(watcher_path)],
188
- stdout=subprocess.DEVNULL,
189
- stderr=subprocess.DEVNULL,
190
- )
215
+ watcher_process = start_transcript_watcher(watcher_path)
191
216
  logger.info("Started Antigravity transcript watcher (PID: %s)", watcher_process.pid)
192
217
  except Exception as exc:
193
218
  logger.warning("Failed to start Antigravity transcript watcher: %s", exc)
@@ -201,7 +226,7 @@ async def lifespan(app: FastAPI):
201
226
  for task in bridge_tasks:
202
227
  task.cancel()
203
228
  if watcher_process:
204
- watcher_process.terminate()
229
+ stop_transcript_watcher(watcher_process)
205
230
 
206
231
 
207
232
  app = FastAPI(
@@ -101,7 +101,7 @@ class ExternalIngestBody(BaseModel):
101
101
 
102
102
  source_app: str = Field(
103
103
  ...,
104
- description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre",
104
+ description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre | zcode | grok_bot",
105
105
  )
106
106
  event_type: str = Field(
107
107
  ...,
@@ -309,6 +309,29 @@ async def audit_session(
309
309
  }
310
310
 
311
311
 
312
+ @router.get("/session/{correlation_id}/tree", dependencies=[Depends(require_api_key)])
313
+ async def audit_session_tree(
314
+ correlation_id: str,
315
+ limit: int = Query(20000, ge=1, le=50000),
316
+ around_utc: str = Query("", max_length=40),
317
+ ):
318
+ """One session folded into investigation-tree nodes.
319
+
320
+ Each ask, its answer and the call it led to become one node, so a detection
321
+ that cites the ask and a policy verdict recorded on it land on the call
322
+ they belong to. A session longer than `limit` is windowed on `around_utc`
323
+ (a detection's first_seen_utc) when given. See `core/audit/session_tree.py`.
324
+ """
325
+ from agentmetry.core.audit.session_tree import session_tree
326
+
327
+ if not settings.audit_export_enabled:
328
+ return {"correlation_id": correlation_id, "enabled": False, "nodes": [], "summary": {}, "truncated": False}
329
+ try:
330
+ return await asyncio.to_thread(session_tree, correlation_id, limit, around_utc)
331
+ except Exception as exc:
332
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
333
+
334
+
312
335
  @router.get("/detections/{correlation_id}", dependencies=[Depends(require_api_key)])
313
336
  async def audit_detections(correlation_id: str):
314
337
  """Run correlated behavioral rules over one session and return detections.
@@ -551,6 +551,7 @@ def cmd_cloud(args: argparse.Namespace) -> int:
551
551
  map_claude_messages,
552
552
  map_claude_session,
553
553
  map_cursor_agent,
554
+ map_outbound_poll,
554
555
  )
555
556
 
556
557
  base = (args.url or "").strip() or None
@@ -576,7 +577,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
576
577
  if not api_key:
577
578
  print("CURSOR_API_KEY is not set.")
578
579
  return 2
579
- for agent in fetch_cursor_agents(api_key, base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE):
580
+ cursor_base = base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE
581
+ post(map_outbound_poll("cursor_cloud", "GET", f"{cursor_base.rstrip('/')}/v1/agents"))
582
+ for agent in fetch_cursor_agents(api_key, cursor_base):
580
583
  runs = []
581
584
  if not args.skip_runs:
582
585
  try:
@@ -594,7 +597,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
594
597
  print("ANTHROPIC_COMPLIANCE_KEY is not set (a Compliance Access Key with read:compliance_user_data, not an admin key).")
595
598
  return 2
596
599
  kind = "remote" if args.remote else "local"
597
- sessions = fetch_claude_sessions(api_key, kind=kind, base=base or "https://api.anthropic.com", limit=args.limit)
600
+ claude_base = base or "https://api.anthropic.com"
601
+ post(map_outbound_poll("claude_cloud", "GET", f"{claude_base.rstrip('/')}/v1/compliance/apps/sessions/{kind}"))
602
+ sessions = fetch_claude_sessions(api_key, kind=kind, base=claude_base, limit=args.limit)
598
603
  for session in sessions[: args.limit]:
599
604
  post(map_claude_session(session))
600
605
  try:
@@ -663,6 +668,21 @@ def cmd_hooks(args: argparse.Namespace) -> int:
663
668
  from agentmetry.core.audit import hook_bootstrap
664
669
  from agentmetry.core.diagnostics import hook_coverage
665
670
 
671
+ if args.action == "install":
672
+ # Before --managed too: a managed install would put the temporary path
673
+ # into machine-wide policy files.
674
+ reason = hook_bootstrap.ephemeral_environment()
675
+ if reason:
676
+ print(
677
+ f"Not installing hooks: {reason}. That cache can be deleted at any "
678
+ "time, and every hook would then fail (most agents silently stop "
679
+ "recording; Copilot CLI blocks every tool call). Install Agentmetry "
680
+ "somewhere that stays, then run this again:\n"
681
+ " uv tool install agentmetry (or: pipx install agentmetry, or: pip install agentmetry)\n"
682
+ " agentmetry hooks install",
683
+ file=sys.stderr,
684
+ )
685
+ return 1
666
686
  if getattr(args, "managed", False):
667
687
  return _hooks_managed(args)
668
688
  if args.action == "status":
@@ -0,0 +1,81 @@
1
+ """Read tool calls out of an Antigravity conversation database.
2
+
3
+ The installed hook only matches `run_command`, so file reads, writes, search
4
+ and the other tools never reach the trail. The conversation database stores
5
+ every step. The payload is a protobuf blob. The tool name is a length-prefixed
6
+ ASCII field and the arguments are the JSON object that follows it. That framing
7
+ was read off a real database on 2026-10-16. A blob that does not match it
8
+ yields nothing.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import sqlite3
15
+ from pathlib import Path
16
+ from typing import Any, Iterator
17
+
18
+
19
+ def _varint(buf: bytes, index: int) -> tuple[int, int]:
20
+ value = 0
21
+ shift = 0
22
+ while index < len(buf) and shift <= 28:
23
+ byte = buf[index]
24
+ index += 1
25
+ value |= (byte & 0x7F) << shift
26
+ if not byte & 0x80:
27
+ return value, index
28
+ shift += 7
29
+ raise ValueError("truncated varint")
30
+
31
+
32
+ def iter_tool_calls(payload: bytes) -> Iterator[tuple[str, dict[str, Any]]]:
33
+ """Yield (tool name, arguments) from one step payload."""
34
+ index = 0
35
+ while True:
36
+ marker = payload.find(b"\x12", index)
37
+ if marker < 0 or marker + 2 >= len(payload):
38
+ return
39
+ name_len = payload[marker + 1]
40
+ if name_len == 0 or name_len > 64 or name_len & 0x80:
41
+ index = marker + 1
42
+ continue
43
+ name_at = marker + 2
44
+ name_end = name_at + name_len
45
+ if name_end >= len(payload) or payload[name_end] != 0x1A:
46
+ index = marker + 1
47
+ continue
48
+ name = payload[name_at:name_end]
49
+ if not name.isascii() or not all(chr(c).isalnum() or c == ord("_") for c in name):
50
+ index = marker + 1
51
+ continue
52
+ try:
53
+ json_len, json_at = _varint(payload, name_end + 1)
54
+ except ValueError:
55
+ index = marker + 1
56
+ continue
57
+ blob = payload[json_at : json_at + json_len]
58
+ if not blob.startswith(b"{"):
59
+ index = marker + 1
60
+ continue
61
+ try:
62
+ args = json.loads(blob)
63
+ except json.JSONDecodeError:
64
+ index = marker + 1
65
+ continue
66
+ if isinstance(args, dict):
67
+ yield name.decode("ascii"), args
68
+ index = json_at + max(json_len, 1)
69
+
70
+
71
+ def tool_names(db_path: Path) -> dict[str, int]:
72
+ """Count tool names in one conversation database. Arguments are not returned."""
73
+ counts: dict[str, int] = {}
74
+ uri = f"file:{Path(db_path).as_posix()}?mode=ro"
75
+ with sqlite3.connect(uri, uri=True) as con:
76
+ for (payload,) in con.execute("SELECT step_payload FROM steps"):
77
+ if not isinstance(payload, (bytes, bytearray)):
78
+ continue
79
+ for name, _args in iter_tool_calls(payload):
80
+ counts[name] = counts.get(name, 0) + 1
81
+ return counts
@@ -26,6 +26,7 @@ from __future__ import annotations
26
26
  import hashlib
27
27
  import json
28
28
  from typing import Any
29
+ from urllib.parse import urlsplit
29
30
 
30
31
  REDACT_KEYS = frozenset(
31
32
  {"authorization", "api_key", "apikey", "password", "secret", "token", "session_key"}
@@ -110,6 +111,29 @@ def tool_summary(args: Any) -> dict[str, Any]:
110
111
  return summary
111
112
 
112
113
 
114
+ def map_outbound_poll(source_app: str, method: str, url: str) -> dict[str, Any]:
115
+ """The request this machine sends. The key is not in the payload.
116
+
117
+ Query strings are dropped: a token does not belong in the trail. The path
118
+ and host are the trace of the send.
119
+ """
120
+ parts = urlsplit(url)
121
+ target = f"{method} {parts.netloc}{parts.path}"
122
+ return {
123
+ "source_app": source_app,
124
+ "adapter": "cloud_bridge",
125
+ "event_type": "tool_called",
126
+ "outcome": "success",
127
+ "correlation_id": f"{source_app}-outbound",
128
+ "reason": "outbound poll; key sent and not stored",
129
+ "tool": {
130
+ "qualified": f"{source_app}.poll",
131
+ "server": source_app,
132
+ "command": target,
133
+ },
134
+ }
135
+
136
+
113
137
  # --- Cursor Cloud Agents API -------------------------------------------------
114
138
 
115
139
  CURSOR_DEFAULT_BASE = "https://api.cursor.com"
@@ -0,0 +1,3 @@
1
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:00:00+00:00","tool":{"arguments":{"command":"docker login registry.example.com --username ci-bot --password-stdin"},"command":"docker login registry.example.com --username ci-bot --password-stdin","input_hash":"1f3b6a8a28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d01","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
2
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:00:30+00:00","tool":{"arguments":{"command":"docker build -t registry.example.com/team/service:build-42 ."},"command":"docker build -t registry.example.com/team/service:build-42 .","input_hash":"2a4c6d8e28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d02","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
3
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:01:20+00:00","tool":{"arguments":{"command":"docker push registry.example.com/team/service:build-42"},"command":"docker push registry.example.com/team/service:build-42","input_hash":"3b5d7e9f28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d03","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
@@ -0,0 +1,4 @@
1
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:00+00:00","tool":{"arguments":{"command":"npm audit --json > reports/npm-audit.json"},"command":"npm audit --json > reports/npm-audit.json","input_hash":"8091234428dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d08","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
2
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:20+00:00","tool":{"arguments":{"command":"pip-audit -r requirements.txt -f json -o reports/pip-audit.json"},"command":"pip-audit -r requirements.txt -f json -o reports/pip-audit.json","input_hash":"91a2345528dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d09","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
3
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:40+00:00","tool":{"arguments":{"command":"curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?limit=20 > reports/npm-advisories.json"},"command":"curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?limit=20 > reports/npm-advisories.json","input_hash":"a2b3456628dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d10","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
4
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e004","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:01:20+00:00","tool":{"arguments":{"command":"python scripts/summarise_audit.py reports/npm-audit.json reports/pip-audit.json reports/npm-advisories.json"},"command":"python scripts/summarise_audit.py reports/npm-audit.json reports/pip-audit.json reports/npm-advisories.json","input_hash":"b3c4567728dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d11","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
@@ -0,0 +1,4 @@
1
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:00:00+00:00","tool":{"arguments":{"command":"terraform init -backend-config=env/staging.backend"},"command":"terraform init -backend-config=env/staging.backend","input_hash":"4c6e8f1028dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d04","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
2
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:02:00+00:00","tool":{"arguments":{"command":"terraform plan -var-file env/staging.tfvars -out tfplan"},"command":"terraform plan -var-file env/staging.tfvars -out tfplan","input_hash":"5d7f901128dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d05","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
3
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:04:00+00:00","tool":{"arguments":{"command":"AWS_PROFILE=staging terraform apply -auto-approve tfplan"},"command":"AWS_PROFILE=staging terraform apply -auto-approve tfplan","input_hash":"6e80912228dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d06","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
4
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d004","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:06:00+00:00","tool":{"arguments":{"command":"aws sts get-caller-identity --profile staging"},"command":"aws sts get-caller-identity --profile staging","input_hash":"7f90123328dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d07","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["cloud_api"]}}
@@ -419,6 +419,34 @@ cases:
419
419
  Thirty read-only calls spread over half an hour. Burst rules measure a
420
420
  time window; a long session must not trip them by length alone.
421
421
 
422
+ - name: container registry publish
423
+ session: benign_container_registry_publish.jsonl
424
+ expect: []
425
+ benign: true
426
+ note: >
427
+ `docker login`, `docker build` and `docker push` against a registry. This
428
+ looks like authenticated egress, but no credential is read from disk or
429
+ the environment; the password arrives through stdin and never belongs in
430
+ the trail.
431
+
432
+ - name: terraform apply with a named profile
433
+ session: benign_terraform_apply_with_profile.jsonl
434
+ expect: []
435
+ benign: true
436
+ note: >
437
+ Infrastructure automation touches cloud state and later calls `aws sts`,
438
+ but it uses a named profile rather than reading credentials. Cloud API use
439
+ by itself is not lateral movement after credential harvest.
440
+
441
+ - name: dependency audit touches remote advisories
442
+ session: benign_dependency_audit_network_burst.jsonl
443
+ expect: []
444
+ benign: true
445
+ note: >
446
+ Package audits query registries and advisory feeds in quick succession,
447
+ then run a local summariser. A short network-heavy audit is not a session
448
+ tool burst, a download cradle or credential exfiltration.
449
+
422
450
  - name: human-driven deletes currently fire
423
451
  session: benign_human_driven_deletes.jsonl
424
452
  expect: [destructive-delete-burst]
@@ -21,13 +21,13 @@ from agentmetry.core.config import settings
21
21
  ExternalApp = Literal[
22
22
  "cursor", "claude", "antigravity", "codex", "mcp_proxy",
23
23
  "qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
24
- "cursor_cloud", "claude_cloud", "grok_build", "agentmetry",
24
+ "cursor_cloud", "claude_cloud", "grok_build", "grok_bot", "agentmetry",
25
25
  ]
26
26
 
27
27
  KNOWN_EXTERNAL_APPS = frozenset({
28
28
  "cursor", "claude", "antigravity", "codex", "mcp_proxy",
29
29
  "qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
30
- "cursor_cloud", "claude_cloud", "grok_build",
30
+ "cursor_cloud", "claude_cloud", "grok_build", "grok_bot",
31
31
  })
32
32
 
33
33
  _ACTION_MAP = {
@@ -0,0 +1,58 @@
1
+ """Grok Bot desktop chat cache to ingest payloads.
2
+
3
+ The desktop app writes the conversation to sand-client-persistence. Those
4
+ lines are the bot's own turns. They are not Cursor tool calls, and posting
5
+ them without source_app grok_bot used to land them as unknown or, before
6
+ that, as Cursor.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from datetime import datetime, timezone
12
+ from typing import Any
13
+
14
+
15
+ def message_text(entry: dict[str, Any]) -> str:
16
+ if entry.get("kind") == "message":
17
+ text = entry.get("content")
18
+ return text if isinstance(text, str) else ""
19
+ message = entry.get("message")
20
+ if isinstance(message, dict):
21
+ text = message.get("content")
22
+ return text if isinstance(text, str) else ""
23
+ return ""
24
+
25
+
26
+ def _timestamp(entry: dict[str, Any]) -> str:
27
+ raw = entry.get("timestampMs")
28
+ if isinstance(raw, (int, float)) and raw > 1_000_000_000_000:
29
+ return datetime.fromtimestamp(raw / 1000, timezone.utc).isoformat()
30
+ return ""
31
+
32
+
33
+ def payloads_for_entries(entries: list[dict[str, Any]], *, agent_id: str) -> list[dict[str, Any]]:
34
+ """One ingest payload per chat line. The command column carries the text."""
35
+ out: list[dict[str, Any]] = []
36
+ for entry in entries:
37
+ text = message_text(entry).strip()
38
+ entry_id = str(entry.get("id") or "")
39
+ if not text or not entry_id:
40
+ continue
41
+ role = "user" if entry.get("role") == "user" else "assistant"
42
+ payload: dict[str, Any] = {
43
+ "source_app": "grok_bot",
44
+ "adapter": "grok_bot_transcript",
45
+ "event_type": "tool_called",
46
+ "correlation_id": agent_id,
47
+ "outcome": "success",
48
+ "tool": {
49
+ "qualified": f"grok_bot.{role}",
50
+ "server": "grok_bot",
51
+ "command": text[:2000],
52
+ },
53
+ }
54
+ stamp = _timestamp(entry)
55
+ if stamp:
56
+ payload["timestamp_utc"] = stamp
57
+ out.append(payload)
58
+ return out
@@ -174,6 +174,36 @@ def _console_script() -> Path | None:
174
174
  return None
175
175
 
176
176
 
177
+ def ephemeral_environment(executable: str | None = None) -> str:
178
+ """Why this interpreter is a temporary one, or "" when it will still exist later.
179
+
180
+ `uvx agentmetry ...` and `pipx run agentmetry ...` run from a cache the tool
181
+ may delete at any time (`uv cache clean`, a pipx cache expiry). A hook
182
+ config written from there names an executable inside that cache: it works
183
+ today, and after the next clean every hook fails. Most agents treat a
184
+ failing hook as a non-blocking error, so recording stops without a word;
185
+ Copilot CLI fails closed on a broken pre-tool hook and blocks every tool
186
+ call. Coverage would still report the agent covered, because it checks the
187
+ hook text in the config, not the file it points at.
188
+ """
189
+ raw = executable or sys.executable
190
+ try:
191
+ resolved = Path(raw).resolve()
192
+ except OSError:
193
+ resolved = Path(raw)
194
+ # Split on both separators by hand: on POSIX a Windows path is one part.
195
+ text = str(resolved).replace("\\", "/").lower()
196
+ parts = [p for p in text.split("/") if p]
197
+ cache = os.environ.get("UV_CACHE_DIR", "").strip()
198
+ if cache and text.startswith(str(Path(cache).resolve()).replace("\\", "/").lower() + "/"):
199
+ return f"this interpreter is inside uv's cache ({resolved})"
200
+ if "uv" in parts and ("archive-v0" in parts or "builds-v0" in parts):
201
+ return f"this interpreter is inside uv's cache ({resolved})"
202
+ if "pipx" in parts and ".cache" in parts:
203
+ return f"this interpreter is inside pipx's run cache ({resolved})"
204
+ return ""
205
+
206
+
177
207
  def hook_target(repo_root: Path | None = None) -> str:
178
208
  """Which of the three ways to reach ingest exists here: repo, frozen, wheel.
179
209