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.
Files changed (133) hide show
  1. package/CLAUDE.md +13 -0
  2. package/README.md +16 -14
  3. package/dist/agents/agent.d.ts +0 -4
  4. package/dist/agents/claude.js +1 -1
  5. package/dist/agents/codex.js +2 -2
  6. package/dist/agents/cursor.js +1 -1
  7. package/dist/agents/deepagents.js +1 -1
  8. package/dist/agents/gemini.js +3 -2
  9. package/dist/agents/goose.js +1 -1
  10. package/dist/agents/hermes.js +1 -1
  11. package/dist/agents/kiro.js +1 -1
  12. package/dist/agents/opencode.js +1 -1
  13. package/dist/agents/qoder.js +1 -1
  14. package/dist/agents/shared-prompt.d.ts +0 -3
  15. package/dist/agents/shared-prompt.js +0 -3
  16. package/dist/commands/info.d.ts +0 -3
  17. package/dist/commands/info.js +0 -5
  18. package/dist/commands/init.d.ts +0 -3
  19. package/dist/commands/init.js +2 -11
  20. package/dist/commands/pair.d.ts +1 -4
  21. package/dist/commands/pair.js +3 -12
  22. package/dist/commands/restart.d.ts +0 -3
  23. package/dist/commands/restart.js +0 -3
  24. package/dist/commands/run.d.ts +1 -14
  25. package/dist/commands/run.js +18 -61
  26. package/dist/commands/serve.d.ts +0 -3
  27. package/dist/commands/serve.js +29 -27
  28. package/dist/config.d.ts +0 -8
  29. package/dist/config.js +0 -8
  30. package/dist/device-capabilities.d.ts +1 -1
  31. package/dist/event-queues.d.ts +6 -21
  32. package/dist/event-queues.js +6 -21
  33. package/dist/events.d.ts +0 -6
  34. package/dist/events.js +1 -9
  35. package/dist/index.js +0 -1
  36. package/dist/mcp-handler.js +1 -2
  37. package/dist/mcp-tools.d.ts +0 -3
  38. package/dist/mcp-tools.js +12 -16
  39. package/dist/nats-client.d.ts +0 -3
  40. package/dist/nats-client.js +1 -4
  41. package/dist/pending-requests.d.ts +4 -18
  42. package/dist/pending-requests.js +4 -18
  43. package/dist/platform/index.d.ts +1 -4
  44. package/dist/platform/index.js +8 -7
  45. package/dist/platform/linux.d.ts +3 -9
  46. package/dist/platform/linux.js +9 -20
  47. package/dist/platform/macos.d.ts +32 -0
  48. package/dist/platform/macos.js +287 -0
  49. package/dist/platform/platform.d.ts +1 -4
  50. package/dist/platform/windows.d.ts +2 -5
  51. package/dist/platform/windows.js +19 -39
  52. package/dist/pwa/assets/index-499vYQvR.js +120 -0
  53. package/dist/pwa/assets/{index-CQxcuDhM.css → index-UaZFu6XL.css} +1 -1
  54. package/dist/pwa/assets/{web-DOyOiwsW.js → web-Bp48ONY3.js} +1 -1
  55. package/dist/pwa/assets/{web-D7Kq3Nvk.js → web-CyJutAy4.js} +1 -1
  56. package/dist/pwa/index.html +2 -2
  57. package/dist/pwa/service-worker.js +1 -1
  58. package/dist/rpc-handler.d.ts +0 -6
  59. package/dist/rpc-handler.js +14 -47
  60. package/dist/spawn-command.d.ts +10 -25
  61. package/dist/spawn-command.js +7 -15
  62. package/dist/task.d.ts +6 -64
  63. package/dist/task.js +7 -70
  64. package/dist/transports/http-transport.d.ts +0 -4
  65. package/dist/transports/http-transport.js +7 -28
  66. package/dist/transports/nats-transport.d.ts +0 -4
  67. package/dist/transports/nats-transport.js +3 -9
  68. package/dist/types.d.ts +3 -7
  69. package/dist/update-checker.d.ts +1 -4
  70. package/dist/update-checker.js +2 -5
  71. package/package.json +1 -1
  72. package/palmier-server/pwa/src/App.css +325 -22
  73. package/palmier-server/pwa/src/App.tsx +2 -0
  74. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +288 -0
  75. package/palmier-server/pwa/src/components/HostMenu.tsx +20 -207
  76. package/palmier-server/pwa/src/components/RunDetailView.tsx +3 -3
  77. package/palmier-server/pwa/src/components/SessionComposer.tsx +11 -2
  78. package/palmier-server/pwa/src/components/SessionsView.tsx +60 -32
  79. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +160 -0
  80. package/palmier-server/pwa/src/components/TaskCard.tsx +1 -1
  81. package/palmier-server/pwa/src/components/TaskForm.tsx +207 -5
  82. package/palmier-server/pwa/src/components/TasksView.tsx +3 -1
  83. package/palmier-server/pwa/src/constants.ts +1 -1
  84. package/palmier-server/pwa/src/native/Device.ts +18 -2
  85. package/palmier-server/pwa/src/pages/Dashboard.tsx +13 -6
  86. package/palmier-server/pwa/src/pages/PairHost.tsx +3 -1
  87. package/palmier-server/pwa/src/pages/PairSetup.tsx +70 -0
  88. package/palmier-server/server/src/index.ts +7 -7
  89. package/palmier-server/server/src/routes/device.ts +4 -4
  90. package/palmier-server/spec.md +38 -7
  91. package/src/agents/agent.ts +0 -4
  92. package/src/agents/claude.ts +1 -1
  93. package/src/agents/codex.ts +2 -2
  94. package/src/agents/cursor.ts +1 -1
  95. package/src/agents/deepagents.ts +1 -1
  96. package/src/agents/gemini.ts +3 -2
  97. package/src/agents/goose.ts +1 -1
  98. package/src/agents/hermes.ts +1 -1
  99. package/src/agents/kiro.ts +1 -1
  100. package/src/agents/opencode.ts +1 -1
  101. package/src/agents/qoder.ts +1 -1
  102. package/src/agents/shared-prompt.ts +0 -3
  103. package/src/commands/info.ts +0 -5
  104. package/src/commands/init.ts +2 -11
  105. package/src/commands/pair.ts +3 -12
  106. package/src/commands/restart.ts +0 -3
  107. package/src/commands/run.ts +18 -65
  108. package/src/commands/serve.ts +28 -27
  109. package/src/config.ts +0 -8
  110. package/src/device-capabilities.ts +3 -2
  111. package/src/event-queues.ts +6 -21
  112. package/src/events.ts +1 -9
  113. package/src/index.ts +0 -1
  114. package/src/mcp-handler.ts +1 -2
  115. package/src/mcp-tools.ts +12 -18
  116. package/src/nats-client.ts +1 -4
  117. package/src/pending-requests.ts +4 -18
  118. package/src/platform/index.ts +5 -7
  119. package/src/platform/linux.ts +9 -20
  120. package/src/platform/macos.ts +310 -0
  121. package/src/platform/platform.ts +1 -4
  122. package/src/platform/windows.ts +19 -40
  123. package/src/rpc-handler.ts +14 -47
  124. package/src/spawn-command.ts +11 -27
  125. package/src/task.ts +7 -70
  126. package/src/transports/http-transport.ts +7 -39
  127. package/src/transports/nats-transport.ts +3 -9
  128. package/src/types.ts +3 -10
  129. package/src/update-checker.ts +2 -5
  130. package/test/macos-plist.test.ts +112 -0
  131. package/test/task-parsing.test.ts +2 -3
  132. package/test/windows-xml.test.ts +11 -12
  133. package/dist/pwa/assets/index-DQfOEB03.js +0 -120
@@ -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`, `set-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.
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 receive**: `SmsBroadcastReceiver` — requires `RECEIVE_SMS` + `SEND_SMS` runtime permissions
22
- - **SMS send**: `SmsHandler` — requires `SEND_SMS` runtime permission (shared with SMS receive toggle)
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 `SET_ALARM` (normal permission, auto-granted)
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. No `schedule_values`.
231
- * `"on_new_sms"` — fires once per new SMS relayed over NATS. No `schedule_values`.
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; `schedule_values` is also omitted for the `on_new_notification` / `on_new_sms` types.
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
 
@@ -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;
@@ -24,7 +24,7 @@ export class ClaudeAgent implements AgentTool {
24
24
  }
25
25
  }
26
26
 
27
- if (followupPrompt) {args.push("-c");} // continue mode for followups
27
+ if (followupPrompt) {args.push("-c");}
28
28
  return { command: "claude", args, stdin: prompt };
29
29
  }
30
30
 
@@ -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");} // continue mode for followups
27
- args.push("-"); // read prompt from stdin
26
+ if (followupPrompt) {args.push("resume", "--last");}
27
+ args.push("-");
28
28
 
29
29
  return { command: "codex", args, stdin: prompt };
30
30
  }
@@ -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");} // continue mode for followups
22
+ if (followupPrompt) {args.push("--continue");}
23
23
  args.push("-p", prompt);
24
24
 
25
25
  return { command: "cursor", args};
@@ -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");} // continue mode for followups
22
+ if (followupPrompt) {args.push("--resume");}
23
23
  args.push("--non-interactive", prompt);
24
24
 
25
25
  return { command: "deepagents", args};
@@ -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");} // continue mode for followups
29
- args.push("--prompt", "-"); // read prompt from stdin to avoid command line length limits
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
  }
@@ -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");} // continue mode for followups
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" } } : {}) };
@@ -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");} // continue mode for followups
22
+ if (followupPrompt) {args.push("--continue");}
23
23
  args.push("-q", prompt);
24
24
 
25
25
  return { command: "hermes", args};
@@ -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");} // continue mode for followups
22
+ if (followupPrompt) {args.push("--resume");}
23
23
  args.push("--no-interactive", prompt);
24
24
 
25
25
  return { command: "kiro-cli", args};
@@ -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");} // continue mode for followups
22
+ if (followupPrompt) {args.push("--continue");}
23
23
  args.push(prompt);
24
24
 
25
25
  return { command: "opencode", args};
@@ -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");} // continue mode for followups
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;
@@ -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) {
@@ -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 happens in the daemon (palmier serve) on startup,
127
- // since the daemon runs elevated and can create S4U scheduled tasks.
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();
@@ -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
  }
@@ -1,8 +1,5 @@
1
1
  import { getPlatform } from "../platform/index.js";
2
2
 
3
- /**
4
- * Restart the palmier serve daemon.
5
- */
6
3
  export async function restartCommand(): Promise<void> {
7
4
  const platform = getPlatform();
8
5
  await platform.restartDaemon();