Docs

Use Kavibay, connect your accounts, build your own widgets — pick your path below.

Kavibay is one transparent, always-on-top window between you and your wallpaper. It holds a fuzzy command palette and a desk of widgets. Clicks fall through the gaps between the cards, so it is a layer over your desktop rather than another window to manage. This page covers using it and building for it; the complete reference lives in the repository.

The Kavibay desk: todo, notes, clock, weather and pomodoro widgets around the command palette, over a nebula wallpaper.
The desk: widgets around the palette, one layer over your wallpaper.

Get started

Version 0.1 runs on Windows 10 and 11. Download the installer from the latest release; a standalone .exe and SHA256SUMS.txt are attached too. The installer needs no admin rights.

  • SmartScreen warns once. The builds are unsigned for now — choose More info → Run anyway. Compare the download against SHA256SUMS.txt if you want to be sure what you are running.
  • Updates. The installer build downloads new releases in the background and offers Restart to update in the tray menu. The standalone .exe does not update itself.
  • First start. Setup asks whether to start at login and, with several displays, which screen to cover. Take the short tour or Skip the tour; the desk arrives with a clock and a to-do list. Settings → Behavior → Replay tour brings the tour back.
  • After that Kavibay waits in the tray. Tap Ctrl twice, or double-click the tray icon.

What v0.1 is, and isn't

  • Windows 10 and 11 only. macOS and Linux ports exist in the source, unsupported.
  • Data integrations: GitHub, Linear, Notion, n8n, Spotify, tado°, Fitbit, Google Calendar. Models: Anthropic, OpenAI, Cloudflare Workers AI.
  • Spotify, Fitbit and Google Calendar need a Client ID from their developer console; one-click sign-in is not there yet.
  • The Wizard needs your own AI key and costs a few cents per widget.
  • No widget store yet — what you build stays on your machine.

Keys

No OS can register a bare modifier as a hotkey, which is why the Ctrl double tap watches the keyboard directly.

The command palette searching for “cl”: Clipboard, Clock, Snippets and Single Purpose AI widgets, an Empty Clipboard action and the AI settings.
Two letters in: widgets, open instances, actions and settings in one list.
KeyDoes
Ctrl CtrlShow / hide the cockpit
Shift+Ctrl+SpaceShow / hide on the screen under the mouse
Ctrl+Space (hold)Peek at the widgets, no palette. Click one while holding and it stays
Ctrl+Shift+QQuick actions on text selected in any app — translate, fix grammar, rewrite
↑ ↓ EnterMove through results; on a widget row, put it on the desk
Ctrl+EnterOpen a widget inside the palette instead of adding it to the desk
TabFill in an action's arguments, or jump into the widget
Ctrl+Shift+1…9Switch desk
Ctrl+Z / Ctrl+YUndo a hide, delete or move
EscapeHide it
Every other key
↓ on an empty queryRecently used commands
← on an empty queryThe widgets on this desk, plus the whole catalog
Ctrl+N / Ctrl+W / Ctrl+H / Ctrl+ROn a palette row: new instance / close (hide) / delete
Ctrl+TOn a folder row: open it in the terminal
Ctrl+Tab / Ctrl+Shift+TabCycle focus through the open widgets and back to the palette
Ctrl+Alt+SJump into the palette search from any widget
Ctrl+S / Ctrl+DOn the widget you are in: pin or unpin / duplicate
Ctrl+W / Ctrl+H / Ctrl+ROn the widget you are in: close/hide / delete it, content included
Ctrl+Shift+←↑→↓Nudge the focused widget by 10px
Ctrl+Alt+←↑→↓Move the active widget or palette by one grid gap
Ctrl+mousewheel · pinchZoom a widget's content (0.5…3), remembered per card
Ctrl+dragMove the whole layout instead of one widget
Double-click a titleRename the widget

Hold Ctrl for 750 ms in the palette or the active widget to reveal the pin/hide and desk shortcut hints. A widget you deleted comes back from Ctrl+Z empty.

Widgets & accounts

