moltbot-scan 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.
- package/LICENSE +21 -0
- package/README.md +473 -0
- package/dist/analysis/llm.d.ts +10 -0
- package/dist/analysis/llm.d.ts.map +1 -0
- package/dist/analysis/llm.js +106 -0
- package/dist/analysis/llm.js.map +1 -0
- package/dist/analysis/patterns.d.ts +19 -0
- package/dist/analysis/patterns.d.ts.map +1 -0
- package/dist/analysis/patterns.js +177 -0
- package/dist/analysis/patterns.js.map +1 -0
- package/dist/analysis/rules.d.ts +6 -0
- package/dist/analysis/rules.d.ts.map +1 -0
- package/dist/analysis/rules.js +55 -0
- package/dist/analysis/rules.js.map +1 -0
- package/dist/analyzers/index.d.ts +5 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +22 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +84 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/demo.d.ts +4 -0
- package/dist/core/demo.d.ts.map +1 -0
- package/dist/core/demo.js +231 -0
- package/dist/core/demo.js.map +1 -0
- package/dist/core/reporter.d.ts +5 -0
- package/dist/core/reporter.d.ts.map +1 -0
- package/dist/core/reporter.js +371 -0
- package/dist/core/reporter.js.map +1 -0
- package/dist/core/scanner.d.ts +9 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +103 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/core/scorer.d.ts +22 -0
- package/dist/core/scorer.d.ts.map +1 -0
- package/dist/core/scorer.js +269 -0
- package/dist/core/scorer.js.map +1 -0
- package/dist/data/batch-scan.d.ts +9 -0
- package/dist/data/batch-scan.d.ts.map +1 -0
- package/dist/data/batch-scan.js +277 -0
- package/dist/data/batch-scan.js.map +1 -0
- package/dist/data/moltbook.d.ts +13 -0
- package/dist/data/moltbook.d.ts.map +1 -0
- package/dist/data/moltbook.js +91 -0
- package/dist/data/moltbook.js.map +1 -0
- package/dist/data/scraper.d.ts +13 -0
- package/dist/data/scraper.d.ts.map +1 -0
- package/dist/data/scraper.js +85 -0
- package/dist/data/scraper.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/express.d.ts +17 -0
- package/dist/middleware/express.d.ts.map +1 -0
- package/dist/middleware/express.js +46 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/generic.d.ts +7 -0
- package/dist/middleware/generic.d.ts.map +1 -0
- package/dist/middleware/generic.js +21 -0
- package/dist/middleware/generic.js.map +1 -0
- package/dist/middleware/index.d.ts +4 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +8 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/sdk/scanner.d.ts +9 -0
- package/dist/sdk/scanner.d.ts.map +1 -0
- package/dist/sdk/scanner.js +92 -0
- package/dist/sdk/scanner.js.map +1 -0
- package/dist/types/index.d.ts +136 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/dist/web/public/index.html +848 -0
- package/dist/web/server.d.ts +3 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +74 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Moltbook Scan</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--window-bg: #1E1E1E;
|
|
10
|
+
--titlebar: #2D2D2D;
|
|
11
|
+
--sidebar-bg: #252526;
|
|
12
|
+
--content-bg: #1E1E1E;
|
|
13
|
+
--hover: #2A2D2E;
|
|
14
|
+
--active: #37373D;
|
|
15
|
+
--text: #CCCCCC;
|
|
16
|
+
--text-dim: #808080;
|
|
17
|
+
--text-bright: #E8E8E8;
|
|
18
|
+
--accent: #0A84FF;
|
|
19
|
+
--green: #34C759;
|
|
20
|
+
--yellow: #FFCC00;
|
|
21
|
+
--orange: #FF9F0A;
|
|
22
|
+
--red: #FF3B30;
|
|
23
|
+
--border: #3C3C3C;
|
|
24
|
+
--radius: 10px;
|
|
25
|
+
--font: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display', 'Helvetica Neue', system-ui, sans-serif;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
29
|
+
|
|
30
|
+
body {
|
|
31
|
+
font-family: var(--font);
|
|
32
|
+
background: var(--window-bg);
|
|
33
|
+
color: var(--text);
|
|
34
|
+
height: 100vh;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
-webkit-font-smoothing: antialiased;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ─── macOS Window Frame ────────────────────────────────────── */
|
|
40
|
+
.macos-window {
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100vh;
|
|
43
|
+
background: var(--window-bg);
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* ─── Title Bar ─────────────────────────────────────────────── */
|
|
50
|
+
.titlebar {
|
|
51
|
+
height: 52px;
|
|
52
|
+
min-height: 52px;
|
|
53
|
+
background: var(--titlebar);
|
|
54
|
+
border-bottom: 1px solid var(--border);
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
padding: 0 16px;
|
|
58
|
+
-webkit-app-region: drag;
|
|
59
|
+
position: relative;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.traffic-lights {
|
|
63
|
+
display: flex;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
z-index: 2;
|
|
66
|
+
}
|
|
67
|
+
.tl {
|
|
68
|
+
width: 12px;
|
|
69
|
+
height: 12px;
|
|
70
|
+
border-radius: 50%;
|
|
71
|
+
position: relative;
|
|
72
|
+
}
|
|
73
|
+
.tl-close { background: #FF5F57; }
|
|
74
|
+
.tl-minimize { background: #FEBC2E; }
|
|
75
|
+
.tl-maximize { background: #28C840; }
|
|
76
|
+
.tl::after {
|
|
77
|
+
content: '';
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 0;
|
|
80
|
+
border-radius: 50%;
|
|
81
|
+
box-shadow: inset 0 0 0 0.5px rgba(0,0,0,0.15);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.titlebar-text {
|
|
85
|
+
position: absolute;
|
|
86
|
+
left: 50%;
|
|
87
|
+
transform: translateX(-50%);
|
|
88
|
+
font-size: 13px;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
color: var(--text-dim);
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 7px;
|
|
94
|
+
user-select: none;
|
|
95
|
+
}
|
|
96
|
+
.titlebar-text svg { flex-shrink: 0; }
|
|
97
|
+
|
|
98
|
+
/* ─── Body: Sidebar + Content ───────────────────────────────── */
|
|
99
|
+
.window-body {
|
|
100
|
+
flex: 1;
|
|
101
|
+
display: flex;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ─── Sidebar ───────────────────────────────────────────────── */
|
|
106
|
+
.sidebar {
|
|
107
|
+
width: 220px;
|
|
108
|
+
min-width: 220px;
|
|
109
|
+
background: var(--sidebar-bg);
|
|
110
|
+
border-right: 1px solid var(--border);
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
overflow-y: auto;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.sidebar-section {
|
|
117
|
+
padding: 12px 12px 4px;
|
|
118
|
+
}
|
|
119
|
+
.sidebar-label {
|
|
120
|
+
font-size: 11px;
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
color: var(--text-dim);
|
|
123
|
+
text-transform: uppercase;
|
|
124
|
+
letter-spacing: 0.5px;
|
|
125
|
+
padding: 0 8px;
|
|
126
|
+
margin-bottom: 4px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.sidebar-item {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
gap: 8px;
|
|
133
|
+
padding: 6px 8px;
|
|
134
|
+
border-radius: 6px;
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
font-size: 13px;
|
|
137
|
+
color: var(--text);
|
|
138
|
+
transition: background 0.1s;
|
|
139
|
+
user-select: none;
|
|
140
|
+
}
|
|
141
|
+
.sidebar-item:hover { background: var(--hover); }
|
|
142
|
+
.sidebar-item.active {
|
|
143
|
+
background: var(--accent);
|
|
144
|
+
color: #fff;
|
|
145
|
+
}
|
|
146
|
+
.sidebar-item .icon {
|
|
147
|
+
width: 20px;
|
|
148
|
+
height: 20px;
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
flex-shrink: 0;
|
|
153
|
+
}
|
|
154
|
+
.sidebar-item .icon svg {
|
|
155
|
+
width: 16px;
|
|
156
|
+
height: 16px;
|
|
157
|
+
}
|
|
158
|
+
.sidebar-item.active .icon svg {
|
|
159
|
+
stroke: #fff;
|
|
160
|
+
fill: none;
|
|
161
|
+
}
|
|
162
|
+
.sidebar-item.active .icon svg .fill-icon {
|
|
163
|
+
fill: #fff;
|
|
164
|
+
stroke: none;
|
|
165
|
+
}
|
|
166
|
+
.sidebar-item .badge-count {
|
|
167
|
+
margin-left: auto;
|
|
168
|
+
font-size: 11px;
|
|
169
|
+
background: rgba(255,255,255,0.1);
|
|
170
|
+
padding: 1px 7px;
|
|
171
|
+
border-radius: 10px;
|
|
172
|
+
font-weight: 500;
|
|
173
|
+
}
|
|
174
|
+
.sidebar-item.active .badge-count {
|
|
175
|
+
background: rgba(255,255,255,0.2);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.sidebar-divider {
|
|
179
|
+
height: 1px;
|
|
180
|
+
background: var(--border);
|
|
181
|
+
margin: 8px 12px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.sidebar-footer {
|
|
185
|
+
margin-top: auto;
|
|
186
|
+
padding: 12px;
|
|
187
|
+
border-top: 1px solid var(--border);
|
|
188
|
+
}
|
|
189
|
+
.sidebar-footer-text {
|
|
190
|
+
font-size: 10px;
|
|
191
|
+
color: var(--text-dim);
|
|
192
|
+
text-align: center;
|
|
193
|
+
line-height: 1.4;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* ─── Content Area ──────────────────────────────────────────── */
|
|
197
|
+
.content {
|
|
198
|
+
flex: 1;
|
|
199
|
+
overflow-y: auto;
|
|
200
|
+
padding: 24px;
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* ─── Toolbar (Search) ──────────────────────────────────────── */
|
|
206
|
+
.toolbar {
|
|
207
|
+
display: flex;
|
|
208
|
+
gap: 8px;
|
|
209
|
+
margin-bottom: 20px;
|
|
210
|
+
}
|
|
211
|
+
.search-field {
|
|
212
|
+
flex: 1;
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
background: #323233;
|
|
216
|
+
border: 1px solid var(--border);
|
|
217
|
+
border-radius: 8px;
|
|
218
|
+
padding: 0 12px;
|
|
219
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
220
|
+
}
|
|
221
|
+
.search-field:focus-within {
|
|
222
|
+
border-color: var(--accent);
|
|
223
|
+
box-shadow: 0 0 0 3px rgba(10,132,255,0.2);
|
|
224
|
+
}
|
|
225
|
+
.search-field .sf-icon {
|
|
226
|
+
font-size: 14px;
|
|
227
|
+
color: var(--text-dim);
|
|
228
|
+
margin-right: 8px;
|
|
229
|
+
}
|
|
230
|
+
.search-field input {
|
|
231
|
+
flex: 1;
|
|
232
|
+
background: none;
|
|
233
|
+
border: none;
|
|
234
|
+
outline: none;
|
|
235
|
+
color: var(--text-bright);
|
|
236
|
+
font-size: 13px;
|
|
237
|
+
font-family: var(--font);
|
|
238
|
+
padding: 8px 0;
|
|
239
|
+
}
|
|
240
|
+
.search-field input::placeholder { color: var(--text-dim); }
|
|
241
|
+
|
|
242
|
+
.toolbar-btn {
|
|
243
|
+
background: var(--accent);
|
|
244
|
+
color: #fff;
|
|
245
|
+
border: none;
|
|
246
|
+
border-radius: 8px;
|
|
247
|
+
padding: 8px 18px;
|
|
248
|
+
font-size: 13px;
|
|
249
|
+
font-weight: 600;
|
|
250
|
+
font-family: var(--font);
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
transition: opacity 0.1s;
|
|
253
|
+
white-space: nowrap;
|
|
254
|
+
}
|
|
255
|
+
.toolbar-btn:hover { opacity: 0.85; }
|
|
256
|
+
.toolbar-btn:disabled { opacity: 0.35; cursor: not-allowed; }
|
|
257
|
+
|
|
258
|
+
/* ─── Welcome / Empty State ─────────────────────────────────── */
|
|
259
|
+
.welcome {
|
|
260
|
+
flex: 1;
|
|
261
|
+
display: flex;
|
|
262
|
+
flex-direction: column;
|
|
263
|
+
align-items: center;
|
|
264
|
+
justify-content: center;
|
|
265
|
+
text-align: center;
|
|
266
|
+
gap: 8px;
|
|
267
|
+
}
|
|
268
|
+
.welcome-icon { margin-bottom: 8px; }
|
|
269
|
+
.welcome h2 {
|
|
270
|
+
font-size: 20px;
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
color: var(--text-bright);
|
|
273
|
+
}
|
|
274
|
+
.welcome p {
|
|
275
|
+
font-size: 13px;
|
|
276
|
+
color: var(--text-dim);
|
|
277
|
+
max-width: 320px;
|
|
278
|
+
}
|
|
279
|
+
.welcome-hint {
|
|
280
|
+
font-size: 12px;
|
|
281
|
+
color: var(--text-dim);
|
|
282
|
+
margin-top: 16px;
|
|
283
|
+
display: flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
gap: 6px;
|
|
286
|
+
}
|
|
287
|
+
.kbd {
|
|
288
|
+
background: #3A3A3C;
|
|
289
|
+
border: 1px solid #555;
|
|
290
|
+
border-radius: 4px;
|
|
291
|
+
padding: 2px 6px;
|
|
292
|
+
font-size: 11px;
|
|
293
|
+
font-family: var(--font);
|
|
294
|
+
color: var(--text);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* ─── Loading ───────────────────────────────────────────────── */
|
|
298
|
+
.loading { display: none; flex: 1; align-items: center; justify-content: center; flex-direction: column; gap: 12px; }
|
|
299
|
+
.loading.active { display: flex; }
|
|
300
|
+
.macos-spinner {
|
|
301
|
+
width: 32px; height: 32px;
|
|
302
|
+
border: 2.5px solid var(--border);
|
|
303
|
+
border-top-color: var(--accent);
|
|
304
|
+
border-radius: 50%;
|
|
305
|
+
animation: spin 0.7s linear infinite;
|
|
306
|
+
}
|
|
307
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
308
|
+
.loading p { font-size: 13px; color: var(--text-dim); }
|
|
309
|
+
|
|
310
|
+
/* ─── Error ─────────────────────────────────────────────────── */
|
|
311
|
+
.error-bar {
|
|
312
|
+
background: rgba(255,59,48,0.1);
|
|
313
|
+
border: 1px solid rgba(255,59,48,0.25);
|
|
314
|
+
border-radius: 8px;
|
|
315
|
+
padding: 10px 14px;
|
|
316
|
+
font-size: 13px;
|
|
317
|
+
color: #FF6961;
|
|
318
|
+
display: none;
|
|
319
|
+
margin-bottom: 16px;
|
|
320
|
+
}
|
|
321
|
+
.error-bar.active { display: block; }
|
|
322
|
+
|
|
323
|
+
/* ─── Report Content ────────────────────────────────────────── */
|
|
324
|
+
.report { display: none; flex-direction: column; gap: 16px; }
|
|
325
|
+
.report.active { display: flex; }
|
|
326
|
+
|
|
327
|
+
/* Score Header */
|
|
328
|
+
.score-header {
|
|
329
|
+
display: flex;
|
|
330
|
+
align-items: center;
|
|
331
|
+
gap: 20px;
|
|
332
|
+
background: #252526;
|
|
333
|
+
border: 1px solid var(--border);
|
|
334
|
+
border-radius: var(--radius);
|
|
335
|
+
padding: 20px 24px;
|
|
336
|
+
}
|
|
337
|
+
.score-circle {
|
|
338
|
+
width: 110px; height: 130px;
|
|
339
|
+
position: relative;
|
|
340
|
+
flex-shrink: 0;
|
|
341
|
+
display: flex;
|
|
342
|
+
flex-direction: column;
|
|
343
|
+
align-items: center;
|
|
344
|
+
}
|
|
345
|
+
.score-circle svg { transform: rotate(-90deg); }
|
|
346
|
+
.score-num {
|
|
347
|
+
position: absolute;
|
|
348
|
+
top: 42%; left: 50%;
|
|
349
|
+
transform: translate(-50%, -50%);
|
|
350
|
+
font-size: 32px;
|
|
351
|
+
font-weight: 700;
|
|
352
|
+
font-variant-numeric: tabular-nums;
|
|
353
|
+
letter-spacing: -1px;
|
|
354
|
+
}
|
|
355
|
+
.score-level-badge {
|
|
356
|
+
margin-top: 6px;
|
|
357
|
+
font-size: 10px;
|
|
358
|
+
font-weight: 700;
|
|
359
|
+
letter-spacing: 1px;
|
|
360
|
+
text-transform: uppercase;
|
|
361
|
+
white-space: nowrap;
|
|
362
|
+
}
|
|
363
|
+
.score-details { flex: 1; }
|
|
364
|
+
.score-agent-name {
|
|
365
|
+
font-size: 18px;
|
|
366
|
+
font-weight: 600;
|
|
367
|
+
color: var(--text-bright);
|
|
368
|
+
margin-bottom: 6px;
|
|
369
|
+
}
|
|
370
|
+
.score-meta {
|
|
371
|
+
display: flex;
|
|
372
|
+
gap: 16px;
|
|
373
|
+
flex-wrap: wrap;
|
|
374
|
+
}
|
|
375
|
+
.score-meta-item {
|
|
376
|
+
font-size: 12px;
|
|
377
|
+
color: var(--text-dim);
|
|
378
|
+
}
|
|
379
|
+
.score-meta-item strong {
|
|
380
|
+
color: var(--text);
|
|
381
|
+
font-weight: 600;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* Panels */
|
|
385
|
+
.panel {
|
|
386
|
+
background: #252526;
|
|
387
|
+
border: 1px solid var(--border);
|
|
388
|
+
border-radius: var(--radius);
|
|
389
|
+
overflow: hidden;
|
|
390
|
+
}
|
|
391
|
+
.panel-title {
|
|
392
|
+
font-size: 11px;
|
|
393
|
+
font-weight: 600;
|
|
394
|
+
color: var(--text-dim);
|
|
395
|
+
text-transform: uppercase;
|
|
396
|
+
letter-spacing: 0.6px;
|
|
397
|
+
padding: 12px 16px 8px;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Breakdown Bars */
|
|
401
|
+
.bd-row {
|
|
402
|
+
display: flex;
|
|
403
|
+
align-items: center;
|
|
404
|
+
gap: 10px;
|
|
405
|
+
padding: 6px 16px;
|
|
406
|
+
}
|
|
407
|
+
.bd-row:last-child { padding-bottom: 12px; }
|
|
408
|
+
.bd-label { width: 72px; font-size: 12px; color: var(--text-dim); flex-shrink: 0; }
|
|
409
|
+
.bd-bar { flex: 1; height: 5px; background: #3A3A3C; border-radius: 3px; overflow: hidden; }
|
|
410
|
+
.bd-bar-fill { height: 100%; border-radius: 3px; transition: width 0.6s cubic-bezier(0.4,0,0.2,1); }
|
|
411
|
+
.bd-score { width: 42px; text-align: right; font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
412
|
+
|
|
413
|
+
/* Findings Table */
|
|
414
|
+
.findings-list { padding: 0 4px 8px; }
|
|
415
|
+
.finding-item {
|
|
416
|
+
display: flex;
|
|
417
|
+
align-items: flex-start;
|
|
418
|
+
gap: 8px;
|
|
419
|
+
padding: 8px 12px;
|
|
420
|
+
border-radius: 6px;
|
|
421
|
+
transition: background 0.1s;
|
|
422
|
+
}
|
|
423
|
+
.finding-item:hover { background: var(--hover); }
|
|
424
|
+
.severity-dot {
|
|
425
|
+
width: 8px; height: 8px;
|
|
426
|
+
border-radius: 50%;
|
|
427
|
+
margin-top: 4px;
|
|
428
|
+
flex-shrink: 0;
|
|
429
|
+
}
|
|
430
|
+
.severity-dot.HIGH { background: var(--red); }
|
|
431
|
+
.severity-dot.MEDIUM { background: var(--orange); }
|
|
432
|
+
.severity-dot.LOW { background: var(--text-dim); }
|
|
433
|
+
.finding-content { flex: 1; }
|
|
434
|
+
.finding-msg { font-size: 13px; color: var(--text); line-height: 1.4; }
|
|
435
|
+
.finding-detail { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
|
|
436
|
+
.severity-label {
|
|
437
|
+
font-size: 10px;
|
|
438
|
+
font-weight: 700;
|
|
439
|
+
padding: 2px 6px;
|
|
440
|
+
border-radius: 4px;
|
|
441
|
+
flex-shrink: 0;
|
|
442
|
+
margin-top: 2px;
|
|
443
|
+
}
|
|
444
|
+
.severity-label.HIGH { color: var(--red); background: rgba(255,59,48,0.12); }
|
|
445
|
+
.severity-label.MEDIUM { color: var(--orange); background: rgba(255,159,10,0.12); }
|
|
446
|
+
.severity-label.LOW { color: var(--text-dim); background: rgba(142,142,147,0.12); }
|
|
447
|
+
|
|
448
|
+
/* Analysis Pills */
|
|
449
|
+
.analysis-row {
|
|
450
|
+
display: flex;
|
|
451
|
+
gap: 8px;
|
|
452
|
+
padding: 8px 16px 14px;
|
|
453
|
+
flex-wrap: wrap;
|
|
454
|
+
}
|
|
455
|
+
.analysis-pill {
|
|
456
|
+
font-size: 12px;
|
|
457
|
+
padding: 5px 12px;
|
|
458
|
+
border-radius: 6px;
|
|
459
|
+
background: #3A3A3C;
|
|
460
|
+
color: var(--text);
|
|
461
|
+
font-weight: 500;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* Action Bar */
|
|
465
|
+
.action-bar {
|
|
466
|
+
display: flex;
|
|
467
|
+
gap: 8px;
|
|
468
|
+
justify-content: flex-end;
|
|
469
|
+
}
|
|
470
|
+
.action-btn {
|
|
471
|
+
background: #3A3A3C;
|
|
472
|
+
border: 1px solid var(--border);
|
|
473
|
+
color: var(--text);
|
|
474
|
+
border-radius: 6px;
|
|
475
|
+
padding: 6px 14px;
|
|
476
|
+
font-size: 12px;
|
|
477
|
+
font-weight: 500;
|
|
478
|
+
font-family: var(--font);
|
|
479
|
+
cursor: pointer;
|
|
480
|
+
transition: background 0.1s;
|
|
481
|
+
}
|
|
482
|
+
.action-btn:hover { background: #4A4A4C; }
|
|
483
|
+
|
|
484
|
+
/* ─── Responsive ────────────────────────────────────────────── */
|
|
485
|
+
@media (max-width: 700px) {
|
|
486
|
+
.sidebar { display: none; }
|
|
487
|
+
.score-header { flex-direction: column; text-align: center; }
|
|
488
|
+
.score-meta { justify-content: center; }
|
|
489
|
+
}
|
|
490
|
+
</style>
|
|
491
|
+
</head>
|
|
492
|
+
<body>
|
|
493
|
+
|
|
494
|
+
<div class="macos-window">
|
|
495
|
+
|
|
496
|
+
<div class="window-body">
|
|
497
|
+
<!-- Sidebar -->
|
|
498
|
+
<div class="sidebar">
|
|
499
|
+
<div class="sidebar-section">
|
|
500
|
+
<div class="sidebar-label">Scanner</div>
|
|
501
|
+
<div class="sidebar-item active" onclick="showPanel('scan')" id="nav-scan">
|
|
502
|
+
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="#808080" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg></span> Scan Agent
|
|
503
|
+
</div>
|
|
504
|
+
<div class="sidebar-item" onclick="showPanel('history')" id="nav-history">
|
|
505
|
+
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="#808080" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 15"/></svg></span> History
|
|
506
|
+
<span class="badge-count" id="historyCount">0</span>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<div class="sidebar-divider"></div>
|
|
511
|
+
|
|
512
|
+
<div class="sidebar-section">
|
|
513
|
+
<div class="sidebar-label">Demo Agents</div>
|
|
514
|
+
<div class="sidebar-item" onclick="runDemo('HelperBot')">
|
|
515
|
+
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="#34C759" stroke-width="2" stroke-linecap="round"><path d="M12 2L3 7v6c0 5.5 3.8 10.7 9 12 5.2-1.3 9-6.5 9-12V7l-9-5z"/><polyline points="9 12 11 14 15 10"/></svg></span> @HelperBot
|
|
516
|
+
</div>
|
|
517
|
+
<div class="sidebar-item" onclick="runDemo('TotallyLegitBot')">
|
|
518
|
+
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="#FF3B30" stroke-width="2" stroke-linecap="round"><path d="M12 2L3 7v6c0 5.5 3.8 10.7 9 12 5.2-1.3 9-6.5 9-12V7l-9-5z"/><line x1="12" y1="8" x2="12" y2="12"/><circle cx="12" cy="15" r="0.5" class="fill-icon" fill="#FF3B30"/></svg></span> @TotallyLegitBot
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<div class="sidebar-divider"></div>
|
|
523
|
+
|
|
524
|
+
<div class="sidebar-section">
|
|
525
|
+
<div class="sidebar-label">Info</div>
|
|
526
|
+
<div class="sidebar-item" onclick="showPanel('about')" id="nav-about">
|
|
527
|
+
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="#808080" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><line x1="12" y1="16" x2="12" y2="12"/><circle cx="12" cy="8" r="0.5" class="fill-icon" fill="#808080"/></svg></span> About
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
<div class="sidebar-footer">
|
|
532
|
+
<div class="sidebar-footer-text">Moltbook Scan v0.1.0</div>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
|
|
536
|
+
<!-- Content -->
|
|
537
|
+
<div class="content" id="contentArea">
|
|
538
|
+
<!-- Toolbar -->
|
|
539
|
+
<div class="toolbar">
|
|
540
|
+
<div class="search-field">
|
|
541
|
+
<span class="sf-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg></span>
|
|
542
|
+
<input type="text" id="agentInput" placeholder="Enter agent name, e.g. @HelperBot" autocomplete="off" />
|
|
543
|
+
</div>
|
|
544
|
+
<button class="toolbar-btn" id="scanBtn" onclick="startScan()">Scan</button>
|
|
545
|
+
</div>
|
|
546
|
+
|
|
547
|
+
<!-- Error -->
|
|
548
|
+
<div class="error-bar" id="errorBox"></div>
|
|
549
|
+
|
|
550
|
+
<!-- Loading -->
|
|
551
|
+
<div class="loading" id="loadingBox">
|
|
552
|
+
<div class="macos-spinner"></div>
|
|
553
|
+
<p>Analyzing agent behavior...</p>
|
|
554
|
+
</div>
|
|
555
|
+
|
|
556
|
+
<!-- Welcome -->
|
|
557
|
+
<div class="welcome" id="welcomeBox">
|
|
558
|
+
<div class="welcome-icon">
|
|
559
|
+
<svg width="56" height="56" viewBox="0 0 72 72" fill="none">
|
|
560
|
+
<circle cx="36" cy="36" r="33" stroke="#0A84FF" stroke-width="2.5" stroke-dasharray="4 3" opacity="0.4"/>
|
|
561
|
+
<circle cx="36" cy="36" r="26" stroke="#0A84FF" stroke-width="1.5" opacity="0.2"/>
|
|
562
|
+
<path d="M36 10 L54 20 V38 C54 50 36 62 36 62 C36 62 18 50 18 38 V20 Z"
|
|
563
|
+
fill="rgba(10,132,255,0.1)" stroke="url(#wg)" stroke-width="2"/>
|
|
564
|
+
<line x1="36" y1="18" x2="36" y2="52" stroke="#0A84FF" stroke-width="1.5" opacity="0.5">
|
|
565
|
+
<animate attributeName="x1" values="24;48;24" dur="2.5s" repeatCount="indefinite"/>
|
|
566
|
+
<animate attributeName="x2" values="24;48;24" dur="2.5s" repeatCount="indefinite"/>
|
|
567
|
+
</line>
|
|
568
|
+
<circle cx="36" cy="34" r="6" fill="none" stroke="#ccc" stroke-width="2"/>
|
|
569
|
+
<circle cx="36" cy="34" r="2.5" fill="#0A84FF"/>
|
|
570
|
+
<circle cx="36" cy="34" r="10" fill="none" stroke="#0A84FF" stroke-width="1" opacity="0.3">
|
|
571
|
+
<animate attributeName="r" values="10;18;10" dur="3s" repeatCount="indefinite"/>
|
|
572
|
+
<animate attributeName="opacity" values="0.3;0;0.3" dur="3s" repeatCount="indefinite"/>
|
|
573
|
+
</circle>
|
|
574
|
+
<defs>
|
|
575
|
+
<linearGradient id="wg" x1="18" y1="10" x2="54" y2="62" gradientUnits="userSpaceOnUse">
|
|
576
|
+
<stop offset="0" stop-color="#0A84FF"/>
|
|
577
|
+
<stop offset="1" stop-color="#5E5CE6"/>
|
|
578
|
+
</linearGradient>
|
|
579
|
+
</defs>
|
|
580
|
+
</svg>
|
|
581
|
+
</div>
|
|
582
|
+
<h2>Moltbook Scan</h2>
|
|
583
|
+
<p>Enter an agent name above or try a demo agent from the sidebar.</p>
|
|
584
|
+
<div class="welcome-hint">
|
|
585
|
+
Press <span class="kbd">Enter</span> to scan
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
|
|
589
|
+
<!-- Report -->
|
|
590
|
+
<div class="report" id="reportBox"></div>
|
|
591
|
+
|
|
592
|
+
<!-- History Panel (hidden by default) -->
|
|
593
|
+
<div id="historyPanel" style="display:none; flex:1; flex-direction:column;">
|
|
594
|
+
<div class="panel" style="flex:1;">
|
|
595
|
+
<div class="panel-title">Scan History</div>
|
|
596
|
+
<div id="historyList" style="padding: 8px 16px 16px;">
|
|
597
|
+
<p style="font-size:13px;color:var(--text-dim);">No scans yet. Try scanning an agent.</p>
|
|
598
|
+
</div>
|
|
599
|
+
</div>
|
|
600
|
+
</div>
|
|
601
|
+
|
|
602
|
+
<!-- About Panel (hidden by default) -->
|
|
603
|
+
<div id="aboutPanel" style="display:none; flex:1;">
|
|
604
|
+
<div class="panel">
|
|
605
|
+
<div class="panel-title">About Moltbook Scan</div>
|
|
606
|
+
<div style="padding: 12px 16px; font-size: 13px; color: var(--text); line-height: 1.6;">
|
|
607
|
+
<p><strong style="color:var(--text-bright)">Moltbook Scan</strong> analyzes Moltbook agents for trust signals across four dimensions:</p>
|
|
608
|
+
<ul style="margin: 10px 0 10px 20px; color: var(--text-dim);">
|
|
609
|
+
<li><strong style="color:var(--text)">Identity (20pts)</strong> — Account age, verification, claim status</li>
|
|
610
|
+
<li><strong style="color:var(--text)">Behavior (30pts)</strong> — Posting patterns, frequency anomalies</li>
|
|
611
|
+
<li><strong style="color:var(--text)">Content (35pts)</strong> — Prompt injection, credential theft, social engineering</li>
|
|
612
|
+
<li><strong style="color:var(--text)">Community (15pts)</strong> — Karma, engagement quality</li>
|
|
613
|
+
</ul>
|
|
614
|
+
<p style="color:var(--text-dim)">Two-layer detection: regex rules (<10ms) filter 95% of content, then Claude Haiku deeply analyzes only suspicious items.</p>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
</div>
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<script>
|
|
623
|
+
const input = document.getElementById('agentInput');
|
|
624
|
+
const scanBtn = document.getElementById('scanBtn');
|
|
625
|
+
const loadingBox = document.getElementById('loadingBox');
|
|
626
|
+
const errorBox = document.getElementById('errorBox');
|
|
627
|
+
const reportBox = document.getElementById('reportBox');
|
|
628
|
+
const welcomeBox = document.getElementById('welcomeBox');
|
|
629
|
+
const historyPanel = document.getElementById('historyPanel');
|
|
630
|
+
const aboutPanel = document.getElementById('aboutPanel');
|
|
631
|
+
const DEMO_AGENTS = ['HelperBot', 'TotallyLegitBot'];
|
|
632
|
+
let scanHistory = [];
|
|
633
|
+
|
|
634
|
+
input.addEventListener('keydown', e => { if (e.key === 'Enter') startScan(); });
|
|
635
|
+
|
|
636
|
+
function showPanel(panel) {
|
|
637
|
+
document.querySelectorAll('.sidebar-item').forEach(el => el.classList.remove('active'));
|
|
638
|
+
welcomeBox.style.display = 'none';
|
|
639
|
+
reportBox.className = 'report';
|
|
640
|
+
historyPanel.style.display = 'none';
|
|
641
|
+
aboutPanel.style.display = 'none';
|
|
642
|
+
document.querySelector('.toolbar').style.display = panel === 'scan' ? 'flex' : 'none';
|
|
643
|
+
|
|
644
|
+
if (panel === 'scan') {
|
|
645
|
+
document.getElementById('nav-scan').classList.add('active');
|
|
646
|
+
if (scanHistory.length === 0 && reportBox.innerHTML === '') {
|
|
647
|
+
welcomeBox.style.display = 'flex';
|
|
648
|
+
} else if (reportBox.innerHTML) {
|
|
649
|
+
reportBox.className = 'report active';
|
|
650
|
+
} else {
|
|
651
|
+
welcomeBox.style.display = 'flex';
|
|
652
|
+
}
|
|
653
|
+
} else if (panel === 'history') {
|
|
654
|
+
document.getElementById('nav-history').classList.add('active');
|
|
655
|
+
historyPanel.style.display = 'flex';
|
|
656
|
+
renderHistory();
|
|
657
|
+
} else if (panel === 'about') {
|
|
658
|
+
document.getElementById('nav-about').classList.add('active');
|
|
659
|
+
aboutPanel.style.display = 'flex';
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function hideAll() {
|
|
664
|
+
welcomeBox.style.display = 'none';
|
|
665
|
+
reportBox.className = 'report';
|
|
666
|
+
errorBox.className = 'error-bar';
|
|
667
|
+
document.querySelector('.toolbar').style.display = 'flex';
|
|
668
|
+
historyPanel.style.display = 'none';
|
|
669
|
+
aboutPanel.style.display = 'none';
|
|
670
|
+
document.querySelectorAll('.sidebar-item').forEach(el => el.classList.remove('active'));
|
|
671
|
+
document.getElementById('nav-scan').classList.add('active');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async function runDemo(name) {
|
|
675
|
+
hideAll();
|
|
676
|
+
loadingBox.className = 'loading active';
|
|
677
|
+
input.value = '@' + name;
|
|
678
|
+
try {
|
|
679
|
+
const res = await fetch('/api/demo/' + encodeURIComponent(name));
|
|
680
|
+
const data = await res.json();
|
|
681
|
+
if (!res.ok) throw new Error(data.error || 'Demo failed');
|
|
682
|
+
addToHistory(data);
|
|
683
|
+
renderReport(data);
|
|
684
|
+
} catch (err) {
|
|
685
|
+
errorBox.textContent = err.message;
|
|
686
|
+
errorBox.className = 'error-bar active';
|
|
687
|
+
} finally {
|
|
688
|
+
loadingBox.className = 'loading';
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function startScan() {
|
|
693
|
+
const raw = input.value.trim();
|
|
694
|
+
if (!raw) return;
|
|
695
|
+
const name = raw.replace(/^@/, '');
|
|
696
|
+
hideAll();
|
|
697
|
+
loadingBox.className = 'loading active';
|
|
698
|
+
scanBtn.disabled = true;
|
|
699
|
+
try {
|
|
700
|
+
if (DEMO_AGENTS.includes(name)) {
|
|
701
|
+
const res = await fetch('/api/demo/' + encodeURIComponent(name));
|
|
702
|
+
const data = await res.json();
|
|
703
|
+
if (res.ok) { addToHistory(data); renderReport(data); return; }
|
|
704
|
+
}
|
|
705
|
+
const res = await fetch('/api/scan/' + encodeURIComponent(name));
|
|
706
|
+
const data = await res.json();
|
|
707
|
+
if (!res.ok) throw new Error(data.error || 'Scan failed');
|
|
708
|
+
addToHistory(data);
|
|
709
|
+
renderReport(data);
|
|
710
|
+
} catch (err) {
|
|
711
|
+
errorBox.textContent = err.message;
|
|
712
|
+
errorBox.className = 'error-bar active';
|
|
713
|
+
} finally {
|
|
714
|
+
loadingBox.className = 'loading';
|
|
715
|
+
scanBtn.disabled = false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function addToHistory(report) {
|
|
720
|
+
scanHistory.unshift({ report, time: new Date().toLocaleTimeString() });
|
|
721
|
+
if (scanHistory.length > 20) scanHistory.pop();
|
|
722
|
+
document.getElementById('historyCount').textContent = scanHistory.length;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function renderHistory() {
|
|
726
|
+
const list = document.getElementById('historyList');
|
|
727
|
+
if (scanHistory.length === 0) {
|
|
728
|
+
list.innerHTML = '<p style="font-size:13px;color:var(--text-dim);">No scans yet.</p>';
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
list.innerHTML = scanHistory.map((h, i) => {
|
|
732
|
+
const c = scoreColor(h.report.level);
|
|
733
|
+
return `<div class="finding-item" onclick="renderReport(scanHistory[${i}].report)" style="cursor:pointer">
|
|
734
|
+
<div class="severity-dot" style="background:${c}"></div>
|
|
735
|
+
<div class="finding-content">
|
|
736
|
+
<div class="finding-msg">${esc(h.report.agentName)}</div>
|
|
737
|
+
<div class="finding-detail">Score: ${h.report.score}/100 · ${h.time}</div>
|
|
738
|
+
</div>
|
|
739
|
+
<span class="severity-label" style="color:${c};background:${c}22">${h.report.level.replace('_',' ')}</span>
|
|
740
|
+
</div>`;
|
|
741
|
+
}).join('');
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function renderReport(r) {
|
|
745
|
+
const c = scoreColor(r.level);
|
|
746
|
+
const labels = { HIGH_TRUST:'HIGH TRUST', MODERATE:'MODERATE', LOW_TRUST:'LOW TRUST', UNTRUSTED:'UNTRUSTED' };
|
|
747
|
+
const pct = (r.score / 100) * 301;
|
|
748
|
+
|
|
749
|
+
reportBox.innerHTML = `
|
|
750
|
+
<div class="score-header">
|
|
751
|
+
<div class="score-circle">
|
|
752
|
+
<svg width="96" height="96" viewBox="0 0 110 110">
|
|
753
|
+
<circle cx="55" cy="55" r="48" fill="none" stroke="#3A3A3C" stroke-width="6"/>
|
|
754
|
+
<circle cx="55" cy="55" r="48" fill="none" stroke="${c}" stroke-width="6"
|
|
755
|
+
stroke-dasharray="${pct} 302" stroke-linecap="round"/>
|
|
756
|
+
</svg>
|
|
757
|
+
<div class="score-num" style="color:${c}">${r.score}</div>
|
|
758
|
+
<div class="score-level-badge" style="color:${c}">${labels[r.level]}</div>
|
|
759
|
+
</div>
|
|
760
|
+
<div class="score-details">
|
|
761
|
+
<div class="score-agent-name">${esc(r.agentName)}</div>
|
|
762
|
+
<div class="score-meta">
|
|
763
|
+
<div class="score-meta-item">Age: <strong>${r.metadata.accountAge}d</strong></div>
|
|
764
|
+
<div class="score-meta-item">Posts: <strong>${r.metadata.postCount}</strong></div>
|
|
765
|
+
<div class="score-meta-item">Verified: <strong>${r.metadata.verified ? 'Yes' : 'No'}</strong></div>
|
|
766
|
+
<div class="score-meta-item">Karma: <strong>${r.metadata.karma}</strong></div>
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
769
|
+
</div>
|
|
770
|
+
|
|
771
|
+
<div class="panel">
|
|
772
|
+
<div class="panel-title">Score Breakdown</div>
|
|
773
|
+
${bdRow('Identity', r.breakdown.identity, 20, c)}
|
|
774
|
+
${bdRow('Behavior', r.breakdown.behavior, 30, c)}
|
|
775
|
+
${bdRow('Content', r.breakdown.content, 35, c)}
|
|
776
|
+
${bdRow('Community', r.breakdown.community, 15, c)}
|
|
777
|
+
</div>
|
|
778
|
+
|
|
779
|
+
${r.findings.length ? `
|
|
780
|
+
<div class="panel">
|
|
781
|
+
<div class="panel-title">Findings (${r.findings.length})</div>
|
|
782
|
+
<div class="findings-list">
|
|
783
|
+
${r.findings.map(f => `
|
|
784
|
+
<div class="finding-item">
|
|
785
|
+
<div class="severity-dot ${f.severity}"></div>
|
|
786
|
+
<div class="finding-content">
|
|
787
|
+
<div class="finding-msg">${esc(f.message)}</div>
|
|
788
|
+
${f.details ? `<div class="finding-detail">${esc(f.details)}</div>` : ''}
|
|
789
|
+
</div>
|
|
790
|
+
<span class="severity-label ${f.severity}">${f.severity}</span>
|
|
791
|
+
</div>
|
|
792
|
+
`).join('')}
|
|
793
|
+
</div>
|
|
794
|
+
</div>` : ''}
|
|
795
|
+
|
|
796
|
+
<div class="panel">
|
|
797
|
+
<div class="panel-title">Analysis</div>
|
|
798
|
+
<div class="analysis-row">
|
|
799
|
+
<span class="analysis-pill">Behavioral: ${esc(r.behavioralPattern)}</span>
|
|
800
|
+
<span class="analysis-pill">Content Risk: ${esc(r.contentRisk)}</span>
|
|
801
|
+
</div>
|
|
802
|
+
</div>
|
|
803
|
+
|
|
804
|
+
<div class="action-bar">
|
|
805
|
+
<button class="action-btn" onclick="copyJSON()">Copy JSON</button>
|
|
806
|
+
<button class="action-btn" onclick="openHTML()">Full Report</button>
|
|
807
|
+
</div>
|
|
808
|
+
`;
|
|
809
|
+
reportBox.className = 'report active';
|
|
810
|
+
window._lastReport = r;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function scoreColor(level) {
|
|
814
|
+
return { HIGH_TRUST:'#34C759', MODERATE:'#FFCC00', LOW_TRUST:'#FF9F0A', UNTRUSTED:'#FF3B30' }[level] || '#808080';
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function bdRow(name, val, max, color) {
|
|
818
|
+
return `<div class="bd-row">
|
|
819
|
+
<div class="bd-label">${name}</div>
|
|
820
|
+
<div class="bd-bar"><div class="bd-bar-fill" style="width:${(val/max)*100}%;background:${color}"></div></div>
|
|
821
|
+
<div class="bd-score">${val}/${max}</div>
|
|
822
|
+
</div>`;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function esc(s) {
|
|
826
|
+
const d = document.createElement('div');
|
|
827
|
+
d.textContent = s;
|
|
828
|
+
return d.innerHTML;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function copyJSON() {
|
|
832
|
+
if (window._lastReport) {
|
|
833
|
+
navigator.clipboard.writeText(JSON.stringify(window._lastReport, null, 2));
|
|
834
|
+
const btn = event.target;
|
|
835
|
+
btn.textContent = 'Copied!';
|
|
836
|
+
setTimeout(() => btn.textContent = 'Copy JSON', 1500);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function openHTML() {
|
|
841
|
+
if (window._lastReport) {
|
|
842
|
+
const name = window._lastReport.agentName.replace('@', '');
|
|
843
|
+
window.open('/api/demo/' + name + '?format=html', '_blank');
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
</script>
|
|
847
|
+
</body>
|
|
848
|
+
</html>
|