Skip to content

Replace the Hammerspoon client with a native Swift agent #2

Description

@STRML

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions