palmier 0.6.6 → 0.6.8

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 (96) hide show
  1. package/README.md +15 -1
  2. package/dist/agents/agent-instructions.md +6 -14
  3. package/dist/agents/aider.js +1 -1
  4. package/dist/agents/claude.js +1 -1
  5. package/dist/agents/cline.js +1 -1
  6. package/dist/agents/codex.js +1 -1
  7. package/dist/agents/copilot.js +1 -1
  8. package/dist/agents/cursor.js +1 -1
  9. package/dist/agents/deepagents.js +1 -1
  10. package/dist/agents/droid.js +1 -1
  11. package/dist/agents/gemini.js +1 -1
  12. package/dist/agents/goose.js +1 -1
  13. package/dist/agents/hermes.js +1 -1
  14. package/dist/agents/kimi.js +1 -1
  15. package/dist/agents/kiro.js +1 -1
  16. package/dist/agents/openclaw.js +1 -1
  17. package/dist/agents/opencode.js +1 -1
  18. package/dist/agents/qoder.js +1 -1
  19. package/dist/agents/qwen.js +1 -1
  20. package/dist/agents/shared-prompt.d.ts +3 -2
  21. package/dist/agents/shared-prompt.js +6 -4
  22. package/dist/commands/plan-generation.md +1 -0
  23. package/dist/commands/run.js +4 -7
  24. package/dist/location-device.d.ts +8 -0
  25. package/dist/location-device.js +32 -0
  26. package/dist/mcp-handler.d.ts +8 -0
  27. package/dist/mcp-handler.js +110 -0
  28. package/dist/mcp-tools.d.ts +27 -0
  29. package/dist/mcp-tools.js +218 -0
  30. package/dist/pwa/assets/{index-DhvJN8ie.css → index-C6Lz09EY.css} +1 -1
  31. package/dist/pwa/assets/index-C8vJwUNi.js +118 -0
  32. package/dist/pwa/assets/web-6UChJFov.js +1 -0
  33. package/dist/pwa/assets/web-NxTETXZK.js +1 -0
  34. package/dist/pwa/index.html +3 -3
  35. package/dist/pwa/service-worker.js +2 -2
  36. package/dist/rpc-handler.js +20 -8
  37. package/dist/spawn-command.js +3 -1
  38. package/dist/transports/http-transport.js +60 -129
  39. package/package.json +1 -1
  40. package/palmier-server/README.md +6 -1
  41. package/palmier-server/package.json +7 -1
  42. package/palmier-server/pnpm-lock.yaml +1025 -1
  43. package/palmier-server/pwa/index.html +1 -1
  44. package/palmier-server/pwa/package.json +3 -0
  45. package/palmier-server/pwa/src/App.css +64 -0
  46. package/palmier-server/pwa/src/api.ts +8 -2
  47. package/palmier-server/pwa/src/components/HostMenu.tsx +102 -1
  48. package/palmier-server/pwa/src/components/TaskCard.tsx +36 -8
  49. package/palmier-server/pwa/src/components/TaskForm.tsx +63 -53
  50. package/palmier-server/pwa/src/components/TaskListView.tsx +94 -78
  51. package/palmier-server/pwa/src/constants.ts +1 -1
  52. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +2 -1
  53. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +3 -0
  54. package/palmier-server/pwa/src/pages/Dashboard.tsx +5 -2
  55. package/palmier-server/pwa/src/pages/PairHost.tsx +10 -1
  56. package/palmier-server/pwa/src/service-worker.ts +7 -7
  57. package/palmier-server/server/.env.example +4 -0
  58. package/palmier-server/server/package.json +1 -0
  59. package/palmier-server/server/src/db.ts +10 -0
  60. package/palmier-server/server/src/fcm.ts +74 -0
  61. package/palmier-server/server/src/index.ts +101 -21
  62. package/palmier-server/server/src/notify.ts +34 -0
  63. package/palmier-server/server/src/push.ts +1 -1
  64. package/palmier-server/server/src/routes/fcm.ts +64 -0
  65. package/palmier-server/server/src/routes/push.ts +6 -5
  66. package/palmier-server/spec.md +4 -2
  67. package/src/agents/agent-instructions.md +6 -14
  68. package/src/agents/aider.ts +1 -1
  69. package/src/agents/claude.ts +1 -1
  70. package/src/agents/cline.ts +1 -1
  71. package/src/agents/codex.ts +1 -1
  72. package/src/agents/copilot.ts +1 -1
  73. package/src/agents/cursor.ts +1 -1
  74. package/src/agents/deepagents.ts +1 -1
  75. package/src/agents/droid.ts +1 -1
  76. package/src/agents/gemini.ts +1 -1
  77. package/src/agents/goose.ts +1 -1
  78. package/src/agents/hermes.ts +1 -1
  79. package/src/agents/kimi.ts +1 -1
  80. package/src/agents/kiro.ts +1 -1
  81. package/src/agents/openclaw.ts +1 -1
  82. package/src/agents/opencode.ts +1 -1
  83. package/src/agents/qoder.ts +1 -1
  84. package/src/agents/qwen.ts +1 -1
  85. package/src/agents/shared-prompt.ts +7 -4
  86. package/src/commands/plan-generation.md +1 -0
  87. package/src/commands/run.ts +4 -7
  88. package/src/location-device.ts +35 -0
  89. package/src/mcp-handler.ts +133 -0
  90. package/src/mcp-tools.ts +253 -0
  91. package/src/rpc-handler.ts +21 -8
  92. package/src/spawn-command.ts +3 -1
  93. package/src/transports/http-transport.ts +57 -128
  94. package/test/agent-instructions.test.ts +68 -5
  95. package/test/fixtures/agent-instructions-snapshot.md +58 -0
  96. package/dist/pwa/assets/index-CXqKVvmk.js +0 -118
@@ -0,0 +1 @@
1
+ import{W as t}from"./index-C8vJwUNi.js";class s extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{s as AppWeb};
@@ -0,0 +1 @@
1
+ import{W as p}from"./index-C8vJwUNi.js";class f extends p{constructor(){super(...arguments),this.group="CapacitorStorage"}async configure({group:e}){typeof e=="string"&&(this.group=e)}async get(e){return{value:this.impl.getItem(this.applyPrefix(e.key))}}async set(e){this.impl.setItem(this.applyPrefix(e.key),e.value)}async remove(e){this.impl.removeItem(this.applyPrefix(e.key))}async keys(){return{keys:this.rawKeys().map(t=>t.substring(this.prefix.length))}}async clear(){for(const e of this.rawKeys())this.impl.removeItem(e)}async migrate(){var e;const t=[],s=[],n="_cap_",o=Object.keys(this.impl).filter(i=>i.indexOf(n)===0);for(const i of o){const r=i.substring(n.length),a=(e=this.impl.getItem(i))!==null&&e!==void 0?e:"",{value:l}=await this.get({key:r});typeof l=="string"?s.push(r):(await this.set({key:r,value:a}),t.push(r))}return{migrated:t,existing:s}}async removeOld(){const e="_cap_",t=Object.keys(this.impl).filter(s=>s.indexOf(e)===0);for(const s of t)this.impl.removeItem(s)}get impl(){return window.localStorage}get prefix(){return this.group==="NativeStorage"?"":`${this.group}.`}rawKeys(){return Object.keys(this.impl).filter(e=>e.indexOf(this.prefix)===0)}applyPrefix(e){return this.prefix+e}}export{f as PreferencesWeb};
@@ -7,9 +7,9 @@
7
7
  <link rel="icon" type="image/x-icon" href="/favicon.ico" />
