install-client.sh:573 links ~/.hammerspoon/init.lua to this repo's client/init.lua. Hammerspoon has exactly one config file, so installing hark claims it. Anyone already using Hammerspoon has to merge by hand, and the installer hard-stops when it finds a real file there (install-client.sh:565), which is the right call but tells you how invasive the arrangement is.
The permission story matters more. Accessibility gets granted to Hammerspoon, not to hark. That is a general-purpose scriptable Lua runtime holding a grant that can observe every keystroke, and its config is a symlink into a git repo, so git pull changes what that grant covers without re-prompting. A single-purpose bundle asks for the same permission with far less behind it.
What it removes
What it has to keep doing
Working from client/init.lua, none of this is optional:
- global Ctrl+Alt+Space on press and release, with the guard against a spurious double key-down
- the recording indicator, and clearing it from the process-exit callback rather than on key-up
- POST the WAV with
X-Hark-Key and Content-Type: audio/wav
- the error-to-alert mapping for 401, 415, 400, 503, and a negative/connection status, each naming its likely cause
- set the clipboard and deliberately do not restore it (see the comment above
hs.pasteboard.setContents)
- synthesize Cmd+V, never Return
- the startup microphone probe that writes
~/.hammerspoon/.hark-mic-status. install-client.sh's check_mic_permission reads that file because it is the only way to learn whether the responsible app has mic permission, rather than the terminal
- the startup Accessibility check, since the hotkey silently never fires without it
- append rec's exit code and stderr to a persisted log
Rough shape
CGEventTap or NSEvent.addGlobalMonitorForEvents for the hotkey, the existing rec.swift capture path, URLSession for the POST, NSPasteboard plus CGEvent for the paste. Call it 300 lines of Swift replacing 505 of Lua plus a GUI dependency.
The hard part is not the event handling. It is TCC identity: an unsigned bare binary can have its grants invalidated when the binary changes, so this likely wants a real .app bundle and a signing story. That decision comes before any code.
Open questions
.app bundle versus a plain launchd agent binary, and what each does to the two permission prompts
- signing: ad-hoc, Developer ID, or unsigned with documented re-granting after every rebuild
- upgrade path for existing installs that already have the symlink and the Hammerspoon grants
- where client config lives once
hark-config.lua is gone
- whether the mic-status file stays the
--doctor contract or gets replaced by something the agent answers directly
install-client.sh:573links~/.hammerspoon/init.luato this repo'sclient/init.lua. Hammerspoon has exactly one config file, so installing hark claims it. Anyone already using Hammerspoon has to merge by hand, and the installer hard-stops when it finds a real file there (install-client.sh:565), which is the right call but tells you how invasive the arrangement is.The permission story matters more. Accessibility gets granted to Hammerspoon, not to hark. That is a general-purpose scriptable Lua runtime holding a grant that can observe every keystroke, and its config is a symlink into a git repo, so
git pullchanges what that grant covers without re-prompting. A single-purpose bundle asks for the same permission with far less behind it.What it removes
hammerspooncask~/.hammerspoon/init.luatakeoverclient/init.luatests/test_client_record.lua, and theluainstall step added to CI in Add CI, and fix a false PASS in install-server.sh --doctor #1What it has to keep doing
Working from
client/init.lua, none of this is optional:X-Hark-KeyandContent-Type: audio/wavhs.pasteboard.setContents)~/.hammerspoon/.hark-mic-status.install-client.sh'scheck_mic_permissionreads that file because it is the only way to learn whether the responsible app has mic permission, rather than the terminalRough shape
CGEventTaporNSEvent.addGlobalMonitorForEventsfor the hotkey, the existingrec.swiftcapture path,URLSessionfor the POST,NSPasteboardplusCGEventfor the paste. Call it 300 lines of Swift replacing 505 of Lua plus a GUI dependency.The hard part is not the event handling. It is TCC identity: an unsigned bare binary can have its grants invalidated when the binary changes, so this likely wants a real
.appbundle and a signing story. That decision comes before any code.Open questions
.appbundle versus a plain launchd agent binary, and what each does to the two permission promptshark-config.luais gone--doctorcontract or gets replaced by something the agent answers directly