palmier 0.9.24 → 0.9.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,14 +11,9 @@ Palmier installs, manages, and runs AI agent CLIs (Claude Code, Gemini CLI, Code
11
11
  The control surface is bidirectional:
12
12
 
13
13
  * **Phone → agents:** start ad-hoc sessions, register schedule- or event-triggered tasks, inspect session output, and respond to agent input/confirmation requests.
14
- * **Agents → phone:** agents can call MCP tools to read device state (location, calendar, contacts, notifications, SMS, battery) and trigger actions (push notifications, full-screen alarms, SMS, email, contact/calendar writes, ringer mode).
14
+ * **Agents → phone:** agents can read device state (location, calendar, contacts, notifications, SMS, battery) and trigger actions (push notifications, full-screen alarms, SMS, email, contact/calendar writes, ringer mode).
15
15
 
16
- Capability access is opt-in per device: each MCP tool is gated behind an Android permission and a per-host toggle. An optional yolo mode auto-approves agent input/confirmation requests.
17
-
18
- It is not:
19
-
20
- * an agent runtime itself — Palmier shells out to the agent CLI and streams its stdio
21
- * a system for driving phone UI like a human tapping through apps — phone access is via OS-level APIs (FCM data messages, content providers, calendar/contacts APIs), not UI automation
16
+ Capability access is opt-in per device: each capability is gated behind an Android permission and a per-host toggle. An optional yolo mode auto-approves agent input/confirmation requests.
22
17
 
23
18
  ## Quick Start
24
19
 
@@ -29,12 +24,12 @@ It is not:
29
24
  curl -fsSL https://palmier.me/install.sh | bash
30
25
  ```
31
26
 
32
- **Windows (PowerShell):**
27
+ **Windows:**
33
28
  ```powershell
34
- irm https://palmier.me/install.ps1 | iex
29
+ powershell -c "irm https://palmier.me/install.ps1 | iex"
35
30
  ```
36
31
 
37
- The one-liner installs Node.js 24+ if needed (via [fnm](https://github.com/Schniz/fnm) on Linux/macOS, winget on Windows), installs `palmier` globally, and runs the setup wizard. If you already have Node.js 24+ and npm:
32
+ The one-liner installs Node.js 24+ if needed, installs `palmier` globally, and runs the setup wizard. If you already have Node.js 24+ and npm:
38
33
  ```bash
39
34
  npm install -g palmier && palmier init
40
35
  ```
@@ -73,6 +73,7 @@ const agentRegistry = {
73
73
  qoder: qoderAgent,
74
74
  hermes: hermesAgent,
75
75
  };
76
+ const TIER_ONE_ORDER = ["claude", "gemini", "codex", "copilot"];
76
77
  export function listInstallableAgents() {
77
78
  const out = [];
78
79
  for (const [key, agent] of Object.entries(agentRegistry)) {
@@ -86,7 +87,17 @@ export function listInstallableAgents() {
86
87
  ...(agent.freeUsage ? { freeUsage: agent.freeUsage } : {}),
87
88
  });
88
89
  }
89
- return out;
90
+ return out.sort((a, b) => {
91
+ const ai = TIER_ONE_ORDER.indexOf(a.key);
92
+ const bi = TIER_ONE_ORDER.indexOf(b.key);
93
+ if (ai !== -1 && bi !== -1)
94
+ return ai - bi;
95
+ if (ai !== -1)
96
+ return -1;
97
+ if (bi !== -1)
98
+ return 1;
99
+ return 0;
100
+ });
90
101
  }
91
102
  /** Detect agents present on PATH and resolve their version when they are
92
103
  * Palmier-managed. An agent is treated as managed if either:
@@ -27,9 +27,7 @@ export function printInstalledAgents(agents) {
27
27
  * cancelled, no installables remain, or the install failed. */
28
28
  export async function pickAndInstallAgent(current, options = {}) {
29
29
  const detectedKeys = new Set(current.map((a) => a.key));
30
- const missing = listInstallableAgents()
31
- .filter((a) => !detectedKeys.has(a.key))
32
- .sort((a, b) => a.label.localeCompare(b.label));
30
+ const missing = listInstallableAgents().filter((a) => !detectedKeys.has(a.key));
33
31
  if (missing.length === 0) {
34
32
  console.log(`\n${dim("All supported agents are already installed.")}`);
35
33
  return null;
@@ -189,7 +187,7 @@ function runAgentAuthFlow(label, command, args) {
189
187
  console.log(`Re-run ${cyan(cmd)} manually after this.\n`);
190
188
  return;
191
189
  }
192
- console.log(green(`Successfully authenticated ${label}.\n`));
190
+ console.log(green(`Finished authenticating ${label}.\n`));
193
191
  }
194
192
  async function waitForEnter(message) {
195
193
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
package/dist/mcp-tools.js CHANGED
@@ -1,8 +1,10 @@
1
+ import * as fs from "fs";
1
2
  import { StringCodec } from "nats";
2
3
  import { registerPending } from "./pending-requests.js";
3
4
  import { getLinkedDevice } from "./linked-device.js";
4
5
  import { getNotifications, onNotificationsChanged } from "./notification-store.js";
5
6
  import { getSmsMessages, onSmsChanged } from "./sms-store.js";
7
+ import { getTaskDir, readHistory, spliceUserMessage } from "./task.js";
6
8
  export class ToolError extends Error {
7
9
  statusCode;
8
10
  constructor(message, statusCode = 500) {
@@ -84,7 +86,9 @@ const requestInputTool = {
84
86
  input_questions: questions,
85
87
  });
86
88
  const response = await pendingPromise;
87
- if (response.length === 1 && response[0] === "aborted") {
89
+ const aborted = response.length === 1 && response[0] === "aborted";
90
+ recordUserInputToRun(ctx, questions, response, aborted);
91
+ if (aborted) {
88
92
  await ctx.publishEvent("_input", {
89
93
  event_type: "input-resolved", host_id: ctx.config.hostId,
90
94
  session_id: ctx.sessionId, status: "aborted",
@@ -98,6 +102,40 @@ const requestInputTool = {
98
102
  return { values: response };
99
103
  },
100
104
  };
105
+ /**
106
+ * Splice the user's answer into the task run file so the conversation history
107
+ * shows what the user typed, independent of whether the agent echoes it.
108
+ * Only writes when sessionId resolves to a task with an active run (i.e. the
109
+ * agent called via the REST endpoint with taskId); silently skips for pure
110
+ * MCP-protocol sessions where sessionId is a random UUID.
111
+ */
112
+ function recordUserInputToRun(ctx, questions, response, aborted) {
113
+ const taskId = ctx.sessionId;
114
+ if (!taskId)
115
+ return;
116
+ const taskDir = getTaskDir(ctx.config.projectRoot, taskId);
117
+ if (!fs.existsSync(taskDir))
118
+ return;
119
+ const { entries } = readHistory(ctx.config.projectRoot, { task_id: taskId, limit: 1 });
120
+ const runId = entries[0]?.run_id;
121
+ if (!runId)
122
+ return;
123
+ const content = aborted
124
+ ? "Cancel"
125
+ : questions.map((q, i) => `**${q}**\n\n${response[i] ?? ""}`).join("\n\n");
126
+ try {
127
+ spliceUserMessage(taskDir, runId, {
128
+ role: "user",
129
+ time: Date.now(),
130
+ content,
131
+ type: "input",
132
+ });
133
+ void ctx.publishEvent(taskId, { event_type: "result-updated", run_id: runId });
134
+ }
135
+ catch {
136
+ // Run file missing or unwritable — best-effort, do not fail the tool call.
137
+ }
138
+ }
101
139
  const requestConfirmationTool = {
102
140
  name: "request-confirmation",
103
141
  description: [