palmier 0.8.1 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +13 -0
- package/README.md +16 -14
- package/dist/agents/agent.d.ts +0 -4
- package/dist/agents/claude.js +1 -1
- package/dist/agents/codex.js +2 -2
- package/dist/agents/cursor.js +1 -1
- package/dist/agents/deepagents.js +1 -1
- package/dist/agents/gemini.js +3 -2
- package/dist/agents/goose.js +1 -1
- package/dist/agents/hermes.js +1 -1
- package/dist/agents/kiro.js +1 -1
- package/dist/agents/opencode.js +1 -1
- package/dist/agents/qoder.js +1 -1
- package/dist/agents/shared-prompt.d.ts +0 -3
- package/dist/agents/shared-prompt.js +0 -3
- package/dist/commands/info.d.ts +0 -3
- package/dist/commands/info.js +0 -5
- package/dist/commands/init.d.ts +0 -3
- package/dist/commands/init.js +2 -11
- package/dist/commands/pair.d.ts +1 -4
- package/dist/commands/pair.js +3 -12
- package/dist/commands/restart.d.ts +0 -3
- package/dist/commands/restart.js +0 -3
- package/dist/commands/run.d.ts +1 -14
- package/dist/commands/run.js +18 -61
- package/dist/commands/serve.d.ts +0 -3
- package/dist/commands/serve.js +29 -27
- package/dist/config.d.ts +0 -8
- package/dist/config.js +0 -8
- package/dist/device-capabilities.d.ts +1 -1
- package/dist/event-queues.d.ts +6 -21
- package/dist/event-queues.js +6 -21
- package/dist/events.d.ts +0 -6
- package/dist/events.js +1 -9
- package/dist/index.js +0 -1
- package/dist/mcp-handler.js +1 -2
- package/dist/mcp-tools.d.ts +0 -3
- package/dist/mcp-tools.js +12 -16
- package/dist/nats-client.d.ts +0 -3
- package/dist/nats-client.js +1 -4
- package/dist/pending-requests.d.ts +4 -18
- package/dist/pending-requests.js +4 -18
- package/dist/platform/index.d.ts +1 -4
- package/dist/platform/index.js +8 -7
- package/dist/platform/linux.d.ts +3 -9
- package/dist/platform/linux.js +9 -20
- package/dist/platform/macos.d.ts +32 -0
- package/dist/platform/macos.js +287 -0
- package/dist/platform/platform.d.ts +1 -4
- package/dist/platform/windows.d.ts +2 -5
- package/dist/platform/windows.js +19 -39
- package/dist/pwa/assets/index-499vYQvR.js +120 -0
- package/dist/pwa/assets/{index-CQxcuDhM.css → index-UaZFu6XL.css} +1 -1
- package/dist/pwa/assets/{web-DOyOiwsW.js → web-Bp48ONY3.js} +1 -1
- package/dist/pwa/assets/{web-D7Kq3Nvk.js → web-CyJutAy4.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/pwa/service-worker.js +1 -1
- package/dist/rpc-handler.d.ts +0 -6
- package/dist/rpc-handler.js +14 -47
- package/dist/spawn-command.d.ts +10 -25
- package/dist/spawn-command.js +7 -15
- package/dist/task.d.ts +6 -64
- package/dist/task.js +7 -70
- package/dist/transports/http-transport.d.ts +0 -4
- package/dist/transports/http-transport.js +7 -28
- package/dist/transports/nats-transport.d.ts +0 -4
- package/dist/transports/nats-transport.js +3 -9
- package/dist/types.d.ts +3 -7
- package/dist/update-checker.d.ts +1 -4
- package/dist/update-checker.js +2 -5
- package/package.json +1 -1
- package/palmier-server/pwa/src/App.css +325 -22
- package/palmier-server/pwa/src/App.tsx +2 -0
- package/palmier-server/pwa/src/components/CapabilityToggles.tsx +288 -0
- package/palmier-server/pwa/src/components/HostMenu.tsx +20 -207
- package/palmier-server/pwa/src/components/RunDetailView.tsx +3 -3
- package/palmier-server/pwa/src/components/SessionComposer.tsx +11 -2
- package/palmier-server/pwa/src/components/SessionsView.tsx +60 -32
- package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +160 -0
- package/palmier-server/pwa/src/components/TaskCard.tsx +1 -1
- package/palmier-server/pwa/src/components/TaskForm.tsx +207 -5
- package/palmier-server/pwa/src/components/TasksView.tsx +3 -1
- package/palmier-server/pwa/src/constants.ts +1 -1
- package/palmier-server/pwa/src/native/Device.ts +18 -2
- package/palmier-server/pwa/src/pages/Dashboard.tsx +13 -6
- package/palmier-server/pwa/src/pages/PairHost.tsx +3 -1
- package/palmier-server/pwa/src/pages/PairSetup.tsx +70 -0
- package/palmier-server/server/src/index.ts +7 -7
- package/palmier-server/server/src/routes/device.ts +4 -4
- package/palmier-server/spec.md +38 -7
- package/src/agents/agent.ts +0 -4
- package/src/agents/claude.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/cursor.ts +1 -1
- package/src/agents/deepagents.ts +1 -1
- package/src/agents/gemini.ts +3 -2
- package/src/agents/goose.ts +1 -1
- package/src/agents/hermes.ts +1 -1
- package/src/agents/kiro.ts +1 -1
- package/src/agents/opencode.ts +1 -1
- package/src/agents/qoder.ts +1 -1
- package/src/agents/shared-prompt.ts +0 -3
- package/src/commands/info.ts +0 -5
- package/src/commands/init.ts +2 -11
- package/src/commands/pair.ts +3 -12
- package/src/commands/restart.ts +0 -3
- package/src/commands/run.ts +18 -65
- package/src/commands/serve.ts +28 -27
- package/src/config.ts +0 -8
- package/src/device-capabilities.ts +3 -2
- package/src/event-queues.ts +6 -21
- package/src/events.ts +1 -9
- package/src/index.ts +0 -1
- package/src/mcp-handler.ts +1 -2
- package/src/mcp-tools.ts +12 -18
- package/src/nats-client.ts +1 -4
- package/src/pending-requests.ts +4 -18
- package/src/platform/index.ts +5 -7
- package/src/platform/linux.ts +9 -20
- package/src/platform/macos.ts +310 -0
- package/src/platform/platform.ts +1 -4
- package/src/platform/windows.ts +19 -40
- package/src/rpc-handler.ts +14 -47
- package/src/spawn-command.ts +11 -27
- package/src/task.ts +7 -70
- package/src/transports/http-transport.ts +7 -39
- package/src/transports/nats-transport.ts +3 -9
- package/src/types.ts +3 -10
- package/src/update-checker.ts +2 -5
- package/test/macos-plist.test.ts +112 -0
- package/test/task-parsing.test.ts +2 -3
- package/test/windows-xml.test.ts +11 -12
- package/dist/pwa/assets/index-DQfOEB03.js +0 -120
package/palmier-server/spec.md
CHANGED
|
@@ -12,18 +12,18 @@ The host supports **Linux** (systemd) and **Windows** (Task Scheduler for both d
|
|
|
12
12
|
|
|
13
13
|
### 1.2 Components
|
|
14
14
|
|
|
15
|
-
* **Host Binary (Node.js):** Runs persistently on the user's host machine as a NATS + HTTP RPC handler. Manages file system operations (task CRUD), OS-level scheduling (systemd), and task generation. Provides a CLI with commands: `palmier init` (provisioning), `palmier pair` (generate pairing code for device pairing), `palmier clients` (manage client tokens), `palmier run <task-id>` (executes a task via the configured agent tool), `palmier uninstall` (stop daemon and remove all scheduled tasks), and `palmier serve` (persistent RPC handler, default command). The `serve` process always starts a local HTTP server (bound to `127.0.0.1` by default, or `0.0.0.0` if LAN mode is enabled) alongside the NATS transport. Exposes a localhost-only MCP server at `/mcp` (streamable HTTP transport) with tools: `notify`, `request-input`, `request-confirmation`, `device-geolocation`, `read-contacts`, `create-contact`, `read-calendar`, `create-calendar-event`, `send-sms-message`, `
|
|
15
|
+
* **Host Binary (Node.js):** Runs persistently on the user's host machine as a NATS + HTTP RPC handler. Manages file system operations (task CRUD), OS-level scheduling (systemd), and task generation. Provides a CLI with commands: `palmier init` (provisioning), `palmier pair` (generate pairing code for device pairing), `palmier clients` (manage client tokens), `palmier run <task-id>` (executes a task via the configured agent tool), `palmier uninstall` (stop daemon and remove all scheduled tasks), and `palmier serve` (persistent RPC handler, default command). The `serve` process always starts a local HTTP server (bound to `127.0.0.1` by default, or `0.0.0.0` if LAN mode is enabled) alongside the NATS transport. Exposes a localhost-only MCP server at `/mcp` (streamable HTTP transport) with tools: `notify`, `request-input`, `request-confirmation`, `device-geolocation`, `read-contacts`, `create-contact`, `read-calendar`, `create-calendar-event`, `send-sms-message`, `send-alarm`, `read-battery`, `set-ringer-mode`; and resources: `notifications://device` (device notifications), `sms-messages://device` (SMS messages). Tools and resources are auto-generated as REST endpoints from shared registries (`ToolDefinition[]`, `ResourceDefinition[]`) — zero duplication. Tool REST endpoints are POST with `taskId` query param; resource REST endpoints are GET. `/request-permission` remains a separate endpoint (not part of the MCP registries). MCP resources support subscriptions — clients call `resources/subscribe` and the server holds the POST response open as an SSE stream, pushing `notifications/resources/updated` notifications when the resource data changes. MCP sessions track agent names from `initialize` clientInfo for logging and UI display. `palmier run` is a short-lived process invoked by systemd. Task execution is abstracted through an `AgentTool` interface (`src/agents/agent.ts`) so different AI CLI tools can be supported — each agent implements `getPromptCommandLine()`, `getTaskRunCommandLine()`, and `init()`. The task's `agent` field (e.g., `"claude"`) selects which agent is used.
|
|
16
16
|
|
|
17
17
|
* **Web Server (Node.js):** Serves the PWA assets (React) via `app.palmier.me` (Cloudflare proxied), manages Web Push VAPID keys, and provides host registration. Uses **PostgreSQL** for persistent storage (host registrations, push subscriptions, FCM tokens). Connects to NATS via TCP to subscribe to `host-event.>` for sending push notifications (confirmations, dismissals, completion/failure). For `POST /api/push/respond` (confirmation responses via push notification action buttons), the Web Server forwards the response to the host via the `task.user_input` NATS RPC. Subscribes to `host.*.push.send` NATS subjects to relay push notification requests from the host CLI. Subscribes to `host.*.fcm.geolocation` to relay device geolocation requests via FCM. Subscribes to `host.*.fcm.contacts`, `host.*.fcm.calendar`, `host.*.fcm.sms`, `host.*.fcm.alarm`, `host.*.fcm.battery`, and `host.*.fcm.ringer` to relay device capability requests via FCM. Provides HTTP endpoints for Android to post responses back (`/api/device/contacts-response`, `/api/device/calendar-response`, `/api/device/sms-response`, `/api/device/alarm-response`, `/api/device/battery-response`, `/api/device/ringer-response`). Co-located with the NATS server on the same machine.
|
|
18
18
|
|
|
19
19
|
* **Android App (Capacitor):** Native Android wrapper for the PWA. Provides FCM push messaging for receiving data messages in the background, `FusedLocationProviderClient` for GPS access, `NotificationListenerService` for capturing device notifications, `BroadcastReceiver` for incoming SMS, and handlers for contacts, calendar, alarms, battery, and ringer mode. All device tools work while the app is in the background via FCM data messages. When a request arrives via FCM, the appropriate handler executes the action and POSTs the result back to the Web Server. Device capabilities and their permissions:
|
|
20
20
|
- **Notifications**: `NotificationListenerService` — requires notification listener access (system settings toggle)
|
|
21
|
-
- **SMS
|
|
22
|
-
- **SMS
|
|
21
|
+
- **SMS Read**: `SmsBroadcastReceiver` — requires `RECEIVE_SMS` runtime permission
|
|
22
|
+
- **SMS Send**: `SmsHandler` — requires `SEND_SMS` runtime permission
|
|
23
23
|
- **Contacts**: `ContactsHandler` — requires `READ_CONTACTS` + `WRITE_CONTACTS` runtime permissions
|
|
24
24
|
- **Calendar**: `CalendarHandler` — requires `READ_CALENDAR` + `WRITE_CALENDAR` runtime permissions
|
|
25
25
|
- **Geolocation**: `GeolocationForegroundService` — requires `ACCESS_FINE_LOCATION` runtime permission
|
|
26
|
-
- **Alarm**: `AlarmHandler` — requires `
|
|
26
|
+
- **Alarm**: `AlarmHandler` + `AlarmActivity` — triggers a full-screen alarm popup with looping ringtone; requires `USE_FULL_SCREEN_INTENT` (Android 14+ requires user grant in settings)
|
|
27
27
|
- **Battery**: `BatteryHandler` — no permission required
|
|
28
28
|
- **Ringer mode**: `RingerHandler` — requires Do Not Disturb access (system settings toggle)
|
|
29
29
|
|
|
@@ -49,6 +49,37 @@ The project is split across two repositories:
|
|
|
49
49
|
|
|
50
50
|
* **`palmier`**: The host binary. A standalone Node.js CLI that runs on the user's machine.
|
|
51
51
|
* **`palmier-server`**: Contains both the Web Server (`server/`) and the PWA (`pwa/`, built with Vite + React). Uses **pnpm** for package management with a pnpm workspace.
|
|
52
|
+
* **`palmier-android`**: Capacitor-based Android wrapper that loads the PWA from `app.palmier.me` in a WebView and exposes native device capabilities via a custom plugin.
|
|
53
|
+
|
|
54
|
+
### 1.5 Android Implementation Details
|
|
55
|
+
|
|
56
|
+
The Android app is a thin native shell over the remotely-hosted PWA. The design decisions below are non-obvious enough that changing them silently would break assumptions the rest of the system makes.
|
|
57
|
+
|
|
58
|
+
**Remote-first WebView.** `capacitor.config.json` sets `server.url` to `https://app.palmier.me`; the WebView fetches the PWA from the cloud on every launch. This means PWA updates ship instantly with no APK rebuild, and release builds run `npx cap sync` only (no PWA build step). Consequences:
|
|
59
|
+
|
|
60
|
+
- **Server mode only.** LAN and Local modes are browser-only. The WebView blocks cleartext `http://<host-ip>:<port>` requests as mixed content, so LAN users must open the PWA from Chrome/Safari directly.
|
|
61
|
+
- **Offline fallback.** When `app.palmier.me` is unreachable, the WebView loads `www/offline.html` (configured via `server.errorPath`), which auto-reloads when connectivity returns.
|
|
62
|
+
- **PWA ships ahead of the APK.** The PWA may reference permission types or native methods that the installed APK doesn't implement yet. `Device.getSupportedPermissions()` returns the set the APK understands; `checkPermission` / `requestPermission` resolve with `{ granted: false, supported: false }` for unknown types rather than throwing. The PWA uses this to hide toggles it can't fulfill.
|
|
63
|
+
|
|
64
|
+
**Unified `Device` Capacitor plugin.** A single plugin (`DevicePlugin.kt`) exposes the entire native surface — FCM token, permission gate, capability whitelist, installed-app enumeration, email-client availability, deep-link events. This replaces seven per-capability permission plugins. Methods: `getFcmToken`, `getSupportedPermissions`, `checkPermission({type})`, `requestPermission({type})`, `setEnabledCapabilities({capabilities})`, `getInstalledApps`, `hasEmailClient`, `addListener("deepLink", ...)`. Permission types: `location`, `smsRead`, `smsSend`, `contacts`, `calendar`, `notificationListener`, `dnd`, `fullScreenIntent`, `postNotifications`.
|
|
65
|
+
|
|
66
|
+
**Capability kill-switch (`CapabilityState`).** Local whitelist persisted as a JSON-array string under `enabledCapabilities` in `CapacitorStorage` SharedPreferences, written only by `DevicePlugin.setEnabledCapabilities` from the PWA's derived state. Native receivers (`SmsBroadcastReceiver`, `DeviceNotificationListenerService`) and all FCM handlers consult this before acting — a second line of defense beyond the server-side capability token. If the user disables a capability in the drawer, the native side refuses to relay events or respond to requests even if the server still asks.
|
|
67
|
+
|
|
68
|
+
**FCM token flow.** The PWA reads the current token on demand via `Device.getFcmToken()`; no cached copy in SharedPreferences. `PalmierFirebaseMessagingService.onNewToken` still re-registers with the relay server itself (using the stored `hostId`) because background token refreshes can fire while the PWA isn't running.
|
|
69
|
+
|
|
70
|
+
**Deep links.** FCM notification taps pass a relative path (e.g. `/runs/:taskId/:runId`) via an `Intent` extra named `deepLink`. `MainActivity.handleDeepLink` forwards the path to `DevicePlugin.emitDeepLink`, which emits a `deepLink` event the PWA's router handles client-side. If the plugin isn't ready yet (intent arrives before `onPostCreate`), `MainActivity` buffers the path and flushes in `onPostCreate`. No external `intent-filter` is registered — Android 11+ `<queries>` entries are declared so `hasEmailClient` and installed-app enumeration work without `QUERY_ALL_PACKAGES`.
|
|
71
|
+
|
|
72
|
+
**Notification listener filtering and debounce.** `DeviceNotificationListenerService` drops notifications from (a) Palmier's own `palmier_tasks` channel to avoid feedback loops and (b) the default SMS app (SMS is captured separately via `SmsBroadcastReceiver`, which arrives before the SMS app's notification). Empty-title+body notifications are skipped. A 2-second debounce per `packageName:title` key dedupes rapid updates; the debounce map is LRU-capped at 200 entries.
|
|
73
|
+
|
|
74
|
+
**SMS capture.** Multi-part SMS arrives as multiple PDUs in a single `SMS_RECEIVED` broadcast. `SmsBroadcastReceiver` groups parts by `displayOriginatingAddress` and concatenates bodies before relaying, otherwise long messages would arrive as fragments.
|
|
75
|
+
|
|
76
|
+
**Alarm capability.** `AlarmHandler` posts a `CATEGORY_ALARM` notification on the DND-bypassing `palmier_alarms` channel with a full-screen intent targeting `AlarmActivity`. `AlarmActivity` extends `AppCompatActivity`, shows over the lock screen (`setShowWhenLocked`/`setTurnScreenOn` on O_MR1+, legacy window flags below), and plays the default alarm ringtone on the alarm audio stream via `RingtoneManager`. Requires `USE_FULL_SCREEN_INTENT`; Android 14+ requires the user to grant it in per-app settings.
|
|
77
|
+
|
|
78
|
+
**Email capability.** FCM `send-email` messages post a "Pending email" notification whose tap launches `EmailActivity` — a translucent `Activity` (not `AppCompatActivity`, so `Theme.Translucent` applies) that builds a `mailto:` URI and starts the email app with `ACTION_SENDTO`. The activity auto-finishes on result, returning the user to the previous screen. `hasEmailClient` gates the toggle in the PWA to avoid enabling a capability no installed app can fulfill.
|
|
79
|
+
|
|
80
|
+
**Location capability.** `GeolocationForegroundService` briefly starts as a foreground service (`FOREGROUND_SERVICE_TYPE_LOCATION` on U+) and uses `FusedLocationProviderClient.getCurrentLocation(PRIORITY_HIGH_ACCURACY)` for a single fix before stopping itself. Requires both `ACCESS_FINE_LOCATION` and `ACCESS_BACKGROUND_LOCATION` on Q+; the plugin requests them sequentially.
|
|
81
|
+
|
|
82
|
+
**Releases.** GitHub Actions builds a signed APK and creates a release when a `v*` tag is pushed. The workflow runs `npm ci && npx cap sync` — no PWA build step since the WebView loads remotely.
|
|
52
83
|
|
|
53
84
|
## 2. Host Provisioning & Device Pairing
|
|
54
85
|
|
|
@@ -227,8 +258,8 @@ requires_confirmation: true
|
|
|
227
258
|
|
|
228
259
|
* `"crons"` — `schedule_values` holds cron expressions.
|
|
229
260
|
* `"specific_times"` — `schedule_values` holds local datetime strings (e.g. `"2026-04-20T09:00"`).
|
|
230
|
-
* `"on_new_notification"` — fires once per new Android notification relayed over NATS.
|
|
231
|
-
* `"on_new_sms"` — fires once per new SMS relayed over NATS.
|
|
261
|
+
* `"on_new_notification"` — fires once per new Android notification relayed over NATS. Optional `schedule_values` holds a single-entry packageName filter; empty/unset matches any app.
|
|
262
|
+
* `"on_new_sms"` — fires once per new SMS relayed over NATS. Optional `schedule_values` holds a single-entry sender filter (phone number or alphanumeric shortcode); compared after normalizing away spaces, dashes, parens, plus sign, and case. Empty/unset matches any sender.
|
|
232
263
|
|
|
233
264
|
For `crons` / `specific_times` the schedule is installed as an OS timer (systemd / Task Scheduler). For the `on_new_*` types the `palmier run` process subscribes directly to the corresponding NATS subject (`host.<hostId>.device.notifications` or `...device.sms`), drains events through a bounded FIFO queue, and invokes the agent once per event with the event payload spliced into the user prompt (mirroring command-triggered mode). These event-driven tasks are not started on save — they launch via `task.run` (the PWA auto-runs them on create, matching command-triggered UX).
|
|
234
265
|
|
|
@@ -313,7 +344,7 @@ Dashboard owns the always-on NATS event subscription and renders pending `confir
|
|
|
313
344
|
|
|
314
345
|
4. For updates: if the user changes the `user_prompt` or `agent`, the name is regenerated. If neither changed, the existing name is preserved. Existing tasks with granted permissions show a clickable "Granted Permissions" link to view them.
|
|
315
346
|
|
|
316
|
-
5. PWA sends `task.create` (or `task.update` with `id`) with the task fields as the message body. The `id` field is **not sent on create** — the host generates a UUID. `schedule_type` and `schedule_values` are omitted when the task has no schedule
|
|
347
|
+
5. PWA sends `task.create` (or `task.update` with `id`) with the task fields as the message body. The `id` field is **not sent on create** — the host generates a UUID. `schedule_type` and `schedule_values` are omitted when the task has no schedule. For `on_new_notification` / `on_new_sms` types, `schedule_values` is sent only when a filter is configured (single packageName for notifications, single sender for SMS).
|
|
317
348
|
|
|
318
349
|
6. Host creates/updates the `tasks/<task-id>/TASK.md` file and returns the **full flat task object** (all frontmatter fields at the top level). The PWA uses this response directly to update the UI.
|
|
319
350
|
|
package/src/agents/agent.ts
CHANGED
|
@@ -26,10 +26,6 @@ export interface CommandLine {
|
|
|
26
26
|
env?: Record<string, string>;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* Interface that each agent tool must implement.
|
|
31
|
-
* Abstracts how plans are generated and tasks are executed across different AI agents.
|
|
32
|
-
*/
|
|
33
29
|
export interface AgentTool {
|
|
34
30
|
/** Return the command and args for a short, non-interactive prompt (e.g. generating a task name). */
|
|
35
31
|
getPromptCommandLine(prompt: string): CommandLine;
|
package/src/agents/claude.ts
CHANGED
package/src/agents/codex.ts
CHANGED
|
@@ -23,8 +23,8 @@ export class CodexAgent implements AgentTool {
|
|
|
23
23
|
args.push(`apps.${p.name}.default_tools_approval_mode="approve"`);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
if (followupPrompt) {args.push("resume", "--last");}
|
|
27
|
-
args.push("-");
|
|
26
|
+
if (followupPrompt) {args.push("resume", "--last");}
|
|
27
|
+
args.push("-");
|
|
28
28
|
|
|
29
29
|
return { command: "codex", args, stdin: prompt };
|
|
30
30
|
}
|
package/src/agents/cursor.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class Cursor implements AgentTool {
|
|
|
19
19
|
if (yolo) {
|
|
20
20
|
args.push("--force");
|
|
21
21
|
}
|
|
22
|
-
if (followupPrompt) {args.push("--continue");}
|
|
22
|
+
if (followupPrompt) {args.push("--continue");}
|
|
23
23
|
args.push("-p", prompt);
|
|
24
24
|
|
|
25
25
|
return { command: "cursor", args};
|
package/src/agents/deepagents.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class DeepAgents implements AgentTool {
|
|
|
19
19
|
if (yolo) {
|
|
20
20
|
args.push("--auto-approve");
|
|
21
21
|
}
|
|
22
|
-
if (followupPrompt) {args.push("--resume");}
|
|
22
|
+
if (followupPrompt) {args.push("--resume");}
|
|
23
23
|
args.push("--non-interactive", prompt);
|
|
24
24
|
|
|
25
25
|
return { command: "deepagents", args};
|
package/src/agents/gemini.ts
CHANGED
|
@@ -25,8 +25,9 @@ export class GeminiAgent implements AgentTool {
|
|
|
25
25
|
args.push("--allowed-tools", tools.join(","));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
if (followupPrompt) {args.push("--resume");}
|
|
29
|
-
|
|
28
|
+
if (followupPrompt) {args.push("--resume");}
|
|
29
|
+
// Read prompt from stdin to avoid command-line length limits.
|
|
30
|
+
args.push("--prompt", "-");
|
|
30
31
|
|
|
31
32
|
return { command: "gemini", args, stdin: prompt };
|
|
32
33
|
}
|
package/src/agents/goose.ts
CHANGED
|
@@ -16,7 +16,7 @@ export class GooseAgent implements AgentTool {
|
|
|
16
16
|
const prompt = followupPrompt ?? getAgentInstructions(task, yolo || !this.supportsPermissions);
|
|
17
17
|
const args = ["run"];
|
|
18
18
|
|
|
19
|
-
if (followupPrompt) {args.push("--resume");}
|
|
19
|
+
if (followupPrompt) {args.push("--resume");}
|
|
20
20
|
args.push("--text", prompt);
|
|
21
21
|
|
|
22
22
|
return { command: "goose", args, ...(yolo ? { env: { GOOSE_MODE: "auto" } } : {}) };
|
package/src/agents/hermes.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class Hermes implements AgentTool {
|
|
|
19
19
|
if (yolo) {
|
|
20
20
|
args.push("--trust-all-tools");
|
|
21
21
|
}
|
|
22
|
-
if (followupPrompt) {args.push("--continue");}
|
|
22
|
+
if (followupPrompt) {args.push("--continue");}
|
|
23
23
|
args.push("-q", prompt);
|
|
24
24
|
|
|
25
25
|
return { command: "hermes", args};
|
package/src/agents/kiro.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class Kiro implements AgentTool {
|
|
|
19
19
|
if (yolo) {
|
|
20
20
|
args.push("--trust-all-tools");
|
|
21
21
|
}
|
|
22
|
-
if (followupPrompt) {args.push("--resume");}
|
|
22
|
+
if (followupPrompt) {args.push("--resume");}
|
|
23
23
|
args.push("--no-interactive", prompt);
|
|
24
24
|
|
|
25
25
|
return { command: "kiro-cli", args};
|
package/src/agents/opencode.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class OpenCodeAgent implements AgentTool {
|
|
|
19
19
|
if (yolo) {
|
|
20
20
|
args.push("--dangerously-skip-permissions");
|
|
21
21
|
}
|
|
22
|
-
if (followupPrompt) {args.push("--continue");}
|
|
22
|
+
if (followupPrompt) {args.push("--continue");}
|
|
23
23
|
args.push(prompt);
|
|
24
24
|
|
|
25
25
|
return { command: "opencode", args};
|
package/src/agents/qoder.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class Qoder implements AgentTool {
|
|
|
19
19
|
if (yolo) {
|
|
20
20
|
args.push("--yolo");
|
|
21
21
|
}
|
|
22
|
-
if (followupPrompt) {args.push("-c");}
|
|
22
|
+
if (followupPrompt) {args.push("-c");}
|
|
23
23
|
args.push("-p", prompt);
|
|
24
24
|
|
|
25
25
|
return { command: "qodercli", args};
|
|
@@ -12,9 +12,6 @@ const AGENT_INSTRUCTIONS_TEMPLATE = fs.readFileSync(
|
|
|
12
12
|
"utf-8",
|
|
13
13
|
);
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Build the full agent prompt: instructions + endpoint docs + task description.
|
|
17
|
-
*/
|
|
18
15
|
export function getAgentInstructions(task: ParsedTask, skipPermissions?: boolean): string {
|
|
19
16
|
const port = loadConfig().httpPort ?? 7256;
|
|
20
17
|
const taskDescription = task.frontmatter.user_prompt;
|
package/src/commands/info.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { loadConfig } from "../config.js";
|
|
2
2
|
import { loadClients } from "../client-store.js";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Print host connection info for setting up clients.
|
|
6
|
-
*/
|
|
7
4
|
export async function infoCommand(): Promise<void> {
|
|
8
5
|
const config = loadConfig();
|
|
9
6
|
const clients = loadClients();
|
|
@@ -11,14 +8,12 @@ export async function infoCommand(): Promise<void> {
|
|
|
11
8
|
console.log(`Host ID: ${config.hostId}`);
|
|
12
9
|
console.log(`Project root: ${config.projectRoot}`);
|
|
13
10
|
|
|
14
|
-
// Detected agents
|
|
15
11
|
if (config.agents && config.agents.length > 0) {
|
|
16
12
|
console.log(`Agents: ${config.agents.map((a) => a.label).join(", ")}`);
|
|
17
13
|
} else {
|
|
18
14
|
console.log(`Agents: (none detected — run \`palmier agents\`)`);
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
// Clients
|
|
22
17
|
console.log(`Clients: ${clients.length} active`);
|
|
23
18
|
|
|
24
19
|
if (clients.length === 0) {
|
package/src/commands/init.ts
CHANGED
|
@@ -15,9 +15,6 @@ const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
|
|
|
15
15
|
const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
|
|
16
16
|
const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Interactive wizard to provision this host.
|
|
20
|
-
*/
|
|
21
18
|
export async function initCommand(): Promise<void> {
|
|
22
19
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
20
|
const ask: AskFn = (q) => new Promise<string>((resolve) => rl.question(q, resolve));
|
|
@@ -27,7 +24,6 @@ export async function initCommand(): Promise<void> {
|
|
|
27
24
|
console.log(`By continuing, you agree to the ${cyan("Terms of Service")} (https://www.palmier.me/terms)`);
|
|
28
25
|
console.log(`and ${cyan("Privacy Policy")} (https://www.palmier.me/privacy).\n`);
|
|
29
26
|
|
|
30
|
-
// Detect agents first — abort if none found
|
|
31
27
|
console.log("Detecting installed agents...");
|
|
32
28
|
const agents = await detectAgents();
|
|
33
29
|
|
|
@@ -41,7 +37,6 @@ export async function initCommand(): Promise<void> {
|
|
|
41
37
|
|
|
42
38
|
console.log(` Found: ${green(agents.map((a) => a.label).join(", "))}\n`);
|
|
43
39
|
|
|
44
|
-
// LAN mode
|
|
45
40
|
const lanAnswer = await ask("Enable LAN access (direct HTTP from local network)? (y/N): ");
|
|
46
41
|
const lanEnabled = lanAnswer.trim().toLowerCase() === "y";
|
|
47
42
|
|
|
@@ -51,7 +46,6 @@ export async function initCommand(): Promise<void> {
|
|
|
51
46
|
const parsed = parseInt(portAnswer.trim(), 10);
|
|
52
47
|
if (parsed > 0 && parsed < 65536) httpPort = parsed;
|
|
53
48
|
|
|
54
|
-
// Display summary and ask for confirmation before making any changes
|
|
55
49
|
console.log(`\n${bold("Setup summary:")}\n`);
|
|
56
50
|
console.log(` ${dim("Task storage:")} ${bold(process.cwd())}`);
|
|
57
51
|
console.log(` All tasks and execution data will be stored here.\n`);
|
|
@@ -64,7 +58,6 @@ export async function initCommand(): Promise<void> {
|
|
|
64
58
|
}
|
|
65
59
|
console.log(` ${dim("Agents:")} ${agents.map((a) => a.label).join(", ")}\n`);
|
|
66
60
|
|
|
67
|
-
// Check for existing tasks to recover
|
|
68
61
|
const existingTasks = listTasks(process.cwd());
|
|
69
62
|
if (existingTasks.length > 0) {
|
|
70
63
|
console.log(` ${dim("Recover tasks:")} ${existingTasks.length} existing task(s) found:`);
|
|
@@ -81,7 +74,6 @@ export async function initCommand(): Promise<void> {
|
|
|
81
74
|
return;
|
|
82
75
|
}
|
|
83
76
|
|
|
84
|
-
// Register with server
|
|
85
77
|
let existingHostId: string | undefined;
|
|
86
78
|
try { existingHostId = loadConfig().hostId; } catch { /* first init */ }
|
|
87
79
|
|
|
@@ -105,7 +97,6 @@ export async function initCommand(): Promise<void> {
|
|
|
105
97
|
}
|
|
106
98
|
}
|
|
107
99
|
|
|
108
|
-
// Build and save config
|
|
109
100
|
const config: HostConfig = {
|
|
110
101
|
hostId: registerResponse.hostId,
|
|
111
102
|
projectRoot: process.cwd(),
|
|
@@ -123,8 +114,8 @@ export async function initCommand(): Promise<void> {
|
|
|
123
114
|
|
|
124
115
|
getPlatform().installDaemon(config);
|
|
125
116
|
|
|
126
|
-
// Task recovery
|
|
127
|
-
//
|
|
117
|
+
// Task recovery runs in the daemon (palmier serve) because that process
|
|
118
|
+
// is elevated and can create S4U scheduled tasks.
|
|
128
119
|
|
|
129
120
|
console.log("\nStarting pairing...");
|
|
130
121
|
rl.close();
|
package/src/commands/pair.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as http from "node:http";
|
|
2
|
+
import * as os from "node:os";
|
|
2
3
|
import { StringCodec } from "nats";
|
|
3
4
|
import { loadConfig } from "../config.js";
|
|
4
5
|
import { connectNats } from "../nats-client.js";
|
|
@@ -21,12 +22,10 @@ function buildPairResponse(config: HostConfig, label?: string) {
|
|
|
21
22
|
return {
|
|
22
23
|
hostId: config.hostId,
|
|
23
24
|
clientToken: client.token,
|
|
25
|
+
hostName: os.hostname(),
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
/**
|
|
28
|
-
* POST to the running serve daemon and long-poll until paired or expired.
|
|
29
|
-
*/
|
|
30
29
|
function httpPairRegister(port: number, code: string): Promise<boolean> {
|
|
31
30
|
const body = JSON.stringify({ code, expiryMs: PAIRING_EXPIRY_MS });
|
|
32
31
|
|
|
@@ -60,10 +59,7 @@ function httpPairRegister(port: number, code: string): Promise<boolean> {
|
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
/**
|
|
64
|
-
* Generate a pairing code and wait for a PWA client to pair.
|
|
65
|
-
* Listens on NATS (server mode) and HTTP (via serve daemon) in parallel.
|
|
66
|
-
*/
|
|
62
|
+
/** Listens on NATS (server mode) and HTTP (via serve daemon) in parallel. */
|
|
67
63
|
export async function pairCommand(): Promise<void> {
|
|
68
64
|
const config = loadConfig();
|
|
69
65
|
const code = generatePairingCode();
|
|
@@ -78,7 +74,6 @@ export async function pairCommand(): Promise<void> {
|
|
|
78
74
|
|
|
79
75
|
const cleanups: Array<() => void | Promise<void>> = [];
|
|
80
76
|
|
|
81
|
-
// Display pairing info
|
|
82
77
|
console.log("");
|
|
83
78
|
console.log("Enter this code in your Palmier app:");
|
|
84
79
|
console.log("");
|
|
@@ -86,7 +81,6 @@ export async function pairCommand(): Promise<void> {
|
|
|
86
81
|
console.log("");
|
|
87
82
|
console.log("Code expires in 1 minute.");
|
|
88
83
|
|
|
89
|
-
// NATS pairing (server mode)
|
|
90
84
|
const nc = await connectNats(config);
|
|
91
85
|
const sc = StringCodec();
|
|
92
86
|
const subject = `pair.${code}`;
|
|
@@ -116,13 +110,11 @@ export async function pairCommand(): Promise<void> {
|
|
|
116
110
|
}
|
|
117
111
|
})();
|
|
118
112
|
|
|
119
|
-
// HTTP pairing — register with serve daemon's /pair-register endpoint
|
|
120
113
|
(async () => {
|
|
121
114
|
const result = await httpPairRegister(httpPort, code);
|
|
122
115
|
if (result) onPaired();
|
|
123
116
|
})();
|
|
124
117
|
|
|
125
|
-
// Wait for pairing or timeout
|
|
126
118
|
const start = Date.now();
|
|
127
119
|
await new Promise<void>((resolve) => {
|
|
128
120
|
const interval = setInterval(() => {
|
|
@@ -133,7 +125,6 @@ export async function pairCommand(): Promise<void> {
|
|
|
133
125
|
}, 500);
|
|
134
126
|
});
|
|
135
127
|
|
|
136
|
-
// Cleanup
|
|
137
128
|
for (const cleanup of cleanups) {
|
|
138
129
|
await cleanup();
|
|
139
130
|
}
|
package/src/commands/restart.ts
CHANGED