Self-hosted clipboard for AI agents and humans alike. Drop files, paste text, process images with vision LLMs — all through a slick web UI, REST API, and MCP server.
| Web UI | Ctrl+V image paste, drag-and-drop, text snippets, syntax highlighting, search |
| Folder Archives | Confirm a dropped folder to have the server create and upload one ZIP archive |
| REST API | List, upload, download, delete, pin files and text snippets |
| MCP Server | 20 tool calls for AI agents (Claude Code, Hermes, Devin, etc.) |
| Vision Pre-processing | Auto-OCR/describe uploaded images via any OpenAI-compatible vision LLM |
| Multi-Prompt Analysis | Analyze one image with multiple prompts — all results stored side-by-side |
| Preset Comparison | Run an image through all vision backends in parallel, rank with LLM judging |
| Client-side Search | Search filenames, vision OCR text, and paste content across both tabs |
| Auto-expire | Configurable TTL per item (1h, 1d, 7d, 30d, never) |
| Persistent Pinning | Mark items as persistent to exempt from expiry |
| Smart MIME Detection | Auto-detects file type from extension + content sniffing when client sends a generic type |
| Short Links | Shareable /{id}/{filename} URLs with auto-redirect, ?download=1, and ?direct=1 |
| Audio Waveform | In-browser waveform player with play/pause, seek, and progress for MP3/WAV/OGG/FLAC |
| OpenAPI 3.0 | Machine-readable spec at /api/openapi.json + Swagger UI |
| Single Go binary | No runtime dependencies; built-in race-enabled test and static-analysis checks |
docker run -d \
-p 127.0.0.1:8080:8080 \
-v ./data:/data \
-e BASE_URL=https://klipbord.example.com \
ghcr.io/jeeftor/klipbord:latestThen open http://localhost:8080 — drop a file, paste an image, share a snippet.
When you put Klipbord behind Cloudflare Access, an Authentik proxy, or another
identity-aware reverse proxy, keep the container on loopback or a private Docker
network. Publishing the container port directly bypasses that access control.
The container runs as UID/GID 10001; if you use an existing bind-mounted data
directory, make that directory writable by that UID/GID before upgrading.
Klipbord also ships as a native server binary; Docker is not required. The web
UI is embedded and item data is stored under DATA_DIR. Install it with:
curl -fsSL https://github.com/jeeftor/klipbord/releases/latest/download/install.sh \
| sh -s -- --component server
kb-serverWhen BASE_URL is unset, kb-server selects an active non-loopback IPv4
address for generated LAN links. Set BASE_URL explicitly for reverse proxies,
TLS, or hosts with multiple network addresses. Install ffmpeg on the host if
you want media metadata probing; all core storage and sharing features need no
other runtime dependency.
Use kb-server --help to see its runtime options. For example:
kb-server --data-dir /srv/klipbord --port 8080 --base-url https://klipbord.example.comkb-cli is the terminal companion for Klipbord. It reads standard input as a text
snippet and uploads file arguments directly:
cat a.txt | kb-cli
kb-cli screenshot.png report.pdf
kb-cli list
kb-cli pin ITEM_ID
kb-cli get ITEM_ID
kb-cli rm ITEM_IDUploads print their share URL to standard output, so they compose cleanly with
shell scripts. Use --json when a script needs item metadata.
kb-cli supports git-style pull/push/sync commands for transferring files
between your local machine and the Klipbord server:
# Pull: download new items from the server (one-shot)
kb-cli pull -d ./downloads
# Pull everything including backlog
kb-cli pull -d ./downloads --all
# Keep polling for new items
kb-cli pull -d ./downloads --watch --interval 10s
# Push: upload local files (one-shot)
kb-cli push photo.png document.pdf
# Upload all files in a directory (one-shot)
kb-cli push -d ./uploads
# Watch a directory and auto-upload new files
kb-cli push -d ./uploads --watch --interval 10s
# Upload and delete local file after success
kb-cli push -d ./uploads --rm
# Sync: bidirectional pull + push (one-shot)
kb-cli sync -d ./shared
# Continuous bidirectional sync
kb-cli sync -d ./shared --watch --interval 10s
# Sync including backlog
kb-cli sync -d ./shared --all| Command | Description | --watch |
--all |
|---|---|---|---|
pull |
Download items from server | Keep polling | Download backlog too |
push |
Upload local files to server | Watch dir for new files | — |
sync |
Bidirectional pull + push | Continuous both ways | Pull backlog too |
All three support -d (directory), --interval (poll interval, default 10s),
and --ttl / --persistent for upload expiration settings.
One-liner (macOS/Linux):
curl -fsSL https://github.com/jeeftor/klipbord/releases/latest/download/install.sh | shThe installer detects your OS/arch, downloads the matching binary, verifies
the SHA256 checksum, and optionally verifies the cosign signature if cosign
is installed. It installs to ~/.local/bin/kb-cli (or /usr/local/bin/kb-cli as root).
Flags:
# Install a specific version
curl -fsSL https://github.com/jeeftor/klipbord/releases/latest/download/install.sh | sh -s -- --version v2.13.0
# Install to a custom directory
curl -fsSL https://github.com/jeeftor/klipbord/releases/latest/download/install.sh | sh -s -- --install-dir /opt/binManual install: Download the matching kb-cli archive from the
GitHub releases page, extract
it, and move the kb-cli binary to your PATH. If you want both programs on a
USB drive or a single host, download the combined kb_<os>_<arch> archive; it
contains both kb-server and kb-cli.
Once installed, run kb-cli login. Profile settings are stored in your normal
config directory while tokens and header values stay in your operating system
keychain.
# Cloudflare Access service token (recommended for a Cloudflare-protected URL)
export CF_ACCESS_CLIENT_ID='...'
export CF_ACCESS_CLIENT_SECRET='...'
kb-cli login --url https://klipbord.example.com --method cloudflare
# A direct OIDC-protected deployment with device authorization enabled
kb-cli login --url https://klipbord.example.com --method oidc \
--issuer https://auth.example.com/application/o/klipbord/ \
--client-id kb-cliWhen Klipbord is behind Authentik forward_auth with CLI detection
configured in Caddy, kb-cli login auto-discovers the OIDC settings —
no --method, --issuer, or --client-id flags needed:
$ kb-cli login
Klipbord server URL [https://klipbord.example.com]: (press enter)
Detected auth method: oidc
Open https://auth.example.com/device?code=810392209 and complete login using code 810392209
Waiting for authorization...
Logged in. Profile "default" is ready.The CLI sends User-Agent: klipbord-cli/<version> on all requests.
Caddy detects this and, when forward_auth rejects an unauthenticated
request, returns a 401 with X-OIDC-Issuer, X-OIDC-Client-ID, and
X-OIDC-Scopes headers instead of a 302 redirect. The CLI reads
these headers to auto-configure the device code flow.
See caddy/flows.md (Pattern F) in the homelab repo for the full
Caddy and Authentik configuration details.
$ kb-cli status
Profile: default
Server: https://klipbord.example.com
Method: oidc
Status: logged inOther supported methods are bearer, headers, and none for loopback-only
development. headers accepts repeated header names and securely prompts for
their values; oidc uses OpenID Connect discovery and refresh tokens.
Use kb-cli profile list and kb-cli profile use NAME when you have more than one
Klipbord connection.
When the Klipbord Authentik proxy provider has HTTP Basic authentication enabled, create an App Password from your Authentik user credentials page. It is distinct from an Authentik API token. Then log in without exposing the password in your shell history:
kb-cli login --url https://klipbord.example.com \
--method authentik-app-password \
--username your-authentik-usernamekb-cli prompts for the app password and stores the resulting Basic authorization
header in your operating system keychain. For unattended use, set
AUTHENTIK_USERNAME and AUTHENTIK_APP_PASSWORD in the calling environment.
kb-cli login saves the connection profile in your platform config directory
(for example, ~/.config/klipbord/config.yaml on Linux) and saves credentials
in your operating system keychain. Put these defaults in .env, load them into
your environment (for example, with direnv), then run kb-cli login once.
The CLI does not load .env automatically:
KB_SERVER=https://kb.example.com
KB_METHOD=authentik-app-password
KB_PROFILE=default
KB_USERNAME=your-authentik-username
KB_PASSWORD=your-authentik-app-passwordKB_SERVER, KB_METHOD, KB_PROFILE, KB_USERNAME, KB_OIDC_ISSUER,
KB_OIDC_CLIENT_ID, and KB_OIDC_SCOPES provide defaults for the matching
login settings. KB_OIDC_SCOPES accepts spaces or commas. Explicit flags
always win. For Authentik, KB_PASSWORD is preferred; the older
AUTHENTIK_APP_PASSWORD and APP_PASSWORD names are also accepted.
Use kb-cli login --debug (or --log-level debug) to diagnose login failures.
Terminal output highlights stages, URLs, HTTP statuses, and recovery advice in
color. Set NO_COLOR=1 to disable these colors; redirected diagnostics stay plain.
Diagnostics go to standard error and include the selected profile and method,
discovery/OIDC stages, and the final API request, status, and elapsed time.
The API probe omits credential values, cookies, response bodies, and URL query
parameters from diagnostics. It reports redirects without following them, so a
browser login page cannot hide the original API response.
If the connection test fails, the error explains that your profile is still saved and gives recovery advice for your login method. For example, an Authentik 401 points you to your username, App Password, and proxy header authentication settings. A saved profile does not mean your credentials were accepted.
$ kb-cli version
kb-cli version v2.14.0
$ kb-cli update --check
An update is available: v2.14.0 (current: v2.13.0). Run 'kb-cli update' to upgrade.
$ kb-cli update
Updating kb-cli v2.13.0 → v2.14.0...
== Installed kb-cli to /home/user/.local/bin/kb-clikb-cli also checks for updates automatically once per day when uploading
files. If a newer version is available, it prints a notice to stderr
(never blocks or fails the upload).
| Env Var | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP port |
DATA_DIR |
./data |
Storage directory (Docker sets this to /data) |
BASE_URL |
Active LAN IPv4 address, or http://localhost:8080 |
Public URL for generating links; set explicitly for proxies/TLS/multi-homed hosts |
MAX_UPLOAD_MB |
2048 |
Max upload size in MB |
VISION_ENABLED |
true |
Enable automatic image analysis on upload |
VISION_REQUEST_TIMEOUT |
2m |
Maximum time for each matrix inference request |
VISION_UNLOAD_TIMEOUT |
2m |
Maximum time to wait for unload and observed memory release |
VISION_ENDPOINT |
(see presets) | OpenAI-compatible vision LLM endpoint (overrides UI config) |
VISION_MODEL |
(see presets) | Vision model name to use (overrides UI config) |
KLIPBORD_OIDC_ISSUER |
(disabled) | OIDC issuer for the optional web login; all KLIPBORD_OIDC_* values below are required together |
KLIPBORD_OIDC_CLIENT_ID |
Confidential OIDC client ID for Klipbord's browser login | |
KLIPBORD_OIDC_CLIENT_SECRET |
Confidential OIDC client secret | |
KLIPBORD_OIDC_REDIRECT_URL |
Exact registered callback URL, such as https://klipbord.example.com/auth/callback |
|
KLIPBORD_OIDC_SESSION_SECRET |
Unique random value of at least 32 bytes used to sign Klipbord sessions | |
KLIPBORD_OIDC_INSECURE_COOKIES |
false |
Local HTTP development only; requires a loopback callback URL |
Set all of the KLIPBORD_OIDC_* variables to show a Log in button. Klipbord
uses the reusable homelab-auth module for Authorization Code + PKCE login and
then displays the signed-in identity in the header. This first integration is
display-only: it does not restrict uploads, downloads, share links, the Config
tab, or any API routes.
Use an HTTPS callback URL in production and register it exactly at your identity
provider. Generate a session secret separately from the client secret, for
example with openssl rand -base64 48. Direct HTTP LAN addresses are not
supported for native login; place Klipbord behind HTTPS or keep web OIDC disabled.
Configure via the Config tab in the UI or via environment variables.
Env vars always win — if VISION_ENDPOINT or VISION_MODEL are set they override the UI. A locked "env" preset appears in the UI to indicate this.
Three presets ship on first boot:
| Preset | Endpoint | Model |
|---|---|---|
lemonade |
http://localhost:13305/v1/chat/completions |
Qwen3-VL-4B-Instruct-GGUF |
ollama |
http://localhost:11434/v1/chat/completions |
llama3.2-vision |
openai |
https://api.openai.com/v1/chat/completions |
gpt-4o-mini |
To disable vision entirely: VISION_ENABLED=false
When an image is uploaded, Klipbord automatically sends it to the configured vision LLM. The result (extracted text + description) is stored alongside the image and surfaced via MCP tools and the REST API.
| Prompt | Use Case |
|---|---|
default |
General-purpose analysis — OCR + description |
terminal |
Terminal screenshots — commands, output, errors |
code |
Code screenshots — preserves indentation, detects language |
document |
Documents/receipts — structured layout extraction |
diagram |
Diagrams/charts — structure, connections, flow |
# Create
curl -X POST -H 'Content-Type: application/json' \
-d '{"name":"ui_mockup","description":"UI mockup analysis","prompt":"Analyze this UI mockup..."}' \
/api/prompts
# Update
curl -X PUT -H 'Content-Type: application/json' \
-d '{"description":"Updated"}' /api/prompts/ui_mockup
# Delete
curl -X DELETE /api/prompts/ui_mockupcurl -X POST /api/analyze/{id}?prompt=terminal
curl -X POST /api/analyze/{id}?prompt=code
curl /api/files/{id} # both results returned in analyses fieldItems
curl /api/files # list all
curl -F 'file=@screenshot.png' -F 'ttl=7d' /api/upload # upload file (MIME auto-detected)
curl /api/files/{id} -o file.png # download
curl -X POST -H 'Content-Type: application/json' \
-d '{"content":"hello","name":"note.txt","ttl":"7d"}' /api/text # create text snippet
curl /api/text/{id} # get raw text
curl -X DELETE /api/files/{id} # delete
curl -X PATCH -H 'Content-Type: application/json' \
-d '{"persistent":true}' /api/files/{id} # pin/unpin
curl -X PATCH -H 'Content-Type: application/json' \
-d '{"mime_type":"image/gif"}' /api/files/{id} # fix MIME typeVision
curl -X POST /api/analyze/{id}?prompt=terminal # trigger analysis
curl -X POST /api/vision/test # test with built-in sample
curl -X POST -H 'Content-Type: application/json' \
-d '{"image_type":"code"}' /api/vision/test # test specific image type
curl -X POST -H 'Content-Type: application/json' \
-d '{"image_type":"terminal"}' /api/vision/compare # compare all presets
curl -X POST -H 'Content-Type: application/json' \
-d '{"item_id":"abc123"}' /api/vision/compare # compare using uploaded image
curl -X POST -H 'Content-Type: application/json' \
-d '{"image_type":"terminal"}' /api/vision/compare-prompts # compare all promptsVision Config
curl /api/config/vision # get config
curl -X POST -H 'Content-Type: application/json' \
-d '{"preset":"ollama"}' /api/config/vision/active # set active preset
curl -X POST -H 'Content-Type: application/json' \
-d '{"enabled":false}' /api/config/vision/enabled # toggle vision
curl -X POST -H 'Content-Type: application/json' \
-d '{"name":"my-llm","endpoint":"http://localhost:8080/v1/chat/completions","model":"my-model"}' \
/api/config/vision/presets # create preset
curl -X DELETE /api/config/vision/presets/my-llm # delete preset
curl -X POST -H 'Content-Type: application/json' \
-d '{"preset":"lemonade"}' /api/config/vision/test # test connectionPrompts
curl /api/prompts # list all
curl /api/prompts/{name} # get one
curl -X POST -H 'Content-Type: application/json' \
-d '{"name":"x","description":"y","prompt":"z"}' /api/prompts # create
curl -X PUT -H 'Content-Type: application/json' \
-d '{"prompt":"new text"}' /api/prompts/{name} # update
curl -X DELETE /api/prompts/{name} # delete (custom only)Health
curl /api/health # → {"status":"ok"}
curl /api/version # → {"version":"vX.Y.Z"}
curl /api/openapi.json{ "mcpServers": { "kb": { "url": "https://klipbord.example.com/mcp" } } }All 20 tools
| Tool | Description |
|---|---|
list_files |
List all items with metadata |
get_file |
Get file content (base64) or text content |
get_file_url |
Get public URL for an item |
upload_file |
Upload a file (base64 content + filename) |
create_text |
Create a text snippet |
get_text |
Get raw text snippet content |
delete_file |
Delete an item |
persist_file |
Pin or unpin an item |
describe_image |
Get vision analysis for an image |
analyze_image |
Trigger/re-trigger vision analysis |
inspect_image |
Ask a focused visual question and return structured visible evidence |
list_prompts |
List all available vision prompts |
create_prompt |
Create a new vision prompt template |
update_prompt |
Update an existing prompt |
delete_prompt |
Delete a custom prompt |
list_vision_presets |
List all configured vision LLM presets |
set_vision_preset |
Switch the active vision LLM preset |
test_vision_preset |
Test connectivity to a preset |
test_vision |
Run the full vision pipeline on a sample image |
compare_vision |
Run image through ALL presets, rank results |
compare_prompts |
Run image through ALL prompts, rank results |
Example tool calls
https://klipbord.example.com/{id}/{filename} # canonical share link (filename visible in URL)
https://klipbord.example.com/{id} # redirects to /{id}/{filename}
https://klipbord.example.com/{id}?direct=1 # serves inline, no redirect (curl -O friendly)
https://klipbord.example.com/{id}?download=1 # redirects to named URL, forces download
https://klipbord.example.com/link/{id} # legacy form (still works)
Each UI section has a stable URL, so it remains selected after a refresh and can be bookmarked.
| Route | UI section |
|---|---|
/clip |
Clipboard items and upload controls |
/persist |
Persistent items |
/config |
Vision configuration |
/mcp-web |
MCP setup and tool reference |
/rest-web |
REST API reference |
/ redirects to /clip. The browser UI routes are intentionally separate from the machine interfaces: REST remains under /api/..., MCP remains at /mcp, and direct item links use the short form /{id} (with /link/{id} as a legacy alias).
| Path | Content |
|---|---|
{DATA_DIR}/files/ |
Uploaded files |
{DATA_DIR}/text/ |
Text snippets |
{DATA_DIR}/chunks/ |
Chunked upload temp files |
{DATA_DIR}/metadata.json |
Item metadata (IDs, names, MIME, TTL, analyses) |
{DATA_DIR}/prompts.json |
Custom vision prompts |
{DATA_DIR}/vision_config.json |
Vision LLM presets and active selection |
- Clearer login troubleshooting: Use
kb-cli login --debugfor request status, timing, and authentication metadata without exposing credentials or response bodies. Failed connection tests now explain the saved-profile state and offer method-specific recovery advice. - More terminal color: Login diagnostics and errors highlight stages, URLs, HTTP statuses, and next steps. Redirected diagnostics stay plain, and
NO_COLORdisables diagnostic colors. - Environment-based login defaults: Configure server, profile, authentication method, username, password, and OIDC settings through
KB_*environment variables; explicit flags take precedence. - Reliable profile selection: Subcommands now honor the supplied
--configand--profileflags. Login probes report redirects instead of following them to browser login pages.
kb pull/kb push/kb sync: Git-style commands for transferring files between your machine and the Klipbord server.pulldownloads new items,pushuploads local files, andsyncdoes both bidirectionally. All three support--watchfor continuous operation and--allto include backlog.pushalso supports--rmto delete local files after upload and--ttl/--persistentfor expiration control.
- Reliable CLI releases: The binary publishing job now checks out the tagged source before creating the GitHub release and uploading its verified archives.
kbcommand-line client: Upload files or piped text, manage items, and authenticate through Cloudflare Access service tokens, bearer tokens, custom headers, or OIDC device login. Release assets now include macOS, Linux, and Windowskbbinaries.- Container-backed visual demo: GitHub Actions runs the Docker image, seeds mixed media, and uploads screenshots plus a short browser-recorded WebM demo as artifacts.
- Browse files control: The clipboard input now exposes a dedicated multi-file upload button.
- Media metadata via ffprobe: Audio and video uploads are probed with
ffprobe(included in Docker image) to extract codec, duration, bitrate, sample rate, channels, and resolution. Metadata is stored in the item record and displayed immediately on page load — no need to play the file first. - Waveform fix: Audio waveforms now render on page load using
OfflineAudioContext(no user gesture required). Previously the waveform only appeared after clicking play due to browser autoplay policy suspending regularAudioContext. - Video info overlay: Video cards show resolution, duration, codec, and bitrate as an overlay on the video player.
- Telegram notifications: CI sends release announcements and CI failure alerts to a Telegram channel via bot. Requires
TELEGRAM_TOKENandTELEGRAM_TOrepo secrets. - Canonical audio MIME types: Registers
.mp3,.wav,.ogg,.flac,.m4a,.aac,.opusat init time viamime.AddExtensionTypeso detection is consistent across Linux and macOS.
- Audio waveform player: Audio files (MP3, WAV, OGG, FLAC, M4A) now show a canvas waveform with play/pause, click-to-seek, and progress overlay. Peaks are computed client-side via the Web Audio API — no backend dependencies. Waveforms lazy-decode when scrolled into view.
- Single-line toolbar: Header and tab bar merged into one row — logo+version (left), tabs (center), help+TTL (right). More compact, less vertical space wasted.
- Short links: Shareable URLs are now
/{id}/{filename}instead of/link/{id}. Bare/{id}auto-redirects to the named form so download tools and AI agents see the correct filename./link/{id}still works as a legacy alias. - MIME auto-detection: Uploads with a generic
Content-Type(e.g.application/octet-streamfrom curl) now detect the real type from the filename extension first, then content sniffing. A.gifuploaded via curl is correctly stored asimage/gif. - Download button: File and image cards now have a dedicated download button that forces
Content-Disposition: attachment. - MIME type editing:
PATCH /api/files/{id}accepts amime_typefield. An in-app tag icon on each file card lets you fix a wrong MIME type manually. - HEAD support:
HEAD /{id}returns headers (filename, content-type, size) without the body — useful for AI tools to cheaply inspect an item. ?direct=1: Skips the filename redirect forcurl -Ocompatibility without-L.
- Vision evidence inspection (
inspect_imageMCP tool) - Live log drawer
- Clipboard loading state fixes
make build # go build -o kb-server .
make run # go run .
make test # go test ./...Tests use a mock vision server — no external LLM required. CI runs tests before building the Docker image; the build job is gated on test.
CI sends notifications to Telegram on release publishes and CI failures. To enable, set two repo secrets:
gh secret set TELEGRAM_TOKEN --repo $(gh repo view --json nameWithOwner -q .nameWithOwner)
gh secret set TELEGRAM_TO --repo $(gh repo view --json nameWithOwner -q .nameWithOwner)TELEGRAM_TOKEN— bot token from @BotFatherTELEGRAM_TO— channel ID (@yourchannelor-1001234567890for private channels)