Skip to content
highercomvePublic

About

AI text editing anywhere on your desktop: proofread, rewrite and translate in any app, live captions, dictation, built-in local models. Built with Oriel (Zig).

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

193 Commits

Folders and files

Repository files navigation

GhostPen on Oriel

Website and downloads Β· Releases Β· Changelog Β· The earlier Rust/Tauri version: ghostpen-tauri

GhostPen, AI-driven text editing anywhere on your desktop, ported from Tauri (Rust) to Oriel (Zig): the first full application built on Oriel, using only its public oriel CLI and API.

Highlight text in any app, press the hotkey (or click the tray icon), pick an action (proofread, rewrite, translate, …); the result is pasted back in place. Also: a Playground with streaming answers, text extraction from clipboard images (OCR through a vision model), live captions of system audio, and voice dictation. Summarize web pages and export them as PDF or Markdown with Mermaid diagrams. Record meetings, revisit speaker-labelled transcripts, and keep summaries and answers alongside the recording.

Any OpenAI-compatible endpoint works (Ollama, LM Studio, OpenAI, OpenRouter, Groq, …; the default is a local Ollama with gemma4:e4b), or GhostPen runs a downloaded model itself: see Built-in models.

Downloads (website, releases). The AppImage, the Windows installer and the macOS app update themselves (Settings β†’ About & updates); deb and rpm installs say when a new version is out.

Package Whisper and Built-in models run on
Linux AppImage, .deb, .rpm any GPU through Vulkan (NVIDIA, AMD, Intel); the CPU without one
Linux, -cuda AppImage, .deb, .rpm NVIDIA GPUs through CUDA (RTX 20xx–50xx; needs the CUDA 13 runtime), else Vulkan
Windows 10/11 setup.exe (per user) any GPU through Vulkan (NVIDIA, AMD, Intel); the CPU without one
macOS 13+ .dmg (Apple Silicon) the GPU through Metal

Linux needs GTK 4.12+ and WebKitGTK 6.0: Ubuntu 24.04, Debian 13, Fedora 39 or newer (Ubuntu 22.04 and Debian 12 ship older GTK 4; the .deb says so). On Wayland the overlays sit at the screen's edge when gtk4-layer-shell is installed (Arch: gtk4-layer-shell); without it they're ordinary windows.

The React frontend is GhostPen's own; the commands, events and settings keep the Tauri version's names and JSON shapes. On first run the Tauri app's settings are imported, and its downloaded whisper models are reused.

The menu, on the Built-in model

Playground Settings β†’ Built-in models
Playground Built-in models

Build and run

oriel doctor --fix      # toolchain: Zig, Node.js, ...
oriel dev               # run with hot reload
oriel build             # zig-out/bin/ghostpen (+ ghostpen-cli)
oriel build -Dcuda      # whisper on an NVIDIA GPU (CUDA toolkit)
oriel build -Dvulkan    # ... or on any GPU through Vulkan (Linux and Windows releases; Windows needs the Vulkan SDK)
oriel package           # deb, rpm, AppImage / setup.exe / .app + .dmg

Using it

Ctrl+Shift+A Menu for the selected text (hotkeys are editable in Settings)
Ctrl+Shift+D Dictation: speak, ⏎ to finish (copied to the clipboard)
Ctrl+Shift+L Live captions of what your computer plays
Tray icon Menu, Dictation, Captions, Playground, Settings, Quit

The same actions from a terminal or a desktop keybinding, handed to the running instance: ghostpen --trigger | --voice-input | --captions | --settings | --playground. On Wayland without the GlobalShortcuts portal, bind these in your compositor, e.g. Hyprland:

bind = CTRL SHIFT, A, exec, ghostpen --trigger

ghostpen-cli runs an action from the terminal with the same settings:

echo "teh quick brown fox" | ghostpen-cli proofread
ghostpen-cli translate --lang French --stream "Good morning"
ghostpen-cli profiles

Link summaries

Select a webpage link and choose Summarize link in GhostPen, or paste the URL into the Summary window. Choose Brief, Standard, or Detailed to set the depth. The answer streams into a reader with adjustable text size, font, and spacing. Read summary reads the answer aloud; Read page reads the source page.

The Summary window keeps exposed model thinking separate from the summary. The Show thinking switch requests thinking for the next summary and reveals any thinking already received, during generation or after it finishes. It is off by default, and the window remembers your choice. Turning it on after a run started with thinking off requires summarizing again to request thinking; the reader explains this when there is nothing to reveal. Models or providers may not expose thinking even when requested. Copy and Read summary use only the summary, even when thinking is visible.

