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,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
- }