freebuff-bridge
by toyro396133
README.md
# freebuff-bridge
> A communication server for **Freebuff Desktop** — a bridge between agents/scripts and Desktop sessions.
**[English](README.md) · עברית ([README.he.md](README.he.md))**
Any tool or script (any language, any platform) can open a new session or talk to an existing agent session, watch live activity, and manage work — all over **HTTP**, **MCP**, or a **Web UI**.
```
┌─────────────────┐ HTTP + SSE ┌────────────────────┐
│ bridge (7777) ├──────────────────►│ orchestrator (dynamic) │ ← Freebuff Desktop
│ bun + Hono │ /api/threads └─────────┬──────────┘
│ reads DB │ /api/thread/:id/message
│ writes via API │ │
│ streams SSE │ ▼
└────────┬────────┘ ┌────────────────────┐
│ reads │ SQLite (.freebuff)│
▼ └────────────────────┘
┌─────────────────┐
│ <project>/ │
│ desktop-v2.db │ ← Freebuff's own DB per project
└─────────────────┘
```
The bridge talks to both the orchestrator (live) and the SQLite audit log (offline), and keeps working even if one of them is unavailable (falls back to **DB-only mode** with a `fallback` flag).
---
## Features
| Area | What you get |
|---|---|
| **REST API** (`/v1/*`) | Threads CRUD, send prompts, stop/resume, switch model/harness live, queue control, SSE streams, wake |
| **MCP server** (stdio) | **14 tools** (`list_projects`, `send_prompt`, `resume_thread`, `set_agent`, `wake_freebuff`, queue tools…) for Claude Code / Cline / any MCP agent |
| **Web UI** (port 7778) | RTL Hebrew dashboard — projects, all threads, single-thread view with state + queue + action buttons |
| **Wake on demand** | `POST /v1/wake` launches `Freebuff.exe` if it's down; `send_prompt`/`enqueue_prompt` auto-wake the app instead of failing |
| **Self-healing** | Discovers the orchestrator's **dynamic port** from its log; re-resolves on port change mid-run; SSE streams reconnect automatically |
| **Version compatibility** | Reads the installed Freebuff version, checks it against a verified table, warns in banner + `/v1/info` + UI |
| **Shape probe** | Validates the live `/api/projects` schema at startup against the verified contract — catches drift even inside a "verified" version |
| **Windows services** | Runs 24/7 via nssm (`FreebuffBridge` on 7777, `FreebuffBridgeUi` on 7778) |
| **Examples** | Copy-paste recipes in 4 languages (Python, bash, PowerShell, make) |
---
## Quick install
```bash
# Prerequisite: Bun (https://bun.sh)
bun install
bun run init # creates ~/.config/freebuff-bridge/instances.json
bun run serve # listens on 127.0.0.1:7777 (localhost-noauth)
# checks
bun run typecheck # tsc --noEmit
bun run test # 175 tests across 10 files
```
That's it — the bridge is live. Open the Web UI:
```bash
bun run ui # Hono JSX + RTL Hebrew on 127.0.0.1:7778
# → open http://127.0.0.1:7778/
```
### One-liner smoke test
```bash
curl -s http://127.0.0.1:7777/v1/info | jq .
```
---
## curl examples
### Reads
```bash
# health + self info (includes orchestrator, freebuff version, shape probe)
curl http://127.0.0.1:7777/v1/info
# all configured machines
curl http://127.0.0.1:7777/v1/instances
# all projects the orchestrator sees
curl http://127.0.0.1:7777/v1/projects
# threads of one project
curl "http://127.0.0.1:7777/v1/projects/C:/path/to/project/threads"
# a specific session (from the DB)
curl http://127.0.0.1:7777/v1/threads/<THREAD_ID>
# recent messages of a session
curl "http://127.0.0.1:7777/v1/threads/<THREAD_ID>/messages?limit=20"
```
### Writes (go through the live orchestrator)
```bash
# open a new session in a chosen project
curl -X POST http://127.0.0.1:7777/v1/threads \
-H 'content-type: application/json' \
--data-raw '{"title":"[bridge] test","projectPath":"C:/my-project","harnessId":"codebuff","model":"deepseek/deepseek-v4-flash"}'
# send a prompt to a session (endpoint is /message, not /prompt)
curl -X POST http://127.0.0.1:7777/v1/threads/<THREAD_ID>/message \
-H 'content-type: application/json' \
--data-raw '{"text":"Check my email"}'
# stop a running turn
curl -X POST http://127.0.0.1:7777/v1/threads/<THREAD_ID>/stop
```
### Resume a stuck session & switch model in real time
```bash
# bring a session back from paused → running
curl -X POST http://127.0.0.1:7777/v1/threads/<THREAD_ID>/resume
# → {"ok":true,"thread":{...}}
# switch only the model (harnessId is auto-filled from current state)
curl -X PATCH http://127.0.0.1:7777/v1/threads/<THREAD_ID> \
-H 'content-type: application/json' \
--data-raw '{"model":"openai/gpt-5.6-luna"}'
# switch model + harness together
curl -X PATCH http://127.0.0.1:7777/v1/threads/<THREAD_ID> \
-H 'content-type: application/json' \
--data-raw '{"harnessId":"codebuff","model":"deepseek/deepseek-v4-flash"}'
# un-pause via PATCH (alias)
curl -X PATCH http://127.0.0.1:7777/v1/threads/<THREAD_ID> \
-H 'content-type: application/json' \
--data-raw '{"queuePaused":false}'
```
⚠️ **PATCH gotchas:**
- When changing only `model`, the bridge reads current state first to fill in `harnessId` — the orchestrator requires **both together**.
- If the model is rejected by your tier (`rejected: true`), the thread gets `model: null` and the previous value is lost. Always check `thread.model` in the response.
- The orchestrator endpoint is `/api/thread/<id>/agent`, not `/model` (verified against the live bundle).
### Wake Freebuff Desktop on demand
```bash
curl -X POST http://127.0.0.1:7777/v1/wake
# → {"ok":true,"alreadyRunning":false,"exePath":"C:/.../Freebuff.exe","port":53810,"elapsedMs":4210}
```
When the orchestrator is down and `autoWake` is on (default), `send_prompt` / `enqueue_prompt` launch Freebuff automatically and retry once instead of returning 502.
### Queue control
```bash
# list a session's queue (pending + done, sorted by position)
curl http://127.0.0.1:7777/v1/threads/<THREAD_ID>/queue
# → {"thread":{...}, "items":[{id, state, prompt, position, ...}], "pending":N}
# enqueue a prompt (runs when the current turn finishes)
curl -X POST http://127.0.0.1:7777/v1/threads/<THREAD_ID>/queue \
-H 'content-type: application/json' \
--data-raw '{"text":"collect today's PRs","label":"pr-digest"}'
# reorder (lower position = runs earlier)
curl -X POST http://127.0.0.1:7777/v1/threads/<THREAD_ID>/reorder \
-H 'content-type: application/json' \
--data-raw '{"id":"<ITEM_ID>","position":-1}'
# edit a queued item's prompt before it runs
curl -X POST http://127.0.0.1:7777/v1/queue/<ITEM_ID>/edit \
-H 'content-type: application/json' \
--data-raw '{"prompt":"rewritten prompt text"}'
# delete from queue
curl -X POST http://127.0.0.1:7777/v1/queue/<ITEM_ID>/delete
# promote to the front (runs immediately)
curl -X POST http://127.0.0.1:7777/v1/queue/<ITEM_ID>/send-now
```
⚠️ **Queue rules:**
- If `queuePaused=true`, items accumulate and don't run until you call `resume_thread` (or `PATCH {"queuePaused":false}`).
- If `queuePaused=false`, items run as soon as the current turn finishes (auto-promote to running).
- `reorder` can fail with 409 "item is not queued" once the item is already running/done.
### Live events (SSE)
```bash
# all orchestrator events
curl -N -H 'accept: text/event-stream' http://127.0.0.1:7777/v1/events
# events for one session
curl -N -H 'accept: text/event-stream' http://127.0.0.1:7777/v1/threads/<THREAD_ID>/stream
```
---
## Running as Windows services (24/7)
The bridge and the Web UI can run as Windows services via **nssm** — auto-start with the machine, restart-on-crash, logs to `.freebuff/`, and wake Freebuff automatically when there is work.
```bash
# one-time: install nssm
winget install -e --id NSSM.NSSM
# install (elevated)
powershell -ExecutionPolicy Bypass -File .\install-service.ps1 # FreebuffBridge (API, 7777)
powershell -ExecutionPolicy Bypass -File .\install-ui-service.ps1 # FreebuffBridgeUi (UI, 7778)
# uninstall (elevated)
powershell -ExecutionPolicy Bypass -File .\uninstall-service.ps1
powershell -ExecutionPolicy Bypass -File .\uninstall-ui-service.ps1
```
**Why nssm?** `sc create` alone can't work here — bun.exe never registers with the Service Control Manager, so SCM kills the service after 30 seconds (error 1053). nssm is the standard wrapper that manages bun as a child process.
**LocalSystem + paths:** services run as SYSTEM, so env overrides (`FREEBUFF_BRIDGE_APPDATA`, `FREEBUFF_BRIDGE_LOCALAPPDATA`, `FREEBUFF_BRIDGE_WAKE_EXE`) are written to the service's `Environment` value (REG_MULTI_SZ — the official SCM mechanism). Note: do **not** use nssm `AppEnvironmentExtra` — any `nssm set` call replaces the whole list.
The tray icon (`bun run tray`) detects when the services already hold ports 7777/7778 and doesn't start duplicate copies.
---
## MCP server for external agents (Claude Code / Cline)
The bridge exposes an **MCP server** on stdio that talks directly to Claude Code / Cline / any MCP-aware agent. **14 tools** are defined:
**Session control (7):** `list_projects` · `list_threads` · `get_thread` · `send_prompt` · `stop_thread` · `resume_thread` · `set_agent`
**Wake (1):** `wake_freebuff`
**Queue control (6):** `list_queue` · `enqueue_prompt` · `reorder_queue_item` · `edit_queue_item` · `delete_queue_item` · `send_queue_item_now`
**Use it from Claude Code (`~/.claude/mcp.json`):**
```json
{
"mcpServers": {
"freebuff-bridge": {
"command": "bun",
"args": ["run", "C:/path/to/freebuff-bridge/src/bridge/mcp.ts"]
}
}
}
```
Make sure the bridge is running first: `bun run serve`. The MCP server connects automatically to `http://127.0.0.1:7777`.
---
## Security
- **Default bind: `127.0.0.1`** (loopback only). The bridge does not expose itself to the network.
- **`localhost-noauth` mode** (default): any local process can talk without a token.
- **`strict` mode**: requires `Authorization: Bearer <token>`. Every instance in `instances.json` gets a unique token.
- **Admin token** via env var (`FREEBUFF_BRIDGE_ADMIN_TOKEN`) grants access to every instance.
- **Per-alias env override**: `FREEBUFF_BRIDGE_TOKEN_<ALIAS_UPPER>` takes priority over the file.
```bash
# strict + admin token
FREEBUFF_BRIDGE_HOST=0.0.0.0 FREEBUFF_BRIDGE_BIND_MODE=strict \
FREEBUFF_BRIDGE_ADMIN_TOKEN=$(openssl rand -hex 32) \
bun run serve
```
---
## Project structure
```
src/
bridge/
db.ts # SQLite + WAL/NORMAL/busy_timeout + typed reads
instances.ts # config store (load/save/find/env-override)
auth.ts # bearer header regex + decideAuth()
ratelimit.ts # sliding-window in-memory limiter
orchestrator.ts # HTTP client to Freebuff's orchestrator (dynamic port discovery)
projects.ts # multi-project discovery (.../.freebuff scan)
wake.ts # find exe + discover port from log + spawn + poll
compat.ts # installed Freebuff version (asar/exe) + compat table
shape.ts # /api/projects schema probe (validate + HTTP)
server.ts # Hono REST API (/v1/*) — reads DB, writes via orchestrator
mcp.ts # MCP stdio server — 14 tools that proxy /v1/*
web/
ui.tsx # Hono JSX server (7778) — SSR HTML + 9 POST actions + SSE
views/ # layout, projects, threads, thread, queue views
cli/
serve.ts # bun run serve (main entry — bridge on 7777)
ui.ts # bun run ui (Web UI on 7778)
tray.ts # bun run tray (Windows tray icon)
tray/
tray.ps1 # PowerShell NotifyIcon + context menu + health poller
tests/ # 10 files, 175 tests
examples/ # copy-paste integrations in 4 languages
```
---
## Windows notes
- `bun:sqlite` opens with `journal_mode=WAL`, `synchronous=NORMAL`, `busy_timeout=5000ms`, and `foreign_keys=ON` so it never collides with the Desktop running in parallel.
- Hebrew/UTF-8 paths are supported.
- The orchestrator port is **dynamic** in current builds — the bridge reads it from `%APPDATA%\Freebuff\logs\orchestrator-stderr.log` (line `listening on http://127.0.0.1:PORT`). Manual check: `curl http://127.0.0.1:<PORT>/api/projects` should return JSON, or just `GET /v1/info` and look at the `orchestrator` field.
## Git
- `bun.lockb` is **meant to be committed** — don't add it to `.gitignore`.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues