palmier 0.4.4 → 0.4.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.
- package/README.md +32 -33
- package/dist/agents/agent-instructions.md +4 -11
- package/dist/agents/agent.d.ts +2 -2
- package/dist/agents/claude.d.ts +1 -1
- package/dist/agents/claude.js +6 -6
- package/dist/agents/codex.d.ts +1 -1
- package/dist/agents/codex.js +5 -5
- package/dist/agents/copilot.d.ts +1 -1
- package/dist/agents/copilot.js +5 -5
- package/dist/agents/gemini.d.ts +1 -1
- package/dist/agents/gemini.js +7 -7
- package/dist/agents/openclaw.d.ts +1 -1
- package/dist/agents/openclaw.js +3 -3
- package/dist/agents/shared-prompt.d.ts +2 -4
- package/dist/agents/shared-prompt.js +9 -4
- package/dist/commands/init.js +31 -2
- package/dist/commands/pair.d.ts +1 -1
- package/dist/commands/pair.js +12 -15
- package/dist/commands/run.js +33 -54
- package/dist/commands/serve.d.ts +1 -1
- package/dist/commands/serve.js +9 -2
- package/dist/events.d.ts +2 -2
- package/dist/events.js +15 -16
- package/dist/index.js +0 -25
- package/dist/pending-requests.d.ts +27 -0
- package/dist/pending-requests.js +39 -0
- package/dist/rpc-handler.js +15 -8
- package/dist/transports/http-transport.d.ts +4 -2
- package/dist/transports/http-transport.js +226 -77
- package/dist/types.d.ts +7 -16
- package/package.json +1 -1
- package/src/agents/agent-instructions.md +4 -11
- package/src/agents/agent.ts +2 -2
- package/src/agents/claude.ts +5 -5
- package/src/agents/codex.ts +4 -4
- package/src/agents/copilot.ts +5 -5
- package/src/agents/gemini.ts +6 -6
- package/src/agents/openclaw.ts +3 -3
- package/src/agents/shared-prompt.ts +12 -6
- package/src/commands/init.ts +34 -3
- package/src/commands/pair.ts +11 -14
- package/src/commands/run.ts +31 -68
- package/src/commands/serve.ts +11 -2
- package/src/events.ts +14 -15
- package/src/index.ts +0 -26
- package/src/pending-requests.ts +55 -0
- package/src/rpc-handler.ts +15 -9
- package/src/transports/http-transport.ts +235 -135
- package/src/types.ts +10 -16
- package/test/agent-output-parsing.test.ts +1 -14
- package/dist/commands/lan.d.ts +0 -8
- package/dist/commands/lan.js +0 -44
- package/dist/commands/notify.d.ts +0 -9
- package/dist/commands/notify.js +0 -43
- package/dist/commands/request-input.d.ts +0 -10
- package/dist/commands/request-input.js +0 -49
- package/dist/lan-lock.d.ts +0 -7
- package/dist/lan-lock.js +0 -18
- package/dist/user-input.d.ts +0 -15
- package/dist/user-input.js +0 -50
- package/src/commands/lan.ts +0 -48
- package/src/commands/notify.ts +0 -44
- package/src/commands/request-input.ts +0 -51
- package/src/lan-lock.ts +0 -16
- package/src/user-input.ts +0 -67
package/README.md
CHANGED
|
@@ -10,18 +10,21 @@ A Node.js CLI that lets you dispatch your own AI agents from your phone. It runs
|
|
|
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
|
|
|
13
|
-
##
|
|
13
|
+
## Access Modes
|
|
14
14
|
|
|
15
|
-
The
|
|
15
|
+
The serve daemon always runs a local HTTP server. Three access modes are available:
|
|
16
16
|
|
|
17
|
-
| Mode | Transport |
|
|
18
|
-
|
|
19
|
-
| **
|
|
20
|
-
| **LAN** | HTTP (direct
|
|
17
|
+
| Mode | Transport | URL | Pairing | Features |
|
|
18
|
+
|------|-----------|-----|---------|----------|
|
|
19
|
+
| **Local** | HTTP (localhost) | `http://localhost:<port>` | Not required | Full access from the host machine, no internet needed |
|
|
20
|
+
| **LAN** | HTTP (direct) | `http://<host-ip>:<port>` | Required | Access from other devices on the local network |
|
|
21
|
+
| **Server** | Cloud relay (NATS) | `https://app.palmier.me` | Required | Push notifications, remote access from anywhere |
|
|
21
22
|
|
|
22
|
-
**
|
|
23
|
+
**Local mode** is always available. The PWA is served at `http://localhost:<port>` and works without pairing or internet. The daemon binds to `127.0.0.1` by default.
|
|
23
24
|
|
|
24
|
-
**LAN mode** is
|
|
25
|
+
**LAN mode** is enabled during `palmier init`. The daemon binds to `0.0.0.0` instead, making the PWA and API endpoints accessible from the local network at `http://<host-ip>:<port>`. Devices must pair via OTP to access. Push notifications are not available.
|
|
26
|
+
|
|
27
|
+
**Server mode** relays communication through the Palmier cloud server (via [NATS](https://nats.io), a lightweight messaging system). All features including push notifications are available. The PWA is served over HTTPS. Server mode and LAN mode can be active at the same time.
|
|
25
28
|
|
|
26
29
|
## Prerequisites
|
|
27
30
|
|
|
@@ -42,8 +45,7 @@ All `palmier` commands should be run from a dedicated Palmier root directory (e.
|
|
|
42
45
|
| Command | Description |
|
|
43
46
|
|---|---|
|
|
44
47
|
| `palmier init` | Interactive setup wizard |
|
|
45
|
-
| `palmier pair` | Generate an OTP code to pair a new device
|
|
46
|
-
| `palmier lan` | Start an on-demand LAN server with built-in pairing |
|
|
48
|
+
| `palmier pair` | Generate an OTP code to pair a new device |
|
|
47
49
|
| `palmier sessions list` | List active session tokens |
|
|
48
50
|
| `palmier sessions revoke <token>` | Revoke a specific session token |
|
|
49
51
|
| `palmier sessions revoke-all` | Revoke all session tokens |
|
|
@@ -51,8 +53,6 @@ All `palmier` commands should be run from a dedicated Palmier root directory (e.
|
|
|
51
53
|
| `palmier serve` | Run the persistent RPC handler (default command) |
|
|
52
54
|
| `palmier restart` | Restart the palmier serve daemon |
|
|
53
55
|
| `palmier run <task-id>` | Execute a specific task |
|
|
54
|
-
| `palmier notify` | Send a push notification to paired devices |
|
|
55
|
-
| `palmier request-input` | Request input from the user during task execution |
|
|
56
56
|
|
|
57
57
|
## Setup
|
|
58
58
|
|
|
@@ -60,14 +60,15 @@ All `palmier` commands should be run from a dedicated Palmier root directory (e.
|
|
|
60
60
|
|
|
61
61
|
1. Install the host: `npm install -g palmier`
|
|
62
62
|
2. Run `palmier init` in your Palmier root directory (e.g., `~/palmier`).
|
|
63
|
-
3. The wizard detects installed agents, registers with the Palmier server, installs a background daemon
|
|
64
|
-
4.
|
|
63
|
+
3. The wizard detects installed agents, configures access modes, registers with the Palmier server, and installs a background daemon.
|
|
64
|
+
4. Open `http://localhost:<port>` to access the app locally — no pairing needed.
|
|
65
|
+
5. To access from other devices, pair via `palmier pair` (run automatically after init).
|
|
65
66
|
|
|
66
|
-
### Pairing
|
|
67
|
+
### Pairing devices
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
Local access (`http://localhost:<port>`) works immediately — no pairing needed.
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
For LAN or server mode, run `palmier pair` on the host to generate an OTP code. Enter it in the PWA — either at `http://<host-ip>:<port>` (LAN mode) or `https://app.palmier.me` (server mode).
|
|
71
72
|
|
|
72
73
|
### Managing sessions
|
|
73
74
|
|
|
@@ -123,23 +124,24 @@ palmier restart
|
|
|
123
124
|
## How It Works
|
|
124
125
|
|
|
125
126
|
- The host runs as a **background daemon** (systemd user service on Linux, Registry Run key on Windows), staying alive via `palmier serve`.
|
|
126
|
-
- **
|
|
127
|
+
- **Device access** — localhost is always trusted (no pairing needed). LAN and server mode devices communicate via direct HTTP or NATS respectively, and must pair via OTP to get a session token.
|
|
127
128
|
- **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).
|
|
128
129
|
- **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.
|
|
129
130
|
- **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.
|
|
130
131
|
- **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.
|
|
131
132
|
- **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.
|
|
132
133
|
- **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.
|
|
133
|
-
- **Conversational run history** — each run
|
|
134
|
-
- **
|
|
135
|
-
- **
|
|
134
|
+
- **Conversational run history** — each run gets its own directory (`tasks/<id>/<timestamp>/`) with a `TASKRUN.md` file containing a conversational thread: assistant messages (agent output), user messages (input responses, permission grants, confirmations), and status entries (started, finished, failed, aborted, stopped). The agent runs inside the run directory, so each run's session files and artifacts are isolated. The PWA displays runs as a chat-like thread with follow-up support.
|
|
135
|
+
- **Follow-up messages** — after a task run completes, users can send follow-up messages from the run detail view. The agent is invoked inline by the serve daemon (no new process spawning), and the response is appended to the same conversation thread.
|
|
136
|
+
- **Real-time updates** — task status changes and result updates are pushed to connected PWA clients via NATS pub/sub (server mode) and/or SSE (local/LAN mode). The run detail view live-updates as the agent produces output. Events are scoped to specific runs.
|
|
137
|
+
- **Agent HTTP endpoints** — the serve daemon exposes localhost-only endpoints (`/notify`, `/request-input`) that agents call to send push notifications and request user input during task execution.
|
|
136
138
|
|
|
137
139
|
## NATS Subjects
|
|
138
140
|
|
|
139
141
|
| Subject | Direction | Description |
|
|
140
142
|
|---|---|---|
|
|
141
143
|
| `host.<hostId>.rpc.<method>` | Client → Host | RPC request/reply (e.g., `task.list`, `task.create`) |
|
|
142
|
-
| `host-event.<hostId>.<taskId>` | Host → Client | Real-time task events (`running-state`, `confirm-request`, `permission-request`, `input-request`) |
|
|
144
|
+
| `host-event.<hostId>.<taskId>` | Host → Client | Real-time task events (`running-state`, `result-updated`, `confirm-request`, `permission-request`, `input-request`) |
|
|
143
145
|
| `host.<hostId>.push.send` | Host → Server | Request server to deliver a push notification |
|
|
144
146
|
| `pair.<code>` | Client → Host | OTP pairing request/reply |
|
|
145
147
|
|
|
@@ -155,7 +157,7 @@ src/
|
|
|
155
157
|
spawn-command.ts # Shared helper for spawning CLI tools
|
|
156
158
|
task.ts # Task file management
|
|
157
159
|
types.ts # Shared type definitions
|
|
158
|
-
|
|
160
|
+
pending-requests.ts # In-memory registry for held HTTP connections (confirmation, permission, input)
|
|
159
161
|
events.ts # Event broadcasting (NATS pub/sub or HTTP SSE)
|
|
160
162
|
agents/
|
|
161
163
|
agent.ts # AgentTool interface, registry, and agent detection
|
|
@@ -169,15 +171,12 @@ src/
|
|
|
169
171
|
commands/
|
|
170
172
|
init.ts # Interactive setup wizard (auto-pair)
|
|
171
173
|
pair.ts # OTP code generation and pairing handler
|
|
172
|
-
lan.ts # On-demand LAN server
|
|
173
174
|
sessions.ts # Session token management CLI (list, revoke, revoke-all)
|
|
174
175
|
info.ts # Print host connection info
|
|
175
176
|
|
|
176
|
-
serve.ts #
|
|
177
|
+
serve.ts # NATS + HTTP transport startup, crash detection polling
|
|
177
178
|
restart.ts # Daemon restart (cross-platform)
|
|
178
179
|
run.ts # Single task execution
|
|
179
|
-
notify.ts # Send push notification to paired devices
|
|
180
|
-
request-input.ts # Request user input during task execution
|
|
181
180
|
platform/
|
|
182
181
|
platform.ts # PlatformService interface
|
|
183
182
|
index.ts # Platform factory (Linux vs Windows)
|
|
@@ -188,16 +187,16 @@ src/
|
|
|
188
187
|
http-transport.ts # HTTP server with RPC, SSE, PWA reverse proxy, and internal event endpoints
|
|
189
188
|
```
|
|
190
189
|
|
|
191
|
-
## Agent
|
|
190
|
+
## Agent HTTP Endpoints
|
|
192
191
|
|
|
193
|
-
|
|
192
|
+
The serve daemon exposes localhost-only HTTP endpoints for agents during task execution. The port is baked into the agent's system prompt automatically.
|
|
194
193
|
|
|
195
|
-
|
|
|
194
|
+
| Endpoint | Method | Description |
|
|
196
195
|
|---|---|---|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
196
|
+
| `/notify` | GET | Send a push notification (requires server mode) |
|
|
197
|
+
| `/request-input` | GET | Request user input; blocks until a response is provided |
|
|
199
198
|
|
|
200
|
-
|
|
199
|
+
See [agent-instructions.md](src/agents/agent-instructions.md) for usage examples.
|
|
201
200
|
|
|
202
201
|
## Uninstalling
|
|
203
202
|
|
|
@@ -20,20 +20,13 @@ If the task fails because a tool was denied or you lack the required permissions
|
|
|
20
20
|
[PALMIER_PERMISSION] Bash(npm test) | Run the test suite via npm
|
|
21
21
|
[PALMIER_PERMISSION] Write | Write generated output files
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## HTTP Endpoints
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The following HTTP endpoints are available at http://localhost:{{PORT}} during task execution.
|
|
26
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
|
|
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.
|
|
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, GET `/request-input?taskId={{TASK_ID}}&descriptions=question+1&descriptions=question+2`. The request blocks until the user responds. The response is `{"values":["answer1","answer2"]}` on success, or `{"aborted":true}` if the user chooses to abort.
|
|
32
28
|
|
|
33
|
-
**Sending push notifications** —
|
|
34
|
-
```
|
|
35
|
-
palmier notify --title "Task Complete" --body "The deployment finished successfully."
|
|
36
|
-
```
|
|
29
|
+
**Sending push notifications** — GET `/notify?title=...&body=...` to send a push notification to the user's devices.
|
|
37
30
|
|
|
38
31
|
---
|
|
39
32
|
|
package/dist/agents/agent.d.ts
CHANGED
|
@@ -12,10 +12,10 @@ export interface CommandLine {
|
|
|
12
12
|
export interface AgentTool {
|
|
13
13
|
/** Return the command and args used to generate a plan from a prompt. */
|
|
14
14
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
15
|
-
/** Return the command and args used to run a task. If
|
|
15
|
+
/** Return the command and args used to run a task. If followupPrompt is provided, use it instead of the task's prompt,
|
|
16
16
|
* and treat it as a continuation of the original run (reuse the same session, etc). extraPermissions are transient
|
|
17
17
|
* permissions granted for this run only (not persisted in frontmatter). */
|
|
18
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
18
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
19
19
|
/** Detect whether the agent CLI is available and perform any agent-specific
|
|
20
20
|
* initialization. Returns true if the agent was detected and initialized successfully. */
|
|
21
21
|
init(): Promise<boolean>;
|
package/dist/agents/claude.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class ClaudeAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=claude.d.ts.map
|
package/dist/agents/claude.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { getAgentInstructions } from "./shared-prompt.js";
|
|
3
3
|
import { SHELL } from "../platform/index.js";
|
|
4
4
|
export class ClaudeAgent {
|
|
5
5
|
getPlanGenerationCommandLine(prompt) {
|
|
@@ -8,16 +8,16 @@ export class ClaudeAgent {
|
|
|
8
8
|
args: ["-p", prompt],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
getTaskRunCommandLine(task,
|
|
12
|
-
const prompt =
|
|
13
|
-
const args = ["--permission-mode", "acceptEdits", "-p"];
|
|
11
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
12
|
+
const prompt = getAgentInstructions(task.frontmatter.id) + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
13
|
+
const args = ["--permission-mode", "acceptEdits", "-p", "--allowedTools", "WebFetch"];
|
|
14
14
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
15
15
|
for (const p of allPerms) {
|
|
16
16
|
args.push("--allowedTools", p.name);
|
|
17
17
|
}
|
|
18
|
-
if (
|
|
18
|
+
if (followupPrompt) {
|
|
19
19
|
args.push("-c");
|
|
20
|
-
} // continue mode for
|
|
20
|
+
} // continue mode for followups
|
|
21
21
|
return { command: "claude", args, stdin: prompt };
|
|
22
22
|
}
|
|
23
23
|
async init() {
|
package/dist/agents/codex.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class CodexAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=codex.d.ts.map
|
package/dist/agents/codex.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { getAgentInstructions } from "./shared-prompt.js";
|
|
3
3
|
import { SHELL } from "../platform/index.js";
|
|
4
4
|
export class CodexAgent {
|
|
5
5
|
getPlanGenerationCommandLine(prompt) {
|
|
@@ -8,8 +8,8 @@ export class CodexAgent {
|
|
|
8
8
|
args: ["exec", "--skip-git-repo-check", prompt],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
getTaskRunCommandLine(task,
|
|
12
|
-
const prompt =
|
|
11
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
12
|
+
const prompt = getAgentInstructions(task.frontmatter.id) + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
13
13
|
// Using danger-full-access until workspace-write is fixed: https://github.com/openai/codex/issues/12572
|
|
14
14
|
const args = ["exec", "--full-auto", "--skip-git-repo-check", "--sandbox", "danger-full-access"];
|
|
15
15
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
@@ -18,9 +18,9 @@ export class CodexAgent {
|
|
|
18
18
|
args.push(`apps.${p.name}.default_tools_approval_mode="approve"`);
|
|
19
19
|
}
|
|
20
20
|
args.push("-"); // read prompt from stdin
|
|
21
|
-
if (
|
|
21
|
+
if (followupPrompt) {
|
|
22
22
|
args.push("resume", "--last");
|
|
23
|
-
} // continue mode for
|
|
23
|
+
} // continue mode for followups
|
|
24
24
|
return { command: "codex", args, stdin: prompt };
|
|
25
25
|
}
|
|
26
26
|
async init() {
|
package/dist/agents/copilot.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class CopilotAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=copilot.d.ts.map
|
package/dist/agents/copilot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { getAgentInstructions } from "./shared-prompt.js";
|
|
3
3
|
import { SHELL } from "../platform/index.js";
|
|
4
4
|
export class CopilotAgent {
|
|
5
5
|
getPlanGenerationCommandLine(prompt) {
|
|
@@ -8,15 +8,15 @@ export class CopilotAgent {
|
|
|
8
8
|
args: ["-p", prompt],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
getTaskRunCommandLine(task,
|
|
12
|
-
const prompt =
|
|
13
|
-
const args = ["-p", prompt];
|
|
11
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
12
|
+
const prompt = getAgentInstructions(task.frontmatter.id) + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
13
|
+
const args = ["-p", prompt, "--allowed-tools", "web_fetch"];
|
|
14
14
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
15
15
|
if (allPerms.length > 0) {
|
|
16
16
|
args.push(`--allow-tool='${allPerms.map((p) => p.name).join(",")}'`);
|
|
17
17
|
;
|
|
18
18
|
}
|
|
19
|
-
if (
|
|
19
|
+
if (followupPrompt) {
|
|
20
20
|
args.push("--continue");
|
|
21
21
|
}
|
|
22
22
|
return { command: "copilot", args };
|
package/dist/agents/gemini.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class GeminiAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=gemini.d.ts.map
|
package/dist/agents/gemini.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { getAgentInstructions } from "./shared-prompt.js";
|
|
3
3
|
import { SHELL } from "../platform/index.js";
|
|
4
4
|
export class GeminiAgent {
|
|
5
5
|
getPlanGenerationCommandLine(prompt) {
|
|
@@ -8,10 +8,10 @@ export class GeminiAgent {
|
|
|
8
8
|
args: ["--approval-mode", "auto_edit", "--prompt", prompt],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
getTaskRunCommandLine(task,
|
|
12
|
-
const prompt =
|
|
13
|
-
const fullPrompt =
|
|
14
|
-
const args = ["--prompt", "-"];
|
|
11
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
12
|
+
const prompt = followupPrompt ?? (task.body || task.frontmatter.user_prompt);
|
|
13
|
+
const fullPrompt = getAgentInstructions(task.frontmatter.id) + "\n\n" + prompt;
|
|
14
|
+
const args = ["--prompt", "--allowed-tools", "web_fetch", "-"];
|
|
15
15
|
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
16
16
|
if (allPerms.length > 0) {
|
|
17
17
|
args.push("--allowed-tools");
|
|
@@ -19,9 +19,9 @@ export class GeminiAgent {
|
|
|
19
19
|
args.push(p.name);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
if (
|
|
22
|
+
if (followupPrompt) {
|
|
23
23
|
args.push("--resume");
|
|
24
|
-
} // continue mode for
|
|
24
|
+
} // continue mode for followups
|
|
25
25
|
return { command: "gemini", args, stdin: fullPrompt };
|
|
26
26
|
}
|
|
27
27
|
async init() {
|
|
@@ -2,7 +2,7 @@ import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
|
2
2
|
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
3
|
export declare class OpenClawAgent implements AgentTool {
|
|
4
4
|
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
-
getTaskRunCommandLine(task: ParsedTask,
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
6
|
init(): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=openclaw.d.ts.map
|
package/dist/agents/openclaw.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { getAgentInstructions } from "./shared-prompt.js";
|
|
3
3
|
export class OpenClawAgent {
|
|
4
4
|
getPlanGenerationCommandLine(prompt) {
|
|
5
5
|
return {
|
|
@@ -7,8 +7,8 @@ export class OpenClawAgent {
|
|
|
7
7
|
args: ["agent", "--local", "--agent", "main", "--message", prompt],
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
-
getTaskRunCommandLine(task,
|
|
11
|
-
const prompt =
|
|
10
|
+
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
11
|
+
const prompt = getAgentInstructions(task.frontmatter.id) + "\n\n" + (followupPrompt ?? (task.body || task.frontmatter.user_prompt));
|
|
12
12
|
// OpenClaw does not support stdin as prompt.
|
|
13
13
|
const args = ["agent", "--local", "--session-id", task.frontmatter.id, "--message", prompt];
|
|
14
14
|
return { command: "openclaw", args };
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Instructs the agent to output structured markers so palmier can determine
|
|
4
|
-
* the task outcome, report files, and permission/input requests.
|
|
2
|
+
* Agent instructions with the serve daemon's HTTP port and task ID baked in.
|
|
5
3
|
*/
|
|
6
|
-
export declare
|
|
4
|
+
export declare function getAgentInstructions(taskId: string): string;
|
|
7
5
|
export declare const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
|
|
8
6
|
export declare const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
|
|
9
7
|
export declare const TASK_REPORT_PREFIX = "[PALMIER_REPORT]";
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
import { loadConfig } from "../config.js";
|
|
4
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const AGENT_INSTRUCTIONS_TEMPLATE = fs.readFileSync(path.join(__dirname, "agent-instructions.md"), "utf-8");
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Instructs the agent to output structured markers so palmier can determine
|
|
8
|
-
* the task outcome, report files, and permission/input requests.
|
|
8
|
+
* Agent instructions with the serve daemon's HTTP port and task ID baked in.
|
|
9
9
|
*/
|
|
10
|
-
export
|
|
10
|
+
export function getAgentInstructions(taskId) {
|
|
11
|
+
const port = loadConfig().httpPort ?? 7400;
|
|
12
|
+
return AGENT_INSTRUCTIONS_TEMPLATE
|
|
13
|
+
.replace(/\{\{PORT\}\}/g, String(port))
|
|
14
|
+
.replace(/\{\{TASK_ID\}\}/g, taskId);
|
|
15
|
+
}
|
|
11
16
|
export const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
|
|
12
17
|
export const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
|
|
13
18
|
export const TASK_REPORT_PREFIX = "[PALMIER_REPORT]";
|
package/dist/commands/init.js
CHANGED
|
@@ -3,6 +3,7 @@ import { loadConfig, saveConfig } from "../config.js";
|
|
|
3
3
|
import { detectAgents } from "../agents/agent.js";
|
|
4
4
|
import { getPlatform } from "../platform/index.js";
|
|
5
5
|
import { pairCommand } from "./pair.js";
|
|
6
|
+
import { detectLanIp } from "../transports/http-transport.js";
|
|
6
7
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
7
8
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
8
9
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
@@ -29,6 +30,33 @@ export async function initCommand() {
|
|
|
29
30
|
process.exit(1);
|
|
30
31
|
}
|
|
31
32
|
console.log(` Found: ${green(agents.map((a) => a.label).join(", "))}\n`);
|
|
33
|
+
// LAN mode
|
|
34
|
+
const lanAnswer = await ask("Enable LAN access (direct HTTP from local network)? (y/N): ");
|
|
35
|
+
const lanEnabled = lanAnswer.trim().toLowerCase() === "y";
|
|
36
|
+
let httpPort = 7400;
|
|
37
|
+
const portLabel = lanEnabled ? "HTTP port for local and LAN access" : "HTTP port for local access";
|
|
38
|
+
const portAnswer = await ask(`${portLabel} (default ${httpPort}): `);
|
|
39
|
+
const parsed = parseInt(portAnswer.trim(), 10);
|
|
40
|
+
if (parsed > 0 && parsed < 65536)
|
|
41
|
+
httpPort = parsed;
|
|
42
|
+
// Display summary and ask for confirmation before making any changes
|
|
43
|
+
console.log(`\n${bold("Setup summary:")}\n`);
|
|
44
|
+
console.log(` ${dim("Task storage:")} ${bold(process.cwd())}`);
|
|
45
|
+
console.log(` All tasks and execution data will be stored here.\n`);
|
|
46
|
+
console.log(` ${dim("Local access:")} ${cyan(`http://localhost:${httpPort}`)}`);
|
|
47
|
+
console.log(` Always available — no internet required.\n`);
|
|
48
|
+
if (lanEnabled) {
|
|
49
|
+
const ip = detectLanIp();
|
|
50
|
+
console.log(` ${dim("LAN access:")} ${cyan(`http://${ip}:${httpPort}`)}`);
|
|
51
|
+
console.log(` Accessible from other devices on your local network. Pairing required.\n`);
|
|
52
|
+
}
|
|
53
|
+
console.log(` ${dim("Agents:")} ${agents.map((a) => a.label).join(", ")}\n`);
|
|
54
|
+
const confirm = await ask("Proceed? (Y/n): ");
|
|
55
|
+
if (confirm.trim().toLowerCase() === "n") {
|
|
56
|
+
console.log("\nSetup cancelled.");
|
|
57
|
+
rl.close();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
32
60
|
// Register with server
|
|
33
61
|
let existingHostId;
|
|
34
62
|
try {
|
|
@@ -54,7 +82,7 @@ export async function initCommand() {
|
|
|
54
82
|
}
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
|
-
// Build config
|
|
85
|
+
// Build and save config
|
|
58
86
|
const config = {
|
|
59
87
|
hostId: registerResponse.hostId,
|
|
60
88
|
projectRoot: process.cwd(),
|
|
@@ -62,9 +90,10 @@ export async function initCommand() {
|
|
|
62
90
|
natsWsUrl: registerResponse.natsWsUrl,
|
|
63
91
|
natsToken: registerResponse.natsToken,
|
|
64
92
|
agents,
|
|
93
|
+
httpPort,
|
|
94
|
+
lanEnabled,
|
|
65
95
|
};
|
|
66
96
|
saveConfig(config);
|
|
67
|
-
console.log(`\n${green("Host provisioned")} ID: ${cyan(config.hostId)}`);
|
|
68
97
|
console.log(`Config saved to ${dim("~/.config/palmier/host.json")}`);
|
|
69
98
|
getPlatform().installDaemon(config);
|
|
70
99
|
console.log("\nStarting pairing...");
|
package/dist/commands/pair.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export declare const PAIRING_EXPIRY_MS: number;
|
|
|
2
2
|
export declare function generatePairingCode(): string;
|
|
3
3
|
/**
|
|
4
4
|
* Generate an OTP code and wait for a PWA client to pair.
|
|
5
|
-
* Listens on NATS
|
|
5
|
+
* Listens on NATS (server mode) and HTTP (via serve daemon) in parallel.
|
|
6
6
|
*/
|
|
7
7
|
export declare function pairCommand(): Promise<void>;
|
|
8
8
|
//# sourceMappingURL=pair.d.ts.map
|
package/dist/commands/pair.js
CHANGED
|
@@ -3,7 +3,6 @@ import { StringCodec } from "nats";
|
|
|
3
3
|
import { loadConfig } from "../config.js";
|
|
4
4
|
import { connectNats } from "../nats-client.js";
|
|
5
5
|
import { addSession } from "../session-store.js";
|
|
6
|
-
import { getLanPort } from "../lan-lock.js";
|
|
7
6
|
const CODE_CHARS = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"; // no O/0/I/1/L
|
|
8
7
|
const CODE_LENGTH = 6;
|
|
9
8
|
export const PAIRING_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
|
|
@@ -20,15 +19,15 @@ function buildPairResponse(config, label) {
|
|
|
20
19
|
};
|
|
21
20
|
}
|
|
22
21
|
/**
|
|
23
|
-
* POST to the running
|
|
22
|
+
* POST to the running serve daemon and long-poll until paired or expired.
|
|
24
23
|
*/
|
|
25
|
-
function
|
|
24
|
+
function httpPairRegister(port, code) {
|
|
26
25
|
const body = JSON.stringify({ code, expiryMs: PAIRING_EXPIRY_MS });
|
|
27
26
|
return new Promise((resolve) => {
|
|
28
27
|
const req = http.request({
|
|
29
28
|
hostname: "127.0.0.1",
|
|
30
29
|
port,
|
|
31
|
-
path: "/
|
|
30
|
+
path: "/pair-register",
|
|
32
31
|
method: "POST",
|
|
33
32
|
headers: { "Content-Type": "application/json" },
|
|
34
33
|
timeout: PAIRING_EXPIRY_MS + 5000,
|
|
@@ -52,11 +51,12 @@ function lanPairRegister(port, code) {
|
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
53
|
* Generate an OTP code and wait for a PWA client to pair.
|
|
55
|
-
* Listens on NATS
|
|
54
|
+
* Listens on NATS (server mode) and HTTP (via serve daemon) in parallel.
|
|
56
55
|
*/
|
|
57
56
|
export async function pairCommand() {
|
|
58
57
|
const config = loadConfig();
|
|
59
58
|
const code = generatePairingCode();
|
|
59
|
+
const httpPort = config.httpPort ?? 7400;
|
|
60
60
|
let paired = false;
|
|
61
61
|
function onPaired() {
|
|
62
62
|
paired = true;
|
|
@@ -70,7 +70,7 @@ export async function pairCommand() {
|
|
|
70
70
|
console.log(` ${code}`);
|
|
71
71
|
console.log("");
|
|
72
72
|
console.log("Code expires in 5 minutes.");
|
|
73
|
-
// NATS pairing (
|
|
73
|
+
// NATS pairing (server mode)
|
|
74
74
|
const nc = await connectNats(config);
|
|
75
75
|
const sc = StringCodec();
|
|
76
76
|
const subject = `pair.${code}`;
|
|
@@ -98,15 +98,12 @@ export async function pairCommand() {
|
|
|
98
98
|
onPaired();
|
|
99
99
|
}
|
|
100
100
|
})();
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
onPaired();
|
|
108
|
-
})();
|
|
109
|
-
}
|
|
101
|
+
// HTTP pairing — register with serve daemon's /pair-register endpoint
|
|
102
|
+
(async () => {
|
|
103
|
+
const result = await httpPairRegister(httpPort, code);
|
|
104
|
+
if (result)
|
|
105
|
+
onPaired();
|
|
106
|
+
})();
|
|
110
107
|
// Wait for pairing or timeout
|
|
111
108
|
const start = Date.now();
|
|
112
109
|
await new Promise((resolve) => {
|