Skip to content

Repository files navigation

Keb

A Chrome extension that turns your browser into a personal knowledge base, powered by pi. Add any web page to your knowledge base with two clicks, then consult and browse structured, interlinked wiki pages compiled by your LLM — all from Chrome's side panel.

The bridge server supports both single-user mode (local development, self-hosted) and hosted multi-user mode (deploy as a SaaS, with user signup/login and per-user workspace isolation).

Built with React 19, TypeScript, Vite, Tailwind CSS v4, and shadcn/ui. Managed as a pnpm workspace monorepo.

Features

  • Add URLs — Right-click any page and choose "Add to knowledge base", or paste a URL directly. pi fetches the page, extracts key concepts, and compiles structured summaries and cross-linked concept pages.
  • Add current page content — Capture the full text of the current tab and compile it directly (no URL fetch needed).
  • Consult — Ask natural-language questions and get streaming LLM responses drawn from your knowledge base, complete with inline references.
  • Browse — Explore all compiled documents and extracted concepts in a scrollable list, with source tracking.
  • Clear workspace — Wipe all documents, summaries, and concepts from a workspace with one click (Browse tab → Danger Zone). Instant — no LLM process needed.
  • Multi-workspace — Switch between isolated knowledge bases for different projects or topics.
  • User authentication — Signup, login, JWT-based sessions. Each user gets an isolated workspace automatically created on signup.
  • Live streaming — Watch the LLM compile pages and answer queries in real time with event-level progress.
  • Standalone bridge — The WebSocket bridge runs independently of pi's TUI, so you can consult your knowledge base from Chrome anytime.

Architecture

