<!-- OpenSmartRoute: Browser extension. https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/EXTENSION -->
# Browser extension

The OpenSmartRoute browser extension is a **pre-send advisor** for the chat sites people already use -
ChatGPT, Claude, Gemini, Microsoft Copilot and Perplexity. While you type, a small card next to the
composer shows what the request is, how many tokens it carries, whether it contains personal details or
pasted secrets, which kind of model it deserves and what it would cost across the OpenSmartRoute
catalogue. Everything runs on-device by default; the platform is only involved when you ask for it.

Get it from the [product page](https://opensmartroute.ai/extension) (store listing or the packaged download for
your browser). Chrome and Edge share one package (Manifest V3); Firefox has a build of its own.

## What the card shows

**The card is quiet by default.** It opens by itself only when there is a reason to interrupt you: personal
details or a key in the draft, a jailbreak pattern, a held send, a model that will not do what the draft needs, a
draft near the model's context limit, or a cheaper model that fits and saves at least a cent on this request.
Everything else - small slips, tips, prices for their own sake - waits in the pill by the field (see *Anywhere you
write*) until you click it. Close the card and it stays closed for that draft.

The card leads with **one verdict line** that is always visible - *Nothing flagged*, *2 personal details in
your draft*, *An API key is in your draft*, *GPT-5 Thinking is more than this needs*, *3 small slips to fix* - with
an icon for its tone (shield, alert, tip, check) and a live region so screen readers hear the change. The
details underneath open by themselves only when there is something to act on; otherwise they stay folded until you
click the chevron, and your choice is remembered for that site (Settings › Checks › *Show the card's details by
default* opens them everywhere). Every detail is one row: an icon, one sentence in plain words, at most one button.

