@aiassesstech/grillo 0.1.0

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 (121) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/README.md +512 -0
  4. package/SKILL.md +87 -0
  5. package/dist/api/server.d.ts +68 -0
  6. package/dist/api/server.d.ts.map +1 -0
  7. package/dist/api/server.js +596 -0
  8. package/dist/api/server.js.map +1 -0
  9. package/dist/audit/audit-log.d.ts +88 -0
  10. package/dist/audit/audit-log.d.ts.map +1 -0
  11. package/dist/audit/audit-log.js +195 -0
  12. package/dist/audit/audit-log.js.map +1 -0
  13. package/dist/certification/certificate.d.ts +80 -0
  14. package/dist/certification/certificate.d.ts.map +1 -0
  15. package/dist/certification/certificate.js +176 -0
  16. package/dist/certification/certificate.js.map +1 -0
  17. package/dist/cli/bin.d.ts +8 -0
  18. package/dist/cli/bin.d.ts.map +1 -0
  19. package/dist/cli/bin.js +12 -0
  20. package/dist/cli/bin.js.map +1 -0
  21. package/dist/cli/config-loader.d.ts +66 -0
  22. package/dist/cli/config-loader.d.ts.map +1 -0
  23. package/dist/cli/config-loader.js +243 -0
  24. package/dist/cli/config-loader.js.map +1 -0
  25. package/dist/cli/runner.d.ts +27 -0
  26. package/dist/cli/runner.d.ts.map +1 -0
  27. package/dist/cli/runner.js +388 -0
  28. package/dist/cli/runner.js.map +1 -0
  29. package/dist/commands/grillo-commands.d.ts +50 -0
  30. package/dist/commands/grillo-commands.d.ts.map +1 -0
  31. package/dist/commands/grillo-commands.js +752 -0
  32. package/dist/commands/grillo-commands.js.map +1 -0
  33. package/dist/commands/inline-commands.d.ts +16 -0
  34. package/dist/commands/inline-commands.d.ts.map +1 -0
  35. package/dist/commands/inline-commands.js +277 -0
  36. package/dist/commands/inline-commands.js.map +1 -0
  37. package/dist/commands/router.d.ts +56 -0
  38. package/dist/commands/router.d.ts.map +1 -0
  39. package/dist/commands/router.js +154 -0
  40. package/dist/commands/router.js.map +1 -0
  41. package/dist/config/defaults.d.ts +9 -0
  42. package/dist/config/defaults.d.ts.map +1 -0
  43. package/dist/config/defaults.js +78 -0
  44. package/dist/config/defaults.js.map +1 -0
  45. package/dist/config/schema.d.ts +573 -0
  46. package/dist/config/schema.d.ts.map +1 -0
  47. package/dist/config/schema.js +142 -0
  48. package/dist/config/schema.js.map +1 -0
  49. package/dist/dashboard/metrics.d.ts +100 -0
  50. package/dist/dashboard/metrics.d.ts.map +1 -0
  51. package/dist/dashboard/metrics.js +282 -0
  52. package/dist/dashboard/metrics.js.map +1 -0
  53. package/dist/dashboard/ui.d.ts +19 -0
  54. package/dist/dashboard/ui.d.ts.map +1 -0
  55. package/dist/dashboard/ui.js +951 -0
  56. package/dist/dashboard/ui.js.map +1 -0
  57. package/dist/discovery/discovery-adapter.d.ts +94 -0
  58. package/dist/discovery/discovery-adapter.d.ts.map +1 -0
  59. package/dist/discovery/discovery-adapter.js +114 -0
  60. package/dist/discovery/discovery-adapter.js.map +1 -0
  61. package/dist/discovery/discovery-service.d.ts +77 -0
  62. package/dist/discovery/discovery-service.d.ts.map +1 -0
  63. package/dist/discovery/discovery-service.js +240 -0
  64. package/dist/discovery/discovery-service.js.map +1 -0
  65. package/dist/drift/detector.d.ts +51 -0
  66. package/dist/drift/detector.d.ts.map +1 -0
  67. package/dist/drift/detector.js +148 -0
  68. package/dist/drift/detector.js.map +1 -0
  69. package/dist/drift/fleet-anomaly.d.ts +28 -0
  70. package/dist/drift/fleet-anomaly.d.ts.map +1 -0
  71. package/dist/drift/fleet-anomaly.js +186 -0
  72. package/dist/drift/fleet-anomaly.js.map +1 -0
  73. package/dist/events/event-bus.d.ts +209 -0
  74. package/dist/events/event-bus.d.ts.map +1 -0
  75. package/dist/events/event-bus.js +184 -0
  76. package/dist/events/event-bus.js.map +1 -0
  77. package/dist/frameworks/framework-registry.d.ts +116 -0
  78. package/dist/frameworks/framework-registry.d.ts.map +1 -0
  79. package/dist/frameworks/framework-registry.js +241 -0
  80. package/dist/frameworks/framework-registry.js.map +1 -0
  81. package/dist/index.d.ts +94 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +254 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/monitoring/continuous-monitor.d.ts +61 -0
  86. package/dist/monitoring/continuous-monitor.d.ts.map +1 -0
  87. package/dist/monitoring/continuous-monitor.js +191 -0
  88. package/dist/monitoring/continuous-monitor.js.map +1 -0
  89. package/dist/notifications/notifier.d.ts +21 -0
  90. package/dist/notifications/notifier.d.ts.map +1 -0
  91. package/dist/notifications/notifier.js +119 -0
  92. package/dist/notifications/notifier.js.map +1 -0
  93. package/dist/notifications/templates.d.ts +14 -0
  94. package/dist/notifications/templates.d.ts.map +1 -0
  95. package/dist/notifications/templates.js +105 -0
  96. package/dist/notifications/templates.js.map +1 -0
  97. package/dist/orchestration/orchestrator.d.ts +99 -0
  98. package/dist/orchestration/orchestrator.d.ts.map +1 -0
  99. package/dist/orchestration/orchestrator.js +426 -0
  100. package/dist/orchestration/orchestrator.js.map +1 -0
  101. package/dist/orchestration/queue.d.ts +17 -0
  102. package/dist/orchestration/queue.d.ts.map +1 -0
  103. package/dist/orchestration/queue.js +121 -0
  104. package/dist/orchestration/queue.js.map +1 -0
  105. package/dist/orchestration/scheduler.d.ts +26 -0
  106. package/dist/orchestration/scheduler.d.ts.map +1 -0
  107. package/dist/orchestration/scheduler.js +110 -0
  108. package/dist/orchestration/scheduler.js.map +1 -0
  109. package/dist/registry/agent-registry.d.ts +106 -0
  110. package/dist/registry/agent-registry.d.ts.map +1 -0
  111. package/dist/registry/agent-registry.js +349 -0
  112. package/dist/registry/agent-registry.js.map +1 -0
  113. package/dist/registry/types.d.ts +158 -0
  114. package/dist/registry/types.d.ts.map +1 -0
  115. package/dist/registry/types.js +44 -0
  116. package/dist/registry/types.js.map +1 -0
  117. package/dist/reports/compliance-report.d.ts +66 -0
  118. package/dist/reports/compliance-report.d.ts.map +1 -0
  119. package/dist/reports/compliance-report.js +208 -0
  120. package/dist/reports/compliance-report.js.map +1 -0
  121. package/package.json +67 -0
