Skip to content

Plan: chat.justinsforge.com "Get the extension" download + works-anywhere Forge Chat extension build

URL: https://mkdocs.justinsforge.com/memory/plans/chat-extension-download-works-anywhere-2026-07-04/

Date: 2026-07-04 Approved spec: Add a download button to the chat.justinsforge.com launcher UI that serves a ready-to-run "works anywhere" build of the Forge Chat Chrome extension. The build is pre-wired to https://chat.justinsforge.com/api and carries an embedded Cloudflare Access service token, so the docked side panel authenticates on any wifi with no token to paste. New GET /api/extension/download endpoint on forge_session_launcher_api.py (behind existing require_auth) zips chrome-extension-forge-chat/ in-memory, overriding config.js + manifest.json host_permissions to the prod domain and injecting the secret. launcher.js (single source of truth, shared by web widget and extension) attaches CF-Access-Client-Id/Secret headers only when config globals are set, so the public web widget is unaffected. Service token stored in ~/.forge-secrets/forge_chat_ext.env, never committed. Out of scope: - Cookie-only (interactive CF Access login) auth path for the extension - .crx packaging / Chrome Web Store publishing - Auto-loading the extension into Chrome (Chrome requires a manual "Load unpacked") - Extension auto-update mechanism - Changing the existing committed LAN build (config.js stays http://192.168.86.50:7365/api as the home default)

Task list

Task 1: Mint the Cloudflare Access service token and store the secret

  • Files: ~/.forge-secrets/forge_chat_ext.env (new, chmod 600, outside repo)
  • What: Via the Cloudflare API (token at ~/.forge-secrets/cloudflare.env), create an Access service token named forge-chat-extension. Add it to the "Justin Secure Only" app (id 1383d9f5-9859-484a-be89-64a53122a104) policy set as a Service Auth include (non-interactive). Write CF_ACCESS_CLIENT_ID=... and CF_ACCESS_CLIENT_SECRET=... to the secret file. The secret is shown by the API only once at creation.
  • Verification: curl -fsS -H "CF-Access-Client-Id: $(grep CF_ACCESS_CLIENT_ID ~/.forge-secrets/forge_chat_ext.env | cut -d= -f2)" -H "CF-Access-Client-Secret: $(grep CF_ACCESS_CLIENT_SECRET ~/.forge-secrets/forge_chat_ext.env | cut -d= -f2)" https://chat.justinsforge.com/api/info | jq .service returns "forge-session-launcher" (proves the service token passes CF Access AND the injected JWT satisfies require_auth). Run from ctx_execute (curl is sandbox-blocked in Bash).
  • Commit: none (secret only; nothing enters the repo this task)

Task 2: Teach launcher.js to attach CF Access service-token headers when configured

  • Files: /home/justinwieb/forge/sites/justinsforge.com/chat/launcher.js
  • What: In the api() helper, after the Authorization header, conditionally add 'CF-Access-Client-Id' and 'CF-Access-Client-Secret' from window.FORGE_CF_ACCESS_CLIENT_ID / window.FORGE_CF_ACCESS_CLIENT_SECRET when both are non-empty. No globals on the public web widget => no behavior change there.
  • Verification: node -e "global.window={}; global.localStorage={getItem:()=>''}; const fs=require('fs'); const s=fs.readFileSync('/home/justinwieb/forge/sites/justinsforge.com/chat/launcher.js','utf8'); if(!/FORGE_CF_ACCESS_CLIENT_ID/.test(s)) throw new Error('header wiring missing'); console.log('ok')"
  • Commit: feat(chat-launcher): attach CF Access service-token headers when config globals set

Task 3: Add the /api/extension/download endpoint

  • Files: /home/justinwieb/forge/scripts/forge_session_launcher_api.py
  • What: Add @app.get("/api/extension/download", dependencies=[Depends(require_auth)]). Read ~/.forge-secrets/forge_chat_ext.env (fail loud with HTTP 500 if missing). Build a zip in-memory from chrome-extension-forge-chat/, but replace config.js with a prod variant (FORGE_LAUNCHER_API_BASE='https://chat.justinsforge.com/api' + the two FORGE_CF_ACCESS_* globals) and rewrite manifest.json host_permissions to ["https://chat.justinsforge.com/*"]. Return StreamingResponse/Response as application/zip, Content-Disposition: attachment; filename="forge-chat-extension.zip". Read-only, idempotent.
  • Verification: python3 -c "import ast,sys; src=open('/home/justinwieb/forge/scripts/forge_session_launcher_api.py').read(); ast.parse(src); assert '/api/extension/download' in src; print('parses+route present')" then restart the launcher and confirm the authed fetch (Task 1 headers) to /api/extension/download returns a zip whose config.js contains chat.justinsforge.com and whose manifest.json host_permissions is the https domain (unzip in a temp dir, grep).
  • Commit: feat(chat-launcher): /api/extension/download serves works-anywhere extension build

