@chrysb/alphaclaw 0.1.10 → 0.1.11

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,135 @@
1
+ /* ── App shell grid ─────────────────────────────── */
2
+
3
+ .app-shell {
4
+ display: grid;
5
+ grid-template-columns: 220px 1fr;
6
+ grid-template-rows: 1fr 24px;
7
+ height: 100vh;
8
+ position: relative;
9
+ z-index: 1;
10
+ }
11
+
12
+ .app-content {
13
+ overflow-y: auto;
14
+ padding: 24px 32px;
15
+ position: relative;
16
+ z-index: 1;
17
+ }
18
+
19
+ /* ── Sidebar ───────────────────────────────────── */
20
+
21
+ .app-sidebar {
22
+ background: var(--bg-sidebar);
23
+ border-right: 1px solid var(--border);
24
+ overflow-y: auto;
25
+ display: flex;
26
+ flex-direction: column;
27
+ }
28
+
29
+ .sidebar-brand {
30
+ padding: 16px;
31
+ font-size: 14px;
32
+ letter-spacing: 0.03em;
33
+ color: var(--text-muted);
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 8px;
37
+ }
38
+
39
+ .sidebar-label {
40
+ padding: 12px 16px 6px;
41
+ font-size: 11px;
42
+ font-weight: 700;
43
+ color: var(--text-dim);
44
+ text-transform: uppercase;
45
+ letter-spacing: 0.1em;
46
+ user-select: none;
47
+ }
48
+
49
+ .sidebar-nav { list-style: none; }
50
+
51
+ .sidebar-nav a {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 8px;
55
+ padding: 6px 16px 6px 24px;
56
+ color: var(--text-muted);
57
+ text-decoration: none;
58
+ font-size: 13px;
59
+ cursor: pointer;
60
+ transition: background 0.1s, color 0.1s;
61
+ position: relative;
62
+ user-select: none;
63
+ }
64
+
65
+ .sidebar-nav a:hover { background: var(--bg-hover); color: var(--text); }
66
+ .sidebar-nav a.active { background: var(--bg-active); color: var(--accent); }
67
+
68
+ .sidebar-nav a.active::before {
69
+ content: '';
70
+ position: absolute;
71
+ left: 0;
72
+ top: 0;
73
+ bottom: 0;
74
+ width: 2px;
75
+ background: var(--accent);
76
+ }
77
+
78
+ /* ── Sidebar footer (update banner) ────────────── */
79
+
80
+ .sidebar-footer {
81
+ margin-top: auto;
82
+ font-size: 11px;
83
+ color: var(--text-dim);
84
+ }
85
+
86
+ .sidebar-footer:empty { display: none; }
87
+
88
+ .sidebar-footer:not(:empty) {
89
+ padding: 12px 16px;
90
+ border-top: 1px solid var(--border);
91
+ }
92
+
93
+ .sidebar-update-btn {
94
+ width: 100%;
95
+ font-size: 11px;
96
+ padding: 5px 10px;
97
+ border-radius: 6px;
98
+ border: 1px solid rgba(227, 179, 65, 0.2);
99
+ color: #e3b341;
100
+ background: rgba(227, 179, 65, 0.08);
101
+ cursor: pointer;
102
+ font-family: inherit;
103
+ white-space: nowrap;
104
+ text-align: center;
105
+ transition: background 0.15s, border-color 0.15s;
106
+ }
107
+
108
+ .sidebar-update-btn:hover { background: rgba(227, 179, 65, 0.14); border-color: rgba(227, 179, 65, 0.35); }
109
+ .sidebar-update-btn:disabled { opacity: 0.5; cursor: not-allowed; }
110
+
111
+ /* ── Statusbar ─────────────────────────────────── */
112
+
113
+ .app-statusbar {
114
+ grid-column: 1 / -1;
115
+ background: var(--bg-sidebar);
116
+ border-top: 1px solid var(--border);
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ padding: 0 12px;
121
+ font-size: 11px;
122
+ color: var(--text-dim);
123
+ }
124
+
125
+ .app-statusbar a { color: var(--text-muted); text-decoration: none; }
126
+ .app-statusbar a:hover { color: var(--accent); }
127
+
128
+ .statusbar-left, .statusbar-right { display: flex; align-items: center; gap: 16px; margin-left: 2px; }
129
+
130
+ /* ── Responsive ────────────────────────────────── */
131
+
132
+ @media (max-width: 768px) {
133
+ .app-shell { grid-template-columns: 1fr; }
134
+ .app-sidebar { display: none; }
135
+ }
@@ -0,0 +1,49 @@
1
+ :root {
2
+ --bg: #0a0e14;
3
+ --bg-sidebar: #0d1117;
4
+ --bg-content: #0a0e14;
5
+ --bg-hover: rgba(99, 235, 255, 0.05);
6
+ --bg-active: rgba(99, 235, 255, 0.08);
7
+ --border: rgba(255, 255, 255, 0.06);
8
+ --text: #c9d1d9;
9
+ --text-muted: #6e7681;
10
+ --text-dim: #383d47;
11
+ --accent: #63ebff;
12
+ --accent-dim: rgba(99, 235, 255, 0.4);
13
+ }
14
+
15
+ html, body { height: 100%; }
16
+
17
+ body {
18
+ background: var(--bg);
19
+ color: var(--text);
20
+ font-family: 'JetBrains Mono', monospace;
21
+ font-size: 13px;
22
+ line-height: 1.6;
23
+ }
24
+
25
+ /* Subtle grid texture overlay */
26
+ body::before {
27
+ content: '';
28
+ position: fixed;
29
+ inset: 0;
30
+ background-image:
31
+ linear-gradient(rgba(255, 255, 255, 0.015) 1px, transparent 1px),
32
+ linear-gradient(90deg, rgba(255, 255, 255, 0.015) 1px, transparent 1px);
33
+ background-size: 48px 48px;
34
+ pointer-events: none;
35
+ z-index: 0;
36
+ }
37
+
38
+ ::placeholder { color: var(--text-dim) !important; opacity: 1 !important; }
39
+ ::-webkit-input-placeholder { color: var(--text-dim) !important; }
40
+ ::-moz-placeholder { color: var(--text-dim) !important; }
41
+
42
+ ::-webkit-scrollbar { width: 6px; }
43
+ ::-webkit-scrollbar-track { background: transparent; }
44
+ ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.08); border-radius: 3px; }
45
+
46
+ /* Google scope picker toggle buttons */
47
+ .scope-btn { background: rgba(255,255,255,0.03); color: var(--text-muted); border: 1px solid var(--border); transition: all 0.15s; }
48
+ .scope-btn:hover { border-color: var(--text-dim); color: var(--text); }
49
+ .scope-btn.active { background: var(--bg-active); color: var(--accent); border-color: var(--accent-dim); }
@@ -0,0 +1,3 @@
1
+ <svg width="48" height="49" viewBox="0 0 48 49" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M23.5518 8.05469L31.0293 1.10938L30.4102 10.7275L34.4512 12.5791C34.5736 12.6156 36.1485 16.9461 37.2354 18.3857L47.0996 23.7285C46.0146 27.8051 40.8701 31.1234 39.874 31.4355C39.4971 31.5491 37.3802 31.375 35.9883 31.0947C34.838 30.812 31.4115 30.31 31.0049 31.2158C29.8234 33.8208 30.8798 36.9984 32.4238 39.6348L29.0186 38.6973C28.5812 42.8006 26.6524 45.6721 23.1943 48.3086C20.6642 44.2054 17.5347 42.1502 13.3564 40.71L13.5596 43.5186C9.89126 39.7415 7.26666 36.0583 6.08496 31.5176L2.95508 34.8965C2.31234 30.0298 2.51557 26.3366 4.38574 22.8086L0 23.9678C1.80268 21.0681 3.67334 18.3856 6.46191 16.0586L4.47754 15.2197C7.46189 12.4892 10.7076 10.6598 13.4189 9.24609C14.7191 5.80743 16.5511 3.07864 20.3691 0L23.5518 8.05469ZM16.3701 31.0635C21.1548 35.592 23.9199 39.2755 24.4492 44.0264C26.8608 40.6795 26.9817 37.9163 27.1025 34.0498L28.46 35.9668C28.1942 34.6209 27.9621 33.2042 28.3994 31.5176C26.5003 32.3803 25.0554 34.0963 24.0889 36.5342C22.3152 34.6203 21.1987 32.1213 20.1113 29.8232L20.792 28.8887L17.4844 28.7334L16.3701 31.0635ZM15.1855 32.7793C14.3447 34.4418 13.3204 36.535 13.3203 38.3594C17.3124 39.86 19.5363 41.049 22.0664 43.4268C20.5028 37.6368 17.0173 34.3501 15.1855 32.7793ZM12.9102 11.9385C11.1992 12.9027 10.0194 13.71 8.72168 14.7129L10.9746 15.6475C8.69107 17.0877 6.79372 18.7144 5.34863 20.4639L9.05273 19.4346C6.70381 22.7066 5.23037 25.4694 5.07812 29.2441L7.50879 26.4043C7.80119 29.0407 7.92229 31.4476 11.1387 37.1699L11.2598 37.0781C11.4894 34.5795 13.1154 32.5249 15.0703 28.54L8.48535 28.6035L12.0303 23.0039L8.81348 23.0645C10.3166 20.614 11.5545 19.0432 13.2969 17.3564C12.7411 15.3652 12.7579 14.0216 12.9102 11.9385ZM24.8379 10.8799C20.9424 13.3182 15.4857 17.7336 13.1367 20.9404L16.2344 20.626L12.7334 26.5059H16.3408L18.6953 26.5371L26.457 26.9795L22.4648 29.9707L23.7627 32.3799C25.5365 29.7923 29.0377 28.4145 31.8262 28.4434C34.4216 28.4748 38.6897 29.1903 39.5283 29.2217C40.495 29.0984 42.3294 27.6994 43.168 26.6289C42.3609 25.9933 41.5223 24.8043 41.7832 23.3037L35.4854 20.0166L32.7158 14.3184L24.8379 10.8799ZM34.0117 19.6084L31.4648 18.8936C30.5297 19.0482 29.5968 19.0479 28.8477 18.0186C28.2603 17.2091 26.8171 16.6438 25.377 16.1992L31.4336 16.0469L34.0117 19.6084ZM19.4707 3.35449C15.8628 7.5109 14.72 11.4285 15.3047 15.7637L17.0781 14.1377C16.7858 11.9339 16.9868 9.63498 18.0742 7.19434L19.8447 12.0615L22.0664 10.2393L19.4707 3.35449ZM25.9131 8.80664L28.165 9.83594L28.3682 6.36816L25.9131 8.80664Z" fill="#00EFFF"/>
3
+ </svg>
@@ -292,9 +292,10 @@ function App() {
292
292
  // Still loading onboard status
293
293
  if (onboarded === null) {
294
294
  return html`
295
- <div class="max-w-lg w-full flex items-center justify-center py-20">
295
+ <div class="min-h-screen flex items-center justify-center" style="position: relative; z-index: 1">
296
296
  <svg
297
- class="animate-spin h-6 w-6 text-gray-500"
297
+ class="animate-spin h-6 w-6"
298
+ style="color: var(--text-muted)"
298
299
  viewBox="0 0 24 24"
299
300
  fill="none"
300
301
  >
@@ -319,71 +320,69 @@ function App() {
319
320
 
320
321
  if (!onboarded) {
321
322
  return html`
322
- <${Welcome} onComplete=${() => setOnboarded(true)} />
323
+ <div class="min-h-screen flex justify-center pt-12 pb-8 px-4" style="position: relative; z-index: 1">
324
+ <${Welcome} onComplete=${() => setOnboarded(true)} />
325
+ </div>
323
326
  <${ToastContainer} />
324
327
  `;
325
328
  }
326
329
 
330
+ const kNavItems = [
331
+ { id: "general", label: "General" },
332
+ { id: "models", label: "Models" },
333
+ { id: "envars", label: "Envars" },
334
+ ];
335
+
327
336
  return html`
328
- <div class="max-w-lg w-full">
329
- <div class="sticky top-0 z-10 bg-[#0a0a0a] pb-3">
330
- <div class="flex items-center gap-3 pb-3">
331
- <div class="text-4xl">🦞</div>
332
- <div>
333
- <h1 class="text-2xl font-semibold">OpenClaw Setup${acVersion ? html`${" "}<span class="text-base font-normal text-gray-600">v${acVersion}</span>` : ""}</h1>
334
- <p class="text-gray-500 text-sm">This should be easy, right?</p>
335
- </div>
337
+ <div class="app-shell">
338
+ <div class="app-sidebar">
339
+ <div class="sidebar-brand">
340
+ <img src="./img/logo.svg" alt="" width="20" height="20" />
341
+ <span><span style="color: var(--accent)">alpha</span>claw</span>
336
342
  </div>
337
-
338
- ${acHasUpdate && acLatest && !acDismissed && html`
339
- <div class="mb-3 flex items-center justify-between gap-3 rounded-lg border border-yellow-500/30 bg-yellow-500/10 px-3 py-2">
340
- <p class="text-sm text-yellow-400">
341
- AlphaClaw <span class="font-medium">v${acLatest}</span> is available
342
- </p>
343
- <div class="flex items-center gap-2 shrink-0">
344
- <button
345
- onclick=${handleAcUpdate}
346
- disabled=${acUpdating}
347
- class="text-xs font-medium px-3 py-1 rounded-lg bg-yellow-500/20 text-yellow-300 hover:bg-yellow-500/30 transition-colors ${acUpdating ? "opacity-50 cursor-not-allowed" : ""}"
348
- >
349
- ${acUpdating ? "Updating..." : "Update now"}
350
- </button>
351
- <button
352
- onclick=${() => setAcDismissed(true)}
353
- class="text-yellow-500/60 hover:text-yellow-400 transition-colors"
354
- title="Dismiss"
355
- >
356
- <svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
357
- <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"/>
358
- </svg>
359
- </button>
360
- </div>
361
- </div>
362
- `}
363
-
364
- <div class="flex gap-1 border-b border-border">
365
- ${["general", "models", "envars"].map(
366
- (t) => html`
367
- <button
368
- onclick=${() => setTab(t)}
369
- class="px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab ===
370
- t
371
- ? "border-white text-white"
372
- : "border-transparent text-gray-500 hover:text-gray-300"}"
343
+ <div class="sidebar-label">Setup</div>
344
+ <nav class="sidebar-nav">
345
+ ${kNavItems.map(
346
+ (item) => html`
347
+ <a
348
+ class=${tab === item.id ? "active" : ""}
349
+ onclick=${() => setTab(item.id)}
373
350
  >
374
- ${t === "general" ? "General" : t === "models" ? "Models" : "Envars"}
375
- </button>
351
+ ${item.label}
352
+ </a>
376
353
  `,
377
354
  )}
355
+ </nav>
356
+ <div class="sidebar-footer">
357
+ ${acHasUpdate && acLatest && !acDismissed ? html`
358
+ <button
359
+ onclick=${handleAcUpdate}
360
+ disabled=${acUpdating}
361
+ class="sidebar-update-btn"
362
+ >${acUpdating ? "Updating..." : `Update to v${acLatest}`}</button>
363
+ ` : null}
364
+ </div>
365
+ </div>
366
+
367
+ <div class="app-content">
368
+ <div class="max-w-2xl space-y-4">
369
+ ${tab === "general"
370
+ ? html`<${GeneralTab} onSwitchTab=${setTab} />`
371
+ : tab === "models"
372
+ ? html`<${Models} />`
373
+ : html`<${Envars} />`}
378
374
  </div>
379
375
  </div>
380
376
 
381
- <div class="space-y-4 pt-4">
382
- ${tab === "general"
383
- ? html`<${GeneralTab} onSwitchTab=${setTab} />`
384
- : tab === "models"
385
- ? html`<${Models} />`
386
- : html`<${Envars} />`}
377
+ <div class="app-statusbar">
378
+ <div class="statusbar-left">
379
+ ${acVersion ? html`<span style="color: var(--text-muted)">v${acVersion}</span>` : null}
380
+ </div>
381
+ <div class="statusbar-right">
382
+ <a href="https://docs.openclaw.ai" target="_blank" rel="noreferrer">docs</a>
383
+ <a href="https://discord.com/invite/clawd" target="_blank" rel="noreferrer">discord</a>
384
+ <a href="https://github.com/openclaw/openclaw" target="_blank" rel="noreferrer">github</a>
385
+ </div>
387
386
  </div>
388
387
  </div>
389
388
  <${ToastContainer} />
@@ -10,7 +10,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
10
10
  const [email, setEmail] = useState("");
11
11
  const [error, setError] = useState("");
12
12
  const [saving, setSaving] = useState(false);
13
- const [instrType, setInstrType] = useState("personal");
13
+ const [instrType, setInstrType] = useState("workspace");
14
14
  const [redirectUriCopied, setRedirectUriCopied] = useState(false);
15
15
  const fileRef = useRef(null);
16
16
 
@@ -63,7 +63,14 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
63
63
  };
64
64
 
65
65
  const btnCls = (type) =>
66
- `px-2 py-1 rounded text-xs ${instrType === type ? "bg-gray-700 text-gray-200" : "bg-gray-800 text-gray-400 hover:text-gray-200"}`;
66
+ `flex-1 text-center border-0 cursor-pointer transition-colors` +
67
+ ` ${instrType === type ? "" : "hover:text-white"}`;
68
+
69
+ const btnStyle = (type) =>
70
+ `font-family: inherit; font-size: 11px; letter-spacing: 0.03em; padding: 5px 10px;` +
71
+ (instrType === type
72
+ ? ` color: var(--accent); background: var(--bg-active);`
73
+ : ` color: var(--text-muted); background: transparent;`);
67
74
 
68
75
  const renderRedirectUriInstruction = () => html`
69
76
  <div class="mt-1 flex items-center gap-2">
@@ -92,7 +99,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
92
99
  }}
93
100
  >
94
101
  <div
95
- class="bg-surface border border-border rounded-xl p-6 max-w-md w-full space-y-4"
102
+ class="bg-surface border border-border rounded-xl p-6 max-w-lg w-full space-y-4"
96
103
  >
97
104
  <h2 class="text-lg font-semibold">Connect Google Workspace</h2>
98
105
  <div class="space-y-3">
@@ -102,8 +109,9 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
102
109
  <a
103
110
  href="https://console.cloud.google.com/apis/credentials"
104
111
  target="_blank"
105
- class="text-blue-400 hover:text-blue-300"
106
- >Create one here →</a
112
+ class="hover:text-white"
113
+ style="color: var(--accent)"
114
+ >Create one →</a
107
115
  >
108
116
  </p>
109
117
  <details
@@ -112,139 +120,135 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
112
120
  <summary class="cursor-pointer font-medium hover:text-gray-200">
113
121
  Step-by-step instructions
114
122
  </summary>
115
- <div class="mt-2 mb-2 flex gap-2">
123
+ <div
124
+ class="mt-2 mb-2 flex overflow-hidden"
125
+ style="border: 1px solid var(--border); border-radius: 6px; background: rgba(255,255,255,0.02)"
126
+ >
116
127
  <button
117
128
  onclick=${() => setInstrType("workspace")}
118
129
  class=${btnCls("workspace")}
130
+ style=${btnStyle("workspace")}
119
131
  >
120
132
  Google Workspace
121
133
  </button>
122
134
  <button
123
135
  onclick=${() => setInstrType("personal")}
124
136
  class=${btnCls("personal")}
137
+ style=${btnStyle("personal")}
125
138
  >
126
139
  Personal Gmail
127
140
  </button>
128
141
  </div>
129
142
  ${instrType === "personal"
130
143
  ? html`
131
- <div>
132
- <ol class="list-decimal list-inside space-y-1.5 ml-1">
144
+ <div style="line-height: 1.7">
145
+ <ol class="list-decimal list-inside space-y-2.5 ml-1">
133
146
  <li>
134
- ${" "}
135
- <a
147
+ ${" "}<a
136
148
  href="https://console.cloud.google.com/projectcreate"
137
149
  target="_blank"
138
- class="text-blue-400 hover:text-blue-300"
150
+ class="hover:text-white"
151
+ style="color: var(--accent)"
139
152
  >Create a Google Cloud project</a
140
- >
141
- (or use existing)
153
+ >${" "}(or use existing)
142
154
  </li>
143
155
  <li>
144
- Go to${" "}
145
- <a
156
+ Go to${" "}<a
146
157
  href="https://console.cloud.google.com/auth/audience"
147
158
  target="_blank"
148
- class="text-blue-400 hover:text-blue-300"
159
+ class="hover:text-white"
160
+ style="color: var(--accent)"
149
161
  >OAuth consent screen</a
150
- >
151
- → set to <strong>External</strong>
162
+ >${" "}→ set to <strong>External</strong>
152
163
  </li>
153
164
  <li>
154
- Under${" "}
155
- <a
165
+ Under${" "}<a
156
166
  href="https://console.cloud.google.com/auth/audience"
157
167
  target="_blank"
158
- class="text-blue-400 hover:text-blue-300"
168
+ class="hover:text-white"
169
+ style="color: var(--accent)"
159
170
  >Test users</a
160
171
  >, <strong>add your own email</strong>
161
172
  </li>
162
173
  <li>
163
- ${" "}
164
- <a
174
+ ${" "}<a
165
175
  href="https://console.cloud.google.com/apis/library"
166
176
  target="_blank"
167
- class="text-blue-400 hover:text-blue-300"
177
+ class="hover:text-white"
178
+ style="color: var(--accent)"
168
179
  >Enable APIs</a
169
- >
170
- for the services you selected below
180
+ >${" "}for the services you selected below
171
181
  </li>
172
182
  <li>
173
- Go to${" "}
174
- <a
183
+ Go to${" "}<a
175
184
  href="https://console.cloud.google.com/apis/credentials"
176
185
  target="_blank"
177
- class="text-blue-400 hover:text-blue-300"
186
+ class="hover:text-white"
187
+ style="color: var(--accent)"
178
188
  >Credentials</a
179
- >
180
- → Create OAuth 2.0 Client ID (Web application)
189
+ >${" "}→ Create OAuth 2.0 Client ID (Web application)
181
190
  </li>
182
191
  <li>
183
- Add redirect URI:
184
- ${renderRedirectUriInstruction()}
192
+ Add redirect URI:${renderRedirectUriInstruction()}
185
193
  </li>
186
194
  <li>
187
195
  Copy Client ID + Secret (or download credentials JSON)
188
196
  </li>
189
197
  </ol>
190
- <p class="mt-2 text-yellow-500/80">
198
+ <p class="mt-3 text-yellow-500/80">
191
199
  ⚠️ App will be in "Testing" mode. Only emails added as
192
200
  Test Users can sign in (up to 100).
193
201
  </p>
194
202
  </div>
195
203
  `
196
204
  : html`
197
- <div>
198
- <ol class="list-decimal list-inside space-y-1.5 ml-1">
205
+ <div style="line-height: 1.7">
206
+ <ol class="list-decimal list-inside space-y-2.5 ml-1">
199
207
  <li>
200
- ${" "}
201
- <a
208
+ ${" "}<a
202
209
  href="https://console.cloud.google.com/projectcreate"
203
210
  target="_blank"
204
- class="text-blue-400 hover:text-blue-300"
211
+ class="hover:text-white"
212
+ style="color: var(--accent)"
205
213
  >Create a Google Cloud project</a
206
- >
207
- (or use existing)
214
+ >${" "}(or use existing)
208
215
  </li>
209
216
  <li>
210
- Go to${" "}
211
- <a
217
+ Go to${" "}<a
212
218
  href="https://console.cloud.google.com/auth/audience"
213
219
  target="_blank"
214
- class="text-blue-400 hover:text-blue-300"
220
+ class="hover:text-white"
221
+ style="color: var(--accent)"
215
222
  >OAuth consent screen</a
216
- >
217
- → set to <strong>Internal</strong> (Workspace only)
223
+ >${" "}→ set to <strong>Internal</strong> (Workspace
224
+ only)
218
225
  </li>
219
226
  <li>
220
- ${" "}
221
- <a
227
+ ${" "}<a
222
228
  href="https://console.cloud.google.com/apis/library"
223
229
  target="_blank"
224
- class="text-blue-400 hover:text-blue-300"
230
+ class="hover:text-white"
231
+ style="color: var(--accent)"
225
232
  >Enable APIs</a
226
- >
227
- for the services you selected below
233
+ >${" "}for the services you selected below
228
234
  </li>
229
235
  <li>
230
- Go to${" "}
231
- <a
236
+ Go to${" "}<a
232
237
  href="https://console.cloud.google.com/apis/credentials"
233
238
  target="_blank"
234
- class="text-blue-400 hover:text-blue-300"
239
+ class="hover:text-white"
240
+ style="color: var(--accent)"
235
241
  >Credentials</a
236
- >
237
- → Create OAuth 2.0 Client ID (Web application)
242
+ >${" "}→ Create OAuth 2.0 Client ID (Web application)
238
243
  </li>
239
244
  <li>
240
- Add redirect URI:
241
- ${renderRedirectUriInstruction()}
245
+ Add redirect URI:${renderRedirectUriInstruction()}
242
246
  </li>
243
247
  <li>
244
248
  Copy Client ID + Secret (or download credentials JSON)
245
249
  </li>
246
250
  </ol>
247
- <p class="mt-2 text-green-500/80">
251
+ <p class="mt-3 text-green-500/80">
248
252
  ✓ Internal apps skip test users and verification. Only
249
253
  users in your Workspace org can authorize this Google app.
250
254
  </p>
@@ -15,15 +15,15 @@ const kGroupLabels = {
15
15
  const kGroupOrder = ["github", "channels", "tools", "custom"];
16
16
 
17
17
  const kHintByKey = {
18
- ANTHROPIC_API_KEY: html`From <a href="https://console.anthropic.com" target="_blank" class="text-blue-400 hover:underline">console.anthropic.com</a>`,
19
- ANTHROPIC_TOKEN: html`From <code class="text-xs bg-black/30 px-1 rounded">claude setup-token</code>`,
20
- OPENAI_API_KEY: html`From <a href="https://platform.openai.com" target="_blank" class="text-blue-400 hover:underline">platform.openai.com</a>`,
21
- GEMINI_API_KEY: html`From <a href="https://aistudio.google.com" target="_blank" class="text-blue-400 hover:underline">aistudio.google.com</a>`,
22
- GITHUB_TOKEN: html`Use a <strong>classic PAT</strong> with <code class="text-xs bg-black/30 px-1 rounded">repo</code> scope from <a href="https://github.com/settings/tokens" target="_blank" class="text-blue-400 hover:underline">github.com/settings/tokens</a>.`,
23
- GITHUB_WORKSPACE_REPO: html`Use <code class="text-xs bg-black/30 px-1 rounded">owner/repo</code> or <code class="text-xs bg-black/30 px-1 rounded">https://github.com/owner/repo</code>`,
24
- TELEGRAM_BOT_TOKEN: html`From <a href="https://t.me/BotFather" target="_blank" class="text-blue-400 hover:underline">@BotFather</a> · <a href="https://docs.openclaw.ai/channels/telegram" target="_blank" class="text-blue-400 hover:underline">full guide</a>`,
25
- DISCORD_BOT_TOKEN: html`From <a href="https://discord.com/developers/applications" target="_blank" class="text-blue-400 hover:underline">Developer Portal</a> · <a href="https://docs.openclaw.ai/channels/discord" target="_blank" class="text-blue-400 hover:underline">full guide</a>`,
26
- BRAVE_API_KEY: html`From <a href="https://brave.com/search/api/" target="_blank" class="text-blue-400 hover:underline">brave.com/search/api</a> — free tier available`,
18
+ ANTHROPIC_API_KEY: html`from <a href="https://console.anthropic.com" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">console.anthropic.com</a>`,
19
+ ANTHROPIC_TOKEN: html`from <code class="text-xs bg-black/30 px-1 rounded">claude setup-token</code>`,
20
+ OPENAI_API_KEY: html`from <a href="https://platform.openai.com" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">platform.openai.com</a>`,
21
+ GEMINI_API_KEY: html`from <a href="https://aistudio.google.com" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">aistudio.google.com</a>`,
22
+ GITHUB_TOKEN: html`classic PAT · <code class="text-xs bg-black/30 px-1 rounded">repo</code> scope · <a href="https://github.com/settings/tokens" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">github settings</a>`,
23
+ GITHUB_WORKSPACE_REPO: html`use <code class="text-xs bg-black/30 px-1 rounded">owner/repo</code> or <code class="text-xs bg-black/30 px-1 rounded">https://github.com/owner/repo</code>`,
24
+ TELEGRAM_BOT_TOKEN: html`from <a href="https://t.me/BotFather" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">@BotFather</a> · <a href="https://docs.openclaw.ai/channels/telegram" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">full guide</a>`,
25
+ DISCORD_BOT_TOKEN: html`from <a href="https://discord.com/developers/applications" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">developer portal</a> · <a href="https://docs.openclaw.ai/channels/discord" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">full guide</a>`,
26
+ BRAVE_API_KEY: html`from <a href="https://brave.com/search/api/" target="_blank" class="hover:underline" style="color: rgba(99, 235, 255, 0.6)">brave.com/search/api</a> — free tier available`,
27
27
  };
28
28
 
29
29
  const getHintContent = (envVar) => kHintByKey[envVar.key] || envVar.hint || "";
@@ -31,20 +31,19 @@ const getHintContent = (envVar) => kHintByKey[envVar.key] || envVar.hint || "";
31
31
  const EnvRow = ({ envVar, onChange, onDelete, disabled }) => {
32
32
  const [visible, setVisible] = useState(false);
33
33
  const isSecret = !!envVar.value;
34
+ const hint = getHintContent(envVar);
34
35
 
35
36
  return html`
36
- <div class="flex items-center gap-2">
37
+ <div class="flex items-start gap-4 px-4 py-3">
38
+ <div class="shrink-0 flex items-center gap-2 pt-1.5" style="width: 200px">
39
+ <span
40
+ class="inline-block w-1.5 h-1.5 rounded-full shrink-0 ${envVar.value
41
+ ? "bg-green-500"
42
+ : "bg-gray-600"}"
43
+ />
44
+ <code class="text-xs truncate" style="color: var(--text-muted)">${envVar.key}</code>
45
+ </div>
37
46
  <div class="flex-1 min-w-0">
38
- <div class="flex items-center gap-2 mb-1">
39
- <span
40
- class="inline-block w-2 h-2 rounded-full ${envVar.value
41
- ? "bg-green-500"
42
- : "bg-gray-600"}"
43
- />
44
- <label class="text-xs font-medium text-gray-400">
45
- ${envVar.label || envVar.key}
46
- </label>
47
- </div>
48
47
  <div class="flex items-center gap-1">
49
48
  <input
50
49
  type=${isSecret && !visible ? "password" : "text"}
@@ -73,10 +72,8 @@ const EnvRow = ({ envVar, onChange, onDelete, disabled }) => {
73
72
  </button>`
74
73
  : null}
75
74
  </div>
76
- ${getHintContent(envVar)
77
- ? html`<p class="text-xs text-gray-600 mt-1">
78
- ${getHintContent(envVar)}
79
- </p>`
75
+ ${hint
76
+ ? html`<p class="text-xs text-gray-600 mt-1">${hint}</p>`
80
77
  : null}
81
78
  </div>
82
79
  </div>
@@ -267,57 +264,59 @@ export const Envars = () => {
267
264
  .filter((g) => grouped[g]?.length)
268
265
  .map(
269
266
  (g) => html`
270
- <div
271
- class="bg-surface border border-border rounded-xl p-4 space-y-3"
272
- >
273
- <h3 class="text-sm font-medium text-gray-400">
267
+ <div class="bg-surface border border-border rounded-xl overflow-hidden">
268
+ <h3 class="text-xs font-semibold px-4 pt-3 pb-2" style="color: var(--text-dim); letter-spacing: 0.05em">
274
269
  ${kGroupLabels[g] || g}
275
270
  </h3>
276
- ${grouped[g].map(
277
- (v) =>
278
- html`<${EnvRow}
279
- envVar=${v}
280
- onChange=${handleChange}
281
- onDelete=${handleDelete}
282
- disabled=${saving}
283
- />`,
284
- )}
271
+ <div class="divide-y divide-border">
272
+ ${grouped[g].map(
273
+ (v) =>
274
+ html`<${EnvRow}
275
+ envVar=${v}
276
+ onChange=${handleChange}
277
+ onDelete=${handleDelete}
278
+ disabled=${saving}
279
+ />`,
280
+ )}
281
+ </div>
285
282
  </div>
286
283
  `,
287
284
  )}
288
285
 
289
- <div class="bg-surface border border-border rounded-xl p-4 space-y-3">
290
- <div class="flex items-center justify-between">
291
- <h3 class="text-sm font-medium text-gray-400">Add Variable</h3>
292
- <span class="text-xs text-gray-600"
293
- >Paste KEY=VALUE or multiple lines</span
294
- >
286
+ <div class="bg-surface border border-border rounded-xl overflow-hidden">
287
+ <div class="flex items-center justify-between px-4 pt-3 pb-2">
288
+ <h3 class="text-xs font-semibold" style="color: var(--text-dim); letter-spacing: 0.05em">Add Variable</h3>
289
+ <span class="text-xs" style="color: var(--text-dim)">Paste KEY=VALUE or multiple lines</span>
295
290
  </div>
296
- <input
297
- type="text"
298
- value=${newKey}
299
- placeholder="VARIABLE_NAME"
300
- onInput=${(e) => handleKeyInput(e.target.value)}
301
- onPaste=${(e) => handlePaste(e, "key")}
302
- onKeyDown=${(e) => e.key === "Enter" && handleAddVar()}
303
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono uppercase"
304
- />
305
- <div class="flex gap-2">
306
- <input
307
- type="text"
308
- value=${newVal}
309
- placeholder="value"
310
- onInput=${(e) => handleValInput(e.target.value)}
311
- onPaste=${(e) => handlePaste(e, "val")}
312
- onKeyDown=${(e) => e.key === "Enter" && handleAddVar()}
313
- class="flex-1 bg-black/30 border border-border rounded-lg px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
314
- />
315
- <button
316
- onclick=${handleAddVar}
317
- class="text-sm px-3 py-1.5 rounded-lg border border-border text-gray-400 hover:text-gray-200 hover:border-gray-500 shrink-0"
318
- >
319
- + Add
320
- </button>
291
+ <div class="flex items-start gap-4 px-4 py-3 border-t border-border">
292
+ <div class="shrink-0" style="width: 200px">
293
+ <input
294
+ type="text"
295
+ value=${newKey}
296
+ placeholder="KEY"
297
+ onInput=${(e) => handleKeyInput(e.target.value)}
298
+ onPaste=${(e) => handlePaste(e, "key")}
299
+ onKeyDown=${(e) => e.key === "Enter" && handleAddVar()}
300
+ class="w-full bg-black/30 border border-border rounded-lg px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono uppercase"
301
+ />
302
+ </div>
303
+ <div class="flex-1 flex gap-2">
304
+ <input
305
+ type="text"
306
+ value=${newVal}
307
+ placeholder="value"
308
+ onInput=${(e) => handleValInput(e.target.value)}
309
+ onPaste=${(e) => handlePaste(e, "val")}
310
+ onKeyDown=${(e) => e.key === "Enter" && handleAddVar()}
311
+ class="flex-1 bg-black/30 border border-border rounded-lg px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
312
+ />
313
+ <button
314
+ onclick=${handleAddVar}
315
+ class="text-xs px-3 py-1.5 rounded-lg border border-border text-gray-400 hover:text-gray-200 hover:border-gray-500 shrink-0"
316
+ >
317
+ + Add
318
+ </button>
319
+ </div>
321
320
  </div>
322
321
  </div>
323
322
 
@@ -175,7 +175,7 @@ export function Google({ gatewayStatus }) {
175
175
  <button
176
176
  onclick=${() => startAuth(email)}
177
177
  disabled=${isAuthed && !scopesChanged}
178
- class="text-sm font-medium px-4 py-2 rounded-lg ${isAuthed &&
178
+ class="text-xs font-medium px-3 py-1.5 rounded-lg ${isAuthed &&
179
179
  !scopesChanged
180
180
  ? "bg-gray-600 text-gray-400 cursor-not-allowed"
181
181
  : "bg-white text-black hover:opacity-85"}"
@@ -185,7 +185,7 @@ export function Google({ gatewayStatus }) {
185
185
  <button
186
186
  type="button"
187
187
  onclick=${() => setModalOpen(true)}
188
- class="text-xs font-medium px-3 py-2 rounded-lg border border-border text-gray-300 hover:border-gray-500"
188
+ class="text-xs font-medium px-3 py-1.5 rounded-lg border border-border text-gray-300 hover:border-gray-500"
189
189
  >
190
190
  Edit credentials
191
191
  </button>
@@ -35,7 +35,7 @@ const kGroups = [
35
35
  hint: html`Create a classic PAT at${" "}<a
36
36
  href="https://github.com/settings/tokens"
37
37
  target="_blank"
38
- class="text-blue-400 hover:underline"
38
+ class="hover:underline" style="color: var(--accent)"
39
39
  >github.com/settings/tokens</a
40
40
  >${" "}with${" "}<code class="text-xs bg-black/30 px-1 rounded">repo</code>${" "}scope`,
41
41
  placeholder: "ghp_...",
@@ -61,12 +61,12 @@ const kGroups = [
61
61
  hint: html`From${" "}<a
62
62
  href="https://t.me/BotFather"
63
63
  target="_blank"
64
- class="text-blue-400 hover:underline"
64
+ class="hover:underline" style="color: var(--accent)"
65
65
  >@BotFather</a
66
66
  >${" "}·${" "}<a
67
67
  href="https://docs.openclaw.ai/channels/telegram"
68
68
  target="_blank"
69
- class="text-blue-400 hover:underline"
69
+ class="hover:underline" style="color: var(--accent)"
70
70
  >full guide</a
71
71
  >`,
72
72
  placeholder: "123456789:AAH...",
@@ -77,12 +77,12 @@ const kGroups = [
77
77
  hint: html`From${" "}<a
78
78
  href="https://discord.com/developers/applications"
79
79
  target="_blank"
80
- class="text-blue-400 hover:underline"
80
+ class="hover:underline" style="color: var(--accent)"
81
81
  >Developer Portal</a
82
82
  >${" "}·${" "}<a
83
83
  href="https://docs.openclaw.ai/channels/discord"
84
84
  target="_blank"
85
- class="text-blue-400 hover:underline"
85
+ class="hover:underline" style="color: var(--accent)"
86
86
  >full guide</a
87
87
  >`,
88
88
  placeholder: "MTQ3...",
@@ -101,7 +101,7 @@ const kGroups = [
101
101
  hint: html`From${" "}<a
102
102
  href="https://brave.com/search/api/"
103
103
  target="_blank"
104
- class="text-blue-400 hover:underline"
104
+ class="hover:underline" style="color: var(--accent)"
105
105
  >brave.com/search/api</a
106
106
  >${" "}-${" "}free tier available`,
107
107
  placeholder: "BSA...",
@@ -309,7 +309,8 @@ export const Welcome = ({ onComplete }) => {
309
309
  if (loading) {
310
310
  return html`
311
311
  <div
312
- class="fixed inset-0 bg-[#0a0a0a] flex items-center justify-center z-50"
312
+ class="fixed inset-0 flex items-center justify-center z-50"
313
+ style="background: var(--bg)"
313
314
  >
314
315
  <div class="flex flex-col items-center gap-4">
315
316
  <svg
@@ -332,7 +333,7 @@ export const Welcome = ({ onComplete }) => {
332
333
  />
333
334
  </svg>
334
335
  <h2 class="text-lg font-semibold text-white">
335
- Initializing OpenClaw
336
+ Initializing <span style="color: var(--accent)">alpha</span>claw
336
337
  </h2>
337
338
  <p class="text-sm text-gray-500">This could take 10–15 seconds</p>
338
339
  </div>
@@ -343,10 +344,10 @@ export const Welcome = ({ onComplete }) => {
343
344
  return html`
344
345
  <div class="max-w-lg w-full space-y-4">
345
346
  <div class="flex items-center gap-3">
346
- <div class="text-4xl">🦞</div>
347
+ <div class="text-4xl">🐾</div>
347
348
  <div>
348
- <h1 class="text-2xl font-semibold">Welcome to OpenClaw</h1>
349
- <p class="text-gray-500 text-sm">Let's get your agent running</p>
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>
350
351
  </div>
351
352
  </div>
352
353
 
@@ -1,90 +1,135 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
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>OpenClaw Setup</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script>
9
- tailwind.config = {
10
- theme: {
11
- extend: {
12
- colors: {
13
- surface: '#141414',
14
- border: '#222',
15
- }
16
- }
17
- }
18
- }
19
- </script>
20
- </head>
21
- <body class="bg-[#0a0a0a] text-gray-200 min-h-screen flex items-center justify-center p-4">
22
- <div class="max-w-sm w-full">
23
- <div class="flex items-center gap-3 mb-6">
24
- <div class="text-4xl">🦞</div>
25
- <div>
26
- <h1 class="text-2xl font-semibold">OpenClaw Setup</h1>
27
- <p class="text-gray-500 text-sm">Enter password to continue</p>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>alphaclaw</title>
7
+ <link
8
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap"
9
+ rel="stylesheet"
10
+ />
11
+ <link rel="stylesheet" href="./css/theme.css" />
12
+ <script src="https://cdn.tailwindcss.com"></script>
13
+ <script>
14
+ tailwind.config = {
15
+ theme: {
16
+ extend: {
17
+ colors: {
18
+ surface: "var(--bg-sidebar)",
19
+ border: "var(--border)",
20
+ },
21
+ fontFamily: {
22
+ mono: ["'JetBrains Mono'", "monospace"],
23
+ },
24
+ },
25
+ },
26
+ };
27
+ </script>
28
+ </head>
29
+ <body class="min-h-screen flex items-center justify-center p-4">
30
+ <div class="max-w-sm w-full relative z-10">
31
+ <div class="text-center mb-6">
32
+ <img
33
+ src="./img/logo.svg"
34
+ alt="alphaclaw"
35
+ class="mx-auto mb-4"
36
+ width="48"
37
+ height="49"
38
+ />
39
+ <h1 class="text-2xl font-semibold mb-2">
40
+ <span style="color: var(--accent)">alpha</span>claw
41
+ </h1>
42
+ <p style="color: var(--text-muted)" class="text-xs mb-4">
43
+ OpenClaw made easier
44
+ </p>
28
45
  </div>
46
+ <form
47
+ id="login-form"
48
+ class="bg-surface border border-border rounded-xl p-4 space-y-3"
49
+ >
50
+ <input
51
+ id="password"
52
+ type="password"
53
+ placeholder="Password"
54
+ autofocus
55
+ class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm outline-none focus:border-gray-500"
56
+ style="color: var(--text)"
57
+ />
58
+ <div id="error" class="text-red-400 text-xs hidden"></div>
59
+ <button
60
+ id="submit-btn"
61
+ type="submit"
62
+ disabled
63
+ class="w-full text-sm font-medium px-4 py-2 rounded-lg bg-gray-800 text-gray-500 cursor-not-allowed"
64
+ >
65
+ Continue
66
+ </button>
67
+ </form>
29
68
  </div>
30
- <form id="login-form" class="bg-surface border border-border rounded-xl p-4 space-y-3">
31
- <input
32
- id="password"
33
- type="password"
34
- placeholder="Password"
35
- autofocus
36
- 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"
37
- />
38
- <div id="error" class="text-red-400 text-xs hidden"></div>
39
- <button type="submit" class="w-full bg-white text-black text-sm font-medium px-4 py-2 rounded-lg hover:opacity-85">
40
- Continue
41
- </button>
42
- </form>
43
- </div>
44
- <script>
45
- const formEl = document.getElementById('login-form');
46
- const submitButtonEl = formEl.querySelector('button[type="submit"]');
69
+ <script>
70
+ const formEl = document.getElementById("login-form");
71
+ const passwordEl = document.getElementById("password");
72
+ const submitButtonEl = document.getElementById("submit-btn");
73
+
74
+ const kEnabledClasses = ["bg-white", "text-black", "hover:opacity-85"];
75
+ const kDisabledClasses = ["bg-gray-800", "text-gray-500", "cursor-not-allowed"];
76
+
77
+ const syncButtonState = () => {
78
+ const hasValue = passwordEl.value.length > 0;
79
+ submitButtonEl.disabled = !hasValue;
80
+ kEnabledClasses.forEach((c) => submitButtonEl.classList.toggle(c, hasValue));
81
+ kDisabledClasses.forEach((c) => submitButtonEl.classList.toggle(c, !hasValue));
82
+ };
83
+
84
+ passwordEl.addEventListener("input", syncButtonState);
47
85
 
48
- const setPendingState = (isPending) => {
49
- submitButtonEl.disabled = isPending;
50
- submitButtonEl.classList.toggle('opacity-70', isPending);
51
- submitButtonEl.classList.toggle('cursor-not-allowed', isPending);
52
- };
86
+ const setPendingState = (isPending) => {
87
+ submitButtonEl.disabled = isPending;
88
+ submitButtonEl.classList.toggle("opacity-70", isPending);
89
+ submitButtonEl.classList.toggle("cursor-not-allowed", isPending);
90
+ };
53
91
 
54
- formEl.addEventListener('submit', async (e) => {
55
- e.preventDefault();
56
- const password = document.getElementById('password').value;
57
- const errorEl = document.getElementById('error');
58
- errorEl.classList.add('hidden');
59
- setPendingState(true);
60
- try {
61
- const res = await fetch('/api/auth/login', {
62
- method: 'POST',
63
- headers: { 'Content-Type': 'application/json' },
64
- body: JSON.stringify({ password }),
65
- });
66
- const data = await res.json();
67
- if (data.ok) {
68
- window.location.href = '/';
69
- } else {
70
- if (res.status === 429) {
71
- const retryAfterFromHeader = Number.parseInt(res.headers.get('retry-after') || '0', 10);
72
- const retryAfterSec = Number.isFinite(data.retryAfterSec) && data.retryAfterSec > 0
73
- ? data.retryAfterSec
74
- : (Number.isFinite(retryAfterFromHeader) && retryAfterFromHeader > 0 ? retryAfterFromHeader : 30);
75
- errorEl.textContent = `Too many attempts. Try again in ${retryAfterSec}s.`;
92
+ formEl.addEventListener("submit", async (e) => {
93
+ e.preventDefault();
94
+ const password = document.getElementById("password").value;
95
+ const errorEl = document.getElementById("error");
96
+ errorEl.classList.add("hidden");
97
+ setPendingState(true);
98
+ try {
99
+ const res = await fetch("/api/auth/login", {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify({ password }),
103
+ });
104
+ const data = await res.json();
105
+ if (data.ok) {
106
+ window.location.href = "/";
76
107
  } else {
77
- errorEl.textContent = data.error || 'Login failed';
108
+ if (res.status === 429) {
109
+ const retryAfterFromHeader = Number.parseInt(
110
+ res.headers.get("retry-after") || "0",
111
+ 10,
112
+ );
113
+ const retryAfterSec =
114
+ Number.isFinite(data.retryAfterSec) && data.retryAfterSec > 0
115
+ ? data.retryAfterSec
116
+ : Number.isFinite(retryAfterFromHeader) &&
117
+ retryAfterFromHeader > 0
118
+ ? retryAfterFromHeader
119
+ : 30;
120
+ errorEl.textContent = `Too many attempts. Try again in ${retryAfterSec}s.`;
121
+ } else {
122
+ errorEl.textContent = data.error || "Login failed";
123
+ }
124
+ errorEl.classList.remove("hidden");
78
125
  }
79
- errorEl.classList.remove('hidden');
126
+ } catch {
127
+ errorEl.textContent = "Connection error";
128
+ errorEl.classList.remove("hidden");
129
+ } finally {
130
+ setPendingState(false);
80
131
  }
81
- } catch {
82
- errorEl.textContent = 'Connection error';
83
- errorEl.classList.remove('hidden');
84
- } finally {
85
- setPendingState(false);
86
- }
87
- });
88
- </script>
89
- </body>
132
+ });
133
+ </script>
134
+ </body>
90
135
  </html>
@@ -3,31 +3,29 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>OpenClaw Setup</title>
6
+ <title>alphaclaw</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="./css/theme.css">
9
+ <link rel="stylesheet" href="./css/shell.css">
7
10
  <script src="https://cdn.tailwindcss.com"></script>
8
11
  <script>
9
12
  tailwind.config = {
10
13
  theme: {
11
14
  extend: {
12
15
  colors: {
13
- surface: '#141414',
14
- border: '#222',
16
+ surface: 'var(--bg-sidebar)',
17
+ border: 'var(--border)',
18
+ },
19
+ fontFamily: {
20
+ mono: ["'JetBrains Mono'", 'monospace'],
15
21
  }
16
22
  }
17
23
  }
18
24
  }
19
25
  </script>
20
- <style>
21
- ::placeholder { color: #444 !important; opacity: 1 !important; }
22
- ::-webkit-input-placeholder { color: #444 !important; }
23
- ::-moz-placeholder { color: #444 !important; }
24
- .scope-btn { background: #1a1a1a; color: #555; border: 1px solid #333; transition: all 0.15s; }
25
- .scope-btn:hover { border-color: #555; }
26
- .scope-btn.active { background: #065f46; color: #6ee7b7; border-color: #059669; }
27
- </style>
28
26
  </head>
29
- <body class="bg-[#0a0a0a] text-gray-200 min-h-screen flex justify-center pt-12 pb-8 px-4">
30
- <div id="app" class="flex justify-center w-full"></div>
27
+ <body>
28
+ <div id="app"></div>
31
29
  <script type="module" src="./js/app.js"></script>
32
30
  </body>
33
31
  </html>
@@ -6,13 +6,10 @@ const http = require("http");
6
6
  const {
7
7
  kLatestVersionCacheTtlMs,
8
8
  kAlphaclawRegistryUrl,
9
- kPackageRoot,
9
+ kNpmPackageRoot,
10
10
  kRootDir,
11
11
  } = require("./constants");
12
12
 
13
- // kPackageRoot is lib/ — the actual npm package root (with package.json) is one level up
14
- const kNpmPackageRoot = path.resolve(kPackageRoot, "..");
15
-
16
13
  const createAlphaclawVersionService = () => {
17
14
  let kUpdateStatusCache = { latestVersion: null, hasUpdate: false, fetchedAt: 0 };
18
15
  let kUpdateInProgress = false;
@@ -10,6 +10,7 @@ const parsePositiveIntEnv = (value, fallbackValue) => {
10
10
  const kRootDir = process.env.ALPHACLAW_ROOT_DIR
11
11
  || path.join(os.homedir(), ".alphaclaw");
12
12
  const kPackageRoot = path.resolve(__dirname, "..");
13
+ const kNpmPackageRoot = path.resolve(kPackageRoot, "..");
13
14
  const kSetupDir = path.join(kPackageRoot, "setup");
14
15
 
15
16
  const PORT = parseInt(process.env.PORT || "3000", 10);
@@ -107,7 +108,7 @@ const kVersionCacheTtlMs = 60 * 1000;
107
108
  const kLatestVersionCacheTtlMs = 10 * 60 * 1000;
108
109
  const kOpenclawRegistryUrl = "https://registry.npmjs.org/openclaw";
109
110
  const kAlphaclawRegistryUrl = "https://registry.npmjs.org/@chrysb%2falphaclaw";
110
- const kAppDir = kPackageRoot;
111
+ const kAppDir = kNpmPackageRoot;
111
112
 
112
113
  const kSystemVars = new Set([
113
114
  "WEBHOOK_TOKEN",
@@ -236,6 +237,7 @@ const SETUP_API_PREFIXES = [
236
237
  module.exports = {
237
238
  kRootDir,
238
239
  kPackageRoot,
240
+ kNpmPackageRoot,
239
241
  kSetupDir,
240
242
  PORT,
241
243
  GATEWAY_PORT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },