@hraness/ghostget 0.18.2 → 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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ Historical entries retain their original delivery coordinates.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.18.3
11
+
12
+ - Apply the shared Lantern material to the homepage: luminous grid glass behind
13
+ the hero, a blurred header, warm open questions, and opaque reading surfaces.
14
+ Preserve clean marks, ordinary documentation, and the inert native preview.
15
+
16
+ - Click the unique reviewed Contact info control when the page-bound
17
+ NavigateToScreen extract is `absent` after adapter 1.36.2. Operator
18
+ smoke on 2026-09-12 was green for Email on tessbloch through DIRECT
19
+ transport that clicked after an absent extract, using the reviewed
20
+ ProfileContactDetailsOverlay `sduiid` and headed clientArguments
21
+ (bound vanity, optional givenName/familyName from profile HTML,
22
+ `isVanityNameResolved`, nested requestMetadata
23
+ states/screenId/knownTemplates, `isModal` true). Official
24
+ `contacts.read` still fell through that absent extract to GraphQL
25
+ or the vanity overlay GET (broken 500/HTML-shell path). The
26
+ contained path now uses the same click and modal snapshot as a
27
+ present action. It still does not mint a navigation POST or track
28
+ headers. Zero or two-plus controls and a missing or ambiguous
29
+ modal still fail closed. GraphQL 403 and vanity HTML-shell
30
+ honesty stay unused once that click is selected. Soft-labels
31
+ stay. Self, non-first-degree, and contradictory distances still
32
+ fail closed, and no email is invented. Adapter bundle 1.36.3.
33
+ This is the seventeenth live Contact-info drift after the 1.36.2
34
+ overlay pathname bind. Cloud has no signed-in LinkedIn session;
35
+ do not treat this landing as live green. Operator smoke:
36
+
37
+ ```sh
38
+ printf '%s' '{"profile_url":"https://www.linkedin.com/in/tessbloch/"}' \
39
+ | ghostget invoke linkedin-web contacts.read --input - --auth linkedin-main --json
40
+ ```
41
+
10
42
  - Keep the unique reviewed Contact info click bound after adapter
11
43
  1.36.1 when contained Chrome follows the live vanity overlay path.
12
44
  Operator smoke on 2026-09-12 clicked that unique control, then
package/README.md CHANGED
@@ -21,7 +21,7 @@ Bring the model, planner, and tool loop you prefer. Ghostget supplies precise we
21
21
  capabilities with local custody and explicit evidence. Its native macOS control
22
22
  panel manages accounts, operation permissions, human approvals, editable OpenAPI
23
23
  interfaces, and a searchable local web request log. See the
