palmier 0.8.1 → 0.8.4
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.
- package/CLAUDE.md +13 -0
- package/README.md +16 -14
- package/dist/agents/agent.d.ts +0 -4
- package/dist/agents/claude.js +1 -1
- package/dist/agents/codex.js +2 -2
- package/dist/agents/cursor.js +1 -1
- package/dist/agents/deepagents.js +1 -1
- package/dist/agents/gemini.js +3 -2
- package/dist/agents/goose.js +1 -1
- package/dist/agents/hermes.js +1 -1
- package/dist/agents/kiro.js +1 -1
- package/dist/agents/opencode.js +1 -1
- package/dist/agents/qoder.js +1 -1
- package/dist/agents/shared-prompt.d.ts +0 -3
- package/dist/agents/shared-prompt.js +0 -3
- package/dist/commands/info.d.ts +0 -3
- package/dist/commands/info.js +0 -5
- package/dist/commands/init.d.ts +0 -3
- package/dist/commands/init.js +2 -11
- package/dist/commands/pair.d.ts +1 -4
- package/dist/commands/pair.js +3 -12
- package/dist/commands/restart.d.ts +0 -3
- package/dist/commands/restart.js +0 -3
- package/dist/commands/run.d.ts +1 -14
- package/dist/commands/run.js +18 -61
- package/dist/commands/serve.d.ts +0 -3
- package/dist/commands/serve.js +29 -27
- package/dist/config.d.ts +0 -8
- package/dist/config.js +0 -8
- package/dist/device-capabilities.d.ts +1 -1
- package/dist/event-queues.d.ts +6 -21
- package/dist/event-queues.js +6 -21
- package/dist/events.d.ts +0 -6
- package/dist/events.js +1 -9
- package/dist/index.js +0 -1
- package/dist/mcp-handler.js +1 -2
- package/dist/mcp-tools.d.ts +0 -3
- package/dist/mcp-tools.js +12 -16
- package/dist/nats-client.d.ts +0 -3
- package/dist/nats-client.js +1 -4
- package/dist/pending-requests.d.ts +4 -18
- package/dist/pending-requests.js +4 -18
- package/dist/platform/index.d.ts +1 -4
- package/dist/platform/index.js +8 -7
- package/dist/platform/linux.d.ts +3 -9
- package/dist/platform/linux.js +9 -20
- package/dist/platform/macos.d.ts +32 -0
- package/dist/platform/macos.js +287 -0
- package/dist/platform/platform.d.ts +1 -4
- package/dist/platform/windows.d.ts +2 -5
- package/dist/platform/windows.js +19 -39
- package/dist/pwa/assets/index-499vYQvR.js +120 -0
- package/dist/pwa/assets/{index-CQxcuDhM.css → index-UaZFu6XL.css} +1 -1
- package/dist/pwa/assets/{web-DOyOiwsW.js → web-Bp48ONY3.js} +1 -1
- package/dist/pwa/assets/{web-D7Kq3Nvk.js → web-CyJutAy4.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/pwa/service-worker.js +1 -1
- package/dist/rpc-handler.d.ts +0 -6
- package/dist/rpc-handler.js +14 -47
- package/dist/spawn-command.d.ts +10 -25
- package/dist/spawn-command.js +7 -15
- package/dist/task.d.ts +6 -64
- package/dist/task.js +7 -70
- package/dist/transports/http-transport.d.ts +0 -4
- package/dist/transports/http-transport.js +7 -28
- package/dist/transports/nats-transport.d.ts +0 -4
- package/dist/transports/nats-transport.js +3 -9
- package/dist/types.d.ts +3 -7
- package/dist/update-checker.d.ts +1 -4
- package/dist/update-checker.js +2 -5
- package/package.json +1 -1
- package/palmier-server/pwa/src/App.css +325 -22
- package/palmier-server/pwa/src/App.tsx +2 -0
- package/palmier-server/pwa/src/components/CapabilityToggles.tsx +288 -0
- package/palmier-server/pwa/src/components/HostMenu.tsx +20 -207
- package/palmier-server/pwa/src/components/RunDetailView.tsx +3 -3
- package/palmier-server/pwa/src/components/SessionComposer.tsx +11 -2
- package/palmier-server/pwa/src/components/SessionsView.tsx +60 -32
- package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +160 -0
- package/palmier-server/pwa/src/components/TaskCard.tsx +1 -1
- package/palmier-server/pwa/src/components/TaskForm.tsx +207 -5
- package/palmier-server/pwa/src/components/TasksView.tsx +3 -1
- package/palmier-server/pwa/src/constants.ts +1 -1
- package/palmier-server/pwa/src/native/Device.ts +18 -2
- package/palmier-server/pwa/src/pages/Dashboard.tsx +13 -6
- package/palmier-server/pwa/src/pages/PairHost.tsx +3 -1
- package/palmier-server/pwa/src/pages/PairSetup.tsx +70 -0
- package/palmier-server/server/src/index.ts +7 -7
- package/palmier-server/server/src/routes/device.ts +4 -4
- package/palmier-server/spec.md +38 -7
- package/src/agents/agent.ts +0 -4
- package/src/agents/claude.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/cursor.ts +1 -1
- package/src/agents/deepagents.ts +1 -1
- package/src/agents/gemini.ts +3 -2
- package/src/agents/goose.ts +1 -1
- package/src/agents/hermes.ts +1 -1
- package/src/agents/kiro.ts +1 -1
- package/src/agents/opencode.ts +1 -1
- package/src/agents/qoder.ts +1 -1
- package/src/agents/shared-prompt.ts +0 -3
- package/src/commands/info.ts +0 -5
- package/src/commands/init.ts +2 -11
- package/src/commands/pair.ts +3 -12
- package/src/commands/restart.ts +0 -3
- package/src/commands/run.ts +18 -65
- package/src/commands/serve.ts +28 -27
- package/src/config.ts +0 -8
- package/src/device-capabilities.ts +3 -2
- package/src/event-queues.ts +6 -21
- package/src/events.ts +1 -9
- package/src/index.ts +0 -1
- package/src/mcp-handler.ts +1 -2
- package/src/mcp-tools.ts +12 -18
- package/src/nats-client.ts +1 -4
- package/src/pending-requests.ts +4 -18
- package/src/platform/index.ts +5 -7
- package/src/platform/linux.ts +9 -20
- package/src/platform/macos.ts +310 -0
- package/src/platform/platform.ts +1 -4
- package/src/platform/windows.ts +19 -40
- package/src/rpc-handler.ts +14 -47
- package/src/spawn-command.ts +11 -27
- package/src/task.ts +7 -70
- package/src/transports/http-transport.ts +7 -39
- package/src/transports/nats-transport.ts +3 -9
- package/src/types.ts +3 -10
- package/src/update-checker.ts +2 -5
- package/test/macos-plist.test.ts +112 -0
- package/test/task-parsing.test.ts +2 -3
- package/test/windows-xml.test.ts +11 -12
- package/dist/pwa/assets/index-DQfOEB03.js +0 -120
package/dist/mcp-tools.js
CHANGED
|
@@ -400,9 +400,9 @@ const sendSmsTool = {
|
|
|
400
400
|
async handler(args, ctx) {
|
|
401
401
|
if (!ctx.nc)
|
|
402
402
|
throw new ToolError("Not connected to server (NATS unavailable)", 503);
|
|
403
|
-
const device = getCapabilityDevice("sms");
|
|
403
|
+
const device = getCapabilityDevice("sms-send");
|
|
404
404
|
if (!device)
|
|
405
|
-
throw new ToolError("No device has SMS
|
|
405
|
+
throw new ToolError("No device has SMS Send enabled", 400);
|
|
406
406
|
const { to, body } = args;
|
|
407
407
|
if (!to || !body)
|
|
408
408
|
throw new ToolError("to and body are required", 400);
|
|
@@ -433,10 +433,10 @@ const sendSmsTool = {
|
|
|
433
433
|
return result;
|
|
434
434
|
},
|
|
435
435
|
};
|
|
436
|
-
const
|
|
437
|
-
name: "send-
|
|
436
|
+
const sendAlarmTool = {
|
|
437
|
+
name: "send-alarm",
|
|
438
438
|
description: [
|
|
439
|
-
"
|
|
439
|
+
"Trigger an alarm on the user's mobile device with an alarm sound and full-screen popup.",
|
|
440
440
|
"Use this to urgently get the user's attention. The device will play an alarm sound and show a full-screen dialog even on the lock screen.",
|
|
441
441
|
"Blocks until the device responds (up to 30 seconds).",
|
|
442
442
|
'Response: `{"ok": true}` on success, or `{"error": "..."}` on failure.',
|
|
@@ -444,17 +444,17 @@ const sendAlertTool = {
|
|
|
444
444
|
inputSchema: {
|
|
445
445
|
type: "object",
|
|
446
446
|
properties: {
|
|
447
|
-
title: { type: "string", description: "
|
|
448
|
-
description: { type: "string", description: "
|
|
447
|
+
title: { type: "string", description: "Alarm title" },
|
|
448
|
+
description: { type: "string", description: "Alarm description/details" },
|
|
449
449
|
},
|
|
450
450
|
required: ["title"],
|
|
451
451
|
},
|
|
452
452
|
async handler(args, ctx) {
|
|
453
453
|
if (!ctx.nc)
|
|
454
454
|
throw new ToolError("Not connected to server (NATS unavailable)", 503);
|
|
455
|
-
const device = getCapabilityDevice("
|
|
455
|
+
const device = getCapabilityDevice("alarm");
|
|
456
456
|
if (!device)
|
|
457
|
-
throw new ToolError("No device has
|
|
457
|
+
throw new ToolError("No device has alarm access enabled", 400);
|
|
458
458
|
const { title, description } = args;
|
|
459
459
|
if (!title)
|
|
460
460
|
throw new ToolError("title is required", 400);
|
|
@@ -465,12 +465,12 @@ const sendAlertTool = {
|
|
|
465
465
|
};
|
|
466
466
|
if (description)
|
|
467
467
|
payload.description = description;
|
|
468
|
-
const ackReply = await ctx.nc.request(`host.${ctx.config.hostId}.fcm.
|
|
468
|
+
const ackReply = await ctx.nc.request(`host.${ctx.config.hostId}.fcm.alarm`, sc.encode(JSON.stringify(payload)), { timeout: 5_000 });
|
|
469
469
|
const ack = JSON.parse(sc.decode(ackReply.data));
|
|
470
470
|
if (ack.error)
|
|
471
471
|
throw new ToolError(ack.error, 502);
|
|
472
472
|
const responsePromise = new Promise((resolve, reject) => {
|
|
473
|
-
const sub = ctx.nc.subscribe(`host.${ctx.config.hostId}.
|
|
473
|
+
const sub = ctx.nc.subscribe(`host.${ctx.config.hostId}.alarm.${ctx.sessionId}`, { max: 1 });
|
|
474
474
|
const timer = setTimeout(() => {
|
|
475
475
|
sub.unsubscribe();
|
|
476
476
|
reject(new ToolError("Device did not respond within 30 seconds", 504));
|
|
@@ -639,7 +639,7 @@ const sendEmailTool = {
|
|
|
639
639
|
return result;
|
|
640
640
|
},
|
|
641
641
|
};
|
|
642
|
-
export const agentTools = [notifyTool, requestInputTool, requestConfirmationTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool,
|
|
642
|
+
export const agentTools = [notifyTool, requestInputTool, requestConfirmationTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool, sendAlarmTool, readBatteryTool, setRingerModeTool];
|
|
643
643
|
export const agentToolMap = new Map(agentTools.map((t) => [t.name, t]));
|
|
644
644
|
const deviceNotificationsResource = {
|
|
645
645
|
uri: "notifications://device",
|
|
@@ -667,9 +667,6 @@ const deviceSmsResource = {
|
|
|
667
667
|
};
|
|
668
668
|
export const agentResources = [deviceNotificationsResource, deviceSmsResource];
|
|
669
669
|
export const agentResourceMap = new Map(agentResources.map((r) => [r.uri, r]));
|
|
670
|
-
/**
|
|
671
|
-
* Generate the HTTP Endpoints markdown section for agent-instructions.md from the tool registry.
|
|
672
|
-
*/
|
|
673
670
|
export function generateEndpointDocs(port, taskId, tools = agentTools, resources = agentResources) {
|
|
674
671
|
const baseUrl = `http://localhost:${port}`;
|
|
675
672
|
const lines = [
|
|
@@ -680,7 +677,6 @@ export function generateEndpointDocs(port, taskId, tools = agentTools, resources
|
|
|
680
677
|
const schema = tool.inputSchema;
|
|
681
678
|
const props = schema.properties ?? {};
|
|
682
679
|
const required = new Set(schema.required ?? []);
|
|
683
|
-
// Build example JSON (body only, no taskId)
|
|
684
680
|
const example = {};
|
|
685
681
|
for (const [key, prop] of Object.entries(props)) {
|
|
686
682
|
if (prop.type === "array")
|
package/dist/nats-client.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { type NatsConnection } from "nats";
|
|
2
2
|
import type { HostConfig } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Connect to NATS using the host config's JWT credentials.
|
|
5
|
-
*/
|
|
6
3
|
export declare function connectNats(config: HostConfig): Promise<NatsConnection>;
|
|
7
4
|
//# sourceMappingURL=nats-client.d.ts.map
|
package/dist/nats-client.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { connect, jwtAuthenticator } from "nats";
|
|
2
|
-
/**
|
|
3
|
-
* Connect to NATS using the host config's JWT credentials.
|
|
4
|
-
*/
|
|
5
2
|
export async function connectNats(config) {
|
|
6
3
|
if (!config.natsJwt || !config.natsNkeySeed) {
|
|
7
4
|
throw new Error("NATS JWT credentials not configured. Re-run palmier init.");
|
|
@@ -10,7 +7,7 @@ export async function connectNats(config) {
|
|
|
10
7
|
servers: config.natsUrl,
|
|
11
8
|
authenticator: jwtAuthenticator(config.natsJwt, new TextEncoder().encode(config.natsNkeySeed)),
|
|
12
9
|
});
|
|
13
|
-
// Do not log
|
|
10
|
+
// Do not log — it would pollute stdout for the MCP server.
|
|
14
11
|
return nc;
|
|
15
12
|
}
|
|
16
13
|
//# sourceMappingURL=nats-client.js.map
|
|
@@ -17,29 +17,15 @@ export interface PendingRequest {
|
|
|
17
17
|
meta?: PendingRequestMeta;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* Only one pending request per key at a time.
|
|
20
|
+
* Key is sessionId for confirmation/input, taskId for permission. Only one
|
|
21
|
+
* pending request per key at a time. `meta` is surfaced via host.info so a
|
|
22
|
+
* freshly-connected PWA can render the modal without replaying events.
|
|
24
23
|
*/
|
|
25
24
|
export declare function registerPending(key: string, type: PendingRequest["type"], params?: PendingRequest["params"], meta?: PendingRequestMeta): Promise<string[]>;
|
|
26
|
-
/**
|
|
27
|
-
* Resolve a pending request with the user's response.
|
|
28
|
-
* Returns true if a pending request was found and resolved.
|
|
29
|
-
*/
|
|
30
25
|
export declare function resolvePending(key: string, value: string[]): boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Get the current pending request for a key (if any).
|
|
33
|
-
*/
|
|
34
26
|
export declare function getPending(key: string): PendingRequest | undefined;
|
|
35
|
-
/**
|
|
36
|
-
* Remove a pending request without resolving it.
|
|
37
|
-
*/
|
|
38
27
|
export declare function removePending(key: string): void;
|
|
39
|
-
/**
|
|
40
|
-
* List all currently-pending requests, stripped of the unserializable `resolve`
|
|
41
|
-
* callback. Used by `host.info` so the PWA can seed its modal state on connect.
|
|
42
|
-
*/
|
|
28
|
+
/** Pending requests stripped of the unserializable `resolve` callback. */
|
|
43
29
|
export declare function listPending(): Array<{
|
|
44
30
|
key: string;
|
|
45
31
|
type: PendingRequest["type"];
|
package/dist/pending-requests.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
const pending = new Map();
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Only one pending request per key at a time.
|
|
3
|
+
* Key is sessionId for confirmation/input, taskId for permission. Only one
|
|
4
|
+
* pending request per key at a time. `meta` is surfaced via host.info so a
|
|
5
|
+
* freshly-connected PWA can render the modal without replaying events.
|
|
7
6
|
*/
|
|
8
7
|
export function registerPending(key, type, params, meta) {
|
|
9
8
|
if (pending.has(key)) {
|
|
@@ -13,10 +12,6 @@ export function registerPending(key, type, params, meta) {
|
|
|
13
12
|
pending.set(key, { type, resolve, params, meta });
|
|
14
13
|
});
|
|
15
14
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Resolve a pending request with the user's response.
|
|
18
|
-
* Returns true if a pending request was found and resolved.
|
|
19
|
-
*/
|
|
20
15
|
export function resolvePending(key, value) {
|
|
21
16
|
const entry = pending.get(key);
|
|
22
17
|
if (!entry)
|
|
@@ -25,22 +20,13 @@ export function resolvePending(key, value) {
|
|
|
25
20
|
entry.resolve(value);
|
|
26
21
|
return true;
|
|
27
22
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Get the current pending request for a key (if any).
|
|
30
|
-
*/
|
|
31
23
|
export function getPending(key) {
|
|
32
24
|
return pending.get(key);
|
|
33
25
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Remove a pending request without resolving it.
|
|
36
|
-
*/
|
|
37
26
|
export function removePending(key) {
|
|
38
27
|
pending.delete(key);
|
|
39
28
|
}
|
|
40
|
-
/**
|
|
41
|
-
* List all currently-pending requests, stripped of the unserializable `resolve`
|
|
42
|
-
* callback. Used by `host.info` so the PWA can seed its modal state on connect.
|
|
43
|
-
*/
|
|
29
|
+
/** Pending requests stripped of the unserializable `resolve` callback. */
|
|
44
30
|
export function listPending() {
|
|
45
31
|
return [...pending.entries()].map(([key, entry]) => ({
|
|
46
32
|
key,
|
package/dist/platform/index.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { PlatformService } from "./platform.js";
|
|
2
|
-
/**
|
|
3
|
-
* On Windows, execSync needs an explicit shell so .cmd shims resolve correctly.
|
|
4
|
-
* On Unix, undefined lets Node use the default shell.
|
|
5
|
-
*/
|
|
2
|
+
/** Windows needs an explicit shell for execSync to resolve .cmd shims. */
|
|
6
3
|
export declare const SHELL: string | undefined;
|
|
7
4
|
export declare function getPlatform(): PlatformService;
|
|
8
5
|
export type { PlatformService } from "./platform.js";
|
package/dist/platform/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { LinuxPlatform } from "./linux.js";
|
|
2
2
|
import { WindowsPlatform } from "./windows.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* On Unix, undefined lets Node use the default shell.
|
|
6
|
-
*/
|
|
3
|
+
import { MacOsPlatform } from "./macos.js";
|
|
4
|
+
/** Windows needs an explicit shell for execSync to resolve .cmd shims. */
|
|
7
5
|
export const SHELL = process.platform === "win32" ? "cmd.exe" : undefined;
|
|
8
6
|
let _instance;
|
|
9
7
|
export function getPlatform() {
|
|
10
8
|
if (!_instance) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
if (process.platform === "win32")
|
|
10
|
+
_instance = new WindowsPlatform();
|
|
11
|
+
else if (process.platform === "darwin")
|
|
12
|
+
_instance = new MacOsPlatform();
|
|
13
|
+
else
|
|
14
|
+
_instance = new LinuxPlatform();
|
|
14
15
|
}
|
|
15
16
|
return _instance;
|
|
16
17
|
}
|
package/dist/platform/linux.d.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import type { PlatformService } from "./platform.js";
|
|
2
2
|
import type { HostConfig, ParsedTask } from "../types.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* hourly: "0 * * * *"
|
|
8
|
-
* daily: "MM HH * * *"
|
|
9
|
-
* weekly: "MM HH * * D"
|
|
10
|
-
* monthly: "MM HH D * *"
|
|
11
|
-
* Arbitrary cron expressions (ranges, lists, steps beyond hourly) are NOT
|
|
12
|
-
* handled because the UI never generates them.
|
|
4
|
+
* Only the 4 cron patterns the PWA UI produces are supported:
|
|
5
|
+
* hourly "0 * * * *", daily "MM HH * * *", weekly "MM HH * * D", monthly "MM HH D * *".
|
|
6
|
+
* Arbitrary expressions (ranges, lists, sub-hour steps) are not handled.
|
|
13
7
|
*/
|
|
14
8
|
export declare function cronToOnCalendar(cron: string): string;
|
|
15
9
|
export declare class LinuxPlatform implements PlatformService {
|
package/dist/platform/linux.js
CHANGED
|
@@ -15,15 +15,9 @@ function getServiceName(taskId) {
|
|
|
15
15
|
return `palmier-task-${taskId}.service`;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* hourly: "0 * * * *"
|
|
22
|
-
* daily: "MM HH * * *"
|
|
23
|
-
* weekly: "MM HH * * D"
|
|
24
|
-
* monthly: "MM HH D * *"
|
|
25
|
-
* Arbitrary cron expressions (ranges, lists, steps beyond hourly) are NOT
|
|
26
|
-
* handled because the UI never generates them.
|
|
18
|
+
* Only the 4 cron patterns the PWA UI produces are supported:
|
|
19
|
+
* hourly "0 * * * *", daily "MM HH * * *", weekly "MM HH * * D", monthly "MM HH D * *".
|
|
20
|
+
* Arbitrary expressions (ranges, lists, sub-hour steps) are not handled.
|
|
27
21
|
*/
|
|
28
22
|
export function cronToOnCalendar(cron) {
|
|
29
23
|
const parts = cron.trim().split(/\s+/);
|
|
@@ -31,7 +25,6 @@ export function cronToOnCalendar(cron) {
|
|
|
31
25
|
throw new Error(`Invalid cron expression (expected 5 fields): ${cron}`);
|
|
32
26
|
}
|
|
33
27
|
const [minute, hour, dayOfMonth, , dayOfWeek] = parts;
|
|
34
|
-
// Map cron day-of-week numbers to systemd abbreviated names
|
|
35
28
|
const dowMap = {
|
|
36
29
|
"0": "Sun", "1": "Mon", "2": "Tue", "3": "Wed",
|
|
37
30
|
"4": "Thu", "5": "Fri", "6": "Sat", "7": "Sun",
|
|
@@ -59,8 +52,8 @@ export class LinuxPlatform {
|
|
|
59
52
|
installDaemon(config) {
|
|
60
53
|
fs.mkdirSync(UNIT_DIR, { recursive: true });
|
|
61
54
|
const palmierBin = process.argv[1] || "palmier";
|
|
62
|
-
// Save the user's shell PATH so restartDaemon can
|
|
63
|
-
//
|
|
55
|
+
// Save the user's shell PATH so restartDaemon can reuse it later — under
|
|
56
|
+
// systemd the daemon itself runs with a limited PATH.
|
|
64
57
|
const userPath = process.env.PATH || "/usr/local/bin:/usr/bin:/bin";
|
|
65
58
|
fs.mkdirSync(path.dirname(PATH_FILE), { recursive: true });
|
|
66
59
|
fs.writeFileSync(PATH_FILE, userPath, "utf-8");
|
|
@@ -93,7 +86,7 @@ WantedBy=default.target
|
|
|
93
86
|
console.error(`Warning: failed to enable systemd service: ${err}`);
|
|
94
87
|
console.error("You may need to start it manually: systemctl --user enable --now palmier.service");
|
|
95
88
|
}
|
|
96
|
-
//
|
|
89
|
+
// Lingering lets the service run without an active login session.
|
|
97
90
|
try {
|
|
98
91
|
execSync(`loginctl enable-linger ${process.env.USER || ""}`, { stdio: "inherit" });
|
|
99
92
|
console.log("Login lingering enabled.");
|
|
@@ -109,13 +102,11 @@ WantedBy=default.target
|
|
|
109
102
|
execSync("systemctl --user disable palmier.service 2>/dev/null", { stdio: "pipe" });
|
|
110
103
|
}
|
|
111
104
|
catch { /* service may not exist */ }
|
|
112
|
-
// Remove daemon service file
|
|
113
105
|
const servicePath = path.join(UNIT_DIR, "palmier.service");
|
|
114
106
|
try {
|
|
115
107
|
fs.unlinkSync(servicePath);
|
|
116
108
|
}
|
|
117
109
|
catch { /* ignore */ }
|
|
118
|
-
// Remove all task timers and services
|
|
119
110
|
try {
|
|
120
111
|
const files = fs.readdirSync(UNIT_DIR).filter((f) => f.startsWith("palmier-task-"));
|
|
121
112
|
for (const f of files) {
|
|
@@ -142,8 +133,8 @@ WantedBy=default.target
|
|
|
142
133
|
console.log("Palmier daemon and tasks uninstalled.");
|
|
143
134
|
}
|
|
144
135
|
async restartDaemon() {
|
|
145
|
-
//
|
|
146
|
-
//
|
|
136
|
+
// From a TTY, snapshot the current PATH; from the daemon (auto-update),
|
|
137
|
+
// reuse whatever was last saved.
|
|
147
138
|
if (process.stdin.isTTY) {
|
|
148
139
|
fs.mkdirSync(path.dirname(PATH_FILE), { recursive: true });
|
|
149
140
|
fs.writeFileSync(PATH_FILE, process.env.PATH || "", "utf-8");
|
|
@@ -181,7 +172,6 @@ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
|
|
|
181
172
|
`;
|
|
182
173
|
fs.writeFileSync(path.join(UNIT_DIR, serviceName), serviceContent, "utf-8");
|
|
183
174
|
daemonReload();
|
|
184
|
-
// Only create and enable a timer if the schedule exists and is enabled
|
|
185
175
|
if (!task.frontmatter.schedule_enabled)
|
|
186
176
|
return;
|
|
187
177
|
const scheduleType = task.frontmatter.schedule_type;
|
|
@@ -248,7 +238,6 @@ WantedBy=timers.target
|
|
|
248
238
|
await execAsync(`systemctl --user stop ${serviceName}`);
|
|
249
239
|
}
|
|
250
240
|
isTaskRunning(taskId) {
|
|
251
|
-
// Check systemd first (for scheduled/on-demand runs)
|
|
252
241
|
const serviceName = getServiceName(taskId);
|
|
253
242
|
try {
|
|
254
243
|
const out = execSync(`systemctl --user show -p ActiveState --value ${serviceName}`, { encoding: "utf-8" });
|
|
@@ -257,7 +246,7 @@ WantedBy=timers.target
|
|
|
257
246
|
return true;
|
|
258
247
|
}
|
|
259
248
|
catch { /* service may not exist */ }
|
|
260
|
-
//
|
|
249
|
+
// Follow-up runs are spawned directly, so check PID too.
|
|
261
250
|
try {
|
|
262
251
|
const taskDir = getTaskDir(loadConfig().projectRoot, taskId);
|
|
263
252
|
const status = readTaskStatus(taskDir);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PlatformService } from "./platform.js";
|
|
2
|
+
import type { HostConfig, ParsedTask } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Convert one of the four PWA-produced cron patterns to a launchd
|
|
5
|
+
* `StartCalendarInterval` dict.
|
|
6
|
+
* hourly "0 * * * *" → { Minute: 0 }
|
|
7
|
+
* daily "MM HH * * *" → { Minute, Hour }
|
|
8
|
+
* weekly "MM HH * * D" → { Minute, Hour, Weekday }
|
|
9
|
+
* monthly "MM HH D * *" → { Minute, Hour, Day }
|
|
10
|
+
* launchd Weekday: Sunday is 0 (cron 7 → 0).
|
|
11
|
+
*/
|
|
12
|
+
export declare function cronToCalendarInterval(cron: string): Record<string, number>;
|
|
13
|
+
/**
|
|
14
|
+
* Convert a PWA `specific_times` value (ISO local datetime like "2026-04-20T09:00")
|
|
15
|
+
* to a `StartCalendarInterval` dict. launchd has no "one-shot at date X" trigger,
|
|
16
|
+
* so we omit Year — the task fires yearly on the same date and time. Sufficient
|
|
17
|
+
* because the PWA regenerates/removes one-off tasks after they run.
|
|
18
|
+
*/
|
|
19
|
+
export declare function specificTimeToCalendarInterval(iso: string): Record<string, number>;
|
|
20
|
+
export declare function buildPlist(dict: Record<string, unknown>): string;
|
|
21
|
+
export declare class MacOsPlatform implements PlatformService {
|
|
22
|
+
installDaemon(config: HostConfig): void;
|
|
23
|
+
uninstallDaemon(): void;
|
|
24
|
+
restartDaemon(): Promise<void>;
|
|
25
|
+
installTaskTimer(config: HostConfig, task: ParsedTask): void;
|
|
26
|
+
removeTaskTimer(taskId: string): void;
|
|
27
|
+
startTask(taskId: string): Promise<void>;
|
|
28
|
+
stopTask(taskId: string): Promise<void>;
|
|
29
|
+
isTaskRunning(taskId: string): boolean;
|
|
30
|
+
getGuiEnv(): Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=macos.d.ts.map
|