palmier 0.2.0 → 0.2.2
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 +5 -1
- package/README.md +135 -45
- package/dist/agents/agent.d.ts +26 -0
- package/dist/agents/agent.js +32 -0
- package/dist/agents/claude.d.ts +8 -0
- package/dist/agents/claude.js +35 -0
- package/dist/agents/codex.d.ts +8 -0
- package/dist/agents/codex.js +41 -0
- package/dist/agents/gemini.d.ts +8 -0
- package/dist/agents/gemini.js +39 -0
- package/dist/agents/openclaw.d.ts +8 -0
- package/dist/agents/openclaw.js +25 -0
- package/dist/agents/shared-prompt.d.ts +11 -0
- package/dist/agents/shared-prompt.js +26 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +19 -0
- package/dist/commands/info.d.ts +5 -0
- package/dist/commands/info.js +40 -0
- package/dist/commands/init.d.ts +7 -2
- package/dist/commands/init.js +139 -49
- package/dist/commands/mcpserver.d.ts +2 -0
- package/dist/commands/mcpserver.js +75 -0
- package/dist/commands/pair.d.ts +6 -0
- package/dist/commands/pair.js +140 -0
- package/dist/commands/plan-generation.md +32 -0
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +258 -114
- package/dist/commands/serve.d.ts +1 -1
- package/dist/commands/serve.js +16 -228
- package/dist/commands/sessions.d.ts +4 -0
- package/dist/commands/sessions.js +30 -0
- package/dist/commands/task-generation.md +1 -1
- package/dist/config.d.ts +5 -5
- package/dist/config.js +24 -6
- package/dist/index.js +58 -5
- package/dist/nats-client.d.ts +3 -3
- package/dist/nats-client.js +2 -2
- package/dist/rpc-handler.d.ts +6 -0
- package/dist/rpc-handler.js +367 -0
- package/dist/session-store.d.ts +12 -0
- package/dist/session-store.js +57 -0
- package/dist/spawn-command.d.ts +26 -0
- package/dist/spawn-command.js +48 -0
- package/dist/systemd.d.ts +2 -2
- package/dist/task.d.ts +45 -2
- package/dist/task.js +155 -14
- package/dist/transports/http-transport.d.ts +6 -0
- package/dist/transports/http-transport.js +243 -0
- package/dist/transports/nats-transport.d.ts +6 -0
- package/dist/transports/nats-transport.js +69 -0
- package/dist/types.d.ts +30 -13
- package/package.json +4 -3
- package/src/agents/agent.ts +62 -0
- package/src/agents/claude.ts +39 -0
- package/src/agents/codex.ts +46 -0
- package/src/agents/gemini.ts +43 -0
- package/src/agents/openclaw.ts +29 -0
- package/src/agents/shared-prompt.ts +26 -0
- package/src/commands/agents.ts +20 -0
- package/src/commands/info.ts +44 -0
- package/src/commands/init.ts +229 -121
- package/src/commands/mcpserver.ts +92 -0
- package/src/commands/pair.ts +163 -0
- package/src/commands/plan-generation.md +32 -0
- package/src/commands/run.ts +323 -129
- package/src/commands/serve.ts +26 -287
- package/src/commands/sessions.ts +32 -0
- package/src/config.ts +30 -10
- package/src/index.ts +67 -6
- package/src/nats-client.ts +4 -4
- package/src/rpc-handler.ts +421 -0
- package/src/session-store.ts +68 -0
- package/src/spawn-command.ts +78 -0
- package/src/systemd.ts +2 -2
- package/src/task.ts +166 -16
- package/src/transports/http-transport.ts +290 -0
- package/src/transports/nats-transport.ts +82 -0
- package/src/types.ts +36 -13
- package/src/commands/task-generation.md +0 -28
package/CLAUDE.md
CHANGED
|
@@ -2,4 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Getting Started
|
|
4
4
|
|
|
5
|
-
Always read `
|
|
5
|
+
Always read `README.md` first before starting any task. For the full platform architecture spec (NATS protocol, data model, execution flow), see `spec.md` in the [palmier-server](../palmier-server) repo.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
When making architectural changes, update `README.md` and the server repo's `spec.md` to reflect the new state.
|
package/README.md
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
# Palmier
|
|
1
|
+
# Palmier
|
|
2
2
|
|
|
3
|
-
A Node.js CLI that runs on your machine as a persistent
|
|
3
|
+
A Node.js CLI that runs on your machine as a persistent daemon. It manages tasks, communicates with the Palmier app via NATS and/or direct HTTP, and executes tasks on schedule or demand using CLI tools.
|
|
4
|
+
|
|
5
|
+
## Connection Modes
|
|
6
|
+
|
|
7
|
+
The host supports three connection modes:
|
|
8
|
+
|
|
9
|
+
| Mode | Transport | Setup | Features |
|
|
10
|
+
|------|-----------|-------|----------|
|
|
11
|
+
| **`nats`** | NATS only | `palmier init --server <url>` → select nats | All features |
|
|
12
|
+
| **`auto`** | Both NATS + HTTP | `palmier init --server <url>` → select auto | All features. PWA auto-detects best route. |
|
|
13
|
+
| **`lan`** | HTTP only | `palmier init --lan` | No push notifications or email MCP. No server needed. |
|
|
14
|
+
|
|
15
|
+
In **auto** mode, the PWA connects directly to the host via HTTP when on the same LAN (lower latency), and falls back to NATS when the host is unreachable. Push notifications and email relay always flow through NATS/server, so no features are lost.
|
|
16
|
+
|
|
17
|
+
In **lan** mode, the host runs a standalone HTTP server. Multiple PWA clients can connect using session tokens. No Palmier web server or NATS broker is needed.
|
|
4
18
|
|
|
5
19
|
## Prerequisites
|
|
6
20
|
|
|
7
21
|
- **Node.js 20+**
|
|
8
|
-
-
|
|
9
|
-
- **Linux with systemd** (the
|
|
22
|
+
- An agent CLI tool for task execution (e.g., Claude Code, Gemini CLI, OpenAI Codex)
|
|
23
|
+
- **Linux with systemd** (the host installs as a systemd user service)
|
|
10
24
|
|
|
11
25
|
## Installation
|
|
12
26
|
|
|
@@ -18,87 +32,173 @@ npm install -g palmier
|
|
|
18
32
|
|
|
19
33
|
| Command | Description |
|
|
20
34
|
|---|---|
|
|
21
|
-
| `palmier init --
|
|
22
|
-
| `palmier
|
|
35
|
+
| `palmier init --server <url>` | Provision the host via the server (prompts for nats/auto mode) |
|
|
36
|
+
| `palmier init --lan` | Provision standalone LAN host (no server needed) |
|
|
37
|
+
| `palmier pair` | Generate an OTP code to pair a new device |
|
|
38
|
+
| `palmier sessions list` | List active session tokens |
|
|
39
|
+
| `palmier sessions revoke <token>` | Revoke a specific session token |
|
|
40
|
+
| `palmier sessions revoke-all` | Revoke all session tokens |
|
|
41
|
+
| `palmier info` | Show host connection info (address, mode) |
|
|
42
|
+
| `palmier serve` | Run the persistent RPC handler (default command) |
|
|
23
43
|
| `palmier run <task-id>` | Execute a specific task |
|
|
44
|
+
| `palmier mcpserver` | Start an MCP server exposing Palmier tools (stdio transport) |
|
|
45
|
+
| `palmier agents` | Re-detect installed agent CLIs and update config |
|
|
24
46
|
|
|
25
47
|
## Setup
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
### Server-provisioned (nats or auto mode)
|
|
50
|
+
|
|
51
|
+
1. Install the host: `npm install -g palmier`
|
|
52
|
+
2. Run `palmier init --server https://your-server-url` in your project directory.
|
|
53
|
+
3. Select connection mode: `auto` (recommended) or `nats`.
|
|
54
|
+
4. The host registers with the server, saves config, installs a systemd service, and generates a pairing code.
|
|
55
|
+
5. Enter the pairing code in the Palmier PWA to connect your device.
|
|
56
|
+
|
|
57
|
+
### Standalone LAN mode
|
|
58
|
+
|
|
59
|
+
1. Install the host: `npm install -g palmier`
|
|
60
|
+
2. Run `palmier init --lan` in your project directory.
|
|
61
|
+
3. A pairing code and address are displayed. Enter both in the PWA.
|
|
62
|
+
|
|
63
|
+
### Pairing additional devices
|
|
64
|
+
|
|
65
|
+
Run `palmier pair` on the host to generate a new OTP code. Each paired device gets its own session token.
|
|
66
|
+
|
|
67
|
+
### Managing sessions
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# List all paired devices
|
|
71
|
+
palmier sessions list
|
|
72
|
+
|
|
73
|
+
# Revoke a specific device's access
|
|
74
|
+
palmier sessions revoke <token>
|
|
75
|
+
|
|
76
|
+
# Revoke all sessions (unpair all devices)
|
|
77
|
+
palmier sessions revoke-all
|
|
78
|
+
```
|
|
31
79
|
|
|
32
80
|
The `init` command:
|
|
33
|
-
-
|
|
34
|
-
-
|
|
81
|
+
- Detects installed agent CLIs (Claude Code, Gemini CLI, Codex CLI) and caches the result
|
|
82
|
+
- Saves host configuration to `~/.config/palmier/host.json`
|
|
83
|
+
- Installs a systemd user service for the host
|
|
84
|
+
- Auto-enters pair mode to connect your first device
|
|
85
|
+
|
|
86
|
+
To re-detect agents after installing or removing a CLI, run `palmier agents`.
|
|
35
87
|
|
|
36
88
|
### Verifying the Service
|
|
37
89
|
|
|
38
|
-
After `palmier init`, verify the
|
|
90
|
+
After `palmier init`, verify the host is running:
|
|
39
91
|
|
|
40
92
|
```bash
|
|
41
93
|
# Check service status
|
|
42
|
-
systemctl --user status palmier
|
|
94
|
+
systemctl --user status palmier.service
|
|
43
95
|
|
|
44
96
|
# View recent logs
|
|
45
|
-
journalctl --user -u palmier
|
|
97
|
+
journalctl --user -u palmier.service -n 50 --no-pager
|
|
46
98
|
|
|
47
99
|
# Follow logs in real time
|
|
48
|
-
journalctl --user -u palmier
|
|
100
|
+
journalctl --user -u palmier.service -f
|
|
49
101
|
```
|
|
50
102
|
|
|
51
103
|
You should see `Active: active (running)` and log output showing a NATS connection. If the service failed:
|
|
52
104
|
|
|
53
105
|
```bash
|
|
54
106
|
# Restart manually
|
|
55
|
-
systemctl --user restart palmier
|
|
107
|
+
systemctl --user restart palmier.service
|
|
56
108
|
|
|
57
109
|
# Check config is valid
|
|
58
|
-
cat ~/.config/palmier/
|
|
110
|
+
cat ~/.config/palmier/host.json
|
|
59
111
|
```
|
|
60
112
|
|
|
61
113
|
## How It Works
|
|
62
114
|
|
|
63
|
-
- The
|
|
64
|
-
- The persistent process (`palmier serve`) is
|
|
65
|
-
- **
|
|
115
|
+
- The host runs as a **systemd user service**, staying alive in the background.
|
|
116
|
+
- The persistent process (`palmier serve`) is an RPC handler. In NATS mode, it derives the method from the NATS subject; in LAN/auto mode, it exposes HTTP endpoints (`POST /rpc/<method>`). The RPC handler (`src/rpc-handler.ts`) is transport-agnostic.
|
|
117
|
+
- **Session tokens** — every RPC request includes a `sessionToken` (in the NATS JSON payload or as an HTTP Bearer token). The host validates the token against `~/.config/palmier/sessions.json` before processing the request. If no sessions exist, validation is skipped.
|
|
118
|
+
- **Task IDs** are generated by the host as UUIDs.
|
|
66
119
|
- All RPC responses (`task.list`, `task.create`, `task.update`) return **flat task objects** — frontmatter fields at the top level, not nested under a `frontmatter` key.
|
|
120
|
+
- **Plan generation is automatic** — `task.create` and `task.update` generate an execution plan and task name by invoking the configured agent CLI. For `task.update`, plans are regenerated only when `user_prompt` or `agent` changes, or when no plan body exists yet.
|
|
121
|
+
- **Run history** — each task run produces a time-stamped result file (`RESULT-<timestamp>.md`) and a task snapshot (`TASK-<timestamp>.md`) in the task directory. A project-level `history.jsonl` file indexes all runs with `{ task_id, result_file }` entries. The `activity.list` RPC reads this index with pagination (`offset`/`limit`, optional `task_id` filter) and enriches each entry with RESULT frontmatter. The `task.result` RPC requires a `result_file` parameter identifying which result to read.
|
|
122
|
+
- The `task.reports` RPC reads one or more report files (each must end with `.md`, plain filenames only) from a task's directory. If the RESULT frontmatter includes a `report_files` field, the PWA shows clickable links in the result dialog that fetch and display each report.
|
|
67
123
|
- Tasks have no separate name field — the `user_prompt` is the primary identifier and display label.
|
|
68
|
-
- Plan generation is optional — if no plan body is present, the agent uses only `user_prompt` as the prompt.
|
|
69
124
|
- **Triggers can be enabled/disabled** via the `triggers_enabled` frontmatter field (default `true`). When disabled, systemd timers are removed; when re-enabled, they are reinstalled. Tasks can still be run manually regardless.
|
|
70
125
|
- Incoming tasks are stored as `TASK.md` files in a local `tasks/` directory.
|
|
71
|
-
- Task execution
|
|
72
|
-
- **Task confirmation** — tasks with `requires_confirmation: true`
|
|
126
|
+
- Task execution is abstracted through an **`AgentTool` interface** (`src/agents/agent.ts`). Each task stores an `agent` field (e.g., `"claude"`) that selects which agent implementation constructs the full command line and arguments. The agent's `getTaskRunCommandLine(task)` method builds the appropriate flags (e.g., `--allowedTools` for Claude based on task permissions). The process is spawned without a shell, and stdin is closed to prevent tools from hanging on an open pipe. The spawned process inherits the default physical GUI session environment (`DISPLAY=:0`) so commands that launch graphical applications (e.g., headed browsers) run within the user's desktop session. Task lifecycle status (`start`, `finish`, `abort`, `fail`) is persisted to a `status.json` file in the task directory. Status changes are broadcast on a unified `host-event.<host_id>.<task_id>` subject via NATS pub/sub (when available) and/or pushed through SSE (in LAN/auto mode) by `run.ts` POSTing to the serve process's `/internal/event` endpoint. All events carry an `event_type` field (`"running-state"`, `"confirm-request"`, or `"confirm-resolved"`) with the same payload shape on both transports. Consumers (PWA, Web Server) subscribe to these notifications and fetch full status from the host via the `task.status` RPC.
|
|
127
|
+
- **Task confirmation** — tasks with `requires_confirmation: true` set `pending_confirmation: true` in `status.json` before execution. A `confirm-request` event is published on `host-event` so the Web Server can send push notifications and connected PWA clients show a confirmation dialog. The user confirms or aborts via the PWA, which calls the `task.user_input` RPC on the host. The `serve` process sets `user_input` to the user's response (e.g., `"confirmed"` or `"aborted"`), and `run` watches the file for `user_input` to become defined, then updates `running_state` accordingly.
|
|
128
|
+
- **MCP server** (`palmier mcpserver`) — starts an MCP server over stdio that exposes platform tools to AI agents (e.g., Claude Code). In NATS/auto mode, connects to NATS on startup. Currently exposes a `send-email` tool that relays email requests to the Web Server via NATS (`host.<host_id>.email.send`).
|
|
73
129
|
|
|
74
130
|
## Project Structure
|
|
75
131
|
|
|
76
132
|
```
|
|
77
133
|
src/
|
|
78
134
|
index.ts # CLI entrypoint (commander setup)
|
|
79
|
-
config.ts #
|
|
80
|
-
|
|
135
|
+
config.ts # Host configuration (read/write ~/.config/palmier)
|
|
136
|
+
rpc-handler.ts # Transport-agnostic RPC handler (with session validation)
|
|
137
|
+
session-store.ts # Session token management (~/.config/palmier/sessions.json)
|
|
138
|
+
nats-client.ts # NATS connection helper
|
|
139
|
+
spawn-command.ts # Shared helper for spawning CLI tools without a shell
|
|
81
140
|
systemd.ts # systemd service installation
|
|
82
141
|
task.ts # Task file management
|
|
83
142
|
types.ts # Shared type definitions
|
|
143
|
+
agents/
|
|
144
|
+
agent.ts # AgentTool interface, registry, and agent detection
|
|
145
|
+
claude.ts # Claude Code agent implementation
|
|
146
|
+
gemini.ts # Gemini CLI agent implementation
|
|
147
|
+
codex.ts # Codex CLI agent implementation
|
|
148
|
+
openclaw.ts # OpenClaw agent implementation
|
|
84
149
|
commands/
|
|
85
|
-
init.ts # Provisioning logic
|
|
86
|
-
|
|
150
|
+
init.ts # Provisioning logic (--server and --lan flows, auto-pair)
|
|
151
|
+
pair.ts # OTP code generation and pairing handler
|
|
152
|
+
sessions.ts # Session token management CLI (list, revoke, revoke-all)
|
|
153
|
+
info.ts # Print host connection info
|
|
154
|
+
agents.ts # Re-detect installed agent CLIs
|
|
155
|
+
serve.ts # Transport selection and startup
|
|
87
156
|
run.ts # Single task execution
|
|
157
|
+
mcpserver.ts # MCP server with platform tools (send-email)
|
|
158
|
+
transports/
|
|
159
|
+
nats-transport.ts # NATS subscription loop (host.<hostId>.rpc.>)
|
|
160
|
+
http-transport.ts # HTTP server with RPC, SSE, and internal event endpoints
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## MCP Server
|
|
164
|
+
|
|
165
|
+
The host includes an MCP server that exposes Palmier platform tools to AI agents like Claude Code.
|
|
166
|
+
|
|
167
|
+
### Setup
|
|
168
|
+
|
|
169
|
+
Add to your Claude Code MCP settings:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"mcpServers": {
|
|
174
|
+
"palmier": {
|
|
175
|
+
"command": "palmier",
|
|
176
|
+
"args": ["mcpserver"]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
88
180
|
```
|
|
89
181
|
|
|
90
|
-
|
|
182
|
+
Requires a provisioned host (`palmier init`). In NATS/auto mode, uses NATS relay. In LAN mode with `serverUrl` configured, uses HTTP relay. Not available in standalone LAN mode without a server.
|
|
183
|
+
|
|
184
|
+
### Available Tools
|
|
91
185
|
|
|
92
|
-
|
|
186
|
+
| Tool | Inputs | Description |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `send-email` | `to`, `subject`, `text` (required); `html`, `reply_to`, `cc`, `bcc` (optional) | Send an email via the platform's SMTP relay |
|
|
93
189
|
|
|
94
|
-
|
|
190
|
+
## Removing a Host
|
|
191
|
+
|
|
192
|
+
To fully remove a host from a machine:
|
|
193
|
+
|
|
194
|
+
1. **Unpair the host from the PWA** (via the host menu).
|
|
95
195
|
|
|
96
196
|
2. **Stop and remove the systemd service:**
|
|
97
197
|
|
|
98
198
|
```bash
|
|
99
|
-
systemctl --user stop palmier
|
|
100
|
-
systemctl --user disable palmier
|
|
101
|
-
rm ~/.config/systemd/user/palmier
|
|
199
|
+
systemctl --user stop palmier.service
|
|
200
|
+
systemctl --user disable palmier.service
|
|
201
|
+
rm ~/.config/systemd/user/palmier.service
|
|
102
202
|
```
|
|
103
203
|
|
|
104
204
|
3. **Remove any task timers and services:**
|
|
@@ -115,7 +215,7 @@ To fully remove an agent from a machine:
|
|
|
115
215
|
systemctl --user daemon-reload
|
|
116
216
|
```
|
|
117
217
|
|
|
118
|
-
5. **Remove the
|
|
218
|
+
5. **Remove the host configuration:**
|
|
119
219
|
|
|
120
220
|
```bash
|
|
121
221
|
rm -rf ~/.config/palmier
|
|
@@ -126,13 +226,3 @@ To fully remove an agent from a machine:
|
|
|
126
226
|
```bash
|
|
127
227
|
rm -rf tasks/
|
|
128
228
|
```
|
|
129
|
-
|
|
130
|
-
7. *(Optional)* **Disable login lingering** if no other user services need it:
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
loginctl disable-linger
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Related
|
|
137
|
-
|
|
138
|
-
See the [palmier](../palmier) repo for the server, API, and PWA.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
2
|
+
export interface CommandLine {
|
|
3
|
+
command: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Interface that each agent tool must implement.
|
|
8
|
+
* Abstracts how plans are generated and tasks are executed across different AI agents.
|
|
9
|
+
*/
|
|
10
|
+
export interface AgentTool {
|
|
11
|
+
/** Return the command and args used to generate a plan from a prompt. */
|
|
12
|
+
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
13
|
+
/** Return the command and args used to run a task. If prompt is provided, use it instead of the task's prompt.
|
|
14
|
+
* extraPermissions are transient permissions granted for this run only (not persisted in frontmatter). */
|
|
15
|
+
getTaskRunCommandLine(task: ParsedTask, prompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
16
|
+
/** Detect whether the agent CLI is available and perform any agent-specific
|
|
17
|
+
* initialization. Returns true if the agent was detected and initialized successfully. */
|
|
18
|
+
init(): Promise<boolean>;
|
|
19
|
+
}
|
|
20
|
+
export interface DetectedAgent {
|
|
21
|
+
key: string;
|
|
22
|
+
label: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function detectAgents(): Promise<DetectedAgent[]>;
|
|
25
|
+
export declare function getAgent(name: string): AgentTool;
|
|
26
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ClaudeAgent } from "./claude.js";
|
|
2
|
+
import { GeminiAgent } from "./gemini.js";
|
|
3
|
+
import { CodexAgent } from "./codex.js";
|
|
4
|
+
const agentRegistry = {
|
|
5
|
+
claude: new ClaudeAgent(),
|
|
6
|
+
gemini: new GeminiAgent(),
|
|
7
|
+
codex: new CodexAgent(),
|
|
8
|
+
};
|
|
9
|
+
const agentLabels = {
|
|
10
|
+
claude: "Claude Code",
|
|
11
|
+
gemini: "Gemini CLI",
|
|
12
|
+
codex: "Codex CLI",
|
|
13
|
+
openclaw: "OpenClaw"
|
|
14
|
+
};
|
|
15
|
+
export async function detectAgents() {
|
|
16
|
+
const detected = [];
|
|
17
|
+
for (const [key, agent] of Object.entries(agentRegistry)) {
|
|
18
|
+
const label = agentLabels[key] ?? key;
|
|
19
|
+
const ok = await agent.init();
|
|
20
|
+
if (ok)
|
|
21
|
+
detected.push({ key, label });
|
|
22
|
+
}
|
|
23
|
+
return detected;
|
|
24
|
+
}
|
|
25
|
+
export function getAgent(name) {
|
|
26
|
+
const agent = agentRegistry[name];
|
|
27
|
+
if (!agent) {
|
|
28
|
+
throw new Error(`Unknown agent: "${name}". Available agents: ${Object.keys(agentRegistry).join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
return agent;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
2
|
+
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
|
+
export declare class ClaudeAgent implements AgentTool {
|
|
4
|
+
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, prompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
|
+
init(): Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { TASK_OUTCOME_SUFFIX } from "./shared-prompt.js";
|
|
3
|
+
export class ClaudeAgent {
|
|
4
|
+
getPlanGenerationCommandLine(prompt) {
|
|
5
|
+
return {
|
|
6
|
+
command: "claude",
|
|
7
|
+
args: ["-p", prompt],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
getTaskRunCommandLine(task, prompt, extraPermissions) {
|
|
11
|
+
prompt = (prompt ?? (task.body || task.frontmatter.user_prompt)) + TASK_OUTCOME_SUFFIX;
|
|
12
|
+
const args = ["-c", "--permission-mode", "acceptEdits", "-p", prompt];
|
|
13
|
+
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
14
|
+
for (const p of allPerms) {
|
|
15
|
+
args.push("--allowedTools", p.name);
|
|
16
|
+
}
|
|
17
|
+
return { command: "claude", args };
|
|
18
|
+
}
|
|
19
|
+
async init() {
|
|
20
|
+
try {
|
|
21
|
+
execSync("claude --version");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
execSync("claude mcp add --transport stdio palmier --scope user -- palmier mcpserver");
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
console.warn("Warning: failed to install MCP for Claude:", err instanceof Error ? err.message : err);
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
2
|
+
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
|
+
export declare class CodexAgent implements AgentTool {
|
|
4
|
+
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, prompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
|
+
init(): Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { TASK_OUTCOME_SUFFIX } from "./shared-prompt.js";
|
|
3
|
+
export class CodexAgent {
|
|
4
|
+
getPlanGenerationCommandLine(prompt) {
|
|
5
|
+
// TODO: fill in
|
|
6
|
+
return {
|
|
7
|
+
command: "codex",
|
|
8
|
+
args: ["exec", "--skip-git-repo-check", prompt],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
getTaskRunCommandLine(task, prompt, extraPermissions) {
|
|
12
|
+
prompt = (prompt ?? (task.body || task.frontmatter.user_prompt)) + TASK_OUTCOME_SUFFIX;
|
|
13
|
+
// TODO: Update sandbox to workspace-write once https://github.com/openai/codex/issues/12572
|
|
14
|
+
// is fixed.
|
|
15
|
+
const args = ["exec", "--full-auto", "--skip-git-repo-check", "--sandbox", "danger-full-access"];
|
|
16
|
+
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
17
|
+
for (const p of allPerms) {
|
|
18
|
+
args.push("--config");
|
|
19
|
+
args.push(`apps.${p.name}.default_tools_approval_mode="approve"`);
|
|
20
|
+
}
|
|
21
|
+
args.push(prompt);
|
|
22
|
+
args.push("resume", "--last");
|
|
23
|
+
return { command: "codex", args };
|
|
24
|
+
}
|
|
25
|
+
async init() {
|
|
26
|
+
try {
|
|
27
|
+
execSync("codex --version");
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
execSync("codex mcp add palmier palmier mcpserver");
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.warn("Warning: failed to install MCP for Codex:", err instanceof Error ? err.message : err);
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
2
|
+
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
|
+
export declare class GeminiAgent implements AgentTool {
|
|
4
|
+
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, prompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
|
+
init(): Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { TASK_OUTCOME_SUFFIX } from "./shared-prompt.js";
|
|
3
|
+
export class GeminiAgent {
|
|
4
|
+
getPlanGenerationCommandLine(prompt) {
|
|
5
|
+
// TODO: fill in
|
|
6
|
+
return {
|
|
7
|
+
command: "gemini",
|
|
8
|
+
args: ["--approval-mode", "auto_edit", "--prompt", prompt],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
getTaskRunCommandLine(task, prompt, extraPermissions) {
|
|
12
|
+
prompt = prompt ?? (task.body || task.frontmatter.user_prompt);
|
|
13
|
+
const args = ["--resume", "--prompt", prompt + TASK_OUTCOME_SUFFIX];
|
|
14
|
+
const allPerms = [...(task.frontmatter.permissions ?? []), ...(extraPermissions ?? [])];
|
|
15
|
+
if (allPerms.length > 0) {
|
|
16
|
+
args.push("--allowed-tools");
|
|
17
|
+
for (const p of allPerms) {
|
|
18
|
+
args.push(p.name);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return { command: "gemini", args };
|
|
22
|
+
}
|
|
23
|
+
async init() {
|
|
24
|
+
try {
|
|
25
|
+
execSync("gemini --version");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
execSync("gemini mcp add --scope user palmier palmier mcpserver");
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.warn("Warning: failed to install MCP for Gemini:", err instanceof Error ? err.message : err);
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParsedTask, RequiredPermission } from "../types.js";
|
|
2
|
+
import type { AgentTool, CommandLine } from "./agent.js";
|
|
3
|
+
export declare class ClaudeAgent implements AgentTool {
|
|
4
|
+
getPlanGenerationCommandLine(prompt: string): CommandLine;
|
|
5
|
+
getTaskRunCommandLine(task: ParsedTask, prompt?: string, extraPermissions?: RequiredPermission[]): CommandLine;
|
|
6
|
+
init(): Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=openclaw.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { TASK_OUTCOME_SUFFIX } from "./shared-prompt.js";
|
|
3
|
+
export class ClaudeAgent {
|
|
4
|
+
getPlanGenerationCommandLine(prompt) {
|
|
5
|
+
return {
|
|
6
|
+
command: "openclaw",
|
|
7
|
+
args: ["agent", "--message", prompt],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
getTaskRunCommandLine(task, prompt, extraPermissions) {
|
|
11
|
+
prompt = (prompt ?? (task.body || task.frontmatter.user_prompt)) + TASK_OUTCOME_SUFFIX;
|
|
12
|
+
const args = ["agent", "--message", prompt];
|
|
13
|
+
return { command: "openclaw", args };
|
|
14
|
+
}
|
|
15
|
+
async init() {
|
|
16
|
+
try {
|
|
17
|
+
execSync("openclaw --version");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=openclaw.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appended to the end of every task prompt.
|
|
3
|
+
* Instructs the agent to output a clear success/failure marker
|
|
4
|
+
* so that palmier can determine the task outcome.
|
|
5
|
+
*/
|
|
6
|
+
export declare const TASK_OUTCOME_SUFFIX = "\n\n---\nIf 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";
|
|
7
|
+
export declare const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
|
|
8
|
+
export declare const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
|
|
9
|
+
export declare const TASK_REPORT_PREFIX = "[PALMIER_REPORT]";
|
|
10
|
+
export declare const TASK_PERMISSION_PREFIX = "[PALMIER_PERMISSION]";
|
|
11
|
+
//# sourceMappingURL=shared-prompt.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appended to the end of every task prompt.
|
|
3
|
+
* Instructs the agent to output a clear success/failure marker
|
|
4
|
+
* so that palmier can determine the task outcome.
|
|
5
|
+
*/
|
|
6
|
+
export const TASK_OUTCOME_SUFFIX = `
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
If you generate report or output files, print each file name on its own line prefixed with [PALMIER_REPORT]: e.g.
|
|
10
|
+
[PALMIER_REPORT] report.md
|
|
11
|
+
[PALMIER_REPORT] summary.md
|
|
12
|
+
|
|
13
|
+
When you are done, output exactly one of these markers as the very last line:
|
|
14
|
+
- Success: [PALMIER_TASK_SUCCESS]
|
|
15
|
+
- Failure: [PALMIER_TASK_FAILURE]
|
|
16
|
+
Do not wrap them in code blocks or add text on the same line.
|
|
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]: e.g.
|
|
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
|
+
export const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
|
|
23
|
+
export const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
|
|
24
|
+
export const TASK_REPORT_PREFIX = "[PALMIER_REPORT]";
|
|
25
|
+
export const TASK_PERMISSION_PREFIX = "[PALMIER_PERMISSION]";
|
|
26
|
+
//# sourceMappingURL=shared-prompt.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { loadConfig, saveConfig } from "../config.js";
|
|
2
|
+
import { detectAgents } from "../agents/agent.js";
|
|
3
|
+
export async function agentsCommand() {
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
console.log("Detecting installed agents...");
|
|
6
|
+
const agents = await detectAgents();
|
|
7
|
+
config.agents = agents;
|
|
8
|
+
saveConfig(config);
|
|
9
|
+
if (agents.length === 0) {
|
|
10
|
+
console.log("No agent CLIs detected.");
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
console.log("Detected agents:");
|
|
14
|
+
for (const a of agents) {
|
|
15
|
+
console.log(` ${a.key} — ${a.label}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as os from "os";
|
|
2
|
+
import { loadConfig } from "../config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Detect the first non-internal IPv4 address.
|
|
5
|
+
*/
|
|
6
|
+
function detectLanIp() {
|
|
7
|
+
const interfaces = os.networkInterfaces();
|
|
8
|
+
for (const name of Object.keys(interfaces)) {
|
|
9
|
+
for (const iface of interfaces[name] ?? []) {
|
|
10
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
11
|
+
return iface.address;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return "127.0.0.1";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Print host connection info for setting up clients.
|
|
19
|
+
*/
|
|
20
|
+
export async function infoCommand() {
|
|
21
|
+
const config = loadConfig();
|
|
22
|
+
const mode = config.mode ?? "nats";
|
|
23
|
+
console.log(`Mode: ${mode}`);
|
|
24
|
+
console.log(`Host ID: ${config.hostId}`);
|
|
25
|
+
if (mode === "lan" || mode === "auto") {
|
|
26
|
+
const lanIp = detectLanIp();
|
|
27
|
+
const port = config.directPort ?? 7400;
|
|
28
|
+
console.log("");
|
|
29
|
+
console.log("Direct connection info:");
|
|
30
|
+
console.log(` Address: ${lanIp}:${port}`);
|
|
31
|
+
console.log(` Token: ${config.directToken}`);
|
|
32
|
+
}
|
|
33
|
+
if (mode === "nats" || mode === "auto") {
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log("NATS connection info:");
|
|
36
|
+
console.log(` URL: ${config.natsUrl}`);
|
|
37
|
+
console.log(` WS: ${config.natsWsUrl}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=info.js.map
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
export interface InitOptions {
|
|
2
|
-
|
|
2
|
+
server?: string;
|
|
3
|
+
lan?: boolean;
|
|
4
|
+
host?: string;
|
|
5
|
+
port?: number;
|
|
3
6
|
}
|
|
4
7
|
/**
|
|
5
|
-
* Provision this
|
|
8
|
+
* Provision this host. Two flows:
|
|
9
|
+
* - palmier init --lan → standalone LAN mode, no server needed
|
|
10
|
+
* - palmier init --server <url> → register with server, prompts for nats/auto mode
|
|
6
11
|
*/
|
|
7
12
|
export declare function initCommand(options: InitOptions): Promise<void>;
|
|
8
13
|
//# sourceMappingURL=init.d.ts.map
|