agentmetry 0.4.0__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.4.0/.dockerignore +5 -0
- agentmetry-0.4.0/.env.agentmetry-demo +29 -0
- agentmetry-0.4.0/.env.example +20 -0
- agentmetry-0.4.0/.gitignore +98 -0
- agentmetry-0.4.0/Dockerfile +18 -0
- agentmetry-0.4.0/PKG-INFO +86 -0
- agentmetry-0.4.0/README.md +45 -0
- agentmetry-0.4.0/agentmetry/__init__.py +12 -0
- agentmetry-0.4.0/agentmetry/api/__init__.py +0 -0
- agentmetry-0.4.0/agentmetry/api/main.py +232 -0
- agentmetry-0.4.0/agentmetry/api/routes/__init__.py +0 -0
- agentmetry-0.4.0/agentmetry/api/routes/audit.py +396 -0
- agentmetry-0.4.0/agentmetry/api/websocket.py +53 -0
- agentmetry-0.4.0/agentmetry/api/ws_bridge.py +34 -0
- agentmetry-0.4.0/agentmetry/cli/__init__.py +941 -0
- agentmetry-0.4.0/agentmetry/cli/__main__.py +5 -0
- agentmetry-0.4.0/agentmetry/core/__init__.py +0 -0
- agentmetry-0.4.0/agentmetry/core/audit/__init__.py +1 -0
- agentmetry-0.4.0/agentmetry/core/audit/adapters/__init__.py +0 -0
- agentmetry-0.4.0/agentmetry/core/audit/adapters/agt.py +304 -0
- agentmetry-0.4.0/agentmetry/core/audit/adapters/cloudevents.py +159 -0
- agentmetry-0.4.0/agentmetry/core/audit/adapters/ecs.py +103 -0
- agentmetry-0.4.0/agentmetry/core/audit/adapters/splunk.py +40 -0
- agentmetry-0.4.0/agentmetry/core/audit/alerts.py +56 -0
- agentmetry-0.4.0/agentmetry/core/audit/canonical.py +150 -0
- agentmetry-0.4.0/agentmetry/core/audit/compliance_digest.py +299 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/__init__.py +9 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/benchmark.py +194 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +6 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +5 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +1 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +1 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +42 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +6 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +4 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +5 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +5 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +5 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +4 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +6 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +6 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +5 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +30 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +3 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +5 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +5 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +4 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +5 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +2 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +6 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +6 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/corpus/corpus.yaml +443 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/disposition.py +651 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/engine.py +78 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/live.py +127 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/live_store.py +355 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/models.py +53 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/rules.py +1314 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/traits.py +648 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/yaml_config.py +91 -0
- agentmetry-0.4.0/agentmetry/core/audit/detection/yaml_rules.py +83 -0
- agentmetry-0.4.0/agentmetry/core/audit/dlp/__init__.py +4 -0
- agentmetry-0.4.0/agentmetry/core/audit/dlp/loader.py +29 -0
- agentmetry-0.4.0/agentmetry/core/audit/dlp/models.py +29 -0
- agentmetry-0.4.0/agentmetry/core/audit/dlp/scanner.py +96 -0
- agentmetry-0.4.0/agentmetry/core/audit/dogfood.py +398 -0
- agentmetry-0.4.0/agentmetry/core/audit/evidence_pack.py +500 -0
- agentmetry-0.4.0/agentmetry/core/audit/external.py +213 -0
- agentmetry-0.4.0/agentmetry/core/audit/hashing.py +21 -0
- agentmetry-0.4.0/agentmetry/core/audit/hook_bootstrap.py +451 -0
- agentmetry-0.4.0/agentmetry/core/audit/identity.py +39 -0
- agentmetry-0.4.0/agentmetry/core/audit/ingest.py +242 -0
- agentmetry-0.4.0/agentmetry/core/audit/migrate.py +73 -0
- agentmetry-0.4.0/agentmetry/core/audit/mitre.py +244 -0
- agentmetry-0.4.0/agentmetry/core/audit/policy.py +99 -0
- agentmetry-0.4.0/agentmetry/core/audit/redaction.py +50 -0
- agentmetry-0.4.0/agentmetry/core/audit/replay.py +54 -0
- agentmetry-0.4.0/agentmetry/core/audit/run_context.py +129 -0
- agentmetry-0.4.0/agentmetry/core/audit/sinks.py +235 -0
- agentmetry-0.4.0/agentmetry/core/audit/spool.py +394 -0
- agentmetry-0.4.0/agentmetry/core/audit/tool_policy/__init__.py +4 -0
- agentmetry-0.4.0/agentmetry/core/audit/tool_policy/evaluator.py +198 -0
- agentmetry-0.4.0/agentmetry/core/audit/tool_policy/loader.py +44 -0
- agentmetry-0.4.0/agentmetry/core/audit/tool_policy/models.py +25 -0
- agentmetry-0.4.0/agentmetry/core/audit/trail_chain.py +300 -0
- agentmetry-0.4.0/agentmetry/core/audit/trail_db.py +491 -0
- agentmetry-0.4.0/agentmetry/core/audit/trail_merkle.py +332 -0
- agentmetry-0.4.0/agentmetry/core/auth.py +54 -0
- agentmetry-0.4.0/agentmetry/core/bus/__init__.py +5 -0
- agentmetry-0.4.0/agentmetry/core/bus/audit_exporter.py +107 -0
- agentmetry-0.4.0/agentmetry/core/bus/bridges.py +26 -0
- agentmetry-0.4.0/agentmetry/core/bus/bus.py +102 -0
- agentmetry-0.4.0/agentmetry/core/bus/events.py +50 -0
- agentmetry-0.4.0/agentmetry/core/bus/outbox.py +124 -0
- agentmetry-0.4.0/agentmetry/core/config.py +177 -0
- agentmetry-0.4.0/agentmetry/core/diagnostics/__init__.py +0 -0
- agentmetry-0.4.0/agentmetry/core/diagnostics/autostart.py +563 -0
- agentmetry-0.4.0/agentmetry/core/diagnostics/doctor.py +535 -0
- agentmetry-0.4.0/agentmetry/core/diagnostics/driver_paths.py +156 -0
- agentmetry-0.4.0/agentmetry/core/diagnostics/env_file.py +45 -0
- agentmetry-0.4.0/agentmetry/core/drivers/__init__.py +4 -0
- agentmetry-0.4.0/agentmetry/core/drivers/host.py +263 -0
- agentmetry-0.4.0/agentmetry/core/drivers/permissions.py +37 -0
- agentmetry-0.4.0/agentmetry/core/drivers/spec.py +118 -0
- agentmetry-0.4.0/agentmetry/core/extensions.py +107 -0
- agentmetry-0.4.0/agentmetry/core/health.py +26 -0
- agentmetry-0.4.0/agentmetry/core/version.py +13 -0
- agentmetry-0.4.0/agentmetry/policies/detection/manifest.yaml +43 -0
- agentmetry-0.4.0/agentmetry/policies/dlp/manifest.yaml +161 -0
- agentmetry-0.4.0/agentmetry/policies/opa/agent_rules.rego +33 -0
- agentmetry-0.4.0/agentmetry/policies/tool/manifest.yaml +117 -0
- agentmetry-0.4.0/pyproject.toml +89 -0
- agentmetry-0.4.0/tests/conftest.py +47 -0
- agentmetry-0.4.0/tests/fixtures/agt_filesink_exfil.jsonl +3 -0
- agentmetry-0.4.0/tests/fixtures/agt_filesink_mixed.jsonl +3 -0
- agentmetry-0.4.0/tests/fixtures/fake_mcp_server.py +15 -0
- agentmetry-0.4.0/tests/test_agentmetry_audit.py +260 -0
- agentmetry-0.4.0/tests/test_agentmetry_ingest_client.py +310 -0
- agentmetry-0.4.0/tests/test_agt_adapter.py +230 -0
- agentmetry-0.4.0/tests/test_audit_sinks.py +213 -0
- agentmetry-0.4.0/tests/test_audit_stats.py +100 -0
- agentmetry-0.4.0/tests/test_audit_tail.py +384 -0
- agentmetry-0.4.0/tests/test_auth.py +33 -0
- agentmetry-0.4.0/tests/test_autostart.py +319 -0
- agentmetry-0.4.0/tests/test_boot_sequence.py +178 -0
- agentmetry-0.4.0/tests/test_burst_windows.py +169 -0
- agentmetry-0.4.0/tests/test_chinese_agent_hooks.py +152 -0
- agentmetry-0.4.0/tests/test_chinese_agent_sprint_b.py +69 -0
- agentmetry-0.4.0/tests/test_chinese_agent_sprint_c.py +126 -0
- agentmetry-0.4.0/tests/test_cli_backup.py +81 -0
- agentmetry-0.4.0/tests/test_cloudevents_adapter.py +229 -0
- agentmetry-0.4.0/tests/test_compliance_digest.py +163 -0
- agentmetry-0.4.0/tests/test_crewai_adapter.py +175 -0
- agentmetry-0.4.0/tests/test_detection_adi.py +314 -0
- agentmetry-0.4.0/tests/test_detection_benchmark.py +97 -0
- agentmetry-0.4.0/tests/test_detection_default_config.py +144 -0
- agentmetry-0.4.0/tests/test_detection_disposition.py +350 -0
- agentmetry-0.4.0/tests/test_detection_disposition_api.py +146 -0
- agentmetry-0.4.0/tests/test_detection_emit_durability.py +128 -0
- agentmetry-0.4.0/tests/test_detection_engine.py +401 -0
- agentmetry-0.4.0/tests/test_detection_evasion.py +304 -0
- agentmetry-0.4.0/tests/test_detection_hf_incident.py +169 -0
- agentmetry-0.4.0/tests/test_detection_rule_identity.py +262 -0
- agentmetry-0.4.0/tests/test_detection_rules_v2.py +177 -0
- agentmetry-0.4.0/tests/test_dlp_agent_env_override.py +70 -0
- agentmetry-0.4.0/tests/test_dlp_invisible_unicode.py +111 -0
- agentmetry-0.4.0/tests/test_dlp_markdown_exfil.py +84 -0
- agentmetry-0.4.0/tests/test_dlp_scanner.py +79 -0
- agentmetry-0.4.0/tests/test_doctor.py +153 -0
- agentmetry-0.4.0/tests/test_dogfood.py +222 -0
- agentmetry-0.4.0/tests/test_dogfood_freeze.py +83 -0
- agentmetry-0.4.0/tests/test_driver_paths.py +142 -0
- agentmetry-0.4.0/tests/test_event_bus.py +127 -0
- agentmetry-0.4.0/tests/test_evidence_pack.py +330 -0
- agentmetry-0.4.0/tests/test_extensions.py +43 -0
- agentmetry-0.4.0/tests/test_external_ingest.py +328 -0
- agentmetry-0.4.0/tests/test_forwarding_shape.py +173 -0
- agentmetry-0.4.0/tests/test_hook_bootstrap.py +144 -0
- agentmetry-0.4.0/tests/test_hook_enforcement_timing.py +125 -0
- agentmetry-0.4.0/tests/test_hook_spool.py +334 -0
- agentmetry-0.4.0/tests/test_isolation.py +65 -0
- agentmetry-0.4.0/tests/test_launch_targets.py +119 -0
- agentmetry-0.4.0/tests/test_live_detection.py +160 -0
- agentmetry-0.4.0/tests/test_live_detection_store.py +124 -0
- agentmetry-0.4.0/tests/test_mcp_audit_proxy.py +57 -0
- agentmetry-0.4.0/tests/test_mitre_dlp.py +170 -0
- agentmetry-0.4.0/tests/test_opensre_adapter.py +145 -0
- agentmetry-0.4.0/tests/test_readme_claims.py +128 -0
- agentmetry-0.4.0/tests/test_redaction.py +66 -0
- agentmetry-0.4.0/tests/test_replay.py +43 -0
- agentmetry-0.4.0/tests/test_static_serving.py +46 -0
- agentmetry-0.4.0/tests/test_subagent_lifecycle.py +198 -0
- agentmetry-0.4.0/tests/test_tool_policy.py +150 -0
- agentmetry-0.4.0/tests/test_tool_policy_agent_cli.py +151 -0
- agentmetry-0.4.0/tests/test_tool_policy_git_hooks.py +119 -0
- agentmetry-0.4.0/tests/test_trail_chain.py +145 -0
- agentmetry-0.4.0/tests/test_trail_merkle.py +265 -0
- agentmetry-0.4.0/tests/test_unattended_agent_policy.py +98 -0
- agentmetry-0.4.0/tests/test_version.py +67 -0
- agentmetry-0.4.0/tests/test_yaml_detection_rules.py +48 -0
- agentmetry-0.4.0/tools/mcp_audit_proxy.py +198 -0
- agentmetry-0.4.0/tools/vault_fs_server.py +84 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Agentmetry — zero-cloud dogfood profile
|
|
2
|
+
# Copy to apps/orchestrator/.env for audit-only runs (no Gemini, no Qdrant).
|
|
3
|
+
#
|
|
4
|
+
# copy apps\orchestrator\.env.agentmetry-demo apps\orchestrator\.env
|
|
5
|
+
# scripts\agentmetry.bat start
|
|
6
|
+
#
|
|
7
|
+
# Generates real tool_called + approval_* events via mock LLM. Draft text is
|
|
8
|
+
# placeholder (mock-dry-run) — fine for JSONL/replay verification, not for Loom.
|
|
9
|
+
# Switch to Gemini or Ollama for the Loom take — see docs/agentmetry-dependency-audit.md Profile B.
|
|
10
|
+
|
|
11
|
+
# --- audit layer (the product) ---
|
|
12
|
+
AGENTMETRY_OPERATOR_ID=home-lab
|
|
13
|
+
AGENTMETRY_AUDIT_EXPORT_ENABLED=1
|
|
14
|
+
AGENTMETRY_AUDIT_SINK=file
|
|
15
|
+
# Log shell commands from Tier B hooks (Cursor/Claude/Codex/Antigravity) in JSONL + dashboard
|
|
16
|
+
AGENTMETRY_AUDIT_LOG_COMMANDS=1
|
|
17
|
+
|
|
18
|
+
# --- runtime, no cloud LLM ---
|
|
19
|
+
AGENTMETRY_LLM_PROVIDER=mock
|
|
20
|
+
AGENTMETRY_ALLOW_MOCK=1
|
|
21
|
+
AGENTMETRY_VAULT_PATH=../../vault
|
|
22
|
+
|
|
23
|
+
# Faster boot for audit-only (no semantic index)
|
|
24
|
+
AGENTMETRY_STARTUP_VAULT_INDEX=false
|
|
25
|
+
|
|
26
|
+
# Not required for audit dogfood — leave unset / empty:
|
|
27
|
+
# GEMINI_API_KEY=
|
|
28
|
+
# AGENTMETRY_QDRANT_URL=
|
|
29
|
+
# AGENTMETRY_USE_POSTGRES=false (default)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Agentmetry orchestrator — copy to .env and adjust
|
|
2
|
+
# cp apps/orchestrator/.env.example apps/orchestrator/.env
|
|
3
|
+
# Never commit .env — it is gitignored
|
|
4
|
+
|
|
5
|
+
# Demo MCP vault path (relative to this directory)
|
|
6
|
+
AGENTMETRY_VAULT_PATH=../../vault
|
|
7
|
+
|
|
8
|
+
AGENTMETRY_OPERATOR_ID=local
|
|
9
|
+
AGENTMETRY_FLEET_ID=
|
|
10
|
+
AGENTMETRY_API_KEY=
|
|
11
|
+
|
|
12
|
+
# Hook boundary — log | block | disable
|
|
13
|
+
AGENTMETRY_TOOL_POLICY_MODE=log
|
|
14
|
+
AGENTMETRY_DLP_MODE=log
|
|
15
|
+
|
|
16
|
+
# Audit export (JSONL + SQLite index)
|
|
17
|
+
AGENTMETRY_AUDIT_EXPORT_ENABLED=1
|
|
18
|
+
AGENTMETRY_AUDIT_SINK=file
|
|
19
|
+
|
|
20
|
+
# More tuning: see repo root .env.example and docs/agentmetry-dogfood-checklist.md
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnp
|
|
4
|
+
.pnp.js
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
dist/
|
|
14
|
+
build/
|
|
15
|
+
|
|
16
|
+
# Next.js
|
|
17
|
+
apps/dashboard/.next/
|
|
18
|
+
apps/dashboard/out/
|
|
19
|
+
|
|
20
|
+
# Environment
|
|
21
|
+
.env
|
|
22
|
+
.env.local
|
|
23
|
+
.env.*.local
|
|
24
|
+
|
|
25
|
+
# Vault runtime artifacts (agent-generated)
|
|
26
|
+
vault/20-Active-Loops/
|
|
27
|
+
vault/30-Archive/
|
|
28
|
+
vault/.obsidian/
|
|
29
|
+
vault/.system/run-log.md
|
|
30
|
+
vault/.system/Approvals-Digest.md
|
|
31
|
+
|
|
32
|
+
# IDE
|
|
33
|
+
.idea/
|
|
34
|
+
.vscode/
|
|
35
|
+
*.swp
|
|
36
|
+
|
|
37
|
+
# OS
|
|
38
|
+
.DS_Store
|
|
39
|
+
Thumbs.db
|
|
40
|
+
|
|
41
|
+
# Docker volumes
|
|
42
|
+
data/
|
|
43
|
+
|
|
44
|
+
# Logs
|
|
45
|
+
*.log
|
|
46
|
+
|
|
47
|
+
# Tier B — machine-local hook wrappers (install scripts regenerate)
|
|
48
|
+
scripts/agentmetry_*_hook.cmd
|
|
49
|
+
|
|
50
|
+
# Local driver toggles (never commit enabled/disabled state)
|
|
51
|
+
vault/.system/drivers.json
|
|
52
|
+
vault/.system/feedback/
|
|
53
|
+
|
|
54
|
+
# Backups
|
|
55
|
+
backups/
|
|
56
|
+
|
|
57
|
+
# Dashboard scratch / scratchpad (never commit)
|
|
58
|
+
apps/dashboard/scratch/
|
|
59
|
+
|
|
60
|
+
# Next.js/TS incremental build artifact
|
|
61
|
+
*.tsbuildinfo
|
|
62
|
+
|
|
63
|
+
# Local-only scratch directories (never part of the published repo):
|
|
64
|
+
# apps/landing/ an unused Vite scaffold (88M of node_modules)
|
|
65
|
+
# Ignored so a stray `git add -A` cannot commit them.
|
|
66
|
+
/apps/landing/
|
|
67
|
+
|
|
68
|
+
# A stale duplicate clone of this repo used to sit at /agentmetry/, and the rule
|
|
69
|
+
# blocking it was `/agentmetry/`. That rule had to go when the Python package was
|
|
70
|
+
# renamed to `agentmetry`.
|
|
71
|
+
#
|
|
72
|
+
# Git anchors a leading slash to the repository root, so `/agentmetry/` never
|
|
73
|
+
# matched apps/orchestrator/agentmetry/ and `git ls-files` listed all 72 files
|
|
74
|
+
# happily. Hatchling anchors it to the *project* root instead, which is
|
|
75
|
+
# apps/orchestrator, so it excluded the entire package and built a wheel
|
|
76
|
+
# containing nothing but dist-info. A published package that installs no code is
|
|
77
|
+
# the worst possible first release, and nothing in the build said a word.
|
|
78
|
+
#
|
|
79
|
+
# Only a nested clone has a .git directory, and our package never will, so this
|
|
80
|
+
# blocks the thing that was actually the problem without any anchoring ambiguity.
|
|
81
|
+
/agentmetry/.git/
|
|
82
|
+
|
|
83
|
+
# Local dev launch config (absolute machine paths)
|
|
84
|
+
.claude/launch.json
|
|
85
|
+
|
|
86
|
+
# Internal review prompts and audit reports. These name pilot pricing, kill
|
|
87
|
+
# gates, and the private repo contents; they belong in agentmetry-notes.
|
|
88
|
+
#
|
|
89
|
+
# The "review" patterns were already here and still let `audit-prompt.md` and
|
|
90
|
+
# `audit-2026-08.md` through, because the rule matched the word we happened to
|
|
91
|
+
# use last time rather than the kind of document. An audit of a security tool
|
|
92
|
+
# is the worst thing to publish by accident: it is an evasion writeup with the
|
|
93
|
+
# author's name on it.
|
|
94
|
+
docs/*review*prompt*.md
|
|
95
|
+
docs/*-review-*.md
|
|
96
|
+
docs/*audit*prompt*.md
|
|
97
|
+
docs/audit-*.md
|
|
98
|
+
docs/*-audit-*.md
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
FROM python:3.12-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
6
|
+
gcc libpq-dev \
|
|
7
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
8
|
+
|
|
9
|
+
COPY pyproject.toml ./
|
|
10
|
+
COPY . .
|
|
11
|
+
|
|
12
|
+
RUN pip install --no-cache-dir -e .
|
|
13
|
+
|
|
14
|
+
RUN mkdir -p /app/data
|
|
15
|
+
|
|
16
|
+
EXPOSE 8000
|
|
17
|
+
|
|
18
|
+
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentmetry
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Local-first flight recorder for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
|
|
5
|
+
Project-URL: Homepage, https://agentmetry.ai
|
|
6
|
+
Project-URL: Source, https://github.com/blitzcrieg1/agentmetry
|
|
7
|
+
Project-URL: Issues, https://github.com/blitzcrieg1/agentmetry/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/blitzcrieg1/agentmetry/blob/master/CHANGELOG.md
|
|
9
|
+
Author: Ioannis L.
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: agentic-ai,ai-agents,ai-security,audit-log,claude-code,cursor,devsecops,dlp,local-first,mitre-attack,siem
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: System :: Logging
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
26
|
+
Requires-Dist: fastapi>=0.115.0
|
|
27
|
+
Requires-Dist: httpx>=0.28.0
|
|
28
|
+
Requires-Dist: mcp>=1.2
|
|
29
|
+
Requires-Dist: pydantic-settings>=2.6.0
|
|
30
|
+
Requires-Dist: pydantic>=2.9.0
|
|
31
|
+
Requires-Dist: pyyaml>=6.0
|
|
32
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
33
|
+
Requires-Dist: tzdata; sys_platform == 'win32'
|
|
34
|
+
Requires-Dist: uvicorn[standard]>=0.32.0
|
|
35
|
+
Requires-Dist: websockets>=14.0
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff==0.16.0; extra == 'dev'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# Agentmetry
|
|
43
|
+
|
|
44
|
+
A local-first flight recorder for AI coding agents.
|
|
45
|
+
|
|
46
|
+
Agentmetry hooks the tool lifecycle of Claude Code, Cursor, Codex and Antigravity,
|
|
47
|
+
writes every tool call, approval and denial to a hash-chained JSONL trail on your
|
|
48
|
+
machine, and runs sequence detection over the session. A credential read followed
|
|
49
|
+
by network egress becomes one finding rather than two unremarkable log lines.
|
|
50
|
+
|
|
51
|
+
Everything runs locally. There are no cloud calls and no telemetry. Forwarding to
|
|
52
|
+
Elastic ECS, Splunk HEC or a webhook exists and is off unless you configure it.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install agentmetry
|
|
56
|
+
agentmetry doctor
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Check the detection claims yourself
|
|
60
|
+
|
|
61
|
+
The corpus ships inside the package, so this works from a clean install:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
agentmetry benchmark
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
It replays recorded sessions through the real rule engine and exits non-zero on
|
|
68
|
+
any missed rule or any false positive. The benign half is the number that
|
|
69
|
+
matters: any tool can fire on an attack, and a feed that cries wolf gets muted.
|
|
70
|
+
|
|
71
|
+
## What it does not do
|
|
72
|
+
|
|
73
|
+
- **It is not a CASB.** It records the agents you wire in. An unmanaged browser
|
|
74
|
+
assistant is invisible to it.
|
|
75
|
+
- **It is a recorder, not a sandbox.** The only enforcement path is pre-execution
|
|
76
|
+
DLP blocking in the hook.
|
|
77
|
+
- **The DLP is regex, not ML.** A starting pack you extend in YAML.
|
|
78
|
+
- **It is a public alpha.** Integration surfaces may still change.
|
|
79
|
+
|
|
80
|
+
Full documentation, the event schema, and the SIEM integration guides are in the
|
|
81
|
+
repository.
|
|
82
|
+
|
|
83
|
+
- Source and issues: https://github.com/blitzcrieg1/agentmetry
|
|
84
|
+
- Website: https://agentmetry.ai
|
|
85
|
+
|
|
86
|
+
Apache-2.0.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Agentmetry
|
|
2
|
+
|
|
3
|
+
A local-first flight recorder for AI coding agents.
|
|
4
|
+
|
|
5
|
+
Agentmetry hooks the tool lifecycle of Claude Code, Cursor, Codex and Antigravity,
|
|
6
|
+
writes every tool call, approval and denial to a hash-chained JSONL trail on your
|
|
7
|
+
machine, and runs sequence detection over the session. A credential read followed
|
|
8
|
+
by network egress becomes one finding rather than two unremarkable log lines.
|
|
9
|
+
|
|
10
|
+
Everything runs locally. There are no cloud calls and no telemetry. Forwarding to
|
|
11
|
+
Elastic ECS, Splunk HEC or a webhook exists and is off unless you configure it.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install agentmetry
|
|
15
|
+
agentmetry doctor
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Check the detection claims yourself
|
|
19
|
+
|
|
20
|
+
The corpus ships inside the package, so this works from a clean install:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
agentmetry benchmark
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
It replays recorded sessions through the real rule engine and exits non-zero on
|
|
27
|
+
any missed rule or any false positive. The benign half is the number that
|
|
28
|
+
matters: any tool can fire on an attack, and a feed that cries wolf gets muted.
|
|
29
|
+
|
|
30
|
+
## What it does not do
|
|
31
|
+
|
|
32
|
+
- **It is not a CASB.** It records the agents you wire in. An unmanaged browser
|
|
33
|
+
assistant is invisible to it.
|
|
34
|
+
- **It is a recorder, not a sandbox.** The only enforcement path is pre-execution
|
|
35
|
+
DLP blocking in the hook.
|
|
36
|
+
- **The DLP is regex, not ML.** A starting pack you extend in YAML.
|
|
37
|
+
- **It is a public alpha.** Integration surfaces may still change.
|
|
38
|
+
|
|
39
|
+
Full documentation, the event schema, and the SIEM integration guides are in the
|
|
40
|
+
repository.
|
|
41
|
+
|
|
42
|
+
- Source and issues: https://github.com/blitzcrieg1/agentmetry
|
|
43
|
+
- Website: https://agentmetry.ai
|
|
44
|
+
|
|
45
|
+
Apache-2.0.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Agentmetry: a local-first flight recorder for AI coding agents.
|
|
2
|
+
|
|
3
|
+
Everything lives under this one top-level package on purpose. The orchestrator
|
|
4
|
+
used to ship `core`, `api` and `cli` as top-level names, which is harmless for an
|
|
5
|
+
editable install inside this repo and antisocial once published: three of the
|
|
6
|
+
most generic importable names in Python, dropped into someone else's
|
|
7
|
+
site-packages, where they collide with whatever else claims them.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from agentmetry.core.version import __version__
|
|
11
|
+
|
|
12
|
+
__all__ = ["__version__"]
|
|
File without changes
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
from logging.handlers import RotatingFileHandler
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from fastapi import FastAPI, Query, WebSocket, WebSocketDisconnect
|
|
10
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
11
|
+
|
|
12
|
+
from agentmetry.api.routes.audit import router as audit_router
|
|
13
|
+
from agentmetry.api.websocket import ws_manager
|
|
14
|
+
from agentmetry.api.ws_bridge import ws_event_bridge
|
|
15
|
+
from agentmetry.core.auth import verify_ws_token
|
|
16
|
+
from agentmetry.core.bus.audit_exporter import audit_exporter
|
|
17
|
+
from agentmetry.core.bus.bridges import outbox_persister
|
|
18
|
+
from agentmetry.core.bus.bus import bus
|
|
19
|
+
from agentmetry.core.bus.outbox import get_outbox
|
|
20
|
+
from agentmetry.core.config import settings
|
|
21
|
+
from agentmetry.core.extensions import load_extensions
|
|
22
|
+
from agentmetry.core.health import get_system_health
|
|
23
|
+
from agentmetry.core.version import __version__
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_LOG_DIR = Path(__file__).resolve().parents[2] / "data" / "logs"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _setup_logging() -> None:
|
|
31
|
+
"""Persist app logs to disk so they survive a closed terminal."""
|
|
32
|
+
root = logging.getLogger()
|
|
33
|
+
if any(isinstance(h, RotatingFileHandler) for h in root.handlers):
|
|
34
|
+
return
|
|
35
|
+
_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
37
|
+
|
|
38
|
+
file_handler = RotatingFileHandler(
|
|
39
|
+
_LOG_DIR / "orchestrator.log",
|
|
40
|
+
maxBytes=5_000_000,
|
|
41
|
+
backupCount=3,
|
|
42
|
+
encoding="utf-8",
|
|
43
|
+
)
|
|
44
|
+
file_handler.setFormatter(formatter)
|
|
45
|
+
root.addHandler(file_handler)
|
|
46
|
+
|
|
47
|
+
console = logging.StreamHandler()
|
|
48
|
+
console.setFormatter(formatter)
|
|
49
|
+
root.addHandler(console)
|
|
50
|
+
|
|
51
|
+
if root.level in (logging.NOTSET, logging.WARNING):
|
|
52
|
+
root.setLevel(logging.INFO)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_setup_logging()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@asynccontextmanager
|
|
61
|
+
async def lifespan(app: FastAPI):
|
|
62
|
+
from agentmetry.core.audit.migrate import backfill_db_from_jsonl
|
|
63
|
+
|
|
64
|
+
# Backfill SQLite from the existing JSONL trail. A broken or locked audit DB
|
|
65
|
+
# must degrade the dashboard, never stop the daemon from booting: the
|
|
66
|
+
# recorder's job is to keep recording.
|
|
67
|
+
try:
|
|
68
|
+
await asyncio.to_thread(backfill_db_from_jsonl)
|
|
69
|
+
except Exception:
|
|
70
|
+
logger.exception("Audit trail backfill failed; continuing without it")
|
|
71
|
+
|
|
72
|
+
# Replay events the hooks captured while this process was down. Same
|
|
73
|
+
# non-fatal contract as the backfill: a broken spool must not stop the
|
|
74
|
+
# recorder from booting.
|
|
75
|
+
#
|
|
76
|
+
# Deliberately not awaited. Draining a few thousand spooled events takes
|
|
77
|
+
# minutes, and awaiting it here holds the ingest port closed for the whole
|
|
78
|
+
# time — so every hook that fires during the drain is refused and spooled,
|
|
79
|
+
# which makes the next drain larger. The recorder has to be reachable first
|
|
80
|
+
# and catch up second.
|
|
81
|
+
from agentmetry.core.audit.spool import drain_forever, drain_spool
|
|
82
|
+
|
|
83
|
+
async def _boot_drain() -> None:
|
|
84
|
+
try:
|
|
85
|
+
await drain_spool()
|
|
86
|
+
except Exception:
|
|
87
|
+
logger.exception("Hook spool drain failed; continuing without it")
|
|
88
|
+
|
|
89
|
+
# Bring the triage index back in step with the trail. `reconcile_at_boot`
|
|
90
|
+
# declines rather than rebuilding when the trail cannot account for a
|
|
91
|
+
# decision the index already holds — a pruned or repointed trail must not
|
|
92
|
+
# silently erase the corrective-action record.
|
|
93
|
+
from agentmetry.core.audit.detection.disposition import reconcile_at_boot
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
await asyncio.to_thread(reconcile_at_boot)
|
|
97
|
+
except Exception:
|
|
98
|
+
logger.exception("Disposition reconcile failed; continuing without it")
|
|
99
|
+
|
|
100
|
+
# Event bus first: everything downstream publishes onto it.
|
|
101
|
+
bus.set_initial_seq(get_outbox().max_seq())
|
|
102
|
+
bridge_tasks = [
|
|
103
|
+
asyncio.create_task(ws_event_bridge(), name="ws-bridge"),
|
|
104
|
+
asyncio.create_task(outbox_persister(), name="outbox-persister"),
|
|
105
|
+
asyncio.create_task(audit_exporter(), name="audit-exporter"),
|
|
106
|
+
asyncio.create_task(_boot_drain(), name="spool-boot-drain"),
|
|
107
|
+
# Keep draining for as long as we run. A boot-only drain leaves a
|
|
108
|
+
# backlog growing unnoticed whenever ingest is unreachable while the
|
|
109
|
+
# process itself stays up.
|
|
110
|
+
asyncio.create_task(drain_forever(), name="spool-drain"),
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
# Drivers mount in the background: a slow npx download must not delay boot.
|
|
114
|
+
from agentmetry.core.drivers.host import get_mcp_host
|
|
115
|
+
|
|
116
|
+
mount_task = asyncio.create_task(get_mcp_host().mount_all(), name="driver-mounts")
|
|
117
|
+
|
|
118
|
+
from agentmetry.core.audit.hook_bootstrap import bootstrap_tier_b_hooks
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
hook_paths = bootstrap_tier_b_hooks()
|
|
122
|
+
if hook_paths.get("cursor"):
|
|
123
|
+
logger.info("Global Cursor hooks ready: %s", hook_paths["cursor"])
|
|
124
|
+
if hook_paths.get("claude"):
|
|
125
|
+
logger.info("Global Claude hooks ready: %s", hook_paths["claude"])
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
logger.warning("Tier B hook bootstrap failed: %s", exc)
|
|
128
|
+
|
|
129
|
+
# Launch transcript watcher for Antigravity in the background
|
|
130
|
+
import subprocess
|
|
131
|
+
import sys
|
|
132
|
+
watcher_process = None
|
|
133
|
+
try:
|
|
134
|
+
watcher_path = Path(__file__).resolve().parents[4] / "scripts" / "antigravity_transcript_watcher.py"
|
|
135
|
+
if watcher_path.exists():
|
|
136
|
+
watcher_process = subprocess.Popen(
|
|
137
|
+
[sys.executable, str(watcher_path)],
|
|
138
|
+
stdout=subprocess.DEVNULL,
|
|
139
|
+
stderr=subprocess.DEVNULL,
|
|
140
|
+
)
|
|
141
|
+
logger.info("Started Antigravity transcript watcher (PID: %s)", watcher_process.pid)
|
|
142
|
+
except Exception as exc:
|
|
143
|
+
logger.warning("Failed to start Antigravity transcript watcher: %s", exc)
|
|
144
|
+
|
|
145
|
+
yield
|
|
146
|
+
mount_task.cancel()
|
|
147
|
+
await get_mcp_host().unmount_all()
|
|
148
|
+
for task in bridge_tasks:
|
|
149
|
+
task.cancel()
|
|
150
|
+
if watcher_process:
|
|
151
|
+
watcher_process.terminate()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
app = FastAPI(
|
|
155
|
+
title="Agentmetry",
|
|
156
|
+
description="Open-source SIEM flight recorder for AI agent tool-use",
|
|
157
|
+
version=__version__,
|
|
158
|
+
lifespan=lifespan,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
app.add_middleware(
|
|
162
|
+
CORSMiddleware,
|
|
163
|
+
allow_origins=[
|
|
164
|
+
"http://localhost:3000",
|
|
165
|
+
"http://127.0.0.1:3000",
|
|
166
|
+
"http://localhost:3001",
|
|
167
|
+
"http://127.0.0.1:3001",
|
|
168
|
+
"http://dashboard:3000",
|
|
169
|
+
],
|
|
170
|
+
allow_origin_regex=r"http://(localhost|127\.0\.0\.1):\d+",
|
|
171
|
+
allow_credentials=True,
|
|
172
|
+
allow_methods=["*"],
|
|
173
|
+
allow_headers=["*"],
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
app.include_router(audit_router, prefix="/api/v1")
|
|
177
|
+
|
|
178
|
+
load_extensions(app, settings=settings)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@app.get("/api/v1/health")
|
|
182
|
+
async def health():
|
|
183
|
+
return await get_system_health()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@app.websocket("/ws/{session_id}")
|
|
190
|
+
async def websocket_endpoint(
|
|
191
|
+
websocket: WebSocket,
|
|
192
|
+
session_id: str,
|
|
193
|
+
token: str | None = Query(None),
|
|
194
|
+
):
|
|
195
|
+
if not verify_ws_token(token, websocket):
|
|
196
|
+
await websocket.close(code=4401)
|
|
197
|
+
return
|
|
198
|
+
await ws_manager.connect(websocket, session_id)
|
|
199
|
+
try:
|
|
200
|
+
while True:
|
|
201
|
+
await websocket.receive_text()
|
|
202
|
+
except WebSocketDisconnect:
|
|
203
|
+
ws_manager.disconnect(websocket, session_id)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
_DASHBOARD_DIR = Path(__file__).resolve().parents[3] / "dashboard" / "out"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def mount_dashboard(target: FastAPI, directory: Path = _DASHBOARD_DIR) -> bool:
|
|
210
|
+
"""Serve the dashboard's static export when it has been built.
|
|
211
|
+
|
|
212
|
+
Single-process mode: `npm run build` in apps/dashboard emits `out/`, which
|
|
213
|
+
the orchestrator serves at the root. Mounted last so it only catches paths
|
|
214
|
+
not already claimed by the API or WebSocket. Dev uses the :3000 dev server
|
|
215
|
+
instead, so this is a no-op when the export is absent.
|
|
216
|
+
"""
|
|
217
|
+
if not directory.is_dir():
|
|
218
|
+
logger.info(
|
|
219
|
+
"Dashboard export not found at %s — run 'npm run build' in "
|
|
220
|
+
"apps/dashboard for single-process serving",
|
|
221
|
+
directory,
|
|
222
|
+
)
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
from fastapi.staticfiles import StaticFiles
|
|
226
|
+
|
|
227
|
+
target.mount("/", StaticFiles(directory=str(directory), html=True), name="dashboard")
|
|
228
|
+
logger.info("Serving dashboard from %s", directory)
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
mount_dashboard(app)
|
|
File without changes
|