@hraness/ghostget 0.18.13 → 0.18.14

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 (33) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +12 -7
  3. package/dist/apple-photos-client.js +1 -1
  4. package/dist/beeper-client.js +42 -6
  5. package/dist/{imessage-direct-install-2gm3kge7.js → imessage-direct-install-5nftwc6p.js} +1 -1
  6. package/dist/{index-maa9hb5z.js → index-g6na3p6k.js} +1 -1
  7. package/dist/{index-en5hycxp.js → index-kaav16fd.js} +4 -2
  8. package/dist/{index-2ymnp8xv.js → index-qcf2f6wm.js} +5 -1
  9. package/dist/{messaging-automation-j4274hvc.js → messaging-automation-58s1366z.js} +1 -1
  10. package/dist/messaging-automation-api.js +2 -2
  11. package/dist/{messaging-native-install-8w1j36ah.js → messaging-native-install-c90nv501.js} +1 -1
  12. package/dist/{whatsapp-automation-runtime-hgh1e9rm.js → whatsapp-automation-runtime-ke1dx91p.js} +1 -1
  13. package/docs/messaging-automation.md +14 -7
  14. package/package.json +8 -4
  15. package/skills/ghostget/references/install.md +5 -5
  16. package/src/assets/adapters/beeper/wrench-web-adapter.json +37 -1
  17. package/src/assets/adapters/beeper/wrench-web-adapter.v2.4.0.json +1500 -0
  18. package/src/beeper-client-types.ts +1 -1
  19. package/src/control/menubar-cli.ts +3 -1
  20. package/src/control/menubar-icon.ts +4 -0
  21. package/src/messaging-automation-descriptors.ts +9 -1
  22. package/src/messaging-automation-factory.ts +7 -5
  23. package/src/messaging-automation-server.ts +2 -2
  24. package/src/messaging-automation-types.ts +3 -2
  25. package/src/messaging-automation-validation.ts +5 -1
  26. package/src/plugins/beeper-linked-device/plugin.ts +3 -1
  27. package/src/provider-plugin-contract-identity.ts +11 -2
  28. package/src/providers/beeper-automation.ts +447 -0
  29. package/src/providers/beeper-local-runtime.ts +61 -21
  30. package/src/providers/beeper-local.ts +4 -4
  31. package/src/providers/beeper-omni.ts +9 -2
  32. package/src/providers/messaging-native-install.ts +2 -1
  33. package/src/version.ts +1 -1
