Hammerspoon
Treat Hammerspoon work as two connected systems:
- the running macOS automation runtime
- the persistent Lua config on disk
Prefer truth from the running instance first, then official docs, then upstream source/Spoons, then local config, then ecosystem corroboration.
Entry point
uv run --script <skill-dir>/scripts/cli.py ...
Set <skill-dir> to this skill directory. Do not rely on shell sourcing, executable bits, or shebang dispatch.
Evidence order
- Runtime introspection via CLI (
status,eval,windows,apps,screens,hotkeys,spoons,config) - Live official docs (
docs search,docs module,docs api) - Live upstream source and Spoons (
source search,spoons search,spoons source) - Local config files on disk
- Ecosystem corroboration (Grep.app, GitHub code search) only after official/runtime sources
Avoid cloning Hammerspoon repos unless explicitly needed. Prefer the cached live fetches.
Workflow
- Inspect runtime state or live docs first
- Inspect the relevant config files only as needed
- Pick the right persistence target
- Preview the intended change if a live preview is useful
- Apply the persistent edit
- Apply live when safe (reload config, evaluate Lua)
- Verify both runtime state and file state
- Call out Accessibility permission, MJConfigFile, or restart requirements explicitly
Default pattern: inspect → preview → apply → verify.
Output discipline
- If the user asked for a report, note, patch file, or any other artifact at a specific path, create it before finalizing
- Do not stop at an acknowledgement, plan, or partial progress update when the task asked for a concrete deliverable
- After writing the requested artifact, summarize the result briefly
Runtime introspection
Default to uv run --script <skill-dir>/scripts/cli.py for Hammerspoon IPC. It wraps hs calls with quoting safety and structured output.
Prefer helper subcommands before custom Lua when they fit the task:
status: Hammerspoon runtime health, version, config directorydoctor: status plus Accessibility/MJConfigFile/CLI setup checkseval: execute Lua throughhsand print the resulteval-file: evaluate a Lua file safely without shell quotingreload: triggerhs.reload()windows: list all windows as JSON-safe summariesapps: list running applicationsscreens: list displayshotkeys: list registered hotkeysspoons: list loaded Spoonsconfig: show config directory path
Use raw hs CLI only when:
- debugging the helper itself
- the helper is unavailable
- a truly tiny one-off probe is simpler and there is no quoting risk
For reusable or multiline queries, prefer a temp Lua file plus eval-file.
Live docs lookup
uv run --script <skill-dir>/scripts/cli.py docs search <query>
uv run --script <skill-dir>/scripts/cli.py docs module <hs.module>
uv run --script <skill-dir>/scripts/cli.py docs api <symbol>
uv run --script <skill-dir>/scripts/cli.py docs refresh [--if-needed]
These fetch official Hammerspoon API docs from hammerspoon.org/docs/ and cache them locally. Use --json for structured output with source_url, signature, excerpt, and line references.
Agents should navigate search results by exact symbols, module names, and source URLs.
Live source and Spoons
uv run --script <skill-dir>/scripts/cli.py source search <pattern>
uv run --script <skill-dir>/scripts/cli.py source fetch [--if-needed]
uv run --script <skill-dir>/scripts/cli.py spoon search <query>
uv run --script <skill-dir>/scripts/cli.py spoon source <name>
Source searches query the official Hammerspoon and Spoons GitHub repositories. Fetches are cached and refreshed only when stale or explicitly requested.
Lua quality
uv run --script <skill-dir>/scripts/cli.py lint <path>
uv run --script <skill-dir>/scripts/cli.py fmt --check <path>
uv run --script <skill-dir>/scripts/cli.py fmt --write <path>
uv run --script <skill-dir>/scripts/cli.py test <path>
uv run --script <skill-dir>/scripts/cli.py annotations status
uv run --script <skill-dir>/scripts/cli.py lsp-config print
These discover tools on PATH (luacheck, stylua, busted, lua-language-server). Missing tools return clear error messages with install guidance; they never silently succeed.
lint: runsluacheckwith Hammerspoon globals (hs,spoon)fmt: runsstylua, never formats implicitlytest: runsbustedfor pure Lua testsannotations: reports whether Hammerspoon EmmyLua annotations appear generatedlsp-config: prints a suggested.luarc.jsonfor Lua language server
Safe live-change pattern
- Make the intended file change first for persistent behavior
- Then apply live only to preview or verify the persistent change
- Prefer precise reloads:
uv run --script <skill-dir>/scripts/cli.py reloaduv run --script <skill-dir>/scripts/cli.py eval-file <temp>.lua
- Call out whether a change requires Accessibility permission grants, an
MJConfigFileupdate, or a full Hammerspoon restart - If you test an ephemeral tweak before writing it, label it clearly as a preview and persist it before finishing
Guardrails
- Do not trust memory alone for Hammerspoon APIs; inspect runtime/docs/source
- Do not assume a specific config layout, Spoon set, hotkey scheme, or config location; inspect first
- Do not auto-install the
hsCLI or Hammerspoon itself without explicit user request - Do not reload Hammerspoon after read-only inspection commands
- Do not mutate the user's
init.luaunless the task explicitly asks for persistent config changes - Do not format, lint, or fix config files unless the task asks
- Prefer
uv run --script <skill-dir>/scripts/cli.pyover rawhscalls - Be explicit about risk when evaluating Lua in a running Hammerspoon
Resources
scripts/cli.py: public dispatcherscripts/hsctl.py: internal engine for runtime, docs, source, and Lua tooling