Files
command-center/omp-docs/tools/handy.md
Frank 62a70caebb docs(okf): 53 Obsidian wikilinks become OKF links
`[[page|Label]]` is Obsidian syntax, not OKF. `kb-lint` resolves it Obsidian-style — by
filename, anywhere in the bundle — so it has never tripped the link gate, while every other
consumer of the bundle (an OKF renderer, a Gitea wiki export, `grep` for reachability) sees
bracketed text where a link should be. That is how two generated pages ended up with no
inbound link at all and nobody noticed: the my-first link census of the bundle reported them
as orphans because their only inbound edges were wikilinks.

Mechanical conversion, 52 links across 17 pages, each resolved against the same page set
`kb-lint` uses: an explicit `|Label` is kept verbatim, an unlabelled link takes the target's
frontmatter `title`. Targets are emitted bundle-absolute (`/tools/niri/tips.md`), the form
the spec recommends and the form 99 existing links in this bundle already use.

Two links were `[[index]]`, which resolves by filename to 21 pages — the ambiguity that
filename-resolution invites. Both meant their own directory's index, so both are now written
out in full: `/tools/gitea/index.md`, `/tools/niri/index.md`.

The linter still tolerates wikilinks; whether it should reject them outright is an open
question on #64, because that would replace the two tests pinning today's tolerance.

Refs #64
2026-10-21 02:11:56 -07:00

234 lines
7.0 KiB
Markdown

