fix: CORS + Vite host filter + Claude API Key labels for Pi LAN access#113
fix: CORS + Vite host filter + Claude API Key labels for Pi LAN access#113webdevtodayjason wants to merge 2 commits intomainfrom
Conversation
New module dashboard/src/aevp/pi-profile.ts exposes three knobs the AEVP system reads at runtime: - getMaxParticlesCap() → 60 (default 180) — hard ceiling on active particles, layered on top of identity preset density. - getDensityScale() → 0.5 (default 1.0) — multiplier applied to presence.particleDensity so identity presets keep their relative character but cost half as much. - getFrameIntervalMs() → 33 (default 0) — minimum ms between render ticks, i.e. ~30fps cap on the AEVP loop. The profile activates if any of these are set: PI_PROFILE=1 (env) VITE_PI_PROFILE=1 (Vite build env) localStorage "argent.piProfile" = "1" (browser runtime) URL ?piProfile=1 (one-shot) Identity presets and personality modulation (warmth/energy/openness/ formality) are untouched — the orb keeps its character, it just costs ~60% less to draw. Buffer allocation (MAX_PARTICLES = 180 in particles.ts) is unchanged; the profile only caps the runtime active count, so it can be flipped on/off without reallocation. Files: - dashboard/src/aevp/pi-profile.ts (new, 94 lines, pure functions) - dashboard/src/aevp/colorMapping.ts (2-line import + 5-line call site) - dashboard/src/aevp/renderer.ts (1-line import + 5-line frame gate) - dashboard/docs/pi-profile.md (new, usage + verification notes) Use case: ArgentOS dashboard running on a Raspberry Pi 5 with a 7" touchscreen, software rendering, no dedicated GPU. Measured reduction pending — review against a baseline tab before/after enabling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…labels
Three bugs fixed for Argent Lite Pi deployment:
1. dashboard/api-server.cjs — CORS allowlist was hardcoded to localhost
origins only. When the operator accesses :8080 via the Pi's hostname
or LAN IP, the preflight check returned 500 "CORS: origin not
allowed", which the SetupWizard's 10-second timeout caught as
"Saving timed out after 10 seconds. Check gateway/API and try again."
Fix: dynamically add os.hostname() and all non-loopback IPv4
addresses to ALLOWED_ORIGINS at startup. Tested from all 3 origins
(localhost, pi5miniAI, 192.168.100.93) → HTTP 201 on save.
2. dashboard/vite.config.ts — Vite 7's host-based request filtering
returned 403 "This host is not allowed" for non-localhost hostnames.
Fix: `allowedHosts: true` (permits all hosts; safe because the
dashboard is a dev/internal tool, not a public-facing service).
3. dashboard/src/components/SetupWizard.tsx — "Claude Setup Token"
label was wrong. Anthropic no longer allows subscription auth tokens
for API access. Changed to "Claude API Key" with a pointer to
console.anthropic.com/settings/keys. Updated:
- Line 360: card title
- Line 490: input label
- Line 504: placeholder (sk-ant-oat01 → sk-ant-api03)
- Line 669: completion summary
Tested:
curl -X POST http://pi5miniAI:8080/api/settings/auth-profiles \
-H "Origin: http://pi5miniAI:8080" -d '...' → HTTP 201
curl -X POST http://192.168.100.93:8080/api/settings/auth-profiles \
-H "Origin: http://192.168.100.93:8080" -d '...' → HTTP 409 (exists)
curl http://pi5miniAI:8080/ → HTTP 200 (was 403)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR is a TREMENDOUS update that introduces the Pi Profile—a beautiful low-intensity rendering mode that cuts particle caps to 60, scales density by 0.5, and throttles to ~33ms intervals. Believe me, we've also expanded CORS origins, updated Claude API key UI copy, and refined Vite configuration. Absolutely fantastic work. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
dashboard/vite.config.ts (1)
70-77:⚠️ Potential issue | 🟠 MajorLock down that host allowlist — we've got a tremendous security problem here, believe me.
Line 73 is a disaster waiting to happen.
allowedHosts: true? That's giving every Host header in the world access to your dev server. Huge vulnerability. The Vite team—very smart people, by the way—they made this crystal clear in their docs: never, ever do this. DNS rebinding attacks, stolen source code, attackers pivoting to local services. Sad! Combined withhost: trueand that wide-open CORS? You're asking for trouble on shared LANs.This exact issue took down prior Vite versions (GHSA-vg6x-rcgg-rjx6). They learned. You need to learn too. Explicit allowlist of trusted hosts—that's the only way. Make it configurable via env variable with safe defaults. That's winning security.
🔧 The Right Way
+const allowedHosts = (process.env.VITE_ALLOWED_HOSTS ?? "") + .split(",") + .map((h) => h.trim()) + .filter(Boolean); export default defineConfig({ // ... server: { port: 8080, host: true, - allowedHosts: true, + allowedHosts: allowedHosts.length > 0 ? allowedHosts : ["localhost", "127.0.0.1"], cors: { origin: true, credentials: true, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dashboard/vite.config.ts` around lines 70 - 77, The dev server config currently sets server.allowedHosts: true (and host: true) which allows any Host header; change server configuration to use an explicit allowlist sourced from an environment variable (e.g., ALLOWED_HOSTS) with safe defaults (e.g., localhost, 127.0.0.1) and validate/sanitize that list; replace allowedHosts: true with the parsed allowlist, make host configurable (do not default to true) and ensure cors.origin is restricted to the same allowlist or a strict origin env var so only trusted hosts are permitted (refer to server.allowedHosts, server.host, and server.cors in the vite config).
🧹 Nitpick comments (2)
dashboard/docs/pi-profile.md (1)
67-68: Prefer symbol/file references over hard-coded line numbers.These line pointers will drift fast; linking by file + function name is more durable.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dashboard/docs/pi-profile.md` around lines 67 - 68, Replace fragile line-number references with durable file+symbol references: instead of "dashboard/src/aevp/colorMapping.ts:448" and "dashboard/src/aevp/renderer.ts:269", cite the relevant symbols such as the cap and scale call sites in colorMapping (e.g., functions or exported identifiers that perform capping/scaling) and the frame gate function or method in renderer (e.g., the frame gate handler or exported renderFrame/renderLoop symbol). Update the markdown to reference these file paths plus the function/identifier names so readers can find the code even if line numbers change.dashboard/src/components/SetupWizard.tsx (1)
362-365: Make the Anthropic keys URL clickable for faster setup.Right now it’s display-only text. A direct link reduces friction.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@dashboard/src/components/SetupWizard.tsx` around lines 362 - 365, In SetupWizard (the JSX that renders the Anthropic key hint), replace the static code block displaying console.anthropic.com/settings/keys with a clickable link: wrap the existing <code className="text-amber-400/70 bg-white/5 px-1 rounded"> element in an <a> pointing to "https://console.anthropic.com/settings/keys", add target="_blank" and rel="noopener noreferrer", keep the same className styles on the code element, and include an accessible aria-label (e.g., "Open Anthropic API keys in new tab") so users can open the Anthropic keys page directly without losing styling or accessibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@dashboard/api-server.cjs`:
- Around line 50-67: The CORS allowlist currently adds only os.hostname() and
IPs to extraHosts before building origins; add ".local" aliases for the Pi
hostname so browser origins like http://hostname.local:8080 are allowed. Update
the logic that builds extraHosts (using the hostname variable and extraHosts
Set) to also push hostname + ".local" (and optionally hostname + ".local." if
you want trailing dot) into extraHosts before the loop that creates origins,
then continue adding the constructed origins to ALLOWED_ORIGINS exactly as done
now (refer to hostname, extraHosts, and ALLOWED_ORIGINS in the diff).
In `@dashboard/docs/pi-profile.md`:
- Around line 22-24: The docs contradict whether toggling the profile requires
reload; pick the correct behavior (either "no reload needed because buffer sized
by MAX_PARTICLES in aevp/particles.ts" or "activation is computed once and
requires reload") and make all mentions consistent: update the sentences around
the hard allocation note and the activation wording so they all state the chosen
behavior, reference MAX_PARTICLES in aevp/particles.ts when explaining the
no-reload case, and remove or rewrite the lines that claim activation is
computed once (the earlier "requires reload" wording) so the three locations
(current lines ~22-24, ~28-29 and ~38) all match.
In `@dashboard/src/aevp/colorMapping.ts`:
- Around line 449-455: The pi-mode cap calculation sets maxParticles using
getMaxParticlesCap(), getDensityScale(), and presence.particleDensity but later
code clamps to a hard 100 instead of using maxParticles, allowing Pi to exceed
the intended cap; update the later clamp to use maxParticles (or
Math.min(maxParticles, 100) if a secondary safety cap is desired) wherever the
particle count or Pi level is clamped so the computed piCap and density scaling
are actually enforced for functions/variables like maxParticles and the value
derived from presence.particleDensity.
In `@dashboard/src/aevp/pi-profile.ts`:
- Around line 30-36: The code currently reads a non-existent custom global
__ARGENT_VITE_ENV__ (referenced as meta and checked via meta?.[name]) which
breaks VITE_PI_PROFILE activation; replace that lookup with the standard Vite
environment access using import.meta.env (e.g. read import.meta.env[name] or
(import.meta as any).env[name]) wherever meta or __ARGENT_VITE_ENV__ is used so
the check (and the same guards for "0" and empty string) works like other
dashboard modules; update any try/catch around that block to reflect the direct
import.meta.env access and remove the custom global references.
In `@dashboard/src/components/SetupWizard.tsx`:
- Around line 360-365: The UI in SetupWizard shows "Claude API Key" but the save
logic still serializes the Anthropic/Claude auth profile with type: "token",
causing a mismatch; update the serialization in the save handler inside the
SetupWizard component (the function that builds/saves the Anthropic auth/profile
object) so that when the UI mode is the API Key variant it emits the matching
type (e.g., "apiKey") and the correct shape (field names matching the backend
contract), and propagate that change to the other save/serialize spots mentioned
(the other blocks that currently emit type: "token") or alternatively revert the
label to "Setup token" so UI and serialized type remain consistent. Ensure
validation and any downstream consumers expect the new type/shape.
---
Outside diff comments:
In `@dashboard/vite.config.ts`:
- Around line 70-77: The dev server config currently sets server.allowedHosts:
true (and host: true) which allows any Host header; change server configuration
to use an explicit allowlist sourced from an environment variable (e.g.,
ALLOWED_HOSTS) with safe defaults (e.g., localhost, 127.0.0.1) and
validate/sanitize that list; replace allowedHosts: true with the parsed
allowlist, make host configurable (do not default to true) and ensure
cors.origin is restricted to the same allowlist or a strict origin env var so
only trusted hosts are permitted (refer to server.allowedHosts, server.host, and
server.cors in the vite config).
---
Nitpick comments:
In `@dashboard/docs/pi-profile.md`:
- Around line 67-68: Replace fragile line-number references with durable
file+symbol references: instead of "dashboard/src/aevp/colorMapping.ts:448" and
"dashboard/src/aevp/renderer.ts:269", cite the relevant symbols such as the cap
and scale call sites in colorMapping (e.g., functions or exported identifiers
that perform capping/scaling) and the frame gate function or method in renderer
(e.g., the frame gate handler or exported renderFrame/renderLoop symbol). Update
the markdown to reference these file paths plus the function/identifier names so
readers can find the code even if line numbers change.
In `@dashboard/src/components/SetupWizard.tsx`:
- Around line 362-365: In SetupWizard (the JSX that renders the Anthropic key
hint), replace the static code block displaying
console.anthropic.com/settings/keys with a clickable link: wrap the existing
<code className="text-amber-400/70 bg-white/5 px-1 rounded"> element in an <a>
pointing to "https://console.anthropic.com/settings/keys", add target="_blank"
and rel="noopener noreferrer", keep the same className styles on the code
element, and include an accessible aria-label (e.g., "Open Anthropic API keys in
new tab") so users can open the Anthropic keys page directly without losing
styling or accessibility.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: a22cde5c-fabf-4afa-a1fe-51a3bd92a0b6
📒 Files selected for processing (7)
dashboard/api-server.cjsdashboard/docs/pi-profile.mddashboard/src/aevp/colorMapping.tsdashboard/src/aevp/pi-profile.tsdashboard/src/aevp/renderer.tsdashboard/src/components/SetupWizard.tsxdashboard/vite.config.ts
| const hostname = os.hostname(); | ||
| const ifaces = os.networkInterfaces(); | ||
| const extraHosts = new Set([hostname]); | ||
| for (const name of Object.keys(ifaces)) { | ||
| for (const iface of ifaces[name] || []) { | ||
| if (!iface.internal && iface.family === "IPv4") { | ||
| extraHosts.add(iface.address); | ||
| } | ||
| } | ||
| } | ||
| for (const host of extraHosts) { | ||
| for (const port of [8080, 5173]) { | ||
| const origin = `http://${host}:${port}`; | ||
| if (!ALLOWED_ORIGINS.includes(origin)) { | ||
| ALLOWED_ORIGINS.push(origin); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Add .local hostname aliases so Pi hostname access is truly covered.
Very strong direction overall, but right now only os.hostname() is allowlisted. On many Pi/LAN setups the browser origin is http://<hostname>.local:<port>, which can still fail CORS.
Proposed patch
const hostname = os.hostname();
const ifaces = os.networkInterfaces();
- const extraHosts = new Set([hostname]);
+ const extraHosts = new Set([hostname]);
+ if (hostname && !hostname.includes(".")) {
+ extraHosts.add(`${hostname}.local`);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const hostname = os.hostname(); | |
| const ifaces = os.networkInterfaces(); | |
| const extraHosts = new Set([hostname]); | |
| for (const name of Object.keys(ifaces)) { | |
| for (const iface of ifaces[name] || []) { | |
| if (!iface.internal && iface.family === "IPv4") { | |
| extraHosts.add(iface.address); | |
| } | |
| } | |
| } | |
| for (const host of extraHosts) { | |
| for (const port of [8080, 5173]) { | |
| const origin = `http://${host}:${port}`; | |
| if (!ALLOWED_ORIGINS.includes(origin)) { | |
| ALLOWED_ORIGINS.push(origin); | |
| } | |
| } | |
| } | |
| const hostname = os.hostname(); | |
| const ifaces = os.networkInterfaces(); | |
| const extraHosts = new Set([hostname]); | |
| if (hostname && !hostname.includes(".")) { | |
| extraHosts.add(`${hostname}.local`); | |
| } | |
| for (const name of Object.keys(ifaces)) { | |
| for (const iface of ifaces[name] || []) { | |
| if (!iface.internal && iface.family === "IPv4") { | |
| extraHosts.add(iface.address); | |
| } | |
| } | |
| } | |
| for (const host of extraHosts) { | |
| for (const port of [8080, 5173]) { | |
| const origin = `http://${host}:${port}`; | |
| if (!ALLOWED_ORIGINS.includes(origin)) { | |
| ALLOWED_ORIGINS.push(origin); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dashboard/api-server.cjs` around lines 50 - 67, The CORS allowlist currently
adds only os.hostname() and IPs to extraHosts before building origins; add
".local" aliases for the Pi hostname so browser origins like
http://hostname.local:8080 are allowed. Update the logic that builds extraHosts
(using the hostname variable and extraHosts Set) to also push hostname +
".local" (and optionally hostname + ".local." if you want trailing dot) into
extraHosts before the loop that creates origins, then continue adding the
constructed origins to ALLOWED_ORIGINS exactly as done now (refer to hostname,
extraHosts, and ALLOWED_ORIGINS in the diff).
| The hard allocation (`MAX_PARTICLES = 180` in `aevp/particles.ts`) is | ||
| unchanged — the buffer still holds room for 180 — so the profile can | ||
| be flipped on and off without a reload or re-alloc. |
There was a problem hiding this comment.
Docs conflict on reload behavior — tighten this up.
Lines 22-24 say no reload is needed, but Lines 28-29 and 38 say activation is computed once and requires reload. Pick one behavior and state it consistently.
Also applies to: 28-29, 38-38
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dashboard/docs/pi-profile.md` around lines 22 - 24, The docs contradict
whether toggling the profile requires reload; pick the correct behavior (either
"no reload needed because buffer sized by MAX_PARTICLES in aevp/particles.ts" or
"activation is computed once and requires reload") and make all mentions
consistent: update the sentences around the hard allocation note and the
activation wording so they all state the chosen behavior, reference
MAX_PARTICLES in aevp/particles.ts when explaining the no-reload case, and
remove or rewrite the lines that claim activation is computed once (the earlier
"requires reload" wording) so the three locations (current lines ~22-24, ~28-29
and ~38) all match.
| // Pi profile: scale density + apply a hard cap on top of identity preset. | ||
| const piCap = getMaxParticlesCap(); | ||
| const piScale = getDensityScale(); | ||
| const maxParticles = Math.min( | ||
| piCap, | ||
| Math.round(100 * presence.particleDensity * piScale), | ||
| ); |
There was a problem hiding this comment.
Pi hard cap can still be exceeded after this block.
Nice cap calculation here, but Line 486 later clamps to 100, not maxParticles. With high personality energy, Pi mode can rise above 60 again.
🔧 Cap-preserving fix
- particleCount = Math.round(clamp(particleCount * (0.7 + p.energy * 0.6), 3, 100));
+ particleCount = Math.round(clamp(particleCount * (0.7 + p.energy * 0.6), 3, maxParticles));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dashboard/src/aevp/colorMapping.ts` around lines 449 - 455, The pi-mode cap
calculation sets maxParticles using getMaxParticlesCap(), getDensityScale(), and
presence.particleDensity but later code clamps to a hard 100 instead of using
maxParticles, allowing Pi to exceed the intended cap; update the later clamp to
use maxParticles (or Math.min(maxParticles, 100) if a secondary safety cap is
desired) wherever the particle count or Pi level is clamped so the computed
piCap and density scaling are actually enforced for functions/variables like
maxParticles and the value derived from presence.particleDensity.
| try { | ||
| // Vite (import.meta.env is statically available only inside Vite-processed | ||
| // modules; protect with a typeof check so this file compiles under raw tsc) | ||
| const meta = (globalThis as { __ARGENT_VITE_ENV__?: Record<string, string | undefined> }) | ||
| .__ARGENT_VITE_ENV__; | ||
| if (meta?.[name] && meta[name] !== "0" && meta[name] !== "") return true; | ||
| } catch { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching for __ARGENT_VITE_ENV__ definitions/usages..."
rg -n --hidden -C 2 '__ARGENT_VITE_ENV__|defineProperty\(.+__ARGENT_VITE_ENV__|window\.__ARGENT_VITE_ENV__|globalThis\.__ARGENT_VITE_ENV__'
echo
echo "Searching for VITE_PI_PROFILE usage patterns..."
rg -n --hidden -C 2 'VITE_PI_PROFILE|import\.meta\.env'Repository: ArgentAIOS/argentos-core
Length of output: 5494
VITE_PI_PROFILE activation path is totally broken — that custom global doesn't exist.
Lines 33–35 reach for __ARGENT_VITE_ENV__, but it's never injected anywhere in the code. Huge problem. Meanwhile, every other file in the dashboard correctly reads import.meta.env directly. This activation method is completely dead.
Fix it to use import.meta.env like all the rest of the application does:
🔧 Correct env read
- const meta = (globalThis as { __ARGENT_VITE_ENV__?: Record<string, string | undefined> })
- .__ARGENT_VITE_ENV__;
- if (meta?.[name] && meta[name] !== "0" && meta[name] !== "") return true;
+ const viteEnv =
+ typeof import.meta !== "undefined"
+ ? ((import.meta as { env?: Record<string, string | undefined> }).env ?? undefined)
+ : undefined;
+ if (viteEnv?.[name] && viteEnv[name] !== "0" && viteEnv[name] !== "") return true;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| // Vite (import.meta.env is statically available only inside Vite-processed | |
| // modules; protect with a typeof check so this file compiles under raw tsc) | |
| const meta = (globalThis as { __ARGENT_VITE_ENV__?: Record<string, string | undefined> }) | |
| .__ARGENT_VITE_ENV__; | |
| if (meta?.[name] && meta[name] !== "0" && meta[name] !== "") return true; | |
| } catch { | |
| try { | |
| // Vite (import.meta.env is statically available only inside Vite-processed | |
| // modules; protect with a typeof check so this file compiles under raw tsc) | |
| const viteEnv = | |
| typeof import.meta !== "undefined" | |
| ? ((import.meta as { env?: Record<string, string | undefined> }).env ?? undefined) | |
| : undefined; | |
| if (viteEnv?.[name] && viteEnv[name] !== "0" && viteEnv[name] !== "") return true; | |
| } catch { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dashboard/src/aevp/pi-profile.ts` around lines 30 - 36, The code currently
reads a non-existent custom global __ARGENT_VITE_ENV__ (referenced as meta and
checked via meta?.[name]) which breaks VITE_PI_PROFILE activation; replace that
lookup with the standard Vite environment access using import.meta.env (e.g.
read import.meta.env[name] or (import.meta as any).env[name]) wherever meta or
__ARGENT_VITE_ENV__ is used so the check (and the same guards for "0" and empty
string) works like other dashboard modules; update any try/catch around that
block to reflect the direct import.meta.env access and remove the custom global
references.
| <div className="text-white font-medium">Claude API Key</div> | ||
| <div className="text-white/40 text-xs mt-0.5"> | ||
| From Anthropic Max subscription. Run{" "} | ||
| From{" "} | ||
| <code className="text-amber-400/70 bg-white/5 px-1 rounded"> | ||
| claude setup-token | ||
| </code>{" "} | ||
| in terminal. | ||
| console.anthropic.com/settings/keys | ||
| </code> |
There was a problem hiding this comment.
UI says “API Key,” but this path still behaves like legacy setup-token flow.
Lines 360/490/504/669 rebrand setup-token as API key, but the selected mode still serializes as type: "token" in save logic. That mismatch can store the wrong auth profile shape.
🔧 Minimal alignment fix
- setAuthType("setup-token");
+ setAuthType("api-key");- {authType === "setup-token"
- ? "Claude API Key"
+ {authType === "api-key"
+ ? "Claude API Key"
: authType === "minimax-key"Also applies to: 489-505, 669-670
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@dashboard/src/components/SetupWizard.tsx` around lines 360 - 365, The UI in
SetupWizard shows "Claude API Key" but the save logic still serializes the
Anthropic/Claude auth profile with type: "token", causing a mismatch; update the
serialization in the save handler inside the SetupWizard component (the function
that builds/saves the Anthropic auth/profile object) so that when the UI mode is
the API Key variant it emits the matching type (e.g., "apiKey") and the correct
shape (field names matching the backend contract), and propagate that change to
the other save/serialize spots mentioned (the other blocks that currently emit
type: "token") or alternatively revert the label to "Setup token" so UI and
serialized type remain consistent. Ensure validation and any downstream
consumers expect the new type/shape.
Bug
When an operator accesses the dashboard via hostname (
pi5miniAI:8080) or LAN IP (192.168.100.93:8080) instead oflocalhost:8080, the SetupWizard save times out with:Root cause: two separate blocks, both triggered by non-localhost origin.
1. CORS allowlist in api-server.cjs (lines 36–43)
Only
localhostand127.0.0.1origins were permitted. A request fromhttp://pi5miniAI:8080→ 500 "CORS: origin not allowed" → the browser'sfetch()gets a network error → 10-second AbortController fires → shows "Saving timed out."Fix: dynamically add
os.hostname()+ all non-loopback IPv4 addresses toALLOWED_ORIGINSat startup.2. Vite
allowedHostsfilter (vite.config.ts)Vite 7 blocks requests whose
Hostheader isn't recognized → 403 "This host is not allowed." Even thoughhost: truewas set (binds all interfaces), requests by hostname got blocked at the application layer.Fix:
allowedHosts: true(permits all hosts; safe — this is a dev/internal tool).3. "Claude Setup Token" label (SetupWizard.tsx)
Anthropic no longer allows subscription auth tokens. The wizard step said "Claude Setup Token" with
sk-ant-oat01-...placeholder. Changed to "Claude API Key" withsk-ant-api03-...placeholder and a pointer toconsole.anthropic.com/settings/keys.Test evidence
All three origins succeed. 409 = profile already created by the first call. Zero CORS errors. Zero timeouts.
Files changed
dashboard/api-server.cjs— dynamic CORS origin expansion (+25 lines)dashboard/vite.config.ts—allowedHosts: true(+1 line)dashboard/src/components/SetupWizard.tsx— label + placeholder changes (4 locations)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation