palmier 0.8.0 → 0.8.3

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 (132) hide show
  1. package/CLAUDE.md +13 -0
  2. package/README.md +11 -11
  3. package/dist/agents/agent.d.ts +0 -4
  4. package/dist/agents/claude.js +1 -1
  5. package/dist/agents/codex.js +2 -2
  6. package/dist/agents/cursor.js +1 -1
  7. package/dist/agents/deepagents.js +1 -1
  8. package/dist/agents/gemini.js +3 -2
  9. package/dist/agents/goose.js +1 -1
  10. package/dist/agents/hermes.js +1 -1
  11. package/dist/agents/kiro.js +1 -1
  12. package/dist/agents/opencode.js +1 -1
  13. package/dist/agents/qoder.js +1 -1
  14. package/dist/agents/shared-prompt.d.ts +0 -3
  15. package/dist/agents/shared-prompt.js +0 -3
  16. package/dist/app-registry.d.ts +10 -0
  17. package/dist/app-registry.js +44 -0
  18. package/dist/commands/info.d.ts +0 -3
  19. package/dist/commands/info.js +0 -5
  20. package/dist/commands/init.d.ts +0 -3
  21. package/dist/commands/init.js +2 -11
  22. package/dist/commands/pair.d.ts +1 -4
  23. package/dist/commands/pair.js +1 -12
  24. package/dist/commands/restart.d.ts +0 -3
  25. package/dist/commands/restart.js +0 -3
  26. package/dist/commands/run.d.ts +1 -14
  27. package/dist/commands/run.js +18 -61
  28. package/dist/commands/serve.d.ts +0 -3
  29. package/dist/commands/serve.js +33 -27
  30. package/dist/config.d.ts +0 -8
  31. package/dist/config.js +0 -8
  32. package/dist/device-capabilities.d.ts +1 -1
  33. package/dist/event-queues.d.ts +6 -21
  34. package/dist/event-queues.js +6 -21
  35. package/dist/events.d.ts +0 -6
  36. package/dist/events.js +1 -9
  37. package/dist/index.js +0 -1
  38. package/dist/mcp-handler.js +1 -2
  39. package/dist/mcp-tools.d.ts +0 -3
  40. package/dist/mcp-tools.js +14 -18
  41. package/dist/nats-client.d.ts +0 -3
  42. package/dist/nats-client.js +1 -4
  43. package/dist/pending-requests.d.ts +4 -18
  44. package/dist/pending-requests.js +4 -18
  45. package/dist/platform/index.d.ts +1 -4
  46. package/dist/platform/index.js +1 -4
  47. package/dist/platform/linux.d.ts +3 -9
  48. package/dist/platform/linux.js +9 -20
  49. package/dist/platform/platform.d.ts +1 -4
  50. package/dist/platform/windows.d.ts +2 -5
  51. package/dist/platform/windows.js +19 -39
  52. package/dist/pwa/assets/index-B0F9mtid.css +1 -0
  53. package/dist/pwa/assets/index-SYs3mcdJ.js +120 -0
  54. package/dist/pwa/assets/{web-CF-N8Di6.js → web-C6lkQj9J.js} +1 -1
  55. package/dist/pwa/assets/{web-BpM3fNCn.js → web-Z1623me-.js} +1 -1
  56. package/dist/pwa/index.html +2 -2
  57. package/dist/pwa/service-worker.js +1 -1
  58. package/dist/rpc-handler.d.ts +0 -6
  59. package/dist/rpc-handler.js +19 -48
  60. package/dist/spawn-command.d.ts +10 -25
  61. package/dist/spawn-command.js +7 -15
  62. package/dist/task.d.ts +6 -64
  63. package/dist/task.js +7 -70
  64. package/dist/transports/http-transport.d.ts +0 -4
  65. package/dist/transports/http-transport.js +6 -28
  66. package/dist/transports/nats-transport.d.ts +0 -4
  67. package/dist/transports/nats-transport.js +3 -9
  68. package/dist/types.d.ts +3 -7
  69. package/dist/update-checker.d.ts +1 -4
  70. package/dist/update-checker.js +2 -5
  71. package/package.json +1 -1
  72. package/palmier-server/README.md +1 -1
  73. package/palmier-server/pwa/src/App.css +170 -20
  74. package/palmier-server/pwa/src/App.tsx +15 -1
  75. package/palmier-server/pwa/src/components/HostMenu.tsx +282 -473
  76. package/palmier-server/pwa/src/components/RunDetailView.tsx +3 -3
  77. package/palmier-server/pwa/src/components/SessionsView.tsx +57 -25
  78. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +160 -0
  79. package/palmier-server/pwa/src/components/TaskCard.tsx +12 -4
  80. package/palmier-server/pwa/src/components/TaskForm.tsx +230 -33
  81. package/palmier-server/pwa/src/components/TasksView.tsx +5 -0
  82. package/palmier-server/pwa/src/constants.ts +1 -1
  83. package/palmier-server/pwa/src/native/Device.ts +66 -0
  84. package/palmier-server/pwa/src/pages/Dashboard.tsx +11 -6
  85. package/palmier-server/pwa/src/pages/PairHost.tsx +18 -2
  86. package/palmier-server/pwa/src/types.ts +1 -1
  87. package/palmier-server/server/src/index.ts +7 -7
  88. package/palmier-server/server/src/routes/device.ts +4 -4
  89. package/palmier-server/spec.md +47 -6
  90. package/src/agents/agent.ts +0 -4
  91. package/src/agents/claude.ts +1 -1
  92. package/src/agents/codex.ts +2 -2
  93. package/src/agents/cursor.ts +1 -1
  94. package/src/agents/deepagents.ts +1 -1
  95. package/src/agents/gemini.ts +3 -2
  96. package/src/agents/goose.ts +1 -1
  97. package/src/agents/hermes.ts +1 -1
  98. package/src/agents/kiro.ts +1 -1
  99. package/src/agents/opencode.ts +1 -1
  100. package/src/agents/qoder.ts +1 -1
  101. package/src/agents/shared-prompt.ts +0 -3
  102. package/src/app-registry.ts +52 -0
  103. package/src/commands/info.ts +0 -5
  104. package/src/commands/init.ts +2 -11
  105. package/src/commands/pair.ts +1 -12
  106. package/src/commands/restart.ts +0 -3
  107. package/src/commands/run.ts +18 -65
  108. package/src/commands/serve.ts +31 -27
  109. package/src/config.ts +0 -8
  110. package/src/device-capabilities.ts +4 -3
  111. package/src/event-queues.ts +6 -21
  112. package/src/events.ts +1 -9
  113. package/src/index.ts +0 -1
  114. package/src/mcp-handler.ts +1 -2
  115. package/src/mcp-tools.ts +14 -20
  116. package/src/nats-client.ts +1 -4
  117. package/src/pending-requests.ts +4 -18
  118. package/src/platform/index.ts +1 -4
  119. package/src/platform/linux.ts +9 -20
  120. package/src/platform/platform.ts +1 -4
  121. package/src/platform/windows.ts +19 -40
  122. package/src/rpc-handler.ts +20 -48
  123. package/src/spawn-command.ts +11 -27
  124. package/src/task.ts +7 -70
  125. package/src/transports/http-transport.ts +6 -39
  126. package/src/transports/nats-transport.ts +3 -9
  127. package/src/types.ts +3 -10
  128. package/src/update-checker.ts +2 -5
  129. package/test/task-parsing.test.ts +2 -3
  130. package/test/windows-xml.test.ts +11 -12
  131. package/dist/pwa/assets/index-FP1Mipr6.js +0 -120
  132. package/dist/pwa/assets/index-bLTn8zBj.css +0 -1