@@ -0,0 +1,951 @@
1
+ /**
2
+ * Grillo Cricket — Dashboard UI
3
+ *
4
+ * Embedded single-page web dashboard. Served as inline HTML from the
5
+ * API server — zero external dependencies, zero build step.
6
+ *
7
+ * Features:
8
+ * - Fleet overview with live status cards
9
+ * - Agent table with status badges and risk tiers
10
+ * - Drift gauges per agent
11
+ * - Hierarchical progression visualization
12
+ * - Audit trail viewer
13
+ * - Auto-refresh via polling
14
+ *
15
+ * All data fetched from /api/grillo/* endpoints.
16
+ */
17
+ // ================================================================
18
+ // Dashboard HTML Generator
19
+ // ================================================================
20
+ export function generateDashboardHTML(config) {
21
+ const productName = config.whiteLabel?.productName ?? "Grillo Cricket";
22
+ const primaryColor = config.whiteLabel?.primaryColor ?? "#4F46E5";
23
+ const footer = config.whiteLabel?.reportFooter ?? "Powered by Grillo Cricket — The Conscience for AI";
24
+ return `<!DOCTYPE html>
25
+ <html lang="en">
26
+ <head>
27
+ <meta charset="UTF-8">
28
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
29
+ <title>${productName} — Fleet Dashboard</title>
30
+ <style>
31
+ :root {
32
+ --primary: ${primaryColor};
33
+ --primary-light: ${primaryColor}20;
34
+ --bg: #0F172A;
35
+ --bg-card: #1E293B;
36
+ --bg-hover: #334155;
37
+ --text: #F1F5F9;
38
+ --text-muted: #94A3B8;
39
+ --text-dim: #64748B;
40
+ --border: #334155;
41
+ --success: #22C55E;
42
+ --warning: #F59E0B;
43
+ --danger: #EF4444;
44
+ --info: #3B82F6;
45
+ --probation: #A855F7;
46
+ --radius: 12px;
47
+ --radius-sm: 8px;
48
+ }
49
+
50
+ * { margin: 0; padding: 0; box-sizing: border-box; }
51
+
52
+ body {
53
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
54
+ background: var(--bg);
55
+ color: var(--text);
56
+ min-height: 100vh;
57
+ line-height: 1.5;
58
+ }
59
+
60
+ /* ---- Header ---- */
61
+ .header {
62
+ background: var(--bg-card);
63
+ border-bottom: 1px solid var(--border);
64
+ padding: 16px 32px;
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: space-between;
68
+ position: sticky;
69
+ top: 0;
70
+ z-index: 100;
71
+ backdrop-filter: blur(12px);
72
+ }
73
+
74
+ .header-left {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 12px;
78
+ }
79
+
80
+ .logo {
81
+ width: 36px;
82
+ height: 36px;
83
+ background: var(--primary);
84
+ border-radius: 10px;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ font-size: 20px;
89
+ }
90
+
91
+ .header h1 {
92
+ font-size: 1.25rem;
93
+ font-weight: 700;
94
+ letter-spacing: -0.02em;
95
+ }
96
+
97
+ .header h1 span {
98
+ color: var(--text-muted);
99
+ font-weight: 400;
100
+ }
101
+
102
+ .header-right {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 16px;
106
+ }
107
+
108
+ .refresh-indicator {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 6px;
112
+ color: var(--text-dim);
113
+ font-size: 0.8rem;
114
+ }
115
+
116
+ .pulse {
117
+ width: 8px;
118
+ height: 8px;
119
+ background: var(--success);
120
+ border-radius: 50%;
121
+ animation: pulse 2s infinite;
122
+ }
123
+
124
+ @keyframes pulse {
125
+ 0%, 100% { opacity: 1; }
126
+ 50% { opacity: 0.3; }
127
+ }
128
+
129
+ /* ---- Navigation Tabs ---- */
130
+ .nav-tabs {
131
+ display: flex;
132
+ gap: 4px;
133
+ padding: 0 32px;
134
+ background: var(--bg-card);
135
+ border-bottom: 1px solid var(--border);
136
+ }
137
+
138
+ .nav-tab {
139
+ padding: 12px 20px;
140
+ color: var(--text-muted);
141
+ cursor: pointer;
142
+ border-bottom: 2px solid transparent;
143
+ font-size: 0.875rem;
144
+ font-weight: 500;
145
+ transition: all 0.2s;
146
+ user-select: none;
147
+ }
148
+
149
+ .nav-tab:hover { color: var(--text); background: var(--bg-hover); }
150
+ .nav-tab.active {
151
+ color: var(--primary);
152
+ border-bottom-color: var(--primary);
153
+ }
154
+
155
+ /* ---- Main Content ---- */
156
+ .main {
157
+ max-width: 1400px;
158
+ margin: 0 auto;
159
+ padding: 24px 32px;
160
+ }
161
+
162
+ .section { display: none; }
163
+ .section.active { display: block; }
164
+
165
+ /* ---- Stat Cards ---- */
166
+ .stats-grid {
167
+ display: grid;
168
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
169
+ gap: 16px;
170
+ margin-bottom: 24px;
171
+ }
172
+
173
+ .stat-card {
174
+ background: var(--bg-card);
175
+ border: 1px solid var(--border);
176
+ border-radius: var(--radius);
177
+ padding: 20px;
178
+ transition: transform 0.15s, border-color 0.15s;
179
+ }
180
+
181
+ .stat-card:hover {
182
+ transform: translateY(-2px);
183
+ border-color: var(--primary);
184
+ }
185
+
186
+ .stat-label {
187
+ font-size: 0.75rem;
188
+ text-transform: uppercase;
189
+ letter-spacing: 0.06em;
190
+ color: var(--text-dim);
191
+ margin-bottom: 4px;
192
+ }
193
+
194
+ .stat-value {
195
+ font-size: 2rem;
196
+ font-weight: 700;
197
+ letter-spacing: -0.03em;
198
+ }
199
+
200
+ .stat-sub {
201
+ font-size: 0.8rem;
202
+ color: var(--text-muted);
203
+ margin-top: 2px;
204
+ }
205
+
206
+ /* ---- Status Bars ---- */
207
+ .status-bar {
208
+ display: flex;
209
+ height: 8px;
210
+ border-radius: 4px;
211
+ overflow: hidden;
212
+ margin: 16px 0;
213
+ background: var(--border);
214
+ }
215
+
216
+ .status-bar-segment {
217
+ height: 100%;
218
+ transition: width 0.5s ease;
219
+ }
220
+
221
+ /* ---- Tables ---- */
222
+ .table-wrap {
223
+ background: var(--bg-card);
224
+ border: 1px solid var(--border);
225
+ border-radius: var(--radius);
226
+ overflow: hidden;
227
+ }
228
+
229
+ table {
230
+ width: 100%;
231
+ border-collapse: collapse;
232
+ }
233
+
234
+ th {
235
+ text-align: left;
236
+ padding: 12px 16px;
237
+ font-size: 0.75rem;
238
+ text-transform: uppercase;
239
+ letter-spacing: 0.06em;
240
+ color: var(--text-dim);
241
+ border-bottom: 1px solid var(--border);
242
+ background: var(--bg-card);
243
+ position: sticky;
244
+ top: 0;
245
+ }
246
+
247
+ td {
248
+ padding: 12px 16px;
249
+ border-bottom: 1px solid var(--border);
250
+ font-size: 0.875rem;
251
+ }
252
+
253
+ tr:last-child td { border-bottom: none; }
254
+ tr:hover td { background: var(--bg-hover); }
255
+
256
+ /* ---- Badges ---- */
257
+ .badge {
258
+ display: inline-block;
259
+ padding: 2px 10px;
260
+ border-radius: 20px;
261
+ font-size: 0.75rem;
262
+ font-weight: 600;
263
+ text-transform: uppercase;
264
+ letter-spacing: 0.04em;
265
+ }
266
+
267
+ .badge-certified { background: var(--success)20; color: var(--success); }
268
+ .badge-failed { background: var(--danger)20; color: var(--danger); }
269
+ .badge-suspended { background: var(--danger)30; color: #FCA5A5; }
270
+ .badge-assessing { background: var(--info)20; color: var(--info); }
271
+ .badge-uncertified { background: var(--text-dim)20; color: var(--text-dim); }
272
+ .badge-expired { background: var(--warning)20; color: var(--warning); }
273
+ .badge-drift_warning { background: var(--warning)30; color: #FDE68A; }
274
+ .badge-probation { background: var(--probation)20; color: var(--probation); }
275
+
276
+ .badge-low { background: var(--success)20; color: var(--success); }
277
+ .badge-medium { background: var(--info)20; color: var(--info); }
278
+ .badge-high { background: var(--warning)20; color: var(--warning); }
279
+ .badge-critical { background: var(--danger)20; color: var(--danger); }
280
+
281
+ /* ---- Hierarchy Ladder ---- */
282
+ .hierarchy-grid {
283
+ display: grid;
284
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
285
+ gap: 16px;
286
+ }
287
+
288
+ .agent-hierarchy-card {
289
+ background: var(--bg-card);
290
+ border: 1px solid var(--border);
291
+ border-radius: var(--radius);
292
+ padding: 20px;
293
+ }
294
+
295
+ .agent-hierarchy-card h3 {
296
+ font-size: 1rem;
297
+ margin-bottom: 12px;
298
+ }
299
+
300
+ .level-bar {
301
+ display: flex;
302
+ align-items: center;
303
+ gap: 10px;
304
+ padding: 8px 0;
305
+ border-bottom: 1px solid var(--border);
306
+ }
307
+
308
+ .level-bar:last-child { border-bottom: none; }
309
+
310
+ .level-icon {
311
+ width: 28px;
312
+ height: 28px;
313
+ border-radius: 50%;
314
+ display: flex;
315
+ align-items: center;
316
+ justify-content: center;
317
+ font-size: 0.7rem;
318
+ font-weight: 700;
319
+ flex-shrink: 0;
320
+ }
321
+
322
+ .level-icon.passed { background: var(--success)30; color: var(--success); }
323
+ .level-icon.failed { background: var(--danger)30; color: var(--danger); }
324
+ .level-icon.available { background: var(--info)30; color: var(--info); }
325
+ .level-icon.locked { background: var(--text-dim)20; color: var(--text-dim); }
326
+
327
+ .level-info { flex: 1; }
328
+ .level-name { font-size: 0.85rem; font-weight: 500; }
329
+ .level-status { font-size: 0.75rem; color: var(--text-muted); }
330
+
331
+ /* ---- Drift Gauges ---- */
332
+ .drift-grid {
333
+ display: grid;
334
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
335
+ gap: 16px;
336
+ }
337
+
338
+ .drift-card {
339
+ background: var(--bg-card);
340
+ border: 1px solid var(--border);
341
+ border-radius: var(--radius);
342
+ padding: 20px;
343
+ }
344
+
345
+ .drift-card h3 {
346
+ font-size: 0.95rem;
347
+ margin-bottom: 12px;
348
+ }
349
+
350
+ .gauge-wrap {
351
+ display: flex;
352
+ align-items: center;
353
+ gap: 12px;
354
+ margin-bottom: 12px;
355
+ }
356
+
357
+ .gauge {
358
+ flex: 1;
359
+ height: 10px;
360
+ background: var(--border);
361
+ border-radius: 5px;
362
+ overflow: hidden;
363
+ position: relative;
364
+ }
365
+
366
+ .gauge-fill {
367
+ height: 100%;
368
+ border-radius: 5px;
369
+ transition: width 0.5s ease, background 0.3s;
370
+ }
371
+
372
+ .gauge-markers {
373
+ position: relative;
374
+ height: 4px;
375
+ margin-top: 2px;
376
+ }
377
+
378
+ .gauge-marker {
379
+ position: absolute;
380
+ top: 0;
381
+ width: 1px;
382
+ height: 4px;
383
+ }
384
+
385
+ .tdi-value {
386
+ font-size: 1.5rem;
387
+ font-weight: 700;
388
+ min-width: 60px;
389
+ text-align: right;
390
+ }
391
+
392
+ .dimension-row {
393
+ display: flex;
394
+ justify-content: space-between;
395
+ align-items: center;
396
+ padding: 4px 0;
397
+ font-size: 0.8rem;
398
+ }
399
+
400
+ .dimension-name { color: var(--text-muted); text-transform: capitalize; }
401
+
402
+ .dimension-delta {
403
+ font-weight: 600;
404
+ }
405
+
406
+ .dimension-delta.positive { color: var(--success); }
407
+ .dimension-delta.negative { color: var(--danger); }
408
+
409
+ /* ---- Audit Trail ---- */
410
+ .audit-controls {
411
+ display: flex;
412
+ gap: 12px;
413
+ margin-bottom: 16px;
414
+ align-items: center;
415
+ }
416
+
417
+ .audit-controls select, .audit-controls input {
418
+ background: var(--bg-card);
419
+ border: 1px solid var(--border);
420
+ color: var(--text);
421
+ padding: 8px 12px;
422
+ border-radius: var(--radius-sm);
423
+ font-size: 0.875rem;
424
+ }
425
+
426
+ .chain-status {
427
+ display: inline-flex;
428
+ align-items: center;
429
+ gap: 6px;
430
+ padding: 6px 14px;
431
+ border-radius: 20px;
432
+ font-size: 0.8rem;
433
+ font-weight: 600;
434
+ }
435
+
436
+ .chain-valid { background: var(--success)20; color: var(--success); }
437
+ .chain-broken { background: var(--danger)20; color: var(--danger); }
438
+
439
+ /* ---- Empty State ---- */
440
+ .empty-state {
441
+ text-align: center;
442
+ padding: 60px 20px;
443
+ color: var(--text-dim);
444
+ }
445
+
446
+ .empty-state h2 {
447
+ font-size: 1.2rem;
448
+ margin-bottom: 8px;
449
+ color: var(--text-muted);
450
+ }
451
+
452
+ /* ---- Footer ---- */
453
+ .footer {
454
+ text-align: center;
455
+ padding: 24px;
456
+ color: var(--text-dim);
457
+ font-size: 0.75rem;
458
+ border-top: 1px solid var(--border);
459
+ margin-top: 40px;
460
+ }
461
+
462
+ /* ---- Responsive ---- */
463
+ @media (max-width: 768px) {
464
+ .header { padding: 12px 16px; }
465
+ .nav-tabs { padding: 0 16px; overflow-x: auto; }
466
+ .main { padding: 16px; }
467
+ .stats-grid { grid-template-columns: repeat(2, 1fr); }
468
+ }
469
+ </style>
470
+ </head>
471
+ <body>
472
+
473
+ <!-- Header -->
474
+ <header class="header">
475
+ <div class="header-left">
476
+ <div class="logo">&#x1F997;</div>
477
+ <h1>${productName} <span>Dashboard</span></h1>
478
+ </div>
479
+ <div class="header-right">
480
+ <div class="refresh-indicator">
481
+ <div class="pulse"></div>
482
+ <span id="lastRefresh">Loading...</span>
483
+ </div>
484
+ </div>
485
+ </header>
486
+
487
+ <!-- Navigation -->
488
+ <nav class="nav-tabs">
489
+ <div class="nav-tab active" data-tab="overview">Overview</div>
490
+ <div class="nav-tab" data-tab="agents">Agents</div>
491
+ <div class="nav-tab" data-tab="hierarchy">Hierarchy</div>
492
+ <div class="nav-tab" data-tab="drift">Drift</div>
493
+ <div class="nav-tab" data-tab="audit">Audit Trail</div>
494
+ </nav>
495
+
496
+ <!-- Main Content -->
497
+ <main class="main">
498
+
499
+ <!-- ===== Overview Tab ===== -->
500
+ <section id="tab-overview" class="section active">
501
+ <div class="stats-grid" id="statsGrid"></div>
502
+ <div id="statusBar"></div>
503
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:24px">
504
+ <div>
505
+ <h3 style="font-size:0.95rem;margin-bottom:12px;color:var(--text-muted)">Status Distribution</h3>
506
+ <div class="table-wrap" id="statusTable"></div>
507
+ </div>
508
+ <div>
509
+ <h3 style="font-size:0.95rem;margin-bottom:12px;color:var(--text-muted)">Risk Distribution</h3>
510
+ <div class="table-wrap" id="riskTable"></div>
511
+ </div>
512
+ </div>
513
+ </section>
514
+
515
+ <!-- ===== Agents Tab ===== -->
516
+ <section id="tab-agents" class="section">
517
+ <div class="table-wrap">
518
+ <table>
519
+ <thead>
520
+ <tr>
521
+ <th>Agent</th>
522
+ <th>Model</th>
523
+ <th>Provider</th>
524
+ <th>Category</th>
525
+ <th>Status</th>
526
+ <th>Risk</th>
527
+ <th>Last Assessed</th>
528
+ <th>Score</th>
529
+ </tr>
530
+ </thead>
531
+ <tbody id="agentTableBody"></tbody>
532
+ </table>
533
+ </div>
534
+ </section>
535
+
536
+ <!-- ===== Hierarchy Tab ===== -->
537
+ <section id="tab-hierarchy" class="section">
538
+ <div class="stats-grid" id="hierarchyStats"></div>
539
+ <div class="hierarchy-grid" id="hierarchyGrid"></div>
540
+ </section>
541
+
542
+ <!-- ===== Drift Tab ===== -->
543
+ <section id="tab-drift" class="section">
544
+ <div class="stats-grid" id="driftStats"></div>
545
+ <div class="drift-grid" id="driftGrid"></div>
546
+ </section>
547
+
548
+ <!-- ===== Audit Tab ===== -->
549
+ <section id="tab-audit" class="section">
550
+ <div class="audit-controls">
551
+ <select id="auditFilter">
552
+ <option value="">All Actions</option>
553
+ <option value="agent_registered">Registered</option>
554
+ <option value="assessment_completed">Assessed</option>
555
+ <option value="assessment_failed">Failed</option>
556
+ <option value="certification_revoked">Suspended</option>
557
+ <option value="agent_reinstated">Reinstated</option>
558
+ <option value="drift_detected">Drift</option>
559
+ <option value="bypass_requested">Bypass</option>
560
+ <option value="config_changed">Config</option>
561
+ </select>
562
+ <div id="chainStatus"></div>
563
+ </div>
564
+ <div class="table-wrap">
565
+ <table>
566
+ <thead>
567
+ <tr>
568
+ <th>#</th>
569
+ <th>Time</th>
570
+ <th>Action</th>
571
+ <th>Actor</th>
572
+ <th>Agent</th>
573
+ <th>Description</th>
574
+ </tr>
575
+ </thead>
576
+ <tbody id="auditTableBody"></tbody>
577
+ </table>
578
+ </div>
579
+ </section>
580
+
581
+ </main>
582
+
583
+ <footer class="footer">${footer}</footer>
584
+
585
+ <script>
586
+ (function() {
587
+ 'use strict';
588
+
589
+ const API = window.location.origin + '/api/grillo';
590
+ let refreshInterval;
591
+
592
+ // ================================================================
593
+ // Tab Navigation
594
+ // ================================================================
595
+
596
+ document.querySelectorAll('.nav-tab').forEach(tab => {
597
+ tab.addEventListener('click', () => {
598
+ document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
599
+ document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
600
+ tab.classList.add('active');
601
+ document.getElementById('tab-' + tab.dataset.tab).classList.add('active');
602
+ });
603
+ });
604
+
605
+ // ================================================================
606
+ // Data Fetching
607
+ // ================================================================
608
+
609
+ async function fetchJSON(path) {
610
+ try {
611
+ const res = await fetch(API + path);
612
+ if (!res.ok) throw new Error(res.statusText);
613
+ return await res.json();
614
+ } catch (e) {
615
+ console.error('Fetch error:', path, e);
616
+ return null;
617
+ }
618
+ }
619
+
620
+ // ================================================================
621
+ // Overview Tab
622
+ // ================================================================
623
+
624
+ function renderOverview(data) {
625
+ if (!data) return;
626
+
627
+ const { agents, total } = data;
628
+ const statusCounts = {};
629
+ const riskCounts = {};
630
+ let dueCount = 0;
631
+ let totalScore = 0;
632
+ let scoreCount = 0;
633
+
634
+ agents.forEach(a => {
635
+ statusCounts[a.certificationStatus] = (statusCounts[a.certificationStatus] || 0) + 1;
636
+ riskCounts[a.riskTier] = (riskCounts[a.riskTier] || 0) + 1;
637
+ if (a.nextAssessmentDue && new Date(a.nextAssessmentDue) <= new Date()) dueCount++;
638
+ if (a.certificationHistory && a.certificationHistory.length > 0) {
639
+ const last = a.certificationHistory[a.certificationHistory.length - 1];
640
+ Object.values(last.scores).forEach(s => { totalScore += s; scoreCount++; });
641
+ }
642
+ });
643
+
644
+ const certified = statusCounts['certified'] || 0;
645
+ const failed = statusCounts['failed'] || 0;
646
+ const suspended = statusCounts['suspended'] || 0;
647
+ const probation = statusCounts['probation'] || 0;
648
+ const driftWarn = statusCounts['drift_warning'] || 0;
649
+ const avgScore = scoreCount > 0 ? (totalScore / scoreCount).toFixed(1) : 'N/A';
650
+ const certRate = total > 0 ? ((certified / total) * 100).toFixed(0) : 0;
651
+
652
+ document.getElementById('statsGrid').innerHTML = [
653
+ statCard('Total Agents', total, 'In fleet'),
654
+ statCard('Certified', certified, certRate + '% rate', 'var(--success)'),
655
+ statCard('Failed', failed, '', 'var(--danger)'),
656
+ statCard('Suspended', suspended + driftWarn, driftWarn + ' drift warnings', 'var(--warning)'),
657
+ statCard('Probation', probation, 'Rebuilding trust', 'var(--probation)'),
658
+ statCard('Avg Score', avgScore, 'Fleet average'),
659
+ statCard('Due', dueCount, 'Need assessment', dueCount > 0 ? 'var(--warning)' : ''),
660
+ ].join('');
661
+
662
+ // Status bar
663
+ const barSegments = [
664
+ { pct: total ? (certified / total) * 100 : 0, color: 'var(--success)' },
665
+ { pct: total ? (probation / total) * 100 : 0, color: 'var(--probation)' },
666
+ { pct: total ? (failed / total) * 100 : 0, color: 'var(--danger)' },
667
+ { pct: total ? (suspended / total) * 100 : 0, color: '#EF4444' },
668
+ { pct: total ? (driftWarn / total) * 100 : 0, color: 'var(--warning)' },
669
+ ];
670
+
671
+ document.getElementById('statusBar').innerHTML =
672
+ '<div class="status-bar">' +
673
+ barSegments.map(s =>
674
+ '<div class="status-bar-segment" style="width:' + s.pct + '%;background:' + s.color + '"></div>'
675
+ ).join('') +
676
+ '</div>';
677
+
678
+ // Tables
679
+ document.getElementById('statusTable').innerHTML = miniTable(
680
+ ['Status', 'Count'],
681
+ Object.entries(statusCounts).filter(([,c]) => c > 0).map(([s, c]) => [badge(s), c])
682
+ );
683
+
684
+ document.getElementById('riskTable').innerHTML = miniTable(
685
+ ['Tier', 'Count'],
686
+ Object.entries(riskCounts).filter(([,c]) => c > 0).map(([t, c]) => [riskBadge(t), c])
687
+ );
688
+ }
689
+
690
+ // ================================================================
691
+ // Agents Tab
692
+ // ================================================================
693
+
694
+ function renderAgents(data) {
695
+ if (!data || !data.agents) return;
696
+ const tbody = document.getElementById('agentTableBody');
697
+
698
+ if (data.agents.length === 0) {
699
+ tbody.innerHTML = '<tr><td colspan="8"><div class="empty-state"><h2>No Agents</h2><p>Register agents with grillo -register</p></div></td></tr>';
700
+ return;
701
+ }
702
+
703
+ tbody.innerHTML = data.agents.map(a => {
704
+ const lastCert = a.certificationHistory && a.certificationHistory.length > 0
705
+ ? a.certificationHistory[a.certificationHistory.length - 1]
706
+ : null;
707
+ const scores = lastCert ? Object.values(lastCert.scores) : [];
708
+ const avgScore = scores.length > 0
709
+ ? (scores.reduce((s, v) => s + v, 0) / scores.length).toFixed(1)
710
+ : '—';
711
+ const lastDate = a.lastAssessedAt
712
+ ? new Date(a.lastAssessedAt).toLocaleDateString()
713
+ : 'Never';
714
+
715
+ return '<tr>' +
716
+ '<td><strong>' + esc(a.agentName) + '</strong><br><span style="color:var(--text-dim);font-size:0.75rem">' + esc(a.agentId) + '</span></td>' +
717
+ '<td>' + esc(a.model) + '</td>' +
718
+ '<td>' + esc(a.provider) + '</td>' +
719
+ '<td style="font-size:0.8rem">' + esc(a.category) + '</td>' +
720
+ '<td>' + badge(a.certificationStatus) + '</td>' +
721
+ '<td>' + riskBadge(a.riskTier) + '</td>' +
722
+ '<td>' + lastDate + '</td>' +
723
+ '<td style="font-weight:600">' + avgScore + '</td>' +
724
+ '</tr>';
725
+ }).join('');
726
+ }
727
+
728
+ // ================================================================
729
+ // Hierarchy Tab
730
+ // ================================================================
731
+
732
+ function renderHierarchy(data) {
733
+ if (!data || !data.agents) return;
734
+ const agents = data.agents;
735
+
736
+ let passed = 0, blocked = 0, unassessed = 0;
737
+ agents.forEach(a => {
738
+ const p = a.hierarchicalProgress;
739
+ if (p.level1_morality === 'passed') passed++;
740
+ else if (p.level1_morality === 'failed') blocked++;
741
+ else unassessed++;
742
+ });
743
+
744
+ document.getElementById('hierarchyStats').innerHTML = [
745
+ statCard('Progressing', passed, 'L1+ passed', 'var(--success)'),
746
+ statCard('Blocked', blocked, 'Failed a level', 'var(--danger)'),
747
+ statCard('Unassessed', unassessed, 'Not yet started'),
748
+ ].join('');
749
+
750
+ const levels = [
751
+ { key: 'level1_morality', name: 'Morality', num: 1 },
752
+ { key: 'level2_virtue', name: 'Virtue', num: 2 },
753
+ { key: 'level3_ethics', name: 'Ethics', num: 3 },
754
+ { key: 'level4_opex', name: 'OpEx', num: 4 },
755
+ ];
756
+
757
+ document.getElementById('hierarchyGrid').innerHTML = agents.map(a => {
758
+ const p = a.hierarchicalProgress;
759
+ return '<div class="agent-hierarchy-card">' +
760
+ '<h3>' + esc(a.agentName) + '</h3>' +
761
+ levels.map(l => {
762
+ const status = p[l.key];
763
+ return '<div class="level-bar">' +
764
+ '<div class="level-icon ' + status + '">L' + l.num + '</div>' +
765
+ '<div class="level-info">' +
766
+ '<div class="level-name">' + l.name + '</div>' +
767
+ '<div class="level-status">' + status.toUpperCase() + '</div>' +
768
+ '</div>' +
769
+ '</div>';
770
+ }).join('') +
771
+ '</div>';
772
+ }).join('');
773
+ }
774
+
775
+ // ================================================================
776
+ // Drift Tab
777
+ // ================================================================
778
+
779
+ function renderDrift(driftData, agentData) {
780
+ if (!agentData || !agentData.agents) return;
781
+ const agents = agentData.agents.filter(a => a.driftBaseline);
782
+
783
+ if (agents.length === 0) {
784
+ document.getElementById('driftStats').innerHTML = statCard('No Baselines', '—', 'Run assessments to establish baselines');
785
+ document.getElementById('driftGrid').innerHTML = '<div class="empty-state"><h2>No Drift Data</h2><p>Agents need at least one assessment to establish a baseline.</p></div>';
786
+ return;
787
+ }
788
+
789
+ const reports = driftData && driftData.reports ? driftData.reports : [];
790
+ let criticalCount = 0, highCount = 0, stableCount = 0;
791
+ reports.forEach(r => {
792
+ if (r.severity === 'critical') criticalCount++;
793
+ else if (r.severity === 'high') highCount++;
794
+ else stableCount++;
795
+ });
796
+
797
+ document.getElementById('driftStats').innerHTML = [
798
+ statCard('Monitored', agents.length, 'With baselines'),
799
+ statCard('Stable', stableCount, 'No drift', 'var(--success)'),
800
+ statCard('High Drift', highCount, 'Watch closely', 'var(--warning)'),
801
+ statCard('Critical', criticalCount, 'Auto-suspended', 'var(--danger)'),
802
+ ].join('');
803
+
804
+ document.getElementById('driftGrid').innerHTML = reports.map(r => {
805
+ const tdiPct = Math.min(r.tdi / 0.5, 1) * 100;
806
+ const tdiColor = r.severity === 'critical' ? 'var(--danger)'
807
+ : r.severity === 'high' ? 'var(--warning)'
808
+ : r.severity === 'medium' ? 'var(--info)'
809
+ : 'var(--success)';
810
+
811
+ const dimRows = Object.entries(r.dimensionDrifts || {}).map(([dim, drift]) => {
812
+ const cls = drift >= 0 ? 'positive' : 'negative';
813
+ const sign = drift >= 0 ? '+' : '';
814
+ return '<div class="dimension-row">' +
815
+ '<span class="dimension-name">' + esc(dim) + '</span>' +
816
+ '<span class="dimension-delta ' + cls + '">' + sign + Number(drift).toFixed(1) + '</span>' +
817
+ '</div>';
818
+ }).join('');
819
+
820
+ return '<div class="drift-card">' +
821
+ '<h3>' + esc(r.agentName) + ' ' + badge(r.severity) + '</h3>' +
822
+ '<div class="gauge-wrap">' +
823
+ '<div class="gauge">' +
824
+ '<div class="gauge-fill" style="width:' + tdiPct + '%;background:' + tdiColor + '"></div>' +
825
+ '</div>' +
826
+ '<div class="tdi-value" style="color:' + tdiColor + '">' + r.tdi.toFixed(3) + '</div>' +
827
+ '</div>' +
828
+ '<div style="display:flex;gap:20px;font-size:0.7rem;color:var(--text-dim);margin-bottom:8px">' +
829
+ '<span>0</span><span style="margin-left:auto">Warning: 0.15</span><span>Critical: 0.30</span>' +
830
+ '</div>' +
831
+ dimRows +
832
+ '</div>';
833
+ }).join('');
834
+ }
835
+
836
+ // ================================================================
837
+ // Audit Tab
838
+ // ================================================================
839
+
840
+ function renderAudit(data) {
841
+ if (!data) return;
842
+ const { entries, verification } = data;
843
+
844
+ document.getElementById('chainStatus').innerHTML =
845
+ verification && verification.valid
846
+ ? '<span class="chain-status chain-valid">Chain Verified</span>'
847
+ : '<span class="chain-status chain-broken">Chain Broken</span>';
848
+
849
+ const filter = document.getElementById('auditFilter').value;
850
+ const filtered = filter ? entries.filter(e => e.action === filter) : entries;
851
+
852
+ const tbody = document.getElementById('auditTableBody');
853
+ if (filtered.length === 0) {
854
+ tbody.innerHTML = '<tr><td colspan="6"><div class="empty-state"><p>No audit entries.</p></div></td></tr>';
855
+ return;
856
+ }
857
+
858
+ tbody.innerHTML = filtered.slice(0, 100).map(e => {
859
+ return '<tr>' +
860
+ '<td style="color:var(--text-dim)">' + (e.sequence || '') + '</td>' +
861
+ '<td style="font-size:0.8rem">' + formatTime(e.timestamp) + '</td>' +
862
+ '<td>' + badge(e.action) + '</td>' +
863
+ '<td>' + esc(e.actor || '—') + '</td>' +
864
+ '<td>' + esc(e.agentId || '—') + '</td>' +
865
+ '<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(e.description || '') + '</td>' +
866
+ '</tr>';
867
+ }).join('');
868
+ }
869
+
870
+ // ================================================================
871
+ // Helpers
872
+ // ================================================================
873
+
874
+ function statCard(label, value, sub, color) {
875
+ const style = color ? 'color:' + color : '';
876
+ return '<div class="stat-card">' +
877
+ '<div class="stat-label">' + label + '</div>' +
878
+ '<div class="stat-value" style="' + style + '">' + value + '</div>' +
879
+ (sub ? '<div class="stat-sub">' + sub + '</div>' : '') +
880
+ '</div>';
881
+ }
882
+
883
+ function badge(status) {
884
+ const cls = 'badge badge-' + String(status).toLowerCase().replace(/[^a-z_]/g, '');
885
+ return '<span class="' + cls + '">' + esc(String(status)) + '</span>';
886
+ }
887
+
888
+ function riskBadge(tier) {
889
+ return '<span class="badge badge-' + String(tier).toLowerCase() + '">' + esc(String(tier)) + '</span>';
890
+ }
891
+
892
+ function miniTable(headers, rows) {
893
+ return '<table>' +
894
+ '<thead><tr>' + headers.map(h => '<th>' + h + '</th>').join('') + '</tr></thead>' +
895
+ '<tbody>' + rows.map(r => '<tr>' + r.map(c => '<td>' + c + '</td>').join('') + '</tr>').join('') + '</tbody>' +
896
+ '</table>';
897
+ }
898
+
899
+ function esc(str) {
900
+ const div = document.createElement('div');
901
+ div.textContent = str;
902
+ return div.innerHTML;
903
+ }
904
+
905
+ function formatTime(ts) {
906
+ if (!ts) return '—';
907
+ const d = new Date(ts);
908
+ return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
909
+ }
910
+
911
+ // ================================================================
912
+ // Refresh Loop
913
+ // ================================================================
914
+
915
+ let agentCache = null;
916
+
917
+ async function refresh() {
918
+ const [agents, audit, drift] = await Promise.all([
919
+ fetchJSON('/agents'),
920
+ fetchJSON('/audit'),
921
+ fetchJSON('/drift/fleet'),
922
+ ]);
923
+
924
+ agentCache = agents;
925
+
926
+ renderOverview(agents);
927
+ renderAgents(agents);
928
+ renderHierarchy(agents);
929
+ renderDrift(drift, agents);
930
+ renderAudit(audit);
931
+
932
+ document.getElementById('lastRefresh').textContent =
933
+ 'Updated ' + new Date().toLocaleTimeString();
934
+ }
935
+
936
+ // Audit filter change
937
+ document.getElementById('auditFilter').addEventListener('change', async () => {
938
+ const data = await fetchJSON('/audit');
939
+ renderAudit(data);
940
+ });
941
+
942
+ // Initial load + auto-refresh
943
+ refresh();
944
+ refreshInterval = setInterval(refresh, 15000);
945
+
946
+ })();
947
+ </script>
948
+ </body>
949
+ </html>`;
950
+ }
951
+ //# sourceMappingURL=ui.js.map