palmier 0.6.0 → 0.6.1

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 (82) hide show
  1. package/.github/workflows/publish.yml +15 -2
  2. package/CLAUDE.md +2 -2
  3. package/DISCLAIMER.md +36 -0
  4. package/README.md +76 -87
  5. package/dist/commands/pair.d.ts +1 -1
  6. package/dist/commands/pair.js +1 -1
  7. package/dist/pwa/apple-touch-icon.png +0 -0
  8. package/dist/pwa/assets/index-C7Ib48wG.js +118 -0
  9. package/dist/pwa/assets/index-_AmC1Rkn.css +1 -0
  10. package/dist/pwa/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
  11. package/dist/pwa/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
  12. package/dist/pwa/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
  13. package/dist/pwa/favicon.ico +0 -0
  14. package/dist/pwa/index.html +17 -0
  15. package/dist/pwa/manifest.webmanifest +1 -0
  16. package/dist/pwa/pwa-192x192.png +0 -0
  17. package/dist/pwa/pwa-512x512.png +0 -0
  18. package/dist/pwa/registerSW.js +1 -0
  19. package/dist/pwa/service-worker.js +2 -0
  20. package/dist/rpc-handler.d.ts +4 -0
  21. package/dist/rpc-handler.js +1 -1
  22. package/dist/transports/http-transport.js +26 -40
  23. package/package.json +2 -2
  24. package/palmier-server/.github/workflows/ci.yml +21 -0
  25. package/palmier-server/.github/workflows/deploy.yml +38 -0
  26. package/palmier-server/CLAUDE.md +13 -0
  27. package/palmier-server/PRODUCTION.md +355 -0
  28. package/palmier-server/README.md +187 -0
  29. package/palmier-server/nats.conf +15 -0
  30. package/palmier-server/package.json +8 -0
  31. package/palmier-server/pnpm-lock.yaml +6597 -0
  32. package/palmier-server/pnpm-workspace.yaml +3 -0
  33. package/palmier-server/pwa/index.html +16 -0
  34. package/palmier-server/pwa/logo/logo-prompt.md +28 -0
  35. package/palmier-server/pwa/logo/logo_20260330.png +0 -0
  36. package/palmier-server/pwa/package.json +30 -0
  37. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  38. package/palmier-server/pwa/public/favicon.ico +0 -0
  39. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  40. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  41. package/palmier-server/pwa/src/App.css +2387 -0
  42. package/palmier-server/pwa/src/App.tsx +21 -0
  43. package/palmier-server/pwa/src/agentLabels.ts +11 -0
  44. package/palmier-server/pwa/src/api.ts +61 -0
  45. package/palmier-server/pwa/src/components/HostMenu.tsx +289 -0
  46. package/palmier-server/pwa/src/components/PlanDialog.tsx +41 -0
  47. package/palmier-server/pwa/src/components/RunDetailView.tsx +293 -0
  48. package/palmier-server/pwa/src/components/RunsView.tsx +254 -0
  49. package/palmier-server/pwa/src/components/TabBar.tsx +31 -0
  50. package/palmier-server/pwa/src/components/TaskCard.tsx +213 -0
  51. package/palmier-server/pwa/src/components/TaskForm.tsx +580 -0
  52. package/palmier-server/pwa/src/components/TaskListView.tsx +415 -0
  53. package/palmier-server/pwa/src/constants.ts +2 -0
  54. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +313 -0
  55. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +135 -0
  56. package/palmier-server/pwa/src/formatTime.ts +10 -0
  57. package/palmier-server/pwa/src/hooks/useBackClose.ts +75 -0
  58. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +17 -0
  59. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +75 -0
  60. package/palmier-server/pwa/src/main.tsx +14 -0
  61. package/palmier-server/pwa/src/pages/Dashboard.tsx +223 -0
  62. package/palmier-server/pwa/src/pages/PairHost.tsx +178 -0
  63. package/palmier-server/pwa/src/service-worker.ts +139 -0
  64. package/palmier-server/pwa/src/types.ts +79 -0
  65. package/palmier-server/pwa/src/vite-env.d.ts +11 -0
  66. package/palmier-server/pwa/tsconfig.json +21 -0
  67. package/palmier-server/pwa/tsconfig.node.json +19 -0
  68. package/palmier-server/pwa/vite.config.ts +47 -0
  69. package/palmier-server/server/.env.example +16 -0
  70. package/palmier-server/server/package.json +33 -0
  71. package/palmier-server/server/src/db.ts +34 -0
  72. package/palmier-server/server/src/index.ts +217 -0
  73. package/palmier-server/server/src/nats.ts +25 -0
  74. package/palmier-server/server/src/push.ts +68 -0
  75. package/palmier-server/server/src/routes/hosts.ts +45 -0
  76. package/palmier-server/server/src/routes/push.ts +100 -0
  77. package/palmier-server/server/tsconfig.json +20 -0
  78. package/palmier-server/spec.md +415 -0
  79. package/src/commands/pair.ts +1 -1
  80. package/src/rpc-handler.ts +1 -1
  81. package/src/transports/http-transport.ts +28 -41
  82. package/test/result-state.test.ts +110 -0