8
8
  <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
9
9
  <title>Palmier</title>
10
- <meta name="description" content="Control AI agents running on your machine from any device. Schedule tasks, monitor runs, and stay in control." />
11
- <script type="module" crossorigin src="/assets/index-CXqKVvmk.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-DhvJN8ie.css">
10
+ <meta name="description" content="Remote control for AI agents running on your own machine. Schedule tasks, approve permissions, and get push notifications." />
11
+ <script type="module" crossorigin src="/assets/index-C8vJwUNi.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-C6Lz09EY.css">
13
13
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -1,2 +1,2 @@
1
- try{self["workbox:core:7.3.0"]&&_()}catch{}const x=(a,...e)=>{let t=a;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t},N=x;class l extends Error{constructor(e,t){const s=N(e,t);super(s),this.name=e,this.details=t}}const f={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},U=a=>[f.prefix,a,f.suffix].filter(e=>e&&e.length>0).join("-"),I=a=>{for(const e of Object.keys(f))a(e)},L={updateDetails:a=>{I(e=>{typeof a[e]=="string"&&(f[e]=a[e])})},getGoogleAnalyticsName:a=>a||U(f.googleAnalytics),getPrecacheName:a=>a||U(f.precache),getPrefix:()=>f.prefix,getRuntimeName:a=>a||U(f.runtime),getSuffix:()=>f.suffix};function P(a,e){const t=e();return a.waitUntil(t),t}try{self["workbox:precaching:7.3.0"]&&_()}catch{}const E="__WB_REVISION__";function O(a){if(!a)throw new l("add-to-cache-list-unexpected-type",{entry:a});if(typeof a=="string"){const i=new URL(a,location.href);return{cacheKey:i.href,url:i.href}}const{revision:e,url:t}=a;if(!t)throw new l("add-to-cache-list-unexpected-type",{entry:a});if(!e){const i=new URL(t,location.href);return{cacheKey:i.href,url:i.href}}const s=new URL(t,location.href),n=new URL(t,location.href);return s.searchParams.set(E,e),{cacheKey:s.href,url:n.href}}class A{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if(e.type==="install"&&t&&t.originalRequest&&t.originalRequest instanceof Request){const n=t.originalRequest.url;s?this.notUpdatedURLs.push(n):this.updatedURLs.push(n)}return s}}}class M{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:t,params:s})=>{const n=(s==null?void 0:s.cacheKey)||this._precacheController.getCacheKeyForURL(t.url);return n?new Request(n,{headers:t.headers}):t},this._precacheController=e}}let y;function W(){if(y===void 0){const a=new Response("");if("body"in a)try{new Response(a.body),y=!0}catch{y=!1}y=!1}return y}async function q(a,e){let t=null;if(a.url&&(t=new URL(a.url).origin),t!==self.location.origin)throw new l("cross-origin-copy-response",{origin:t});const s=a.clone(),i={headers:new Headers(s.headers),status:s.status,statusText:s.statusText},r=W()?s.body:await s.blob();return new Response(r,i)}const S=a=>new URL(String(a),location.href).href.replace(new RegExp(`^${location.origin}`),"");function K(a,e){const t=new URL(a);for(const s of e)t.searchParams.delete(s);return t.href}async function j(a,e,t,s){const n=K(e.url,t);if(e.url===n)return a.match(e,s);const i=Object.assign(Object.assign({},s),{ignoreSearch:!0}),r=await a.keys(e,i);for(const c of r){const o=K(c.url,t);if(n===o)return a.match(c,s)}}class D{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}const H=new Set;async function F(){for(const a of H)await a()}function B(a){return new Promise(e=>setTimeout(e,a))}try{self["workbox:strategies:7.3.0"]&&_()}catch{}function R(a){return typeof a=="string"?new Request(a):a}class ${constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new D,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(const s of this._plugins)this._pluginStateMap.set(s,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){const{event:t}=this;let s=R(e);if(s.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){const r=await t.preloadResponse;if(r)return r}const n=this.hasCallback("fetchDidFail")?s.clone():null;try{for(const r of this.iterateCallbacks("requestWillFetch"))s=await r({request:s.clone(),event:t})}catch(r){if(r instanceof Error)throw new l("plugin-error-request-will-fetch",{thrownErrorMessage:r.message})}const i=s.clone();try{let r;r=await fetch(s,s.mode==="navigate"?void 0:this._strategy.fetchOptions);for(const c of this.iterateCallbacks("fetchDidSucceed"))r=await c({event:t,request:i,response:r});return r}catch(r){throw n&&await this.runCallbacks("fetchDidFail",{error:r,event:t,originalRequest:n.clone(),request:i.clone()}),r}}async fetchAndCachePut(e){const t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){const t=R(e);let s;const{cacheName:n,matchOptions:i}=this._strategy,r=await this.getCacheKey(t,"read"),c=Object.assign(Object.assign({},i),{cacheName:n});s=await caches.match(r,c);for(const o of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await o({cacheName:n,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s}async cachePut(e,t){const s=R(e);await B(0);const n=await this.getCacheKey(s,"write");if(!t)throw new l("cache-put-with-no-response",{url:S(n.url)});const i=await this._ensureResponseSafeToCache(t);if(!i)return!1;const{cacheName:r,matchOptions:c}=this._strategy,o=await self.caches.open(r),h=this.hasCallback("cacheDidUpdate"),p=h?await j(o,n.clone(),["__WB_REVISION__"],c):null;try{await o.put(n,h?i.clone():i)}catch(u){if(u instanceof Error)throw u.name==="QuotaExceededError"&&await F(),u}for(const u of this.iterateCallbacks("cacheDidUpdate"))await u({cacheName:r,oldResponse:p,newResponse:i.clone(),request:n,event:this.event});return!0}async getCacheKey(e,t){const s=`${e.url} | ${t}`;if(!this._cacheKeys[s]){let n=e;for(const i of this.iterateCallbacks("cacheKeyWillBeUsed"))n=R(await i({mode:t,request:n,event:this.event,params:this.params}));this._cacheKeys[s]=n}return this._cacheKeys[s]}hasCallback(e){for(const t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(const t of this._strategy.plugins)if(typeof t[e]=="function"){const s=this._pluginStateMap.get(t);yield i=>{const r=Object.assign(Object.assign({},i),{state:s});return t[e](r)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){const e=this._extendLifetimePromises.splice(0),s=(await Promise.allSettled(e)).find(n=>n.status==="rejected");if(s)throw s.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,s=!1;for(const n of this.iterateCallbacks("cacheWillUpdate"))if(t=await n({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return s||t&&t.status!==200&&(t=void 0),t}}class V{constructor(e={}){this.cacheName=L.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,s=typeof e.request=="string"?new Request(e.request):e.request,n="params"in e?e.params:void 0,i=new $(this,{event:t,request:s,params:n}),r=this._getResponse(i,s,t),c=this._awaitComplete(r,i,s,t);return[r,c]}async _getResponse(e,t,s){await e.runCallbacks("handlerWillStart",{event:s,request:t});let n;try{if(n=await this._handle(t,e),!n||n.type==="error")throw new l("no-response",{url:t.url})}catch(i){if(i instanceof Error){for(const r of e.iterateCallbacks("handlerDidError"))if(n=await r({error:i,event:s,request:t}),n)break}if(!n)throw i}for(const i of e.iterateCallbacks("handlerWillRespond"))n=await i({event:s,request:t,response:n});return n}async _awaitComplete(e,t,s,n){let i,r;try{i=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:n,request:s,response:i}),await t.doneWaiting()}catch(c){c instanceof Error&&(r=c)}if(await t.runCallbacks("handlerDidComplete",{event:n,request:s,response:i,error:r}),t.destroy(),r)throw r}}class d extends V{constructor(e={}){e.cacheName=L.getPrecacheName(e.cacheName),super(e),this._fallbackToNetwork=e.fallbackToNetwork!==!1,this.plugins.push(d.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){const s=await t.cacheMatch(e);return s||(t.event&&t.event.type==="install"?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let s;const n=t.params||{};if(this._fallbackToNetwork){const i=n.integrity,r=e.integrity,c=!r||r===i;s=await t.fetch(new Request(e,{integrity:e.mode!=="no-cors"?r||i:void 0})),i&&c&&e.mode!=="no-cors"&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,s.clone()))}else throw new l("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return s}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();const s=await t.fetch(e);if(!await t.cachePut(e,s.clone()))throw new l("bad-precaching-response",{url:e.url,status:s.status});return s}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(const[s,n]of this.plugins.entries())n!==d.copyRedirectedCacheableResponsesPlugin&&(n===d.defaultPrecacheCacheabilityPlugin&&(e=s),n.cacheWillUpdate&&t++);t===0?this.plugins.push(d.defaultPrecacheCacheabilityPlugin):t>1&&e!==null&&this.plugins.splice(e,1)}}d.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:a}){return!a||a.status>=400?null:a}};d.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:a}){return a.redirected?await q(a):a}};class G{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new d({cacheName:L.getPrecacheName(e),plugins:[...t,new M({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this._installAndActiveListenersAdded=!0)}addToCacheList(e){const t=[];for(const s of e){typeof s=="string"?t.push(s):s&&s.revision===void 0&&t.push(s.url);const{cacheKey:n,url:i}=O(s),r=typeof s!="string"&&s.revision?"reload":"default";if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==n)throw new l("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:n});if(typeof s!="string"&&s.integrity){if(this._cacheKeysToIntegrities.has(n)&&this._cacheKeysToIntegrities.get(n)!==s.integrity)throw new l("add-to-cache-list-conflicting-integrities",{url:i});this._cacheKeysToIntegrities.set(n,s.integrity)}if(this._urlsToCacheKeys.set(i,n),this._urlsToCacheModes.set(i,r),t.length>0){const c=`Workbox is precaching URLs without revision info: ${t.join(", ")}
2
- This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return P(e,async()=>{const t=new A;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),h=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:h,event:e}))}const{updatedURLs:s,notUpdatedURLs:n}=t;return{updatedURLs:s,notUpdatedURLs:n}})}activate(e){return P(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),n=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of s)n.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new l("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k;const v=()=>(k||(k=new G),k);try{self["workbox:routing:7.3.0"]&&_()}catch{}const T="GET",C=a=>a&&typeof a=="object"?a:{handle:a};class m{constructor(e,t,s=T){this.handler=C(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=C(e)}}class J extends m{constructor(e,t,s){const n=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(n,t,s)}}class Q{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(n=>{typeof n=="string"&&(n=[n]);const i=new Request(...n);return this.handleRequest({request:i,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:n,url:s});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let h;try{h=c.handle({url:s,request:e,event:t,params:i})}catch(u){h=Promise.reject(u)}const p=r&&r.catchHandler;return h instanceof Promise&&(this._catchHandler||p)&&(h=h.catch(async u=>{if(p)try{return await p.handle({url:s,request:e,event:t,params:i})}catch(g){g instanceof Error&&(u=g)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),h}findMatchingRoute({url:e,sameOrigin:t,request:s,event:n}){const i=this._routes.get(s.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:s,event:n});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=T){this._defaultHandlerMap.set(t,C(e))}setCatchHandler(e){this._catchHandler=C(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new l("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new l("unregister-route-route-not-registered")}}let w;const z=()=>(w||(w=new Q,w.addFetchListener(),w.addCacheListener()),w);function X(a,e,t){let s;if(typeof a=="string"){const i=new URL(a,location.href),r=({url:c})=>c.href===i.href;s=new m(r,e,t)}else if(a instanceof RegExp)s=new J(a,e,t);else if(typeof a=="function")s=new m(a,e,t);else if(a instanceof m)s=a;else throw new l("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return z().registerRoute(s),s}function Y(a,e=[]){for(const t of[...a.searchParams.keys()])e.some(s=>s.test(t))&&a.searchParams.delete(t);return a}function*Z(a,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:n}={}){const i=new URL(a,location.href);i.hash="",yield i.href;const r=Y(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(s){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(n){const c=n({url:i});for(const o of c)yield o.href}}class ee extends m{constructor(e,t){const s=({request:n})=>{const i=e.getURLsToCacheKeys();for(const r of Z(n.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(s,e.strategy)}}function te(a){const e=v(),t=new ee(e,a);X(t)}function se(a){v().precache(a)}function ae(a,e){se(a),te(e)}ae([{"revision":"38013143dc2183340ede8bc1c5124507","url":"registerSW.js"},{"revision":"3bd4228b57f2d66d08d81e0bdaa2bc54","url":"index.html"},{"revision":null,"url":"assets/index-DhvJN8ie.css"},{"revision":null,"url":"assets/index-CXqKVvmk.js"},{"revision":"fcc457fce855ad0df7178e0786c0d4ef","url":"apple-touch-icon.png"},{"revision":"276650c30bc4effc7d649ec66519aab6","url":"favicon.ico"},{"revision":"2e46512b835c05e17787059909305f22","url":"pwa-192x192.png"},{"revision":"ec5652b5834b4711337743e80e506a41","url":"pwa-512x512.png"},{"revision":"9f51698004b9cc4d787c75695b74de9d","url":"manifest.webmanifest"}]);const ne="/api/push/respond";self.addEventListener("message",a=>{});self.addEventListener("push",a=>{var r;if(!a.data)return;let e;try{e=a.data.json()}catch{e={title:"Palmier",body:a.data.text()}}const t=e.type??((r=e.data)==null?void 0:r.type);if(t==="confirm-dismiss"||t==="permission-dismiss"||t==="input-dismiss"){const c=e.data??e,o=c.task_id,h=c.host_id;a.waitUntil(self.registration.getNotifications().then(p=>{var u,g;for(const b of p)((u=b.data)==null?void 0:u.task_id)===o&&((g=b.data)==null?void 0:g.host_id)===h&&b.close()}));return}const s=e.title??"Palmier";let n=e.body??"";!n&&t==="confirm"&&(n="A task requires confirmation to run."),!n&&t==="permission"&&(n="A task needs additional permissions to continue."),!n&&t==="input"&&(n="A task needs your input to continue.");const i={body:n,icon:"/pwa-192x192.png",badge:"/pwa-192x192.png",data:e.data??e,vibrate:[100,50,100]};t==="confirm"&&(i.actions=[{action:"confirm",title:"Confirm"},{action:"abort",title:"Abort"}]),a.waitUntil(self.registration.showNotification(s,i))});self.addEventListener("notificationclick",a=>{const e=a.notification;e.close();const t=e.data??{},s=a.action;if(s&&t.type==="confirm"&&t.task_id&&t.host_id){const n=s==="confirm"?"confirmed":"aborted";a.waitUntil(fetch(ne,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:t.type,task_id:t.task_id,host_id:t.host_id,response:n})}).catch(i=>{console.error("Failed to send push response:",i)}))}else{const n=t.task_id,i=t.run_id,r=n&&i?`/runs/${encodeURIComponent(n)}/${encodeURIComponent(i)}`:n?`/runs/${encodeURIComponent(n)}/latest`:"/";a.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(c=>{for(const o of c)if(o.url.includes(self.location.origin)&&"focus"in o)return o.navigate(r),o.focus();return self.clients.openWindow(r)}))}});self.addEventListener("install",()=>{self.skipWaiting()});self.addEventListener("activate",a=>{a.waitUntil(self.clients.claim())});
1
+ try{self["workbox:core:7.3.0"]&&_()}catch{}const N=(n,...e)=>{let t=n;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t},E=N;class h extends Error{constructor(e,t){const s=E(e,t);super(s),this.name=e,this.details=t}}const f={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},U=n=>[f.prefix,n,f.suffix].filter(e=>e&&e.length>0).join("-"),O=n=>{for(const e of Object.keys(f))n(e)},L={updateDetails:n=>{O(e=>{typeof n[e]=="string"&&(f[e]=n[e])})},getGoogleAnalyticsName:n=>n||U(f.googleAnalytics),getPrecacheName:n=>n||U(f.precache),getPrefix:()=>f.prefix,getRuntimeName:n=>n||U(f.runtime),getSuffix:()=>f.suffix};function v(n,e){const t=e();return n.waitUntil(t),t}try{self["workbox:precaching:7.3.0"]&&_()}catch{}const A="__WB_REVISION__";function M(n){if(!n)throw new h("add-to-cache-list-unexpected-type",{entry:n});if(typeof n=="string"){const i=new URL(n,location.href);return{cacheKey:i.href,url:i.href}}const{revision:e,url:t}=n;if(!t)throw new h("add-to-cache-list-unexpected-type",{entry:n});if(!e){const i=new URL(t,location.href);return{cacheKey:i.href,url:i.href}}const s=new URL(t,location.href),a=new URL(t,location.href);return s.searchParams.set(A,e),{cacheKey:s.href,url:a.href}}class W{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if(e.type==="install"&&t&&t.originalRequest&&t.originalRequest instanceof Request){const a=t.originalRequest.url;s?this.notUpdatedURLs.push(a):this.updatedURLs.push(a)}return s}}}class q{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:t,params:s})=>{const a=(s==null?void 0:s.cacheKey)||this._precacheController.getCacheKeyForURL(t.url);return a?new Request(a,{headers:t.headers}):t},this._precacheController=e}}let w;function S(){if(w===void 0){const n=new Response("");if("body"in n)try{new Response(n.body),w=!0}catch{w=!1}w=!1}return w}async function j(n,e){let t=null;if(n.url&&(t=new URL(n.url).origin),t!==self.location.origin)throw new h("cross-origin-copy-response",{origin:t});const s=n.clone(),i={headers:new Headers(s.headers),status:s.status,statusText:s.statusText},r=S()?s.body:await s.blob();return new Response(r,i)}const D=n=>new URL(String(n),location.href).href.replace(new RegExp(`^${location.origin}`),"");function T(n,e){const t=new URL(n);for(const s of e)t.searchParams.delete(s);return t.href}async function H(n,e,t,s){const a=T(e.url,t);if(e.url===a)return n.match(e,s);const i=Object.assign(Object.assign({},s),{ignoreSearch:!0}),r=await n.keys(e,i);for(const c of r){const o=T(c.url,t);if(a===o)return n.match(c,s)}}class F{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}const B=new Set;async function $(){for(const n of B)await n()}function V(n){return new Promise(e=>setTimeout(e,n))}try{self["workbox:strategies:7.3.0"]&&_()}catch{}function C(n){return typeof n=="string"?new Request(n):n}class G{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new F,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(const s of this._plugins)this._pluginStateMap.set(s,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){const{event:t}=this;let s=C(e);if(s.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){const r=await t.preloadResponse;if(r)return r}const a=this.hasCallback("fetchDidFail")?s.clone():null;try{for(const r of this.iterateCallbacks("requestWillFetch"))s=await r({request:s.clone(),event:t})}catch(r){if(r instanceof Error)throw new h("plugin-error-request-will-fetch",{thrownErrorMessage:r.message})}const i=s.clone();try{let r;r=await fetch(s,s.mode==="navigate"?void 0:this._strategy.fetchOptions);for(const c of this.iterateCallbacks("fetchDidSucceed"))r=await c({event:t,request:i,response:r});return r}catch(r){throw a&&await this.runCallbacks("fetchDidFail",{error:r,event:t,originalRequest:a.clone(),request:i.clone()}),r}}async fetchAndCachePut(e){const t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){const t=C(e);let s;const{cacheName:a,matchOptions:i}=this._strategy,r=await this.getCacheKey(t,"read"),c=Object.assign(Object.assign({},i),{cacheName:a});s=await caches.match(r,c);for(const o of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await o({cacheName:a,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s}async cachePut(e,t){const s=C(e);await V(0);const a=await this.getCacheKey(s,"write");if(!t)throw new h("cache-put-with-no-response",{url:D(a.url)});const i=await this._ensureResponseSafeToCache(t);if(!i)return!1;const{cacheName:r,matchOptions:c}=this._strategy,o=await self.caches.open(r),l=this.hasCallback("cacheDidUpdate"),d=l?await H(o,a.clone(),["__WB_REVISION__"],c):null;try{await o.put(a,l?i.clone():i)}catch(u){if(u instanceof Error)throw u.name==="QuotaExceededError"&&await $(),u}for(const u of this.iterateCallbacks("cacheDidUpdate"))await u({cacheName:r,oldResponse:d,newResponse:i.clone(),request:a,event:this.event});return!0}async getCacheKey(e,t){const s=`${e.url} | ${t}`;if(!this._cacheKeys[s]){let a=e;for(const i of this.iterateCallbacks("cacheKeyWillBeUsed"))a=C(await i({mode:t,request:a,event:this.event,params:this.params}));this._cacheKeys[s]=a}return this._cacheKeys[s]}hasCallback(e){for(const t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(const t of this._strategy.plugins)if(typeof t[e]=="function"){const s=this._pluginStateMap.get(t);yield i=>{const r=Object.assign(Object.assign({},i),{state:s});return t[e](r)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){const e=this._extendLifetimePromises.splice(0),s=(await Promise.allSettled(e)).find(a=>a.status==="rejected");if(s)throw s.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,s=!1;for(const a of this.iterateCallbacks("cacheWillUpdate"))if(t=await a({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return s||t&&t.status!==200&&(t=void 0),t}}class J{constructor(e={}){this.cacheName=L.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,s=typeof e.request=="string"?new Request(e.request):e.request,a="params"in e?e.params:void 0,i=new G(this,{event:t,request:s,params:a}),r=this._getResponse(i,s,t),c=this._awaitComplete(r,i,s,t);return[r,c]}async _getResponse(e,t,s){await e.runCallbacks("handlerWillStart",{event:s,request:t});let a;try{if(a=await this._handle(t,e),!a||a.type==="error")throw new h("no-response",{url:t.url})}catch(i){if(i instanceof Error){for(const r of e.iterateCallbacks("handlerDidError"))if(a=await r({error:i,event:s,request:t}),a)break}if(!a)throw i}for(const i of e.iterateCallbacks("handlerWillRespond"))a=await i({event:s,request:t,response:a});return a}async _awaitComplete(e,t,s,a){let i,r;try{i=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:a,request:s,response:i}),await t.doneWaiting()}catch(c){c instanceof Error&&(r=c)}if(await t.runCallbacks("handlerDidComplete",{event:a,request:s,response:i,error:r}),t.destroy(),r)throw r}}class p extends J{constructor(e={}){e.cacheName=L.getPrecacheName(e.cacheName),super(e),this._fallbackToNetwork=e.fallbackToNetwork!==!1,this.plugins.push(p.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){const s=await t.cacheMatch(e);return s||(t.event&&t.event.type==="install"?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let s;const a=t.params||{};if(this._fallbackToNetwork){const i=a.integrity,r=e.integrity,c=!r||r===i;s=await t.fetch(new Request(e,{integrity:e.mode!=="no-cors"?r||i:void 0})),i&&c&&e.mode!=="no-cors"&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,s.clone()))}else throw new h("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return s}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();const s=await t.fetch(e);if(!await t.cachePut(e,s.clone()))throw new h("bad-precaching-response",{url:e.url,status:s.status});return s}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(const[s,a]of this.plugins.entries())a!==p.copyRedirectedCacheableResponsesPlugin&&(a===p.defaultPrecacheCacheabilityPlugin&&(e=s),a.cacheWillUpdate&&t++);t===0?this.plugins.push(p.defaultPrecacheCacheabilityPlugin):t>1&&e!==null&&this.plugins.splice(e,1)}}p.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:n}){return!n||n.status>=400?null:n}};p.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:n}){return n.redirected?await j(n):n}};class Q{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new p({cacheName:L.getPrecacheName(e),plugins:[...t,new q({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this._installAndActiveListenersAdded=!0)}addToCacheList(e){const t=[];for(const s of e){typeof s=="string"?t.push(s):s&&s.revision===void 0&&t.push(s.url);const{cacheKey:a,url:i}=M(s),r=typeof s!="string"&&s.revision?"reload":"default";if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==a)throw new h("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:a});if(typeof s!="string"&&s.integrity){if(this._cacheKeysToIntegrities.has(a)&&this._cacheKeysToIntegrities.get(a)!==s.integrity)throw new h("add-to-cache-list-conflicting-integrities",{url:i});this._cacheKeysToIntegrities.set(a,s.integrity)}if(this._urlsToCacheKeys.set(i,a),this._urlsToCacheModes.set(i,r),t.length>0){const c=`Workbox is precaching URLs without revision info: ${t.join(", ")}
2
+ This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new W;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of s)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k;const x=()=>(k||(k=new Q),k);try{self["workbox:routing:7.3.0"]&&_()}catch{}const I="GET",b=n=>n&&typeof n=="object"?n:{handle:n};class R{constructor(e,t,s=I){this.handler=b(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=b(e)}}class z extends R{constructor(e,t,s){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,s)}}class X{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const a=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:s});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:s,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const d=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||d)&&(l=l.catch(async u=>{if(d)try{return await d.handle({url:s,request:e,event:t,params:i})}catch(g){g instanceof Error&&(u=g)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){const i=this._routes.get(s.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:s,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=I){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let m;const Y=()=>(m||(m=new X,m.addFetchListener(),m.addCacheListener()),m);function Z(n,e,t){let s;if(typeof n=="string"){const i=new URL(n,location.href),r=({url:c})=>c.href===i.href;s=new R(r,e,t)}else if(n instanceof RegExp)s=new z(n,e,t);else if(typeof n=="function")s=new R(n,e,t);else if(n instanceof R)s=n;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return Y().registerRoute(s),s}function ee(n,e=[]){for(const t of[...n.searchParams.keys()])e.some(s=>s.test(t))&&n.searchParams.delete(t);return n}function*te(n,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:a}={}){const i=new URL(n,location.href);i.hash="",yield i.href;const r=ee(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(s){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class se extends R{constructor(e,t){const s=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of te(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(s,e.strategy)}}function ne(n){const e=x(),t=new se(e,n);Z(t)}function ae(n){x().precache(n)}function ie(n,e){ae(n),ne(e)}ie([{"revision":"38013143dc2183340ede8bc1c5124507","url":"registerSW.js"},{"revision":"e99cf000b780427d2b7a5907aa6778ea","url":"index.html"},{"revision":null,"url":"assets/web-NxTETXZK.js"},{"revision":null,"url":"assets/web-6UChJFov.js"},{"revision":null,"url":"assets/index-C8vJwUNi.js"},{"revision":null,"url":"assets/index-C6Lz09EY.css"},{"revision":"fcc457fce855ad0df7178e0786c0d4ef","url":"apple-touch-icon.png"},{"revision":"276650c30bc4effc7d649ec66519aab6","url":"favicon.ico"},{"revision":"2e46512b835c05e17787059909305f22","url":"pwa-192x192.png"},{"revision":"ec5652b5834b4711337743e80e506a41","url":"pwa-512x512.png"},{"revision":"9f51698004b9cc4d787c75695b74de9d","url":"manifest.webmanifest"}]);const re="/api/push/respond";self.addEventListener("message",n=>{});self.addEventListener("push",n=>{var r;if(!n.data)return;let e;try{e=n.data.json()}catch{e={title:"Palmier",body:n.data.text()}}const t=e.type??((r=e.data)==null?void 0:r.type);if(t==="confirm-dismiss"||t==="permission-dismiss"||t==="input-dismiss"){const c=e.data??e,o=c.host_id,l=c.session_id,d=c.task_id;n.waitUntil(self.registration.getNotifications().then(u=>{var g,P,K;for(const y of u)if(((g=y.data)==null?void 0:g.host_id)===o){if(l&&((P=y.data)==null?void 0:P.session_id)===l){y.close();continue}d&&((K=y.data)==null?void 0:K.task_id)===d&&y.close()}}));return}const s=e.title??"Palmier";let a=e.body??"";!a&&t==="confirm"&&(a="A task requires confirmation to run."),!a&&t==="permission"&&(a="A task needs additional permissions to continue."),!a&&t==="input"&&(a="A task needs your input to continue.");const i={body:a,icon:"/pwa-192x192.png",badge:"/pwa-192x192.png",data:e.data??e,vibrate:[100,50,100]};t==="confirm"&&(i.actions=[{action:"confirm",title:"Confirm"},{action:"abort",title:"Abort"}]),n.waitUntil(self.registration.showNotification(s,i))});self.addEventListener("notificationclick",n=>{const e=n.notification;e.close();const t=e.data??{},s=n.action;if(s&&t.type==="confirm"&&t.session_id&&t.host_id){const a=s==="confirm"?"confirmed":"aborted";n.waitUntil(fetch(re,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:t.session_id,host_id:t.host_id,response:a})}).catch(i=>{console.error("Failed to send push response:",i)}))}else{const a=t.task_id,i=t.run_id,r=a&&i?`/runs/${encodeURIComponent(a)}/${encodeURIComponent(i)}`:a?`/runs/${encodeURIComponent(a)}/latest`:"/";n.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(c=>{for(const o of c)if(o.url.includes(self.location.origin)&&"focus"in o)return o.navigate(r),o.focus();return self.clients.openWindow(r)}))}});self.addEventListener("install",()=>{self.skipWaiting()});self.addEventListener("activate",n=>{n.waitUntil(self.clients.claim())});
@@ -12,6 +12,7 @@ import crossSpawn from "cross-spawn";
12
12
  import { getAgent } from "./agents/agent.js";
13
13
  import { validateClient } from "./client-store.js";
14
14
  import { publishHostEvent } from "./events.js";
15
+ import { getLocationDevice, setLocationDevice, clearLocationDevice } from "./location-device.js";
15
16
  import { currentVersion, performUpdate } from "./update-checker.js";
16
17
  import { parseReportFiles, parseTaskOutcome, stripPalmierMarkers } from "./commands/run.js";
17
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -104,7 +105,6 @@ async function generatePlan(projectRoot, userPrompt, agentName) {
104
105
  const fullPrompt = PLAN_GENERATION_PROMPT + userPrompt;
105
106
  const planAgent = getAgent(agentName);
106
107
  const { command, args, stdin, env: agentEnv } = planAgent.getPlanGenerationCommandLine(fullPrompt);
107
- console.log(`[generatePlan] Running: ${command} ${args.join(" ")}`);
108
108
  const { output } = await spawnCommand(command, args, {
109
109
  cwd: projectRoot,
110
110
  timeout: 120_000,
@@ -142,25 +142,27 @@ export function createRpcHandler(config, nc) {
142
142
  body: task.body,
143
143
  status: status ? {
144
144
  ...status,
145
- ...(pending?.type === "confirmation" ? { pending_confirmation: true } : {}),
146
145
  ...(pending?.type === "permission" ? { pending_permission: pending.params } : {}),
147
- ...(pending?.type === "input" ? { pending_input: pending.params } : {}),
148
146
  } : undefined,
149
147
  };
150
148
  }
151
149
  async function handleRpc(request) {
152
- // Client token validation: skip for trusted localhost requests
153
- if (!request.localhost && (!request.clientToken || !validateClient(request.clientToken))) {
150
+ // Client token validation: skip for trusted localhost requests and
151
+ // task.user_input (server-originated push responses; gated by getPending instead)
152
+ const skipAuth = request.method === "task.user_input";
153
+ if (!skipAuth && !request.localhost && (!request.clientToken || !validateClient(request.clientToken))) {
154
154
  return { error: "Unauthorized" };
155
155
  }
156
156
  switch (request.method) {
157
157
  case "task.list": {
158
158
  const tasks = listTasks(config.projectRoot);
159
+ const locDevice = getLocationDevice();
159
160
  return {
160
161
  tasks: tasks.map((task) => flattenTask(task)),
161
162
  agents: config.agents ?? [],
162
163
  version: currentVersion,
163
164
  host_platform: process.platform,
165
+ location_client_token: locDevice?.clientToken ?? null,
164
166
  };
165
167
  }
166
168
  case "task.get": {
@@ -367,7 +369,7 @@ export function createRpcHandler(config, nc) {
367
369
  const child = crossSpawn(cmd, cmdArgs, {
368
370
  cwd: followupRunDir,
369
371
  stdio: [stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
370
- env: { ...process.env, ...followupAgentEnv, PALMIER_TASK_ID: params.id },
372
+ env: { ...process.env, ...followupAgentEnv },
371
373
  windowsHide: true,
372
374
  });
373
375
  if (stdin != null)
@@ -498,9 +500,7 @@ export function createRpcHandler(config, nc) {
498
500
  return {
499
501
  task_id: params.id,
500
502
  ...status,
501
- ...(pending?.type === "confirmation" ? { pending_confirmation: true } : {}),
502
503
  ...(pending?.type === "permission" ? { pending_permission: pending.params } : {}),
503
- ...(pending?.type === "input" ? { pending_input: pending.params } : {}),
504
504
  };
505
505
  }
506
506
  case "task.result": {
@@ -604,6 +604,18 @@ export function createRpcHandler(config, nc) {
604
604
  return { error };
605
605
  return { ok: true };
606
606
  }
607
+ case "device.location.enable": {
608
+ const params = request.params;
609
+ if (!params.fcmToken)
610
+ return { error: "fcmToken is required" };
611
+ const clientToken = request.clientToken ?? "";
612
+ setLocationDevice(clientToken, params.fcmToken);
613
+ return { ok: true };
614
+ }
615
+ case "device.location.disable": {
616
+ clearLocationDevice();
617
+ return { ok: true };
618
+ }
607
619
  default:
608
620
  return { error: `Unknown method: ${request.method}` };
609
621
  }
@@ -42,7 +42,9 @@ export function spawnCommand(command, args, opts) {
42
42
  const finalArgs = process.platform === "win32"
43
43
  ? args.map((a) => a.replace(/[\r\n]+/g, " "))
44
44
  : args;
45
- // console.log(`[spawn] ${command} ${finalArgs.join(" ")}`);
45
+ const truncate = (s, max = 100) => s.length > max ? s.slice(0, max) + "..." : s;
46
+ const displayArgs = finalArgs.map((arg) => truncate(arg));
47
+ console.log(`[spawn] ${command} ${displayArgs.join(" ")}`);
46
48
  const child = crossSpawn(command, finalArgs, {
47
49
  cwd: opts.cwd,
48
50
  stdio: [opts.stdin != null ? "pipe" : "ignore", "pipe", "pipe"],
@@ -5,7 +5,9 @@ import { StringCodec } from "nats";
5
5
  import { validateClient, addClient } from "../client-store.js";
6
6
  import { registerPending } from "../pending-requests.js";
7
7
  import * as fs from "node:fs";
8
- import { getTaskDir, parseTaskFile, spliceUserMessage } from "../task.js";
8
+ import { agentToolMap, ToolError } from "../mcp-tools.js";
9
+ import { handleMcpRequest, getAgentName } from "../mcp-handler.js";
10
+ import { getTaskDir } from "../task.js";
9
11
  const assetCache = new Map();
10
12
  const PWA_DIR = path.join(import.meta.dirname, "..", "pwa");
11
13
  const CONTENT_TYPES = {
@@ -65,18 +67,6 @@ export function detectLanIp() {
65
67
  }
66
68
  return "127.0.0.1";
67
69
  }
68
- /** Find the latest (highest-numbered) run directory for a task. */
69
- function findLatestRunId(taskDir) {
70
- try {
71
- const dirs = fs.readdirSync(taskDir)
72
- .filter((f) => /^\d+$/.test(f) && fs.statSync(`${taskDir}/${f}`).isDirectory())
73
- .sort();
74
- return dirs.length > 0 ? dirs[dirs.length - 1] : null;
75
- }
76
- catch {
77
- return null;
78
- }
79
- }
80
70
  /**
81
71
  * Start the HTTP transport: server with RPC, SSE, PWA proxy, pairing, and
82
72
  * localhost-only agent endpoints (notify, request-input, confirmation, permission).
@@ -136,9 +126,66 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
136
126
  }
137
127
  broadcastSseEvent({ task_id: taskId, ...payload });
138
128
  }
129
+ function makeToolContext(sessionId) {
130
+ return { config, nc, publishEvent, sessionId, agentName: getAgentName(sessionId) };
131
+ }
139
132
  const server = http.createServer(async (req, res) => {
140
133
  const url = new URL(req.url ?? "/", `http://localhost:${port}`);
141
134
  const pathname = url.pathname;
135
+ // ── MCP streamable HTTP endpoint ──────────────────────────────────
136
+ if (req.method === "POST" && pathname === "/mcp") {
137
+ if (!isLocalhost(req)) {
138
+ sendJson(res, 403, { error: "localhost only" });
139
+ return;
140
+ }
141
+ try {
142
+ const body = await readBody(req);
143
+ const sessionId = req.headers["mcp-session-id"];
144
+ const ctx = makeToolContext(sessionId ?? "");
145
+ const result = await handleMcpRequest(body, sessionId, ctx);
146
+ if (result.sessionId) {
147
+ res.setHeader("Mcp-Session-Id", result.sessionId);
148
+ }
149
+ sendJson(res, 200, result.body);
150
+ }
151
+ catch (err) {
152
+ sendJson(res, 500, { error: String(err) });
153
+ }
154
+ return;
155
+ }
156
+ // ── Auto-generated REST endpoints from MCP tool registry ──────────
157
+ if (req.method === "POST" && agentToolMap.has(pathname.slice(1))) {
158
+ if (!isLocalhost(req)) {
159
+ sendJson(res, 403, { error: "localhost only" });
160
+ return;
161
+ }
162
+ const tool = agentToolMap.get(pathname.slice(1));
163
+ try {
164
+ const taskId = url.searchParams.get("taskId");
165
+ if (!taskId) {
166
+ sendJson(res, 400, { error: "taskId query parameter is required" });
167
+ return;
168
+ }
169
+ const taskDir = getTaskDir(config.projectRoot, taskId);
170
+ if (!fs.existsSync(taskDir)) {
171
+ sendJson(res, 404, { error: `Task not found: ${taskId}` });
172
+ return;
173
+ }
174
+ const body = await readBody(req);
175
+ const args = body.trim() ? JSON.parse(body) : {};
176
+ const ctx = makeToolContext(taskId);
177
+ console.log(`[mcp] REST [${taskId.slice(0, 8)}] ${tool.name}`);
178
+ const result = await tool.handler(args, ctx);
179
+ console.log(`[mcp] REST [${taskId.slice(0, 8)}] ${tool.name} done:`, JSON.stringify(result).slice(0, 200));
180
+ sendJson(res, 200, result);
181
+ }
182
+ catch (err) {
183
+ const status = err instanceof ToolError ? err.statusCode : 500;
184
+ console.error(`[mcp] REST ${tool.name} error:`, err.message ?? String(err));
185
+ sendJson(res, status, { error: err.message ?? String(err) });
186
+ }
187
+ return;
188
+ }
142
189
  // ── Localhost-only endpoints (no auth) ─────────────────────────────
143
190
  if (req.method === "POST" && pathname === "/event") {
144
191
  if (!isLocalhost(req)) {
@@ -192,122 +239,6 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
192
239
  }
193
240
  return;
194
241
  }
195
- // ── POST /notify — send push notification via NATS ─────────────────
196
- if (req.method === "POST" && pathname === "/notify") {
197
- if (!isLocalhost(req)) {
198
- sendJson(res, 403, { error: "localhost only" });
199
- return;
200
- }
201
- if (!nc) {
202
- sendJson(res, 503, { error: "NATS not connected — push notifications require server mode" });
203
- return;
204
- }
205
- try {
206
- const body = await readBody(req);
207
- const { taskId: notifTaskId, title, body: notifBody } = JSON.parse(body);
208
- if (!title || !notifBody) {
209
- sendJson(res, 400, { error: "title and body are required" });
210
- return;
211
- }
212
- const sc = StringCodec();
213
- const payload = { hostId: config.hostId, title, body: notifBody };
214
- if (notifTaskId)
215
- payload.task_id = notifTaskId;
216
- const subject = `host.${config.hostId}.push.send`;
217
- const reply = await nc.request(subject, sc.encode(JSON.stringify(payload)), { timeout: 15_000 });
218
- const result = JSON.parse(sc.decode(reply.data));
219
- if (result.ok) {
220
- sendJson(res, 200, { ok: true });
221
- }
222
- else {
223
- sendJson(res, 502, { error: result.error ?? "Push notification failed" });
224
- }
225
- }
226
- catch (err) {
227
- sendJson(res, 500, { error: `Failed to send notification: ${err}` });
228
- }
229
- return;
230
- }
231
- // ── POST /request-input — held connection until user responds ────────
232
- if (req.method === "POST" && pathname === "/request-input") {
233
- if (!isLocalhost(req)) {
234
- sendJson(res, 403, { error: "localhost only" });
235
- return;
236
- }
237
- try {
238
- const body = await readBody(req);
239
- const { taskId, runId, descriptions } = JSON.parse(body);
240
- if (!taskId || !descriptions?.length) {
241
- sendJson(res, 400, { error: "taskId and descriptions are required" });
242
- return;
243
- }
244
- const taskDir = getTaskDir(config.projectRoot, taskId);
245
- const task = parseTaskFile(taskDir);
246
- // Resolve runId: use provided value, otherwise find the latest run directory
247
- const effectiveRunId = runId ?? findLatestRunId(taskDir);
248
- const pendingPromise = registerPending(taskId, "input", descriptions);
249
- await publishEvent(taskId, {
250
- event_type: "input-request",
251
- host_id: config.hostId,
252
- input_descriptions: descriptions,
253
- name: task.frontmatter.name,
254
- });
255
- const response = await pendingPromise;
256
- const questionsBlock = "\n\n" + descriptions.map((d) => `**${d}**`).join("\n");
257
- if (response.length === 1 && response[0] === "aborted") {
258
- await publishEvent(taskId, { event_type: "input-resolved", host_id: config.hostId, status: "aborted" });
259
- if (effectiveRunId) {
260
- spliceUserMessage(taskDir, effectiveRunId, { role: "user", time: Date.now(), content: "Aborted", type: "input" }, questionsBlock);
261
- await publishEvent(taskId, { event_type: "result-updated", run_id: effectiveRunId });
262
- }
263
- sendJson(res, 200, { aborted: true });
264
- }
265
- else {
266
- await publishEvent(taskId, { event_type: "input-resolved", host_id: config.hostId, status: "provided" });
267
- if (effectiveRunId) {
268
- spliceUserMessage(taskDir, effectiveRunId, { role: "user", time: Date.now(), content: response.join("\n"), type: "input" }, questionsBlock);
269
- await publishEvent(taskId, { event_type: "result-updated", run_id: effectiveRunId });
270
- }
271
- sendJson(res, 200, { values: response });
272
- }
273
- }
274
- catch (err) {
275
- sendJson(res, 500, { error: String(err) });
276
- }
277
- return;
278
- }
279
- // ── POST /request-confirmation — held connection ────────────────────
280
- if (req.method === "POST" && pathname === "/request-confirmation") {
281
- if (!isLocalhost(req)) {
282
- sendJson(res, 403, { error: "localhost only" });
283
- return;
284
- }
285
- try {
286
- const body = await readBody(req);
287
- const { taskId } = JSON.parse(body);
288
- if (!taskId) {
289
- sendJson(res, 400, { error: "taskId is required" });
290
- return;
291
- }
292
- const pendingPromise = registerPending(taskId, "confirmation");
293
- await publishEvent(taskId, {
294
- event_type: "confirm-request",
295
- host_id: config.hostId,
296
- });
297
- const response = await pendingPromise;
298
- const confirmed = response[0] === "confirmed";
299
- await publishEvent(taskId, {
300
- event_type: "confirm-resolved",
301
- host_id: config.hostId,
302
- status: confirmed ? "confirmed" : "aborted",
303
- });
304
- sendJson(res, 200, { confirmed });
305
- }
306
- catch (err) {
307
- sendJson(res, 500, { error: String(err) });
308
- }
309
- return;
310
- }
311
242
  // ── POST /request-permission — held connection ──────────────────────
312
243
  if (req.method === "POST" && pathname === "/request-permission") {
313
244
  if (!isLocalhost(req)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Palmier host CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hongxu Cai",
@@ -28,7 +28,8 @@ Palmier is a platform for remotely scheduling, managing, and executing autonomou
28
28
  - **PWA** -- React 19 + Vite progressive web app. Connects to NATS over WebSocket for real-time task updates and to the web server for host registration and push notifications. No user accounts — paired hosts are stored in localStorage.
29
29
  - **Web Server** -- Express + TypeScript API server. Handles host registration, push notifications (subscribes to `host-event.>` pub/sub for confirmation and completion events), and push notification relay (for host CLI requests via NATS). In production, also serves the built PWA static files.
30
30
  - **NATS Server** -- Message broker. Provides pub/sub messaging and request-reply for real-time communication between all components.
31
- - **Host** -- Runs on remote Linux/Windows machines to execute tasks via pluggable agent tools (e.g., Claude Code, Codex, Gemini). Each agent implements an `AgentTool` interface that handles command construction. Communicates with the platform over NATS and exposes a local HTTP server with agent-facing endpoints (`/notify`, `/request-input`) for task execution flows. See the [palmier](https://github.com/caihongxu/palmier) repo.
31
+ - **Host** -- Runs on remote Linux/Windows machines to execute tasks via pluggable agent tools (e.g., Claude Code, Codex, Gemini). Each agent implements an `AgentTool` interface that handles command construction. Communicates with the platform over NATS and exposes a local MCP server (`/mcp`, streamable HTTP) and auto-generated REST endpoints for agent-facing tools (`/notify`, `/request-input`, `/request-confirmation`, `/device-geolocation`). See the [palmier](https://github.com/caihongxu/palmier) repo.
32
+ - **Android App** -- Native Android wrapper (Capacitor) for the PWA. Provides FCM push messaging and background GPS access. The server sends FCM data messages to wake the device for geolocation requests. See the [palmier-android](https://github.com/caihongxu/palmier-android) repo.
32
33
 
33
34
  ## Prerequisites
34
35
 
@@ -145,6 +146,7 @@ The host runs on a separate Linux machine. See the [palmier README](https://gith
145
146
  | `VAPID_PUBLIC_KEY` | VAPID public key for web push | *(generated via web-push)* |
146
147
  | `VAPID_PRIVATE_KEY` | VAPID private key for web push | *(generated via web-push)* |
147
148
  | `VAPID_MAILTO` | Contact email for VAPID | `mailto:admin@example.com` |
149
+ | `GOOGLE_APPLICATION_CREDENTIALS` | Path to Firebase service account JSON (for FCM) | `/path/to/service-account.json` |
148
150
 
149
151
  > **LAN setup note:** `NATS_URL` uses `localhost` because the server connects to NATS locally. `NATS_HOST_URL`, `NATS_WS_URL` must use the LAN IP so remote hosts and browsers can reach them. Firewall must allow inbound on ports 3000 (HTTP), 4222 (NATS TCP), 5173 (Vite dev), and 9222 (NATS WebSocket).
150
152
 
@@ -160,6 +162,8 @@ All endpoints are prefixed with `/api`. No user authentication is required.
160
162
  | `DELETE` | `/api/push/subscribe` | Remove a push notification subscription |
161
163
  | `GET` | `/api/push/vapid-key` | Get the VAPID public key |
162
164
  | `POST` | `/api/push/respond` | Respond to a pending task confirmation via push notification |
165
+ | `POST` | `/api/fcm/register` | Register an FCM token for a host (Android device) |
166
+ | `POST` | `/api/fcm/geolocation-response` | Receive device location from Android, forward via NATS |
163
167
  | `GET` | `/health` | Health check |
164
168
 
165
169
 
@@ -185,3 +189,4 @@ All endpoints are prefixed with `/api`. No user authentication is required.
185
189
  ## Related Repositories
186
190
 
187
191
  - [palmier](https://github.com/caihongxu/palmier) -- The host binary, published as `palmier` on npm. Install with `npm install -g palmier`. Uses npm (not pnpm).
192
+ - [palmier-android](https://github.com/caihongxu/palmier-android) -- Native Android wrapper (Capacitor) for the PWA. Provides FCM and background GPS.
@@ -3,6 +3,12 @@
3
3
  "private": true,
4
4
  "packageManager": "pnpm@10.32.1",
5
5
  "pnpm": {
6
- "onlyBuiltDependencies": ["bcrypt", "esbuild"]
6
+ "onlyBuiltDependencies": [
7
+ "bcrypt",
8
+ "esbuild"
9
+ ]
10
+ },
11
+ "dependencies": {
12
+ "firebase-admin": "^13.8.0"
7
13
  }
8
14
  }