@hraness/ghostget 0.17.6 → 0.18.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 (114) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/README.md +16 -9
  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-en5hycxp.js +175 -0
  10. package/dist/{index-cytf8d9p.js → index-hfbygww8.js} +1 -1
  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 +147 -0
  23. package/docs/imessage-direct-provider.md +31 -7
  24. package/docs/messaging-automation.md +103 -0
  25. package/package.json +66 -7
  26. package/skills/ghostget/SKILL.md +3 -1
  27. package/skills/ghostget/references/control-panel.md +43 -0
  28. package/skills/ghostget/references/install.md +5 -5
  29. package/skills/ghostget/references/linkedin-adapter.md +222 -12
  30. package/skills/ghostget/references/platform-patterns.md +1 -1
  31. package/src/args.ts +18 -5
  32. package/src/assets/adapters/imessage/wrench-web-adapter.json +126 -0
  33. package/src/assets/adapters/linkedin/wrench-web-adapter.json +1 -1
  34. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +144 -0
  35. package/src/assets/messaging-runtime/NOTICE.txt +2580 -0
  36. package/src/assets/messaging-runtime/imsg-darwin-arm64.gz +0 -0
  37. package/src/assets/messaging-runtime/phone-number-info.plist.gz +0 -0
  38. package/src/assets/messaging-runtime/phone-number-metadata.json.gz +0 -0
  39. package/src/assets/messaging-runtime/phone-number-privacy.plist.gz +0 -0
  40. package/src/assets/messaging-runtime/wacli-darwin-arm64.gz +0 -0
  41. package/src/auth.ts +35 -1
  42. package/src/beeper-client-types.ts +1 -1
  43. package/src/cli.ts +18 -0
  44. package/src/confirmed-write-platform.ts +16 -1
  45. package/src/control/account-revision.ts +16 -0
  46. package/src/control/activity.ts +104 -0
  47. package/src/control/approval-broker.ts +59 -0
  48. package/src/control/approval-client.ts +49 -0
  49. package/src/control/bundled-interfaces.ts +20 -0
  50. package/src/control/cli.ts +20 -0
  51. package/src/control/connections.ts +87 -0
  52. package/src/control/credential-helper.ts +152 -0
  53. package/src/control/helper.ts +79 -0
  54. package/src/control/interface-cli.ts +22 -0
  55. package/src/control/interface-json.ts +94 -0
  56. package/src/control/interface-schema.ts +120 -0
  57. package/src/control/interfaces.ts +438 -0
  58. package/src/control/protocol.ts +182 -0
  59. package/src/control/service.ts +104 -0
  60. package/src/control/validation.ts +103 -0
  61. package/src/control/vault.ts +105 -0
  62. package/src/control/web-gateway.ts +62 -0
  63. package/src/control/web-policy.ts +56 -0
  64. package/src/ghostget.ts +18 -4
  65. package/src/messaging-automation-api.ts +12 -0
  66. package/src/messaging-automation-descriptors.ts +29 -0
  67. package/src/messaging-automation-factory.ts +148 -0
  68. package/src/messaging-automation-server.ts +183 -0
  69. package/src/messaging-automation-types.ts +155 -0
  70. package/src/messaging-automation-validation.ts +110 -0
  71. package/src/messaging-automation.ts +356 -0
  72. package/src/messaging-runtime.ts +3 -0
  73. package/src/oauth-google.ts +11 -5
  74. package/src/omni-runtime.ts +18 -3
  75. package/src/operation-permission-store.ts +92 -0
  76. package/src/operation-permission.ts +308 -0
  77. package/src/pinned-https.ts +5 -0
  78. package/src/plugins/imessage-direct/plugin.ts +12 -1
  79. package/src/plugins/imessage-direct/vendor/0003-feat-rpc-add-no-fetch-rich-cards.patch +276 -0
  80. package/src/plugins/imessage-direct/vendor/provenance.json +84 -10
  81. package/src/plugins/linkedin-web/plugin.ts +1 -1
  82. package/src/plugins/whatsapp-linked-device/plugin.ts +7 -2
  83. package/src/plugins/whatsapp-linked-device/vendor/0001-ghostget-private-messaging.patch +1093 -0
  84. package/src/plugins/whatsapp-linked-device/vendor/README.md +39 -0
  85. package/src/plugins/whatsapp-linked-device/vendor/provenance.json +45 -0
  86. package/src/provider-http.ts +11 -3
  87. package/src/provider-plugin-contract-identity.ts +2 -2
  88. package/src/provider-plugin-import-analysis.ts +52 -0
  89. package/src/provider-plugin-module-analysis.ts +21 -1
  90. package/src/provider-plugin-registry.ts +4 -8
  91. package/src/provider-plugin.ts +18 -8
  92. package/src/providers/imessage-automation.ts +250 -0
  93. package/src/providers/imessage-direct-install.ts +7 -0
  94. package/src/providers/imessage-direct-runtime.ts +32 -2
  95. package/src/providers/imessage-direct.ts +13 -3
  96. package/src/providers/linkedin-contact-failure.ts +21 -0
  97. package/src/providers/linkedin-contact-platform.ts +8 -1
  98. package/src/providers/linkedin-contact-program.ts +34 -5
  99. package/src/providers/linkedin-web-contact.ts +1223 -35
  100. package/src/providers/linkedin-web-profile-browser.ts +659 -45
  101. package/src/providers/linkedin-web-runtime.ts +1 -0
  102. package/src/providers/linkedin-web.ts +46 -2
  103. package/src/providers/messaging-native-artifacts.ts +38 -0
  104. package/src/providers/messaging-native-install.ts +75 -0
  105. package/src/providers/whatsapp-automation-runtime.ts +240 -0
  106. package/src/providers/whatsapp-automation.ts +153 -0
  107. package/src/read-client.ts +12 -2
  108. package/src/runtime.ts +81 -9
  109. package/src/state-helper.ts +2 -0
  110. package/src/storage.ts +71 -1
  111. package/src/usage.ts +9 -1
  112. package/src/version.ts +1 -1
  113. package/src/web-session-contract-definitions.ts +1 -1
  114. package/tsconfig.json +3 -0