Channels are normalized in the backend from provider reasoning fields, explicit leading reasoning blocks, or a thought channel opened by the local chat template. Ordinary headings and literal tokens in the answer stay intact. Output is limited to 1 MiB per request; exceeding the limit stops the summary with a recoverable error.

Mermaid code fences render as diagrams in the reader. Use Export β†’ PDF to save the summary with those diagrams, or Export β†’ Markdown to keep the editable Markdown and diagram source. Choose a location in the save dialog to write the file. Large diagrams continue in labeled, overlapping panels at a readable size; headings stay with their first panel. Invalid diagrams remain readable as source. PDF diagrams use a light palette even when the reader uses dark mode.

Meetings

Keep the recording, speaker-labelled transcript, clean text, summary, and saved questions together. Play the audio while following individual words, jump to a line, or ask for decisions and action items with timestamps you can revisit.

Open Meetings and choose Record meeting to capture system audio and the microphone configured in Settings without showing live captions. Choose Stop and save when finished: the existing save flow prepares the transcript, speaker labels, clean text, and optional summary. Recording continues if you hide the window; reopen Meetings to stop it. A recording does not require Whisper to be loaded, but transcription after stopping needs the configured model downloaded. Live captions and recording share audio capture, so stop one before starting the other.

The meeting notebook uses the same paper-and-ink reader design as link summaries, with saved conversations beside the transcript, summary, and questions. You can browse earlier meetings while recording a new one.

The Notes tab is your own notebook for each meeting. Write during recording or afterwards; notes save automatically, with Markdown preview and a manual Save notes button. They are stored as notes.md in the meeting folder and kept when you rerun transcription or summarization.

Use the meeting's Chat tab to ask questions across its transcript, clean text, personal notes, summary, and earlier questions. Latest note edits are saved before asking. Answers distinguish personal notes from spoken content.

Turn on πŸŽ™ Meeting in the captions bar, then Start: the captions also record what your computer plays and your microphone ("Me"). Stop saves the meeting in Documents/GhostPen/Meetings/<date>_<time>/: the whole audio is transcribed again (more accurately than the live captions), the speakers are told apart by voice, and your AI profile proofreads it into a clean text, with a title. Tray β†’ Meetings lists them: play one and the line being said is highlighted, click a line to play from it, rename speakers (every line follows), merge them, move a line to another speaker, edit the text, copy it as text, Markdown or subtitles, or proofread and find the speakers again. Name a speaker and pick Remember this voice: later meetings name them by themselves, live in the captions too. The Summary tab summarizes the meeting (overview, decisions, action items; long meetings in parts) and answers your own questions about it ("What did Alex say about me?"), each saved with the meeting and read aloud on request.

Through speakers instead of a headset the microphone hears the call: those lines are recognized as the others' (same words, same moment) and dropped. Each meeting folder holds audio.wav, meeting.json, transcript.md and final.md; nothing leaves the machine except the text sent to your AI profile for the proofread.

Speakers come from Oriel's oriel.speakers (voice-detect.cpp) and the WeSpeaker ResNet34 model trained on VoxCeleb (WeSpeaker, CC-BY-4.0; downloaded the first time, 26 MB, SHA-256 verified).

Read aloud (the built-in voice)

A Kokoro-82M voice runs inside the app, on this computer, no network. When a result is shown in the menu, πŸ”Š Speak reads it (the S key does the same; press again to stop). A translated result reads back in that language's voice; the summary window adds Read summary and Read page with its own Stop. Long texts come out in sentence chunks, one after the next, so reading from the menu starts right away. Also from a terminal:

ghostpen --say "Good morning" --lang es --voice ef_dora

The voice is Oriel's oriel.tts: synthesis runs on the GPU where GhostPen was built with one (-Dvulkan, -Dcuda), else on the CPU, and the first sound comes after the first sentence. The first use downloads the voice model (Kokoro 82M Q8_0, ~135 MB; SHA-256 verified) and the voice pack for the text's language (511 KB each; 14 curated voices covering the languages the translate menu offers) into <data dir>/GhostPen/tts. Settings β†’ Voices manages it all: download and delete the model and the voice packs, with progress bars, where the voice runs, and the phoneme-data row. The espeak-ng phoneme data ships with GhostPen (espeak-ng-data next to the program), so no system package is needed.

