Skip to content

feat(linkedin): add Sales Navigator messaging commands#1632

Closed
hanzili wants to merge 11 commits into
jackwener:mainfrom
hanzili:feat/linkedin-messaging-commands
Closed

feat(linkedin): add Sales Navigator messaging commands#1632
hanzili wants to merge 11 commits into
jackwener:mainfrom
hanzili:feat/linkedin-messaging-commands

Conversation

@hanzili
Copy link
Copy Markdown
Contributor

@hanzili hanzili commented May 17, 2026

Summary

Follow-up fixes and Sales Navigator messaging additions for the LinkedIn commands.

  • Add linkedin salesnav-search for Sales Navigator lead search.
  • Add linkedin salesnav-message for Sales Navigator InMail API validation and explicit-send delivery.
  • Add read-only linkedin salesnav-inbox, backed by salesApiMessagingThreads, with API pagination across the Sales Navigator inbox.
  • Add read-only linkedin salesnav-thread, backed by salesApiMessagingThreads/<thread>, for full message history by thread id, Sales Navigator inbox URL, lead URL, recipient urn, or exact recipient name.
  • Preserve the Sales Navigator Rest.li decoration gotcha: decoration parentheses are percent-encoded as %28 and %29 to avoid HTTP 400.
  • Keep the earlier LinkedIn messaging fixes in this branch: regular inbox pagination, thread-snapshot full-history pagination, sent-invitations cleanup, and stale page identity recovery.

Test plan

Local gates pass:

  • npm run test:adapter -- clis/linkedin/salesnav-inbox.test.js clis/linkedin/salesnav-thread.test.js clis/linkedin/salesnav-search.test.js clis/linkedin/salesnav-message.test.js
  • npm run build
  • npm run check:silent-column-drop
  • npm run check:typed-error-lint
  • npm run typecheck

Live Sales Navigator smoke tests passed against a logged-in session:

  • node dist/src/main.js linkedin salesnav-inbox --limit 3 -f json
  • node dist/src/main.js linkedin salesnav-thread 'Rachael Stolberg' --limit 10 -f json

🤖 Generated with Hermes / Claude Code orchestration

hanzili and others added 8 commits May 17, 2026 03:07
LinkedIn appends an "(Edited)" marker inside the DOM message body, so
the DOM-extracted copy of an edited message ("AI focused(Edited)") no
longer matched its Voyager-API copy ("AI focused"). The speaker+text
dedup key missed it, leaving a phantom duplicate that — lacking a
timestamp — sorted to the end and masqueraded as the newest message.

Strip a trailing "(edited)" marker during DOM extraction so the text
matches the API copy and dedupes correctly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
thread-snapshot returned only LinkedIn's most recent ~20-message batch
and presented it as the complete thread, so the "oldest" message was
usually a mid-conversation line rather than the real start.

LinkedIn lazy-loads older messages only on scroll in a full-height
viewport, which never happens in a headless automation window. Instead
of scrolling, replay the messengerMessages GraphQL query directly with
a deliveredAt anchor (countBefore:20), walking the oldest timestamp
backward until a page returns nothing. The conversationUrn is lifted,
already RestLi-encoded, from the recent request the page fires on load.

Verified end to end: a thread that previously returned 20 messages now
returns its full 89-message history, oldest-first from the real opener.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
sent-invitations emitted the person's name concatenated with their job
headline ("Kyle Chang Manager at C2C Premium Seafood"), an empty
profile_url on every row, a junk row from the Received/Sent nav tabs,
and a date field that could capture a stray month substring (the "may"
inside a surname).

Rewrite the extraction: card text is no longer whitespace-collapsed
before line splitting, so the name and headline stay separable; the
profile link is matched by href rather than requiring visible text (the
LinkedIn card link wraps only the avatar); rows are deduped by name with
a merge so the profile URL is filled from whichever card carries it; the
date is matched by a precise Sent/Invited relative-time pattern; and
only cards with a Withdraw affordance are emitted, dropping nav tabs.

Verified e2e: 10 clean rows with correct names, profile URLs and dates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hanzili hanzili force-pushed the feat/linkedin-messaging-commands branch from 3c5d22b to 167cd33 Compare May 17, 2026 07:08
hanzili and others added 3 commits May 17, 2026 12:49
thread-snapshot's older-history pagination used a hardcoded LinkedIn
persisted-query id. LinkedIn rotates these ids on redeploys, which would
silently cap snapshots at the recent ~20 messages until the id was
updated in source.

Discover the id at runtime instead: widen the viewport via CDP and wheel
the thread pane up so LinkedIn fires its own older-history request, then
read that request URL back from the Performance API and re-anchor it for
pagination. The hardcoded id remains only as a fallback. Also expose
historyComplete in the snapshot so a rejected older-history fetch is
visible rather than silently truncating.

Verified e2e: Eugene 34 messages / 2 pages, Bill 89 / 5 pages, both with
runtime discovery firing and historyComplete true.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…search

New OpenCLI linkedin command: searches LinkedIn Sales Navigator for
people leads by keyword and returns structured rows (name, title,
company, location, degree, profile_url).

Replays the salesApiLeadSearch API directly (csrf + restli headers,
start/count pagination) rather than scraping the UI. Sales Navigator
returns no /in/ vanity URL, so the profile URL is derived from the
obfuscated member token in the lead's fs_salesProfile entityUrn, which
linkedin.com/in/<token> resolves. Falls back to pastPositions when a
lead has no current position.

Verified e2e against a live Sales Navigator session; 4 unit tests added.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hanzili hanzili changed the title fix(linkedin): full pagination and output cleanup for messaging commands feat(linkedin): add Sales Navigator messaging commands May 18, 2026
@hanzili
Copy link
Copy Markdown
Contributor Author

hanzili commented May 18, 2026

Superseded by consolidated canonical PR #1647, which includes the messaging commands, Sales Navigator commands, improved connect verification, and sent-invitations.

@hanzili hanzili closed this May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant