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.
- {agentmetry-0.10.0 → agentmetry-0.10.2}/.gitignore +2 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/PKG-INFO +2 -2
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/main.py +48 -14
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/cli/__init__.py +78 -16
- agentmetry-0.10.2/agentmetry/core/audit/cursor_cloud_cache.py +305 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/evaluator.py +8 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/doctor.py +11 -2
- agentmetry-0.10.2/agentmetry/core/scanner/instruction_files.py +192 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/scan.py +84 -11
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/version.py +1 -1
- agentmetry-0.10.2/agentmetry/dashboard_static/.gitkeep +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/404.html +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/__next.__PAGE__.txt +27 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/__next._full.txt +23 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/__next._tree.txt +5 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/B9YcBHt0iAnRAn2NpdHC2/_buildManifest.js +11 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/B9YcBHt0iAnRAn2NpdHC2/_clientMiddlewareManifest.js +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/B9YcBHt0iAnRAn2NpdHC2/_ssgManifest.js +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/0umf92h1sb77c.js +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/1ix59xuhq-q14.css +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/1q292q_rc4_p6.js +22 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/2-sq6__g8tt5-.js +4 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/2gvu8dyrhibpa.js +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/31qp8jfkw0x2h.js +31 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/3ia-1961js2lo.js +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/3in77zrqn0wml.css +8 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/chunks/turbopack-3-ry682tkyiqk.js +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-mono-latin-400-normal.2lhng2ntocry9.woff +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-mono-latin-400-normal.3xdfs0-p_zi4c.woff2 +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-mono-latin-500-normal.11q3guvgd1r20.woff2 +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-mono-latin-500-normal.1fgk-kmy-32_a.woff +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-400-normal.19i24--p24z3r.woff +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-400-normal.1xf5nwxyfigak.woff2 +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-500-normal.14sc3d7sk_zzh.woff +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-500-normal.2i4a1iuj6lbvw.woff2 +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-600-normal.2ovda_azncp9b.woff2 +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_next/static/media/ibm-plex-sans-latin-600-normal.2wn5p1s7u3utm.woff +0 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_not-found/__next._full.txt +22 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_not-found/__next._not-found.__PAGE__.txt +28 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_not-found/__next._tree.txt +4 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_not-found.html +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/_not-found.txt +22 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/agentmetry-icon-white.svg +4 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/index.html +1 -0
- agentmetry-0.10.2/agentmetry/dashboard_static/index.txt +23 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/policies/tool/manifest.yaml +7 -1
- {agentmetry-0.10.0 → agentmetry-0.10.2}/pyproject.toml +10 -1
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cli_commands.py +54 -1
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cloud_bridge.py +57 -0
- agentmetry-0.10.2/tests/test_cursor_cloud_cache.py +191 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_doctor.py +20 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_scanner.py +118 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_static_serving.py +39 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_tool_policy_agent_cli.py +22 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/.dockerignore +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/.env.agentmetry-demo +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/.env.example +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/Dockerfile +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/README.md +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/routes/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/routes/audit.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/routes/auth.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/trusted_host.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/websocket.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/api/ws_bridge.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/cli/__main__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/api_token.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/agt.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/chronicle.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/ecs.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/sentinel.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/adapters/splunk.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/alerts.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/antigravity_trajectory.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/atlas.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/canonical.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/cloud_bridge.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/compliance_digest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/benchmark.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/corpus/corpus.yaml +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/disposition.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/engine.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/live.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/live_store.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/models.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/rules.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/traits.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/yaml_config.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dlp/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dlp/loader.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dlp/models.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dlp/scanner.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/dogfood.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/evidence_pack.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/external.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/forwarder.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/grok_bot_transcript.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/hashing.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/heartbeat.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/hook_bootstrap.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/identity.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/ingest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/ingest_quota.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/managed_hooks.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/migrate.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/mitre.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/otel_ingest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/policy.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/policy_settings.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/posture_event.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/redaction.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/replay.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/run_context.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/session_tree.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/signing.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/sinks.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/spool.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/capability.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/loader.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/tool_policy/models.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_anchor.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_chain.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_db.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_merkle.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/audit/trail_rotation.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/auth.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/audit_exporter.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/bridges.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/bus.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/events.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/bus/outbox.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/config.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/dashboard_session.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/autostart.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/driver_paths.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/env_file.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/hook_coverage.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/machine_view.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/mcp_inventory.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/diagnostics/posture.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/drivers/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/drivers/host.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/drivers/permissions.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/drivers/spec.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/extensions.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/health.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/managed_policy.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/operator_identity.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/paths.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/injection.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/known_packages.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/core/scanner/typosquat.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/demo.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/hooks/__init__.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/hooks/ingest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/hooks/mcp_proxy.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/policies/detection/manifest.yaml +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/policies/dlp/manifest.yaml +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/agentmetry/policies/opa/agent_rules.rego +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/conftest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/codex_hooks/README.md +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/fake_mcp_server.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/otel_claude_code_session.json +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/scanner/benign_skill/SKILL.md +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/scanner/benign_tools.json +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/scanner/poisoned_skill/SKILL.md +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/fixtures/scanner/poisoned_tools.json +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_agentmetry_audit.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_agentmetry_ingest_client.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_agt_adapter.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_antigravity_trajectory.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ask_gate_phase_a.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_atlas_detection_enrichment.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_atlas_mapping.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_audit_sinks.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_audit_stats.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_audit_tail.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_auth.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_auth_default.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_autostart.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_boot_sequence.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_burst_windows.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_chinese_agent_hooks.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_chinese_agent_sprint_b.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_chinese_agent_sprint_c.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_chronicle_adapter.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cli_backup.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cli_env_var_names.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_cloudevents_adapter.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_codex_windows_command.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_compliance_digest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_container_and_legacy_runtime.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_copilot_hooks.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_coverage_observed.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_crewai_adapter.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_demo.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_demo_isolation.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_demo_mode_flag.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_adi.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_benchmark.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_default_config.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_disposition.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_disposition_api.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_duplicate_emission.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_emit_durability.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_engine.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_evasion.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_hf_incident.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_quoted_command_words.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_rule_identity.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_detection_rules_v2.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dlp_agent_env_override.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dlp_invisible_unicode.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dlp_markdown_exfil.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dlp_scanner.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_doctor_json_and_remediation.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dogfood.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_dogfood_freeze.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_driver_paths.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ecs_threat.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_event_bus.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_event_signing.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_evidence_pack.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_extensions.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_external_ingest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_forwarder.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_forwarding_shape.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_funding_manifest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_grok_bot_transcript.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hash_key.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_heartbeat.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_bootstrap.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_coverage.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_decision_format.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_enforcement_timing.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_import_cost.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_real_process.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_hook_spool.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_identity_enrich.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_identity_provider.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ingest_rate_limit.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ingest_roundtrip.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_ingest_token.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_isolation.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_launch_targets.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_live_detection.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_live_detection_store.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_machine_view.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_managed_hooks.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_managed_policy.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_audit_proxy.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_concealed_text.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_inventory.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_inventory_event.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_meta_fingerprint.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_proxy_packaged.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mcp_schema.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_mitre_dlp.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_opensre_adapter.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_operator_attribution.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_otel_ingest.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_paths.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_posture.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_powershell_encoding.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_readme_claims.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_redaction.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_replay.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_sentinel.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_serve_script_binds_loopback.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_session_tree.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_shared_install.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_sigma_pack.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_subagent_lifecycle.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_suite_leaves_real_home_alone.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_tool_policy.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_tool_policy_git_hooks.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trail_anchor.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trail_chain.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trail_concurrency.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trail_merkle.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_transcript_watcher.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_trusted_host.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_unattended_agent_policy.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_user_profile_dir.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_version.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tests/test_yaml_detection_rules.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tools/generate_sigma_pack.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tools/mcp_audit_proxy.py +0 -0
- {agentmetry-0.10.0 → agentmetry-0.10.2}/tools/vault_fs_server.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentmetry
|
|
3
|
-
Version: 0.10.
|
|
4
|
-
Summary:
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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 <session></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
|
|
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
|
|
332
|
-
|
|
333
|
-
|
|
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
|
|
316
|
-
"""
|
|
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
|
-
|
|
319
|
-
f"{
|
|
320
|
-
params=
|
|
324
|
+
resp = httpx.get(
|
|
325
|
+
f"{_api_base_url(port)}{path}",
|
|
326
|
+
params=params,
|
|
327
|
+
headers=_api_headers(),
|
|
321
328
|
timeout=10.0,
|
|
322
|
-
)
|
|
329
|
+
)
|
|
323
330
|
except Exception:
|
|
324
|
-
print("Not running - start Agentmetry first (
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
"
|
|
257
|
-
"
|
|
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(
|