commit e7e5310ef30a6611f02cbdf5dae8639ac1853ca5 Author: weishaupt <1+weishaupt@noreply.localhost> Date: Fri Oct 23 01:56:58 2026 +0000 Add Nudge setup, removal and README diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1bf65c --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Nudge + +Get a phone notification when Claude needs you. + +If you step away while Claude works, it can stall on a permission prompt, a folder it needs access to, or a yes before something big. Nudge sends a short message to your phone through [ntfy](https://ntfy.sh) so you know to come back. There are two parts. + +**Claude Code on your PC.** A hook pings your phone whenever Claude Code stops to ask you something. + +**The Claude chat app.** A skill tells Claude to send you a specific message before it needs something, like "I need access to C:\wallpapers\youtube". + +The ntfy app is free, needs no account, and delivers messages instantly, even with the screen off. + +## Set up + +1. Download Nudge.zip from the Releases page and extract it. +2. Double-click **Setup.bat**. It makes a private topic name for you and adds the hook to your Claude Code settings. Your existing settings are kept, and a backup is saved next to the file. +3. Install the **ntfy** app on your phone. Tap **+**, type the topic name Setup showed you, leave "use another server" unchecked, tick "instant delivery in doze mode", and subscribe. +4. Allow notifications for ntfy. On Android, set ntfy's battery option to **Unrestricted**, or delivery can be delayed when the screen is off. +5. Back in Setup, press Enter to send a test message. It should pop up on your phone. +6. Restart Claude Code. + +## The chat app skill + +Setup also writes **MY-SKILL.md** with your topic filled in. Add it to Claude as a skill, or paste its contents into your Claude instructions. After that, Claude will send you a message before it needs something from you. + +## Good to know + +- Claude Code only sends the notification when it actually stops to ask. If your session is set to approve everything automatically, there is nothing to notify you about. +- Keep your topic name private. Anyone who knows it can send messages to your phone, and the messages pass through the public ntfy.sh server. Do not put passwords or private data in them. +- Claude's own mobile push needs Remote Control, which only works for sessions running on your own computer. Nudge works without it. +- Windows 10 and 11 include curl.exe, which is all Nudge needs. + +## Remove + +Double-click **Remove.bat**. It takes the hook out of your settings and leaves everything else alone. + +## Files + +| File | What it does | +| --- | --- | +| Setup.bat, Setup.ps1 | Makes your topic, adds the hook, writes MY-SKILL.md, sends a test | +| Remove.bat, Remove.ps1 | Removes the hook | +| chat-skill\SKILL.md | Template for the chat app skill | + +Nudge is not affiliated with ntfy or Anthropic. diff --git a/Remove.bat b/Remove.bat new file mode 100644 index 0000000..3cc2530 --- /dev/null +++ b/Remove.bat @@ -0,0 +1,5 @@ +@echo off +title Nudge removal +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Remove.ps1" +echo. +pause diff --git a/Remove.ps1 b/Remove.ps1 new file mode 100644 index 0000000..15c739b --- /dev/null +++ b/Remove.ps1 @@ -0,0 +1,42 @@ +# Nudge removal. Run it with Remove.bat. +param( + [string]$SettingsPath = (Join-Path $env:USERPROFILE '.claude\settings.json') +) +$ErrorActionPreference = 'Stop' +$marker = 'ntfy.sh/nudge-' +$removed = 0 + +if (Test-Path -LiteralPath $SettingsPath) { + $raw = Get-Content -LiteralPath $SettingsPath -Raw + $settings = $null + try { $settings = $raw | ConvertFrom-Json } catch { } + if ($null -eq $settings) { + Write-Host 'Could not read settings.json, so nothing was changed.' + exit 1 + } + if ($settings.PSObject.Properties.Name -contains 'hooks' -and + $settings.hooks.PSObject.Properties.Name -contains 'Notification') { + $keep = @() + foreach ($entry in @($settings.hooks.Notification)) { + $isNudge = $false + foreach ($h in @($entry.hooks)) { + if ($h.command -and $h.command.ToString().Contains($marker)) { $isNudge = $true } + } + if ($isNudge) { $removed++ } else { $keep += $entry } + } + if ($keep.Count -gt 0) { + $settings.hooks.Notification = @($keep) + } else { + $settings.hooks.PSObject.Properties.Remove('Notification') + } + if (@($settings.hooks.PSObject.Properties).Count -eq 0) { + $settings.PSObject.Properties.Remove('hooks') + } + $json = $settings | ConvertTo-Json -Depth 20 + [System.IO.File]::WriteAllText($SettingsPath, $json, (New-Object System.Text.UTF8Encoding($false))) + } +} +$topicFile = Join-Path (Split-Path -Parent $SettingsPath) 'nudge-topic.txt' +if (Test-Path -LiteralPath $topicFile) { Remove-Item -LiteralPath $topicFile -Force } +Write-Host ('Removed ' + $removed + ' Nudge hook(s). Other settings were left alone.') +Write-Host 'You can also unsubscribe from the topic in the ntfy app.' diff --git a/Setup.bat b/Setup.bat new file mode 100644 index 0000000..bf0a2b0 --- /dev/null +++ b/Setup.bat @@ -0,0 +1,5 @@ +@echo off +title Nudge setup +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Setup.ps1" +echo. +pause diff --git a/Setup.ps1 b/Setup.ps1 new file mode 100644 index 0000000..23051b9 --- /dev/null +++ b/Setup.ps1 @@ -0,0 +1,108 @@ +# Nudge setup. Run it with Setup.bat. +# Adds a Claude Code hook that sends a phone notification through ntfy.sh +# whenever Claude Code stops to ask you something. +param( + [string]$SettingsPath = (Join-Path $env:USERPROFILE '.claude\settings.json'), + [string]$KitFolder = $PSScriptRoot, + [switch]$NoTest +) +$ErrorActionPreference = 'Stop' + +function New-Topic { + $chars = 'abcdefghijklmnopqrstuvwxyz0123456789' + $bytes = New-Object byte[] 16 + $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() + $rng.GetBytes($bytes) + $rng.Dispose() + $s = '' + foreach ($b in $bytes) { $s += $chars[$b % $chars.Length] } + return 'nudge-' + $s +} + +$marker = 'ntfy.sh/' +$dir = Split-Path -Parent $SettingsPath +if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } +$topicFile = Join-Path $dir 'nudge-topic.txt' + +# Keep the same topic if Setup is run twice. +if (Test-Path -LiteralPath $topicFile) { + $topic = (Get-Content -LiteralPath $topicFile -Raw).Trim() +} else { + $topic = New-Topic + [System.IO.File]::WriteAllText($topicFile, $topic, (New-Object System.Text.UTF8Encoding($false))) +} + +$command = 'curl.exe -s -H "Title: Claude Code" -d "Needs your attention" ' + $marker + $topic + +# Read existing settings, or start fresh. +$settings = $null +if (Test-Path -LiteralPath $SettingsPath) { + $raw = Get-Content -LiteralPath $SettingsPath -Raw + if ($raw -and $raw.Trim().Length -gt 0) { + try { $settings = $raw | ConvertFrom-Json } + catch { + Write-Host 'Your settings.json could not be read, so Nudge did not touch it.' + Write-Host 'Nothing was changed. Fix or move that file and run Setup again.' + exit 1 + } + Copy-Item -LiteralPath $SettingsPath -Destination ($SettingsPath + '.nudge-backup') -Force + } +} +if ($null -eq $settings) { $settings = New-Object PSObject } + +if (-not ($settings.PSObject.Properties.Name -contains 'hooks')) { + $settings | Add-Member -NotePropertyName 'hooks' -NotePropertyValue (New-Object PSObject) +} +$hooks = $settings.hooks +if (-not ($hooks.PSObject.Properties.Name -contains 'Notification')) { + $hooks | Add-Member -NotePropertyName 'Notification' -NotePropertyValue @() +} + +# Drop any earlier Nudge entry, keep everything else. +$keep = @() +foreach ($entry in @($hooks.Notification)) { + $isNudge = $false + foreach ($h in @($entry.hooks)) { + if ($h.command -and $h.command.ToString().Contains($marker + 'nudge-')) { $isNudge = $true } + } + if (-not $isNudge) { $keep += $entry } +} +$new = [PSCustomObject]@{ + matcher = '' + hooks = @([PSCustomObject]@{ type = 'command'; command = $command }) +} +$hooks.Notification = @($keep + $new) + +$json = $settings | ConvertTo-Json -Depth 20 +[System.IO.File]::WriteAllText($SettingsPath, $json, (New-Object System.Text.UTF8Encoding($false))) + +# Make a ready-to-use chat skill with the topic filled in. +$template = Join-Path $KitFolder 'chat-skill\SKILL.md' +if (Test-Path -LiteralPath $template) { + $out = Join-Path $KitFolder 'MY-SKILL.md' + $text = (Get-Content -LiteralPath $template -Raw).Replace('YOUR-TOPIC', $topic) + [System.IO.File]::WriteAllText($out, $text, (New-Object System.Text.UTF8Encoding($false))) +} + +Write-Host '' +Write-Host 'Nudge is set up.' +Write-Host '' +Write-Host 'Your private topic name is:' +Write-Host (' ' + $topic) +Write-Host 'Keep it to yourself. Anyone who knows it can send messages to your phone.' +Write-Host '' +Write-Host 'Next, on your phone:' +Write-Host ' 1. Install the ntfy app.' +Write-Host ' 2. Tap +, type the topic name above, leave "use another server" unchecked,' +Write-Host ' and tick "instant delivery in doze mode". Tap Subscribe.' +Write-Host ' 3. Allow notifications, and set the ntfy battery option to Unrestricted.' +Write-Host '' +if (-not $NoTest) { + $a = Read-Host 'Subscribed? Press Enter to send a test message (or type N to skip)' + if ($a -notmatch '^[Nn]') { + & curl.exe -s -H 'Title: Nudge' -d 'It works. This is a test from Nudge.' ($marker + $topic) | Out-Null + Write-Host 'Test sent. It should show up on your phone in a moment.' + } +} +Write-Host '' +Write-Host 'Restart Claude Code so it loads the hook.'