palmier 0.9.6 → 0.9.7

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