palmier 0.6.0 → 0.6.2

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 (110) 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/agents/agent-instructions.md +1 -1
  6. package/dist/agents/agent.d.ts +2 -0
  7. package/dist/agents/agent.js +21 -0
  8. package/dist/agents/aider.d.ts +9 -0
  9. package/dist/agents/aider.js +32 -0
  10. package/dist/agents/cursor.d.ts +9 -0
  11. package/dist/agents/cursor.js +35 -0
  12. package/dist/agents/deepagents.d.ts +9 -0
  13. package/dist/agents/deepagents.js +35 -0
  14. package/dist/agents/droid.d.ts +9 -0
  15. package/dist/agents/droid.js +32 -0
  16. package/dist/agents/goose.d.ts +9 -0
  17. package/dist/agents/goose.js +32 -0
  18. package/dist/agents/opencode.d.ts +9 -0
  19. package/dist/agents/opencode.js +35 -0
  20. package/dist/agents/openhands.d.ts +9 -0
  21. package/dist/agents/openhands.js +35 -0
  22. package/dist/commands/pair.d.ts +1 -1
  23. package/dist/commands/pair.js +1 -1
  24. package/dist/commands/run.js +2 -2
  25. package/dist/pwa/apple-touch-icon.png +0 -0
  26. package/dist/pwa/assets/index-ByhOhTz1.js +118 -0
  27. package/dist/pwa/assets/index-_AmC1Rkn.css +1 -0
  28. package/dist/pwa/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
  29. package/dist/pwa/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
  30. package/dist/pwa/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
  31. package/dist/pwa/favicon.ico +0 -0
  32. package/dist/pwa/index.html +17 -0
  33. package/dist/pwa/manifest.webmanifest +1 -0
  34. package/dist/pwa/pwa-192x192.png +0 -0
  35. package/dist/pwa/pwa-512x512.png +0 -0
  36. package/dist/pwa/registerSW.js +1 -0
  37. package/dist/pwa/service-worker.js +2 -0
  38. package/dist/rpc-handler.d.ts +4 -0
  39. package/dist/rpc-handler.js +5 -4
  40. package/dist/transports/http-transport.js +29 -41
  41. package/package.json +2 -2
  42. package/palmier-server/.github/workflows/ci.yml +21 -0
  43. package/palmier-server/.github/workflows/deploy.yml +38 -0
  44. package/palmier-server/CLAUDE.md +13 -0
  45. package/palmier-server/PRODUCTION.md +355 -0
  46. package/palmier-server/README.md +187 -0
  47. package/palmier-server/nats.conf +15 -0
  48. package/palmier-server/package.json +8 -0
  49. package/palmier-server/pnpm-lock.yaml +6597 -0
  50. package/palmier-server/pnpm-workspace.yaml +3 -0
  51. package/palmier-server/pwa/index.html +16 -0
  52. package/palmier-server/pwa/logo/logo-prompt.md +28 -0
  53. package/palmier-server/pwa/logo/logo_20260330.png +0 -0
  54. package/palmier-server/pwa/package.json +30 -0
  55. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  56. package/palmier-server/pwa/public/favicon.ico +0 -0
  57. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  58. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  59. package/palmier-server/pwa/src/App.css +2387 -0
  60. package/palmier-server/pwa/src/App.tsx +21 -0
  61. package/palmier-server/pwa/src/agentLabels.ts +11 -0
  62. package/palmier-server/pwa/src/api.ts +61 -0
  63. package/palmier-server/pwa/src/components/HostMenu.tsx +289 -0
  64. package/palmier-server/pwa/src/components/PlanDialog.tsx +41 -0
  65. package/palmier-server/pwa/src/components/RunDetailView.tsx +293 -0
  66. package/palmier-server/pwa/src/components/RunsView.tsx +254 -0
  67. package/palmier-server/pwa/src/components/TabBar.tsx +31 -0
  68. package/palmier-server/pwa/src/components/TaskCard.tsx +213 -0
  69. package/palmier-server/pwa/src/components/TaskForm.tsx +580 -0
  70. package/palmier-server/pwa/src/components/TaskListView.tsx +415 -0
  71. package/palmier-server/pwa/src/constants.ts +2 -0
  72. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +313 -0
  73. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +135 -0
  74. package/palmier-server/pwa/src/formatTime.ts +10 -0
  75. package/palmier-server/pwa/src/hooks/useBackClose.ts +75 -0
  76. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +17 -0
  77. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +75 -0
  78. package/palmier-server/pwa/src/main.tsx +14 -0
  79. package/palmier-server/pwa/src/pages/Dashboard.tsx +223 -0
  80. package/palmier-server/pwa/src/pages/PairHost.tsx +178 -0
  81. package/palmier-server/pwa/src/service-worker.ts +139 -0
  82. package/palmier-server/pwa/src/types.ts +79 -0
  83. package/palmier-server/pwa/src/vite-env.d.ts +11 -0
  84. package/palmier-server/pwa/tsconfig.json +21 -0
  85. package/palmier-server/pwa/tsconfig.node.json +19 -0
  86. package/palmier-server/pwa/vite.config.ts +47 -0
  87. package/palmier-server/server/.env.example +16 -0
  88. package/palmier-server/server/package.json +33 -0
  89. package/palmier-server/server/src/db.ts +34 -0
  90. package/palmier-server/server/src/index.ts +219 -0
  91. package/palmier-server/server/src/nats.ts +25 -0
  92. package/palmier-server/server/src/push.ts +68 -0
  93. package/palmier-server/server/src/routes/hosts.ts +45 -0
  94. package/palmier-server/server/src/routes/push.ts +100 -0
  95. package/palmier-server/server/tsconfig.json +20 -0
  96. package/palmier-server/spec.md +415 -0
  97. package/src/agents/agent-instructions.md +1 -1
  98. package/src/agents/agent.ts +23 -0
  99. package/src/agents/aider.ts +37 -0
  100. package/src/agents/cursor.ts +38 -0
  101. package/src/agents/deepagents.ts +38 -0
  102. package/src/agents/droid.ts +37 -0
  103. package/src/agents/goose.ts +35 -0
  104. package/src/agents/opencode.ts +38 -0
  105. package/src/agents/openhands.ts +38 -0
  106. package/src/commands/pair.ts +1 -1
  107. package/src/commands/run.ts +2 -2
  108. package/src/rpc-handler.ts +5 -4
  109. package/src/transports/http-transport.ts +31 -43
  110. package/test/result-state.test.ts +110 -0
