palmier 0.2.4 → 0.2.6

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 (78) hide show
  1. package/.github/workflows/ci.yml +16 -0
  2. package/LICENSE +190 -0
  3. package/README.md +288 -223
  4. package/dist/agents/agent.d.ts +6 -3
  5. package/dist/agents/agent.js +2 -0
  6. package/dist/agents/claude.d.ts +1 -1
  7. package/dist/agents/claude.js +15 -9
  8. package/dist/agents/codex.d.ts +1 -1
  9. package/dist/agents/codex.js +14 -10
  10. package/dist/agents/gemini.d.ts +1 -1
  11. package/dist/agents/gemini.js +15 -9
  12. package/dist/agents/openclaw.d.ts +2 -2
  13. package/dist/agents/openclaw.js +8 -7
  14. package/dist/agents/shared-prompt.d.ts +5 -4
  15. package/dist/agents/shared-prompt.js +10 -8
  16. package/dist/commands/agents.js +11 -0
  17. package/dist/commands/info.js +17 -10
  18. package/dist/commands/init.d.ts +2 -10
  19. package/dist/commands/init.js +171 -163
  20. package/dist/commands/mcpserver.js +11 -21
  21. package/dist/commands/plan-generation.md +24 -32
  22. package/dist/commands/restart.d.ts +5 -0
  23. package/dist/commands/restart.js +9 -0
  24. package/dist/commands/run.js +295 -114
  25. package/dist/commands/serve.js +83 -4
  26. package/dist/commands/sessions.js +1 -4
  27. package/dist/commands/task-cleanup.d.ts +14 -0
  28. package/dist/commands/task-cleanup.js +84 -0
  29. package/dist/config.js +1 -1
  30. package/dist/events.d.ts +10 -0
  31. package/dist/events.js +29 -0
  32. package/dist/index.js +14 -7
  33. package/dist/platform/index.d.ts +4 -0
  34. package/dist/platform/index.js +12 -0
  35. package/dist/platform/linux.d.ts +13 -0
  36. package/dist/platform/linux.js +207 -0
  37. package/dist/platform/platform.d.ts +24 -0
  38. package/dist/platform/platform.js +2 -0
  39. package/dist/platform/windows.d.ts +14 -0
  40. package/dist/platform/windows.js +218 -0
  41. package/dist/rpc-handler.d.ts +2 -1
  42. package/dist/rpc-handler.js +53 -55
  43. package/dist/spawn-command.d.ts +32 -6
  44. package/dist/spawn-command.js +38 -13
  45. package/dist/transports/http-transport.js +2 -7
  46. package/dist/transports/nats-transport.d.ts +4 -2
  47. package/dist/transports/nats-transport.js +3 -4
  48. package/dist/types.d.ts +4 -2
  49. package/package.json +5 -3
  50. package/src/agents/agent.ts +8 -3
  51. package/src/agents/claude.ts +44 -39
  52. package/src/agents/codex.ts +14 -12
  53. package/src/agents/gemini.ts +15 -10
  54. package/src/agents/openclaw.ts +8 -7
  55. package/src/agents/shared-prompt.ts +10 -8
  56. package/src/commands/agents.ts +11 -0
  57. package/src/commands/info.ts +18 -10
  58. package/src/commands/init.ts +201 -186
  59. package/src/commands/mcpserver.ts +11 -22
  60. package/src/commands/plan-generation.md +24 -32
  61. package/src/commands/restart.ts +9 -0
  62. package/src/commands/run.ts +367 -133
  63. package/src/commands/serve.ts +101 -5
  64. package/src/commands/sessions.ts +1 -4
  65. package/src/config.ts +1 -1
  66. package/src/cross-spawn.d.ts +5 -0
  67. package/src/events.ts +38 -0
  68. package/src/index.ts +16 -7
  69. package/src/platform/index.ts +16 -0
  70. package/src/platform/linux.ts +231 -0
  71. package/src/platform/platform.ts +31 -0
  72. package/src/platform/windows.ts +234 -0
  73. package/src/rpc-handler.ts +56 -63
  74. package/src/spawn-command.ts +120 -78
  75. package/src/transports/http-transport.ts +2 -5
  76. package/src/transports/nats-transport.ts +4 -4
  77. package/src/types.ts +4 -2
  78. package/src/systemd.ts +0 -164
@@ -0,0 +1,84 @@
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 { getTaskDir, readTaskStatus, writeTaskStatus, appendHistory, parseTaskFile } from "../task.js";
6
+ import { publishHostEvent } from "../events.js";
7
+ /**
8
+ * Write a minimal RESULT file for a task that exited without writing one itself.
9
+ * Uses the status.json time_stamp as the start time.
10
+ */
11
+ function writeCleanupResult(taskDir, taskName, runningState, startTime) {
12
+ const endTime = Date.now();
13
+ const resultFileName = `RESULT-${endTime}.md`;
14
+ // Find the task snapshot file that matches the start time
15
+ const taskSnapshotName = `TASK-${startTime}.md`;
16
+ const taskFile = fs.existsSync(path.join(taskDir, taskSnapshotName)) ? taskSnapshotName : "";
17
+ const content = `---\ntask_name: ${taskName}\nrunning_state: ${runningState}\nstart_time: ${startTime}\nend_time: ${endTime}\ntask_file: ${taskFile}\n---\nTask process exited unexpectedly.`;
18
+ fs.writeFileSync(path.join(taskDir, resultFileName), content, "utf-8");
19
+ return resultFileName;
20
+ }
21
+ /**
22
+ * Post-exit cleanup for a task process.
23
+ *
24
+ * Called by the platform hook (ExecStopPost on Linux, wrapper script on Windows)
25
+ * after the main `palmier run <taskId>` process exits.
26
+ *
27
+ * - If status.json shows "finish" or "fail", the process handled its own cleanup — no-op.
28
+ * - If status.json shows "abort", the RPC handler already wrote status and broadcast —
29
+ * just write the RESULT file and append history.
30
+ * - If status.json shows "start", the process died unexpectedly — write "fail" status,
31
+ * RESULT file, append history, and broadcast event.
32
+ */
33
+ export async function taskCleanupCommand(taskId) {
34
+ const config = loadConfig();
35
+ const taskDir = getTaskDir(config.projectRoot, taskId);
36
+ const status = readTaskStatus(taskDir);
37
+ if (!status) {
38
+ console.log(`[task-cleanup] No status.json for task ${taskId}, nothing to do.`);
39
+ return;
40
+ }
41
+ // Process already handled its own cleanup
42
+ if (status.running_state === "finish" || status.running_state === "fail") {
43
+ console.log(`[task-cleanup] Task ${taskId} already in terminal state: ${status.running_state}`);
44
+ return;
45
+ }
46
+ // Read task name for RESULT file
47
+ let taskName = taskId;
48
+ try {
49
+ const task = parseTaskFile(taskDir);
50
+ taskName = task.frontmatter.name || taskId;
51
+ }
52
+ catch { /* use taskId as fallback name */ }
53
+ const startTime = status.time_stamp;
54
+ if (status.running_state === "abort") {
55
+ // RPC handler already wrote status and broadcast — just write RESULT + history
56
+ console.log(`[task-cleanup] Task ${taskId} was aborted via RPC, writing RESULT.`);
57
+ const resultFileName = writeCleanupResult(taskDir, taskName, "abort", startTime);
58
+ appendHistory(config.projectRoot, { task_id: taskId, result_file: resultFileName });
59
+ return;
60
+ }
61
+ // status.running_state === "start" — unexpected death
62
+ console.log(`[task-cleanup] Task ${taskId} died unexpectedly, marking as failed.`);
63
+ writeTaskStatus(taskDir, { running_state: "fail", time_stamp: Date.now() });
64
+ const resultFileName = writeCleanupResult(taskDir, taskName, "fail", startTime);
65
+ appendHistory(config.projectRoot, { task_id: taskId, result_file: resultFileName });
66
+ // Broadcast failure event via NATS and/or HTTP, consistent with other status pushes
67
+ const mode = config.mode ?? "nats";
68
+ const useNats = mode === "nats" || mode === "auto";
69
+ const useHttp = mode === "lan" || mode === "auto";
70
+ let nc;
71
+ try {
72
+ if (useNats) {
73
+ nc = await connectNats(config);
74
+ }
75
+ const payload = { event_type: "running-state", running_state: "fail", name: taskName };
76
+ await publishHostEvent(nc, config, taskId, payload, useHttp);
77
+ }
78
+ finally {
79
+ if (nc && !nc.isClosed()) {
80
+ await nc.drain();
81
+ }
82
+ }
83
+ }
84
+ //# sourceMappingURL=task-cleanup.js.map
package/dist/config.js CHANGED
@@ -9,7 +9,7 @@ const CONFIG_FILE = path.join(CONFIG_DIR, "host.json");
9
9
  */
