{"id":50501362,"url":"https://github.com/stackonehq/stack-nudge","last_synced_at":"2026-06-02T12:00:50.608Z","repository":{"id":359360560,"uuid":"1215786532","full_name":"StackOneHQ/stack-nudge","owner":"StackOneHQ","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-01T12:53:43.000Z","size":2841,"stargazers_count":7,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-01T14:25:25.375Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/StackOneHQ.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T08:55:07.000Z","updated_at":"2026-05-30T20:51:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/StackOneHQ/stack-nudge","commit_stats":null,"previous_names":["stackonehq/stack-nudge"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/StackOneHQ/stack-nudge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOneHQ%2Fstack-nudge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOneHQ%2Fstack-nudge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOneHQ%2Fstack-nudge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOneHQ%2Fstack-nudge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/StackOneHQ","download_url":"https://codeload.github.com/StackOneHQ/stack-nudge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOneHQ%2Fstack-nudge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33820643,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-02T12:00:25.190Z","updated_at":"2026-06-02T12:00:50.592Z","avatar_url":"https://github.com/StackOneHQ.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"assets/logo_full.png\" alt=\"StackNudge\" width=\"200\" /\u003e\n  \u003ch1\u003eStackNudge\u003c/h1\u003e\n  \u003cp\u003e\u003cstrong\u003eNotifications for AI coding agents.\u003c/strong\u003e\u003c/p\u003e\n  \u003cp\u003eGet a banner + sound when your agent finishes a task or pauses for your approval — step away without missing a beat.\u003c/p\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://github.com/StackOneHQ/stack-nudge/releases/latest\"\u003e\u003cimg alt=\"Latest release\" src=\"https://img.shields.io/github/v/release/StackOneHQ/stack-nudge?display_name=tag\u0026sort=semver\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/StackOneHQ/stack-nudge/actions/workflows/ci.yml\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/StackOneHQ/stack-nudge/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"MIT licensed\" src=\"https://img.shields.io/badge/license-MIT-blue.svg\"\u003e\u003c/a\u003e\n    \u003cimg alt=\"macOS 13+\" src=\"https://img.shields.io/badge/macOS-13%2B-black?logo=apple\"\u003e\n  \u003c/p\u003e\n  \u003cp\u003e\n    Maintained by \u003ca href=\"https://www.stackone.com/\"\u003eStackOne\u003c/a\u003e ·\n    \u003ca href=\"https://github.com/StackOneHQ\"\u003e@StackOneHQ\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n---\n  \u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://github.com/user-attachments/assets/c73cb65b-ff96-48bf-9371-3f1d3241c6cb\" width=\"48%\" alt=\"StackNudge banner\" /\u003e\n    \u0026nbsp;\n    \u003cimg src=\"https://github.com/user-attachments/assets/e4fecb5d-376e-44eb-a68a-220cb0b30507\" width=\"48%\" alt=\"StackNudge panel\" /\u003e\n  \u003c/p\u003e\n\n## Supports\n\n| Agent | Status |\n|-------|--------|\n| Claude Code | ✅ |\n| Cursor | ✅ |\n| Codex | ✅ |\n| Gemini CLI | ✅ † |\n| Any hooks-capable agent | ✅ — point it at `notify.sh` |\n\n† Gemini's tool-permission hook is observability-only — the banner shows the prompt, but the actual Allow / Deny click has to happen in Gemini's terminal. Claude Code, Cursor, and Codex permission events can be approved from the panel directly.\n\n**Platforms:** macOS — full app with panel, click-to-focus banners, auto-update, quota tracking, voice. Linux (PulseAudio / ALSA / libnotify) and Windows (Git Bash / WSL) get audio + basic notifications via `notify.sh` only.\n\n## Install\n\n### macOS\n\nDownload the latest release from [GitHub Releases](https://github.com/StackOneHQ/stack-nudge/releases/latest) — pick the `.tar.gz` matching your Mac's architecture (`arm64` for Apple Silicon, `x86_64` for Intel), expand, and drag `StackNudge.app` into `~/Applications/`.\n\nCLI shortcut (requires [`gh`](https://cli.github.com/)):\n\n```bash\nARCH=$(uname -m)\ngh release download --repo StackOneHQ/stack-nudge --pattern \"stack-nudge-*-macos-${ARCH}.tar.gz*\"\nshasum -a 256 -c stack-nudge-*-macos-${ARCH}.tar.gz.sha256\ntar xzf stack-nudge-*-macos-${ARCH}.tar.gz\nmv StackNudge.app ~/Applications/\nopen ~/Applications/StackNudge.app\n```\n\nOn first launch, stack-nudge runs a one-screen wizard:\n\n1. Detects which agents you have configured (`~/.claude`, `~/.cursor`, `~/.gemini`).\n2. Wires their hook configs to `notify.sh`.\n3. Registers itself + the voice engine as launchd agents so they start at login.\n\nEverything is self-contained inside the `.app` — no Xcode CLT, no Python, no shell-script bootstrap required. The bundle ships with a portable Python + stackvox (the offline voice engine) already installed. The Kokoro voice model downloads lazily the first time you enable voice notifications.\n\nSubsequent releases install automatically via the in-app auto-updater (Settings → \"Update available · vX.Y.Z\" when a new release exists).\n\n### Linux / Windows\n\nThese platforms get the audio + libnotify path only — no panel, no click-to-focus, no auto-update. The shell installer is what wires `notify.sh` into agent hooks:\n\n```bash\ngit clone https://github.com/StackOneHQ/stack-nudge.git\ncd stack-nudge\n./install.sh\n```\n\n**Prerequisites:** Python ≥ 3.10 (the bundled voice engine [stackvox](https://github.com/StackOneHQ/stackvox) requires it).\n\nThe installer auto-wires hooks for **Claude Code** (`~/.claude/settings.json`) and **Cursor** (`~/.cursor/hooks.json`). Gemini CLI and Codex are supported through the same `notify.sh` entry-point, but their hooks must be wired manually — see [Manual setup](#manual-setup) below.\n\n### From source (macOS dev)\n\nIf you're working on stack-nudge itself and want to build from source rather than download a release:\n\n```bash\ngit clone https://github.com/StackOneHQ/stack-nudge.git\ncd stack-nudge\n./install.sh\n```\n\nSame script as Linux/Windows; on macOS it additionally builds and installs the panel `.app`. Requires Xcode CLT. See [Development](#development) for the inner-loop tools.\n\n## How it works\n\nEach supported agent has a hooks system. `stack-nudge` registers these hooks:\n\n| Agent | Event | What happens |\n|-------|-------|--------------|\n| Claude Code | `Stop` | Banner when the turn ends |\n| Claude Code | `PermissionRequest` | Banner when Claude pauses for approval |\n| Cursor | `stop` | Banner when agent turn ends |\n| Gemini CLI | session end | Banner when agent finishes |\n\nThe hook calls `notify.sh \u003cagent\u003e \u003cevent\u003e`, which plays a sound and shows a banner via:\n\n1. **macOS** — the native `stack-nudge.app` (click-to-focus routes back to your editor)\n2. **Linux** — `paplay` / `aplay` / `notify-send`\n3. **Windows** — `powershell [console]::beep`\n\n### Click-to-focus (macOS)\n\nWhen you click the banner, `stack-nudge.app` uses System Events to raise the exact window that triggered the notification — even if you have multiple Cursor or terminal windows open. Supported apps:\n\n- Cursor, VS Code, Zed\n- iTerm2, Warp, Ghostty, Terminal.app\n\nIf the target app is already in focus when the notification fires, the banner is suppressed and only the sound plays.\n\n\u003e **Note on Zed:** Zed itself doesn't expose an external hook system, so stack-nudge relies on the agent's hooks (e.g. `~/.claude/settings.json` for Claude Code) firing from inside Zed's integrated terminal. Click-to-focus and frontmost-window suppression are wired up via `TERM_PROGRAM=zed`, which Zed sets automatically.\n\n### Immediate focus mode\n\nIf you'd rather have your editor focus automatically — no click needed:\n\n```bash\nexport STACKNUDGE_ACTIVATE_IMMEDIATELY=true\n```\n\nAdd that to your shell profile.\n\n### Keyboard-native panel (macOS)\n\nIf you'd rather not click banners with the mouse, stack-nudge runs a small floating panel that you summon with a hotkey. It has four tabs — **Events**, **Sessions**, **Usage**, and **Settings** — and is fully keyboard-driven.\n\nThe panel is installed and registered as a launchd agent by `./install.sh` — no opt-in needed. To run quietly without macOS banners (panel-only):\n\n```bash\nSTACKNUDGE_BANNER=false   # optional — suppress macOS banners when using the panel\n```\n\nDefault hotkey is `cmd+opt+n`. Hit it from anywhere to summon the panel; hit it again while focused to hide. Switch tabs with `Cmd+1` (Events), `Cmd+2` (Sessions), `Cmd+3` (Usage), `Cmd+4` (Settings) — or click them. Banner and panel can run together, alone, or both off — the sound and voice still fire as passive signals.\n\n#### Events tab\n\nRecent nudges in chronological order. Each shows agent, message, project name, time.\n\n| Key | Action |\n|-----|--------|\n| `↑ ↓` | Move selection |\n| `⏎` | Approve permission / focus source editor |\n| `O` | Focus source editor without approving |\n| `⌫` | Dismiss the selected nudge locally |\n| `Esc` | Hide the panel |\n\nWhen you press `⏎` on a permission event in a VS Code / Cursor terminal pane, stack-nudge walks the editor's accessibility tree to focus the right pane (matched by the agent name in the tab title) before sending Enter — so the approval keystroke lands in the agent's terminal, not whatever was last focused. Falls back gracefully if the pane can't be found.\n\n#### Sessions tab\n\nLive list of running agent processes (`claude`, `gemini`, `codex` — including node-hosted variants like `gemini-cli`). Polls every 3 seconds while visible. Sessions that exit linger for 30s with `ended Ns ago`.\n\nFor Claude Code sessions specifically, stack-nudge reads `~/.claude/sessions/\u003cpid\u003e.json` (Claude Code's per-process sidecar) to surface live data without waiting for a hook event:\n\n- **Live status dot** — yellow for `busy` (turn in flight), green for `idle` (waiting for input).\n- **Session name** from the sidecar when set to anything other than the default `main-agent` (falls back to the project name otherwise).\n- **Context-window usage** — `293K tokens · opus-4-7` beneath the project path, updated on each Claude turn.\n\nRows are ordered busy-first, then by most-recent activity. Status label reads `busy · 2 min ago` / `idle · 1 hr ago`.\n\n| Key | Action |\n|-----|--------|\n| `↑ ↓` | Move selection |\n| `⏎` | Focus the session's source terminal |\n| `n` | Rename the selected session inline |\n| `⌫` | Send SIGTERM to the agent process |\n| `Esc` | Hide the panel |\n\n#### Usage tab\n\nReachable from the tab strip or `Cmd+3`. Renders your Claude Code subscription quota — the same numbers `claude /usage` shows in the terminal — but always available without typing the command:\n\n- **Current session** (5-hour rolling window)\n- **Current week (all models)**\n- **Current week (Opus only)** *(when your plan has the tier)*\n- **Current week (Sonnet only)** *(when your plan has the tier)*\n\nBars are color-coded: green below 50%, yellow 50–80%, red 80%+. Reset times shown per tier.\n\nData is fetched from the same endpoint Claude Code's own statusline uses (`/api/oauth/usage`), reading your OAuth token from the macOS Keychain. **The first time stack-nudge polls you'll see a keychain dialog — click \"Always Allow\"** to grant access (one-time, per release).\n\nPolls every 60 seconds while the panel is visible, or every 5 minutes by default in the background (configurable via Settings → Usage → \"Poll frequency\"; underlying key `STACKNUDGE_USAGE_POLL_MIN`). On the Usage tab: `r` triggers a manual sync, `p` pauses/resumes the poller.\n\n#### Threshold-crossing notifications\n\nWhen any quota tier reaches your configured threshold, stack-nudge fires a banner — *\"Weekly quota at 85% — resets May 17\"* — once per period per tier, so you get a heads-up before hitting the cap. Configure in Settings → Usage:\n\n- **Quota tracking** — master switch for the whole feature (default on; off disables polling entirely)\n- **Quota alerts** — banner toggle (default on)\n- **Alert threshold** — 50% / 70% / 80% / 90% / 95% (default 80%)\n- **Poll frequency** — 1 / 2 / 5 / 10 / 15 / 30 min (default 5)\n\n#### Per-session context alerts\n\nIndependent of quota: stack-nudge can also fire a banner when an individual Claude Code session's context window fills past a threshold you pick. The banner names the session — *\"Context filling up — classifier-evolution-2 — at 175K tokens (opus-4-7). Consider /compact.\"* — so you know exactly which one to act on.\n\n- **Context alert at** — `Off` / 100K / 150K / 175K / 200K / 300K / 500K / 750K (default Off)\n- Absolute tokens, not a percent — Claude 4.x context windows vary (Opus/Sonnet 1M, Haiku 200K, opt-in betas), and there's no reliable way to infer the limit from the model ID alone.\n- One banner per session per threshold; the dedup re-arms whenever the session's token count drops by ≥20K (the characteristic shape of a `/compact` or `/clear`), so refilling after a compact alerts you again.\n\n#### Settings tab\n\nReachable from the tab strip or `Cmd+4`. Keyboard-driven rows for hotkey, behavior toggles (banner, mute when focused, pin panel, launch at login), sound picks (with preview-on-cycle), voice notifications + picker + speed (with preview-on-cycle using a random conversational phrase), usage config (quota tracking + alerts + threshold + poll frequency + context alert threshold), and action rows (edit phrases, check permissions, open config file, view release notes, check for updates, uninstall, quit).\n\n| Key | Action |\n|-----|--------|\n| `↑ ↓` / `Tab` | Move selection |\n| `← →` | Cycle the selected row's value (toggles flip, sounds/voices step) |\n| `⏎` | Activate (toggles flip, action rows fire, hotkey row records a new combo) |\n| `Esc` | Back to events |\n\nThe hotkey row records live: press `⏎` on it, press the new combo, and stack-nudge re-registers the global hotkey and writes it to config. If the combo is already grabbed by another app, the previous one stays and an inline error explains why.\n\nstack-nudge also watches `~/.stack-nudge/config` for external edits, so changes you make via \"Open config file…\" or another editor flow back into the running panel without a restart.\n\n### Menu bar (macOS)\n\nWhen the panel daemon is running, a bell icon appears in your menu bar. The same items you can reach from the in-panel Settings tab are mirrored here for one-click access without summoning the panel:\n\n| Item | What it does |\n|------|--------------|\n| `Hotkey · …` | Shows your current hotkey (info only) |\n| `Show banners` | Toggles `STACKNUDGE_BANNER`. Enabling fires a confirmation banner. |\n| `Voice notifications` | Toggles `STACKNUDGE_VOICE`. Enabling speaks *\"Voice notifications enabled\"*. |\n| `Show panel` | Brings the floating panel up (handy when no events are queued) |\n| `Check permissions…` | Opens the permissions checker (see below) |\n| `Open config file…` | Opens `~/.stack-nudge/config` in your default editor |\n| `Quit stack-nudge panel` | Exits the daemon |\n\nToggles re-read the live config every time the menu opens, so changes you make to `~/.stack-nudge/config` directly stay in sync. Banner and voice changes take effect immediately for the next nudge — no daemon restart needed.\n\n### Permissions (macOS)\n\nThe panel needs two privacy grants to fully work:\n\n| Permission                       | Why                                                                                                |\n|----------------------------------|----------------------------------------------------------------------------------------------------|\n| **Accessibility**                | Required for `AXIsProcessTrusted()` to return true — without it, the Enter-to-approve keystroke is silently skipped |\n| **Automation → System Events**   | Required for the AppleScript that focuses the right app and window when you act on a nudge        |\n\nOpen **Check permissions…** from the menu bar for a live status view. Each row has:\n\n- **Reset \u0026 prompt** — clears the existing TCC entry, then triggers macOS's standard grant dialog\n- **Settings** — opens System Settings to the right pane\n\nThe window is set to float above System Settings so you can grant both in one pass without losing it.\n\n#### The rebuild gotcha\n\nstack-nudge's apps are **ad-hoc signed**, so every rebuild produces a new cdhash. macOS's TCC database binds permissions to that cdhash, which means a fresh build silently invalidates prior grants — even though System Settings still *shows* the entry as \"on\". `AXIsProcessTrusted()` returns false because the running binary's hash no longer matches.\n\nIf approval has stopped working after a rebuild, hit **Reset \u0026 prompt** in the permissions checker. It runs `tccutil reset`, then triggers a fresh dialog bound to the current cdhash.\n\n### Auto-update\n\nstack-nudge polls GitHub Releases on launch and every 6 hours. When a newer release exists, the Settings tab gets a small accent dot and an \"Update available · vX.Y.Z\" row at the top of the list. Click it (or press Enter while it's selected) for a confirmation view with the release notes, then \"Update Now\" runs the install:\n\n1. Downloads the arch-appropriate `.tar.gz` artifact for your Mac (~150–200 MB)\n2. Verifies the SHA256 against the sidecar checksum file\n3. Extracts to a temp directory, strips the `com.apple.quarantine` xattr\n4. Atomic-swaps `~/Applications/stack-nudge.app` with the new bundle (keeps the old as `.app.old` for safety)\n5. Runs `launchctl kickstart -k` — the current process dies, launchd brings up the new bundle\n6. The new bundle's first launch shows a welcome-style \"Updated to vX.Y.Z\" screen with the release notes\n\nNo source clone, no swiftc rebuild on the user's machine — the new bundle is the already-signed-and-notarized artifact from CI. Updates are fast and don't disturb the user's Xcode CLT or Python install (or lack thereof).\n\nWhile the StackOne stack-nudge repo is private the auto-updater falls back to your local `gh` CLI auth (`gh api`) to read the release metadata. Org members with `gh` configured see no friction; the actual artifact download uses the release's signed asset URL.\n\n### Phrase editor\n\nThe phrase pools that power [Voice notifications](#voice-notifications) can be customised in-app. Settings → \"Edit phrases…\" opens a keyboard-driven editor where you can:\n\n- Toggle individual built-in phrases on or off (`Space`)\n- Add your own custom phrases (typed inline, `Enter` to commit)\n- Remove custom phrases (`⌫`)\n\nPer-pool customisations are stored in `~/.stack-nudge/phrases.user.json` and merged with the built-in pools at notification time. Disable a built-in phrase you find too cheery, add ones in your own voice — the same random-selection logic still applies.\n\n### Voice notifications\n\nstack-nudge uses [stackvox](https://github.com/StackOneHQ/stackvox), an offline Kokoro-82M TTS engine that speaks notifications aloud with ~13 ms latency. `./install.sh` pip-installs it from PyPI into an isolated venv at `~/.stack-nudge/venv` — no separate setup needed.\n\n**Enable in your config** (`~/.stack-nudge/config`):\n\n```bash\nSTACKNUDGE_VOICE=true\n```\n\nThe voice daemon starts automatically on first notification and is registered as a login item so it stays running across reboots.\n\nVoice fires whenever `STACKNUDGE_VOICE=true`, alongside the banner and panel surfaces. The frontmost-suppression check still applies — when the source window is already focused, sound, banner, panel post, *and* voice are all suppressed (you don't need a nudge for the thing you're looking at).\n\nWhen voice is enabled, the chime is suppressed automatically — voice replaces sound rather than playing alongside it.\n\n#### Phrasing\n\nVoice messages are picked at random from per-event phrase pools and labelled with the project name (cwd basename, with hyphens/underscores split and a few acronyms expanded — `CLI` → `C L I`, `MCP` → `M C P`, etc.). For a project called `unified-cloud-api` you'll hear things like:\n\n- **Stop:** *\"unified cloud api is ready for you\"* / *\"task complete in unified cloud api\"* / *\"output ready in unified cloud api\"*\n- **Permission:** *\"unified cloud api requires a decision\"* / *\"unified cloud api has a question for you\"* / *\"unified cloud api is awaiting approval\"*\n\nPhrase pools live in `~/.stack-nudge/phrases/` — `en.sh`, `fr.sh`, `hi.sh`, `it.sh`, `pt.sh`. The right pool is picked from the configured voice's prefix (`af_*`/`am_*`/`bf_*`/`bm_*` → en, `ff_*` → fr, `hf_*`/`hm_*` → hi, `if_*`/`im_*` → it, `pf_*`/`pm_*` → pt) so a French voice speaks French phrasing.\n\nOptional tuning (also in `~/.stack-nudge/config`):\n\n```bash\nSTACKNUDGE_VOICE_NAME=af_heart   # voice ID (run `~/.stack-nudge/venv/bin/stackvox voices` for the full list)\nSTACKNUDGE_VOICE_SPEED=1.1       # playback speed (1.0 = normal)\n```\n\n## Sounds\n\n| Event | macOS | Linux | Windows |\n|-------|-------|-------|---------|\n| Agent done | `Glass.aiff` | freedesktop bell | 800 Hz beep |\n| Waiting for approval | `Ping.aiff` | freedesktop bell | 1200 Hz beep |\n\nAny file from `/System/Library/Sounds/` works on macOS: Basso, Blow, Bottle, Frog, Funk, Glass, Hero, Morse, Ping, Pop, Purr, Sosumi, Submarine, Tink. Override per-event in `~/.stack-nudge/config`:\n\n```bash\nSTACKNUDGE_SOUND_STOP=Glass\nSTACKNUDGE_SOUND_PERMISSION=Ping\n```\n\nThe Settings tab exposes the same picks with audio preview on each change.\n\n## Uninstall\n\n### macOS — in-app\n\nOpen the panel (`⌘⌥N`), go to **Settings → Uninstall stack-nudge…**, confirm. The app tears down:\n\n- Hook entries in `~/.claude/settings.json`, `~/.cursor/hooks.json`, and `~/.gemini/settings.json`\n- The launchd agents (`com.stackonehq.stack-nudge`, `…-daemon`)\n- `~/.stack-nudge/` (config, `notify.sh`, phrases)\n- Moves `stack-nudge.app` to Trash and quits\n\nSettings (config, the cached Kokoro voice model in `~/.cache/huggingface/`, your macOS keychain entry for Claude Code) are not touched.\n\n### Linux / Windows / fallback\n\n```bash\ngit pull        # if you cloned a while back — older uninstall.sh lacks hook cleanup\n./uninstall.sh\n```\n\nSame set of cleanups as the in-app path, useful when the .app isn't reachable or the in-app uninstall failed mid-flight.\n\n## Manual setup\n\nClaude Code, Cursor, Codex, and Gemini CLI are auto-wired by the first-launch wizard. For other hooks-capable agents (or to integrate from a custom script), all you need is to invoke `notify.sh \u003cagent-label\u003e \u003cevent\u003e` from wherever your agent emits lifecycle events. `\u003cevent\u003e` should be `stop` (agent finished a turn) or `permission` (waiting for approval); `\u003cagent-label\u003e` can be anything — it just controls the banner title.\n\nExample block in any agent's hooks config:\n\n```json\n{\n  \"hooks\": {\n    \"Stop\": [\n      {\n        \"matcher\": \"\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"$HOME/.stack-nudge/notify.sh my-agent stop\", \"timeout\": 30 }\n        ]\n      }\n    ]\n  }\n}\n```\n\n## Development\n\n```bash\nmake build      # builds stack-nudge.app into build/\nmake install    # full install (build + copy + register hooks + launchd)\nmake reload     # rebuild + replace installed app + refresh notify.sh + bounce the daemon\nmake dev        # watch sources; auto-reload on .swift / Info.plist / notify.sh / phrase changes\nmake uninstall  # remove app, hooks, launchd agents, ~/.stack-nudge/\n```\n\n`make dev` is the inner-loop tool — leave it running in another terminal, save a Swift file or `notify.sh`, and the daemon bounces with the new build in ~2 seconds.\n\nSource layout:\n\n- `panel/` — the single persistent `stack-nudge.app` binary: hotkey, floating NSPanel, socket listener for incoming events, macOS banner posting via `UNUserNotificationCenter`, sessions list, settings, permissions window, auto-updater, quota probe\n- `shared/` — code shared with the standalone Linux/Windows surfaces (currently `AppActivator.swift`)\n- `phrases/` — per-language voice phrase pools sourced by `notify.sh` at hook time\n- `notify.sh` — the shell entry-point CC/Cursor/Gemini hooks invoke; on macOS posts events to the running app via Unix-domain socket, on Linux/Windows handles audio + libnotify directly\n\nSwift compiled with `swiftc` directly. No Xcode, no SPM, no dependencies.\n\n## Terms of use\n\nBy using stack-nudge or its source code, you agree to the following:\n\n- Use is governed by the [MIT License](LICENSE). The software is provided \"as is\", without warranty of any kind.\n- Participation in the project (issues, pull requests, discussions) is subject to the [Code of Conduct](CODE_OF_CONDUCT.md).\n- Suspected security vulnerabilities should be reported privately per [SECURITY.md](SECURITY.md), not via public issues.\n- The project is maintained by [StackOne](https://www.stackone.com/); contributions remain under the licence the contributor submits them under (MIT unless otherwise noted).\n\n## License\n\nMIT — see [LICENSE](LICENSE) for the full text. Copyright © 2026 StackOne Technologies Ltd. and contributors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstackonehq%2Fstack-nudge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstackonehq%2Fstack-nudge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstackonehq%2Fstack-nudge/lists"}