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,209 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import * as fs from "fs";
4
- import * as path from "path";
5
- import { fileURLToPath } from "url";
6
- import { generateEndpointDocs, type ToolDefinition, type ResourceDefinition } from "../src/mcp-tools.js";
7
-
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- const templatePath = path.join(__dirname, "..", "src", "agents", "agent-instructions.md");
10
- const template = fs.readFileSync(templatePath, "utf-8");
11
-
12
- /** Mock tools with a known, stable shape for testing */
13
- const mockTools: ToolDefinition[] = [
14
- {
15
- name: "mock-action",
16
- description: [
17
- "Perform a mock action.",
18
- 'Response: `{"ok": true}` on success.',
19
- ],
20
- inputSchema: {
21
- type: "object",
22
- properties: {
23
- title: { type: "string", description: "Action title" },
24
- detail: { type: "string", description: "Optional detail" },
25
- },
26
- required: ["title"],
27
- },
28
- handler: async () => ({ ok: true }),
29
- },
30
- {
31
- name: "mock-query",
32
- description: [
33
- "Query mock data from the device.",
34
- "Blocks until the device responds.",
35
- 'Response: `{"data": ...}` on success.',
36
- ],
37
- inputSchema: {
38
- type: "object",
39
- properties: {
40
- tags: {
41
- type: "array",
42
- items: { type: "string" },
43
- description: "Filter tags",
44
- },
45
- },
46
- },
47
- handler: async () => ({ data: [] }),
48
- },
49
- ];
50
-
51
- /** Mock resources with a known, stable shape for testing */
52
- const mockResources: ResourceDefinition[] = [
53
- {
54
- uri: "mock://data",
55
- name: "Mock Data",
56
- description: [
57
- "Get mock data from the device.",
58
- "Response: JSON array of data objects.",
59
- ],
60
- mimeType: "application/json",
61
- restPath: "/mock-data",
62
- read: () => [],
63
- },
64
- ];
65
-
66
- /** Minimal replica of getAgentInstructions that doesn't need host.json */
67
- function buildInstructions(taskId: string, opts?: { skipPermissions?: boolean }): string {
68
- let instructions = template
69
- .replace(/\{\{ENDPOINT_DOCS\}\}/g, generateEndpointDocs(7256, taskId, mockTools, mockResources))
70
- .replace(/\{\{TASK_DESCRIPTION\}\}/g, "Test task prompt");
71
- if (opts?.skipPermissions) {
72
- instructions = instructions.replace(/## Permissions\r?\n[\s\S]*?(?=## |\r?\n---)/m, "");
73
- }
74
- return instructions;
75
- }
76
-
77
- describe("getAgentInstructions", () => {
78
- it("includes Permissions section by default", () => {
79
- const result = buildInstructions("test-task-id");
80
- assert.match(result, /## Permissions/);
81
- assert.match(result, /PALMIER_PERMISSION/);
82
- });
83
-
84
- it("strips Permissions section when skipPermissions is true", () => {
85
- const result = buildInstructions("test-task-id", { skipPermissions: true });
86
- assert.doesNotMatch(result, /## Permissions/);
87
- assert.doesNotMatch(result, /PALMIER_PERMISSION/);
88
- });
89
-
90
- it("preserves other sections when Permissions is stripped", () => {
91
- const result = buildInstructions("test-task-id", { skipPermissions: true });
92
- assert.match(result, /## Reporting Output/);
93
- assert.match(result, /## Completion/);
94
- assert.match(result, /## HTTP Endpoints/);
95
- });
96
-
97
- it("replaces all template variables", () => {
98
- const result = buildInstructions("my-task-123");
99
- assert.doesNotMatch(result, /\{\{ENDPOINT_DOCS\}\}/);
100
- assert.doesNotMatch(result, /\{\{TASK_DESCRIPTION\}\}/);
101
- });
102
-
103
- it("includes task ID in endpoint examples", () => {
104
- const result = buildInstructions("my-task-123");
105
- assert.match(result, /my-task-123/);
106
- });
107
-
108
- it("includes port in endpoint URL", () => {
109
- const result = buildInstructions("test");
110
- assert.match(result, /localhost:7256/);
111
- });
112
-
113
- it("includes task description", () => {
114
- const result = buildInstructions("test");
115
- assert.match(result, /Test task prompt/);
116
- });
117
-
118
- });
119
-
120
-
121
- describe("generateEndpointDocs", () => {
122
- const docs = generateEndpointDocs(7256, "test-id", mockTools, mockResources);
123
-
124
- it("matches expected full output", () => {
125
- const expected = [
126
- "The following HTTP endpoints are available during task execution. Use curl to call them.",
127
- "",
128
- "**`POST http://localhost:7256/mock-action?taskId=test-id`** — Perform a mock action.",
129
- "```json",
130
- '{"title":"...","detail":"..."}',
131
- "```",
132
- "- `title` (required, string): Action title",
133
- "- `detail` (optional, string): Optional detail",
134
- '- Response: `{"ok": true}` on success.',
135
- "",
136
- "**`POST http://localhost:7256/mock-query?taskId=test-id`** — Query mock data from the device.",
137
- "```json",
138
- '{"tags":["..."]}',
139
- "```",
140
- "- `tags` (optional, string array): Filter tags",
141
- "- Blocks until the device responds.",
142
- '- Response: `{"data": ...}` on success.',
143
- "",
144
- "**`GET http://localhost:7256/mock-data?taskId=test-id`** — Get mock data from the device.",
145
- "- Response: JSON array of data objects.",
146
- ].join("\n");
147
- assert.equal(docs, expected);
148
- });
149
-
150
- it("generates docs for all provided tools", () => {
151
- for (const tool of mockTools) {
152
- assert.match(docs, new RegExp(`POST http://localhost:7256/${tool.name}\\?taskId=`), `Missing endpoint for ${tool.name}`);
153
- }
154
- });
155
-
156
- it("includes taskId parameter for every endpoint", () => {
157
- const endpointBlocks = docs.split("**`POST");
158
- // First element is the header, skip it
159
- for (let i = 1; i < endpointBlocks.length; i++) {
160
- assert.match(endpointBlocks[i], /taskId/, `Missing taskId in endpoint block ${i}`);
161
- }
162
- });
163
-
164
- it("includes port in the header", () => {
165
- assert.match(docs, /localhost:7256/);
166
- });
167
-
168
- it("includes task ID in query parameters", () => {
169
- assert.match(docs, /taskId=test-id/);
170
- });
171
-
172
- it("includes response descriptions", () => {
173
- for (const tool of mockTools) {
174
- if (tool.description.length > 1) {
175
- assert.match(docs, /Response:/, `Missing response description for ${tool.name}`);
176
- }
177
- }
178
- });
179
-
180
- it("marks required and optional parameters correctly", () => {
181
- assert.match(docs, /\(required, string\)/);
182
- // "detail" has no required entry, so it should be optional
183
- assert.match(docs, /\(optional, string\)/);
184
- });
185
-
186
- it("handles array-type parameters", () => {
187
- assert.match(docs, /\(optional, string array\)/);
188
- assert.match(docs, /Filter tags/);
189
- });
190
-
191
- it("renders multi-line descriptions as bullet points", () => {
192
- assert.match(docs, /- Blocks until the device responds\./);
193
- });
194
-
195
- it("generates GET endpoints for all provided resources", () => {
196
- for (const resource of mockResources) {
197
- assert.match(docs, new RegExp(`GET http://localhost:7256${resource.restPath}`), `Missing endpoint for ${resource.uri}`);
198
- }
199
- });
200
-
201
- it("includes resource description as bullet points", () => {
202
- assert.match(docs, /- Response: JSON array of data objects\./);
203
- });
204
-
205
- it("generates no resource endpoints when resources array is empty", () => {
206
- const docsNoResources = generateEndpointDocs(7256, "test-id", mockTools, []);
207
- assert.doesNotMatch(docsNoResources, /GET http/);
208
- });
209
- });
@@ -1,74 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { parseTaskOutcome, parseReportFiles, parsePermissions } from "../src/commands/run.js";
4
-
5
- describe("parseTaskOutcome", () => {
6
- it("returns 'finished' for success marker", () => {
7
- assert.equal(parseTaskOutcome("some output\n[PALMIER_TASK_SUCCESS]"), "finished");
8
- });
9
-
10
- it("returns 'failed' for failure marker", () => {
11
- assert.equal(parseTaskOutcome("some output\n[PALMIER_TASK_FAILURE]"), "failed");
12
- });
13
-
14
- it("returns 'finished' when no marker is present", () => {
15
- assert.equal(parseTaskOutcome("just some regular output"), "finished");
16
- });
17
-
18
- it("returns 'failed' when both markers present (failure takes priority)", () => {
19
- assert.equal(parseTaskOutcome("[PALMIER_TASK_SUCCESS]\n[PALMIER_TASK_FAILURE]"), "failed");
20
- });
21
-
22
- it("only looks at last 500 chars", () => {
23
- const padding = "x".repeat(600);
24
- assert.equal(parseTaskOutcome("[PALMIER_TASK_FAILURE]" + padding), "finished");
25
- });
26
- });
27
-
28
- describe("parseReportFiles", () => {
29
- it("extracts report file names", () => {
30
- const output = "doing work\n[PALMIER_REPORT] report.md\nmore work\n[PALMIER_REPORT] summary.md";
31
- assert.deepEqual(parseReportFiles(output), ["report.md", "summary.md"]);
32
- });
33
-
34
- it("returns empty array when no reports", () => {
35
- assert.deepEqual(parseReportFiles("no reports here"), []);
36
- });
37
-
38
- it("trims whitespace from file names", () => {
39
- assert.deepEqual(parseReportFiles("[PALMIER_REPORT] report.md "), ["report.md"]);
40
- });
41
-
42
- it("ignores placeholder examples from echoed prompt", () => {
43
- const output = "[PALMIER_REPORT] <filename>\n[PALMIER_REPORT] actual-report.md";
44
- assert.deepEqual(parseReportFiles(output), ["actual-report.md"]);
45
- });
46
- });
47
-
48
- describe("parsePermissions", () => {
49
- it("extracts permissions with name and description", () => {
50
- const output = "[PALMIER_PERMISSION] Read | Read file contents\n[PALMIER_PERMISSION] Bash(npm test) | Run tests";
51
- const perms = parsePermissions(output);
52
- assert.equal(perms.length, 2);
53
- assert.deepEqual(perms[0], { name: "Read", description: "Read file contents" });
54
- assert.deepEqual(perms[1], { name: "Bash(npm test)", description: "Run tests" });
55
- });
56
-
57
- it("handles permission without description", () => {
58
- const perms = parsePermissions("[PALMIER_PERMISSION] Write");
59
- assert.deepEqual(perms, [{ name: "Write", description: "" }]);
60
- });
61
-
62
- it("returns empty array when no permissions", () => {
63
- assert.deepEqual(parsePermissions("no permissions"), []);
64
- });
65
-
66
- it("ignores placeholder examples from echoed prompt", () => {
67
- const output = "[PALMIER_PERMISSION] <tool_name> | <description>\n[PALMIER_PERMISSION] Read | Read files";
68
- const perms = parsePermissions(output);
69
- assert.equal(perms.length, 1);
70
- assert.deepEqual(perms[0], { name: "Read", description: "Read files" });
71
- });
72
- });
73
-
74
-
@@ -1,41 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { cronToOnCalendar } from "../src/platform/linux.js";
4
-
5
- describe("cronToOnCalendar", () => {
6
- it("converts hourly cron", () => {
7
- assert.equal(cronToOnCalendar("0 * * * *"), "*-*-* *:00:00");
8
- });
9
-
10
- it("converts daily cron", () => {
11
- assert.equal(cronToOnCalendar("30 9 * * *"), "*-*-* 09:30:00");
12
- });
13
-
14
- it("converts weekly Monday cron", () => {
15
- assert.equal(cronToOnCalendar("0 10 * * 1"), "Mon *-*-* 10:00:00");
16
- });
17
-
18
- it("converts weekly Sunday (day 0)", () => {
19
- assert.equal(cronToOnCalendar("0 8 * * 0"), "Sun *-*-* 08:00:00");
20
- });
21
-
22
- it("converts weekly Sunday (day 7)", () => {
23
- assert.equal(cronToOnCalendar("0 8 * * 7"), "Sun *-*-* 08:00:00");
24
- });
25
-
26
- it("converts monthly cron", () => {
27
- assert.equal(cronToOnCalendar("0 14 15 * *"), "*-*-15 14:00:00");
28
- });
29
-
30
- it("pads single-digit hours and minutes", () => {
31
- assert.equal(cronToOnCalendar("5 3 * * *"), "*-*-* 03:05:00");
32
- });
33
-
34
- it("throws on invalid cron expression", () => {
35
- assert.throws(() => cronToOnCalendar("bad"), /Invalid cron/);
36
- });
37
-
38
- it("throws on too few fields", () => {
39
- assert.throws(() => cronToOnCalendar("0 * *"), /Invalid cron/);
40
- });
41
- });
@@ -1,112 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import {
4
- cronToCalendarInterval,
5
- specificTimeToCalendarInterval,
6
- buildPlist,
7
- } from "../src/platform/macos.js";
8
-
9
- describe("cronToCalendarInterval", () => {
10
- it("converts hourly cron", () => {
11
- assert.deepEqual(cronToCalendarInterval("0 * * * *"), { Minute: 0 });
12
- });
13
-
14
- it("converts daily cron", () => {
15
- assert.deepEqual(cronToCalendarInterval("30 9 * * *"), { Minute: 30, Hour: 9 });
16
- });
17
-
18
- it("converts weekly Monday cron", () => {
19
- assert.deepEqual(cronToCalendarInterval("0 10 * * 1"), { Minute: 0, Hour: 10, Weekday: 1 });
20
- });
21
-
22
- it("converts weekly Sunday (day 0)", () => {
23
- assert.deepEqual(cronToCalendarInterval("0 8 * * 0"), { Minute: 0, Hour: 8, Weekday: 0 });
24
- });
25
-
26
- it("converts weekly Sunday (day 7 -> 0)", () => {
27
- assert.deepEqual(cronToCalendarInterval("0 8 * * 7"), { Minute: 0, Hour: 8, Weekday: 0 });
28
- });
29
-
30
- it("converts monthly cron", () => {
31
- assert.deepEqual(cronToCalendarInterval("0 14 15 * *"), { Minute: 0, Hour: 14, Day: 15 });
32
- });
33
-
34
- it("throws on invalid cron expression", () => {
35
- assert.throws(() => cronToCalendarInterval("bad"), /Invalid cron/);
36
- });
37
-
38
- it("throws on too few fields", () => {
39
- assert.throws(() => cronToCalendarInterval("0 * *"), /Invalid cron/);
40
- });
41
- });
42
-
43
- describe("specificTimeToCalendarInterval", () => {
44
- it("parses an ISO local datetime", () => {
45
- assert.deepEqual(
46
- specificTimeToCalendarInterval("2026-04-20T09:00"),
47
- { Month: 4, Day: 20, Hour: 9, Minute: 0 },
48
- );
49
- });
50
-
51
- it("parses an ISO datetime with seconds", () => {
52
- assert.deepEqual(
53
- specificTimeToCalendarInterval("2026-12-31T23:59:30"),
54
- { Month: 12, Day: 31, Hour: 23, Minute: 59 },
55
- );
56
- });
57
-
58
- it("throws on malformed input", () => {
59
- assert.throws(() => specificTimeToCalendarInterval("not-a-date"), /Invalid specific_times/);
60
- });
61
- });
62
-
63
- describe("buildPlist", () => {
64
- it("emits a valid plist envelope with ProgramArguments", () => {
65
- const xml = buildPlist({
66
- Label: "me.palmier.host",
67
- ProgramArguments: ["/usr/local/bin/node", "/opt/palmier/dist/index.js", "serve"],
68
- RunAtLoad: true,
69
- });
70
-
71
- assert.ok(xml.startsWith(`<?xml version="1.0" encoding="UTF-8"?>`), "xml header");
72
- assert.ok(xml.includes(`<!DOCTYPE plist PUBLIC`), "doctype");
73
- assert.ok(xml.includes(`<plist version="1.0">`), "plist tag");
74
- assert.ok(xml.includes(`<key>Label</key>`), "label key");
75
- assert.ok(xml.includes(`<string>me.palmier.host</string>`), "label value");
76
- assert.ok(xml.includes(`<key>ProgramArguments</key>`), "program args key");
77
- assert.ok(xml.includes(`<string>/usr/local/bin/node</string>`), "program args first");
78
- assert.ok(xml.includes(`<true/>`), "boolean");
79
- });
80
-
81
- it("serializes StartCalendarInterval as an array of dicts", () => {
82
- const xml = buildPlist({
83
- Label: "me.palmier.task.abc",
84
- StartCalendarInterval: [
85
- { Minute: 0, Hour: 9 },
86
- { Minute: 30, Hour: 14, Weekday: 1 },
87
- ],
88
- });
89
-
90
- assert.ok(xml.includes(`<key>StartCalendarInterval</key>`));
91
- assert.ok(xml.includes(`<array>`));
92
- assert.ok(xml.includes(`<key>Minute</key>`));
93
- assert.ok(xml.includes(`<integer>0</integer>`));
94
- assert.ok(xml.includes(`<integer>9</integer>`));
95
- assert.ok(xml.includes(`<key>Weekday</key>`));
96
- assert.ok(xml.includes(`<integer>1</integer>`));
97
- });
98
-
99
- it("nests dicts (EnvironmentVariables.PATH)", () => {
100
- const xml = buildPlist({
101
- EnvironmentVariables: { PATH: "/usr/local/bin:/usr/bin:/bin" },
102
- });
103
- assert.ok(xml.includes(`<key>EnvironmentVariables</key>`));
104
- assert.ok(xml.includes(`<key>PATH</key>`));
105
- assert.ok(xml.includes(`<string>/usr/local/bin:/usr/bin:/bin</string>`));
106
- });
107
-
108
- it("escapes XML special characters in strings", () => {
109
- const xml = buildPlist({ Label: "a & b <c>" });
110
- assert.ok(xml.includes(`<string>a &amp; b &lt;c&gt;</string>`));
111
- });
112
- });
@@ -1,57 +0,0 @@
1
- import { describe, it, beforeEach } from "node:test";
2
- import assert from "node:assert/strict";
3
-
4
- // Re-import fresh module state for each test file run
5
- // Since the store is module-level state, we test the exported functions directly
6
- import { addNotification, getNotifications, onNotificationsChanged, type DeviceNotification } from "../src/notification-store.js";
7
-
8
- function makeNotification(id: string, overrides?: Partial<DeviceNotification>): DeviceNotification {
9
- return {
10
- id,
11
- packageName: "com.example.app",
12
- appName: "Example",
13
- title: `Title ${id}`,
14
- text: `Text ${id}`,
15
- timestamp: Date.now(),
16
- receivedAt: Date.now(),
17
- ...overrides,
18
- };
19
- }
20
-
21
- describe("notification-store", () => {
22
- it("stores and retrieves notifications", () => {
23
- const before = getNotifications().length;
24
- addNotification(makeNotification("test-1"));
25
- const after = getNotifications();
26
- assert.equal(after.length, before + 1);
27
- assert.equal(after[after.length - 1].id, "test-1");
28
- });
29
-
30
- it("returns a defensive copy", () => {
31
- const a = getNotifications();
32
- const b = getNotifications();
33
- assert.notStrictEqual(a, b);
34
- });
35
-
36
- it("evicts oldest when exceeding max", () => {
37
- const before = getNotifications().length;
38
- // Add enough to exceed 50
39
- for (let i = 0; i < 60; i++) {
40
- addNotification(makeNotification(`evict-${i}`));
41
- }
42
- const result = getNotifications();
43
- assert.ok(result.length <= 50, `Expected <= 50, got ${result.length}`);
44
- });
45
-
46
- it("notifies listeners on add", () => {
47
- let called = 0;
48
- const unsub = onNotificationsChanged(() => { called++; });
49
- addNotification(makeNotification("listener-1"));
50
- assert.equal(called, 1);
51
- addNotification(makeNotification("listener-2"));
52
- assert.equal(called, 2);
53
- unsub();
54
- addNotification(makeNotification("listener-3"));
55
- assert.equal(called, 2); // no longer called after unsubscribe
56
- });
57
- });
@@ -1,35 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { generatePairingCode, PAIRING_EXPIRY_MS } from "../src/commands/pair.js";
4
-
5
- describe("generatePairingCode", () => {
6
- it("generates a 6-character code", () => {
7
- const code = generatePairingCode();
8
- assert.equal(code.length, 6);
9
- });
10
-
11
- it("only contains allowed characters (no O/0/I/1/L)", () => {
12
- const allowed = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
13
- for (let i = 0; i < 50; i++) {
14
- const code = generatePairingCode();
15
- for (const ch of code) {
16
- assert.ok(allowed.includes(ch), `Character '${ch}' is not in allowed set`);
17
- }
18
- }
19
- });
20
-
21
- it("generates unique codes", () => {
22
- const codes = new Set<string>();
23
- for (let i = 0; i < 100; i++) {
24
- codes.add(generatePairingCode());
25
- }
26
- // With 30^6 ≈ 729M possibilities, 100 codes should all be unique
27
- assert.equal(codes.size, 100);
28
- });
29
- });
30
-
31
- describe("PAIRING_EXPIRY_MS", () => {
32
- it("is 1 minute", () => {
33
- assert.equal(PAIRING_EXPIRY_MS, 60 * 1000);
34
- });
35
- });
@@ -1,110 +0,0 @@
1
- import { describe, it, beforeEach } from "node:test";
2
- import assert from "node:assert/strict";
3
- import * as fs from "fs";
4
- import * as os from "os";
5
- import * as path from "path";
6
- import {
7
- createRunDir,
8
- appendRunMessage,
9
- beginStreamingMessage,
10
- } from "../src/task.js";
11
- import { parseResultFrontmatter } from "../src/rpc-handler.js";
12
-
13
- let taskDir: string;
14
- let runId: string;
15
-
16
- function setup() {
17
- taskDir = fs.mkdtempSync(path.join(os.tmpdir(), "palmier-test-"));
18
- runId = createRunDir(taskDir, "Test Task", 1000, "claude");
19
- }
20
-
21
- function readRaw(): string {
22
- return fs.readFileSync(path.join(taskDir, runId, "TASKRUN.md"), "utf-8");
23
- }
24
-
25
- describe("parseResultFrontmatter — monitoring state", () => {
26
- beforeEach(setup);
27
-
28
- it("returns 'monitoring' when monitoring is the last message", () => {
29
- appendRunMessage(taskDir, runId, { role: "status", time: 1000, content: "", type: "started" });
30
- appendRunMessage(taskDir, runId, { role: "status", time: 1001, content: "", type: "monitoring" });
31
-
32
- const result = parseResultFrontmatter(readRaw());
33
- assert.equal(result.running_state, "monitoring");
34
- });
35
-
36
- it("returns 'started' when an assistant message follows monitoring", () => {
37
- appendRunMessage(taskDir, runId, { role: "status", time: 1000, content: "", type: "started" });
38
- appendRunMessage(taskDir, runId, { role: "status", time: 1001, content: "", type: "monitoring" });
39
- const writer = beginStreamingMessage(taskDir, runId, 1002);
40
- writer.write("Working on it...");
41
- writer.end();
42
-
43
- const result = parseResultFrontmatter(readRaw());
44
- assert.equal(result.running_state, "started");
45
- });
46
-
47
- it("returns 'monitoring' after agent finishes and monitoring resumes", () => {
48
- appendRunMessage(taskDir, runId, { role: "status", time: 1000, content: "", type: "started" });
49
- appendRunMessage(taskDir, runId, { role: "status", time: 1001, content: "", type: "monitoring" });
50
- // Agent processes a line
51
- const writer = beginStreamingMessage(taskDir, runId, 1002);
52
- writer.write("Done processing line.");
53
- writer.end();
54
- // Back to monitoring
55
- appendRunMessage(taskDir, runId, { role: "status", time: 1003, content: "", type: "monitoring" });
56
-
57
- const result = parseResultFrontmatter(readRaw());
58
- assert.equal(result.running_state, "monitoring");
59
- });
60
-
61
- it("returns 'started' when a user message follows monitoring", () => {
62
- appendRunMessage(taskDir, runId, { role: "status", time: 1000, content: "", type: "started" });
63
- appendRunMessage(taskDir, runId, { role: "status", time: 1001, content: "", type: "monitoring" });
64
- appendRunMessage(taskDir, runId, { role: "user", time: 1002, content: "some input" });
65
-
66
- const result = parseResultFrontmatter(readRaw());
67
- assert.equal(result.running_state, "started");
68
- });
69
- });
70
-
71
- describe("parseResultFrontmatter — standard states", () => {
72
- beforeEach(setup);
73
-
74
- it("returns 'started' for a running task", () => {
75
- appendRunMessage(taskDir, runId, { role: "status", time: 1000, content: "", type: "started" });
76
- appendRunMessage(taskDir, runId, { role: "user", time: 1001, content: "Do something" });
77
-
78
- const result = parseResultFrontmatter(readRaw());
79
- assert.equal(result.running_state, "started");
80
- });
81
-
82
- it("returns 'finished' for a completed task", () => {
83
- appendRunMessage(taskDir, runId, { role: "status", time: 1000, content: "", type: "started" });
84
- appendRunMessage(taskDir, runId, { role: "user", time: 1001, content: "Do something" });
85
- const writer = beginStreamingMessage(taskDir, runId, 1002);
86
- writer.write("Done.");
87
- writer.end();
88
- appendRunMessage(taskDir, runId, { role: "status", time: 1003, content: "", type: "finished" });
89
-
90
- const result = parseResultFrontmatter(readRaw());
91
- assert.equal(result.running_state, "finished");
92
- });
93
-
94
- it("returns 'failed' for a failed task", () => {
95
- appendRunMessage(taskDir, runId, { role: "status", time: 1000, content: "", type: "started" });
96
- appendRunMessage(taskDir, runId, { role: "status", time: 1001, content: "", type: "failed" });
97
-
98
- const result = parseResultFrontmatter(readRaw());
99
- assert.equal(result.running_state, "failed");
100
- });
101
-
102
- it("returns 'followup' when started again after terminal state", () => {
103
- appendRunMessage(taskDir, runId, { role: "status", time: 1000, content: "", type: "started" });
104
- appendRunMessage(taskDir, runId, { role: "status", time: 1001, content: "", type: "finished" });
105
- appendRunMessage(taskDir, runId, { role: "status", time: 1002, content: "", type: "started" });
106
-
107
- const result = parseResultFrontmatter(readRaw());
108
- assert.equal(result.running_state, "followup");
109
- });
110
- });