---
type: reference
title: Handy
description: Offline speech-to-text transcription with WebKitGTK backend and Tauri v2 UI.
resource: https://github.com/cjpais/Handy
tags:
- handy
- stt
- speech-to-text
- transcription
- wayland
- tauri
timestamp: 2026-10-09
verified: 2026-10-09
---
# Handy
Handy is an offline speech-to-text (STT) transcription tool built with Tauri v2 and WebKitGTK. It runs entirely offline, supports multiple GPU backends, and provides real-time transcription with optional post-processing.
**Installed version:** 0.9.6-1 (`local/handy-bin`, `/usr/bin/handy`) — **0.9.8 upgrade pending**: `yay -Syu handy-bin` (root work is operator-owned; agents never sudo).
**Upstream latest:** 0.9.8 (as of 2026-10-03)
**Binary:** `/usr/bin/handy`
**Config:** `~/.config/com.pais.handy/` (upstream docs) / `~/.local/share/com.pais.handy/settings_store.json` (live observed)
---
## Architecture
Handy uses Tauri v2 for its UI (WebKitGTK for rendering) and a native Rust backend for STT processing. On Wayland, it requires compositor-level global shortcuts.
**Live settings snapshot (blankpad):**
| Setting | Value |
|---------|-------|
| `paste_method` | `none` |
| `copy_to_clipboard` | `true` |
| `push_to_talk` | `true` |
| `bindings` | transcribe=`ctrl+space`, transcribe_with_post_process=`ctrl+shift+space`, cancel=`escape` |
| `keyboard_implementation` | `handy_keys` |
| `overlay` | `bottom` (live), `true` (config) |
| `model` | `handy-computer/canary-180m-flash-gguf` (Q8_0) |
| `transcribe_gpu` | `vulkan` (PCI 07:00.0) |
| `autostart_enabled` | `false` (niri-managed) |
| `experimental` | `true` |
| `filler_word_removal` | `true` |
| `post_process_enabled` | `true` (fleet-wired) |
| `clipboard_handling` | `copy_to_clipboard` |
| `provider` | `custom` (base_url=`http://192.168.0.143:7771/v1`, model=`frank-secondary`) |
| `prompt` | `default_improve_transcriptions` |
---
## Configuration
### Config Location
**Upstream docs:** `~/.config/com.pais.handy/`
**Live observed:** `~/.local/share/com.pais.handy/settings_store.json`
Both locations may contain settings; the live data directory is the authoritative runtime store.
### Live Settings (blankpad)
The store is nested: everything lives under a top-level `settings` object.
Post-processing wiring (applied 2026-10-09) touches five keys plus the
`custom` entry inside `post_process_providers`:
```json
{ "settings": {
"post_process_enabled": true,
"post_process_provider_id": "custom",
"post_process_models": { "custom": "frank-secondary", "openai": "", "...": "" },
"post_process_selected_prompt_id": "default_improve_transcriptions",
"post_process_providers": [
{ "id": "custom", "base_url": "http://192.168.0.143:7771/v1",
"allow_base_url_edit": true, "models_endpoint": "/models", "...": "" }
],
"post_process_api_keys": { "custom": "", "...": "" },
"autostart_enabled": false,
"push_to_talk": true,
"paste_method": "none",
"clipboard_handling": "copy_to_clipboard",
"keyboard_implementation": "handy_keys",
"overlay_position": "bottom",
"overlay_style": "live"
} }
```
Repo-side merge patch + apply procedure:
`dotfiles/zz-blankpad/.local/share/com.pais.handy/` (README.md).
---
## Command-Line Interface
### Startup Flags
```bash
handy --start-hidden # Start without showing the UI
handy --no-tray # Hide the system tray icon
handy --debug # Enable debug mode (Ctrl+Shift+D)
```
### Remote Control
Send commands to a running instance:
```bash
handy --toggle-transcription # Toggle transcription on/off
handy --toggle-post-process # Toggle post-processing on/off
handy --cancel # Cancel the current transcription
```
---
## Wayland Integration
### Global Shortcuts
On Wayland, global shortcuts **must** be defined in the compositor (niri), not in Handy itself.
**File:** `~/.config/niri/cfg/keybinds.kdl`
```kdl
// Live (cfg/keybinds.kdl):
Mod+Shift+Space hotkey-overlay-title="Handy: toggle post-process recording" allow-when-locked=true { spawn-sh "handy --toggle-post-process"; }
// Transcription itself stays on Handy's own ctrl+space (keyboard_implementation="handy_keys").
```
### Text Input
**Requirement:** `wtype` or `dotool` for text input (NOT installed on blankpad).
**Current config:** `paste_method=none` + manual `Ctrl+V` paste.
### Overlay
**Default:** Disabled on Linux (can steal focus and break paste).
**Live config:** `mode=bottom`, `live=true`.
---
## Troubleshooting
### WebKit Renderer Issue
**Symptom:** Blank or corrupted rendering.
**Fix:**
```bash
export WEBKIT_DISABLE_DMABUF_RENDERER=1
handy
```
### SIGUSR1 Crash
**Trap:** The upstream SIGUSR1 handler was **REMOVED** (WebKitGTK now owns it).
```bash
# WRONG (may crash the app):
pkill -USR1 handy
# CORRECT (toggle transcription):
pkill -USR2 -n handy
# or use CLI:
handy --toggle-transcription
```
### Debug Mode
```bash
handy --debug
# or press Ctrl+Shift+D in a running instance
```
---
## Dependencies
| Package | Purpose |
|---------|---------|
| `handy` | Main application |
| `tauri-apps` | Tauri v2 runtime |
| `webkit2gtk` | WebKitGTK rendering |
| `gtk-layer-shell` | Wayland layer-shell support |
| `vulkan` | GPU-accelerated STT |
| `ffmpeg` | Audio processing |
---
## Fleet LLM Endpoint
**Post-processing pipeline:** `handy` → `http://192.168.0.143:7771/v1`, model id
`frank-secondary` (the served id; harness callsign for the same unit is
`frank-zealot` — same llama.cpp server, also the fleet compaction endpoint).
**Endpoint configuration:**
- **Provider:** `custom` (the one preset with `allow_base_url_edit: true`)
- **Base URL:** `http://192.168.0.143:7771/v1`
- **Model:** `frank-secondary`
- **Prompt:** `default_improve_transcriptions`
### Known issues
**Thinking-model empty content (2026-10-09, open).** `frank-secondary` is a
thinking model. Default chat completions return `content: ""` with the answer
trapped in `reasoning_content` — Handy reads `content`, so post-processed
transcripts arrive empty. Verified per-request cure:
`"chat_template_kwargs": {"enable_thinking": false}` (probe below returns
cleaned text in `content`). Handy has no field to send extra body keys, and
the cure CANNOT be set server-global on :7771 without degrading fleet
compaction (same server). Proposed fix (not yet implemented): a dedicated
no-think unit/port for Handy traffic, e.g. llama-server on `:7772` with
thinking disabled at the template level, then repoint `custom.base_url`.
```bash
curl -s http://192.168.0.143:7771/v1/chat/completions -d '{
"model": "frank-secondary",
"messages": [{"role":"user","content":"Clean this transcript: twenty five items cost ten dollars um ok."}],
"max_tokens": 200,
"chat_template_kwargs": {"enable_thinking": false}}'
```
See [Noctalia Shell](/tools/noctalia-shell.md) for IPC integration patterns.
---
## See Also
- [noctalia-shell.md](/tools/noctalia-shell.md) — Desktop environment integration
- [niri](niri/index.md) — Compositor configuration
- [`handy --help`](https://github.com/cjpais/Handy) — CLI reference