agentmetry 0.10.0__tar.gz → 0.10.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 (365) hide show
  1. {agentmetry-0.10.0 → agentmetry-0.10.2}/.gitignore +2 -0
  2. {agentmetry-0.10.0 → agentmetry-0.10.2}/PKG-INFO +2 -2
  3. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/main.py +48 -14
  4. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/cli/__init__.py +78 -16
  5. agentmetry-0.10.2/agentmetry/core/audit/cursor_cloud_cache.py +305 -0
  6. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/evaluator.py +8 -0
  7. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/doctor.py +11 -2
  8. agentmetry-0.10.2/agentmetry/core/scanner/instruction_files.py +192 -0
  9. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/scan.py +84 -11
  10. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/version.py +1 -1
  11. agentmetry-0.10.2/agentmetry/dashboard_static/.gitkeep +0 -0
  12. agentmetry-0.10.2/agentmetry/dashboard_static/404.html +1 -0
  13. agentmetry-0.10.2/agentmetry/dashboard_static/__next.__PAGE__.txt +27 -0
  14. agentmetry-0.10.2/agentmetry/dashboard_static/__next._full.txt +23 -0
  15. agentmetry-0.10.2/agentmetry/dashboard_static/__next._tree.txt +5 -0
  16. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/B9YcBHt0iAnRAn2NpdHC2/_buildManifest.js +11 -0
  17. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/B9YcBHt0iAnRAn2NpdHC2/_clientMiddlewareManifest.js +1 -0
  18. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/B9YcBHt0iAnRAn2NpdHC2/_ssgManifest.js +1 -0
  19. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  20. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/0umf92h1sb77c.js +1 -0
  21. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/1ix59xuhq-q14.css +1 -0
  22. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/1q292q_rc4_p6.js +22 -0
  23. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/2-sq6__g8tt5-.js +4 -0
  24. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/2gvu8dyrhibpa.js +1 -0
  25. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/31qp8jfkw0x2h.js +31 -0
  26. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/3ia-1961js2lo.js +1 -0
  27. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/3in77zrqn0wml.css +8 -0
  28. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/turbopack-3-ry682tkyiqk.js +1 -0
  29. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-mono-latin-400-normal.2lhng2ntocry9.woff +0 -0
  30. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-mono-latin-400-normal.3xdfs0-p_zi4c.woff2 +0 -0
  31. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-mono-latin-500-normal.11q3guvgd1r20.woff2 +0 -0
  32. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-mono-latin-500-normal.1fgk-kmy-32_a.woff +0 -0
  33. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-400-normal.19i24--p24z3r.woff +0 -0
  34. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-400-normal.1xf5nwxyfigak.woff2 +0 -0
  35. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-500-normal.14sc3d7sk_zzh.woff +0 -0
  36. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-500-normal.2i4a1iuj6lbvw.woff2 +0 -0
  37. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-600-normal.2ovda_azncp9b.woff2 +0 -0
  38. agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-600-normal.2wn5p1s7u3utm.woff +0 -0
  39. agentmetry-0.10.2/agentmetry/dashboard_static/_not-found/__next._full.txt +22 -0
  40. agentmetry-0.10.2/agentmetry/dashboard_static/_not-found/__next._not-found.__PAGE__.txt +28 -0
  41. agentmetry-0.10.2/agentmetry/dashboard_static/_not-found/__next._tree.txt +4 -0
  42. agentmetry-0.10.2/agentmetry/dashboard_static/_not-found.html +1 -0
  43. agentmetry-0.10.2/agentmetry/dashboard_static/_not-found.txt +22 -0
  44. agentmetry-0.10.2/agentmetry/dashboard_static/agentmetry-icon-white.svg +4 -0
  45. agentmetry-0.10.2/agentmetry/dashboard_static/index.html +1 -0
  46. agentmetry-0.10.2/agentmetry/dashboard_static/index.txt +23 -0
  47. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/policies/tool/manifest.yaml +7 -1
  48. {agentmetry-0.10.0 → agentmetry-0.10.2}/pyproject.toml +10 -1
  49. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cli_commands.py +54 -1
  50. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cloud_bridge.py +57 -0
  51. agentmetry-0.10.2/tests/test_cursor_cloud_cache.py +191 -0
  52. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_doctor.py +20 -0
  53. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_scanner.py +118 -0
  54. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_static_serving.py +39 -0
  55. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_tool_policy_agent_cli.py +22 -0
  56. {agentmetry-0.10.0 → agentmetry-0.10.2}/.dockerignore +0 -0
  57. {agentmetry-0.10.0 → agentmetry-0.10.2}/.env.agentmetry-demo +0 -0
  58. {agentmetry-0.10.0 → agentmetry-0.10.2}/.env.example +0 -0
  59. {agentmetry-0.10.0 → agentmetry-0.10.2}/Dockerfile +0 -0
  60. {agentmetry-0.10.0 → agentmetry-0.10.2}/README.md +0 -0
  61. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/__init__.py +0 -0
  62. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/__init__.py +0 -0
  63. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/routes/__init__.py +0 -0
  64. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/routes/audit.py +0 -0
  65. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/routes/auth.py +0 -0
  66. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/trusted_host.py +0 -0
  67. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/websocket.py +0 -0
  68. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/ws_bridge.py +0 -0
  69. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/cli/__main__.py +0 -0
  70. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/__init__.py +0 -0
  71. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/api_token.py +0 -0
  72. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/__init__.py +0 -0
  73. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/__init__.py +0 -0
  74. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/agt.py +0 -0
  75. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/chronicle.py +0 -0
  76. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
  77. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/ecs.py +0 -0
  78. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/sentinel.py +0 -0
  79. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/splunk.py +0 -0
  80. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/alerts.py +0 -0
  81. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/antigravity_trajectory.py +0 -0
  82. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/atlas.py +0 -0
  83. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/canonical.py +0 -0
  84. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/cloud_bridge.py +0 -0
  85. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/compliance_digest.py +0 -0
  86. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/__init__.py +0 -0
  87. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/benchmark.py +0 -0
  88. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
  89. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
  90. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
  91. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
  92. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
  93. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
  94. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
  95. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
  96. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
  97. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
  98. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
  99. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
  100. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
  101. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
  102. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
  103. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
  104. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
  105. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
  106. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
  107. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
  108. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
  109. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
  110. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
  111. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
  112. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
  113. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
  114. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
  115. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
  116. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
  117. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl +0 -0
  118. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
  119. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl +0 -0
  120. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
  121. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
  122. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
  123. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
  124. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
  125. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
  126. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
  127. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
  128. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
  129. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
  130. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
  131. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
  132. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
  133. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
  134. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
  135. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
  136. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
  137. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
  138. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
  139. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
  140. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
  141. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
  142. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl +0 -0
  143. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
  144. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
  145. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/corpus.yaml +0 -0
  146. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/disposition.py +0 -0
  147. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/engine.py +0 -0
  148. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/live.py +0 -0
  149. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/live_store.py +0 -0
  150. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/models.py +0 -0
  151. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/rules.py +0 -0
  152. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/traits.py +0 -0
  153. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/yaml_config.py +0 -0
  154. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
  155. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dlp/__init__.py +0 -0
  156. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dlp/loader.py +0 -0
  157. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dlp/models.py +0 -0
  158. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dlp/scanner.py +0 -0
  159. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dogfood.py +0 -0
  160. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/evidence_pack.py +0 -0
  161. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/external.py +0 -0
  162. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/forwarder.py +0 -0
  163. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/grok_bot_transcript.py +0 -0
  164. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/hashing.py +0 -0
  165. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/heartbeat.py +0 -0
  166. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/hook_bootstrap.py +0 -0
  167. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/identity.py +0 -0
  168. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/ingest.py +0 -0
  169. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/ingest_quota.py +0 -0
  170. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/managed_hooks.py +0 -0
  171. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
  172. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/migrate.py +0 -0
  173. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/mitre.py +0 -0
  174. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/otel_ingest.py +0 -0
  175. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/policy.py +0 -0
  176. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/policy_settings.py +0 -0
  177. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/posture_event.py +0 -0
  178. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/redaction.py +0 -0
  179. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/replay.py +0 -0
  180. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/run_context.py +0 -0
  181. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/session_tree.py +0 -0
  182. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/signing.py +0 -0
  183. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/sinks.py +0 -0
  184. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/spool.py +0 -0
  185. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
  186. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/capability.py +0 -0
  187. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/loader.py +0 -0
  188. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/models.py +0 -0
  189. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_anchor.py +0 -0
  190. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_chain.py +0 -0
  191. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_db.py +0 -0
  192. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_merkle.py +0 -0
  193. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_rotation.py +0 -0
  194. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/auth.py +0 -0
  195. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/__init__.py +0 -0
  196. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/audit_exporter.py +0 -0
  197. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/bridges.py +0 -0
  198. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/bus.py +0 -0
  199. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/events.py +0 -0
  200. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/outbox.py +0 -0
  201. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/config.py +0 -0
  202. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/dashboard_session.py +0 -0
  203. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/__init__.py +0 -0
  204. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/autostart.py +0 -0
  205. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/driver_paths.py +0 -0
  206. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/env_file.py +0 -0
  207. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/hook_coverage.py +0 -0
  208. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/machine_view.py +0 -0
  209. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/mcp_inventory.py +0 -0
  210. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
  211. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/posture.py +0 -0
  212. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/drivers/__init__.py +0 -0
  213. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/drivers/host.py +0 -0
  214. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/drivers/permissions.py +0 -0
  215. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/drivers/spec.py +0 -0
  216. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/extensions.py +0 -0
  217. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/health.py +0 -0
  218. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/managed_policy.py +0 -0
  219. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/operator_identity.py +0 -0
  220. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/paths.py +0 -0
  221. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/__init__.py +0 -0
  222. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/injection.py +0 -0
  223. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/known_packages.py +0 -0
  224. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/typosquat.py +0 -0
  225. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/demo.py +0 -0
  226. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/hooks/__init__.py +0 -0
  227. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/hooks/ingest.py +0 -0
  228. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/hooks/mcp_proxy.py +0 -0
  229. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/policies/detection/manifest.yaml +0 -0
  230. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/policies/dlp/manifest.yaml +0 -0
  231. {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/policies/opa/agent_rules.rego +0 -0
  232. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/conftest.py +0 -0
  233. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
  234. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
  235. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/codex_hooks/README.md +0 -0
  236. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
  237. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
  238. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/fake_mcp_server.py +0 -0
  239. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/otel_claude_code_session.json +0 -0
  240. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/scanner/benign_skill/SKILL.md +0 -0
  241. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/scanner/benign_tools.json +0 -0
  242. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/scanner/poisoned_skill/SKILL.md +0 -0
  243. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/scanner/poisoned_tools.json +0 -0
  244. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_agentmetry_audit.py +0 -0
  245. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_agentmetry_ingest_client.py +0 -0
  246. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_agt_adapter.py +0 -0
  247. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_antigravity_trajectory.py +0 -0
  248. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ask_gate_phase_a.py +0 -0
  249. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_atlas_detection_enrichment.py +0 -0
  250. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_atlas_mapping.py +0 -0
  251. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_audit_sinks.py +0 -0
  252. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_audit_stats.py +0 -0
  253. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_audit_tail.py +0 -0
  254. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_auth.py +0 -0
  255. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_auth_default.py +0 -0
  256. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_autostart.py +0 -0
  257. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_boot_sequence.py +0 -0
  258. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_burst_windows.py +0 -0
  259. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_chinese_agent_hooks.py +0 -0
  260. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_chinese_agent_sprint_b.py +0 -0
  261. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_chinese_agent_sprint_c.py +0 -0
  262. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_chronicle_adapter.py +0 -0
  263. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cli_backup.py +0 -0
  264. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cli_env_var_names.py +0 -0
  265. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cloudevents_adapter.py +0 -0
  266. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_codex_windows_command.py +0 -0
  267. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_compliance_digest.py +0 -0
  268. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_container_and_legacy_runtime.py +0 -0
  269. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_copilot_hooks.py +0 -0
  270. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_coverage_observed.py +0 -0
  271. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_crewai_adapter.py +0 -0
  272. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_demo.py +0 -0
  273. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_demo_isolation.py +0 -0
  274. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_demo_mode_flag.py +0 -0
  275. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_adi.py +0 -0
  276. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_benchmark.py +0 -0
  277. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_default_config.py +0 -0
  278. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_disposition.py +0 -0
  279. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_disposition_api.py +0 -0
  280. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_duplicate_emission.py +0 -0
  281. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_emit_durability.py +0 -0
  282. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_engine.py +0 -0
  283. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_evasion.py +0 -0
  284. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_hf_incident.py +0 -0
  285. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_quoted_command_words.py +0 -0
  286. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_rule_identity.py +0 -0
  287. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_rules_v2.py +0 -0
  288. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dlp_agent_env_override.py +0 -0
  289. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dlp_invisible_unicode.py +0 -0
  290. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dlp_markdown_exfil.py +0 -0
  291. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dlp_scanner.py +0 -0
  292. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_doctor_json_and_remediation.py +0 -0
  293. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dogfood.py +0 -0
  294. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dogfood_freeze.py +0 -0
  295. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_driver_paths.py +0 -0
  296. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ecs_threat.py +0 -0
  297. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_event_bus.py +0 -0
  298. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_event_signing.py +0 -0
  299. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_evidence_pack.py +0 -0
  300. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_extensions.py +0 -0
  301. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_external_ingest.py +0 -0
  302. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_forwarder.py +0 -0
  303. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_forwarding_shape.py +0 -0
  304. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_funding_manifest.py +0 -0
  305. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_grok_bot_transcript.py +0 -0
  306. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hash_key.py +0 -0
  307. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_heartbeat.py +0 -0
  308. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_bootstrap.py +0 -0
  309. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_coverage.py +0 -0
  310. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_decision_format.py +0 -0
  311. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_enforcement_timing.py +0 -0
  312. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_import_cost.py +0 -0
  313. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_real_process.py +0 -0
  314. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_spool.py +0 -0
  315. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_identity_enrich.py +0 -0
  316. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_identity_provider.py +0 -0
  317. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ingest_rate_limit.py +0 -0
  318. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ingest_roundtrip.py +0 -0
  319. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ingest_token.py +0 -0
  320. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_isolation.py +0 -0
  321. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_launch_targets.py +0 -0
  322. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_live_detection.py +0 -0
  323. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_live_detection_store.py +0 -0
  324. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_machine_view.py +0 -0
  325. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_managed_hooks.py +0 -0
  326. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_managed_policy.py +0 -0
  327. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_audit_proxy.py +0 -0
  328. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_concealed_text.py +0 -0
  329. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_inventory.py +0 -0
  330. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_inventory_event.py +0 -0
  331. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_meta_fingerprint.py +0 -0
  332. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_proxy_packaged.py +0 -0
  333. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_schema.py +0 -0
  334. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mitre_dlp.py +0 -0
  335. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_opensre_adapter.py +0 -0
  336. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_operator_attribution.py +0 -0
  337. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_otel_ingest.py +0 -0
  338. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_paths.py +0 -0
  339. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_posture.py +0 -0
  340. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_powershell_encoding.py +0 -0
  341. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_readme_claims.py +0 -0
  342. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_redaction.py +0 -0
  343. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_replay.py +0 -0
  344. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_sentinel.py +0 -0
  345. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_serve_script_binds_loopback.py +0 -0
  346. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_session_tree.py +0 -0
  347. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_shared_install.py +0 -0
  348. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_sigma_pack.py +0 -0
  349. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_subagent_lifecycle.py +0 -0
  350. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_suite_leaves_real_home_alone.py +0 -0
  351. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_tool_policy.py +0 -0
  352. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_tool_policy_git_hooks.py +0 -0
  353. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trail_anchor.py +0 -0
  354. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trail_chain.py +0 -0
  355. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trail_concurrency.py +0 -0
  356. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trail_merkle.py +0 -0
  357. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_transcript_watcher.py +0 -0
  358. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trusted_host.py +0 -0
  359. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_unattended_agent_policy.py +0 -0
  360. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_user_profile_dir.py +0 -0
  361. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_version.py +0 -0
  362. {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_yaml_detection_rules.py +0 -0
  363. {agentmetry-0.10.0 → agentmetry-0.10.2}/tools/generate_sigma_pack.py +0 -0
  364. {agentmetry-0.10.0 → agentmetry-0.10.2}/tools/mcp_audit_proxy.py +0 -0
  365. {agentmetry-0.10.0 → agentmetry-0.10.2}/tools/vault_fs_server.py +0 -0
@@ -16,6 +16,8 @@ build/
16
16
  # Next.js
17
17
  apps/dashboard/.next/
18
18
  apps/dashboard/out/
19
+ # The same export copied into the package by the release build, for the wheel.
20
+ apps/orchestrator/agentmetry/dashboard_static/
19
21
 
20
22
  # Environment
21
23
  .env
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentmetry
3
- Version: 0.10.0
4
- Summary: Local-first endpoint sensor for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
3
+ Version: 0.10.2
4
+ Summary: Open-source security for AI coding agents: record, detect and block at the tool boundary, with a verifiable audit trail and SIEM forwarding
5
5
  Project-URL: Homepage, https://agentmetry.ai
6
6
  Project-URL: Source, https://github.com/blitzcrieg1/agentmetry
7
7
  Project-URL: Issues, https://github.com/blitzcrieg1/agentmetry/issues
@@ -315,23 +315,57 @@ async def websocket_endpoint(
315
315
  ws_manager.disconnect(websocket, session_id)
316
316
 
317
317
 
318
- _DASHBOARD_DIR = Path(__file__).resolve().parents[3] / "dashboard" / "out"
319
-
320
-
321
- def mount_dashboard(target: FastAPI, directory: Path = _DASHBOARD_DIR) -> bool:
322
- """Serve the dashboard's static export when it has been built.
323
-
324
- Single-process mode: `npm run build` in apps/dashboard emits `out/`, which
325
- the orchestrator serves at the root. Mounted last so it only catches paths
326
- not already claimed by the API or WebSocket. Dev uses the :3000 dev server
327
- instead, so this is a no-op when the export is absent.
318
+ #: A source checkout's build, which a developer rebuilds and expects to see.
319
+ _CHECKOUT_DASHBOARD_DIR = Path(__file__).resolve().parents[3] / "dashboard" / "out"
320
+ #: The same export, bundled inside the wheel by the release build. Until 0.10.1
321
+ #: nothing put it there, so `pip install agentmetry`, `agentmetry dashboard`
322
+ #: signed the browser in and landed it on a JSON 404.
323
+ _PACKAGED_DASHBOARD_DIR = Path(__file__).resolve().parents[1] / "dashboard_static"
324
+
325
+ _DASHBOARD_MISSING_PAGE = """<!DOCTYPE html>
326
+ <html lang="en"><head><meta charset="utf-8"><title>Agentmetry</title>
327
+ <meta name="viewport" content="width=device-width, initial-scale=1">
328
+ <style>body{font:16px/1.6 system-ui,sans-serif;max-width:40rem;margin:4rem auto;padding:0 1rem}
329
+ code{background:#f2f2f2;padding:.1em .3em;border-radius:3px}</style></head>
330
+ <body><h1>Agentmetry is recording.</h1>
331
+ <p>The dashboard is not part of this install. Packages from 0.10.1 include it:
332
+ <code>pip install --upgrade agentmetry</code>, then restart the recorder.</p>
333
+ <p>Recording, detection and the trail do not depend on it. From a shell:
334
+ <code>agentmetry stats</code>, <code>agentmetry replay &lt;session&gt;</code>,
335
+ <code>agentmetry verify --trail</code>.</p></body></html>"""
336
+
337
+
338
+ def dashboard_dir() -> Path | None:
339
+ """Where the dashboard export is: a checkout's build first, then the package's."""
340
+ for candidate in (_CHECKOUT_DASHBOARD_DIR, _PACKAGED_DASHBOARD_DIR):
341
+ if (candidate / "index.html").is_file():
342
+ return candidate
343
+ return None
344
+
345
+
346
+ def mount_dashboard(target: FastAPI, directory: Path | None = None) -> bool:
347
+ """Serve the dashboard's static export when there is one.
348
+
349
+ Single-process mode: the orchestrator serves the export at the root,
350
+ mounted last so it only catches paths not already claimed by the API or
351
+ WebSocket. Dev uses the :3000 dev server instead. With no export, the root
352
+ serves a page saying so, because the sign-in link redirects there and a
353
+ JSON 404 reads as a broken product.
328
354
  """
329
- if not directory.is_dir():
355
+ if directory is None:
356
+ directory = dashboard_dir()
357
+ if directory is None or not directory.is_dir():
330
358
  logger.info(
331
- "Dashboard export not found at %s — run 'npm run build' in "
332
- "apps/dashboard for single-process serving",
333
- directory,
359
+ "Dashboard export not found (looked in %s and %s); serving a notice at /",
360
+ _CHECKOUT_DASHBOARD_DIR,
361
+ _PACKAGED_DASHBOARD_DIR,
334
362
  )
363
+ from fastapi.responses import HTMLResponse
364
+
365
+ @target.get("/", include_in_schema=False, name="dashboard-missing")
366
+ async def dashboard_missing() -> HTMLResponse:
367
+ return HTMLResponse(_DASHBOARD_MISSING_PAGE, headers={"Cache-Control": "no-cache"})
368
+
335
369
  return False
336
370
 
337
371
  from fastapi.staticfiles import StaticFiles
@@ -312,16 +312,41 @@ def cmd_status(args: argparse.Namespace) -> int:
312
312
  return 0
313
313
 
314
314
 
315
- def cmd_stats(args: argparse.Namespace) -> int:
316
- """Audit trail metrics over a window — dogfood weekly counts."""
315
+ def _api_get_json(port: int, path: str, what: str, params: dict | None = None) -> dict | None:
316
+ """GET an API route with this install's token, or print why not and return None.
317
+
318
+ Every route but health has needed the per-install token since 0.9.3, and
319
+ `stats` and `detections` were still calling without it. The 401 body was
320
+ read as if it were the answer, so both printed an empty result over a trail
321
+ with events and a critical detection in it. A refusal is now an error.
322
+ """
317
323
  try:
318
- data = httpx.get(
319
- f"{_base_url(args.port)}/api/v1/audit/stats",
320
- params={"days": args.days},
324
+ resp = httpx.get(
325
+ f"{_api_base_url(port)}{path}",
326
+ params=params,
327
+ headers=_api_headers(),
321
328
  timeout=10.0,
322
- ).json()
329
+ )
323
330
  except Exception:
324
- print("Not running - start Agentmetry first (stats reads via the API).")
331
+ print(f"Not running - start Agentmetry first ({what} reads via the API).")
332
+ return None
333
+ if resp.status_code in (401, 403):
334
+ print(
335
+ f"The recorder refused the request (HTTP {resp.status_code}): this shell has no "
336
+ "valid API token for it. Run it as the account that started the recorder, or "
337
+ "set AGENTMETRY_API_KEY."
338
+ )
339
+ return None
340
+ if resp.status_code >= 400:
341
+ print(f"FAILED: HTTP {resp.status_code} from {path}")
342
+ return None
343
+ return resp.json()
344
+
345
+
346
+ def cmd_stats(args: argparse.Namespace) -> int:
347
+ """Audit trail metrics over a window — dogfood weekly counts."""
348
+ data = _api_get_json(args.port, "/api/v1/audit/stats", "stats", {"days": args.days})
349
+ if data is None:
325
350
  return 1
326
351
 
327
352
  if not data.get("enabled", True):
@@ -349,13 +374,10 @@ def cmd_stats(args: argparse.Namespace) -> int:
349
374
 
350
375
 
351
376
  def cmd_detections(args: argparse.Namespace) -> int:
352
- try:
353
- data = httpx.get(
354
- f"{_base_url(args.port)}/api/v1/audit/detections/{args.correlation_id}",
355
- timeout=10.0,
356
- ).json()
357
- except Exception:
358
- print("Not running - start Agentmetry first (detections reads via the API).")
377
+ data = _api_get_json(
378
+ args.port, f"/api/v1/audit/detections/{args.correlation_id}", "detections"
379
+ )
380
+ if data is None:
359
381
  return 1
360
382
  if not data.get("enabled", True):
361
383
  print("Audit export is disabled, so no sessions are recorded.")
@@ -595,6 +617,39 @@ _HOOK_INSTALLERS = ("cursor", "claude", "codex", "qwen", "kimi", "qoder", "codeb
595
617
 
596
618
 
597
619
 
620
+ def _cloud_cursor_cache(args: argparse.Namespace) -> int:
621
+ """Record Cursor cloud-agent tool calls from the desktop app's local cache.
622
+
623
+ No vendor key and no network call to Cursor: the calls are read from the
624
+ state database the Cursor app keeps on this machine, read-only. See
625
+ `core/audit/cursor_cloud_cache.py` for what that cache does and does not hold.
626
+ """
627
+ from agentmetry.core.audit.cursor_cloud_cache import STATE_FILE, default_db_path, import_cache
628
+ from agentmetry.core.paths import data_dir
629
+ from agentmetry.hooks.ingest import post_ingest
630
+
631
+ db = Path(args.db).expanduser() if args.db else default_db_path()
632
+ if db is None or not db.is_file():
633
+ print(f"No Cursor state database at {db}. Is the Cursor desktop app installed here? (--db to point at one)")
634
+ return 2
635
+
636
+ def post(payload: dict) -> bool:
637
+ # No spool: a call that did not land is simply not marked as recorded,
638
+ # and the next run picks it up from the cache again.
639
+ return post_ingest(payload, quiet=True, spool=False)
640
+
641
+ result = import_cache(db, post=post, state_path=data_dir() / STATE_FILE, dry_run=args.dry_run)
642
+ verb = "would record" if args.dry_run else "recorded"
643
+ count = result.new if args.dry_run else result.posted
644
+ print(f"cursor-cache: {result.agents} cloud agent conversation(s), {result.calls} finished tool call(s).")
645
+ print(f" {verb} {count}, already recorded {result.already_seen}, failed {result.failed}.")
646
+ if result.skipped:
647
+ print(f" skipped: {dict(result.skipped)}")
648
+ if result.tools:
649
+ print(" by tool: " + ", ".join(f"{name} {n}" for name, n in result.tools.most_common()))
650
+ return 1 if result.failed else 0
651
+
652
+
598
653
  def cmd_cloud(args: argparse.Namespace) -> int:
599
654
  """Poll a vendor cloud-agent API once and record what it exposes.
600
655
 
@@ -605,6 +660,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
605
660
  """
606
661
  import os
607
662
 
663
+ if args.provider == "cursor-cache":
664
+ return _cloud_cursor_cache(args)
665
+
608
666
  import httpx
609
667
 
610
668
  from agentmetry.core.audit.cloud_bridge import (
@@ -1624,10 +1682,13 @@ def main(argv: list[str] | None = None) -> int:
1624
1682
  stats = sub.add_parser("stats", help="audit trail metrics for dogfood (events, detections)")
1625
1683
  stats.add_argument("--days", type=int, default=7)
1626
1684
  cloud = sub.add_parser("cloud", help="record what a vendor cloud-agent API exposes (Cursor, Claude)")
1627
- cloud.add_argument("provider", choices=("cursor", "claude"))
1685
+ cloud.add_argument("provider", choices=("cursor", "claude", "cursor-cache"))
1686
+ cloud.add_argument("--db", default="", help="cursor-cache: Cursor state database (default: the desktop app's)")
1687
+ cloud.add_argument("--dry-run", action="store_true", help="cursor-cache: count what would be recorded, post nothing")
1628
1688
  cloud.add_argument("--url", default="", help="vendor API base override")
1629
1689
  cloud.add_argument("--limit", type=int, default=10, help="max sessions/agents per poll")
1630
1690
  cloud.add_argument("--skip-runs", action="store_true", help="cursor: agents only, skip per-run fetch")
1691
+ cloud.add_argument("--remote", action="store_true", help="claude: the Compliance API's remote sessions instead of local ones (not Claude Code on the web, which that API excludes)")
1631
1692
  detections = sub.add_parser("detections", help="list detections for one session")
1632
1693
  detections.add_argument("correlation_id", help="correlation_id / session id")
1633
1694
  disposition = sub.add_parser("disposition", help="close an audit detection from the shell")
@@ -1738,7 +1799,8 @@ def main(argv: list[str] | None = None) -> int:
1738
1799
  scan.add_argument(
1739
1800
  "target", nargs="?", default=None,
1740
1801
  help="a file or folder (skills, rules, a saved tools/list), npm:NAME or pypi:NAME; "
1741
- "omit to scan the MCP servers configured on this machine",
1802
+ "omit to scan the MCP servers configured on this machine and the agent "
1803
+ "instruction files in Downloads, on the Desktop and in the home folder",
1742
1804
  )
1743
1805
  scan.add_argument("--project", default=None, help="project root whose MCP config to include (default: cwd)")
1744
1806
  scan.add_argument("--known", default=None, help="file of extra known package names, one per line (npm:/pypi: or bare)")
@@ -0,0 +1,305 @@
1
+ """Cursor cloud-agent tool calls, read from the Cursor desktop app's own cache.
2
+
3
+ A cloud agent runs on Cursor's VM, where no hook on this machine fires. When the
4
+ desktop app streams one of those agents, it caches the conversation in its
5
+ local state database (`User/globalStorage/state.vscdb`), and the cache holds
6
+ every tool call the agent made: the terminal commands, the files read and
7
+ edited, searches, web fetches and MCP calls, each with its arguments, status
8
+ and result. Paths in those arguments are under `/workspace`, the cloud VM's
9
+ checkout, so these are calls that ran there, not on this machine.
10
+
11
+ This module reads that cache read-only and turns each completed tool call into
12
+ the payload Cursor's own `postToolUse` hook would have produced, then runs it
13
+ through the hook client's mapper. The cloud events therefore get the same
14
+ argument hashing, command traits and ATT&CK labels as local Cursor events, and
15
+ the same rules fire on them. Nothing else leaves the cache: results are never
16
+ forwarded, only an exit code when the result carries one.
17
+
18
+ ## What this sees, and what it does not
19
+
20
+ * **Only agents the desktop app has streamed.** The cache is filled when an
21
+ agent is opened in the app (`createdFromBackgroundAgent.shouldStreamMessages`).
22
+ An agent nobody opened here leaves nothing to read.
23
+ * **After the fact.** This is a record, never a gate. A call is read once it
24
+ has completed, errored or been cancelled, and nothing here can block one.
25
+ * **An undocumented format.** The layout was read off a real database on
26
+ 2026-10-21: `composerData:<id>` rows carrying `createdFromBackgroundAgent.bcId`,
27
+ and `bubbleId:<id>:<bubble>` rows carrying `toolFormerData`. A row that does
28
+ not match it is skipped and counted, never guessed at.
29
+ * **A claim, not proof.** The cache is a file the user can edit or delete.
30
+ Cursor's own run stream (`cloud_bridge.py`) is the independent view to check
31
+ it against, keyed on the same `cursor-cloud-<bcId>` correlation id.
32
+
33
+ ## Reading safely
34
+
35
+ The database is opened with `mode=ro`, so a running Cursor is never blocked or
36
+ written to. Only rows that name a cloud agent are selected, which keeps a
37
+ multi-gigabyte database cheap to poll. A call already recorded is remembered by
38
+ a digest of its agent and tool-call id in a small state file beside the trail,
39
+ so a second run imports nothing twice.
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import hashlib
45
+ import json
46
+ import os
47
+ import sqlite3
48
+ import sys
49
+ from collections import Counter
50
+ from collections.abc import Callable, Iterator
51
+ from dataclasses import dataclass, field
52
+ from pathlib import Path
53
+ from typing import Any
54
+
55
+ SOURCE_APP = "cursor_cloud"
56
+ ADAPTER = "cursor_cloud_cache"
57
+ STATE_FILE = "cursor-cloud-cache.seen.json"
58
+
59
+ #: Tool names the cloud agent uses, mapped to the names Cursor's local hooks
60
+ #: record, so a rule keyed on `cursor.Read` or `shell.run` fires on both.
61
+ _TOOL_NAMES = {
62
+ "read_file_v2": "Read",
63
+ "edit_file_v2": "Write",
64
+ "delete_file": "Delete",
65
+ "ripgrep_raw_search": "Grep",
66
+ "glob_file_search": "Glob",
67
+ "web_fetch": "WebFetch",
68
+ "web_search": "WebSearch",
69
+ "task_v2": "Task",
70
+ }
71
+ _SHELL_TOOLS = frozenset({"run_terminal_command_v2", "run_terminal_cmd"})
72
+ _FINAL_STATUSES = frozenset({"completed", "error", "cancelled"})
73
+
74
+
75
+ def default_db_path() -> Path | None:
76
+ """Where the Cursor desktop app keeps its state database on this OS."""
77
+ if sys.platform == "win32":
78
+ base = os.environ.get("APPDATA", "")
79
+ root = Path(base) / "Cursor" if base else None
80
+ elif sys.platform == "darwin":
81
+ root = Path.home() / "Library" / "Application Support" / "Cursor"
82
+ else:
83
+ root = Path(os.environ.get("XDG_CONFIG_HOME", "") or Path.home() / ".config") / "Cursor"
84
+ if root is None:
85
+ return None
86
+ return root / "User" / "globalStorage" / "state.vscdb"
87
+
88
+
89
+ @dataclass(frozen=True)
90
+ class CloudToolCall:
91
+ bc_id: str
92
+ tool_call_id: str
93
+ name: str
94
+ status: str
95
+ created_at: str
96
+ args: dict[str, Any]
97
+ exit_code: int | None = None
98
+
99
+
100
+ @dataclass
101
+ class ImportResult:
102
+ agents: int = 0
103
+ calls: int = 0
104
+ new: int = 0
105
+ posted: int = 0
106
+ already_seen: int = 0
107
+ failed: int = 0
108
+ skipped: Counter = field(default_factory=Counter)
109
+ tools: Counter = field(default_factory=Counter)
110
+
111
+
112
+ def open_readonly(db_path: Path) -> sqlite3.Connection:
113
+ return sqlite3.connect(f"file:{Path(db_path).as_posix()}?mode=ro", uri=True, timeout=5)
114
+
115
+
116
+ def _json(value: Any) -> Any:
117
+ if isinstance(value, (dict, list)):
118
+ return value
119
+ if isinstance(value, (bytes, bytearray)):
120
+ value = value.decode("utf-8", errors="replace")
121
+ if isinstance(value, str) and value.strip()[:1] in "{[":
122
+ try:
123
+ return json.loads(value)
124
+ except ValueError:
125
+ return None
126
+ return None
127
+
128
+
129
+ def iter_cloud_composers(con: sqlite3.Connection) -> Iterator[tuple[str, str]]:
130
+ """(composer id, cloud agent id) for every conversation streamed from a cloud agent."""
131
+ rows = con.execute(
132
+ "SELECT key, value FROM cursorDiskKV"
133
+ " WHERE key LIKE 'composerData:%' AND value LIKE '%createdFromBackgroundAgent%'"
134
+ )
135
+ for key, value in rows:
136
+ data = _json(value)
137
+ origin = data.get("createdFromBackgroundAgent") if isinstance(data, dict) else None
138
+ bc_id = origin.get("bcId") if isinstance(origin, dict) else None
139
+ if isinstance(bc_id, str) and bc_id:
140
+ yield str(key).split(":", 1)[1], bc_id
141
+
142
+
143
+ def _exit_code(result: Any) -> int | None:
144
+ data = _json(result)
145
+ code = data.get("exitCode") if isinstance(data, dict) else None
146
+ return code if isinstance(code, int) and not isinstance(code, bool) else None
147
+
148
+
149
+ def iter_tool_calls(
150
+ con: sqlite3.Connection, composer_id: str, bc_id: str, skipped: Counter | None = None
151
+ ) -> Iterator[CloudToolCall]:
152
+ """Every finished tool call in one cloud agent's cached conversation."""
153
+ skipped = skipped if skipped is not None else Counter()
154
+ rows = con.execute(
155
+ "SELECT value FROM cursorDiskKV WHERE key LIKE ?", (f"bubbleId:{composer_id}:%",)
156
+ )
157
+ for (value,) in rows:
158
+ bubble = _json(value)
159
+ tool = bubble.get("toolFormerData") if isinstance(bubble, dict) else None
160
+ if not isinstance(tool, dict):
161
+ continue
162
+ name = tool.get("name")
163
+ call_id = tool.get("toolCallId")
164
+ status = str(tool.get("status") or "")
165
+ if not isinstance(name, str) or not name or not isinstance(call_id, str) or not call_id:
166
+ skipped["unrecognised_row"] += 1
167
+ continue
168
+ if status not in _FINAL_STATUSES:
169
+ skipped["not_finished"] += 1
170
+ continue
171
+ args = _json(tool.get("params"))
172
+ if not isinstance(args, dict):
173
+ args = _json(tool.get("rawArgs"))
174
+ yield CloudToolCall(
175
+ bc_id=bc_id,
176
+ tool_call_id=call_id,
177
+ name=name,
178
+ status=status,
179
+ created_at=str(bubble.get("createdAt") or ""),
180
+ args=args if isinstance(args, dict) else {},
181
+ exit_code=_exit_code(tool.get("result")) if name in _SHELL_TOOLS else None,
182
+ )
183
+
184
+
185
+ def _mcp_tool(name: str) -> str:
186
+ """`mcp-<server>-<tool>` to `MCP:<tool>`, the name the local hooks record.
187
+
188
+ Server names contain dashes too, so only the last segment is taken as the
189
+ tool. A tool whose own name has a dash keeps only its tail.
190
+ """
191
+ tail = name.rsplit("-", 1)[-1]
192
+ return f"MCP:{tail or 'unknown'}"
193
+
194
+
195
+ def hook_event(call: CloudToolCall) -> tuple[str, dict[str, Any]]:
196
+ """The (hook name, payload) Cursor's local hooks would have sent for this call."""
197
+ data: dict[str, Any] = {"conversation_id": call.bc_id}
198
+ if call.status == "error":
199
+ data["error"] = "tool_error"
200
+ elif call.status == "cancelled":
201
+ data["error"] = "cancelled"
202
+ if call.exit_code is not None:
203
+ data["exit_code"] = call.exit_code
204
+
205
+ if call.name in _SHELL_TOOLS:
206
+ command = call.args.get("command")
207
+ data["command"] = command if isinstance(command, str) else ""
208
+ return "afterShellExecution", data
209
+
210
+ if call.name.startswith("mcp-"):
211
+ tool_name = _mcp_tool(call.name)
212
+ tool_input = dict(call.args)
213
+ else:
214
+ tool_name = _TOOL_NAMES.get(call.name, call.name.removesuffix("_v2"))
215
+ tool_input = dict(call.args)
216
+ path = call.args.get("path") or call.args.get("targetFile") or call.args.get("relativeWorkspacePath")
217
+ if tool_name in ("Read", "Write", "Delete") and isinstance(path, str):
218
+ # Only the path, as the local hooks send it. Edit bodies stay behind.
219
+ tool_input = {"path": path}
220
+ data["tool_name"] = tool_name
221
+ data["tool_input"] = tool_input
222
+ return "postToolUse", data
223
+
224
+
225
+ def to_payload(call: CloudToolCall) -> dict[str, Any] | None:
226
+ """One cached cloud tool call as an external-ingest payload, hashed in-process."""
227
+ from agentmetry.hooks.ingest import _hash_tool_args, map_cursor_hook
228
+
229
+ hook_name, data = hook_event(call)
230
+ payload = _hash_tool_args(map_cursor_hook(hook_name, data))
231
+ if not payload:
232
+ return None
233
+ payload["source_app"] = SOURCE_APP
234
+ payload["adapter"] = ADAPTER
235
+ payload["correlation_id"] = f"cursor-cloud-{call.bc_id}"
236
+ payload["session_id"] = call.bc_id
237
+ if call.created_at:
238
+ payload["timestamp_utc"] = call.created_at
239
+ if call.status == "cancelled":
240
+ payload["reason"] = "cancelled"
241
+ payload.setdefault("initiator", {})["actor_type"] = "agent"
242
+ return payload
243
+
244
+
245
+ def _seen_key(call: CloudToolCall) -> str:
246
+ return hashlib.sha256(f"{call.bc_id}\n{call.tool_call_id}".encode()).hexdigest()[:32]
247
+
248
+
249
+ def _load_seen(path: Path) -> set[str]:
250
+ try:
251
+ data = json.loads(path.read_text(encoding="utf-8"))
252
+ except (OSError, ValueError):
253
+ return set()
254
+ return set(data.get("seen", [])) if isinstance(data, dict) else set()
255
+
256
+
257
+ def _save_seen(path: Path, seen: set[str]) -> None:
258
+ path.parent.mkdir(parents=True, exist_ok=True)
259
+ tmp = path.with_suffix(path.suffix + ".tmp")
260
+ tmp.write_text(json.dumps({"v": 1, "seen": sorted(seen)}), encoding="utf-8")
261
+ os.replace(tmp, path)
262
+
263
+
264
+ def import_cache(
265
+ db_path: Path,
266
+ *,
267
+ post: Callable[[dict[str, Any]], bool],
268
+ state_path: Path,
269
+ dry_run: bool = False,
270
+ ) -> ImportResult:
271
+ """Read every cached cloud tool call not yet recorded, and post it.
272
+
273
+ A call is remembered only after `post` accepted it, so a run that fails
274
+ half way resumes where it stopped instead of skipping the rest.
275
+ """
276
+ result = ImportResult()
277
+ seen = _load_seen(state_path)
278
+ con = open_readonly(db_path)
279
+ try:
280
+ for composer_id, bc_id in iter_cloud_composers(con):
281
+ result.agents += 1
282
+ for call in iter_tool_calls(con, composer_id, bc_id, result.skipped):
283
+ result.calls += 1
284
+ result.tools[call.name] += 1
285
+ key = _seen_key(call)
286
+ if key in seen:
287
+ result.already_seen += 1
288
+ continue
289
+ payload = to_payload(call)
290
+ if payload is None:
291
+ result.skipped["unmapped"] += 1
292
+ continue
293
+ result.new += 1
294
+ if dry_run:
295
+ continue
296
+ if post(payload):
297
+ seen.add(key)
298
+ result.posted += 1
299
+ else:
300
+ result.failed += 1
301
+ finally:
302
+ con.close()
303
+ if not dry_run:
304
+ _save_seen(state_path, seen)
305
+ return result
@@ -98,6 +98,14 @@ def _extract_command(hook_data: dict[str, Any] | str, qualified: str = "") -> st
98
98
  val = hook_data.get("value")
99
99
  if val is not None and str(val).strip():
100
100
  return str(val)
101
+ # Codex's apply_patch carries its target paths inside the patch text, not
102
+ # under a path key, so a config-write rule needs the patch itself.
103
+ if q.endswith("apply_patch"):
104
+ for container in (hook_data, *nested):
105
+ for key in ("patch", "input"):
106
+ val = container.get(key)
107
+ if isinstance(val, str) and val.strip():
108
+ return val
101
109
 
102
110
  # Path fallback: lets a rule deny writes to agent-execution config.
103
111
  for container in (hook_data, *nested):
@@ -250,11 +250,20 @@ def _check_hooks_installed(report: DoctorReport) -> None:
250
250
  # health checks' answer, not this one's.
251
251
  report.ok("hooks", f"Hooks installed for: {', '.join(covered)}")
252
252
  if uncovered:
253
+ # It used to name scripts/install_<agent>_hooks.ps1, which exist only in a
254
+ # git checkout: a pip install was told to run files it does not have.
255
+ # `hooks install` writes every agent's hooks except Antigravity's.
256
+ fix = "Run `agentmetry hooks install`."
257
+ if "antigravity" in uncovered:
258
+ fix += (
259
+ " Antigravity's hooks come from scripts/install_antigravity_hooks.ps1 "
260
+ "in a clone of the repository."
261
+ )
253
262
  report.warn(
254
263
  "hooks",
255
264
  f"Installed on this machine but NOT recorded: {', '.join(uncovered)}. "
256
- "Run scripts/install_<agent>_hooks.ps1 for each. Codex additionally "
257
- "needs its /hooks trust prompt approved, or it skips them silently.",
265
+ f"{fix} Codex additionally needs its /hooks trust prompt approved, or it "
266
+ "skips them silently.",
258
267
  )
259
268
  if unverified:
260
269
  report.warn(