agent-connect 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-build-manifest.json +26 -0
  3. package/.next/app-path-routes-manifest.json +1 -0
  4. package/.next/build-manifest.json +32 -0
  5. package/.next/export-marker.json +1 -0
  6. package/.next/images-manifest.json +1 -0
  7. package/.next/next-minimal-server.js.nft.json +1 -0
  8. package/.next/next-server.js.nft.json +1 -0
  9. package/.next/package.json +1 -0
  10. package/.next/prerender-manifest.js +1 -0
  11. package/.next/prerender-manifest.json +1 -0
  12. package/.next/react-loadable-manifest.json +1 -0
  13. package/.next/required-server-files.json +1 -0
  14. package/.next/routes-manifest.json +1 -0
  15. package/.next/server/app/_not-found/page.js +1 -0
  16. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  18. package/.next/server/app/_not-found.html +1 -0
  19. package/.next/server/app/_not-found.meta +6 -0
  20. package/.next/server/app/_not-found.rsc +9 -0
  21. package/.next/server/app/api/config/route.js +1 -0
  22. package/.next/server/app/api/config/route.js.nft.json +1 -0
  23. package/.next/server/app/api/notify/route.js +4 -0
  24. package/.next/server/app/api/notify/route.js.nft.json +1 -0
  25. package/.next/server/app/api/proxy/[...path]/route.js +1 -0
  26. package/.next/server/app/api/proxy/[...path]/route.js.nft.json +1 -0
  27. package/.next/server/app/api/subscribe/route.js +1 -0
  28. package/.next/server/app/api/subscribe/route.js.nft.json +1 -0
  29. package/.next/server/app/api/unsubscribe/route.js +1 -0
  30. package/.next/server/app/api/unsubscribe/route.js.nft.json +1 -0
  31. package/.next/server/app/index.html +3 -0
  32. package/.next/server/app/index.meta +5 -0
  33. package/.next/server/app/index.rsc +7 -0
  34. package/.next/server/app/page.js +3 -0
  35. package/.next/server/app/page.js.nft.json +1 -0
  36. package/.next/server/app/page_client-reference-manifest.js +1 -0
  37. package/.next/server/app-paths-manifest.json +9 -0
  38. package/.next/server/chunks/471.js +2 -0
  39. package/.next/server/chunks/682.js +6 -0
  40. package/.next/server/chunks/948.js +2 -0
  41. package/.next/server/chunks/972.js +12 -0
  42. package/.next/server/chunks/font-manifest.json +1 -0
  43. package/.next/server/font-manifest.json +1 -0
  44. package/.next/server/functions-config-manifest.json +1 -0
  45. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  46. package/.next/server/middleware-build-manifest.js +1 -0
  47. package/.next/server/middleware-manifest.json +6 -0
  48. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  49. package/.next/server/next-font-manifest.js +1 -0
  50. package/.next/server/next-font-manifest.json +1 -0
  51. package/.next/server/pages/404.html +1 -0
  52. package/.next/server/pages/500.html +1 -0
  53. package/.next/server/pages/_app.js +1 -0
  54. package/.next/server/pages/_app.js.nft.json +1 -0
  55. package/.next/server/pages/_document.js +1 -0
  56. package/.next/server/pages/_document.js.nft.json +1 -0
  57. package/.next/server/pages/_error.js +1 -0
  58. package/.next/server/pages/_error.js.nft.json +1 -0
  59. package/.next/server/pages-manifest.json +1 -0
  60. package/.next/server/server-reference-manifest.js +1 -0
  61. package/.next/server/server-reference-manifest.json +1 -0
  62. package/.next/server/webpack-runtime.js +1 -0
  63. package/.next/static/chunks/23-effea5b940252795.js +2 -0
  64. package/.next/static/chunks/app/_not-found/page-94c683a891433c6f.js +1 -0
  65. package/.next/static/chunks/app/layout-9a44cf92d0cc38f4.js +1 -0
  66. package/.next/static/chunks/app/page-21abf84ee85e2de6.js +1 -0
  67. package/.next/static/chunks/fd9d1056-62aaf4b921c84028.js +1 -0
  68. package/.next/static/chunks/framework-f66176bb897dc684.js +33 -0
  69. package/.next/static/chunks/main-476fa195f30b59a4.js +1 -0
  70. package/.next/static/chunks/main-app-668219266b78f0b2.js +1 -0
  71. package/.next/static/chunks/pages/_app-6a626577ffa902a4.js +1 -0
  72. package/.next/static/chunks/pages/_error-1be831200e60c5c0.js +1 -0
  73. package/.next/static/chunks/polyfills-78c92fac7aa8fdd8.js +1 -0
  74. package/.next/static/chunks/webpack-889906d2d9470bce.js +1 -0
  75. package/.next/static/css/11741012df4dbecf.css +3 -0
  76. package/.next/static/izMHYLE7eERU7ILgJJ2ug/_buildManifest.js +1 -0
  77. package/.next/static/izMHYLE7eERU7ILgJJ2ug/_ssgManifest.js +1 -0
  78. package/.next/types/app/api/config/route.ts +343 -0
  79. package/.next/types/app/api/notify/route.ts +343 -0
  80. package/.next/types/app/api/proxy/[...path]/route.ts +343 -0
  81. package/.next/types/app/api/subscribe/route.ts +343 -0
  82. package/.next/types/app/api/unsubscribe/route.ts +343 -0
  83. package/.next/types/app/layout.ts +79 -0
  84. package/.next/types/app/page.ts +79 -0
  85. package/.next/types/package.json +1 -0
  86. package/LICENSE +21 -0
  87. package/README.md +81 -0
  88. package/app/api/config/route.ts +24 -0
  89. package/app/api/notify/route.ts +79 -0
  90. package/app/api/proxy/[...path]/route.ts +45 -0
  91. package/app/api/subscribe/route.ts +26 -0
  92. package/app/api/unsubscribe/route.ts +32 -0
  93. package/app/globals.css +8 -0
  94. package/app/layout.tsx +37 -0
  95. package/app/page.tsx +432 -0
  96. package/bin/agent-connect.js +199 -0
  97. package/bin/claude-hook.js +60 -0
  98. package/bin/config.js +60 -0
  99. package/bin/setup.js +193 -0
  100. package/bin/start.js +148 -0
  101. package/lib/subscriptions.ts +58 -0
  102. package/lib/webpush.ts +44 -0
  103. package/next.config.js +14 -0
  104. package/package.json +77 -0
  105. package/postcss.config.js +6 -0
  106. package/public/icon-192.png +0 -0
  107. package/public/icon-512.png +0 -0
  108. package/public/manifest.json +24 -0
  109. package/public/push-sw.js +92 -0
  110. package/public/sw.js +1 -0
  111. package/public/workbox-4754cb34.js +1 -0
  112. package/public/worker-izMHYLE7eERU7ILgJJ2ug.js +1 -0
  113. package/server-nextjs.js +60 -0
  114. package/server.js +316 -0
  115. package/tailwind.config.js +11 -0
  116. package/tsconfig.json +26 -0
  117. package/worker/index.js +93 -0
