A 401/403 with an HTML error page (nginx) showed as "HTTP 401: 401
Authorization Required 401 Authorization Required nginx". Now: "The endpoint
wants an API key: enter it in ⚙" before one is set, "The endpoint refused
the API key (...)" after.
On isokit.buildfor.life, entering https://inference.p0.contact/v1 in the
agent window's settings made the page call the inference server directly,
which browsers refuse (its nginx answers the CORS preflight with 401):
"The browser could not reach ... (Failed to fetch)".
- the relay meta names its upstream (data-upstream; build_web.py
--agent-upstream, the deploy passes the inference server): that URL now
goes through the relay and is saved as "follow the site's default"
- an unreachable endpoint says what to do: the relay's URL on a site with
one, else isokit studio or isokit.buildfor.life
- check_web.py: relayed upstream and the unreachable message
✦ Agent in the Studio's top bar opens a floating chat window. The model
(any OpenAI-compatible endpoint with tool calling; default B4L's inference
server, https://inference.p0.contact/v1) gets the 15 MCP tools plus two page
tools (select_object, set_view) and the Studio's state (selection, level,
rotation) in its system prompt. Answers and reasoning stream in; tool calls
show as rows with arguments and results; object ids link to the object;
Stop ends a turn; Undo takes the agent's edits back. The window moves by
its title bar and resizes at its top-left corner.
- studio/agent.js: shared by both front ends (waits for studio.js's
isokit:studio event); MCP over /mcp or isokit.mcp in the web Studio
- isokit studio: /api/agent/{config,models,chat} relay the calls on their
own threads (ureq, chunked and flushed per read), so the key stays on the
machine and the endpoint needs no CORS; settings from ISOKIT_LLM_URL /
_KEY / _MODEL or agent.json in the user's config folder
- web Studio: calls the endpoint from the page with a key kept in the
browser; build_web.py --agent-url names a same-site relay as default; the
deploy builds with /llm/v1, which deploy/Caddyfile forwards to the
inference server (its nginx refuses the CORS preflight)
- tools/mock_llm.py + tools/agent_checks.py: the agent window checks in both
check_studio.py and check_web.py
The status bar ends with "isokit <engine version>"; the web build adds
the commit (linked on Gitea), its date as a tooltip and a "+" when built
from a tree with uncommitted changes (build_web.py writes a
<meta name="isokit-build"> from GITHUB_SHA or git). /api/meta now carries
"engine". studio/favicon.svg (isometric cube in the accent green) is
served by `isokit studio` (also on /view) and copied into the web build.
check_web.py and check_studio.py check both.
Gitea Actions workflow builds dist/web (rustup + wasm32 target, prebuilt
wasm-bindgen CLI at the Cargo.lock version, tools/build_web.py) and
publishes it to /var/www/isokit.buildfor.life on s1 with the blog's
two-directory rename swap and the same VPS_* secrets as cnx_vinyl.
deploy/Caddyfile mirrors the vhost applied on s1 (no-cache so the
unversioned wasm glue never goes stale at Cloudflare).
A standalone "Rust stable GNU" install ahead of ~/.cargo/bin made cargo
fail with E0463 (no wasm32 std). The script now puts CARGO_HOME/bin first,
checks for rustup and the wasm32 target, and says what to install.
- isokit-wasm: wasm-bindgen build of the engine; a project in memory behind
the same JSON API as `isokit studio`, plus the MCP tools
- isokit-project: Store trait (FsStore folder, MemStore memory) under a
generic Workspace; Project::from_files; shared Studio API module (api.rs)
used by serve.rs and the WASM build; web_time for clocks in the browser
- studio.js runs on a pluggable backend (window.IsoKitBackend); no Studio
keys while a dialog is open
- web/: projects in IndexedDB, new/demo/delete, zip export/import (stored and
deflated), folder import by drop or picker, folders opened live via File
System Access with outside edits followed, code editor, page build and
preview, two tabs in step, isokit.mcp() and WebMCP registration
- tools/build_web.py -> dist/web (static), tools/check_web.py (44 checks)