Build Keyboard Maestro
A background agent where a trigger runs a sequence of deterministic actions with conditions and loops, every run is logged step by step, a macro can be stopped mid-run, and nothing executes without you having seen what it will do.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree if you own one
Why Every action surface here is a platform API.
Get it Apple menu, then About This Mac.
Verify
About This Mac reports macOS 14 or higher - installfree
Why Builds and signs the agent.
Get it Mac App Store or developer.apple.com/xcode. open ↗
Verify
xcodebuild -version prints 16 or higher - decidefree
Why Keystrokes, menu clicks and window control all require it, and it lets the engine do anything you could do.
Get it System Settings, then Privacy and Security, then Accessibility. Re-granted during development whenever the binary changes.
Verify
A keystroke action reaches another application - have readyfree
Why Phase 3 needs variety: one that responds well to Accessibility, one that is slow to activate, and one that ignores window commands.
Get it Anything already installed. Include at least one slow starter, since activation timing is the common bug.
Verify
Each is exercised by a macro and each case is handled - account99 USD per year
Why Product phase only. Without it, no notarization and a warning on other machines.
Get it Enrol at developer.apple.com/programs. open ↗
Verify
A Developer ID Application certificate appears in Xcode settings
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- macros(id, name, group_id, enabled, notes) - triggers(id, macro_id, kind, spec_json) · kind is hotkey, app_launch, app_quit, folder_change, time or manual - actions(id, macro_id, parent_id, ordinal, kind, params_json) · a tree, because conditions and loops contain other actions - variables(macro_id, name, value, scope) · scope is run or persistent - runs(id, macro_id, started_at, finished_at, state, trigger_kind), run_steps(run_id, action_id, at, duration_ms, result, error) - The run_steps table is the debugger. A macro engine without one is a black box that occasionally does something surprising.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
MACROS_PATH | required | ~/Library/Application Support/Macros/macros.json | Macro definitions. Readable and version controllable. |
RUN_LOG_DB | required | ~/Library/Application Support/Macros/runs.db | Step-by-step run history. This is the debugger. |
MAX_LOOP_ITERATIONS | required | 1000 | Hard cap on any loop. Exceeding it stops the macro and logs why. |
MAX_RUN_SECONDS | required | 120 | Hard cap on a single run. A macro that exceeds it is halted. |
SHELL_TIMEOUT_MS | required | 10000 | Per shell action, killing the process group on expiry. |
RUN_LOG_RETENTION_DAYS | required | 30 | How long run history is kept. |
The build, in order
An interpreter you can watch
A macro of three actions runs, and you can see each step complete.
Parent and ordinal, because conditions and loops contain children. Flat lists cannot express control flow.
Files
Action.swiftWalk the tree, log every step's start, duration and result. The log is written as it goes, not at the end.
Files
Interpreter.swiftCheck for cancellation between actions and record the run as cancelled. A macro you cannot stop is a macro you will not run.
Files
Interpreter.swift
done when · tick each as it passeswatch out- Building the action library before the execution model means every action is written against an interpreter that later changes shape. Get run logging, stepping and cancellation right with three trivial actions, then add the useful ones against a stable contract.
Control flow
Macros can branch and repeat without becoming unstoppable.
Variables, frontmost application, file existence and window state. Each is a small evaluator returning a boolean.
Files
Conditions.swiftWith children, evaluated per iteration. The interpreter already walks trees, so this is a node kind.
Files
Loops.swiftBoth, with the reason recorded in the run log. Test by writing a deliberately infinite loop.
Files
Interpreter.swift
done when · tick each as it passeswatch out- A while loop whose condition never becomes false will run until the machine is restarted, and it will do so while holding whatever the loop was doing. A hard iteration cap and a run timeout are not defensive extras; they are what makes loops safe to offer at all.
Actions that do something
Macros can act on applications, text, files and the clipboard.
Built into the activation action rather than left to the user. This is the most common macro bug and it is fixable once.
Files
Actions/Activate.swiftKeystrokes, menu clicks, shell, clipboard, window and open. Each independent behind one interface.
Files
Actions/A shell action's output becomes a variable a later action can use. This is what makes sequences useful rather than merely sequential.
Files
Actions/Shell.swift
done when · tick each as it passeswatch out- Sending keystrokes to an application that has not finished activating delivers them to whatever was frontmost a moment ago, which at best does nothing and at worst types into the wrong document. Wait for activation to complete before sending, and make that wait part of the action rather than a pause the user has to remember to add.
Triggers
Macros fire on their own, and two macros never fight over one trigger.
Hotkeys through the Carbon registration, application events through workspace notifications, folder changes through FSEvents.
Files
Triggers/Size stable across two checks. The same rule any folder watcher needs.
Files
Triggers/Folder.swiftCompare against enabled macros and refuse rather than resolving by order.
Files
Conflicts.swift
done when · tick each as it passeswatch out- A folder change trigger firing on an incomplete file copy runs the macro against a partial file, which for anything that moves or processes files is worse than not running at all. Reuse a settle check before firing, exactly as a folder watcher would.
Sharing macros safely
A macro can be exported and imported, and importing one is an informed decision.
A schema version, refused if newer than this build understands.
Files
Export.swiftEvery action listed, shell and keystroke actions individually approved, applied atomically.
Files
ImportPreview.swiftThe interpreter logs without executing side effects. Verify a file action changed nothing.
Files
Interpreter.swift
done when · tick each as it passeswatch out- An imported macro containing a shell action is arbitrary code that will run on a trigger you may not have read carefully. List every shell command in the import preview and require individual approval, because macros are exactly the kind of file people download and run.
Signing, notarization and updatesproduct builder
Others can install it and understand what they are granting.
Hardened runtime, Developer ID, notarytool, stapler.
terminalxcrun notarytool submit Macros.zip --keychain-profile AC --wait
EdDSA key pair, private half outside the repository, appcast over HTTPS.
Files
appcast.xmlAccessibility lets this run anything you could. Say so, and describe the caps and dry run.
Files
README.md
done when · tick each as it passeswatch out- Signing invalidates the Accessibility grant testers gave your unsigned build and macOS will not prompt again. Put the removal steps in the release notes.
That is the whole plan for Keyboard Maestro. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- The action library accumulated over two decades.
- Image and screen recognition actions.
- Guaranteed compatibility across macOS releases.
- The community macro library.
- deep macOS automation, action library, reliability, and long-term compatibility
- years of OS-version edge cases
- large extension ecosystem
- cross-device sync
- commercial support and rapid compatibility fixes
- A macro palette triggered by a hotkey, listing macros for the frontmost application
- Clipboard and text token expansion inside action parameters
- Recording a sequence of actions to seed a macro you then edit
Need the files? The project pack on the verdict page hands your agent the whole brief · more productivity utilities.