@chrysb/alphaclaw 0.1.3 → 0.1.5

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.
@@ -12,6 +12,8 @@ import {
12
12
  fetchOnboardStatus,
13
13
  fetchDashboardUrl,
14
14
  updateSyncCron,
15
+ fetchAlphaclawVersion,
16
+ updateAlphaclaw,
15
17
  } from "./lib/api.js";
16
18
  import { usePolling } from "./hooks/usePolling.js";
17
19
  import { Gateway } from "./components/gateway.js";
@@ -240,6 +242,11 @@ const GeneralTab = ({ onSwitchTab }) => {
240
242
  function App() {
241
243
  const [onboarded, setOnboarded] = useState(null);
242
244
  const [tab, setTab] = useState("general");
245
+ const [acVersion, setAcVersion] = useState(null);
246
+ const [acLatest, setAcLatest] = useState(null);
247
+ const [acHasUpdate, setAcHasUpdate] = useState(false);
248
+ const [acUpdating, setAcUpdating] = useState(false);
249
+ const [acDismissed, setAcDismissed] = useState(false);
243
250
 
244
251
  useEffect(() => {
245
252
  fetchOnboardStatus()
@@ -247,6 +254,41 @@ function App() {
247
254
  .catch(() => setOnboarded(false));
248
255
  }, []);
249
256
 
257
+ useEffect(() => {
258
+ if (!onboarded) return;
259
+ let active = true;
260
+ const check = async () => {
261
+ try {
262
+ const data = await fetchAlphaclawVersion(false);
263
+ if (!active) return;
264
+ setAcVersion(data.currentVersion || null);
265
+ setAcLatest(data.latestVersion || null);
266
+ setAcHasUpdate(!!data.hasUpdate);
267
+ } catch {}
268
+ };
269
+ check();
270
+ const id = setInterval(check, 5 * 60 * 1000);
271
+ return () => { active = false; clearInterval(id); };
272
+ }, [onboarded]);
273
+
274
+ const handleAcUpdate = async () => {
275
+ if (acUpdating) return;
276
+ setAcUpdating(true);
277
+ try {
278
+ const data = await updateAlphaclaw();
279
+ if (data.ok) {
280
+ showToast("AlphaClaw updated — restarting...", "success");
281
+ setTimeout(() => window.location.reload(), 5000);
282
+ } else {
283
+ showToast(data.error || "AlphaClaw update failed", "error");
284
+ setAcUpdating(false);
285
+ }
286
+ } catch (err) {
287
+ showToast(err.message || "Could not update AlphaClaw", "error");
288
+ setAcUpdating(false);
289
+ }
290
+ };
291
+
250
292
  // Still loading onboard status
251
293
  if (onboarded === null) {
252
294
  return html`
@@ -288,11 +330,37 @@ function App() {
288
330
  <div class="flex items-center gap-3 pb-3">
289
331
  <div class="text-4xl">🦞</div>
290
332
  <div>
291
- <h1 class="text-2xl font-semibold">OpenClaw Setup</h1>
333
+ <h1 class="text-2xl font-semibold">OpenClaw Setup${acVersion ? html`${" "}<span class="text-base font-normal text-gray-600">v${acVersion}</span>` : ""}</h1>
292
334
  <p class="text-gray-500 text-sm">This should be easy, right?</p>
293
335
  </div>
294
336
  </div>
295
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
+
296
364
  <div class="flex gap-1 border-b border-border">
297
365
  ${["general", "models", "envars"].map(
298
366
  (t) => html`
@@ -15,15 +15,78 @@ 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>. Fine-grained tokens can fail unless they include equivalent repo write permissions.`,
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{" "}
19
+ <a
20
+ href="https://console.anthropic.com"
21
+ target="_blank"
22
+ class="text-blue-400 hover:underline"
23
+ >console.anthropic.com</a
24
+ >`,
25
+ ANTHROPIC_TOKEN: html`From{" "}
26
+ <code class="text-xs bg-black/30 px-1 rounded">claude setup-token</code>`,
27
+ OPENAI_API_KEY: html`From{" "}
28
+ <a
29
+ href="https://platform.openai.com"
30
+ target="_blank"
31
+ class="text-blue-400 hover:underline"
32
+ >platform.openai.com</a
33
+ >`,
34
+ GEMINI_API_KEY: html`From{" "}
35
+ <a
36
+ href="https://aistudio.google.com"
37
+ target="_blank"
38
+ class="text-blue-400 hover:underline"
39
+ >aistudio.google.com</a
40
+ >`,
41
+ GITHUB_TOKEN: html`Use a <strong>classic PAT</strong> with{" "}
42
+ <code class="text-xs bg-black/30 px-1 rounded">repo</code> scope from
43
+ <a
44
+ href="https://github.com/settings/tokens"
45
+ target="_blank"
46
+ class="text-blue-400 hover:underline"
47
+ >github.com/settings/tokens</a
48
+ >.`,
49
+ GITHUB_WORKSPACE_REPO: html`Use{" "}
50
+ <code class="text-xs bg-black/30 px-1 rounded">owner/repo</code> or
51
+ <code class="text-xs bg-black/30 px-1 rounded"
52
+ >https://github.com/owner/repo</code
53
+ >`,
54
+ TELEGRAM_BOT_TOKEN: html`From{" "}
55
+ <a
56
+ href="https://t.me/BotFather"
57
+ target="_blank"
58
+ class="text-blue-400 hover:underline"
59
+ >@BotFather</a
60
+ >
61
+ ·
62
+ <a
63
+ href="https://docs.openclaw.ai/channels/telegram"
64
+ target="_blank"
65
+ class="text-blue-400 hover:underline"
66
+ >full guide</a
67
+ >`,
68
+ DISCORD_BOT_TOKEN: html`From{" "}
69
+ <a
70
+ href="https://discord.com/developers/applications"
71
+ target="_blank"
72
+ class="text-blue-400 hover:underline"
73
+ >Developer Portal</a
74
+ >
75
+ ·
76
+ <a
77
+ href="https://docs.openclaw.ai/channels/discord"
78
+ target="_blank"
79
+ class="text-blue-400 hover:underline"
80
+ >full guide</a
81
+ >`,
82
+ BRAVE_API_KEY: html`From{" "}
83
+ <a
84
+ href="https://brave.com/search/api/"
85
+ target="_blank"
86
+ class="text-blue-400 hover:underline"
87
+ >brave.com/search/api</a
88
+ >
89
+ — free tier available`,
27
90
  };
28
91
 
29
92
  const getHintContent = (envVar) => kHintByKey[envVar.key] || envVar.hint || "";
@@ -74,7 +137,9 @@ const EnvRow = ({ envVar, onChange, onDelete, disabled }) => {
74
137
  : null}
75
138
  </div>
76
139
  ${getHintContent(envVar)
77
- ? html`<p class="text-xs text-gray-600 mt-1">${getHintContent(envVar)}</p>`
140
+ ? html`<p class="text-xs text-gray-600 mt-1">
141
+ ${getHintContent(envVar)}
142
+ </p>`
78
143
  : null}
79
144
  </div>
80
145
  </div>
@@ -118,7 +183,9 @@ export const Envars = () => {
118
183
  if (saving) return;
119
184
  setSaving(true);
120
185
  try {
121
- const toSave = vars.filter((v) => v.editable).map((v) => ({ key: v.key, value: v.value }));
186
+ const toSave = vars
187
+ .filter((v) => v.editable)
188
+ .map((v) => ({ key: v.key, value: v.value }));
122
189
  const result = await saveEnvVars(toSave);
123
190
  const needsRestart = !!result?.restartRequired;
124
191
  setRestartRequired(needsRestart);
@@ -153,11 +220,19 @@ export const Envars = () => {
153
220
  const [newVal, setNewVal] = useState("");
154
221
 
155
222
  const parsePaste = (input) => {
156
- const lines = input.split("\n").map((l) => l.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
223
+ const lines = input
224
+ .split("\n")
225
+ .map((l) => l.trim())
226
+ .filter(Boolean)
227
+ .filter((l) => !l.startsWith("#"));
157
228
  const pairs = [];
158
229
  for (const line of lines) {
159
230
  const eqIdx = line.indexOf("=");
160
- if (eqIdx > 0) pairs.push({ key: line.slice(0, eqIdx).trim(), value: line.slice(eqIdx + 1).trim() });
231
+ if (eqIdx > 0)
232
+ pairs.push({
233
+ key: line.slice(0, eqIdx).trim(),
234
+ value: line.slice(eqIdx + 1).trim(),
235
+ });
161
236
  }
162
237
  return pairs;
163
238
  };
@@ -173,7 +248,15 @@ export const Envars = () => {
173
248
  if (existing) {
174
249
  existing.value = value;
175
250
  } else {
176
- next.push({ key, value, label: key, group: "custom", hint: "", source: "env_file", editable: true });
251
+ next.push({
252
+ key,
253
+ value,
254
+ label: key,
255
+ group: "custom",
256
+ hint: "",
257
+ source: "env_file",
258
+ editable: true,
259
+ });
177
260
  }
178
261
  added++;
179
262
  }
@@ -223,7 +306,10 @@ export const Envars = () => {
223
306
  };
224
307
 
225
308
  const handleAddVar = () => {
226
- const key = newKey.trim().toUpperCase().replace(/[^A-Z0-9_]/g, "_");
309
+ const key = newKey
310
+ .trim()
311
+ .toUpperCase()
312
+ .replace(/[^A-Z0-9_]/g, "_");
227
313
  if (!key) return;
228
314
  addVars([{ key, value: newVal }]);
229
315
  setNewKey("");
@@ -257,16 +343,18 @@ export const Envars = () => {
257
343
  onChange=${handleChange}
258
344
  onDelete=${handleDelete}
259
345
  disabled=${saving}
260
- />`
346
+ />`,
261
347
  )}
262
348
  </div>
263
- `
349
+ `,
264
350
  )}
265
351
 
266
352
  <div class="bg-surface border border-border rounded-xl p-4 space-y-3">
267
353
  <div class="flex items-center justify-between">
268
354
  <h3 class="text-sm font-medium text-gray-400">Add Variable</h3>
269
- <span class="text-xs text-gray-600">Paste KEY=VALUE or multiple lines</span>
355
+ <span class="text-xs text-gray-600"
356
+ >Paste KEY=VALUE or multiple lines</span
357
+ >
270
358
  </div>
271
359
  <input
272
360
  type="text"
@@ -326,11 +414,7 @@ export const Envars = () => {
326
414
  >
327
415
  ${saving
328
416
  ? html`<span class="flex items-center justify-center gap-2">
329
- <svg
330
- class="animate-spin h-4 w-4"
331
- viewBox="0 0 24 24"
332
- fill="none"
333
- >
417
+ <svg class="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
334
418
  <circle
335
419
  class="opacity-25"
336
420
  cx="12"
@@ -3,10 +3,8 @@ import { useEffect, useState } from "https://esm.sh/preact/hooks";
3
3
  import htm from "https://esm.sh/htm";
4
4
  import {
5
5
  fetchOpenclawVersion,
6
- fetchAlphaclawVersion,
7
6
  restartGateway,
8
7
  updateOpenclaw,
9
- updateAlphaclaw,
10
8
  } from "../lib/api.js";
11
9
  import { showToast } from "./toast.js";
12
10
  const html = htm.bind(h);
@@ -82,7 +80,7 @@ function VersionRow({ label, currentVersion, fetchVersion, applyUpdate, tagsUrl
82
80
  <div class="flex items-center justify-between gap-3">
83
81
  <div class="min-w-0">
84
82
  <p class="text-sm text-gray-300 truncate">
85
- <span class="text-gray-500">${label}</span>${" "}v${version || "unknown"}
83
+ <span class="text-gray-500">${label}</span>${" "}${version ? `v${version}` : "..."}
86
84
  </p>
87
85
  ${error && html`<p class="text-xs text-yellow-500 mt-1">${error}</p>`}
88
86
  </div>
@@ -147,7 +145,7 @@ export function Gateway({ status, openclawVersion }) {
147
145
  Restart
148
146
  </button>
149
147
  </div>
150
- <div class="mt-3 pt-3 border-t border-border space-y-3">
148
+ <div class="mt-3 pt-3 border-t border-border">
151
149
  <${VersionRow}
152
150
  label="OpenClaw"
153
151
  currentVersion=${openclawVersion}
@@ -155,13 +153,6 @@ export function Gateway({ status, openclawVersion }) {
155
153
  applyUpdate=${updateOpenclaw}
156
154
  tagsUrl="https://github.com/openclaw/openclaw/tags"
157
155
  />
158
- <${VersionRow}
159
- label="AlphaClaw"
160
- currentVersion=${null}
161
- fetchVersion=${fetchAlphaclawVersion}
162
- applyUpdate=${updateAlphaclaw}
163
- tagsUrl="https://www.npmjs.com/package/@chrysb/alphaclaw"
164
- />
165
156
  </div>
166
157
  </div>`;
167
158
  }
@@ -76,9 +76,6 @@ const createOnboardingService = ({
76
76
  hasCodexOauth,
77
77
  workspaceDir: WORKSPACE_DIR,
78
78
  });
79
- console.log(
80
- `[onboard] Running: openclaw onboard ${onboardArgs.join(" ").replace(/sk-[^\s]+/g, "***")}`,
81
- );
82
79
  await shellCmd(`openclaw onboard ${onboardArgs.map((a) => `"${a}"`).join(" ")}`, {
83
80
  env: {
84
81
  ...process.env,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },