@hraness/ghostget 0.18.0 → 0.18.3

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 (76) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/README.md +7 -7
  3. package/dist/apple-photos-client.js +2 -1
  4. package/dist/beeper-client.js +2 -1
  5. package/dist/client.js +1 -0
  6. package/dist/imessage-direct-install-2gm3kge7.js +324 -0
  7. package/dist/index-01eeae9e.js +5 -0
  8. package/dist/index-2ymnp8xv.js +145 -0
  9. package/dist/{index-9wca02er.js → index-cnbz0gws.js} +1 -1
  10. package/dist/index-en5hycxp.js +175 -0
  11. package/dist/index-n4szk3nw.js +50 -0
  12. package/dist/index-yq6maz71.js +1105 -0
  13. package/dist/index-z1w83f81.js +4 -0
  14. package/dist/index.js +4 -47
  15. package/dist/messaging-automation-api.js +21 -0
  16. package/dist/messaging-automation-j4274hvc.js +609 -0
  17. package/dist/messaging-native-install-8w1j36ah.js +16 -0
  18. package/dist/messaging.js +1 -0
  19. package/dist/omni-client.js +1 -0
  20. package/dist/whatsapp-automation-runtime-hgh1e9rm.js +845 -0
  21. package/dist/whatsapp-client.js +1 -0
  22. package/docs/control-panel.md +2 -2
  23. package/docs/imessage-direct-provider.md +31 -7
  24. package/docs/messaging-automation.md +103 -0
  25. package/package.json +22 -2
  26. package/skills/ghostget/references/control-panel.md +1 -1
  27. package/skills/ghostget/references/install.md +5 -5
  28. package/skills/ghostget/references/linkedin-adapter.md +215 -13
  29. package/skills/ghostget/references/platform-patterns.md +1 -1
  30. package/src/args.ts +18 -5
  31. package/src/assets/adapters/imessage/wrench-web-adapter.json +126 -0
  32. package/src/assets/adapters/linkedin/wrench-web-adapter.json +1 -1
  33. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +144 -0
  34. package/src/assets/messaging-runtime/NOTICE.txt +2580 -0
  35. package/src/assets/messaging-runtime/imsg-darwin-arm64.gz +0 -0
  36. package/src/assets/messaging-runtime/phone-number-info.plist.gz +0 -0
  37. package/src/assets/messaging-runtime/phone-number-metadata.json.gz +0 -0
  38. package/src/assets/messaging-runtime/phone-number-privacy.plist.gz +0 -0
  39. package/src/assets/messaging-runtime/wacli-darwin-arm64.gz +0 -0
  40. package/src/beeper-client-types.ts +1 -1
  41. package/src/ghostget.ts +16 -4
  42. package/src/messaging-automation-api.ts +12 -0
  43. package/src/messaging-automation-descriptors.ts +29 -0
  44. package/src/messaging-automation-factory.ts +148 -0
  45. package/src/messaging-automation-server.ts +183 -0
  46. package/src/messaging-automation-types.ts +155 -0
  47. package/src/messaging-automation-validation.ts +110 -0
  48. package/src/messaging-automation.ts +356 -0
  49. package/src/plugins/imessage-direct/plugin.ts +12 -1
  50. package/src/plugins/imessage-direct/vendor/0003-feat-rpc-add-no-fetch-rich-cards.patch +276 -0
  51. package/src/plugins/imessage-direct/vendor/provenance.json +84 -10
  52. package/src/plugins/linkedin-web/plugin.ts +1 -1
  53. package/src/plugins/whatsapp-linked-device/plugin.ts +7 -2
  54. package/src/plugins/whatsapp-linked-device/vendor/0001-ghostget-private-messaging.patch +1093 -0
  55. package/src/plugins/whatsapp-linked-device/vendor/README.md +39 -0
  56. package/src/plugins/whatsapp-linked-device/vendor/provenance.json +45 -0
  57. package/src/provider-plugin.ts +14 -0
  58. package/src/providers/imessage-automation.ts +250 -0
  59. package/src/providers/imessage-direct-install.ts +7 -0
  60. package/src/providers/imessage-direct-runtime.ts +32 -2
  61. package/src/providers/imessage-direct.ts +13 -3
  62. package/src/providers/linkedin-contact-failure.ts +21 -0
  63. package/src/providers/linkedin-contact-platform.ts +8 -1
  64. package/src/providers/linkedin-contact-program.ts +31 -6
  65. package/src/providers/linkedin-web-contact.ts +1072 -51
  66. package/src/providers/linkedin-web-profile-browser.ts +659 -45
  67. package/src/providers/linkedin-web-runtime.ts +1 -0
  68. package/src/providers/linkedin-web.ts +46 -2
  69. package/src/providers/messaging-native-artifacts.ts +38 -0
  70. package/src/providers/messaging-native-install.ts +75 -0
  71. package/src/providers/whatsapp-automation-runtime.ts +240 -0
  72. package/src/providers/whatsapp-automation.ts +153 -0
  73. package/src/usage.ts +3 -1
  74. package/src/version.ts +1 -1
  75. package/src/web-session-contract-definitions.ts +1 -1
  76. package/tsconfig.json +3 -0
@@ -3,6 +3,7 @@ import {
3
3
  canonicalJson,
4
4
  sha256
5
5
  } from "./index-gwk7rbyj.js";
6
+ import"./index-z1w83f81.js";
6
7
 
7
8
  // src/whatsapp-client.ts
8
9
  import { spawnSync } from "child_process";
@@ -9,7 +9,7 @@ and capabilities refresh when you return to the app, after changes made in the
9
9
  app, or when you choose Refresh. Permission decisions always validate current
10
10
  account and integration state.
11
11
 
12
- Build instructions and native qualification live in [desktop/README.md](https://github.com/hraness/ghostget/blob/v0.18.0/desktop/README.md).
12
+ Build instructions and native qualification live in [desktop/README.md](https://github.com/hraness/ghostget/blob/v0.18.3/desktop/README.md).
13
13
  The CLI's canonical five-file GitHub Release contract is unchanged. A source
14
14
  build is not a signed or notarized public macOS installer.
15
15
 
@@ -123,7 +123,7 @@ User and imported interfaces are distinguished from bundled interfaces. Remote
123
123
  references, executable import hooks, arbitrary HTTP templates, and unsupported
124
124
  input-schema constructs are rejected or remain visibly inert as appropriate.
125
125
  An interface without an executor does not become executable by importing it.
126
- Use the existing [provider plugin protocol](https://github.com/hraness/ghostget/blob/v0.18.0/docs/plugins.md) when a new executor is
126
+ Use the existing [provider plugin protocol](https://github.com/hraness/ghostget/blob/v0.18.3/docs/plugins.md) when a new executor is
127
127
  needed. OpenAPI import does not grant it account access.
128
128
 
129
129
  Middleware and LLM approval are future extensions. Future hooks may propose
@@ -19,7 +19,12 @@ The vendored patch stack applies to `openclaw/imsg` 0.14.1 at commit
19
19
  `292db82d89293867ef847a2875667fea0fdd5dc1`, isolates AppleScript payloads.
20
20
  The second, `c5994f00d17969fd7772fd2772e7b3591089513a`, adds the read-only exact
21
21
  `chats.get(chat_id)` lookup required to revalidate a route without relying on a
22
- bounded recent-chat scan. Patch SHA-256 values, changed files, and the full
22
+ bounded recent-chat scan. The third, `520b82ab025c1d4d57333b552aa65c9ae3fdb5fd`,
23
+ adds `send.rich` URL cards with `fetch_metadata: false`. The admitted `.3`
24
+ executable includes this mode. It bypasses the helper's LinkPresentation metadata
25
+ and image fetching while retaining the native rich-card payload. The patch also
26
+ corrects exact-chat test fixtures and requires the shipped PhoneNumberKit resource
27
+ bundle in release builds. Patch SHA-256 values, changed files, and the full
23
28
  artifact review boundary are recorded in
24
29
  `src/plugins/imessage-direct/vendor/provenance.json`.
25
30
 
@@ -47,25 +52,44 @@ usable, not which Apple account Messages will choose.
47
52
 
48
53
  ## Build and installation boundary
49
54
 
50
- Apply both vendored mail patches in order to an otherwise clean checkout at the exact base,
51
- then build `imsg` in release mode. The current reviewed macOS arm64 executable
55
+ The admitted binary includes all three vendored mail patches applied to the exact base,
56
+ and was built in release mode. The current reviewed macOS arm64 executable
52
57
  SHA-256 is
53
- `77a0db864dfd247cd0a9142dd98997960582e6f150f76ecd3bf1c38944f2bf71`.
54
- Compiler, SDK, and command provenance are in the manifest. A different byte is
58
+ `46c4c73c81c7db2d516c2d467c66aff03c73de196996d646bc8afce0ea85cff6`.
59
+ Two independent release builds produce identical signed bytes with `-Xlinker -S`.
60
+ This omits debug build paths before the default UUID and ad-hoc signature are
61
+ generated. The adjacent resource bundle was exercised with the release
62
+ PhoneNumberKit code. Compiler, SDK, and command provenance are in the manifest. A different byte is
55
63
  not this reviewed transport, even if it prints version 0.14.1.
56
64
 
57
65
  Install only that byte sequence through the checked installer:
58
66
 
59
67
  ```sh
60
- ghostget imessage transport install --binary /absolute/path/to/imsg --json
68
+ ghostget imessage transport install --json
61
69
  ```
62
70
 
63
- The installer supports only the declared current platform, reads the source
71
+ The default uses the exact compressed executable and PhoneNumberKit resources
72
+ bundled with Ghostget. An explicit `--binary /absolute/path/to/imsg` may supply
73
+ the same pinned executable. The installer supports only the declared current platform, reads the source
64
74
  without following a symlink, checks owner, mode, size, stability, and SHA-256,
65
75
  and installs by an exclusive same-filesystem link. It never replaces mismatched
66
76
  existing bytes. Software build, installation, Messages login, and account
67
77
  recovery remain operator workflows, not provider operations.
68
78
 
79
+ The separate [owner messaging host](messaging-automation.md) supports native
80
+ reactions, stickers and polls only when the existing compatible
81
+ Messages bridge reports those operations available. Upstream's injected bridge
82
+ requires a SIP-disabled Mac. Installing Ghostget's CLI binary does not install
83
+ or inject that bridge, disable SIP, or relaunch Messages. A normal Mac without
84
+ that explicit setup can still use the reviewed AppleScript text and attachment
85
+ path after the required macOS permissions are granted.
86
+
87
+ Native rich cards require the compatible bridge to report `send.rich` available.
88
+ The messaging-host path requests `fetch_metadata: false` for those cards.
89
+ The card contains the URL and its host name, without a fetched title or preview
90
+ image. This confines this helper's metadata preparation; it does not claim to
91
+ control all network behavior inside Messages or on the recipient's device.
92
+
69
93
  Bind the local Messages store after the reviewed binary is installed:
70
94
 
71
95
  ```sh
@@ -0,0 +1,103 @@
1
+ # Owner messaging host
2
+
3
+ `ghostget messaging automation serve --stdio` is the trusted owner control port
4
+ for enrolled iMessage and WhatsApp conversations. It is never an agent tool.
5
+ All configuration, targets, message bodies and assets arrive on stdin; stdout
6
+ contains only protocol responses. Do not run it in a terminal or record its
7
+ streams in general logs. It does not pair accounts or start synchronization on
8
+ initialization.
9
+
10
+ The `@hraness/ghostget/messaging-automation` SDK exports the closed message,
11
+ grant, plan and receipt types, an explicit `createMessagingAutomationHost` for
12
+ trusted owner-supplied providers, and `installBundledMessagingRuntime`. Imports
13
+ are inert in Node and Bun; host construction requires Bun. Supplied providers
14
+ must enforce their own exact permissions and cleanup. Use the stdio host above
15
+ for Ghostget's built-in accounts and managed registry; its session factory is
16
+ internal and is not part of the SDK.
17
+
18
+ Install the current bundled `imessage-direct` or `whatsapp-web` adapter, bind an
19
+ explicit existing Ghostget account, and enable managed operation permissions in
20
+ Ghostget. Grant `allow` separately to `messaging.automation.read`, the required
21
+ `messaging.automation.send.<kind>` operations, and (WhatsApp only)
22
+ `messaging.automation.sync`. An old `messaging.send` allow does not authorize this
23
+ host. `ask`, `deny`, and an unmanaged policy do not provide unattended authority.
24
+ Updated manifests and automatic implementation closures invalidate old grants.
25
+ These catalog entries cannot dispatch through generic `invoke` or `confirm`.
26
+
27
+ The private transport binaries are separate owner setup:
28
+
29
+ ```sh
30
+ ghostget imessage transport install --json
31
+ ghostget whatsapp automation install --json
32
+ ```
33
+
34
+ The default installs the exact compressed runtime included in the Ghostget package. An optional `--binary /absolute/reviewed-file` selects an explicit source instead. Only exact pinned bytes are accepted. Installation starts no helper. The
35
+ WhatsApp binary is the reviewed wacli build with durable events and exact action
36
+ claims; a stock wacli does not qualify. Existing account pairing and macOS
37
+ permissions remain Ghostget setup responsibilities.
38
+
39
+ Each newline-delimited JSON request is `{protocol,id,method,params}`, with
40
+ `protocol` exactly `ghostget.messaging-automation/1`. IDs are distinct active
41
+ ASCII identifiers of at most 64 bytes. Successful responses contain
42
+ `{protocol,id,ok:true,result}`; failures contain a bounded generic
43
+ `{protocol,id,ok:false,error:{code,message}}`. No diagnostic contains credentials,
44
+ provider state paths, or message contents. Clients must correlate IDs: priority
45
+ responses can arrive before a pending ordinary response.
46
+
47
+ | Method | Exact params | Result |
48
+ | --- | --- | --- |
49
+ | initialize | providers: 1–2 `{provider,authId}`, distinct networks | `{initialized:true}` |
50
+ | status | provider | Current identity and permissions-intersected capabilities |
51
+ | start | provider | Current status; WhatsApp sync starts explicitly |
52
+ | conversations | provider, limit (1–200) | Current individual conversations |
53
+ | enroll | provider, coordinate | Exact identity/participants enrollment and historical baseline |
54
+ | enrollments | empty object | Persisted enrollments |
55
+ | grant | intentId, enrollmentId, expectedBindingDigest, actions, expiresAt, maximumActions, minimumIntervalMs | Bounded owner grant |
56
+ | grant.by-intent | intentId | `{grant}`; null only when no matching durable grant exists |
57
+ | grant.get | grantId | Current grant expiry, revocation and consumed quota |
58
+ | revoke | grantId | `{revoked:true}` |
59
+ | poll | enrollmentId | Refresh and admit new events; gaps remain explicit |
60
+ | history | enrollmentId, limit (1–200) | Stored admitted history and enrollment; no additional provider read |
61
+ | events | enrollmentIds, cursor (nullable), limit (1–500) | Durable scoped event page |
62
+ | asset | bytesBase64, sha256 | assetId, byte count, digest, expiry |
63
+ | prepare | enrollmentId, expectedRevision, intentId, actions | Exact two-minute plan |
64
+ | submit | planId, grantId | Durable terminal/uncertain action receipt |
65
+ | cancel | planId | `{cancelled:boolean}`; join submit to learn its outcome |
66
+ | run | runId | Retained exact run receipt |
67
+ | close | empty object | `{closed:true}` only after cleanup joins |
68
+
69
+ Ordinary requests are serialized by the client; the server rejects a second
70
+ ordinary in-flight request. Cancel, revoke and close have a separate capacity of
71
+ eight and may interrupt a pending submit. Cancellation never proves that an
72
+ already-started action was unsent. Grant creation requires an owner-persisted intent ID; recover an uncertain response with `grant.by-intent` before doing anything else. Keep reading the original submit response,
73
+ and reconcile its exact run identity without resubmitting uncertain work.
74
+
75
+ Frames are bounded to 24 MiB; responses to 32 MiB. Assets use canonical Base64 and
76
+ a checked SHA-256, at most 16 MiB each, 64 MiB total and 32 entries. An unclaimed
77
+ asset lasts five minutes; prepare binds it to one plan's expiry, and only that
78
+ submit can resolve its bytes. Terminal submit removes its assets. Assets do not
79
+ survive restart and never accept arbitrary filesystem paths.
80
+
81
+ Every provider operation rechecks the account incarnation, exact implementation
82
+ identity and managed permission. Explicit sync keeps a durable cleanup admission
83
+ for the lifetime of the owned WhatsApp process; loss or revocation of its sync
84
+ allow closes the provider, checked at one-second intervals. iMessage operations
85
+ join and release their own admissions. A cleanup failure retains the existing
86
+ Ghostget recovery marker across restart. Never remove it or take over an unknown
87
+ socket merely because the parent process exited. The owner must use the existing
88
+ Ghostget recovery process to establish exact descendant and resource cleanup.
89
+
90
+ The host stores enrollments, grants, historical baselines, durable cursors and
91
+ action claims privately in the selected Ghostget state home. Source generation,
92
+ account or participant drift prevents continuation. Historical bootstrap does
93
+ not become a live event; possible gaps prevent automatic sends. Provider
94
+ acceptance is reported separately from delivery or read status. Available rich
95
+ actions depend on the current pinned helper and explicit permissions;
96
+ app-clips and arbitrary mini-app experiences remain unavailable.
97
+
98
+ Native iMessage links use the pinned `.3` helper and require the compatible
99
+ bridge to report `send.rich` available. The host sets `fetch_metadata: false`
100
+ to build a native URL/host-title card without helper metadata or image fetching.
101
+ This does not control all network behavior inside Messages or on the recipient
102
+ device. WhatsApp link sharing uses its existing text payload with preview
103
+ fetching disabled.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/ghostget",
3
- "version": "0.18.0",
3
+ "version": "0.18.3",
4
4
  "description": "Open-source CLI and TypeScript SDK for precise web capabilities for AI agents: page capture, verified media archives, encrypted reads, and typed provider operations.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -74,6 +74,10 @@
74
74
  "./messaging": {
75
75
  "types": "./src/messaging-types.ts",
76
76
  "import": "./dist/messaging.js"
77
+ },
78
+ "./messaging-automation": {
79
+ "types": "./src/messaging-automation-types.ts",
80
+ "import": "./dist/messaging-automation-api.js"
77
81
  }
78
82
  },
79
83
  "bin": {
@@ -315,6 +319,21 @@
315
319
  "src/message-like-me-agentic-messaging.ts",
316
320
  "src/messaging-types.ts",
317
321
  "src/messaging.ts",
322
+ "src/messaging-automation-api.ts",
323
+ "src/messaging-automation-types.ts",
324
+ "src/messaging-automation-validation.ts",
325
+ "src/messaging-automation.ts",
326
+ "src/messaging-automation-descriptors.ts",
327
+ "src/messaging-automation-factory.ts",
328
+ "src/messaging-automation-server.ts",
329
+ "src/providers/imessage-automation.ts",
330
+ "src/providers/whatsapp-automation.ts",
331
+ "src/providers/whatsapp-automation-runtime.ts",
332
+ "src/providers/messaging-native-install.ts",
333
+ "src/providers/messaging-native-artifacts.ts",
334
+ "src/assets/messaging-runtime",
335
+ "src/plugins/whatsapp-linked-device/vendor",
336
+ "docs/messaging-automation.md",
318
337
  "src/messaging-action-store.ts",
319
338
  "src/messaging-runtime.ts",
320
339
  "src/messaging-store.ts",
@@ -348,6 +367,7 @@
348
367
  "src/plugins/imessage-direct/plugin.ts",
349
368
  "src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch",
350
369
  "src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch",
370
+ "src/plugins/imessage-direct/vendor/0003-feat-rpc-add-no-fetch-rich-cards.patch",
351
371
  "src/plugins/imessage-direct/vendor/provenance.json",
352
372
  "src/plugins/linkedin-official/plugin.ts",
353
373
  "src/plugins/linkedin-web/plugin.ts",
@@ -573,7 +593,7 @@
573
593
  "website:test": "bun test ./website ./edge",
574
594
  "website:typecheck": "tsc --noEmit --project website/tsconfig.json && tsc --noEmit --project edge/tsconfig.json",
575
595
  "website:check": "bun run check:theme && bun run website:typecheck && bun run website:test && bun run website:build",
576
- "build": "bun -e 'await (await import(\"node:fs/promises\")).rm(\"./dist\", { recursive: true, force: true })' && bun build ./src/index.ts ./src/client.ts ./src/beeper-client.ts ./src/apple-photos-client.ts ./src/whatsapp-client.ts ./src/omni-client.ts ./src/messaging.ts --outdir ./dist --root ./src --target bun --format esm --splitting --packages external",
596
+ "build": "bun -e 'await (await import(\"node:fs/promises\")).rm(\"./dist\", { recursive: true, force: true })' && bun build ./src/index.ts ./src/client.ts ./src/beeper-client.ts ./src/apple-photos-client.ts ./src/whatsapp-client.ts ./src/omni-client.ts ./src/messaging.ts ./src/messaging-automation-api.ts --outdir ./dist --root ./src --target bun --format esm --splitting --packages external",
577
597
  "typecheck": "tsc --noEmit",
578
598
  "whatsapp:transport:install": "sh src/scripts/install-whatsapp-protocol.sh",
579
599
  "test:unit": "bun test --no-orphans --timeout 180000 --max-concurrency \"${GOMAXPROCS:-4}\" ./src --path-ignore-patterns='**/src/omni-runtime.test.ts'",
@@ -40,4 +40,4 @@ Unsupported executors remain inert. Follow the existing provider-plugin
40
40
  authoring protocol to add an executor; do not synthesize arbitrary authenticated
41
41
  HTTP or broaden an existing grant. Interface changes invalidate exact grants.
42
42
 
43
- The product reference is the [Ghostget control panel guide](https://github.com/hraness/ghostget/blob/v0.18.0/docs/control-panel.md).
43
+ The product reference is the [Ghostget control panel guide](https://github.com/hraness/ghostget/blob/v0.18.3/docs/control-panel.md).
@@ -17,34 +17,34 @@ If Bun is missing, stop and direct the user to the official
17
17
  [Bun installation guide](https://bun.sh/docs/installation). Do not switch
18
18
  package managers or pipe an unreviewed installer into a shell.
19
19
 
20
- This reference is authored for the exact v0.18.0 release coordinate. Use it
20
+ This reference is authored for the exact v0.18.3 release coordinate. Use it
21
21
  only from the matching release-bound Agent Skill after its canonical archive and
22
22
  immutable GitHub Release exist. If the coordinate is not public, stop instead
23
23
  of substituting `main`, another tag, or a different package version. Install
24
24
  that exact release and its reviewed bundled adapter manifests:
25
25
 
26
26
  ```sh
27
- bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.0/hraness-ghostget-0.18.0.tgz
27
+ bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.3/hraness-ghostget-0.18.3.tgz
28
28
  ghostget adapter sync-bundled --json
29
29
  ghostget --help
30
30
  ghostget doctor --json
31
31
  ```
32
32
 
33
- The package is `@hraness/ghostget`; `@hraness/ghostget@0.18.0` is an optional npm
33
+ The package is `@hraness/ghostget`; `@hraness/ghostget@0.18.3` is an optional npm
34
34
  mirror only after verified registry publication. Canonical installation does not
35
35
  wait for registry publication.
36
36
 
37
37
  When upgrading from Wrench, use `ghostget` for new commands and
38
38
  `GHOSTGET_STATE_HOME` for an explicit state root. Existing state is selected in
39
39
  place; do not rename, copy, or delete a state directory as part of the upgrade.
40
- The [migration guide](https://github.com/hraness/ghostget/blob/v0.18.0/docs/ghostget-migration.md)
40
+ The [migration guide](https://github.com/hraness/ghostget/blob/v0.18.3/docs/ghostget-migration.md)
41
41
  explains the retained state aliases and durable protocol names.
42
42
 
43
43
  Do not clone the repository merely to run the CLI. Importing the SDK is a
44
44
  separate project dependency and does not install a global command:
45
45
 
46
46
  ```sh
47
- bun add https://github.com/hraness/ghostget/releases/download/v0.18.0/hraness-ghostget-0.18.0.tgz
47
+ bun add https://github.com/hraness/ghostget/releases/download/v0.18.3/hraness-ghostget-0.18.3.tgz
48
48
  ```
49
49
 
50
50
  `ghostget adapter sync-bundled` upgrades exact bundled baselines, including an
@@ -232,27 +232,55 @@ relationship on that vanity from the profile page. Current LinkedIn pages are
232
232
  SDUI/RSC and often omit classic `bpr-guid-*` Profile embeds. The binder
233
233
  therefore walks both those Voyager code payloads and `window.__como_rehydration__`.
234
234
  Current pages assign that global as either a JSON object (`= { … }`) or an RSC
235
- flight array (`= [ "1:I[…]\\n2:{…}" ]`). The binder accepts both, decodes
236
- flight rows that carry JSON objects, arrays, or JSON strings, and treats
235
+ flight array (`= [ "1:I[…]\\n2:{…}" ]`). Some current arrays also carry a
236
+ non-flight SDUI string slot (not `1:…` rows) that still contains
237
+ `networkDistance` and `vieweeMemberUrn`. The binder accepts both assignment
238
+ shapes, decodes flight rows that carry JSON objects, arrays, or JSON strings,
239
+ feeds non-flight string slots through the same bootstrap decoder, and treats
237
240
  `memberDistance`, `networkDistance`, or `distance` of `DISTANCE_1`,
238
241
  `1`, or `"1"` as first-degree when that value is joined to the requested vanity
239
242
  or its profile URN (`entityUrn`, `objectUrn`, `profileUrn`, `vieweeMemberUrn`,
240
243
  or `vieweeProfileId` plus vanity). Current Como trees can nest around depth 60,
241
244
  so the walk keeps a node ceiling and a depth ceiling of 128 instead of aborting
242
- at depth 32. Distance may appear on PROFILE_VIEW breadcrumb or RSC string rows
243
- rather than on the same decoded object as the identity. Empty, missing, or
244
- import-only bootstrap stays fail-closed.
245
+ at depth 32. Distance may appear on PROFILE_VIEW breadcrumb or RSC string rows,
246
+ including multi-escaped Como fragments such as `networkDistance\":1` beside a
247
+ `vieweeMemberUrn` or member id that is not the same `fsd_profile` URN bound
248
+ from `vieweeProfileId`. A unique first-degree distance on that viewee join is
249
+ accepted. Incidental viewer or other `fsd_profile` URNs
250
+ in the same breadcrumb do not steal identity. Empty, missing, ambiguous, or
251
+ contradictory distances stay fail-closed.
245
252
 
246
253
  When the same page already embeds Contact-info fields, including a labeled
247
254
  Email row, the operation projects those fields and does not issue a second
248
- fetch. Otherwise it GETs `/voyager/api/graphql` for
249
- `voyagerIdentityDashProfileContactInfo`, using a page-resolved `queryId` when
250
- the HTML contains exactly one decorated revision and `queryName` otherwise.
251
- Variables are exactly `(profileUrn:{urn})`. Classic
252
- `/voyager/api/identity/profiles/{vanity}/profileContactInfo` now returns HTTP
253
- 410 and is rejected. The executable contract is that reviewed first-party GET
254
- or the page-embedded fields, not a caller-selected RSC body, DOM click, or
255
- selector. Ghostget does not message, connect, or InMail.
255
+ fetch. Long SDUI strings used as field labels are skipped instead of aborting
256
+ the profile-stage walk. Otherwise it first inspects the page's exact
257
+ Contact-info NavigateToScreen action. A reviewed action still admits only
258
+ the ProfileContactDetailsOverlay URL (`sduiid` equal to that overlay
259
+ `screenId`). Adapter 1.36.3 then clicks the unique accessible name
260
+ `Contact info` on the already-loaded 1st-degree profile and projects Email
261
+ from that modal DOM, including when that control's `href` matches
262
+ `/overlay/contact-info/` and contained Chrome follows that reviewed vanity
263
+ overlay pathname after click. When the NavigateToScreen extract is
264
+ `absent`, the same click still uses that reviewed overlay `sduiid` plus a
265
+ headed clientArguments payload from the bound vanity, optional
266
+ givenName/familyName parsed from profile HTML when unique,
267
+ `isVanityNameResolved`, nested requestMetadata
268
+ states/screenId/knownTemplates, and `isModal` true. Malformed or
269
+ unsupported action fields stop the operation. Zero or two-plus matching
270
+ controls, a path other than the bound profile or
271
+ `/in/:publicIdentifier/overlay/contact-info/`, or a missing or ambiguous
272
+ dialog, fail closed. The contained path does not mint a synthetic
273
+ navigation POST or track headers, and it does not fetch that overlay href
274
+ as a navigation GET. GraphQL `voyagerIdentityDashProfileContactInfo` and
275
+ the vanity overlay RSC GET stay unused once that click is selected.
276
+ ScreenId-only and `profileUrn`-bound
277
+ `/flagship-web/rsc-action/actions/navigation` overlay GET URLs stay rejected
278
+ because live dormant sessions return HTTP 500 `application/octet-stream`.
279
+ Classic `/voyager/api/identity/profiles/{vanity}/profileContactInfo` now
280
+ returns HTTP 410 and is rejected. The executable contract is the reviewed
281
+ Contact info click and modal snapshot, or the page-embedded fields. It
282
+ accepts no caller-selected RSC body, selector, or script. This contact
283
+ read does not message, connect, or InMail.
256
284
 
257
285
  The projection returns `email` when LinkedIn shows it, plus any of the vanity
258
286
  profile link, connected-since date, phone numbers, websites, and birthday.
@@ -287,6 +315,180 @@ the binder required a classic URN on the vanity record and required
287
315
  `networkDistance` on a decoded object. Adapter 1.24.0 joins `vieweeProfileId`
288
316
  plus vanity and reads breadcrumb or RSC string-row distance.
289
317
 
318
+ A 2026-09-11 signed-in capture of another 1st-degree profile still bound
319
+ `profiles.read` and still reached a unique `vieweeProfileId` profile URN after
320
+ the 1.24.0 walk. `contacts.read` then failed closed because relationship
321
+ distance sat on a PROFILE_VIEW breadcrumb next to `vieweeMemberUrn` / member
322
+ ids, not on a vanity or `fsd_profile` record the binder treated as the same
323
+ target. Adapter 1.25.0 joins that unique viewee distance, including when the
324
+ breadcrumb also carries an incidental viewer URN, and still fails closed for
325
+ self, non-first-degree, and contradictory distances.
326
+
327
+ A later 2026-09-11 signed-in capture of another 1st-degree profile still bound
328
+ `profiles.read` and still joined `vieweeProfileId` identity after the 1.25.0
329
+ walk. `contacts.read` then failed closed because PROFILE_VIEW breadcrumbs kept
330
+ `networkDistance` and `vieweeMemberUrn` in multi-escaped Como string rows
331
+ (`networkDistance\":1`), so the walk produced no distance records for the
332
+ viewee join. Adapter 1.26.0 peels those JSON string escapes and reads the
333
+ escaped key/value form. Self, non-first-degree, and contradictory distances
334
+ still fail closed.
335
+
336
+ A later 2026-09-11 signed-in capture of another 1st-degree profile still bound
337
+ `profiles.read` and still joined `vieweeProfileId` identity after the 1.26.0
338
+ walk. `contacts.read` then failed closed because distance lived in Como
339
+ rehydration array slot `[6]`, a ~25KB SDUI string with `networkDistance` and
340
+ `vieweeMemberUrn`. That slot was not an RSC flight, so
341
+ `decodeComoRehydrationValue` discarded it (`[]`) before the 1.26.0 peel.
342
+ `decodeStringBootstrap` already recovered the distance record from the same
343
+ string. Adapter 1.27.0 feeds non-flight Como string slots through that
344
+ decoder and still routes true RSC flights through `decodeRscFlightRecords`.
345
+ Self, non-first-degree, and contradictory distances still fail closed.
346
+
347
+ A later signed-in capture of a known 1st-degree profile still bound
348
+ identity, profile HTML, and distance, but Contact-info GraphQL returned
349
+ HTTP 403 `text/html` and the page still omitted
350
+ `voyagerIdentityDashProfileContactInfo.<32hex>`. The signed-in UI opens
351
+ Contact info through `ProfileContactDetailsOverlay`, not GraphQL.
352
+ Adapter 1.28.0 GETs that overlay with the bound `profileUrn` when queryId
353
+ is absent or after that reviewed GraphQL rejection, and projects Email
354
+ from the RSC or SDUI overlay payload. ScreenId-only overlay URLs stay
355
+ rejected. Self, non-first-degree, and contradictory distances still fail
356
+ closed.
357
+
358
+ A later signed-in capture of the same 1st-degree path still bound
359
+ identity and distance, but the 1.28.0 navigation GET returned HTTP 500
360
+ `application/octet-stream` for screenId-only, `profileUrn`-bound, and
361
+ html variants. `/in/:publicIdentifier/overlay/contact-info/` returned
362
+ HTML 200 for the signed-in Contact-info modal. Long SDUI strings in
363
+ profile HTML also aborted the best-effort embedded walk at profile
364
+ stage and misreported as relationship-binding contract-drift. Adapter
365
+ 1.29.0 skips non-bounded field labels during that walk and GETs the
366
+ exact vanity overlay path with RSC headers instead of the 500
367
+ navigation route. GraphQL Contact-info remains a 403 `text/html` path
368
+ when a unique queryId is present. Self, non-first-degree, and
369
+ contradictory distances still fail closed.
370
+
371
+ A later 2026-09-11 smoke of the same 1st-degree path after adapter 1.29.0
372
+ still bound identity and distance, and the vanity overlay GET returned
373
+ HTML 200. That body was the signed-in profile HTML shell (~500KB+): no
374
+ projectable Email, no mailto, and no Contact-info JSON or RSC flight.
375
+ GraphQL Contact-info stayed HTTP 403 `text/html` with `queryId=null`.
376
+ The live Contact-info Email still appears only after the headed UI opens
377
+ the modal. Dormant contained Chrome stays GET-only and cannot mint that
378
+ client-filled payload. Adapter 1.30.0 treats an HTML-200 shell as
379
+ omitted Contact-info fields after 1st-degree binding, still projects
380
+ overlay HTML that carries Como Email, a unique mailto, or an RSC/SDUI
381
+ flight, and keeps `/flagship-web/rsc-action/actions/navigation` overlay
382
+ GETs rejected.
383
+
384
+ A later headed signed-in capture of the same 1st-degree Contact-info
385
+ click showed Email in the modal. The first network response that
386
+ contained the Email label was POST
387
+ `/flagship-web/rsc-action/actions/navigation?screenId=com.linkedin.sdui.flagshipnav.profile.ProfileContactDetailsOverlay&sduiid=com.linkedin.sdui.flagshipnav.profile.ProfileContactDetailsOverlay`
388
+ with `Accept: */*`, `Content-Type: application/json`, no RSC or Next
389
+ router headers, and JSON body keys `clientArguments` and `isModal`. The
390
+ response was HTTP 200 `application/octet-stream` RSC flight. Headed
391
+ capture bound `sduiid` to that same overlay `screenId` constant. Dormant
392
+ profile HTML NavigateToScreen for Contact info carries `pageKey`
393
+ `profile_view_base_contact_details` and `requestedArguments.payload`
394
+ (`vanityName`, `givenName`, `familyName`, `isVanityNameResolved`) but
395
+ omits `sduiid`. Adapter 1.31.0 POSTs the exact allowlisted URL after
396
+ 1st-degree bind, peels reviewed payload keys, and copies `$type` /
397
+ `requestedStateKeys` / `requestMetadata` only when those reviewed fields
398
+ are already on the action. It does not invent a stolen `sduiid`, `$type`,
399
+ or `requestMetadata`. A different `sduiid` fails closed. GraphQL
400
+ Contact-info stays a 403 `text/html` fallback. Navigation overlay GETs
401
+ stay rejected. The vanity overlay GET remains HTML-shell honesty when
402
+ the profile page omits that NavigateToScreen action. Soft-labels stay.
403
+ Keep cookies, `li_at`, CSRF, `JSESSIONID`, live emails, and raw HARs out
404
+ of Git.
405
+
406
+ A later signed-in smoke after adapter 1.31.0 still bound 1st-degree
407
+ distance and selected the navigation POST family, but live
408
+ NavigateToScreen `requestedArguments` keeps `requestMetadata` a sibling
409
+ of `payload` and uses `$type` values `proto.sdui.actions.requests.RequestedArguments`
410
+ and `proto.sdui.common.RequestMetadata`. Treating `requestMetadata` as an
411
+ unreviewed extra key, or rejecting `proto.sdui.*` in favor of
412
+ `com.linkedin.*` only, aborted before POST. A payload-only body then
413
+ returned HTTP 500. Adapter 1.32.0 copies that reviewed sibling shape,
414
+ lifts headed nested `payload.requestMetadata` to the sibling placement
415
+ when the two agree, and admits `proto.sdui.*` types only on this overlay
416
+ POST body. Extra keys and unrelated proto prefixes still fail closed.
417
+
418
+ A later signed-in smoke after adapter 1.32.0 peeled that sibling
419
+ `proto.sdui` shape as `kind=action`, then the POST still returned HTTP
420
+ 500 `text/html`. The body sent empty sibling `RequestMetadata{$type}`
421
+ only. Headed 200 Email capture `POST-navigation-body-shape-20260912.md`
422
+ nests `requestMetadata` under `payload` with `$type`, `states: []`,
423
+ overlay `screenId`, and `knownTemplates: []`, and keeps
424
+ `clientArguments.$type` plus `requestedStateKeys: []`. Adapter 1.33.0
425
+ emits that exact headed shape, including when dormant HTML omits
426
+ metadata, copies reviewed string lists when the page already has them,
427
+ and does not invent opaque state objects. Contained Chrome already
428
+ sends `Accept: */*`, `Content-Type: application/json`, and session
429
+ `csrf-token`; it does not mint `X-Li-*` track values. The query string
430
+ stays `sduiid=` matching overlay `screenId`. If a later 500 persists
431
+ after this exact body, the remaining gap is headed-only non-empty
432
+ `states` / `knownTemplates`, not sibling placement.
433
+
434
+ A later signed-in smoke after adapter 1.33.0 kept that exact headed
435
+ body and still returned HTTP 500 `text/html` from contained Chrome.
436
+ The POST ran from the signed-in origin fetch context, not the loaded
437
+ profile document, so it omitted page-instance headers the SPA sets.
438
+ Adapter 1.34.0 opens the exact reviewed profile URL, copies a unique
439
+ `d_flagship3_profile*` `x-li-page-instance` from observed same-origin
440
+ rsc-action or voyager requests or that document, and forwards optional
441
+ `x-li-track` (`mpName` `web` or `voyager-web`),
442
+ `x-li-application-version`, `x-li-application-instance`,
443
+ `x-li-anchor-page-key`, and `x-li-rsc-stream=true` only when the same
444
+ observation already has those values.
445
+
446
+ A later signed-in smoke after adapter 1.34.0 kept that exact headed
447
+ body and forwarded page-instance, track (`mpName=web`), application
448
+ version/instance, anchor page key, and `rscStream=true`, then still
449
+ returned HTTP 500 `text/html`. The same contained-browser
450
+ `/flagship-web/rsc-action/` observations on the bound profile already
451
+ carried `x-li-page-instance-tracking-id`, `x-li-pageforestid`,
452
+ `x-li-traceparent`, and `x-li-tracestate`; 1.34.0 left those names off.
453
+ Adapter 1.35.0 copies those original strings from the same selected
454
+ rsc-action observation under a reviewed allowlist, copies
455
+ `x-li-layout-tree` only when that observation has it, and does not mint
456
+ any of those values when they are absent. Missing or malformed observed
457
+ values fail closed before POST. Live query key stays `sduiid=` (headed
458
+ 200 Email POST and public SDUI `server-request` / `pagination` routes);
459
+ `sduid=` notes are transcription typos. Missing or ambiguous
460
+ page-instance fails closed before POST. Headed successful body
461
+ Content-Length was 400; compact emit at 398 stays.
462
+
463
+ A later signed-in headed proof on 2026-09-12 opened tessbloch, clicked
464
+ Contact info, and showed Email in the modal DOM. The same contained
465
+ synthetic navigation POST still returned HTTP 500 `text/html` after the
466
+ exact headed body and the full observed X-Li set. Adapter 1.36.0 kept
467
+ the page-instance bind, then clicked only the unique reviewed Contact
468
+ info control and projected that modal. Operator smoke the same day
469
+ found that unique control and fail-closed before click because its
470
+ href matched `/overlay/contact-info/`. The raw eval error was exactly
471
+ `Contact-info control targeted the vanity overlay GET`. Adapter 1.36.1
472
+ clicks that unique control even when the href is the vanity overlay
473
+ GET. Operator smoke after that click fail-closed because contained
474
+ Chrome left `/in/tessbloch/` for
475
+ `/in/tessbloch/overlay/contact-info/`. Adapter 1.36.2 treats that
476
+ reviewed vanity overlay pathname as still bound after click, then
477
+ snapshots the unique dialog. Operator smoke after that bind was green
478
+ for Email on tessbloch when the extract was `absent` and the operator
479
+ clicked with the reviewed overlay `sduiid` plus headed clientArguments.
480
+ Official `contacts.read` still used GraphQL or the vanity overlay GET
481
+ on that absent extract. Adapter 1.36.3 uses the same click and modal
482
+ snapshot when extract is `absent`. Authwall and any other path still
483
+ fail closed. It does not mint a navigation POST or track headers.
484
+ Cloud has no signed-in LinkedIn session; do not treat this landing as
485
+ live green.
486
+
487
+ ```sh
488
+ printf '%s' '{"profile_url":"https://www.linkedin.com/in/tessbloch/"}' \
489
+ | ghostget invoke linkedin-web contacts.read --input - --auth linkedin-dormant-20260911 --json
490
+ ```
491
+
290
492
  Self profiles fail closed with guidance to use `profiles.read`. Second-degree,
291
493
  third-degree, and out-of-network profiles fail closed because LinkedIn hid
292
494
  Contact info from that viewer. The operation never invents a hidden email.
@@ -20,7 +20,7 @@ Leave the state `capture-required` when current evidence is absent, ambiguous, e
20
20
  ## LinkedIn
21
21
 
22
22
  - Use `linkedin-web` with a browser-session/cookie realm for consumer Home feed, a member's recent-activity posts, inbox folders, conversations, and native article-editor surfaces. The separate official `linkedin` OAuth adapter covers approved post, comment, reply, repost, and reaction scopes; it does not supply the consumer Home feed, recent-activity pager, or inbox.
23
- - Current bundle 1.24.0 observes `linkedin-web profiles.read@1`, `organizations.read@1`, `contacts.read@1`, `feeds.read@2` for `feed=profile-activity`, `articles.draft.save@7`, and image-only `posts.publish@3`. The two profile-stat reads use a path-backed contained Chrome realm, bind the current member before projecting one exact self profile or requested organization Page, and keep the optional private connection read sequential with the self-profile read. `contacts.read@1` uses the same contained Chrome path, requires a 1st-degree relationship on the requested vanity from classic Profile embeds or Como rehydration (JSON object, RSC flight array, `vieweeProfileId` plus vanity, and breadcrumb or string-row distance), and projects Contact-info email plus any shown profile link, connected-since date, phones, websites, or birthday. The profile-activity page uses the same contained Chrome path, binds the signed-in viewer, resolves the live `voyagerFeedDashProfileUpdates` query, and projects one recent-activity page with engagement counts when present; `feed=home` remains capture-required. The fixed browser evaluation performs only bounded, exact-route first-party fetches; it does not click or inspect LinkedIn DOM. A cookie-only standalone client is not a fallback because current live evidence shows LinkedIn invalidates an exported `li_at` outside its browser/device context. The Article contract binds the numeric member subject to the normalized Article-author profile, creates or replaces one exact private draft, keeps a supplied cover in the Article banner rather than the body, and can preserve the independently read existing banner during an exact replacement without another cover upload. It supports paragraphs/H1/H2/native blockquotes, native HTTPS links, and 1–20 bounded inline JPEG/PNG/WebP images with required alt text and optional captions, and verifies the exact unpublished editor-response readback. Its fixed cover and inline-image single-upload registrations, byte transfers, autosaves, and readbacks run inside contained Chrome without DOM automation. The separate post contract stages a real-size optional PNG through bounded ordered commands, admits one exact image transfer and post create, durably retains the accepted share target, and independently verifies it. `linkedin-web media.publish@1` separately reserves an MP4 route but remains capture-required; the official `linkedin posts.publish` OAuth contract already observes MP4.
23
+ - Current bundle 1.36.3 observes `linkedin-web profiles.read@1`, `organizations.read@1`, `contacts.read@1`, `feeds.read@2` for `feed=profile-activity`, `articles.draft.save@7`, and image-only `posts.publish@3`. The two profile-stat reads use a path-backed contained Chrome realm, bind the current member before projecting one exact self profile or requested organization Page, and keep the optional private connection read sequential with the self-profile read. `contacts.read@1` uses the same contained Chrome path, requires a 1st-degree relationship on the requested vanity from classic Profile embeds or Como rehydration (JSON object, RSC flight array, non-flight Como string slots through `decodeStringBootstrap`, `vieweeProfileId` plus vanity, and breadcrumb, string-row, or `vieweeMemberUrn` distance, including multi-escaped PROFILE_VIEW rows and when that member id is not the bound `fsd_profile` URN), and projects Contact-info email plus any shown profile link, connected-since date, phones, websites, or birthday from page-embedded fields while skipping non-bounded SDUI field labels, or from the modal opened by clicking the unique reviewed Contact info control on that already-loaded 1st-degree profile after the same page-instance bind, including when that control's href matches `/overlay/contact-info/` and the document pathname stays on the bound profile or that reviewed vanity overlay path. A present NavigateToScreen action still admits only the reviewed ProfileContactDetailsOverlay URL (`sduiid` equal to that overlay `screenId`). When that extract is `absent`, the same click still uses that reviewed overlay `sduiid` plus a headed clientArguments payload from the bound vanity, optional givenName/familyName from profile HTML, `isVanityNameResolved`, and nested requestMetadata. The contained path does not mint a synthetic navigation POST or track headers. GraphQL Contact-info and the vanity `/overlay/contact-info/` RSC GET stay unused once that click is selected. An HTML-200 profile shell that omits Email fails as omitted Contact-info fields after that 1st-degree bind; overlay, navigation-flight, or modal HTML that still carries Como Email, a unique mailto, or an RSC/SDUI flight still projects. ScreenId-only, `profileUrn`-bound, and GET `/flagship-web/rsc-action/actions/navigation` overlay URLs stay rejected because live dormant sessions return HTTP 500. The profile-activity page uses the same contained Chrome path, binds the signed-in viewer, resolves the live `voyagerFeedDashProfileUpdates` query, and projects one recent-activity page with engagement counts when present; `feed=home` remains capture-required. The fixed browser evaluation performs only bounded, exact-route first-party fetches, except the reviewed unique Contact info click and modal snapshot on `contacts.read`; it does not accept a caller-selected selector. A cookie-only standalone client is not a fallback because current live evidence shows LinkedIn invalidates an exported `li_at` outside its browser/device context. The Article contract binds the numeric member subject to the normalized Article-author profile, creates or replaces one exact private draft, keeps a supplied cover in the Article banner rather than the body, and can preserve the independently read existing banner during an exact replacement without another cover upload. It supports paragraphs/H1/H2/native blockquotes, native HTTPS links, and 1–20 bounded inline JPEG/PNG/WebP images with required alt text and optional captions, and verifies the exact unpublished editor-response readback. Its fixed cover and inline-image single-upload registrations, byte transfers, autosaves, and readbacks run inside contained Chrome without DOM automation. The separate post contract stages a real-size optional PNG through bounded ordered commands, admits one exact image transfer and post create, durably retains the accepted share target, and independently verifies it. `linkedin-web media.publish@1` separately reserves an MP4 route but remains capture-required; the official `linkedin posts.publish` OAuth contract already observes MP4.
24
24
  - Every other LinkedIn web operation remains capture-required. Explicit inert reservations cover inbox folders, one bounded page of recommended connections, one connection invitation, Article reads/publication, and other comment/message/repost surfaces. Their retained candidates do not confer executable internal requests.
25
25
  - Exclude presence, messaging badges, delivery acknowledgements, seen/read receipts, and notification badge traffic from every R1 contract.
26
26
  - Bind the current viewer's person/member identity to the auth realm. `organizations.read` views a Page and does not confer Page-actor authority. For organization actions, additionally bind the administered organization actor selected by the plan.