Rename the add-on Bookmarks API and drop CLI copy from the product UI.

This commit is contained in:
alexveley
2026-09-22 07:33:35 -04:00
parent 9242c01015
commit de0814f3ed
7 changed files with 27 additions and 54 deletions
+4 -2
View File
@@ -1,4 +1,6 @@
# Agent handshake — Firefox Agent Bridge # Agent handshake — Bookmarks API for Scripting and AI
Use this when working **in this repo**. The shipped product is the add-on under `extension/` only. Do not treat `tools/` as part of the listing.
Use this when a script or coding agent needs to **create, update, move, or delete Firefox bookmarks while Firefox is open**. Do not edit `places.sqlite` if this bridge is reachable. Use this when a script or coding agent needs to **create, update, move, or delete Firefox bookmarks while Firefox is open**. Do not edit `places.sqlite` if this bridge is reachable.
@@ -15,7 +17,7 @@ If `http://127.0.0.1:17634/health` fails, the host is not up. If `/health` works
This is a normal API token, not an SSH key. This is a normal API token, not an SSH key.
1. In Firefox, open the Agent Bridge toolbar icon → **Manage clients**. 1. In Firefox, open the Bookmarks API toolbar icon → **Manage clients**.
2. Name the client (e.g. `cursor-agent`) and click **Generate secret**. 2. Name the client (e.g. `cursor-agent`) and click **Generate secret**.
3. Copy the `fab_…` value **once**. Store it however you already store secrets: env var, Vaultwarden, `.env`, CI secret, Cursor env. 3. Copy the `fab_…` value **once**. Store it however you already store secrets: env var, Vaultwarden, `.env`, CI secret, Cursor env.
4. Give tooling only that string: 4. Give tooling only that string:
+9 -38
View File
@@ -1,53 +1,24 @@
# Firefox Agent Bridge # Bookmarks API for Scripting and AI
A small Firefox add-on that exposes an **allowlisted** WebExtension API to local scripts and agents — after you issue them a secret from inside Firefox. A Firefox add-on that lets **local** scripts and agents call an allowlisted `browser.bookmarks` API — after you issue them a secret from inside the browser.
This is **not** a bookmark sync engine. Firefox already has `browser.bookmarks`. The add-on is a bridge: while Firefox is open, a script on the same machine can call those functions over `http://127.0.0.1:17634` with a Bearer token. The published product is the add-on in `extension/`. It does not ship helper scripts. Callers use ordinary HTTP and a Bearer token, stored however they already keep secrets.
Agents: start at [AGENTS.md](AGENTS.md). This is not a bookmark sync engine, and it is not a Mozilla product. Mozillas add-on naming rule is “Name for Firefox,” never “Firefox Name”; this listing uses neither form in the title.
## Why this exists
Firefox will not let an outside process talk to Places. Editing `places.sqlite` while the browser is running loses deletes. The supported path is `browser.bookmarks` inside an extension, plus [native messaging](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging) so a localhost HTTP host can reach that extension.
## Setup ## Setup
1. Register the native host (once): 1. Register the native host once (`tools/install-native-host.ps1` on this workstation).
2. Load the add-on (temporary via `about:debugging`, or a signed `.xpi` — [docs/signing.md](docs/signing.md)).
3. Toolbar icon → **Manage clients** → generate a secret → store it with your secrets.
```powershell While Firefox is open, `http://127.0.0.1:17634/health` answers if the host is up. Authenticated calls use `Authorization: Bearer <secret>` on `/v1/call`. Method list: the repo [AGENTS.md](AGENTS.md) (for people working on this codebase, not for the AMO listing).
powershell -NoProfile -File tools\install-native-host.ps1
```
2. Load the add-on (`about:debugging` temporary, or a signed `.xpi` — [docs/signing.md](docs/signing.md)).
3. Click the toolbar icon → **Manage clients** → name a client → **Generate secret**. Copy the `fab_…` token into whatever secret store you already use.
4. Leave Firefox open. `GET http://127.0.0.1:17634/health` should return JSON.
## Call it
```powershell
$env:FAB_TOKEN = "fab_…" # the value from the Manage clients modal
python tools/client.py meta.methods
python tools/client.py bookmarks.search '[{"title":"10.132.x.x"}]'
```
`curl` works the same: `Authorization: Bearer fab_…`.
## Security ## Security
- Binds **127.0.0.1** only. Browser `Origin` headers are rejected. - Binds **127.0.0.1** only. Browser `Origin` headers are rejected.
- Each caller is an issued token. The add-on stores a hash, not the secret. - Each caller is an issued token. The add-on stores a hash, not the secret.
- Only `bookmarks.*` (plus `meta.*`) in `extension/background.js`. No history, cookies, tabs, or `onMessageExternal`. - Only `bookmarks.*` (plus `meta.*`). No history, cookies, tabs, or `onMessageExternal`.
## Pieces
| Piece | Role |
|-------|------|
| `extension/` | Add-on, popup, Manage clients page |
| `host/` | Native host: stdio to Firefox, HTTP to scripts |
| `tools/client.py` | Bearer caller (`FAB_TOKEN`) |
| `examples/replace_named_folder.py` | Sample folder replace |
## License ## License
+1 -1
View File
@@ -42,4 +42,4 @@ Bump `version`, sign again on the same channel and id. For unlisted self-distrib
## 5. Listed (later) ## 5. Listed (later)
`web-ext sign --channel=listed` plus an AMO metadata JSON (name, summary, license MIT). Expect listing copy that says: local scripts only, loopback HTTP, bearer token, bookmarks allowlist. Do not claim it is a general Firefox remote-control tool. `web-ext sign --channel=listed` plus an AMO metadata JSON (name **Bookmarks API for Scripting and AI**, license MIT). Listing copy: local scripts only, loopback HTTP, bearer token, bookmarks allowlist. Do not title it “Firefox …”. Mozilla only allows “Name for Firefox”; this add-on uses neither form.
+5 -4
View File
@@ -1,8 +1,9 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Firefox Agent Bridge", "name": "Bookmarks API for Scripting and AI",
"version": "0.2.0", "short_name": "Bookmarks API",
"description": "Let local scripts and agents call an allowlisted WebExtension API after you issue them a secret.", "version": "0.3.0",
"description": "Issue a secret, then let local scripts and agents call an allowlisted bookmarks API.",
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
"id": "firefox-agent-bridge@easygoingaming.com", "id": "firefox-agent-bridge@easygoingaming.com",
@@ -14,7 +15,7 @@
"scripts": ["background.js"] "scripts": ["background.js"]
}, },
"action": { "action": {
"default_title": "Firefox Agent Bridge", "default_title": "Bookmarks API",
"default_popup": "popup.html" "default_popup": "popup.html"
}, },
"options_ui": { "options_ui": {
+5 -6
View File
@@ -2,18 +2,18 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Firefox Agent Bridge</title> <title>Bookmarks API</title>
<link rel="stylesheet" href="ui.css"> <link rel="stylesheet" href="ui.css">
</head> </head>
<body class="page"> <body class="page">
<header> <header>
<h1>Firefox Agent Bridge</h1> <h1>Bookmarks API for Scripting and AI</h1>
<p class="muted">Issue a secret to each script or agent. The add-on keeps only a hash. Put the secret in an env var, vault, or <code>.env</code> — whatever you already use.</p> <p class="muted">Issue a secret to each client. Store it with your secrets. This add-on keeps only a hash.</p>
<p id="status" class="muted"></p> <p id="status" class="muted"></p>
</header> </header>
<section class="row"> <section class="row">
<input id="name" type="text" maxlength="64" placeholder="Client name, e.g. cursor-agent" autocomplete="off"> <input id="name" type="text" maxlength="64" placeholder="Client name" autocomplete="off">
<button id="create" type="button">Generate secret</button> <button id="create" type="button">Generate secret</button>
</section> </section>
<p id="error" class="error" hidden></p> <p id="error" class="error" hidden></p>
@@ -34,10 +34,9 @@
<dialog id="secret-modal"> <dialog id="secret-modal">
<h2>Copy this secret now</h2> <h2>Copy this secret now</h2>
<p class="muted">It will not be shown again. Store it the same way you store any other API token.</p> <p class="muted">It will not be shown again. Store it with your secrets.</p>
<label class="sr">Secret</label> <label class="sr">Secret</label>
<textarea id="secret" readonly rows="3"></textarea> <textarea id="secret" readonly rows="3"></textarea>
<p class="hint">Example: <code>set FAB_TOKEN=&lt;secret&gt;</code> then <code>python tools/client.py meta.methods</code></p>
<div class="row"> <div class="row">
<button id="copy" type="button">Copy</button> <button id="copy" type="button">Copy</button>
<button id="close" type="button">I saved it</button> <button id="close" type="button">I saved it</button>
+1 -1
View File
@@ -5,7 +5,7 @@
<link rel="stylesheet" href="ui.css"> <link rel="stylesheet" href="ui.css">
</head> </head>
<body class="popup"> <body class="popup">
<h1>Agent Bridge</h1> <h1>Bookmarks API</h1>
<p id="status" class="muted">Checking host…</p> <p id="status" class="muted">Checking host…</p>
<button id="manage" type="button">Manage clients</button> <button id="manage" type="button">Manage clients</button>
<script src="popup.js"></script> <script src="popup.js"></script>
+2 -2
View File
@@ -23,7 +23,7 @@ if (Test-Path $TokenPath) {
$manifest = @{ $manifest = @{
name = $HostName name = $HostName
description = "Firefox Agent Bridge native host" description = "Bookmarks API native host"
path = $CmdPath path = $CmdPath
type = "stdio" type = "stdio"
allowed_extensions = @($ExtensionId) allowed_extensions = @($ExtensionId)
@@ -38,4 +38,4 @@ Write-Host "registered $regPath"
Write-Host "" Write-Host ""
Write-Host "Load the add-on, then use the toolbar icon → Manage clients to generate a secret." Write-Host "Load the add-on, then use the toolbar icon → Manage clients to generate a secret."
Write-Host "Put that fab_… token in FAB_TOKEN for scripts. Do not store it under $StateDir." Write-Host "Store that secret with your other secrets. Do not keep it under $StateDir."