Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 25, 2026Voitta Bookmarklet
An AI assistant you open on any web page with a bookmarklet. It reads the page you are on, runs Python for analysis and reports, and keeps your conversations and projects on your own machine. Bring your own model: an Anthropic, OpenAI, Gemini or Requesty API key, or a Claude subscription.
Contents
- Quick start (Docker)
- First run
- Other ways to run it
- Upgrade, back up, remove
- Configuration
- Development
- License
Quick start (Docker)
You need Docker (Docker Desktop, Rancher Desktop, or Docker Engine on Linux). Nothing else is installed on your machine.
docker run -d --name voitta-bookmarklet --restart unless-stopped \
-p 127.0.0.1:12358:12358 -p 127.0.0.1:12359:12359 \
-v voitta-bookmarklet-data:/data \
ghcr.io/voitta-ai/voitta-bookmarklet:latest
Then open http://127.0.0.1:12358/bookmarklets and continue with First run.
- Keep the
127.0.0.1:prefix on both ports. A single-user install has no login, so it must not be reachable from other machines. - Everything you create (conversations, projects, scripts, settings, API
keys) lives in the
voitta-bookmarklet-datavolume, not in the container. - To build the image yourself instead of pulling it:
git clone https://github.com/voitta-ai/voitta-bookmarklet && cd voitta-bookmarklet && docker build -t voitta-bookmarklet ., then usevoitta-bookmarkletas the image name above.
First run
Install the bookmarklet
Open http://127.0.0.1:12358/bookmarklets. It offers two bookmarklets. Drag the one you need to your bookmarks bar:
- Voitta (Salesforce), the bridge bookmarklet, works on any page without certificates. It opens a small popup window that relays to the backend, so allow pop-ups for the site the first time. Use this one with Docker unless you set up HTTPS.
- Voitta, the standard bookmarklet, loads the assistant straight into the
page. Browsers block a web page from loading scripts from
http://127.0.0.1, so this one needs HTTPS.
Click the bookmarklet on any page. The assistant panel opens beside it.
Choose a model
The first time it opens, the panel shows Settings (the gear icon brings it
back later). Pick a provider and paste its API key. Anthropic, OpenAI, Google
Gemini and Requesty are supported. If you have a Claude Pro or Max
subscription, choose Claude (subscription) if it is listed, and paste a
token from claude setup-token.
Keys are stored in settings.json in your data directory (the data volume
under Docker).
Optional: HTTPS for the standard bookmarklet
Use mkcert to create a certificate
your browser trusts for 127.0.0.1, and mount it into the container:
mkcert -install # once per machine
mkdir -p ~/.voitta-certs && cd ~/.voitta-certs
mkcert -cert-file 127.0.0.1+1.pem -key-file 127.0.0.1+1-key.pem 127.0.0.1 localhost
docker rm -f voitta-bookmarklet
docker run -d --name voitta-bookmarklet --restart unless-stopped \
-p 127.0.0.1:12358:12358 -p 127.0.0.1:12359:12359 \
-v voitta-bookmarklet-data:/data \
-v ~/.voitta-certs:/app/backend/certs:ro \
ghcr.io/voitta-ai/voitta-bookmarklet:latest
The backend then serves https://127.0.0.1:12358. Open
https://127.0.0.1:12358/bookmarklets again and drag the standard
bookmarklet. The bridge port, 12359, stays plain HTTP by design.
Other ways to run it
From source (macOS or Linux)
Needs Python 3.11+, Node.js 20+ and git.
git clone https://github.com/voitta-ai/voitta-bookmarklet
cd voitta-bookmarklet
git submodule update --init --recursive --depth 1 # optional: source for the code RAG corpus
./build.sh # frontend bundle + backend venv
./start.sh # http://127.0.0.1:12358
For the standard bookmarklet, create the TLS pair in backend/certs/ before
./start.sh. The pair is per-machine and git-ignored:
mkcert -install
mkdir -p backend/certs && (cd backend/certs && \
mkcert -cert-file 127.0.0.1+1.pem -key-file 127.0.0.1+1-key.pem 127.0.0.1 localhost)
Data defaults to ~/Library/Application Support/Voitta Bookmarklet/backend. Set
VOITTA_DATA_ROOT to put it elsewhere.
macOS menu-bar app
./tray.sh runs the same backend from a menu-bar icon. The menu has About,
Open, Copy bookmarklet, Settings (with the MCP-debug toggle), Show data
folder, (Re)create TLS certs, Reset and Quit. ./build_app.sh packages it as
Voitta Bookmarklet.app. See OPERATIONS.md §15.
Shared server with Google sign-in
./server-start.sh sets up a headless Linux server: it builds, indexes the
docs and serves plain HTTP for a reverse proxy that terminates TLS. Copy
.env.example to .env and set the Google sign-in client to require login.
Each user then gets their own data. See OPERATIONS.md.
Upgrade, back up, remove
docker pull ghcr.io/voitta-ai/voitta-bookmarklet:latest
docker rm -f voitta-bookmarklet # the data volume is kept
# then re-run the docker run command from Quick start
docker run --rm -v voitta-bookmarklet-data:/data -v "$PWD":/backup alpine \
tar czf /backup/voitta-bookmarklet-data.tgz -C /data . # back up
docker rm -f voitta-bookmarklet && docker volume rm voitta-bookmarklet-data # remove everything
Coming from Voitta Compute (the previous name)? The old
ghcr.io/voitta-ai/voitta-compute image gets no new releases. Your data
is in the voitta-compute-data volume; copy it into the new one before
the first docker run:
docker rm -f voitta-compute
docker volume create voitta-bookmarklet-data
docker run --rm -v voitta-compute-data:/from -v voitta-bookmarklet-data:/to \
alpine cp -a /from/. /to/
The macOS app moves its data from Voitta Compute to Voitta Bookmarklet
on its first launch. Quit Voitta Compute first, then delete
Voitta Compute.app.
Configuration
Settings are edited in the panel (gear icon). It has a Global tab and one tab
for each plugin with settings. They are stored in
~/.config/voitta-bookmarklet/settings.json, or /data/config/settings.json
under Docker. You don't need to edit that file by hand.
Plugins add site-specific tools and prompts. Six ship today: default
(always on), ebay, google, linkedin, veed and voitta-enterprise. See
docs/05-plugins.md.
Development
Layout
voitta-bookmarklet/
├── backend/ FastAPI + Chainlit, agent loop, tool registry
├── frontend/ Vite IIFE bundle, React widget, primitives
├── plugins/ Host-scoped extensions (manifest + BE module + FE widget + docs + prompt)
├── docs/ *** MASTER COPY of all prose docs ***
├── lib-sources/ Vendored libraries as git submodules (see below)
├── rag/ Built RAG indexes (gitignored, rebuildable)
├── scripts/ Dev tooling (RAG builder)
├── Dockerfile Container image (frontend build + backend + docs RAG)
├── start.sh Run uvicorn directly (also the container's entrypoint)
├── tray.sh macOS menu-bar tray (uvicorn on a daemon thread)
└── build.sh Install FE deps, build FE bundle, set up BE venv
Chainlit owns the chat context. The React frontend talks to it through
@chainlit/react-client. One FastAPI process at 127.0.0.1:12358 serves
both the Chainlit socket and the built bookmarklet bundle. A plain-HTTP
sibling listener on :12359 serves the bridge for pages with a strict CSP.
Single source of truth for docs:
- Core docs →
docs/- Plugin docs →
plugins/<name>/docs/
build_app.shcopies both intosrc/voitta_bookmarklet/resources/at build time. Theresources/subdirectories (docs/,frontend_dist/,plugins/,vendor_js/) are gitignored — never edit them directly.
lib-sources: vendored libraries
These libraries live here as git submodules so the LLM can grep through
their source via the RAG code corpus:
| Submodule | Indexed roots | Why |
|---|---|---|
pallets/jinja | src/jinja2/, docs/, examples/ | Python — Jinja2 template engine source + RST API docs |
kieler/elkjs | src/, typings/ | TypeScript — ELK layout engine (used by kind="elk") |
eclipse/elk | plugins/, docs/, test/ | Java — Eclipse ELK algorithm implementations |
mrdoob/three.js | src/, docs/, examples/jsm/ | JavaScript — three.js core + API docs |
After cloning, run:
git submodule update --init --recursive --depth 1
To bump pinned versions later:
git submodule update --remote --merge
git add lib-sources/<repo>
git commit -m "Bump <repo> to <sha>"
The Docker image leaves lib-sources/ out, so it has the docs corpus only.
RAG
Two corpora, both Chroma (dense) + bm25s (sparse) with hybrid score fusion:
python scripts/build_rag.py # both corpora
python scripts/build_rag.py --corpus docs # docs/ + plugins/*/docs/ (fast, ~1s)
python scripts/build_rag.py --corpus code # lib-sources/* (slower, ~1 min)
python scripts/build_rag.py --corpus code --repo three.js # one repo only
python scripts/build_rag.py --corpus code --repo three.js,elkjs # subset
Each run is a full rewrite of the named corpus — --repo three.js
REPLACES the code corpus with just three.js chunks, it doesn't merge.
Use it for fast iteration after bumping a submodule, then rebuild
all when you're done.
After editing any file under docs/ or plugins/*/docs/, always run:
python scripts/build_rag.py --corpus docs
--corpus docs only touches the docs index, so you don't pay the
cost of re-walking lib-sources/.
The LLM queries them via the rag_query tool — pass corpus="docs"
(default) or corpus="code". The code corpus returns chunks with
repo, path, folder, lang, kind (module / class / function /
method), and symbol metadata so the model can navigate to a
specific file or pull neighbouring chunks via rag_get_chunk_range.
Indexes live under rag/.chroma{,_code}/ + rag/.bm25{,_code}/ —
gitignored, ~few MB combined, rebuilt in ~1 min.
MCP endpoint
The BE exposes a FastMCP server at /mcp (streamable HTTP, on the
app's own port) for external MCP clients such as Claude Code. It carries
three tool families, each behind its own switch in the tray's Settings
dialog (all off by default):
- Agent tools (
vb_*, switch "Expose agent tools to external agents") — the tool registry the in-app agent uses, so an external agent can do everything the in-app agent can, with no API key in the app.vb_sessions→vb_instructions(the in-app agent's system prompt for that page) →vb_list_tools/vb_describe_tools→vb_call_tool. Page tools run in the chosen bookmarklet tab as its user. Not offered:ask_user_question(the external agent asks its own user). Seebackend/app/services/mcp_registry.py. - Debugging tools (
mcp_*, switch "Enable MCP debugging tools") — inspect the app itself:mcp_sessions,mcp_page,mcp_eval(raw JS in a tab),mcp_screenshot, devtools capture. Seebackend/app/services/mcp_server.py. - In-app agent (
mcp_inject_text, switch "Let external agents message the in-app agent") — post a message into a tab's chat; the in-app agent runs it as the user's own message, on the app's model access.
A family that is off is neither listed nor callable. Every tool's
description starts with [Agent tools], [Debugging] or [In-app agent], and the server
instructions tell clients which family to use for what. The endpoint is
also loopback-only and refuses requests carrying a browser Origin.
Under Docker the peer is the container bridge, not loopback, so /mcp
refuses it; run from source to use it.
Connect Claude Code (the URL is in the tray's Settings dialog):
claude mcp add --transport http voitta-bookmarklet https://127.0.0.1:12358/mcp/
Docs
docs/ has the numbered prose docs (overview, architecture,
frontend, providers, tool catalogue, plugins, reports, workspace). They're
indexed into the docs RAG corpus alongside every plugin's docs/ tree,
so the LLM can look up its own design without leaving the chat.
OPERATIONS.md is the operator's reference.
Tests
cd backend && ./.venv/bin/python -m pytest