@@ -96,7 +96,7 @@ export type BeeperContactInteractionExportReceipt = Readonly<{
96
96
  implementation: Readonly<{
97
97
  producer: Readonly<{
98
98
  package: "@hraness/ghostget";
99
- version: "0.18.13";
99
+ version: "0.18.14";
100
100
  }>;
101
101
  officialCli: Readonly<{
102
102
  implementation: "github.com/beeper/cli";
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import { handleCompanionCommand, openBrowser, type CompanionOptions, type MenuItem } from "@hraness/desktop-foundation";
6
6
  import { createSupportOffer } from "@hraness/support-foundation";
7
7
  import { ghostgetSupportProfile } from "../support-profile";
8
+ import { TRAY_ICON } from "./menubar-icon";
8
9
  import { ghostgetStateHome } from "../storage";
9
10
  import { CONTROL_PROTOCOL, type ActivityRow, type ApprovalView, type CapabilityView, type ControlRequest, type ControlResponse, type ControlSnapshot } from "./protocol";
10
11
  import type { ControlEnvironment } from "./web-policy";
@@ -426,7 +427,8 @@ export function companionOptions(
426
427
  return {
427
428
  appId: "ghostget",
428
429
  name: "Ghostget",
429
- title: "Gg",
430
+ title: "\u{1f47b}",
431
+ icon: TRAY_ICON,
430
432
  tooltip: "Ghostget · control, outputs and CLI help",
431
433
  stateDir: join(ghostgetStateHome(environment), "menubar"),
432
434
  refreshMs: 5_000,
@@ -0,0 +1,4 @@
1
+ /** Ghost status mark (U+1F47B), pre-rendered at 32px from Twemoji
2
+ * (CC-BY 4.0 — https://twemoji.twitter.com). macOS renders the emoji title
3
+ * natively; icon-only tray surfaces use this art. */
4
+ export const TRAY_ICON = { width: 32, height: 32, rgba: "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" } as const;
@@ -10,8 +10,15 @@ export function automationPermissionOperation(operation: string): string {
10
10
  throw new Error("This messaging action has no admitted host permission.");
11
11
  }
12
12
 
13
+ const AUTOMATION_SEND_KINDS: Readonly<Record<AutomationProviderId, readonly string[]>> = Object.freeze({
14
+ imessage: Object.freeze(["text", "attachment", "reaction", "sticker", "link", "poll"]),
15
+ whatsapp: Object.freeze(["text", "attachment", "reaction", "sticker", "link", "poll"]),
16
+ // The pinned Beeper local transport admits exact text sends only.
17
+ beeper: Object.freeze(["text"]),
18
+ });
19
+
13
20
  export function automationOperationDefinitions(provider: AutomationProviderId): readonly LocalCliPluginOperationDefinitionV1[] {
14
- const names = ["messaging.automation.read", ...(provider === "whatsapp" ? ["messaging.automation.sync"] : []), ...["text", "attachment", "reaction", "sticker", "link", "poll"].map(kind => `messaging.automation.send.${kind}`)];
21
+ const names = ["messaging.automation.read", ...(provider === "whatsapp" ? ["messaging.automation.sync"] : []), ...AUTOMATION_SEND_KINDS[provider].map(kind => `messaging.automation.send.${kind}`)];
15
22
  return names.map(name => ({
16
23
  name, contractVersion: 1, risk: name.endsWith(".read") ? "R1" : name.endsWith(".sync") ? "R2" : "R3",
17
24
  input: { properties: {}, required: [] },
@@ -27,3 +34,4 @@ export function automationOperationDefinitions(provider: AutomationProviderId):
27
34
  // pinned runtime. The owner permission kernel stays outside provider plugins.
28
35
  export function loadImsgAutomationRuntime() { return import("./providers/imessage-automation"); }
29
36
  export function loadWhatsAppAutomationRuntime() { return import("./providers/whatsapp-automation"); }
37
+ export function loadBeeperAutomationRuntime() { return import("./providers/beeper-automation"); }
@@ -4,7 +4,7 @@ import type { GhostgetAuth } from "./auth";
4
4
  import { MessagingAutomationHost } from "./messaging-automation";
5
5
  import type { AutomationActionKind, AutomationProviderId, AutomationProviderStatus, MessagingAutomationProvider } from "./messaging-automation-types";
6
6
  import { AUTOMATION_ACTION_KINDS } from "./messaging-automation-validation";
7
- import { automationPermissionOperation, loadImsgAutomationRuntime, loadWhatsAppAutomationRuntime } from "./messaging-automation-descriptors";
7
+ import { automationPermissionOperation, loadBeeperAutomationRuntime, loadImsgAutomationRuntime, loadWhatsAppAutomationRuntime } from "./messaging-automation-descriptors";
8
8
  import { describeOperationPermission, type OperationPermissionDescription } from "./operation-permission";
9
9
  import { loadProviderPluginExtensionRuntime } from "./provider-plugin";
10
10
  import type { ProviderPluginRegistry } from "./provider-plugin-registry";
@@ -29,7 +29,7 @@ export async function createMessagingAutomationSession(options: MessagingAutomat
29
29
  const starts = new Map<AutomationProviderId, (signal?: AbortSignal) => Promise<AutomationProviderStatus>>();
30
30
  try {
31
31
  for (const selected of options.providers) {
32
- const adapterId = selected.provider === "imessage" ? "imessage-direct" : "whatsapp-web";
32
+ const adapterId = selected.provider === "imessage" ? "imessage-direct" : selected.provider === "beeper" ? "beeper-local" : "whatsapp-web";
33
33
  let custody: WebSessionCleanupAdmissionController | undefined;
34
34
  let custodyIdentity: string | undefined;
35
35
  let poisoned = false, persistent = false, closed = false, started = false;
@@ -59,7 +59,7 @@ export async function createMessagingAutomationSession(options: MessagingAutomat
59
59
  pluginImplementationHash: options.registry.implementationHash(description.resolution.binding).toString("hex"),
60
60
  adapterId, adapterHash: description.coordinate.manifestHash, surfaceId: selected.provider,
61
61
  authId: selected.authId, authHash: sha256(canonicalJson(auth)),
62
- transport: selected.provider === "imessage" ? "local-cli" : "web-session-api",
62
+ transport: selected.provider === "whatsapp" ? "web-session-api" : "local-cli",
63
63
  executionIdentityHash: implementationIdentity,
64
64
  }, options.environment);
65
65
  custodyIdentity = coordinate;
@@ -85,10 +85,12 @@ export async function createMessagingAutomationSession(options: MessagingAutomat
85
85
  return finishing;
86
86
  };
87
87
  const execution = { environment: options.environment, registerCleanupBarrier };
88
- const binding = options.registry.requireOperationDefinition(selected.provider === "imessage" ? "local-cli" : "linked-device", selected.provider, "messaging.automation.read", 1).binding;
88
+ const binding = options.registry.requireOperationDefinition(selected.provider === "whatsapp" ? "linked-device" : "local-cli", selected.provider, "messaging.automation.read", 1).binding;
89
89
  const concrete: Concrete = await loadProviderPluginExtensionRuntime(binding.loadRuntime, async () => selected.provider === "imessage"
90
90
  ? (await loadImsgAutomationRuntime()).createImsgAutomationProvider({ authorize, execution, resolveAsset: options.resolveAsset })
91
- : (await loadWhatsAppAutomationRuntime()).createWhatsAppAutomationProvider({ authorize, execution, resolveAsset: options.resolveAsset }));
91
+ : selected.provider === "beeper"
92
+ ? (await loadBeeperAutomationRuntime()).createBeeperAutomationProvider({ authorize, execution })
93
+ : (await loadWhatsAppAutomationRuntime()).createWhatsAppAutomationProvider({ authorize, execution, resolveAsset: options.resolveAsset }));
92
94
  const call = async <T>(work: () => Promise<T>): Promise<T> => {
93
95
  if (poisoned) throw new AutomationHostRecoveryRequired("Host is fenced");
94
96
  if (closed) throw new Error("Host closed");
@@ -15,7 +15,7 @@ type Asset = { bytes: Uint8Array; sha256: string; expires: number; plan: string
15
15
  const MAX_ASSET = 16 * 1024 * 1024;
16
16
  const MAX_FRAME = 24 * 1024 * 1024;
17
17
  const MAX_RESPONSE = 32 * 1024 * 1024;
18
- const provider = (value: unknown): AutomationProviderId => { if (value !== "imessage" && value !== "whatsapp") throw new Error("Invalid provider"); return value; };
18
+ const provider = (value: unknown): AutomationProviderId => { if (value !== "imessage" && value !== "whatsapp" && value !== "beeper") throw new Error("Invalid provider"); return value; };
19
19
  export class AutomationHostRecoveryRequired extends Error {}
20
20
 
21
21
  /** One trusted owner connection. No agent receives this port. */
@@ -44,7 +44,7 @@ export class MessagingAutomationRpcServer {
44
44
  if (method === "initialize") {
45
45
  if (this.initialized || this.closed) throw new Error("Already initialized");
46
46
  const r = automationRecord(raw, ["providers"]);
47
- const accounts = automationArray(r.providers, 2).map(value => {
47
+ const accounts = automationArray(r.providers, 3).map(value => {
48
48
  const item = automationRecord(value, ["provider", "authId"]); const authId = automationText(item.authId, 48);
49
49
  if (!/^[a-z][a-z0-9-]{0,47}$/u.test(authId)) throw new Error("Invalid account identifier");
50
50
  return { provider: provider(item.provider), authId };
@@ -1,11 +1,12 @@
1
1
  /** Closed host-side messaging contract. Provider credentials and local paths never cross it. */
2
2
  export const MESSAGING_AUTOMATION_PROTOCOL = "ghostget.messaging-automation/1" as const;
3
3
 
4
- export type AutomationProviderId = "imessage" | "whatsapp";
4
+ export type AutomationProviderId = "imessage" | "whatsapp" | "beeper";
5
5
  export type AutomationActionKind = AutomationAction["kind"];
6
6
  export type AutomationCoordinate =
7
7
  | Readonly<{ provider: "imessage"; chatGuid: string; service: "iMessage"; observedChatRowId: number }>
8
- | Readonly<{ provider: "whatsapp"; conversationJid: string }>;
8
+ | Readonly<{ provider: "whatsapp"; conversationJid: string }>
9
+ | Readonly<{ provider: "beeper"; accountId: string; conversationId: string }>;
9
10
  export type AutomationIdentity = Readonly<{
10
11
  provider: AutomationProviderId;
11
12
  authId: string;
@@ -53,13 +53,17 @@ export function parseAutomationCoordinate(value: unknown): AutomationCoordinate
53
53
  if (!chatGuid.startsWith("iMessage;") || r.service !== "iMessage") throw new Error("Only exact iMessage conversations are supported.");
54
54
  return Object.freeze({ provider, chatGuid, service: "iMessage", observedChatRowId: automationInteger(r.observedChatRowId, 1, Number.MAX_SAFE_INTEGER) });
55
55
  }
56
+ if (provider === "beeper") {
57
+ const r = automationRecord(value, ["provider", "accountId", "conversationId"]);
58
+ return Object.freeze({ provider, accountId: automationText(r.accountId, 512), conversationId: automationText(r.conversationId, 2048) });
59
+ }
56
60
  const r = automationRecord(value, ["provider", "conversationJid"]);
57
61
  if (provider !== "whatsapp" || typeof r.conversationJid !== "string" || !/^(?:[1-9][0-9]{4,14}@s\.whatsapp\.net|[1-9][0-9]{4,19}@lid)$/u.test(r.conversationJid)) throw new Error("Only exact individual WhatsApp conversations are supported.");
58
62
  return Object.freeze({ provider, conversationJid: r.conversationJid });
59
63
  }
60
64
  export function parseAutomationIdentity(value: unknown): AutomationIdentity {
61
65
  const r = automationRecord(value, ["provider", "authId", "accountIdentity", "accountSubject", "implementationIdentity", "sourceGeneration"]);
62
- if (r.provider !== "imessage" && r.provider !== "whatsapp") throw new Error("Messaging provider is invalid.");
66
+ if (r.provider !== "imessage" && r.provider !== "whatsapp" && r.provider !== "beeper") throw new Error("Messaging provider is invalid.");
63
67
  return Object.freeze({ provider: r.provider, authId: automationId(r.authId), accountIdentity: automationDigest(r.accountIdentity), accountSubject: automationText(r.accountSubject, 512), implementationIdentity: automationDigest(r.implementationIdentity), sourceGeneration: automationText(r.sourceGeneration, 256) });
64
68
  }
65
69
  export function parseAutomationActionKind(value: unknown): AutomationActionKind {
@@ -1,3 +1,4 @@
1
+ import { automationOperationDefinitions } from "../../messaging-automation-descriptors";
1
2
  import {
2
3
  defineProviderPlugin,
3
4
  lazyLocalCliRuntime,
@@ -250,12 +251,13 @@ const operations = Object.freeze([
250
251
  operationDefinition(action, 2)),
251
252
  operationDefinition("messaging.read", 3),
252
253
  operationDefinition("contacts.list", 3),
254
+ ...automationOperationDefinitions("beeper"),
253
255
  ]);
254
256
 
255
257
  export const beeperLinkedDevicePlugin = defineProviderPlugin({
256
258
  apiVersion: 1,
257
259
  id: "beeper-linked-device",
258
- version: "2.4.0",
260
+ version: "2.5.0",
259
261
  displayName: "Beeper Pinned Local CLI",
260
262
  sourceKind: "built-in",
261
263
  implementationSources: providerImplementationEntry(import.meta.url),
@@ -69,6 +69,10 @@ const BEEPER_2_3_BINDING_ROUTE_COORDINATES = Object.freeze([
69
69
  "local-cli:beeper/contacts.list@2",
70
70
  "local-cli:beeper/messaging.read@3",
71
71
  ]);
72
+ const BEEPER_2_4_BINDING_ROUTE_COORDINATES = Object.freeze([
73
+ ...BEEPER_2_3_BINDING_ROUTE_COORDINATES,
74
+ "local-cli:beeper/contacts.list@3",
75
+ ]);
72
76
 
73
77
  const REDDIT_1_3_BINDING_ROUTE_COORDINATES = Object.freeze([
74
78
  "comments.create@1", "comments.read@1", "communities.membership.set@1",
@@ -84,10 +88,15 @@ const REDDIT_1_3_BINDING_ROUTE_COORDINATES = Object.freeze([
84
88
  const identities = Object.freeze({
85
89
  "beeper-linked-device": {
86
90
  schemaVersion: 1,
87
- pluginVersion: "2.4.0",
88
- implementationSha256: "a989e65e372aa63af41cb36f32d6a1d769c734ba0adc207dd2f25d916f877ff5",
91
+ pluginVersion: "2.5.0",
92
+ implementationSha256: "112abd8d9a819c03d56ac0d8d7e4b5178cde5b83b1cf41144e38c4ce42bc3945",
89
93
  legacyCurrentReadImplementationSha256: [],
90
94
  legacyDistributionReadImplementationSha256: [
95
+ {
96
+ implementationSha256:
97
+ "a989e65e372aa63af41cb36f32d6a1d769c734ba0adc207dd2f25d916f877ff5",
98
+ routes: [...BEEPER_2_4_BINDING_ROUTE_COORDINATES],
99
+ },
91
100
  {
92
101
  implementationSha256:
93
102
  "2d2cef38ce2d0c193f4e6890c51d59f8a3547d9011d5c3aa8df18f5f077ebcdd",