@@ -232,23 +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 or arrays, 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
- or its profile URN (`entityUrn`, `objectUrn`, `profileUrn`, or `vieweeMemberUrn`).
240
- Empty, missing, or import-only bootstrap stays fail-closed.
242
+ or its profile URN (`entityUrn`, `objectUrn`, `profileUrn`, `vieweeMemberUrn`,
243
+ or `vieweeProfileId` plus vanity). Current Como trees can nest around depth 60,
244
+ so the walk keeps a node ceiling and a depth ceiling of 128 instead of aborting
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.
241
252
 
242
253
  When the same page already embeds Contact-info fields, including a labeled
243
254
  Email row, the operation projects those fields and does not issue a second
244
- fetch. Otherwise it GETs `/voyager/api/graphql` for
245
- `voyagerIdentityDashProfileContactInfo`, using a page-resolved `queryId` when
246
- the HTML contains exactly one decorated revision and `queryName` otherwise.
247
- Variables are exactly `(profileUrn:{urn})`. Classic
248
- `/voyager/api/identity/profiles/{vanity}/profileContactInfo` now returns HTTP
249
- 410 and is rejected. The executable contract is that reviewed first-party GET
250
- or the page-embedded fields, not a caller-selected RSC body, DOM click, or
251
- 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.2 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. Malformed or unsupported action fields stop
264
+ the operation. Zero or two-plus matching controls, a path other than the
265
+ bound profile or `/in/:publicIdentifier/overlay/contact-info/`, or a
266
+ missing or ambiguous dialog, fail closed. The contained path does not mint
267
+ a synthetic navigation POST or track headers, and it does not fetch that
268
+ overlay href as a navigation GET. Only when the
269
+ action is absent
270
+ does it use a page-resolved GraphQL `queryId` for
271
+ `voyagerIdentityDashProfileContactInfo`, when the HTML contains exactly one
272
+ decorated revision. Variables are exactly `(profileUrn:{urn})`. When that
273
+ queryId is absent, or when the GraphQL GET returns a reviewed HTTP 403
274
+ `text/html` rejection, the operation GETs the exact vanity
275
+ `/in/:publicIdentifier/overlay/contact-info/` overlay with RSC browser
276
+ binding. 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 those reviewed
281
+ first-party reads, the reviewed Contact info click and modal snapshot, or
282
+ the page-embedded fields. It accepts no caller-selected RSC body, selector,
283
+ or script. This contact read does not message, connect, or InMail.
252
284
 