31 widgets ship with the app, plus two palette-only actions. Add one from the command palette or from the Widget Gallery widget.

Productivity
Alarm · Clipboard · Clock · Focus Tracker · Moodist · Notes · Single Purpose AI · Pomodoro · Snippets · Stopwatch · Time Tracker · Timer · Todo
Information
Calendar · GitHub Actions · Spotify Playlists · Stocks · Tado · Weather
Tools
Calculator · Color Picker · Emoji Picker · Widget Gallery · Redacted · Snake · Widget Wizard
System
AI Usage · Launcher Buttons · System Info
Media
Image · Now Playing
Actions
Confetti · Kill Port
Settings → Extensions listing the 31 built-in extensions, each with an on/off switch.
Settings → Extensions: every built-in widget, switched on or off.

Accounts are entered once in Settings → Integrations → Credentials. One saved account is a connection, and a platform may have several — one Linear workspace for work, one for home. Which connection a widget uses is chosen per widget in its settings; a deleted connection never silently falls back to another account. Linear, Notion, n8n and Fitbit have no widget of their own: a widget you build with the Wizard reads from them.

Full catalogue with what each widget needs: extensions/README.md.

AI features

Kavibay ships no model and no subscription. Use your own API key, or an AI client you already run, connected over MCP.

Bring your own key

Add an Anthropic, OpenAI or Cloudflare Workers AI key under Settings → AI — the same encrypted store as every other credential — and choose which models to switch on. You pay the provider directly, per use. The key unlocks:

  • Widget Wizard — describe a widget, get a sandboxed one built (Anthropic or OpenAI).
  • Quick actions (Ctrl+Shift+Q) — translate, fix grammar or rewrite selected text in any app.
  • Single Purpose AI — a widget for one prompt you run often, with streaming output.

The key stays in Rust. A widget that uses a model asks the host to make the call and never sees the key.

Settings → AI with Anthropic, OpenAI and Cloudflare Workers AI providers, each with a Set up button.
Settings → AI: one key per provider, then choose which models to switch on.

Use the client you already have

Claude Code, Codex or another MCP client can build widgets on its own plan, with no API key in Kavibay — see MCP server.

Build your own

Every widget is an extension, and there is no registry file to edit — the host scans folders, and the folder name is the extension's id. Three ways in:

For compiled extensions, pick the smallest tier that fits — going up later is cheap, and most widgets never leave S:

TierWhenBackend
SClient-only, or one plain API callNone, or a declared endpoint in api.json
MPer-instance settings, menus, richer UISame as S
LStreaming, pagination, shared cache, OS APIsIts own Rust module

Widget Wizard

Add the Wizard from the palette and describe what you want — “a tracker for how much water I drink today”. It generates a runtime package, previews it in the same sandbox every package gets, and Save moves it into your widgets. You can watch a recorded run in the browser.

The Widget Wizard: a conversation asking for a water tracker on the left, a live preview of the generated widget showing 1.75 L, 58 % on the right.
Two sentences, one widget: the conversation on the left, the live sandboxed preview on the right.
  1. Your sentence goes to the model, with the package format and the house style as its system prompt.
  2. Files are written to a draft under ~/.kavibay/extensions-custom/.drafts/.
  3. A live preview runs the draft in the sandbox. Ask for changes as often as you like.
  4. Save promotes it to ~/.kavibay/extensions-custom/, and it appears in the palette like a built-in widget.
The saved water tracker widget on the desk, beside the command palette.
After Save: the new widget on the desk, like any built-in one.
  • The left column lists projects: a widget plus every conversation about it. Pick one to change it — the Wizard checks it out into a draft, and Save replaces it in place.
  • After each generation, up to three suggestion chips propose a next change. Clicking one adds it to the input; nothing is sent until you press Send.
  • Share exports the widget as a ZIP, captures a screenshot, or records a short MP4 clip. Someone else installs the ZIP with Import Widget in the palette, or by dropping it onto Kavibay.
  • The Wizard can only write into its own drafts directory — it cannot reach a widget you already installed. Results vary with the model; asking for a fix or two is normal.

MCP server

