agentmetry 0.9.6__tar.gz → 0.9.7__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.7}/PKG-INFO +1 -1
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/routes/audit.py +15 -2
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/cli/__init__.py +88 -0
- agentmetry-0.9.7/agentmetry/core/audit/cloud_bridge.py +317 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/evidence_pack.py +65 -8
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/external.py +4 -2
- agentmetry-0.9.7/agentmetry/core/diagnostics/machine_view.py +101 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/version.py +1 -1
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/demo.py +1 -1
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/hooks/ingest.py +10 -1
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_agentmetry_ingest_client.py +43 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_cli_commands.py +7 -9
- agentmetry-0.9.7/tests/test_cloud_bridge.py +149 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_copilot_hooks.py +8 -2
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_evidence_pack.py +77 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_external_ingest.py +15 -0
- agentmetry-0.9.7/tests/test_machine_view.py +111 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/.dockerignore +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/.env.agentmetry-demo +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/.env.example +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/.gitignore +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/Dockerfile +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/README.md +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/main.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/routes/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/routes/auth.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/trusted_host.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/websocket.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/api/ws_bridge.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/cli/__main__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/api_token.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/agt.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/chronicle.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/ecs.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/sentinel.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/adapters/splunk.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/alerts.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/atlas.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/canonical.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/compliance_digest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/benchmark.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/corpus/corpus.yaml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/disposition.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/engine.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/live.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/live_store.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/models.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/rules.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/traits.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/yaml_config.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dlp/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dlp/loader.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dlp/models.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dlp/scanner.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/dogfood.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/forwarder.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/hashing.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/heartbeat.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/hook_bootstrap.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/identity.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/ingest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/managed_hooks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/migrate.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/mitre.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/otel_ingest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/policy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/policy_settings.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/posture_event.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/redaction.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/replay.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/run_context.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/signing.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/sinks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/spool.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/capability.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/loader.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/tool_policy/models.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_anchor.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_chain.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_db.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_merkle.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/audit/trail_rotation.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/auth.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/audit_exporter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/bridges.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/bus.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/events.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/bus/outbox.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/config.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/dashboard_session.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/autostart.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/doctor.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/driver_paths.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/env_file.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/hook_coverage.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/mcp_inventory.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/diagnostics/posture.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/drivers/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/drivers/host.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/drivers/permissions.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/drivers/spec.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/extensions.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/health.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/operator_identity.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/core/paths.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/hooks/__init__.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/hooks/mcp_proxy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/policies/detection/manifest.yaml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/policies/dlp/manifest.yaml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/policies/opa/agent_rules.rego +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/agentmetry/policies/tool/manifest.yaml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/pyproject.toml +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/conftest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/codex_hooks/README.md +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/fake_mcp_server.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/fixtures/otel_claude_code_session.json +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_agentmetry_audit.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_agt_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_ask_gate_phase_a.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_atlas_detection_enrichment.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_atlas_mapping.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_audit_sinks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_audit_stats.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_audit_tail.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_auth.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_auth_default.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_autostart.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_boot_sequence.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_burst_windows.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_chinese_agent_hooks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_chinese_agent_sprint_b.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_chinese_agent_sprint_c.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_chronicle_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_cli_backup.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_cli_env_var_names.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_cloudevents_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_compliance_digest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_container_and_legacy_runtime.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_crewai_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_demo.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_adi.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_benchmark.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_default_config.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_disposition.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_disposition_api.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_duplicate_emission.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_emit_durability.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_engine.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_evasion.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_hf_incident.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_quoted_command_words.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_rule_identity.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_detection_rules_v2.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dlp_agent_env_override.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dlp_invisible_unicode.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dlp_markdown_exfil.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dlp_scanner.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_doctor.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dogfood.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_dogfood_freeze.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_driver_paths.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_ecs_threat.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_event_bus.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_event_signing.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_extensions.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_forwarder.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_forwarding_shape.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_funding_manifest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hash_key.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_heartbeat.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_bootstrap.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_coverage.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_decision_format.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_enforcement_timing.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_import_cost.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_real_process.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_hook_spool.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_identity_enrich.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_identity_provider.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_ingest_roundtrip.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_isolation.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_launch_targets.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_live_detection.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_live_detection_store.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_managed_hooks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_audit_proxy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_concealed_text.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_inventory.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_inventory_event.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_meta_fingerprint.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_proxy_packaged.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mcp_schema.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_mitre_dlp.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_opensre_adapter.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_operator_attribution.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_otel_ingest.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_paths.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_posture.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_powershell_encoding.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_readme_claims.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_redaction.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_replay.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_sentinel.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_serve_script_binds_loopback.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_shared_install.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_sigma_pack.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_static_serving.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_subagent_lifecycle.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_suite_leaves_real_home_alone.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_tool_policy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_tool_policy_agent_cli.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_tool_policy_git_hooks.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trail_anchor.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trail_chain.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trail_concurrency.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trail_merkle.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_trusted_host.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_unattended_agent_policy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_version.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tests/test_yaml_detection_rules.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tools/generate_sigma_pack.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/tools/mcp_audit_proxy.py +0 -0
- {agentmetry-0.9.6 → agentmetry-0.9.7}/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.7
|
|
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,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",
|
|
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,
|
|
@@ -452,6 +453,18 @@ async def audit_export_evidence():
|
|
|
452
453
|
)
|
|
453
454
|
|
|
454
455
|
|
|
456
|
+
@router.get("/machine", dependencies=[Depends(require_api_key)])
|
|
457
|
+
async def machine_view():
|
|
458
|
+
"""This machine's agents, posture, MCP servers and tool policy.
|
|
459
|
+
|
|
460
|
+
Read-only. The payload is the local dashboard's "This machine" page. It
|
|
461
|
+
carries no command lines, paths, or secret values.
|
|
462
|
+
"""
|
|
463
|
+
from agentmetry.core.diagnostics.machine_view import snapshot
|
|
464
|
+
|
|
465
|
+
return await asyncio.to_thread(snapshot)
|
|
466
|
+
|
|
467
|
+
|
|
455
468
|
@router.get("/status", dependencies=[Depends(require_api_key)])
|
|
456
469
|
async def audit_status():
|
|
457
470
|
"""Freshness + per-source counts so the dashboard can show 'last event N min ago'.
|
|
@@ -529,6 +529,88 @@ 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
|
+
)
|
|
555
|
+
|
|
556
|
+
base = (args.url or "").strip() or None
|
|
557
|
+
posted = 0
|
|
558
|
+
|
|
559
|
+
def post(payload: dict) -> bool:
|
|
560
|
+
nonlocal posted
|
|
561
|
+
url = os.environ.get("AGENTMETRY_URL", "http://127.0.0.1:8000").rstrip("/")
|
|
562
|
+
key = os.environ.get("AGENTMETRY_API_KEY", "")
|
|
563
|
+
headers = {"Content-Type": "application/json"}
|
|
564
|
+
if key:
|
|
565
|
+
headers["Authorization"] = f"Bearer {key}"
|
|
566
|
+
resp = httpx.post(f"{url}/api/v1/audit/ingest", json=payload, headers=headers, timeout=15)
|
|
567
|
+
ok = resp.status_code < 300
|
|
568
|
+
if ok:
|
|
569
|
+
posted += 1
|
|
570
|
+
else:
|
|
571
|
+
print(f" ingest refused ({resp.status_code}): {resp.text[:120]}")
|
|
572
|
+
return ok
|
|
573
|
+
|
|
574
|
+
if args.provider == "cursor":
|
|
575
|
+
api_key = os.environ.get("CURSOR_API_KEY", "")
|
|
576
|
+
if not api_key:
|
|
577
|
+
print("CURSOR_API_KEY is not set.")
|
|
578
|
+
return 2
|
|
579
|
+
for agent in fetch_cursor_agents(api_key, base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE):
|
|
580
|
+
runs = []
|
|
581
|
+
if not args.skip_runs:
|
|
582
|
+
try:
|
|
583
|
+
runs = fetch_cursor_runs(str(agent.get("id")), api_key, base or "https://api.cursor.com")
|
|
584
|
+
except httpx.HTTPError as exc:
|
|
585
|
+
print(f" runs fetch failed for {agent.get('id')}: {exc}")
|
|
586
|
+
for payload in map_cursor_agent(agent, runs):
|
|
587
|
+
post(payload)
|
|
588
|
+
print(f"cursor: {posted} events recorded.")
|
|
589
|
+
return 0
|
|
590
|
+
|
|
591
|
+
if args.provider == "claude":
|
|
592
|
+
api_key = os.environ.get("ANTHROPIC_COMPLIANCE_KEY", "")
|
|
593
|
+
if not api_key:
|
|
594
|
+
print("ANTHROPIC_COMPLIANCE_KEY is not set (a Compliance Access Key with read:compliance_user_data, not an admin key).")
|
|
595
|
+
return 2
|
|
596
|
+
kind = "remote" if args.remote else "local"
|
|
597
|
+
sessions = fetch_claude_sessions(api_key, kind=kind, base=base or "https://api.anthropic.com", limit=args.limit)
|
|
598
|
+
for session in sessions[: args.limit]:
|
|
599
|
+
post(map_claude_session(session))
|
|
600
|
+
try:
|
|
601
|
+
messages = fetch_claude_messages(api_key, kind, str(session.get("id")), base or "https://api.anthropic.com")
|
|
602
|
+
except httpx.HTTPError as exc:
|
|
603
|
+
print(f" messages fetch failed for {session.get('id')}: {exc}")
|
|
604
|
+
continue
|
|
605
|
+
for payload in map_claude_messages(session, messages):
|
|
606
|
+
post(payload)
|
|
607
|
+
print(f"claude ({kind}): {posted} events recorded.")
|
|
608
|
+
return 0
|
|
609
|
+
|
|
610
|
+
print(f"unknown provider: {args.provider}")
|
|
611
|
+
return 2
|
|
612
|
+
|
|
613
|
+
|
|
532
614
|
def _hooks_status() -> int:
|
|
533
615
|
"""Coverage as an exit code, for a deployment tool rather than a person.
|
|
534
616
|
|
|
@@ -1419,6 +1501,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1419
1501
|
sub.add_parser("status", help="orchestrator health and audit export status")
|
|
1420
1502
|
stats = sub.add_parser("stats", help="audit trail metrics for dogfood (events, detections)")
|
|
1421
1503
|
stats.add_argument("--days", type=int, default=7)
|
|
1504
|
+
cloud = sub.add_parser("cloud", help="record what a vendor cloud-agent API exposes (Cursor, Claude)")
|
|
1505
|
+
cloud.add_argument("provider", choices=("cursor", "claude"))
|
|
1506
|
+
cloud.add_argument("--url", default="", help="vendor API base override")
|
|
1507
|
+
cloud.add_argument("--limit", type=int, default=10, help="max sessions/agents per poll")
|
|
1508
|
+
cloud.add_argument("--skip-runs", action="store_true", help="cursor: agents only, skip per-run fetch")
|
|
1422
1509
|
detections = sub.add_parser("detections", help="list detections for one session")
|
|
1423
1510
|
detections.add_argument("correlation_id", help="correlation_id / session id")
|
|
1424
1511
|
disposition = sub.add_parser("disposition", help="close an audit detection from the shell")
|
|
@@ -1610,6 +1697,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1610
1697
|
"hook": cmd_hook,
|
|
1611
1698
|
"status": cmd_status,
|
|
1612
1699
|
"stats": cmd_stats,
|
|
1700
|
+
"cloud": cmd_cloud,
|
|
1613
1701
|
"detections": cmd_detections,
|
|
1614
1702
|
"disposition": cmd_disposition,
|
|
1615
1703
|
"logs": cmd_logs,
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""Cloud Bridge: vendor cloud-agent surfaces, recorded into the local trail.
|
|
2
|
+
|
|
3
|
+
Cloud agents execute on vendor infrastructure, where no local hook can fire.
|
|
4
|
+
What the vendors expose instead is an API over the agent's lifecycle, and —
|
|
5
|
+
for Cursor — a live stream of its tool calls. This module turns those
|
|
6
|
+
surfaces into the same external-ingest payloads the local hook client sends,
|
|
7
|
+
so cloud sessions land in the same hash-chained trail, the same detections
|
|
8
|
+
and the same SIEM sinks as everything else.
|
|
9
|
+
|
|
10
|
+
Privacy posture, identical to the hook path: tool arguments are reduced to a
|
|
11
|
+
SHA-256 over redacted, canonically serialised JSON; result and response
|
|
12
|
+
bodies are hashed, never stored; no prompt or response text is forwarded.
|
|
13
|
+
Vendors encode parts of their payloads (Claude delivers `tool_use.input` as a
|
|
14
|
+
JSON-encoded *string*); those are decoded here, then hashed — decoding is for
|
|
15
|
+
classification, never for storage.
|
|
16
|
+
|
|
17
|
+
Boundary, stated once: this records what the vendor's API exposes. Cursor's
|
|
18
|
+
stream covers tool calls live during a run and expires afterwards; Claude's
|
|
19
|
+
Compliance API serves transcripts to an admin-scoped key. What happens inside
|
|
20
|
+
a vendor VM beyond those surfaces is the vendor's custody, and this module
|
|
21
|
+
does not pretend otherwise.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import hashlib
|
|
27
|
+
import json
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
REDACT_KEYS = frozenset(
|
|
31
|
+
{"authorization", "api_key", "apikey", "password", "secret", "token", "session_key"}
|
|
32
|
+
)
|
|
33
|
+
_COMMAND_KEYS = ("command", "cmd", "script", "CommandLine")
|
|
34
|
+
_PATH_KEYS = ("path", "filepath", "file_path", "AbsolutePath", "TargetFile")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def redact_arguments(args: Any) -> Any:
|
|
38
|
+
if isinstance(args, dict):
|
|
39
|
+
return {
|
|
40
|
+
k: ("<redacted>" if str(k).lower() in REDACT_KEYS else v)
|
|
41
|
+
for k, v in args.items()
|
|
42
|
+
}
|
|
43
|
+
return {"value": args} if args is not None else {}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def canonical_arguments(args: Any) -> str:
|
|
47
|
+
return json.dumps(redact_arguments(args), sort_keys=True, separators=(",", ":"), default=str)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def hash_arguments(args: Any) -> str:
|
|
51
|
+
"""SHA-256 over redacted, canonically serialised arguments.
|
|
52
|
+
|
|
53
|
+
Keep in sync with agentmetry.hooks.ingest.hash_arguments — a sync test
|
|
54
|
+
asserts both produce the same digest for the same input, so a cloud event
|
|
55
|
+
and a local event carrying the same call hash identically.
|
|
56
|
+
"""
|
|
57
|
+
return hashlib.sha256(canonical_arguments(args).encode("utf-8")).hexdigest()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def decode_maybe_encoded(value: Any) -> Any:
|
|
61
|
+
"""Decode the encodings vendors put around structured fields.
|
|
62
|
+
|
|
63
|
+
Claude's Compliance API delivers `tool_use.input` as a JSON-encoded
|
|
64
|
+
string; some streams wrap payloads in base64. Decode for classification,
|
|
65
|
+
never store the decoded content: everything that reaches a canonical
|
|
66
|
+
event from here is a hash, a length or a name.
|
|
67
|
+
"""
|
|
68
|
+
if not isinstance(value, str):
|
|
69
|
+
return value
|
|
70
|
+
text = value.strip()
|
|
71
|
+
if text[:1] in "{[":
|
|
72
|
+
try:
|
|
73
|
+
return json.loads(text)
|
|
74
|
+
except ValueError:
|
|
75
|
+
return value
|
|
76
|
+
try:
|
|
77
|
+
import base64
|
|
78
|
+
|
|
79
|
+
decoded = base64.b64decode(text, validate=True)
|
|
80
|
+
candidate = decoded.decode("utf-8")
|
|
81
|
+
if candidate[:1] in "{[":
|
|
82
|
+
return json.loads(candidate)
|
|
83
|
+
except (ValueError, UnicodeDecodeError):
|
|
84
|
+
# Not base64, or not UTF-8, or not JSON inside: return the original
|
|
85
|
+
# string and let the caller hash it as an opaque value.
|
|
86
|
+
return value
|
|
87
|
+
return value
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def tool_summary(args: Any) -> dict[str, Any]:
|
|
91
|
+
"""The only argument content that survives: a hashed digest, a length,
|
|
92
|
+
and — for shell-shaped tools — whether the command reached a remote host.
|
|
93
|
+
Command text, paths and values do not pass through this function's output.
|
|
94
|
+
"""
|
|
95
|
+
summary: dict[str, Any] = {
|
|
96
|
+
"input_hash": hash_arguments(args),
|
|
97
|
+
"input_size": len(canonical_arguments(args)),
|
|
98
|
+
}
|
|
99
|
+
if isinstance(args, dict):
|
|
100
|
+
for key in _COMMAND_KEYS:
|
|
101
|
+
val = args.get(key)
|
|
102
|
+
if isinstance(val, str) and val.strip():
|
|
103
|
+
summary["has_command"] = True
|
|
104
|
+
break
|
|
105
|
+
else:
|
|
106
|
+
for key in _PATH_KEYS:
|
|
107
|
+
if isinstance(args.get(key), str) and args[key].strip():
|
|
108
|
+
summary["has_path"] = True
|
|
109
|
+
break
|
|
110
|
+
return summary
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# --- Cursor Cloud Agents API -------------------------------------------------
|
|
114
|
+
|
|
115
|
+
CURSOR_DEFAULT_BASE = "https://api.cursor.com"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def map_cursor_agent(agent: dict[str, Any], runs: list[dict[str, Any]] | None = None) -> list[dict[str, Any]]:
|
|
119
|
+
"""One cloud agent + its runs -> external-ingest payloads.
|
|
120
|
+
|
|
121
|
+
Cursor exposes lifecycle and outcome: agent status, run status, duration,
|
|
122
|
+
pushed branches and the PR URL. Tool-call detail is not in these
|
|
123
|
+
endpoints; when the connector attaches to a live stream (v1.1) those
|
|
124
|
+
arrive as separate tool_called events.
|
|
125
|
+
"""
|
|
126
|
+
agent_id = str(agent.get("id") or "unknown")
|
|
127
|
+
status = str(agent.get("status") or "unknown")
|
|
128
|
+
out: list[dict[str, Any]] = [
|
|
129
|
+
{
|
|
130
|
+
"source_app": "cursor_cloud",
|
|
131
|
+
"adapter": "cloud_bridge",
|
|
132
|
+
"event_type": "session_start" if status == "ACTIVE" else "session_end",
|
|
133
|
+
"outcome": "success",
|
|
134
|
+
"correlation_id": f"cursor-cloud-{agent_id}",
|
|
135
|
+
"session_id": agent_id,
|
|
136
|
+
"reason": f"cloud_agent:{status}",
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
for run in runs or []:
|
|
140
|
+
git = run.get("git") or {}
|
|
141
|
+
branches = git.get("branches") or []
|
|
142
|
+
pr_url = str(next((b.get("prUrl") for b in branches if b.get("prUrl")), "") or "")
|
|
143
|
+
out.append(
|
|
144
|
+
{
|
|
145
|
+
"source_app": "cursor_cloud",
|
|
146
|
+
"adapter": "cloud_bridge",
|
|
147
|
+
"event_type": "session_end",
|
|
148
|
+
"outcome": "success" if str(run.get("status")) == "FINISHED" else str(run.get("status") or "unknown").lower(),
|
|
149
|
+
"correlation_id": f"cursor-cloud-{agent_id}",
|
|
150
|
+
"session_id": str(run.get("id") or ""),
|
|
151
|
+
"reason": (
|
|
152
|
+
f"cloud_run:{run.get('status')};duration_ms:{run.get('durationMs', 0)};"
|
|
153
|
+
f"branches:{len(branches)};pr:{'yes' if pr_url else 'no'}"
|
|
154
|
+
),
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
return out
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def map_cursor_stream_tool_call(call: dict[str, Any], agent_id: str, run_id: str) -> dict[str, Any] | None:
|
|
161
|
+
"""One tool_call event from a live run stream -> a tool_called payload.
|
|
162
|
+
|
|
163
|
+
`args` arrives as an object or an encoded string; either way it becomes a
|
|
164
|
+
hash here. `result` is never forwarded — only its presence and size.
|
|
165
|
+
"""
|
|
166
|
+
args = decode_maybe_encoded(call.get("args"))
|
|
167
|
+
summary = tool_summary(args)
|
|
168
|
+
name = str(call.get("name") or "unknown")
|
|
169
|
+
status = str(call.get("status") or "unknown")
|
|
170
|
+
result = call.get("result")
|
|
171
|
+
return {
|
|
172
|
+
"source_app": "cursor_cloud",
|
|
173
|
+
"adapter": "cloud_bridge",
|
|
174
|
+
"event_type": "tool_called",
|
|
175
|
+
"outcome": "success" if status == "completed" else status,
|
|
176
|
+
"correlation_id": f"cursor-cloud-{agent_id}",
|
|
177
|
+
"session_id": run_id,
|
|
178
|
+
"tool": {
|
|
179
|
+
"qualified": f"cursor_cloud.{name}",
|
|
180
|
+
"server": "cursor_cloud",
|
|
181
|
+
"input_hash": summary["input_hash"],
|
|
182
|
+
"input_size": summary["input_size"],
|
|
183
|
+
"has_result": result is not None,
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# --- Claude Compliance API ---------------------------------------------------
|
|
189
|
+
|
|
190
|
+
CLAUDE_DEFAULT_BASE = "https://api.anthropic.com"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def map_claude_tool_use(block: dict[str, Any], session_id: str) -> dict[str, Any]:
|
|
194
|
+
"""One tool_use transcript block -> a tool_called payload.
|
|
195
|
+
|
|
196
|
+
Claude encodes `input` as a JSON-encoded string; decode_maybe_encoded
|
|
197
|
+
turns it back into structure so the same redact-and-hash pipeline applies
|
|
198
|
+
as for local events. Only the hash, size and tool name are forwarded.
|
|
199
|
+
"""
|
|
200
|
+
decoded = decode_maybe_encoded(block.get("input"))
|
|
201
|
+
summary = tool_summary(decoded)
|
|
202
|
+
name = str(block.get("name") or "unknown")
|
|
203
|
+
truncated = bool(block.get("truncated"))
|
|
204
|
+
return {
|
|
205
|
+
"source_app": "claude_cloud",
|
|
206
|
+
"adapter": "cloud_bridge",
|
|
207
|
+
"event_type": "tool_called",
|
|
208
|
+
"outcome": "success",
|
|
209
|
+
"correlation_id": session_id,
|
|
210
|
+
"session_id": session_id,
|
|
211
|
+
"tool": {
|
|
212
|
+
"qualified": f"claude_cloud.{name}",
|
|
213
|
+
"server": "claude_cloud",
|
|
214
|
+
"input_hash": summary["input_hash"],
|
|
215
|
+
"input_size": summary["input_size"],
|
|
216
|
+
"truncated": truncated,
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def map_claude_messages(session: dict[str, Any], messages: dict[str, Any]) -> list[dict[str, Any]]:
|
|
222
|
+
"""A Compliance API transcript -> tool_called payloads, one per tool_use.
|
|
223
|
+
|
|
224
|
+
Text, thinking and system content are dropped here, not stored: the
|
|
225
|
+
transcript's words belong to the vendor's retention policy, while the
|
|
226
|
+
sequence of tool calls belongs in the customer's trail.
|
|
227
|
+
"""
|
|
228
|
+
session_id = str(session.get("id") or "unknown")
|
|
229
|
+
out: list[dict[str, Any]] = []
|
|
230
|
+
for message in messages.get("data", []) or []:
|
|
231
|
+
for block in message.get("content", []) or []:
|
|
232
|
+
if not isinstance(block, dict) or block.get("type") != "tool_use":
|
|
233
|
+
continue
|
|
234
|
+
out.append(map_claude_tool_use(block, session_id))
|
|
235
|
+
return out
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def map_claude_session(session: dict[str, Any]) -> dict[str, Any]:
|
|
239
|
+
"""A listed session -> a session_start payload (metadata only)."""
|
|
240
|
+
return {
|
|
241
|
+
"source_app": "claude_cloud",
|
|
242
|
+
"adapter": "cloud_bridge",
|
|
243
|
+
"event_type": "session_start",
|
|
244
|
+
"outcome": "success",
|
|
245
|
+
"correlation_id": str(session.get("id") or "unknown"),
|
|
246
|
+
"session_id": str(session.get("id") or ""),
|
|
247
|
+
"reason": f"cloud_session:{session.get('created_at', '')[:10]}",
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# --- Grok Build (reserved) ----------------------------------------------------
|
|
252
|
+
|
|
253
|
+
def map_grok_build_session_unsupported() -> None:
|
|
254
|
+
"""Reserved for the Grok Build on-disk log parser.
|
|
255
|
+
|
|
256
|
+
Grok Build is a local CLI: its tool executions happen on this machine,
|
|
257
|
+
so the capture surface is its session logs, not a vendor cloud API. The
|
|
258
|
+
parser lands when a real session-log sample exists to write it against —
|
|
259
|
+
an invented format would be a parser that parses nothing.
|
|
260
|
+
"""
|
|
261
|
+
raise NotImplementedError(
|
|
262
|
+
"grok_build capture lands with the on-disk log parser; install Grok "
|
|
263
|
+
"Build, run one session, and point the parser at its log directory."
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# --- Fetchers (poll-once semantics; the caller owns scheduling) ---------------
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def fetch_cursor_agents(api_key: str, base: str = CURSOR_DEFAULT_BASE) -> list[dict[str, Any]]:
|
|
271
|
+
import httpx
|
|
272
|
+
|
|
273
|
+
resp = httpx.get(
|
|
274
|
+
f"{base.rstrip('/')}/v1/agents",
|
|
275
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
276
|
+
timeout=30,
|
|
277
|
+
)
|
|
278
|
+
resp.raise_for_status()
|
|
279
|
+
return resp.json().get("items", [])
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def fetch_cursor_runs(agent_id: str, api_key: str, base: str = CURSOR_DEFAULT_BASE) -> list[dict[str, Any]]:
|
|
283
|
+
import httpx
|
|
284
|
+
|
|
285
|
+
resp = httpx.get(
|
|
286
|
+
f"{base.rstrip('/')}/v1/agents/{agent_id}/runs",
|
|
287
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
288
|
+
timeout=30,
|
|
289
|
+
)
|
|
290
|
+
resp.raise_for_status()
|
|
291
|
+
return resp.json().get("runs", []) or resp.json().get("items", [])
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def fetch_claude_sessions(api_key: str, kind: str = "local", base: str = CLAUDE_DEFAULT_BASE, limit: int = 20) -> list[dict[str, Any]]:
|
|
295
|
+
import httpx
|
|
296
|
+
|
|
297
|
+
resp = httpx.get(
|
|
298
|
+
f"{base.rstrip('/')}/v1/compliance/apps/sessions/{kind}",
|
|
299
|
+
headers={"x-api-key": api_key, "anthropic-version": "2023-06-01"},
|
|
300
|
+
params={"limit": limit},
|
|
301
|
+
timeout=30,
|
|
302
|
+
)
|
|
303
|
+
resp.raise_for_status()
|
|
304
|
+
return resp.json().get("data", [])
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def fetch_claude_messages(api_key: str, kind: str, session_id: str, base: str = CLAUDE_DEFAULT_BASE, limit: int = 500) -> dict[str, Any]:
|
|
308
|
+
import httpx
|
|
309
|
+
|
|
310
|
+
resp = httpx.get(
|
|
311
|
+
f"{base.rstrip('/')}/v1/compliance/apps/sessions/{kind}/{session_id}/messages",
|
|
312
|
+
headers={"x-api-key": api_key, "anthropic-version": "2023-06-01"},
|
|
313
|
+
params={"limit": limit},
|
|
314
|
+
timeout=30,
|
|
315
|
+
)
|
|
316
|
+
resp.raise_for_status()
|
|
317
|
+
return resp.json()
|
|
@@ -27,7 +27,15 @@ from agentmetry.core.audit.run_context import resolve_operator
|
|
|
27
27
|
from agentmetry.core.config import settings
|
|
28
28
|
from agentmetry.core.version import __version__
|
|
29
29
|
|
|
30
|
-
SCHEMA_VERSION = "2.
|
|
30
|
+
SCHEMA_VERSION = "2.2"
|
|
31
|
+
# One evidence export reads at most this many events. Hitting the cap used to
|
|
32
|
+
# drop the rest with no mark on the pack, so a period looked complete. The pack
|
|
33
|
+
# now says so in meta, and the integrity hash covers that statement.
|
|
34
|
+
_EXPORT_EVENT_CAP = 200_000
|
|
35
|
+
# Packs at this version and above hash `meta` (except the digest itself).
|
|
36
|
+
# Older packs hashed the body only, and `verify` still accepts that scope so a
|
|
37
|
+
# pack already handed to an auditor keeps verifying.
|
|
38
|
+
_META_IN_INTEGRITY: tuple[int, ...] = (2, 2)
|
|
31
39
|
|
|
32
40
|
|
|
33
41
|
_COMPLIANCE_MAPPING = {
|
|
@@ -79,7 +87,9 @@ _COMPLIANCE_MAPPING = {
|
|
|
79
87
|
"audit proxy. It does not observe unmanaged assistants (browser "
|
|
80
88
|
"ChatGPT, unhooked IDEs) and is not a CASB, a sandbox, or a model "
|
|
81
89
|
"evaluation tool. Absence of an event is not evidence that nothing "
|
|
82
|
-
"happened outside the monitored boundary."
|
|
90
|
+
"happened outside the monitored boundary. When meta.events_truncated "
|
|
91
|
+
"is true, the export hit its event cap and this pack is a prefix of "
|
|
92
|
+
"the requested window, not the whole period."
|
|
83
93
|
),
|
|
84
94
|
"disclaimer": (
|
|
85
95
|
"This pack is an operator-generated audit artifact. It is not legal "
|
|
@@ -381,8 +391,39 @@ def _summarize(
|
|
|
381
391
|
}
|
|
382
392
|
|
|
383
393
|
|
|
384
|
-
def
|
|
385
|
-
|
|
394
|
+
def _schema_tuple(version: object) -> tuple[int, ...]:
|
|
395
|
+
"""`2.2` -> `(2, 2)`. A missing or non-numeric version sorts below every real one."""
|
|
396
|
+
if not isinstance(version, str) or not version:
|
|
397
|
+
return (0,)
|
|
398
|
+
parts: list[int] = []
|
|
399
|
+
for piece in version.split("."):
|
|
400
|
+
if not piece.isdigit():
|
|
401
|
+
break
|
|
402
|
+
parts.append(int(piece))
|
|
403
|
+
return tuple(parts) or (0,)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _covers_meta(schema_version: object) -> bool:
|
|
407
|
+
return _schema_tuple(schema_version) >= _META_IN_INTEGRITY
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _integrity_subject(
|
|
411
|
+
body: dict[str, Any], meta: dict[str, Any] | None = None
|
|
412
|
+
) -> dict[str, Any]:
|
|
413
|
+
"""What the digest is computed over.
|
|
414
|
+
|
|
415
|
+
Schema 2.2 and later include `meta` with `integrity_sha256` removed, so the
|
|
416
|
+
date range, producer and trail-chain binding cannot be relabelled without
|
|
417
|
+
breaking the hash. Older packs pass `meta=None` and hash the body alone.
|
|
418
|
+
"""
|
|
419
|
+
if meta is None:
|
|
420
|
+
return body
|
|
421
|
+
covered = {key: value for key, value in meta.items() if key != "integrity_sha256"}
|
|
422
|
+
return {"meta": covered, **body}
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _integrity_hash(subject: dict[str, Any]) -> str:
|
|
426
|
+
canonical = json.dumps(subject, sort_keys=True, separators=(",", ":"), default=str)
|
|
386
427
|
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
387
428
|
|
|
388
429
|
|
|
@@ -412,7 +453,10 @@ def build_evidence_pack(
|
|
|
412
453
|
from agentmetry.core.audit.trail_db import get_trail_db
|
|
413
454
|
|
|
414
455
|
trail_db = get_trail_db()
|
|
415
|
-
events = trail_db.read_between(start_ts, end_ts)
|
|
456
|
+
events = trail_db.read_between(start_ts, end_ts, limit=_EXPORT_EVENT_CAP + 1)
|
|
457
|
+
events_truncated = len(events) > _EXPORT_EVENT_CAP
|
|
458
|
+
if events_truncated:
|
|
459
|
+
events = events[:_EXPORT_EVENT_CAP]
|
|
416
460
|
|
|
417
461
|
tool_calls = _extract_tool_calls(events)
|
|
418
462
|
approvals = _extract_approvals(events)
|
|
@@ -449,11 +493,14 @@ def build_evidence_pack(
|
|
|
449
493
|
"query_end_ts": end_ts,
|
|
450
494
|
"source": "audit_trail",
|
|
451
495
|
"raw_events_included": include_raw_events,
|
|
496
|
+
"events_truncated": events_truncated,
|
|
452
497
|
"trail_chain": _trail_chain_state(),
|
|
453
498
|
},
|
|
454
499
|
**body,
|
|
455
500
|
}
|
|
456
|
-
pack["meta"]["integrity_sha256"] = _integrity_hash(
|
|
501
|
+
pack["meta"]["integrity_sha256"] = _integrity_hash(
|
|
502
|
+
_integrity_subject(body, pack["meta"])
|
|
503
|
+
)
|
|
457
504
|
return pack
|
|
458
505
|
|
|
459
506
|
|
|
@@ -483,8 +530,14 @@ def default_export_path(
|
|
|
483
530
|
|
|
484
531
|
|
|
485
532
|
def verify_evidence_pack(pack: dict[str, Any]) -> tuple[bool, str]:
|
|
486
|
-
"""Return (ok, message). Recomputes integrity_sha256
|
|
533
|
+
"""Return (ok, message). Recomputes integrity_sha256.
|
|
534
|
+
|
|
535
|
+
Schema 2.2 and later cover `meta` except the digest. Earlier schemas cover
|
|
536
|
+
the body only, which is what those packs were hashed with.
|
|
537
|
+
"""
|
|
487
538
|
meta = pack.get("meta") or {}
|
|
539
|
+
if not isinstance(meta, dict):
|
|
540
|
+
return False, "meta must be an object"
|
|
488
541
|
stored = meta.get("integrity_sha256")
|
|
489
542
|
if not stored:
|
|
490
543
|
return False, "missing meta.integrity_sha256"
|
|
@@ -495,7 +548,11 @@ def verify_evidence_pack(pack: dict[str, Any]) -> tuple[bool, str]:
|
|
|
495
548
|
if not isinstance(body.get(key), dict):
|
|
496
549
|
body[key] = {}
|
|
497
550
|
|
|
498
|
-
|
|
551
|
+
if _covers_meta(meta.get("schema_version")):
|
|
552
|
+
subject = _integrity_subject(body, meta)
|
|
553
|
+
else:
|
|
554
|
+
subject = _integrity_subject(body)
|
|
555
|
+
expected = _integrity_hash(subject)
|
|
499
556
|
if expected != stored:
|
|
500
557
|
return False, f"integrity mismatch (expected {expected[:16]}…, got {stored[:16]}…)"
|
|
501
558
|
return True, "integrity verified"
|
|
@@ -20,12 +20,14 @@ from agentmetry.core.config import settings
|
|
|
20
20
|
|
|
21
21
|
ExternalApp = Literal[
|
|
22
22
|
"cursor", "claude", "antigravity", "codex", "mcp_proxy",
|
|
23
|
-
"qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "
|
|
23
|
+
"qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
|
|
24
|
+
"cursor_cloud", "claude_cloud", "grok_build", "agentmetry",
|
|
24
25
|
]
|
|
25
26
|
|
|
26
27
|
KNOWN_EXTERNAL_APPS = frozenset({
|
|
27
28
|
"cursor", "claude", "antigravity", "codex", "mcp_proxy",
|
|
28
|
-
"qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot",
|
|
29
|
+
"qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
|
|
30
|
+
"cursor_cloud", "claude_cloud", "grok_build",
|
|
29
31
|
})
|
|
30
32
|
|
|
31
33
|
_ACTION_MAP = {
|