| Row | Source | What you can do |
|---|---|---|
| What the router sees (task, tokens, the kind of model it needs - *a quick model is enough*, *a standard model fits*, *needs a thinking model*) | On-device signals (a TypeScript port of the router's heuristics) | Read it |
| Personal details (email, phone, card, IBAN, SSN, Aadhaar, IP, API keys and tokens) | On-device patterns, the same list the platform's guard uses | **Redact** swaps them in the composer for `<EMAIL_1>`-style placeholders; **Undo** puts the original back for 30 seconds. While the send guard holds Enter the row also offers **Redact and send** and **Send anyway** |
| The model the site has selected | The picker label matched against the catalogue and priced for this draft | *X fits this request, about $0.002*; *X is more than this needs - Y does it for about $0.0003 (-90%)* when a lighter model of the same vendor fits; *X may not be enough* when the draft needs reasoning. Switch in the site's picker |
| Small slips, filler | On-device writing checks | **Fix** repairs typos, doubled words and capitals; **Trim** removes filler and repeats. Tips that are not mistakes - a missing audience or format, a vague ask - sit behind one line, *N tips for a better answer*; open it and **Add** appends the missing line |
| Your router's pick and price | Connected only: an on-device estimate over the catalogue, or the workspace's quote (`POST /api/v1/estimate`) when *Send my draft to get prices* is on | The pick with its price and, when the site's own model is known and dearer by at least half a cent, *saves $0.012 vs GPT-5 Thinking* - a real comparison, never a percentage against a model you were not going to use. One line for the cheapest and the best, **Compare all** for the full list, *Quoted by* followed by the workspace's name in the footer. An anonymous quote that hit the rate limit says when to retry and offers **Connect** |
| Improve with your router | Connected only | **Proofread**, **Make clearer**, **Shorten**, **Continue** - the result is shown first; **Use this** replaces the draft |
| Connect your workspace | Signed out | One button to Settings; the public catalogue is never shown as if it were your models |

The extension **reads only the draft in the composer** and the model label. It never reads or changes
what the site sends, never blocks a request and never automates the site's UI beyond replacing your own
text when you click Redact, Fix, Trim or Use this - and sending it when you click *Send anyway* or *Redact and
send* on a held draft. On sites that are not one of the five chat sites the card says *draft only*: the
conversation on the page is never read. When a chat site changes its layout and the built-in selectors no longer
find the box, the card carries on through the general-purpose selectors and the popup says so.

## Anywhere you write, anything you select

The extension behaves like a writing assistant and an AI sidebar at the same time:

- **Writing widget** - a small pill sits in the bottom-right corner of the field you are typing in, in any text
  field once *Help in every text field* is on and on the chat sites out of the box. It has three controls:
  - the **sparkle** opens the *commands popover*: one box (*What can OpenSmartRoute help you with?*) with the send
    arrow inside it (grey until there is something to send, `Enter` sends, `Shift`+`Enter` a new line) whose
    instruction runs on the selection inside the field, or the whole field, through your router. The answer
    **streams**: the result panel opens the moment you send, shimmering lines stand in until the first words arrive,
    the text grows with a caret, and **Stop** keeps what has come so far. When it is done: **Insert** / **Replace
    selection**, **Copy** and the target and cost that answered. *Starter ideas* -
    **Rewrite this text** (three variants in a *Rephrase / Simplify / More formal / More casual / Shorten / Expand*
    menu, the changed words marked, **Show more** for another three, one click puts the pick into the field),
    **Fix spelling and grammar**, **Make it shorter**, **Summarise**, **Translate**, **Draft a reply**, **Continue
    writing**. Signed out it offers **Connect** instead;
  - the **score** - on the chat sites a **count**: how many things are worth a look before you send (personal
    details, slips Fix can repair, a model worth changing, a jailbreak pattern); the pill stays empty when there
    is nothing. Everywhere else a 0-100 *writing score* computed on the device from the same checks the card runs
    (spelling, clarity, delivery, style, terms, privacy; personal details and secrets cost the most). Its
    colour is the risk: green, amber for personal details, red for secrets or financial identifiers - and its
    label says the same in words. Click it to open or close the card; the card also opens by itself for the reasons
    listed under *What the card shows*;
  - the **power** button (shown on hover) asks *Disable OpenSmartRoute suggestions on this site?* - **Disable
    until next visit** (this browser session), **Disable forever** (the domain joins *Turned off on these sites*
    in Settings, removable there) or **Keep it on**. The popup's switch for the active tab does the same. Every
    switch - the power button, the popup, the chat-site switches and the *Help in every text field*, *Where the
    widget appears* and *Turned off on these sites* controls in Settings - reaches the open tabs at once: the widget
    goes or comes back without a reload.
- **Selection actions** - select at least a dozen characters of text on any enabled page (outside a field you
  are editing) and, once the mouse settles, a rounded toolbar appears above it: *Explain*,
  *Summarise*, *Rewrite*, *Translate*, *Fix grammar* and *Ask*. Each runs through the sidebar with the selection
  attached, so the router picks the model and you see the decision under the answer. The same actions are in the
  right-click menu, together with *Summarise this page* and *Open routed chat*.
- **Your own commands** (Settings › Commands) - prompts you save once and run with one click: they join the
  selection toolbar and the right-click menu next to the built-in actions, the writing widget's commands popover
  under *Your commands*, and the [desktop app](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/DESKTOP.md)'s command bar (the same list, the same shape). Write
  `{{selection}}` where the text you selected (or typed in the field) goes; `{{page}}` (the page's text), `{{title}}`,
  `{{url}}`, `{{app}}` (the site) and `{{language}}` are filled in the same way, and a prompt that names no text gets
  it quoted under it, exactly as *Summarise* does. `{{page}}` and `{{url}}` exist only in the browser: a command that
  uses them is not offered on the desktop bar, and Settings says so when you tick it. Each command says where it
  appears (selected text, writing widget, desktop bar - or
  everywhere); **Add three examples** seeds *Reply in my tone*, *Turn into a post* and *Explain for a newcomer* to
  edit. Up to 50 commands, kept in this browser; an organisation can pin the list (`commands` below).
- **Answer in** (Settings › Commands) - the language every routed answer is asked for, in the sidebar and in the
  commands; empty means the language of the question. It travels as a system line in front of each request.
- **Export** (the download icon in the sidebar's header) - the open conversation as a Markdown file through the
  browser's downloads: who said what, the target and the price under each answer, the attached text quoted. Nothing
  is uploaded; the file is written from what the sidebar shows.
- **Edge tab and shortcut** - on pages that are not one of the chat sites a tab on the right edge slides out to
  *Sidebar* and opens the sidebar; click it again (or press `Alt+Shift+O` again) and the sidebar closes. Right-click
  the tab to hide it on that site; `Alt+Shift+O` and the popup work anywhere, including the chat sites, which get no
  tab of their own. The empty sidebar offers idea tiles - *Brainstorm ideas*,
  *Plan a project*, *Create content* (each starts a prompt in the box) and *Prompt library* (the marketplace) - and
  quick-action tiles - *Summarise this page* and, for whatever is attached, *Explain*, *Summarise*, *Rewrite*,
  *Translate*; a context chip shows what will travel with the next message and can be detached.
- **Help in every text field** (Settings › More options, on by default) - the writing widget follows the field you
  type in on any site: a `textarea`, a text input, a rich editor (`contenteditable`, including the ones that live in
  a frame or in a web component), a classic WYSIWYG frame in design mode. Mail, docs, tickets, forms and CMS editors
  get the widget without anything to set up; the extension reads only the field you type in, never the page around
  it. Turn the switch off to keep the advisor to the chat sites and Google Docs; a **Revoke** row takes the
  browser's site access back as well.
  **Where the widget appears** narrows it to *All sites* or *Only these domains* (one per line, `*.example.com`
  covers the subdomains); **Turned off on these sites** lists the domains disabled from the widget (the power
  button on the widget adds the site you are on, for this visit or for good).
- **Google Docs** - a document is a site of its own, on out of the box (Settings › Sites), and works without
  *Help in every text field*. Docs paints the page on a canvas, so there is no text field for the widget to sit in
  and the browser's own selection is always empty; the extension reads the selection from the hidden editor frame
  Docs types into, places the selection toolbar over the highlighted lines, and finds the pill at the edge of the
  page. Select a passage and the toolbar's actions (Summarise, Explain, Rewrite, Translate, your own commands) hand
  it to the sidebar; the card on the pill reads the selection - personal details, slips, the model the request
  deserves - and **Redact**, **Fix** and **Use this** write the result back over the selection the way a paste
  does. When Docs will not take the paste the text is put on the clipboard and the card says to press `Ctrl+V`. With
  the document's words visible to the extension (Docs offers them to an extension that announces itself before the
  editor loads; Chrome, Edge and Firefox) the sidebar's *Summarise this page* reads the document; in Safari it
  reads the selection alone. Nothing is read from pixels, and nothing is sent until you ask.

## Surfaces

| Surface | What it is for |
|---|---|
| **Card** (on the page) | The verdict on your draft and the actions on it. Nothing to configure there |
| **Popup** (toolbar icon) | Status: the connected workspace (a selector when the profile remembers more than one), one switch for the site in the active tab (a chat site's adapter, or the writing widget on any other site), today's counters (drafts checked, details hidden, tokens saved, model tips) or a one-line hint while there are none, tracked API calls when tracking is on, *Open the sidebar*, the extension's version, an **Update to x.y.z** card when a newer build is published and the colour-scheme button (system / light / dark). No settings |
| **Sidebar** (edge tab, `Alt+Shift+O`, popup) | Two tabs: **Ask** - chat through your router, with the decision under every answer - and **Suggestions** - the writing score of the field you are typing in, its categories and findings with one-click *Fix* / *Add* / *Redact*, and the stats (words, characters, words per sentence, reading time, grade level). **Sessions** keeps earlier chats of this browser session |
| **Settings** (gear in the popup, or the browser's extension options) | Everything, in one place, saved as you change it. Four essentials are visible - Workspace (Connect; the platform address, tenant and key live in a *Self-hosted or using a key* fold), Privacy, Chat sites, Checks - and the rest sits behind **More options** (help in every text field, where the widget appears, the sites turned off, selection actions, the edge tab, API tracking, one-click connect, model prices). Folds open by themselves when a setting inside them is already on |
| **Welcome** (opens once after install) | Two steps: *1 Explore features* - four slides (the widget, the commands, the score, the sidebar) - and *2 Activate* - **Connect a workspace** (the code sign-in) and **Help in every text field** (the browser's permission prompt). Both optional, both changeable later; *Skip* at any point |

All surfaces share one design system - the brand palette, a stroke icon set, pills,
chips and tiles - and follow the *page's* colour scheme on the chat sites (a dark ChatGPT under a light OS gets a
dark card and the reverse mark). Copy is written for a general reader: no tiers, catalogues or quotes on screen -
*a quick model is enough*, *public prices*, *from your workspace*.

## Analysis policy

Settings › Privacy › **Send my draft to get prices**. Connecting a workspace turns it on (personal details hidden
first); you can turn it back off.

| Level | What leaves the browser |
|---|---|
| **Off** (default until you connect) | Only the public catalogue download (`GET /api/v1/models`, no user data). Signed out, the card shows the on-device checks and a *Connect* row - it does not list the public catalogue as if it were your models; connected, an estimate computed on this device from public prices |
| **On** | The draft is sent to `POST /api/v1/estimate` - anonymously (rate limited) or with your key - **after** on-device redaction unless you turn that off. The platform stores no request text (activity rows carry request id and metadata only). The card labels the result *From your workspace* or *Anonymous quote · public prices* |

Keys are kept in `chrome.storage.local` (not synced) while **Stay signed in** (Settings › Privacy, on by default) is
on; turn it off and the key lives in `chrome.storage.session` - memory only - and you connect again after the
browser restarts. Sign out from the popup or Settings, or revoke the key from the dashboard's API keys page.

### Managed by an organisation

An administrator installs the extension in every browser of every account and settles its settings before anyone
opens it - no store visit, no developer mode, no first-run choice. Two policies do it, and one command writes both
in the shape each browser reads. Download the **policy kit** from the extension page
(`/extension/download/policy`, the `opensmartroute-extension-policy-<version>.zip` of the current build), unzip it
anywhere with Node.js 18 or newer and run the script it carries:

```bash
node policy.mjs --out policy/ --release https://opensmartroute.ai/extension/release.json --settings settings.json
```

The kit is self-contained - `policy.mjs`, the `managed_schema.json` it validates against, a `settings.example.json`
to start from and a README with this command. `--release` takes the store ids and the signed Firefox package from
the website's manifest (explicit `--chrome-id`, `--edge-id`, `--firefox-xpi` win over it); `--settings` is a JSON
object of the keys below. It writes:

| File | Deliver with |
|---|---|
| `opensmartroute-extension-windows.reg` | Group Policy, Intune (as a script or PowerShell `reg import`), a login script; imports as administrator. HKLM `ExtensionInstallForcelist` items 900 and 901 for Chrome and Edge (the rest of your list is untouched), `3rdparty\extensions\<id>\policy` for the settings, Firefox's `ExtensionSettings` and `3rdparty\Extensions\<id>` |
| `opensmartroute-extension-macos.mobileconfig` | Jamf, Intune, Kandji, Mosyle or any MDM: one profile with a payload per browser (`com.google.Chrome`, `com.microsoft.Edge`, their `.extensions.<id>` settings domains, `org.mozilla.firefox`) |
| `opensmartroute-extension-firefox-policies.json` | Firefox's `distribution/policies.json` (Windows, macOS) or `/etc/firefox/policies/policies.json` (Linux) |
| `opensmartroute-extension-chrome-linux.json`, `-edge-linux.json` | `/etc/opt/chrome/policies/managed/`, `/etc/opt/edge/policies/managed/` |
| `opensmartroute-extension-ExtensionSettings.json` | Google Admin's or Intune's policy editor, when you keep the force-install in your own `ExtensionSettings` |

The settings an administrator may pin (the extension declares them in `managed_schema.json`; anything else is
refused by the script and ignored by the browsers): `platformUrl`, `apiPrefix`, `tenant`, `policy`, `sites`,
`everywhere`, `siteMode`, `allowedDomains`, `blockedDomains`, `consoles`, `shareObserved`, `logActivity`,
`staySignedIn`, `askBeforeSend`, `commands` (a list of `{ "id", "label", "prompt", "where" }` objects that replaces
the person's own commands) and `answerLanguage`. Pinned keys are shown locked in Settings with *Set by your organisation*. Chrome
and Edge read them from the `3rdparty` policy (`Software\Policies\Google\Chrome\3rdparty\extensions\<id>\policy` or the
Edge equivalent; on macOS the `com.google.Chrome.extensions.<id>` domain), Firefox from `policies.json` ->
`3rdparty.Extensions.<id>`:

```json
{ "platformUrl": "https://router.example.com", "apiPrefix": "/api/v1", "tenant": "acme", "policy": "quote", "staySignedIn": false }
```

Deploying the [desktop app](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/DESKTOP.md#installing-for-an-organisation) for every account? Its Windows installer takes
`/EXTENSIONS=all` and registers the extension for every account itself (an *Enable OpenSmartRoute?* prompt per
person, no policy); the files above are the way to a silent, forced install, and the only way on macOS.

### Diagnostics

Settings › More options › Advanced › **Copy diagnostics** puts a support bundle on the clipboard: version and
channel, browser, the platform address, every setting with secrets redacted, which keys are policy-pinned, today's
adapter health per site and the last events the worker recorded (quote failures, message errors, install and
update). Nothing is uploaded on its own. The popup also tells you when the extension could not find the message
box on a chat site today ("the site may have changed", with a *Report it* link) or when it fell back to its
general-purpose mode there.

## Connect

Popup or Settings -> **Connect**. The extension starts a device sign-in
(`POST /api/v1/auth/device/code`), opens the approval page in a tab and polls
`POST /api/v1/auth/device/token` until you approve; the minted key is labelled *Browser extension* in
the dashboard. For a self-hosted `osr serve` or an existing key, open Settings › Workspace › *Self-hosted or
using a key*, paste the token and set the API style to *Self-hosted osr serve*.

Once connected, quotes are metered as `estimate` on your workspace, tenant rules apply
(set the tenant in Settings) and the traffic is attributed to the app `browser-extension`: every call carries
`X-OSR-App: browser-extension`, and the dashboard's *Usage* page breaks metered calls down **by app**
(`GET /api/v1/usage` -> `by_app`), so a workspace can see how much of its routed traffic came from the
extension, the CLI or its own integrations.

**Several workspaces.** A person with more than one workspace (their own and a team's, a customer's) connects
each one in turn - the popup's **Add** and Settings' **Add another** run the same sign-in - and the browser profile
remembers all of them. A *Workspace* selector appears in the popup once there are two; Settings › Workspace lists
them with **Use** and a forget button. The card's quote row says which one priced the draft (*From Acme*), the
sidebar's header chip names it, and **Sign out** leaves only that key: another remembered workspace takes over,
so a revoked or expired key never leaves you signed out of everything. The list follows *Stay signed in* the way
the key does - on disk while it is on, in memory only while it is off. The workspace record (name, plan) is
re-read from the platform when it is over an hour old, so a rename or a plan change reaches the popup.

## Routed chat in the sidebar

Click the toolbar icon on Chrome or Edge (click it again to hide the sidebar), or the overview's **Open the
sidebar** on Firefox and Safari (or **Continue in the sidebar** on the card when you are connected). The
sidebar sends your messages to `POST /v1/chat/completions` with `model: "auto"` and streams the answer as
rendered Markdown (headings, lists, tables, code blocks with their own *Copy*; never raw HTML). While an answer
streams a **Stop** button takes Send's place; what arrived stays and is marked *stopped*. Under each answer it
shows the decision as chips - *answered by* the target (on the hosted platform a link that opens the request's
trace on the dashboard: the decision, the spans, the outcome), the cost
and latency the platform measured, the steps when there were several and *backup model* when a fallback ran -
**Copy** for the whole answer and thumbs that post `POST /api/v1/feedback`, so the workspace's learners hear how
the answer went. The preference chips in the composer (*Balanced*, *Cheapest*, *Best*, *Fastest*) set the objective
(`osr.objective`: quality / cost / latency) for every request. Every call the extension makes - quotes, chat,
the catalogue refresh, sign-in, the activity uploads - carries a request id and a W3C `traceparent`, so each one
is a traceable row on the workspace with `app: browser-extension`. **Attach selection** and **Attach the page** in the
header pull text in from the active tab without leaving the panel; from a chat site, *Continue in the sidebar*
carries the visible turns and your draft across as history. **The conversation lives in your workspace**, not in
the browser: the first question starts a conversation the platform keeps (`osr.conversation`), every later one
sends only the new message and the platform prepends the thread - the last twelve turns verbatim, everything older
as a rolling summary - so it survives closing the browser, continues on the desktop app or in another browser,
and never re-sends itself. **Sessions** lists those conversations (marked *from the desktop* when they started
there), reopens or deletes any of them, and still keeps up to twenty browser-only threads from before you
connected. A question that reads like a change of subject gets a line under its answer offering **Move to a new
chat**; the platform moves that exchange into a conversation of its own. When you say something about yourself -
*I work at Northwind*, *we use Rust*, *answer briefly*, *remember that …* - a line under the answer confirms what
was **remembered**, and later questions on every client get that context without repeating it; *forget …* removes
it. The settings page's **Memory** section lists every remembered fact with a bin, an *Add* field and the switch
that turns it off for good. On Chrome and
Edge a one-time bubble points at the browser's pin so the panel is one click away. When something fails the status
line says what happened in plain words and offers the one action that
helps - *Try again* (the question goes back into the box), *Connect* (the key was revoked), *See plans* (the plan's
allowance is used up), *Open settings* (the platform could not be reached).

The **Suggestions** tab needs no workspace: the content script scores the field on the device and hands the panel
the score, the findings and the stats (never the text beyond a 200-character preview). *Fix*, *Add* and *Redact*
on a finding change the field in the page; the score follows.

## Tracking provider API calls

Two channels, both off until you switch them on in Settings › More options › API tracking.

- **Provider consoles and your own apps** - an observe-only `webRequest` listener on the LLM API hosts
  (OpenAI, Azure OpenAI, Anthropic, Gemini, OpenRouter, Groq, Mistral, Together, DeepSeek, xAI, Cohere), on
  `localhost` / `127.0.0.1` and on any origin you add. It reads the request body (model, messages) and the
  status, estimates the completion from `max_tokens`, prices the call from the catalogue and never blocks or
  changes anything. The browser asks for access to those hosts once. The popup shows today's calls and
  estimated cost per vendor.
- **DevTools panel** (F12 -> *OpenSmartRoute*) - every LLM call the inspected tab makes with the exact
  `usage` from the response body (OpenAI chat and Responses, Anthropic Messages, Gemini, Cohere; JSON and
  SSE), latency, status, the detected task and the catalogue price; vendor and model chips that narrow the
  table (the totals follow what is shown - the tab's model mix at a glance); a detail view with the messages and
  the answer; JSONL / CSV export; and **Replay through OpenSmartRoute**, which sends the same messages through
  the router and shows both answers with both costs side by side.

**Share what was tracked with my workspace** (Settings › More options › API tracking, connected) uploads call *shapes* - vendor, model,
token counts, site, task type, complexity - to `POST /api/v1/usage/observed` every few minutes. No text
field is ever sent; the platform rejects unknown fields (`422`). The workspace's opportunity report
(`GET /api/v1/usage/opportunity`) compares what those calls cost at list price with what the router's
recommended target would have cost for the same token shape.

### Activity on the workspace

**Log my activity to the workspace** (Settings › Advanced, on by default) records what the extension did as
events on the connected workspace - `POST /api/v1/usage/activity`, posted every few minutes with the observed
usage. An event is a kind, the site's hostname, a count and a figure; there is no field for text and the
platform refuses one. The kinds: `card.analysed` (a draft checked, with the saving between the card's pick and the
dearest candidate), `quote.fetched`, `model.hinted`, `pii.redacted` (with the number of details), `draft.compacted`
(with the tokens saved), `writing.fixed`, `writing.suggested`, `writing.answered` (with the target and request id
of the answer written into a text field), `send.held`, `session.signed_in` / `session.signed_out`,
`extension.installed` / `extension.updated`. The workspace's Apps page turns them into counts per kind and
site; the plan decides how long they are kept and how many a day are recorded (see PLATFORM.md). Administrators
can pin the switch with the `logActivity` managed-storage key.

The first event the extension records for a workspace also sends *Your browser extension is connected* to the
person who connected it: what the extension does, that the workspace sees counts only, the Apps page and this
guide, and what to do if they did not install it. A workspace that routes through the API for three days
without the extension or the desktop app ever reporting in gets one note about the two (*Getting started* mail,
switchable on the account page).

### The AI spend page

Signed-in users read the report on `/platform/dashboard/spend`: observed calls and spend, the
router's quote for the same requests, the gap, the models with the widest gap and the target the router
would have picked, breakdowns by model, site and task, the per-call history (`GET /api/v1/usage/observed`,
newest first, never any text) and a CSV export. Only calls the catalogue can price count towards the gap,
so both sides describe the same requests; the page says so and never calls the number a saving. Rows are
kept for 90 days and deleted with the workspace. While nothing has been observed yet the page explains
what to install and how to switch sharing on.

## Install

The product page `/extension` on the website is the front door. It detects the visitor's browser (Chrome
and the other Chromium browsers, Edge, Firefox, Safari) and offers, in this order:

1. **the store listing** for that browser - one click, no developer mode - as soon as the release carries it
   (`stores` in `latest.json`, see below): Chrome Web Store,
   Microsoft Edge Add-ons, Firefox Add-ons, Mac App Store;
2. **a vendor-signed package that installs from the click**: the Firefox build is signed by Mozilla (AMO,
   unlisted channel) and served as an `.xpi`, so **Add to Firefox** installs without `about:debugging`;
3. **the packaged build** for that browser - **Download for Chrome / Edge** - followed by a guided
   developer-mode install whose steps follow the selected browser (extensions page address with a copy
   button, Developer mode, Load unpacked); Safari's package is the Mac app the store will carry;
4. when no build is published yet, the page says so and shows the same steps for the day it does.

Chrome and Edge cannot install an extension from a website without developer mode - Google and Microsoft
removed every non-store path in 2018 - so for those two the store listing is the only one-click install and
every stable release is submitted to both stores. The desktop app shortens the wait: **Get the
extension** on its welcome screen downloads the Chrome or Edge package, unpacks it into a folder of its own
under the app's data directory and names that folder, so the two clicks left are Developer mode and **Load
unpacked**. The folder is removed again with the extension (*Remove* in Settings, or uninstalling the app).

The page recognises an installed extension - the platform-origin content script sets
`data-osr-extension="<version>"` on `<html>` - and turns every call to action into "Installed" with a link to
the AI spend page. The **Product** menu carries the extension as its featured card with a browser-aware
"Add to Chrome" button, visitors on a supported browser without the extension see a floating install prompt
once per month on the public pages, and signed-in users see a one-time, dismissible suggestion on the
dashboard overview and the Usage page.

**Where the packages come from.** Every release publishes one package per store - Chrome, Edge, Firefox and
Safari (the Safari package is a Mac app) - stamped with the release version, or with a four-part preview
number for developer previews so no two builds share a file name (see [RELEASES.md](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/RELEASES.md)). The
Firefox build is signed by Mozilla: previews as a one-click unlisted `.xpi`, stable releases through the
Firefox Add-ons review queue once the listing is live, delivered by the store after approval. Mozilla's
review never holds the other browsers back: while signing is pending the manifest lists the Firefox zip in
place of the `.xpi`, and the signed package takes its place once Mozilla has issued it. Stable Chrome and
Edge packages are submitted to their stores' review; the product page switches to a store button the moment
a listing is approved. Each channel publishes a manifest (`latest.json` for stable, `preview.json` for
previews) naming every package with its size, SHA-256, kind (`zip`, `xpi`, `app`), signature state and the
live store URLs.

The website serves:

- `GET /extension/download/{chrome|edge|firefox|safari}` - 302 to the current package for that browser (a
  Mozilla-signed `.xpi` for Firefox installs on arrival; Edge takes its own package or Chrome's); the stable
  release by default, `?channel=preview` for the developer preview; 404 while no build is published;
- `GET /extension/download/policy` - 302 to the administrator's policy kit of the same build (`policy` in the
  manifest), see *Managed by an organisation*;
- `GET /extension/release.json` - the manifest with same-origin download links, for scripts and the
  extension's own update check (`?channel=preview` likewise; `channel` in the answer says which build it is).

### Stores and signing without a paid developer programme

No browser needs a paid licence to *run* the extension; what a paid or verified account buys is the one-click
install from the vendor's store. What each browser needs:

| Browser | One-click path | What it needs | Without it |
|---|---|---|---|
| Firefox | Mozilla signs the package (AMO, unlisted channel) and **Add to Firefox** installs from the click. | A free Firefox account and its API credentials (`OSR_EXTENSION_AMO_JWT_ISSUER` / `_SECRET`). | Load Temporary Add-on from `about:debugging`. |
| Edge | Microsoft Edge Add-ons listing. | A free Partner Center registration for Edge Add-ons; `OSR_EXTENSION_EDGE_PRODUCT_ID`, `_CLIENT_ID` and `_API_KEY`. With them set, every stable version is submitted. | Developer mode: download the package, `edge://extensions`, Load unpacked. Managed devices: the `ExtensionSettings` force-install policy on the product page installs it without developer mode. |
| Chrome and other Chromium browsers | Chrome Web Store listing. | A Chrome Web Store developer account (a one-time registration fee, no yearly programme). Register the extension once by hand in the developer dashboard (the store creates no items through the API), note the item id and the publisher id, enable the *Chrome Web Store API* on a Google Cloud project and give it a service account (added under Developer Dashboard > Account) or a Desktop-app OAuth client. With `OSR_EXTENSION_CWS_*` set, every stable version is submitted through the Web Store API (V2; the V1 API is switched off on 2026-10-15). | Developer mode as for Edge; the `ExtensionInstallForcelist` policy for managed devices. |
| Safari | Mac App Store listing. | An Apple Developer Program membership (yearly fee) to sign and notarise the Mac app. | The Xcode wrapper is still published; anyone with a free Apple ID opens it in Xcode and runs it on their own Mac (Safari > Settings > Developer > *Allow unsigned extensions*). |

In short: Firefox costs nothing and is signed today, Edge costs nothing but a registration, Chrome a one-time
fee, Safari a yearly membership. The product page and the dashboard's Apps page never claim a store that is not
configured: they show the signed package, the packaged build with its steps, or the store button - whichever
the release manifest can back.

**Updates.** A store install updates through its store. The signed Firefox `.xpi` from the downloads page updates
itself too: its manifest names `/extension/updates.json` (the preview build `?channel=preview`), Firefox reads it
daily, checks the SHA-256 and installs the newer signed package - the same mechanism the store uses. A package
installed unpacked on Chrome or Edge and the Safari app have no updater, so the extension asks
`/extension/release.json` for its own channel twice a day (and when you open the overview or the sidebar after that
long): a small dot appears on the toolbar icon, the sidebar shows an **Update to x.y.z** line under its header and
the overview an **Update to x.y.z** card, both linking to the package for
your browser and saying what to do (download, then *Reload* the unpacked folder). Nothing is downloaded or installed
by itself on those browsers.

**Self-hosted platform.** The packages ask permission for `https://opensmartroute.ai` only. When you enter another
platform URL in the options, the browser asks you to allow that site (an optional host permission); decline and
the hosted platform stays selected.

While no stable release of the extension has been published yet, the developer preview stands in on both
routes and the product page says so ("developer preview" next to the version). Versioned packages are
immutable (cached for a year); the two manifests are the moving pointers (cached 5 minutes, mirrored by the
website). A stable release publishes the stable packages; every change to the platform publishes a new
developer preview. The source repository is private: the downloads and the store
listings are the only distribution, and `/extension/release.json` carries the SHA-256 to verify a package against.

**Managed browsers.** A web page cannot install an extension by itself (browsers removed inline install in
2018); what installs it automatically for everyone in a company is the `ExtensionSettings` policy - Group
Policy or Intune for Edge and Chrome on Windows, Google Admin for managed Chrome profiles. The product page
renders the snippet with the store id (`NEXT_PUBLIC_EXTENSION_CHROME_ID` / `_EDGE_ID`):

```json
{ "<extension-id>": { "installation_mode": "force_installed", "update_url": "https://clients2.google.com/service/update2/crx" } }
```

Edge uses the same shape with `https://edge.microsoft.com/extensionwebstorebase/v1/crx`.

Loading a downloaded package by hand (the guided installer on `/extension` shows the same steps for your browser):

- Chrome: `chrome://extensions` -> Developer mode -> **Load unpacked** -> the unzipped folder (the one holding
  `manifest.json`).
- Edge: `edge://extensions` -> Developer mode -> **Load unpacked** -> the same folder.
- Firefox: a signed `.xpi` installs when you open it; an unsigned zip loads through
  `about:debugging#/runtime/this-firefox` -> **Load Temporary Add-on** -> `manifest.json`.
- Safari: unzip the app, move `OpenSmartRoute.app` to Applications, open it once, then Safari > Settings >
  Extensions; an unsigned build needs Develop > **Allow unsigned extensions** first.

The same packages can be built from a checkout with `npm run build:all` (or one of `build:chrome`,
`build:edge`, `build:firefox`, `build:safari`), `npm run safari:xcode` (macOS: the Xcode project and app) and
`npm run pack` (zips + the channel manifest in `dist/`).

Permissions: `storage`, `alarms`, `sidePanel`, `webRequest` (observe-only; no install warning), `contextMenus`,
`scripting` and `activeTab` (page summaries and waking the tabs already open when a setting changes), and every
`http` / `https` site for the content script - the writing widget works in any text field out of the box, so the
browser names that at install (Firefox asks for it the first time instead; until it is granted the advisor runs
on the chat sites and Google Docs alone). The hosted platform origin (`https://opensmartroute.ai`) is where the
worker talks to. A published package carries no local address (`localhost`, `127.0.0.1`), so the one-click
connect bar never runs on a site you are developing. A self-hosted
platform URL on another origin - a local `osr serve` included - is requested when you save it.

## Test

Every package is installed into a real Chrome, Edge and Firefox and driven through a sign-in, a redaction and a
routed request before it is published. Nothing reaches a store or the downloads page until those checks pass.

## Design

The card and the pages follow the brand guide: the real mark (positive artwork on light surfaces, reverse
on dark), the Quicksand wordmark with *Smart* in the cyan text tone, the website's editorial palette in both
colour schemes - warm cream and charcoal neutrals (paper, snow, mist, line, ink, slate, muted) and one warm accent,
*clay*, for everything you can press or follow (primary buttons, the send control, switches, links, focus rings,
the streaming caret, the toolbar badge) - hairline rules instead of shadows for emphasis, mono numerals in
the quote ledger and the three brand hues reserved for the mark, the tier badge, the pick labels and
informational chips. Icons are the
website's set (lucide, one stroke weight) and every one names the thing it stands for - a pen for *rewrite*, a spell-check mark for *fix*, a power
button for *turn off here*; the generic sparkles and wands are not used anywhere. The extension, the desktop app
and the site share one look.
The extension's own pages (popup, sidebar, Settings, welcome, DevTools) follow the system colour scheme or the one
pinned under **Settings › Appearance** (also the sun / moon / monitor button in the popup and sidebar header); the
card and the widget follow the *page* they sit on, so a dark ChatGPT under a light OS gets a dark card. The card sits
just above the composer, right-aligned with it, collapses to a single line, folds long candidate lists
behind *Show all*, shows a loading row while a draft is first analysed and respects `prefers-reduced-motion`.

## Privacy summary

- Default policy is local only; nothing about your prompt leaves the browser.
- With the quote policy, personal details are redacted on-device before the draft is sent.
- The verdict says *Nothing flagged* - no personal-detail **patterns** were found (emails, phones, cards, IBAN,
  SSN, Aadhaar, IP addresses, API keys). Names, addresses and free-text facts are not detected; read before you send.
- No third-party analytics. What reaches *your* workspace is the activity described under *Activity on the
  workspace* - counts, token estimates, the site category, the target that answered, never text - and, only if you
  switch it on, the model and token counts observed on the chat sites. No remote code: the heuristics and adapters
  are bundled; the catalogue is data. Diagnostics are copied by you, never uploaded.
- Keys are stored locally (or in memory only with *Stay signed in* off), never synced, and can be revoked from the
  dashboard at any time.