253
285
  The projection returns `email` when LinkedIn shows it, plus any of the vanity
254
286
  profile link, connected-since date, phone numbers, websites, and birthday.
@@ -273,6 +305,184 @@ string still carried vanity-joined `networkDistance: 1` next to
273
305
  array form and still uses `queryName` when the page embeds no decorated
274
306
  queryId.
275
307
 
308
+ A 2026-09-10 signed-in capture of another 1st-degree profile still bound
309
+ `profiles.read` and still omitted classic `entityUrn` / `objectUrn` /
310
+ `profileUrn` keys on vanity records. Those records carried `vanityName`,
311
+ `vieweeProfileId`, and `isSelfView` instead, and the live Como tree reached
312
+ about depth 59 with far fewer than the node ceiling. The depth-32 abort treated
313
+ that page as contract-drift. After a deeper walk, identity still failed because
314
+ the binder required a classic URN on the vanity record and required
315
+ `networkDistance` on a decoded object. Adapter 1.24.0 joins `vieweeProfileId`
316
+ plus vanity and reads breadcrumb or RSC string-row distance.
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. Authwall and any other path still fail
478
+ closed. It does not mint a navigation POST or track headers. Cloud has
479
+ no signed-in LinkedIn session; do not treat this landing as live green.
480
+
481
+ ```sh
482
+ printf '%s' '{"profile_url":"https://www.linkedin.com/in/tessbloch/"}' \
483
+ | ghostget invoke linkedin-web contacts.read --input - --auth linkedin-dormant-20260911 --json
484
+ ```
485
+
276
486
  Self profiles fail closed with guidance to use `profiles.read`. Second-degree,
277
487
  third-degree, and out-of-network profiles fail closed because LinkedIn hid
