Vibecode Keyboard Maestro
track this build5 phases, 15 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Keyboard Maestro, create user-approved desktop macros from triggers and deterministic actions. The hard boundary is deep macos automation, action library, reliability, and long-term compatibility, plus os integration polish and maintenance.
You are building a lean indie version of Keyboard Maestro. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Keyboard Maestro · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 and SwiftUI in Xcode | the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces | | App type | A background agent with LSUIElement true | it waits for triggers | | Execution | An interpreter over an action tree, not compiled code | an interpreter can be paused, stepped and logged; generated code cannot | | Storage | Macros as JSON in Application Support, versioned | macros are documents people share, and a shared document must be readable before it is trusted | | Logging | Every action's inputs, outputs and duration | a macro that did the wrong thing is only debuggable if you can see what each step actually received | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac running macOS 14 or newer** · free 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 - [ ] **Xcode 16 or newer** · free - Why: Builds and signs the agent. - Get it: Mac App Store or developer.apple.com/xcode. - Verify: xcodebuild -version prints 16 or higher - [ ] **Willingness to grant Accessibility to your own build** · free - 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 - [ ] **Three applications with different automation behaviour** · free - 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 - [ ] **Apple Developer Program membership** (optional) · 99 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. - Verify: A Developer ID Application certificate appears in Xcode settings ## Quick start ```sh xcrun notarytool submit Macros.zip --keychain-profile AC --wait ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Keyboard Maestro, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Keyboard Maestro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a macOS macro engine: triggers fire action sequences with conditions, loops and a visible execution log to replace Keyboard Maestro. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Language: Swift 6 and SwiftUI in Xcode · the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces - App type: A background agent with LSUIElement true · it waits for triggers - Execution: An interpreter over an action tree, not compiled code · an interpreter can be paused, stepped and logged; generated code cannot - Storage: Macros as JSON in Application Support, versioned · macros are documents people share, and a shared document must be readable before it is trusted - Logging: Every action's inputs, outputs and duration · a macro that did the wrong thing is only debuggable if you can see what each step actually received ### Data model (create this before Phase 1) - 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. ### Phase 1 · An interpreter you can watch Build: An action tree with a small set of kinds to begin: pause, notify and set variable. An interpreter walking the tree, logging every step with its duration and result. A manual trigger and a stop control that halts a running macro between actions. Trap: 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. Done when: A three action macro runs and each step appears in the log with its duration. Stopping a running macro halts it between actions and records the run as cancelled. A macro that errors records which action failed and why, and does not continue past it. Do not build yet: No useful actions and no automatic triggers. The engine first. ### Phase 2 · Control flow Build: If and else with conditions on variables, the frontmost application, a file's existence and window state. Repeat with a count and while with a condition. Enforce a maximum iteration count and a maximum run duration so a bad condition cannot loop forever. Trap: 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. Done when: A condition on the frontmost application branches correctly. A repeat runs its count exactly. A while loop with a never-false condition stops at the iteration cap and records why. A macro exceeding the run timeout is halted and logged. Do not build yet: No real actions and no automatic triggers yet. ### Phase 3 · Actions that do something Build: Add the useful action kinds: send keystrokes, click a menu item through the Accessibility API, run a shell command, manipulate the clipboard, move and resize windows, and open files or applications. Each records its inputs and outputs in the run log. Trap: 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. Done when: A macro activates an application, waits for it, and sends a keystroke that arrives in that application. A menu item is clicked by name through Accessibility. A shell action's output is captured into a variable and appears in the run log. A window action moves a window belonging to another application. Do not build yet: No automatic triggers yet. ### Phase 4 · Triggers Build: Hotkey, application launch and quit, folder change and scheduled triggers. Detect conflicts when two enabled macros claim the same trigger, and refuse to save rather than letting one silently win. Trap: 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. Done when: A hotkey fires its macro from any application. An application launch trigger fires once per launch rather than repeatedly. A folder trigger fires after a copy completes rather than during it. Saving a macro whose trigger collides with an enabled macro is refused with both named. Do not build yet: No sharing or distribution yet. ### Phase 5 · Sharing macros safely Build: Export as versioned JSON. Import with a preview listing every action, with shell commands and keystroke actions called out individually and approved separately. A dry run mode that logs what a macro would do without performing side effects. Trap: 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. Done when: An exported macro imports into a fresh installation and behaves identically. An import preview lists every shell command and each requires approval. A dry run logs the intended actions and performs no side effect, verified by checking that a file action changed nothing. An unknown action kind is refused rather than partially imported. Do not build yet: Signing and distribution come last. ### Phase 6 · Signing, notarization and updates Build: Hardened runtime, Developer ID signing, notarization, stapling and Sparkle updates. State plainly what Accessibility permits, since a macro engine with that permission can do essentially anything the user can. Trap: Signing invalidates the Accessibility grant testers gave your unsigned build and macOS will not prompt again. Put the removal steps in the release notes. Done when: A notarized build opens on a fresh Mac with no Gatekeeper warning and requests Accessibility once. An appcast update installs and relaunches. The README states what the permission allows and what the dry run and iteration caps protect against. Do not build yet: Nothing after this. ### Out of scope (and why) - The action library of the commercial product, which has accumulated hundreds of specific actions over two decades and is most of what people are paying for. - Image and screen recognition actions, which are a different kind of engineering and are genuinely useful. - Compatibility across macOS releases without maintenance, since Accessibility and the scripting surfaces both shift. - The community macro library, which is other people's work. ### README must contain - That Accessibility lets this engine do anything the user could, and what the caps and dry run protect against - That every run is logged step by step, and where the log lives - That loops have an iteration cap and runs have a timeout, both configurable - That imported macros can contain shell commands and each is approved individually - That a trigger collision is refused at save rather than resolved silently ===== AGENTS.md ===== # Agent instructions · Keyboard Maestro indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift 6 and SwiftUI in Xcode, A background agent with LSUIElement true, An interpreter over an action tree, not compiled code, Macros as JSON in Application Support, versioned, Every action's inputs, outputs and duration. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An interpreter you can watch A macro of three actions runs, and you can see each step complete. ### Steps 1. Model actions as a tree Parent and ordinal, because conditions and loops contain children. Flat lists cannot express control flow. Files: `Action.swift` 2. Write the interpreter with logging Walk the tree, log every step's start, duration and result. The log is written as it goes, not at the end. Files: `Interpreter.swift` 3. Support cancellation Check 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 - [ ] Each step appears in the log with its duration - [ ] Stopping halts between actions and records cancellation - [ ] An error names the failing action and stops the run - [ ] The log is written during the run rather than after ### Watch 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. ## Phase 2 · Control flow Macros can branch and repeat without becoming unstoppable. ### Steps 1. Add conditions Variables, frontmost application, file existence and window state. Each is a small evaluator returning a boolean. Files: `Conditions.swift` 2. Add repeat and while With children, evaluated per iteration. The interpreter already walks trees, so this is a node kind. Files: `Loops.swift` 3. Cap iterations and duration Both, with the reason recorded in the run log. Test by writing a deliberately infinite loop. Files: `Interpreter.swift` ### Done when - [ ] A frontmost application condition branches correctly - [ ] A repeat runs exactly its count - [ ] An infinite while stops at the cap with the reason logged - [ ] A long macro is halted by the run timeout ### Watch 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. ## Phase 3 · Actions that do something Macros can act on applications, text, files and the clipboard. ### Steps 1. Wait for activation before acting 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.swift` 2. Add the core action kinds Keystrokes, menu clicks, shell, clipboard, window and open. Each independent behind one interface. Files: `Actions/` 3. Capture outputs into variables 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 - [ ] A keystroke arrives in the application the macro activated - [ ] A menu item is clicked by name - [ ] Shell output is captured into a variable and logged - [ ] A window action moves another application's window ### Watch 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. ## Phase 4 · Triggers Macros fire on their own, and two macros never fight over one trigger. ### Steps 1. Register triggers Hotkeys through the Carbon registration, application events through workspace notifications, folder changes through FSEvents. Files: `Triggers/` 2. Settle before firing on file changes Size stable across two checks. The same rule any folder watcher needs. Files: `Triggers/Folder.swift` 3. Detect trigger conflicts at save Compare against enabled macros and refuse rather than resolving by order. Files: `Conflicts.swift` ### Done when - [ ] A hotkey fires from any application - [ ] A launch trigger fires once per launch - [ ] A folder trigger waits for a copy to complete - [ ] A colliding trigger is refused at save with both macros named ### Watch 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. ## Phase 5 · Sharing macros safely A macro can be exported and imported, and importing one is an informed decision. ### Steps 1. Version the export format A schema version, refused if newer than this build understands. Files: `Export.swift` 2. Preview and approve on import Every action listed, shell and keystroke actions individually approved, applied atomically. Files: `ImportPreview.swift` 3. Add a dry run mode The interpreter logs without executing side effects. Verify a file action changed nothing. Files: `Interpreter.swift` ### Done when - [ ] An exported macro imports and behaves identically - [ ] Shell actions require individual approval on import - [ ] A dry run performs no side effects - [ ] An unknown action kind is refused rather than partly imported ### Watch 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. ## Not in this build - The action library accumulated over two decades. - Image and screen recognition actions. - Guaranteed compatibility across macOS releases. - The community macro library. ## After v1, if you want it - 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 ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Macro definitions. Readable and version controllable. MACROS_PATH=~/Library/Application Support/Macros/macros.json # Required. Step-by-step run history. This is the debugger. RUN_LOG_DB=~/Library/Application Support/Macros/runs.db # Required. Hard cap on any loop. Exceeding it stops the macro and logs why. MAX_LOOP_ITERATIONS=1000 # Required. Hard cap on a single run. A macro that exceeds it is halted. MAX_RUN_SECONDS=120 # Required. Per shell action, killing the process group on expiry. SHELL_TIMEOUT_MS=10000 # Required. How long run history is kept. RUN_LOG_RETENTION_DAYS=30
You are building a lean indie version of Keyboard Maestro. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Keyboard Maestro · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 and SwiftUI in Xcode | the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces | | App type | A background agent with LSUIElement true | it waits for triggers | | Execution | An interpreter over an action tree, not compiled code | an interpreter can be paused, stepped and logged; generated code cannot | | Storage | Macros as JSON in Application Support, versioned | macros are documents people share, and a shared document must be readable before it is trusted | | Logging | Every action's inputs, outputs and duration | a macro that did the wrong thing is only debuggable if you can see what each step actually received | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac running macOS 14 or newer** · free 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 - [ ] **Xcode 16 or newer** · free - Why: Builds and signs the agent. - Get it: Mac App Store or developer.apple.com/xcode. - Verify: xcodebuild -version prints 16 or higher - [ ] **Willingness to grant Accessibility to your own build** · free - 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 - [ ] **Three applications with different automation behaviour** · free - 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 - [ ] **Apple Developer Program membership** (optional) · 99 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. - Verify: A Developer ID Application certificate appears in Xcode settings ## Quick start ```sh xcrun notarytool submit Macros.zip --keychain-profile AC --wait ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Keyboard Maestro, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Keyboard Maestro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a macOS macro engine: triggers fire action sequences with conditions, loops and a visible execution log to replace Keyboard Maestro. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Language: Swift 6 and SwiftUI in Xcode · the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces - App type: A background agent with LSUIElement true · it waits for triggers - Execution: An interpreter over an action tree, not compiled code · an interpreter can be paused, stepped and logged; generated code cannot - Storage: Macros as JSON in Application Support, versioned · macros are documents people share, and a shared document must be readable before it is trusted - Logging: Every action's inputs, outputs and duration · a macro that did the wrong thing is only debuggable if you can see what each step actually received ### Data model (create this before Phase 1) - 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. ### Phase 1 · An interpreter you can watch Build: An action tree with a small set of kinds to begin: pause, notify and set variable. An interpreter walking the tree, logging every step with its duration and result. A manual trigger and a stop control that halts a running macro between actions. Trap: 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. Done when: A three action macro runs and each step appears in the log with its duration. Stopping a running macro halts it between actions and records the run as cancelled. A macro that errors records which action failed and why, and does not continue past it. Do not build yet: No useful actions and no automatic triggers. The engine first. ### Phase 2 · Control flow Build: If and else with conditions on variables, the frontmost application, a file's existence and window state. Repeat with a count and while with a condition. Enforce a maximum iteration count and a maximum run duration so a bad condition cannot loop forever. Trap: 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. Done when: A condition on the frontmost application branches correctly. A repeat runs its count exactly. A while loop with a never-false condition stops at the iteration cap and records why. A macro exceeding the run timeout is halted and logged. Do not build yet: No real actions and no automatic triggers yet. ### Phase 3 · Actions that do something Build: Add the useful action kinds: send keystrokes, click a menu item through the Accessibility API, run a shell command, manipulate the clipboard, move and resize windows, and open files or applications. Each records its inputs and outputs in the run log. Trap: 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. Done when: A macro activates an application, waits for it, and sends a keystroke that arrives in that application. A menu item is clicked by name through Accessibility. A shell action's output is captured into a variable and appears in the run log. A window action moves a window belonging to another application. Do not build yet: No automatic triggers yet. ### Phase 4 · Triggers Build: Hotkey, application launch and quit, folder change and scheduled triggers. Detect conflicts when two enabled macros claim the same trigger, and refuse to save rather than letting one silently win. Trap: 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. Done when: A hotkey fires its macro from any application. An application launch trigger fires once per launch rather than repeatedly. A folder trigger fires after a copy completes rather than during it. Saving a macro whose trigger collides with an enabled macro is refused with both named. Do not build yet: No sharing or distribution yet. ### Phase 5 · Sharing macros safely Build: Export as versioned JSON. Import with a preview listing every action, with shell commands and keystroke actions called out individually and approved separately. A dry run mode that logs what a macro would do without performing side effects. Trap: 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. Done when: An exported macro imports into a fresh installation and behaves identically. An import preview lists every shell command and each requires approval. A dry run logs the intended actions and performs no side effect, verified by checking that a file action changed nothing. An unknown action kind is refused rather than partially imported. Do not build yet: Signing and distribution come last. ### Phase 6 · Signing, notarization and updates Build: Hardened runtime, Developer ID signing, notarization, stapling and Sparkle updates. State plainly what Accessibility permits, since a macro engine with that permission can do essentially anything the user can. Trap: Signing invalidates the Accessibility grant testers gave your unsigned build and macOS will not prompt again. Put the removal steps in the release notes. Done when: A notarized build opens on a fresh Mac with no Gatekeeper warning and requests Accessibility once. An appcast update installs and relaunches. The README states what the permission allows and what the dry run and iteration caps protect against. Do not build yet: Nothing after this. ### Out of scope (and why) - The action library of the commercial product, which has accumulated hundreds of specific actions over two decades and is most of what people are paying for. - Image and screen recognition actions, which are a different kind of engineering and are genuinely useful. - Compatibility across macOS releases without maintenance, since Accessibility and the scripting surfaces both shift. - The community macro library, which is other people's work. ### README must contain - That Accessibility lets this engine do anything the user could, and what the caps and dry run protect against - That every run is logged step by step, and where the log lives - That loops have an iteration cap and runs have a timeout, both configurable - That imported macros can contain shell commands and each is approved individually - That a trigger collision is refused at save rather than resolved silently ===== AGENTS.md ===== # Agent instructions · Keyboard Maestro indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift 6 and SwiftUI in Xcode, A background agent with LSUIElement true, An interpreter over an action tree, not compiled code, Macros as JSON in Application Support, versioned, Every action's inputs, outputs and duration. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An interpreter you can watch A macro of three actions runs, and you can see each step complete. ### Steps 1. Model actions as a tree Parent and ordinal, because conditions and loops contain children. Flat lists cannot express control flow. Files: `Action.swift` 2. Write the interpreter with logging Walk the tree, log every step's start, duration and result. The log is written as it goes, not at the end. Files: `Interpreter.swift` 3. Support cancellation Check 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 - [ ] Each step appears in the log with its duration - [ ] Stopping halts between actions and records cancellation - [ ] An error names the failing action and stops the run - [ ] The log is written during the run rather than after ### Watch 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. ## Phase 2 · Control flow Macros can branch and repeat without becoming unstoppable. ### Steps 1. Add conditions Variables, frontmost application, file existence and window state. Each is a small evaluator returning a boolean. Files: `Conditions.swift` 2. Add repeat and while With children, evaluated per iteration. The interpreter already walks trees, so this is a node kind. Files: `Loops.swift` 3. Cap iterations and duration Both, with the reason recorded in the run log. Test by writing a deliberately infinite loop. Files: `Interpreter.swift` ### Done when - [ ] A frontmost application condition branches correctly - [ ] A repeat runs exactly its count - [ ] An infinite while stops at the cap with the reason logged - [ ] A long macro is halted by the run timeout ### Watch 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. ## Phase 3 · Actions that do something Macros can act on applications, text, files and the clipboard. ### Steps 1. Wait for activation before acting 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.swift` 2. Add the core action kinds Keystrokes, menu clicks, shell, clipboard, window and open. Each independent behind one interface. Files: `Actions/` 3. Capture outputs into variables 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 - [ ] A keystroke arrives in the application the macro activated - [ ] A menu item is clicked by name - [ ] Shell output is captured into a variable and logged - [ ] A window action moves another application's window ### Watch 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. ## Phase 4 · Triggers Macros fire on their own, and two macros never fight over one trigger. ### Steps 1. Register triggers Hotkeys through the Carbon registration, application events through workspace notifications, folder changes through FSEvents. Files: `Triggers/` 2. Settle before firing on file changes Size stable across two checks. The same rule any folder watcher needs. Files: `Triggers/Folder.swift` 3. Detect trigger conflicts at save Compare against enabled macros and refuse rather than resolving by order. Files: `Conflicts.swift` ### Done when - [ ] A hotkey fires from any application - [ ] A launch trigger fires once per launch - [ ] A folder trigger waits for a copy to complete - [ ] A colliding trigger is refused at save with both macros named ### Watch 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. ## Phase 5 · Sharing macros safely A macro can be exported and imported, and importing one is an informed decision. ### Steps 1. Version the export format A schema version, refused if newer than this build understands. Files: `Export.swift` 2. Preview and approve on import Every action listed, shell and keystroke actions individually approved, applied atomically. Files: `ImportPreview.swift` 3. Add a dry run mode The interpreter logs without executing side effects. Verify a file action changed nothing. Files: `Interpreter.swift` ### Done when - [ ] An exported macro imports and behaves identically - [ ] Shell actions require individual approval on import - [ ] A dry run performs no side effects - [ ] An unknown action kind is refused rather than partly imported ### Watch 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. ## Not in this build - The action library accumulated over two decades. - Image and screen recognition actions. - Guaranteed compatibility across macOS releases. - The community macro library. ## After v1, if you want it - 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 ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Macro definitions. Readable and version controllable. MACROS_PATH=~/Library/Application Support/Macros/macros.json # Required. Step-by-step run history. This is the debugger. RUN_LOG_DB=~/Library/Application Support/Macros/runs.db # Required. Hard cap on any loop. Exceeding it stops the macro and logs why. MAX_LOOP_ITERATIONS=1000 # Required. Hard cap on a single run. A macro that exceeds it is halted. MAX_RUN_SECONDS=120 # Required. Per shell action, killing the process group on expiry. SHELL_TIMEOUT_MS=10000 # Required. How long run history is kept. RUN_LOG_RETENTION_DAYS=30
You are building a production product version of Keyboard Maestro. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Keyboard Maestro · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Keyboard Maestro, create user-approved desktop macros from triggers and deterministic actions. The hard boundary is deep macos automation, action library, reliability, and long-term compatibility, plus os integration polish and maintenance. ## Product outcome A macro engine whose every run is visible step by step, whose loops cannot run forever, which can be stopped mid-run, whose imports are approved action by action, and which can show you what a macro would do before it does it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - macOS 14 or later - Xcode - only the permissions required by the selected feature ## Explicit non-goals for v1 - 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 ## Success criteria - An infinite loop is confirmed to stop at the cap - Cancellation is confirmed to halt a running macro - A dry run is confirmed to perform no side effects - Shell actions are confirmed to require individual import approval - A trigger collision is confirmed refused at save ===== BRIEF.md ===== # Build brief · Keyboard Maestro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a macOS macro engine: triggers fire action sequences with conditions, loops and a visible execution log to replace Keyboard Maestro. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Language: Swift 6 and SwiftUI in Xcode · the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces - App type: A background agent with LSUIElement true · it waits for triggers - Execution: An interpreter over an action tree, not compiled code · an interpreter can be paused, stepped and logged; generated code cannot - Storage: Macros as JSON in Application Support, versioned · macros are documents people share, and a shared document must be readable before it is trusted - Logging: Every action's inputs, outputs and duration · a macro that did the wrong thing is only debuggable if you can see what each step actually received ### Data model (create this before Phase 1) - 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. ### Phase 1 · An interpreter you can watch Build: An action tree with a small set of kinds to begin: pause, notify and set variable. An interpreter walking the tree, logging every step with its duration and result. A manual trigger and a stop control that halts a running macro between actions. Trap: 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. Done when: A three action macro runs and each step appears in the log with its duration. Stopping a running macro halts it between actions and records the run as cancelled. A macro that errors records which action failed and why, and does not continue past it. Do not build yet: No useful actions and no automatic triggers. The engine first. ### Phase 2 · Control flow Build: If and else with conditions on variables, the frontmost application, a file's existence and window state. Repeat with a count and while with a condition. Enforce a maximum iteration count and a maximum run duration so a bad condition cannot loop forever. Trap: 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. Done when: A condition on the frontmost application branches correctly. A repeat runs its count exactly. A while loop with a never-false condition stops at the iteration cap and records why. A macro exceeding the run timeout is halted and logged. Do not build yet: No real actions and no automatic triggers yet. ### Phase 3 · Actions that do something Build: Add the useful action kinds: send keystrokes, click a menu item through the Accessibility API, run a shell command, manipulate the clipboard, move and resize windows, and open files or applications. Each records its inputs and outputs in the run log. Trap: 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. Done when: A macro activates an application, waits for it, and sends a keystroke that arrives in that application. A menu item is clicked by name through Accessibility. A shell action's output is captured into a variable and appears in the run log. A window action moves a window belonging to another application. Do not build yet: No automatic triggers yet. ### Phase 4 · Triggers Build: Hotkey, application launch and quit, folder change and scheduled triggers. Detect conflicts when two enabled macros claim the same trigger, and refuse to save rather than letting one silently win. Trap: 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. Done when: A hotkey fires its macro from any application. An application launch trigger fires once per launch rather than repeatedly. A folder trigger fires after a copy completes rather than during it. Saving a macro whose trigger collides with an enabled macro is refused with both named. Do not build yet: No sharing or distribution yet. ### Phase 5 · Sharing macros safely Build: Export as versioned JSON. Import with a preview listing every action, with shell commands and keystroke actions called out individually and approved separately. A dry run mode that logs what a macro would do without performing side effects. Trap: 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. Done when: An exported macro imports into a fresh installation and behaves identically. An import preview lists every shell command and each requires approval. A dry run logs the intended actions and performs no side effect, verified by checking that a file action changed nothing. An unknown action kind is refused rather than partially imported. Do not build yet: Signing and distribution come last. ### Phase 6 · Signing, notarization and updates Build: Hardened runtime, Developer ID signing, notarization, stapling and Sparkle updates. State plainly what Accessibility permits, since a macro engine with that permission can do essentially anything the user can. Trap: Signing invalidates the Accessibility grant testers gave your unsigned build and macOS will not prompt again. Put the removal steps in the release notes. Done when: A notarized build opens on a fresh Mac with no Gatekeeper warning and requests Accessibility once. An appcast update installs and relaunches. The README states what the permission allows and what the dry run and iteration caps protect against. Do not build yet: Nothing after this. ### Out of scope (and why) - The action library of the commercial product, which has accumulated hundreds of specific actions over two decades and is most of what people are paying for. - Image and screen recognition actions, which are a different kind of engineering and are genuinely useful. - Compatibility across macOS releases without maintenance, since Accessibility and the scripting surfaces both shift. - The community macro library, which is other people's work. ### README must contain - That Accessibility lets this engine do anything the user could, and what the caps and dry run protect against - That every run is logged step by step, and where the log lives - That loops have an iteration cap and runs have a timeout, both configurable - That imported macros can contain shell commands and each is approved individually - That a trigger collision is refused at save rather than resolved silently ===== ARCHITECTURE.md ===== # Architecture · Keyboard Maestro ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 and SwiftUI in Xcode | the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces | | App type | A background agent with LSUIElement true | it waits for triggers | | Execution | An interpreter over an action tree, not compiled code | an interpreter can be paused, stepped and logged; generated code cannot | | Storage | Macros as JSON in Application Support, versioned | macros are documents people share, and a shared document must be readable before it is trusted | | Logging | Every action's inputs, outputs and duration | a macro that did the wrong thing is only debuggable if you can see what each step actually received | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Interpreter | Walking the action tree, logging, cancellation and caps. | Not swappable for a compiler. The ability to step, log and stop is the product. | | Actions | Each thing a macro can do. | Independent kinds behind one interface, so adding one touches nothing else. | | Conditions and loops | Control flow. | Evaluators returning booleans. The caps apply regardless of implementation. | | Triggers | Firing macros and refusing collisions. | Each trigger kind is independent. The settle check on file triggers must travel with any change. | | Import and export | Sharing macros safely. | JSON so macros are readable before they are trusted, which is the whole safety argument. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MACROS_PATH` · required · Macro definitions. Readable and version controllable. - `RUN_LOG_DB` · required · Step-by-step run history. This is the debugger. - `MAX_LOOP_ITERATIONS` · required · Hard cap on any loop. Exceeding it stops the macro and logs why. - `MAX_RUN_SECONDS` · required · Hard cap on a single run. A macro that exceeds it is halted. - `SHELL_TIMEOUT_MS` · required · Per shell action, killing the process group on expiry. - `RUN_LOG_RETENTION_DAYS` · required · How long run history is kept. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Keyboard Maestro product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Swift 6 and SwiftUI in Xcode, A background agent with LSUIElement true, An interpreter over an action tree, not compiled code, Macros as JSON in Application Support, versioned, Every action's inputs, outputs and duration. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - 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 invalidates the Accessibility grant testers gave your unsigned build and macOS will not prompt again. Put the removal steps in the release notes. ===== MILESTONES.md ===== # Delivery milestones · Keyboard Maestro Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · An interpreter you can watch A macro of three actions runs, and you can see each step complete. ### Steps 1. Model actions as a tree Parent and ordinal, because conditions and loops contain children. Flat lists cannot express control flow. Files: `Action.swift` 2. Write the interpreter with logging Walk the tree, log every step's start, duration and result. The log is written as it goes, not at the end. Files: `Interpreter.swift` 3. Support cancellation Check 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 - [ ] Each step appears in the log with its duration - [ ] Stopping halts between actions and records cancellation - [ ] An error names the failing action and stops the run - [ ] The log is written during the run rather than after ### Watch 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. ## M2 · Control flow Macros can branch and repeat without becoming unstoppable. ### Steps 1. Add conditions Variables, frontmost application, file existence and window state. Each is a small evaluator returning a boolean. Files: `Conditions.swift` 2. Add repeat and while With children, evaluated per iteration. The interpreter already walks trees, so this is a node kind. Files: `Loops.swift` 3. Cap iterations and duration Both, with the reason recorded in the run log. Test by writing a deliberately infinite loop. Files: `Interpreter.swift` ### Done when - [ ] A frontmost application condition branches correctly - [ ] A repeat runs exactly its count - [ ] An infinite while stops at the cap with the reason logged - [ ] A long macro is halted by the run timeout ### Watch 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. ## M3 · Actions that do something Macros can act on applications, text, files and the clipboard. ### Steps 1. Wait for activation before acting 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.swift` 2. Add the core action kinds Keystrokes, menu clicks, shell, clipboard, window and open. Each independent behind one interface. Files: `Actions/` 3. Capture outputs into variables 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 - [ ] A keystroke arrives in the application the macro activated - [ ] A menu item is clicked by name - [ ] Shell output is captured into a variable and logged - [ ] A window action moves another application's window ### Watch 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. ## M4 · Triggers Macros fire on their own, and two macros never fight over one trigger. ### Steps 1. Register triggers Hotkeys through the Carbon registration, application events through workspace notifications, folder changes through FSEvents. Files: `Triggers/` 2. Settle before firing on file changes Size stable across two checks. The same rule any folder watcher needs. Files: `Triggers/Folder.swift` 3. Detect trigger conflicts at save Compare against enabled macros and refuse rather than resolving by order. Files: `Conflicts.swift` ### Done when - [ ] A hotkey fires from any application - [ ] A launch trigger fires once per launch - [ ] A folder trigger waits for a copy to complete - [ ] A colliding trigger is refused at save with both macros named ### Watch 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. ## M5 · Sharing macros safely A macro can be exported and imported, and importing one is an informed decision. ### Steps 1. Version the export format A schema version, refused if newer than this build understands. Files: `Export.swift` 2. Preview and approve on import Every action listed, shell and keystroke actions individually approved, applied atomically. Files: `ImportPreview.swift` 3. Add a dry run mode The interpreter logs without executing side effects. Verify a file action changed nothing. Files: `Interpreter.swift` ### Done when - [ ] An exported macro imports and behaves identically - [ ] Shell actions require individual approval on import - [ ] A dry run performs no side effects - [ ] An unknown action kind is refused rather than partly imported ### Watch 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. ## M6 · Signing, notarization and updates (production only) Others can install it and understand what they are granting. ### Steps 1. Sign and notarize Hardened runtime, Developer ID, notarytool, stapler. ```sh xcrun notarytool submit Macros.zip --keychain-profile AC --wait ``` 2. Ship updates with Sparkle EdDSA key pair, private half outside the repository, appcast over HTTPS. Files: `appcast.xml` 3. State the blast radius Accessibility lets this run anything you could. Say so, and describe the caps and dry run. Files: `README.md` ### Done when - [ ] spctl reports the notarized build accepted - [ ] Accessibility is requested once and macros then work - [ ] An appcast update installs and relaunches - [ ] The README states what the permission allows ### Watch 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. ===== OPERATIONS.md ===== # Operations · Keyboard Maestro ## Backup The macro file is small and belongs in version control, which also gives you history when a macro starts misbehaving. The run log is larger and only worth keeping for the retention window. ## Restore Replace the macro file and relaunch. Macros referencing applications that are not installed should be disabled rather than erroring, so a restore onto another machine degrades gracefully. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Watch the rate of runs hitting the iteration or duration cap. A macro that starts hitting a cap has usually had its assumptions invalidated by an application update rather than being written wrongly. ## Incident checklist If a macro did something unexpected, the run log names every step and its inputs, which is why it is written during the run. Disable the macro before debugging, because triggers will keep firing while you read. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] An infinite loop is confirmed to stop at the cap - [ ] Cancellation is confirmed to halt a running macro - [ ] A dry run is confirmed to perform no side effects - [ ] Shell actions are confirmed to require individual import approval - [ ] A trigger collision is confirmed refused at save ## Launch constraint Do not market omitted Keyboard Maestro capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Macro definitions. Readable and version controllable. MACROS_PATH=~/Library/Application Support/Macros/macros.json # Required. Step-by-step run history. This is the debugger. RUN_LOG_DB=~/Library/Application Support/Macros/runs.db # Required. Hard cap on any loop. Exceeding it stops the macro and logs why. MAX_LOOP_ITERATIONS=1000 # Required. Hard cap on a single run. A macro that exceeds it is halted. MAX_RUN_SECONDS=120 # Required. Per shell action, killing the process group on expiry. SHELL_TIMEOUT_MS=10000 # Required. How long run history is kept. RUN_LOG_RETENTION_DAYS=30
# Keyboard Maestro · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 and SwiftUI in Xcode | the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces | | App type | A background agent with LSUIElement true | it waits for triggers | | Execution | An interpreter over an action tree, not compiled code | an interpreter can be paused, stepped and logged; generated code cannot | | Storage | Macros as JSON in Application Support, versioned | macros are documents people share, and a shared document must be readable before it is trusted | | Logging | Every action's inputs, outputs and duration | a macro that did the wrong thing is only debuggable if you can see what each step actually received | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A Mac running macOS 14 or newer** · free 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 - [ ] **Xcode 16 or newer** · free - Why: Builds and signs the agent. - Get it: Mac App Store or developer.apple.com/xcode. - Verify: xcodebuild -version prints 16 or higher - [ ] **Willingness to grant Accessibility to your own build** · free - 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 - [ ] **Three applications with different automation behaviour** · free - 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 - [ ] **Apple Developer Program membership** (optional) · 99 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. - Verify: A Developer ID Application certificate appears in Xcode settings ## Quick start ```sh xcrun notarytool submit Macros.zip --keychain-profile AC --wait ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Keyboard Maestro, and the README should say so rather than pretend.
# Build brief · Keyboard Maestro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a macOS macro engine: triggers fire action sequences with conditions, loops and a visible execution log to replace Keyboard Maestro. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Language: Swift 6 and SwiftUI in Xcode · the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces - App type: A background agent with LSUIElement true · it waits for triggers - Execution: An interpreter over an action tree, not compiled code · an interpreter can be paused, stepped and logged; generated code cannot - Storage: Macros as JSON in Application Support, versioned · macros are documents people share, and a shared document must be readable before it is trusted - Logging: Every action's inputs, outputs and duration · a macro that did the wrong thing is only debuggable if you can see what each step actually received ### Data model (create this before Phase 1) - 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. ### Phase 1 · An interpreter you can watch Build: An action tree with a small set of kinds to begin: pause, notify and set variable. An interpreter walking the tree, logging every step with its duration and result. A manual trigger and a stop control that halts a running macro between actions. Trap: 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. Done when: A three action macro runs and each step appears in the log with its duration. Stopping a running macro halts it between actions and records the run as cancelled. A macro that errors records which action failed and why, and does not continue past it. Do not build yet: No useful actions and no automatic triggers. The engine first. ### Phase 2 · Control flow Build: If and else with conditions on variables, the frontmost application, a file's existence and window state. Repeat with a count and while with a condition. Enforce a maximum iteration count and a maximum run duration so a bad condition cannot loop forever. Trap: 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. Done when: A condition on the frontmost application branches correctly. A repeat runs its count exactly. A while loop with a never-false condition stops at the iteration cap and records why. A macro exceeding the run timeout is halted and logged. Do not build yet: No real actions and no automatic triggers yet. ### Phase 3 · Actions that do something Build: Add the useful action kinds: send keystrokes, click a menu item through the Accessibility API, run a shell command, manipulate the clipboard, move and resize windows, and open files or applications. Each records its inputs and outputs in the run log. Trap: 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. Done when: A macro activates an application, waits for it, and sends a keystroke that arrives in that application. A menu item is clicked by name through Accessibility. A shell action's output is captured into a variable and appears in the run log. A window action moves a window belonging to another application. Do not build yet: No automatic triggers yet. ### Phase 4 · Triggers Build: Hotkey, application launch and quit, folder change and scheduled triggers. Detect conflicts when two enabled macros claim the same trigger, and refuse to save rather than letting one silently win. Trap: 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. Done when: A hotkey fires its macro from any application. An application launch trigger fires once per launch rather than repeatedly. A folder trigger fires after a copy completes rather than during it. Saving a macro whose trigger collides with an enabled macro is refused with both named. Do not build yet: No sharing or distribution yet. ### Phase 5 · Sharing macros safely Build: Export as versioned JSON. Import with a preview listing every action, with shell commands and keystroke actions called out individually and approved separately. A dry run mode that logs what a macro would do without performing side effects. Trap: 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. Done when: An exported macro imports into a fresh installation and behaves identically. An import preview lists every shell command and each requires approval. A dry run logs the intended actions and performs no side effect, verified by checking that a file action changed nothing. An unknown action kind is refused rather than partially imported. Do not build yet: Signing and distribution come last. ### Phase 6 · Signing, notarization and updates Build: Hardened runtime, Developer ID signing, notarization, stapling and Sparkle updates. State plainly what Accessibility permits, since a macro engine with that permission can do essentially anything the user can. Trap: Signing invalidates the Accessibility grant testers gave your unsigned build and macOS will not prompt again. Put the removal steps in the release notes. Done when: A notarized build opens on a fresh Mac with no Gatekeeper warning and requests Accessibility once. An appcast update installs and relaunches. The README states what the permission allows and what the dry run and iteration caps protect against. Do not build yet: Nothing after this. ### Out of scope (and why) - The action library of the commercial product, which has accumulated hundreds of specific actions over two decades and is most of what people are paying for. - Image and screen recognition actions, which are a different kind of engineering and are genuinely useful. - Compatibility across macOS releases without maintenance, since Accessibility and the scripting surfaces both shift. - The community macro library, which is other people's work. ### README must contain - That Accessibility lets this engine do anything the user could, and what the caps and dry run protect against - That every run is logged step by step, and where the log lives - That loops have an iteration cap and runs have a timeout, both configurable - That imported macros can contain shell commands and each is approved individually - That a trigger collision is refused at save rather than resolved silently
# Agent instructions · Keyboard Maestro indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Swift 6 and SwiftUI in Xcode, A background agent with LSUIElement true, An interpreter over an action tree, not compiled code, Macros as JSON in Application Support, versioned, Every action's inputs, outputs and duration. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. - 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.
# Build plan · 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · An interpreter you can watch A macro of three actions runs, and you can see each step complete. ### Steps 1. Model actions as a tree Parent and ordinal, because conditions and loops contain children. Flat lists cannot express control flow. Files: `Action.swift` 2. Write the interpreter with logging Walk the tree, log every step's start, duration and result. The log is written as it goes, not at the end. Files: `Interpreter.swift` 3. Support cancellation Check 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 - [ ] Each step appears in the log with its duration - [ ] Stopping halts between actions and records cancellation - [ ] An error names the failing action and stops the run - [ ] The log is written during the run rather than after ### Watch 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. ## Phase 2 · Control flow Macros can branch and repeat without becoming unstoppable. ### Steps 1. Add conditions Variables, frontmost application, file existence and window state. Each is a small evaluator returning a boolean. Files: `Conditions.swift` 2. Add repeat and while With children, evaluated per iteration. The interpreter already walks trees, so this is a node kind. Files: `Loops.swift` 3. Cap iterations and duration Both, with the reason recorded in the run log. Test by writing a deliberately infinite loop. Files: `Interpreter.swift` ### Done when - [ ] A frontmost application condition branches correctly - [ ] A repeat runs exactly its count - [ ] An infinite while stops at the cap with the reason logged - [ ] A long macro is halted by the run timeout ### Watch 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. ## Phase 3 · Actions that do something Macros can act on applications, text, files and the clipboard. ### Steps 1. Wait for activation before acting 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.swift` 2. Add the core action kinds Keystrokes, menu clicks, shell, clipboard, window and open. Each independent behind one interface. Files: `Actions/` 3. Capture outputs into variables 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 - [ ] A keystroke arrives in the application the macro activated - [ ] A menu item is clicked by name - [ ] Shell output is captured into a variable and logged - [ ] A window action moves another application's window ### Watch 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. ## Phase 4 · Triggers Macros fire on their own, and two macros never fight over one trigger. ### Steps 1. Register triggers Hotkeys through the Carbon registration, application events through workspace notifications, folder changes through FSEvents. Files: `Triggers/` 2. Settle before firing on file changes Size stable across two checks. The same rule any folder watcher needs. Files: `Triggers/Folder.swift` 3. Detect trigger conflicts at save Compare against enabled macros and refuse rather than resolving by order. Files: `Conflicts.swift` ### Done when - [ ] A hotkey fires from any application - [ ] A launch trigger fires once per launch - [ ] A folder trigger waits for a copy to complete - [ ] A colliding trigger is refused at save with both macros named ### Watch 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. ## Phase 5 · Sharing macros safely A macro can be exported and imported, and importing one is an informed decision. ### Steps 1. Version the export format A schema version, refused if newer than this build understands. Files: `Export.swift` 2. Preview and approve on import Every action listed, shell and keystroke actions individually approved, applied atomically. Files: `ImportPreview.swift` 3. Add a dry run mode The interpreter logs without executing side effects. Verify a file action changed nothing. Files: `Interpreter.swift` ### Done when - [ ] An exported macro imports and behaves identically - [ ] Shell actions require individual approval on import - [ ] A dry run performs no side effects - [ ] An unknown action kind is refused rather than partly imported ### Watch 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. ## Not in this build - The action library accumulated over two decades. - Image and screen recognition actions. - Guaranteed compatibility across macOS releases. - The community macro library. ## After v1, if you want it - 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
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Macro definitions. Readable and version controllable. MACROS_PATH=~/Library/Application Support/Macros/macros.json # Required. Step-by-step run history. This is the debugger. RUN_LOG_DB=~/Library/Application Support/Macros/runs.db # Required. Hard cap on any loop. Exceeding it stops the macro and logs why. MAX_LOOP_ITERATIONS=1000 # Required. Hard cap on a single run. A macro that exceeds it is halted. MAX_RUN_SECONDS=120 # Required. Per shell action, killing the process group on expiry. SHELL_TIMEOUT_MS=10000 # Required. How long run history is kept. RUN_LOG_RETENTION_DAYS=30
# Keyboard Maestro · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Keyboard Maestro, create user-approved desktop macros from triggers and deterministic actions. The hard boundary is deep macos automation, action library, reliability, and long-term compatibility, plus os integration polish and maintenance. ## Product outcome A macro engine whose every run is visible step by step, whose loops cannot run forever, which can be stopped mid-run, whose imports are approved action by action, and which can show you what a macro would do before it does it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - macOS 14 or later - Xcode - only the permissions required by the selected feature ## Explicit non-goals for v1 - 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 ## Success criteria - An infinite loop is confirmed to stop at the cap - Cancellation is confirmed to halt a running macro - A dry run is confirmed to perform no side effects - Shell actions are confirmed to require individual import approval - A trigger collision is confirmed refused at save
# Build brief · Keyboard Maestro The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a macOS macro engine: triggers fire action sequences with conditions, loops and a visible execution log to replace Keyboard Maestro. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Language: Swift 6 and SwiftUI in Xcode · the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces - App type: A background agent with LSUIElement true · it waits for triggers - Execution: An interpreter over an action tree, not compiled code · an interpreter can be paused, stepped and logged; generated code cannot - Storage: Macros as JSON in Application Support, versioned · macros are documents people share, and a shared document must be readable before it is trusted - Logging: Every action's inputs, outputs and duration · a macro that did the wrong thing is only debuggable if you can see what each step actually received ### Data model (create this before Phase 1) - 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. ### Phase 1 · An interpreter you can watch Build: An action tree with a small set of kinds to begin: pause, notify and set variable. An interpreter walking the tree, logging every step with its duration and result. A manual trigger and a stop control that halts a running macro between actions. Trap: 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. Done when: A three action macro runs and each step appears in the log with its duration. Stopping a running macro halts it between actions and records the run as cancelled. A macro that errors records which action failed and why, and does not continue past it. Do not build yet: No useful actions and no automatic triggers. The engine first. ### Phase 2 · Control flow Build: If and else with conditions on variables, the frontmost application, a file's existence and window state. Repeat with a count and while with a condition. Enforce a maximum iteration count and a maximum run duration so a bad condition cannot loop forever. Trap: 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. Done when: A condition on the frontmost application branches correctly. A repeat runs its count exactly. A while loop with a never-false condition stops at the iteration cap and records why. A macro exceeding the run timeout is halted and logged. Do not build yet: No real actions and no automatic triggers yet. ### Phase 3 · Actions that do something Build: Add the useful action kinds: send keystrokes, click a menu item through the Accessibility API, run a shell command, manipulate the clipboard, move and resize windows, and open files or applications. Each records its inputs and outputs in the run log. Trap: 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. Done when: A macro activates an application, waits for it, and sends a keystroke that arrives in that application. A menu item is clicked by name through Accessibility. A shell action's output is captured into a variable and appears in the run log. A window action moves a window belonging to another application. Do not build yet: No automatic triggers yet. ### Phase 4 · Triggers Build: Hotkey, application launch and quit, folder change and scheduled triggers. Detect conflicts when two enabled macros claim the same trigger, and refuse to save rather than letting one silently win. Trap: 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. Done when: A hotkey fires its macro from any application. An application launch trigger fires once per launch rather than repeatedly. A folder trigger fires after a copy completes rather than during it. Saving a macro whose trigger collides with an enabled macro is refused with both named. Do not build yet: No sharing or distribution yet. ### Phase 5 · Sharing macros safely Build: Export as versioned JSON. Import with a preview listing every action, with shell commands and keystroke actions called out individually and approved separately. A dry run mode that logs what a macro would do without performing side effects. Trap: 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. Done when: An exported macro imports into a fresh installation and behaves identically. An import preview lists every shell command and each requires approval. A dry run logs the intended actions and performs no side effect, verified by checking that a file action changed nothing. An unknown action kind is refused rather than partially imported. Do not build yet: Signing and distribution come last. ### Phase 6 · Signing, notarization and updates Build: Hardened runtime, Developer ID signing, notarization, stapling and Sparkle updates. State plainly what Accessibility permits, since a macro engine with that permission can do essentially anything the user can. Trap: Signing invalidates the Accessibility grant testers gave your unsigned build and macOS will not prompt again. Put the removal steps in the release notes. Done when: A notarized build opens on a fresh Mac with no Gatekeeper warning and requests Accessibility once. An appcast update installs and relaunches. The README states what the permission allows and what the dry run and iteration caps protect against. Do not build yet: Nothing after this. ### Out of scope (and why) - The action library of the commercial product, which has accumulated hundreds of specific actions over two decades and is most of what people are paying for. - Image and screen recognition actions, which are a different kind of engineering and are genuinely useful. - Compatibility across macOS releases without maintenance, since Accessibility and the scripting surfaces both shift. - The community macro library, which is other people's work. ### README must contain - That Accessibility lets this engine do anything the user could, and what the caps and dry run protect against - That every run is logged step by step, and where the log lives - That loops have an iteration cap and runs have a timeout, both configurable - That imported macros can contain shell commands and each is approved individually - That a trigger collision is refused at save rather than resolved silently
# Architecture · Keyboard Maestro ## Stack | Part | Choice | Why | | --- | --- | --- | | Language | Swift 6 and SwiftUI in Xcode | the action library reaches into AppleScript, the Accessibility API and the shell, all of which are platform surfaces | | App type | A background agent with LSUIElement true | it waits for triggers | | Execution | An interpreter over an action tree, not compiled code | an interpreter can be paused, stepped and logged; generated code cannot | | Storage | Macros as JSON in Application Support, versioned | macros are documents people share, and a shared document must be readable before it is trusted | | Logging | Every action's inputs, outputs and duration | a macro that did the wrong thing is only debuggable if you can see what each step actually received | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Interpreter | Walking the action tree, logging, cancellation and caps. | Not swappable for a compiler. The ability to step, log and stop is the product. | | Actions | Each thing a macro can do. | Independent kinds behind one interface, so adding one touches nothing else. | | Conditions and loops | Control flow. | Evaluators returning booleans. The caps apply regardless of implementation. | | Triggers | Firing macros and refusing collisions. | Each trigger kind is independent. The settle check on file triggers must travel with any change. | | Import and export | Sharing macros safely. | JSON so macros are readable before they are trusted, which is the whole safety argument. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MACROS_PATH` · required · Macro definitions. Readable and version controllable. - `RUN_LOG_DB` · required · Step-by-step run history. This is the debugger. - `MAX_LOOP_ITERATIONS` · required · Hard cap on any loop. Exceeding it stops the macro and logs why. - `MAX_RUN_SECONDS` · required · Hard cap on a single run. A macro that exceeds it is halted. - `SHELL_TIMEOUT_MS` · required · Per shell action, killing the process group on expiry. - `RUN_LOG_RETENTION_DAYS` · required · How long run history is kept. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Keyboard Maestro product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Swift 6 and SwiftUI in Xcode, A background agent with LSUIElement true, An interpreter over an action tree, not compiled code, Macros as JSON in Application Support, versioned, Every action's inputs, outputs and duration. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - 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 invalidates the Accessibility grant testers gave your unsigned build and macOS will not prompt again. Put the removal steps in the release notes.
# Delivery milestones · Keyboard Maestro Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · An interpreter you can watch A macro of three actions runs, and you can see each step complete. ### Steps 1. Model actions as a tree Parent and ordinal, because conditions and loops contain children. Flat lists cannot express control flow. Files: `Action.swift` 2. Write the interpreter with logging Walk the tree, log every step's start, duration and result. The log is written as it goes, not at the end. Files: `Interpreter.swift` 3. Support cancellation Check 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 - [ ] Each step appears in the log with its duration - [ ] Stopping halts between actions and records cancellation - [ ] An error names the failing action and stops the run - [ ] The log is written during the run rather than after ### Watch 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. ## M2 · Control flow Macros can branch and repeat without becoming unstoppable. ### Steps 1. Add conditions Variables, frontmost application, file existence and window state. Each is a small evaluator returning a boolean. Files: `Conditions.swift` 2. Add repeat and while With children, evaluated per iteration. The interpreter already walks trees, so this is a node kind. Files: `Loops.swift` 3. Cap iterations and duration Both, with the reason recorded in the run log. Test by writing a deliberately infinite loop. Files: `Interpreter.swift` ### Done when - [ ] A frontmost application condition branches correctly - [ ] A repeat runs exactly its count - [ ] An infinite while stops at the cap with the reason logged - [ ] A long macro is halted by the run timeout ### Watch 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. ## M3 · Actions that do something Macros can act on applications, text, files and the clipboard. ### Steps 1. Wait for activation before acting 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.swift` 2. Add the core action kinds Keystrokes, menu clicks, shell, clipboard, window and open. Each independent behind one interface. Files: `Actions/` 3. Capture outputs into variables 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 - [ ] A keystroke arrives in the application the macro activated - [ ] A menu item is clicked by name - [ ] Shell output is captured into a variable and logged - [ ] A window action moves another application's window ### Watch 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. ## M4 · Triggers Macros fire on their own, and two macros never fight over one trigger. ### Steps 1. Register triggers Hotkeys through the Carbon registration, application events through workspace notifications, folder changes through FSEvents. Files: `Triggers/` 2. Settle before firing on file changes Size stable across two checks. The same rule any folder watcher needs. Files: `Triggers/Folder.swift` 3. Detect trigger conflicts at save Compare against enabled macros and refuse rather than resolving by order. Files: `Conflicts.swift` ### Done when - [ ] A hotkey fires from any application - [ ] A launch trigger fires once per launch - [ ] A folder trigger waits for a copy to complete - [ ] A colliding trigger is refused at save with both macros named ### Watch 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. ## M5 · Sharing macros safely A macro can be exported and imported, and importing one is an informed decision. ### Steps 1. Version the export format A schema version, refused if newer than this build understands. Files: `Export.swift` 2. Preview and approve on import Every action listed, shell and keystroke actions individually approved, applied atomically. Files: `ImportPreview.swift` 3. Add a dry run mode The interpreter logs without executing side effects. Verify a file action changed nothing. Files: `Interpreter.swift` ### Done when - [ ] An exported macro imports and behaves identically - [ ] Shell actions require individual approval on import - [ ] A dry run performs no side effects - [ ] An unknown action kind is refused rather than partly imported ### Watch 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. ## M6 · Signing, notarization and updates (production only) Others can install it and understand what they are granting. ### Steps 1. Sign and notarize Hardened runtime, Developer ID, notarytool, stapler. ```sh xcrun notarytool submit Macros.zip --keychain-profile AC --wait ``` 2. Ship updates with Sparkle EdDSA key pair, private half outside the repository, appcast over HTTPS. Files: `appcast.xml` 3. State the blast radius Accessibility lets this run anything you could. Say so, and describe the caps and dry run. Files: `README.md` ### Done when - [ ] spctl reports the notarized build accepted - [ ] Accessibility is requested once and macros then work - [ ] An appcast update installs and relaunches - [ ] The README states what the permission allows ### Watch 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.
# Operations · Keyboard Maestro ## Backup The macro file is small and belongs in version control, which also gives you history when a macro starts misbehaving. The run log is larger and only worth keeping for the retention window. ## Restore Replace the macro file and relaunch. Macros referencing applications that are not installed should be disabled rather than erroring, so a restore onto another machine degrades gracefully. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Watch the rate of runs hitting the iteration or duration cap. A macro that starts hitting a cap has usually had its assumptions invalidated by an application update rather than being written wrongly. ## Incident checklist If a macro did something unexpected, the run log names every step and its inputs, which is why it is written during the run. Disable the macro before debugging, because triggers will keep firing while you read. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] An infinite loop is confirmed to stop at the cap - [ ] Cancellation is confirmed to halt a running macro - [ ] A dry run is confirmed to perform no side effects - [ ] Shell actions are confirmed to require individual import approval - [ ] A trigger collision is confirmed refused at save ## Launch constraint Do not market omitted Keyboard Maestro capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Macro definitions. Readable and version controllable. MACROS_PATH=~/Library/Application Support/Macros/macros.json # Required. Step-by-step run history. This is the debugger. RUN_LOG_DB=~/Library/Application Support/Macros/runs.db # Required. Hard cap on any loop. Exceeding it stops the macro and logs why. MAX_LOOP_ITERATIONS=1000 # Required. Hard cap on a single run. A macro that exceeds it is halted. MAX_RUN_SECONDS=120 # Required. Per shell action, killing the process group on expiry. SHELL_TIMEOUT_MS=10000 # Required. How long run history is kept. RUN_LOG_RETENTION_DAYS=30
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Keyboard Maestro because small utilities are technically reproducible, but customers pay for polish and immediate fixes when an OS update breaks a daily habit. The recurring cost buys permissions, accessibility APIs, sandboxing, signing, notarization, OS regressions, updates, backups, and support, not just the visible interface.
xdeep macOS automation, action library, reliability, and long-term compatibility
xyears of OS-version edge cases
xlarge extension ecosystem
xcross-device sync
xcommercial support and rapid compatibility fixes
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Keyboard Maestro pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| keyboard maestro | custom | — | Perpetual licence for the current major version; no published numeric macro or action cap. |
| upgrade | custom | — | Upgrade path for owners of an older major version. |
free tierno free tier
billingone-time purchase; no monthly or annual plan; 30-day trial
hidden costsFuture major versions can require a paid upgrade; the current-version licence remains usable. Current purchase and upgrade amounts were not exposed in crawlable official text.
verified 2026-08-14 · source ↗
Vibecode Keyboard Maestro
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Keyboard Maestro replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Keyboard Maestro cost?
Keyboard Maestro's pricing is usage-based or varies by plan · Sold as a one-time Mac licence..
What do I lose by replacing Keyboard Maestro?
Honestly: 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. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Keyboard Maestro?
Yes: Apple Automator (Old, unfashionable, and still able to turn folders, services, calendars, and app actions into local workflows.) Apple Shortcuts (Apple's built-in macro box: plenty of deterministic actions, fewer background triggers, no extra invoice.) The prompt is for when you want it exactly your way.