@@ -0,0 +1 @@
1
+ @font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(data:font/woff2;base64,d09GMgABAAAAAAa0ABQAAAAADOwAAAZHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbbhwoP0hWQVJtP01WQVJGBmA/U1RBVIEcAGQvXBEICoMkgmcLFgAwhEwBNgIkAyYEIAWGXAdiDAcbOgtRlHLSRcD8TEzkdoghPRuspKefbVnJIM5yRfA8vZv9uTNJSCZY21Scrigr6qyJ/C3sE1OFKuVP3e+lxUTbYio1zb/kFdk2bufY2BlhWERRBrcDUBhB5GEpd2Cy+MDxqf9zLPX+XVsk5r+s50d6IpWLatqbdT6f2MAikiOKQocbjya2QT0DqPDX4R0jQBgAUAiCRhDA0EkvYeLk9X3I2bEWI8jBAihIANm+kUWQBgggWChcBBdFhXlAUgOCAgBA0AgKjUJ8HKvjddFCKSgAVklEBYAeEJgCQJH0qB3B+neDg4sAA4hAD0AVIEAA0IACJGeaUjMIIIhBUE/zNABjTQMBwEVRcwcYBUCObLERABAgjAA1M/ZSa9hSi4OlpsnZ2KBOVUsAZlRGl1W0NZ6gSwhQPIAFs7YFL0QS91vRQgdgx2VDAnIegKpQvwAJIoLgooBAGtCJcHizg0TDiz8vhPgIcVXaDJu37ZyDAIjFy4AAiC1JAA0olJwN6nFAACQk/zfQVUuA3t5ELKMT9hpBSRj+HBSggYCIAIEkUoC4PCIQIWHGA8+IStxf0PW15ntP22gc+Wo+BLyQAHECQiAVd+c3Ba8gZ4NOACBaLOhsbIMkAXEmuHsSAvkf4oJaAogH6nEQAzoBIEihUAwAA0AACAObAGcANYAAABaqgMmpWAhiiVyrvNyqFV8tMQ5Iyq6r9Lf7W82i8ILl2cv8zbfdJ5lnPzSnvt/XXPfRR/5pH3xA+u5uS0798EPJ3Fft33w36Wc+8E/7aIB/9fvvb1pE9erNXLB9mkw//cSg8Cnx03VGXDvZnRzKqV9Xkhn7eRHb3wVjHpg19nkZpX9bBU+vnHegrsuQcbW9Bi7oO33h+8SU0Tly/MbeYUuG1cftVz6oNxrW0qH1iezq446fkDFo/rgvTkqt7zmteFzncZ0uxXti09FFF2z+C7CODpjUudOE5NSeb3bv/mbPKcmOEzpWQ3VBgBAuj/AOY3qMm7UvgA8qo3+qvAUBBAX0wADQG4AG+jCKaSxkBe/zvcTG8jRpRZgqhCog4sd++cOo7J+/RdvaaMoc78Ri/PNPSclx6fDvG1Kt3qQFi1rq+5EaavTtt9TVvfvucYsA5wc4oESU+E1ikF9TkrOkIsIH5Fawx7SBNLnMcWlM3skBluCmQNs7GeyFvYVDamsqwhxx18n/+WdxzDgOLU1AqbUlb3m+KOT9+ONvY/7XlrdLS//5v7nN97XHj0VzJpJvz4spq0V7ioXDuv2YrSxtF/KZmvbWfKtorXSh8dfvgGu+ev/nFSaXM6jyuCE/f/EpmuP7Rqu8R98vP/+tXUdrOgKlZP9Q/s+fRF9+3CHZd4EugfPv158me9X7v/6aSMCxMJmIr//65bNXngl7+uiPP5oKx+y27D8trp+psUzMacpm80XlnYeMAct0LynxhNYgaP3dtn8/LBAi+ksr+7NvjjtJV+UXrz0uLXV1x08AZRN1S2A8EaVE5afk/dfGjz//+fdHn36Xdttz2d9/z+bawZ8/slNN1aYtq+ZPGzNuPGdwOsfYflwmHCkPgs5DcKizvdpS/3gEUMW9/lvQvWp1bGTW1fpngC/uSVYAfH3TeSusL8TtlHUD4KAAEHjahk4YpuffGwEBN/dUmpHqdHyJ3Ap8O3UlQ4gCNrMRwLBBKiskL6a+RoBi/9XGlgc8L4/CUejQxaiIyqmomdhktOhfgDbDfaNDb4+yKIPd6IgmzDa0CByijWFmL2dlSRKagTIWXeIU9HDphoZiJeBTjAefHMxDhVSRFUgVcOkW3EGMahYVMFjWHhMluB2wAcbHYqF1LpsDF9C6s+CI2fDgh4wSuFEyGadjXAmIk3CugIRibLIti9ZtC8S4VSqfikGqPaoI122XyRYLBmsOmdiiTpqK1OklUQzpMcZmQRQV4M4oJCMkfRQXK+qvjifUcQd1bRdetW/LWjacYxvcttnVjWg5h0q4xw6rZyejSpZVZ78LzC4uyDNRQ4bymHSTMyM+SZ7D75mg/7YTlmNz7W8T00h0VEiGKB+F7iWYZFvSTiA4LVxttm2ATt5EoUWLJbY4EnLGrfsvEROlHtzlKn3H9VUT5tU/2dt3/EBv7foYzV/W4upyj04woO/gh6Vwwt3WGQAA) format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2) format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2) format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2) format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--color-bg: #F7F8FC;--color-surface: #ffffff;--color-primary: #2E5CE5;--color-primary-hover: #1D4ED8;--color-primary-subtle: #EEF2FF;--color-secondary: #64748B;--color-secondary-hover: #475569;--color-text: #1A1F36;--color-text-secondary: #697386;--color-muted: #94A3B8;--color-success: #22C55E;--color-error: #EF4444;--color-error-bg: #FEF2F2;--color-warning: #F59E0B;--color-border: #E2E8F0;--color-border-subtle: #F1F5F9;--color-hover: rgba(15, 23, 42, .06);--color-overlay: rgba(15, 23, 42, .5);--color-input-focus: rgba(46, 92, 229, .15);--shadow-xs: 0 1px 2px rgba(15, 23, 42, .05);--shadow-sm: 0 1px 3px rgba(15, 23, 42, .06), 0 1px 2px rgba(15, 23, 42, .04);--shadow-md: 0 4px 6px -1px rgba(15, 23, 42, .07), 0 2px 4px -2px rgba(15, 23, 42, .05);--shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, .08), 0 4px 6px -4px rgba(15, 23, 42, .04);--shadow-xl: 0 20px 25px -5px rgba(15, 23, 42, .1), 0 8px 10px -6px rgba(15, 23, 42, .06);--radius-sm: 8px;--radius-md: 12px;--radius-lg: 16px;--radius-full: 50%;--font-sans: "Plus Jakarta Sans Variable", "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--space-xs: 4px;--space-sm: 8px;--space-md: 16px;--space-lg: 24px;--space-xl: 32px;--space-2xl: 48px;--transition-fast: .12s ease;--transition-base: .2s ease}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;-webkit-text-size-adjust:100%}body{font-family:var(--font-sans);background:var(--color-bg);color:var(--color-text);line-height:1.6;min-height:100dvh;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#root{min-height:100dvh}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid transparent;border-radius:var(--radius-sm);font-family:var(--font-sans);font-size:.875rem;font-weight:600;padding:10px 18px;cursor:pointer;transition:all var(--transition-base);-webkit-tap-highlight-color:transparent;letter-spacing:-.01em;line-height:1.25}.btn:disabled{opacity:.45;cursor:not-allowed}.btn:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.btn-primary{background:var(--color-primary);color:#fff;box-shadow:0 1px 2px #2e5ce54d,inset 0 1px #ffffff1a}.btn-primary:hover:not(:disabled){background:var(--color-primary-hover);box-shadow:0 2px 4px #2e5ce559,inset 0 1px #ffffff1a;transform:translateY(-.5px)}.btn-primary:active:not(:disabled){transform:translateY(0);box-shadow:0 1px 2px #2e5ce54d}.btn-secondary{background:var(--color-surface);color:var(--color-text);border-color:var(--color-border);box-shadow:var(--shadow-xs)}.btn-secondary:hover:not(:disabled){background:var(--color-border-subtle);border-color:#cbd5e1}.btn-danger{background:var(--color-error);color:#fff;box-shadow:0 1px 2px #ef44444d}.btn-danger:hover:not(:disabled){background:#dc2626;box-shadow:0 2px 4px #ef444459}.btn-link{background:none;border:none;color:var(--color-primary);padding:0;font-weight:500;box-shadow:none}.btn-link:hover:not(:disabled){text-decoration:underline;background:none;box-shadow:none}.btn-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle;margin-right:6px}.btn-sm{font-size:.8125rem;padding:6px 12px;border-radius:6px}.btn-full{width:100%}.form-label{display:flex;flex-direction:column;gap:6px;font-size:.8125rem;font-weight:600;color:var(--color-text);margin-bottom:var(--space-md);letter-spacing:-.01em}.form-input,.form-textarea,.form-select{font-family:var(--font-sans);font-size:.9375rem;padding:10px 12px;border:1.5px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);transition:border-color var(--transition-fast),box-shadow var(--transition-fast);width:100%}.form-input:focus,.form-textarea:focus,.form-select:focus{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 3px var(--color-input-focus)}.form-input-mono{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.8125rem}.agent-picker-label{font-size:.8125rem;color:var(--color-text-secondary);white-space:nowrap}.form-input::placeholder,.form-textarea::placeholder{color:var(--color-muted)}.form-textarea{resize:vertical;min-height:80px;line-height:1.5}.form-error{background:var(--color-error-bg);color:var(--color-error);border:1px solid #FECACA;border-radius:var(--radius-sm);padding:var(--space-sm) var(--space-md);font-size:.8125rem;font-weight:500;margin-bottom:var(--space-md)}.form-warning{background:#fef3c7;color:#92400e;border:1px solid #FDE68A;border-radius:var(--radius-sm);padding:var(--space-sm) var(--space-md);font-size:.8125rem;font-weight:500;margin-bottom:var(--space-md)}.dashboard{min-height:100dvh;display:flex;flex-direction:column;padding-bottom:0}.dashboard-main{flex:1;padding:var(--space-md);width:100%}.status-dot{display:inline-block;width:8px;height:8px;border-radius:var(--radius-full);flex-shrink:0;box-shadow:0 0 0 2px #fffc}.status-spinner{display:inline-flex;align-items:center;gap:2px;flex-shrink:0;height:8px}.status-spinner:before,.status-spinner>span,.status-spinner:after{content:"";display:block;width:3px;height:3px;border-radius:var(--radius-full);background-color:var(--color-success);animation:marching-dot 1.2s ease-in-out infinite}.status-spinner:before{animation-delay:0s}.status-spinner>span{animation-delay:.2s}.status-spinner:after{animation-delay:.4s}@keyframes marching-dot{0%,60%,to{opacity:.25;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.pair-page{display:flex;align-items:flex-start;justify-content:center;min-height:100dvh;padding:var(--space-xl) var(--space-md);background:var(--color-bg)}.pair-card{width:100%;max-width:420px;display:flex;flex-direction:column;gap:var(--space-lg)}.pair-header{text-align:center}.pair-title{font-size:1.5rem;font-weight:700;color:var(--color-text);letter-spacing:-.02em}.pair-subtitle{margin-top:var(--space-xs);font-size:.875rem;color:var(--color-text-secondary)}.pair-instructions{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-md)}.pair-instruction-block{display:flex;flex-direction:column;gap:var(--space-sm)}.pair-instruction-heading{font-size:.8125rem;font-weight:600;color:var(--color-text);letter-spacing:-.01em}.pair-steps{list-style:none;counter-reset:pair-step;display:flex;flex-direction:column;gap:6px;padding:0}.pair-steps li{counter-increment:pair-step;font-size:.8125rem;color:var(--color-text-secondary);line-height:1.5;display:flex;flex-wrap:wrap;gap:0 var(--space-sm)}.pair-steps li:before{content:counter(pair-step) ".";font-weight:600;color:var(--color-muted);min-width:16px;flex-shrink:0}.pair-steps li .pair-command{flex-basis:100%}.pair-steps code{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:4px;padding:1px 5px;color:var(--color-text);white-space:nowrap}.pair-command{display:block;margin-top:4px;padding:6px 10px!important;border-radius:var(--radius-sm)!important;overflow-x:auto;-webkit-overflow-scrolling:touch}.pair-instruction-divider{height:1px;background:var(--color-border)}.pair-form{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-lg);display:flex;flex-direction:column;gap:var(--space-md);box-shadow:var(--shadow-sm)}.pair-code-input{font-size:1.5rem!important;letter-spacing:.25em;text-align:center;padding:14px 12px!important;text-transform:uppercase}.pair-code-input::placeholder{letter-spacing:.25em;opacity:.35}.pair-label-hint{font-weight:400;color:var(--color-muted);font-size:.75rem}.pair-error{font-size:.8125rem;color:var(--color-error);background:var(--color-error-bg);padding:var(--space-sm) var(--space-md);border-radius:var(--radius-sm);line-height:1.4}.loading-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--space-2xl) 0;gap:var(--space-sm)}.empty-state-text{font-size:1rem;font-weight:600;color:var(--color-text-secondary)}.empty-state-hint{font-size:.8125rem;color:var(--color-muted)}.revoked-state{display:flex;flex-direction:column;align-items:center;text-align:center;padding:var(--space-2xl) var(--space-lg);margin:var(--space-md) 0;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);animation:revokedFadeIn .35s ease}@keyframes revokedFadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.revoked-icon{width:56px;height:56px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-full);background:var(--color-error-bg);color:var(--color-error);margin-bottom:var(--space-md)}.revoked-title{font-size:1.125rem;font-weight:700;color:var(--color-text);margin-bottom:var(--space-xs)}.revoked-description{font-size:.875rem;line-height:1.6;color:var(--color-text-secondary);max-width:320px;margin-bottom:var(--space-lg)}.revoked-command{padding:8px 16px;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);margin-bottom:var(--space-lg)}.revoked-command code{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.8125rem;color:var(--color-text);letter-spacing:-.01em}.revoked-actions{display:flex;gap:var(--space-sm);width:100%;max-width:280px}.revoked-actions .btn{flex:1}.spinner{width:24px;height:24px;border:2.5px solid var(--color-border);border-top-color:var(--color-primary);border-radius:var(--radius-full);animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.new-task-input-card{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md);margin-bottom:var(--space-sm);cursor:pointer}.new-task-input-card:hover{border-color:var(--color-primary)}.new-task-placeholder{font-size:.875rem;color:var(--color-muted)}.section-label{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-muted);margin:var(--space-md) 0 var(--space-xs)}.agent-picker-section-inline{display:flex;align-items:center;gap:var(--space-xs);margin-left:auto}.agent-picker-section-inline .agent-picker-label{font-size:.8rem;color:var(--color-text-secondary);white-space:nowrap}.agent-picker-section-inline .form-select{width:auto;min-width:0;font-size:.8rem;padding:4px 8px}.task-list{display:flex;flex-direction:column;gap:10px}.task-card{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-sm);cursor:pointer;transition:box-shadow var(--transition-base),border-color var(--transition-base);-webkit-tap-highlight-color:transparent}.task-card:hover{box-shadow:var(--shadow-md);border-color:#cbd5e1}.task-card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm)}.task-card-title-row{display:flex;align-items:center;gap:10px;min-width:0;flex:1}.task-card-name{font-size:.9375rem;font-weight:600;letter-spacing:-.01em;color:var(--color-text);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.task-card-meta{display:flex;flex-wrap:wrap;gap:var(--space-sm);font-size:.75rem;color:var(--color-text-secondary)}.task-card-agent{color:var(--color-text-tertiary)}.task-card-last-event-link{color:var(--color-primary);cursor:pointer;text-decoration:underline}.task-card-actions{display:flex;gap:var(--space-xs);flex-shrink:0}.task-card-menu{position:relative}.task-card-menu-btn{background:none;border:none;color:var(--color-text-secondary);font-size:1.5rem;min-width:44px;min-height:44px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);cursor:pointer;line-height:1}.task-card-menu-btn:hover{background:var(--color-hover);color:var(--color-text)}.task-card-menu-dropdown{position:absolute;right:0;top:100%;margin-top:4px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-md);min-width:150px;z-index:10;overflow:hidden}.task-card-menu-dropdown button{display:flex;align-items:center;gap:var(--space-sm);width:100%;text-align:left;padding:var(--space-sm) var(--space-md);background:none;border:none;font-size:.9375rem;color:var(--color-text);cursor:pointer;white-space:nowrap}.task-card-menu-dropdown button:hover{background:var(--color-hover)}.task-card-menu-dropdown .menu-item-danger{color:var(--color-error)}.menu-icon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;font-size:.875rem;flex-shrink:0;line-height:1}.bottom-sheet-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);z-index:1000;display:flex;align-items:flex-end;justify-content:center;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn var(--transition-fast)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.bottom-sheet{background:var(--color-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;width:100%;max-width:480px;padding:var(--space-sm) var(--space-md) var(--space-xl);box-shadow:var(--shadow-xl);animation:sheetSlideUp .25s cubic-bezier(.16,1,.3,1)}@keyframes sheetSlideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.bottom-sheet-handle{width:36px;height:4px;background:var(--color-border);border-radius:2px;margin:0 auto var(--space-md)}.bottom-sheet-title{font-size:.9375rem;font-weight:600;color:var(--color-text);padding:0 var(--space-xs) var(--space-md);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bottom-sheet-actions{display:flex;flex-direction:column}.bottom-sheet-actions button{display:flex;align-items:center;gap:var(--space-md);width:100%;text-align:left;padding:var(--space-md);background:none;border:none;border-radius:var(--radius-sm);font-size:.9375rem;font-family:var(--font-sans);color:var(--color-text);cursor:pointer;transition:background var(--transition-fast)}.bottom-sheet-actions button:active{background:var(--color-hover)}.bottom-sheet-actions .menu-item-danger{color:var(--color-error)}.task-form-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:transparent;display:flex;align-items:stretch;justify-content:center;z-index:100;padding:0;overflow:hidden;overscroll-behavior:contain}.task-form{background:var(--color-surface);border-radius:0;padding:var(--space-lg) var(--space-md);width:100%;max-width:none;max-height:none;overflow-y:auto;animation:slideUp .25s cubic-bezier(.16,1,.3,1);display:flex;flex-direction:column;gap:var(--space-md)}@media(min-width:600px){.task-form-overlay{background:var(--color-overlay);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);align-items:center;padding:var(--space-md)}.task-form{border-radius:var(--radius-lg);box-shadow:var(--shadow-xl);max-width:540px;max-height:90dvh;padding:var(--space-lg)}}@keyframes slideUp{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}.task-form-header{display:flex;align-items:center;justify-content:space-between}.task-form h2{font-size:1.0625rem;font-weight:700;letter-spacing:-.02em;margin-bottom:0}.plan-actions{display:flex;gap:var(--space-sm);flex-wrap:wrap}.plan-dialog{display:flex;flex-direction:column;gap:var(--space-md);overflow:hidden;flex:1;min-height:0}.plan-dialog h2{margin:0;font-size:1.125rem}.plan-dialog-scroll{flex:1;min-height:0;overflow-y:auto}.plan-dialog .plan-preview{max-height:none}.plan-empty{color:var(--color-text-secondary);font-size:.875rem;font-style:italic;margin:var(--space-md) 0}.permissions-section{border-top:1px solid var(--color-border);padding-top:var(--space-md);margin-top:var(--space-md)}.permissions-section h3{margin:0 0 var(--space-xs) 0;font-size:.95rem}.permissions-list{margin:0;padding-left:1.25em}.permission-item{margin-bottom:var(--space-sm);display:flex;flex-direction:column}.permission-tool{font-weight:600;font-family:var(--font-mono, monospace)}.permission-desc{color:var(--color-text-secondary);font-size:.9em}.plan-dialog-actions{display:flex;gap:var(--space-sm);align-items:center;justify-content:flex-end}@media(min-width:600px){.plan-dialog{max-width:none}}.result-times{display:flex;flex-direction:column;gap:var(--space-xs);font-size:.8125rem;color:var(--color-text-secondary);margin-bottom:var(--space-sm)}.skeleton-line{height:.875rem;border-radius:var(--radius-sm);background:var(--color-border);animation:skeleton-pulse 1.2s ease-in-out infinite}@keyframes skeleton-pulse{0%,to{opacity:.4}50%{opacity:1}}.plan-preview{font-size:.8125rem;line-height:1.6;color:var(--color-text-secondary)}.plan-preview h1,.plan-preview h2,.plan-preview h3,.plan-preview h4{color:var(--color-text);margin:var(--space-sm) 0 var(--space-xs) 0}.plan-preview h1{font-size:1.1rem}.plan-preview h2{font-size:1rem}.plan-preview h3{font-size:.9375rem}.plan-preview p{margin:var(--space-xs) 0}.plan-preview ul,.plan-preview ol{padding-left:var(--space-md);margin:var(--space-xs) 0}.plan-preview code{background:var(--color-border);border-radius:3px;padding:1px 4px;font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem}.plan-preview pre{background:var(--color-border);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto}.plan-preview pre code{background:none;padding:0}.plan-preview table{width:100%;border-collapse:collapse;margin:var(--space-xs) 0}.plan-preview th,.plan-preview td{border:1px solid var(--color-border);padding:var(--space-xs) var(--space-sm);text-align:left}.plan-preview th{background:var(--color-border);color:var(--color-text);font-weight:600}.triggers-section-body{display:flex;flex-direction:column;gap:2px}.triggers-section-body.disabled{opacity:.4;pointer-events:none}.trigger-row-card{display:flex;align-items:center;gap:8px;padding:4px 0}.trigger-row-content{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}.trigger-row-top{display:flex;align-items:center;gap:8px}.trigger-row-top .form-select{flex:0 0 auto;width:auto}.trigger-row-top .form-input{flex:1}.trigger-details{display:flex;gap:6px}.trigger-details .form-input[type=date]{flex:1;min-width:0}.trigger-details .form-input[type=time]{flex:0 0 auto;width:auto}.trigger-row-card .form-select,.trigger-row-card .form-input{margin-bottom:0;font-size:.8125rem;padding:6px 8px;height:32px;box-sizing:border-box;min-width:0;flex:1}.trigger-remove-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:none;border-radius:var(--radius-full);background:transparent;color:var(--color-muted);font-size:1.1rem;line-height:1;cursor:pointer;transition:all var(--transition-fast);flex-shrink:0}.trigger-remove-btn:hover{background:var(--color-error-bg);color:var(--color-error)}.trigger-add-btn{display:inline-flex;align-items:center;gap:4px;border:1px dashed var(--color-border);border-radius:var(--radius-sm);background:transparent;color:var(--color-text-secondary);font-family:var(--font-sans);font-size:.8125rem;font-weight:500;padding:8px 14px;cursor:pointer;transition:all var(--transition-fast);width:100%;justify-content:center}.trigger-add-btn:hover{border-color:var(--color-primary);color:var(--color-primary);background:var(--color-primary-subtle)}.form-select{width:auto;min-width:80px}.toggles-section{display:flex;flex-direction:column;gap:10px;margin-bottom:var(--space-md)}.toggle-label{display:flex;align-items:center;gap:10px;font-size:.875rem;font-weight:500;cursor:pointer;-webkit-tap-highlight-color:transparent;color:var(--color-text)}.toggle-label input[type=checkbox]{width:16px;height:16px;accent-color:var(--color-primary);border-radius:4px}.toggles-group{display:flex;flex-direction:column;gap:var(--space-xs)}.command-section,.command-section-active .toggle-label{margin-bottom:0}.command-help-text{font-size:.75rem;color:var(--color-text-secondary);line-height:1.4;margin:6px 0 10px}.command-section-active .form-input{font-size:.8125rem;padding:6px 8px;height:32px;box-sizing:border-box}.form-actions{display:flex;gap:var(--space-sm);position:sticky;bottom:0;background:var(--color-surface);padding:var(--space-sm) 0}.confirm-modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);display:flex;align-items:center;justify-content:center;z-index:1000;padding:var(--space-md);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.confirm-modal{background:var(--color-surface);border-radius:var(--radius-lg);border:1px solid var(--color-border);padding:var(--space-xl);width:100%;max-width:380px;text-align:center;box-shadow:var(--shadow-xl);animation:slideUp .25s cubic-bezier(.16,1,.3,1)}.confirm-modal-title{font-size:1.125rem;font-weight:700;letter-spacing:-.02em;margin-bottom:var(--space-sm)}.confirm-modal-message{font-size:.9375rem;color:var(--color-text-secondary);margin-bottom:var(--space-lg);line-height:1.5}.confirm-modal-actions{display:flex;gap:var(--space-sm);justify-content:center}.confirm-modal-actions .btn{flex:1;padding:10px var(--space-lg);font-size:.875rem}.permission-modal{text-align:left;max-width:400px}.permission-modal .confirm-modal-title{text-align:center}.permission-modal .confirm-modal-message{text-align:center;word-break:break-word}.permission-list{display:flex;flex-direction:column;gap:var(--space-xs);margin-bottom:var(--space-lg)}.permission-item{display:flex;flex-direction:column;gap:2px;padding:var(--space-sm);background:var(--color-hover);border-radius:var(--radius-sm)}.permission-name{font-size:.8125rem;font-weight:600;color:var(--color-text)}.permission-desc{font-size:.75rem;color:var(--color-text-secondary);line-height:1.4}.permission-actions{display:flex;gap:var(--space-sm)}.permission-actions .btn{flex:1;padding:10px var(--space-md);font-size:.875rem}.permission-abort-link{display:block;width:100%;margin-top:var(--space-sm);background:none;border:none;color:var(--color-error);font-size:.8125rem;cursor:pointer;text-align:center;padding:var(--space-xs) 0;opacity:.8}.permission-abort-link:hover{opacity:1;text-decoration:underline}.input-modal{text-align:left;max-width:400px}.input-modal .confirm-modal-title{text-align:center}.input-modal .confirm-modal-message{text-align:center;word-break:break-word}.input-list{display:flex;flex-direction:column;gap:var(--space-md);margin-bottom:var(--space-lg)}.input-item{display:flex;flex-direction:column;gap:var(--space-xs)}.input-label{font-size:.8125rem;font-weight:600;color:var(--color-text)}.input-field{width:100%;padding:var(--space-sm) var(--space-md);font-size:.875rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);box-sizing:border-box}.input-field:focus{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb, 99, 102, 241),.2)}.input-actions{display:flex;gap:var(--space-sm)}.input-actions .btn{flex:1;padding:10px var(--space-md);font-size:.875rem}.host-picker-inline{border:1px solid var(--color-border);border-radius:var(--radius-md);overflow:hidden}.host-picker-list{max-height:240px;overflow-y:auto;padding:var(--space-xs) 0}.host-picker-item-wrapper{position:relative}.host-picker-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:none;background:none;cursor:pointer;font-family:var(--font-sans);font-size:.8125rem;color:var(--color-text);text-align:left;transition:background var(--transition-fast);-webkit-tap-highlight-color:transparent}.host-picker-item:hover{background:var(--color-border-subtle)}.host-picker-item-active,.host-picker-item-active:hover{background:var(--color-primary-subtle)}.host-picker-item-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px}.host-picker-pending{font-size:.6875rem;font-weight:500;color:var(--color-muted);background:var(--color-border-subtle);padding:1px 6px;border-radius:4px;flex-shrink:0}.host-picker-item-actions{flex-shrink:0;width:24px;display:flex;align-items:center;justify-content:center}.host-picker-edit-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:none;cursor:pointer;color:var(--color-muted);border-radius:4px;flex-shrink:0;transition:all var(--transition-fast)}.host-picker-edit-btn:hover{color:var(--color-primary);background:var(--color-primary-subtle)}.host-picker-rename-input{font-size:.8125rem!important;padding:3px 6px!important;flex:1;min-width:0}.host-picker-delete-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:none;cursor:pointer;color:var(--color-muted);border-radius:4px;flex-shrink:0;transition:all var(--transition-fast)}.host-picker-delete-btn:hover{color:var(--color-error);background:var(--color-error-bg)}.hamburger-btn{display:flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:none;background:none;color:var(--color-text);cursor:pointer;border-radius:var(--radius-sm);transition:background var(--transition-fast)}.hamburger-btn:hover{background:var(--color-primary-subtle)}.drawer-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);z-index:100;animation:drawerFadeIn .2s ease}.drawer-panel{position:fixed;top:0;left:0;bottom:0;width:280px;max-width:80vw;background:var(--color-surface);box-shadow:var(--shadow-xl);z-index:101;display:flex;flex-direction:column;animation:drawerSlideIn .25s ease}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-md);border-bottom:1px solid var(--color-border)}.drawer-title{font-size:1.125rem;font-weight:800;color:var(--color-primary);letter-spacing:-.04em}.drawer-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:none;background:none;color:var(--color-text-secondary);cursor:pointer;border-radius:var(--radius-sm);transition:background var(--transition-fast)}.drawer-close-btn:hover{background:var(--color-border-subtle);color:var(--color-text)}.drawer-section{padding:var(--space-md)}.drawer-section-label{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-muted);margin-bottom:var(--space-sm)}.drawer-footer{margin-top:auto;padding:var(--space-md)}.drawer-version{font-size:.75rem;color:var(--color-muted)}.drawer-legal{font-size:.75rem;color:var(--color-muted);margin-top:var(--space-xs)}.drawer-legal a{color:var(--color-muted);text-decoration:none}.drawer-legal a:hover{text-decoration:underline}.drawer-legal-sep{margin:0 var(--space-xs)}.drawer-divider{height:1px;background:var(--color-border);margin:0 var(--space-md)}@keyframes drawerFadeIn{0%{opacity:0}to{opacity:1}}@keyframes drawerFadeOut{0%{opacity:1}to{opacity:0}}@keyframes drawerSlideIn{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes drawerSlideOut{0%{transform:translate(0)}to{transform:translate(-100%)}}.drawer-overlay-closing{animation:drawerFadeOut .2s ease forwards}.drawer-panel-closing{animation:drawerSlideOut .2s ease forwards}.tab-bar{display:flex;align-items:center;background:color-mix(in srgb,var(--color-surface) 92%,transparent);border-bottom:1px solid var(--color-border);position:sticky;top:0;z-index:10;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:12px 0;border:none;background:none;font-family:var(--font-sans);font-size:.875rem;font-weight:600;color:var(--color-text-secondary);cursor:pointer;border-bottom:2px solid transparent;transition:color var(--transition-fast),border-color var(--transition-fast)}.tab-icon{flex-shrink:0}.tab-btn:hover{color:var(--color-text)}.tab-btn-active{color:var(--color-primary);border-bottom-color:var(--color-primary)}.runs-view{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}.runs-card{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md);display:flex;align-items:center;gap:var(--space-sm);cursor:pointer;transition:box-shadow var(--transition-base),border-color var(--transition-base);-webkit-tap-highlight-color:transparent}.runs-card:hover{box-shadow:var(--shadow-md);border-color:#cbd5e1}.runs-card-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-xs)}.runs-card-name{font-size:.9375rem;font-weight:600;letter-spacing:-.01em;color:var(--color-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.runs-card-meta{display:flex;flex-wrap:wrap;gap:var(--space-sm);font-size:.75rem;color:var(--color-text-secondary)}.runs-card-chevron{flex-shrink:0;align-self:center;color:var(--color-text-secondary);font-size:1.25rem;line-height:1;opacity:.4;transition:opacity var(--transition-base)}.runs-card:hover .runs-card-chevron{opacity:.8}.runs-filter-chip{display:inline-flex;align-items:center;gap:var(--space-xs);padding:4px 10px;font-size:.8125rem;color:var(--color-text-secondary);background:var(--color-bg);border:1px solid var(--color-border);border-radius:999px}.runs-filter-chip button{display:inline-flex;align-items:center;justify-content:center;border:none;background:none;cursor:pointer;padding:0;color:var(--color-text-secondary);font-size:1rem;line-height:1}.runs-filter-chip button:hover{color:var(--color-text)}.run-detail{display:flex;flex-direction:column;gap:var(--space-md);background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md)}.run-detail-back{display:inline-flex;align-items:center;gap:var(--space-xs);border:none;background:none;cursor:pointer;padding:0;color:var(--color-text-secondary);font-size:.8125rem}.run-detail-back:hover{color:var(--color-text)}.chat-thread{display:flex;flex-direction:column;gap:var(--space-sm);overflow-y:auto;flex:1;min-height:0}.chat-message{display:flex;flex-direction:column;gap:var(--space-xs);max-width:85%;min-width:0;padding:var(--space-sm) var(--space-md);border-radius:var(--radius-md);font-size:.8125rem;line-height:1.6}.chat-message--assistant{align-self:flex-start;background:var(--color-surface);border:1px solid var(--color-border)}.chat-message--user{align-self:flex-end;background:var(--color-primary-subtle);border:1px solid var(--color-border-subtle)}.chat-message-agent{font-size:.6875rem;font-weight:500;color:var(--color-text-tertiary);margin-bottom:var(--space-xs)}.chat-message-content{color:var(--color-text);overflow-wrap:break-word}.chat-message-content h1,.chat-message-content h2,.chat-message-content h3,.chat-message-content h4{margin:.75em 0 .25em;font-weight:600}.chat-message-content h1{font-size:1.1rem}.chat-message-content h2{font-size:1rem}.chat-message-content h3{font-size:.9375rem}.chat-message-content p{margin:.5em 0}.chat-message-content ul,.chat-message-content ol{margin:.5em 0;padding-left:1.5em}.chat-message-content code{font-size:.8em;background:var(--color-hover);padding:.15em .35em;border-radius:4px}.chat-message-content pre{background:var(--color-bg);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto;margin:.5em 0}.chat-message-content pre code{background:none;padding:0}.chat-message-content table{border-collapse:collapse;width:100%;margin:.5em 0}.chat-message-content th,.chat-message-content td{border:1px solid var(--color-border);padding:.35em .6em;text-align:left}.chat-message-content th{background:var(--color-bg);font-weight:600}.chat-message-meta{display:flex;align-items:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted)}.chat-message-type{font-weight:600;text-transform:uppercase;letter-spacing:.04em;font-size:.625rem}.chat-message-attachments{display:flex;flex-wrap:wrap;gap:var(--space-xs)}.chat-attachment-chip{display:inline-flex;align-items:center;padding:.2em .6em;font-size:.75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);cursor:pointer;color:var(--color-primary);font-weight:500}.chat-attachment-chip:hover{background:var(--color-primary-subtle)}.chat-message-report{margin-top:var(--space-xs);padding:var(--space-sm);background:var(--color-bg);border-radius:var(--radius-sm);border:1px solid var(--color-border);font-size:.8125rem;line-height:1.6;color:var(--color-text-secondary)}.chat-status{display:flex;align-items:center;justify-content:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted);padding:var(--space-xs) 0}.chat-status-time{color:var(--color-muted)}.chat-monitoring-indicator{display:flex;align-items:center;justify-content:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted);padding:var(--space-md) 0}.chat-monitoring-dot{width:6px;height:6px;border-radius:50%;background:var(--color-muted);animation:monitoring-pulse 1.5s ease-in-out infinite}@keyframes monitoring-pulse{0%,to{opacity:.3}50%{opacity:1}}.chat-abort-bar{display:flex;justify-content:center;padding:var(--space-sm) 0;flex-shrink:0}.chat-abort-btn{color:var(--color-error);border-color:var(--color-error)}.chat-abort-btn:hover:not(:disabled){background:var(--color-error-bg)}.chat-stop-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border-radius:50%;flex-shrink:0;margin-left:auto;background:var(--color-error);color:#fff;border-color:var(--color-error)}.chat-stop-btn:hover:not(:disabled){background:#dc2626;border-color:#dc2626}.chat-input-bar{display:flex;gap:var(--space-xs);padding:var(--space-sm) 0;flex-shrink:0}.chat-input{flex:1;min-width:0;padding:var(--space-sm) var(--space-md);border:1px solid var(--color-border);border-radius:var(--radius-md);font-size:.8125rem;outline:none;background:var(--color-surface);color:var(--color-text)}.chat-input:focus{border-color:var(--color-primary);box-shadow:0 0 0 2px var(--color-primary-subtle)}.chat-input:disabled{opacity:.6}.chat-send-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border-radius:50%;flex-shrink:0}.report-dialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;background:var(--color-overlay);display:flex;align-items:stretch;justify-content:center}.report-dialog{display:flex;flex-direction:column;width:100%;max-width:800px;background:var(--color-surface);overflow:hidden}.report-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-sm) var(--space-md);border-bottom:1px solid var(--color-border);flex-shrink:0}.report-dialog-title{font-size:.875rem;font-weight:600;color:var(--color-text)}.report-dialog-close{display:flex;align-items:center;justify-content:center;border:none;background:none;cursor:pointer;color:var(--color-text-secondary);padding:4px;border-radius:var(--radius-sm)}.report-dialog-close:hover{background:var(--color-hover);color:var(--color-text)}.report-dialog-body{flex:1;overflow-y:auto;padding:var(--space-md);font-size:.8125rem;line-height:1.6;color:var(--color-text)}.report-dialog-body h1,.report-dialog-body h2,.report-dialog-body h3,.report-dialog-body h4{margin:.75em 0 .25em;font-weight:600}.report-dialog-body h1{font-size:1.1rem}.report-dialog-body h2{font-size:1rem}.report-dialog-body h3{font-size:.9375rem}.report-dialog-body p{margin:.5em 0}.report-dialog-body img{max-width:100%;height:auto}.report-dialog-body ul,.report-dialog-body ol{margin:.5em 0;padding-left:1.5em}.report-dialog-body code{font-size:.8em;background:var(--color-hover);padding:.15em .35em;border-radius:4px}.report-dialog-body pre{background:var(--color-bg);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto;margin:.5em 0}.report-dialog-body pre code{background:none;padding:0}.report-dialog-body table{border-collapse:collapse;width:100%;margin:.5em 0}.report-dialog-body th,.report-dialog-body td{border:1px solid var(--color-border);padding:.35em .6em;text-align:left}.report-dialog-body th{background:var(--color-bg);font-weight:600}.chat-typing-indicator{display:flex;align-items:center;gap:4px;padding:4px 0}.chat-typing-indicator span{width:6px;height:6px;border-radius:50%;background:var(--color-muted);animation:chat-typing 1.4s infinite}.chat-typing-indicator span:nth-child(2){animation-delay:.2s}.chat-typing-indicator span:nth-child(3){animation-delay:.4s}@keyframes chat-typing{0%,60%,to{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.pair-consent{font-size:.75rem;color:var(--color-muted);text-align:center;line-height:1.5}.pair-consent a{color:var(--color-muted);text-decoration:underline}.pair-consent a:hover{color:var(--color-text-secondary)}.dashboard-content{display:flex;flex-direction:column;flex:1;min-height:100dvh;max-width:800px;margin:0 auto;width:100%}.drawer-panel-desktop{position:sticky;top:0;height:100dvh;width:280px;min-width:280px;background:var(--color-surface);border-right:1px solid var(--color-border);display:flex;flex-direction:column;overflow-y:auto;animation:none}@media(min-width:768px){.dashboard{flex-direction:row}.dashboard-main{padding:var(--space-lg)}}
Binary file
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#2E5CE5" />
7
+ <link rel="icon" type="image/x-icon" href="/favicon.ico" />
8
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
9
+ <title>Palmier</title>
10
+ <meta name="description" content="Run your own AI agents from your phone. Schedule tasks, monitor runs, and stay in control." />
11
+ <script type="module" crossorigin src="/assets/index-C7Ib48wG.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-_AmC1Rkn.css">
13
+ <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -0,0 +1 @@
1
+ {"name":"Palmier","short_name":"Palmier","description":"Remotely schedule and manage autonomous tasks on your machines","start_url":"/","display":"standalone","background_color":"#ffffff","theme_color":"#2E5CE5","lang":"en","scope":"/","icons":[{"src":"pwa-192x192.png","sizes":"192x192","type":"image/png"},{"src":"pwa-512x512.png","sizes":"512x512","type":"image/png"}]}
Binary file
Binary file
@@ -0,0 +1 @@
1
+ if('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/service-worker.js', { scope: '/' })})}
@@ -0,0 +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":"71a4d15dd177ae999a3a05f4c859695f","url":"index.html"},{"revision":null,"url":"assets/index-_AmC1Rkn.css"},{"revision":null,"url":"assets/index-C7Ib48wG.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":"4b3fc21d55063c21d167d6b9f39ae5a2","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&&(t.type==="complete"||t.type==="fail")?`/runs/${encodeURIComponent(n)}/${encodeURIComponent(i)}`:n&&(t.type==="complete"||t.type==="fail")?`/runs/${encodeURIComponent(n)}`:"/";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,5 +1,9 @@
1
1
  import { type NatsConnection } from "nats";