Kavibay runs a local MCP server inside the app, on by default, at http://127.0.0.1:43127/mcp. A client you already use writes and validates drafts; you review the preview in the Widget Wizard and press Save. Configure it under Settings → Integrations → MCP Server. Kavibay must be running while the client connects.

Claude Code

claude mcp add --transport http kavibay http://127.0.0.1:43127/mcp

Codex

codex mcp add kavibay --url http://127.0.0.1:43127/mcp

Any other client: add a custom Streamable HTTP server with the same URL. If you change the port, change it in the URL too — a port conflict is reported, never moved silently.

MCP can read the authoring guides and provider schemas, list, read, write, edit and validate drafts, and check out a saved custom widget into a new draft. It cannot save, enable or delete a widget, grant permissions, call a model or read credentials. The server binds to IPv4 loopback only, rejects requests with an Origin header, and has no authentication — never publish the URL through a tunnel or proxy.

Runtime packages

A runtime package is a folder you drop into ~/.kavibay/extensions/. It renders in a sandboxed iframe and talks to the host through one small bridge. Packages are untrusted by design: the host holds the secrets, makes the network requests, and shows you exactly what a package declared before you enable it.

my-widget/
  manifest.json
  api.json            # only if you need network data
  ui/
    index.html
    app.js

manifest.json

{
  "id": "my-widget",
  "name": "My Widget",
  "version": "1.0.0",
  "description": "What it does, in one line.",
  "ui": {
    "entry": "ui/index.html",
    "defaultOffset": { "x": 0, "y": 0 },
    "defaultSize": { "w": 260, "h": 180 }
  },
  "commands": [],
  "permissions": ["storage.instance"]
}

The folder name must equal manifest.id. Then enable Settings → Behavior → Developer Extensions, open Settings → Extensions → Runtime packages, Rescan, and enable your package.

What the sandbox forbids

These fail silently — a widget that renders as an empty box, or a button that does nothing, is almost always one of them:

  • No inline <script> and no onclick="…". Code goes in a .js file, events via addEventListener.
  • No scripts, fonts or stylesheets from a CDN. Everything ships in the package; inline <style> is fine.
  • No fetch or XMLHttpRequest. Network data comes from declared endpoints.
  • No localStorage, cookies or IndexedDB — the frame has an opaque origin. Use kavibay.storage.
  • No form submission: a submit handler never runs. Listen for click and Enter instead.
  • No pop-ups or navigation: window.open and target="_blank" do nothing.

The SDK

Load it from the host, then your own script. Do not copy it into your package — the host serves that path itself, so every package runs the current version.

<script src="@kavibay/runtime.js"></script>
<script src="app.js"></script>
// Per-instance storage (permission: storage.instance)
await kavibay.storage.set({ city: "Berlin" });
const saved = await kavibay.storage.get();   // null when nothing is stored

// Declared HTTP (permission: network.declared + api.json)
const res = await kavibay.http("forecast", { latitude: 52.52, longitude: 13.405 });
if (!res.ok) return showError(res.code);
render(res.data);

// Bring the widget to the front (permission: background.pop)
await kavibay.pop({ sound: true });

Storage is namespaced to your package and the widget instance. Render from defaults immediately and redraw when storage answers; save the whole state after every change, because set replaces what is stored.

Network: declare, don't fetch

Endpoints go into api.json, and the host calls them for you. The description is shown to the person before they enable your package — write it for them.

{
  "schemaVersion": 1,
  "endpoints": [
    {
      "id": "forecast",
      "description": "Reads the current temperature for a location from Open-Meteo.",
      "method": "GET",
      "url": "https://api.open-meteo.com/v1/forecast",
      "query": {
        "latitude": { "type": "number", "required": true },
        "longitude": { "type": "number", "required": true },
        "current": { "type": "const", "value": "temperature_2m" }
      },
      "cache": { "ttlSeconds": 600 },
      "rate": { "minIntervalSeconds": 60 }
    }
  ]
}
  • The target is fixed. Nothing a caller passes can change scheme, host or port, or add a path segment.
  • No redirects, no private network. Loopback, private, link-local, CGNAT and ULA addresses are refused.
  • Bounded. 10 s timeout, 1 MiB response cap, four concurrent calls, your declared interval and 500 calls per day.
  • Credentials without holding them. Set credential to a type such as githubPat; the host attaches the person's granted account to your declared request only. You get the response, never the token.
  • Changing api.json requires re-consent. Until the person has reviewed the new endpoints, calls return consent_stale.

