palmier 0.9.6 → 0.9.8

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