2
2
  import type { HostConfig, RpcMessage } from "./types.js";
3
+ /**
4
+ * Parse RESULT frontmatter and conversation messages.
5
+ */
6
+ export declare function parseResultFrontmatter(raw: string): Record<string, unknown>;
3
7
  /**
4
8
  * Create a transport-agnostic RPC handler bound to the given config.
5
9
  */
@@ -19,7 +19,7 @@ const PLAN_GENERATION_PROMPT = fs.readFileSync(path.join(__dirname, "commands",
19
19
  /**
20
20
  * Parse RESULT frontmatter and conversation messages.
21
21
  */
22
- function parseResultFrontmatter(raw) {
22
+ export function parseResultFrontmatter(raw) {
23
23
  const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
24
24
  if (!fmMatch)
25
25
  return { messages: [] };
@@ -1,14 +1,13 @@
1
1
  import * as http from "node:http";
2
2
  import * as os from "os";
3
+ import * as path from "node:path";
3
4
  import { StringCodec } from "nats";
4
5
  import { validateClient, addClient } from "../client-store.js";
5
6
  import { registerPending } from "../pending-requests.js";
6
7
  import * as fs from "node:fs";
7
8
  import { getTaskDir, parseTaskFile, spliceUserMessage } from "../task.js";
8
- const PWA_ORIGIN = "https://app.palmier.me";
9
9
  const assetCache = new Map();
10
- /** Paths currently being fetched (dedup concurrent requests). */
11
- const assetInflight = new Map();
10
+ const PWA_DIR = path.join(import.meta.dirname, "..", "pwa");
12
11
  const CONTENT_TYPES = {
13
12
  ".html": "text/html; charset=utf-8",
14
13
  ".js": "application/javascript",
@@ -19,6 +18,7 @@ const CONTENT_TYPES = {
19
18
  ".woff2": "font/woff2",
20
19
  ".woff": "font/woff",
21
20
  ".svg": "image/svg+xml",
21
+ ".webmanifest": "application/manifest+json",
22
22
  };
23
23
  function guessContentType(urlPath) {
24
24
  if (urlPath === "/")
@@ -26,46 +26,32 @@ function guessContentType(urlPath) {
26
26
  const ext = urlPath.match(/\.[^.]+$/)?.[0] ?? "";
27
27
  return CONTENT_TYPES[ext] ?? "application/octet-stream";
28
28
  }
29
- async function fetchBuffer(url) {
30
- const res = await fetch(url);
31
- if (!res.ok)
32
- throw new Error(`${res.status} ${res.statusText} for ${url}`);
33
- return Buffer.from(await res.arrayBuffer());
34
- }
35
29
  /**
36
- * Fetch a PWA asset on-the-fly, caching in memory.
37
- * Returns null if the asset cannot be fetched.
30
+ * Read a PWA asset from the bundled pwa/ directory, caching in memory.
31
+ * Returns null if the file does not exist.
38
32
  */
39
- async function getAsset(urlPath) {
33
+ function getAsset(urlPath) {
40
34
  const cached = assetCache.get(urlPath);
41
35
  if (cached)
42
36
  return cached;
43
- // Dedup concurrent requests for the same path
44
- const inflight = assetInflight.get(urlPath);
45
- if (inflight)
46
- return inflight;
47
- const promise = (async () => {
48
- try {
49
- let data = await fetchBuffer(`${PWA_ORIGIN}${urlPath}`);
50
- // Inject LAN mode marker into index HTML so the PWA can detect it's served by palmier
51
- if (urlPath === "/") {
52
- const html = data.toString("utf-8").replace("</head>", "<script>window.__PALMIER_SERVE__=true</script></head>");
53
- data = Buffer.from(html, "utf-8");
54
- }
55
- const asset = { data, contentType: guessContentType(urlPath) };
56
- assetCache.set(urlPath, asset);
57
- return asset;
58
- }
59
- catch (err) {
60
- console.warn(`[pwa] Failed to fetch ${urlPath}: ${err}`);
61
- return null;
62
- }
63
- finally {
64
- assetInflight.delete(urlPath);
37
+ const filePath = path.join(PWA_DIR, urlPath === "/" ? "index.html" : urlPath);
38
+ // Prevent path traversal
39
+ if (!filePath.startsWith(PWA_DIR))
40
+ return null;
41
+ try {
42
+ let data = fs.readFileSync(filePath);
43
+ // Inject marker into index HTML so the PWA can detect it's served by palmier
44
+ if (urlPath === "/") {
45
+ const html = data.toString("utf-8").replace("</head>", "<script>window.__PALMIER_SERVE__=true</script></head>");
46
+ data = Buffer.from(html, "utf-8");
65
47
  }
66
- })();
67
- assetInflight.set(urlPath, promise);
68
- return promise;
48
+ const asset = { data, contentType: guessContentType(urlPath) };
49
+ assetCache.set(urlPath, asset);
50
+ return asset;
51
+ }
52
+ catch {
53
+ return null;
54
+ }
69
55
  }
70
56
  const pendingPairs = new Map();
71
57
  export function detectLanIp() {
@@ -354,7 +340,7 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
354
340
  }
355
341
  return;
356
342
  }
357
- // ── Public pair endpoint — no auth, PWA posts OTP code here ────────
343
+ // ── Public pair endpoint — no auth, PWA posts pairing code here ────────
358
344
  if (req.method === "POST" && pathname === "/pair") {
359
345
  try {
360
346
  const body = await readBody(req);
@@ -395,9 +381,9 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
395
381
  return;
396
382
  }
397
383
  // Try exact path, then fall back to index.html (SPA routing)
398
- let asset = await getAsset(pathname);
384
+ let asset = getAsset(pathname);
399
385
  if (!asset && pathname !== "/") {
400
- asset = await getAsset("/");
386
+ asset = getAsset("/");
401
387
  }
402
388
  if (asset) {
403
389
  res.writeHead(200, { "Content-Type": asset.contentType });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Palmier host CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hongxu Cai",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "scripts": {
22
22
  "dev": "tsx src/index.ts",
23
- "build": "tsc && node -e \"require('fs').cpSync('src/commands/plan-generation.md','dist/commands/plan-generation.md');require('fs').cpSync('src/agents/agent-instructions.md','dist/agents/agent-instructions.md')\"",
23
+ "build": "tsc && node -e \"const fs=require('fs');fs.cpSync('src/commands/plan-generation.md','dist/commands/plan-generation.md');fs.cpSync('src/agents/agent-instructions.md','dist/agents/agent-instructions.md');const p=process.env.PALMIER_PWA_DIST||'../palmier-server/pwa/dist';if(fs.existsSync(p))fs.cpSync(p,'dist/pwa',{recursive:true});else console.warn('PWA dist not found at '+p+', skipping')\"",
24
24
  "test": "tsx --test test/**/*.test.ts",
25
25
  "prepare": "npm run build",
26
26
  "start": "node dist/index.js"
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches-ignore: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v6
13
+ - uses: pnpm/action-setup@v4
14
+ - uses: actions/setup-node@v6
15
+ with:
16
+ node-version: 24
17
+ cache: pnpm
18
+ - run: pnpm install --frozen-lockfile
19
+ - run: cd server && pnpm build
20
+ - run: cd pwa && pnpm build
21
+ # - run: pnpm test
@@ -0,0 +1,38 @@
1
+ name: Deploy
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v6
12
+ - uses: pnpm/action-setup@v4
13
+ - uses: actions/setup-node@v6
14
+ with:
15
+ node-version: 24
16
+ cache: pnpm
17
+ - run: pnpm install --frozen-lockfile
18
+ - run: cd server && pnpm build
19
+ - run: cd pwa && pnpm build
20
+ # - run: pnpm test
21
+
22
+ deploy:
23
+ needs: build
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - name: Deploy to VPS
27
+ uses: appleboy/ssh-action@v1
28
+ with:
29
+ host: ${{ secrets.VPS_HOST }}
30
+ username: ${{ secrets.VPS_USER }}
31
+ key: ${{ secrets.SSH_PRIVATE_KEY }}
32
+ script: |
33
+ cd ~/source/palmier-server
34
+ git pull
35
+ pnpm install --frozen-lockfile
36
+ cd pwa && pnpm build && cd ..
37
+ cd server && pnpm build && cd ..
38
+ sudo systemctl restart palmier-server
@@ -0,0 +1,13 @@
1
+ # CLAUDE.md
2
+
3
+ ## Getting Started
4
+
5
+ Always read `spec.md` and `README.md` first before starting any task. These files contain the project specification and setup instructions that should inform all work.
6
+
7
+ ## PWA
8
+
9
+ The PWA (`pwa/`) must be a mobile-friendly, responsive web app. All UI components and layouts should be designed mobile-first and work well across screen sizes.
10
+
11
+ ## Documentation
12
+
13
+ When making architectural changes, update `spec.md` and `README.md` to reflect the new state. If the change also affects the palmier (host) repo, update its `README.md` as well.