Repository navigation
feat: notify a person when a terminal finishes, including Web Push - #123
Merged
Merged
Conversation
`nightcrow notify [MESSAGE]`, run inside a pane, sends the pane's NIGHTCROW_PANE_TOKEN to the daemon as a one-shot request. The daemon asks each repository's hub which live pane holds the token; that hub broadcasts an `attention` frame, which now carries an optional message. Hubs also publish these to a session-wide `Notices` registry, for listeners not attached to the repository (Web Push, next). Entering a plugin's `needs_attention` recovery state is published the same way, once per entry. The web viewer decodes `attention` and shows the message as a toast. The TUI keeps its project-tab marker and ignores the message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K9Y6nr4J3kMp6GVQNUd9Ry
`web::viewer::push` listens to the session's `Notices` and sends each as a
Web Push message to every subscribed browser. One background thread
encrypts to the browser's key (RFC 8291 aes128gcm, checked against the
RFC's worked example), signs a VAPID token (RFC 8292 ES256) and posts over
https with ureq. Notices for one pane are coalesced for 10 seconds, and a
404/410 answer drops the subscription.
The key and subscriptions live owner-only in ~/.nightcrow/push. New
authenticated routes: GET /api/push/key and POST /api/push/{subscribe,
unsubscribe,status,test}. An endpoint must be https on a public host name;
there is no push-service allowlist, since anyone able to subscribe can
already run shell commands here.
Adds p256, hkdf, aes-gcm and base64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K9Y6nr4J3kMp6GVQNUd9Ry
A loopback-bound viewer refuses any Host that is not a loopback name, to stop DNS rebinding. A local HTTPS proxy such as `tailscale serve` forwards the browser's host name (machine.tailnet.ts.net), so it was refused too, leaving off-loopback binding as the only way to put HTTPS in front. `[web_viewer] allowed_hosts` lists names the loopback check also accepts, matched without the port and ignoring case. A listed name stays safe from rebinding because the operator controls what it resolves to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K9Y6nr4J3kMp6GVQNUd9Ry
scripts/visual/Dockerfile copies nothing from the checkout, which is bind-mounted at run time, but `docker build .` still uploaded the whole tree, including a multi-gigabyte target/. Ignore everything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K9Y6nr4J3kMp6GVQNUd9Ry
A service worker (public/sw.js) shows each push and, when tapped, opens the viewer with `notice-repo`/`notice-pane` in the address. The page applies that once at startup: it remembers the pane as the project's last one and puts the project in front before its first poll, so the usual attach focus selects it. The worker caches nothing and answers no fetch. The settings menu gains a Notifications section: a per-browser switch, a test button, and the reason when push cannot be offered (not a secure page, iOS outside a Home Screen app, permission blocked, no browser support, or no daemon key). Permission is asked only from that switch. A subscription the daemon has dropped is dropped in the browser on the next read, so the switch does not claim "on" falsely. docs/notifications.md covers enabling it, `tailscale serve` with `allowed_hosts`, iOS, and why notifications may not arrive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K9Y6nr4J3kMp6GVQNUd9Ry
code0xff
force-pushed
the
feat/web-push-notifications
branch
from
October 29, 2026 01:31
1af5d67 to
e008143
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Let a terminal pane tell its person that work is done, and reach that person on a phone whose browser is closed, without installing a separate app.
Changes (one commit each, each builds and tests on its own)
nightcrow notify [MESSAGE]: run inside a pane (for example from a Claude CodeStophook or Codexnotify). It identifies the pane byNIGHTCROW_PANE_TOKENthrough a one-shot daemon request. The hub broadcastsattentionwith an optionalmessage. Hubs also publish to a new session-wideNoticesregistry, as does entering the recovery plugin'sneeds_attentionstate (once per entry). The viewer shows the message as a toast; the TUI keeps its tab marker.web::viewer::push): RFC 8291aes128gcmencryption to the browser's key, checked byte for byte against the RFC's worked example. RFC 8292 VAPID ES256 signing. One sender thread with a 64-entry queue and a 10 s per-pane coalescing window; 404/410 drops the subscription. The key and subscriptions are stored owner-only in~/.nightcrow/push. Routes:GET /api/push/key,POST /api/push/{subscribe,unsubscribe,status,test}. An endpoint must be https on a public host name. There is deliberately no push-service allowlist: Samsung Internet's push host could not be confirmed, and anyone able to subscribe can already run shell commands here.[web_viewer] allowed_hosts: a loopback-bound viewer refused the*.ts.netHost thattailscale serveforwards. Listed names now pass the rebinding check..dockerignore: the visual-test image copies nothing from the checkout, butdocker build .uploaded a 22 GBtarget/.New dependencies:
p256,hkdf,aes-gcm,base64. Docs: newdocs/notifications.md, plusconfiguration.md,web-viewer.md,architecture/web.mdandplugin-host.md.Verification
cargo test --workspaceandcargo clippy --workspace --all-targets --all-features -D warningspass. The suite also passes at each intermediate commit.npm test954 passed,tsc -b, lint, and build. Visual regressions 7/7 in the pinned Docker runtime.nightcrow notify build donetyped into a pane showed the toast.tailscale serve, and iOS needs a Home Screen install.tailscale serveHost behaviour is taken from reports, not tested here.🤖 Generated with Claude Code
https://claude.ai/code/session_01K9Y6nr4J3kMp6GVQNUd9Ry