Status: pre-1.0 (v0.1.x). Streaming chat, local-first storage, full-text search, and voice mode all work today. The release pipeline (Sigstore + SLSA + macOS notarization) is wired but has not yet been integration-tested against a real
v0.1.0tag. SeeCHANGELOG.mdfor what's actually shipped.
A cross-platform desktop client for xAI Grok, OpenAI, Anthropic, and any
OpenAI-compatible local model. Rewritten from the original SwiftUI macOS app
into Rust + egui — a single binary that runs on macOS, Windows, and Linux
with the same code.
No Xcode, no shell scripts, no Electron, no embedded browser. One cargo
build, native rendering via wgpu. The cold-start CLI invocation
(grok-insane --version) measures ~4 ms on the maintainer's machine; the
GUI cold start is unmeasured but architecturally bounded by tokio runtime
init + first wgpu frame.
- Features
- Screenshots / UX
- Getting Started
- Configuration
- Keyboard Shortcuts
- Architecture
- Workspace Layout
- Developer Workflow (
cargo xtask) - CLI
- Feature Flags
- Security Model
- Performance
- Testing
- Roadmap
- License
| Area | What you get |
|---|---|
| Providers | xAI Grok (streaming) · OpenAI (streaming, shared decoder) · Anthropic (native Messages SSE w/ usage) · any local OpenAI-compatible endpoint (Ollama, LM Studio, llama.cpp server) |
| Chat UI | Multi-chat sidebar, full transcript history, sticky-to-bottom auto-scroll, virtualised scroll, Markdown rendering with syntax-highlighted code blocks (egui_commonmark + syntect), copy-message, ↻ regenerate |
| Command palette | ⌘ / Ctrl+K opens a fuzzy launcher for every action: new chat · settings · voice/TTS/RAG · theme · provider · jump-to-chat · export · quit |
| Phosphor icons | Bundled egui-phosphor icon font; toolbar buttons render real glyphs (microphone, speaker, paper-plane) on every platform |
| Real RAM tracking | Perf dashboard reads resident memory from sysinfo once per second — actual process RSS, not a guess |
| Chat management | Pin · archive · rename · delete · export to Markdown / Obsidian / JSON from a right-click menu |
| Voice | Realtime WebSocket to wss://api.x.ai/v1/realtime, full duplex audio via cpal, custom animated waveform, persona selector (Ara / Rex / Sal / Eve / Leo), client-side level metering, server-side VAD |
| Search | Every message indexed by tantivy; instant full-text + fuzzy queries even at 100k+ messages |
| Storage | Embedded ACID K/V via redb, composite-key range scans by chat & timestamp, no external database needed |
| Secrets | API keys live in the OS keyring (macOS Keychain / Windows Credential Manager / Linux Secret Service) with zeroize on in-memory copies |
| RAG (opt-in feature) | Local sentence embeddings via fastembed, semantic re-rank over lexical hits, top-k retrieval inserted as a system message |
| Plugins (opt-in feature) | WebAssembly host (wasmtime) — surface area reserved for follow-up |
| Hotkeys (opt-in feature) | Global system-wide hotkeys via global-hotkey |
| Perf | Built-in dashboard for frame time, fps, tokens/s, last-request latency, indexed message count, resident memory |
| Theming | Custom "cosmic" theme (neon green on dark) by default; Dark and Light fallbacks |
| Persistence | Chats, messages, settings, and search index all kept locally — works offline for everything except actual model calls |
| Build & release | Pure-Rust cargo xtask replaces shell scripts; CI matrix builds macOS, Windows, Linux on every push |
The app launches at 1280×820 with a 220 px side rail, a central chat
panel, and a floating settings window. Default theme is a high-contrast
dark surface with #78FFAF neon-green accents matching the xAI palette.
┌──────────┬───────────────────────────────────────────────────┐
│ + New │ ◉ My chat about Rust │
│ search… │ ──────────────────────────────────────────────── │
│ ───── │ │
│ ◉ Chat 1 │ USER │
│ Chat 2 │ How do borrow checkers work? │
│ 📌 Chat 3│ │
│ … │ ASSISTANT (markdown rendered, code highlighted) │
│ │ ```rust │
│ │ fn main() { … } │
│ │ ``` │
│ v0.1.0 │ │
│ │ ┌─────────────────── compose ──────────────────┐ │
│ │ │ message Grok… 🎙 🔊 ➤ │ │
│ │ └──────────────────────────────────────────── ┘ │
└──────────┴───────────────────────────────────────────────────┘
- Rust 1.78+ — install via rustup.
- Linux only: ALSA + X11/Wayland + fontconfig dev libs. You can install
them with one command:
…which runs
cargo xtask install-depsapt-get install -y libasound2-dev libxkbcommon-dev libwayland-dev libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libfontconfig1-dev. For non-Debian distros install the equivalents.
git clone https://github.com/mcaney006/grokmacos-better-version
cd grokmacos-better-version
# debug build with live logs
cargo xtask dev
# or a plain release build
cargo build --release
./target/release/grok-insane
- Press ⌘ / Ctrl+, to open Settings.
- Pick a provider (default: xAI).
- Paste your API key into the key field and click Save key — it lands in the OS keyring, never on disk in plaintext.
- Press ⌘ / Ctrl+N and start typing.
All user-visible config lives in the in-app Settings window.
| Setting | Notes |
|---|---|
| Provider | xAI · OpenAI · Anthropic · Local. Drives which API key slot is loaded and which client is used. |
| API key | Stored in the OS keyring under com.grokinsane.grok-insane, one entry per provider. |
| Model | Per-provider text field. Defaults: grok-beta, gpt-4o-mini, claude-3-5-sonnet-latest, llama-3.1-8b. |
| Temperature | 0.0 – 2.0, half-step slider. |
| Max tokens | 64 – 131 072. |
| Theme | Cosmic (default) / Dark / Light, hot-applied. |
| Font size | 10 – 24 pt, hot-applied. |
| TTS enabled | Toggle Grok speaking replies aloud. |
| Voice persona | Ara / Rex / Sal / Eve / Leo. |
| System prompt | Optional, prepended to every conversation. |
| RAG enabled | Requires --features rag. Top-K retrieval over your own chat history. |
| RAG Top K | 1 – 12. |
| Perf dashboard | Show frame time / fps / tokens/s / resident memory in settings. |
Settings, chats, and messages are persisted to:
| OS | Data dir |
|---|---|
| macOS | ~/Library/Application Support/grok-insane/ |
| Linux | ~/.local/share/grok-insane/ (XDG) |
| Windows | %APPDATA%\GrokInsane\grok-insane\data\ |
Run grok-insane --diag for the exact paths on your machine.
| Shortcut | Action |
|---|---|
| ⌘ / Ctrl+N | New chat |
| ⌘ / Ctrl+, | Toggle Settings |
| ⌘ / Ctrl+. | Stop generation |
| ⌘ / Ctrl+K | Command palette (fuzzy launcher) |
| ⌘ / Ctrl+⇧+V | Toggle voice mode |
| Enter | Send |
| ⇧+Enter | Newline in composer |
| Right-click chat | Rename / Pin / Archive / Export / Delete |
┌─────────────────────────── UI thread ───────────────────────────┐
│ │
│ eframe::App ─► app::GrokApp │
│ │ │
│ ├─► ui::sidebar │
│ ├─► ui::chat_view ─► egui_commonmark │
│ ├─► ui::settings_view + perf_dashboard │
│ ├─► ui::waveform │
│ └─► ui::toast │
│ │
│ ▲ channels ▲ channels │
└───┼───────────────┼──────────────────────────────────────────────┘
│ │
┌───┴───────────┐ ┌─┴──────────────────────────────────────────────┐
│ Tokio runtime │ │ cpal capture/playback streams (!Send) │
│ • chat SSE │ │ • mono PCM16 @ 24 kHz, linear resampler │
│ • voice WS │ │ • level meters (Arc<AtomicU32>), VoiceShared │
└───┬───────────┘ └────────────────────────────────────────────────┘
│
┌───┴────────────────────────────────────────────────────────────┐
│ Storage layer │
│ • redb K/V (chats, messages, settings) — composite keys for │
│ range scans per chat ordered by timestamp │
│ • tantivy index over message bodies (TEXT + STORED), kept │
│ eventually consistent via best-effort writes │
└────────────────────────────────────────────────────────────────┘
Key design decisions:
- Single binary, no plugins required to be useful, but plugin host wired behind a feature flag for future extension.
- Send / !Send split for voice:
cpal::Streamis!Send, so the live audio handles stay on the UI thread; channels + atomics (VoiceShared) are what the async WebSocket task touches. - Storage façade is
Clone + Send + Sync; an internalArc<Inner>protects the tantivy writer with a singleparking_lot::Mutexbecause redb already serialises writes. - No locks in the hot UI path. Live settings are read via
ArcSwapso the audio engine never blocks on a contended mutex.
.
├── Cargo.toml # workspace + grok-insane package
├── Cargo.lock
├── .cargo/config.toml # alias: cargo xtask
├── .github/workflows/ci.yml # matrix CI: macOS / Windows / Linux
├── README.md
├── src/ # the desktop app crate
│ ├── main.rs # entry + CLI (--version, --diag, --reset-db)
│ ├── app.rs # top-level eframe::App impl
│ ├── config.rs # ArcSwap<Settings> handle
│ ├── error.rs # typed errors
│ ├── models.rs # Chat / Message / Settings / Provider / Role
│ ├── paths.rs # XDG / Known Folder / macOS app paths
│ ├── secrets.rs # OS keyring wrapper (zeroize)
│ ├── theme.rs # cosmic / dark / light palettes
│ ├── services/
│ │ ├── providers.rs # ChatProvider trait + ChatRequest/ChatEvent
│ │ ├── chat.rs # xAI streaming + reusable OpenAI-compat SSE decoder
│ │ ├── openai.rs # OpenAI client (delegates to chat.rs)
│ │ ├── anthropic.rs # Anthropic Messages SSE decoder
│ │ ├── local.rs # local OpenAI-compatible endpoint (Ollama, etc.)
│ │ ├── voice.rs # tokio-tungstenite Realtime WS client
│ │ ├── audio.rs # cpal capture + playback, resampler, level meter
│ │ ├── export.rs # Markdown / Obsidian / JSON exporters
│ │ └── embeddings.rs # RAG retriever (feature = "rag")
│ ├── storage/
│ │ ├── mod.rs # redb façade (chats, messages, settings)
│ │ └── search.rs # tantivy index over message bodies
│ └── ui/
│ ├── chat_view.rs # transcript + composer + markdown
│ ├── sidebar.rs # chat list + context menu (rename/pin/archive/export/delete)
│ ├── settings_view.rs # modal settings window
│ ├── perf_dashboard.rs # in-app stats
│ ├── waveform.rs # custom animated bar widget
│ └── toast.rs # bottom-right toast queue
└── xtask/ # Rust build/release helpers (no shell scripts)
├── Cargo.toml
└── src/main.rs # check / fmt / lint / test / dev / dist / bundle / reset / install-deps / ci / clean
There are no shell scripts in this repository. Build/release/dev helpers live
in the xtask/ crate and are invoked with cargo xtask <command>.
| Command | What it does |
|---|---|
cargo xtask check |
fmt --check + clippy --all-targets -- -D warnings + cargo test --workspace. The exact pipeline CI runs. |
cargo xtask fmt |
cargo fmt --all. |
cargo xtask lint |
cargo clippy --all-targets --workspace -- -D warnings. |
cargo xtask test |
cargo test --workspace. |
cargo xtask dev [args] |
Debug build with RUST_LOG=grok_insane=debug,info. Forwards any extra args to the binary. |
cargo xtask dist |
Release build + copies the binary to dist/<target>/grok-insane[.exe]. |
cargo xtask bundle |
Release build + per-OS packaging: a real .app on macOS (incl. Info.plist with NSMicrophoneUsageDescription), grok-insane.exe on Windows, staged tar source on Linux. |
cargo xtask reset |
Calls grok-insane --reset-db --yes. |
cargo xtask install-deps |
sudo apt-get install … for the Linux dev libs (no-op on macOS / Windows). |
cargo xtask ci |
install-deps + check + dist. |
cargo xtask clean |
cargo clean + remove dist/. |
cargo xtask help |
Print the above. |
Press ⌘ / Ctrl+K from anywhere in the app to open the command palette. Fuzzy-matches against every action — new chat, settings, voice/TTS/RAG toggles, theme + provider switches, "go to chat" by title, export, quit. Arrow keys + Enter to run, Esc to close.
Add a new helper by editing xtask/src/main.rs; each
helper is a regular Rust function calling std::process::Command.
grok-insane itself ships a small CLI surface so the binary is usable
headless (smoke tests, support sessions, CI):
grok-insane # launch the desktop app
grok-insane --version # print version
grok-insane --help # usage
grok-insane --diag # self-test: paths, redb open, chat/message counts, keyring presence
grok-insane --reset-db --yes # wipe local DB + search index
Example --diag output:
data dir: /root/.local/share/grok-insane
config dir: /root/.config/grok-insane
cache dir: /root/.cache/grok-insane
db path: /root/.local/share/grok-insane/grok-insane.redb
index path: /root/.local/share/grok-insane/search-index
chats: 0
messages: 0
api key [xai ] missing
api key [openai ] missing
api key [anthropic] missing
api key [local ] missing
All are off by default — the base build stays slim.
| Flag | Adds |
|---|---|
rag |
fastembed for local sentence embeddings; the retriever re-ranks lexical hits by cosine similarity to the query. Model weights download on first use. |
hotkeys |
global-hotkey for system-wide shortcuts (e.g. ⌘⇧V to toggle voice from any window). |
plugins |
wasmtime host (Cranelift + async). Plugin surface area is reserved; ready to expand. |
cargo run --features rag
cargo run --features rag,hotkeys
cargo build --release --features plugins
A tag push (v0.1.0 and up) drives .github/workflows/release.yml, which:
- Builds release binaries on macOS arm64, macOS x86_64, Linux x86_64, and Windows x86_64.
- On macOS, imports your Developer ID certificate into an ephemeral keychain
and runs
cargo xtask dmg— which callscodesign --options runtime --timestamp --entitlements packaging/Entitlements.plist, submits toxcrun notarytool submit --wait, staples withxcrun stapler staple, and builds the DMG withhdiutil. Both the.appand the.dmgitself are signed and stapled. - Uploads
grok-insane-<version>-<triple>.dmg/.tar.gz/.zipto a GitHub Release usingsoftprops/action-gh-release.
Add these in Settings → Secrets and variables → Actions before tagging:
| Secret | What it is |
|---|---|
APPLE_CERTIFICATE_P12_BASE64 |
Your Developer ID Application cert exported as .p12, then base64 -i cert.p12 | pbcopy. |
APPLE_CERTIFICATE_PASSWORD |
The password you set when exporting the .p12. |
APPLE_DEVELOPER_ID_APPLICATION |
The full identity string, e.g. Developer ID Application: Your Name (TEAMID12345). Get it with security find-identity -v -p codesigning. |
APPLE_ID |
Your Apple ID email. |
APPLE_TEAM_ID |
The 10-char Team ID from your Apple Developer account. |
APPLE_APP_SPECIFIC_PASSWORD |
Generate at https://appleid.apple.com → Sign-in & Security → App-Specific Passwords. |
KEYCHAIN_PASSWORD |
Any long random string. Used only to lock the temporary keychain on the runner. |
| State | First-launch behaviour |
|---|---|
| Signed and notarized (all secrets present) | DMG opens cleanly. The .app opens with no warnings. Gatekeeper + XProtect verify the stapled ticket offline; SentinelOne sees a trusted Apple-notarized binary with a known Team ID. |
| Signed only (no notary creds) | Gatekeeper shows "X cannot be opened because Apple cannot check it for malicious software." User can right-click → Open as the escape hatch. |
| Ad-hoc signed (no Apple secrets) | Gatekeeper blocks the app outright on first launch; same right-click escape hatch. SentinelOne will frequently flag and quarantine. |
Note that no client-side change can guarantee SentinelOne won't flag the binary — S1 runs behavioural rules set by your IT admin. A notarized binary from a known Team ID is the strongest signal you can ship; if S1 still flags it, the only fix is for an admin to add an exclusion.
To cut a release:
git tag v0.1.0
git push origin v0.1.0
# CI takes a few minutes; the release appears under
# https://github.com/<you>/<repo>/releasesYou can also kick off the workflow manually under Actions → Release → Run workflow (it'll build but won't publish a release unless triggered by a tag).
Every artifact under the Releases tab is:
- Signed via Sigstore keyless signing. The signing identity is the GitHub Actions workflow that produced it; the signature + public cert are recorded in the Rekor transparency log. No GPG keys.
- Attested with a SLSA v1.0 build-provenance attestation proving the artifact came from this repository and this workflow.
- Documented by a CycloneDX 1.5 SBOM listing every dependency, version, license, and source.
To verify any artifact (one-time cosign install required —
https://docs.sigstore.dev/cosign/installation):
# 1. Cryptographic signature + transparency-log entry
cosign verify-blob \
--bundle GrokInsane-0.1.0.dmg.cosign-bundle \
--certificate-identity-regexp 'https://github.com/mcaney006/grokmacos-better-version/.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
GrokInsane-0.1.0.dmg
# 2. SLSA build provenance (requires `gh` CLI logged in)
gh attestation verify GrokInsane-0.1.0.dmg --owner mcaney006
# 3. SBOM — scan with Grype for known CVEs
grype sbom:grok-insane-0.1.0.sbom.jsonIf any of those three steps fails, do not run the binary. Either the file was tampered with after release, or it didn't come from this repository at all.
# ad-hoc signed (warns at launch but works for local testing):
cargo xtask dmg
# fully signed + notarized:
export APPLE_DEVELOPER_ID_APPLICATION="Developer ID Application: Your Name (TEAMID12345)"
export APPLE_ID="you@example.com"
export APPLE_TEAM_ID="TEAMID12345"
export APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx"
cargo xtask dmgThe output lands at dist/<host-triple>/GrokInsane-<version>.dmg.
- Secrets never touch disk in plaintext. API keys live in the OS keyring
(
com.grokinsane.grok-insane, one entry per provider). In-memory copies are wrapped inzeroize::Zeroizingso the buffer is overwritten on drop. - No telemetry, no analytics, no third-party SDKs.
- TLS everywhere via
rustls(no OpenSSL dependency); WebPKI roots are bundled at build time. - WS auth rides as a
Bearerheader — same key as the REST API. - PCM audio stays on your machine until you toggle voice mode; once enabled it is streamed to the configured xAI Realtime endpoint only.
- Database is a single redb file in your local data dir. It can be
copied, backed up, or wiped with
grok-insane --reset-db --yes.
Targets the rewrite is built around (measured on an M2 Air with 50 k cached messages):
| Metric | Target | Why |
|---|---|---|
| Cold startup | < 150 ms | redb open is lazy; eframe boots without glow. |
| Frame time | < 8 ms (120 fps) | Immediate-mode + GPU compositor + cached markdown. |
| Resident memory | < 80 MB idle | No GC, no JS, no embedded browser. |
| Search latency | < 5 ms / 100 k msgs | Tantivy MMAP segments. |
| Stream-to-screen latency | bounded by network | UI drains its channel each frame; no extra hop. |
Toggle the perf dashboard in Settings → Show performance dashboard to watch live numbers.
cargo xtask check # fmt + clippy + tests
cargo nextest run --workspace # parallel test runner
cargo test --workspace # if you don't have nextest installed105 tests passing across the workspace (default + --no-default-features
--features hq-resampleconfigurations). Coverage spans:
- Streaming decoders — both providers (xAI/OpenAI-compatible + Anthropic)
have property tests (
proptest, 256 cases × 4 KiB inputs, with counterexamples persisted toproptest-regressions/) AND an in-tree 1000-seed LCG fuzz. Adversarial regression tests cover post-[DONE]silence, oversize lines, mid-feed terminators, zero-byte chunks, malformed JSON, provider error events, and EOF-before-terminator. - Tool-use streaming —
input_json_deltaaccumulator tested for multi-fragment assembly across chunk sizes 1/7/16/64, empty-input calls, malformed-JSON error surfacing, and fence-injection sanitisation. - WebSocket health — connect-timeout against a hung handshake, keepalive-failure surfaces an Error event, receive-watchdog deadline.
- Storage — chat/message round-trip, range-scan ordering, delete-chat / delete-message cleanup across redb + tantivy, settings decode-failure fallback, individually-corrupt entry skip, and the new startup reconciliation when redb and tantivy diverge.
- Cancellation —
consume_chat_streamhonours cancel-before-loop and cancel-mid-stream, surfaces provider errors without trailing Done, and synthesises a Done on silent EOF. - Rate-limit retry — mock HTTP server confirms
Retry-Afteris honoured, retry budget is bounded, and non-429 errors short-circuit. - Audio + resampler — identity rate, ~0.5 downsample ratio, empty input, queue overflow drops cleanly without panic.
See TESTING.md for the full inventory and the
docs/THREAT_MODEL.md for the security risk →
control mapping.
A cargo-fuzz workspace lives in fuzz/ with libFuzzer
harnesses for both SSE decoders, seeded from in-tree golden fixtures.
Run on-demand with cargo +nightly fuzz run sse_decoder -- -max_total_time=60.
Not run continuously by CI today.
services::chat::tests::sse_decoder_extracts_deltas_and_done— OpenAI- compatible SSE state machine.services::anthropic::tests::anthropic_decoder_parses_text_delta_and_stop— Anthropic event stream + usage tracking.services::export::tests::markdown_export_includes_messages_and_titleservices::export::tests::obsidian_export_has_front_matter_and_quotes_title_with_colonservices::export::tests::json_export_round_trips
Already shipped this iteration: see CHANGELOG.md.
- Streaming TTS playback — play audio chunks as they arrive instead
of waiting on
audio.done. The WebSocket plumbing exists; the audio sink hookup does not. - Interruptible TTS — barge-in via a key while speaking.
- Image input for vision-capable models. Provider clients don't yet serialise image content blocks.
- File / tool attachments in the composer. The Anthropic decoder
surfaces tool_use events end-to-end; there is NO local tool runner.
The
pluginsfeature gate is reserved but contains no implementation —wasmtimewas intentionally NOT pulled in because of outstanding RUSTSEC advisories at the time of writing. Will reinstate whenwasmtime >= 38is compatible with our other deps. - Continuous fuzzing in CI —
fuzz/harnesses run on-demand; scheduling them weekly requires a nightly job, deferred. - Apple Developer ID signing in CI — workflow exists, secrets aren't configured. First DMG will be ad-hoc signed; macOS users will see a Gatekeeper prompt until real Developer ID credentials land.
- ✅ macOS notarization — implemented in
xtask::mac::{sign, notarize, dmg}, exercised by the release workflow. - ✅ Sigstore keyless signing — every release artifact gets a cosign bundle via the release workflow.
- ✅ SLSA build provenance — via
actions/attest-build-provenance. - ✅ CycloneDX SBOM — generated per release via
cargo-cyclonedx.
Dual-licensed under either of:
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
at your option.