feat(linkedin): add Sales Navigator messaging commands#1632
Closed
hanzili wants to merge 11 commits into
Closed
Conversation
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>
3c5d22b to
167cd33
Compare
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>
Contributor
Author
|
Superseded by consolidated canonical PR #1647, which includes the messaging commands, Sales Navigator commands, improved connect verification, and sent-invitations. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up fixes and Sales Navigator messaging additions for the LinkedIn commands.
linkedin salesnav-searchfor Sales Navigator lead search.linkedin salesnav-messagefor Sales Navigator InMail API validation and explicit-send delivery.linkedin salesnav-inbox, backed bysalesApiMessagingThreads, with API pagination across the Sales Navigator inbox.linkedin salesnav-thread, backed bysalesApiMessagingThreads/<thread>, for full message history by thread id, Sales Navigator inbox URL, lead URL, recipient urn, or exact recipient name.%28and%29to avoid HTTP 400.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.jsnpm run buildnpm run check:silent-column-dropnpm run check:typed-error-lintnpm run typecheckLive Sales Navigator smoke tests passed against a logged-in session:
node dist/src/main.js linkedin salesnav-inbox --limit 3 -f jsonnode dist/src/main.js linkedin salesnav-thread 'Rachael Stolberg' --limit 10 -f json🤖 Generated with Hermes / Claude Code orchestration