10
10
  export function loadConfig() {
11
11
  if (!fs.existsSync(CONFIG_FILE)) {
12
- throw new Error("Host not provisioned. Run `palmier init --token <token>` or `palmier init --lan` first.\n" +
12
+ throw new Error("Host not provisioned. Run `palmier init` first.\n" +
13
13
  `Expected config at: ${CONFIG_FILE}`);
14
14
  }
15
15
  const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
@@ -0,0 +1,10 @@
1
+ import { type NatsConnection } from "nats";
2
+ import type { HostConfig } from "./types.js";
3
+ /**
4
+ * Broadcast an event to connected clients via NATS and/or HTTP SSE.
5
+ *
6
+ * - NATS: publishes to `host-event.{hostId}.{taskId}`
7
+ * - HTTP: POSTs to the daemon's `/internal/event` endpoint which fans out via SSE
8
+ */
9
+ export declare function publishHostEvent(nc: NatsConnection | undefined, config: HostConfig, taskId: string, payload: Record<string, unknown>, useHttp: boolean): Promise<void>;
10
+ //# sourceMappingURL=events.d.ts.map
package/dist/events.js ADDED
@@ -0,0 +1,29 @@
1
+ import { StringCodec } from "nats";
2
+ const sc = StringCodec();
3
+ /**
4
+ * Broadcast an event to connected clients via NATS and/or HTTP SSE.
5
+ *
6
+ * - NATS: publishes to `host-event.{hostId}.{taskId}`
7
+ * - HTTP: POSTs to the daemon's `/internal/event` endpoint which fans out via SSE
8
+ */
9
+ export async function publishHostEvent(nc, config, taskId, payload, useHttp) {
10
+ const subject = `host-event.${config.hostId}.${taskId}`;
11
+ if (nc) {
12
+ nc.publish(subject, sc.encode(JSON.stringify(payload)));
13
+ console.log(`[nats] ${subject} →`, payload);
14
+ }
15
+ if (useHttp && config.directPort) {
16
+ try {
17
+ await fetch(`http://localhost:${config.directPort}/internal/event`, {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify({ task_id: taskId, ...payload }),
21
+ });
22
+ console.log(`[http] host-event: ${taskId} →`, payload);
23
+ }
24
+ catch (err) {
25
+ console.error(`[http] Failed to push event:`, err);
26
+ }
27
+ }
28
+ }
29
+ //# sourceMappingURL=events.js.map
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { serveCommand } from "./commands/serve.js";
11
11
  import { mcpserverCommand } from "./commands/mcpserver.js";
12
12
  import { agentsCommand } from "./commands/agents.js";
13
13
  import { pairCommand } from "./commands/pair.js";
14
+ import { restartCommand } from "./commands/restart.js";
14
15
  import { sessionsListCommand, sessionsRevokeCommand, sessionsRevokeAllCommand } from "./commands/sessions.js";
15
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
17
  const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
@@ -22,12 +23,8 @@ program
22
23
  program
23
24
  .command("init")
24
25
  .description("Provision this host")
25
- .option("--server <url>", "Register with Palmier server at the given URL")
26
- .option("--lan", "Standalone LAN mode (no server needed)")
27
- .option("--host <ip>", "Override detected LAN IP address")
28
- .option("--port <port>", "Direct HTTP port (default: 7400)", parseInt)
29
- .action(async (options) => {
30
- await initCommand(options);
26
+ .action(async () => {
27
+ await initCommand();
31
28
  });
32
29
  program
33
30
  .command("info")
@@ -42,11 +39,17 @@ program
42
39
  await runCommand(taskId);
43
40
  });
44
41
  program
45
- .command("serve", { isDefault: true })
42
+ .command("serve")
46
43
  .description("Start the persistent RPC handler")
47
44
  .action(async () => {
48
45
  await serveCommand();
49
46
  });
47
+ program
48
+ .command("restart")
49
+ .description("Restart the palmier serve daemon")
50
+ .action(async () => {
51
+ await restartCommand();
52
+ });
50
53
  program
51
54
  .command("mcpserver")
52
55
  .description("Start an MCP server exposing Palmier tools (stdio transport)")
@@ -86,6 +89,10 @@ sessionsCmd
86
89
  .action(async () => {
87
90
  await sessionsRevokeAllCommand();
88
91
  });
92
+ // No subcommand → default to serve
93
+ if (process.argv.length <= 2) {
94
+ process.argv.push("serve");
95
+ }
89
96
  program.parseAsync(process.argv).catch((err) => {
90
97
  console.error(err);
91
98
  process.exit(1);
@@ -0,0 +1,4 @@
1
+ import type { PlatformService } from "./platform.js";
2
+ export declare function getPlatform(): PlatformService;
3
+ export type { PlatformService } from "./platform.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { LinuxPlatform } from "./linux.js";
2
+ import { WindowsPlatform } from "./windows.js";
3
+ let _instance;
4
+ export function getPlatform() {
5
+ if (!_instance) {
6
+ _instance = process.platform === "win32"
7
+ ? new WindowsPlatform()
8
+ : new LinuxPlatform();
9
+ }
10
+ return _instance;
11
+ }
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ import type { PlatformService } from "./platform.js";
2
+ import type { HostConfig, ParsedTask } from "../types.js";
3
+ export declare class LinuxPlatform implements PlatformService {
4
+ installDaemon(config: HostConfig): void;
5
+ restartDaemon(): Promise<void>;
6
+ installTaskTimer(config: HostConfig, task: ParsedTask): void;
7
+ removeTaskTimer(taskId: string): void;
8
+ startTask(taskId: string): Promise<void>;
9
+ stopTask(taskId: string): Promise<void>;
10
+ isTaskRunning(taskId: string): boolean;
11
+ getGuiEnv(): Record<string, string>;
12
+ }
13
+ //# sourceMappingURL=linux.d.ts.map
@@ -0,0 +1,207 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { homedir } from "os";
4
+ import { execSync, exec } from "child_process";
5
+ import { promisify } from "util";
6
+ const execAsync = promisify(exec);
7
+ const UNIT_DIR = path.join(homedir(), ".config", "systemd", "user");
8
+ function getTimerName(taskId) {
9
+ return `palmier-task-${taskId}.timer`;
10
+ }
11
+ function getServiceName(taskId) {
12
+ return `palmier-task-${taskId}.service`;
13
+ }
14
+ /**
15
+ * Convert a cron expression to a systemd OnCalendar string.
16
+ *
17
+ * Only the 4 cron patterns the PWA UI can produce are supported:
18
+ * hourly: "0 * * * *"
19
+ * daily: "MM HH * * *"
20
+ * weekly: "MM HH * * D"
21
+ * monthly: "MM HH D * *"
22
+ * Arbitrary cron expressions (ranges, lists, steps beyond hourly) are NOT
23
+ * handled because the UI never generates them.
24
+ */
25
+ function cronToOnCalendar(cron) {
26
+ const parts = cron.trim().split(/\s+/);
27
+ if (parts.length !== 5) {
28
+ throw new Error(`Invalid cron expression (expected 5 fields): ${cron}`);
29
+ }
30
+ const [minute, hour, dayOfMonth, , dayOfWeek] = parts;
31
+ // Map cron day-of-week numbers to systemd abbreviated names
32
+ const dowMap = {
33
+ "0": "Sun", "1": "Mon", "2": "Tue", "3": "Wed",
34
+ "4": "Thu", "5": "Fri", "6": "Sat", "7": "Sun",
35
+ };
36
+ const monthPart = "*";
37
+ const dayPart = dayOfMonth === "*" ? "*" : dayOfMonth.padStart(2, "0");
38
+ const hourPart = hour === "*" ? "*" : hour.padStart(2, "0");
39
+ const minutePart = minute === "*" ? "*" : minute.padStart(2, "0");
40
+ if (dayOfWeek !== "*") {
41
+ const dow = dowMap[dayOfWeek] ?? dayOfWeek;
42
+ return `${dow} *-${monthPart}-${dayPart} ${hourPart}:${minutePart}:00`;
43
+ }
44
+ return `*-${monthPart}-${dayPart} ${hourPart}:${minutePart}:00`;
45
+ }
46
+ function daemonReload() {
47
+ try {
48
+ execSync("systemctl --user daemon-reload", { encoding: "utf-8" });
49
+ }
50
+ catch (err) {
51
+ const e = err;
52
+ console.error(`daemon-reload failed: ${e.stderr || err}`);
53
+ }
54
+ }
55
+ export class LinuxPlatform {
56
+ installDaemon(config) {
57
+ fs.mkdirSync(UNIT_DIR, { recursive: true });
58
+ const palmierBin = process.argv[1] || "palmier";
59
+ const serviceContent = `[Unit]
60
+ Description=Palmier Host
61
+ After=network-online.target
62
+ Wants=network-online.target
63
+
64
+ [Service]
65
+ Type=simple
66
+ ExecStart=${palmierBin} serve
67
+ WorkingDirectory=${config.projectRoot}
68
+ Restart=on-failure
69
+ RestartSec=5
70
+ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
71
+
72
+ [Install]
73
+ WantedBy=default.target
74
+ `;
75
+ const servicePath = path.join(UNIT_DIR, "palmier.service");
76
+ fs.writeFileSync(servicePath, serviceContent, "utf-8");
77
+ console.log("Systemd service installed at:", servicePath);
78
+ try {
79
+ execSync("systemctl --user daemon-reload", { stdio: "inherit" });
80
+ execSync("systemctl --user enable palmier.service", { stdio: "inherit" });
81
+ execSync("systemctl --user restart palmier.service", { stdio: "inherit" });
82
+ console.log("Palmier host service enabled and started.");
83
+ }
84
+ catch (err) {
85
+ console.error(`Warning: failed to enable systemd service: ${err}`);
86
+ console.error("You may need to start it manually: systemctl --user enable --now palmier.service");
87
+ }
88
+ // Enable lingering so service runs without active login session
89
+ try {
90
+ execSync(`loginctl enable-linger ${process.env.USER || ""}`, { stdio: "inherit" });
91
+ console.log("Login lingering enabled.");
92
+ }
93
+ catch (err) {
94
+ console.error(`Warning: failed to enable linger: ${err}`);
95
+ }
96
+ console.log("\nHost initialization complete!");
97
+ }
98
+ async restartDaemon() {
99
+ execSync("systemctl --user restart palmier.service", { stdio: "inherit" });
100
+ console.log("Palmier daemon restarted.");
101
+ }
102
+ installTaskTimer(config, task) {
103
+ fs.mkdirSync(UNIT_DIR, { recursive: true });
104
+ const taskId = task.frontmatter.id;
105
+ const serviceName = getServiceName(taskId);
106
+ const timerName = getTimerName(taskId);
107
+ const palmierBin = process.argv[1] || "palmier";
108
+ const serviceContent = `[Unit]
109
+ Description=Palmier Task: ${taskId}
110
+
111
+ [Service]
112
+ Type=oneshot
113
+ TimeoutStartSec=infinity
114
+ ExecStart=${palmierBin} run ${taskId}
115
+ WorkingDirectory=${config.projectRoot}
116
+ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
117
+ `;
118
+ fs.writeFileSync(path.join(UNIT_DIR, serviceName), serviceContent, "utf-8");
119
+ daemonReload();
120
+ // Only create and enable a timer if triggers exist and are enabled
121
+ if (!task.frontmatter.triggers_enabled)
122
+ return;
123
+ const triggers = task.frontmatter.triggers || [];
124
+ const onCalendarLines = [];
125
+ for (const trigger of triggers) {
126
+ if (trigger.type === "cron") {
127
+ onCalendarLines.push(`OnCalendar=${cronToOnCalendar(trigger.value)}`);
128
+ }
129
+ else if (trigger.type === "once") {
130
+ onCalendarLines.push(`OnActiveSec=${trigger.value}`);
131
+ }
132
+ }
133
+ if (onCalendarLines.length > 0) {
134
+ const timerContent = `[Unit]
135
+ Description=Timer for Palmier Task: ${taskId}
136
+
137
+ [Timer]
138
+ ${onCalendarLines.join("\n")}
139
+ Persistent=true
140
+
141
+ [Install]
142
+ WantedBy=timers.target
143
+ `;
144
+ fs.writeFileSync(path.join(UNIT_DIR, timerName), timerContent, "utf-8");
145
+ daemonReload();
146
+ try {
147
+ execSync(`systemctl --user enable --now ${timerName}`, { encoding: "utf-8" });
148
+ }
149
+ catch (err) {
150
+ const e = err;
151
+ console.error(`Failed to enable timer ${timerName}: ${e.stderr || err}`);
152
+ }
153
+ }
154
+ }
155
+ removeTaskTimer(taskId) {
156
+ const timerName = getTimerName(taskId);
157
+ const serviceName = getServiceName(taskId);
158
+ const timerPath = path.join(UNIT_DIR, timerName);
159
+ const servicePath = path.join(UNIT_DIR, serviceName);
160
+ if (fs.existsSync(timerPath)) {
161
+ try {
162
+ execSync(`systemctl --user stop ${timerName}`, { encoding: "utf-8" });
163
+ }
164
+ catch { /* timer might not be running */ }
165
+ try {
166
+ execSync(`systemctl --user disable ${timerName}`, { encoding: "utf-8" });
167
+ }
168
+ catch { /* timer might not be enabled */ }
169
+ fs.unlinkSync(timerPath);
170
+ }
171
+ if (fs.existsSync(servicePath))
172
+ fs.unlinkSync(servicePath);
173
+ daemonReload();
174
+ }
175
+ async startTask(taskId) {
176
+ const serviceName = getServiceName(taskId);
177
+ await execAsync(`systemctl --user start --no-block ${serviceName}`);
178
+ }
179
+ async stopTask(taskId) {
180
+ const serviceName = getServiceName(taskId);
181
+ await execAsync(`systemctl --user stop ${serviceName}`);
182
+ }
183
+ isTaskRunning(taskId) {
184
+ const serviceName = getServiceName(taskId);
185
+ try {
186
+ // is-active exits 0 only for "active". For oneshot services (Type=oneshot),
187
+ // the state is "activating" while running, which exits non-zero.
188
+ // Use show -p ActiveState to reliably get the state without exit code issues.
189
+ const out = execSync(`systemctl --user show -p ActiveState --value ${serviceName}`, { encoding: "utf-8" });
190
+ const state = out.trim();
191
+ return state === "active" || state === "activating";
192
+ }
193
+ catch {
194
+ return false;
195
+ }
196
+ }
197
+ getGuiEnv() {
198
+ const uid = process.getuid?.();
199
+ const runtimeDir = process.env.XDG_RUNTIME_DIR ||
200
+ (uid !== undefined ? `/run/user/${uid}` : "");
201
+ return {
202
+ DISPLAY: ":0",
203
+ ...(runtimeDir ? { XDG_RUNTIME_DIR: runtimeDir } : {}),
204
+ };
205
+ }
206
+ }
207
+ //# sourceMappingURL=linux.js.map
@@ -0,0 +1,24 @@
1
+ import type { HostConfig, ParsedTask } from "../types.js";
2
+ /**
3
+ * Abstracts OS-specific daemon, scheduling, and process management.
4
+ * Linux uses systemd; Windows uses Task Scheduler; macOS will use launchd.
5
+ */
6
+ export interface PlatformService {
7
+ /** Install the main `palmier serve` daemon to start at boot. */
8
+ installDaemon(config: HostConfig): void;
9
+ /** Restart the `palmier serve` daemon. */
10
+ restartDaemon(): Promise<void>;
11
+ /** Install a scheduled trigger (timer) for a task. */
12
+ installTaskTimer(config: HostConfig, task: ParsedTask): void;
13
+ /** Remove a task's scheduled trigger and service files. */
14
+ removeTaskTimer(taskId: string): void;
15
+ /** Start a task execution (non-blocking). */
16
+ startTask(taskId: string): Promise<void>;
17
+ /** Abort/stop a running task. */
18
+ stopTask(taskId: string): Promise<void>;
19
+ /** Check if a task is currently running via the system scheduler. */
20
+ isTaskRunning(taskId: string): boolean;
21
+ /** Return env vars needed for GUI access (Linux: DISPLAY, etc.). */
22
+ getGuiEnv(): Record<string, string>;
23
+ }
24
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1,14 @@
1
+ import type { PlatformService } from "./platform.js";
2
+ import type { HostConfig, ParsedTask } from "../types.js";
3
+ export declare class WindowsPlatform implements PlatformService {
4
+ installDaemon(config: HostConfig): void;
5
+ restartDaemon(): Promise<void>;
6
+ private spawnDaemon;
7
+ installTaskTimer(config: HostConfig, task: ParsedTask): void;
8
+ removeTaskTimer(taskId: string): void;
9
+ startTask(taskId: string): Promise<void>;
10
+ stopTask(taskId: string): Promise<void>;
11
+ isTaskRunning(taskId: string): boolean;
12
+ getGuiEnv(): Record<string, string>;
13
+ }
14
+ //# sourceMappingURL=windows.d.ts.map