A note on licenses: the binaries that read text aloud link espeak-ng, which is GPL-3.0-or-later. GhostPen's own code stays MIT; espeak-ng's data and the Apache-2.0 Kokoro voices come with the shipped parts their licenses apply to (the packaged espeak-ng data itself is GPL'd, the voices Apache). No audio leaves the machine either way.

Built-in models

A profile set to Built-in runs the model inside GhostPen itself, with the llama.cpp compiled into it: no Ollama, LM Studio or other server, and nothing sent over the network. In Settings β†’ Built-in models:

  • Download one of the catalog models (Qwen3.5 2B/4B/9B, Gemma 3 4B, Gemma 4 E4B) from Hugging Face. Downloads can be paused and resumed, and are checked against the SHA-256 Hugging Face publishes. They go to <data dir>/GhostPen/models (~/.local/share/GhostPen/models on Linux).
  • GGUF files that LM Studio (~/.lmstudio/models) or GhostReel (~/.ghostreel/models) already downloaded are found and reused, catalog or not.
  • Use points the "Built-in (GhostPen)" profile at a model (creating the profile if needed) and makes it active.
  • Runner settings: the context window, the GPU (as many layers as its free memory holds; the rest runs on the CPU) and how long the model stays loaded between actions (10 minutes by default).

The model runs in a helper process (ghostpen --llm-helper, started and stopped by GhostPen), so a crash or running out of memory can't take the app down. The Linux and Windows releases run it on the GPU through Vulkan (any vendor; the CPU without a Vulkan driver), and the -cuda releases through CUDA on NVIDIA GPUs when the CUDA 13 runtime is installed (Vulkan otherwise); build with -Dcuda for CUDA; macOS uses Metal. ghostpen-cli uses Built-in profiles too (ghostpen-cli models lists them). Extract Text (reading an image's text) works on built-in models that have their image projector, llama.cpp's mmproj: every catalog model has one, downloaded with the model ("Add image support" for a model you already have), and LM Studio's mmproj-*.gguf next to a model is picked up. Those models show reads images in Settings.

Built-in profiles are new to this port: the Tauri app, if it reads the same settings, sees them as endpoints with no URL.

GPU backends: CUDA, Vulkan, CPU

Measured on an RTX 4070 with an AMD Ryzen 7 7800X3D 8-Core Processor, GhostPen 0.2.4 built with the same CPU target, warm runs:

CUDA Vulkan CPU
Whisper small, 66 s of speech 0.52 s 0.60 s 4.75 s
Qwen3.5 2B: prompt (1,390 tokens) 12,858 tok/s 10,571 tok/s 193 tok/s
Qwen3.5 2B: generation 216 tok/s 195 tok/s 36 tok/s
Ornith 1.5 9B Q4_K_M: prompt 3,631 tok/s 3,068 tok/s
Ornith 1.5 9B Q4_K_M: generation 74.9 tok/s 69.6 tok/s

Vulkan's first transcription after start also compiles its pipelines (1.5 s instead of 0.6 s).

Compared with the Rust (Tauri) GhostPen

The same app on both stacks: GhostPen's React frontend, the same commands, settings and prompts. Measured on 2026-09-28 on one Linux machine (Ryzen 7 7800X3D, 16 threads, RTX 4070; Arch Linux, Hyprland) with Zig 0.16.0 and Rust 1.98.1: GhostPen 0.2.10 on Oriel 0.6.14, and the Tauri version at ab0c1c4 (0.1.3), both built for release with whisper on the GPU (CUDA).

The menu in both versions, on Wayland

