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 namedforge-chat-extension. Add it to the "Justin Secure Only" app (id1383d9f5-9859-484a-be89-64a53122a104) policy set as a Service Auth include (non-interactive). WriteCF_ACCESS_CLIENT_ID=...andCF_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 .servicereturns"forge-session-launcher"(proves the service token passes CF Access AND the injected JWT satisfiesrequire_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 theAuthorizationheader, conditionally add'CF-Access-Client-Id'and'CF-Access-Client-Secret'fromwindow.FORGE_CF_ACCESS_CLIENT_ID/window.FORGE_CF_ACCESS_CLIENT_SECRETwhen 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 fromchrome-extension-forge-chat/, but replaceconfig.jswith a prod variant (FORGE_LAUNCHER_API_BASE='https://chat.justinsforge.com/api'+ the twoFORGE_CF_ACCESS_*globals) and rewritemanifest.jsonhost_permissionsto["https://chat.justinsforge.com/*"]. ReturnStreamingResponse/Responseasapplication/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/downloadreturns a zip whoseconfig.jscontainschat.justinsforge.comand whosemanifest.jsonhost_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. whenlocation.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 || trueis not applicable; instead: restart launcher, loadhttp://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.shso the extension bundle'slauncher.js/launcher.csspick up the Task 2/4 changes and do not drift. Restart theforge-session-launcherservice so the new endpoint is live. - Verification:
bash /home/justinwieb/forge/scripts/forge_chat_ext_sync.shprints "Synced ...", thendiff /home/justinwieb/forge/sites/justinsforge.com/chat/launcher.js /home/justinwieb/forge/chrome-extension-forge-chat/launcher.jsis empty, and the authed/api/extension/downloadfetch (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.jshas the https base + both CF globals,manifest.jsonhost_permissions ishttps://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 -> assertconfig.jscontainshttps://chat.justinsforge.com/apiandFORGE_CF_ACCESS_CLIENT_SECRET, assertmanifest.jsonhost_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/downloadendpoint, the~/.forge-secrets/forge_chat_ext.envsecret, the CF Access service-token policy on app1383d9f5-..., 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.shpasses;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