Skip to content

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.js sets window.FORGE_CHAT_URL, panel.js assigns it to the frame. Both external (MV3 page CSP blocks inline scripts). No bundled SPA , launcher.js/css were deleted from the extension dir.
  • manifest.json: permissions:["sidePanel","declarativeNetRequest"], host_permissions:["http://192.168.86.50:7365/*"] (LAN default), no default_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.py sends Content-Security-Policy: frame-ancestors 'self' chrome-extension://<id> on / and /ui* (const CHAT_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/api from Vector WORKS and is fast.
  • An extension page with host_permissions CAN 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_TOKEN in forge/.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, open chrome://extensions, enable Developer mode, Load unpacked, pick the forge-chat-extension folder. The button is hidden inside the side panel itself (location.protocol === 'chrome-extension:').
  • What it serves: GET /api/extension/download (auth via require_auth, so only reachable through CF Access) zips chrome-extension-forge-chat/ but for prod it (1) rewrites config.js to window.FORGE_CHAT_URL='https://chat.justinsforge.com/ui/', (2) sets manifest.json host_permissions to https://chat.justinsforge.com/* and adds a declarative_net_rules ruleset, and (3) writes rules.json , a DNR modifyHeaders rule that sets CF-Access-Client-Id/Secret on every request to chat.justinsforge.com (all resource types). rules.json is 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_id dd0d6466-ec1e-44ee-a185-4f118263bd7b), added as a non_identity ("Service Auth") policy on the chat.justinsforge.com Access 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 a non_identity policy, rewrite the secret file. The next download picks it up automatically (built per-request). A non_identity policy is required, a plain allow policy 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.

  • 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]]