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

7.0 KiB

type, title, description, resource, tags, timestamp, verified
type title description resource tags timestamp verified
reference Handy Offline speech-to-text transcription with WebKitGTK backend and Tauri v2 UI. https://github.com/cjpais/Handy
handy
stt
speech-to-text
transcription
wayland
tauri
2026-10-09 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:

{ "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

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:

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

// 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:

export WEBKIT_DISABLE_DMABUF_RENDERER=1
handy

SIGUSR1 Crash

Trap: The upstream SIGUSR1 handler was REMOVED (WebKitGTK now owns it).

# WRONG (may crash the app):
pkill -USR1 handy

# CORRECT (toggle transcription):
pkill -USR2 -n handy
# or use CLI:
handy --toggle-transcription

Debug Mode

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.

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 for IPC integration patterns.


See Also