feat: web push notifications (VAPID)

- push_utils.py: subscription storage + send helper (auto-prunes 410 endpoints)
- routers/push.py: GET /api/push/vapid-key (public), POST/DELETE /api/push/subscribe
- sw.js: push event listener shows notification; notificationclick focuses/opens tab
- app.js: subscribe/unsubscribe flow + "Enable notifications" toggle in settings dropdown
- tools/notify.py: web_push orchestrator tool (user-level, no admin required)
- VAPID keys in .env; pywebpush added to requirements.txt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-05 19:38:58 -04:00
parent 0b96772fa6
commit ddf44a2aee
14 changed files with 350 additions and 13 deletions

View File

@@ -63,7 +63,7 @@ from tools.scratch import (
scratch_append as _scratch_append,
scratch_clear as _scratch_clear,
)
from tools.notify import nc_talk_send as _nc_talk_send, email_send as _email_send
from tools.notify import nc_talk_send as _nc_talk_send, email_send as _email_send, web_push as _web_push
# ── Declaration imports ───────────────────────────────────────────────────────
@@ -89,7 +89,7 @@ TOOL_CATEGORIES: dict[str, list[str]] = {
"Cron": ["cron_list", "cron_add", "cron_remove", "cron_toggle"],
"Reminders": ["reminders_add", "reminders_list", "reminders_remove", "reminders_clear"],
"Scratchpad": ["scratch_read", "scratch_write", "scratch_append", "scratch_clear"],
"Notifications": ["email_send", "nc_talk_send"],
"Notifications": ["web_push", "email_send", "nc_talk_send"],
"Aether Journals": [
"ae_journal_list", "ae_journal_search",
"ae_journal_entries_list", "ae_journal_entry_read",
@@ -142,6 +142,7 @@ _CALLABLES: dict[str, callable] = {
"scratch_clear": _scratch_clear,
"email_send": _email_send,
"nc_talk_send": _nc_talk_send,
"web_push": _web_push,
}
# ── Role-based access control ─────────────────────────────────────────────────