palmier 0.8.11 → 0.9.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.
Files changed (41) hide show
  1. package/README.md +3 -1
  2. package/dist/linked-device.d.ts +9 -0
  3. package/dist/linked-device.js +45 -0
  4. package/dist/mcp-tools.js +19 -19
  5. package/dist/pwa/assets/index-BLCVzS_l.js +120 -0
  6. package/dist/pwa/assets/{index-DhphickB.css → index-Cjjw24Ok.css} +1 -1
  7. package/dist/pwa/assets/{web-4WNPL7z3.js → web-C2AU9S9n.js} +1 -1
  8. package/dist/pwa/assets/{web-DjwsAB0V.js → web-CfD_ah7K.js} +1 -1
  9. package/dist/pwa/assets/{web-Bpd2nO1M.js → web-DugGj1t8.js} +1 -1
  10. package/dist/pwa/index.html +2 -2
  11. package/dist/pwa/service-worker.js +2 -2
  12. package/dist/rpc-handler.js +17 -23
  13. package/package.json +1 -1
  14. package/palmier-server/README.md +2 -1
  15. package/palmier-server/pwa/src/App.css +37 -0
  16. package/palmier-server/pwa/src/App.tsx +36 -15
  17. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +65 -225
  18. package/palmier-server/pwa/src/components/HostMenu.tsx +110 -21
  19. package/palmier-server/pwa/src/components/RunDetailView.tsx +2 -2
  20. package/palmier-server/pwa/src/components/SessionComposer.tsx +9 -8
  21. package/palmier-server/pwa/src/components/SessionsView.tsx +5 -3
  22. package/palmier-server/pwa/src/components/TabBar.tsx +7 -5
  23. package/palmier-server/pwa/src/components/TaskForm.tsx +5 -3
  24. package/palmier-server/pwa/src/constants.ts +1 -1
  25. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +41 -41
  26. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +17 -60
  27. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +6 -7
  28. package/palmier-server/pwa/src/native/Device.ts +23 -38
  29. package/palmier-server/pwa/src/pages/Dashboard.tsx +36 -41
  30. package/palmier-server/pwa/src/pages/PairHost.tsx +20 -1
  31. package/palmier-server/pwa/src/pages/PairSetup.tsx +98 -39
  32. package/palmier-server/pwa/src/service-worker.ts +9 -6
  33. package/palmier-server/pwa/src/types.ts +2 -0
  34. package/palmier-server/spec.md +37 -11
  35. package/src/linked-device.ts +52 -0
  36. package/src/mcp-tools.ts +19 -19
  37. package/src/rpc-handler.ts +14 -22
  38. package/dist/device-capabilities.d.ts +0 -9
  39. package/dist/device-capabilities.js +0 -36
  40. package/dist/pwa/assets/index-B7S0YoMo.js +0 -120
  41. package/src/device-capabilities.ts +0 -57
package/src/mcp-tools.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { StringCodec, type NatsConnection } from "nats";
2
2
  import { registerPending } from "./pending-requests.js";
3
- import { getCapabilityDevice } from "./device-capabilities.js";
3
+ import { getLinkedDevice } from "./linked-device.js";
4
4
  import { getNotifications, onNotificationsChanged } from "./notification-store.js";
5
5
  import { getSmsMessages, onSmsChanged } from "./sms-store.js";
6
6
  import type { HostConfig } from "./types.js";
