palmier 0.9.5 → 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 (258) hide show
  1. package/README.md +30 -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 +3 -4
  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/apple-touch-icon.png +0 -0
  91. package/dist/pwa/assets/index-D1bIhEbd.css +1 -0
  92. package/dist/pwa/assets/{index-Cvffaohh.js → index-DWvRAUiy.js} +31 -31
  93. package/dist/pwa/assets/{web-qdLcAD7T.js → web-C4iZbqTC.js} +1 -1
  94. package/dist/pwa/assets/{web-ChtbM4nv.js → web-CBFqJGX6.js} +1 -1
  95. package/dist/pwa/assets/{web-hExASsqW.js → web-DL4uXOpS.js} +1 -1
  96. package/dist/pwa/favicon.ico +0 -0
  97. package/dist/pwa/index.html +3 -3
  98. package/dist/pwa/manifest.webmanifest +1 -1
  99. package/dist/pwa/pwa-192x192.png +0 -0
  100. package/dist/pwa/pwa-512x512.png +0 -0
  101. package/dist/pwa/service-worker.js +1 -1
  102. package/dist/rpc-handler.d.ts +0 -1
  103. package/dist/rpc-handler.js +3 -10
  104. package/dist/sms-store.d.ts +0 -1
  105. package/dist/sms-store.js +0 -1
  106. package/dist/spawn-command.d.ts +0 -1
  107. package/dist/spawn-command.js +0 -1
  108. package/dist/task.d.ts +1 -1
  109. package/dist/task.js +14 -1
  110. package/dist/transports/http-transport.d.ts +0 -1
  111. package/dist/transports/http-transport.js +0 -1
  112. package/dist/transports/nats-transport.d.ts +0 -1
  113. package/dist/transports/nats-transport.js +0 -1
  114. package/dist/types.d.ts +0 -1
  115. package/dist/types.js +0 -1
  116. package/dist/update-checker.d.ts +0 -1
  117. package/dist/update-checker.js +0 -1
  118. package/package.json +5 -1
  119. package/.github/workflows/ci.yml +0 -16
  120. package/.github/workflows/publish.yml +0 -37
  121. package/CLAUDE.md +0 -22
  122. package/dist/pwa/assets/index-DBgOYBrB.css +0 -1
  123. package/palmier-server/.github/workflows/ci.yml +0 -21
  124. package/palmier-server/.github/workflows/deploy.yml +0 -38
  125. package/palmier-server/CLAUDE.md +0 -17
  126. package/palmier-server/PRODUCTION.md +0 -358
  127. package/palmier-server/README.md +0 -231
  128. package/palmier-server/nats.conf +0 -19
  129. package/palmier-server/package.json +0 -15
  130. package/palmier-server/pnpm-lock.yaml +0 -7639
  131. package/palmier-server/pnpm-workspace.yaml +0 -3
  132. package/palmier-server/pwa/index.html +0 -16
  133. package/palmier-server/pwa/logo/logo-prompt.md +0 -28
  134. package/palmier-server/pwa/logo/logo_20260330.png +0 -0
  135. package/palmier-server/pwa/package.json +0 -34
  136. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  137. package/palmier-server/pwa/public/favicon.ico +0 -0
  138. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  139. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  140. package/palmier-server/pwa/src/App.css +0 -3004
  141. package/palmier-server/pwa/src/App.tsx +0 -59
  142. package/palmier-server/pwa/src/agentLabels.ts +0 -11
  143. package/palmier-server/pwa/src/api.ts +0 -67
  144. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
  145. package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -114
  146. package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
  147. package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
  148. package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
  149. package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
  150. package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
  151. package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
  152. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
  153. package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
  154. package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
  155. package/palmier-server/pwa/src/components/TaskForm.tsx +0 -764
  156. package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
  157. package/palmier-server/pwa/src/constants.ts +0 -2
  158. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
  159. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
  160. package/palmier-server/pwa/src/draftGuard.ts +0 -24
  161. package/palmier-server/pwa/src/formatTime.ts +0 -44
  162. package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
  163. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
  164. package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
  165. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
  166. package/palmier-server/pwa/src/main.tsx +0 -14
  167. package/palmier-server/pwa/src/native/Device.ts +0 -49
  168. package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
  169. package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
  170. package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
  171. package/palmier-server/pwa/src/service-worker.ts +0 -142
  172. package/palmier-server/pwa/src/types.ts +0 -75
  173. package/palmier-server/pwa/src/vite-env.d.ts +0 -11
  174. package/palmier-server/pwa/tsconfig.json +0 -21
  175. package/palmier-server/pwa/tsconfig.node.json +0 -19
  176. package/palmier-server/pwa/vite.config.ts +0 -47
  177. package/palmier-server/server/.env.example +0 -20
  178. package/palmier-server/server/package.json +0 -36
  179. package/palmier-server/server/src/db.ts +0 -44
  180. package/palmier-server/server/src/fcm.ts +0 -74
  181. package/palmier-server/server/src/index.ts +0 -688
  182. package/palmier-server/server/src/nats-jwt.ts +0 -299
  183. package/palmier-server/server/src/nats-setup.ts +0 -48
  184. package/palmier-server/server/src/nats.ts +0 -33
  185. package/palmier-server/server/src/notify.ts +0 -34
  186. package/palmier-server/server/src/push.ts +0 -68
  187. package/palmier-server/server/src/routes/device.ts +0 -224
  188. package/palmier-server/server/src/routes/fcm.ts +0 -64
  189. package/palmier-server/server/src/routes/hosts.ts +0 -56
  190. package/palmier-server/server/src/routes/push.ts +0 -101
  191. package/palmier-server/server/tsconfig.json +0 -20
  192. package/palmier-server/spec.md +0 -533
  193. package/src/agents/agent-instructions.md +0 -28
  194. package/src/agents/agent.ts +0 -114
  195. package/src/agents/aider.ts +0 -35
  196. package/src/agents/claude.ts +0 -39
  197. package/src/agents/cline.ts +0 -35
  198. package/src/agents/codex.ts +0 -40
  199. package/src/agents/copilot.ts +0 -37
  200. package/src/agents/cursor.ts +0 -36
  201. package/src/agents/deepagents.ts +0 -36
  202. package/src/agents/droid.ts +0 -35
  203. package/src/agents/gemini.ts +0 -43
  204. package/src/agents/goose.ts +0 -33
  205. package/src/agents/hermes.ts +0 -36
  206. package/src/agents/kimi.ts +0 -35
  207. package/src/agents/kiro.ts +0 -36
  208. package/src/agents/openclaw.ts +0 -29
  209. package/src/agents/opencode.ts +0 -36
  210. package/src/agents/qoder.ts +0 -36
  211. package/src/agents/qwen.ts +0 -32
  212. package/src/agents/shared-prompt.ts +0 -30
  213. package/src/client-store.ts +0 -68
  214. package/src/commands/clients.ts +0 -29
  215. package/src/commands/info.ts +0 -29
  216. package/src/commands/init.ts +0 -165
  217. package/src/commands/pair.ts +0 -137
  218. package/src/commands/restart.ts +0 -6
  219. package/src/commands/run.ts +0 -608
  220. package/src/commands/serve.ts +0 -211
  221. package/src/commands/uninstall.ts +0 -9
  222. package/src/config.ts +0 -36
  223. package/src/cross-spawn.d.ts +0 -5
  224. package/src/event-queues.ts +0 -41
  225. package/src/events.ts +0 -29
  226. package/src/index.ts +0 -111
  227. package/src/linked-device.ts +0 -52
  228. package/src/mcp-handler.ts +0 -200
  229. package/src/mcp-tools.ts +0 -839
  230. package/src/nats-client.ts +0 -19
  231. package/src/network.ts +0 -96
  232. package/src/notification-store.ts +0 -30
  233. package/src/pending-requests.ts +0 -73
  234. package/src/platform/index.ts +0 -20
  235. package/src/platform/linux.ts +0 -296
  236. package/src/platform/macos.ts +0 -329
  237. package/src/platform/platform.ts +0 -31
  238. package/src/platform/windows.ts +0 -299
  239. package/src/rpc-handler.ts +0 -694
  240. package/src/sms-store.ts +0 -28
  241. package/src/spawn-command.ts +0 -123
  242. package/src/task.ts +0 -330
  243. package/src/transports/http-transport.ts +0 -478
  244. package/src/transports/nats-transport.ts +0 -76
  245. package/src/types.ts +0 -89
  246. package/src/update-checker.ts +0 -40
  247. package/test/agent-instructions.test.ts +0 -209
  248. package/test/agent-output-parsing.test.ts +0 -74
  249. package/test/linux-cron.test.ts +0 -41
  250. package/test/macos-plist.test.ts +0 -112
  251. package/test/notification-store.test.ts +0 -57
  252. package/test/pairing.test.ts +0 -35
  253. package/test/result-state.test.ts +0 -110
  254. package/test/task-parsing.test.ts +0 -82
  255. package/test/taskrun-messages.test.ts +0 -224
  256. package/test/tsconfig.json +0 -9
  257. package/test/windows-xml.test.ts +0 -89
  258. 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 also 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
- });