Validation fails closed: an unknown key, an endpoint id with a dash (review_requests, never review-requests), an http:// URL, an IP-literal host or an Authorization header rejects the whole package. Not supported on purpose: streaming, WebSockets, pagination loops, request chains, file uploads, arbitrary headers.

Everything else — parameter types, error codes, versioning, the security probes — is in runtime-packages.md.

Contract packages

The second package format, for widgets that read from a provider — a connected account like tado° or Linear, or an open data source. A provider is credentials, refresh, caching and rate limits, all in the host; the package gets a typed handle to queries somebody already wrote.

The widget needsFormat
A connected account — anything with a loginContract package
A public URL, or nothing but its own stored stateRuntime package

A contract package is manifest.json, index.html and widget.js, with no build step. The manifest lists the providers in widget.requires.providers — always a list — together with the queries it reads and the actions it calls. The person ticks which accounts it may use. Details and the rules that fail silently: contract-packages.md; what each provider returns: provider-schema.md.

Your data & security

Everything durable sits in ~/.kavibay (%USERPROFILE%\.kavibay), outside the Windows roaming profile.

WhatWhere
Settings, meant to be edited by handsettings.json
Layout, desks, appearance, per-widget settingsweb-storage.json
API keys and OAuth tokens — encryptedcredentials.db
Runtime packages you installedextensions/
Widget Wizard drafts and saved widgetsextensions-custom/
Regenerable caches — safe to deletecache/
  • Secrets are encrypted at rest with Windows DPAPI, bound to your account, so a copied file is unreadable elsewhere. A key is never returned to the interface and leaves Rust only towards its provider.
  • Widgets never see a credential. They get a handle and learn only whether they are connected.
  • Packages run sandboxed — scripts only, no same-origin access, no direct network. Permissions fail closed: a missing permission list means deny.
  • Where the protection stops: encryption at rest defends against someone taking the file, not against code running as you. The http allowlist of a package is not a security boundary either — a widget allowed to reach an API can encode what it read into the request. Install packages you trust.

Found a vulnerability? Please use private vulnerability reporting rather than a public issue.

Build from source

You need Windows 10 or 11, Node 22.13+ with pnpm (corepack enable), stable Rust via rustup, and Visual Studio Build Tools with Desktop development with C++. The Tauri CLI comes from the project's dependencies.

git clone https://github.com/kavibay/kavibay
cd kavibay
pnpm install
pnpm run tauri dev

The first run compiles the Rust backend and takes a few minutes; later runs are fast and the interface hot-reloads. To package it yourself:

pnpm run package:win              # NSIS installer
pnpm run package:win:standalone   # single .exe

Contribution paths and what CI checks: CONTRIBUTING.md. Experimental Linux and macOS notes: getting-started.md.

Troubleshooting

SymptomFix
App runs, nothing visibleWorking as designed — tap Ctrl twice
Ctrl+Space does not peekAnother app grabbed the hotkey
A package shows an empty box, or a button does nothingCheck what the sandbox forbids — inline scripts and form submits fail silently
A package forgets everything on reloadDeclare storage.instance in manifest.json
A new widget folder does not show upThe folder name must equal the manifest id; rescan, or restart the dev command
Port 1420 is already in useKill the other dev server — Tauri needs that exact port
cargo not foundFresh rustup install, stale PATH. Open a new terminal
Link errors in the Rust buildMSVC build tools are missing

Still stuck? Bugs go to Issues, ideas and finished widgets to Discussions, and anything else to the feedback form.

Full reference

The complete docs live next to the code:

Adapted from the Kavibay docs, licensed CC BY 4.0.

← Back to the start page