On this Wayland desktop the Rust version runs in manual mode (no synthetic Ctrl+C, so the menu asks you to copy first; in this test it didn't pick up a manual copy either, and its Playground stopped responding). The Oriel version copies the selection and pastes the result itself.

Running ΒΉ

Rust / Tauri 2 Zig / Oriel
Start β†’ Settings window shown Β² 214 ms 265 ms
Idle memory, 20 s after start (PSS, app + WebKit processes) 659 MB (app 266 MB) 297 MB (app 142 MB)
Memory with the whisper model loaded Β³ 962 MB (app 554 MB) 805 MB (app 153 MB + whisper runner)
Idle CPU over 30 s 0.06 s 0.00 s
Transcribing a 6.1 s clip (large-v3-turbo q5_0, CUDA) ⁴ 0.38 s (first request 0.55 s) 0.22 s (first request 0.33 s)
Web content sandboxed no (WebKitGTK 4.1 default) yes (bubblewrap, WebKitGTK 6.0)
Synthetic copy/paste on Wayland no (manual mode) yes (virtual keyboard)
Runs AI models itself no (an endpoint: Ollama, LM Studio, …) yes: Built-in models, or an endpoint

GhostPen on Oriel creates a window (a web process each) only when it's first shown, and runs whisper in a runner process started on the first transcription and stopped after five idle minutes: the CUDA runtime and the model aren't in the app, and their memory goes back when it's unused.

Shipping

Rust / Tauri 2 Zig / Oriel
App binary, stripped 49.4 MB (whisper and its CUDA kernels inside) 11.1 MB (whisper and llama.cpp) + 38.3 MB libggml-cuda.so
CLI binary, stripped 7.8 MB 1.5 MB
.deb / .rpm 30.3 MB / 30.3 MB (app and CLI, unstripped: 62 + 11 MB) 24.0 MB / 24.6 MB (app, CLI and libggml-cuda.so, stripped)
AppImage 546 MB (bundles GTK 3, WebKitGTK and 190 more libraries) 25.1 MB (uses the system's WebKitGTK 6.0)
Toolkit (Linux) GTK 3, WebKitGTK 4.1 GTK 4, WebKitGTK 6.0

Building ⁡

Rust / Tauri 2 Zig / Oriel
Code (app + frontend, generated files excluded) 8,086 lines (4,851 Rust, 2,227 TS, 1,008 CSS) 10,191 lines (6,151 Zig, 3,108 TS, 932 CSS), with Built-in models, voice activity detection, draggable overlays
Dependencies 627 crates (Cargo.lock) 2 packages (Oriel, zigimg); Oriel itself has 9
Clean release build, app and CLI 289 s 386 s
Release rebuild, nothing changed ⁢ 48 s 0.7 s
Release rebuild after a one-line edit ⁷ 48 s 50 s
Packages (deb, rpm, AppImage), after a build 148 s 25 s
Debug build (development), clean 180 s 46 s
Debug rebuild after a one-line edit ⁸ 3.4 s 11.7 s (7.2 s with -Ddev_debug_info=false)
Build cache, release and debug builds 7.7 GB target/ + 1.8 GB ~/.cargo/registry (shared by all Rust projects) 1.9 GB (.zig-cache 1.7 GB + global 218 MB) + 256 MB sources (zig-pkg/)

Rust's cache grows with every configuration built: GhostReel, a Tauri app with two model runners, has 9.3 GB in target/ (7.5 GB of it debug builds) on this machine, besides the shared registry.

¹ On a private X display (Xvfb) with a private D-Bus session, the same for both. ² From exec until the "GhostPen Settings" window is mapped (--settings), median of 5 runs after a warm-up. About 40 ms of Oriel's difference is WebKitGTK 6.0's process sandbox. ³ With the transcription server (GHOSTPEN_STT_SERVER=1) after four requests. On Oriel, "app" is the app process alone; the whisper runner holds the model and the CUDA runtime. ⁴ tests/speech.wav through the transcription server, the same model and the same text back; the time is the HTTP round trip, median of the warm requests. Oriel's whisper.cpp (1.9.4) skips the clip's silence with its voice activity detection; the Rust version uses whisper-rs 0.15. The first transcription after starting (or five idle minutes) also starts the runner, about 0.1 s. ⁡ Fresh clones and empty build caches (Zig's global cache too), dependency sources downloaded beforehand (npm, the Cargo registry, zig-pkg/). Rust: tauri build --no-bundle --features captions-cuda, then cargo build --release --features cli --bin ghostpen-cli; Oriel: oriel build -Dcuda. Both include the frontend and whisper.cpp with its CUDA kernels; the Oriel build also compiles llama.cpp for Built-in models. ⁢ The same command again. tauri build rebuilds the frontend, which makes the app crate recompile, and the CLI (another feature set) recompiles it once more. Zig caches by content, so nothing is recompiled. ⁷ A string in a log line of the app's main source file. ⁸ cargo build vs zig build build-dev (what oriel dev rebuilds). Rust recompiles one crate incrementally; Zig compiles the whole app as one unit through LLVM, and most of that time is debug info.

Application logs

Run ghostpen logs to print the application log, or ghostpen logs --follow (-f) to keep watching new entries. Press Ctrl+C to stop following. These commands work while GhostPen is running and do not open a window.

Logs are stored in $XDG_DATA_HOME/dev.ghostpen.Oriel/app.log (default ~/.local/share/dev.ghostpen.Oriel/app.log) on Linux, ~/Library/Logs/dev.ghostpen.Oriel/app.log on macOS, and %LOCALAPPDATA%\dev.ghostpen.Oriel\app.log on Windows.

Transcription server for other tools

GHOSTPEN_STT_SERVER=1 ghostpen also serves the whisper model as an OpenAI-compatible API, so local tools (an agent that receives voice notes, a video indexer) transcribe through GhostPen's model instead of loading another: POST /v1/audio/transcriptions (multipart file, any format ffmpeg reads; language; response_format json, text, verbose_json with timestamped segments, srt or vtt), GET /v1/models, GET /health. It binds GHOSTPEN_STT_BIND (default 0.0.0.0:8771), serves Settings β†’ Live Captions' model or GHOSTPEN_STT_MODEL, and shares it with captions and dictation (one copy in memory). Needs ffmpeg.

Updates

Settings β†’ About & updates shows the version, checks for a new release and, with "Update automatically" on (the default), checks a minute after start and every 12 hours. The Windows installer, the AppImage and the macOS app update themselves (the new version starts on the next launch, or with "Restart now"); deb/rpm installs and source builds only say a new version exists. Each release publishes a latest.json signed with Ed25519 (the GHOSTPEN_UPDATE_KEY secret); GhostPen installs only what matches the public key in build.zig. GHOSTPEN_UPDATE_MANIFEST=<https url> checks another manifest, e.g. to try a release before publishing it.

Permissions

Declared with oriel permission add (see build.zig): accessibility (synthetic copy/paste into other apps; macOS asks on first run), microphone (dictation) and system audio (captions). On macOS, captions need a loopback device such as BlackHole.

Tests

npm --prefix frontend test # Summary component: channels, toggle, Copy/TTS, snapshots
zig build test            # AI client, settings, images, models
tests/e2e.sh              # headless: select β†’ trigger β†’ Proofread β†’ pasted; Playground streaming
tests/e2e-audio.sh        # headless: captions overlay, dictation β†’ proofread β†’ clipboard
RECORD_ONLY=1 tests/e2e-meetings.sh # Meetings recorder β†’ transcript, summary, playback

The Summary regression fixture in src/fixtures/summary-gemma4-e4b.json records a live E4B run with the original standard Summary prompt and think=false, including the producer's actual initial channel. It also records the comparison run: the old empty-thought prefill produced Thinking Process: … <channel|>Answer, while the plain model-turn prompt produced only the summary. Both runs used the same synthetic article, seed 42, temperature 0.2, 4096-token context, and a CPU-only validation build of the same application sources (Metal disabled in the temporary build's Oriel dependency). A third captured run with think=true contains both channels; its regression checks that no thinking enters the answer before or after completion. This is evidence for that case, not a guarantee about arbitrary unmarked model prose.

The fixture also keeps formatted_request and formatted_output for the clarified Summary prompt. E4B could copy the old bulleted instructions as - ## Key points and - ## Details when thinking was off. The instructions now distinguish section headings, prose, and list items. The live check requires actual heading lines and bullets; the component regression checks that Key points and Details render as sections in a newly generated summary.

To repeat the live check with an already installed E4B model and your built GhostPen executable (no downloads or settings changes):

python3 tests/summary-live.py --exe zig-out/bin/ghostpen-dev \
  --model "/path/to/gemma-4-E4B-it-Q4_K_M.gguf" --record /tmp/summary-live.jsonl
# Request thinking and check that it is followed by the summary:
python3 tests/summary-live.py --exe zig-out/bin/ghostpen-dev \
  --model "/path/to/gemma-4-E4B-it-Q4_K_M.gguf" --thinking

The end-to-end tests run on a private X display (Xvfb + D-Bus) against a mock AI endpoint (tests/mock_openai.py); the audio test feeds tests/speech.wav instead of the sound server ($GHOSTPEN_TEST_AUDIO).

Layout

File
src/main.zig Commands, events, the menu flow, windows, tray, hotkeys, launch flags
src/ai.zig OpenAI-compatible client: prompts, reasoning-leak retry, SSE streaming, vision, /models; local profiles go to local_llm.zig
src/local_llm.zig, src/llm_helper.zig Built-in models: the runner's client (start, idle unload, cancel) and the runner (llama.cpp, JSON lines)
src/llm_models.zig, src/chat_format.zig Model catalog, resumable verified downloads, models found on disk; prompt formats (Gemma 4/3, ChatML, Qwen thinking)
src/settings.zig, src/store.zig Settings schema (Tauri-compatible) and the Oriel store
src/captions.zig, src/dictation.zig, src/models.zig Whisper captions and dictation, one shared model
src/image.zig PNG helpers for OCR and previews
src/cli.zig ghostpen-cli
frontend/ GhostPen's React UI (src/api.ts over Oriel's invoke/listen)

License

MIT: see LICENSE.

About

AI text editing anywhere on your desktop: proofread, rewrite and translate in any app, live captions, dictation, built-in local models. Built with Oriel (Zig).

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages