@chrysb/alphaclaw 0.1.14 → 0.1.16

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.
@@ -0,0 +1,57 @@
1
+ import { h } from "https://esm.sh/preact";
2
+ import htm from "https://esm.sh/htm";
3
+
4
+ const html = htm.bind(h);
5
+
6
+ export const WelcomeHeader = ({
7
+ groups,
8
+ step,
9
+ isSetupStep,
10
+ stepNumber,
11
+ activeStepLabel,
12
+ vals,
13
+ hasAi,
14
+ }) => {
15
+ const progressSteps = [...groups, { id: "setup", title: "Initializing" }];
16
+
17
+ return html`
18
+ <div class="text-center mb-1">
19
+ <img
20
+ src="./img/logo.svg"
21
+ alt="alphaclaw"
22
+ class="mx-auto mb-3"
23
+ width="32"
24
+ height="33"
25
+ />
26
+ <h1 class="text-2xl font-semibold mb-2">Setup</h1>
27
+ <p style="color: var(--text-muted)" class="text-sm">
28
+ Let's get your agent running
29
+ </p>
30
+ <p class="text-xs my-2" style="color: var(--text-dim)">
31
+ Step ${stepNumber} of ${progressSteps.length} - ${activeStepLabel}
32
+ </p>
33
+ </div>
34
+
35
+ <div class="flex items-center gap-2">
36
+ ${progressSteps.map((group, idx) => {
37
+ const isFinalStep = idx === progressSteps.length - 1;
38
+ const isActive = idx === step;
39
+ const isComplete = isFinalStep
40
+ ? isSetupStep
41
+ : idx < step && group.validate(vals, { hasAi });
42
+ const bg = isActive
43
+ ? "rgba(99, 235, 255, 0.9)"
44
+ : isComplete
45
+ ? "rgba(99, 235, 255, 0.55)"
46
+ : "rgba(82, 94, 122, 0.45)";
47
+ return html`
48
+ <div
49
+ class="h-1 flex-1 rounded-full transition-colors"
50
+ style=${{ background: bg }}
51
+ title=${group.title}
52
+ ></div>
53
+ `;
54
+ })}
55
+ </div>
56
+ `;
57
+ };
@@ -0,0 +1,45 @@
1
+ import { h } from "https://esm.sh/preact";
2
+ import htm from "https://esm.sh/htm";
3
+
4
+ const html = htm.bind(h);
5
+
6
+ export const WelcomeSetupStep = ({ error, loading, onRetry }) => html`
7
+ <div class="py-10 flex flex-col items-center text-center gap-4">
8
+ <svg
9
+ class="animate-spin h-8 w-8 text-white"
10
+ viewBox="0 0 24 24"
11
+ fill="none"
12
+ >
13
+ <circle
14
+ class="opacity-25"
15
+ cx="12"
16
+ cy="12"
17
+ r="10"
18
+ stroke="currentColor"
19
+ stroke-width="4"
20
+ />
21
+ <path
22
+ class="opacity-75"
23
+ fill="currentColor"
24
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
25
+ />
26
+ </svg>
27
+ <h3 class="text-lg font-semibold text-white">Initializing OpenClaw...</h3>
28
+ <p class="text-sm text-gray-500">This could take 10-15 seconds</p>
29
+ </div>
30
+
31
+ ${error
32
+ ? html`<div class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm">
33
+ ${error}
34
+ </div>
35
+ <button
36
+ onclick=${onRetry}
37
+ disabled=${loading}
38
+ class="w-full text-sm font-medium px-4 py-3 rounded-xl transition-all ${loading
39
+ ? "bg-gray-800 text-gray-500 cursor-not-allowed"
40
+ : "bg-white text-black hover:opacity-85"}"
41
+ >
42
+ ${loading ? "Retrying..." : "Retry"}
43
+ </button>`
44
+ : null}
45
+ `;
@@ -46,7 +46,7 @@ export function ScopePicker({ scopes, onToggle, apiStatus, loading }) {
46
46
  apiIndicator = html`<span class="text-gray-500 text-xs flex items-center gap-1"><span class="inline-block w-3 h-3 border-2 border-gray-500 border-t-transparent rounded-full animate-spin"></span></span>`;
47
47
  } else if (api) {
48
48
  if (api.status === 'ok') {
49
- apiIndicator = html`<a href=${api.enableUrl || getApiEnableUrl(s.key)} target="_blank" class="text-green-500 hover:text-green-300 text-xs">✓ API</a>`;
49
+ apiIndicator = html`<a href=${api.enableUrl || getApiEnableUrl(s.key)} target="_blank" class="text-green-500 hover:text-green-300 text-xs px-1.5 py-0.5 rounded bg-green-500/10">API ✓</a>`;
50
50
  } else if (api.status === 'not_enabled') {
51
51
  apiIndicator = html`<a href=${api.enableUrl} target="_blank" class="text-red-400 hover:text-red-300 text-xs underline">Enable API</a>`;
52
52
  } else if (api.status === 'error') {
@@ -0,0 +1,45 @@
1
+ import { h } from "https://esm.sh/preact";
2
+ import { useState } from "https://esm.sh/preact/hooks";
3
+ import htm from "https://esm.sh/htm";
4
+ const html = htm.bind(h);
5
+
6
+ /**
7
+ * Reusable input with show/hide toggle for secret values.
8
+ *
9
+ * Props:
10
+ * value, onInput, placeholder, inputClass, disabled
11
+ * isSecret – treat as password field (default true)
12
+ */
13
+ export const SecretInput = ({
14
+ value = "",
15
+ onInput,
16
+ placeholder = "",
17
+ inputClass = "",
18
+ disabled = false,
19
+ isSecret = true,
20
+ }) => {
21
+ const [visible, setVisible] = useState(false);
22
+ const showToggle = isSecret;
23
+
24
+ return html`
25
+ <div class="flex-1 min-w-0 flex items-center gap-1">
26
+ <input
27
+ type=${isSecret && !visible ? "password" : "text"}
28
+ value=${value}
29
+ placeholder=${placeholder}
30
+ onInput=${onInput}
31
+ disabled=${disabled}
32
+ class=${inputClass}
33
+ />
34
+ ${showToggle
35
+ ? html`<button
36
+ type="button"
37
+ onclick=${() => setVisible((v) => !v)}
38
+ class="text-gray-500 hover:text-gray-300 px-1 text-xs shrink-0"
39
+ >
40
+ ${visible ? "Hide" : "Show"}
41
+ </button>`
42
+ : null}
43
+ </div>
44
+ `;
45
+ };
@@ -12,113 +12,24 @@ import {
12
12
  getModelProvider,
13
13
  getFeaturedModels,
14
14
  getVisibleAiFieldKeys,
15
- kAllAiAuthFields,
16
15
  } from "../lib/model-config.js";
16
+ import { kWelcomeGroups } from "./onboarding/welcome-config.js";
17
+ import { WelcomeHeader } from "./onboarding/welcome-header.js";
18
+ import { WelcomeSetupStep } from "./onboarding/welcome-setup-step.js";
19
+ import { WelcomeFormStep } from "./onboarding/welcome-form-step.js";
17
20
  const html = htm.bind(h);
18
-
19
- const kGroups = [
20
- {
21
- id: "ai",
22
- title: "Primary Agent Model",
23
- description: "Choose your main model and authenticate its provider",
24
- fields: kAllAiAuthFields,
25
- validate: (vals, ctx = {}) => !!(vals.MODEL_KEY && ctx.hasAi),
26
- },
27
- {
28
- id: "github",
29
- title: "GitHub",
30
- description: "Backs up your agent's config and workspace",
31
- fields: [
32
- {
33
- key: "GITHUB_TOKEN",
34
- label: "Personal Access Token",
35
- hint: html`Create a classic PAT at${" "}<a
36
- href="https://github.com/settings/tokens"
37
- target="_blank"
38
- class="hover:underline" style="color: var(--accent)"
39
- >github.com/settings/tokens</a
40
- >${" "}with${" "}<code class="text-xs bg-black/30 px-1 rounded">repo</code>${" "}scope`,
41
- placeholder: "ghp_...",
42
- },
43
- {
44
- key: "GITHUB_WORKSPACE_REPO",
45
- label: "Workspace Repo",
46
- hint: "A new private repo will be created for you",
47
- placeholder: "username/my-agent",
48
- isText: true,
49
- },
50
- ],
51
- validate: (vals) => !!(vals.GITHUB_TOKEN && vals.GITHUB_WORKSPACE_REPO),
52
- },
53
- {
54
- id: "channels",
55
- title: "Channels",
56
- description: "At least one is required to talk to your agent",
57
- fields: [
58
- {
59
- key: "TELEGRAM_BOT_TOKEN",
60
- label: "Telegram Bot Token",
61
- hint: html`From${" "}<a
62
- href="https://t.me/BotFather"
63
- target="_blank"
64
- class="hover:underline" style="color: var(--accent)"
65
- >@BotFather</a
66
- >${" "}·${" "}<a
67
- href="https://docs.openclaw.ai/channels/telegram"
68
- target="_blank"
69
- class="hover:underline" style="color: var(--accent)"
70
- >full guide</a
71
- >`,
72
- placeholder: "123456789:AAH...",
73
- },
74
- {
75
- key: "DISCORD_BOT_TOKEN",
76
- label: "Discord Bot Token",
77
- hint: html`From${" "}<a
78
- href="https://discord.com/developers/applications"
79
- target="_blank"
80
- class="hover:underline" style="color: var(--accent)"
81
- >Developer Portal</a
82
- >${" "}·${" "}<a
83
- href="https://docs.openclaw.ai/channels/discord"
84
- target="_blank"
85
- class="hover:underline" style="color: var(--accent)"
86
- >full guide</a
87
- >`,
88
- placeholder: "MTQ3...",
89
- },
90
- ],
91
- validate: (vals) => !!(vals.TELEGRAM_BOT_TOKEN || vals.DISCORD_BOT_TOKEN),
92
- },
93
- {
94
- id: "tools",
95
- title: "Tools (optional)",
96
- description: "Enable extra capabilities for your agent",
97
- fields: [
98
- {
99
- key: "BRAVE_API_KEY",
100
- label: "Brave Search API Key",
101
- hint: html`From${" "}<a
102
- href="https://brave.com/search/api/"
103
- target="_blank"
104
- class="hover:underline" style="color: var(--accent)"
105
- >brave.com/search/api</a
106
- >${" "}-${" "}free tier available`,
107
- placeholder: "BSA...",
108
- },
109
- ],
110
- validate: () => true,
111
- },
112
- ];
21
+ const kOnboardingStorageKey = "openclaw_setup";
22
+ const kOnboardingStepKey = "_step";
113
23
 
114
24
  export const Welcome = ({ onComplete }) => {
115
- const [vals, setVals] = useState(() => {
25
+ const [initialSetupState] = useState(() => {
116
26
  try {
117
- return JSON.parse(localStorage.getItem("openclaw_setup") || "{}");
27
+ return JSON.parse(localStorage.getItem(kOnboardingStorageKey) || "{}");
118
28
  } catch {
119
29
  return {};
120
30
  }
121
31
  });
32
+ const [vals, setVals] = useState(() => ({ ...initialSetupState }));
122
33
  const [models, setModels] = useState([]);
123
34
  const [modelsLoading, setModelsLoading] = useState(true);
124
35
  const [modelsError, setModelsError] = useState(null);
@@ -133,10 +44,6 @@ export const Welcome = ({ onComplete }) => {
133
44
  const [error, setError] = useState(null);
134
45
  const codexPopupPollRef = useRef(null);
135
46
 
136
- useEffect(() => {
137
- localStorage.setItem("openclaw_setup", JSON.stringify(vals));
138
- }, [vals]);
139
-
140
47
  useEffect(() => {
141
48
  fetchModels()
142
49
  .then((result) => {
@@ -225,7 +132,31 @@ export const Welcome = ({ onComplete }) => {
225
132
  ? !!(codexStatus.connected || vals.OPENAI_API_KEY)
226
133
  : false;
227
134
 
228
- const allValid = kGroups.every((g) => g.validate(vals, { hasAi }));
135
+ const allValid = kWelcomeGroups.every((g) => g.validate(vals, { hasAi }));
136
+ const kFinalSetupStep = kWelcomeGroups.length;
137
+ const [step, setStep] = useState(() => {
138
+ const parsedStep = Number.parseInt(
139
+ String(initialSetupState?.[kOnboardingStepKey] || ""),
140
+ 10,
141
+ );
142
+ if (!Number.isFinite(parsedStep)) return 0;
143
+ return Math.max(0, Math.min(kFinalSetupStep, parsedStep));
144
+ });
145
+ const isSetupStep = step === kFinalSetupStep;
146
+ const activeGroup = !isSetupStep ? kWelcomeGroups[step] : null;
147
+ const currentGroupValid = activeGroup
148
+ ? activeGroup.validate(vals, { hasAi })
149
+ : false;
150
+
151
+ useEffect(() => {
152
+ localStorage.setItem(
153
+ kOnboardingStorageKey,
154
+ JSON.stringify({
155
+ ...vals,
156
+ [kOnboardingStepKey]: step,
157
+ }),
158
+ );
159
+ }, [vals, step]);
229
160
 
230
161
  const startCodexAuth = () => {
231
162
  if (codexStatus.connected) return;
@@ -287,6 +218,7 @@ export const Welcome = ({ onComplete }) => {
287
218
 
288
219
  const handleSubmit = async () => {
289
220
  if (!allValid || loading) return;
221
+ setStep(kFinalSetupStep);
290
222
  setLoading(true);
291
223
  setError(null);
292
224
 
@@ -297,7 +229,7 @@ export const Welcome = ({ onComplete }) => {
297
229
  .map(([key, value]) => ({ key, value }));
298
230
  const result = await runOnboard(vars, vals.MODEL_KEY);
299
231
  if (!result.ok) throw new Error(result.error || "Onboarding failed");
300
- localStorage.removeItem("openclaw_setup");
232
+ localStorage.removeItem(kOnboardingStorageKey);
301
233
  onComplete();
302
234
  } catch (err) {
303
235
  console.error("Onboard error:", err);
@@ -306,237 +238,76 @@ export const Welcome = ({ onComplete }) => {
306
238
  }
307
239
  };
308
240
 
309
- if (loading) {
310
- return html`
311
- <div
312
- class="fixed inset-0 flex items-center justify-center z-50"
313
- style="background: var(--bg)"
314
- >
315
- <div class="flex flex-col items-center gap-4">
316
- <svg
317
- class="animate-spin h-8 w-8 text-white"
318
- viewBox="0 0 24 24"
319
- fill="none"
320
- >
321
- <circle
322
- class="opacity-25"
323
- cx="12"
324
- cy="12"
325
- r="10"
326
- stroke="currentColor"
327
- stroke-width="4"
328
- />
329
- <path
330
- class="opacity-75"
331
- fill="currentColor"
332
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
333
- />
334
- </svg>
335
- <h2 class="text-lg font-semibold text-white">
336
- Initializing <span style="color: var(--accent)">alpha</span>claw
337
- </h2>
338
- <p class="text-sm text-gray-500">This could take 10–15 seconds</p>
339
- </div>
340
- </div>
341
- `;
342
- }
241
+ const goBack = () => {
242
+ if (isSetupStep) return;
243
+ setStep((prev) => Math.max(0, prev - 1));
244
+ };
343
245
 
344
- return html`
345
- <div class="max-w-lg w-full space-y-4">
346
- <div class="flex items-center gap-3">
347
- <div class="text-4xl">🐾</div>
348
- <div>
349
- <h1 class="text-2xl font-semibold">Welcome to <span style="color: var(--accent)">alpha</span>claw</h1>
350
- <p style="color: var(--text-muted)" class="text-sm">Let's get your agent running</p>
351
- </div>
352
- </div>
246
+ const goNext = () => {
247
+ if (!activeGroup || !currentGroupValid) return;
248
+ setStep((prev) => Math.min(kWelcomeGroups.length - 1, prev + 1));
249
+ };
353
250
 
354
- ${kGroups.map(
355
- (group) => html`
356
- <div class="bg-surface border border-border rounded-xl p-4 space-y-3">
357
- <div class="flex items-center justify-between">
358
- <div>
359
- <h2 class="text-sm font-medium text-gray-200">
360
- ${group.title}
361
- </h2>
362
- <p class="text-xs text-gray-500">${group.description}</p>
363
- </div>
364
- ${group.validate(vals, { hasAi })
365
- ? html`<span
366
- class="text-xs font-medium px-2 py-0.5 rounded-full bg-green-900/50 text-green-400"
367
- >✓</span
368
- >`
369
- : group.id !== "tools"
370
- ? html`<span
371
- class="text-xs font-medium px-2 py-0.5 rounded-full bg-yellow-900/50 text-yellow-400"
372
- >Required</span
373
- >`
374
- : null}
375
- </div>
251
+ const activeStepLabel = isSetupStep
252
+ ? "Initializing"
253
+ : activeGroup?.title || "Setup";
254
+ const stepNumber = isSetupStep ? kWelcomeGroups.length + 1 : step + 1;
376
255
 
377
- ${group.id === "ai" &&
378
- html`
379
- <div class="space-y-1">
380
- <label class="text-xs font-medium text-gray-400">Model</label>
381
- <select
382
- value=${vals.MODEL_KEY || ""}
383
- onInput=${(e) => set("MODEL_KEY", e.target.value)}
384
- class="w-full bg-black/30 border border-border rounded-lg pl-3 pr-8 py-2 text-sm text-gray-200 outline-none focus:border-gray-500"
385
- >
386
- <option value="">Select a model</option>
387
- ${modelOptions.map(
388
- (model) => html`
389
- <option value=${model.key}>
390
- ${model.label || model.key}
391
- </option>
392
- `,
393
- )}
394
- </select>
395
- <p class="text-xs text-gray-600">
396
- ${modelsLoading
397
- ? "Loading model catalog..."
398
- : modelsError
399
- ? modelsError
400
- : ""}
401
- </p>
402
- ${canToggleFullCatalog &&
403
- html`
404
- <button
405
- type="button"
406
- onclick=${() => setShowAllModels((prev) => !prev)}
407
- class="text-xs text-gray-500 hover:text-gray-300"
408
- >
409
- ${showAllModels
410
- ? "Show recommended models"
411
- : "Show full model catalog"}
412
- </button>
413
- `}
414
- </div>
415
- `}
416
- ${group.id === "ai" &&
417
- selectedProvider === "openai-codex" &&
418
- html`
419
- <div
420
- class="bg-black/20 border border-border rounded-lg p-3 space-y-2"
421
- >
422
- <div class="flex items-center justify-between">
423
- <span class="text-xs text-gray-400">Codex OAuth</span>
424
- ${codexLoading
425
- ? html`<span class="text-xs text-gray-500"
426
- >Checking...</span
427
- >`
428
- : codexStatus.connected
429
- ? html`<span class="text-xs text-green-400"
430
- >Connected</span
431
- >`
432
- : html`<span class="text-xs text-yellow-400"
433
- >Not connected</span
434
- >`}
435
- </div>
436
- <div class="flex gap-2">
437
- <button
438
- type="button"
439
- onclick=${startCodexAuth}
440
- class="text-xs font-medium px-3 py-1.5 rounded-lg ${codexStatus.connected
441
- ? "border border-border text-gray-300 hover:border-gray-500"
442
- : "bg-white text-black hover:opacity-85"}"
443
- >
444
- ${codexStatus.connected
445
- ? "Reconnect Codex"
446
- : "Connect Codex OAuth"}
447
- </button>
448
- ${codexStatus.connected &&
449
- html`
450
- <button
451
- type="button"
452
- onclick=${handleCodexDisconnect}
453
- class="text-xs font-medium px-3 py-1.5 rounded-lg border border-border text-gray-300 hover:border-gray-500"
454
- >
455
- Disconnect
456
- </button>
457
- `}
458
- </div>
459
- ${!codexStatus.connected &&
460
- codexAuthStarted &&
461
- html`
462
- <div class="space-y-1 pt-1">
463
- <p class="text-xs text-gray-500">
464
- ${codexAuthWaiting
465
- ? "Complete login in the popup, then paste the full redirect URL from the address bar (starts with "
466
- : "Paste the full redirect URL from the address bar (starts with "}
467
- <code class="text-xs bg-black/30 px-1 rounded"
468
- >http://localhost:1455/auth/callback</code
469
- >)
470
- ${codexAuthWaiting
471
- ? " to finish setup."
472
- : " to finish setup."}
473
- </p>
474
- <input
475
- type="text"
476
- value=${codexManualInput}
477
- onInput=${(e) => setCodexManualInput(e.target.value)}
478
- placeholder="http://localhost:1455/auth/callback?code=...&state=..."
479
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-xs text-gray-200 outline-none focus:border-gray-500"
480
- />
481
- <button
482
- type="button"
483
- onclick=${completeCodexAuth}
484
- disabled=${!codexManualInput.trim() || codexExchanging}
485
- class="text-xs font-medium px-3 py-1.5 rounded-lg ${!codexManualInput.trim() ||
486
- codexExchanging
487
- ? "bg-gray-700 text-gray-400 cursor-not-allowed"
488
- : "bg-white text-black hover:opacity-85"}"
489
- >
490
- ${codexExchanging
491
- ? "Completing..."
492
- : "Complete Codex OAuth"}
493
- </button>
494
- </div>
495
- `}
496
- </div>
497
- `}
498
- ${(group.id === "ai"
499
- ? group.fields.filter((field) =>
500
- visibleAiFieldKeys.has(field.key),
501
- )
502
- : group.fields
503
- ).map(
504
- (field) => html`
505
- <div class="space-y-1">
506
- <label class="text-xs font-medium text-gray-400"
507
- >${field.label}</label
508
- >
509
- <input
510
- type=${field.isText ? "text" : "password"}
511
- placeholder=${field.placeholder || ""}
512
- value=${vals[field.key] || ""}
513
- onInput=${(e) => set(field.key, e.target.value)}
514
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
515
- />
516
- <p class="text-xs text-gray-600">${field.hint}</p>
517
- </div>
518
- `,
519
- )}
520
- </div>
521
- `,
522
- )}
523
- ${error
524
- ? html`<div
525
- class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
526
- >
527
- ${error}
528
- </div>`
529
- : null}
256
+ return html`
257
+ <div class="max-w-lg w-full space-y-5">
258
+ <${WelcomeHeader}
259
+ groups=${kWelcomeGroups}
260
+ step=${step}
261
+ isSetupStep=${isSetupStep}
262
+ stepNumber=${stepNumber}
263
+ activeStepLabel=${activeStepLabel}
264
+ vals=${vals}
265
+ hasAi=${hasAi}
266
+ />
530
267
 
531
- <button
532
- onclick=${handleSubmit}
533
- disabled=${!allValid}
534
- class="w-full text-sm font-medium px-4 py-3 rounded-xl transition-all ${allValid
535
- ? "bg-white text-black hover:opacity-85"
536
- : "bg-gray-800 text-gray-500 cursor-not-allowed"}"
537
- >
538
- Complete Setup
539
- </button>
268
+ <div class="bg-surface border border-border rounded-xl p-4 space-y-3">
269
+ ${isSetupStep
270
+ ? html`<${WelcomeSetupStep}
271
+ error=${error}
272
+ loading=${loading}
273
+ onRetry=${handleSubmit}
274
+ />`
275
+ : html`
276
+ <${WelcomeFormStep}
277
+ activeGroup=${activeGroup}
278
+ vals=${vals}
279
+ hasAi=${hasAi}
280
+ setValue=${set}
281
+ modelOptions=${modelOptions}
282
+ modelsLoading=${modelsLoading}
283
+ modelsError=${modelsError}
284
+ canToggleFullCatalog=${canToggleFullCatalog}
285
+ showAllModels=${showAllModels}
286
+ setShowAllModels=${setShowAllModels}
287
+ selectedProvider=${selectedProvider}
288
+ codexLoading=${codexLoading}
289
+ codexStatus=${codexStatus}
290
+ startCodexAuth=${startCodexAuth}
291
+ handleCodexDisconnect=${handleCodexDisconnect}
292
+ codexAuthStarted=${codexAuthStarted}
293
+ codexAuthWaiting=${codexAuthWaiting}
294
+ codexManualInput=${codexManualInput}
295
+ setCodexManualInput=${setCodexManualInput}
296
+ completeCodexAuth=${completeCodexAuth}
297
+ codexExchanging=${codexExchanging}
298
+ visibleAiFieldKeys=${visibleAiFieldKeys}
299
+ error=${error}
300
+ step=${step}
301
+ totalGroups=${kWelcomeGroups.length}
302
+ currentGroupValid=${currentGroupValid}
303
+ goBack=${goBack}
304
+ goNext=${goNext}
305
+ loading=${loading}
306
+ allValid=${allValid}
307
+ handleSubmit=${handleSubmit}
308
+ />
309
+ `}
310
+ </div>
540
311
  </div>
541
312
  `;
542
313
  };
@@ -40,7 +40,7 @@
40
40
  <span style="color: var(--accent)">alpha</span>claw
41
41
  </h1>
42
42
  <p style="color: var(--text-muted)" class="text-xs mb-4">
43
- OpenClaw made easier
43
+ OpenClaw made easy
44
44
  </p>
45
45
  </div>
46
46
  <form
@@ -72,13 +72,21 @@
72
72
  const submitButtonEl = document.getElementById("submit-btn");
73
73
 
74
74
  const kEnabledClasses = ["bg-white", "text-black", "hover:opacity-85"];
75
- const kDisabledClasses = ["bg-gray-800", "text-gray-500", "cursor-not-allowed"];
75
+ const kDisabledClasses = [
76
+ "bg-gray-800",
77
+ "text-gray-500",
78
+ "cursor-not-allowed",
79
+ ];
76
80
 
77
81
  const syncButtonState = () => {
78
82
  const hasValue = passwordEl.value.length > 0;
79
83
  submitButtonEl.disabled = !hasValue;
80
- kEnabledClasses.forEach((c) => submitButtonEl.classList.toggle(c, hasValue));
81
- kDisabledClasses.forEach((c) => submitButtonEl.classList.toggle(c, !hasValue));
84
+ kEnabledClasses.forEach((c) =>
85
+ submitButtonEl.classList.toggle(c, hasValue),
86
+ );
87
+ kDisabledClasses.forEach((c) =>
88
+ submitButtonEl.classList.toggle(c, !hasValue),
89
+ );
82
90
  };
83
91
 
84
92
  passwordEl.addEventListener("input", syncButtonState);