palmier 0.4.2 → 0.4.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.
Files changed (45) hide show
  1. package/README.md +18 -30
  2. package/dist/agents/agent-instructions.md +40 -0
  3. package/dist/agents/claude.js +2 -8
  4. package/dist/agents/codex.js +0 -6
  5. package/dist/agents/copilot.js +0 -20
  6. package/dist/agents/gemini.js +0 -6
  7. package/dist/agents/shared-prompt.d.ts +1 -2
  8. package/dist/agents/shared-prompt.js +5 -18
  9. package/dist/commands/notify.d.ts +9 -0
  10. package/dist/commands/notify.js +43 -0
  11. package/dist/commands/request-input.d.ts +10 -0
  12. package/dist/commands/request-input.js +49 -0
  13. package/dist/commands/run.d.ts +4 -5
  14. package/dist/commands/run.js +90 -105
  15. package/dist/commands/serve.js +31 -28
  16. package/dist/index.js +15 -5
  17. package/dist/platform/linux.js +16 -6
  18. package/dist/platform/windows.js +54 -14
  19. package/dist/rpc-handler.js +217 -54
  20. package/dist/spawn-command.d.ts +1 -1
  21. package/dist/spawn-command.js +13 -1
  22. package/dist/task.d.ts +18 -7
  23. package/dist/task.js +70 -27
  24. package/dist/types.d.ts +10 -1
  25. package/package.json +2 -3
  26. package/src/agents/agent-instructions.md +40 -0
  27. package/src/agents/claude.ts +2 -7
  28. package/src/agents/codex.ts +0 -5
  29. package/src/agents/copilot.ts +0 -19
  30. package/src/agents/gemini.ts +0 -5
  31. package/src/agents/shared-prompt.ts +10 -18
  32. package/src/commands/notify.ts +44 -0
  33. package/src/commands/request-input.ts +51 -0
  34. package/src/commands/run.ts +98 -129
  35. package/src/commands/serve.ts +34 -36
  36. package/src/index.ts +16 -5
  37. package/src/platform/linux.ts +17 -7
  38. package/src/platform/windows.ts +53 -15
  39. package/src/rpc-handler.ts +244 -57
  40. package/src/spawn-command.ts +13 -2
  41. package/src/task.ts +79 -29
  42. package/src/types.ts +11 -1
  43. package/dist/commands/mcpserver.d.ts +0 -2
  44. package/dist/commands/mcpserver.js +0 -93
  45. package/src/commands/mcpserver.ts +0 -113
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  **Website:** [palmier.me](https://www.palmier.me) | **App:** [app.palmier.me](https://app.palmier.me)
8
8
 
9
- A Node.js CLI that lets you run your own AI agents from your phone. It runs on your machine as a persistent daemon, letting you create, schedule, and monitor agent tasks from any device via a cloud relay (NATS) and/or direct HTTP.
9
+ A Node.js CLI that lets you dispatch your own AI agents from your phone. It runs on your machine as a persistent daemon, letting you create, schedule, and monitor agent tasks from any device via a cloud relay (NATS) and/or direct HTTP.
10
10
 
11
11
  > **Important:** By using Palmier, you agree to the [Terms of Service](https://www.palmier.me/terms) and [Privacy Policy](https://www.palmier.me/privacy). See the [Disclaimer](#disclaimer) section below.
12
12
 
@@ -51,7 +51,8 @@ All `palmier` commands should be run from a dedicated Palmier root directory (e.
51
51
  | `palmier serve` | Run the persistent RPC handler (default command) |
52
52
  | `palmier restart` | Restart the palmier serve daemon |
53
53
  | `palmier run <task-id>` | Execute a specific task |
54
- | `palmier mcpserver` | Start an MCP server exposing Palmier tools (stdio transport) |
54
+ | `palmier notify` | Send a push notification to paired devices |
55
+ | `palmier request-input` | Request input from the user during task execution |
55
56
 
56
57
  ## Setup
57
58
 
@@ -126,12 +127,12 @@ palmier restart
126
127
  - **Tasks** are stored locally as Markdown files in a `tasks/` directory. Each task has a name, prompt, execution plan, and optional schedules (cron schedules or one-time dates).
127
128
  - **Plan generation** is automatic — when you create or update a task, the host invokes your chosen agent CLI to generate an execution plan and name.
128
129
  - **Schedules** are backed by systemd timers (Linux) or Task Scheduler (Windows). You can enable/disable them without deleting the task, and any task can still be run manually at any time.
129
- - **Task execution** uses the system scheduler on both platforms — `systemctl --user start` on Linux, `schtasks /run` on Windows. The daemon polls every 30 seconds to detect crashed tasks (processes that exited without updating status) and marks them as failed, broadcasting the failure to connected clients.
130
+ - **Task execution** uses the system scheduler on both platforms — `systemctl --user start` on Linux, `schtasks /run` on Windows. On Windows, tasks run via a VBS wrapper (`wscript.exe`) to avoid visible console windows. The daemon polls every 30 seconds to detect crashed tasks (processes that exited without updating status) and marks them as failed, broadcasting the failure to connected clients.
130
131
  - **Command-triggered tasks** — optionally specify a shell command (e.g., `tail -f /var/log/app.log`). Palmier runs the command continuously and invokes the agent for each line of stdout, passing it alongside your prompt. Useful for log monitoring, event-driven automation, and reactive workflows.
131
132
  - **Task confirmation** — tasks can optionally require your approval before running. You'll get a push notification (server mode) or a prompt in the PWA to confirm or abort.
132
- - **Run history** — each run produces a timestamped result file. You can view results and reports from the PWA.
133
- - **Real-time updates** — task status changes (started, finished, failed) are pushed to connected PWA clients via NATS pub/sub (server mode) and/or SSE (LAN mode).
134
- - **MCP server** (`palmier mcpserver`) exposes platform tools (e.g., `send-push-notification`) to AI agents like Claude Code over stdio.
133
+ - **Conversational run history** — each run produces a timestamped `RESULT-{ts}.md` file with a conversational structure: a sequence of assistant messages (agent output), user messages (input responses, permission grants, confirmations), and status entries (started, finished, failed, aborted). Timing and outcome are derived from status messages — no redundant frontmatter. The PWA displays these as a chat-like thread. Reports are attached per-message.
134
+ - **Real-time updates** — task status changes and result updates are pushed to connected PWA clients via NATS pub/sub (server mode) and/or SSE (LAN mode). The run detail view live-updates as the agent produces output.
135
+ - **Agent CLI commands** `palmier notify` and `palmier request-input` allow agents to send push notifications and request user input during task execution without requiring MCP support.
135
136
 
136
137
  ## NATS Subjects
137
138
 
@@ -158,6 +159,8 @@ src/
158
159
  events.ts # Event broadcasting (NATS pub/sub or HTTP SSE)
159
160
  agents/
160
161
  agent.ts # AgentTool interface, registry, and agent detection
162
+ shared-prompt.ts # Agent instructions loader
163
+ agent-instructions.md # System prompt injected into every agent invocation
161
164
  claude.ts # Claude Code agent implementation
162
165
  gemini.ts # Gemini CLI agent implementation
163
166
  codex.ts # Codex CLI agent implementation
@@ -173,7 +176,8 @@ src/
173
176
  serve.ts # Transport selection, startup, and crash detection polling
174
177
  restart.ts # Daemon restart (cross-platform)
175
178
  run.ts # Single task execution
176
- mcpserver.ts # MCP server with platform tools (send-push-notification)
179
+ notify.ts # Send push notification to paired devices
180
+ request-input.ts # Request user input during task execution
177
181
  platform/
178
182
  platform.ts # PlatformService interface
179
183
  index.ts # Platform factory (Linux vs Windows)
@@ -184,32 +188,16 @@ src/
184
188
  http-transport.ts # HTTP server with RPC, SSE, PWA reverse proxy, and internal event endpoints
185
189
  ```
186
190
 
187
- ## MCP Server
191
+ ## Agent CLI Commands
188
192
 
189
- The host includes an MCP server that exposes Palmier platform tools to AI agents like Claude Code.
193
+ These commands are available to agents during task execution. They are included in the agent's system prompt automatically.
190
194
 
191
- ### Setup
192
-
193
- Add to your Claude Code MCP settings:
194
-
195
- ```json
196
- {
197
- "mcpServers": {
198
- "palmier": {
199
- "command": "palmier",
200
- "args": ["mcpserver"]
201
- }
202
- }
203
- }
204
- ```
205
-
206
- Requires a provisioned host (`palmier init`) with server mode enabled.
207
-
208
- ### Available Tools
209
-
210
- | Tool | Inputs | Description |
195
+ | Command | Flags | Description |
211
196
  |---|---|---|
212
- | `send-push-notification` | `title`, `body` (required) | Send a push notification to all paired devices |
197
+ | `palmier notify` | `--title <title>` `--body <body>` | Send a push notification to all paired devices |
198
+ | `palmier request-input` | `--description <desc...>` | Request input from the user; blocks until a response is provided |
199
+
200
+ Push notifications require server mode to be enabled. `request-input` requires the `PALMIER_TASK_ID` environment variable (set automatically during task execution).
213
201
 
214
202
  ## Uninstalling
215
203
 
@@ -0,0 +1,40 @@
1
+ You are an AI agent executing a task on behalf of the user via the Palmier platform. Follow these instructions carefully.
2
+
3
+ ## Reporting Output
4
+
5
+ If you generate report or output files, print each file path on its own line prefixed with [PALMIER_REPORT]:
6
+ [PALMIER_REPORT] report.md
7
+ [PALMIER_REPORT] summary.md
8
+
9
+ ## Completion
10
+
11
+ When you are done, output exactly one of these markers as the very last line:
12
+ - Success: [PALMIER_TASK_SUCCESS]
13
+ - Failure: [PALMIER_TASK_FAILURE]
14
+ Do not wrap them in code blocks or add text on the same line.
15
+
16
+ ## Permissions
17
+
18
+ If the task fails because a tool was denied or you lack the required permissions, print each required permission on its own line prefixed with [PALMIER_PERMISSION]:
19
+ [PALMIER_PERMISSION] Read | Read file contents from the repository
20
+ [PALMIER_PERMISSION] Bash(npm test) | Run the test suite via npm
21
+ [PALMIER_PERMISSION] Write | Write generated output files
22
+
23
+ ## CLI Commands
24
+
25
+ You have access to the following palmier CLI commands:
26
+
27
+ **Requesting user input** — If you need any information you do not have (credentials, configuration values, preferences, clarifications, etc.) or the task explicitly asks you to get input from the user, do NOT fail the task. Instead, request it:
28
+ ```
29
+ palmier request-input --description "What is the database connection string?" --description "What is the API key?"
30
+ ```
31
+ The command blocks until the user responds and prints each value on its own line. If the user aborts, the command exits with a non-zero status.
32
+
33
+ **Sending push notifications** — If you need to send a push notification to the user:
34
+ ```
35
+ palmier notify --title "Task Complete" --body "The deployment finished successfully."
36
+ ```
37
+
38
+ ---
39
+
40
+ The task to execute follows below.
@@ -9,8 +9,8 @@ export class ClaudeAgent {
9
9
  };
10
10
  }
11
11
  getTaskRunCommandLine(task, retryPrompt, extraPermissions) {
12
- const prompt = retryPrompt ?? (task.body || task.frontmatter.user_prompt);
13
- const args = ["--permission-mode", "acceptEdits", "--append-system-prompt", AGENT_INSTRUCTIONS, "-p"];
12
+ const prompt = AGENT_INSTRUCTIONS + "\n\n" + (retryPrompt ?? (task.body || task.frontmatter.user_prompt));
13
+ const args = ["--permission-mode", "acceptEdits", "-p"];
14
14
  const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
15
15
  for (const p of allPerms) {
16
16
  args.push("--allowedTools", p.name);
@@ -27,12 +27,6 @@ export class ClaudeAgent {
27
27
  catch {
28
28
  return false;
29
29
  }
30
- try {
31
- execSync("claude mcp add --transport stdio palmier --scope user -- palmier mcpserver", { stdio: "ignore", shell: SHELL });
32
- }
33
- catch {
34
- // MCP registration is best-effort; agent still works without it
35
- }
36
30
  return true;
37
31
  }
38
32
  }
@@ -30,12 +30,6 @@ export class CodexAgent {
30
30
  catch {
31
31
  return false;
32
32
  }
33
- try {
34
- execSync("codex mcp add palmier palmier mcpserver", { stdio: "ignore", shell: SHELL });
35
- }
36
- catch {
37
- // MCP registration is best-effort; agent still works without it
38
- }
39
33
  return true;
40
34
  }
41
35
  }
@@ -1,6 +1,3 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { homedir } from "os";
4
1
  import { execSync } from "child_process";
5
2
  import { AGENT_INSTRUCTIONS } from "./shared-prompt.js";
6
3
  import { SHELL } from "../platform/index.js";
@@ -31,23 +28,6 @@ export class CopilotAgent {
31
28
  catch {
32
29
  return false;
33
30
  }
34
- // Register Palmier MCP server in ~/.copilot/mcp-config.json
35
- try {
36
- const configDir = path.join(homedir(), ".copilot");
37
- const configFile = path.join(configDir, "mcp-config.json");
38
- let config = {};
39
- if (fs.existsSync(configFile)) {
40
- config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
41
- }
42
- const servers = (config.mcpServers ?? {});
43
- servers.palmier = { command: "palmier", args: ["mcpserver"] };
44
- config.mcpServers = servers;
45
- fs.mkdirSync(configDir, { recursive: true });
46
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2), "utf-8");
47
- }
48
- catch {
49
- // MCP registration is best-effort
50
- }
51
31
  return true;
52
32
  }
53
33
  }
@@ -31,12 +31,6 @@ export class GeminiAgent {
31
31
  catch {
32
32
  return false;
33
33
  }
34
- try {
35
- execSync("gemini mcp add --scope user palmier palmier mcpserver", { stdio: "ignore", shell: SHELL });
36
- }
37
- catch {
38
- // MCP registration is best-effort; agent still works without it
39
- }
40
34
  return true;
41
35
  }
42
36
  }
@@ -3,10 +3,9 @@
3
3
  * Instructs the agent to output structured markers so palmier can determine
4
4
  * the task outcome, report files, and permission/input requests.
5
5
  */
6
- export declare const AGENT_INSTRUCTIONS = "If you generate report or output files, print each file name on its own line prefixed with [PALMIER_REPORT]: e.g.\n[PALMIER_REPORT] report.md\n[PALMIER_REPORT] summary.md\n\nWhen you are done, output exactly one of these markers as the very last line:\n- Success: [PALMIER_TASK_SUCCESS]\n- Failure: [PALMIER_TASK_FAILURE]\nDo not wrap them in code blocks or add text on the same line.\n\nIf the task fails because a tool was denied or you lack the required permissions, print each required permission on its own line prefixed with [PALMIER_PERMISSION]: e.g.\n[PALMIER_PERMISSION] Read | Read file contents from the repository\n[PALMIER_PERMISSION] Bash(npm test) | Run the test suite via npm\n[PALMIER_PERMISSION] Write | Write generated output files\n\nIf the task requires information from the user that you do not have (such as credentials, connection strings, API keys, or configuration values), print each required input on its own line prefixed with [PALMIER_INPUT]: e.g.\n[PALMIER_INPUT] What is the database connection string?\n[PALMIER_INPUT] What is the API key for the external service?";
6
+ export declare const AGENT_INSTRUCTIONS: string;
7
7
  export declare const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
8
8
  export declare const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
9
9
  export declare const TASK_REPORT_PREFIX = "[PALMIER_REPORT]";
10
10
  export declare const TASK_PERMISSION_PREFIX = "[PALMIER_PERMISSION]";
11
- export declare const TASK_INPUT_PREFIX = "[PALMIER_INPUT]";
12
11
  //# sourceMappingURL=shared-prompt.d.ts.map
@@ -1,28 +1,15 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { fileURLToPath } from "url";
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1
5
  /**
2
6
  * Instructions prepended or injected as system prompt for every task invocation.
3
7
  * Instructs the agent to output structured markers so palmier can determine
4
8
  * the task outcome, report files, and permission/input requests.
5
9
  */
6
- export const AGENT_INSTRUCTIONS = `If you generate report or output files, print each file name on its own line prefixed with [PALMIER_REPORT]: e.g.
7
- [PALMIER_REPORT] report.md
8
- [PALMIER_REPORT] summary.md
9
-
10
- When you are done, output exactly one of these markers as the very last line:
11
- - Success: [PALMIER_TASK_SUCCESS]
12
- - Failure: [PALMIER_TASK_FAILURE]
13
- Do not wrap them in code blocks or add text on the same line.
14
-
15
- If the task fails because a tool was denied or you lack the required permissions, print each required permission on its own line prefixed with [PALMIER_PERMISSION]: e.g.
16
- [PALMIER_PERMISSION] Read | Read file contents from the repository
17
- [PALMIER_PERMISSION] Bash(npm test) | Run the test suite via npm
18
- [PALMIER_PERMISSION] Write | Write generated output files
19
-
20
- If the task requires information from the user that you do not have (such as credentials, connection strings, API keys, or configuration values), print each required input on its own line prefixed with [PALMIER_INPUT]: e.g.
21
- [PALMIER_INPUT] What is the database connection string?
22
- [PALMIER_INPUT] What is the API key for the external service?`;
10
+ export const AGENT_INSTRUCTIONS = fs.readFileSync(path.join(__dirname, "agent-instructions.md"), "utf-8");
23
11
  export const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
24
12
  export const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
25
13
  export const TASK_REPORT_PREFIX = "[PALMIER_REPORT]";
26
14
  export const TASK_PERMISSION_PREFIX = "[PALMIER_PERMISSION]";
27
- export const TASK_INPUT_PREFIX = "[PALMIER_INPUT]";
28
15
  //# sourceMappingURL=shared-prompt.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Send a push notification to the user via NATS.
3
+ * Usage: palmier notify --title "Title" --body "Body text"
4
+ */
5
+ export declare function notifyCommand(opts: {
6
+ title: string;
7
+ body: string;
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1,43 @@
1
+ import { StringCodec } from "nats";
2
+ import { loadConfig } from "../config.js";
3
+ import { connectNats } from "../nats-client.js";
4
+ /**
5
+ * Send a push notification to the user via NATS.
6
+ * Usage: palmier notify --title "Title" --body "Body text"
7
+ */
8
+ export async function notifyCommand(opts) {
9
+ const config = loadConfig();
10
+ const nc = await connectNats(config);
11
+ if (!nc) {
12
+ console.error("Error: NATS connection required for push notifications.");
13
+ process.exit(1);
14
+ }
15
+ const sc = StringCodec();
16
+ const payload = {
17
+ hostId: config.hostId,
18
+ title: opts.title,
19
+ body: opts.body,
20
+ };
21
+ try {
22
+ const subject = `host.${config.hostId}.push.send`;
23
+ const reply = await nc.request(subject, sc.encode(JSON.stringify(payload)), {
24
+ timeout: 15_000,
25
+ });
26
+ const result = JSON.parse(sc.decode(reply.data));
27
+ if (result.ok) {
28
+ console.log("Push notification sent successfully.");
29
+ }
30
+ else {
31
+ console.error(`Failed to send push notification: ${result.error}`);
32
+ process.exit(1);
33
+ }
34
+ }
35
+ catch (err) {
36
+ console.error(`Error sending push notification: ${err}`);
37
+ process.exit(1);
38
+ }
39
+ finally {
40
+ await nc.drain();
41
+ }
42
+ }
43
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Request input from the user and print responses to stdout.
3
+ * Usage: palmier request-input --description "Question 1" --description "Question 2"
4
+ *
5
+ * Requires PALMIER_TASK_ID and PALMIER_RUN_DIR environment variables.
6
+ */
7
+ export declare function requestInputCommand(opts: {
8
+ description: string[];
9
+ }): Promise<void>;
10
+ //# sourceMappingURL=request-input.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { loadConfig } from "../config.js";
2
+ import { connectNats } from "../nats-client.js";
3
+ import { getTaskDir, parseTaskFile, appendRunMessage } from "../task.js";
4
+ import { requestUserInput, publishInputResolved } from "../user-input.js";
5
+ /**
6
+ * Request input from the user and print responses to stdout.
7
+ * Usage: palmier request-input --description "Question 1" --description "Question 2"
8
+ *
9
+ * Requires PALMIER_TASK_ID and PALMIER_RUN_DIR environment variables.
10
+ */
11
+ export async function requestInputCommand(opts) {
12
+ const taskId = process.env.PALMIER_TASK_ID;
13
+ if (!taskId) {
14
+ console.error("Error: PALMIER_TASK_ID environment variable is not set.");
15
+ process.exit(1);
16
+ }
17
+ const config = loadConfig();
18
+ const nc = await connectNats(config);
19
+ const taskDir = getTaskDir(config.projectRoot, taskId);
20
+ const task = parseTaskFile(taskDir);
21
+ const runId = process.env.PALMIER_RUN_DIR?.split(/[/\\]/).pop();
22
+ try {
23
+ const response = await requestUserInput(nc, config, taskId, task.frontmatter.name, taskDir, opts.description);
24
+ await publishInputResolved(nc, config, taskId, response === "aborted" ? "aborted" : "provided");
25
+ if (response === "aborted") {
26
+ if (runId) {
27
+ appendRunMessage(taskDir, runId, { role: "user", time: Date.now(), content: "Input request aborted.", type: "input" });
28
+ }
29
+ console.error("User aborted the input request.");
30
+ process.exit(1);
31
+ }
32
+ if (runId) {
33
+ const lines = opts.description.map((desc, i) => `**${desc}** ${response[i]}`);
34
+ appendRunMessage(taskDir, runId, { role: "user", time: Date.now(), content: lines.join("\n"), type: "input" });
35
+ }
36
+ for (let i = 0; i < opts.description.length; i++) {
37
+ console.log(response[i]);
38
+ }
39
+ }
40
+ catch (err) {
41
+ console.error(`Error requesting user input: ${err}`);
42
+ process.exit(1);
43
+ }
44
+ finally {
45
+ if (nc)
46
+ await nc.drain();
47
+ }
48
+ }
49
+ //# sourceMappingURL=request-input.js.map
@@ -1,4 +1,8 @@
1
1
  import type { TaskRunningState, RequiredPermission } from "../types.js";
2
+ /**
3
+ * Strip [PALMIER_*] marker lines from agent output.
4
+ */
5
+ export declare function stripPalmierMarkers(output: string): string;
2
6
  /**
3
7
  * Execute a task by ID.
4
8
  */
@@ -13,11 +17,6 @@ export declare function parseReportFiles(output: string): string[];
13
17
  * Looks for lines matching: [PALMIER_PERMISSION] <tool> | <description>
14
18
  */
15
19
  export declare function parsePermissions(output: string): RequiredPermission[];
16
- /**
17
- * Extract user input requests from agent output.
18
- * Looks for lines matching: [PALMIER_INPUT] <description>
19
- */
20
- export declare function parseInputRequests(output: string): string[];
21
20
  /**
22
21
  * Parse the agent's output for success/failure markers.
23
22
  * Falls back to "finished" if no marker is found.