@@ -0,0 +1,92 @@
1
+ // Minimal push-only service worker for Agent Connect
2
+ // No workbox, no precaching — just push notification handling
3
+
4
+ self.addEventListener('install', () => {
5
+ self.skipWaiting();
6
+ });
7
+
8
+ self.addEventListener('activate', (event) => {
9
+ event.waitUntil(self.clients.claim());
10
+ });
11
+
12
+ // Push event - handle incoming push notifications
13
+ self.addEventListener('push', (event) => {
14
+ const promise = (async () => {
15
+ try {
16
+ let data = {
17
+ title: 'Agent Connect',
18
+ body: 'New notification',
19
+ icon: '/icon-192.png',
20
+ badge: '/icon-192.png',
21
+ type: 'completed',
22
+ };
23
+
24
+ if (event.data) {
25
+ try {
26
+ data = { ...data, ...event.data.json() };
27
+ } catch (e) {
28
+ data.body = event.data.text();
29
+ }
30
+ }
31
+
32
+ const typeConfig = {
33
+ completed: { tag: 'completed', vibrate: [100, 50, 100] },
34
+ input_needed: { tag: 'input_needed', vibrate: [200, 100, 200, 100, 200] },
35
+ error: { tag: 'error', vibrate: [500, 200, 500] },
36
+ };
37
+
38
+ const config = typeConfig[data.type] || typeConfig.completed;
39
+
40
+ const options = {
41
+ body: data.body,
42
+ icon: data.icon,
43
+ badge: data.badge,
44
+ tag: config.tag,
45
+ vibrate: config.vibrate,
46
+ data: {
47
+ url: '/',
48
+ type: data.type,
49
+ ...data.data,
50
+ },
51
+ requireInteraction: data.type === 'input_needed',
52
+ };
53
+
54
+ await self.registration.showNotification(data.title, options);
55
+ } catch (err) {
56
+ console.error('Push handler error:', err);
57
+ await self.registration.showNotification('Push Error', {
58
+ body: err.message || String(err),
59
+ tag: 'push-error',
60
+ });
61
+ }
62
+ })();
63
+
64
+ event.waitUntil(promise);
65
+ });
66
+
67
+ // Notification click event - handle user interaction
68
+ self.addEventListener('notificationclick', (event) => {
69
+ event.notification.close();
70
+
71
+ if (event.action === 'dismiss') {
72
+ return;
73
+ }
74
+
75
+ event.waitUntil(
76
+ clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
77
+ for (const client of clientList) {
78
+ if (client.url.includes(self.location.origin) && 'focus' in client) {
79
+ return client.focus();
80
+ }
81
+ }
82
+ if (clients.openWindow) {
83
+ return clients.openWindow('/');
84
+ }
85
+ })
86
+ );
87
+ });
88
+
89
+ // Notification close event
90
+ self.addEventListener('notificationclose', (event) => {
91
+ // no-op, can be used for analytics
92
+ });
package/public/sw.js ADDED
@@ -0,0 +1 @@
1
+ if(!self.define){let e,s={};const n=(n,i)=>(n=new URL(n+".js",i).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(i,t)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(s[a])return;let c={};const r=e=>n(e,a),o={module:{uri:a},exports:c,require:r};s[a]=Promise.all(i.map(e=>o[e]||r(e))).then(e=>(t(...e),c))}}define(["./workbox-4754cb34"],function(e){"use strict";importScripts("worker-izMHYLE7eERU7ILgJJ2ug.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"43c8650db5770fc4f168bfdacc3827da"},{url:"/_next/static/chunks/23-effea5b940252795.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/app/_not-found/page-94c683a891433c6f.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/app/layout-9a44cf92d0cc38f4.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/app/page-21abf84ee85e2de6.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/fd9d1056-62aaf4b921c84028.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/framework-f66176bb897dc684.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/main-476fa195f30b59a4.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/main-app-668219266b78f0b2.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/pages/_app-6a626577ffa902a4.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/pages/_error-1be831200e60c5c0.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js",revision:"79330112775102f91e1010318bae2bd3"},{url:"/_next/static/chunks/webpack-889906d2d9470bce.js",revision:"izMHYLE7eERU7ILgJJ2ug"},{url:"/_next/static/css/11741012df4dbecf.css",revision:"11741012df4dbecf"},{url:"/_next/static/izMHYLE7eERU7ILgJJ2ug/_buildManifest.js",revision:"2ec694eb52ae4f523f265a46bae4d768"},{url:"/_next/static/izMHYLE7eERU7ILgJJ2ug/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/icon-192.png",revision:"51483e2e6796974f3d1a6b55ebbcec7c"},{url:"/icon-512.png",revision:"9f14deb90dae6562180d1f4702f25ba3"},{url:"/manifest.json",revision:"79b5e9a1311de9a77d850bb007661bd3"},{url:"/push-sw.js",revision:"acffd268f02c591360773dc8d1f23da1"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:n,state:i})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});
@@ -0,0 +1 @@
1
+ define(["exports"],function(t){"use strict";try{self["workbox:core:6.5.4"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:6.5.4"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class r{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class i extends r{constructor(t,e,s){super(({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)},e,s)}}class a{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)})}addCacheListener(){self.addEventListener("message",t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map(e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})}));t.waitUntil(s),t.ports&&t.ports[0]&&s.then(()=>t.ports[0].postMessage(!0))}})}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:i}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let a=i&&i.handler;const o=t.method;if(!a&&this.i.has(o)&&(a=this.i.get(o)),!a)return;let c;try{c=a.handle({url:s,request:t,event:e,params:r})}catch(t){c=Promise.reject(t)}const h=i&&i.catchHandler;return c instanceof Promise&&(this.o||h)&&(c=c.catch(async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:r})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n})),c}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const r=this.t.get(s.method)||[];for(const i of r){let r;const a=i.match({url:t,sameOrigin:e,request:s,event:n});if(a)return r=a,(Array.isArray(r)&&0===r.length||a.constructor===Object&&0===Object.keys(a).length||"boolean"==typeof a)&&(r=void 0),{route:i,params:r}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let o;const c=()=>(o||(o=new a,o.addFetchListener(),o.addCacheListener()),o);function h(t,e,n){let a;if("string"==typeof t){const s=new URL(t,location.href);a=new r(({url:t})=>t.href===s.href,e,n)}else if(t instanceof RegExp)a=new i(t,e,n);else if("function"==typeof t)a=new r(t,e,n);else{if(!(t instanceof r))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});a=t}return c().registerRoute(a),a}try{self["workbox:strategies:6.5.4"]&&_()}catch(t){}const u={cacheWillUpdate:async({response:t})=>200===t.status||0===t.status?t:null},l={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},f=t=>[l.prefix,t,l.suffix].filter(t=>t&&t.length>0).join("-"),w=t=>t||f(l.precache),d=t=>t||f(l.runtime);function p(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class y{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const g=new Set;function m(t){return"string"==typeof t?new Request(t):t}class v{constructor(t,e){this.h={},Object.assign(this,e),this.event=e.event,this.u=t,this.l=new y,this.p=[],this.m=[...t.plugins],this.v=new Map;for(const t of this.m)this.v.set(t,{});this.event.waitUntil(this.l.promise)}async fetch(t){const{event:e}=this;let n=m(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const r=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const i=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.u.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:i,response:t});return t}catch(t){throw r&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:r.clone(),request:i.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=m(t);let s;const{cacheName:n,matchOptions:r}=this.u,i=await this.getCacheKey(e,"read"),a=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(i,a);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:r,cachedResponse:s,request:i,event:this.event})||void 0;return s}async cachePut(t,e){const n=m(t);var r;await(r=0,new Promise(t=>setTimeout(t,r)));const i=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(a=i.url,new URL(String(a),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var a;const o=await this.R(e);if(!o)return!1;const{cacheName:c,matchOptions:h}=this.u,u=await self.caches.open(c),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const r=p(e.url,s);if(e.url===r)return t.match(e,n);const i=Object.assign(Object.assign({},n),{ignoreSearch:!0}),a=await t.keys(e,i);for(const e of a)if(r===p(e.url,s))return t.match(e,n)}(u,i.clone(),["__WB_REVISION__"],h):null;try{await u.put(i,l?o.clone():o)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of g)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:c,oldResponse:f,newResponse:o.clone(),request:i,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.h[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=m(await t({mode:e,request:n,event:this.event,params:this.params}));this.h[s]=n}return this.h[s]}hasCallback(t){for(const e of this.u.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.u.plugins)if("function"==typeof e[t]){const s=this.v.get(e),n=n=>{const r=Object.assign(Object.assign({},n),{state:s});return e[t](r)};yield n}}waitUntil(t){return this.p.push(t),t}async doneWaiting(){let t;for(;t=this.p.shift();)await t}destroy(){this.l.resolve(null)}async R(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class R{constructor(t={}){this.cacheName=d(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,r=new v(this,{event:e,request:s,params:n}),i=this.q(r,s,e);return[i,this.D(i,r,s,e)]}async q(t,e,n){let r;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(r=await this.U(e,t),!r||"error"===r.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const i of t.iterateCallbacks("handlerDidError"))if(r=await i({error:s,event:n,request:e}),r)break;if(!r)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))r=await s({event:n,request:e,response:r});return r}async D(t,e,s,n){let r,i;try{r=await t}catch(i){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await e.doneWaiting()}catch(t){t instanceof Error&&(i=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:i}),e.destroy(),i)throw i}}function b(t){t.then(()=>{})}function q(){return q=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var s=arguments[e];for(var n in s)({}).hasOwnProperty.call(s,n)&&(t[n]=s[n])}return t},q.apply(null,arguments)}let D,U;const x=new WeakMap,L=new WeakMap,I=new WeakMap,C=new WeakMap,E=new WeakMap;let N={get(t,e,s){if(t instanceof IDBTransaction){if("done"===e)return L.get(t);if("objectStoreNames"===e)return t.objectStoreNames||I.get(t);if("store"===e)return s.objectStoreNames[1]?void 0:s.objectStore(s.objectStoreNames[0])}return k(t[e])},set:(t,e,s)=>(t[e]=s,!0),has:(t,e)=>t instanceof IDBTransaction&&("done"===e||"store"===e)||e in t};function O(t){return t!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(U||(U=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(t)?function(...e){return t.apply(B(this),e),k(x.get(this))}:function(...e){return k(t.apply(B(this),e))}:function(e,...s){const n=t.call(B(this),e,...s);return I.set(n,e.sort?e.sort():[e]),k(n)}}function T(t){return"function"==typeof t?O(t):(t instanceof IDBTransaction&&function(t){if(L.has(t))return;const e=new Promise((e,s)=>{const n=()=>{t.removeEventListener("complete",r),t.removeEventListener("error",i),t.removeEventListener("abort",i)},r=()=>{e(),n()},i=()=>{s(t.error||new DOMException("AbortError","AbortError")),n()};t.addEventListener("complete",r),t.addEventListener("error",i),t.addEventListener("abort",i)});L.set(t,e)}(t),e=t,(D||(D=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])).some(t=>e instanceof t)?new Proxy(t,N):t);var e}function k(t){if(t instanceof IDBRequest)return function(t){const e=new Promise((e,s)=>{const n=()=>{t.removeEventListener("success",r),t.removeEventListener("error",i)},r=()=>{e(k(t.result)),n()},i=()=>{s(t.error),n()};t.addEventListener("success",r),t.addEventListener("error",i)});return e.then(e=>{e instanceof IDBCursor&&x.set(e,t)}).catch(()=>{}),E.set(e,t),e}(t);if(C.has(t))return C.get(t);const e=T(t);return e!==t&&(C.set(t,e),E.set(e,t)),e}const B=t=>E.get(t);const P=["get","getKey","getAll","getAllKeys","count"],M=["put","add","delete","clear"],W=new Map;function j(t,e){if(!(t instanceof IDBDatabase)||e in t||"string"!=typeof e)return;if(W.get(e))return W.get(e);const s=e.replace(/FromIndex$/,""),n=e!==s,r=M.includes(s);if(!(s in(n?IDBIndex:IDBObjectStore).prototype)||!r&&!P.includes(s))return;const i=async function(t,...e){const i=this.transaction(t,r?"readwrite":"readonly");let a=i.store;return n&&(a=a.index(e.shift())),(await Promise.all([a[s](...e),r&&i.done]))[0]};return W.set(e,i),i}N=(t=>q({},t,{get:(e,s,n)=>j(e,s)||t.get(e,s,n),has:(e,s)=>!!j(e,s)||t.has(e,s)}))(N);try{self["workbox:expiration:6.5.4"]&&_()}catch(t){}const S="cache-entries",K=t=>{const e=new URL(t,location.href);return e.hash="",e.href};class A{constructor(t){this._=null,this.L=t}I(t){const e=t.createObjectStore(S,{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1})}C(t){this.I(t),this.L&&function(t,{blocked:e}={}){const s=indexedDB.deleteDatabase(t);e&&s.addEventListener("blocked",t=>e(t.oldVersion,t)),k(s).then(()=>{})}(this.L)}async setTimestamp(t,e){const s={url:t=K(t),timestamp:e,cacheName:this.L,id:this.N(t)},n=(await this.getDb()).transaction(S,"readwrite",{durability:"relaxed"});await n.store.put(s),await n.done}async getTimestamp(t){const e=await this.getDb(),s=await e.get(S,this.N(t));return null==s?void 0:s.timestamp}async expireEntries(t,e){const s=await this.getDb();let n=await s.transaction(S).store.index("timestamp").openCursor(null,"prev");const r=[];let i=0;for(;n;){const s=n.value;s.cacheName===this.L&&(t&&s.timestamp<t||e&&i>=e?r.push(n.value):i++),n=await n.continue()}const a=[];for(const t of r)await s.delete(S,t.id),a.push(t.url);return a}N(t){return this.L+"|"+K(t)}async getDb(){return this._||(this._=await function(t,e,{blocked:s,upgrade:n,blocking:r,terminated:i}={}){const a=indexedDB.open(t,e),o=k(a);return n&&a.addEventListener("upgradeneeded",t=>{n(k(a.result),t.oldVersion,t.newVersion,k(a.transaction),t)}),s&&a.addEventListener("blocked",t=>s(t.oldVersion,t.newVersion,t)),o.then(t=>{i&&t.addEventListener("close",()=>i()),r&&t.addEventListener("versionchange",t=>r(t.oldVersion,t.newVersion,t))}).catch(()=>{}),o}("workbox-expiration",1,{upgrade:this.C.bind(this)})),this._}}class F{constructor(t,e={}){this.O=!1,this.T=!1,this.k=e.maxEntries,this.B=e.maxAgeSeconds,this.P=e.matchOptions,this.L=t,this.M=new A(t)}async expireEntries(){if(this.O)return void(this.T=!0);this.O=!0;const t=this.B?Date.now()-1e3*this.B:0,e=await this.M.expireEntries(t,this.k),s=await self.caches.open(this.L);for(const t of e)await s.delete(t,this.P);this.O=!1,this.T&&(this.T=!1,b(this.expireEntries()))}async updateTimestamp(t){await this.M.setTimestamp(t,Date.now())}async isURLExpired(t){if(this.B){const e=await this.M.getTimestamp(t),s=Date.now()-1e3*this.B;return void 0===e||e<s}return!1}async delete(){this.T=!1,await this.M.expireEntries(1/0)}}try{self["workbox:range-requests:6.5.4"]&&_()}catch(t){}async function H(t,e){try{if(206===e.status)return e;const n=t.headers.get("range");if(!n)throw new s("no-range-header");const r=function(t){const e=t.trim().toLowerCase();if(!e.startsWith("bytes="))throw new s("unit-must-be-bytes",{normalizedRangeHeader:e});if(e.includes(","))throw new s("single-range-only",{normalizedRangeHeader:e});const n=/(\d*)-(\d*)/.exec(e);if(!n||!n[1]&&!n[2])throw new s("invalid-range-values",{normalizedRangeHeader:e});return{start:""===n[1]?void 0:Number(n[1]),end:""===n[2]?void 0:Number(n[2])}}(n),i=await e.blob(),a=function(t,e,n){const r=t.size;if(n&&n>r||e&&e<0)throw new s("range-not-satisfiable",{size:r,end:n,start:e});let i,a;return void 0!==e&&void 0!==n?(i=e,a=n+1):void 0!==e&&void 0===n?(i=e,a=r):void 0!==n&&void 0===e&&(i=r-n,a=r),{start:i,end:a}}(i,r.start,r.end),o=i.slice(a.start,a.end),c=o.size,h=new Response(o,{status:206,statusText:"Partial Content",headers:e.headers});return h.headers.set("Content-Length",String(c)),h.headers.set("Content-Range",`bytes ${a.start}-${a.end-1}/${i.size}`),h}catch(t){return new Response("",{status:416,statusText:"Range Not Satisfiable"})}}function $(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:6.5.4"]&&_()}catch(t){}function z(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const r=new URL(n,location.href),i=new URL(n,location.href);return r.searchParams.set("__WB_REVISION__",e),{cacheKey:r.href,url:i.href}}class G{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class V{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.W.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.W=t}}let J,Q;async function X(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const r=t.clone(),i={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},a=e?e(i):i,o=function(){if(void 0===J){const t=new Response("");if("body"in t)try{new Response(t.body),J=!0}catch(t){J=!1}J=!1}return J}()?r.body:await r.blob();return new Response(o,a)}class Y extends R{constructor(t={}){t.cacheName=w(t.cacheName),super(t),this.j=!1!==t.fallbackToNetwork,this.plugins.push(Y.copyRedirectedCacheableResponsesPlugin)}async U(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.S(t,e):await this.K(t,e))}async K(t,e){let n;const r=e.params||{};if(!this.j)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=r.integrity,i=t.integrity,a=!i||i===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?i||s:void 0})),s&&a&&"no-cors"!==t.mode&&(this.A(),await e.cachePut(t,n.clone()))}return n}async S(t,e){this.A();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}A(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==Y.copyRedirectedCacheableResponsesPlugin&&(n===Y.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(Y.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}Y.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},Y.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await X(t):t};class Z{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.F=new Map,this.H=new Map,this.$=new Map,this.u=new Y({cacheName:w(t),plugins:[...e,new V({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.u}precache(t){this.addToCacheList(t),this.G||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.G=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:r}=z(n),i="string"!=typeof n&&n.revision?"reload":"default";if(this.F.has(r)&&this.F.get(r)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.F.get(r),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.$.has(t)&&this.$.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:r});this.$.set(t,n.integrity)}if(this.F.set(r,t),this.H.set(r,i),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return $(t,async()=>{const e=new G;this.strategy.plugins.push(e);for(const[e,s]of this.F){const n=this.$.get(s),r=this.H.get(e),i=new Request(e,{integrity:n,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:i,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}})}activate(t){return $(t,async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.F.values()),n=[];for(const r of e)s.has(r.url)||(await t.delete(r),n.push(r.url));return{deletedURLs:n}})}getURLsToCacheKeys(){return this.F}getCachedURLs(){return[...this.F.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.F.get(e.href)}getIntegrityForCacheKey(t){return this.$.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}const tt=()=>(Q||(Q=new Z),Q);class et extends r{constructor(t,e){super(({request:s})=>{const n=t.getURLsToCacheKeys();for(const r of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:r}={}){const i=new URL(t,location.href);i.hash="",yield i.href;const a=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some(t=>t.test(s))&&t.searchParams.delete(s);return t}(i,e);if(yield a.href,s&&a.pathname.endsWith("/")){const t=new URL(a.href);t.pathname+=s,yield t.href}if(n){const t=new URL(a.href);t.pathname+=".html",yield t.href}if(r){const t=r({url:i});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(r);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}},t.strategy)}}t.CacheFirst=class extends R{async U(t,e){let n,r=await e.cacheMatch(t);if(!r)try{r=await e.fetchAndCachePut(t)}catch(t){t instanceof Error&&(n=t)}if(!r)throw new s("no-response",{url:t.url,error:n});return r}},t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:e,cacheName:s,cachedResponse:n})=>{if(!n)return null;const r=this.V(n),i=this.J(s);b(i.expireEntries());const a=i.updateTimestamp(e.url);if(t)try{t.waitUntil(a)}catch(t){}return r?n:null},this.cacheDidUpdate=async({cacheName:t,request:e})=>{const s=this.J(t);await s.updateTimestamp(e.url),await s.expireEntries()},this.X=t,this.B=t.maxAgeSeconds,this.Y=new Map,t.purgeOnQuotaError&&function(t){g.add(t)}(()=>this.deleteCacheAndMetadata())}J(t){if(t===d())throw new s("expire-custom-caches-only");let e=this.Y.get(t);return e||(e=new F(t,this.X),this.Y.set(t,e)),e}V(t){if(!this.B)return!0;const e=this.Z(t);if(null===e)return!0;return e>=Date.now()-1e3*this.B}Z(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),s=new Date(e).getTime();return isNaN(s)?null:s}async deleteCacheAndMetadata(){for(const[t,e]of this.Y)await self.caches.delete(t),await e.delete();this.Y=new Map}},t.NetworkFirst=class extends R{constructor(t={}){super(t),this.plugins.some(t=>"cacheWillUpdate"in t)||this.plugins.unshift(u),this.tt=t.networkTimeoutSeconds||0}async U(t,e){const n=[],r=[];let i;if(this.tt){const{id:s,promise:a}=this.et({request:t,logs:n,handler:e});i=s,r.push(a)}const a=this.st({timeoutId:i,request:t,logs:n,handler:e});r.push(a);const o=await e.waitUntil((async()=>await e.waitUntil(Promise.race(r))||await a)());if(!o)throw new s("no-response",{url:t.url});return o}et({request:t,logs:e,handler:s}){let n;return{promise:new Promise(e=>{n=setTimeout(async()=>{e(await s.cacheMatch(t))},1e3*this.tt)}),id:n}}async st({timeoutId:t,request:e,logs:s,handler:n}){let r,i;try{i=await n.fetchAndCachePut(e)}catch(t){t instanceof Error&&(r=t)}return t&&clearTimeout(t),!r&&i||(i=await n.cacheMatch(e)),i}},t.RangeRequestsPlugin=class{constructor(){this.cachedResponseWillBeUsed=async({request:t,cachedResponse:e})=>e&&t.headers.has("range")?await H(t,e):e}},t.StaleWhileRevalidate=class extends R{constructor(t={}){super(t),this.plugins.some(t=>"cacheWillUpdate"in t)||this.plugins.unshift(u)}async U(t,e){const n=e.fetchAndCachePut(t).catch(()=>{});e.waitUntil(n);let r,i=await e.cacheMatch(t);if(i);else try{i=await n}catch(t){t instanceof Error&&(r=t)}if(!i)throw new s("no-response",{url:t.url,error:r});return i}},t.cleanupOutdatedCaches=function(){self.addEventListener("activate",t=>{const e=w();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter(s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t);return await Promise.all(s.map(t=>self.caches.delete(t))),s})(e).then(t=>{}))})},t.clientsClaim=function(){self.addEventListener("activate",()=>self.clients.claim())},t.precacheAndRoute=function(t,e){!function(t){tt().precache(t)}(t),function(t){const e=tt();h(new et(e,t))}(e)},t.registerRoute=h});
@@ -0,0 +1 @@
1
+ (()=>{var e=[,(e,t,o)=>{var r=o(2);e.exports=function(e,t,o){return(t=r(t))in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e},e.exports.__esModule=!0,e.exports.default=e.exports},(e,t,o)=>{var r=o(3).default,n=o(4);e.exports=function(e){var t=n(e,"string");return"symbol"===r(t)?t:String(t)},e.exports.__esModule=!0,e.exports.default=e.exports},e=>{function t(o){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(o)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},(e,t,o)=>{var r=o(3).default;e.exports=function(e,t){if("object"!==r(e)||null===e)return e;var o=e[Symbol.toPrimitive];if(void 0!==o){var n=o.call(e,t||"default");if("object"!==r(n))return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)},e.exports.__esModule=!0,e.exports.default=e.exports}],t={};function o(r){var n=t[r];if(void 0!==n)return n.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,o),i.exports}(()=>{var e=o(1);function t(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,r)}return o}function r(o){for(var r=1;r<arguments.length;r++){var n=null!=arguments[r]?arguments[r]:{};r%2?t(Object(n),!0).forEach(function(t){e(o,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(o,Object.getOwnPropertyDescriptors(n)):t(Object(n)).forEach(function(e){Object.defineProperty(o,e,Object.getOwnPropertyDescriptor(n,e))})}return o}self.addEventListener("push",e=>{const t=(async()=>{try{console.log("Push received:",e);let t={title:"Agent Connect",body:"New notification",icon:"/icon-192.png",badge:"/icon-192.png",type:"completed"};if(e.data)try{t=r(r({},t),e.data.json())}catch(o){t.body=e.data.text()}const o={completed:{tag:"completed",vibrate:[100,50,100]},input_needed:{tag:"input_needed",vibrate:[200,100,200,100,200]},error:{tag:"error",vibrate:[500,200,500]}},n=o[t.type]||o.completed,i={body:t.body,icon:t.icon,badge:t.badge,tag:n.tag,vibrate:n.vibrate,data:r({url:"/",type:t.type},t.data),requireInteraction:"input_needed"===t.type};await self.registration.showNotification(t.title,i)}catch(e){console.error("Push handler error:",e),await self.registration.showNotification("Push Error",{body:e.message||String(e),tag:"push-error"})}})();e.waitUntil(t)}),self.addEventListener("notificationclick",e=>{console.log("Notification clicked:",e),e.notification.close(),"dismiss"!==e.action&&e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0}).then(e=>{for(const t of e)if(t.url.includes(self.location.origin)&&"focus"in t)return t.focus();if(clients.openWindow)return clients.openWindow("/")}))}),self.addEventListener("notificationclose",e=>{console.log("Notification closed:",e)})})()})();
@@ -0,0 +1,60 @@
1
+ // Custom server for Next.js
2
+ // Supports both HTTPS (self-signed certs) and HTTP (for use with Tailscale Serve)
3
+
4
+ const https = require('https');
5
+ const http = require('http');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { parse } = require('url');
9
+
10
+ const CERTS_DIR = path.join(__dirname, 'certs');
11
+ const PORT = process.env.PORT || 3110;
12
+
13
+ // Import Next.js
14
+ const next = require('next');
15
+ const dev = process.env.NODE_ENV !== 'production';
16
+ const hostname = process.env.LISTEN_HOST || '0.0.0.0';
17
+ const appDir = process.env.NEXT_APP_DIR || __dirname;
18
+ const app = next({ dev, hostname, port: PORT, dir: appDir });
19
+ const handle = app.getRequestHandler();
20
+
21
+ // Determine if we should use HTTPS directly or let Tailscale Serve handle TLS
22
+ const useHttps = process.env.DISABLE_SSL !== 'true' &&
23
+ fs.existsSync(path.join(CERTS_DIR, 'server.key')) &&
24
+ fs.existsSync(path.join(CERTS_DIR, 'server.crt'));
25
+
26
+ app.prepare().then(() => {
27
+ const requestHandler = async (req, res) => {
28
+ try {
29
+ const parsedUrl = parse(req.url, true);
30
+ await handle(req, res, parsedUrl);
31
+ } catch (err) {
32
+ console.error('Error handling request:', err);
33
+ res.statusCode = 500;
34
+ res.end('Internal server error');
35
+ }
36
+ };
37
+
38
+ let server;
39
+ let protocol;
40
+
41
+ if (useHttps) {
42
+ const sslOptions = {
43
+ key: fs.readFileSync(path.join(CERTS_DIR, 'server.key')),
44
+ cert: fs.readFileSync(path.join(CERTS_DIR, 'server.crt')),
45
+ };
46
+ server = https.createServer(sslOptions, requestHandler);
47
+ protocol = 'https';
48
+ } else {
49
+ server = http.createServer(requestHandler);
50
+ protocol = 'http';
51
+ }
52
+
53
+ const displayHost = process.env.APP_HOSTNAME || hostname;
54
+ server.listen(PORT, hostname, () => {
55
+ console.log(`Next.js server running on ${protocol}://${displayHost}:${PORT}`);
56
+ if (!useHttps) {
57
+ console.log('Running in HTTP mode (TLS handled by Tailscale Serve)');
58
+ }
59
+ });
60
+ });
package/server.js ADDED
@@ -0,0 +1,316 @@
1
+ // Standalone API server for Agent Notifier
2
+ // Runs on port 3109 (backend API)
3
+ // The Next.js frontend runs separately on port 3110
4
+
5
+ const https = require('https');
6
+ const http = require('http');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const webpush = require('web-push');
10
+
11
+ const os = require('os');
12
+
13
+ const PORT = process.env.API_PORT || 3109;
14
+ const DATA_DIR = process.env.AGENT_CONNECT_DATA_DIR || path.join(os.homedir(), '.agent-connect', 'data');
15
+ const SUBSCRIPTIONS_FILE = path.join(DATA_DIR, 'subscriptions.json');
16
+
17
+ // Load config from .env.local first (source of truth for VAPID keys used by browser),
18
+ // then ~/.agent-connect/config.json as fallback for non-env settings
19
+ function loadConfig() {
20
+ let hasEnvLocal = false;
21
+
22
+ // Try .env.local first — this is the source of truth for VAPID keys
23
+ // since the Next.js frontend serves these to the browser
24
+ try {
25
+ const envPath = path.join(__dirname, '.env.local');
26
+ const envContent = fs.readFileSync(envPath, 'utf-8');
27
+ envContent.split('\n').forEach(line => {
28
+ const trimmed = line.trim();
29
+ if (!trimmed || trimmed.startsWith('#')) return;
30
+ const [key, ...valueParts] = trimmed.split('=');
31
+ if (key && valueParts.length > 0) {
32
+ process.env[key.trim()] = process.env[key.trim()] || valueParts.join('=').trim();
33
+ }
34
+ });
35
+ hasEnvLocal = true;
36
+ } catch {
37
+ // .env.local not found, will try config.json
38
+ }
39
+
40
+ // Load ~/.agent-connect/config.json as fallback (fills in any gaps)
41
+ try {
42
+ const configPath = path.join(os.homedir(), '.agent-connect', 'config.json');
43
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
44
+
45
+ // Warn if VAPID keys differ between .env.local and config.json
46
+ const envPublicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || process.env.VAPID_PUBLIC_KEY;
47
+ if (hasEnvLocal && config.vapidPublicKey && envPublicKey && config.vapidPublicKey !== envPublicKey) {
48
+ console.warn('Warning: VAPID public key in config.json differs from .env.local');
49
+ console.warn(' .env.local key will be used (source of truth for browser subscriptions)');
50
+ }
51
+
52
+ if (config.vapidPublicKey) process.env.VAPID_PUBLIC_KEY = process.env.VAPID_PUBLIC_KEY || config.vapidPublicKey;
53
+ if (config.vapidPublicKey) process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || config.vapidPublicKey;
54
+ if (config.vapidPrivateKey) process.env.VAPID_PRIVATE_KEY = process.env.VAPID_PRIVATE_KEY || config.vapidPrivateKey;
55
+ if (config.vapidSubject) process.env.VAPID_SUBJECT = process.env.VAPID_SUBJECT || config.vapidSubject;
56
+ if (config.hostname) process.env.APP_HOSTNAME = process.env.APP_HOSTNAME || config.hostname;
57
+ if (config.apiPort) process.env.API_PORT = process.env.API_PORT || String(config.apiPort);
58
+ return true;
59
+ } catch {
60
+ if (!hasEnvLocal) {
61
+ console.warn('Warning: No config found. Run: agent-connect setup');
62
+ return false;
63
+ }
64
+ }
65
+
66
+ return hasEnvLocal;
67
+ }
68
+
69
+ loadConfig();
70
+
71
+ // Configure web-push
72
+ const publicKey = process.env.VAPID_PUBLIC_KEY || process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
73
+ const privateKey = process.env.VAPID_PRIVATE_KEY;
74
+ const subject = process.env.VAPID_SUBJECT || 'mailto:admin@example.com';
75
+
76
+ if (publicKey && privateKey) {
77
+ webpush.setVapidDetails(subject, publicKey, privateKey);
78
+ console.log(`VAPID public key: ${publicKey.substring(0, 20)}...`);
79
+ } else {
80
+ console.error('VAPID keys not configured! Run: npm run generate-vapid-keys');
81
+ }
82
+
83
+ // Ensure data directory exists
84
+ function ensureDataDir() {
85
+ if (!fs.existsSync(DATA_DIR)) {
86
+ fs.mkdirSync(DATA_DIR, { recursive: true });
87
+ }
88
+ }
89
+
90
+ // Read subscriptions
91
+ function getSubscriptions() {
92
+ ensureDataDir();
93
+ try {
94
+ const data = fs.readFileSync(SUBSCRIPTIONS_FILE, 'utf-8');
95
+ return JSON.parse(data);
96
+ } catch {
97
+ return [];
98
+ }
99
+ }
100
+
101
+ // Write subscriptions
102
+ function saveSubscriptions(subscriptions) {
103
+ ensureDataDir();
104
+ fs.writeFileSync(SUBSCRIPTIONS_FILE, JSON.stringify(subscriptions, null, 2));
105
+ }
106
+
107
+ // Parse JSON body
108
+ async function parseBody(req) {
109
+ return new Promise((resolve, reject) => {
110
+ let body = '';
111
+ req.on('data', chunk => body += chunk);
112
+ req.on('end', () => {
113
+ try {
114
+ resolve(body ? JSON.parse(body) : {});
115
+ } catch (e) {
116
+ reject(new Error('Invalid JSON'));
117
+ }
118
+ });
119
+ req.on('error', reject);
120
+ });
121
+ }
122
+
123
+ // Send JSON response
124
+ function sendJson(res, status, data) {
125
+ res.writeHead(status, {
126
+ 'Content-Type': 'application/json',
127
+ 'Access-Control-Allow-Origin': '*',
128
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
129
+ 'Access-Control-Allow-Headers': 'Content-Type',
130
+ });
131
+ res.end(JSON.stringify(data));
132
+ }
133
+
134
+ // Request handler
135
+ async function handleRequest(req, res) {
136
+ const url = new URL(req.url, `http://localhost:${PORT}`);
137
+ const pathname = url.pathname;
138
+
139
+ // Handle CORS preflight
140
+ if (req.method === 'OPTIONS') {
141
+ res.writeHead(204, {
142
+ 'Access-Control-Allow-Origin': '*',
143
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
144
+ 'Access-Control-Allow-Headers': 'Content-Type',
145
+ });
146
+ res.end();
147
+ return;
148
+ }
149
+
150
+ try {
151
+ // Subscribe endpoint
152
+ if (pathname === '/api/subscribe' && req.method === 'POST') {
153
+ const subscription = await parseBody(req);
154
+
155
+ if (!subscription.endpoint || !subscription.keys?.p256dh || !subscription.keys?.auth) {
156
+ return sendJson(res, 400, { error: 'Invalid subscription object' });
157
+ }
158
+
159
+ const subscriptions = getSubscriptions();
160
+ const exists = subscriptions.some(sub => sub.endpoint === subscription.endpoint);
161
+
162
+ if (!exists) {
163
+ subscriptions.push(subscription);
164
+ saveSubscriptions(subscriptions);
165
+ }
166
+
167
+ return sendJson(res, 200, { success: true, message: 'Subscribed successfully' });
168
+ }
169
+
170
+ // Unsubscribe endpoint
171
+ if (pathname === '/api/unsubscribe' && req.method === 'POST') {
172
+ const { endpoint } = await parseBody(req);
173
+
174
+ if (!endpoint) {
175
+ return sendJson(res, 400, { error: 'Endpoint is required' });
176
+ }
177
+
178
+ const subscriptions = getSubscriptions();
179
+ const filtered = subscriptions.filter(sub => sub.endpoint !== endpoint);
180
+
181
+ if (filtered.length !== subscriptions.length) {
182
+ saveSubscriptions(filtered);
183
+ return sendJson(res, 200, { success: true, message: 'Unsubscribed successfully' });
184
+ }
185
+
186
+ return sendJson(res, 404, { error: 'Subscription not found' });
187
+ }
188
+
189
+ // Notify endpoint
190
+ if (pathname === '/api/notify' && (req.method === 'POST' || req.method === 'GET')) {
191
+ let payload;
192
+
193
+ if (req.method === 'GET') {
194
+ payload = {
195
+ title: url.searchParams.get('title') || 'Agent Notifier',
196
+ body: url.searchParams.get('body') || 'Test notification',
197
+ type: url.searchParams.get('type') || 'completed',
198
+ };
199
+ } else {
200
+ payload = await parseBody(req);
201
+ }
202
+
203
+ if (!payload.title || !payload.body) {
204
+ return sendJson(res, 400, { error: 'Title and body are required' });
205
+ }
206
+
207
+ const subscriptions = getSubscriptions();
208
+
209
+ if (subscriptions.length === 0) {
210
+ return sendJson(res, 200, { success: true, message: 'No subscriptions to notify', sent: 0 });
211
+ }
212
+
213
+ const invalidEndpoints = [];
214
+ let sentCount = 0;
215
+
216
+ const notificationPayload = JSON.stringify({
217
+ title: payload.title,
218
+ body: payload.body,
219
+ icon: payload.icon || '/icon-192.png',
220
+ badge: payload.badge || '/icon-192.png',
221
+ type: payload.type || 'completed',
222
+ data: payload.data || {},
223
+ });
224
+
225
+ const pushOptions = {
226
+ urgency: 'high',
227
+ TTL: 60,
228
+ };
229
+
230
+ await Promise.all(
231
+ subscriptions.map(async (subscription) => {
232
+ try {
233
+ await webpush.sendNotification(subscription, notificationPayload, pushOptions);
234
+ sentCount++;
235
+ } catch (error) {
236
+ if (error.statusCode === 404 || error.statusCode === 410) {
237
+ invalidEndpoints.push(subscription.endpoint);
238
+ } else {
239
+ console.error('Failed to send notification:', {
240
+ statusCode: error.statusCode,
241
+ body: error.body,
242
+ endpoint: subscription.endpoint,
243
+ message: error.message,
244
+ });
245
+ }
246
+ }
247
+ })
248
+ );
249
+
250
+ // Clean up invalid subscriptions
251
+ if (invalidEndpoints.length > 0) {
252
+ const cleaned = subscriptions.filter(sub => !invalidEndpoints.includes(sub.endpoint));
253
+ saveSubscriptions(cleaned);
254
+ }
255
+
256
+ return sendJson(res, 200, {
257
+ success: true,
258
+ message: `Notification sent to ${sentCount} subscriber(s)`,
259
+ sent: sentCount,
260
+ cleaned: invalidEndpoints.length,
261
+ });
262
+ }
263
+
264
+ // Health check
265
+ if (pathname === '/api/health' || pathname === '/health') {
266
+ return sendJson(res, 200, { status: 'ok', subscriptions: getSubscriptions().length });
267
+ }
268
+
269
+ // 404
270
+ sendJson(res, 404, { error: 'Not found' });
271
+
272
+ } catch (error) {
273
+ console.error('Request error:', error);
274
+ sendJson(res, 500, { error: error.message || 'Internal server error' });
275
+ }
276
+ }
277
+
278
+ // Create and start server
279
+ const HOST = process.env.API_HOST || '0.0.0.0';
280
+ const CERTS_DIR = path.join(__dirname, 'certs');
281
+
282
+ // Check if SSL certificates exist (can be disabled with DISABLE_SSL=true)
283
+ const useHttps = process.env.DISABLE_SSL !== 'true' &&
284
+ fs.existsSync(path.join(CERTS_DIR, 'server.key')) &&
285
+ fs.existsSync(path.join(CERTS_DIR, 'server.crt'));
286
+
287
+ let server;
288
+ let protocol;
289
+
290
+ if (useHttps) {
291
+ const sslOptions = {
292
+ key: fs.readFileSync(path.join(CERTS_DIR, 'server.key')),
293
+ cert: fs.readFileSync(path.join(CERTS_DIR, 'server.crt')),
294
+ };
295
+ server = https.createServer(sslOptions, handleRequest);
296
+ protocol = 'https';
297
+ } else {
298
+ console.warn('Running in HTTP mode (use Tailscale Serve or a reverse proxy for HTTPS).\n');
299
+ server = http.createServer(handleRequest);
300
+ protocol = 'http';
301
+ }
302
+
303
+ const displayHost = process.env.APP_HOSTNAME || HOST;
304
+
305
+ server.listen(PORT, HOST, () => {
306
+ console.log(`Agent Notifier API server running on ${protocol}://${HOST}:${PORT}`);
307
+ console.log(`\nEndpoints:`);
308
+ console.log(` POST ${protocol}://${displayHost}:${PORT}/api/subscribe`);
309
+ console.log(` POST ${protocol}://${displayHost}:${PORT}/api/unsubscribe`);
310
+ console.log(` POST ${protocol}://${displayHost}:${PORT}/api/notify`);
311
+ console.log(` GET ${protocol}://${displayHost}:${PORT}/api/health`);
312
+ console.log(`\nExample notification:`);
313
+ console.log(` curl ${useHttps ? '-k ' : ''}-X POST ${protocol}://${displayHost}:${PORT}/api/notify \\`);
314
+ console.log(` -H "Content-Type: application/json" \\`);
315
+ console.log(` -d '{"title": "Claude Code", "body": "Task completed!", "type": "completed"}'`);
316
+ });
@@ -0,0 +1,11 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
5
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
6
+ ],
7
+ theme: {
8
+ extend: {},
9
+ },
10
+ plugins: [],
11
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["dom", "dom.iterable", "esnext"],
4
+ "allowJs": true,
5
+ "skipLibCheck": true,
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "module": "esnext",
10
+ "moduleResolution": "bundler",
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "jsx": "preserve",
14
+ "incremental": true,
15
+ "plugins": [
16
+ {
17
+ "name": "next"
18
+ }
19
+ ],
20
+ "paths": {
21
+ "@/*": ["./*"]
22
+ }
23
+ },
24
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25
+ "exclude": ["node_modules"]
26
+ }