clawmagic 1.0.1

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 (46) hide show
  1. package/README.md +49 -0
  2. package/configs/base/AGENTS.md +93 -0
  3. package/configs/base/HEARTBEAT.md +58 -0
  4. package/configs/base/MEMORY.md +20 -0
  5. package/configs/base/SESSION_HANDOFF.md +18 -0
  6. package/configs/base/TOOLS.md +22 -0
  7. package/configs/base/USER.md +10 -0
  8. package/configs/creator/SOUL.md +55 -0
  9. package/configs/developer/SOUL.md +39 -0
  10. package/configs/pro/SOUL.md +50 -0
  11. package/configs/realestate/SOUL.md +46 -0
  12. package/configs/standard/SOUL.md +37 -0
  13. package/dist/configure.d.ts +5 -0
  14. package/dist/configure.d.ts.map +1 -0
  15. package/dist/configure.js +268 -0
  16. package/dist/configure.js.map +1 -0
  17. package/dist/detect.d.ts +4 -0
  18. package/dist/detect.d.ts.map +1 -0
  19. package/dist/detect.js +156 -0
  20. package/dist/detect.js.map +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +542 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/license.d.ts +6 -0
  26. package/dist/license.d.ts.map +1 -0
  27. package/dist/license.js +125 -0
  28. package/dist/license.js.map +1 -0
  29. package/dist/security.d.ts +3 -0
  30. package/dist/security.d.ts.map +1 -0
  31. package/dist/security.js +100 -0
  32. package/dist/security.js.map +1 -0
  33. package/dist/server.d.ts +12 -0
  34. package/dist/server.d.ts.map +1 -0
  35. package/dist/server.js +227 -0
  36. package/dist/server.js.map +1 -0
  37. package/dist/types.d.ts +42 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/dist/types.js +46 -0
  40. package/dist/types.js.map +1 -0
  41. package/dist/validate-keys.d.ts +6 -0
  42. package/dist/validate-keys.d.ts.map +1 -0
  43. package/dist/validate-keys.js +83 -0
  44. package/dist/validate-keys.js.map +1 -0
  45. package/package.json +47 -0
  46. package/src/web/index.html +746 -0
