clawaid 1.0.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/web/index.html ADDED
@@ -0,0 +1,1226 @@
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>ClawAid</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ body {
11
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, sans-serif;
12
+ background: #fafafa;
13
+ color: #202124;
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ }
19
+
20
+ .container {
21
+ width: 100%;
22
+ max-width: 600px;
23
+ padding: 48px 24px 80px;
24
+ }
25
+
26
+ /* Header */
27
+ .header {
28
+ text-align: center;
29
+ margin-bottom: 40px;
30
+ }
31
+
32
+ .logo {
33
+ font-size: 40px;
34
+ margin-bottom: 8px;
35
+ }
36
+
37
+ .header h1 {
38
+ font-size: 28px;
39
+ font-weight: 400;
40
+ color: #202124;
41
+ margin-bottom: 6px;
42
+ }
43
+
44
+ .header p {
45
+ font-size: 14px;
46
+ color: #5f6368;
47
+ }
48
+
49
+ /* Status card */
50
+ .card {
51
+ background: #fff;
52
+ border: 1px solid #e8eaed;
53
+ border-radius: 12px;
54
+ padding: 32px;
55
+ margin-bottom: 16px;
56
+ }
57
+
58
+ /* Progress log */
59
+ .log-area {
60
+ background: #f8f9fa;
61
+ border: 1px solid #e0e0e0;
62
+ border-radius: 8px;
63
+ padding: 16px;
64
+ font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
65
+ font-size: 12px;
66
+ color: #3c4043;
67
+ line-height: 1.6;
68
+ max-height: 280px;
69
+ overflow-y: auto;
70
+ white-space: pre-wrap;
71
+ word-break: break-word;
72
+ }
73
+
74
+ .log-area .log-line { display: block; }
75
+ .log-area .log-line.success { color: #137333; }
76
+ .log-area .log-line.error { color: #c5221f; }
77
+ .log-area .log-line.warn { color: #e37400; }
78
+ .log-area .log-line.meta { color: #1a73e8; font-weight: 600; }
79
+
80
+ /* Show details toggle */
81
+ .details-toggle {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 6px;
85
+ font-size: 12px;
86
+ color: #1a73e8;
87
+ cursor: pointer;
88
+ margin-top: 12px;
89
+ user-select: none;
90
+ background: none;
91
+ border: none;
92
+ padding: 0;
93
+ }
94
+ .details-toggle:hover { text-decoration: underline; }
95
+ .details-toggle .toggle-arrow { transition: transform 0.2s; display: inline-block; }
96
+ .details-toggle.open .toggle-arrow { transform: rotate(90deg); }
97
+
98
+ .details-panel {
99
+ margin-top: 8px;
100
+ display: none;
101
+ }
102
+ .details-panel.open { display: block; }
103
+
104
+ /* Spinner */
105
+ .spinner {
106
+ display: inline-block;
107
+ width: 18px;
108
+ height: 18px;
109
+ border: 2px solid #e0e0e0;
110
+ border-top-color: #1a73e8;
111
+ border-radius: 50%;
112
+ animation: spin 0.8s linear infinite;
113
+ flex-shrink: 0;
114
+ }
115
+
116
+ @keyframes spin {
117
+ to { transform: rotate(360deg); }
118
+ }
119
+
120
+ /* Status row */
121
+ .status-row {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 10px;
125
+ font-size: 15px;
126
+ color: #202124;
127
+ margin-bottom: 4px;
128
+ }
129
+
130
+ .status-sub {
131
+ font-size: 12px;
132
+ color: #9aa0a6;
133
+ margin-left: 28px;
134
+ }
135
+
136
+ /* Phase label */
137
+ .phase-label {
138
+ font-size: 10px;
139
+ font-weight: 600;
140
+ text-transform: uppercase;
141
+ letter-spacing: 1px;
142
+ color: #9aa0a6;
143
+ margin-bottom: 16px;
144
+ }
145
+
146
+ /* Diagnosis card */
147
+ .diagnosis-section {
148
+ margin-bottom: 20px;
149
+ }
150
+
151
+ .section-label {
152
+ font-size: 11px;
153
+ font-weight: 600;
154
+ color: #9aa0a6;
155
+ text-transform: uppercase;
156
+ letter-spacing: 0.5px;
157
+ margin-bottom: 8px;
158
+ }
159
+
160
+ .diagnosis-text {
161
+ font-size: 15px;
162
+ color: #202124;
163
+ line-height: 1.6;
164
+ }
165
+
166
+ .root-cause {
167
+ background: #fef7e0;
168
+ border: 1px solid #f9ab00;
169
+ border-radius: 8px;
170
+ padding: 10px 14px;
171
+ font-size: 13px;
172
+ color: #7a4f00;
173
+ margin-top: 10px;
174
+ }
175
+
176
+ .confidence-bar {
177
+ height: 4px;
178
+ background: #e8f0fe;
179
+ border-radius: 2px;
180
+ margin-top: 12px;
181
+ overflow: hidden;
182
+ }
183
+
184
+ .confidence-fill {
185
+ height: 100%;
186
+ background: #1a73e8;
187
+ border-radius: 2px;
188
+ transition: width 0.5s ease;
189
+ }
190
+
191
+ .confidence-label {
192
+ font-size: 11px;
193
+ color: #9aa0a6;
194
+ margin-top: 4px;
195
+ }
196
+
197
+ /* Collapsible reasoning */
198
+ .collapsible-toggle {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 6px;
202
+ font-size: 12px;
203
+ color: #5f6368;
204
+ cursor: pointer;
205
+ margin-top: 12px;
206
+ background: none;
207
+ border: none;
208
+ padding: 0;
209
+ user-select: none;
210
+ }
211
+ .collapsible-toggle:hover { color: #202124; }
212
+ .collapsible-toggle .arrow { transition: transform 0.2s; display: inline-block; }
213
+ .collapsible-toggle.open .arrow { transform: rotate(90deg); }
214
+ .collapsible-panel { display: none; margin-top: 8px; }
215
+ .collapsible-panel.open { display: block; }
216
+
217
+ /* Divider */
218
+ .divider {
219
+ border: none;
220
+ border-top: 1px solid #f1f3f4;
221
+ margin: 16px 0;
222
+ }
223
+
224
+ /* Option cards */
225
+ .options-list {
226
+ display: flex;
227
+ flex-direction: column;
228
+ gap: 12px;
229
+ margin-top: 8px;
230
+ }
231
+
232
+ .option-card {
233
+ border: 1.5px solid #e0e0e0;
234
+ border-radius: 10px;
235
+ padding: 16px;
236
+ transition: border-color 0.15s;
237
+ position: relative;
238
+ }
239
+
240
+ .option-card.recommended {
241
+ border-color: #1a73e8;
242
+ background: #f8fbff;
243
+ }
244
+
245
+ .option-card-header {
246
+ display: flex;
247
+ align-items: flex-start;
248
+ gap: 10px;
249
+ margin-bottom: 8px;
250
+ }
251
+
252
+ .option-letter {
253
+ width: 26px;
254
+ height: 26px;
255
+ border-radius: 50%;
256
+ background: #e8eaed;
257
+ color: #3c4043;
258
+ font-size: 13px;
259
+ font-weight: 700;
260
+ display: flex;
261
+ align-items: center;
262
+ justify-content: center;
263
+ flex-shrink: 0;
264
+ }
265
+
266
+ .option-card.recommended .option-letter {
267
+ background: #1a73e8;
268
+ color: #fff;
269
+ }
270
+
271
+ .option-title-row {
272
+ flex: 1;
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 8px;
276
+ flex-wrap: wrap;
277
+ }
278
+
279
+ .option-title {
280
+ font-size: 14px;
281
+ font-weight: 600;
282
+ color: #202124;
283
+ }
284
+
285
+ .recommended-badge {
286
+ font-size: 10px;
287
+ background: #e8f0fe;
288
+ color: #1a73e8;
289
+ border: 1px solid #c5d9f7;
290
+ border-radius: 4px;
291
+ padding: 2px 6px;
292
+ font-weight: 600;
293
+ }
294
+
295
+ .risk-badge {
296
+ font-size: 10px;
297
+ padding: 2px 6px;
298
+ border-radius: 4px;
299
+ font-weight: 600;
300
+ }
301
+ .risk-low { background: #e6f4ea; color: #137333; }
302
+ .risk-medium { background: #fef7e0; color: #7a4f00; }
303
+ .risk-high { background: #fce8e6; color: #c5221f; }
304
+
305
+ .option-description {
306
+ font-size: 13px;
307
+ color: #5f6368;
308
+ line-height: 1.5;
309
+ margin-left: 36px;
310
+ }
311
+
312
+ .option-fix-btn {
313
+ display: inline-flex;
314
+ align-items: center;
315
+ gap: 6px;
316
+ margin-top: 12px;
317
+ margin-left: 36px;
318
+ padding: 8px 18px;
319
+ font-size: 13px;
320
+ font-weight: 500;
321
+ background: #f1f3f4;
322
+ color: #3c4043;
323
+ border: none;
324
+ border-radius: 6px;
325
+ cursor: pointer;
326
+ transition: background 0.15s;
327
+ }
328
+
329
+ .option-card.recommended .option-fix-btn {
330
+ background: #1a73e8;
331
+ color: #fff;
332
+ }
333
+
334
+ .option-fix-btn:hover { opacity: 0.85; }
335
+ .option-fix-btn:disabled { opacity: 0.5; cursor: not-allowed; }
336
+
337
+ /* Auto-fix progress */
338
+ .auto-fix-row {
339
+ display: flex;
340
+ align-items: center;
341
+ gap: 12px;
342
+ padding: 12px 0;
343
+ }
344
+
345
+ .auto-fix-icon {
346
+ font-size: 20px;
347
+ }
348
+
349
+ .auto-fix-text {
350
+ font-size: 15px;
351
+ font-weight: 500;
352
+ color: #202124;
353
+ }
354
+
355
+ .auto-fix-sub {
356
+ font-size: 12px;
357
+ color: #9aa0a6;
358
+ margin-top: 2px;
359
+ }
360
+
361
+ /* Buttons */
362
+ .btn {
363
+ display: inline-flex;
364
+ align-items: center;
365
+ gap: 8px;
366
+ padding: 12px 24px;
367
+ font-size: 15px;
368
+ font-weight: 500;
369
+ border: none;
370
+ border-radius: 8px;
371
+ cursor: pointer;
372
+ transition: background 0.15s, transform 0.1s;
373
+ width: 100%;
374
+ justify-content: center;
375
+ }
376
+
377
+ .btn:active { transform: scale(0.98); }
378
+ .btn:disabled { opacity: 0.5; cursor: not-allowed; }
379
+
380
+ .btn-primary {
381
+ background: #1a73e8;
382
+ color: #fff;
383
+ }
384
+ .btn-primary:hover:not(:disabled) { background: #1557b0; }
385
+
386
+ .btn-secondary {
387
+ background: #f1f3f4;
388
+ color: #3c4043;
389
+ }
390
+ .btn-secondary:hover:not(:disabled) { background: #e8eaed; }
391
+
392
+ .btn-coffee {
393
+ background: #FFDD00;
394
+ color: #000;
395
+ font-size: 14px;
396
+ }
397
+ .btn-coffee:hover:not(:disabled) { background: #f0cf00; }
398
+
399
+ /* Input field */
400
+ .input-group { margin-bottom: 16px; }
401
+
402
+ .input-label {
403
+ display: block;
404
+ font-size: 13px;
405
+ font-weight: 500;
406
+ color: #3c4043;
407
+ margin-bottom: 6px;
408
+ }
409
+
410
+ .input-field {
411
+ width: 100%;
412
+ padding: 10px 14px;
413
+ font-size: 14px;
414
+ border: 1px solid #dadce0;
415
+ border-radius: 8px;
416
+ outline: none;
417
+ transition: border-color 0.15s;
418
+ font-family: monospace;
419
+ color: #202124;
420
+ }
421
+ .input-field:focus { border-color: #1a73e8; box-shadow: 0 0 0 2px rgba(26,115,232,0.2); }
422
+
423
+ .trust-badge {
424
+ display: flex;
425
+ align-items: center;
426
+ gap: 6px;
427
+ font-size: 12px;
428
+ color: #137333;
429
+ margin-top: 8px;
430
+ background: #e6f4ea;
431
+ padding: 6px 10px;
432
+ border-radius: 6px;
433
+ }
434
+
435
+ /* Result screens */
436
+ .result-icon {
437
+ font-size: 48px;
438
+ text-align: center;
439
+ margin-bottom: 16px;
440
+ }
441
+
442
+ .result-title {
443
+ font-size: 22px;
444
+ font-weight: 500;
445
+ text-align: center;
446
+ margin-bottom: 8px;
447
+ }
448
+
449
+ .result-desc {
450
+ font-size: 14px;
451
+ color: #5f6368;
452
+ text-align: center;
453
+ line-height: 1.6;
454
+ margin-bottom: 24px;
455
+ }
456
+
457
+ .report-area {
458
+ background: #f8f9fa;
459
+ border: 1px solid #e0e0e0;
460
+ border-radius: 8px;
461
+ padding: 16px;
462
+ font-family: monospace;
463
+ font-size: 11px;
464
+ color: #3c4043;
465
+ max-height: 200px;
466
+ overflow-y: auto;
467
+ white-space: pre-wrap;
468
+ word-break: break-all;
469
+ margin-bottom: 12px;
470
+ }
471
+
472
+ /* Alternatives */
473
+ .alt-list { list-style: none; margin-top: 6px; }
474
+ .alt-list li {
475
+ font-size: 13px;
476
+ color: #5f6368;
477
+ padding: 4px 0;
478
+ display: flex;
479
+ gap: 6px;
480
+ }
481
+ .alt-list li::before { content: '→'; color: #9aa0a6; flex-shrink: 0; }
482
+
483
+ /* Sections */
484
+ .section { display: none; }
485
+ .section.visible { display: block; }
486
+
487
+ @keyframes fadeIn {
488
+ from { opacity: 0; transform: translateY(8px); }
489
+ to { opacity: 1; transform: translateY(0); }
490
+ }
491
+ .fade-in { animation: fadeIn 0.25s ease; }
492
+
493
+ /* Footer */
494
+ .footer {
495
+ text-align: center;
496
+ font-size: 12px;
497
+ color: #9aa0a6;
498
+ margin-top: 32px;
499
+ }
500
+ .footer a { color: #1a73e8; text-decoration: none; }
501
+ .footer a:hover { text-decoration: underline; }
502
+
503
+ /* Copy button */
504
+ .copy-btn {
505
+ background: none;
506
+ border: 1px solid #dadce0;
507
+ border-radius: 6px;
508
+ padding: 6px 12px;
509
+ font-size: 12px;
510
+ cursor: pointer;
511
+ color: #5f6368;
512
+ transition: background 0.15s;
513
+ }
514
+ .copy-btn:hover { background: #f1f3f4; }
515
+
516
+ /* Round badge */
517
+ .round-badge {
518
+ display: inline-flex;
519
+ align-items: center;
520
+ gap: 6px;
521
+ background: #e8f0fe;
522
+ color: #1a73e8;
523
+ font-size: 12px;
524
+ font-weight: 600;
525
+ padding: 4px 10px;
526
+ border-radius: 20px;
527
+ margin-bottom: 12px;
528
+ }
529
+ </style>
530
+ </head>
531
+ <body>
532
+ <div class="container">
533
+
534
+ <!-- Header -->
535
+ <div class="header">
536
+ <div class="logo" style="font-size:48px; line-height:1;">🦞🩹</div>
537
+ <h1>ClawAid</h1>
538
+ <p>Automatic diagnostics and repair for OpenClaw</p>
539
+ </div>
540
+
541
+ <!-- ===== SECTION: Privacy ===== -->
542
+ <div id="section-privacy" class="section visible card fade-in">
543
+ <div style="text-align:center; font-size:32px; margin-bottom:12px;">🔒</div>
544
+ <div style="font-size:16px; font-weight:500; text-align:center; margin-bottom:12px;">Before we start</div>
545
+ <div style="font-size:13px; color:#5f6368; line-height:1.6; margin-bottom:16px;">
546
+ ClawAid will collect your OpenClaw config, gateway status, and recent logs to diagnose issues.
547
+ <ul style="margin-top:8px; padding-left:20px;">
548
+ <li>API keys are partially redacted before sending</li>
549
+ <li>Data is sent to <strong>our diagnostic service</strong> for analysis only</li>
550
+ <li>Nothing is stored or shared beyond this session</li>
551
+ </ul>
552
+ </div>
553
+ <button class="btn btn-primary" id="btn-start-scan">
554
+ 🩺 Start Scan
555
+ </button>
556
+ </div>
557
+
558
+ <!-- ===== SECTION: Scanning ===== -->
559
+ <div id="section-scanning" class="section card fade-in">
560
+ <div class="phase-label">Phase 1 — Scanning</div>
561
+ <div class="status-row">
562
+ <span class="spinner"></span>
563
+ <span id="scan-status-text">Starting scan...</span>
564
+ </div>
565
+ <div class="status-sub" id="scan-sub-text"></div>
566
+
567
+ <button class="details-toggle" id="btn-toggle-scan-log" onclick="toggleDetails('scan-log')">
568
+ <span class="toggle-arrow">▶</span> Show details
569
+ </button>
570
+ <div class="details-panel" id="details-scan-log">
571
+ <div class="log-area" id="log-area">Connecting to diagnostic engine...</div>
572
+ </div>
573
+ </div>
574
+
575
+ <!-- ===== SECTION: API Key Input ===== -->
576
+ <div id="section-apikey" class="section card fade-in">
577
+ <div class="phase-label">Action Required</div>
578
+ <div class="diagnosis-section">
579
+ <div class="section-label">API Key Required</div>
580
+ <p class="diagnosis-text" id="apikey-instructions">
581
+ ClawAid needs an OpenRouter API key to run diagnostics.
582
+ </p>
583
+ </div>
584
+ <hr class="divider">
585
+ <div class="input-group">
586
+ <label class="input-label" for="apikey-input">OpenRouter API Key</label>
587
+ <input
588
+ class="input-field"
589
+ id="apikey-input"
590
+ type="password"
591
+ placeholder="sk-or-..."
592
+ autocomplete="off"
593
+ spellcheck="false"
594
+ />
595
+ <div class="trust-badge">
596
+ 🔒 Local only. Not stored. Only sent to OpenRouter for AI diagnosis.
597
+ </div>
598
+ <div style="margin-top: 6px;">
599
+ <a href="https://openrouter.ai/keys" target="_blank" style="font-size: 12px; color: #1a73e8;">
600
+ Get an OpenRouter API key →
601
+ </a>
602
+ </div>
603
+ </div>
604
+ <button class="btn btn-primary" id="btn-submit-key">
605
+ Start Diagnosis
606
+ </button>
607
+ </div>
608
+
609
+ <!-- ===== SECTION: Diagnosis ===== -->
610
+ <div id="section-diagnosis" class="section card fade-in">
611
+ <div class="phase-label">Phase 2 — Diagnosis</div>
612
+ <div id="round-badge-diag" class="round-badge" style="display:none;">
613
+ <span>🔄</span><span id="round-badge-text">Round 2</span>
614
+ </div>
615
+
616
+ <div class="diagnosis-section">
617
+ <div class="section-label">What's wrong</div>
618
+ <p class="diagnosis-text" id="diag-text">Analyzing...</p>
619
+ <div class="root-cause" id="root-cause-box"></div>
620
+ <div class="confidence-bar">
621
+ <div class="confidence-fill" id="confidence-fill" style="width:0%"></div>
622
+ </div>
623
+ <div class="confidence-label" id="confidence-label">Confidence: —</div>
624
+ </div>
625
+
626
+ <button class="collapsible-toggle" id="toggle-reasoning" onclick="toggleReasoning()">
627
+ <span class="arrow">▶</span> Diagnostic details
628
+ </button>
629
+ <div class="collapsible-panel" id="reasoning-panel">
630
+ <div class="log-area" id="reasoning-log" style="max-height:180px;"></div>
631
+ </div>
632
+
633
+ <div id="alternatives-section" style="display:none; margin-top:12px;">
634
+ <hr class="divider">
635
+ <div class="section-label">Other possibilities</div>
636
+ <ul class="alt-list" id="alt-list"></ul>
637
+ </div>
638
+ </div>
639
+
640
+ <!-- ===== SECTION: Options (user choice) ===== -->
641
+ <div id="section-options" class="section card fade-in">
642
+ <div class="phase-label">Phase 3 — Choose a Fix</div>
643
+ <div id="round-badge-opts" class="round-badge" style="display:none;">
644
+ <span>🔄</span><span id="round-opts-text">Round 2</span>
645
+ </div>
646
+ <p style="font-size:14px; color:#5f6368; margin-bottom:16px;">
647
+ Select a repair option below. The recommended fix is highlighted.
648
+ </p>
649
+ <div class="options-list" id="options-list"></div>
650
+ </div>
651
+
652
+ <!-- ===== SECTION: Auto-executing ===== -->
653
+ <div id="section-auto-fix" class="section card fade-in">
654
+ <div class="phase-label">Phase 3 — Auto-fixing</div>
655
+ <div class="auto-fix-row">
656
+ <div class="auto-fix-icon">🔧</div>
657
+ <div>
658
+ <div class="auto-fix-text">Auto-fixing...</div>
659
+ <div class="auto-fix-sub" id="auto-fix-sub">Applying recommended fix</div>
660
+ </div>
661
+ <span class="spinner" style="margin-left:auto;"></span>
662
+ </div>
663
+
664
+ <button class="details-toggle" id="btn-toggle-fix-log" onclick="toggleDetails('fix-log')">
665
+ <span class="toggle-arrow">▶</span> Show details
666
+ </button>
667
+ <div class="details-panel" id="details-fix-log">
668
+ <div class="log-area" id="exec-log"></div>
669
+ </div>
670
+ </div>
671
+
672
+ <!-- ===== SECTION: Fixed ===== -->
673
+ <div id="section-fixed" class="section card fade-in">
674
+ <div class="result-icon">✅</div>
675
+ <div class="result-title">OpenClaw is fixed!</div>
676
+
677
+ <div id="fixed-summary" style="text-align:left; margin: 20px 0;">
678
+ <div style="margin-bottom:16px;">
679
+ <div style="font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:1px; color:#9aa0a6; margin-bottom:6px;">What was wrong</div>
680
+ <div id="fixed-what-wrong" style="font-size:14px; color:#3c4043; line-height:1.6;"></div>
681
+ </div>
682
+ <div style="border-top:1px solid #e8eaed; padding-top:16px;">
683
+ <div style="font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:1px; color:#9aa0a6; margin-bottom:6px;">What we fixed</div>
684
+ <div id="fixed-what-fixed" style="font-size:14px; color:#3c4043; line-height:1.6;"></div>
685
+ </div>
686
+ </div>
687
+
688
+ <div style="margin-bottom:16px;">
689
+ <button class="details-toggle" id="btn-fixed-details" onclick="toggleFixedDetails()">
690
+ <span class="toggle-arrow">▶</span> Show technical details
691
+ </button>
692
+ <div class="details-panel" id="fixed-details-panel" style="margin-top:8px;">
693
+ <p id="fixed-explanation" style="font-size:13px; color:#5f6368;"></p>
694
+ </div>
695
+ </div>
696
+
697
+ <button class="btn btn-coffee" onclick="window.open('https://buymeacoffee.com/clawaid','_blank')">
698
+ ☕ Buy me a coffee
699
+ </button>
700
+ <div style="margin-top: 8px; text-align: center; font-size: 12px; color: #9aa0a6;">
701
+ ClawAid is free. If it saved your day, a coffee helps keep it alive!
702
+ </div>
703
+ </div>
704
+
705
+ <!-- ===== SECTION: Healthy ===== -->
706
+ <div id="section-healthy" class="section card fade-in">
707
+ <div class="result-icon">💚</div>
708
+ <div class="result-title">OpenClaw is healthy!</div>
709
+ <p class="result-desc" id="healthy-explanation">Your system is running normally. No issues detected.</p>
710
+
711
+ <div id="healthy-reasoning" style="display:none; text-align:left; margin-bottom:12px;">
712
+ <button class="details-toggle" onclick="this.nextElementSibling.classList.toggle('open'); this.querySelector('.toggle-arrow').textContent = this.nextElementSibling.classList.contains('open') ? '▼' : '▶'">
713
+ <span class="toggle-arrow">▶</span> How we diagnosed this
714
+ </button>
715
+ <div class="details-panel" id="healthy-reasoning-list" style="margin-top:8px;"></div>
716
+ </div>
717
+
718
+ <div id="healthy-warnings" style="display:none; text-align:left; margin-bottom:16px;">
719
+ <div style="font-size:13px; font-weight:600; color:#e37400; margin-bottom:8px;">⚠️ Non-critical notes</div>
720
+ <ul id="healthy-warnings-list" style="list-style:none; padding:0; margin:0;"></ul>
721
+ </div>
722
+
723
+ <button class="btn btn-secondary" onclick="location.reload()" style="margin-bottom: 8px;">
724
+ 🔄 Scan Again
725
+ </button>
726
+ </div>
727
+
728
+ <!-- ===== SECTION: Not Fixed ===== -->
729
+ <div id="section-notfixed" class="section card fade-in">
730
+ <div class="result-icon">😔</div>
731
+ <div class="result-title">Couldn't fix automatically</div>
732
+ <p class="result-desc" id="notfixed-explanation">
733
+ After multiple attempts, the issue couldn't be resolved automatically.
734
+ Here's the diagnostic report to share with the community.
735
+ </p>
736
+
737
+ <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
738
+ <span style="font-size: 13px; font-weight: 500; color: #3c4043;">Diagnostic Report</span>
739
+ <button class="copy-btn" id="btn-copy-report">Copy</button>
740
+ </div>
741
+ <div class="report-area" id="report-area"></div>
742
+
743
+ <button class="btn btn-secondary" onclick="window.open('https://discord.gg/openclaw','_blank')" style="margin-bottom: 8px;">
744
+ 💬 Get Help on Discord
745
+ </button>
746
+ <button class="btn btn-secondary" onclick="location.reload()">
747
+ 🔄 Try Again
748
+ </button>
749
+ </div>
750
+
751
+ <div class="footer">
752
+ <p>ClawAid v1.0.0 · <a href="https://github.com/openclaw" target="_blank">GitHub</a></p>
753
+ </div>
754
+
755
+ </div>
756
+
757
+ <script>
758
+ (function() {
759
+ 'use strict';
760
+
761
+ // State
762
+ let sessionId = null;
763
+ let currentState = 'idle';
764
+ let eventSource = null;
765
+ let diagnosisData = null; // last diagnosis received
766
+ let chosenOption = null; // option selected for fix
767
+
768
+ // Elements
769
+ const $ = id => document.getElementById(id);
770
+
771
+ const sections = {
772
+ privacy: $('section-privacy'),
773
+ scanning: $('section-scanning'),
774
+ apikey: $('section-apikey'),
775
+ diagnosis: $('section-diagnosis'),
776
+ options: $('section-options'),
777
+ autofix: $('section-auto-fix'),
778
+ fixed: $('section-fixed'),
779
+ healthy: $('section-healthy'),
780
+ notfixed: $('section-notfixed'),
781
+ };
782
+
783
+ function showOnly(name) {
784
+ Object.entries(sections).forEach(([key, el]) => {
785
+ el.classList.toggle('visible', key === name);
786
+ });
787
+ currentState = name;
788
+ }
789
+
790
+ function appendLog(areaId, msg, cls) {
791
+ const area = $(areaId);
792
+ if (!area) return;
793
+ const line = document.createElement('span');
794
+ line.className = 'log-line' + (cls ? ' ' + cls : '');
795
+ line.textContent = msg + '\n';
796
+ area.appendChild(line);
797
+ area.scrollTop = area.scrollHeight;
798
+ }
799
+
800
+ function classForMessage(msg) {
801
+ if (msg.startsWith('✓') || msg.startsWith('✅') || msg.includes('Success') || msg.includes('fixed')) return 'success';
802
+ if (msg.startsWith('❌') || msg.startsWith('✗') || msg.includes('Error') || msg.includes('failed')) return 'error';
803
+ if (msg.startsWith('⚠️') || msg.startsWith('Warning')) return 'warn';
804
+ if (msg.startsWith('🤖') || msg.startsWith('💭') || msg.startsWith('🔄') || msg.startsWith('📡')) return 'meta';
805
+ return '';
806
+ }
807
+
808
+ // Details toggle (scan + fix log panels)
809
+ window.toggleDetails = function(panelSuffix) {
810
+ const btnId = 'btn-toggle-' + panelSuffix;
811
+ const panelId = 'details-' + panelSuffix;
812
+ const btn = $(btnId);
813
+ const panel = $(panelId);
814
+ if (!btn || !panel) return;
815
+ const isOpen = panel.classList.toggle('open');
816
+ btn.classList.toggle('open', isOpen);
817
+ btn.querySelector('.toggle-arrow').textContent = isOpen ? '▼' : '▶';
818
+ btn.childNodes[1].textContent = isOpen ? ' Hide details' : ' Show details';
819
+ };
820
+
821
+ // Fixed details toggle
822
+ window.toggleFixedDetails = function() {
823
+ const btn = $('btn-fixed-details');
824
+ const panel = $('fixed-details-panel');
825
+ const isOpen = panel.classList.toggle('open');
826
+ btn.querySelector('.toggle-arrow').textContent = isOpen ? '▼' : '▶';
827
+ btn.childNodes[1].textContent = isOpen ? ' Hide technical details' : ' Show technical details';
828
+ };
829
+
830
+ // Reasoning toggle
831
+ window.toggleReasoning = function() {
832
+ const btn = $('toggle-reasoning');
833
+ const panel = $('reasoning-panel');
834
+ const isOpen = panel.classList.toggle('open');
835
+ btn.classList.toggle('open', isOpen);
836
+ btn.querySelector('.arrow').textContent = isOpen ? '▼' : '▶';
837
+ btn.childNodes[1].textContent = isOpen ? ' Hide diagnostic details' : ' Diagnostic details';
838
+ };
839
+
840
+ // Start SSE connection
841
+ function startDiagnosis() {
842
+ showOnly('scanning');
843
+ $('log-area').textContent = '';
844
+ $('exec-log').textContent = '';
845
+
846
+ eventSource = new EventSource('/api/diagnose');
847
+
848
+ eventSource.onmessage = function(e) {
849
+ let parsed;
850
+ try { parsed = JSON.parse(e.data); } catch { return; }
851
+
852
+ const { type, data } = parsed;
853
+ sessionId = parsed.sessionId || sessionId;
854
+
855
+ switch(type) {
856
+ case 'session_start':
857
+ sessionId = data.sessionId;
858
+ $('scan-status-text').textContent = 'Checking gateway...';
859
+ break;
860
+
861
+ case 'progress': {
862
+ const msg = data.message || '';
863
+ const cls = classForMessage(msg);
864
+ appendLog('log-area', msg, cls);
865
+ appendLog('exec-log', msg, cls);
866
+ appendLog('reasoning-log', msg, cls);
867
+
868
+ // Update clean status text
869
+ const clean = msg.replace(/^[🔍🤔🔧✅⚠️❌💭🔄→📡🔒 ]+/, '').trim().slice(0, 80);
870
+ if (clean) {
871
+ $('scan-status-text').textContent = clean;
872
+ $('auto-fix-sub').textContent = clean;
873
+ }
874
+ break;
875
+ }
876
+
877
+ case 'state_change':
878
+ handleStateChange(data.state);
879
+ break;
880
+
881
+ case 'diagnosis':
882
+ showDiagnosis(data);
883
+ break;
884
+
885
+ case 'action_result':
886
+ break;
887
+
888
+ case 'verify_result':
889
+ appendLog('exec-log', '─────────────────────────────────────', '');
890
+ if (data.fixed) {
891
+ appendLog('exec-log', '✅ Verified — issue resolved!', 'success');
892
+ appendLog('exec-log', ' ' + data.explanation, 'success');
893
+ } else {
894
+ appendLog('exec-log', '⚠️ Not yet fixed: ' + data.explanation, 'warn');
895
+ appendLog('exec-log', ' Re-analyzing...', '');
896
+ }
897
+ break;
898
+
899
+ case 'request_input':
900
+ if (data.field === 'apiKey') showApiKeyInput(data);
901
+ break;
902
+
903
+ case 'complete':
904
+ handleComplete(data);
905
+ break;
906
+
907
+ case 'error':
908
+ appendLog('log-area', '─────────────────────────────────────', '');
909
+ appendLog('log-area', '❌ Error: ' + data.message, 'error');
910
+ appendLog('log-area', ' Check your API key and internet connection, then reload to try again.', 'warn');
911
+ $('scan-status-text').textContent = 'Error — click "Show details"';
912
+ showOnly('scanning');
913
+ // Auto-open details on error
914
+ const detailPanel = $('details-scan-log');
915
+ if (detailPanel && !detailPanel.classList.contains('open')) {
916
+ window.toggleDetails('scan-log');
917
+ }
918
+ break;
919
+ }
920
+ };
921
+
922
+ eventSource.onerror = function() {
923
+ if (currentState !== 'fixed' && currentState !== 'notfixed' && currentState !== 'healthy') {
924
+ appendLog('log-area', '⚠️ Connection lost. The server may have stopped.', 'warn');
925
+ }
926
+ };
927
+ }
928
+
929
+ function handleStateChange(state) {
930
+ switch(state) {
931
+ case 'observing':
932
+ showOnly('scanning');
933
+ $('scan-status-text').textContent = 'Checking gateway...';
934
+ break;
935
+ case 'diagnosing':
936
+ showOnly('scanning');
937
+ $('scan-status-text').textContent = 'Analyzing your system...';
938
+ $('scan-sub-text').textContent = 'This may take a few seconds';
939
+ appendLog('log-area', '─────────────────────────────────────', '');
940
+ appendLog('log-area', '🤖 Sending data to AI for diagnosis...', 'meta');
941
+ break;
942
+ case 'showing_options':
943
+ // Options will show via 'diagnosis' event + render options
944
+ break;
945
+ case 'auto_executing':
946
+ $('exec-log').textContent = '';
947
+ showOnly('autofix');
948
+ break;
949
+ case 'executing':
950
+ $('exec-log').textContent = '';
951
+ showOnly('autofix');
952
+ break;
953
+ case 'verifying':
954
+ $('auto-fix-sub').textContent = 'Verifying repair...';
955
+ appendLog('exec-log', '─────────────────────────────────────', '');
956
+ appendLog('exec-log', '🔍 Checking if the fix worked...', 'meta');
957
+ break;
958
+ case 'needs_api_key':
959
+ break; // handled by request_input event
960
+ case 'error':
961
+ $('scan-status-text').textContent = 'Error occurred';
962
+ break;
963
+ case 'fixed':
964
+ showOnly('fixed');
965
+ break;
966
+ case 'healthy':
967
+ showOnly('healthy');
968
+ break;
969
+ case 'not_fixed':
970
+ showOnly('notfixed');
971
+ break;
972
+ }
973
+ }
974
+
975
+ function showDiagnosis(data) {
976
+ diagnosisData = data;
977
+ const confidence = Math.round((data.confidence || 0.5) * 100);
978
+ const options = data.options || [];
979
+ const round = data.round || 1;
980
+
981
+ // Render reasoning as clean list
982
+ const reasoningLog = $('reasoning-log');
983
+ reasoningLog.innerHTML = '';
984
+ const reasoning = data.reasoning || [];
985
+ if (reasoning.length > 0) {
986
+ reasoning.forEach(function(r, i) {
987
+ const line = document.createElement('div');
988
+ line.style.cssText = 'padding:6px 0; font-size:13px; color:#3c4043; border-bottom:1px solid #f1f3f4;';
989
+ line.textContent = (i+1) + '. ' + r;
990
+ reasoningLog.appendChild(line);
991
+ });
992
+ } else {
993
+ reasoningLog.textContent = 'No reasoning details available.';
994
+ }
995
+
996
+ // Update diagnosis card
997
+ $('diag-text').textContent = data.diagnosis || '';
998
+ $('root-cause-box').textContent = '🎯 ' + (data.rootCause || 'Unknown root cause');
999
+ $('confidence-fill').style.width = confidence + '%';
1000
+ $('confidence-label').textContent = 'Confidence: ' + confidence + '%';
1001
+
1002
+ // Round badge
1003
+ if (round > 1) {
1004
+ $('round-badge-diag').style.display = 'inline-flex';
1005
+ $('round-badge-text').textContent = 'Round ' + round + ' — new approach';
1006
+ $('round-badge-opts').style.display = 'inline-flex';
1007
+ $('round-opts-text').textContent = 'Round ' + round + ' — new approach';
1008
+ } else {
1009
+ $('round-badge-diag').style.display = 'none';
1010
+ $('round-badge-opts').style.display = 'none';
1011
+ }
1012
+
1013
+ // Alternatives
1014
+ const alts = data.alternativeHypotheses || [];
1015
+ if (alts.length > 0) {
1016
+ $('alternatives-section').style.display = 'block';
1017
+ const altList = $('alt-list');
1018
+ altList.innerHTML = '';
1019
+ alts.forEach(alt => {
1020
+ const li = document.createElement('li');
1021
+ li.textContent = alt;
1022
+ altList.appendChild(li);
1023
+ });
1024
+ } else {
1025
+ $('alternatives-section').style.display = 'none';
1026
+ }
1027
+
1028
+ // Show diagnosis card
1029
+ showOnly('diagnosis');
1030
+
1031
+ // Auto-check if we should show options too immediately
1032
+ const recommended = options.filter(o => o.recommended);
1033
+ const canAutoExecute = recommended.length > 0 && recommended.every(o => o.autoExecute);
1034
+
1035
+ if (canAutoExecute) {
1036
+ // Store recommended option for result page
1037
+ chosenOption = recommended[0] || null;
1038
+ // Auto-execute: show diagnosis briefly, then trigger fix
1039
+ setTimeout(function() {
1040
+ fetch('/api/fix', {
1041
+ method: 'POST',
1042
+ headers: { 'Content-Type': 'application/json' },
1043
+ body: JSON.stringify({ sessionId: sessionId }),
1044
+ });
1045
+ }, 1200);
1046
+ } else if (options.length > 0) {
1047
+ // Render option cards and switch to options section
1048
+ renderOptions(options);
1049
+ // Show diagnosis briefly then switch to options after 1s
1050
+ setTimeout(() => showOnly('options'), 3000);
1051
+ }
1052
+ }
1053
+
1054
+ function renderOptions(options) {
1055
+ const list = $('options-list');
1056
+ list.innerHTML = '';
1057
+
1058
+ options.forEach(opt => {
1059
+ const card = document.createElement('div');
1060
+ card.className = 'option-card' + (opt.recommended ? ' recommended' : '');
1061
+
1062
+ const riskClass = 'risk-' + (opt.risk || 'low');
1063
+
1064
+ card.innerHTML = `
1065
+ <div class="option-card-header">
1066
+ <div class="option-letter">${escHtml(opt.id)}</div>
1067
+ <div class="option-title-row">
1068
+ <span class="option-title">${escHtml(opt.title)}</span>
1069
+ ${opt.recommended ? '<span class="recommended-badge">★ Recommended</span>' : ''}
1070
+ <span class="risk-badge ${riskClass}">${escHtml(opt.risk)}</span>
1071
+ </div>
1072
+ </div>
1073
+ <div class="option-description">${escHtml(opt.description)}</div>
1074
+ <button class="option-fix-btn" data-option-id="${escHtml(opt.id)}">
1075
+ 🔧 Fix with this option
1076
+ </button>
1077
+ `;
1078
+
1079
+ list.appendChild(card);
1080
+ });
1081
+
1082
+ // Attach click handlers
1083
+ list.querySelectorAll('.option-fix-btn').forEach(btn => {
1084
+ btn.addEventListener('click', async function() {
1085
+ const optionId = this.dataset.optionId;
1086
+ // Store chosen option for result page
1087
+ chosenOption = (diagnosisData && diagnosisData.options) ? diagnosisData.options.find(o => o.id === optionId) || diagnosisData.options.find(o => o.recommended) : null;
1088
+ // Disable all buttons
1089
+ list.querySelectorAll('.option-fix-btn').forEach(b => { b.disabled = true; });
1090
+
1091
+ $('exec-log').textContent = '';
1092
+ appendLog('exec-log', '🔧 Starting repair: Option ' + optionId + '...', 'meta');
1093
+ showOnly('autofix');
1094
+
1095
+ try {
1096
+ await fetch('/api/fix', {
1097
+ method: 'POST',
1098
+ headers: { 'Content-Type': 'application/json' },
1099
+ body: JSON.stringify({ sessionId, optionId }),
1100
+ });
1101
+ } catch(e) {
1102
+ appendLog('exec-log', '❌ Failed to start fix: ' + e.message, 'error');
1103
+ }
1104
+ });
1105
+ });
1106
+ }
1107
+
1108
+ function showApiKeyInput(data) {
1109
+ if (data.instructions) $('apikey-instructions').textContent = data.instructions;
1110
+ showOnly('apikey');
1111
+ $('apikey-input').focus();
1112
+ }
1113
+
1114
+ function handleComplete(data) {
1115
+ if (eventSource) {
1116
+ eventSource.close();
1117
+ eventSource = null;
1118
+ }
1119
+
1120
+ if (data.healthy) {
1121
+ $('healthy-explanation').textContent = data.explanation || 'Your system is running normally. No issues detected.';
1122
+ // Show reasoning if any
1123
+ const reasoning = data.reasoning || [];
1124
+ if (reasoning.length > 0) {
1125
+ $('healthy-reasoning').style.display = 'block';
1126
+ const list = $('healthy-reasoning-list');
1127
+ list.innerHTML = '';
1128
+ reasoning.forEach(function(r, i) {
1129
+ const div = document.createElement('div');
1130
+ div.style.cssText = 'padding:4px 0; font-size:13px; color:#5f6368;';
1131
+ div.textContent = (i+1) + '. ' + r;
1132
+ list.appendChild(div);
1133
+ });
1134
+ }
1135
+ // Show warnings if any
1136
+ const warnings = data.warnings || [];
1137
+ if (warnings.length > 0) {
1138
+ $('healthy-warnings').style.display = 'block';
1139
+ const list = $('healthy-warnings-list');
1140
+ list.innerHTML = '';
1141
+ warnings.forEach(function(w) {
1142
+ const li = document.createElement('li');
1143
+ li.style.cssText = 'font-size:13px; color:#5f6368; padding:6px 10px; background:#fef7e0; border-radius:6px; margin-bottom:6px; line-height:1.4;';
1144
+ li.textContent = w;
1145
+ list.appendChild(li);
1146
+ });
1147
+ }
1148
+ showOnly('healthy');
1149
+ } else if (data.fixed) {
1150
+ // Populate result summary
1151
+ const wrongEl = $('fixed-what-wrong');
1152
+ const fixedEl = $('fixed-what-fixed');
1153
+ if (wrongEl) wrongEl.textContent = (diagnosisData && diagnosisData.diagnosis) || data.explanation || 'An issue was detected and resolved.';
1154
+ if (fixedEl && chosenOption) {
1155
+ fixedEl.innerHTML = '<strong>' + chosenOption.title + '</strong><br><span style="color:#5f6368">' + (chosenOption.description || '') + '</span>';
1156
+ } else if (fixedEl) {
1157
+ fixedEl.textContent = data.explanation || 'OpenClaw has been repaired!';
1158
+ }
1159
+ $('fixed-explanation').textContent = data.explanation || 'OpenClaw has been repaired!';
1160
+ showOnly('fixed');
1161
+ } else {
1162
+ $('notfixed-explanation').textContent = data.explanation || 'Could not automatically fix the issue.';
1163
+ if (data.diagnosticReport) $('report-area').textContent = data.diagnosticReport;
1164
+ showOnly('notfixed');
1165
+ }
1166
+ }
1167
+
1168
+ function escHtml(str) {
1169
+ return String(str)
1170
+ .replace(/&/g, '&amp;')
1171
+ .replace(/</g, '&lt;')
1172
+ .replace(/>/g, '&gt;')
1173
+ .replace(/"/g, '&quot;');
1174
+ }
1175
+
1176
+ // API key submit
1177
+ $('btn-submit-key').addEventListener('click', async function() {
1178
+ const key = $('apikey-input').value.trim();
1179
+ if (!key) {
1180
+ $('apikey-input').focus();
1181
+ $('apikey-input').style.borderColor = '#c5221f';
1182
+ return;
1183
+ }
1184
+
1185
+ $('apikey-input').style.borderColor = '';
1186
+ this.disabled = true;
1187
+ this.textContent = 'Starting...';
1188
+
1189
+ showOnly('scanning');
1190
+ $('log-area').textContent = '';
1191
+ $('scan-status-text').textContent = 'Initializing with API key...';
1192
+
1193
+ try {
1194
+ await fetch('/api/input', {
1195
+ method: 'POST',
1196
+ headers: { 'Content-Type': 'application/json' },
1197
+ body: JSON.stringify({ sessionId, field: 'apiKey', value: key }),
1198
+ });
1199
+ } catch(e) {
1200
+ showOnly('apikey');
1201
+ this.disabled = false;
1202
+ this.textContent = 'Start Diagnosis';
1203
+ }
1204
+ });
1205
+
1206
+ $('apikey-input').addEventListener('keydown', function(e) {
1207
+ if (e.key === 'Enter') $('btn-submit-key').click();
1208
+ });
1209
+
1210
+ $('btn-copy-report').addEventListener('click', function() {
1211
+ const text = $('report-area').textContent;
1212
+ navigator.clipboard.writeText(text).then(() => {
1213
+ this.textContent = 'Copied!';
1214
+ setTimeout(() => { this.textContent = 'Copy'; }, 2000);
1215
+ });
1216
+ });
1217
+
1218
+ // Privacy notice — start scan on button click
1219
+ $('btn-start-scan').addEventListener('click', function() {
1220
+ startDiagnosis();
1221
+ });
1222
+
1223
+ })();
1224
+ </script>
1225
+ </body>
1226
+ </html>