nat-engine 1__py3-none-any.whl
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.
- mannf/__init__.py +33 -0
- mannf/__main__.py +10 -0
- mannf/_version.py +8 -0
- mannf/agents/__init__.py +7 -0
- mannf/agents/analyzer_agent.py +9 -0
- mannf/agents/base.py +9 -0
- mannf/agents/bdi_agent.py +9 -0
- mannf/agents/belief_state.py +9 -0
- mannf/agents/coordinator_agent.py +9 -0
- mannf/agents/executor_agent.py +9 -0
- mannf/agents/monitor_agent.py +9 -0
- mannf/agents/oracle_agent.py +9 -0
- mannf/agents/planner_agent.py +9 -0
- mannf/agents/test_agent.py +9 -0
- mannf/anomaly/__init__.py +7 -0
- mannf/anomaly/enhanced_detector.py +9 -0
- mannf/cli.py +9 -0
- mannf/core/__init__.py +26 -0
- mannf/core/agents/__init__.py +52 -0
- mannf/core/agents/accessibility_scanner_agent.py +245 -0
- mannf/core/agents/analyzer_agent.py +224 -0
- mannf/core/agents/autonomous_loop_agent.py +1086 -0
- mannf/core/agents/autonomous_loop_models.py +62 -0
- mannf/core/agents/autonomous_run_differ.py +427 -0
- mannf/core/agents/base.py +128 -0
- mannf/core/agents/bdi_agent.py +330 -0
- mannf/core/agents/belief_state.py +202 -0
- mannf/core/agents/browser_coordinator_agent.py +224 -0
- mannf/core/agents/browser_executor_agent.py +410 -0
- mannf/core/agents/coordinator_agent.py +262 -0
- mannf/core/agents/executor_agent.py +222 -0
- mannf/core/agents/monitor_agent.py +188 -0
- mannf/core/agents/oracle_agent.py +150 -0
- mannf/core/agents/performance_testing_agent.py +279 -0
- mannf/core/agents/planner_agent.py +128 -0
- mannf/core/agents/test_agent.py +249 -0
- mannf/core/agents/visual_regression_agent.py +311 -0
- mannf/core/agents/web_crawler_agent.py +510 -0
- mannf/core/agents/worker_pool.py +366 -0
- mannf/core/anomaly/__init__.py +14 -0
- mannf/core/anomaly/enhanced_detector.py +541 -0
- mannf/core/browser/__init__.py +63 -0
- mannf/core/browser/accessibility_scanner.py +424 -0
- mannf/core/browser/discovery_model.py +178 -0
- mannf/core/browser/dom_snapshot.py +349 -0
- mannf/core/browser/ingestor_bridge.py +371 -0
- mannf/core/browser/performance_metrics.py +217 -0
- mannf/core/browser/reflection_analyzer.py +442 -0
- mannf/core/browser/scenario_generator.py +1100 -0
- mannf/core/browser/security_scenario_generator.py +695 -0
- mannf/core/browser/visual_comparer.py +159 -0
- mannf/core/diagnostics/__init__.py +28 -0
- mannf/core/diagnostics/failure_clusterer.py +211 -0
- mannf/core/diagnostics/flake_detector.py +233 -0
- mannf/core/diagnostics/root_cause_analyzer.py +273 -0
- mannf/core/distributed/__init__.py +16 -0
- mannf/core/distributed/endpoint.py +139 -0
- mannf/core/distributed/system_under_test.py +207 -0
- mannf/core/functional_orchestrator.py +428 -0
- mannf/core/messaging/__init__.py +11 -0
- mannf/core/messaging/bus.py +113 -0
- mannf/core/messaging/messages.py +89 -0
- mannf/core/nat_orchestrator.py +342 -0
- mannf/core/neural/__init__.py +183 -0
- mannf/core/orchestrator.py +272 -0
- mannf/core/prioritization/__init__.py +17 -0
- mannf/core/prioritization/adaptive_controller.py +509 -0
- mannf/core/prioritization/belief_prioritizer.py +231 -0
- mannf/core/prioritization/risk_scorer.py +430 -0
- mannf/core/reporting/__init__.py +12 -0
- mannf/core/reporting/unified_report.py +664 -0
- mannf/core/testing/__init__.py +17 -0
- mannf/core/testing/adaptive_controller.py +149 -0
- mannf/core/testing/models.py +179 -0
- mannf/core/validation/__init__.py +10 -0
- mannf/core/validation/self_validation_runner.py +180 -0
- mannf/dashboard/__init__.py +7 -0
- mannf/dashboard/app.py +9 -0
- mannf/dashboard/models.py +9 -0
- mannf/dashboard/static/index.html +2538 -0
- mannf/dashboard/telemetry.py +9 -0
- mannf/distributed/__init__.py +7 -0
- mannf/distributed/endpoint.py +9 -0
- mannf/distributed/system_under_test.py +9 -0
- mannf/healing/__init__.py +7 -0
- mannf/healing/graphql_schema_diff.py +9 -0
- mannf/healing/healer.py +9 -0
- mannf/healing/models.py +9 -0
- mannf/healing/schema_diff.py +9 -0
- mannf/integrations/__init__.py +7 -0
- mannf/integrations/auth.py +9 -0
- mannf/integrations/graphql_parser.py +9 -0
- mannf/integrations/graphql_sut.py +9 -0
- mannf/integrations/http_sut.py +9 -0
- mannf/integrations/openapi_parser.py +9 -0
- mannf/integrations/postman_parser.py +9 -0
- mannf/llm/__init__.py +7 -0
- mannf/llm/anthropic_provider.py +9 -0
- mannf/llm/base.py +9 -0
- mannf/llm/config.py +9 -0
- mannf/llm/factory.py +9 -0
- mannf/llm/openai_provider.py +9 -0
- mannf/llm/prompts.py +9 -0
- mannf/messaging/__init__.py +7 -0
- mannf/messaging/bus.py +9 -0
- mannf/messaging/messages.py +9 -0
- mannf/nat_orchestrator.py +9 -0
- mannf/neural/__init__.py +7 -0
- mannf/orchestrator.py +9 -0
- mannf/prioritization/__init__.py +7 -0
- mannf/prioritization/adaptive_controller.py +9 -0
- mannf/prioritization/belief_prioritizer.py +9 -0
- mannf/prioritization/risk_scorer.py +9 -0
- mannf/product/__init__.py +29 -0
- mannf/product/admin/__init__.py +3 -0
- mannf/product/admin/routes.py +514 -0
- mannf/product/auth/__init__.py +5 -0
- mannf/product/auth/saml.py +212 -0
- mannf/product/billing/__init__.py +5 -0
- mannf/product/billing/audit.py +160 -0
- mannf/product/billing/feature_gates.py +180 -0
- mannf/product/billing/metering.py +179 -0
- mannf/product/billing/notifications.py +181 -0
- mannf/product/billing/plans.py +133 -0
- mannf/product/billing/rate_limits.py +35 -0
- mannf/product/billing/stripe_billing.py +906 -0
- mannf/product/billing/tenant_auth.py +233 -0
- mannf/product/billing/tenant_manager.py +873 -0
- mannf/product/cli.py +3900 -0
- mannf/product/cli_admin.py +408 -0
- mannf/product/dashboard/__init__.py +61 -0
- mannf/product/dashboard/app.py +3567 -0
- mannf/product/dashboard/models.py +460 -0
- mannf/product/dashboard/static/index.html +6347 -0
- mannf/product/dashboard/static/manifest.json +25 -0
- mannf/product/dashboard/static/pwa-icon-192.png +0 -0
- mannf/product/dashboard/static/pwa-icon-512.png +0 -0
- mannf/product/dashboard/static/sw.js +64 -0
- mannf/product/dashboard/telemetry.py +547 -0
- mannf/product/database.py +145 -0
- mannf/product/demo.py +844 -0
- mannf/product/doctor.py +509 -0
- mannf/product/exporters/__init__.py +65 -0
- mannf/product/exporters/azuredevops_exporter.py +257 -0
- mannf/product/exporters/base.py +307 -0
- mannf/product/exporters/bugzilla_exporter.py +200 -0
- mannf/product/exporters/dedup.py +275 -0
- mannf/product/exporters/finding_adapter.py +216 -0
- mannf/product/exporters/github_exporter.py +197 -0
- mannf/product/exporters/gitlab_exporter.py +215 -0
- mannf/product/exporters/jira_exporter.py +180 -0
- mannf/product/exporters/linear_exporter.py +195 -0
- mannf/product/exporters/loader.py +233 -0
- mannf/product/exporters/pagerduty_exporter.py +363 -0
- mannf/product/exporters/sentry_exporter.py +322 -0
- mannf/product/exporters/servicenow_exporter.py +240 -0
- mannf/product/exporters/shortcut_exporter.py +231 -0
- mannf/product/exporters/webhook_exporter.py +383 -0
- mannf/product/formatters/__init__.py +18 -0
- mannf/product/formatters/allure_formatter.py +161 -0
- mannf/product/formatters/ctrf_formatter.py +149 -0
- mannf/product/healing/__init__.py +30 -0
- mannf/product/healing/graphql_schema_diff.py +152 -0
- mannf/product/healing/healer.py +141 -0
- mannf/product/healing/models.py +175 -0
- mannf/product/healing/schema_diff.py +251 -0
- mannf/product/ingestors/__init__.py +77 -0
- mannf/product/ingestors/base.py +256 -0
- mannf/product/ingestors/bgstm_ingestor.py +764 -0
- mannf/product/ingestors/curl_ingestor.py +1019 -0
- mannf/product/ingestors/cypress_ingestor.py +487 -0
- mannf/product/ingestors/gherkin_ingestor.py +967 -0
- mannf/product/ingestors/graphql_ingestor.py +845 -0
- mannf/product/ingestors/grpc_ingestor.py +591 -0
- mannf/product/ingestors/har_ingestor.py +976 -0
- mannf/product/ingestors/loader.py +284 -0
- mannf/product/ingestors/models.py +146 -0
- mannf/product/ingestors/openapi_ingestor.py +606 -0
- mannf/product/ingestors/playwright_ingestor.py +449 -0
- mannf/product/ingestors/postman_ingestor.py +631 -0
- mannf/product/ingestors/traffic_ingestor.py +679 -0
- mannf/product/ingestors/websocket_ingestor.py +526 -0
- mannf/product/integrations/__init__.py +21 -0
- mannf/product/integrations/auth.py +190 -0
- mannf/product/integrations/graphql_parser.py +436 -0
- mannf/product/integrations/graphql_sut.py +247 -0
- mannf/product/integrations/grpc_sut.py +469 -0
- mannf/product/integrations/http_sut.py +237 -0
- mannf/product/integrations/kafka_adapter.py +342 -0
- mannf/product/integrations/openapi_parser.py +513 -0
- mannf/product/integrations/postman_parser.py +467 -0
- mannf/product/integrations/webhook_receiver.py +344 -0
- mannf/product/integrations/websocket_sut.py +434 -0
- mannf/product/llm/__init__.py +25 -0
- mannf/product/llm/anthropic_provider.py +94 -0
- mannf/product/llm/base.py +267 -0
- mannf/product/llm/config.py +48 -0
- mannf/product/llm/factory.py +42 -0
- mannf/product/llm/openai_provider.py +93 -0
- mannf/product/llm/prompts.py +403 -0
- mannf/product/llm/root_cause_service.py +311 -0
- mannf/product/llm/test_plan_models.py +78 -0
- mannf/product/metrics.py +149 -0
- mannf/product/middleware/__init__.py +3 -0
- mannf/product/middleware/audit_middleware.py +112 -0
- mannf/product/middleware/tenant_isolation.py +114 -0
- mannf/product/models.py +347 -0
- mannf/product/notifications/__init__.py +24 -0
- mannf/product/notifications/dispatcher.py +411 -0
- mannf/product/onboarding.py +190 -0
- mannf/product/orchestration/__init__.py +39 -0
- mannf/product/orchestration/ingest_scan_orchestrator.py +339 -0
- mannf/product/orchestration/pipeline.py +401 -0
- mannf/product/orchestrator.py +987 -0
- mannf/product/orchestrator_models.py +269 -0
- mannf/product/regression/__init__.py +36 -0
- mannf/product/regression/differ.py +172 -0
- mannf/product/regression/masking.py +100 -0
- mannf/product/regression/models.py +232 -0
- mannf/product/regression/recorder.py +124 -0
- mannf/product/regression/replayer.py +168 -0
- mannf/product/reports/__init__.py +10 -0
- mannf/product/reports/pdf.py +132 -0
- mannf/product/scheduling/__init__.py +57 -0
- mannf/product/scheduling/cron_utils.py +251 -0
- mannf/product/scheduling/engine.py +473 -0
- mannf/product/scheduling/models.py +86 -0
- mannf/product/scheduling/queue.py +894 -0
- mannf/product/scheduling/store.py +235 -0
- mannf/product/security/__init__.py +21 -0
- mannf/product/security/belief_guided.py +143 -0
- mannf/product/security/checks/__init__.py +55 -0
- mannf/product/security/checks/base.py +69 -0
- mannf/product/security/checks/bfla.py +77 -0
- mannf/product/security/checks/bola.py +77 -0
- mannf/product/security/checks/bopla.py +80 -0
- mannf/product/security/checks/broken_auth.py +86 -0
- mannf/product/security/checks/graphql_security.py +299 -0
- mannf/product/security/checks/inventory.py +70 -0
- mannf/product/security/checks/misconfig.py +158 -0
- mannf/product/security/checks/resource_consumption.py +70 -0
- mannf/product/security/checks/sensitive_flows.py +80 -0
- mannf/product/security/checks/ssrf.py +101 -0
- mannf/product/security/checks/unsafe_consumption.py +120 -0
- mannf/product/security/models.py +92 -0
- mannf/product/security/plugin_loader.py +182 -0
- mannf/product/security/reporter.py +92 -0
- mannf/product/security/scanner.py +183 -0
- mannf/product/server.py +6220 -0
- mannf/product/setup_wizard.py +873 -0
- mannf/product/status.py +404 -0
- mannf/product/storage/__init__.py +10 -0
- mannf/product/storage/artifact_store.py +343 -0
- mannf/product/telemetry.py +300 -0
- mannf/product/uninstall.py +169 -0
- mannf/product/upgrade.py +139 -0
- mannf/product/weights/__init__.py +13 -0
- mannf/product/weights/blob_store.py +299 -0
- mannf/product/weights/factory.py +42 -0
- mannf/product/weights/registry.py +159 -0
- mannf/product/weights/store.py +210 -0
- mannf/regression/__init__.py +7 -0
- mannf/regression/differ.py +9 -0
- mannf/regression/masking.py +9 -0
- mannf/regression/models.py +9 -0
- mannf/regression/recorder.py +9 -0
- mannf/regression/replayer.py +9 -0
- mannf/security/__init__.py +7 -0
- mannf/security/belief_guided.py +9 -0
- mannf/security/checks/__init__.py +7 -0
- mannf/security/checks/base.py +9 -0
- mannf/security/checks/bfla.py +9 -0
- mannf/security/checks/bola.py +9 -0
- mannf/security/checks/bopla.py +9 -0
- mannf/security/checks/broken_auth.py +9 -0
- mannf/security/checks/graphql_security.py +9 -0
- mannf/security/checks/inventory.py +9 -0
- mannf/security/checks/misconfig.py +9 -0
- mannf/security/checks/resource_consumption.py +9 -0
- mannf/security/checks/sensitive_flows.py +9 -0
- mannf/security/checks/ssrf.py +9 -0
- mannf/security/checks/unsafe_consumption.py +9 -0
- mannf/security/models.py +9 -0
- mannf/security/reporter.py +9 -0
- mannf/security/scanner.py +9 -0
- mannf/server.py +9 -0
- mannf/testing/__init__.py +7 -0
- mannf/testing/adaptive_controller.py +9 -0
- mannf/testing/models.py +9 -0
- mannf/weights/__init__.py +7 -0
- mannf/weights/registry.py +9 -0
- mannf/weights/store.py +9 -0
- nat_engine-1.dist-info/METADATA +555 -0
- nat_engine-1.dist-info/RECORD +299 -0
- nat_engine-1.dist-info/WHEEL +5 -0
- nat_engine-1.dist-info/entry_points.txt +4 -0
- nat_engine-1.dist-info/licenses/LICENSE +651 -0
- nat_engine-1.dist-info/licenses/NOTICE +178 -0
- nat_engine-1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Copyright (C) 2026 Brad Guider
|
|
2
|
+
# This file is part of NAT (Neural Agent Testing Framework).
|
|
3
|
+
# Licensed under the AGPL-3.0. See LICENSE for details.
|
|
4
|
+
|
|
5
|
+
"""NAT Admin CLI — manage tenants, API keys, and plans.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
nat-admin tenant create --name "Acme Corp" --email admin@acme.com [--plan free]
|
|
10
|
+
nat-admin tenant list [--plan free|pro|team|enterprise] [--limit 50]
|
|
11
|
+
nat-admin tenant show <tenant-id>
|
|
12
|
+
nat-admin tenant update <tenant-id> --plan pro [--quota 500] [--security-quota 100]
|
|
13
|
+
nat-admin tenant deactivate <tenant-id>
|
|
14
|
+
nat-admin tenant rotate-key <tenant-id>
|
|
15
|
+
nat-admin tenant usage <tenant-id>
|
|
16
|
+
|
|
17
|
+
Each subcommand supports ``--json`` for machine-readable output.
|
|
18
|
+
|
|
19
|
+
Exit codes:
|
|
20
|
+
0 — success
|
|
21
|
+
1 — error (tenant not found, validation error, etc.)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
import asyncio
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import sys
|
|
31
|
+
from typing import Any, Optional
|
|
32
|
+
|
|
33
|
+
from mannf.product.billing.tenant_manager import TenantManager
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Output helpers
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _print_json(data: Any) -> None:
|
|
42
|
+
print(json.dumps(data, indent=2, default=str))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _print_table(rows: list[dict], columns: Optional[list[str]] = None) -> None:
|
|
46
|
+
"""Print a list of dicts as a simple ASCII table."""
|
|
47
|
+
if not rows:
|
|
48
|
+
print("(no results)")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if columns is None:
|
|
52
|
+
columns = list(rows[0].keys())
|
|
53
|
+
|
|
54
|
+
# Calculate column widths
|
|
55
|
+
widths: dict[str, int] = {col: len(col) for col in columns}
|
|
56
|
+
for row in rows:
|
|
57
|
+
for col in columns:
|
|
58
|
+
val = str(row.get(col, ""))
|
|
59
|
+
if len(val) > widths[col]:
|
|
60
|
+
widths[col] = min(len(val), 60) # cap at 60 chars
|
|
61
|
+
|
|
62
|
+
header = " ".join(col.upper().ljust(widths[col]) for col in columns)
|
|
63
|
+
separator = " ".join("-" * widths[col] for col in columns)
|
|
64
|
+
print(header)
|
|
65
|
+
print(separator)
|
|
66
|
+
for row in rows:
|
|
67
|
+
line = " ".join(
|
|
68
|
+
str(row.get(col, ""))[:widths[col]].ljust(widths[col])
|
|
69
|
+
for col in columns
|
|
70
|
+
)
|
|
71
|
+
print(line)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _print_record(data: dict) -> None:
|
|
75
|
+
"""Print a single record as aligned key-value pairs."""
|
|
76
|
+
max_key = max((len(k) for k in data), default=0)
|
|
77
|
+
for key, value in data.items():
|
|
78
|
+
print(f" {key.ljust(max_key)} {value}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# DB initialisation
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _init_db() -> None:
|
|
87
|
+
"""Ensure the database engine is initialised from DATABASE_URL."""
|
|
88
|
+
db_url = os.environ.get("DATABASE_URL")
|
|
89
|
+
if not db_url:
|
|
90
|
+
print(
|
|
91
|
+
"Error: DATABASE_URL is not set. "
|
|
92
|
+
"Set it to a PostgreSQL connection string and retry.",
|
|
93
|
+
file=sys.stderr,
|
|
94
|
+
)
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
from mannf.product.database import _build_engine # noqa: PLC0415
|
|
99
|
+
|
|
100
|
+
if _build_engine() is None:
|
|
101
|
+
print(
|
|
102
|
+
"Error: Failed to initialise the database engine. "
|
|
103
|
+
"Check DATABASE_URL and ensure the database is reachable.",
|
|
104
|
+
file=sys.stderr,
|
|
105
|
+
)
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
except ImportError as exc:
|
|
108
|
+
print(f"Error: Database module not available: {exc}", file=sys.stderr)
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Command handlers
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def _cmd_create(args: argparse.Namespace) -> int:
|
|
118
|
+
manager = TenantManager()
|
|
119
|
+
try:
|
|
120
|
+
result = await manager.create_tenant(
|
|
121
|
+
name=args.name,
|
|
122
|
+
email=args.email,
|
|
123
|
+
plan_tier=args.plan,
|
|
124
|
+
)
|
|
125
|
+
except (RuntimeError, ValueError) as exc:
|
|
126
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
127
|
+
return 1
|
|
128
|
+
|
|
129
|
+
if args.json:
|
|
130
|
+
_print_json(result)
|
|
131
|
+
else:
|
|
132
|
+
print("Tenant created successfully.")
|
|
133
|
+
print()
|
|
134
|
+
_print_record(result)
|
|
135
|
+
print()
|
|
136
|
+
print("⚠️ Save the api_key now — it will not be shown again.")
|
|
137
|
+
return 0
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
async def _cmd_list(args: argparse.Namespace) -> int:
|
|
141
|
+
manager = TenantManager()
|
|
142
|
+
try:
|
|
143
|
+
tenants = await manager.list_tenants(
|
|
144
|
+
plan_filter=args.plan,
|
|
145
|
+
active_only=not args.all,
|
|
146
|
+
limit=args.limit,
|
|
147
|
+
)
|
|
148
|
+
except RuntimeError as exc:
|
|
149
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
150
|
+
return 1
|
|
151
|
+
|
|
152
|
+
if args.json:
|
|
153
|
+
_print_json(tenants)
|
|
154
|
+
else:
|
|
155
|
+
_print_table(
|
|
156
|
+
tenants,
|
|
157
|
+
columns=["tenant_id", "name", "email", "plan", "is_active", "created_at"],
|
|
158
|
+
)
|
|
159
|
+
print(f"\n{len(tenants)} tenant(s) listed.")
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def _cmd_show(args: argparse.Namespace) -> int:
|
|
164
|
+
manager = TenantManager()
|
|
165
|
+
try:
|
|
166
|
+
result = await manager.get_tenant(args.tenant_id)
|
|
167
|
+
except RuntimeError as exc:
|
|
168
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
169
|
+
return 1
|
|
170
|
+
|
|
171
|
+
if result is None:
|
|
172
|
+
print(f"Error: Tenant {args.tenant_id!r} not found.", file=sys.stderr)
|
|
173
|
+
return 1
|
|
174
|
+
|
|
175
|
+
if args.json:
|
|
176
|
+
_print_json(result)
|
|
177
|
+
else:
|
|
178
|
+
_print_record(result)
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def _cmd_update(args: argparse.Namespace) -> int:
|
|
183
|
+
manager = TenantManager()
|
|
184
|
+
try:
|
|
185
|
+
result = await manager.update_tenant(
|
|
186
|
+
tenant_id=args.tenant_id,
|
|
187
|
+
plan_tier=args.plan,
|
|
188
|
+
monthly_scan_quota=args.quota,
|
|
189
|
+
monthly_security_scan_quota=args.security_quota,
|
|
190
|
+
)
|
|
191
|
+
except KeyError as exc:
|
|
192
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
193
|
+
return 1
|
|
194
|
+
except (RuntimeError, ValueError) as exc:
|
|
195
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
196
|
+
return 1
|
|
197
|
+
|
|
198
|
+
if args.json:
|
|
199
|
+
_print_json(result)
|
|
200
|
+
else:
|
|
201
|
+
print("Tenant updated.")
|
|
202
|
+
print()
|
|
203
|
+
_print_record(result)
|
|
204
|
+
return 0
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def _cmd_deactivate(args: argparse.Namespace) -> int:
|
|
208
|
+
manager = TenantManager()
|
|
209
|
+
try:
|
|
210
|
+
result = await manager.deactivate_tenant(args.tenant_id)
|
|
211
|
+
except KeyError as exc:
|
|
212
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
213
|
+
return 1
|
|
214
|
+
except RuntimeError as exc:
|
|
215
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
216
|
+
return 1
|
|
217
|
+
|
|
218
|
+
if args.json:
|
|
219
|
+
_print_json(result)
|
|
220
|
+
else:
|
|
221
|
+
print(f"Tenant {args.tenant_id} has been deactivated.")
|
|
222
|
+
print()
|
|
223
|
+
_print_record(result)
|
|
224
|
+
return 0
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
async def _cmd_rotate_key(args: argparse.Namespace) -> int:
|
|
228
|
+
manager = TenantManager()
|
|
229
|
+
try:
|
|
230
|
+
result = await manager.rotate_api_key(args.tenant_id)
|
|
231
|
+
except KeyError as exc:
|
|
232
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
233
|
+
return 1
|
|
234
|
+
except RuntimeError as exc:
|
|
235
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
236
|
+
return 1
|
|
237
|
+
|
|
238
|
+
if args.json:
|
|
239
|
+
_print_json(result)
|
|
240
|
+
else:
|
|
241
|
+
print("API key rotated.")
|
|
242
|
+
print()
|
|
243
|
+
_print_record(result)
|
|
244
|
+
print()
|
|
245
|
+
print("⚠️ Save the new api_key now — it will not be shown again.")
|
|
246
|
+
return 0
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def _cmd_usage(args: argparse.Namespace) -> int:
|
|
250
|
+
manager = TenantManager()
|
|
251
|
+
try:
|
|
252
|
+
result = await manager.get_tenant_usage(args.tenant_id)
|
|
253
|
+
except KeyError as exc:
|
|
254
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
255
|
+
return 1
|
|
256
|
+
except RuntimeError as exc:
|
|
257
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
258
|
+
return 1
|
|
259
|
+
|
|
260
|
+
if args.json:
|
|
261
|
+
_print_json(result)
|
|
262
|
+
else:
|
|
263
|
+
_print_record(result)
|
|
264
|
+
return 0
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
# Argument parser
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
273
|
+
parser = argparse.ArgumentParser(
|
|
274
|
+
prog="nat-admin",
|
|
275
|
+
description="NAT Admin CLI — manage tenants, API keys, and plans.",
|
|
276
|
+
)
|
|
277
|
+
parser.add_argument(
|
|
278
|
+
"--json",
|
|
279
|
+
action="store_true",
|
|
280
|
+
default=False,
|
|
281
|
+
help="Output results as JSON instead of a formatted table.",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
subparsers = parser.add_subparsers(dest="resource", metavar="<resource>")
|
|
285
|
+
subparsers.required = True
|
|
286
|
+
|
|
287
|
+
# ---- tenant ----
|
|
288
|
+
tenant_parser = subparsers.add_parser("tenant", help="Tenant operations.")
|
|
289
|
+
tenant_subs = tenant_parser.add_subparsers(dest="action", metavar="<action>")
|
|
290
|
+
tenant_subs.required = True
|
|
291
|
+
|
|
292
|
+
# tenant create
|
|
293
|
+
create_p = tenant_subs.add_parser("create", help="Create a new tenant.")
|
|
294
|
+
create_p.add_argument("--name", required=True, help="Tenant display name.")
|
|
295
|
+
create_p.add_argument("--email", required=True, help="Tenant billing email.")
|
|
296
|
+
create_p.add_argument(
|
|
297
|
+
"--plan",
|
|
298
|
+
default="free",
|
|
299
|
+
choices=["free", "pro", "team", "enterprise"],
|
|
300
|
+
help="Plan tier (default: free).",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# tenant list
|
|
304
|
+
list_p = tenant_subs.add_parser("list", help="List tenants.")
|
|
305
|
+
list_p.add_argument(
|
|
306
|
+
"--plan",
|
|
307
|
+
choices=["free", "pro", "team", "enterprise"],
|
|
308
|
+
default=None,
|
|
309
|
+
help="Filter by plan tier.",
|
|
310
|
+
)
|
|
311
|
+
list_p.add_argument(
|
|
312
|
+
"--limit",
|
|
313
|
+
type=int,
|
|
314
|
+
default=100,
|
|
315
|
+
metavar="N",
|
|
316
|
+
help="Maximum number of results (default: 100).",
|
|
317
|
+
)
|
|
318
|
+
list_p.add_argument(
|
|
319
|
+
"--all",
|
|
320
|
+
action="store_true",
|
|
321
|
+
default=False,
|
|
322
|
+
help="Include inactive tenants.",
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# tenant show
|
|
326
|
+
show_p = tenant_subs.add_parser("show", help="Show tenant details.")
|
|
327
|
+
show_p.add_argument("tenant_id", metavar="<tenant-id>")
|
|
328
|
+
|
|
329
|
+
# tenant update
|
|
330
|
+
update_p = tenant_subs.add_parser("update", help="Update tenant settings.")
|
|
331
|
+
update_p.add_argument("tenant_id", metavar="<tenant-id>")
|
|
332
|
+
update_p.add_argument(
|
|
333
|
+
"--plan",
|
|
334
|
+
choices=["free", "pro", "team", "enterprise"],
|
|
335
|
+
default=None,
|
|
336
|
+
help="New plan tier.",
|
|
337
|
+
)
|
|
338
|
+
update_p.add_argument(
|
|
339
|
+
"--quota",
|
|
340
|
+
type=int,
|
|
341
|
+
default=None,
|
|
342
|
+
metavar="N",
|
|
343
|
+
dest="quota",
|
|
344
|
+
help="Override monthly scan quota.",
|
|
345
|
+
)
|
|
346
|
+
update_p.add_argument(
|
|
347
|
+
"--security-quota",
|
|
348
|
+
type=int,
|
|
349
|
+
default=None,
|
|
350
|
+
metavar="N",
|
|
351
|
+
dest="security_quota",
|
|
352
|
+
help="Override monthly security scan quota.",
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# tenant deactivate
|
|
356
|
+
deactivate_p = tenant_subs.add_parser("deactivate", help="Deactivate a tenant.")
|
|
357
|
+
deactivate_p.add_argument("tenant_id", metavar="<tenant-id>")
|
|
358
|
+
|
|
359
|
+
# tenant rotate-key
|
|
360
|
+
rotate_p = tenant_subs.add_parser("rotate-key", help="Rotate the tenant's API key.")
|
|
361
|
+
rotate_p.add_argument("tenant_id", metavar="<tenant-id>")
|
|
362
|
+
|
|
363
|
+
# tenant usage
|
|
364
|
+
usage_p = tenant_subs.add_parser("usage", help="Show current month's usage.")
|
|
365
|
+
usage_p.add_argument("tenant_id", metavar="<tenant-id>")
|
|
366
|
+
|
|
367
|
+
return parser
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# ---------------------------------------------------------------------------
|
|
371
|
+
# Dispatch
|
|
372
|
+
# ---------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
_ACTION_MAP = {
|
|
376
|
+
"create": _cmd_create,
|
|
377
|
+
"list": _cmd_list,
|
|
378
|
+
"show": _cmd_show,
|
|
379
|
+
"update": _cmd_update,
|
|
380
|
+
"deactivate": _cmd_deactivate,
|
|
381
|
+
"rotate-key": _cmd_rotate_key,
|
|
382
|
+
"usage": _cmd_usage,
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def main(argv: Optional[list[str]] = None) -> None:
|
|
387
|
+
"""Entry point for the ``nat-admin`` CLI command."""
|
|
388
|
+
parser = _build_parser()
|
|
389
|
+
args = parser.parse_args(argv)
|
|
390
|
+
|
|
391
|
+
# Propagate the top-level --json flag to the parsed namespace so
|
|
392
|
+
# sub-command handlers can access it.
|
|
393
|
+
if not hasattr(args, "json"):
|
|
394
|
+
args.json = False
|
|
395
|
+
|
|
396
|
+
_init_db()
|
|
397
|
+
|
|
398
|
+
handler = _ACTION_MAP.get(args.action)
|
|
399
|
+
if handler is None:
|
|
400
|
+
parser.print_help()
|
|
401
|
+
sys.exit(1)
|
|
402
|
+
|
|
403
|
+
exit_code = asyncio.run(handler(args))
|
|
404
|
+
sys.exit(exit_code)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
if __name__ == "__main__":
|
|
408
|
+
main()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Copyright (C) 2026 Brad Guider
|
|
2
|
+
# This file is part of NAT (Neural Agent Testing Framework).
|
|
3
|
+
# Licensed under the AGPL-3.0. See LICENSE for details.
|
|
4
|
+
# Commercial licensing available — see COMMERCIAL_LICENSE.md.
|
|
5
|
+
|
|
6
|
+
"""NAT Web Dashboard package.
|
|
7
|
+
|
|
8
|
+
Provides a single-page web dashboard for visualizing BDI agent belief
|
|
9
|
+
states, endpoint risk heatmaps, anomaly alerts, and scan history.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from mannf.product.dashboard.app import router as dashboard_router
|
|
13
|
+
from mannf.product.dashboard.models import (
|
|
14
|
+
A11yViolationSummary,
|
|
15
|
+
AnomalyTimelineEntry,
|
|
16
|
+
BeliefStateSnapshot,
|
|
17
|
+
BeliefTrend,
|
|
18
|
+
DashboardSummary,
|
|
19
|
+
DiscoveryRequest,
|
|
20
|
+
DiscoveryResult,
|
|
21
|
+
DiscoveryScenario,
|
|
22
|
+
EndpointRiskCard,
|
|
23
|
+
FailureCluster,
|
|
24
|
+
FlakinessSummary,
|
|
25
|
+
FunctionalTestItem,
|
|
26
|
+
FunctionalTestResult,
|
|
27
|
+
InvoiceEntry,
|
|
28
|
+
OnboardingStatus,
|
|
29
|
+
OnboardingStep,
|
|
30
|
+
PerfMetrics,
|
|
31
|
+
PredictiveRiskScore,
|
|
32
|
+
RootCauseSuggestion,
|
|
33
|
+
UsageSummary,
|
|
34
|
+
VisualDiffEntry,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"dashboard_router",
|
|
39
|
+
"DashboardSummary",
|
|
40
|
+
"EndpointRiskCard",
|
|
41
|
+
"BeliefStateSnapshot",
|
|
42
|
+
"AnomalyTimelineEntry",
|
|
43
|
+
"FunctionalTestResult",
|
|
44
|
+
"FunctionalTestItem",
|
|
45
|
+
"VisualDiffEntry",
|
|
46
|
+
"A11yViolationSummary",
|
|
47
|
+
"PerfMetrics",
|
|
48
|
+
"UsageSummary",
|
|
49
|
+
"InvoiceEntry",
|
|
50
|
+
"OnboardingStatus",
|
|
51
|
+
"OnboardingStep",
|
|
52
|
+
"DiscoveryRequest",
|
|
53
|
+
"DiscoveryScenario",
|
|
54
|
+
"DiscoveryResult",
|
|
55
|
+
# Phase 6.5
|
|
56
|
+
"BeliefTrend",
|
|
57
|
+
"FlakinessSummary",
|
|
58
|
+
"FailureCluster",
|
|
59
|
+
"RootCauseSuggestion",
|
|
60
|
+
"PredictiveRiskScore",
|
|
61
|
+
]
|