@@ -0,0 +1,746 @@
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>ClawMagic — OpenClaw, Configured Automagically</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital@1&display=swap" rel="stylesheet">
9
+ <style>
10
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
11
+
12
+ :root {
13
+ --bg: #0a0a0a;
14
+ --bg-card: #141414;
15
+ --bg-card-hover: #1a1a1a;
16
+ --border: #2a2a2a;
17
+ --border-accent: #DA7756;
18
+ --text: #e5e5e5;
19
+ --text-dim: #888;
20
+ --text-muted: #555;
21
+ --accent: #DA7756;
22
+ --accent-glow: rgba(218, 119, 86, 0.15);
23
+ --green: #4ade80;
24
+ --red: #f87171;
25
+ --yellow: #fbbf24;
26
+ --radius: 12px;
27
+ --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
28
+ --font-serif: 'Playfair Display', Georgia, serif;
29
+ }
30
+
31
+ body {
32
+ font-family: var(--font);
33
+ background: var(--bg);
34
+ color: var(--text);
35
+ min-height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: center;
39
+ -webkit-font-smoothing: antialiased;
40
+ }
41
+
42
+ /* Loading screen */
43
+ #loading {
44
+ position: fixed; inset: 0;
45
+ background: var(--bg);
46
+ display: flex; flex-direction: column;
47
+ align-items: center; justify-content: center;
48
+ z-index: 1000;
49
+ transition: opacity 0.4s ease;
50
+ }
51
+ #loading.hidden { opacity: 0; pointer-events: none; }
52
+ #loading .spinner {
53
+ width: 32px; height: 32px;
54
+ border: 3px solid var(--border);
55
+ border-top-color: var(--accent);
56
+ border-radius: 50%;
57
+ animation: spin 0.8s linear infinite;
58
+ }
59
+ #loading p { margin-top: 16px; color: var(--text-dim); font-size: 14px; }
60
+ @keyframes spin { to { transform: rotate(360deg); } }
61
+
62
+ /* Layout */
63
+ .container {
64
+ max-width: 720px;
65
+ width: 100%;
66
+ padding: 48px 24px 80px;
67
+ }
68
+
69
+ /* Logo */
70
+ .logo {
71
+ font-size: 28px;
72
+ letter-spacing: -0.5px;
73
+ user-select: none;
74
+ }
75
+ .logo-claw { font-weight: 800; color: var(--text); }
76
+ .logo-magic { font-family: var(--font-serif); font-style: italic; color: var(--accent); font-weight: 400; }
77
+
78
+ /* Steps */
79
+ .step {
80
+ display: none;
81
+ animation: fadeIn 0.4s ease;
82
+ }
83
+ .step.active { display: block; }
84
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
85
+
86
+ /* Typography */
87
+ h1 { font-size: 32px; font-weight: 700; line-height: 1.2; margin-bottom: 8px; }
88
+ h2 { font-size: 20px; font-weight: 600; margin-bottom: 16px; color: var(--text); }
89
+ .subtitle { color: var(--text-dim); font-size: 15px; line-height: 1.5; margin-bottom: 32px; }
90
+ .step-indicator {
91
+ font-size: 12px; text-transform: uppercase; letter-spacing: 1.5px;
92
+ color: var(--text-muted); margin-bottom: 24px;
93
+ }
94
+
95
+ /* Buttons */
96
+ .btn {
97
+ display: inline-flex; align-items: center; gap: 8px;
98
+ padding: 12px 28px;
99
+ border-radius: 8px; border: none;
100
+ font-size: 15px; font-weight: 600; font-family: var(--font);
101
+ cursor: pointer; transition: all 0.2s ease;
102
+ text-decoration: none;
103
+ }
104
+ .btn-primary {
105
+ background: var(--accent); color: #fff;
106
+ }
107
+ .btn-primary:hover { background: #c4684a; transform: translateY(-1px); }
108
+ .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
109
+ .btn-secondary {
110
+ background: var(--bg-card); color: var(--text-dim); border: 1px solid var(--border);
111
+ }
112
+ .btn-secondary:hover { border-color: var(--text-dim); color: var(--text); }
113
+ .btn-big {
114
+ padding: 16px 40px; font-size: 16px; width: 100%; justify-content: center;
115
+ }
116
+
117
+ /* Tags / badges */
118
+ .badge {
119
+ display: inline-block; padding: 3px 10px; border-radius: 20px;
120
+ font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
121
+ }
122
+ .badge-tier { background: var(--accent-glow); color: var(--accent); }
123
+ .badge-score { background: rgba(74,222,128,0.1); color: var(--green); }
124
+
125
+ /* Info row */
126
+ .info-row {
127
+ display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 32px;
128
+ }
129
+
130
+ /* Flavor grid */
131
+ .flavor-grid {
132
+ display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 24px;
133
+ }
134
+ .flavor-card {
135
+ background: var(--bg-card); border: 2px solid var(--border);
136
+ border-radius: var(--radius); padding: 20px;
137
+ cursor: pointer; transition: all 0.25s ease;
138
+ position: relative;
139
+ }
140
+ .flavor-card:hover { border-color: #444; background: var(--bg-card-hover); }
141
+ .flavor-card.selected { border-color: var(--accent); background: var(--accent-glow); }
142
+ .flavor-card .icon { font-size: 28px; margin-bottom: 10px; display: block; }
143
+ .flavor-card .name { font-size: 16px; font-weight: 700; margin-bottom: 4px; }
144
+ .flavor-card .desc { font-size: 13px; color: var(--text-dim); line-height: 1.4; margin-bottom: 8px; }
145
+ .flavor-card .skill-count { font-size: 12px; color: var(--text-muted); }
146
+ .flavor-card .features-list {
147
+ max-height: 0; overflow: hidden; transition: max-height 0.3s ease;
148
+ margin-top: 0;
149
+ }
150
+ .flavor-card.selected .features-list { max-height: 200px; margin-top: 12px; }
151
+ .features-list ul { list-style: none; padding: 0; }
152
+ .features-list li {
153
+ font-size: 12px; color: var(--text-dim); padding: 3px 0;
154
+ padding-left: 16px; position: relative;
155
+ }
156
+ .features-list li::before { content: '✓'; position: absolute; left: 0; color: var(--green); font-size: 11px; }
157
+ /* Full-width last card when odd count */
158
+ .flavor-grid .flavor-card:last-child:nth-child(odd) { grid-column: 1 / -1; }
159
+
160
+ /* Provider cards */
161
+ .provider-card {
162
+ background: var(--bg-card); border: 1px solid var(--border);
163
+ border-radius: var(--radius); padding: 20px; margin-bottom: 12px;
164
+ transition: border-color 0.2s;
165
+ }
166
+ .provider-card.recommended { border-color: #3a3a3a; }
167
+ .provider-header {
168
+ display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;
169
+ }
170
+ .provider-name { font-size: 15px; font-weight: 600; }
171
+ .provider-tag {
172
+ font-size: 10px; padding: 2px 8px; border-radius: 10px;
173
+ background: var(--accent-glow); color: var(--accent);
174
+ }
175
+ .provider-status {
176
+ display: flex; align-items: center; gap: 6px;
177
+ font-size: 12px; margin-top: 10px;
178
+ }
179
+ .status-dot {
180
+ width: 8px; height: 8px; border-radius: 50%;
181
+ }
182
+ .status-dot.none { background: var(--text-muted); }
183
+ .status-dot.valid { background: var(--green); }
184
+ .status-dot.invalid { background: var(--red); }
185
+ .status-dot.testing { background: var(--yellow); animation: pulse 1s infinite; }
186
+ @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
187
+
188
+ .key-input-row { display: flex; gap: 8px; align-items: stretch; }
189
+ .key-input-wrap {
190
+ flex: 1; position: relative;
191
+ }
192
+ .key-input-wrap input {
193
+ width: 100%; padding: 10px 38px 10px 12px;
194
+ background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
195
+ color: var(--text); font-family: monospace; font-size: 13px;
196
+ outline: none; transition: border-color 0.2s;
197
+ }
198
+ .key-input-wrap input:focus { border-color: var(--accent); }
199
+ .key-input-wrap input::placeholder { color: var(--text-muted); }
200
+ .eye-toggle {
201
+ position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
202
+ background: none; border: none; color: var(--text-dim); cursor: pointer;
203
+ font-size: 16px; padding: 2px;
204
+ }
205
+ .btn-test {
206
+ padding: 8px 16px; font-size: 12px; font-weight: 600;
207
+ background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
208
+ color: var(--text-dim); cursor: pointer; white-space: nowrap;
209
+ font-family: var(--font); transition: all 0.2s;
210
+ }
211
+ .btn-test:hover { border-color: var(--text-dim); color: var(--text); }
212
+ .btn-test:disabled { opacity: 0.4; cursor: not-allowed; }
213
+ .btn-skip {
214
+ background: none; border: none; color: var(--text-muted); cursor: pointer;
215
+ font-size: 12px; font-family: var(--font); padding: 4px 0; text-align: right;
216
+ display: block; margin-left: auto;
217
+ }
218
+ .btn-skip:hover { color: var(--text-dim); }
219
+
220
+ .routing-preview {
221
+ margin-top: 20px; padding: 14px 16px;
222
+ background: var(--bg); border-radius: 8px; border: 1px solid var(--border);
223
+ font-size: 13px; color: var(--text-dim);
224
+ }
225
+ .routing-preview span { color: var(--text); font-weight: 600; }
226
+
227
+ /* Review section */
228
+ .review-section {
229
+ background: var(--bg-card); border: 1px solid var(--border);
230
+ border-radius: var(--radius); padding: 20px; margin-bottom: 16px;
231
+ }
232
+ .review-row {
233
+ display: flex; justify-content: space-between; align-items: center;
234
+ padding: 8px 0; border-bottom: 1px solid #1e1e1e;
235
+ }
236
+ .review-row:last-child { border-bottom: none; }
237
+ .review-label { color: var(--text-dim); font-size: 13px; }
238
+ .review-value { font-size: 13px; font-weight: 600; }
239
+
240
+ /* Progress list */
241
+ .progress-list { margin: 24px 0; }
242
+ .progress-item {
243
+ display: flex; align-items: center; gap: 10px;
244
+ padding: 8px 0; font-size: 14px; color: var(--text-muted);
245
+ transition: color 0.3s;
246
+ }
247
+ .progress-item.done { color: var(--text); }
248
+ .progress-item .check { color: var(--green); font-size: 16px; opacity: 0; transition: opacity 0.3s; }
249
+ .progress-item.done .check { opacity: 1; }
250
+ .progress-item .pending { color: var(--text-muted); font-size: 14px; }
251
+
252
+ /* Success */
253
+ .success-check {
254
+ width: 80px; height: 80px; margin: 0 auto 24px;
255
+ border-radius: 50%; background: rgba(74,222,128,0.1);
256
+ display: flex; align-items: center; justify-content: center;
257
+ font-size: 40px;
258
+ animation: scaleIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
259
+ }
260
+ @keyframes scaleIn { from { transform: scale(0); } to { transform: scale(1); } }
261
+
262
+ .score-gauge {
263
+ display: flex; align-items: center; justify-content: center; gap: 16px;
264
+ margin: 24px 0; font-size: 18px;
265
+ }
266
+ .score-gauge .old { color: var(--text-muted); text-decoration: line-through; }
267
+ .score-gauge .arrow { color: var(--text-dim); }
268
+ .score-gauge .new { color: var(--green); font-weight: 700; font-size: 28px; }
269
+
270
+ .next-steps {
271
+ margin: 32px 0; text-align: left;
272
+ }
273
+ .next-step {
274
+ display: flex; gap: 12px; padding: 14px 0;
275
+ border-bottom: 1px solid #1a1a1a;
276
+ }
277
+ .next-step:last-child { border-bottom: none; }
278
+ .step-num {
279
+ width: 28px; height: 28px; border-radius: 50%;
280
+ background: var(--bg-card); border: 1px solid var(--border);
281
+ display: flex; align-items: center; justify-content: center;
282
+ font-size: 12px; font-weight: 700; color: var(--text-dim);
283
+ flex-shrink: 0;
284
+ }
285
+ .step-content { flex: 1; }
286
+ .step-content p { font-size: 14px; margin-bottom: 6px; }
287
+ .step-content code {
288
+ display: inline-flex; align-items: center; gap: 8px;
289
+ padding: 6px 12px; background: var(--bg-card); border: 1px solid var(--border);
290
+ border-radius: 6px; font-family: monospace; font-size: 13px; color: var(--accent);
291
+ }
292
+ .copy-btn {
293
+ background: none; border: none; color: var(--text-dim); cursor: pointer;
294
+ font-size: 14px; padding: 0; transition: color 0.2s;
295
+ }
296
+ .copy-btn:hover { color: var(--text); }
297
+
298
+ /* Nav buttons row */
299
+ .nav-row {
300
+ display: flex; justify-content: space-between; align-items: center;
301
+ margin-top: 32px; gap: 12px;
302
+ }
303
+ .nav-row .spacer { flex: 1; }
304
+
305
+ /* Summary stats row */
306
+ .stats-row {
307
+ display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
308
+ margin: 20px 0;
309
+ }
310
+ .stat-card {
311
+ background: var(--bg-card); border: 1px solid var(--border);
312
+ border-radius: 8px; padding: 14px; text-align: center;
313
+ }
314
+ .stat-card .val { font-size: 22px; font-weight: 700; color: var(--accent); }
315
+ .stat-card .label { font-size: 11px; color: var(--text-dim); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
316
+
317
+ a { color: var(--accent); text-decoration: none; }
318
+ a:hover { text-decoration: underline; }
319
+
320
+ .text-center { text-align: center; }
321
+ .mt-8 { margin-top: 8px; }
322
+ .mt-16 { margin-top: 16px; }
323
+ .mt-24 { margin-top: 24px; }
324
+ .mb-8 { margin-bottom: 8px; }
325
+ </style>
326
+ </head>
327
+ <body>
328
+
329
+ <!-- Loading screen -->
330
+ <div id="loading">
331
+ <div class="spinner"></div>
332
+ <p>Connecting to ClawMagic...</p>
333
+ </div>
334
+
335
+ <div class="container">
336
+
337
+ <!-- ═══════════ STEP 1: Welcome ═══════════ -->
338
+ <div class="step active" id="step-1">
339
+ <div class="step-indicator">Step 1 of 5</div>
340
+ <div class="logo" style="margin-bottom: 6px;">
341
+ <span class="logo-claw">Claw</span><span class="logo-magic">Magic</span> 🪄
342
+ </div>
343
+ <h1 style="margin-top: 24px;">Configured Automagically</h1>
344
+ <p class="subtitle">Let's turn your OpenClaw install into a production-ready AI agent in under two minutes.</p>
345
+ <div class="info-row">
346
+ <span class="badge badge-score" id="w-version">OpenClaw —</span>
347
+ <span class="badge badge-score" id="w-score">Security: —</span>
348
+ <span class="badge badge-tier" id="w-tier">—</span>
349
+ </div>
350
+ <p style="font-size: 13px; color: var(--text-dim); margin-bottom: 32px;" id="w-email"></p>
351
+ <button class="btn btn-primary" onclick="goTo(2)">Let's get started →</button>
352
+ </div>
353
+
354
+ <!-- ═══════════ STEP 2: Flavor ═══════════ -->
355
+ <div class="step" id="step-2">
356
+ <div class="step-indicator">Step 2 of 5</div>
357
+ <h2>Choose Your Flavor</h2>
358
+ <p class="subtitle">Each flavor pre-configures skills, model routing, and workspace files for your use case.</p>
359
+ <div class="flavor-grid" id="flavor-grid"></div>
360
+ <div class="nav-row">
361
+ <button class="btn btn-secondary" onclick="goTo(1)">← Back</button>
362
+ <button class="btn btn-primary" id="btn-flavor-next" disabled onclick="goTo(3)">Continue →</button>
363
+ </div>
364
+ </div>
365
+
366
+ <!-- ═══════════ STEP 3: API Keys ═══════════ -->
367
+ <div class="step" id="step-3">
368
+ <div class="step-indicator">Step 3 of 5</div>
369
+ <h2>API Keys</h2>
370
+ <p class="subtitle">Connect your AI providers. At least one is needed for your agent to work.</p>
371
+ <div id="provider-cards"></div>
372
+ <div class="routing-preview" id="routing-preview">
373
+ <strong>Model routing:</strong> <span id="routing-text">Add a provider to configure routing</span>
374
+ </div>
375
+ <div class="nav-row">
376
+ <button class="btn btn-secondary" onclick="goTo(2)">← Back</button>
377
+ <button class="btn btn-primary" onclick="goTo(4)">Continue →</button>
378
+ </div>
379
+ </div>
380
+
381
+ <!-- ═══════════ STEP 4: Review & Configure ═══════════ -->
382
+ <div class="step" id="step-4">
383
+ <div class="step-indicator">Step 4 of 5</div>
384
+ <h2>Review & Configure</h2>
385
+ <p class="subtitle">Here's what we'll set up. Hit configure when ready.</p>
386
+ <div class="review-section" id="review-summary"></div>
387
+ <button class="btn btn-primary btn-big mt-24" id="btn-configure" onclick="runConfigure()">
388
+ 🪄 Configure OpenClaw
389
+ </button>
390
+ <div class="progress-list" id="progress-list" style="display:none;"></div>
391
+ <div class="nav-row" id="review-nav">
392
+ <button class="btn btn-secondary" onclick="goTo(3)">← Back</button>
393
+ <div></div>
394
+ </div>
395
+ </div>
396
+
397
+ <!-- ═══════════ STEP 5: Success ═══════════ -->
398
+ <div class="step" id="step-5">
399
+ <div class="text-center">
400
+ <div class="success-check">✓</div>
401
+ <h1>You're all set!</h1>
402
+ <p class="subtitle">OpenClaw is production-ready.</p>
403
+ </div>
404
+ <div class="score-gauge">
405
+ <span class="old" id="s-before">0</span>
406
+ <span class="arrow">→</span>
407
+ <span class="new" id="s-after">0</span>
408
+ <span style="font-size:13px; color: var(--text-dim);">security score</span>
409
+ </div>
410
+ <div class="stats-row" id="success-stats"></div>
411
+ <div class="next-steps">
412
+ <h2>What's next</h2>
413
+ <div class="next-step">
414
+ <div class="step-num">1</div>
415
+ <div class="step-content">
416
+ <p>Restart your gateway</p>
417
+ <code>openclaw gateway restart <button class="copy-btn" onclick="copyText('openclaw gateway restart')">📋</button></code>
418
+ </div>
419
+ </div>
420
+ <div class="next-step">
421
+ <div class="step-num">2</div>
422
+ <div class="step-content"><p>Send a test message to your agent</p></div>
423
+ </div>
424
+ <div class="next-step">
425
+ <div class="step-num">3</div>
426
+ <div class="step-content"><p>Join our Discord: <a href="https://discord.gg/clawd" target="_blank">discord.gg/clawd</a></p></div>
427
+ </div>
428
+ </div>
429
+ <div class="text-center mt-24">
430
+ <button class="btn btn-primary btn-big" onclick="shutdown()">Close</button>
431
+ </div>
432
+ </div>
433
+
434
+ </div><!-- container -->
435
+
436
+ <script>
437
+ // ═══════════ State ═══════════
438
+ const state = {
439
+ flavor: null,
440
+ flavorData: [],
441
+ providers: {
442
+ anthropic: { key: '', status: 'none' },
443
+ openai: { key: '', status: 'none' },
444
+ google: { key: '', status: 'none' },
445
+ },
446
+ report: null,
447
+ ocInfo: null,
448
+ license: null,
449
+ };
450
+
451
+ const API = '';
452
+
453
+ // ═══════════ Init ═══════════
454
+ async function init() {
455
+ let retries = 0;
456
+ while (retries < 20) {
457
+ try {
458
+ const res = await fetch(API + '/api/status');
459
+ if (res.ok) {
460
+ const data = await res.json();
461
+ state.ocInfo = data.openclaw;
462
+ state.license = data.license;
463
+ break;
464
+ }
465
+ } catch {}
466
+ retries++;
467
+ await sleep(500);
468
+ }
469
+
470
+ if (!state.ocInfo) {
471
+ document.getElementById('loading').innerHTML = '<p style="color:#f87171">Could not connect to ClawMagic server.</p>';
472
+ return;
473
+ }
474
+
475
+ // Populate welcome
476
+ document.getElementById('w-version').textContent = 'OpenClaw ' + (state.ocInfo.version || 'unknown');
477
+ document.getElementById('w-score').textContent = 'Security: ' + state.ocInfo.securityScore + '/100';
478
+ if (state.license) {
479
+ document.getElementById('w-tier').textContent = state.license.tier;
480
+ if (state.license.email) {
481
+ document.getElementById('w-email').textContent = 'Licensed to ' + state.license.email;
482
+ }
483
+ }
484
+
485
+ // Load flavors
486
+ try {
487
+ const fres = await fetch(API + '/api/flavors');
488
+ const fdata = await fres.json();
489
+ state.flavorData = fdata.flavors;
490
+ renderFlavors();
491
+ } catch {}
492
+
493
+ // Render providers
494
+ renderProviders();
495
+
496
+ // Hide loading
497
+ document.getElementById('loading').classList.add('hidden');
498
+ setTimeout(() => document.getElementById('loading').style.display = 'none', 400);
499
+ }
500
+
501
+ // ═══════════ Navigation ═══════════
502
+ function goTo(step) {
503
+ document.querySelectorAll('.step').forEach(s => s.classList.remove('active'));
504
+ document.getElementById('step-' + step).classList.add('active');
505
+ window.scrollTo({ top: 0, behavior: 'smooth' });
506
+
507
+ if (step === 4) renderReview();
508
+ }
509
+
510
+ // ═══════════ Flavors ═══════════
511
+ function renderFlavors() {
512
+ const grid = document.getElementById('flavor-grid');
513
+ grid.innerHTML = '';
514
+ state.flavorData.forEach(f => {
515
+ const card = document.createElement('div');
516
+ card.className = 'flavor-card' + (state.flavor === f.id ? ' selected' : '');
517
+ card.onclick = () => selectFlavor(f.id);
518
+ card.innerHTML = `
519
+ <span class="icon">${f.icon}</span>
520
+ <div class="name">${f.name}</div>
521
+ <div class="desc">${f.description}</div>
522
+ <div class="skill-count">${f.skillCount} skills</div>
523
+ <div class="features-list">
524
+ <ul>${f.features.map(ft => '<li>' + ft + '</li>').join('')}</ul>
525
+ </div>
526
+ `;
527
+ grid.appendChild(card);
528
+ });
529
+ }
530
+
531
+ function selectFlavor(id) {
532
+ state.flavor = id;
533
+ renderFlavors();
534
+ document.getElementById('btn-flavor-next').disabled = false;
535
+ }
536
+
537
+ // ═══════════ Providers ═══════════
538
+ const providerMeta = [
539
+ { id: 'anthropic', name: 'Anthropic', placeholder: 'sk-ant-...', recommended: true },
540
+ { id: 'openai', name: 'OpenAI', placeholder: 'sk-...', recommended: false },
541
+ { id: 'google', name: 'Google Gemini', placeholder: 'AI...', recommended: false },
542
+ ];
543
+
544
+ function renderProviders() {
545
+ const container = document.getElementById('provider-cards');
546
+ container.innerHTML = '';
547
+ providerMeta.forEach(p => {
548
+ const s = state.providers[p.id];
549
+ const statusLabel = s.status === 'valid' ? 'Valid' : s.status === 'invalid' ? 'Invalid' : s.status === 'testing' ? 'Testing...' : 'Not configured';
550
+ const card = document.createElement('div');
551
+ card.className = 'provider-card' + (p.recommended ? ' recommended' : '');
552
+ card.innerHTML = `
553
+ <div class="provider-header">
554
+ <span class="provider-name">${p.name}</span>
555
+ ${p.recommended ? '<span class="provider-tag">Recommended</span>' : ''}
556
+ </div>
557
+ <div class="key-input-row">
558
+ <div class="key-input-wrap">
559
+ <input type="password" placeholder="${p.placeholder}" id="key-${p.id}" value="${s.key}"
560
+ oninput="onKeyInput('${p.id}', this.value)">
561
+ <button class="eye-toggle" onclick="toggleEye('${p.id}')" title="Show/hide">👁</button>
562
+ </div>
563
+ <button class="btn-test" id="test-${p.id}" onclick="testKey('${p.id}')" ${!s.key ? 'disabled' : ''}>Test</button>
564
+ </div>
565
+ <div class="provider-status">
566
+ <div class="status-dot ${s.status}"></div>
567
+ <span>${statusLabel}</span>
568
+ </div>
569
+ <button class="btn-skip" onclick="skipProvider('${p.id}')">Skip for now</button>
570
+ `;
571
+ container.appendChild(card);
572
+ });
573
+ updateRouting();
574
+ }
575
+
576
+ function onKeyInput(id, val) {
577
+ state.providers[id].key = val.trim();
578
+ state.providers[id].status = val.trim() ? 'none' : 'none';
579
+ const btn = document.getElementById('test-' + id);
580
+ if (btn) btn.disabled = !val.trim();
581
+ }
582
+
583
+ function toggleEye(id) {
584
+ const input = document.getElementById('key-' + id);
585
+ input.type = input.type === 'password' ? 'text' : 'password';
586
+ }
587
+
588
+ async function testKey(id) {
589
+ const key = state.providers[id].key;
590
+ if (!key) return;
591
+ state.providers[id].status = 'testing';
592
+ renderProviders();
593
+
594
+ try {
595
+ const res = await fetch(API + '/api/validate-key', {
596
+ method: 'POST',
597
+ headers: { 'Content-Type': 'application/json' },
598
+ body: JSON.stringify({ provider: id, key }),
599
+ });
600
+ const data = await res.json();
601
+ state.providers[id].status = data.valid ? 'valid' : 'invalid';
602
+ } catch {
603
+ state.providers[id].status = 'invalid';
604
+ }
605
+ renderProviders();
606
+ }
607
+
608
+ function skipProvider(id) {
609
+ state.providers[id].key = '';
610
+ state.providers[id].status = 'none';
611
+ renderProviders();
612
+ }
613
+
614
+ function updateRouting() {
615
+ const order = ['anthropic', 'openai', 'google'];
616
+ const names = { anthropic: 'Anthropic', openai: 'OpenAI', google: 'Google' };
617
+ const active = order.filter(id => state.providers[id].key);
618
+ const el = document.getElementById('routing-text');
619
+ if (active.length === 0) {
620
+ el.innerHTML = 'Add a provider to configure routing';
621
+ } else {
622
+ el.innerHTML = active.map((id, i) => {
623
+ const role = i === 0 ? 'Primary' : 'Fallback';
624
+ return `<span>${role}: ${names[id]}</span>`;
625
+ }).join(' → ');
626
+ }
627
+ }
628
+
629
+ // ═══════════ Review ═══════════
630
+ function renderReview() {
631
+ const flavorInfo = state.flavorData.find(f => f.id === state.flavor) || {};
632
+ const providerNames = { anthropic: 'Anthropic', openai: 'OpenAI', google: 'Google' };
633
+ const providerList = Object.entries(state.providers)
634
+ .map(([id, p]) => {
635
+ if (p.key && (p.status === 'valid' || p.status === 'none')) return providerNames[id] + ' ✅';
636
+ if (p.key && p.status === 'invalid') return providerNames[id] + ' ❌';
637
+ return providerNames[id] + ' ⏭️';
638
+ }).join('<br>');
639
+
640
+ document.getElementById('review-summary').innerHTML = `
641
+ <div class="review-row"><span class="review-label">Flavor</span><span class="review-value">${flavorInfo.icon || ''} ${flavorInfo.name || state.flavor}</span></div>
642
+ <div class="review-row"><span class="review-label">Skills</span><span class="review-value">${flavorInfo.skillCount || 0} active</span></div>
643
+ <div class="review-row"><span class="review-label">Providers</span><span class="review-value" style="text-align:right">${providerList}</span></div>
644
+ <div class="review-row"><span class="review-label">Security</span><span class="review-value">File permissions, auth hardening</span></div>
645
+ <div class="review-row"><span class="review-label">Files</span><span class="review-value">openclaw.json, workspace files, memory dirs</span></div>
646
+ `;
647
+ }
648
+
649
+ // ═══════════ Configure ═══════════
650
+ const configSteps = [
651
+ 'Backing up existing configuration',
652
+ 'Writing openclaw.json',
653
+ 'Security hardening',
654
+ 'Configuring model routing',
655
+ 'Initializing memory system',
656
+ 'Activating skills',
657
+ 'Installing workspace files',
658
+ ];
659
+
660
+ async function runConfigure() {
661
+ const btn = document.getElementById('btn-configure');
662
+ btn.disabled = true;
663
+ btn.textContent = 'Configuring...';
664
+ document.getElementById('review-nav').style.display = 'none';
665
+
666
+ // Show progress
667
+ const pl = document.getElementById('progress-list');
668
+ pl.style.display = 'block';
669
+ pl.innerHTML = configSteps.map(s => `
670
+ <div class="progress-item" data-step="${s}">
671
+ <span class="check">✅</span>
672
+ <span class="pending">○</span>
673
+ <span>${s}</span>
674
+ </div>
675
+ `).join('');
676
+
677
+ // Animate progress items
678
+ const items = pl.querySelectorAll('.progress-item');
679
+ for (let i = 0; i < items.length - 1; i++) {
680
+ await sleep(350 + Math.random() * 200);
681
+ items[i].classList.add('done');
682
+ items[i].querySelector('.pending').style.display = 'none';
683
+ }
684
+
685
+ // Fire the real request
686
+ const apiKeys = Object.entries(state.providers)
687
+ .filter(([, p]) => p.key)
688
+ .map(([id, p]) => ({ provider: id, key: p.key }));
689
+
690
+ try {
691
+ const res = await fetch(API + '/api/configure', {
692
+ method: 'POST',
693
+ headers: { 'Content-Type': 'application/json' },
694
+ body: JSON.stringify({ flavor: state.flavor, apiKeys }),
695
+ });
696
+ const data = await res.json();
697
+ state.report = data.report || {};
698
+
699
+ // Complete last step
700
+ items[items.length - 1].classList.add('done');
701
+ items[items.length - 1].querySelector('.pending').style.display = 'none';
702
+
703
+ await sleep(600);
704
+
705
+ // Populate success page
706
+ document.getElementById('s-before').textContent = (state.report.beforeScore || 0) + '/100';
707
+ document.getElementById('s-after').textContent = (state.report.afterScore || 0) + '/100';
708
+
709
+ const validProviders = apiKeys.length;
710
+ const skillsActivated = state.report.skillsActivated || 0;
711
+ const filesCreated = (state.report.filesCreated || []).length;
712
+
713
+ document.getElementById('success-stats').innerHTML = `
714
+ <div class="stat-card"><div class="val">${validProviders}</div><div class="label">Providers</div></div>
715
+ <div class="stat-card"><div class="val">${skillsActivated}</div><div class="label">Skills</div></div>
716
+ <div class="stat-card"><div class="val">${filesCreated}</div><div class="label">Files Created</div></div>
717
+ `;
718
+
719
+ goTo(5);
720
+ } catch (err) {
721
+ btn.disabled = false;
722
+ btn.textContent = '🪄 Configure OpenClaw';
723
+ document.getElementById('review-nav').style.display = 'flex';
724
+ pl.innerHTML = '<p style="color:var(--red);">Configuration failed: ' + (err.message || 'Unknown error') + '</p>';
725
+ }
726
+ }
727
+
728
+ // ═══════════ Utils ═══════════
729
+ function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
730
+
731
+ function copyText(text) {
732
+ navigator.clipboard.writeText(text).then(() => {
733
+ // Brief feedback — no alert needed
734
+ }).catch(() => {});
735
+ }
736
+
737
+ async function shutdown() {
738
+ try { await fetch(API + '/api/shutdown', { method: 'POST' }); } catch {}
739
+ document.body.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100vh;color:#888;font-family:sans-serif;"><p>ClawMagic closed. You can close this tab.</p></div>';
740
+ }
741
+
742
+ // ═══════════ Boot ═══════════
743
+ init();
744
+ </script>
745
+ </body>
746
+ </html>