Files
braindump/README.md
T
codingetandClaude a5f62512e8 initial implementation: brain-dump session server with dual LLM backends
Headless Koa v3 server exposing REST + SSE around the brain-dump skill:
- AgentBackend adapter seam with two implementations: Claude Agent SDK
  (skill loading, session resume via sdk_session_id) and a hand-rolled
  OpenAI-compatible tool-calling loop (vault-scoped file tools, SKILL.md
  injected into the system prompt, history persisted in SQLite)
- session CRUD + one-turn-at-a-time SSE streaming (text deltas, file_write
  events, turn_complete), per-session lock, abort on disconnect
- node:sqlite storage (sessions, messages, turn_events) with migrations
- vault allow-list + symlink-aware path-escape prevention (incl. dangling
  symlink defense), read-only git status/diff endpoints, configurable CORS
- deterministic mock chat-completions server (scripts/mock-openai.ts) for
  end-to-end testing without a real model

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:48:17 +00:00

103 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# braindump
A headless server that runs brain-dump note-taking sessions into an Obsidian-style vault, exposing a web API meant to be consumed by a web UI or Obsidian plugin. An LLM agent interviews you conversationally and writes linked markdown notes into the vault as the conversation progresses, following the `brain-dump` skill (incremental writes, 13 questions per message, no extrapolation, TODO markers, `projects/<name>/` layout with wikilinks).
Two interchangeable LLM backends, selected per session:
- **`claude-sdk`** — the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). Gets file tools, skill loading (`~/.claude/skills/brain-dump`), and session resume from the SDK. Uses your Anthropic credentials.
- **`openai-compat`** — any OpenAI-compatible chat-completions endpoint (openrouter, vllm, ollama). A built-in agent loop supplies vault-scoped file tools and injects the skill into the system prompt.
## Requirements
- Node ≥ 22 (uses `node:sqlite`)
- For `claude-sdk`: Anthropic credentials (the SDK inherits Claude Code auth or `ANTHROPIC_API_KEY`)
## Setup
```sh
npm install
cp config.example.jsonc config.jsonc # edit: vaults, backends, models
npm run web # boots, auto-migrates the db, listens
```
Config is JSONC; path from `BRAINDUMP_CONFIG` (default `./config.jsonc`). `"${ENV_VAR}"` string values are resolved from the environment; `~` is expanded in paths. Only vaults listed in `vaults` are writable — that allow-list is the write fence.
> **Note:** an exported `PORT` env var overrides the config file's `port` (watch out under code-server, which exports `PORT` globally). `BRAINDUMP_DB` overrides `dbPath`.
There is **no auth** — put it behind an authenticating reverse proxy. CORS origins are configurable (`app://obsidian.md` is in the example for Obsidian plugins).
## API
Base path `/api`. Errors are `{"ok":false,"error":"<code>"}`.
| Method | Path | Description |
|---|---|---|
| GET | `/health` | liveness |
| GET | `/vaults` | configured vaults |
| POST | `/sessions` | create session — `{vault, topic, backend?, model?}` |
| GET | `/sessions[?status=]` | list sessions |
| GET | `/sessions/:id` | session + transcript |
| PATCH | `/sessions/:id` | update `{topic?, status?}` |
| DELETE | `/sessions/:id[?hard=true]` | archive (soft) or delete row; never touches vault files |
| POST | `/sessions/:id/messages` | send a user message — **response is an SSE stream** for the turn |
| GET | `/sessions/:id/file?path=` | read a vault file (path-validated) |
| GET | `/sessions/:id/git/status` | `git status --porcelain`, parsed |
| GET | `/sessions/:id/git/diff[?path=]` | raw diff (read-only; commits are always left to you) |
### The turn stream
`POST /sessions/:id/messages` with `{"text": "..."}` responds with `text/event-stream`:
```
event: text data: {"delta":"What's the goal of the project?"}
event: tool_start data: {"tool":"write_file","path":"projects/x/x.md"}
event: file_write data: {"path":"projects/x/x.md","bytes":412}
event: tool_end data: {"tool":"write_file","ok":true}
event: assistant_message data: {"text":"...full assistant text..."}
event: turn_complete data: {"turnCount":1}
```
Consume with `fetch` + a ReadableStream reader (or `curl -N`). One turn at a time per session — a concurrent POST gets `409 turn_in_progress`. Closing the connection aborts the turn; files already written stay (writes are incremental by design). Heartbeat comments keep proxies from timing out.
## Curl walkthrough
```sh
# create a session
SID=$(curl -s localhost:3000/api/sessions -H 'content-type: application/json' \
-d '{"vault":"brain","topic":"my-project","backend":"claude-sdk"}' | jq -r .session.id)
# first turn — watch text stream and files land
curl -N localhost:3000/api/sessions/$SID/messages -H 'content-type: application/json' \
-d '{"text":"Brain dump my-project: a CLI tool that does X, written in Go, status: early prototype."}'
# what changed in the vault?
curl -s localhost:3000/api/sessions/$SID/git/status | jq
curl -s "localhost:3000/api/sessions/$SID/file?path=projects/my-project/my-project.md" | jq -r .content
# keep going — the agent asks 1-3 questions per turn and extends the notes
curl -N localhost:3000/api/sessions/$SID/messages -H 'content-type: application/json' \
-d '{"text":"It also has a TUI mode. TODO on my side: pick a license."}'
# review + commit yourself when happy (braindump never commits)
git -C ~/git/brain status
```
## Development
```sh
npm run typecheck
npm test # pathSafety + Db unit tests
PORT=11500 npx tsx scripts/mock-openai.ts # deterministic mock chat-completions server
```
`scripts/mock-openai.ts` scripts a full tool-calling session (including a vault-escape probe when the user message contains `ESCAPE`), so the whole openai-compat path can be tested end-to-end without a real model — point `backends.openai-compat.baseUrl` at it.
### Layout
- `src/agent/``AgentBackend` interface + the two adapters (`claudeSdk/`, `openaiCompat/`) and the stub
- `src/vault/` — vault allow-list resolution, `pathSafety` (symlink-aware escape prevention), read-only git helpers
- `src/webapi/` — Koa router, SSE plumbing, middleware
- `src/db/``node:sqlite` wrapper + migrations (sessions, messages, turn_events)
Backends own message persistence: `openai-compat` stores its full replayable history in SQLite; `claude-sdk` stores display rows and resumes via the SDK's own transcript (`sdk_session_id`).