24
- [control panel guide](docs/control-panel.md) and [native source build](https://github.com/hraness/ghostget/blob/v0.18.2/desktop/README.md).
24
+ [control panel guide](docs/control-panel.md) and [native source build](https://github.com/hraness/ghostget/blob/v0.18.3/desktop/README.md).
25
25
 
26
26
  The separate public web gateway admits exact HTTPS retrieval URLs under human
27
27
  domain and path rules. Use `ghostget web request <url>` with the app open, and
@@ -51,7 +51,7 @@ ghostget plugin list
51
51
 
52
52
  ## Built-in provider catalog
53
53
 
54
- This v0.18.2 source tree supports executable actions for 20 services: Beeper,
54
+ This v0.18.3 source tree supports executable actions for 20 services: Beeper,
55
55
  Bluesky, ClasificadosOnline, Facebook, Facebook Groups, Facebook Marketplace,
56
56
  GitHub, Gmail, Hacker News, Instagram, iMessage, LinkedIn, Reddit, Substack,
57
57
  Threads, TikTok, Twitch, WhatsApp, X, and YouTube.
@@ -116,7 +116,7 @@ owns the narrow capability boundary that can sit beneath them.
116
116
  Wrench is now Ghostget. The package name is `@hraness/ghostget`, and the CLI
117
117
  command is `ghostget`. Read the [migration guide](docs/ghostget-migration.md)
118
118
  before updating an existing installation. After its optional npm mirror is
119
- verified public, `@hraness/ghostget@0.18.2` is also available from the registry.
119
+ verified public, `@hraness/ghostget@0.18.3` is also available from the registry.
120
120
 
121
121
  This README describes the package version in this source tree. Its versioned
122
122
  GitHub archive and Agent Skill become a supported public release after the
@@ -127,9 +127,9 @@ The optional npm mirror can follow later without delaying canonical delivery.
127
127
  Install the single Ghostget Agent Skill with either runner:
128
128
 
129
129
  ```sh
130
- npx skills add hraness/ghostget#v0.18.2
130
+ npx skills add hraness/ghostget#v0.18.3
131
131
  # or
132
- bunx skills add hraness/ghostget#v0.18.2
132
+ bunx skills add hraness/ghostget#v0.18.3
133
133
  ```
134
134
 
135
135
  The skill teaches Codex, Claude Code, Cursor, and other compatible coding
@@ -139,7 +139,7 @@ install the CLI if it is missing. Start a new agent session after installation.
139
139
  After the matching immutable Release exists, install this exact canonical archive:
140
140
 
141
141
  ```sh
142
- bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.2/hraness-ghostget-0.18.2.tgz
142
+ bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.3/hraness-ghostget-0.18.3.tgz
143
143
  ghostget adapter sync-bundled --json
144
144
  ghostget doctor
145
145
  ```
@@ -167,7 +167,7 @@ For that same released coordinate, install Ghostget in an agent or application
167
167
  that owns its own model, planning, tool loop, approvals, and interface:
168
168
 
169
169
  ```sh
170
- bun add https://github.com/hraness/ghostget/releases/download/v0.18.2/hraness-ghostget-0.18.2.tgz
170
+ bun add https://github.com/hraness/ghostget/releases/download/v0.18.3/hraness-ghostget-0.18.3.tgz
171
171
  ```
172
172
 
173
173
  ```ts
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  GHOSTGET_VERSION
4
- } from "./index-hfbygww8.js";
4
+ } from "./index-cnbz0gws.js";
5
5
  import {
6
6
  canonicalJson,
7
7
  sha256
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-26yq8q16.js";
5
5
  import {
6
6
  GHOSTGET_VERSION
7
- } from "./index-hfbygww8.js";
7
+ } from "./index-cnbz0gws.js";
8
8
  import {
9
9
  canonicalJson,
10
10
  sha256
@@ -1,5 +1,5 @@
1
1
  // @bun
2
2
  // src/version.ts
3
- var GHOSTGET_VERSION = "0.18.2";
3
+ var GHOSTGET_VERSION = "0.18.3";
4
4
 
5
5
  export { GHOSTGET_VERSION };
@@ -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.2/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.2/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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/ghostget",
3
- "version": "0.18.2",
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",
@@ -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.2/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.2 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.2/hraness-ghostget-0.18.2.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.2` 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.2/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.2/hraness-ghostget-0.18.2.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
@@ -256,31 +256,31 @@ fetch. Long SDUI strings used as field labels are skipped instead of aborting
256
256
  the profile-stage walk. Otherwise it first inspects the page's exact
257
257
  Contact-info NavigateToScreen action. A reviewed action still admits only
258
258
  the ProfileContactDetailsOverlay URL (`sduiid` equal to that overlay
259
- `screenId`). Adapter 1.36.2 then clicks the unique accessible name
259
+ `screenId`). Adapter 1.36.3 then clicks the unique accessible name
260
260
  `Contact info` on the already-loaded 1st-degree profile and projects Email
261
261
  from that modal DOM, including when that control's `href` matches
262
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
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
277
  `/flagship-web/rsc-action/actions/navigation` overlay GET URLs stay rejected
278
278
  because live dormant sessions return HTTP 500 `application/octet-stream`.
279
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.
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.
284
284
 
285
285
  The projection returns `email` when LinkedIn shows it, plus any of the vanity
286
286
  profile link, connected-since date, phone numbers, websites, and birthday.
@@ -474,9 +474,15 @@ GET. Operator smoke after that click fail-closed because contained
474
474
  Chrome left `/in/tessbloch/` for
475
475
  `/in/tessbloch/overlay/contact-info/`. Adapter 1.36.2 treats that
476
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.
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.
480
486
 
481
487
  ```sh
482
488
  printf '%s' '{"profile_url":"https://www.linkedin.com/in/tessbloch/"}' \
@@ -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.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.
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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 4,
3
3
  "id": "linkedin-web",
4
- "version": "1.36.2",
4
+ "version": "1.36.3",
5
5
  "displayName": "LinkedIn (Authenticated Web API)",
6
6
  "surfaceId": "linkedin",
7
7
  "origins": [
@@ -96,7 +96,7 @@ export type BeeperContactInteractionExportReceipt = Readonly<{
96
96
  implementation: Readonly<{
97
97
  producer: Readonly<{
98
98
  package: "@hraness/ghostget";
99
- version: "0.18.2";
99
+ version: "0.18.3";
100
100
  }>;
101
101
  officialCli: Readonly<{
102
102
  implementation: "github.com/beeper/cli";
@@ -4,18 +4,17 @@ import { readAttempt, type ReadEffectFailure, withReadResource } from "../read-e
4
4
  import type { WebSessionExecution } from "../web-session-execution";
5
5
  import { LinkedInContactPlatform } from "./linkedin-contact-platform";
6
6
  import {
7
- isLinkedInContactGraphqlUnavailable,
8
7
  linkedInContactDiagnostic,
9
8
  linkedInContactReadFailure,
10
9
  type LinkedInContactStage,
11
10
  } from "./linkedin-contact-failure";
12
11
  import {
12
+ buildLinkedInAbsentContactNavigationAction,
13
13
  extractLinkedInContactNavigationAction,
14
14
  linkedInContactNavigationActionError,
15
15
  projectLinkedInContactInfo,
16
16
  projectLinkedInEmbeddedContactFields,
17
17
  projectLinkedInProfileContactBinding,
18
- resolveLinkedInProfileContactInfoQueryId,
19
18
  type LinkedInContactInfoTarget,
20
19
  } from "./linkedin-web-contact";
21
20
 
@@ -65,25 +64,22 @@ export function linkedInContactReadProgram(
65
64
  sduiid: navigation.action.sduiid,
66
65
  clientArguments: navigation.action.clientArguments,
67
66
  });
68
- } else if (navigation.kind !== "absent") {
67
+ } else if (navigation.kind === "absent") {
68
+ const fallback = yield* readAttempt(() => buildLinkedInAbsentContactNavigationAction({
69
+ profileHtml,
70
+ publicIdentifier: binding.vanity,
71
+ profileUrn: binding.profileUrn,
72
+ }));
73
+ contactPayload = yield* platform.contactNavigation(browser, {
74
+ profileUrl: target.url,
75
+ profileUrn: binding.profileUrn,
76
+ sduiid: fallback.sduiid,
77
+ clientArguments: fallback.clientArguments,
78
+ });
79
+ } else {
69
80
  yield* readAttempt(() => {
70
81
  throw linkedInContactNavigationActionError(navigation);
71
82
  });
72
- } else {
73
- const queryId = resolveLinkedInProfileContactInfoQueryId(profileHtml);
74
- const contactInput = {
75
- profileUrl: target.url,
76
- profileUrn: binding.profileUrn,
77
- ...(queryId === undefined ? {} : { queryId }),
78
- };
79
- contactPayload = queryId === undefined
80
- ? yield* platform.contactOverlay(browser, contactInput)
81
- : yield* platform.contactPayload(browser, contactInput).pipe(
82
- Effect.catchIf(
83
- (error) => isLinkedInContactGraphqlUnavailable(error),
84
- () => platform.contactOverlay(browser, contactInput),
85
- ),
86
- );
87
83
  }
88
84
  }
89
85
  stage = "projection";
@@ -1571,6 +1571,55 @@ export function linkedInContactNavigationActionError(
1571
1571
  );
1572
1572
  }
1573
1573
 
1574
+ export function extractLinkedInProfileContactNames(input: {
1575
+ readonly profileHtml: unknown;
1576
+ readonly publicIdentifier: string;
1577
+ }): {
1578
+ readonly givenName?: string;
1579
+ readonly familyName?: string;
1580
+ } {
1581
+ const publicIdentifier = linkedInPersonalProfilePublicIdentifier(input.publicIdentifier);
1582
+ const given = new Set<string>();
1583
+ const family = new Set<string>();
1584
+ for (const record of embeddedRecords(input.profileHtml)) {
1585
+ if (vanityFromRecord(record) !== publicIdentifier) continue;
1586
+ const givenName = optionalPersonName(record.givenName ?? record.firstName);
1587
+ const familyName = optionalPersonName(record.familyName ?? record.lastName);
1588
+ if (givenName !== null) given.add(givenName);
1589
+ if (familyName !== null) family.add(familyName);
1590
+ }
1591
+ const uniqueGiven = given.size === 1 ? [...given][0] : undefined;
1592
+ const uniqueFamily = family.size === 1 ? [...family][0] : undefined;
1593
+ return Object.freeze({
1594
+ ...(uniqueGiven === undefined ? {} : { givenName: uniqueGiven }),
1595
+ ...(uniqueFamily === undefined ? {} : { familyName: uniqueFamily }),
1596
+ });
1597
+ }
1598
+
1599
+ export function buildLinkedInAbsentContactNavigationAction(input: {
1600
+ readonly profileHtml: unknown;
1601
+ readonly publicIdentifier: string;
1602
+ readonly profileUrn: string;
1603
+ }): LinkedInContactNavigationAction {
1604
+ const publicIdentifier = linkedInPersonalProfilePublicIdentifier(input.publicIdentifier);
1605
+ profileUrn(input.profileUrn);
1606
+ const names = extractLinkedInProfileContactNames({
1607
+ profileHtml: input.profileHtml,
1608
+ publicIdentifier,
1609
+ });
1610
+ return Object.freeze({
1611
+ sduiid: LINKEDIN_CONTACT_DETAILS_OVERLAY_SCREEN_ID,
1612
+ clientArguments: canonicalContactNavigationClientArguments({
1613
+ payload: {
1614
+ vanityName: publicIdentifier,
1615
+ ...names,
1616
+ isVanityNameResolved: true,
1617
+ },
1618
+ }),
1619
+ isModal: true,
1620
+ });
1621
+ }
1622
+
1574
1623
  export function extractLinkedInContactNavigationAction(input: {
1575
1624
  readonly profileHtml: unknown;
1576
1625
  readonly publicIdentifier: string;
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Canonical immutable Ghostget package release identity. */
2
- export const GHOSTGET_VERSION = "0.18.2" as const;
2
+ export const GHOSTGET_VERSION = "0.18.3" as const;