LLM-built macros with native KM actions -- a working example, and the method that makes it reliable
I've been encouraging this community to take LLM-assisted macro building seriously for a while, and I think the pushback in this thread is fair on one point: most of what gets shown is an LLM writing an AppleScript and a one-action macro wrapping it. That's useful, but it isn't what @Nige_S and @DocOck are asking to see. So here is the other thing -- a macro built from native KM actions, generated by an LLM (Claude, in my case), with the method that makes it come out right.
The attached example
Back At My Desk.kmmacros -- when I come back to my Mac after time away, one hotkey (⌃⌥⌘B) asks what the time held and logs it. Six native action types, no scripts anywhere:
-
Prompt With List (choices, free-typing allowed) → Local__What
-
If/Then/Else gating on a Variable condition (Local__What is not empty)
-
Set Variable to Text composing a timestamped line with %ICUDateTime%
-
Write File appending to a log
-
Notification confirming, and Play Sound (Tink)
-
Cancel path: the Else branch notifies that nothing was logged
Small on purpose -- it's a demonstration that the structure comes out right: nested actions inside Then/Else branches, a Variable condition with the correct keys, a hotkey trigger with the right modifier math. Import note, learned the embarrassing way while writing this post: the imported macro group arrives disabled, and the macro inside it can show as enabled while the group checkbox -- over in the Groups column -- is off. The hotkey then does nothing and Engine.log stays empty, which looks exactly like a broken macro. Enable the GROUP, then the macro. Two more first-run notes: Prompt With List takes Return or a double-click to accept (a single click only highlights), and the Write File append action creates the log file on first run -- no need to pre-create it.
This isn't a toy category for me. My daily setup runs about 25 generated macros across 5 palettes -- window management, folder-listing pipelines, clipboard workflows. Some of those do carry AppleScript or shell payloads where a script is honestly the right tool, and I won't pretend otherwise. But the skeleton -- groups, triggers, prompts, conditions, branching -- is native actions, generated, and editable in the KM editor like anything built by hand, which is my personal test for whether it's really a macro.
The method: never let the LLM guess plist keys
@Nige_S asked the right question earlier: where does the model get the action format from? The answer that works is: from your own library. KM's plist format isn't fully documented, and an LLM that guesses keys produces files KM rejects or half-imports. So the standing rule I use is: every action type gets its keys from a real specimen before it's ever generated.
Your own Keyboard Maestro Macros.plist is the best specimen file there is -- mine is 51 MB of ground truth. This is the extractor I have the LLM run first (read-only):
import plistlib
with open("/tmp/km.xml", "rb") as f: # plutil -convert xml1 -o /tmp/km.xml "~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Macros.plist"
data = plistlib.load(f)
found = {}
def walk(x):
if isinstance(x, dict):
t = x.get("MacroActionType")
if isinstance(t, str) and t not in found:
found[t] = x
for v in x.values(): walk(v)
elif isinstance(x, list):
for v in x: walk(v)
walk(data)
print(sorted(found)) # every action type you have ever used
# then print found["IfThenElse"] etc. and hand THOSE keys to the LLM as the template
Point the model at the specimens it needs and the "unknown format" problem essentially disappears. (Work on a copy; never write the live plist.)
Tips, extending what's already in this thread
Ben's list above is good and matches my experience -- especially the MacroGroup wrapper and fresh UUIDs. Adding what my mistakes file has accumulated:
-
Generate with plistlib (or any real plist serializer), never hand-written XML. Round-trip read the file back before importing; a file that won't round-trip won't import.
-
Specimen-first, as above. When an action type has never been used in your library, build it once by hand in KM, then Edit > Copy as XML -- that's your template forever.
-
Modifier math is addition on the Carbon masks (⌘256 ⇧512 ⌥2048 ⌃4096); ⌃⌥⌘ = 6400. Wrong math fails silently as the wrong hotkey.
-
Test any embedded AppleScript standalone with osascript before it goes inside an action -- inside a macro it fails silently and only Engine.log knows.
-
After import, verify from the outside: osascript -e 'tell application "Keyboard Maestro" to count every macro group' and read the macro's XML back. Structural verification (KM accepted it) and runtime verification (it actually ran) are different claims; keep them separate.
-
Keep the mistakes file. Every failure gets written down where the LLM reads it next time. Mine started as three lines; it's now the difference between "mostly works" and "works."
The barrier this removes isn't knowledge of KM -- everyone here has that. It's the half-hour of clicking between "I know exactly what I want" and having it. The ideas that never got built because of that half-hour are the real cost, and that's what's changed for me.
(Attachment: Back At My Desk.kmmacros, 4 KB. Import lands disabled by design.)
Back At My Desk.kmmacros (5.5 KB)