palmier 0.7.6 → 0.7.7
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/dist/agents/shared-prompt.js +1 -1
- package/dist/commands/init.js +3 -2
- package/dist/commands/pair.js +1 -1
- package/dist/commands/run.js +4 -4
- package/dist/commands/serve.js +1 -1
- package/dist/config.js +2 -2
- package/dist/device-capabilities.d.ts +1 -1
- package/dist/events.js +1 -1
- package/dist/mcp-tools.js +64 -1
- package/dist/nats-client.d.ts +1 -1
- package/dist/nats-client.js +6 -3
- package/dist/pwa/assets/index-Bt8Hhaw3.js +118 -0
- package/dist/pwa/assets/{web-DnuoxUd4.js → web-CkWrlNwc.js} +1 -1
- package/dist/pwa/assets/{web-7raT3zOZ.js → web-lx34oBi7.js} +1 -1
- package/dist/pwa/index.html +1 -1
- package/dist/pwa/service-worker.js +1 -1
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- package/palmier-server/PRODUCTION.md +31 -28
- package/palmier-server/README.md +35 -5
- package/palmier-server/nats.conf +9 -5
- package/palmier-server/package.json +2 -1
- package/palmier-server/pnpm-lock.yaml +6 -0
- package/palmier-server/pwa/src/components/HostMenu.tsx +58 -0
- package/palmier-server/pwa/src/constants.ts +1 -1
- package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +9 -5
- package/palmier-server/pwa/src/pages/PairHost.tsx +6 -3
- package/palmier-server/server/package.json +3 -1
- package/palmier-server/server/src/index.ts +83 -2
- package/palmier-server/server/src/nats-jwt.ts +299 -0
- package/palmier-server/server/src/nats-setup.ts +48 -0
- package/palmier-server/server/src/nats.ts +12 -4
- package/palmier-server/server/src/routes/device.ts +24 -0
- package/palmier-server/server/src/routes/hosts.ts +13 -2
- package/palmier-server/spec.md +6 -5
- package/src/agents/shared-prompt.ts +1 -1
- package/src/commands/init.ts +7 -5
- package/src/commands/pair.ts +1 -1
- package/src/commands/run.ts +4 -4
- package/src/commands/serve.ts +1 -1
- package/src/config.ts +2 -2
- package/src/device-capabilities.ts +1 -0
- package/src/events.ts +1 -1
- package/src/mcp-tools.ts +68 -1
- package/src/nats-client.ts +10 -3
- package/src/types.ts +3 -2
- package/test/agent-instructions.test.ts +10 -10
- package/dist/pwa/assets/index-uSwkmHBs.js +0 -118
|
@@ -9,7 +9,7 @@ const AGENT_INSTRUCTIONS_TEMPLATE = fs.readFileSync(path.join(__dirname, "agent-
|
|
|
9
9
|
* Build the full agent prompt: instructions + endpoint docs + task description.
|
|
10
10
|
*/
|
|
11
11
|
export function getAgentInstructions(task, skipPermissions) {
|
|
12
|
-
const port = loadConfig().httpPort ??
|
|
12
|
+
const port = loadConfig().httpPort ?? 7256;
|
|
13
13
|
const taskDescription = task.frontmatter.user_prompt;
|
|
14
14
|
let instructions = AGENT_INSTRUCTIONS_TEMPLATE
|
|
15
15
|
.replace(/\{\{ENDPOINT_DOCS\}\}/g, generateEndpointDocs(port, task.frontmatter.id))
|
package/dist/commands/init.js
CHANGED
|
@@ -34,7 +34,7 @@ export async function initCommand() {
|
|
|
34
34
|
// LAN mode
|
|
35
35
|
const lanAnswer = await ask("Enable LAN access (direct HTTP from local network)? (y/N): ");
|
|
36
36
|
const lanEnabled = lanAnswer.trim().toLowerCase() === "y";
|
|
37
|
-
let httpPort =
|
|
37
|
+
let httpPort = 7256;
|
|
38
38
|
const portLabel = lanEnabled ? "HTTP port for local and LAN access" : "HTTP port for local access";
|
|
39
39
|
const portAnswer = await ask(`${portLabel} (default ${httpPort}): `);
|
|
40
40
|
const parsed = parseInt(portAnswer.trim(), 10);
|
|
@@ -98,7 +98,8 @@ export async function initCommand() {
|
|
|
98
98
|
projectRoot: process.cwd(),
|
|
99
99
|
natsUrl: registerResponse.natsUrl,
|
|
100
100
|
natsWsUrl: registerResponse.natsWsUrl,
|
|
101
|
-
|
|
101
|
+
natsJwt: registerResponse.natsJwt,
|
|
102
|
+
natsNkeySeed: registerResponse.natsNkeySeed,
|
|
102
103
|
agents,
|
|
103
104
|
httpPort,
|
|
104
105
|
lanEnabled,
|
package/dist/commands/pair.js
CHANGED
|
@@ -56,7 +56,7 @@ function httpPairRegister(port, code) {
|
|
|
56
56
|
export async function pairCommand() {
|
|
57
57
|
const config = loadConfig();
|
|
58
58
|
const code = generatePairingCode();
|
|
59
|
-
const httpPort = config.httpPort ??
|
|
59
|
+
const httpPort = config.httpPort ?? 7256;
|
|
60
60
|
let paired = false;
|
|
61
61
|
function onPaired() {
|
|
62
62
|
paired = true;
|
package/dist/commands/run.js
CHANGED
|
@@ -36,7 +36,7 @@ async function invokeAgentWithRetries(ctx, invokeTask) {
|
|
|
36
36
|
const { command, args, stdin, env: agentEnv } = ctx.agent.getTaskRunCommandLine(invokeTask, undefined, ctx.task.frontmatter.yolo_mode ? "yolo" : ctx.transientPermissions);
|
|
37
37
|
const result = await spawnCommand(command, args, {
|
|
38
38
|
cwd: getRunDir(ctx.taskDir, ctx.runId),
|
|
39
|
-
env: { ...ctx.guiEnv, ...agentEnv, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ??
|
|
39
|
+
env: { ...ctx.guiEnv, ...agentEnv, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ?? 7256) },
|
|
40
40
|
echoStdout: true,
|
|
41
41
|
resolveOnFailure: true,
|
|
42
42
|
stdin,
|
|
@@ -258,7 +258,7 @@ async function runCommandTriggeredMode(ctx) {
|
|
|
258
258
|
await publishHostEvent(ctx.nc, ctx.config.hostId, ctx.taskId, { event_type: "result-updated", run_id: ctx.runId });
|
|
259
259
|
const child = spawnStreamingCommand(commandStr, {
|
|
260
260
|
cwd: getRunDir(ctx.taskDir, ctx.runId),
|
|
261
|
-
env: { ...ctx.guiEnv, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ??
|
|
261
|
+
env: { ...ctx.guiEnv, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ?? 7256) },
|
|
262
262
|
});
|
|
263
263
|
let linesProcessed = 0;
|
|
264
264
|
let invocationsSucceeded = 0;
|
|
@@ -394,7 +394,7 @@ async function publishTaskEvent(nc, config, taskDir, taskId, eventType, taskName
|
|
|
394
394
|
await publishHostEvent(nc, config.hostId, taskId, payload);
|
|
395
395
|
}
|
|
396
396
|
async function requestPermission(config, task, taskDir, requiredPermissions) {
|
|
397
|
-
const port = config.httpPort ??
|
|
397
|
+
const port = config.httpPort ?? 7256;
|
|
398
398
|
const res = await fetch(`http://localhost:${port}/request-permission`, {
|
|
399
399
|
method: "POST",
|
|
400
400
|
headers: { "Content-Type": "application/json" },
|
|
@@ -416,7 +416,7 @@ async function requestPermission(config, task, taskDir, requiredPermissions) {
|
|
|
416
416
|
return response;
|
|
417
417
|
}
|
|
418
418
|
async function requestConfirmation(config, task, taskDir) {
|
|
419
|
-
const port = config.httpPort ??
|
|
419
|
+
const port = config.httpPort ?? 7256;
|
|
420
420
|
const res = await fetch(`http://localhost:${port}/request-confirmation?taskId=${encodeURIComponent(task.frontmatter.id)}`, {
|
|
421
421
|
method: "POST",
|
|
422
422
|
headers: { "Content-Type": "application/json" },
|
package/dist/commands/serve.js
CHANGED
|
@@ -113,7 +113,7 @@ export async function serveCommand() {
|
|
|
113
113
|
});
|
|
114
114
|
}, POLL_INTERVAL_MS);
|
|
115
115
|
const handleRpc = createRpcHandler(config, nc);
|
|
116
|
-
const httpPort = config.httpPort ??
|
|
116
|
+
const httpPort = config.httpPort ?? 7256;
|
|
117
117
|
// Start NATS transport (loops forever, fire-and-forget)
|
|
118
118
|
if (nc) {
|
|
119
119
|
startNatsTransport(config, handleRpc, nc);
|
package/dist/config.js
CHANGED
|
@@ -17,8 +17,8 @@ export function loadConfig() {
|
|
|
17
17
|
if (!config.hostId) {
|
|
18
18
|
throw new Error("Invalid host config: missing hostId");
|
|
19
19
|
}
|
|
20
|
-
if (!config.natsUrl || !config.
|
|
21
|
-
throw new Error("Invalid host config: missing
|
|
20
|
+
if (!config.natsUrl || !config.natsJwt || !config.natsNkeySeed) {
|
|
21
|
+
throw new Error("Invalid host config: missing NATS JWT credentials. Re-run palmier init.");
|
|
22
22
|
}
|
|
23
23
|
return config;
|
|
24
24
|
}
|
|
@@ -2,7 +2,7 @@ export interface RegisteredDevice {
|
|
|
2
2
|
clientToken: string;
|
|
3
3
|
fcmToken: string;
|
|
4
4
|
}
|
|
5
|
-
export type DeviceCapability = "location" | "notifications" | "sms" | "contacts" | "calendar" | "alert" | "battery" | "dnd";
|
|
5
|
+
export type DeviceCapability = "location" | "notifications" | "sms" | "contacts" | "calendar" | "alert" | "battery" | "email" | "dnd";
|
|
6
6
|
export declare function getCapabilityDevice(capability: DeviceCapability): RegisteredDevice | null;
|
|
7
7
|
export declare function setCapabilityDevice(capability: DeviceCapability, clientToken: string, fcmToken: string): void;
|
|
8
8
|
export declare function clearCapabilityDevice(capability: DeviceCapability): void;
|
package/dist/events.js
CHANGED
|
@@ -14,7 +14,7 @@ export async function publishHostEvent(nc, hostId, taskId, payload) {
|
|
|
14
14
|
console.log(`[nats] ${subject} →`, payload);
|
|
15
15
|
}
|
|
16
16
|
const config = loadConfig();
|
|
17
|
-
const port = config.httpPort ??
|
|
17
|
+
const port = config.httpPort ?? 7256;
|
|
18
18
|
try {
|
|
19
19
|
await fetch(`http://localhost:${port}/event`, {
|
|
20
20
|
method: "POST",
|
package/dist/mcp-tools.js
CHANGED
|
@@ -567,7 +567,70 @@ const setRingerModeTool = {
|
|
|
567
567
|
return result;
|
|
568
568
|
},
|
|
569
569
|
};
|
|
570
|
-
|
|
570
|
+
const sendEmailTool = {
|
|
571
|
+
name: "send-email",
|
|
572
|
+
description: [
|
|
573
|
+
"Send an email from the user's mobile device.",
|
|
574
|
+
"When you need to send an email, use this tool. The email app opens on the device with the draft pre-filled for the user to review and send.",
|
|
575
|
+
'Response: `{"ok": true}` on success, or `{"error": "..."}` on failure.',
|
|
576
|
+
],
|
|
577
|
+
inputSchema: {
|
|
578
|
+
type: "object",
|
|
579
|
+
properties: {
|
|
580
|
+
to: { type: "string", description: "Recipient email address" },
|
|
581
|
+
subject: { type: "string", description: "Email subject" },
|
|
582
|
+
body: { type: "string", description: "Email body text" },
|
|
583
|
+
cc: { type: "string", description: "CC recipient(s)" },
|
|
584
|
+
bcc: { type: "string", description: "BCC recipient(s)" },
|
|
585
|
+
},
|
|
586
|
+
required: ["to"],
|
|
587
|
+
},
|
|
588
|
+
async handler(args, ctx) {
|
|
589
|
+
if (!ctx.nc)
|
|
590
|
+
throw new ToolError("Not connected to server (NATS unavailable)", 503);
|
|
591
|
+
const device = getCapabilityDevice("email");
|
|
592
|
+
if (!device)
|
|
593
|
+
throw new ToolError("No device has email access enabled", 400);
|
|
594
|
+
const { to, subject, body, cc, bcc } = args;
|
|
595
|
+
if (!to)
|
|
596
|
+
throw new ToolError("to is required", 400);
|
|
597
|
+
const sc = StringCodec();
|
|
598
|
+
const payload = {
|
|
599
|
+
hostId: ctx.config.hostId, requestId: ctx.sessionId, fcmToken: device.fcmToken,
|
|
600
|
+
to,
|
|
601
|
+
};
|
|
602
|
+
if (subject)
|
|
603
|
+
payload.subject = subject;
|
|
604
|
+
if (body)
|
|
605
|
+
payload.body = body;
|
|
606
|
+
if (cc)
|
|
607
|
+
payload.cc = cc;
|
|
608
|
+
if (bcc)
|
|
609
|
+
payload.bcc = bcc;
|
|
610
|
+
const ackReply = await ctx.nc.request(`host.${ctx.config.hostId}.fcm.email`, sc.encode(JSON.stringify(payload)), { timeout: 5_000 });
|
|
611
|
+
const ack = JSON.parse(sc.decode(ackReply.data));
|
|
612
|
+
if (ack.error)
|
|
613
|
+
throw new ToolError(ack.error, 502);
|
|
614
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
615
|
+
const sub = ctx.nc.subscribe(`host.${ctx.config.hostId}.email.${ctx.sessionId}`, { max: 1 });
|
|
616
|
+
const timer = setTimeout(() => {
|
|
617
|
+
sub.unsubscribe();
|
|
618
|
+
reject(new ToolError("Device did not respond within 30 seconds", 504));
|
|
619
|
+
}, 30_000);
|
|
620
|
+
(async () => {
|
|
621
|
+
for await (const msg of sub) {
|
|
622
|
+
clearTimeout(timer);
|
|
623
|
+
resolve(sc.decode(msg.data));
|
|
624
|
+
}
|
|
625
|
+
})();
|
|
626
|
+
});
|
|
627
|
+
const result = JSON.parse(await responsePromise);
|
|
628
|
+
if (result.error)
|
|
629
|
+
return { error: result.error };
|
|
630
|
+
return result;
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
export const agentTools = [notifyTool, requestInputTool, requestConfirmationTool, deviceGeolocationTool, readContactsTool, createContactTool, readCalendarTool, createCalendarEventTool, sendSmsTool, sendEmailTool, sendAlertTool, readBatteryTool, setRingerModeTool];
|
|
571
634
|
export const agentToolMap = new Map(agentTools.map((t) => [t.name, t]));
|
|
572
635
|
const deviceNotificationsResource = {
|
|
573
636
|
uri: "notifications://device",
|
package/dist/nats-client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type NatsConnection } from "nats";
|
|
2
2
|
import type { HostConfig } from "./types.js";
|
|
3
3
|
/**
|
|
4
|
-
* Connect to NATS using the host config's
|
|
4
|
+
* Connect to NATS using the host config's JWT credentials.
|
|
5
5
|
*/
|
|
6
6
|
export declare function connectNats(config: HostConfig): Promise<NatsConnection>;
|
|
7
7
|
//# sourceMappingURL=nats-client.d.ts.map
|
package/dist/nats-client.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { connect } from "nats";
|
|
1
|
+
import { connect, jwtAuthenticator } from "nats";
|
|
2
2
|
/**
|
|
3
|
-
* Connect to NATS using the host config's
|
|
3
|
+
* Connect to NATS using the host config's JWT credentials.
|
|
4
4
|
*/
|
|
5
5
|
export async function connectNats(config) {
|
|
6
|
+
if (!config.natsJwt || !config.natsNkeySeed) {
|
|
7
|
+
throw new Error("NATS JWT credentials not configured. Re-run palmier init.");
|
|
8
|
+
}
|
|
6
9
|
const nc = await connect({
|
|
7
10
|
servers: config.natsUrl,
|
|
8
|
-
|
|
11
|
+
authenticator: jwtAuthenticator(config.natsJwt, new TextEncoder().encode(config.natsNkeySeed)),
|
|
9
12
|
});
|
|
10
13
|
// Do not log anything as that will pollute stdout for mcp server.
|
|
11
14
|
return nc;
|