palmier 0.9.6 → 0.9.7

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 (250) hide show
  1. package/README.md +28 -13
  2. package/dist/agents/agent.d.ts +0 -1
  3. package/dist/agents/agent.js +0 -1
  4. package/dist/agents/aider.d.ts +0 -1
  5. package/dist/agents/aider.js +0 -1
  6. package/dist/agents/claude.d.ts +0 -1
  7. package/dist/agents/claude.js +0 -1
  8. package/dist/agents/cline.d.ts +0 -1
  9. package/dist/agents/cline.js +0 -1
  10. package/dist/agents/codex.d.ts +0 -1
  11. package/dist/agents/codex.js +0 -1
  12. package/dist/agents/copilot.d.ts +0 -1
  13. package/dist/agents/copilot.js +0 -1
  14. package/dist/agents/cursor.d.ts +0 -1
  15. package/dist/agents/cursor.js +0 -1
  16. package/dist/agents/deepagents.d.ts +0 -1
  17. package/dist/agents/deepagents.js +0 -1
  18. package/dist/agents/droid.d.ts +0 -1
  19. package/dist/agents/droid.js +0 -1
  20. package/dist/agents/gemini.d.ts +0 -1
  21. package/dist/agents/gemini.js +0 -1
  22. package/dist/agents/goose.d.ts +0 -1
  23. package/dist/agents/goose.js +0 -1
  24. package/dist/agents/hermes.d.ts +0 -1
  25. package/dist/agents/hermes.js +0 -1
  26. package/dist/agents/kimi.d.ts +0 -1
  27. package/dist/agents/kimi.js +0 -1
  28. package/dist/agents/kiro.d.ts +0 -1
  29. package/dist/agents/kiro.js +0 -1
  30. package/dist/agents/openclaw.d.ts +0 -1
  31. package/dist/agents/openclaw.js +0 -1
  32. package/dist/agents/opencode.d.ts +0 -1
  33. package/dist/agents/opencode.js +0 -1
  34. package/dist/agents/qoder.d.ts +0 -1
  35. package/dist/agents/qoder.js +0 -1
  36. package/dist/agents/qwen.d.ts +0 -1
  37. package/dist/agents/qwen.js +0 -1
  38. package/dist/agents/shared-prompt.d.ts +0 -1
  39. package/dist/agents/shared-prompt.js +0 -1
  40. package/dist/client-store.d.ts +0 -1
  41. package/dist/client-store.js +0 -1
  42. package/dist/commands/clients.d.ts +0 -1
  43. package/dist/commands/clients.js +0 -1
  44. package/dist/commands/info.d.ts +0 -1
  45. package/dist/commands/info.js +0 -1
  46. package/dist/commands/init.d.ts +0 -1
  47. package/dist/commands/init.js +1 -2
  48. package/dist/commands/pair.d.ts +0 -1
  49. package/dist/commands/pair.js +0 -1
  50. package/dist/commands/restart.d.ts +0 -1
  51. package/dist/commands/restart.js +0 -1
  52. package/dist/commands/run.d.ts +0 -1
  53. package/dist/commands/run.js +0 -1
  54. package/dist/commands/serve.d.ts +0 -1
  55. package/dist/commands/serve.js +0 -1
  56. package/dist/commands/uninstall.d.ts +0 -1
  57. package/dist/commands/uninstall.js +0 -1
  58. package/dist/config.d.ts +0 -1
  59. package/dist/config.js +0 -1
  60. package/dist/event-queues.d.ts +0 -1
  61. package/dist/event-queues.js +0 -1
  62. package/dist/events.d.ts +0 -1
  63. package/dist/events.js +0 -1
  64. package/dist/index.d.ts +0 -1
  65. package/dist/index.js +0 -1
  66. package/dist/linked-device.d.ts +0 -1
  67. package/dist/linked-device.js +0 -1
  68. package/dist/mcp-handler.d.ts +0 -1
  69. package/dist/mcp-handler.js +0 -1
  70. package/dist/mcp-tools.d.ts +0 -1
  71. package/dist/mcp-tools.js +0 -1
  72. package/dist/nats-client.d.ts +0 -1
  73. package/dist/nats-client.js +0 -1
  74. package/dist/network.d.ts +0 -1
  75. package/dist/network.js +0 -1
  76. package/dist/notification-store.d.ts +0 -1
  77. package/dist/notification-store.js +0 -1
  78. package/dist/pending-requests.d.ts +0 -1
  79. package/dist/pending-requests.js +0 -1
  80. package/dist/platform/index.d.ts +0 -1
  81. package/dist/platform/index.js +0 -1
  82. package/dist/platform/linux.d.ts +0 -1
  83. package/dist/platform/linux.js +0 -1
  84. package/dist/platform/macos.d.ts +0 -1
  85. package/dist/platform/macos.js +0 -1
  86. package/dist/platform/platform.d.ts +0 -1
  87. package/dist/platform/platform.js +0 -1
  88. package/dist/platform/windows.d.ts +0 -1
  89. package/dist/platform/windows.js +0 -1
  90. package/dist/pwa/assets/{index-MLEFUP3r.js → index-DWvRAUiy.js} +31 -31
  91. package/dist/pwa/assets/{web-B1sKCc7e.js → web-C4iZbqTC.js} +1 -1
  92. package/dist/pwa/assets/{web-ETD-8ZHd.js → web-CBFqJGX6.js} +1 -1
  93. package/dist/pwa/assets/{web-B4xEa6WO.js → web-DL4uXOpS.js} +1 -1
  94. package/dist/pwa/index.html +2 -2
  95. package/dist/pwa/service-worker.js +1 -1
  96. package/dist/rpc-handler.d.ts +0 -1
  97. package/dist/rpc-handler.js +0 -1
  98. package/dist/sms-store.d.ts +0 -1
  99. package/dist/sms-store.js +0 -1
  100. package/dist/spawn-command.d.ts +0 -1
  101. package/dist/spawn-command.js +0 -1
  102. package/dist/task.d.ts +0 -1
  103. package/dist/task.js +0 -1
  104. package/dist/transports/http-transport.d.ts +0 -1
  105. package/dist/transports/http-transport.js +0 -1
  106. package/dist/transports/nats-transport.d.ts +0 -1
  107. package/dist/transports/nats-transport.js +0 -1
  108. package/dist/types.d.ts +0 -1
  109. package/dist/types.js +0 -1
  110. package/dist/update-checker.d.ts +0 -1
  111. package/dist/update-checker.js +0 -1
  112. package/package.json +5 -1
  113. package/.github/workflows/ci.yml +0 -16
  114. package/.github/workflows/publish.yml +0 -37
  115. package/CLAUDE.md +0 -22
  116. package/palmier-server/.github/workflows/ci.yml +0 -21
  117. package/palmier-server/.github/workflows/deploy.yml +0 -38
  118. package/palmier-server/CLAUDE.md +0 -17
  119. package/palmier-server/PRODUCTION.md +0 -358
  120. package/palmier-server/README.md +0 -231
  121. package/palmier-server/nats.conf +0 -19
  122. package/palmier-server/package.json +0 -15
  123. package/palmier-server/pnpm-lock.yaml +0 -7639
  124. package/palmier-server/pnpm-workspace.yaml +0 -3
  125. package/palmier-server/pwa/index.html +0 -16
  126. package/palmier-server/pwa/logo/logo_20260421.png +0 -0
  127. package/palmier-server/pwa/package.json +0 -34
  128. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  129. package/palmier-server/pwa/public/favicon.ico +0 -0
  130. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  131. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  132. package/palmier-server/pwa/src/App.css +0 -3012
  133. package/palmier-server/pwa/src/App.tsx +0 -59
  134. package/palmier-server/pwa/src/agentLabels.ts +0 -11
  135. package/palmier-server/pwa/src/api.ts +0 -67
  136. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
  137. package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -113
  138. package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
  139. package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
  140. package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
  141. package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
  142. package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
  143. package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
  144. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
  145. package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
  146. package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
  147. package/palmier-server/pwa/src/components/TaskForm.tsx +0 -766
  148. package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
  149. package/palmier-server/pwa/src/constants.ts +0 -2
  150. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
  151. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
  152. package/palmier-server/pwa/src/draftGuard.ts +0 -24
  153. package/palmier-server/pwa/src/formatTime.ts +0 -44
  154. package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
  155. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
  156. package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
  157. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
  158. package/palmier-server/pwa/src/main.tsx +0 -14
  159. package/palmier-server/pwa/src/native/Device.ts +0 -49
  160. package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
  161. package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
  162. package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
  163. package/palmier-server/pwa/src/service-worker.ts +0 -142
  164. package/palmier-server/pwa/src/types.ts +0 -75
  165. package/palmier-server/pwa/src/vite-env.d.ts +0 -11
  166. package/palmier-server/pwa/tsconfig.json +0 -21
  167. package/palmier-server/pwa/tsconfig.node.json +0 -19
  168. package/palmier-server/pwa/vite.config.ts +0 -47
  169. package/palmier-server/server/.env.example +0 -20
  170. package/palmier-server/server/package.json +0 -36
  171. package/palmier-server/server/src/db.ts +0 -44
  172. package/palmier-server/server/src/fcm.ts +0 -74
  173. package/palmier-server/server/src/index.ts +0 -688
  174. package/palmier-server/server/src/nats-jwt.ts +0 -299
  175. package/palmier-server/server/src/nats-setup.ts +0 -48
  176. package/palmier-server/server/src/nats.ts +0 -33
  177. package/palmier-server/server/src/notify.ts +0 -34
  178. package/palmier-server/server/src/push.ts +0 -68
  179. package/palmier-server/server/src/routes/device.ts +0 -224
  180. package/palmier-server/server/src/routes/fcm.ts +0 -64
  181. package/palmier-server/server/src/routes/hosts.ts +0 -56
  182. package/palmier-server/server/src/routes/push.ts +0 -101
  183. package/palmier-server/server/tsconfig.json +0 -20
  184. package/palmier-server/spec.md +0 -533
  185. package/src/agents/agent-instructions.md +0 -28
  186. package/src/agents/agent.ts +0 -114
  187. package/src/agents/aider.ts +0 -35
  188. package/src/agents/claude.ts +0 -39
  189. package/src/agents/cline.ts +0 -35
  190. package/src/agents/codex.ts +0 -40
  191. package/src/agents/copilot.ts +0 -37
  192. package/src/agents/cursor.ts +0 -36
  193. package/src/agents/deepagents.ts +0 -36
  194. package/src/agents/droid.ts +0 -35
  195. package/src/agents/gemini.ts +0 -43
  196. package/src/agents/goose.ts +0 -33
  197. package/src/agents/hermes.ts +0 -36
  198. package/src/agents/kimi.ts +0 -35
  199. package/src/agents/kiro.ts +0 -36
  200. package/src/agents/openclaw.ts +0 -29
  201. package/src/agents/opencode.ts +0 -36
  202. package/src/agents/qoder.ts +0 -36
  203. package/src/agents/qwen.ts +0 -32
  204. package/src/agents/shared-prompt.ts +0 -30
  205. package/src/client-store.ts +0 -68
  206. package/src/commands/clients.ts +0 -29
  207. package/src/commands/info.ts +0 -29
  208. package/src/commands/init.ts +0 -165
  209. package/src/commands/pair.ts +0 -137
  210. package/src/commands/restart.ts +0 -6
  211. package/src/commands/run.ts +0 -608
  212. package/src/commands/serve.ts +0 -211
  213. package/src/commands/uninstall.ts +0 -9
  214. package/src/config.ts +0 -36
  215. package/src/cross-spawn.d.ts +0 -5
  216. package/src/event-queues.ts +0 -41
  217. package/src/events.ts +0 -29
  218. package/src/index.ts +0 -111
  219. package/src/linked-device.ts +0 -52
  220. package/src/mcp-handler.ts +0 -200
  221. package/src/mcp-tools.ts +0 -839
  222. package/src/nats-client.ts +0 -19
  223. package/src/network.ts +0 -96
  224. package/src/notification-store.ts +0 -30
  225. package/src/pending-requests.ts +0 -73
  226. package/src/platform/index.ts +0 -20
  227. package/src/platform/linux.ts +0 -296
  228. package/src/platform/macos.ts +0 -329
  229. package/src/platform/platform.ts +0 -31
  230. package/src/platform/windows.ts +0 -299
  231. package/src/rpc-handler.ts +0 -691
  232. package/src/sms-store.ts +0 -28
  233. package/src/spawn-command.ts +0 -123
  234. package/src/task.ts +0 -343
  235. package/src/transports/http-transport.ts +0 -478
  236. package/src/transports/nats-transport.ts +0 -76
  237. package/src/types.ts +0 -89
  238. package/src/update-checker.ts +0 -40
  239. package/test/agent-instructions.test.ts +0 -209
  240. package/test/agent-output-parsing.test.ts +0 -74
  241. package/test/linux-cron.test.ts +0 -41
  242. package/test/macos-plist.test.ts +0 -112
  243. package/test/notification-store.test.ts +0 -57
  244. package/test/pairing.test.ts +0 -35
  245. package/test/result-state.test.ts +0 -110
  246. package/test/task-parsing.test.ts +0 -82
  247. package/test/taskrun-messages.test.ts +0 -224
  248. package/test/tsconfig.json +0 -9
  249. package/test/windows-xml.test.ts +0 -89
  250. package/tsconfig.json +0 -19
