Build Alfred Powerpack
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.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree if you 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 - 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 - 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.
- 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 - account99 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.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
LAUNCHER_DB_PATH | required | ~/Library/Application Support/Launcher/launcher.db | Where the SQLite database lives. Keep it under Application Support so a Time Machine backup includes it. |
LAUNCHER_CLIP_LIMIT | required | 500 | How many clipboard rows to keep. Chosen by you; the oldest unpinned rows are deleted past this. |
LAUNCHER_WORKFLOW_TIMEOUT_MS | required | 1500 | How long a workflow script may run before it is killed. Keep it short; this runs on every keystroke. |
SPARKLE_FEED_URL | optional | https://example.com/appcast.xml | Product phase only. The HTTPS URL where you publish your Sparkle appcast. |
The build, in order
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.
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.plistUse 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.swiftSubclass 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.swiftCall 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 passeswatch 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.
App search with frecency
Typing two or three letters puts the app you actually meant at the top, and it gets better with use.
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.swiftScore 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.swiftOn 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.swiftRescan 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 passeswatch 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.
Clipboard history
Everything you copy is searchable later, except the things that must never be stored.
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.swiftBefore 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.swiftHash 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.swiftA 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 passeswatch 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.
Snippets
A trigger you type anywhere expands into a block of text, without dropping characters.
NSEvent.addGlobalMonitorForEvents for keyDown requires Accessibility. Check AXIsProcessTrustedWithOptions and show your own explanation before the system prompt appears.
Files
SnippetWatcher.swiftKeep 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.swiftSave 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.swiftIf 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 passeswatch 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.
Script workflows
Your own scripts become result providers, so the launcher does things this build never anticipated.
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.mdUse 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.swiftMap 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.swiftA 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 passeswatch 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.
Signing, notarization and updatesproduct builder
The app installs on a machine that is not yours without a Gatekeeper warning, and can ship a fix.
Turn on the hardened runtime in Signing and Capabilities. Sign with Developer ID Application, which requires a paid Apple Developer account.
terminalcodesign --force --options runtime --sign "Developer ID Application: YOUR NAME" Launcher.app
Submit the zipped app with notarytool, wait for acceptance, then staple the ticket so it validates offline.
terminalxcrun notarytool submit Launcher.zip --keychain-profile AC --wait xcrun stapler staple Launcher.app
Generate an EdDSA key pair, keep the private key out of the repository, and publish appcast.xml over HTTPS.
Files
appcast.xmlSMAppService.mainApp.register() from a settings toggle. The old login item APIs are deprecated and behave differently under sandboxing.
Files
Settings.swift
done when · tick each as it passeswatch out- Screen and Accessibility permissions are granted to a specific code signature. Re-signing with a different identity silently invalidates the grant and macOS will not prompt again; the user has to remove the app in System Settings first. Say this in your release notes the first time you change identity.
That is the whole plan for Alfred Powerpack. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Cross-device sync of clips or snippets. There is no server here by design.
- The Alfred workflow gallery, which is the actual reason many people pay.
- Deep integrations with Contacts, Music, Mail and password managers.
- Scrolling file previews and the file navigation system Alfred grew over a decade.
- years of macOS integration, workflow ecosystem, reliability, and community
- years of OS-version edge cases
- large extension ecosystem
- cross-device sync
- commercial support and rapid compatibility fixes
- File search with NSMetadataQuery, once you accept the permission prompts it brings
- A calculator result row using NSExpression
- Sharing workflows as a signed folder others can drop in
Need the files? The project pack on the verdict page hands your agent the whole brief · more productivity utilities.