@@ -179,8 +179,8 @@ const deviceGeolocationTool: ToolDefinition = {
179
179
  async handler(_args, ctx) {
180
180
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
181
181
 
182
- const device = getCapabilityDevice("location");
183
- if (!device) throw new ToolError("No device has location access enabled", 400);
182
+ const device = getLinkedDevice();
183
+ if (!device) throw new ToolError("No linked device configured", 400);
184
184
 
185
185
  const sc = StringCodec();
186
186
 
@@ -227,8 +227,8 @@ const readContactsTool: ToolDefinition = {
227
227
  async handler(_args, ctx) {
228
228
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
229
229
 
230
- const device = getCapabilityDevice("contacts");
231
- if (!device) throw new ToolError("No device has contacts access enabled", 400);
230
+ const device = getLinkedDevice();
231
+ if (!device) throw new ToolError("No linked device configured", 400);
232
232
 
233
233
  const sc = StringCodec();
234
234
 
@@ -280,8 +280,8 @@ const createContactTool: ToolDefinition = {
280
280
  async handler(args, ctx) {
281
281
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
282
282
 
283
- const device = getCapabilityDevice("contacts");
284
- if (!device) throw new ToolError("No device has contacts access enabled", 400);
283
+ const device = getLinkedDevice();
284
+ if (!device) throw new ToolError("No linked device configured", 400);
285
285
 
286
286
  const { name, phone, email } = args as { name: string; phone?: string; email?: string };
287
287
  if (!name) throw new ToolError("name is required", 400);
@@ -338,8 +338,8 @@ const readCalendarTool: ToolDefinition = {
338
338
  async handler(args, ctx) {
339
339
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
340
340
 
341
- const device = getCapabilityDevice("calendar");
342
- if (!device) throw new ToolError("No device has calendar access enabled", 400);
341
+ const device = getLinkedDevice();
342
+ if (!device) throw new ToolError("No linked device configured", 400);
343
343
 
344
344
  const { startDate, endDate } = args as { startDate?: number; endDate?: number };
345
345
  const sc = StringCodec();
@@ -399,8 +399,8 @@ const createCalendarEventTool: ToolDefinition = {
399
399
  async handler(args, ctx) {
400
400
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
401
401
 
402
- const device = getCapabilityDevice("calendar");
403
- if (!device) throw new ToolError("No device has calendar access enabled", 400);
402
+ const device = getLinkedDevice();
403
+ if (!device) throw new ToolError("No linked device configured", 400);
404
404
 
405
405
  const { title, startTime, endTime, location, description } = args as {
406
406
  title: string; startTime: number; endTime: number; location?: string; description?: string;
@@ -462,7 +462,7 @@ const sendSmsTool: ToolDefinition = {
462
462
  async handler(args, ctx) {
463
463
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
464
464
 
465
- const device = getCapabilityDevice("sms-send");
465
+ const device = getLinkedDevice();
466
466
  if (!device) throw new ToolError("No device has SMS Send enabled", 400);
467
467
 
468
468
  const { to, body } = args as { to: string; body: string };
@@ -521,8 +521,8 @@ const sendAlarmTool: ToolDefinition = {
521
521
  async handler(args, ctx) {
522
522
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
523
523
 
524
- const device = getCapabilityDevice("alarm");
525
- if (!device) throw new ToolError("No device has alarm access enabled", 400);
524
+ const device = getLinkedDevice();
525
+ if (!device) throw new ToolError("No linked device configured", 400);
526
526
 
527
527
  const { title, description } = args as { title: string; description?: string };
528
528
  if (!title) throw new ToolError("title is required", 400);
@@ -578,8 +578,8 @@ const readBatteryTool: ToolDefinition = {
578
578
  async handler(_args, ctx) {
579
579
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
580
580
 
581
- const device = getCapabilityDevice("battery");
582
- if (!device) throw new ToolError("No device has battery access enabled", 400);
581
+ const device = getLinkedDevice();
582
+ if (!device) throw new ToolError("No linked device configured", 400);
583
583
 
584
584
  const sc = StringCodec();
585
585
 
@@ -629,7 +629,7 @@ const setRingerModeTool: ToolDefinition = {
629
629
  async handler(args, ctx) {
630
630
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
631
631
 
632
- const device = getCapabilityDevice("dnd");
632
+ const device = getLinkedDevice();
633
633
  if (!device) throw new ToolError("No device has Do Not Disturb control enabled", 400);
634
634
 
635
635
  const { mode } = args as { mode: string };
@@ -687,8 +687,8 @@ const sendEmailTool: ToolDefinition = {
687
687
  async handler(args, ctx) {
688
688
  if (!ctx.nc) throw new ToolError("Not connected to server (NATS unavailable)", 503);
689
689
 
690
- const device = getCapabilityDevice("send-email");
691
- if (!device) throw new ToolError("No device has send-email access enabled", 400);
690
+ const device = getLinkedDevice();
691
+ if (!device) throw new ToolError("No linked device configured", 400);
692
692
 
693
693
  const { to, subject, body, cc, bcc } = args as { to: string; subject?: string; body?: string; cc?: string; bcc?: string };
694
694
  if (!to) throw new ToolError("to is required", 400);
@@ -9,9 +9,9 @@ import { getPlatform } from "./platform/index.js";
9
9
  import { spawnCommand } from "./spawn-command.js";
10
10
  import crossSpawn from "cross-spawn";
11
11
  import { getAgent } from "./agents/agent.js";
12
- import { validateClient } from "./client-store.js";
12
+ import { validateClient, revokeClient } from "./client-store.js";
13
13
  import { publishHostEvent } from "./events.js";
14
- import { getCapabilityDevice, setCapabilityDevice, clearCapabilityDevice, type DeviceCapability } from "./device-capabilities.js";
14
+ import { getLinkedDevice, setLinkedDevice, clearLinkedDevice, clearLinkedDeviceIfMatches } from "./linked-device.js";
15
15
  import { currentVersion, performUpdate } from "./update-checker.js";
16
16
  import { parseReportFiles, parseTaskOutcome, stripPalmierMarkers } from "./commands/run.js";
17
17
  import { clearTaskQueue } from "./event-queues.js";
@@ -144,15 +144,11 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
144
144
 
145
145
  switch (request.method) {
146
146
  case "host.info": {
147
- const capabilities: Record<string, string | null> = {};
148
- for (const capability of ["location", "notifications", "sms-read", "sms-send", "contacts", "calendar", "alarm", "battery", "dnd", "send-email"] as const) {
149
- capabilities[capability] = getCapabilityDevice(capability)?.clientToken ?? null;
150
- }
151
147
  return {
152
148
  agents: config.agents ?? [],
153
149
  version: currentVersion,
154
150
  host_platform: process.platform,
155
- capability_tokens: capabilities,
151
+ linked_client_token: getLinkedDevice()?.clientToken ?? null,
156
152
  pending_prompts: listPending(),
157
153
  lan_url: buildLanUrl(config.httpPort ?? 7256, config.defaultInterface),
158
154
  };
@@ -635,31 +631,27 @@ export function createRpcHandler(config: HostConfig, nc?: NatsConnection) {
635
631
  return { ok: true };
636
632
  }
637
633
 
638
- case "device.location.enable": {
634
+ case "device.link": {
639
635
  const params = request.params as { fcmToken: string };
640
636
  if (!params.fcmToken) return { error: "fcmToken is required" };
641
637
  const clientToken = request.clientToken ?? "";
642
- setCapabilityDevice("location", clientToken, params.fcmToken);
638
+ if (!clientToken) return { error: "Unauthorized" };
639
+ setLinkedDevice(clientToken, params.fcmToken);
643
640
  return { ok: true };
644
641
  }
645
642
 
646
- case "device.location.disable": {
647
- clearCapabilityDevice("location");
648
- return { ok: true };
649
- }
650
-
651
- case "device.capability.enable": {
652
- const params = request.params as { capability: DeviceCapability; fcmToken: string };
653
- if (!params.capability || !params.fcmToken) return { error: "capability and fcmToken are required" };
643
+ case "device.unlink": {
654
644
  const clientToken = request.clientToken ?? "";
655
- setCapabilityDevice(params.capability, clientToken, params.fcmToken);
645
+ const current = getLinkedDevice();
646
+ if (current?.clientToken === clientToken) clearLinkedDevice();
656
647
  return { ok: true };
657
648
  }
658
649
 
659
- case "device.capability.disable": {
660
- const params = request.params as { capability: DeviceCapability };
661
- if (!params.capability) return { error: "capability is required" };
662
- clearCapabilityDevice(params.capability);
650
+ case "clients.revoke_self": {
651
+ const clientToken = request.clientToken ?? "";
652
+ if (!clientToken) return { error: "Unauthorized" };
653
+ clearLinkedDeviceIfMatches(clientToken);
654
+ revokeClient(clientToken);
663
655
  return { ok: true };
664
656
  }
665
657
 
@@ -1,9 +0,0 @@
1
- export interface RegisteredDevice {
2
- clientToken: string;
3
- fcmToken: string;
4
- }
5
- export type DeviceCapability = "location" | "notifications" | "sms-read" | "sms-send" | "contacts" | "calendar" | "alarm" | "battery" | "send-email" | "dnd";
6
- export declare function getCapabilityDevice(capability: DeviceCapability): RegisteredDevice | null;
7
- export declare function setCapabilityDevice(capability: DeviceCapability, clientToken: string, fcmToken: string): void;
8
- export declare function clearCapabilityDevice(capability: DeviceCapability): void;
9
- //# sourceMappingURL=device-capabilities.d.ts.map
@@ -1,36 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { CONFIG_DIR } from "./config.js";
4
- const CAPABILITIES_FILE = path.join(CONFIG_DIR, "device-capabilities.json");
5
- function readAll() {
6
- try {
7
- if (!fs.existsSync(CAPABILITIES_FILE))
8
- return {};
9
- return JSON.parse(fs.readFileSync(CAPABILITIES_FILE, "utf-8"));
10
- }
11
- catch {
12
- return {};
13
- }
14
- }
15
- function writeAll(map) {
16
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
17
- fs.writeFileSync(CAPABILITIES_FILE, JSON.stringify(map, null, 2), "utf-8");
18
- }
19
- export function getCapabilityDevice(capability) {
20
- const map = readAll();
21
- const device = map[capability];
22
- if (!device?.clientToken || !device?.fcmToken)
23
- return null;
24
- return device;
25
- }
26
- export function setCapabilityDevice(capability, clientToken, fcmToken) {
27
- const map = readAll();
28
- map[capability] = { clientToken, fcmToken };
29
- writeAll(map);
30
- }
31
- export function clearCapabilityDevice(capability) {
32
- const map = readAll();
33
- delete map[capability];
34
- writeAll(map);
35
- }
36
- //# sourceMappingURL=device-capabilities.js.map