Files

109 lines
4.3 KiB
PowerShell

# 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.'