Task 4: Add the "Get the extension" download button to the widget UI

  • Files: /home/justinwieb/forge/sites/justinsforge.com/chat/index.html, /home/justinwieb/forge/sites/justinsforge.com/chat/launcher.css, /home/justinwieb/forge/sites/justinsforge.com/chat/launcher.js
  • What: Add a control (a labelled download button/link, matching the existing .fl-info-icon / gold-accent styling) that points at ${BASE.replace('/api','')}/api/extension/download (a plain anchor works: the browser sends the CF Access cookie natively). Include a one-line tooltip/blurb explaining "download, unzip, Load unpacked in Chrome, works on any wifi." Keep it out of the extension's own side panel (hide when running inside the extension, i.e. when location.protocol === 'chrome-extension:') so the panel doesn't offer to download itself.
  • Verification: bash /home/justinwieb/forge/scripts/forge_preview_site.sh chat 2>/dev/null || true is not applicable; instead: restart launcher, load http://127.0.0.1:7365/ui/ and confirm the button renders and its href resolves to /api/extension/download. Screenshot via /screenshot http://127.0.0.1:7365/ui/ for visual confirm.
  • Commit: feat(chat-launcher): add Get-the-extension download button to launcher UI

Task 5: Sync the shared assets into the extension bundle and restart the service

  • Files: /home/justinwieb/forge/chrome-extension-forge-chat/launcher.js, /home/justinwieb/forge/chrome-extension-forge-chat/launcher.css (regenerated by the sync script)
  • What: Run scripts/forge_chat_ext_sync.sh so the extension bundle's launcher.js/launcher.css pick up the Task 2/4 changes and do not drift. Restart the forge-session-launcher service so the new endpoint is live.
  • Verification: bash /home/justinwieb/forge/scripts/forge_chat_ext_sync.sh prints "Synced ...", then diff /home/justinwieb/forge/sites/justinsforge.com/chat/launcher.js /home/justinwieb/forge/chrome-extension-forge-chat/launcher.js is empty, and the authed /api/extension/download fetch (Task 1 headers) still returns HTTP 200 zip after restart.
  • Commit: chore(chat-ext): sync launcher assets into extension bundle

Task 6: End-to-end verification of the works-anywhere build

  • Files: none (verification only)
  • What: Download the zip through the live endpoint, unzip it, and statically confirm the build is correct: config.js has the https base + both CF globals, manifest.json host_permissions is https://chat.justinsforge.com/*, and the CF service token (Task 1 headers embedded in that config.js) authenticates against /api/info. This is the "test the path before claiming done" gate; a full off-LAN Chrome load happens on Justin's Mac.
  • Verification: ctx_execute script: authed GET /api/extension/download -> save zip -> unzip to temp -> assert config.js contains https://chat.justinsforge.com/api and FORGE_CF_ACCESS_CLIENT_SECRET, assert manifest.json host_permissions https, then replay those creds against /api/info -> service == forge-session-launcher. Print PASS/FAIL.
  • Commit: none (verification only)

Task 7: Register and document

  • Files: /home/justinwieb/forge/memory/general/reference_forge_chat_extension.md (update: new download path + works-anywhere build + service-token auth), ~/.claude/projects/-home-justinwieb-forge/memory/MEMORY.md (touch the existing Forge Chat ext index line if wording changes), /home/justinwieb/forge/scripts/forge_session_launcher_api.py (endpoint already registered in Task 3; no-op here)
  • What: Document the /api/extension/download endpoint, the ~/.forge-secrets/forge_chat_ext.env secret, the CF Access service-token policy on app 1383d9f5-..., and how to rotate/revoke the token. Note the LAN build remains the committed default. Run the eval harness.
  • Verification: bash /home/justinwieb/forge/scripts/forge_eval_run.sh passes; grep -q 'api/extension/download' /home/justinwieb/forge/memory/general/reference_forge_chat_extension.md
  • Commit: docs(chat-ext): document works-anywhere download build + service-token auth