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.
Files changed (299) hide show
  1. mannf/__init__.py +33 -0
  2. mannf/__main__.py +10 -0
  3. mannf/_version.py +8 -0
  4. mannf/agents/__init__.py +7 -0
  5. mannf/agents/analyzer_agent.py +9 -0
  6. mannf/agents/base.py +9 -0
  7. mannf/agents/bdi_agent.py +9 -0
  8. mannf/agents/belief_state.py +9 -0
  9. mannf/agents/coordinator_agent.py +9 -0
  10. mannf/agents/executor_agent.py +9 -0
  11. mannf/agents/monitor_agent.py +9 -0
  12. mannf/agents/oracle_agent.py +9 -0
  13. mannf/agents/planner_agent.py +9 -0
  14. mannf/agents/test_agent.py +9 -0
  15. mannf/anomaly/__init__.py +7 -0
  16. mannf/anomaly/enhanced_detector.py +9 -0
  17. mannf/cli.py +9 -0
  18. mannf/core/__init__.py +26 -0
  19. mannf/core/agents/__init__.py +52 -0
  20. mannf/core/agents/accessibility_scanner_agent.py +245 -0
  21. mannf/core/agents/analyzer_agent.py +224 -0
  22. mannf/core/agents/autonomous_loop_agent.py +1086 -0
  23. mannf/core/agents/autonomous_loop_models.py +62 -0
  24. mannf/core/agents/autonomous_run_differ.py +427 -0
  25. mannf/core/agents/base.py +128 -0
  26. mannf/core/agents/bdi_agent.py +330 -0
  27. mannf/core/agents/belief_state.py +202 -0
  28. mannf/core/agents/browser_coordinator_agent.py +224 -0
  29. mannf/core/agents/browser_executor_agent.py +410 -0
  30. mannf/core/agents/coordinator_agent.py +262 -0
  31. mannf/core/agents/executor_agent.py +222 -0
  32. mannf/core/agents/monitor_agent.py +188 -0
  33. mannf/core/agents/oracle_agent.py +150 -0
  34. mannf/core/agents/performance_testing_agent.py +279 -0
  35. mannf/core/agents/planner_agent.py +128 -0
  36. mannf/core/agents/test_agent.py +249 -0
  37. mannf/core/agents/visual_regression_agent.py +311 -0
  38. mannf/core/agents/web_crawler_agent.py +510 -0
  39. mannf/core/agents/worker_pool.py +366 -0
  40. mannf/core/anomaly/__init__.py +14 -0
  41. mannf/core/anomaly/enhanced_detector.py +541 -0
  42. mannf/core/browser/__init__.py +63 -0
  43. mannf/core/browser/accessibility_scanner.py +424 -0
  44. mannf/core/browser/discovery_model.py +178 -0
  45. mannf/core/browser/dom_snapshot.py +349 -0
  46. mannf/core/browser/ingestor_bridge.py +371 -0
  47. mannf/core/browser/performance_metrics.py +217 -0
  48. mannf/core/browser/reflection_analyzer.py +442 -0
  49. mannf/core/browser/scenario_generator.py +1100 -0
  50. mannf/core/browser/security_scenario_generator.py +695 -0
  51. mannf/core/browser/visual_comparer.py +159 -0
  52. mannf/core/diagnostics/__init__.py +28 -0
  53. mannf/core/diagnostics/failure_clusterer.py +211 -0
  54. mannf/core/diagnostics/flake_detector.py +233 -0
  55. mannf/core/diagnostics/root_cause_analyzer.py +273 -0
  56. mannf/core/distributed/__init__.py +16 -0
  57. mannf/core/distributed/endpoint.py +139 -0
  58. mannf/core/distributed/system_under_test.py +207 -0
  59. mannf/core/functional_orchestrator.py +428 -0
  60. mannf/core/messaging/__init__.py +11 -0
  61. mannf/core/messaging/bus.py +113 -0
  62. mannf/core/messaging/messages.py +89 -0
  63. mannf/core/nat_orchestrator.py +342 -0
  64. mannf/core/neural/__init__.py +183 -0
  65. mannf/core/orchestrator.py +272 -0
  66. mannf/core/prioritization/__init__.py +17 -0
  67. mannf/core/prioritization/adaptive_controller.py +509 -0
  68. mannf/core/prioritization/belief_prioritizer.py +231 -0
  69. mannf/core/prioritization/risk_scorer.py +430 -0
  70. mannf/core/reporting/__init__.py +12 -0
  71. mannf/core/reporting/unified_report.py +664 -0
  72. mannf/core/testing/__init__.py +17 -0
  73. mannf/core/testing/adaptive_controller.py +149 -0
  74. mannf/core/testing/models.py +179 -0
  75. mannf/core/validation/__init__.py +10 -0
  76. mannf/core/validation/self_validation_runner.py +180 -0
  77. mannf/dashboard/__init__.py +7 -0
  78. mannf/dashboard/app.py +9 -0
  79. mannf/dashboard/models.py +9 -0
  80. mannf/dashboard/static/index.html +2538 -0
  81. mannf/dashboard/telemetry.py +9 -0
  82. mannf/distributed/__init__.py +7 -0
  83. mannf/distributed/endpoint.py +9 -0
  84. mannf/distributed/system_under_test.py +9 -0
  85. mannf/healing/__init__.py +7 -0
  86. mannf/healing/graphql_schema_diff.py +9 -0
  87. mannf/healing/healer.py +9 -0
  88. mannf/healing/models.py +9 -0
  89. mannf/healing/schema_diff.py +9 -0
  90. mannf/integrations/__init__.py +7 -0
  91. mannf/integrations/auth.py +9 -0
  92. mannf/integrations/graphql_parser.py +9 -0
  93. mannf/integrations/graphql_sut.py +9 -0
  94. mannf/integrations/http_sut.py +9 -0
  95. mannf/integrations/openapi_parser.py +9 -0
  96. mannf/integrations/postman_parser.py +9 -0
  97. mannf/llm/__init__.py +7 -0
  98. mannf/llm/anthropic_provider.py +9 -0
  99. mannf/llm/base.py +9 -0
  100. mannf/llm/config.py +9 -0
  101. mannf/llm/factory.py +9 -0
  102. mannf/llm/openai_provider.py +9 -0
  103. mannf/llm/prompts.py +9 -0
  104. mannf/messaging/__init__.py +7 -0
  105. mannf/messaging/bus.py +9 -0
  106. mannf/messaging/messages.py +9 -0
  107. mannf/nat_orchestrator.py +9 -0
  108. mannf/neural/__init__.py +7 -0
  109. mannf/orchestrator.py +9 -0
  110. mannf/prioritization/__init__.py +7 -0
  111. mannf/prioritization/adaptive_controller.py +9 -0
  112. mannf/prioritization/belief_prioritizer.py +9 -0
  113. mannf/prioritization/risk_scorer.py +9 -0
  114. mannf/product/__init__.py +29 -0
  115. mannf/product/admin/__init__.py +3 -0
  116. mannf/product/admin/routes.py +514 -0
  117. mannf/product/auth/__init__.py +5 -0
  118. mannf/product/auth/saml.py +212 -0
  119. mannf/product/billing/__init__.py +5 -0
  120. mannf/product/billing/audit.py +160 -0
  121. mannf/product/billing/feature_gates.py +180 -0
  122. mannf/product/billing/metering.py +179 -0
  123. mannf/product/billing/notifications.py +181 -0
  124. mannf/product/billing/plans.py +133 -0
  125. mannf/product/billing/rate_limits.py +35 -0
  126. mannf/product/billing/stripe_billing.py +906 -0
  127. mannf/product/billing/tenant_auth.py +233 -0
  128. mannf/product/billing/tenant_manager.py +873 -0
  129. mannf/product/cli.py +3900 -0
  130. mannf/product/cli_admin.py +408 -0
  131. mannf/product/dashboard/__init__.py +61 -0
  132. mannf/product/dashboard/app.py +3567 -0
  133. mannf/product/dashboard/models.py +460 -0
  134. mannf/product/dashboard/static/index.html +6347 -0
  135. mannf/product/dashboard/static/manifest.json +25 -0
  136. mannf/product/dashboard/static/pwa-icon-192.png +0 -0
  137. mannf/product/dashboard/static/pwa-icon-512.png +0 -0
  138. mannf/product/dashboard/static/sw.js +64 -0
  139. mannf/product/dashboard/telemetry.py +547 -0
  140. mannf/product/database.py +145 -0
  141. mannf/product/demo.py +844 -0
  142. mannf/product/doctor.py +509 -0
  143. mannf/product/exporters/__init__.py +65 -0
  144. mannf/product/exporters/azuredevops_exporter.py +257 -0
  145. mannf/product/exporters/base.py +307 -0
  146. mannf/product/exporters/bugzilla_exporter.py +200 -0
  147. mannf/product/exporters/dedup.py +275 -0
  148. mannf/product/exporters/finding_adapter.py +216 -0
  149. mannf/product/exporters/github_exporter.py +197 -0
  150. mannf/product/exporters/gitlab_exporter.py +215 -0
  151. mannf/product/exporters/jira_exporter.py +180 -0
  152. mannf/product/exporters/linear_exporter.py +195 -0
  153. mannf/product/exporters/loader.py +233 -0
  154. mannf/product/exporters/pagerduty_exporter.py +363 -0
  155. mannf/product/exporters/sentry_exporter.py +322 -0
  156. mannf/product/exporters/servicenow_exporter.py +240 -0
  157. mannf/product/exporters/shortcut_exporter.py +231 -0
  158. mannf/product/exporters/webhook_exporter.py +383 -0
  159. mannf/product/formatters/__init__.py +18 -0
  160. mannf/product/formatters/allure_formatter.py +161 -0
  161. mannf/product/formatters/ctrf_formatter.py +149 -0
  162. mannf/product/healing/__init__.py +30 -0
  163. mannf/product/healing/graphql_schema_diff.py +152 -0
  164. mannf/product/healing/healer.py +141 -0
  165. mannf/product/healing/models.py +175 -0
  166. mannf/product/healing/schema_diff.py +251 -0
  167. mannf/product/ingestors/__init__.py +77 -0
  168. mannf/product/ingestors/base.py +256 -0
  169. mannf/product/ingestors/bgstm_ingestor.py +764 -0
  170. mannf/product/ingestors/curl_ingestor.py +1019 -0
  171. mannf/product/ingestors/cypress_ingestor.py +487 -0
  172. mannf/product/ingestors/gherkin_ingestor.py +967 -0
  173. mannf/product/ingestors/graphql_ingestor.py +845 -0
  174. mannf/product/ingestors/grpc_ingestor.py +591 -0
  175. mannf/product/ingestors/har_ingestor.py +976 -0
  176. mannf/product/ingestors/loader.py +284 -0
  177. mannf/product/ingestors/models.py +146 -0
  178. mannf/product/ingestors/openapi_ingestor.py +606 -0
  179. mannf/product/ingestors/playwright_ingestor.py +449 -0
  180. mannf/product/ingestors/postman_ingestor.py +631 -0
  181. mannf/product/ingestors/traffic_ingestor.py +679 -0
  182. mannf/product/ingestors/websocket_ingestor.py +526 -0
  183. mannf/product/integrations/__init__.py +21 -0
  184. mannf/product/integrations/auth.py +190 -0
  185. mannf/product/integrations/graphql_parser.py +436 -0
  186. mannf/product/integrations/graphql_sut.py +247 -0
  187. mannf/product/integrations/grpc_sut.py +469 -0
  188. mannf/product/integrations/http_sut.py +237 -0
  189. mannf/product/integrations/kafka_adapter.py +342 -0
  190. mannf/product/integrations/openapi_parser.py +513 -0
  191. mannf/product/integrations/postman_parser.py +467 -0
  192. mannf/product/integrations/webhook_receiver.py +344 -0
  193. mannf/product/integrations/websocket_sut.py +434 -0
  194. mannf/product/llm/__init__.py +25 -0
  195. mannf/product/llm/anthropic_provider.py +94 -0
  196. mannf/product/llm/base.py +267 -0
  197. mannf/product/llm/config.py +48 -0
  198. mannf/product/llm/factory.py +42 -0
  199. mannf/product/llm/openai_provider.py +93 -0
  200. mannf/product/llm/prompts.py +403 -0
  201. mannf/product/llm/root_cause_service.py +311 -0
  202. mannf/product/llm/test_plan_models.py +78 -0
  203. mannf/product/metrics.py +149 -0
  204. mannf/product/middleware/__init__.py +3 -0
  205. mannf/product/middleware/audit_middleware.py +112 -0
  206. mannf/product/middleware/tenant_isolation.py +114 -0
  207. mannf/product/models.py +347 -0
  208. mannf/product/notifications/__init__.py +24 -0
  209. mannf/product/notifications/dispatcher.py +411 -0
  210. mannf/product/onboarding.py +190 -0
  211. mannf/product/orchestration/__init__.py +39 -0
  212. mannf/product/orchestration/ingest_scan_orchestrator.py +339 -0
  213. mannf/product/orchestration/pipeline.py +401 -0
  214. mannf/product/orchestrator.py +987 -0
  215. mannf/product/orchestrator_models.py +269 -0
  216. mannf/product/regression/__init__.py +36 -0
  217. mannf/product/regression/differ.py +172 -0
  218. mannf/product/regression/masking.py +100 -0
  219. mannf/product/regression/models.py +232 -0
  220. mannf/product/regression/recorder.py +124 -0
  221. mannf/product/regression/replayer.py +168 -0
  222. mannf/product/reports/__init__.py +10 -0
  223. mannf/product/reports/pdf.py +132 -0
  224. mannf/product/scheduling/__init__.py +57 -0
  225. mannf/product/scheduling/cron_utils.py +251 -0
  226. mannf/product/scheduling/engine.py +473 -0
  227. mannf/product/scheduling/models.py +86 -0
  228. mannf/product/scheduling/queue.py +894 -0
  229. mannf/product/scheduling/store.py +235 -0
  230. mannf/product/security/__init__.py +21 -0
  231. mannf/product/security/belief_guided.py +143 -0
  232. mannf/product/security/checks/__init__.py +55 -0
  233. mannf/product/security/checks/base.py +69 -0
  234. mannf/product/security/checks/bfla.py +77 -0
  235. mannf/product/security/checks/bola.py +77 -0
  236. mannf/product/security/checks/bopla.py +80 -0
  237. mannf/product/security/checks/broken_auth.py +86 -0
  238. mannf/product/security/checks/graphql_security.py +299 -0
  239. mannf/product/security/checks/inventory.py +70 -0
  240. mannf/product/security/checks/misconfig.py +158 -0
  241. mannf/product/security/checks/resource_consumption.py +70 -0
  242. mannf/product/security/checks/sensitive_flows.py +80 -0
  243. mannf/product/security/checks/ssrf.py +101 -0
  244. mannf/product/security/checks/unsafe_consumption.py +120 -0
  245. mannf/product/security/models.py +92 -0
  246. mannf/product/security/plugin_loader.py +182 -0
  247. mannf/product/security/reporter.py +92 -0
  248. mannf/product/security/scanner.py +183 -0
  249. mannf/product/server.py +6220 -0
  250. mannf/product/setup_wizard.py +873 -0
  251. mannf/product/status.py +404 -0
  252. mannf/product/storage/__init__.py +10 -0
  253. mannf/product/storage/artifact_store.py +343 -0
  254. mannf/product/telemetry.py +300 -0
  255. mannf/product/uninstall.py +169 -0
  256. mannf/product/upgrade.py +139 -0
  257. mannf/product/weights/__init__.py +13 -0
  258. mannf/product/weights/blob_store.py +299 -0
  259. mannf/product/weights/factory.py +42 -0
  260. mannf/product/weights/registry.py +159 -0
  261. mannf/product/weights/store.py +210 -0
  262. mannf/regression/__init__.py +7 -0
  263. mannf/regression/differ.py +9 -0
  264. mannf/regression/masking.py +9 -0
  265. mannf/regression/models.py +9 -0
  266. mannf/regression/recorder.py +9 -0
  267. mannf/regression/replayer.py +9 -0
  268. mannf/security/__init__.py +7 -0
  269. mannf/security/belief_guided.py +9 -0
  270. mannf/security/checks/__init__.py +7 -0
  271. mannf/security/checks/base.py +9 -0
  272. mannf/security/checks/bfla.py +9 -0
  273. mannf/security/checks/bola.py +9 -0
  274. mannf/security/checks/bopla.py +9 -0
  275. mannf/security/checks/broken_auth.py +9 -0
  276. mannf/security/checks/graphql_security.py +9 -0
  277. mannf/security/checks/inventory.py +9 -0
  278. mannf/security/checks/misconfig.py +9 -0
  279. mannf/security/checks/resource_consumption.py +9 -0
  280. mannf/security/checks/sensitive_flows.py +9 -0
  281. mannf/security/checks/ssrf.py +9 -0
  282. mannf/security/checks/unsafe_consumption.py +9 -0
  283. mannf/security/models.py +9 -0
  284. mannf/security/reporter.py +9 -0
  285. mannf/security/scanner.py +9 -0
  286. mannf/server.py +9 -0
  287. mannf/testing/__init__.py +7 -0
  288. mannf/testing/adaptive_controller.py +9 -0
  289. mannf/testing/models.py +9 -0
  290. mannf/weights/__init__.py +7 -0
  291. mannf/weights/registry.py +9 -0
  292. mannf/weights/store.py +9 -0
  293. nat_engine-1.dist-info/METADATA +555 -0
  294. nat_engine-1.dist-info/RECORD +299 -0
  295. nat_engine-1.dist-info/WHEEL +5 -0
  296. nat_engine-1.dist-info/entry_points.txt +4 -0
  297. nat_engine-1.dist-info/licenses/LICENSE +651 -0
  298. nat_engine-1.dist-info/licenses/NOTICE +178 -0
  299. 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
+ ]