Thanks for your interest in making stack-nudge better. This doc covers local setup, how to run the app during development, and conventions for commits and PRs.
git clone https://github.com/StackOneHQ/stack-nudge.git
cd stack-nudge
./install.sh # builds + installs the .apps, registers hooks, sets up the voice venvinstall.sh is idempotent — re-run it any time after pulling changes to refresh the installed binaries and notify.sh.
System dependencies:
- macOS — Swift 5.9+ via the Xcode Command Line Tools (
xcode-select --install). PortAudio ships with the system; voice will additionally pullstackvoxfrom PyPI into a venv at~/.stack-nudge/venv. - Linux — Bash,
paplay/aplay/notify-sendfor sound notifications. The Swift panel app is macOS-only; on Linux only thenotify.shflow runs.
make build # builds both .app bundles into build/
make install # full install (build + copy + register hooks + launchd)
make reload # rebuild + replace installed panel + refresh notify.sh + bounce daemon
make dev # watch sources; auto-reload on .swift / Info.plist / notify.sh / phrase changes
make uninstall # remove apps, hooks, launchd agents, ~/.stack-nudge/make dev is the main inner-loop tool. Leave it running in another terminal while you edit Swift files or notify.sh — the daemon bounces with the new build in ~2 seconds.
The Swift sources are compiled directly with swiftc for the shipping binaries — no Xcode project, no third-party Swift dependencies. There is a Package.swift manifest, but it exists only so swift test can run a unit-test suite over the testable parts of the panel; the apps themselves are still built by build.sh.
make test # equivalent to `swift test`Tests live in Tests/StackNudgePanelCoreTests/ and cover the pure-logic surfaces: Hotkey.parse / encode, ConfigFile.parse / apply / bool, NudgeKind, and EventStore.
swift test on macOS needs XCTest, which only ships with full Xcode. If you only have the Command Line Tools installed (xcode-select -p returns /Library/Developer/CommandLineTools), install Xcode and run:
sudo xcode-select -s /Applications/Xcode.app/Contents/DeveloperCI runs the suite on every push and PR — swift test is one of the required checks.
notifier/— the transient banner-renderer that fires per nudge and exitspanel/— the persistent floating-panel daemon (hotkey, NSPanel, socket listener, menu bar, sessions list, settings, permissions window)shared/— code shared by both binaries (currentlyAppActivator.swift)phrases/— per-language voice phrase pools sourced bynotify.shat hook timenotify.sh— the shell entry-point CC/Cursor/Gemini hooks invoke; routes events to banner / panel / voice surfacesassets/— logo / icon source files
The panel daemon needs Accessibility and Automation → System Events to send the approval keystroke. Each make reload re-signs the binary with a new ad-hoc signature, which invalidates prior TCC grants — System Settings still shows the entry as "on" but AXIsProcessTrusted() returns false. The Permissions checker (menu bar → Check permissions…) has a Reset & prompt button that runs tccutil reset and triggers a fresh dialog bound to the current cdhash. Use it whenever approval stops working after a build.
- Branch names:
<type>/<short-topic>(e.g.feat/voice-phrases,fix/permissions-crash,chore/oss-readiness). - Commit messages follow Conventional Commits:
feat: add Xfix: handle Ydocs: update READMErefactor: extract panel componentstest: cover hotkey parserchore: bump dependencies
- Prefer small, focused commits over large omnibus ones. The exception is genuinely-coupled work that has to land together; in that case, structure the commit body to explain the coupling.
-
make buildsucceeds locally and you've manually verified the change withmake reload. - No new compiler warnings in your changed files (existing
NSUserNotificationdeprecation warnings innotifier/are pre-existing — leave them). - README updated if user-visible behavior changed.
- No credentials, API keys, or PII in the diff.
- Branch is rebased on the latest
mainif it's been more than a day.
- Swift: keep the no-Xcode / no-SPM constraint. New files go under
panel/,notifier/, orshared/and are added to the explicit file list inbuild.sh. No third-party Swift dependencies — the value of the build's simplicity outweighs the convenience of pulling libraries. - Shell:
notify.shand thephrases/*.share POSIX-bash. Usebash -nto syntax-check before committing. Avoidbashismsthat aren't strictly necessary. - Filesystem paths should resolve relative to the script (
BASH_SOURCEfor shell,NSHomeDirectory()/~/.stack-nudge/...for Swift) rather than being hardcoded against$HOMEor/Users/.... - Comments explain the why — particularly for AppKit / AX edge cases that aren't obvious from the code. Don't comment what the code already says.
Do not file public GitHub issues for security vulnerabilities — see SECURITY.md.