# linkedincli Full LinkedIn platform management from your terminal. 43 commands for profiles, posts, messaging, connections, search, feed, engagement, and more — powered by cookie session auth. Works as a **CLI** and an **MCP server** (for Claude Code, Cursor, Windsurf, and other AI agents). ## Install ```bash # Install globally npm install -g @bcharleson/linkedincli # This installs the `linkedin` command: linkedin --help # Or run without installing npx @bcharleson/linkedincli --help ``` > **Note:** The npm package is `@bcharleson/linkedincli` but the CLI command is just **`linkedin`**. ## Local harness only A **live LinkedIn session** (cookies, Chrome cookie import, Voyager API calls) must run on a **local harness only** — your own computer, such as a laptop or Mac mini. Do **not** run a live session from Grok Bot, a cloud VM, or any remote runner. Safe use from those environments is limited to: - installing the `linkedin` CLI binary, or - calling an MCP server that is already running on the local harness Cookies and Voyager calls never leave the local machine. Do not put `li_at` / `JSESSIONID` in a cloud or Grok Bot environment. The opt-in `--from-chrome` and `LINKEDIN_HTTP=curl-impersonate` paths are **local-only**: they read a Chrome profile and/or `curl_chrome123` on that same machine. ## Sessions keep getting killed? Default Node `fetch` has a TLS/JA3 fingerprint that is not Chrome. LinkedIn often detects that and **invalidates `li_at` on the first Voyager call** (issue [#1](https://github.com/bcharleson/linkedincli/issues/1)). Manual `li_at` + `JSESSIONID` paste still works for some accounts, but is the fragile path. Two **opt-in, local-only** workarounds (default transport remains Node `fetch`): 1. **`LINKEDIN_HTTP=curl-impersonate`** — on the local harness, shell out to `curl_chrome123` so the TLS ClientHello matches Chrome. 2. **`--from-chrome` / `LINKEDIN_FROM_CHROME=1`** — on the local harness, read the full `linkedin.com` cookie jar from a local Chrome profile (not just the two auth tokens). Use them together on the local machine when possible. ### Install curl-impersonate (local harness) Install `curl_chrome123` on the same local machine that holds the LinkedIn session. The transport looks for it on `PATH` (override with `LINKEDIN_CURL_IMPERSONATE_BIN`). ```bash # Nix nix profile install nixpkgs#curl-impersonate-chrome # Homebrew brew install curl-impersonate # Confirm the binary exists curl_chrome123 --version export LINKEDIN_HTTP=curl-impersonate ``` If the binary is named differently on your platform, point at it: ```bash export LINKEDIN_CURL_IMPERSONATE_BIN=/usr/local/bin/curl_chrome123 export LINKEDIN_HTTP=curl-impersonate ``` ## Quick Start ### Option A — Read cookies from Chrome (local macOS/Linux only) If you are already logged into LinkedIn in Chrome **on this machine**, the CLI can decrypt cookies from the local profile. This sends the full cookie jar (not just `li_at` + `JSESSIONID`), which matches a real browser more closely. Local-only — do not point this at a remote Chrome profile or run it from a cloud agent. ```bash # macOS will prompt to unlock Keychain ("Chrome Safe Storage") the first time. # Requires the `sqlite3` CLI (preinstalled on macOS; `sudo apt install sqlite3` on Linux). linkedin --from-chrome profile me --pretty # Or persist the two auth tokens into ~/.linkedin-cli/config.json linkedin login --from-chrome # Non-default Chrome profile linkedin --from-chrome --chrome-profile "Profile 1" status --verify ``` Environment equivalents: `LINKEDIN_FROM_CHROME=1`, `LINKEDIN_CHROME_PROFILE=Default`. Optional: `LINKEDIN_CHROME_USER_DATA_DIR` to point at a custom user-data directory (Chrome/Chromium). Windows is not supported for `--from-chrome` (Chrome 127+ App-Bound Encryption). Cookie values are never printed to stdout/stderr. ### Option B — Paste cookies manually Open LinkedIn in your browser → DevTools (`F12`) → Application → Cookies → `linkedin.com` Copy these two values: - **`li_at`** — your session token (long string starting with `AQED...`) - **`JSESSIONID`** — your session ID (starts with `ajax:`) ```bash linkedin login # Paste your li_at and JSESSIONID when prompted ``` Or non-interactively: ```bash linkedin login --li-at "AQEDxxxxxxx" --jsessionid "ajax:1234567890" ``` Manual paste + default Node fetch may still get the session killed. Prefer Option A plus `LINKEDIN_HTTP=curl-impersonate` on the local harness. ### Use it ```bash # View your profile linkedin profile me --pretty # Create a post linkedin posts create --text "Hello LinkedIn! Posted from my terminal." # Search for people linkedin search people --keywords "software engineer" --network F --pretty # Check your messages linkedin messaging conversations --pretty # React to a post linkedin engage react 7123456789 --type LIKE ``` ## All Commands ### Profile (9 commands) ```bash linkedin profile me # Your own profile linkedin profile view # View any profile linkedin profile contact-info # Email, phone, websites linkedin profile skills # List skills linkedin profile network # Connections, followers, distance linkedin profile badges # Premium, influencer, etc. linkedin profile privacy # Privacy settings linkedin profile posts # Recent posts by a user linkedin profile disconnect # Remove a connection ``` ### Posts (3 commands) ```bash linkedin posts create --text "My post" # Text post linkedin posts create --text "With image" --image ./pic.jpg # Image post linkedin posts create --text "Inner circle" --visibility connections linkedin posts edit --text "Updated text" # Edit a post linkedin posts delete # Delete a post ``` ### Feed (3 commands) ```bash linkedin feed view # Your feed (chronological) linkedin feed view --count 50 # More items linkedin feed user # Someone's activity linkedin feed company # Company updates ``` ### Engagement (5 commands) ```bash linkedin engage react --type LIKE # Like linkedin engage react --type PRAISE # Celebrate linkedin engage react --type EMPATHY # Love linkedin engage react --type INTEREST # Insightful linkedin engage react --type ENTERTAINMENT # Funny linkedin engage react --type APPRECIATION # Support linkedin engage comment --text "Great post!" linkedin engage comments-list linkedin engage reactions linkedin engage share --text "Worth reading" ``` ### Connections (7 commands) ```bash linkedin connections send # Send request linkedin connections send -m "Let's connect!" # With message linkedin connections received # Pending received linkedin connections sent # Pending sent linkedin connections accept --secret # Accept linkedin connections reject --secret # Reject linkedin connections withdraw # Withdraw sent linkedin connections remove # Unfriend ``` ### Messaging (6 commands) ```bash linkedin messaging conversations # All conversations linkedin messaging conversation-with # With specific person linkedin messaging messages # Read messages linkedin messaging send -t "Hello!" # Reply linkedin messaging send-new -r , -t "Hi!" # New conversation linkedin messaging mark-read # Mark as read ``` ### Search (4 commands) ```bash linkedin search people --keywords "CTO" --network F # 1st connections linkedin search people --keywords "engineer" --company 1035 # At a company linkedin search people --title "VP Sales" --geo 103644278 # By region linkedin search companies --keywords "AI startups" linkedin search jobs --keywords "engineer" --remote --experience 4 # search posts is unavailable (LinkedIn CONTENT SRP). Use profile posts for a known author: linkedin profile posts ACoAABxxxxxxx --limit 20 ``` ### Companies (3 commands) ```bash linkedin companies view # Company info linkedin companies follow # Follow linkedin companies unfollow # Unfollow ``` ### Jobs (2 commands) ```bash linkedin jobs view # Job details linkedin jobs skills # Skill match insights ``` ### Analytics (1 command) ```bash linkedin analytics profile-views # Who viewed your profile ``` ## Global Options Every command supports these flags: | Flag | Description | |------|-------------| | `--li-at ` | Override li_at cookie | | `--jsessionid ` | Override JSESSIONID cookie | | `--from-chrome` | Read cookies from a Chrome profile on this local machine | | `--chrome-profile ` | Chrome profile directory (default: `Default`) | | `--output pretty` | Pretty-printed JSON | | `--pretty` | Shorthand for `--output pretty` | | `--quiet` | No output, exit codes only | | `--fields ` | Comma-separated fields to include | ## Environment Variables ```bash export LINKEDIN_LI_AT="your_li_at_cookie" export LINKEDIN_JSESSIONID="your_jsessionid_cookie" # Opt-in: avoid Node fetch TLS fingerprint (requires curl_chrome123) export LINKEDIN_HTTP=curl-impersonate # export LINKEDIN_CURL_IMPERSONATE_BIN=/path/to/curl_chrome123 # Opt-in: read the full LinkedIn cookie jar from Chrome export LINKEDIN_FROM_CHROME=1 # export LINKEDIN_CHROME_PROFILE="Profile 1" ``` Auth resolution order: `--from-chrome` / `LINKEDIN_FROM_CHROME` → `--li-at`/`--jsessionid` flags → env vars → `~/.linkedin-cli/config.json` `linkedin status --verify` now classifies LinkedIn 3xx login/challenge redirects as `session_valid: false` with an auth message instead of a generic network error. ## MCP Server (AI Agents) All 43 commands are available as MCP tools. The MCP process that talks to LinkedIn must run on the **local harness**. Cloud agents and Grok Bot may install the CLI binary or call this local MCP — they must not hold cookies or originate Voyager calls. ### Local Claude Code / Cursor / Windsurf Add to the MCP config **on the local machine**: ```json { "mcpServers": { "linkedin": { "command": "linkedin", "args": ["mcp"], "env": { "LINKEDIN_LI_AT": "your_li_at_cookie", "LINKEDIN_JSESSIONID": "your_jsessionid_cookie", "LINKEDIN_HTTP": "curl-impersonate" } } } } ``` Or if using `npx`: ```json { "mcpServers": { "linkedin": { "command": "npx", "args": ["-y", "@bcharleson/linkedincli", "mcp"] } } } ``` Then a **local** AI agent can manage LinkedIn through that MCP process. Remote/cloud agents should call this local server rather than starting their own session. ## Cookie Expiration LinkedIn `li_at` cookies expire periodically (usually every few weeks). They can also be invalidated immediately when the client TLS fingerprint does not look like Chrome. When your session expires: ```bash linkedin status --verify # Check if session is valid linkedin login --from-chrome # or: linkedin login ``` ## Search posts limitation `linkedin search posts` is **not available**. LinkedIn's CONTENT resultType on `voyagerSearchDashClusters.b0928897b71bd00a5a7291755dcd64f0` still returns HTTP 200 but `included[]` is only a `FeedbackCard` (issue [#2](https://github.com/bcharleson/linkedincli/issues/2)). People and company search on the same queryId still work. A replacement content-search `queryId` has not been verified from public/in-repo sources, so this CLI does not invent one. For posts **by a specific member**, use `profile posts` (`identity/profileUpdatesV2`): ```bash linkedin profile posts --limit 20 ``` ## Disclaimer This tool uses LinkedIn's internal Voyager API via cookie session authentication. It is not affiliated with or endorsed by LinkedIn. Use responsibly and in compliance with LinkedIn's terms of service. The authors are not responsible for any account restrictions that may result from automated usage. ## License MIT