278
488
  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.23.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 or RSC flight array), 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.2 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`); the contained path does not mint a synthetic navigation POST or track headers. GraphQL Contact-info stays a 403 `text/html` fallback when a unique queryId is present and no navigation action is bound. The vanity `/overlay/contact-info/` RSC GET remains HTML-shell honesty when the profile omits that action. 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.
package/src/args.ts CHANGED
@@ -36,6 +36,8 @@ type MessagingArguments = {
36
36
 
37
37
  export type GhostgetArguments =
38
38
  | { readonly command: "help" }
39
+ | { readonly command: "messaging-automation-serve" }
40
+ | { readonly command: "whatsapp-automation-install"; readonly binary?: string; readonly json: boolean }
39
41
  | { readonly command: "clip"; readonly arguments: readonly string[] }
40
42
  | { readonly command: "read"; readonly arguments: readonly string[] }
41
43
  | { readonly command: "media"; readonly arguments: readonly string[] }
@@ -69,7 +71,7 @@ export type GhostgetArguments =
69
71
  }
70
72
  | {
71
73
  readonly command: "imessage-transport-install";
72
- readonly binary: string;
74
+ readonly binary?: string;
73
75
  readonly json: boolean;
74
76
  }
75
77
  | { readonly command: "doctor"; readonly json: boolean }
@@ -464,6 +466,11 @@ function optionalPositiveInteger(
464
466
 
465
467
  function parseMessagingArguments(raw: readonly string[]): ParseGhostgetResult {
466
468
  const operation = raw[0];
469
+ if (operation === "automation") {
470
+ return raw.length === 3 && raw[1] === "serve" && raw[2] === "--stdio"
471
+ ? { ok: true, value: { command: "messaging-automation-serve" } }
472
+ : { ok: false, message: "Use messaging automation serve --stdio; all owner configuration and message data must arrive on stdin." };
473
+ }
467
474
  if (operation === "reconcile") {
468
475
  const runId = raw[1];
469
476
  if (runId === undefined) {
@@ -923,6 +930,13 @@ export function parseGhostgetArguments(raw: readonly string[]): ParseGhostgetRes
923
930
  };
924
931
  }
925
932
  if (first === "whatsapp") {
933
+ if (raw[1] === "automation" && raw[2] === "install") {
934
+ const parsed = optionValues(raw.slice(3), ["--binary"], ["--json"]);
935
+ if (isFailure(parsed)) return parsed;
936
+ const binary = parsed.values["--binary"];
937
+ if (binary !== undefined && (!isAbsolute(binary) || resolve(binary) !== binary || Buffer.byteLength(binary) > 4096 || /[\0\r\n]/u.test(binary))) return { ok: false, message: "whatsapp automation install requires --binary <normalized-absolute-reviewed-wacli-file>" };
938
+ return { ok: true, value: { command: "whatsapp-automation-install", ...(binary === undefined ? {} : { binary }), json: parsed.booleans.has("--json") } };
939
+ }
926
940
  if (raw[1] !== "export-message-like-me") {
927
941
  return {
928
942
  ok: false,
@@ -966,11 +980,10 @@ export function parseGhostgetArguments(raw: readonly string[]): ParseGhostgetRes
966
980
  if (isFailure(parsed)) return parsed;
967
981
  const binary = parsed.values["--binary"];
968
982
  if (
969
- binary === undefined
970
- || !isAbsolute(binary)
983
+ binary !== undefined && (!isAbsolute(binary)
971
984
  || resolve(binary) !== binary
972
985
  || Buffer.byteLength(binary, "utf8") > 4_096
973
- || /[\0\r\n]/u.test(binary)
986
+ || /[\0\r\n]/u.test(binary))
974
987
  ) {
975
988
  return {
976
989
  ok: false,
@@ -982,7 +995,7 @@ export function parseGhostgetArguments(raw: readonly string[]): ParseGhostgetRes
982
995
  ok: true,
983
996
  value: {
984
997
  command: "imessage-transport-install",
985
- binary,
998
+ ...(binary === undefined ? {} : { binary }),
986
999
  json: parsed.booleans.has("--json"),
987
1000
  },
988
1001
  };
@@ -231,6 +231,132 @@
231
231
  "timeoutMs": 30000,
232
232
  "maxOutputBytes": 1048576
233
233
  }
234
+ },
235
+ "messaging.automation.read": {
236
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
237
+ "risk": "R1",
238
+ "sideEffect": "none",
239
+ "idempotency": "none",
240
+ "dedupeWindowMs": 0,
241
+ "input": {
242
+ "properties": {},
243
+ "required": []
244
+ },
245
+ "localCli": {
246
+ "surface": "imessage",
247
+ "action": "messaging.automation.read",
248
+ "contractVersion": 1,
249
+ "timeoutMs": 30000,
250
+ "maxOutputBytes": 8388608
251
+ }
252
+ },
253
+ "messaging.automation.send.text": {
254
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
255
+ "risk": "R3",
256
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
257
+ "idempotency": "local-at-most-once",
258
+ "dedupeWindowMs": 86400000,
259
+ "input": {
260
+ "properties": {},
261
+ "required": []
262
+ },
263
+ "localCli": {
264
+ "surface": "imessage",
265
+ "action": "messaging.automation.send.text",
266
+ "contractVersion": 1,
267
+ "timeoutMs": 30000,
268
+ "maxOutputBytes": 8388608
269
+ }
270
+ },
271
+ "messaging.automation.send.attachment": {
272
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
273
+ "risk": "R3",
274
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
275
+ "idempotency": "local-at-most-once",
276
+ "dedupeWindowMs": 86400000,
277
+ "input": {
278
+ "properties": {},
279
+ "required": []
280
+ },
281
+ "localCli": {
282
+ "surface": "imessage",
283
+ "action": "messaging.automation.send.attachment",
284
+ "contractVersion": 1,
285
+ "timeoutMs": 30000,
286
+ "maxOutputBytes": 8388608
287
+ }
288
+ },
289
+ "messaging.automation.send.reaction": {
290
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
291
+ "risk": "R3",
292
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
293
+ "idempotency": "local-at-most-once",
294
+ "dedupeWindowMs": 86400000,
295
+ "input": {
296
+ "properties": {},
297
+ "required": []
298
+ },
299
+ "localCli": {
300
+ "surface": "imessage",
301
+ "action": "messaging.automation.send.reaction",
302
+ "contractVersion": 1,
303
+ "timeoutMs": 30000,
304
+ "maxOutputBytes": 8388608
305
+ }
306
+ },
307
+ "messaging.automation.send.sticker": {
308
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
309
+ "risk": "R3",
310
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
311
+ "idempotency": "local-at-most-once",
312
+ "dedupeWindowMs": 86400000,
313
+ "input": {
314
+ "properties": {},
315
+ "required": []
316
+ },
317
+ "localCli": {
318
+ "surface": "imessage",
319
+ "action": "messaging.automation.send.sticker",
320
+ "contractVersion": 1,
321
+ "timeoutMs": 30000,
322
+ "maxOutputBytes": 8388608
323
+ }
324
+ },
325
+ "messaging.automation.send.link": {
326
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
327
+ "risk": "R3",
328
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
329
+ "idempotency": "local-at-most-once",
330
+ "dedupeWindowMs": 86400000,
331
+ "input": {
332
+ "properties": {},
333
+ "required": []
334
+ },
335
+ "localCli": {
336
+ "surface": "imessage",
337
+ "action": "messaging.automation.send.link",
338
+ "contractVersion": 1,
339
+ "timeoutMs": 30000,
340
+ "maxOutputBytes": 8388608
341
+ }
342
+ },
343
+ "messaging.automation.send.poll": {
344
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
345
+ "risk": "R3",
346
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
347
+ "idempotency": "local-at-most-once",
348
+ "dedupeWindowMs": 86400000,
349
+ "input": {
350
+ "properties": {},
351
+ "required": []
352
+ },
353
+ "localCli": {
354
+ "surface": "imessage",
355
+ "action": "messaging.automation.send.poll",
356
+ "contractVersion": 1,
357
+ "timeoutMs": 30000,
358
+ "maxOutputBytes": 8388608
359
+ }
234
360
  }
235
361
  }
236
362
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 4,
3
3
  "id": "linkedin-web",
4
- "version": "1.23.0",
4
+ "version": "1.36.2",
5
5
  "displayName": "LinkedIn (Authenticated Web API)",
6
6
  "surfaceId": "linkedin",
7
7
  "origins": [
@@ -395,6 +395,150 @@
395
395
  "timeoutMs": 600000,
396
396
  "maxOutputBytes": 8388608
397
397
  }
398
+ },
399
+ "messaging.automation.read": {
400
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
401
+ "risk": "R1",
402
+ "sideEffect": "none",
403
+ "idempotency": "none",
404
+ "dedupeWindowMs": 0,
405
+ "input": {
406
+ "properties": {},
407
+ "required": []
408
+ },
409
+ "webSession": {
410
+ "site": "whatsapp",
411
+ "action": "messaging.automation.read",
412
+ "contractVersion": 1,
413
+ "timeoutMs": 30000,
414
+ "maxOutputBytes": 8388608
415
+ }
416
+ },
417
+ "messaging.automation.sync": {
418
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
419
+ "risk": "R2",
420
+ "sideEffect": "Maintain an explicitly started linked-device sync session; may emit protocol acknowledgements.",
421
+ "idempotency": "local-at-most-once",
422
+ "dedupeWindowMs": 86400000,
423
+ "input": {
424
+ "properties": {},
425
+ "required": []
426
+ },
427
+ "webSession": {
428
+ "site": "whatsapp",
429
+ "action": "messaging.automation.sync",
430
+ "contractVersion": 1,
431
+ "timeoutMs": 30000,
432
+ "maxOutputBytes": 8388608
433
+ }
434
+ },
435
+ "messaging.automation.send.text": {
436
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
437
+ "risk": "R3",
438
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
439
+ "idempotency": "local-at-most-once",
440
+ "dedupeWindowMs": 86400000,
441
+ "input": {
442
+ "properties": {},
443
+ "required": []
444
+ },
445
+ "webSession": {
446
+ "site": "whatsapp",
447
+ "action": "messaging.automation.send.text",
448
+ "contractVersion": 1,
449
+ "timeoutMs": 30000,
450
+ "maxOutputBytes": 8388608
451
+ }
452
+ },
453
+ "messaging.automation.send.attachment": {
454
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
455
+ "risk": "R3",
456
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
457
+ "idempotency": "local-at-most-once",
458
+ "dedupeWindowMs": 86400000,
459
+ "input": {
460
+ "properties": {},
461
+ "required": []
462
+ },
463
+ "webSession": {
464
+ "site": "whatsapp",
465
+ "action": "messaging.automation.send.attachment",
466
+ "contractVersion": 1,
467
+ "timeoutMs": 30000,
468
+ "maxOutputBytes": 8388608
469
+ }
470
+ },
471
+ "messaging.automation.send.reaction": {
472
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
473
+ "risk": "R3",
474
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
475
+ "idempotency": "local-at-most-once",
476
+ "dedupeWindowMs": 86400000,
477
+ "input": {
478
+ "properties": {},
479
+ "required": []
480
+ },
481
+ "webSession": {
482
+ "site": "whatsapp",
483
+ "action": "messaging.automation.send.reaction",
484
+ "contractVersion": 1,
485
+ "timeoutMs": 30000,
486
+ "maxOutputBytes": 8388608
487
+ }
488
+ },
489
+ "messaging.automation.send.sticker": {
490
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
491
+ "risk": "R3",
492
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
493
+ "idempotency": "local-at-most-once",
494
+ "dedupeWindowMs": 86400000,
495
+ "input": {
496
+ "properties": {},
497
+ "required": []
498
+ },
499
+ "webSession": {
500
+ "site": "whatsapp",
501
+ "action": "messaging.automation.send.sticker",
502
+ "contractVersion": 1,
503
+ "timeoutMs": 30000,
504
+ "maxOutputBytes": 8388608
505
+ }
506
+ },
507
+ "messaging.automation.send.link": {
508
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
509
+ "risk": "R3",
510
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
511
+ "idempotency": "local-at-most-once",
512
+ "dedupeWindowMs": 86400000,
513
+ "input": {
514
+ "properties": {},
515
+ "required": []
516
+ },
517
+ "webSession": {
518
+ "site": "whatsapp",
519
+ "action": "messaging.automation.send.link",
520
+ "contractVersion": 1,
521
+ "timeoutMs": 30000,
522
+ "maxOutputBytes": 8388608
523
+ }
524
+ },
525
+ "messaging.automation.send.poll": {
526
+ "description": "Scoped owner automation permission; use ghostget messaging automation serve --stdio. Generic invocation is unavailable.",
527
+ "risk": "R3",
528
+ "sideEffect": "Submit one exact action through an enrolled conversation and an unexpired bounded owner grant; delivery remains unknown.",
529
+ "idempotency": "local-at-most-once",
530
+ "dedupeWindowMs": 86400000,
531
+ "input": {
532
+ "properties": {},
533
+ "required": []
534
+ },
535
+ "webSession": {
536
+ "site": "whatsapp",
537
+ "action": "messaging.automation.send.poll",
538
+ "contractVersion": 1,
539
+ "timeoutMs": 30000,
540
+ "maxOutputBytes": 8388608
541
+ }
398
542
  }
399
543
  }
400
544
  }