@@ -11,8 +11,6 @@ import { handleMcpRequest, getAgentName, getResourceSubscriptions } from "../mcp
11
11
  import { getTaskDir } from "../task.js";
12
12
  import { popEvent } from "../event-queues.js";
13
13
 
14
- // ── Bundled PWA asset serving ───────────────────────────────────────────
15
-
16
14
  interface CachedAsset {
17
15
  data: Buffer;
18
16
  contentType: string;
@@ -41,22 +39,18 @@ function guessContentType(urlPath: string): string {
41
39
  return CONTENT_TYPES[ext] ?? "application/octet-stream";
42
40
  }
43
41
 
44
- /**
45
- * Read a PWA asset from the bundled pwa/ directory, caching in memory.
46
- * Returns null if the file does not exist.
47
- */
48
42
  function getAsset(urlPath: string): CachedAsset | null {
49
43
  const cached = assetCache.get(urlPath);
50
44
  if (cached) return cached;
51
45
 
52
46
  const filePath = path.join(PWA_DIR, urlPath === "/" ? "index.html" : urlPath);
53
47
 
54
- // Prevent path traversal
48
+ // Prevent path traversal.
55
49
  if (!filePath.startsWith(PWA_DIR)) return null;
56
50
 
57
51
  try {
58
52
  let data = fs.readFileSync(filePath);
59
- // Inject marker into index HTML so the PWA can detect it's served by palmier
53
+ // Marker lets the PWA detect it's served by palmier.
60
54
  if (urlPath === "/") {
61
55
  const html = data.toString("utf-8").replace("</head>", "<script>window.__PALMIER_SERVE__=true</script></head>");
62
56
  data = Buffer.from(html, "utf-8");
@@ -90,10 +84,6 @@ export function detectLanIp(): string {
90
84
  return "127.0.0.1";
91
85
  }
92
86
 
93
- /**
94
- * Start the HTTP transport: server with RPC, SSE, PWA proxy, pairing, and
95
- * localhost-only agent endpoints (notify, request-input, confirmation, permission).
96
- */
97
87
  export async function startHttpTransport(
98
88
  config: HostConfig,
99
89
  handleRpc: (req: RpcMessage) => Promise<unknown>,
@@ -126,7 +116,6 @@ export async function startHttpTransport(
126
116
  resource.subscribe(() => broadcastResourceUpdated(resource.uri));
127
117
  }
128
118
 
129
- // If a pairing code is provided, pre-register it
130
119
  if (pairingCode) {
131
120
  const EXPIRY_MS = 24 * 60 * 60 * 1000;
132
121
  const timer = setTimeout(() => { pendingPairs.delete(pairingCode); }, EXPIRY_MS);
@@ -171,9 +160,6 @@ export async function startHttpTransport(
171
160
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
172
161
  }
173
162
 
174
- /**
175
- * Publish an event via NATS and SSE.
176
- */
177
163
  async function publishEvent(taskId: string, payload: Record<string, unknown>): Promise<void> {
178
164
  const sc = StringCodec();
179
165
  const subject = `host-event.${config.hostId}.${taskId}`;
@@ -191,8 +177,6 @@ export async function startHttpTransport(
191
177
  const url = new URL(req.url ?? "/", `http://localhost:${port}`);
192
178
  const pathname = url.pathname;
193
179
 
194
- // ── MCP streamable HTTP endpoint ──────────────────────────────────
195
-
196
180
  if (req.method === "POST" && pathname === "/mcp") {
197
181
  if (!isLocalhost(req)) { sendJson(res, 403, { error: "localhost only" }); return; }
198
182
  try {
@@ -204,7 +188,7 @@ export async function startHttpTransport(
204
188
  res.setHeader("Mcp-Session-Id", result.sessionId);
205
189
  }
206
190
  if (result.stream && sessionId) {
207
- // Keep response open as SSE stream for server-initiated notifications
191
+ // Keep the response open as SSE for server-initiated notifications.
208
192
  res.writeHead(200, {
209
193
  "Content-Type": "text/event-stream",
210
194
  "Cache-Control": "no-cache",
@@ -227,8 +211,6 @@ export async function startHttpTransport(
227
211
  return;
228
212
  }
229
213
 
230
- // ── Auto-generated REST endpoints from MCP tool registry ──────────
231
-
232
214
  if (req.method === "POST" && agentToolMap.has(pathname.slice(1))) {
233
215
  if (!isLocalhost(req)) { sendJson(res, 403, { error: "localhost only" }); return; }
234
216
  const tool = agentToolMap.get(pathname.slice(1))!;
@@ -258,8 +240,6 @@ export async function startHttpTransport(
258
240
  return;
259
241
  }
260
242
 
261
- // ── Auto-generated REST endpoints from MCP resource registry ────
262
-
263
243
  const matchedResource = req.method === "GET" && agentResources.find((r) => r.restPath === pathname);
264
244
  if (matchedResource) {
265
245
  if (!isLocalhost(req)) { sendJson(res, 403, { error: "localhost only" }); return; }
@@ -280,8 +260,6 @@ export async function startHttpTransport(
280
260
  return;
281
261
  }
282
262
 
283
- // ── Event queue pop (used by event-triggered palmier run) ─────────
284
-
285
263
  if (req.method === "POST" && pathname === "/task-event/pop") {
286
264
  if (!isLocalhost(req)) { sendJson(res, 403, { error: "localhost only" }); return; }
287
265
  const taskId = url.searchParams.get("taskId");
@@ -293,8 +271,6 @@ export async function startHttpTransport(
293
271
  return;
294
272
  }
295
273
 
296
- // ── Localhost-only endpoints (no auth) ─────────────────────────────
297
-
298
274
  if (req.method === "POST" && pathname === "/event") {
299
275
  if (!isLocalhost(req)) { sendJson(res, 403, { error: "localhost only" }); return; }
300
276
  try {
@@ -334,8 +310,6 @@ export async function startHttpTransport(
334
310
  return;
335
311
  }
336
312
 
337
- // ── POST /request-permission — held connection ──────────────────────
338
-
339
313
  if (req.method === "POST" && pathname === "/request-permission") {
340
314
  if (!isLocalhost(req)) { sendJson(res, 403, { error: "localhost only" }); return; }
341
315
  try {
@@ -376,8 +350,6 @@ export async function startHttpTransport(
376
350
  return;
377
351
  }
378
352
 
379
- // ── Public pair endpoint — no auth, PWA posts pairing code here ────────
380
-
381
353
  if (req.method === "POST" && pathname === "/pair") {
382
354
  try {
383
355
  const body = await readBody(req);
@@ -404,16 +376,14 @@ export async function startHttpTransport(
404
376
  return;
405
377
  }
406
378
 
407
- // ── PWA assets (on-the-fly, cached) ────────────────────────────────
408
-
409
- // Skip service worker and manifest — they require HTTPS which LAN mode doesn't use
379
+ // Service worker and manifest require HTTPS, which LAN mode doesn't use.
410
380
  const SKIP = new Set(["/registerSW.js", "/service-worker.js", "/manifest.webmanifest"]);
411
381
 
412
382
  const isApiRoute = pathname === "/events" || pathname.startsWith("/rpc/");
413
383
  if (!isApiRoute) {
414
384
  if (SKIP.has(pathname)) { sendJson(res, 404, { error: "Not found" }); return; }
415
385
 
416
- // Try exact path, then fall back to index.html (SPA routing)
386
+ // Fall back to index.html for SPA routing.
417
387
  let asset = getAsset(pathname);
418
388
  if (!asset && pathname !== "/") {
419
389
  asset = getAsset("/");
@@ -428,14 +398,12 @@ export async function startHttpTransport(
428
398
  return;
429
399
  }
430
400
 
431
- // ── API endpoints require auth (localhost is trusted) ───────────────
432
-
401
+ // Localhost is trusted; all other API callers require a client token.
433
402
  if (!isLocalhost(req) && !checkAuth(req)) {
434
403
  sendJson(res, 401, { error: "Unauthorized" });
435
404
  return;
436
405
  }
437
406
 
438
- // SSE event stream
439
407
  if (req.method === "GET" && pathname === "/events") {
440
408
  res.writeHead(200, {
441
409
  "Content-Type": "text/event-stream",
@@ -456,7 +424,6 @@ export async function startHttpTransport(
456
424
  return;
457
425
  }
458
426
 
459
- // RPC endpoint: POST /rpc/<method>
460
427
  if (req.method === "POST" && pathname.startsWith("/rpc/")) {
461
428
  const method = pathname.slice("/rpc/".length);
462
429
  if (!method) { sendJson(res, 400, { error: "Missing RPC method" }); return; }
@@ -1,10 +1,6 @@
1
1
  import { StringCodec, type NatsConnection, type Msg, type Subscription } from "nats";
2
2
  import type { HostConfig, RpcMessage } from "../types.js";
3
3
 
4
- /**
5
- * Start the NATS transport using an existing connection.
6
- * Subscribe to RPC subjects and dispatch to handler.
7
- */
8
4
  export async function startNatsTransport(
9
5
  config: HostConfig,
10
6
  handleRpc: (req: RpcMessage) => Promise<unknown>,
@@ -16,7 +12,6 @@ export async function startNatsTransport(
16
12
  console.log(`[nats] Subscribing to: ${subject}`);
17
13
  const sub = nc.subscribe(subject);
18
14
 
19
- // Graceful shutdown
20
15
  const shutdown = async () => {
21
16
  console.log("[nats] Shutting down...");
22
17
  sub.unsubscribe();
@@ -28,12 +23,11 @@ export async function startNatsTransport(
28
23
  process.on("SIGTERM", shutdown);
29
24
 
30
25
  async function processMessage(msg: Msg) {
31
- // Derive RPC method from subject: ...rpc.<method parts>
26
+ // Subject format: ...rpc.<method parts>
32
27
  const subjectTokens = msg.subject.split(".");
33
28
  const rpcIdx = subjectTokens.indexOf("rpc");
34
29
  const method = rpcIdx >= 0 ? subjectTokens.slice(rpcIdx + 1).join(".") : "";
35
30
 
36
- // Parse params from message body
37
31
  let params: Record<string, unknown> = {};
38
32
  if (msg.data && msg.data.length > 0) {
39
33
  const raw = sc.decode(msg.data).trim();
@@ -50,7 +44,7 @@ export async function startNatsTransport(
50
44
  }
51
45
  }
52
46
 
53
- // Extract clientToken from params (PWA includes it in the payload)
47
+ // PWA includes the client token in the payload.
54
48
  const clientToken = typeof params.clientToken === "string" ? params.clientToken : undefined;
55
49
  delete params.clientToken;
56
50
 
@@ -72,7 +66,7 @@ export async function startNatsTransport(
72
66
 
73
67
  async function consumeSubscription(subscription: Subscription) {
74
68
  for await (const msg of subscription) {
75
- // Handle RPC without blocking the message loop so heartbeats keep flowing
69
+ // Don't await heartbeats must keep flowing while RPC runs.
76
70
  processMessage(msg);
77
71
  }
78
72
  }
package/src/types.ts CHANGED
@@ -7,12 +7,10 @@ export interface HostConfig {
7
7
  natsJwt?: string;
8
8
  natsNkeySeed?: string;
9
9
 
10
- // Detected agent CLIs
11
10
  agents?: Array<{ key: string; label: string; supportsPermissions: boolean; supportsYolo: boolean }>;
12
11
 
13
- // HTTP server port (default 7256)
14
12
  httpPort?: number;
15
- // Whether to accept non-localhost HTTP connections
13
+ /** Whether to accept non-localhost HTTP connections. */
16
14
  lanEnabled?: boolean;
17
15
  }
18
16
 
@@ -25,8 +23,8 @@ export interface TaskFrontmatter {
25
23
  * Task schedule.
26
24
  * - `crons`: `schedule_values` holds cron expressions (e.g. "0 9 * * *")
27
25
  * - `specific_times`: `schedule_values` holds local datetime strings (e.g. "2026-04-20T09:00")
28
- * - `on_new_notification`: fires on each new Android notification from NATS; no `schedule_values`
29
- * - `on_new_sms`: fires on each new SMS from NATS; no `schedule_values`
26
+ * - `on_new_notification`: fires on each new Android notification from NATS. Optional `schedule_values` holds a single-entry packageName filter; empty/unset matches any app.
27
+ * - `on_new_sms`: fires on each new SMS from NATS. Optional `schedule_values` holds a single-entry sender filter; compared after normalization (strip spaces/dashes/parens/plus, lowercase). Empty/unset matches any sender.
30
28
  */
31
29
  schedule_type?: "crons" | "specific_times" | "on_new_notification" | "on_new_sms";
32
30
  schedule_values?: string[];
@@ -50,11 +48,6 @@ export interface ParsedTask {
50
48
  */
51
49
  export type TaskRunningState = "started" | "finished" | "aborted" | "failed";
52
50
 
53
- /**
54
- * Persisted to `status.json` in the task directory. Used for crash detection
55
- * (checkStaleTasks) and abort signalling. Interactive request flows (confirmation,
56
- * permission, input) are handled via held HTTP connections on the serve daemon.
57
- */
58
51
  export interface TaskStatus {
59
52
  running_state: TaskRunningState;
60
53
  time_stamp: number;
@@ -12,10 +12,7 @@ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "
12
12
  export const isDevBuild = fs.existsSync(path.join(packageRoot, ".git"));
13
13
  export const currentVersion = isDevBuild ? `${pkg.version}-dev` : pkg.version;
14
14
 
15
- /**
16
- * Run the update and restart the daemon.
17
- * Returns an error message if the update fails.
18
- */
15
+ /** Returns an error message if the update fails. */
19
16
  export async function performUpdate(): Promise<string | null> {
20
17
  try {
21
18
  const { output, exitCode } = await spawnCommand("npm", ["update", "-g", "palmier"], {
@@ -28,7 +25,7 @@ export async function performUpdate(): Promise<string | null> {
28
25
  return `Update failed. Please run manually:\nnpm update -g palmier`;
29
26
  }
30
27
  console.log("[update] Update installed, restarting daemon...");
31
- // Small delay to allow the RPC response to be sent
28
+ // Delay so the RPC response finishes sending first.
32
29
  setTimeout(() => {
33
30
  getPlatform().restartDaemon().catch((err) => {
34
31
  console.error("[update] Restart failed:", err);
@@ -35,16 +35,15 @@ requires_confirmation: false
35
35
  assert.equal(result.frontmatter.agent, "claude");
36
36
  });
37
37
 
38
- it("defaults triggers_enabled to true", () => {
38
+ it("defaults schedule_enabled to true", () => {
39
39
  const content = `---
40
40
  id: abc123
41
41
  user_prompt: Do something
42
- triggers: []
43
42
  requires_confirmation: false
44
43
  ---`;
45
44
 
46
45
  const result = parseTaskContent(content);
47
- assert.equal(result.frontmatter.triggers_enabled, true);
46
+ assert.equal(result.frontmatter.schedule_enabled, true);
48
47
  });
49
48
 
50
49
  it("derives name from user_prompt when not specified", () => {
@@ -1,55 +1,54 @@
1
1
  import { describe, it } from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { triggerToXml, buildTaskXml } from "../src/platform/windows.js";
3
+ import { scheduleValueToXml, buildTaskXml } from "../src/platform/windows.js";
4
4
 
5
- describe("triggerToXml", () => {
6
- it("converts a once trigger to TimeTrigger", () => {
7
- const xml = triggerToXml({ type: "once", value: "2026-03-28T09:00" });
5
+ describe("scheduleValueToXml", () => {
6
+ it("converts a specific_times value to TimeTrigger", () => {
7
+ const xml = scheduleValueToXml("specific_times", "2026-03-28T09:00");
8
8
  assert.equal(xml, "<TimeTrigger><StartBoundary>2026-03-28T09:00:00</StartBoundary></TimeTrigger>");
9
9
  });
10
10
 
11
11
  it("converts hourly cron to TimeTrigger with PT1H repetition", () => {
12
- const xml = triggerToXml({ type: "cron", value: "0 * * * *" });
12
+ const xml = scheduleValueToXml("crons", "0 * * * *");
13
13
  assert.ok(xml.includes("<Interval>PT1H</Interval>"), "should have hourly interval");
14
14
  assert.ok(xml.includes("<TimeTrigger>"), "should be a TimeTrigger");
15
15
  });
16
16
 
17
17
  it("converts daily cron to CalendarTrigger with DaysInterval", () => {
18
- const xml = triggerToXml({ type: "cron", value: "30 9 * * *" });
18
+ const xml = scheduleValueToXml("crons", "30 9 * * *");
19
19
  assert.ok(xml.includes("<ScheduleByDay>"), "should use ScheduleByDay");
20
20
  assert.ok(xml.includes("<DaysInterval>1</DaysInterval>"), "should have interval 1");
21
21
  assert.ok(xml.includes("T09:30:00"), "should encode time as 09:30");
22
22
  });
23
23
 
24
24
  it("converts weekly cron to CalendarTrigger with DaysOfWeek", () => {
25
- const xml = triggerToXml({ type: "cron", value: "0 10 * * 1" });
25
+ const xml = scheduleValueToXml("crons", "0 10 * * 1");
26
26
  assert.ok(xml.includes("<ScheduleByWeek>"), "should use ScheduleByWeek");
27
27
  assert.ok(xml.includes("<Monday />"), "day 1 should be Monday");
28
28
  assert.ok(xml.includes("T10:00:00"), "should encode time as 10:00");
29
29
  });
30
30
 
31
31
  it("converts weekly cron for Sunday (day 0)", () => {
32
- const xml = triggerToXml({ type: "cron", value: "0 8 * * 0" });
32
+ const xml = scheduleValueToXml("crons", "0 8 * * 0");
33
33
  assert.ok(xml.includes("<Sunday />"), "day 0 should be Sunday");
34
34
  });
35
35
 
36
36
  it("converts weekly cron for Sunday (day 7)", () => {
37
- const xml = triggerToXml({ type: "cron", value: "0 8 * * 7" });
37
+ const xml = scheduleValueToXml("crons", "0 8 * * 7");
38
38
  assert.ok(xml.includes("<Sunday />"), "day 7 should also be Sunday");
39
39
  });
40
40
 
41
41
  it("converts monthly cron to CalendarTrigger with DaysOfMonth", () => {
42
- const xml = triggerToXml({ type: "cron", value: "0 14 15 * *" });
42
+ const xml = scheduleValueToXml("crons", "0 14 15 * *");
43
43
  assert.ok(xml.includes("<ScheduleByMonth>"), "should use ScheduleByMonth");
44
44
  assert.ok(xml.includes("<Day>15</Day>"), "should have day 15");
45
45
  assert.ok(xml.includes("T14:00:00"), "should encode time as 14:00");
46
- // All months should be listed
47
46
  assert.ok(xml.includes("<January />"), "should include January");
48
47
  assert.ok(xml.includes("<December />"), "should include December");
49
48
  });
50
49
 
51
50
  it("throws on invalid cron expression", () => {
52
- assert.throws(() => triggerToXml({ type: "cron", value: "bad" }), /Invalid cron/);
51
+ assert.throws(() => scheduleValueToXml("crons", "bad"), /Invalid cron/);
53
52
  });
54
53
  });
55
54