All work

Ghost

A hotkey-summoned AI overlay that never leaves the machine it runs on.

Role
Design & engineering
Year
2026
Discipline
Desktop application
Status
Live
Stack
Rust · Tauri 2 · React · TypeScript · SQLite · Vite
Ghost: Desktop AI Copilot

The problem

Every AI assistant worth using lives in a browser tab. That means breaking focus: switch window, find the tab, paste context in, copy the answer back out. The interruption costs more than the answer is worth. And the moment a tool is useful enough to paste real work into, where that work goes starts to matter.

What I built

Ghost is a Tauri 2 application: a Rust core with a React overlay rendered in the system webview. That split is the whole architectural bet. The Rust side owns the global hotkey, the window, the provider calls, and the database; the webview only draws. It keeps the binary small and keeps anything sensitive out of the frontend bundle, where it would be readable by anyone who unpacked the app.

It is local-first by design, not as a feature. Conversations, settings, and history live in a SQLite file on the machine. API keys go straight into the OS credential manager (Windows Credential Manager, macOS Keychain, Linux Secret Service) rather than into a config file or shipped in the bundle. Nothing is synced, because there is nowhere to sync to.

Providers sit behind a single Rust trait. Groq is the default for latency, with xAI, Ollama, LM Studio, OpenRouter, and any OpenAI-compatible endpoint supported through the same interface. The trade-off is deliberate: a thin abstraction means losing provider-specific features, and in exchange a new backend is one implementation rather than a new code path through the app.

Distribution on Windows goes through CrabNebula Cloud with an NSIS installer that needs no administrator rights. This matters, because the people most likely to want a local-first tool are often the ones who cannot install software that asks for admin.

A detail worth explaining

Why the global hotkey is the hard part

A tray app that appears over whatever you are working in has to do three things that fight each other: register a shortcut the OS will honour from inside any other application, show a window without stealing focus from the one underneath, and dismiss itself without leaving the previous window in a strange state. Each platform disagrees about all three. Doing this from the Rust side rather than the webview is what makes it tractable. The webview has no concept of a window it does not own, and no way to register a shortcut the compositor respects.

rust
// The overlay is a separate always-on-top window that is shown and
// hidden rather than created and destroyed. Spawning a webview per
// invocation costs ~200ms of visible lag; toggling costs nothing.
app.global_shortcut().on_shortcut("CommandOrControl+Space", |app, _, event| {
    if event.state() != ShortcutState::Pressed {
        return;
    }
    if let Some(overlay) = app.get_webview_window("overlay") {
        match overlay.is_visible() {
            Ok(true) => { let _ = overlay.hide(); }
            _ => {
                let _ = overlay.show();
                let _ = overlay.set_focus();
            }
        }
    }
})?;

Outcome

Ghost ships on Windows as a signed NSIS installer and runs from the tray with a Ctrl+Space summon. Six providers work through one interface, and no conversation or key leaves the machine.