openvibers/openvibe.extensions AGENTS.md
AGENTS.md
AGENTS.md — OpenVibe.Extensions
Project Overview
OpenVibe.Extensions is the home of OpenVibe's first-party client surfaces (ADR-023): the OpenVibe Companion, a
Manifest V3 browser extension for Chromium and Firefox, plus the kiosk companion and the Raspberry Pi hardware
scripts. There is no server, port, domain or database in this repository; the extension talks to first-party
services from the person's browser.
- Package:
openvibe-extensions0.9.2 (private) · Runtime: Node ≥22 for build/test · License: MIT - Read first: [README.md](README.md) (authoritative on permissions and privacy), [store/privacy.md](store/privacy.md),
[store/listing.md](store/listing.md), [SECURITY.md](SECURITY.md), [CHANGELOG.md](CHANGELOG.md).
Network context: the Companion calls OpenVibe.Coupons (public API; cpx_ install token), OpenVibe.Network (Dock
state, OAuth), OpenVibe.Chat (Dock Chats), OpenVibe.Codes (Dock agent view), OpenVibe.Mail (masks, with the person's
bearer), OpenVibe.Locker (encrypted logins) and OpenVibe.Live (the agent view's embed). Its hand-off buttons open
OpenVibe.Rent, OpenVibe.Watch, OpenVibe.Food and OpenVibe.Reviews in a new tab. It is a client: it owns no account,
coupon, mail, locker or Dock data.
Commands
npm test # every test/*.test.js; no browser needed, local stubs
npm test -- manifest # only files whose name contains the word
npm test -- --strict # a skipped test fails the run
npm run pack # dist/openvibe-companion-<version>.zip (load unpacked; API https://openvibe.coupons)
npm run pack:stores # dist/…-chrome.zip and …-firefox.zip (store uploads, runs the audit first)
npm run pack:dev # a local API build (http://localhost:4850, dev only)
npm run lint:firefox # Mozilla web-ext lint, warnings as errors (CI runs it)
npm run icons # regenerate browser/icons/
node scripts/check-panel.js # the side panel in headless Chrome (also test/panel.test.js)
node scripts/check-autoapply.js # autoapply on fixture checkouts in headless Chrome
node test/locker-browser.js # optional: Playwright/Chromium (set PLAYWRIGHT_MODULE), 12 screenshots, clipboard
No dependencies for the extension or the test runner; tests/scripts use Node's standard library. There is no
build step for the shipped code: the zip is exactly browser/.
Architecture at a Glance
browser/manifest.json— MV3 for Chromium (minimum 114) and Firefox (gecko id, strict 128); permissions
activeTab,identity,scripting,storage,alarms,clipboardWrite, and Chrome-onlyoffscreen/sidePanel;
host permissions only for the named OpenVibe origins; the CSP (no remote script, no inline script, frames only Live).browser/background.js— the service worker; wireslib/mask-service.jsand the Dock/agent/mail/locker modules
tobrowser.*.browser/panel.html+panel.js+panel.css— the OpenVibe Dock in the side panel (shared/is the released
OpenVibe.Shared Dock, bundled because the CSP allows no remote script); tabs Vibe/Agents/Chats/Activity; the Agents
tab reads the person's token from Codes (GET /api/v1/me/agent-view) and embeds Live.browser/popup.html+popup.js— DOM wiring only;lib/controller.jsholds the coupon logic (tested in Link);
lib/format.jsthe wording;lib/api.jsthe only Coupons client.browser/options.html+options.js— connect/disconnect acpx_token, grant the API host permission, the
pasted-token fallback and sign-out.browser/lib/—config.js(origins, scopes, keys),host.js(URL → hostname, everything else dropped),
handoff.js(cleaned Rent/Watch/Food/Reviews URLs),autoapply.js(injected coupon-box runner),
rating.js/listing.js(injected, read-only),platform.js(chrome.*/browser.*adapter),
auth.js(PKCE OAuth, refresh, sign-out),network-api.js(Dock state),mail-api.js/mask*.js,
locker-*.js(envelope, vault, fill, clipboard, passwords),controller.js,format.js.browser/shared/— the vendored Dock/SDK browser bundle (dock*.js,ov-locker-core.js,kit.css, glyphs,
brands). Treat it as a release: update from the pinned Shared/SDK version, do not hand-edit crypto.kiosk-companion/— the Firefox new-tab/history bridge for OpenVibe.Live's/kiosk(moved from Live).hardware/— the Raspberry Pi streamer/controller scripts (moved from Live;streamer.py,controller.py).scripts/—audit.js(the permission audit),pack.js(load/store zips, reproducible),check-panel.js,
check-autoapply.js,lint-firefox.js,icons.js; tests in [test/](test/).store/—listing.md,privacy.md,screenshots.md: the exact store fields/disclosures. The owner uploads;
never upload from CI.
Permissions, Privacy and Credentials
- Only four buttons touch a page, and only after a click (
activeTab): "Try these codes" (lib/autoapply.js),
"Save its rating" (lib/rating.js), "Keep on my Rent board"/"Check the rent" (lib/listing.js). No content
scripts, noexternally_connectable, no web-accessible resources, no<all_urls>, notabs/cookies/webRequest. - The Coupons part sends only the hostname (
lib/host.jsdrops path/query/fragment/port/credentials), with
credentials: 'omit',referrerPolicy: 'no-referrer',redirect: 'error'. Never send page content or the full URL. - A
cpx_token is an install token withcoupons.lookupand optionallycoupons.report; it is not a Network
session. Refuse to store anything that is notcpx_. The Dock's OAuth is a public client (companion, PKCE S256,
no secret) with scopesdock activity chat.read agent.view mail.mask.manage locker.login.manage run.fill.approve;
the access token stays instorage.session(memory), the rotating refresh token instorage.local(this device,
never sync). Sign-out revokes at the Network. - Locker: the popup unlocks with the passphrase/recovery kit (a passkey belongs to openvibe.locker, so it offers
Open Locker); keys live only instorage.session/background memory, never local/sync; lock now, account
change, browser restart and 15 idle minutes clear them. Logins match the exact origin (scheme/host/port); fill
requires a click, refuses ambiguous forms, re-checks the origin before writing, and never runs on page load.
Copy clears the clipboard 30 s after the latest copy (does not read it). - Mail masks: only OpenVibe.Mail's
/api/v1/maskswith the person's bearer and themail.mask.managescope; a mask is
never offered on anopenvibe.*site and none is created until the person presses the button; a site with a mask
reuses it, never a second one. - CSP is a guarantee:
default-src 'none',script-src 'self', styles only'unsafe-inline', images'self' blob:,
frames only Live, object/base none. Noeval, no remote code, noinnerHTMLin the extension's own code. - Host permissions and
connect-srcmust agree withlib/config.jsfor every origin; the audit fails if one widens.
Key Pitfalls
- Never widen the manifest, host permissions, CSP or page access. Any extra permission, origin, content script,
web-accessible resource,externally_connectable, optional permission,unsafe-evalor remote script is a privacy
regression and fails [test/manifest.test.js](test/manifest.test.js). Add a feature within the existing surface. - Never store a token in
storage.sync. Only thecpx_install token (local), the Dock access token (session)
and the rotating refresh token (local) exist; the old pastednetworkTokenis a temporary fallback only. - Hand-offs open a visible new tab on an allow-listed
https://openvibe.<tld>origin with a cleaned URL (no
credentials/fragment/tracking); nothing opens when the popup opens, andtabs.openrefuses any other origin. - No page content is sent except the one read the person asked for;
test/privacy.test.jsinspects every request
on checkout URLs full of personal data. - Vendored
shared/is a release, not source: keep the SDK/Shared version and the crypto framing
(locker-payload.js/locker-passwords.jsfrom Locker9c2744e) compatible; runnpm test -- locker,
npm run pack:stores,npm run lint:firefox. - Firefox MV3 background pages are non-persistent: a clipboard alarm must back up an in-memory timer; Chrome uses
the documented offscreen clipboard document. Do not assume a long-lived page. - The Dock's agent view is behind two server-side gaps: Codes' session check and Live's
frame-ancestorsdo not
yet accept the extension/Dock credential, so the view is bundled and tested against a stub but hidden in
production until Codes or Network accepts that token. Do not fake it on. - Version: bump
browser/manifest.jsonfor every store upload; stores refuse a version they have seen. - Never upload from CI.
pack:storesbuilds; the owner uploads with [store/listing.md](store/listing.md),
[store/privacy.md](store/privacy.md) and [store/screenshots.md](store/screenshots.md).
Testing
npm test runs each file in its own process with local stubs and fake browser APIs: the manifest/permission audit,
API client, privacy on checkout URLs, hand-offs, the popup controller, pack reproducibility, the Dock's network API,
auth (PKCE/refresh/sign-out), messages, mask rules/service/router, Locker payload/vault/passwords/fill, and the
headless-Chrome panel check (skips without Chrome). scripts/check-autoapply.js runs the injected coupon runner on
real checkout fixtures; test/locker-browser.js is the optional Playwright check (screenshots + clipboard). A skipped
test is not a pass; --strict makes one fail. Always node --check changed JS.
Documentation
- [README.md](README.md) — purpose, Companion features, what it can see/send/stores, the install token, Dock in the
side panel, layout, tests, packing, store release, dependencies, Locker passwords. - [store/listing.md](store/listing.md), [store/privacy.md](store/privacy.md), [store/screenshots.md](store/screenshots.md).
- [kiosk-companion/README.md](kiosk-companion/README.md), [hardware/README.md](hardware/README.md).
- [CHANGELOG.md](CHANGELOG.md), [SECURITY.md](SECURITY.md).