┌─────────────────────────────────────────────────────┐
│                   Chrome Extension                   │
│  (React side panel + service worker)                │
│  packages/extension/                                │
└──────────┬──────────────────────────────────────────┘
           │ WebSocket (ws://) + HTTP
           ▼
┌─────────────────────────────────────────────────────┐
│                   Bridge Server                      │
│  packages/bridge/                                   │
│                                                     │
│  ┌──────────┐  ┌──────────────────────────────┐     │
│  │ HTTP Auth │  │  WebSocket Keb Operations     │     │
│  │ /api/*    │  │  add / query / sync / repair │     │
│  │           │  │  clear                       │     │
│  └─────┬─────┘  └──────────┬───────────────────┘     │
│        │                   │                         │
│        ▼                   ▼                         │
│  ┌──────────┐  ┌───────────────────────────┐        │
│  │ UserStore│  │  KebStore (pi-keb adapter)  │        │
│  │ (SQLite) │  │  → FilesystemStore        │        │
│  └──────────┘  └──────────┬────────────────┘        │
│                           │                          │
│                           ▼                          │
│                    pi --mode rpc                     │
│                    (spawned child processes)         │
└──────────────────────────┬──────────────────────────┘
                           │
                           ▼
              ~/.pi/agent/keb/workspaces/
                ├── alice/    (registry, source/, wiki/)
                ├── bob/
                └── ...
  1. @keb/bridge — Combined HTTP + WebSocket server. HTTP handles user auth (signup, login, token verification). WebSocket handles Keb operations (add, add-content, query, sync, repair). Written in JS with full JSDoc type annotations.
  2. @keb/extension — React side panel + TypeScript service worker. Built with Vite.
  3. pi-keb — pi extension providing the knowledge base. Included as a git submodule at packages/pi-keb/. The bridge imports its FilesystemStore directly for filesystem reads and workspace creation.

Knowledge base format

The compiled knowledge base lives on disk at ~/.pi/agent/keb/workspaces/<name>/ (or ~/.pi/agent/keb/ for default). Each workspace follows the Open Knowledge Format (OKF) v0.1:

workspace/
├── registry.json              # Operational cache (document tracking, dedup)
├── source/                    # Raw source files / fetched HTML
└── wiki/                      # OKF bundle root
    ├── index.md                  # Directory listing (Documents + Concepts sections)
    ├── log.md                    # Update history (created on workspace init)
    ├── summaries/                # Summary documents (type: Summary)
    │   ├── architecture.md
    │   └── ...
    └── concepts/                 # Cross-document topic syntheses (type: Concept)
        ├── caching-strategy.md
        └── ...

Summary frontmatter:

---
type: Summary
title: Architecture
description: Key architectural decisions and patterns.
resource: https://github.com/org/repo/blob/main/docs/architecture.md
tags: [architecture, design]
timestamp: 2026-05-26T14:30:00Z
keb_name: "architecture"       # producer key: docName slug
keb_source: "architecture.md"  # producer key: original filename
---

Concept frontmatter:

---
type: Concept
title: Caching Strategy
description: How caching is implemented across the architecture and design.
tags: [caching, performance]
timestamp: 2026-05-26T14:30:00Z
keb_name: "caching-strategy"              # producer key: concept slug
keb_sources: ["summary/architecture", "summary/design"]
keb_needs_review: false
---

Key differences from the legacy pi-keb format: OKF standard fields (type, title, description, tags, timestamp) replace custom frontmatter. keb_* keys preserve pi-keb-specific metadata. Wiki-links ([[...]]) are replaced with standard markdown links (/summaries/foo.md, /concepts/bar.md). Code-generated footers are removed.

Port & adapter pattern

The bridge follows the same port/adapter architecture as pi-keb:

Port Adapter (today) Swappable to
KebStore (keb read/workspace ops) PiKebStore → pi-keb's FilesystemStore —
UserStore (user credentials) SqliteUserStore (SQLite) PostgreSQL
(spawnPi) PiRpcSpawner → spawns pi --mode rpc —

Swap adapters by changing one factory call in bridge-server.js. Handlers never know which adapter is in use.

Prerequisites

  • pi installed and in your $PATH
  • pi-keb extension installed in pi

Installation

0. Prerequisites

Install pi, configure your preferred LLM provider, and install the pi-keb extension:

curl -fsSL https://pi.dev/install.sh | sh
# then inside pi, set up your LLM provider via /login
pi install git:github.com/auto-medica-labs/pi-keb

1. Clone the repo (with submodules)

git clone --recurse-submodules https://github.com/auto-medica-labs/keb.git
cd keb
pnpm install
pnpm build

If you already cloned without --recurse-submodules:

git submodule update --init --recursive

2. Start the bridge server

The bridge has two modes. Default is local mode (no auth needed).

Quick start (local mode)

pnpm bridge
# or: pnpm bridge:dev  (auto-restarts on file changes)

The first run compiles pi-keb's standalone adapter automatically. Keep it running while using the extension.

Configuration

To customize the bridge (change mode, set LLM keys, etc.), create a .env file from the example:

cp packages/bridge/.env.example packages/bridge/.env
# edit packages/bridge/.env with your keys and settings

The bridge auto-loads packages/bridge/.env if it exists. Key settings:

Variable Default Description
KEB_MODE local local (no auth) or hosted (auth required)
JWT_SECRET random JWT signing secret (required for hosted mode)
PORT 9876 HTTP + WebSocket listen port
HOST 127.0.0.1 Listen address (0.0.0.0 for Docker)
LLM_PROVIDER — Custom provider name (e.g., ollama, anthropic)
LLM_BASE_URL — API base URL for custom provider
LLM_MODEL — Model ID (e.g., llama3.1:8b, claude-sonnet-4-20250514)
LLM_API openai-completions API type: openai-completions, anthropic-messages, google-generative-ai, openai-responses
LLM_API_KEY — API key (required by pi, can be dummy for local models)
LLM_MODEL_NAME — Optional human-readable model name
LLM_REASONING — Set true for reasoning-capable models
LLM_THINKING off Thinking level: off, low, medium, high, xhigh
ADMIN_KEY — Secret for GET /api/status (sent via X-API-Key header)
R2_ACCOUNT_ID — Cloudflare account ID (for R2 backup sidecar)
R2_ACCESS_KEY_ID — R2 API token access key ID
R2_SECRET_ACCESS_KEY — R2 API token secret
R2_BUCKET — R2 bucket name (e.g., keb-backups)
R2_BACKUP_RETENTION_DAYS 30 Prune backups older than this many days
ANTHROPIC_API_KEY — Legacy: native pi Anthropic key (alternative to LLM_*)
PI_DEFAULT_PROVIDER — Legacy: default provider (e.g., anthropic)
PI_DEFAULT_MODEL — Legacy: default model (e.g., claude-sonnet-4-20250514)

See .env.example for all supported LLM providers.

Hosted mode (multi-user with auth)

In your .env, set:

KEB_MODE=hosted
JWT_SECRET=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")

Then run pnpm bridge as usual. Users can sign up at POST /api/signup and login at POST /api/login. Each user gets an isolated workspace.

3. Load the extension in Chrome

Option A — Local build (from source)

  1. Open chrome://extensions
  2. Enable Developer mode
  3. Click Load unpacked
  4. Select the packages/extension/dist/ directory

The extension icon will appear in your toolbar.

Option B — Chrome Web Store

Install Keb from the Chrome Web Store like any other extension. You still need to start the bridge server yourself.

Scripts

Script Description
pnpm build Production build of extension + landing page (Vite)
pnpm dev Vite dev server for UI development
pnpm bridge Start the WebSocket bridge server (runs build:pi-keb first)
pnpm bridge:dev Start bridge with auto-restart on changes (runs build:pi-keb first)
pnpm build:pi-keb Compile pi-keb standalone adapter
pnpm typecheck Type-check both packages (tsc + JSDoc)
pnpm lint Lint with oxlint (no-unused-vars, no-explicit-any)
pnpm format Auto-format all files with oxfmt
pnpm format:check Check formatting (CI-friendly)

Project Structure

keb/
├── pnpm-workspace.yaml
├── package.json                  # Root workspace orchestrator
├── .oxlintrc.json                # Lint rules
├── .gitmodules                   # pi-keb submodule declaration
├── AGENTS.md                     # AI agent instructions for this project
├── packages/
│   ├── pi-keb/                   # git submodule → github.com/auto-medica-labs/pi-keb
│   │   ├── extensions/keb/        #   pi extension source (TS)
│   │   │   ├── adapters/         #   FilesystemStore, HttpFetcher
│   │   │   ├── ports/types.ts    #   KnowledgeBaseStore interface
│   │   │   └── utils.ts          #   slugify, docNameFromFile, etc.
│   │   └── dist/standalone/      #   compiled JS + declarations (auto-generated)
│   ├── bridge/                   # @keb/bridge
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── tsconfig.build-pi-keb.json  # compiles pi-keb standalone adapter
│   │   ├── Dockerfile
│   │   ├── backup.Dockerfile   # Alpine + rclone backup sidecar image
│   │   ├── docker-compose.yml
│   │   ├── Caddyfile           # Reverse proxy config for production
│   │   ├── DEPLOYMENT.md       # Step-by-step production deployment guide
│   │   ├── entrypoint.sh
│   │   ├── .env.example
│   │   ├── scripts/
│   │   │   ├── build-pi-keb.js
│   │   │   └── backup-to-r2.sh    # Daily R2 backup script (rclone)
│   │   └── src/
│   │       ├── bridge-server.js   # HTTP + WebSocket server entry point
│   │       ├── adapters/
│   │       │   ├── pi-keb-store.js      # KebStore → wraps pi-keb's FilesystemStore
│   │       │   ├── pi-rpc-spawner.js   # spawns pi --mode rpc child processes
│   │       │   └── user-store-sqlite.js  # UserStore → SQLite adapter
│   │       ├── ports/
│   │       │   ├── keb-store.js         # KebStore interface
│   │       │   └── user-store.js       # UserStore interface
│   │       ├── handlers/
│   │       │   ├── auth-handler.js     # HTTP /api/signup, /api/login, /api/me
│   │       │   ├── add-url-handler.js  # URL fetch + compile
│   │       │   ├── add-content-handler.js # HTML captur e + compile
│   │       │   ├── repair-handler.js   # re-compile interrupted docs
│   │       │   ├── query-handler.js
│   │       │   ├── sync-handler.js
│   │       │   └── clear-handler.js    # wipe workspace (no pi process)
│   │       └── lib/
│   │           ├── auth.js             # JWT, bcrypt, username validation
│   │           └── utils.js            # logging, JSON, URL helpers
│   └── extension/                # @keb/extension
│       ├── package.json
│       ├── tsconfig.json
│       ├── vite.config.ts
│       ├── components.json       # shadcn/ui config
│       ├── index.html
│       ├── public/
│       │   ├── manifest.json     # Chrome extension manifest (Manifest V3)
│       │   └── icons/
│       └── src/
│           ├── components/ui/    # shadcn/ui components
│           ├── lib/
│           │   ├── store.ts      # chrome.storage.local cache + bridge config
│           │   ├── utils.ts      # cn(), slugify, normalizeUrl, hashing
│           │   ├── ws.ts         # WebSocket client (local + hosted modes)
│           │   └── api.ts        # HTTP client for login/signup/me
│           ├── sidepanel/
│           │   ├── main.tsx      # React entry point
│           │   ├── App.tsx       # Main app (auth flow, WS connection, state, tabs)
│           │   └── components/
│           │       ├── Header.tsx
│           │       ├── AuthPanel.tsx     # Login / signup form
│           │       ├── SettingsPanel.tsx  # Mode toggle, bridge URL, logout
│           │       ├── AddPanel.tsx
│           │       ├── QueryPanel.tsx
│           │       ├── BrowsePanel.tsx
│           │       └── Footer.tsx
│           ├── service-worker.ts # Background service worker
│           └── index.css         # Global styles (Tailwind + shadcn theme)

Docker

# Build
docker build -f packages/bridge/Dockerfile -t keb-bridge .

# Run
docker run -d \
  --name keb-bridge \
  -p 9876:9876 \
  --env-file packages/bridge/.env \
  -v keb-data:/root/.pi/agent/keb \
  keb-bridge

The Dockerfile has four stages:

  1. pi-layer — installs pi + pi-keb globally
  2. deps-layer — installs bridge npm deps (including TypeScript)
  3. pi-keb-build — compiles pi-keb standalone adapter to JS
  4. final — minimal node:22-slim with production deps only

Reverse proxy with Caddy

A Caddyfile is included in packages/bridge/. It proxies api.mdevd.co/keb/v1 → the bridge container, stripping the /keb/v1 path prefix so the bridge receives clean /api/* paths. Caddy auto-provisions TLS via Let's Encrypt and handles WebSocket upgrades transparently.

Important: the path pattern is handle_path /keb/v1* (not /keb/v1/*) — this ensures the WebSocket root path /keb/v1 (no trailing slash) matches. The extension connects to wss://api.mdevd.co/keb/v1 with no additional path segment.

Backup sidecar

The project includes a dedicated backup sidecar (keb-backup) that archives the Keb data directory daily at midnight Bangkok time (00:00 UTC+7) and uploads it to Cloudflare R2. It runs as an Alpine container with rclone + dcron, mounting the data directory read-only. Configure it via R2_* environment variables (see table above). See packages/bridge/DEPLOYMENT.md for full setup instructions.

Docker Compose snippet

services:
  bridge:
    build:
      context: ./packages/bridge
      dockerfile: Dockerfile
    env_file: packages/bridge/.env
    volumes:
      - keb-data:/root/.pi/agent/keb
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./packages/bridge/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
    restart: unless-stopped

  backup:
    build:
      context: ./packages/bridge
      dockerfile: backup.Dockerfile
    env_file: packages/bridge/.env
    volumes:
      - keb-data:/data:ro
    restart: unless-stopped

volumes:
  keb-data:
  caddy-data:

Horizontal scaling

Running multiple bridge instances behind a load balancer is not safe with the default adapters:

Component Default adapter Multi-instance issue
UserStore SQLite (users.db) File-level locking — concurrent writes from two instances will corrupt or block.
KebStore FilesystemStore (markdown + JSON) pi child processes writing to the same workspace will race on registry entries and files.

Even with sticky sessions, cross-user writes collide on the shared users.db. The architecture is designed for vertical scaling (single instance, more resources) out of the box.

To scale horizontally, swap the adapters to distributed backends — the port/adapter pattern makes this a one-line change in bridge-server.js:

UserStore port  →  swap SQLite for a Postgres adapter     (shared users.db)
KebStore port    →  swap FilesystemStore for S3/Postgres    (shared Keb data)

With both stores backed by distributed databases, multiple bridge instances can share state safely. The bridge server itself is stateless (JWT verification uses only the shared secret, no session store).

HTTP API

Health check (all modes)

GET /api/healthcheck — no auth required, works in both local and hosted modes.

curl http://127.0.0.1:9876/api/healthcheck
# → {"status":"ok","mode":"hosted"}

Used by Docker healthcheck, monitoring, and load balancer probes.

Status (admin only)

GET /api/status — live runtime metrics. Requires X-API-Key header matching ADMIN_KEY env var. Returns 501 if ADMIN_KEY is not configured.

curl -H "X-API-Key: your-admin-key" http://127.0.0.1:9876/api/status

Response includes connected clients, active pi operations by type, and per-workspace document counts with last activity timestamps.

Auth endpoints (hosted mode only)

When KEB_MODE=hosted, the bridge exposes these endpoints on the same port as the WebSocket. In local mode all HTTP routes (except /api/healthcheck) return 404.

Endpoint Method Body Response
/api/signup POST {"username":"...", "password":"..."} {"token":"...", "username":"..."}
/api/login POST {"username":"...", "password":"..."} {"token":"...", "username":"..."}
/api/me GET (Bearer token header) {"username":"...", "createdAt":"..."}

Signup flow:

  1. Validates username (slugified, 3-30 chars, [a-z0-9-]) and password (min 8 chars)
  2. Hashes password with bcrypt (12 rounds)
  3. Stores user in ~/.pi/agent/keb/users.db
  4. Creates workspace at ~/.pi/agent/keb/workspaces/<username>/
  5. Returns JWT (30-day expiry)

Error responses: {"error":"..."} with HTTP 400 (validation), 401 (auth), 409 (duplicate), 500 (internal).

WebSocket Protocol

The bridge listens on ws://127.0.0.1:9876 by default (local mode). The extension's Settings panel lets you configure the bridge URL for either mode — hosted mode defaults to wss://api.mdevd.co/keb/v1. All messages are JSON.

Auth (hosted mode only)

In hosted mode, the first message must be:

{ "type": "auth", "token": "<jwt>" }

Response: {"type":"auth_ok", "username":"alice"} or error + close.

In local mode, no auth message is needed — clients can send any operation immediately.

Keb Operations

Message Fields Description
add operationId, url, workspace? Compile a URL into the knowledge base
add-content operationId, html, url?, title?, workspace? Compile captured page HTML
query operationId, text, workspace? Query the knowledge base
repair operationId, workspace? Re-compile interrupted documents
sync workspace? Full state dump (registry, index, summaries, concepts, workspaces)
clear workspace? Delete all workspace content (source/, wiki/, registry). Sends back empty sync_result. No pi process.

Bridge → Client: Error/StdErr

Message Fields Description
stderr operationId, text Raw stderr from pi child process
error operationId, message Operation-level error

Bridge URL Configuration

The Settings panel (gear icon) includes a Bridge URL input field, but it behaves differently per mode:

Mode Default Bridge URL Configurable?
Local ws://127.0.0.1:9876 Yes — editable in Settings
Hosted wss://api.mdevd.co/keb/v1 No — hardcoded at build time

In hosted mode, the bridge URL is a build-time constant (HOSTED_BRIDGE_URL in lib/env.ts, overridable via VITE_HOSTED_BRIDGE_URL env var). The Settings panel hides the URL input entirely when in hosted mode — there is no runtime override. In local mode, the URL is freely editable and persisted to chrome.storage.local.

Note: In hosted mode, the workspace field is ignored — the server enforces the authenticated username as the workspace for all operations. In local mode, workspace is optional; the bridge treats undefined as the default workspace (the extension sends "default" explicitly).

Every add/query/repair message includes a client-generated operationId (nanoid). The bridge echoes it back in all responses so the client can correlate streaming events with the originating request.

Responses include typed events (text_delta, tool_execution_start, tool_execution_end, agent_end) streamed in real time, plus a done / error message when the operation completes. All response frames carry the same operationId as the request.

Concurrent Operations

The bridge supports multiple concurrent operations over a single WebSocket connection. Each operation (add, query, repair) spawns an independent pi RPC process and is tracked by its operationId in a Map<operationId, ChildProcess>. Starting a new operation no longer kills in-progress ones — the client routes streamed events to the correct UI card via the echoed operationId.

Client-side: each tab (Add, Consult) tracks only its latest operation of that type. Starting a new add wipes the previous add's timeline; starting a new query wipes the previous query's timeline. Cross-tab concurrency works: an add can compile while a query streams its answer, each in its own tab.

Lint Rules

Configured in .oxlintrc.json:

Rule Description
eslint/no-unused-vars No unused variable declarations
typescript/no-explicit-any No explicit any type annotations

TypeScript strict mode also enforces noImplicitAny, noUnusedLocals, and noUnusedParameters at compile time.

License

This project uses a dual license:

Package License Scope
@keb/extension MIT Free for any use, modification, and distribution
@keb/bridge MIT with SaaS restriction Free for self-hosted use; no competing SaaS/cloud hosting permitted

The bridge license allows you to use, modify, and self-host the bridge freely, but prohibits offering it to third parties as a hosted, managed, or SaaS product where the primary value is the bridge's functionality itself.

Hosted Bridge URL

The extension's hosted mode bridge URL (wss://api.mdevd.co/keb/v1) is a build-time constant set in packages/extension/src/lib/env.ts. It is NOT configurable at runtime — the Settings panel hides the URL input in hosted mode. To target a different hosted bridge (e.g., a staging instance), set the VITE_HOSTED_BRIDGE_URL environment variable at build time:

VITE_HOSTED_BRIDGE_URL=wss://staging.example.com/keb/v1 pnpm build

Vite inlines this value into the bundle, so it cannot be changed after the extension is built.

About

A Chrome extension that turns your browser into a personal knowledge base

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages