Build Alfred Powerpack

YESback to the verdict

0%0 of 25 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A menu bar agent that opens on a global hotkey, finds and launches apps by fuzzy match with frecency ranking, keeps a searchable clipboard history that respects concealed pasteboard types, expands text snippets through the pasteboard, and runs your own shell scripts as result providers.

estimated effort weekendthe files for this build are in the project pack

LanguageSwift 6 and SwiftUI, built in XcodeApp typeA background agent with LSUIElement set to trueHotkeyCarbon RegisterEventHotKeyStorageSQLite through the C API in a container directorySnippet deliveryNSPasteboard plus a synthetic Cmd-V

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree if you already own a Mac

    Why The whole build is AppKit. There is no way to develop or test this on another platform.

    Get it Any Mac from the last few years running Sonoma or later. Check with the Apple menu, then About This Mac.

    Verify About This Mac reports macOS 14 or higher

  2. installfree

    Why Builds the app, signs it, and provides the Instruments profiler you will want when the keystroke monitor feels slow.

    Get it Install from the Mac App Store, or download from developer.apple.com/xcode. It is a large download, so start it before you read further. open ↗

    Verify xcodebuild -version prints 16 or higher

  3. decidefree

    Why SwiftUI covers the panel, but hotkeys, the pasteboard and event monitors are all AppKit and Carbon. You will be reading Apple documentation, not tutorials.

    Get it If you have never written Swift, build the panel in Phase 1 by itself and stop there for a day before continuing.

  4. decidefree

    Why Snippets and paste-back cannot work without it. You are granting a program you compiled the ability to read keystrokes, so understand what that means before you turn it on.

    Get it System Settings > Privacy and Security > Accessibility, then add your built app. During development this must be re-granted whenever the binary path changes.

    Verify AXIsProcessTrusted() returns true from the running app

  5. 99 USD per year

    Why Only needed for the product phase. Without it you cannot get a Developer ID certificate, so you cannot notarize, so the app shows a Gatekeeper warning on anyone else's Mac.

    Get it Enrol at developer.apple.com/programs. Enrolment is reviewed and can take a day or two. open ↗

    Verify A Developer ID Application certificate appears in Xcode > Settings > Accounts

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- items(id, kind, title, subtitle, path, bundle_id, indexed_at) · kind is app, file or workflow_result
- usage(item_id, used_at, query_prefix) · one row per launch, the input to frecency
- clips(id, kind, content, preview, source_bundle_id, hash, copied_at, pinned)
- snippets(id, trigger, body, expand_count, last_used_at)
- workflows(id, name, keyword, script_path, timeout_ms, enabled)
- Index items on (kind, title) and clips on (copied_at desc); the query runs on every keystroke.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
LAUNCHER_DB_PATHrequired~/Library/Application Support/Launcher/launcher.dbWhere the SQLite database lives. Keep it under Application Support so a Time Machine backup includes it.
LAUNCHER_CLIP_LIMITrequired500How many clipboard rows to keep. Chosen by you; the oldest unpinned rows are deleted past this.
LAUNCHER_WORKFLOW_TIMEOUT_MSrequired1500How long a workflow script may run before it is killed. Keep it short; this runs on every keystroke.
SPARKLE_FEED_URLoptionalhttps://example.com/appcast.xmlProduct phase only. The HTTPS URL where you publish your Sparkle appcast.

The build, in order

  1. Agent, hotkey and window

    A background app with no Dock icon that appears on a global hotkey from inside any other application and disappears on Escape.

    1. File > New > Project > macOS > App, SwiftUI lifecycle. Add the key Application is agent (UIElement) with value YES to Info.plist. Run it: nothing should appear in the Dock.

      Files Info.plist

    2. Use MenuBarExtra in SwiftUI. This is the only visible surface until the hotkey works, and it is how you quit a Dock-less app you cannot click.

      Files App.swift

    3. Subclass NSPanel with .nonactivatingPanel and .hudWindow, set canBecomeKey to true, level to .floating and hidesOnDeactivate to true. A plain NSWindow cannot take keyboard input without activating the app.

      Files LauncherPanel.swift

    4. Call RegisterEventHotKey with a signature and an EventHotKeyID, and install a Carbon event handler for kEventHotKeyPressed. Store the chosen key combination in UserDefaults so it survives a relaunch.

      Files Hotkey.swift

    done when · tick each as it passes
    watch out
    • RegisterEventHotKey needs no permission at all. Sending keystrokes into other apps later does, and Accessibility is a separate grant from Input Monitoring. Do not ask for either in this phase or you will train the user to refuse the one that matters.
  2. App search with frecency

    Typing two or three letters puts the app you actually meant at the top, and it gets better with use.

    1. Read the three application directories with FileManager, take every .app bundle, and pull CFBundleDisplayName or CFBundleName plus the bundle identifier from its Info.plist. Store one row per app in items.

      Files Indexer.swift

    2. Score a subsequence match: contiguous runs score higher, a match at a word boundary scores higher, and build an acronym from capitalised words so ahk finds Adobe Handbrake Kit. Return a score, not a boolean.

      Files Match.swift

    3. On every launch insert into usage with the query prefix that produced it. Score an item as matchScore multiplied by (1 plus a decayed sum over its usage rows), halving each row's weight every fourteen days.

      Files Ranking.swift

    4. Rescan every few minutes, and watch the application directories with an FSEvents stream so an install shows up without waiting.

      Files Indexer.swift

    done when · tick each as it passes
    watch out
    • Do not walk the whole disk. A recursive scan of a home directory takes minutes, hits permission dialogs for Documents and Desktop, and buys you nothing a bundle scan of three directories does not.
  3. Clipboard history

    Everything you copy is searchable later, except the things that must never be stored.

    1. A repeating timer at roughly 200ms compares NSPasteboard.general.changeCount to the last value you saw. Only read the pasteboard when the count moved.

      Files ClipboardWatcher.swift

    2. Before reading contents, inspect the item types. Skip anything declaring org.nspasteboard.ConcealedType, org.nspasteboard.TransientType or org.nspasteboard.AutoGeneratedType. Write a test that feeds a synthetic concealed item and asserts nothing was stored.

      Files ClipboardWatcher.swift

    3. Hash the text or image bytes. On a hash you already hold, update copied_at instead of inserting. Cap history by row count and by total bytes, and delete oldest first, never pinned rows.

      Files ClipStore.swift

    4. A second registered hotkey opens the panel filtered to clips. Selecting a row writes it to the pasteboard and posts Cmd-V. This is where the Accessibility grant is first needed, so explain it in the prompt you show.

      Files Paste.swift

    done when · tick each as it passes
    watch out
    • NSPasteboard has no change notification of any kind. Polling changeCount is not a workaround, it is the supported approach; every clipboard manager on the platform does it.
    • Password managers mark their pasteboard items with org.nspasteboard.ConcealedType and similar transient types. If you ignore those markers your history quietly becomes a plaintext password log. Skip any item carrying a concealed, transient or autogenerated type.
  4. Snippets

    A trigger you type anywhere expands into a block of text, without dropping characters.

    1. NSEvent.addGlobalMonitorForEvents for keyDown requires Accessibility. Check AXIsProcessTrustedWithOptions and show your own explanation before the system prompt appears.

      Files SnippetWatcher.swift

    2. Keep a short rolling buffer of recent characters and reset it on Return, Escape, a click or a focus change. Match the longest trigger that ends at the buffer tail.

      Files SnippetWatcher.swift

    3. Save the current pasteboard items, send backspaces for the trigger length, write the body, send Cmd-V, then restore the saved items after a short delay. Restoring too early pastes the wrong thing.

      Files Expand.swift

    4. If the frontmost element is a secure text field, do not attempt the expansion. Show a brief notification saying why.

      Files Expand.swift

    done when · tick each as it passes
    watch out
    • Do not type the body with one CGEvent per character. It drops characters under load, mangles anything outside ASCII, and behaves differently in every text engine. Paste and restore is the approach that works.
    • Secure text fields refuse synthetic input by design. Detect the failure and show it, rather than leaving the user staring at a field where nothing happened.
  5. Script workflows

    Your own scripts become result providers, so the launcher does things this build never anticipated.

    1. One JSON object per line with title, subtitle, an optional icon path and an arg passed back on selection. Write the contract in the README before implementing it.

      Files docs/workflows.md

    2. Use Process with a dispatch queue. Cancel and kill the previous run when a new keystroke arrives, so an abandoned query never outlives its input.

      Files WorkflowRunner.swift

    3. Map JSON lines to rows. On selection re-invoke the script with the arg and a mode flag so it can act rather than list.

      Files WorkflowRunner.swift

    4. A directory search using mdfind and a unit converter. They prove the contract and give the user something to copy.

      Files examples/

    done when · tick each as it passes
    watch out
    • Run the script off the main thread with a hard timeout and kill the process group on timeout, not just the child. A script that blocks on stdin will otherwise hang the whole launcher on every keystroke.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more productivity utilities.