Lobe turns deliberate X bookmarks into organized, searchable context. You keep using X's native bookmark button. Lobe captures the post at that moment, returns control immediately, then classifies why you saved it in a durable background job.
This repository contains the complete first product slice:
- A Chrome Manifest V3 extension built with WXT and React 19
- A Bun and Hono API with a durable background worker
- PostgreSQL, Drizzle, and pgvector persistence
- Intent classification and embeddings through Vercel AI SDK
- GPT-5.6 Luna for classification and selector repair
text-embedding-3-smallembeddings with indexed, sub-200 ms retrieval- A responsive React 19 library with search, filters, corrections, and taste signals
- A shared shadcn component package, Hugeicons, Geist, and a pure-black theme
The current scope is intentionally explicit-save only, X only, and does not include MCP or passive browsing analysis.
The extension does not redraw or replace X's bookmark icon. It listens for a click on X's real semantic controls:
article[data-testid="tweet"]
button[data-testid="bookmark"]
button[data-testid="removeBookmark"]
That keeps X responsible for the exact SVG, spacing, hover state, animation, and saved state. Lobe extracts only the nearest post, including quoted-post boundaries, media, author, canonical status URL, and an optional rendered screenshot.
The selectors live in a versioned recipe. When a layout no longer matches, the extension reports a compact semantic DOM sketch with no post text or handles. The server can derive one replacement recipe with GPT-5.6 Luna, validate it, cache it, and share it with every extension install.
apps/
extension/ WXT extension, X capture, popup, and settings
server/ Hono API and background worker
web/ Searchable bookmark library and taste profile
packages/
ai/ Vercel AI SDK classification, embeddings, and recipe repair
db/ Drizzle schema, migration, repositories, and job queue
shared/ Runtime schemas, intent metadata, messages, and recipes
ui/ Shared shadcn primitives and Tailwind design tokens
The original product exploration is preserved in
lobe-brainstorm-report.html.
Requirements: Bun and Docker.
bun install
cp .env.example .env
openssl rand -hex 24Put the generated value in LOBE_API_TOKEN. Add OPENAI_API_KEY to use live AI
classification and semantic embeddings. The OpenAI key stays on the server and
must never be put in a VITE_ or WXT_PUBLIC_ variable.
Start and migrate PostgreSQL:
bun run db:up
bun run db:migrateRun the API and web app in separate terminals:
bun run dev:server
bun run dev:webOpen http://localhost:5173, then connect with the same
LOBE_API_TOKEN.
Build the unpacked extension:
bun run --filter '@lobe/extension' buildThen:
- Open
chrome://extensionsin Chrome, Brave, Arc, Edge, or another Chromium browser. - Enable Developer mode.
- Choose Load unpacked.
- Select
apps/extension/.output/chrome-mv3. - Open Lobe's extension settings and enter
http://localhost:8787plus yourLOBE_API_TOKEN. - Bookmark or unbookmark a post on X normally.
For live extension development, run bun run dev:extension instead.
native X bookmark click
-> persist capture and queue job in one transaction
-> return 202 immediately
-> classify intent in the worker
-> create pgvector embedding
-> mark ready and update the library
Intent is inferred as one of: Try it, Build similar, Learn, Reference, Buy, or Share. A confident result stays silent. A low-confidence result shows a compact, nonblocking prompt in the lower corner of X. The user can select the best intent and explain why the post mattered, or dismiss the prompt and leave the save marked as unsure. Submitted explanations are attached to the save and retrieved through the post's vector as examples when similar posts are classified later. Every unsure save also stays available in the library's Needs review queue.
Without an OpenAI key, the API remains usable with transparent deterministic classification and PostgreSQL text search. It never presents fallback output as an AI result.
Run the complete local gate:
bun run checkThe database integration suites are explicit because they require the Docker database:
bun run --filter '@lobe/db' test:db
bun run --filter '@lobe/server' test:dbBenchmark the indexed search path with 10,000 generated saves and a 200 ms failure budget:
bun run benchmark:vectorThe generated Chrome artifact is available under
apps/extension/.output/chrome-mv3 after a build, or as a zip after
bun run --filter '@lobe/extension' zip.