Files
bookmarks-api/AGENTS.md
T

4.5 KiB

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.

Preconditions

  1. Native host installed: powershell -NoProfile -File tools/install-native-host.ps1
  2. Add-on loaded (temporary via about:debugging, or a signed install)
  3. A client secret issued from the add-on UI: toolbar icon → Manage clients → Generate secret
  4. Firefox running

If http://127.0.0.1:17634/health fails, the host is not up. If /health works but calls 401, you do not have a current FAB_TOKEN.

Auth

This is a normal API token, not an SSH key.

  1. In Firefox, open the Bookmarks API toolbar icon → Manage clients.
  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.
  4. Give tooling only that string:
set FAB_TOKEN=fab_…
python tools/client.py meta.methods

FAB_TOKEN_FILE pointing at a file that contains only the token is also fine. --token on the CLI overrides both.

The add-on stores SHA-256(token) only. Revoke from the same page. A local process that never received the secret cannot call the API.

CLI fallback (same store, prints the token once):

python tools/register_client.py add --name cursor-agent

Preferred caller

python tools/client.py METHOD [ARGS_JSON]

ARGS_JSON is a JSON array matching the WebExtension function arguments.

python tools/client.py bookmarks.search "[{\"title\":\"10.132.x.x\"}]"

HTTP:

POST /v1/call
Authorization: Bearer fab_…
Content-Type: application/json

{"method": "bookmarks.search", "args": [{"title": "10.132.x.x"}]}

Allowlisted methods (v0.2)

Method Args Notes
meta.ping (none) Bridge alive
meta.methods (none) This table
bookmarks.getTree (none) Full tree
bookmarks.getSubTree id
bookmarks.getChildren id
bookmarks.get id or [id, …]
bookmarks.search {title? url? query?} or string
bookmarks.create {parentId?, title, url?} Omit url to create a folder
bookmarks.update id, {title? url?}
bookmarks.move id, {parentId? index?}
bookmarks.remove id Bookmark or empty folder
bookmarks.removeTree id Folder and descendants
bookmarks.getRecent numberOfItems

Do not invent other browser.* names. Adding an API means editing ALLOWED in extension/background.js and reloading the extension.

REST aliases use the same Bearer token. GET /health has no token (loopback liveness only).

Typical folder replace

  1. bookmarks.search {title: "…"} — take the hit without a url. Fail if zero or many.
  2. bookmarks.getChildren on that id.
  3. bookmarks.remove each bookmark; bookmarks.removeTree each child folder.
  4. bookmarks.create each new item with parentId set.

examples/replace_named_folder.py does that (FAB_TOKEN must be set).

Failure modes

Symptom Cause What to do
Connection refused on :17634 Firefox closed or add-on/host missing Open Firefox; load add-on; install native host
401 Bearer token required Unsigned request Set FAB_TOKEN
401 unknown token Revoked, typo, or never generated Manage clients → generate again
502 method not allowed Typo or API not in ALLOWED meta.methods
Temporary add-on gone after restart Unsigned on Firefox Release Load again, or sign via AMO

Security boundaries

  • No runtime.onMessageExternal. Other add-ons cannot call the dispatcher.
  • Native host allowed_extensions is pinned to this add-on's gecko id.
  • HTTP is 127.0.0.1 only; browser Origin headers are rejected.
  • Secrets are ordinary Bearer tokens. Do not write them under %LOCALAPPDATA%\firefox-agent-bridge.
  • Another add-on with the bookmarks permission already has Places.
  • Do not add tabs, history, <all_urls>, or onMessageExternal without a new threat review.

What this project is not

  • Not a bidirectional sync implementation
  • Not a general Firefox remote-control surface
  • Not a reason to keep editing places.sqlite on a live profile