@@ -0,0 +1,355 @@
1
+ # Production Deployment Guide
2
+
3
+ Single-instance deployment on Ubuntu 24 with TLS support. NATS and PostgreSQL run in Docker; the Palmier web server runs directly on the host via systemd. Caddy handles TLS termination with automatic Let's Encrypt certificates. Deployments are automated via GitHub Actions — pushing to `main` triggers a CI build, then deploys to the VPS over SSH.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Internet
9
+
10
+ ├── palmier.me → Cloudflare → Caddy → redirect to www
11
+ ├── www.palmier.me → Cloudflare Pages (landing site, static)
12
+ ├── app.palmier.me → Cloudflare → Caddy → localhost:3000 (Palmier server, HTTPS)
13
+ └── nats.palmier.me → Caddy (direct) → localhost:9222 (NATS WebSocket)
14
+ → port 4222 direct (NATS TCP for hosts)
15
+
16
+ Caddy (ports 80/443, auto TLS)
17
+
18
+ Docker
19
+ ├── NATS (ports 4222 TCP public, 9222 WS localhost)
20
+ └── PostgreSQL (port 5432 localhost)
21
+
22
+ systemd
23
+ └── Palmier Server (port 3000 localhost)
24
+ ```
25
+
26
+ ## Prerequisites
27
+
28
+ Three DNS records pointing to your server's public IP:
29
+
30
+ - `palmier.me` — root domain (Cloudflare proxied, redirects to `www`)
31
+ - `www.palmier.me` — Landing site (Cloudflare Pages, static)
32
+ - `app.palmier.me` — PWA + API over HTTPS (Cloudflare proxied)
33
+ - `nats.palmier.me` — NATS WebSocket + TCP (DNS only, no Cloudflare proxy)
34
+
35
+ ### Install Node.js 24+
36
+
37
+ ```bash
38
+ curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash -
39
+ sudo apt install -y nodejs
40
+ ```
41
+
42
+ ### Install pnpm
43
+
44
+ ```bash
45
+ sudo npm install -g pnpm
46
+ ```
47
+
48
+ ### Install Docker
49
+
50
+ ```bash
51
+ sudo apt install -y docker.io docker-compose-v2
52
+ sudo usermod -aG docker $USER
53
+ # Log out and back in for group to take effect
54
+ ```
55
+
56
+ ### Install Caddy
57
+
58
+ ```bash
59
+ sudo apt install -y caddy
60
+ ```
61
+
62
+ ## 1. TLS with Caddy
63
+
64
+ Caddy auto-provisions Let's Encrypt certificates with zero configuration.
65
+
66
+ Create `/etc/caddy/Caddyfile`:
67
+
68
+ ```
69
+ palmier.me {
70
+ redir https://www.palmier.me{uri} permanent
71
+ }
72
+
73
+ app.palmier.me {
74
+ reverse_proxy localhost:3000
75
+ }
76
+
77
+ nats.palmier.me {
78
+ @websocket {
79
+ header Connection *Upgrade*
80
+ header Upgrade websocket
81
+ }
82
+ reverse_proxy @websocket localhost:9222
83
+ }
84
+ ```
85
+
86
+ This gives you:
87
+
88
+ - `https://app.palmier.me` — PWA + API (auto TLS, Cloudflare CDN for static assets)
89
+ - `wss://nats.palmier.me` — NATS WebSocket (auto TLS, direct to VPS)
90
+ - `https://www.palmier.me` — Landing site (Cloudflare Pages, no VPS involvement)
91
+
92
+ LAN mode is served directly by the palmier host binary (reverse-proxying PWA assets from `app.palmier.me`), so no separate domain is needed.
93
+
94
+ Caddy auto-provisions Let's Encrypt certs for both domains. Set Cloudflare SSL/TLS mode to **Full (strict)** so Cloudflare verifies the origin cert when connecting to your VPS.
95
+
96
+ No need to configure TLS in NATS itself.
97
+
98
+ ```bash
99
+ sudo systemctl enable --now caddy
100
+ ```
101
+
102
+ ## 2. Docker Compose for NATS + PostgreSQL
103
+
104
+ Create a directory for production Docker config:
105
+
106
+ ```bash
107
+ mkdir -p ~/palmier-prod
108
+ ```
109
+
110
+ ### docker-compose.yml
111
+
112
+ Create `~/palmier-prod/docker-compose.yml`:
113
+
114
+ ```yaml
115
+ services:
116
+ nats:
117
+ image: nats:2-alpine
118
+ restart: unless-stopped
119
+ ports:
120
+ - "4222:4222"
121
+ - "127.0.0.1:9222:9222"
122
+ volumes:
123
+ - ./nats.conf:/etc/nats/nats.conf:ro
124
+ - nats-data:/data
125
+ command: ["-c", "/etc/nats/nats.conf"]
126
+
127
+ postgres:
128
+ image: postgres:17-alpine
129
+ restart: unless-stopped
130
+ ports:
131
+ - "127.0.0.1:5432:5432"
132
+ environment:
133
+ POSTGRES_DB: palmier
134
+ POSTGRES_USER: palmier
135
+ POSTGRES_PASSWORD: <generate-a-strong-password>
136
+ volumes:
137
+ - pgdata:/var/lib/postgresql/data
138
+
139
+ volumes:
140
+ nats-data:
141
+ pgdata:
142
+ ```
143
+
144
+ ### nats.conf
145
+
146
+ Create `~/palmier-prod/nats.conf`:
147
+
148
+ ```
149
+ listen: 0.0.0.0:4222
150
+
151
+ websocket {
152
+ listen: "0.0.0.0:9222"
153
+ no_tls: true
154
+ }
155
+
156
+ authorization {
157
+ token: "<generate-a-new-token>"
158
+ }
159
+ ```
160
+
161
+ Generate secrets:
162
+
163
+ ```bash
164
+ # NATS token
165
+ openssl rand -hex 32
166
+
167
+ # Postgres password
168
+ openssl rand -hex 16
169
+ ```
170
+
171
+ Start the containers:
172
+
173
+ ```bash
174
+ cd ~/palmier-prod
175
+ docker compose up -d
176
+ ```
177
+
178
+ ## 3. NATS TCP for Remote Hosts
179
+
180
+ Hosts connect to NATS over TCP (port 4222). Since Caddy only handles HTTP/WebSocket, port 4222 must be open on the firewall for hosts to connect. The NATS token provides authentication.
181
+
182
+ For encrypted host connections, you can add a TLS-terminating TCP proxy (e.g., nginx stream block) in front of port 4222. For a single-user setup, token auth alone is sufficient.
183
+
184
+ ## 4. Firewall
185
+
186
+ ```bash
187
+ sudo ufw allow 22/tcp # SSH (must allow before enabling!)
188
+ sudo ufw allow 80/tcp # Caddy HTTP -> HTTPS redirect
189
+ sudo ufw allow 443/tcp # Caddy HTTPS
190
+ sudo ufw allow 4222/tcp # NATS TCP for hosts
191
+ sudo ufw enable
192
+ ```
193
+
194
+ ## 5. Clone the Repository
195
+
196
+ Set up VPS git access first (see [VPS Git Access](#vps-git-access)), then clone:
197
+
198
+ ```bash
199
+ mkdir -p ~/source
200
+ cd ~/source
201
+ git clone git@github.com:caihongxu/palmier-server.git
202
+ cd palmier-server
203
+ ```
204
+
205
+ The first push to `main` will build and start the server automatically via GitHub Actions.
206
+
207
+ ## 6. Configure Environment
208
+
209
+ Create `server/.env`:
210
+
211
+ ```bash
212
+ PORT=3000
213
+ DATABASE_URL=postgresql://palmier:<your-pg-password>@localhost:5432/palmier
214
+
215
+ NATS_URL=nats://localhost:4222
216
+ NATS_HOST_URL=nats://nats.palmier.me:4222
217
+ NATS_WS_URL=wss://nats.palmier.me
218
+ NATS_TOKEN=<your-nats-token-from-nats.conf>
219
+
220
+ VAPID_PUBLIC_KEY=<generated>
221
+ VAPID_PRIVATE_KEY=<generated>
222
+ VAPID_MAILTO=mailto:you@palmier.me
223
+ ```
224
+
225
+ Key differences from development:
226
+
227
+ - `NATS_WS_URL` uses `wss://` through Caddy (not direct `ws://`)
228
+ - `NATS_HOST_URL` uses your public domain so remote hosts can reach NATS
229
+
230
+ Generate VAPID keys:
231
+
232
+ ```bash
233
+ npx web-push generate-vapid-keys
234
+ ```
235
+
236
+ ## 7. Palmier systemd Service
237
+
238
+ Create `/etc/systemd/system/palmier-server.service`:
239
+
240
+ ```ini
241
+ [Unit]
242
+ Description=Palmier Server
243
+ After=network.target docker.service
244
+
245
+ [Service]
246
+ Type=simple
247
+ User=hongxu
248
+ WorkingDirectory=/home/hongxu/source/palmier-server/server
249
+ ExecStart=/usr/bin/node dist/index.js
250
+ Restart=on-failure
251
+ RestartSec=5
252
+ EnvironmentFile=/home/hongxu/source/palmier-server/server/.env
253
+
254
+ [Install]
255
+ WantedBy=multi-user.target
256
+ ```
257
+
258
+ Enable and start:
259
+
260
+ ```bash
261
+ sudo systemctl daemon-reload
262
+ sudo systemctl enable --now palmier-server
263
+ ```
264
+
265
+ ## 8. Verify
266
+
267
+ At this point, all services should be running (Docker from step 2, Caddy from step 1, Palmier from step 7). Verify:
268
+
269
+ ```bash
270
+ sudo systemctl status caddy
271
+ sudo systemctl status palmier-server
272
+ cd ~/palmier-prod && docker compose ps
273
+ ```
274
+
275
+ - `https://app.palmier.me` — should load the PWA (server mode)
276
+ - `https://app.palmier.me/health` — should return OK
277
+ - Logs: `journalctl -u palmier-server -f`
278
+ - Docker logs: `cd ~/palmier-prod && docker compose logs -f`
279
+
280
+ ## CI/CD with GitHub Actions
281
+
282
+ Deployments are automated via two GitHub Actions workflows in `.github/workflows/`:
283
+
284
+ - **`ci.yml`** — runs on every push (except `main`) and pull requests. Builds server + PWA to catch errors early.
285
+ - **`deploy.yml`** — runs on push to `main`. Builds in CI first, then SSHs into the VPS to pull, build, and restart the service.
286
+
287
+ ### GitHub Secrets
288
+
289
+ Add these in the repo settings (Settings → Secrets and variables → Actions):
290
+
291
+ | Secret | Description |
292
+ |---|---|
293
+ | `VPS_HOST` | Droplet IP address |
294
+ | `VPS_USER` | SSH username (e.g., `hongxu`) |
295
+ | `SSH_PRIVATE_KEY` | Ed25519 private key for SSH access |
296
+
297
+ ### VPS SSH Setup
298
+
299
+ Generate a deploy key pair and add the public key to the VPS:
300
+
301
+ ```bash
302
+ ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/palmier-deploy -N ""
303
+ ```
304
+
305
+ Append the public key to the VPS `~/.ssh/authorized_keys`. Add the private key as the `SSH_PRIVATE_KEY` GitHub secret.
306
+
307
+ ### VPS Git Access
308
+
309
+ The VPS needs to pull from the private repo. Add a read-only deploy key:
310
+
311
+ 1. Generate a key on the VPS: `ssh-keygen -t ed25519 -C "palmier-server-deploy" -f ~/.ssh/deploy-key -N ""`
312
+ 2. Add the public key to the repo (Settings → Deploy keys, read-only)
313
+ 3. Configure SSH on the VPS to use the key for GitHub:
314
+ ```
315
+ # ~/.ssh/config
316
+ Host github.com
317
+ IdentityFile ~/.ssh/deploy-key
318
+ ```
319
+
320
+ ### Passwordless sudo for deploy
321
+
322
+ The deploy workflow needs to restart the systemd service. Allow the deploy user to do this without a password:
323
+
324
+ ```bash
325
+ sudo visudo -f /etc/sudoers.d/palmier-deploy
326
+ ```
327
+
328
+ Add:
329
+
330
+ ```
331
+ hongxu ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart palmier-server
332
+ ```
333
+
334
+ ### Deploying
335
+
336
+ Push to `main` and the deploy workflow handles everything automatically. To deploy manually:
337
+
338
+ ```bash
339
+ ssh hongxu@<vps-ip>
340
+ cd ~/source/palmier-server
341
+ git pull
342
+ pnpm install --frozen-lockfile
343
+ cd pwa && pnpm build && cd ..
344
+ cd server && pnpm build && cd ..
345
+ sudo systemctl restart palmier-server
346
+ ```
347
+
348
+ ## Component Summary
349
+
350
+ | Component | Runtime | Ports |
351
+ |---|---|---|
352
+ | PostgreSQL | Docker | 5432 (localhost only) |
353
+ | NATS | Docker | 4222 (public, for hosts), 9222 (localhost, proxied by Caddy) |
354
+ | Palmier Server | systemd + Node.js | 3000 (localhost only) |
355
+ | Caddy | systemd | 80, 443 (public) |
@@ -0,0 +1,187 @@
1
+ # Palmier Server
2
+
3
+ ![Deploy](https://github.com/caihongxu/palmier-server/actions/workflows/deploy.yml/badge.svg)
4
+
5
+ Palmier is a platform for remotely scheduling, managing, and executing autonomous AI tasks on host machines via a progressive web app. Uses NATS for real-time communication (pub/sub and request-reply) and push notifications for task progress.
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ +-----------+ +----------------+ +------------------+
11
+ | | HTTP | | NATS | |
12
+ | PWA |------>| Web Server |<----->| NATS Server |
13
+ | (React) | | (Express) | | |
14
+ | | | | | |
15
+ +-----------+ +----------------+ +------------------+
16
+ | | ^
17
+ | NATS WebSocket | PostgreSQL | NATS
18
+ +-------------------------------------------> |
19
+ | |
20
+ +----v----+ +-------+-------+
21
+ | | | |
22
+ | DB | | Host(s) |
23
+ | (PG) | | (separate |
24
+ | | | repo) |
25
+ +---------+ +---------------+
26
+ ```
27
+
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
+ - **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
+ - **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.
32
+
33
+ ## Prerequisites
34
+
35
+ - Node.js 24+
36
+ - pnpm (`npm install -g pnpm`)
37
+ - PostgreSQL 17
38
+ - NATS server with WebSocket enabled
39
+
40
+ ## Getting Started
41
+
42
+ 1. **Clone the repository**
43
+
44
+ ```bash
45
+ git clone https://github.com/caihongxu/palmier-server.git
46
+ cd palmier-server
47
+ ```
48
+
49
+ 2. **Install dependencies** (uses pnpm workspaces)
50
+
51
+ ```bash
52
+ pnpm install
53
+ ```
54
+
55
+ 3. **Configure environment variables**
56
+
57
+ ```bash
58
+ cp server/.env.example server/.env
59
+ ```
60
+
61
+ Edit `server/.env` and fill in the values (see table below).
62
+
63
+ 4. **Generate VAPID keys** for web push notifications
64
+
65
+ ```bash
66
+ npx web-push generate-vapid-keys
67
+ ```
68
+
69
+ Copy the public and private keys into `server/.env`.
70
+
71
+ 5. **Create the PostgreSQL database**
72
+
73
+ ```bash
74
+ createdb palmier
75
+ ```
76
+
77
+ Update `DATABASE_URL` in `server/.env` with your connection string. Tables are created automatically on server startup.
78
+
79
+ 6. **Start the NATS server** with the included config
80
+
81
+ ```bash
82
+ nats-server -c nats.conf
83
+ ```
84
+
85
+ The config enables WebSocket on port 9222 and token-based auth.
86
+
87
+ ## Development
88
+
89
+ Development uses two servers: Vite for the PWA (with hot module replacement) and Express for the API. Vite proxies `/api/*` requests to Express so everything appears same-origin.
90
+
91
+ ```bash
92
+ # Terminal 1 -- API server (port 3000)
93
+ cd server
94
+ pnpm dev
95
+
96
+ # Terminal 2 -- PWA dev server (port 5173, proxies /api to :3000)
97
+ cd pwa
98
+ pnpm dev
99
+
100
+ # To proxy to a remote API server instead of localhost:
101
+ # Linux / macOS:
102
+ API_URL=https://app.palmier.me pnpm dev
103
+ # Windows (PowerShell):
104
+ $env:API_URL="https://app.palmier.me"; pnpm dev
105
+ ```
106
+
107
+ Open `http://localhost:5173` in your browser.
108
+
109
+ ### Production
110
+
111
+ In production, Express serves the built PWA static files directly — one server, one port.
112
+
113
+ ```bash
114
+ # Build the PWA
115
+ cd pwa && pnpm build
116
+
117
+ # Start the server (serves API + PWA on port 3000)
118
+ cd server && pnpm start
119
+ ```
120
+
121
+ Open `http://localhost:3000`.
122
+
123
+ ### Host Setup
124
+
125
+ The host runs on a separate Linux machine. See the [palmier README](https://github.com/caihongxu/palmier) for full details.
126
+
127
+ 1. On the host machine, in your project directory, run:
128
+ ```bash
129
+ palmier init
130
+ ```
131
+ The interactive wizard detects agents, configures access modes (HTTP port, LAN access), shows a summary for confirmation, registers with the server, saves config to `~/.config/palmier/host.json`, installs a background daemon, and generates a pairing code.
132
+
133
+ 2. Enter the pairing code in the PWA to connect your device to the host.
134
+
135
+ ## Environment Variables
136
+
137
+ | Variable | Description | Example |
138
+ |---|---|---|
139
+ | `PORT` | HTTP server port | `3000` |
140
+ | `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:password@localhost:5432/palmier` |
141
+ | `NATS_URL` | NATS server URL (TCP, for server's own connection) | `nats://localhost:4222` |
142
+ | `NATS_HOST_URL` | NATS URL sent to hosts during registration (use LAN IP) | `nats://192.168.1.100:4222` |
143
+ | `NATS_WS_URL` | NATS WebSocket URL sent to PWA clients | `wss://nats.palmier.me` (prod) or `ws://192.168.1.100:9222` (LAN) |
144
+ | `NATS_TOKEN` | NATS authentication token | *(from nats.conf)* |
145
+ | `VAPID_PUBLIC_KEY` | VAPID public key for web push | *(generated via web-push)* |
146
+ | `VAPID_PRIVATE_KEY` | VAPID private key for web push | *(generated via web-push)* |
147
+ | `VAPID_MAILTO` | Contact email for VAPID | `mailto:admin@example.com` |
148
+
149
+ > **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
+
151
+ ## API Endpoints
152
+
153
+ All endpoints are prefixed with `/api`. No user authentication is required.
154
+
155
+ | Method | Path | Description |
156
+ |---|---|---|
157
+ | `POST` | `/api/hosts/register` | Register a new host (returns hostId + NATS config) |
158
+ | `GET` | `/api/config` | Get NATS WebSocket credentials for the PWA |
159
+ | `POST` | `/api/push/subscribe` | Register a push notification subscription |
160
+ | `DELETE` | `/api/push/subscribe` | Remove a push notification subscription |
161
+ | `GET` | `/api/push/vapid-key` | Get the VAPID public key |
162
+ | `POST` | `/api/push/respond` | Respond to a pending task confirmation via push notification |
163
+ | `GET` | `/health` | Health check |
164
+
165
+
166
+ ## Key Implementation Notes
167
+
168
+ - **No user accounts** — the PWA stores paired hosts in localStorage. Each device pairs with a host via a pairing code and receives a client token.
169
+ - **Client tokens** are generated and validated on the host, not the server. They are included in every NATS RPC payload and as Bearer tokens for HTTP requests.
170
+ - **NATS RPC** — the RPC method is derived from the NATS subject (e.g., `...rpc.task.list` → `task.list`), not the message body. The body contains request parameters plus `clientToken`. All NATS requests go through a centralized `request()` helper in `HostConnectionContext` that handles encoding/decoding and logging.
171
+ - **Pairing** — `palmier pair` (or auto-pair after `palmier init`) generates a 6-char pairing code. The PWA enters the code, which routes to the host via NATS (`pair.<CODE>`) or HTTP (`POST /pair`). The host validates the code and returns a client token.
172
+ - **Task IDs** are generated by the host as UUIDs.
173
+ - **Triggers can be enabled/disabled** — the `triggers_enabled` frontmatter field (default `true`) controls whether systemd timers are installed. When disabled, timers are removed but the task can still be run manually. The "Enable Triggers" checkbox only appears in the form when triggers exist.
174
+ - **Host responses** return flat task objects (frontmatter fields at the top level, not nested) for `task.list`, `task.create`, and `task.update`.
175
+ - **NATS "503"** means "no responders" — the dashboard silently handles this when no host is connected, showing an empty task list instead of an error.
176
+ - **Helmet CSP** is disabled (`contentSecurityPolicy: false`) to allow NATS WebSocket connections and inline Vite scripts during dev.
177
+ - **Static file serving** is conditional — Express only serves `pwa/dist/` if the directory exists, so it doesn't interfere during dev when using Vite.
178
+ - **No CORS** needed — Vite proxy handles same-origin in dev, Express static serving handles it in production.
179
+ - **Push notifications** — the PWA registers a service worker (`injectManifest` strategy via vite-plugin-pwa) and subscribes the browser for Web Push. The Web Server subscribes to `host-event.>` and sends push notifications for confirmation requests, task completions, and task failures.
180
+ - **Markdown rendering** — Generated task plans and task results are rendered as rich formatted text using `react-markdown` with `remark-gfm` (GitHub Flavored Markdown), supporting tables, strikethrough, task lists, and autolinks.
181
+ - **Task confirmation** — the Dashboard discovers pending confirmations from the `task.list` RPC response (tasks with a pending request in the serve daemon's in-memory registry, reported via `task.status`). When found, it shows a full-screen confirmation modal. Push notification action buttons trigger `POST /api/push/respond`, which forwards to the `task.user_input` NATS RPC.
182
+ - **Task event tracking** — task lifecycle events are persisted to `status.json` on the host (for crash detection) and broadcast via `host-event.<host_id>.<task_id>` pub/sub and HTTP SSE. The PWA loads initial status from `task.list` and subscribes to events for real-time updates.
183
+ - **NATS config** (`nats.conf`) enables WebSocket on port 9222 (for browser clients) and token-based auth.
184
+
185
+ ## Related Repositories
186
+
187
+ - [palmier](https://github.com/caihongxu/palmier) -- The host binary, published as `palmier` on npm. Install with `npm install -g palmier`. Uses npm (not pnpm).
@@ -0,0 +1,15 @@
1
+ # NATS Server Configuration for Palmier
2
+
3
+ # TCP listener (for server + agent)
4
+ listen: 0.0.0.0:4222
5
+
6
+ # WebSocket listener (for PWA browser clients)
7
+ websocket {
8
+ listen: "0.0.0.0:9222"
9
+ no_tls: true
10
+ }
11
+
12
+ # Token-based authentication
13
+ authorization {
14
+ token: "592b229f9df46e9ecaedbfcf9e2e5bcc6432367c114b20a0c7854b6f4542184a"
15
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "palmier-server",
3
+ "private": true,
4
+ "packageManager": "pnpm@10.32.1",
5
+ "pnpm": {
6
+ "onlyBuiltDependencies": ["bcrypt", "esbuild"]
7
+ }
8
+ }