Skip to main content

Hotkeys and launchers

Hammerspoon​

Key pieces:

In init.lua, Hyper is configured as ctrl+alt+cmd.

This setup pulls the EmmyLua.spoon via externals:

Workflow:

  • Edit modules under home/dot_hammerspoon/.
  • Reload Hammerspoon config from the app menu.
  • Verify configured keybindings in init.lua and module files.

Shared search panel​

Double-tap Command to toggle the Hammerspoon action panel. Type an application name to launch or focus it, or use one of these explicit search prefixes:

InputResultEnter
SafariInstalled applications, exact names before partial matchesLaunch/focus
tr en спасибоOpenRouter translation into the target languageCopy translation
gh repo:owner/name queryGitHub issues and PRs with repository, number and statusOpen in browser
emoji woman technologistEmoji matched by name, alias and tagsCopy Unicode
sym right arrowCommon arrows, keyboard, math, currency and punctuation symbolsCopy Unicode
ud word or phraseUrban Dictionary suggestions with inline definitionsOpen definition

The empty panel lists search commands. Select one to insert its prefix. A keyword only routes to its provider when followed by a space; otherwise it also searches apps. Move with Ctrl-J/Ctrl-K, the arrow keys or Ctrl-N/Ctrl-P (selection wraps), press Enter to activate, and Escape or Ctrl-C to dismiss; clicking elsewhere also dismisses. One Hammerspoon key tap owns these keys by physical position (they work under any input layout) and consumes them only while the panel is visible, so other apps never lose them. The chooser's own key handling does nothing while another app is active (the usual case after the gesture). Activating a status-only row dismisses without taking an action. Copy actions do not paste into the previous app. App and browser launches run in background tasks, so activation releases the panel without waiting for the application to start. Launch failures show an alert.

Application discovery refreshes on each opening. It scans /Applications, /System/Applications, ~/Applications, CoreServices' Applications folder and Finder, including nested folders and symlinks without descending into app bundles. Paths distinguish duplicate names. Matching ignores case and diacritics, including composed/decomposed Unicode names. It does not depend on Spotlight indexing.

GitHub uses the installed gh CLI and its existing authentication (gh auth login if needed); no token belongs in the Hammerspoon config. Queries support GitHub's repo:owner/name, is:pr, is:issue, and is:open filters. The first 30 matches are shown. Query changes and dismissal terminate pending CLI searches. Private temporary captures are removed after completion or cancellation; reloading or quitting Hammerspoon stops outstanding writers and removes their captures too.

Emoji search runs offline against 1,870 entries from GitHub gemoji. The vendored Lua data retains its MIT license. Symbol search is a curated local set; both support multiple words and copy the exact Unicode sequence. Signed aliases such as emoji -1 remain literal. Names and queries share the app search's Unicode case/diacritic folding.

Translation calls OpenRouter's chat API directly with deepseek/deepseek-v4.1-flash-1017, the model ,q uses, with reasoning off and no tools, so the text being translated cannot steer an agent. tr <target> <text> lets the model infer the source; tr <source>><target> <text> states it. Only these codes are offered: en, ru, nl, es, fr, hy, de, it, tr, az, zh (Simplified), zh-Hant (Traditional), ja, ko, la (Latin), pt, ar, hi, uk, pl, fa (Persian), ka (Georgian), el (Greek), he, sv, id, vi; others are rejected without a request. Text is sent to OpenRouter and its upstream provider.

Results show the translation in its native script, with a Latin-script transliteration beneath it for an English reader. Text already in Latin script is not repeated. Enter copies only the translation, preserving its line breaks. The model returns both fields together; malformed or missing fields produce an error row.

The API key comes from pass show openrouter/api/token on the first translation and stays only in Hammerspoon's memory until reload; a 401 response drops it so the next query reads pass again. A missing key, network failure, API error or cut-off reply (finish_reason other than stop) appears as a non-selectable row, never as a copyable partial translation. Results from superseded queries are ignored. If the GPG passphrase is not cached, the first lookup opens pinentry, which takes focus and closes the panel; reopen it and retry once the passphrase is entered. Before transliteration was added, a native check found all 13 non-English codes and Armenian/Azerbaijani→English were correct; the first translation (including the pass read) took about 1.6 s, later ones about 0.8 s median. Requests ask OpenRouter to route by throughput: in 72 measured requests that stayed under 1 s (0.44 s median), while default and latency-sorted routing each produced ~10 s outliers. A typical translation costs about $0.00004.

The gesture accepts either Command key and uses the system double-click interval. Both presses must be short, with no intervening typing, other modifiers, mouse clicks or scrolling. Normal Command shortcuts keep propagating and do not open the panel. Secure Input can prevent macOS from delivering events to Hammerspoon. Hyper+U is no longer bound; existing window-management and Hyper+R reload shortcuts are unchanged. Spotlight keeps its own shortcut and search surface.

The shared chooser in readonly_search.lua owns keyword routing, debounce, cancellation, and URL/copy/app activation. Its providers table maps keywords to modules exposing title, hint and search(term, publish, is_current). Cheap local lookups set immediate = true; translation retains debounce to avoid a paid request per keystroke. A search may return a cancellation function. Providers must ignore responses and avoid follow-up work once is_current() is false. The modifier gesture lives in readonly_double_command.lua.

Urban Dictionary's provider is readonly_urban_dictionary.lua. It fetches live autocomplete terms and displays a definition excerpt below each. It chooses the highest thumbs_up among returned matching definitions, keeping API order for ties, including zero vote counts. It does not fetch additional definition pages; long text truncates to the native row width. Missing definitions and network errors appear inline. Late results cannot replace the root panel or a newer search.

No custom signed app, Apple developer account or additional launcher is needed. Alfred's package declaration and repository preferences have been removed.

Deploy only the Hammerspoon files, then reload with Hyper+R or the app menu:

chezmoi apply --no-tty --exclude scripts ~/.hammerspoon/{init,search,double_command,urban_dictionary,github,characters,emoji,applications,search_text,translation}.lua

Karabiner​

Workflow:

  • Edit karabiner.json.
  • Import/reload through Karabiner-Elements.
  • Validate by testing the modified key mapping directly.