@@ -1,232 +0,0 @@
1
- import { useState } from "react";
2
- import { useNavigate } from "react-router-dom";
3
- import { connect, jwtAuthenticator, StringCodec } from "nats.ws";
4
- import { Capacitor } from "@capacitor/core";
5
- import { Preferences } from "@capacitor/preferences";
6
- import { useHostStore } from "../contexts/HostStoreContext";
7
- import { Device } from "../native/Device";
8
- import { SERVER_URL } from "../api";
9
- import type { PairedHost } from "../types";
10
-
11
- interface PairResponse {
12
- hostId: string;
13
- clientToken: string;
14
- hostName?: string;
15
- }
16
-
17
- /** Loopback (Local) mode: PWA is served by palmier serve on localhost. */
18
- const isLoopback = !!(window as any).__PALMIER_SERVE__
19
- && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
20
-
21
- const isNative = Capacitor.isNativePlatform();
22
-
23
- export default function PairHost() {
24
- const [code, setCode] = useState("");
25
- const [makeLinked, setMakeLinked] = useState(true);
26
- const [pairing, setPairing] = useState(false);
27
- const [error, setError] = useState<string | null>(null);
28
- const { addPairedHost } = useHostStore();
29
- const navigate = useNavigate();
30
-
31
- async function handlePair() {
32
- const trimmedCode = code.trim().toUpperCase();
33
- if (!trimmedCode) {
34
- setError("Enter a pairing code.");
35
- return;
36
- }
37
-
38
- setPairing(true);
39
- setError(null);
40
-
41
- try {
42
- let response: PairResponse;
43
-
44
- if (isLoopback) {
45
- const res = await fetch("/pair", {
46
- method: "POST",
47
- headers: { "Content-Type": "application/json" },
48
- body: JSON.stringify({ code: trimmedCode, label: navigator.userAgent }),
49
- });
50
-
51
- if (!res.ok) {
52
- const body = await res.json().catch(() => ({ error: "Connection failed" })) as { error?: string };
53
- throw new Error(body.error || `HTTP ${res.status}`);
54
- }
55
-
56
- response = await res.json() as PairResponse;
57
- } else {
58
- const configRes = await fetch(`${SERVER_URL}/api/config`);
59
- if (!configRes.ok) throw new Error("Failed to fetch server config");
60
- const config = await configRes.json() as { natsWsUrl: string; natsJwt: string; natsNkeySeed: string };
61
- if (!config.natsWsUrl) throw new Error("Server has no NATS WebSocket URL configured");
62
-
63
- const nc = await connect({
64
- servers: config.natsWsUrl,
65
- authenticator: jwtAuthenticator(
66
- config.natsJwt,
67
- new TextEncoder().encode(config.natsNkeySeed),
68
- ),
69
- });
70
-
71
- const sc = StringCodec();
72
- const subject = `pair.${trimmedCode}`;
73
- const msg = await nc.request(
74
- subject,
75
- sc.encode(JSON.stringify({ label: navigator.userAgent })),
76
- { timeout: 10000 },
77
- );
78
-
79
- response = JSON.parse(sc.decode(msg.data)) as PairResponse;
80
- await nc.close();
81
- }
82
-
83
- const host: PairedHost = {
84
- hostId: response.hostId,
85
- clientToken: response.clientToken,
86
- directUrl: isLoopback ? window.location.origin : undefined,
87
- ...(response.hostName ? { name: response.hostName } : {}),
88
- };
89
-
90
- addPairedHost(host);
91
-
92
- if (Capacitor.isNativePlatform() && Device) {
93
- // Native receivers (SmsBroadcastReceiver, DeviceNotificationListenerService)
94
- // read hostId to address relay messages.
95
- await Preferences.set({ key: "hostId", value: response.hostId });
96
-
97
- // Register this device's FCM token with the relay server so it can wake
98
- // the device on the paired host's behalf. Moved here from native so the
99
- // APK no longer needs to read hostId or trigger registration itself.
100
- try {
101
- const { token: fcmToken } = await Device.getFcmToken();
102
- await fetch(`${SERVER_URL}/api/fcm/register`, {
103
- method: "POST",
104
- headers: { "Content-Type": "application/json" },
105
- body: JSON.stringify({ hostId: response.hostId, fcmToken }),
106
- });
107
- } catch (err) {
108
- console.warn("FCM token registration failed:", err);
109
- }
110
- }
111
-
112
- const base = `/hosts/${encodeURIComponent(response.hostId)}`;
113
- navigate(isNative && makeLinked ? `${base}/pair/setup` : base);
114
- } catch (err) {
115
- const message = err instanceof Error ? err.message : String(err);
116
- if (message.includes("timeout") || message.includes("TIMEOUT") || message.includes("503") || message.toLowerCase().includes("no responders")) {
117
- setError("Code not found or expired. Check the code and try again.");
118
- } else {
119
- setError(message);
120
- }
121
- } finally {
122
- setPairing(false);
123
- }
124
- }
125
-
126
- return (
127
- <div className="pair-page">
128
- <div className="pair-card">
129
- <div className="pair-header">
130
- <h1 className="pair-title">{isLoopback ? "Pair" : "Pair with Host"}</h1>
131
- <p className="pair-subtitle">
132
- {isLoopback
133
- ? "Enter the pairing code shown in your terminal."
134
- : "Connect this device to a Palmier host"}
135
- </p>
136
- </div>
137
-
138
- {!isLoopback && (
139
- <div className="pair-instructions">
140
- <div className="pair-instruction-block">
141
- <h3 className="pair-instruction-heading">Setting up a new host?</h3>
142
- <ol className="pair-steps">
143
- <li>Install at least one agent CLI (e.g., <a href="https://www.palmier.me/agents" target="_blank" rel="noopener noreferrer">Claude Code, Gemini CLI, Codex CLI</a>)</li>
144
- <li>Install Palmier on your host machine.
145
- <span className="pair-platform-label">Linux / macOS:</span>
146
- <code className="pair-command">curl -fsSL https://palmier.me/install.sh | bash</code>
147
- <span className="pair-platform-label">Windows (PowerShell):</span>
148
- <code className="pair-command">irm https://palmier.me/install.ps1 | iex</code>
149
- </li>
150
- <li>Run the setup wizard:
151
- <code className="pair-command">palmier init</code>
152
- </li>
153
- <li>A pairing code will display automatically</li>
154
- </ol>
155
- </div>
156
- <div className="pair-instruction-divider" />
157
- <div className="pair-instruction-block">
158
- <h3 className="pair-instruction-heading">Pairing an existing host?</h3>
159
- <ol className="pair-steps">
160
- <li>
161
- Run <code>palmier pair</code> on the host machine
162
- </li>
163
- <li>Enter the 6-character code below</li>
164
- </ol>
165
- </div>
166
- </div>
167
- )}
168
-
169
- <div className="pair-form">
170
- <label className="form-label" htmlFor="pair-code">
171
- Pairing code
172
- <input
173
- id="pair-code"
174
- type="text"
175
- maxLength={6}
176
- value={code}
177
- onChange={(e) => setCode(e.target.value.toUpperCase())}
178
- placeholder="A7K9M2"
179
- className="form-input form-input-mono pair-code-input"
180
- autoFocus
181
- autoComplete="off"
182
- disabled={pairing}
183
- />
184
- </label>
185
-
186
- {isNative && (
187
- <label className="pair-checkbox">
188
- <input
189
- type="checkbox"
190
- checked={makeLinked}
191
- onChange={(e) => setMakeLinked(e.target.checked)}
192
- disabled={pairing}
193
- />
194
- <span className="pair-checkbox-text">
195
- <span className="pair-checkbox-title">Link the host to this device</span>
196
- <span className="pair-checkbox-hint">
197
- {makeLinked
198
- ? "The host will use this device for SMS, contacts, calendar, location, and alarms. Only one device can be linked to the host."
199
- : "This device won't provide SMS, contacts, calendar, location, or alarms to the host. You can link it later from the menu."}
200
- </span>
201
- </span>
202
- </label>
203
- )}
204
-
205
- {error && <p className="pair-error">{error}</p>}
206
-
207
- <button
208
- className="btn btn-primary btn-full"
209
- onClick={handlePair}
210
- disabled={pairing || !code.trim()}
211
- >
212
- {pairing && <span className="btn-spinner" />}
213
- {pairing ? "Pairing..." : "Pair"}
214
- </button>
215
- <button
216
- className="btn btn-secondary btn-full"
217
- onClick={() => navigate("/")}
218
- disabled={pairing}
219
- >
220
- Cancel
221
- </button>
222
-
223
- <p className="pair-consent">
224
- By pairing, you agree to our{" "}
225
- <a href="https://www.palmier.me/terms" target="_blank" rel="noopener noreferrer">Terms of Service</a> and{" "}
226
- <a href="https://www.palmier.me/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>.
227
- </p>
228
- </div>
229
- </div>
230
- </div>
231
- );
232
- }
@@ -1,134 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { createPortal } from "react-dom";
3
- import { useNavigate } from "react-router-dom";
4
- import { useHostConnection } from "../contexts/HostConnectionContext";
5
- import { useHostStore } from "../contexts/HostStoreContext";
6
- import CapabilityToggles from "../components/CapabilityToggles";
7
- import { Device } from "../native/Device";
8
-
9
- interface HostInfoResponse {
10
- lan_url?: string | null;
11
- linked_client_token?: string | null;
12
- }
13
-
14
- type Phase = "loading" | "confirming" | "linking" | "wizard" | "linkError";
15
-
16
- export default function PairSetup() {
17
- const navigate = useNavigate();
18
- const { connected, request, activeHost } = useHostConnection();
19
- const { setHostLanUrl, pairedHosts } = useHostStore();
20
- const isFirstHost = pairedHosts.length <= 1;
21
-
22
- const [phase, setPhase] = useState<Phase>("loading");
23
- const [linkedClientToken, setLinkedClientToken] = useState<string | null>(null);
24
- const [linkError, setLinkError] = useState<string | null>(null);
25
-
26
- function goToHost() {
27
- navigate(`/hosts/${encodeURIComponent(activeHost.hostId)}`, { replace: true });
28
- }
29
-
30
- // Phase: loading → fetch host.info, then transition.
31
- useEffect(() => {
32
- if (!connected || phase !== "loading") return;
33
- let cancelled = false;
34
- request<HostInfoResponse>("host.info").catch(() => ({} as HostInfoResponse)).then((info) => {
35
- if (cancelled) return;
36
- setHostLanUrl(activeHost.hostId, info.lan_url ?? undefined);
37
- const linked = info.linked_client_token ?? null;
38
- setLinkedClientToken(linked);
39
- const otherDeviceLinked = !!linked && linked !== activeHost.clientToken;
40
- setPhase(otherDeviceLinked ? "confirming" : "linking");
41
- });
42
- return () => { cancelled = true; };
43
- }, [connected, phase, activeHost, request, setHostLanUrl]);
44
-
45
- // Phase: linking → call device.link, then either show wizard (first host)
46
- // or navigate (subsequent host).
47
- useEffect(() => {
48
- if (phase !== "linking") return;
49
- let cancelled = false;
50
- (async () => {
51
- try {
52
- if (Device) {
53
- const { token: fcmToken } = await Device.getFcmToken();
54
- if (!fcmToken) throw new Error("Could not read FCM token");
55
- await request("device.link", { fcmToken });
56
- }
57
- if (cancelled) return;
58
- if (isFirstHost) setPhase("wizard");
59
- else goToHost();
60
- } catch (err) {
61
- if (cancelled) return;
62
- setLinkError(err instanceof Error ? err.message : String(err));
63
- setPhase("linkError");
64
- }
65
- })();
66
- return () => { cancelled = true; };
67
- // eslint-disable-next-line react-hooks/exhaustive-deps
68
- }, [phase]);
69
-
70
- function confirmLink() { setPhase("linking"); }
71
- function cancelLink() { goToHost(); }
72
- function retryLink() { setLinkError(null); setPhase("linking"); }
73
-
74
- const linkModal = phase === "confirming" && createPortal(
75
- <div className="confirm-modal-overlay" onClick={cancelLink}>
76
- <div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
77
- <h2 className="confirm-modal-title">Link the host to this device?</h2>
78
- <p className="confirm-modal-message">
79
- Another device is already linked to this host. Only one device can be linked to the host — switching will disable those capabilities on the currently linked device.
80
- </p>
81
- <div className="confirm-modal-actions">
82
- <button className="btn btn-secondary" onClick={cancelLink}>Cancel</button>
83
- <button className="btn btn-primary" onClick={confirmLink}>Link</button>
84
- </div>
85
- </div>
86
- </div>,
87
- document.body,
88
- );
89
-
90
- if (phase === "loading" || phase === "confirming" || phase === "linking" || phase === "linkError") {
91
- const isWizardCandidate = isFirstHost;
92
- const showSpinner = phase === "loading" || phase === "confirming" || phase === "linking";
93
- return (
94
- <div className="pair-setup">
95
- <div className="pair-setup-inner">
96
- {isWizardCandidate && <h1 className="pair-setup-title">Device Capabilities</h1>}
97
- <div className="pair-setup-loading">
98
- {showSpinner && <span className="spinner spinner-lg" />}
99
- {phase === "loading" && <span>Connecting to host…</span>}
100
- {phase === "confirming" && <span>Awaiting confirmation…</span>}
101
- {phase === "linking" && <span>Linking device…</span>}
102
- {phase === "linkError" && (
103
- <>
104
- <p className="pair-error">{linkError}</p>
105
- <button className="btn btn-primary" onClick={retryLink}>Retry</button>
106
- <button className="btn btn-secondary" onClick={goToHost}>Skip linking</button>
107
- </>
108
- )}
109
- </div>
110
- </div>
111
- {linkModal}
112
- </div>
113
- );
114
- }
115
-
116
- // phase === "wizard"
117
- void linkedClientToken;
118
- return (
119
- <div className="pair-setup">
120
- <div className="pair-setup-inner">
121
- <h1 className="pair-setup-title">Device Capabilities</h1>
122
- <p className="pair-setup-description">
123
- Choose what the host can use this device for. You can change these later from the menu.
124
- </p>
125
-
126
- <CapabilityToggles />
127
-
128
- <div className="pair-setup-actions">
129
- <button className="btn btn-primary btn-full" onClick={goToHost}>Finish</button>
130
- </div>
131
- </div>
132
- </div>
133
- );
134
- }
@@ -1,142 +0,0 @@
1
- /// <reference lib="webworker" />
2
- import { precacheAndRoute } from "workbox-precaching";
3
-
4
- declare const self: ServiceWorkerGlobalScope;
5
-
6
- // Precache assets injected by vite-plugin-pwa
7
- precacheAndRoute(self.__WB_MANIFEST);
8
-
9
- const API_URL = "/api/push/respond";
10
-
11
- // hostId stored for potential future use (e.g., scoped push responses)
12
- self.addEventListener("message", (_event) => {
13
- // Handle messages from the main app (e.g., set-host-id)
14
- });
15
-
16
- self.addEventListener("push", (event) => {
17
- if (!event.data) return;
18
-
19
- let payload: {
20
- title?: string;
21
- body?: string;
22
- data?: Record<string, unknown>;
23
- type?: string;
24
- };
25
- try {
26
- payload = event.data.json();
27
- } catch {
28
- payload = { title: "Palmier", body: event.data.text() };
29
- }
30
-
31
- const type = payload.type ?? (payload.data as Record<string, unknown>)?.type;
32
-
33
- // Silent dismiss: close matching notification without showing a new one
34
- if (type === "confirm-dismiss" || type === "permission-dismiss" || type === "input-dismiss") {
35
- const data = payload.data ?? payload;
36
- const dataHostId = (data as Record<string, unknown>).host_id;
37
- const requestId = (data as Record<string, unknown>).session_id;
38
- const taskId = (data as Record<string, unknown>).task_id; // permission-dismiss still uses task_id
39
- event.waitUntil(
40
- self.registration.getNotifications().then((notifications) => {
41
- for (const n of notifications) {
42
- if (n.data?.host_id !== dataHostId) continue;
43
- if (requestId && n.data?.session_id === requestId) { n.close(); continue; }
44
- if (taskId && n.data?.task_id === taskId) { n.close(); } // permission only
45
- }
46
- })
47
- );
48
- return;
49
- }
50
-
51
- const title = payload.title ?? "Palmier";
52
- let body = payload.body ?? "";
53
- if (!body && type === "confirm") {
54
- body = "A task requires confirmation to run.";
55
- }
56
- if (!body && type === "permission") {
57
- body = "A task needs additional permissions to continue.";
58
- }
59
- if (!body && type === "input") {
60
- body = "A task needs your input to continue.";
61
- }
62
-
63
- const options: NotificationOptions & { vibrate?: number[]; actions?: Array<{ action: string; title: string }> } = {
64
- body,
65
- icon: "/pwa-192x192.png",
66
- badge: "/pwa-192x192.png",
67
- data: payload.data ?? payload,
68
- vibrate: [100, 50, 100],
69
- };
70
-
71
- // Add action buttons for confirmation notifications
72
- if (type === "confirm") {
73
- options.actions = [
74
- { action: "confirm", title: "Confirm" },
75
- { action: "abort", title: "Abort" },
76
- ];
77
- }
78
-
79
- event.waitUntil(self.registration.showNotification(title, options));
80
- });
81
-
82
- self.addEventListener("notificationclick", (event) => {
83
- const notification = event.notification;
84
- notification.close();
85
-
86
- const data = notification.data ?? {};
87
- const action = event.action;
88
-
89
- if (action && data.type === "confirm" && data.session_id && data.host_id) {
90
- const response = action === "confirm" ? "confirmed" : "aborted";
91
-
92
- event.waitUntil(
93
- fetch(API_URL, {
94
- method: "POST",
95
- headers: { "Content-Type": "application/json" },
96
- body: JSON.stringify({
97
- session_id: data.session_id,
98
- host_id: data.host_id,
99
- response,
100
- }),
101
- }).catch((err) => {
102
- console.error("Failed to send push response:", err);
103
- })
104
- );
105
- } else {
106
- // User tapped the notification body — open the PWA.
107
- // For task-complete/fail notifications, deep-link to the result view
108
- // scoped to the originating host so the PWA switches hosts automatically.
109
- const hostId = data.host_id;
110
- const taskId = data.task_id;
111
- const runId = data.run_id;
112
- const hostPrefix = hostId ? `/hosts/${encodeURIComponent(hostId)}` : "";
113
- const targetUrl = hostPrefix && taskId && runId
114
- ? `${hostPrefix}/runs/${encodeURIComponent(taskId)}/${encodeURIComponent(runId)}`
115
- : hostPrefix && taskId
116
- ? `${hostPrefix}/runs/${encodeURIComponent(taskId)}/latest`
117
- : hostPrefix || "/";
118
-
119
- event.waitUntil(
120
- self.clients
121
- .matchAll({ type: "window", includeUncontrolled: true })
122
- .then((clients) => {
123
- for (const client of clients) {
124
- if (client.url.includes(self.location.origin) && "focus" in client) {
125
- (client as WindowClient).navigate(targetUrl);
126
- return client.focus();
127
- }
128
- }
129
- return self.clients.openWindow(targetUrl);
130
- })
131
- );
132
- }
133
- });
134
-
135
- // Activate immediately
136
- self.addEventListener("install", () => {
137
- self.skipWaiting();
138
- });
139
-
140
- self.addEventListener("activate", (event) => {
141
- event.waitUntil(self.clients.claim());
142
- });
@@ -1,75 +0,0 @@
1
- export interface AgentInfo {
2
- key: string;
3
- label: string;
4
- supportsPermissions: boolean;
5
- supportsYolo: boolean;
6
- }
7
-
8
-
9
- export interface Task {
10
- id: string;
11
- name: string;
12
- user_prompt: string;
13
- agent?: string;
14
- schedule_type?: "crons" | "specific_times" | "on_new_notification" | "on_new_sms";
15
- schedule_values?: string[];
16
- schedule_enabled: boolean;
17
- requires_confirmation: boolean;
18
- yolo_mode?: boolean;
19
- foreground_mode?: boolean;
20
- permissions?: RequiredPermission[];
21
- command?: string;
22
- }
23
-
24
- export interface RequiredPermission {
25
- name: string;
26
- description: string;
27
- }
28
-
29
- export interface ConfirmNotification {
30
- type: "confirm";
31
- task_id: string;
32
- host_id: string;
33
- }
34
-
35
- export type TaskRunningState = "started" | "finished" | "aborted" | "failed";
36
-
37
- export interface TaskStatus {
38
- running_state: TaskRunningState;
39
- /** UTC time in milliseconds since epoch */
40
- time_stamp: number;
41
- }
42
-
43
- export interface HistoryEntry {
44
- task_id: string;
45
- run_id: string;
46
- // Enriched by taskrun.list RPC from TASKRUN.md:
47
- task_name?: string;
48
- running_state?: string;
49
- start_time?: number;
50
- end_time?: number;
51
- error?: string;
52
- }
53
-
54
- export interface ConversationMessage {
55
- role: "assistant" | "user" | "status";
56
- time: number;
57
- content: string;
58
- type?: "input" | "permission" | "confirmation" | "monitoring" | "started" | "finished" | "failed" | "aborted" | "stopped" | "error";
59
- attachments?: string[];
60
- }
61
-
62
- /** A host paired via pairing code (stored in localStorage). */
63
- export interface PairedHost {
64
- hostId: string;
65
- clientToken: string;
66
- name?: string;
67
- /** If set, all communication uses HTTP to this URL instead of NATS. Set only for loopback (Local mode). */
68
- directUrl?: string;
69
- /** Host's LAN URL, refreshed from each `host.info` response so laptop/DHCP IP changes propagate. Native Capacitor app probes for reachability and routes RPC over HTTP when reachable; events stay on NATS. */
70
- lanUrl?: string;
71
- /** Last-used agent key for this host. Seeds the agent picker on the session composer and task form. */
72
- lastAgent?: string;
73
- /** IANA timezone from the host, refreshed on each `host.info`. Drives all time rendering. */
74
- timezone?: string;
75
- }
@@ -1,11 +0,0 @@
1
- /// <reference types="vite/client" />
2
-
3
- declare module "@fontsource-variable/plus-jakarta-sans";
4
-
5
- interface ImportMetaEnv {
6
- readonly VITE_API_URL: string;
7
- }
8
-
9
- interface ImportMeta {
10
- readonly env: ImportMetaEnv;
11
- }
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "useDefineForClassFields": true,
5
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
- "module": "ESNext",
7
- "skipLibCheck": true,
8
- "moduleResolution": "Bundler",
9
- "allowImportingTsExtensions": true,
10
- "isolatedModules": true,
11
- "moduleDetection": "force",
12
- "noEmit": true,
13
- "jsx": "react-jsx",
14
- "strict": true,
15
- "noUnusedLocals": true,
16
- "noUnusedParameters": true,
17
- "noFallthroughCasesInSwitch": true,
18
- "noUncheckedSideEffectImports": true
19
- },
20
- "include": ["src"]
21
- }
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": ["ES2023"],
5
- "module": "ESNext",
6
- "skipLibCheck": true,
7
- "moduleResolution": "Bundler",
8
- "allowImportingTsExtensions": true,
9
- "isolatedModules": true,
10
- "moduleDetection": "force",
11
- "noEmit": true,
12
- "strict": true,
13
- "noUnusedLocals": true,
14
- "noUnusedParameters": true,
15
- "noFallthroughCasesInSwitch": true,
16
- "noUncheckedSideEffectImports": true
17
- },
18
- "include": ["vite.config.ts"]
19
- }
@@ -1,47 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import react from "@vitejs/plugin-react";
3
- import { VitePWA } from "vite-plugin-pwa";
4
-
5
- export default defineConfig({
6
- server: {
7
- host: true,
8
- proxy: {
9
- "/api": process.env.API_URL || "http://localhost:3000"
10
- },
11
- },
12
- plugins: [
13
- react(),
14
- VitePWA({
15
- strategies: "injectManifest",
16
- srcDir: "src",
17
- filename: "service-worker.ts",
18
- registerType: "autoUpdate",
19
- devOptions: {
20
- enabled: true,
21
- type: "module",
22
- },
23
- includeAssets: ["favicon.ico", "apple-touch-icon.png"],
24
- manifest: {
25
- name: "Palmier",
26
- short_name: "Palmier",
27
- description: "Bridge your AI agents and your phone. Your AI agents use your phone as a tool — GPS, email, calendar, contacts — and you use your phone as an agent remote.",
28
- start_url: "/",
29
- display: "standalone",
30
- background_color: "#ffffff",
31
- theme_color: "#2E5CE5",
32
- icons: [
33
- {
34
- src: "pwa-192x192.png",
35
- sizes: "192x192",
36
- type: "image/png",
37
- },
38
- {
39
- src: "pwa-512x512.png",
40
- sizes: "512x512",
41
- type: "image/png",
42
- },
43
- ],
44
- },
45
- }),
46
- ],
47
- });