Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 20 additions & 121 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
# Local Wisper

Local speech-to-text for x86_64 Linux. Record from the command line or a Sway
keybinding, transcribe locally with NVIDIA Parakeet, and send the result to the
clipboard or focused window.
<p align="center">
<img src="assets/logo.svg" alt="Local Wisper logo" width="160">
</p>

Local Wisper keeps one model warm for fast repeated transcription. It selects
FP16 CUDA inference when CUDA works and falls back to INT8 CPU inference. Users
do not need to choose a model, device, or weight format.
# Local Wisper

This release is experimental. It installs a native `lw` executable and does
not need Python during normal use. Faster Whisper is no longer supported.
Local speech-to-text for Linux, built around Sway. Press a shortcut, speak, and
the transcript appears in the focused window without sending your audio to the
cloud.

## Requirements

Expand All @@ -19,59 +16,32 @@ not need Python during normal use. Faster Whisper is no longer supported.
- `wtype` for typing into the focused Sway window
- BAML 0.16, `cargo`, `curl`, and `sha256sum` to build and install

An NVIDIA GPU is optional. Systems without working CUDA use the CPU
automatically.

## Install

```bash
git clone https://github.com/none23/local-wisper.git
cd local-wisper
./install.sh
lw preload
```

The installer creates:

- `~/.local/bin/lw`
- `~/.config/local-wisper/env`
- `~/.config/local-wisper/glossary.txt`

It leaves existing configuration files untouched. Make sure `~/.local/bin` is
in `PATH`.

The first `lw preload` downloads and verifies the Parakeet files for the
selected device. Later commands reuse the same cached files and warm model.
Local Wisper allows only one model process per user to avoid duplicate RAM or
VRAM use.

On a Manjaro system with an NVIDIA GPU, the installer can place a private copy
of cuDNN 9 under `~/.local/lib/local-wisper` when the system does not provide
it. CPU-only systems skip CUDA setup. Normal use needs neither Python, Cargo,
nor the BAML CLI.
The installer adds `lw` to `~/.local/bin`. Make sure that directory is in your
`PATH`.

## Usage

- `lw`: record until Enter, transcribe, print the result, and copy it
- `lw preload`: load the model before the first recording
- `lw sway-start`: begin a detached recording
- `lw sway-stop`: stop, transcribe, and deliver the recording
- `lw sway-cancel`: discard the active Sway recording

Run `lw --help` to print the command summary.
Sway is the primary integration. The `lw` CLI is available for debugging; run
`lw --help` when you need it.

## Sway integration

Install the supplied wrapper:
Install the supplied Sway wrapper:

```bash
install -Dm755 integrations/sway/local-wisper.sh \
~/.config/sway/scripts/local-wisper.sh
```

A minimal Sway configuration looks like this:
Add the following to your Sway configuration:

```text
```sway
set $local_wisper $HOME/.config/sway/scripts/local-wisper.sh
set $mode_local_wisper local-wisper

Expand All @@ -86,87 +56,16 @@ mode "$mode_local_wisper" {
bindsym $mod+grave exec $local_wisper sway-start, mode "$mode_local_wisper"
```

The wrapper reads `~/.config/local-wisper/env` and types completed transcripts
into the focused window by default. Existing Sway wrappers remain compatible.

Speech-to-text works better as a system-level feature than an editor feature,
so this version removes the Neovim plugin. Sway is the only bundled
integration.
Press <kbd>Mod</kbd>+<kbd>`</kbd> to start recording. Press it again, or press
<kbd>Enter</kbd>, to transcribe and type into the focused window. Press
<kbd>Escape</kbd> to cancel.

## Transcript cleanup
## Configuration

Local cleanup handles spoken decimals, phrases such as `numeric three`,
statement formatting, and deterministic glossary replacements.

Optional OpenAI cleanup improves punctuation and recurring technical terms.
When enabled, transcripts with six or more words are sent to `gpt-5.6-luna`.
Add the following values to `~/.config/local-wisper/env`:
Local Wisper works without an API key. OpenAI can optionally post-process
transcripts. Enable it in `~/.config/local-wisper/env`:

```bash
export OPENAI_API_KEY='...'
export LW_POST_PROCESS_MODEL='gpt-5.6-luna'
export LW_POST_PROCESS_TIMEOUT='20'
export LW_POST_PROCESS_GLOSSARY_FILE="$HOME/.config/local-wisper/glossary.txt"
```

If model cleanup fails or reaches the deadline, Local Wisper returns the local
result. The glossary supports four sections:

```text
[always]
engine x -> nginx

[likely]
cloud code -> Claude Code

[contextual]
codecs -> Codex

[terms]
TypeScript
```

- `[always]` applies a local replacement every time.
- `[likely]` asks model cleanup to prefer the replacement.
- `[contextual]` applies when the surrounding text supports it.
- `[terms]` supplies preferred spelling and capitalization.

## Performance

On the development machine, an 11.04-second recording took 0.36 seconds with
FP16 CUDA and 0.80 seconds with INT8 CPU. The CPU model process used about 1 GB
of memory. Results depend on the machine.

## Technical notes

The `lw` executable runs the application in BAML and uses a small Rust host for
native Parakeet ONNX inference and Linux process operations. A per-user model
service keeps Parakeet warm. Its authenticated loopback endpoint is available
only through a private user runtime directory.

The model files are pinned and verified before use. A per-user lock covers
model selection, download, and loading so concurrent commands cannot create a
second model process.

## Development

Install `pre-commit` and `shellcheck`, then enable the fast local checks:

```bash
pre-commit install
pre-commit run --all-files
```

After changing a `.baml` file, run:

```bash
baml fmt
baml check
baml test
baml generate
cargo test
```

The generated Rust SDK is build output and is not committed. Run
`baml generate` before a direct Cargo build. The installer performs this step
automatically.