Getting Started
Install, configure, and run Blackwood in a few minutes.
Contents
1. Prerequisites
- An OpenAI API key — used for transcription (Whisper), vision/OCR, chat, and embeddings.
- macOS (Apple Silicon or Intel) or Linux (x86_64 or arm64).
If building from source, you also need:
- Go 1.23+
- Node.js 20+ (for the web UI)
2. Install
Option A: Download a release
Grab the latest binary from GitHub Releases:
# macOS (Apple Silicon)
curl -L https://github.com/csweichel/blackwood/releases/latest/download/blackwood_darwin_arm64.tar.gz | tar xz
# macOS (Intel)
curl -L https://github.com/csweichel/blackwood/releases/latest/download/blackwood_darwin_amd64.tar.gz | tar xz
# Linux (x86_64)
curl -L https://github.com/csweichel/blackwood/releases/latest/download/blackwood_linux_amd64.tar.gz | tar xz
# Linux (arm64)
curl -L https://github.com/csweichel/blackwood/releases/latest/download/blackwood_linux_arm64.tar.gz | tar xz
Move the binary somewhere on your PATH:
sudo mv blackwood /usr/local/bin/
Option B: Build from source
git clone https://github.com/csweichel/blackwood.git
cd blackwood
# Build the web UI
cd web && npm ci && npm run build && cd ..
# Copy built UI into the embed directory
rm -rf cmd/blackwood/static && cp -r web/dist cmd/blackwood/static
# Build the server
go build -o blackwood ./cmd/blackwood
3. Configure
Interactive setup (recommended)
The quickest way to configure Blackwood is the interactive setup command. It creates directories, stores your secrets, and generates a config file:
./blackwood setup
Once complete, skip ahead to Run the Server.
Manual setup
Alternatively, create the data directory and config by hand:
mkdir -p ~/.blackwood/secrets
Store your OpenAI API key
Secrets are read from files, not stored in the config directly. This avoids leaking keys in version control or process listings.
# Write your API key to a file (replace sk-... with your actual key)
echo -n "sk-..." > ~/.blackwood/secrets/openai-api-key
chmod 600 ~/.blackwood/secrets/openai-api-key
Create the config file
Save this as ~/.blackwood/config.yaml:
server:
addr: ":8080"
data_dir: ~/.blackwood
openai:
api_key_file: ~/.blackwood/secrets/openai-api-key
model: gpt-5.2
chat_model: gpt-5.2
embedding_model: text-embedding-3-small
Env var fallback: If you prefer environment variables, skip the config file entirely. Set OPENAI_API_KEY and Blackwood will use it. The config file takes priority when both are present.
Config reference
All fields and their defaults:
| Field | Default | Description |
|---|---|---|
server.addr |
:8080 |
Listen address |
server.data_dir |
~/.blackwood |
SQLite database and attachments |
server.tls.cert_file |
— | Path to TLS certificate (enables HTTPS when both cert and key are set) |
server.tls.key_file |
— | Path to TLS private key |
openai.api_key_file |
— | Path to file containing your OpenAI API key |
openai.model |
gpt-5.2 |
Model for OCR and vision |
openai.chat_model |
same as model |
Model for RAG chat |
openai.embedding_model |
text-embedding-3-small |
Model for semantic embeddings |
openai.ocr_prompt |
built-in | Custom prompt for handwriting OCR |
whatsapp.verify_token |
— | Webhook verification token |
whatsapp.app_secret_file |
— | Path to file containing app secret |
whatsapp.access_token_file |
— | Path to file containing access token |
whatsapp.phone_number_id |
— | WhatsApp Business phone number ID |
telegram.bot_token_file |
— | Path to file containing Telegram bot token |
telegram.allowed_chat_ids |
[] (allow all) | List of Telegram chat IDs allowed to use the bot |
granola.oauth_token_file |
— | Path to file containing Granola OAuth token (from blackwood granola-login) |
granola.poll_interval |
1h | How often to check for new or updated meeting notes |
4. Run the Server
blackwood --config ~/.blackwood/config.yaml
Open http://localhost:8080 in your browser. You should see the calendar view with today's date.
CLI flags override config values:
# Listen on a different port
blackwood --config ~/.blackwood/config.yaml --addr :3000
# Use a different data directory
blackwood --config ~/.blackwood/config.yaml --data-dir /var/lib/blackwood
Quick start without a config file:
export OPENAI_API_KEY=sk-...
blackwood
This uses all defaults: listens on :8080, stores data in ~/.blackwood.
5. Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Cmd+D |
Jump to today's note |
Cmd+/ |
Toggle between notes and chat |
Cmd+T |
Insert current time (in edit mode) |
Cmd+Enter |
Save and exit edit mode |
Esc |
Exit edit mode |
On Windows/Linux, use Ctrl instead of Cmd.
6. WhatsApp Integration (Optional)
Blackwood can receive text, voice, and photo messages via WhatsApp. Messages are appended to today's daily note.
Setup
- Create a Meta Developer app with WhatsApp Business API access.
- In the app dashboard, note your Phone Number ID and generate a permanent Access Token.
- Choose a Verify Token (any string you pick).
- Store the secrets:
echo -n "your-app-secret" > ~/.blackwood/secrets/whatsapp-app-secret
echo -n "your-access-token" > ~/.blackwood/secrets/whatsapp-access-token
chmod 600 ~/.blackwood/secrets/whatsapp-*
Add the WhatsApp section to your config:
whatsapp:
verify_token: your-verify-token
app_secret_file: ~/.blackwood/secrets/whatsapp-app-secret
access_token_file: ~/.blackwood/secrets/whatsapp-access-token
phone_number_id: "123456789"
Webhook URL
In the Meta Developer dashboard, set the webhook URL to:
https://your-domain.com/api/webhooks/whatsapp
Blackwood must be reachable from the internet. Use a reverse proxy (nginx, Caddy) or a tunnel (ngrok, Cloudflare Tunnel) to expose it.
Security: The webhook validates request signatures using your app secret. Do not expose the server without HTTPS in production.
7. Telegram Bot (Optional)
Send text, voice messages, and photos from Telegram to your daily notes. The bot uses long polling — no public URL or webhook setup is needed.
Create a bot with @BotFather
- Open Telegram and search for @BotFather.
- Send
/newbotand follow the prompts to choose a display name and username for your bot. - BotFather replies with a bot token like
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11. Copy it.
Store the token
echo -n "YOUR_BOT_TOKEN" > ~/.blackwood/secrets/telegram-bot-token
chmod 600 ~/.blackwood/secrets/telegram-bot-token
Configure
Add the Telegram section to your config:
telegram:
bot_token_file: ~/.blackwood/secrets/telegram-bot-token
Without a config file: Set the TELEGRAM_BOT_TOKEN environment variable instead.
Authorize your chat
Start Blackwood. The server logs will show a 6-digit authorization code:
telegram: bot started — send this code to the bot to authorize a chat auth_code=482910
Open your bot in Telegram and send that code as a message. The bot replies with “✓ Authorized!” and your chat is connected. The authorization is persisted in the database — you only need to do this once.
The code rotates after each use, so you can authorize multiple devices or group chats by checking the logs each time.
To disconnect a chat, send /revoke to the bot.
Static allowlist: You can also pre-authorize chat IDs in the config file. These are always authorized and can’t be revoked via /revoke:
telegram:
bot_token_file: ~/.blackwood/secrets/telegram-bot-token
allowed_chat_ids:
- 123456789
What the bot does
| You send | Blackwood does |
|---|---|
| Text message | Adds it as a text entry in today's note |
| Voice message | Transcribes via Whisper, adds transcription as entry |
| Photo | Describes via vision model, adds description as entry |
All messages are appended to the daily note with a timestamp and “Telegram” source label. Audio and photo files are stored as attachments alongside the note.
Security: Keep your bot token secret. Anyone with the token has full control over the bot. Without authorization, the bot rejects all messages and tells the sender to provide the auth code.
8. Granola Meeting Notes (Optional)
Blackwood can automatically import meeting notes from Granola via the Granola MCP server. The sync runs periodically, fetching new or updated notes and writing them as entries on the day each meeting occurred.
Each imported note includes the meeting title, date, attendees, AI-enhanced notes, private notes, and transcript (paid Granola tiers).
Setup
- Log in via OAuth:
blackwood granola-loginThis opens your browser for authentication and saves the token to
~/.blackwood/secrets/granola-oauth-token. - Add the
granolasection to your config:granola: oauth_token_file: ~/.blackwood/secrets/granola-oauth-token poll_interval: 1h
Alternatively, set the GRANOLA_OAUTH_TOKEN environment variable. Granola sync auto-enables when an OAuth token is configured.
9. Viwoods File Watcher (Optional)
Blackwood can watch a directory for Viwoods .note files, run OCR on each page, and append the results to your daily notes. Add the watcher section to your config:
watcher:
watch_dir: /path/to/viwoods/notes
poll_interval: 30s
The watcher runs inside the main blackwood process. No separate binary is needed.
10. Running as a Service
To run Blackwood on startup, create a systemd user service:
mkdir -p ~/.config/systemd/user
Save as ~/.config/systemd/user/blackwood.service:
[Unit]
Description=Blackwood Server
After=network.target
[Service]
ExecStart=/usr/local/bin/blackwood --config %h/.blackwood/config.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
Enable and start:
systemctl --user daemon-reload
systemctl --user enable --now blackwood
# Check status
systemctl --user status blackwood
# View logs
journalctl --user -u blackwood -f
macOS: Use a launchd plist instead. Place it in ~/Library/LaunchAgents/ and load with launchctl load.