agentmetry 0.9.6__tar.gz → 0.9.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentmetry-0.9.6 → agentmetry-0.9.8}/PKG-INFO +1 -1
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/api/main.py +33 -8
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/api/routes/audit.py +38 -2
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/cli/__init__.py +108 -0
- agentmetry-0.9.8/agentmetry/core/audit/antigravity_trajectory.py +81 -0
- agentmetry-0.9.8/agentmetry/core/audit/cloud_bridge.py +341 -0
- agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl +3 -0
- agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl +4 -0
- agentmetry-0.9.8/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl +4 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/corpus.yaml +28 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/evidence_pack.py +65 -8
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/external.py +4 -2
- agentmetry-0.9.8/agentmetry/core/audit/grok_bot_transcript.py +58 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/hook_bootstrap.py +30 -0
- agentmetry-0.9.8/agentmetry/core/audit/session_tree.py +190 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/trail_db.py +76 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/doctor.py +49 -4
- agentmetry-0.9.8/agentmetry/core/diagnostics/machine_view.py +101 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/version.py +1 -1
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/demo.py +1 -1
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/hooks/ingest.py +10 -1
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_agentmetry_ingest_client.py +43 -0
- agentmetry-0.9.8/tests/test_antigravity_trajectory.py +35 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_cli_commands.py +7 -9
- agentmetry-0.9.8/tests/test_cloud_bridge.py +163 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_copilot_hooks.py +8 -2
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_doctor.py +78 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_evidence_pack.py +77 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_external_ingest.py +15 -0
- agentmetry-0.9.8/tests/test_grok_bot_transcript.py +44 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_hook_bootstrap.py +59 -0
- agentmetry-0.9.8/tests/test_machine_view.py +111 -0
- agentmetry-0.9.8/tests/test_session_tree.py +188 -0
- agentmetry-0.9.8/tests/test_transcript_watcher.py +162 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/.dockerignore +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/.env.agentmetry-demo +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/.env.example +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/.gitignore +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/Dockerfile +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/README.md +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/api/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/api/routes/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/api/routes/auth.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/api/trusted_host.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/api/websocket.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/api/ws_bridge.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/cli/__main__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/api_token.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/agt.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/chronicle.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/ecs.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/sentinel.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/adapters/splunk.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/alerts.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/atlas.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/canonical.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/compliance_digest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/benchmark.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/disposition.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/engine.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/live.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/live_store.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/models.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/rules.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/traits.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/yaml_config.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/loader.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/models.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/dlp/scanner.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/dogfood.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/forwarder.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/hashing.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/heartbeat.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/identity.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/ingest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/managed_hooks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/migrate.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/mitre.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/otel_ingest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/policy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/policy_settings.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/posture_event.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/redaction.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/replay.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/run_context.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/signing.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/sinks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/spool.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/capability.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/loader.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/tool_policy/models.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/trail_anchor.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/trail_chain.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/trail_merkle.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/audit/trail_rotation.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/auth.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/bus/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/bus/audit_exporter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/bus/bridges.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/bus/bus.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/bus/events.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/bus/outbox.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/config.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/dashboard_session.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/autostart.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/driver_paths.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/env_file.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/hook_coverage.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/mcp_inventory.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/diagnostics/posture.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/drivers/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/drivers/host.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/drivers/permissions.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/drivers/spec.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/extensions.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/health.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/operator_identity.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/core/paths.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/hooks/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/hooks/mcp_proxy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/policies/detection/manifest.yaml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/policies/dlp/manifest.yaml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/policies/opa/agent_rules.rego +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/agentmetry/policies/tool/manifest.yaml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/pyproject.toml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/conftest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/README.md +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/fixtures/fake_mcp_server.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/fixtures/otel_claude_code_session.json +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_agentmetry_audit.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_agt_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_ask_gate_phase_a.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_atlas_detection_enrichment.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_atlas_mapping.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_audit_sinks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_audit_stats.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_audit_tail.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_auth.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_auth_default.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_autostart.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_boot_sequence.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_burst_windows.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_chinese_agent_hooks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_chinese_agent_sprint_b.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_chinese_agent_sprint_c.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_chronicle_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_cli_backup.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_cli_env_var_names.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_cloudevents_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_compliance_digest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_container_and_legacy_runtime.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_crewai_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_demo.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_adi.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_benchmark.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_default_config.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_disposition.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_disposition_api.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_duplicate_emission.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_emit_durability.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_engine.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_evasion.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_hf_incident.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_quoted_command_words.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_rule_identity.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_detection_rules_v2.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_dlp_agent_env_override.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_dlp_invisible_unicode.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_dlp_markdown_exfil.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_dlp_scanner.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_dogfood.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_dogfood_freeze.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_driver_paths.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_ecs_threat.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_event_bus.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_event_signing.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_extensions.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_forwarder.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_forwarding_shape.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_funding_manifest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_hash_key.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_heartbeat.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_hook_coverage.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_hook_decision_format.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_hook_enforcement_timing.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_hook_import_cost.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_hook_real_process.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_hook_spool.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_identity_enrich.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_identity_provider.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_ingest_roundtrip.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_isolation.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_launch_targets.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_live_detection.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_live_detection_store.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_managed_hooks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_mcp_audit_proxy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_mcp_concealed_text.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_mcp_inventory.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_mcp_inventory_event.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_mcp_meta_fingerprint.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_mcp_proxy_packaged.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_mcp_schema.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_mitre_dlp.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_opensre_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_operator_attribution.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_otel_ingest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_paths.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_posture.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_powershell_encoding.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_readme_claims.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_redaction.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_replay.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_sentinel.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_serve_script_binds_loopback.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_shared_install.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_sigma_pack.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_static_serving.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_subagent_lifecycle.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_suite_leaves_real_home_alone.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_tool_policy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_tool_policy_agent_cli.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_tool_policy_git_hooks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_trail_anchor.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_trail_chain.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_trail_concurrency.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_trail_merkle.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_trusted_host.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_unattended_agent_policy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_version.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tests/test_yaml_detection_rules.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tools/generate_sigma_pack.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tools/mcp_audit_proxy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.8}/tools/vault_fs_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentmetry
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.8
|
|
4
4
|
Summary: Local-first flight recorder for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
|
|
5
5
|
Project-URL: Homepage, https://agentmetry.ai
|
|
6
6
|
Project-URL: Source, https://github.com/blitzcrieg1/agentmetry
|
|
@@ -2,6 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
5
8
|
from contextlib import asynccontextmanager
|
|
6
9
|
from logging.handlers import RotatingFileHandler
|
|
7
10
|
from pathlib import Path
|
|
@@ -76,6 +79,34 @@ def _start_forwarders() -> list[asyncio.Task]:
|
|
|
76
79
|
return tasks
|
|
77
80
|
|
|
78
81
|
|
|
82
|
+
def start_transcript_watcher(watcher_path: Path) -> subprocess.Popen:
|
|
83
|
+
"""Start the Antigravity transcript watcher, tied to this process's life.
|
|
84
|
+
|
|
85
|
+
Its stdin is a pipe only this process holds, and the lifeline variable
|
|
86
|
+
tells it to exit at end-of-file. When this process dies, however it dies,
|
|
87
|
+
the pipe closes and the watcher follows. Without it each restart left a
|
|
88
|
+
watcher behind, and every one of them recorded every Antigravity call.
|
|
89
|
+
"""
|
|
90
|
+
return subprocess.Popen(
|
|
91
|
+
[sys.executable, str(watcher_path)],
|
|
92
|
+
stdin=subprocess.PIPE,
|
|
93
|
+
stdout=subprocess.DEVNULL,
|
|
94
|
+
stderr=subprocess.DEVNULL,
|
|
95
|
+
env={**os.environ, "AGENTMETRY_WATCHER_LIFELINE": "1"},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def stop_transcript_watcher(process: subprocess.Popen) -> None:
|
|
100
|
+
"""Close the lifeline, then terminate. Closing alone is enough; terminate is
|
|
101
|
+
for a watcher that predates the lifeline."""
|
|
102
|
+
try:
|
|
103
|
+
if process.stdin:
|
|
104
|
+
process.stdin.close()
|
|
105
|
+
except OSError:
|
|
106
|
+
pass
|
|
107
|
+
process.terminate()
|
|
108
|
+
|
|
109
|
+
|
|
79
110
|
@asynccontextmanager
|
|
80
111
|
async def lifespan(app: FastAPI):
|
|
81
112
|
from agentmetry.core.audit.migrate import backfill_db_from_jsonl
|
|
@@ -177,17 +208,11 @@ async def lifespan(app: FastAPI):
|
|
|
177
208
|
)
|
|
178
209
|
|
|
179
210
|
# Launch transcript watcher for Antigravity in the background
|
|
180
|
-
import subprocess
|
|
181
|
-
import sys
|
|
182
211
|
watcher_process = None
|
|
183
212
|
try:
|
|
184
213
|
watcher_path = Path(__file__).resolve().parents[4] / "scripts" / "antigravity_transcript_watcher.py"
|
|
185
214
|
if watcher_path.exists():
|
|
186
|
-
watcher_process =
|
|
187
|
-
[sys.executable, str(watcher_path)],
|
|
188
|
-
stdout=subprocess.DEVNULL,
|
|
189
|
-
stderr=subprocess.DEVNULL,
|
|
190
|
-
)
|
|
215
|
+
watcher_process = start_transcript_watcher(watcher_path)
|
|
191
216
|
logger.info("Started Antigravity transcript watcher (PID: %s)", watcher_process.pid)
|
|
192
217
|
except Exception as exc:
|
|
193
218
|
logger.warning("Failed to start Antigravity transcript watcher: %s", exc)
|
|
@@ -201,7 +226,7 @@ async def lifespan(app: FastAPI):
|
|
|
201
226
|
for task in bridge_tasks:
|
|
202
227
|
task.cancel()
|
|
203
228
|
if watcher_process:
|
|
204
|
-
watcher_process
|
|
229
|
+
stop_transcript_watcher(watcher_process)
|
|
205
230
|
|
|
206
231
|
|
|
207
232
|
app = FastAPI(
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
from datetime import datetime, timedelta, timezone
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, Literal
|
|
@@ -100,7 +101,7 @@ class ExternalIngestBody(BaseModel):
|
|
|
100
101
|
|
|
101
102
|
source_app: str = Field(
|
|
102
103
|
...,
|
|
103
|
-
description="cursor | claude | antigravity | codex | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre",
|
|
104
|
+
description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre | zcode | grok_bot",
|
|
104
105
|
)
|
|
105
106
|
event_type: str = Field(
|
|
106
107
|
...,
|
|
@@ -228,7 +229,7 @@ async def audit_tail(
|
|
|
228
229
|
),
|
|
229
230
|
sources: str | None = Query(
|
|
230
231
|
None,
|
|
231
|
-
description="Comma-separated source
|
|
232
|
+
description="Comma-separated source.app values. Known names include cursor, claude, codex, copilot, antigravity, qwen, kimi, qoder, codebuddy, mcp_proxy, trae, crewai, opensre",
|
|
232
233
|
),
|
|
233
234
|
since_minutes: int | None = Query(
|
|
234
235
|
None,
|
|
@@ -308,6 +309,29 @@ async def audit_session(
|
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
|
|
312
|
+
@router.get("/session/{correlation_id}/tree", dependencies=[Depends(require_api_key)])
|
|
313
|
+
async def audit_session_tree(
|
|
314
|
+
correlation_id: str,
|
|
315
|
+
limit: int = Query(20000, ge=1, le=50000),
|
|
316
|
+
around_utc: str = Query("", max_length=40),
|
|
317
|
+
):
|
|
318
|
+
"""One session folded into investigation-tree nodes.
|
|
319
|
+
|
|
320
|
+
Each ask, its answer and the call it led to become one node, so a detection
|
|
321
|
+
that cites the ask and a policy verdict recorded on it land on the call
|
|
322
|
+
they belong to. A session longer than `limit` is windowed on `around_utc`
|
|
323
|
+
(a detection's first_seen_utc) when given. See `core/audit/session_tree.py`.
|
|
324
|
+
"""
|
|
325
|
+
from agentmetry.core.audit.session_tree import session_tree
|
|
326
|
+
|
|
327
|
+
if not settings.audit_export_enabled:
|
|
328
|
+
return {"correlation_id": correlation_id, "enabled": False, "nodes": [], "summary": {}, "truncated": False}
|
|
329
|
+
try:
|
|
330
|
+
return await asyncio.to_thread(session_tree, correlation_id, limit, around_utc)
|
|
331
|
+
except Exception as exc:
|
|
332
|
+
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
333
|
+
|
|
334
|
+
|
|
311
335
|
@router.get("/detections/{correlation_id}", dependencies=[Depends(require_api_key)])
|
|
312
336
|
async def audit_detections(correlation_id: str):
|
|
313
337
|
"""Run correlated behavioral rules over one session and return detections.
|
|
@@ -452,6 +476,18 @@ async def audit_export_evidence():
|
|
|
452
476
|
)
|
|
453
477
|
|
|
454
478
|
|
|
479
|
+
@router.get("/machine", dependencies=[Depends(require_api_key)])
|
|
480
|
+
async def machine_view():
|
|
481
|
+
"""This machine's agents, posture, MCP servers and tool policy.
|
|
482
|
+
|
|
483
|
+
Read-only. The payload is the local dashboard's "This machine" page. It
|
|
484
|
+
carries no command lines, paths, or secret values.
|
|
485
|
+
"""
|
|
486
|
+
from agentmetry.core.diagnostics.machine_view import snapshot
|
|
487
|
+
|
|
488
|
+
return await asyncio.to_thread(snapshot)
|
|
489
|
+
|
|
490
|
+
|
|
455
491
|
@router.get("/status", dependencies=[Depends(require_api_key)])
|
|
456
492
|
async def audit_status():
|
|
457
493
|
"""Freshness + per-source counts so the dashboard can show 'last event N min ago'.
|
|
@@ -529,6 +529,93 @@ def cmd_install(args: argparse.Namespace) -> int:
|
|
|
529
529
|
_HOOK_INSTALLERS = ("cursor", "claude", "codex", "qwen", "kimi", "qoder", "codebuddy", "copilot")
|
|
530
530
|
|
|
531
531
|
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def cmd_cloud(args: argparse.Namespace) -> int:
|
|
535
|
+
"""Poll a vendor cloud-agent API once and record what it exposes.
|
|
536
|
+
|
|
537
|
+
Everything this records passes through the same external ingest as the
|
|
538
|
+
hooks: canonicalized server-side, hash-chained, detected against. The
|
|
539
|
+
key comes from the environment (CURSOR_API_KEY, ANTHROPIC_COMPLIANCE_KEY)
|
|
540
|
+
and is never logged.
|
|
541
|
+
"""
|
|
542
|
+
import os
|
|
543
|
+
|
|
544
|
+
import httpx
|
|
545
|
+
|
|
546
|
+
from agentmetry.core.audit.cloud_bridge import (
|
|
547
|
+
fetch_claude_messages,
|
|
548
|
+
fetch_claude_sessions,
|
|
549
|
+
fetch_cursor_agents,
|
|
550
|
+
fetch_cursor_runs,
|
|
551
|
+
map_claude_messages,
|
|
552
|
+
map_claude_session,
|
|
553
|
+
map_cursor_agent,
|
|
554
|
+
map_outbound_poll,
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
base = (args.url or "").strip() or None
|
|
558
|
+
posted = 0
|
|
559
|
+
|
|
560
|
+
def post(payload: dict) -> bool:
|
|
561
|
+
nonlocal posted
|
|
562
|
+
url = os.environ.get("AGENTMETRY_URL", "http://127.0.0.1:8000").rstrip("/")
|
|
563
|
+
key = os.environ.get("AGENTMETRY_API_KEY", "")
|
|
564
|
+
headers = {"Content-Type": "application/json"}
|
|
565
|
+
if key:
|
|
566
|
+
headers["Authorization"] = f"Bearer {key}"
|
|
567
|
+
resp = httpx.post(f"{url}/api/v1/audit/ingest", json=payload, headers=headers, timeout=15)
|
|
568
|
+
ok = resp.status_code < 300
|
|
569
|
+
if ok:
|
|
570
|
+
posted += 1
|
|
571
|
+
else:
|
|
572
|
+
print(f" ingest refused ({resp.status_code}): {resp.text[:120]}")
|
|
573
|
+
return ok
|
|
574
|
+
|
|
575
|
+
if args.provider == "cursor":
|
|
576
|
+
api_key = os.environ.get("CURSOR_API_KEY", "")
|
|
577
|
+
if not api_key:
|
|
578
|
+
print("CURSOR_API_KEY is not set.")
|
|
579
|
+
return 2
|
|
580
|
+
cursor_base = base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE
|
|
581
|
+
post(map_outbound_poll("cursor_cloud", "GET", f"{cursor_base.rstrip('/')}/v1/agents"))
|
|
582
|
+
for agent in fetch_cursor_agents(api_key, cursor_base):
|
|
583
|
+
runs = []
|
|
584
|
+
if not args.skip_runs:
|
|
585
|
+
try:
|
|
586
|
+
runs = fetch_cursor_runs(str(agent.get("id")), api_key, base or "https://api.cursor.com")
|
|
587
|
+
except httpx.HTTPError as exc:
|
|
588
|
+
print(f" runs fetch failed for {agent.get('id')}: {exc}")
|
|
589
|
+
for payload in map_cursor_agent(agent, runs):
|
|
590
|
+
post(payload)
|
|
591
|
+
print(f"cursor: {posted} events recorded.")
|
|
592
|
+
return 0
|
|
593
|
+
|
|
594
|
+
if args.provider == "claude":
|
|
595
|
+
api_key = os.environ.get("ANTHROPIC_COMPLIANCE_KEY", "")
|
|
596
|
+
if not api_key:
|
|
597
|
+
print("ANTHROPIC_COMPLIANCE_KEY is not set (a Compliance Access Key with read:compliance_user_data, not an admin key).")
|
|
598
|
+
return 2
|
|
599
|
+
kind = "remote" if args.remote else "local"
|
|
600
|
+
claude_base = base or "https://api.anthropic.com"
|
|
601
|
+
post(map_outbound_poll("claude_cloud", "GET", f"{claude_base.rstrip('/')}/v1/compliance/apps/sessions/{kind}"))
|
|
602
|
+
sessions = fetch_claude_sessions(api_key, kind=kind, base=claude_base, limit=args.limit)
|
|
603
|
+
for session in sessions[: args.limit]:
|
|
604
|
+
post(map_claude_session(session))
|
|
605
|
+
try:
|
|
606
|
+
messages = fetch_claude_messages(api_key, kind, str(session.get("id")), base or "https://api.anthropic.com")
|
|
607
|
+
except httpx.HTTPError as exc:
|
|
608
|
+
print(f" messages fetch failed for {session.get('id')}: {exc}")
|
|
609
|
+
continue
|
|
610
|
+
for payload in map_claude_messages(session, messages):
|
|
611
|
+
post(payload)
|
|
612
|
+
print(f"claude ({kind}): {posted} events recorded.")
|
|
613
|
+
return 0
|
|
614
|
+
|
|
615
|
+
print(f"unknown provider: {args.provider}")
|
|
616
|
+
return 2
|
|
617
|
+
|
|
618
|
+
|
|
532
619
|
def _hooks_status() -> int:
|
|
533
620
|
"""Coverage as an exit code, for a deployment tool rather than a person.
|
|
534
621
|
|
|
@@ -581,6 +668,21 @@ def cmd_hooks(args: argparse.Namespace) -> int:
|
|
|
581
668
|
from agentmetry.core.audit import hook_bootstrap
|
|
582
669
|
from agentmetry.core.diagnostics import hook_coverage
|
|
583
670
|
|
|
671
|
+
if args.action == "install":
|
|
672
|
+
# Before --managed too: a managed install would put the temporary path
|
|
673
|
+
# into machine-wide policy files.
|
|
674
|
+
reason = hook_bootstrap.ephemeral_environment()
|
|
675
|
+
if reason:
|
|
676
|
+
print(
|
|
677
|
+
f"Not installing hooks: {reason}. That cache can be deleted at any "
|
|
678
|
+
"time, and every hook would then fail (most agents silently stop "
|
|
679
|
+
"recording; Copilot CLI blocks every tool call). Install Agentmetry "
|
|
680
|
+
"somewhere that stays, then run this again:\n"
|
|
681
|
+
" uv tool install agentmetry (or: pipx install agentmetry, or: pip install agentmetry)\n"
|
|
682
|
+
" agentmetry hooks install",
|
|
683
|
+
file=sys.stderr,
|
|
684
|
+
)
|
|
685
|
+
return 1
|
|
584
686
|
if getattr(args, "managed", False):
|
|
585
687
|
return _hooks_managed(args)
|
|
586
688
|
if args.action == "status":
|
|
@@ -1419,6 +1521,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1419
1521
|
sub.add_parser("status", help="orchestrator health and audit export status")
|
|
1420
1522
|
stats = sub.add_parser("stats", help="audit trail metrics for dogfood (events, detections)")
|
|
1421
1523
|
stats.add_argument("--days", type=int, default=7)
|
|
1524
|
+
cloud = sub.add_parser("cloud", help="record what a vendor cloud-agent API exposes (Cursor, Claude)")
|
|
1525
|
+
cloud.add_argument("provider", choices=("cursor", "claude"))
|
|
1526
|
+
cloud.add_argument("--url", default="", help="vendor API base override")
|
|
1527
|
+
cloud.add_argument("--limit", type=int, default=10, help="max sessions/agents per poll")
|
|
1528
|
+
cloud.add_argument("--skip-runs", action="store_true", help="cursor: agents only, skip per-run fetch")
|
|
1422
1529
|
detections = sub.add_parser("detections", help="list detections for one session")
|
|
1423
1530
|
detections.add_argument("correlation_id", help="correlation_id / session id")
|
|
1424
1531
|
disposition = sub.add_parser("disposition", help="close an audit detection from the shell")
|
|
@@ -1610,6 +1717,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1610
1717
|
"hook": cmd_hook,
|
|
1611
1718
|
"status": cmd_status,
|
|
1612
1719
|
"stats": cmd_stats,
|
|
1720
|
+
"cloud": cmd_cloud,
|
|
1613
1721
|
"detections": cmd_detections,
|
|
1614
1722
|
"disposition": cmd_disposition,
|
|
1615
1723
|
"logs": cmd_logs,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Read tool calls out of an Antigravity conversation database.
|
|
2
|
+
|
|
3
|
+
The installed hook only matches `run_command`, so file reads, writes, search
|
|
4
|
+
and the other tools never reach the trail. The conversation database stores
|
|
5
|
+
every step. The payload is a protobuf blob. The tool name is a length-prefixed
|
|
6
|
+
ASCII field and the arguments are the JSON object that follows it. That framing
|
|
7
|
+
was read off a real database on 2026-10-16. A blob that does not match it
|
|
8
|
+
yields nothing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sqlite3
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Iterator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _varint(buf: bytes, index: int) -> tuple[int, int]:
|
|
20
|
+
value = 0
|
|
21
|
+
shift = 0
|
|
22
|
+
while index < len(buf) and shift <= 28:
|
|
23
|
+
byte = buf[index]
|
|
24
|
+
index += 1
|
|
25
|
+
value |= (byte & 0x7F) << shift
|
|
26
|
+
if not byte & 0x80:
|
|
27
|
+
return value, index
|
|
28
|
+
shift += 7
|
|
29
|
+
raise ValueError("truncated varint")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def iter_tool_calls(payload: bytes) -> Iterator[tuple[str, dict[str, Any]]]:
|
|
33
|
+
"""Yield (tool name, arguments) from one step payload."""
|
|
34
|
+
index = 0
|
|
35
|
+
while True:
|
|
36
|
+
marker = payload.find(b"\x12", index)
|
|
37
|
+
if marker < 0 or marker + 2 >= len(payload):
|
|
38
|
+
return
|
|
39
|
+
name_len = payload[marker + 1]
|
|
40
|
+
if name_len == 0 or name_len > 64 or name_len & 0x80:
|
|
41
|
+
index = marker + 1
|
|
42
|
+
continue
|
|
43
|
+
name_at = marker + 2
|
|
44
|
+
name_end = name_at + name_len
|
|
45
|
+
if name_end >= len(payload) or payload[name_end] != 0x1A:
|
|
46
|
+
index = marker + 1
|
|
47
|
+
continue
|
|
48
|
+
name = payload[name_at:name_end]
|
|
49
|
+
if not name.isascii() or not all(chr(c).isalnum() or c == ord("_") for c in name):
|
|
50
|
+
index = marker + 1
|
|
51
|
+
continue
|
|
52
|
+
try:
|
|
53
|
+
json_len, json_at = _varint(payload, name_end + 1)
|
|
54
|
+
except ValueError:
|
|
55
|
+
index = marker + 1
|
|
56
|
+
continue
|
|
57
|
+
blob = payload[json_at : json_at + json_len]
|
|
58
|
+
if not blob.startswith(b"{"):
|
|
59
|
+
index = marker + 1
|
|
60
|
+
continue
|
|
61
|
+
try:
|
|
62
|
+
args = json.loads(blob)
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
index = marker + 1
|
|
65
|
+
continue
|
|
66
|
+
if isinstance(args, dict):
|
|
67
|
+
yield name.decode("ascii"), args
|
|
68
|
+
index = json_at + max(json_len, 1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def tool_names(db_path: Path) -> dict[str, int]:
|
|
72
|
+
"""Count tool names in one conversation database. Arguments are not returned."""
|
|
73
|
+
counts: dict[str, int] = {}
|
|
74
|
+
uri = f"file:{Path(db_path).as_posix()}?mode=ro"
|
|
75
|
+
with sqlite3.connect(uri, uri=True) as con:
|
|
76
|
+
for (payload,) in con.execute("SELECT step_payload FROM steps"):
|
|
77
|
+
if not isinstance(payload, (bytes, bytearray)):
|
|
78
|
+
continue
|
|
79
|
+
for name, _args in iter_tool_calls(payload):
|
|
80
|
+
counts[name] = counts.get(name, 0) + 1
|
|
81
|
+
return counts
|