Forge Chat Chrome extension (docked side panel)¶
URL: https://mkdocs.justinsforge.com/memory/general/reference_forge_chat_extension/
Toolbar extension that opens the Forge session launcher as a docked Chrome
side panel (Chrome 114+). Lives at chrome-extension-forge-chat/. Replaced the
earlier floating-window build (2026-07-02) after a true dropdown proved
impossible for the CF-Access-fronted site.
THIN SHELL rebuild (2026-07-04) , auto-updates from the website¶
Why: MV3 bans extension pages from loading/executing remote JS, so the old
build bundled launcher.js/css and every launcher change forced a manual
re-download + reload on each machine. Rebuilt so the panel is now a full-bleed
iframe of the LIVE app: all JS/CSS/HTML come from the server, so a website change
shows up on the next panel reload with no reinstall, ever.
sidepanel.html: just an<iframe id="app">filling the panel.config.jssetswindow.FORGE_CHAT_URL,panel.jsassigns it to the frame. Both external (MV3 page CSP blocks inline scripts). No bundled SPA ,launcher.js/csswere deleted from the extension dir.manifest.json:permissions:["sidePanel","declarativeNetRequest"],host_permissions:["http://192.168.86.50:7365/*"](LAN default), nodefault_popup. Carries a fixed"key"so the extension id is stable:oolcpdakkjdlmodlinblbalhaodhiflb(private key~/.forge-secrets/forge_chat_ext_key.pem, chmod 600). The id is load-bearing: the app's framing header names it.background.js: unchanged ,setPanelBehavior({openPanelOnActionClick:true}).- Framing header:
forge_session_launcher_api.pysendsContent-Security-Policy: frame-ancestors 'self' chrome-extension://<id>on/and/ui*(constCHAT_EXT_ID). Chrome honours CSP frame-ancestors over X-Frame-Options, so this is what lets the panel iframe the app and blocks everyone else. Verified through the CF tunnel (header survives, 200 with token). - One-time cost: getting onto the thin shell needs ONE last re-download + reload. After that, launcher changes auto-load.
Transport + auth (the crux)¶
The launcher (scripts/forge_session_launcher_api.py) binds 0.0.0.0:7365 and
require_auth() accepts either a CF Access JWT or Authorization: Bearer
<SESSION_LAUNCHER_TOKEN> (the "tailnet/localhost fallback"). The extension uses
the bearer path and hits Console directly over the LAN:
- Tailscale Vector->Console is BLOCKED (ACL = Finn+Console only); do not rely on it.
- LAN
http://192.168.86.50:7365/apifrom Vector WORKS and is fast. - An extension page with
host_permissionsCAN fetch the plaintext-http LAN API (mixed-content / PNA does NOT block it; verified by spike 2026-07-02, returns HTTP 403 on a bad token). - Token =
SESSION_LAUNCHER_TOKENinforge/.env(slt_...). Pasted once into the panel's token field, stored in per-origin localStorage on each machine. Never committed.
If Console moves or you use a machine off this LAN, update
FORGE_LAUNCHER_API_BASE in sidepanel.html + host_permissions in
manifest.json. Fallback transport if LAN ever dies: CF Access service token
against https://chat.justinsforge.com/api (https, no mixed content), documented
in the plan.
Works-anywhere build + one-click download (2026-07-04)¶
The committed extension is the LAN build (config.js -> http://192.168.86.50:7365/api),
for use at home. For any other network there is a works-anywhere build you
download from the launcher UI itself:
- Get it: on
chat.justinsforge.com, click the gold "⤓ Extension" button in the top bar (its(i)shows the Load-unpacked steps). Unzip, openchrome://extensions, enable Developer mode, Load unpacked, pick theforge-chat-extensionfolder. The button is hidden inside the side panel itself (location.protocol === 'chrome-extension:'). - What it serves:
GET /api/extension/download(auth viarequire_auth, so only reachable through CF Access) zipschrome-extension-forge-chat/but for prod it (1) rewritesconfig.jstowindow.FORGE_CHAT_URL='https://chat.justinsforge.com/ui/', (2) setsmanifest.jsonhost_permissionstohttps://chat.justinsforge.com/*and adds adeclarative_net_rulesruleset, and (3) writesrules.json, a DNRmodifyHeadersrule that setsCF-Access-Client-Id/Secreton every request tochat.justinsforge.com(all resource types).rules.jsonis the only place the secret lives; it exists only in the built zip, never in the source tree. Fails loud (HTTP 500) if the secret is missing. - Auth path (thin shell): the iframe's document load + every subresource/API
call goes to
chat.justinsforge.com; the DNR rule injects the service-token headers, CF validates at the edge, no interactive Access login and no reliance on third-party cookies. The public web widget is unaffected (it has no extension, so no DNR) and keeps riding the interactive CF Access login cookie. - Secret: CF Access service token
forge-chat-extension(token_iddd0d6466-ec1e-44ee-a185-4f118263bd7b), added as anon_identity("Service Auth") policy on thechat.justinsforge.comAccess app (1383d9f5-9859-484a-be89-64a53122a104). Client id/secret in~/.forge-secrets/forge_chat_ext.env(chmod 600, never committed). - Rotate/revoke: delete the token in CF Zero Trust > Access > Service Auth (or
via API
DELETE /accounts/{acct}/access/service_tokens/{id}), mint a new one, add it to the same app as anon_identitypolicy, rewrite the secret file. The next download picks it up automatically (built per-request). Anon_identitypolicy is required, a plainallowpolicy still forces the interactive login page for service tokens.
Single source of truth¶
sites/justinsforge.com/chat/launcher.{js,css} + index.html are canonical and
served live by the launcher API's StaticFiles mount (UI_DIR). The thin-shell
extension iframes that, so launcher/CSS/HTML changes need NO extension action ,
they ship on page reload. scripts/forge_chat_ext_sync.sh --push now only pushes
the shell files (manifest/background/sidepanel/config/panel.js) to
vector:forge-chat-ext/, needed only when a shell file changes (rare).
Deploy¶
Extension files must live on the Chrome machine. Vector (home LAN) copy:
C:\Users\Justi\forge-chat-ext (LAN build, loaded unpacked; push via
forge_chat_ext_sync.sh --push). Any off-LAN machine (Sol, a laptop at a hotel):
use the ⤓ Extension download button above, no manual file copy or LAN needed.
Reload the card after any update.
Related¶
- Plan:
memory/plans/forge-chat-side-panel-2026-07-02.md - Launcher API surface + auth:
scripts/forge_session_launcher_api.py - [[reference_session_launcher]] | [[reference_vector_session_launcher]]