| name | linkedin-toolkit |
| description | Triage your LinkedIn inbox and network end-to-end via the lnx CLI. Use when the user wants to: (1) refresh their LinkedIn pipeline (extract conversations + connections + invitations, classify recruiter pitches, research companies, score and rank, produce shortlist + reports), (2) fetch one-off data (a single profile, conversation thread, or invitations list), (3) star/unstar conversations, (4) send a plain-text message to an existing thread, (5) any individual step of the pipeline (Phase 1-5). Triggers on: 'refresh my LinkedIn data', 'rebuild my opportunities table', 'who's pitching me right now', 'rank LinkedIn offers by salary', 'find which roles are still open', 'analyze LinkedIn connections', 'export my LinkedIn inbox', 'check what companies are hiring me', 'send a LinkedIn message', 'star this conversation', 'lnx fetch X', 'lnx refresh'. |
linkedin-toolkit (lnx)
This skill lets the user triage their LinkedIn inbox + network end-to-end — recruiter pitches, connections, careers verification, ranking, reports — by talking to you in natural language. The deterministic plumbing is the lnx CLI; the LLM judgment work is sub-agents you dispatch.
First-run dialogue (do this when triggered without a clear ask)
If the user says something vague like "let's use linkedin-toolkit" or "help me with LinkedIn", do NOT immediately start fetching. Greet them and offer the menu. Use this template:
Hey! The LinkedIn toolkit can do a few things for you — pick one or describe what you're trying to accomplish:
Big workflow (15-30 min, full pipeline)
- 🔄 Refresh everything — extract inbox + network, classify every recruiter pitch, research each company, rank by money + prestige + open status. Produces a shortlist of opportunities to act on.
Quick lookups (seconds)
- 📥 Who's pitching me right now? — surface unread recruiter messages from the last N days
- 💰 Rank my offers by salary — re-score the existing dataset
- 🔍 Find which roles are still open — verify careers pages for companies that pitched me
- 👥 Show my useful contacts at notable companies — for cold-outbound pathways
- 📄 Fetch a specific profile (
<public-id> or LinkedIn URL)
- 📨 Look at a specific conversation (paste URN or recipient name)
Write actions (irreversible — I'll confirm before sending)
- ⭐ Star/unstar a conversation
- ✉️ Send a plain-text reply to an existing thread
Maintenance
- ⚙️ Check auth (
lnx auth test)
- 📊 Re-generate reports from existing data
What sounds useful?
Then once they pick, follow the relevant workflow below.
Setup check (run before the first command in a session)
Always verify the CLI is installed:
lnx --version
Expected: lnx 0.1.0 (or newer).
If lnx is not found, tell the user:
uv tool install --from git+https://github.com/vicnaum/linkedin-toolkit linkedin-toolkit
Then verify auth — but be smart about which mode the user is in:
- If you're being driven from Chrome MCP and there's a logged-in
linkedin.com tab in tabs_context_mcp → browser mode is fine, no creds needed. Skip lnx auth test.
- Otherwise → run
lnx auth test. If it fails, two paths:
- Ask the user to open
linkedin.com in a Chrome MCP tab and log in (cleanest).
- Walk them through a one-time DevTools paste:
lnx auth set --li-at "AQED..." --jsessionid 'ajax:...'.
The 5-phase pipeline (for "refresh my LinkedIn data")
| Phase | What | Driver |
|---|
| 1 | Extract — invitations, network, conversations + messages, starred | Python (lnx refresh --phase=1) |
| 2 | Classify recruiter pitches → opportunities | Sub-agents reading phase2/batch_*/prompt.md |
| 3 | Classify network connections → useful contacts | Sub-agents reading phase3/batch_*/prompt.md |
| 4 | Research companies + verify careers pages | Sub-agents (WebSearch + WebFetch + Chrome MCP fallback) |
| 5 | Dedupe, score, generate reports | Python (lnx pipeline score) |
Output layout (--output ./lnx-data/ by default):
lnx-data/
├── invitations.json
├── network.json
├── conversations.json (with messages inline; cleaned)
├── starred.json
├── profile_cache.json (managed by browser cacher; user-populated)
├── phase{2,3,4}/batch_NNN/{input,prompt,output}.{json,md}
├── phase{2,3,4}_merged.json
├── OPPORTUNITIES_TABLE.md
├── SHORTLIST.md
├── SALARY_TRENDS.md
├── COMPANIES.md
└── CONTACTS.md
Full-refresh workflow
lnx refresh --phase=1 --output=./lnx-data/
lnx refresh --phase=2 --output=./lnx-data/
lnx pipeline merge 2 --output=./lnx-data/
lnx refresh --phase=3 --output=./lnx-data/
lnx pipeline merge 3 --output=./lnx-data/
lnx refresh --phase=4 --output=./lnx-data/
lnx pipeline merge 4 --output=./lnx-data/
lnx refresh --phase=5 --output=./lnx-data/
Or run all phases in one go (Claude pauses at each agent-driven phase):
lnx refresh --output=./lnx-data/
Important during long phases: check in with the user at phase boundaries. Don't silently chew through 30 minutes of work. After phase 1, summarize what was extracted (counts) and confirm the user wants to proceed to phase 2.
Per-phase agent dispatch (Phases 2, 3, 4)
When the CLI prints PHASE N NEEDS AGENTS — M batches prepared:
-
List the prompts: ls ./lnx-data/phaseN/batch_*/prompt.md
-
For each batch, spawn a Task sub-agent (subagent_type: general-purpose) with this prompt template:
Read the prompt at <absolute-path-to-prompt.md>. It tells you which input file to read and which output file to write. Follow it exactly. Do NOT skip the validation step (schemas/<name>.schema.json). When done, write the output JSON and confirm completion.
-
Run agents in parallel where possible — one tool message with multiple Task calls (the agents are independent per batch).
-
After all agents finish, run lnx pipeline merge <N> --output=./lnx-data/ to consolidate.
-
Inspect phase{N}_merged.json for any obviously-wrong records before continuing.
If Claude is being driven autonomously and the user wants to be notified when each phase completes, suggest using ScheduleWakeup or a loop skill rather than blocking.
Quick-lookup workflows (no pipeline needed)
For all of these, write the output to ./lnx-data/ if the user is mid-pipeline; otherwise default to a tmp dir or stdout.
lnx fetch unread --with-messages --clean -o /tmp/unread.json
lnx fetch profile <public-id-or-LinkedIn-URL>
lnx fetch messages <conversation_urn>
lnx fetch network --since-ms <epoch_ms>
For "rank my offers by salary" / "re-generate reports" — re-run phase 5 only:
lnx refresh --phase=5 --output=./lnx-data/
Write actions (always confirm)
lnx star <conversation_urn>
lnx unstar <conversation_urn>
lnx send <conversation_urn> "Hi, ..."
All three prompt for [y/N] unless --yes is passed. Never pass --yes without explicit user authorization in chat.
For sending: always show the user the drafted text + recipient FIRST, get their explicit "yes send", THEN run the command. Don't chain "I'll draft and send" — split into two turns.
Browser-mode profile caching
Phase 3 (classify connections) needs profile_cache.json populated with profile data for each network member. Best done via the long-running JS cacher:
lnx browser cache_profiles
This prints the JS to stdout. Capture it and dispatch via mcp__claude-in-chrome__javascript_tool against a logged-in linkedin.com tab. The snippet:
- accepts a list of
publicIdentifiers + an optional existing slim cache
- fetches each profile with 0.6-1.0s pacing (jittered)
- flushes state every 50 profiles to handle interruption
- halts on rate-limit or 5+ consecutive 403s
For one-off browser fetches:
lnx browser fetch_profile
Paste into javascript_tool, then call await fetchLinkedInProfile('alice-doe-12345').
Common gotchas
- Auth feels like it works but no data comes back. §
Q-CSRF-EXACT — csrf-token header must equal JSESSIONID cookie verbatim, including the ajax: prefix.
- GraphQL endpoints return 200 but elements are empty. §
Q-DATA-DOUBLE-WRAP (2026-04+) — the real root sits at j.data.data.<key>, not j.data.<key>. The toolkit handles this; mention it if you're writing custom JS.
- Conversations endpoint returns URN refs instead of records. §
Q-CONVERSATIONS-NORMALIZED — records live in included[] filtered by $type == com.linkedin.messenger.Conversation.
- Rate limit (HTTP 429). Stop immediately. Don't retry. Wait ~5 minutes. Empirical safe rates documented in
references/endpoints.md § 13.
- Profile fetch returns 403. §
Q-PROFILE-403-PER-PROFILE — that single profile is restricted, not a session-wide problem. Skip and continue. 5+ consecutive 403s = treat as session issue and halt.
Reference
references/endpoints.md — full Voyager API spec, every named quirk (Q-CSRF-EXACT, Q-PAGINATION-TRAP, Q-NORMALIZED-RESOLVE, Q-VARIABLES-ENCODING, Q-FLUCTUATING-ROOT-KEY, Q-DATA-DOUBLE-WRAP, Q-CONVERSATIONS-NORMALIZED, Q-MAILBOX-COUNTS-RENAMED, ...) + cookbook of copy-paste JS/Python snippets
ARCHITECTURE.md — three-layer model, how to add a new endpoint or phase
lnx/prompts/*.md — sub-agent templates (read these to understand what each phase asks the LLM to do)
Honest limitations to share with the user
- HttpOnly
li_at — can't be read from page-context JS. Browser-mode workflows don't need to (browser attaches it automatically). Headless mode requires a one-time DevTools paste.
- Stealth pitches — recruiter-only outreach from companies with no public careers page can't be verified without engaging. Marked
stealth_or_no_site.
- Tier list curated —
lnx/config/tier_overrides.json reflects a Solidity / smart-contract engineer's market view as of 2026-04. The user should fork and edit if their domain differs.
- Full refresh ≠ free — agent-driven phases call sub-agents many times (typically 5-50 batches across phases 2-4). Token cost. If the user wants a quick sample, suggest
--phase=1,5 only or a small subset of conversations.