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,211 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { loadConfig } from "../config.js";
4
- import { connectNats } from "../nats-client.js";
5
- import { createRpcHandler } from "../rpc-handler.js";
6
- import { startNatsTransport } from "../transports/nats-transport.js";
7
- import { startHttpTransport } from "../transports/http-transport.js";
8
- import { getTaskDir, readTaskStatus, writeTaskStatus, parseTaskFile, appendRunMessage, listTasks, readFollowupStatus, deleteFollowupStatus } from "../task.js";
9
- import { publishHostEvent } from "../events.js";
10
- import { getPlatform } from "../platform/index.js";
11
- import { detectAgents } from "../agents/agent.js";
12
- import { saveConfig } from "../config.js";
13
- import type { HostConfig } from "../types.js";
14
- import { CONFIG_DIR } from "../config.js";
15
- import { StringCodec, type NatsConnection } from "nats";
16
- import { addNotification } from "../notification-store.js";
17
- import { addSmsMessage } from "../sms-store.js";
18
- import { enqueueEvent } from "../event-queues.js";
19
-
20
- const POLL_INTERVAL_MS = 30_000;
21
- const DAEMON_PID_FILE = path.join(CONFIG_DIR, "daemon.pid");
22
-
23
- /**
24
- * Reconcile tasks stuck in "started" whose process is no longer alive, and
25
- * clean up OS scheduler units for one-off tasks that have already terminated.
26
- * The system scheduler (Task Scheduler / systemd) is the authoritative source.
27
- */
28
- async function checkStaleTasks(
29
- config: HostConfig,
30
- nc: NatsConnection | undefined,
31
- ): Promise<void> {
32
- const tasksRoot = path.join(config.projectRoot, "tasks");
33
- if (!fs.existsSync(tasksRoot)) return;
34
-
35
- const platform = getPlatform();
36
- const taskIds = fs.readdirSync(tasksRoot).filter((f) =>
37
- fs.statSync(path.join(tasksRoot, f)).isDirectory()
38
- );
39
-
40
- for (const taskId of taskIds) {
41
- const taskDir = getTaskDir(config.projectRoot, taskId);
42
- const status = readTaskStatus(taskDir);
43
- if (!status) continue;
44
-
45
- let task;
46
- try { task = parseTaskFile(taskDir); } catch { continue; }
47
-
48
- if (status.running_state === "started" && !platform.isTaskRunning(taskId)) {
49
- console.log(`[monitor] Task ${taskId} process exited unexpectedly, marking as failed.`);
50
- const endTime = Date.now();
51
- writeTaskStatus(taskDir, { running_state: "failed", time_stamp: endTime });
52
-
53
- const runId = fs.readdirSync(taskDir)
54
- .filter((f) => /^\d+$/.test(f) && fs.existsSync(path.join(taskDir, f, "TASKRUN.md")))
55
- .sort()
56
- .pop();
57
-
58
- if (runId) {
59
- appendRunMessage(taskDir, runId, {
60
- role: "status",
61
- time: endTime,
62
- content: "",
63
- type: "failed",
64
- });
65
- }
66
-
67
- await publishHostEvent(nc, config.hostId, taskId, {
68
- event_type: "running-state",
69
- running_state: "failed",
70
- name: task.frontmatter.name || taskId,
71
- });
72
- }
73
-
74
- if (task.frontmatter.one_off && status.running_state !== "started") {
75
- try { platform.removeTaskTimer(taskId); } catch { /* best-effort */ }
76
- }
77
-
78
- // Reconcile orphaned follow-ups: if a run has a persisted follow-up PID
79
- // but that process is no longer alive, clear the file and mark the run
80
- // as failed so the UI doesn't claim it's still running.
81
- const runIds = fs.readdirSync(taskDir).filter((f) =>
82
- /^\d+$/.test(f) && fs.existsSync(path.join(taskDir, f, "TASKRUN.md"))
83
- );
84
- for (const runId of runIds) {
85
- const runDir = path.join(taskDir, runId);
86
- const followup = readFollowupStatus(runDir);
87
- if (!followup) continue;
88
- try {
89
- process.kill(followup.pid, 0);
90
- } catch {
91
- deleteFollowupStatus(runDir);
92
- appendRunMessage(taskDir, runId, {
93
- role: "status",
94
- time: Date.now(),
95
- content: "",
96
- type: "failed",
97
- });
98
- await publishHostEvent(nc, config.hostId, taskId, { event_type: "result-updated", run_id: runId });
99
- }
100
- }
101
- }
102
- }
103
-
104
- export async function serveCommand(): Promise<void> {
105
- const config = loadConfig();
106
-
107
- // PID file lets `palmier restart` find us regardless of how we were started
108
- fs.writeFileSync(DAEMON_PID_FILE, String(process.pid), "utf-8");
109
-
110
- console.log("Starting...");
111
-
112
- const agents = await detectAgents();
113
- config.agents = agents;
114
- saveConfig(config);
115
- console.log(`Detected agents: ${agents.map((a) => a.key).join(", ") || "none"}`);
116
-
117
- let nc: NatsConnection | undefined;
118
- try {
119
- nc = await connectNats(config);
120
- console.log("[nats] Connected");
121
- } catch (err) {
122
- console.warn(`[nats] Connection failed (server mode unavailable): ${err}`);
123
- }
124
-
125
- await checkStaleTasks(config, nc);
126
-
127
- // Reinstall scheduler entries for all tasks (recovery after init/reinstall)
128
- const platform = getPlatform();
129
- const allTasks = listTasks(config.projectRoot);
130
- for (const task of allTasks) {
131
- try {
132
- platform.installTaskTimer(config, task);
133
- } catch (err) {
134
- console.error(`Warning: failed to install timer for task ${task.frontmatter.id}: ${err}`);
135
- }
136
- }
137
-
138
- setInterval(() => {
139
- checkStaleTasks(config, nc).catch((err) => {
140
- console.error("[monitor] Error checking stale tasks:", err);
141
- });
142
- }, POLL_INTERVAL_MS);
143
-
144
- const handleRpc = createRpcHandler(config, nc);
145
- const httpPort = config.httpPort ?? 7256;
146
-
147
- if (nc) {
148
- startNatsTransport(config, handleRpc, nc);
149
-
150
- const sc = StringCodec();
151
-
152
- // Match phone numbers regardless of formatting; letters preserved for shortcodes.
153
- function normalizeSender(raw: string): string {
154
- return raw.replace(/[\s\-()+]/g, "").toLowerCase();
155
- }
156
-
157
- function dispatchDeviceEvent(scheduleType: "on_new_notification" | "on_new_sms", payload: string, parsed?: unknown): void {
158
- for (const task of listTasks(config.projectRoot)) {
159
- if (task.frontmatter.schedule_type !== scheduleType) continue;
160
- if (!task.frontmatter.schedule_enabled) continue;
161
- if (scheduleType === "on_new_notification" && task.frontmatter.schedule_values && task.frontmatter.schedule_values.length > 0) {
162
- const pkg = (parsed as { packageName?: string } | undefined)?.packageName;
163
- if (!pkg || !task.frontmatter.schedule_values.includes(pkg)) continue;
164
- }
165
- if (scheduleType === "on_new_sms" && task.frontmatter.schedule_values && task.frontmatter.schedule_values.length > 0) {
166
- const sender = (parsed as { sender?: string } | undefined)?.sender;
167
- const normalizedSender = sender ? normalizeSender(sender) : "";
168
- if (!normalizedSender || !task.frontmatter.schedule_values.some((s) => normalizeSender(s) === normalizedSender)) continue;
169
- }
170
- const { shouldStart } = enqueueEvent(task.frontmatter.id, payload);
171
- if (shouldStart) {
172
- platform.startTask(task.frontmatter.id).catch((err) => {
173
- console.error(`[event-trigger] Failed to start ${task.frontmatter.id}:`, err);
174
- });
175
- }
176
- }
177
- }
178
-
179
- const notifSub = nc.subscribe(`host.${config.hostId}.device.notifications`);
180
- (async () => {
181
- for await (const msg of notifSub) {
182
- const raw = sc.decode(msg.data);
183
- let parsed: unknown;
184
- try {
185
- parsed = JSON.parse(raw);
186
- addNotification({ ...(parsed as object), receivedAt: Date.now() } as Parameters<typeof addNotification>[0]);
187
- } catch (err) {
188
- console.error("[nats] Failed to parse device notification:", err);
189
- }
190
- dispatchDeviceEvent("on_new_notification", raw, parsed);
191
- }
192
- })();
193
-
194
- const smsSub = nc.subscribe(`host.${config.hostId}.device.sms`);
195
- (async () => {
196
- for await (const msg of smsSub) {
197
- const raw = sc.decode(msg.data);
198
- let parsed: unknown;
199
- try {
200
- parsed = JSON.parse(raw);
201
- addSmsMessage({ ...(parsed as object), receivedAt: Date.now() } as Parameters<typeof addSmsMessage>[0]);
202
- } catch (err) {
203
- console.error("[nats] Failed to parse device SMS:", err);
204
- }
205
- dispatchDeviceEvent("on_new_sms", raw, parsed);
206
- }
207
- })();
208
- }
209
-
210
- await startHttpTransport(config, handleRpc, httpPort, nc);
211
- }
@@ -1,9 +0,0 @@
1
- import { getPlatform } from "../platform/index.js";
2
-
3
- export async function uninstallCommand(): Promise<void> {
4
- const platform = getPlatform();
5
- platform.uninstallDaemon();
6
-
7
- console.log("\nTo uninstall the package: npm uninstall -g palmier");
8
- console.log("To also remove configuration and task data, see https://github.com/caihongxu/palmier#uninstalling");
9
- }
package/src/config.ts DELETED
@@ -1,36 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { homedir } from "os";
4
- import type { HostConfig } from "./types.js";
5
-
6
- const CONFIG_DIR = path.join(homedir(), ".config", "palmier");
7
- const CONFIG_FILE = path.join(CONFIG_DIR, "host.json");
8
-
9
- export function loadConfig(): HostConfig {
10
- if (!fs.existsSync(CONFIG_FILE)) {
11
- throw new Error(
12
- "Host not provisioned. Run `palmier init` first.\n" +
13
- `Expected config at: ${CONFIG_FILE}`
14
- );
15
- }
16
-
17
- const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
18
- const config = JSON.parse(raw) as HostConfig;
19
-
20
- if (!config.hostId) {
21
- throw new Error("Invalid host config: missing hostId");
22
- }
23
-
24
- if (!config.natsUrl || !config.natsJwt || !config.natsNkeySeed) {
25
- throw new Error("Invalid host config: missing NATS JWT credentials. Re-run palmier init.");
26
- }
27
-
28
- return config;
29
- }
30
-
31
- export function saveConfig(config: HostConfig): void {
32
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
33
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
34
- }
35
-
36
- export { CONFIG_DIR, CONFIG_FILE };
@@ -1,5 +0,0 @@
1
- declare module "cross-spawn" {
2
- import type { ChildProcess, SpawnOptions } from "child_process";
3
- function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess;
4
- export default spawn;
5
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * Per-task in-memory event queues for on_new_notification / on_new_sms schedules.
3
- * The daemon owns the NATS subscription and populates these queues; the
4
- * `palmier run` process drains via /task-event/pop.
5
- *
6
- * Invariants:
7
- * - popEvent clears activeRuns atomically when the queue empties, so a
8
- * fresh startTask cannot race the tearing-down run.
9
- * - enqueueEvent returns shouldStart=true only on the idle→active edge.
10
- */
11
-
12
- const MAX_QUEUE_SIZE = 100;
13
-
14
- const queues = new Map<string, string[]>();
15
- const activeRuns = new Set<string>();
16
-
17
- export function enqueueEvent(taskId: string, payload: string): { shouldStart: boolean } {
18
- const queue = queues.get(taskId) ?? [];
19
- if (queue.length >= MAX_QUEUE_SIZE) queue.shift();
20
- queue.push(payload);
21
- queues.set(taskId, queue);
22
-
23
- if (activeRuns.has(taskId)) return { shouldStart: false };
24
- activeRuns.add(taskId);
25
- return { shouldStart: true };
26
- }
27
-
28
- export function popEvent(taskId: string): { event: string } | { empty: true } {
29
- const queue = queues.get(taskId);
30
- if (queue && queue.length > 0) {
31
- return { event: queue.shift()! };
32
- }
33
- activeRuns.delete(taskId);
34
- return { empty: true };
35
- }
36
-
37
- /** Remove any state for a task (called from task.delete). */
38
- export function clearTaskQueue(taskId: string): void {
39
- queues.delete(taskId);
40
- activeRuns.delete(taskId);
41
- }
package/src/events.ts DELETED
@@ -1,29 +0,0 @@
1
- import { StringCodec, type NatsConnection } from "nats";
2
- import { loadConfig } from "./config.js";
3
-
4
- const sc = StringCodec();
5
-
6
- export async function publishHostEvent(
7
- nc: NatsConnection | undefined,
8
- hostId: string,
9
- taskId: string,
10
- payload: Record<string, unknown>,
11
- ): Promise<void> {
12
- const subject = `host-event.${hostId}.${taskId}`;
13
-
14
- if (nc) {
15
- nc.publish(subject, sc.encode(JSON.stringify(payload)));
16
- console.log(`[nats] ${subject} →`, payload);
17
- }
18
-
19
- const config = loadConfig();
20
- const port = config.httpPort ?? 7256;
21
- try {
22
- await fetch(`http://localhost:${port}/event`, {
23
- method: "POST",
24
- headers: { "Content-Type": "application/json" },
25
- body: JSON.stringify({ task_id: taskId, ...payload }),
26
- });
27
- console.log(`[http] host-event: ${taskId} →`, payload);
28
- } catch { /* serve HTTP may not be ready yet */ }
29
- }
package/src/index.ts DELETED
@@ -1,111 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import "dotenv/config";
4
- import { readFileSync } from "fs";
5
- import { fileURLToPath } from "url";
6
- import { dirname, join } from "path";
7
- import { Command } from "commander";
8
- import { initCommand } from "./commands/init.js";
9
- import { infoCommand } from "./commands/info.js";
10
- import { runCommand } from "./commands/run.js";
11
- import { serveCommand } from "./commands/serve.js";
12
-
13
- import { pairCommand } from "./commands/pair.js";
14
- import { restartCommand } from "./commands/restart.js";
15
- import { clientsListCommand, clientsRevokeCommand, clientsRevokeAllCommand } from "./commands/clients.js";
16
- import { uninstallCommand } from "./commands/uninstall.js";
17
-
18
- const __dirname = dirname(fileURLToPath(import.meta.url));
19
- const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
20
-
21
- const program = new Command();
22
-
23
- program
24
- .name("palmier")
25
- .description("Palmier host CLI")
26
- .version(pkg.version);
27
-
28
- program
29
- .command("init")
30
- .description("Provision this host")
31
- .action(async () => {
32
- await initCommand();
33
- });
34
-
35
- program
36
- .command("info")
37
- .description("Show host connection info")
38
- .action(async () => {
39
- await infoCommand();
40
- });
41
-
42
- program
43
- .command("run <task-id>")
44
- .description("Execute a task by ID")
45
- .action(async (taskId: string) => {
46
- await runCommand(taskId);
47
- });
48
-
49
- program
50
- .command("serve")
51
- .description("Start the persistent RPC handler")
52
- .action(async () => {
53
- await serveCommand();
54
- });
55
-
56
- program
57
- .command("restart")
58
- .description("Restart the palmier serve daemon")
59
- .action(async () => {
60
- await restartCommand();
61
- });
62
-
63
-
64
- program
65
- .command("pair")
66
- .description("Generate a pairing code for connecting a PWA client")
67
- .action(async () => {
68
- await pairCommand();
69
- });
70
-
71
-
72
- const clientsCmd = program
73
- .command("clients")
74
- .description("Manage paired clients");
75
-
76
- clientsCmd
77
- .command("list")
78
- .description("List active clients")
79
- .action(async () => {
80
- await clientsListCommand();
81
- });
82
-
83
- clientsCmd
84
- .command("revoke <token>")
85
- .description("Revoke a client by token")
86
- .action(async (token: string) => {
87
- await clientsRevokeCommand(token);
88
- });
89
-
90
- clientsCmd
91
- .command("revoke-all")
92
- .description("Revoke all clients")
93
- .action(async () => {
94
- await clientsRevokeAllCommand();
95
- });
96
-
97
- program
98
- .command("uninstall")
99
- .description("Stop the daemon and remove all scheduled tasks")
100
- .action(async () => {
101
- await uninstallCommand();
102
- });
103
-
104
- if (process.argv.length <= 2) {
105
- process.argv.push("serve");
106
- }
107
-
108
- program.parseAsync(process.argv).catch((err) => {
109
- console.error(err);
110
- process.exit(1);
111
- });
@@ -1,52 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { CONFIG_DIR } from "./config.js";
4
-
5
- const LINKED_DEVICE_FILE = path.join(CONFIG_DIR, "linked-device.json");
6
-
7
- export interface LinkedDevice {
8
- clientToken: string;
9
- fcmToken: string;
10
- }
11
-
12
- function read(): LinkedDevice | null {
13
- try {
14
- if (!fs.existsSync(LINKED_DEVICE_FILE)) return null;
15
- const raw = fs.readFileSync(LINKED_DEVICE_FILE, "utf-8");
16
- const parsed = JSON.parse(raw) as Partial<LinkedDevice>;
17
- if (!parsed?.clientToken || !parsed?.fcmToken) return null;
18
- return { clientToken: parsed.clientToken, fcmToken: parsed.fcmToken };
19
- } catch {
20
- return null;
21
- }
22
- }
23
-
24
- function write(device: LinkedDevice | null): void {
25
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
26
- if (!device) {
27
- if (fs.existsSync(LINKED_DEVICE_FILE)) fs.unlinkSync(LINKED_DEVICE_FILE);
28
- return;
29
- }
30
- fs.writeFileSync(LINKED_DEVICE_FILE, JSON.stringify(device, null, 2), "utf-8");
31
- }
32
-
33
- export function getLinkedDevice(): LinkedDevice | null {
34
- return read();
35
- }
36
-
37
- export function setLinkedDevice(clientToken: string, fcmToken: string): void {
38
- write({ clientToken, fcmToken });
39
- }
40
-
41
- export function clearLinkedDevice(): void {
42
- write(null);
43
- }
44
-
45
- export function clearLinkedDeviceIfMatches(clientToken: string): boolean {
46
- const current = read();
47
- if (current?.clientToken === clientToken) {
48
- write(null);
49
- return true;
50
- }
51
- return false;
52
- }
@@ -1,200 +0,0 @@
1
- import { randomUUID } from "crypto";
2
- import { agentTools, agentToolMap, agentResources, agentResourceMap, ToolError, type ToolContext } from "./mcp-tools.js";
3
-
4
- interface JsonRpcRequest {
5
- jsonrpc: string;
6
- id?: string | number | null;
7
- method: string;
8
- params?: Record<string, unknown>;
9
- }
10
-
11
- export interface McpResponse {
12
- body: object;
13
- sessionId?: string;
14
- /** If true, the HTTP transport should keep the response open as an SSE stream for server-initiated notifications. */
15
- stream?: boolean;
16
- }
17
-
18
- /** sessionId → subscribed resource URIs */
19
- const resourceSubscriptions = new Map<string, Set<string>>();
20
-
21
- export function getResourceSubscriptions(): Map<string, Set<string>> {
22
- return resourceSubscriptions;
23
- }
24
-
25
- const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
26
- const sessionAgents = new Map<string, { agentName: string; expiresAt: number }>();
27
-
28
- export function getAgentName(sessionId: string): string | undefined {
29
- const entry = sessionAgents.get(sessionId);
30
- if (!entry) return undefined;
31
- if (Date.now() > entry.expiresAt) {
32
- sessionAgents.delete(sessionId);
33
- return undefined;
34
- }
35
- return entry.agentName;
36
- }
37
-
38
- function pruneExpiredSessions(): void {
39
- const now = Date.now();
40
- for (const [id, entry] of sessionAgents) {
41
- if (now > entry.expiresAt) {
42
- sessionAgents.delete(id);
43
- resourceSubscriptions.delete(id);
44
- }
45
- }
46
- }
47
-
48
- function rpcError(id: string | number | null, code: number, message: string): object {
49
- return { jsonrpc: "2.0", id, error: { code, message } };
50
- }
51
-
52
- function rpcResult(id: string | number | null, result: unknown): object {
53
- return { jsonrpc: "2.0", id, result };
54
- }
55
-
56
- export async function handleMcpRequest(body: string, sessionId: string | undefined, ctx: ToolContext): Promise<McpResponse> {
57
- let req: JsonRpcRequest;
58
- try {
59
- req = JSON.parse(body);
60
- } catch {
61
- return { body: rpcError(null, -32700, "Parse error") };
62
- }
63
-
64
- const id = req.id ?? null;
65
-
66
- if (req.jsonrpc !== "2.0") {
67
- return { body: rpcError(id, -32600, "Invalid Request: missing jsonrpc 2.0") };
68
- }
69
-
70
- const agent = sessionId ? getAgentName(sessionId) : undefined;
71
- const sid = sessionId?.slice(0, 8) ?? "none";
72
- const logPrefix = agent ? `[mcp] [${sid}] [${agent}]` : `[mcp] [${sid}]`;
73
- console.log(`${logPrefix} ${req.method}${req.method === "tools/call" ? ` → ${req.params?.name}` : ""}`);
74
-
75
- switch (req.method) {
76
- case "initialize": {
77
- const newSessionId = randomUUID();
78
- const clientInfo = req.params?.clientInfo as { name?: string; version?: string } | undefined;
79
- const agentName = clientInfo
80
- ? `${clientInfo.name || "unknown"}${clientInfo.version ? ` ${clientInfo.version}` : ""}`
81
- : undefined;
82
-
83
- if (agentName) {
84
- sessionAgents.set(newSessionId, { agentName, expiresAt: Date.now() + SESSION_TTL_MS });
85
- pruneExpiredSessions();
86
- }
87
-
88
- console.log(`[mcp] [${newSessionId.slice(0, 8)}] Session initialized${agentName ? ` (${agentName})` : ""}`);
89
- return {
90
- body: rpcResult(id, {
91
- protocolVersion: "2025-03-26",
92
- capabilities: { tools: {}, resources: { subscribe: true } },
93
- serverInfo: { name: "palmier", version: "1.0.0" },
94
- }),
95
- sessionId: newSessionId,
96
- };
97
- }
98
-
99
- case "tools/list": {
100
- return {
101
- body: rpcResult(id, {
102
- tools: agentTools.map((t) => ({
103
- name: t.name,
104
- description: t.description.join(" "),
105
- inputSchema: t.inputSchema,
106
- })),
107
- }),
108
- };
109
- }
110
-
111
- case "tools/call": {
112
- const name = req.params?.name as string | undefined;
113
- const args = (req.params?.arguments ?? {}) as Record<string, unknown>;
114
-
115
- if (!name) return { body: rpcError(id, -32602, "Missing params.name") };
116
-
117
- const tool = agentToolMap.get(name);
118
- if (!tool) return { body: rpcError(id, -32602, `Unknown tool: ${name}`) };
119
-
120
- try {
121
- const result = await tool.handler(args, ctx);
122
- console.log(`${logPrefix} tools/call ${name} done:`, JSON.stringify(result).slice(0, 200));
123
- return {
124
- body: rpcResult(id, {
125
- content: [{ type: "text", text: JSON.stringify(result) }],
126
- }),
127
- };
128
- } catch (err: any) {
129
- const message = err instanceof ToolError ? err.message : String(err);
130
- console.error(`${logPrefix} tools/call ${name} error:`, message);
131
- return {
132
- body: rpcResult(id, {
133
- content: [{ type: "text", text: JSON.stringify({ error: message }) }],
134
- isError: true,
135
- }),
136
- };
137
- }
138
- }
139
-
140
- case "resources/list": {
141
- return {
142
- body: rpcResult(id, {
143
- resources: agentResources.map((r) => ({
144
- uri: r.uri,
145
- name: r.name,
146
- description: r.description[0],
147
- mimeType: r.mimeType,
148
- })),
149
- }),
150
- };
151
- }
152
-
153
- case "resources/read": {
154
- const uri = req.params?.uri as string;
155
- const resource = agentResourceMap.get(uri);
156
- if (!resource) {
157
- return { body: rpcError(id, -32602, `Unknown resource: ${uri}`) };
158
- }
159
- console.log(`${logPrefix} resources/read ${uri}`);
160
- const content = resource.read();
161
- console.log(`${logPrefix} resources/read ${uri} done: ${JSON.stringify(content).slice(0, 200)}`);
162
- return {
163
- body: rpcResult(id, {
164
- contents: [{
165
- uri: resource.uri,
166
- mimeType: resource.mimeType,
167
- text: JSON.stringify(content),
168
- }],
169
- }),
170
- };
171
- }
172
-
173
- case "resources/subscribe": {
174
- const uri = req.params?.uri as string;
175
- if (!agentResourceMap.has(uri)) {
176
- return { body: rpcError(id, -32602, `Unknown resource: ${uri}`) };
177
- }
178
- if (!sessionId) {
179
- return { body: rpcError(id, -32600, "Session required for subscriptions") };
180
- }
181
- if (!resourceSubscriptions.has(sessionId)) {
182
- resourceSubscriptions.set(sessionId, new Set());
183
- }
184
- resourceSubscriptions.get(sessionId)!.add(uri);
185
- return { body: rpcResult(id, {}), stream: true };
186
- }
187
-
188
- case "resources/unsubscribe": {
189
- const uri = req.params?.uri as string;
190
- if (sessionId) {
191
- resourceSubscriptions.get(sessionId)?.delete(uri);
192
- }
193
- return { body: rpcResult(id, {}) };
194
- }
195
-
196
- default:
197
- console.warn(`${logPrefix} Unknown method: ${req.method}`);
198
- return { body: rpcError(id, -32601, `Method not found: ${req.method}`) };
199
- }
200
- }