Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/linkedin-tool.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@plotday/twister": minor
---

Added: `AuthProvider.LinkedIn` and a built-in `LinkedIn` tool that exposes a
provider-agnostic surface for the LinkedIn Voyager messaging API. The tool's
implementation lives in the API worker so request signing, header spoofing,
rate limiting, and cookie management stay out of open-source connector code.
21 changes: 21 additions & 0 deletions connectors/linkedin-messaging/LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Plot Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 56 additions & 0 deletions connectors/linkedin-messaging/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# @plotday/connector-linkedin-messaging

LinkedIn direct messages and inbound connection requests, surfaced in your
Plot inbox.

## How it works

LinkedIn does not expose a public OAuth scope for personal messaging. This
connector — like Beeper, Unipile, and Kondo — reuses your authenticated
LinkedIn session: when you "Connect LinkedIn" in Plot, the desktop or
mobile app opens an in-app webview pointed at `linkedin.com/login`. Once
you log in, Plot captures the `li_at` session cookie (and your browser's
User-Agent) and stores them encrypted at rest. All subsequent API calls go
through Plot's privileged server-side LinkedIn tool, which keeps the
Voyager wire format, request signing, and rate limiting hidden from this
open-source connector.

## What's synced

- **1:1 direct messages.** Each conversation becomes a Plot thread; each
message becomes a note.
- **Group conversations.** Same model — the thread's participants
populate from the conversation.
- **Inbound connection requests with notes.** Surfaces as a separate
link type so you can reply or accept directly from Plot.

Replies you send from Plot are written back to LinkedIn; marking a
conversation read in Plot marks it read on LinkedIn.

## Sync cadence

LinkedIn does not offer webhooks for personal messaging, so this connector
polls with an adaptive cadence:

- **5 minutes** when you (or LinkedIn) were active in the last hour.
- **15 minutes** when activity is within the last six hours.
- **30 minutes** when the inbox is idle.

The cadence resets to "fresh" each time you send a reply through Plot.

## Privacy / safety

Your `li_at` cookie grants full access to your LinkedIn account. Plot
treats it like any other access token:

- Encrypted at rest, never logged.
- Never passed into this open-source connector — only Plot's server can
read it.
- One-tap disconnect in Plot also stops all sync immediately.

LinkedIn may invalidate your session at any time; Plot will prompt you to
reconnect when that happens.

## License

MIT
49 changes: 49 additions & 0 deletions connectors/linkedin-messaging/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
{
"name": "@plotday/connector-linkedin-messaging",
"displayName": "LinkedIn",
"description": "LinkedIn messages and connection requests in your Plot inbox",
"logoUrl": "https://api.iconify.design/logos/linkedin-icon.svg",
"publisher": "Plot",
"publisherUrl": "https://plot.day",
"author": "Plot <team@plot.day> (https://plot.day)",
"license": "MIT",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"@plotday/connector": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"private": true,
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"deploy": "plot deploy",
"lint": "plot lint"
},
"dependencies": {
"@plotday/twister": "workspace:^"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "https://github.com/plotday/plot.git",
"directory": "connectors/linkedin-messaging"
},
"homepage": "https://plot.day",
"bugs": {
"url": "https://github.com/plotday/plot/issues"
},
"keywords": [
"plot",
"connector",
"linkedin",
"messaging"
]
}
1 change: 1 addition & 0 deletions connectors/linkedin-messaging/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export { default, LinkedInMessaging } from "./linkedin";
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add LinkedIn auth provider, built-in tool type, and messaging connector by KrisBraun · Pull Request #140 · plotday/plot · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/linkedin-tool.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@plotday/twister": minor
---

Added: `AuthProvider.LinkedIn` and a built-in `LinkedIn` tool that exposes a
provider-agnostic surface for the LinkedIn Voyager messaging API. The tool's
implementation lives in the API worker so request signing, header spoofing,
rate limiting, and cookie management stay out of open-source connector code.
21 changes: 21 additions & 0 deletions connectors/linkedin-messaging/LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Plot Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 56 additions & 0 deletions connectors/linkedin-messaging/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# @plotday/connector-linkedin-messaging

LinkedIn direct messages and inbound connection requests, surfaced in your
Plot inbox.

## How it works

LinkedIn does not expose a public OAuth scope for personal messaging. This
connector — like Beeper, Unipile, and Kondo — reuses your authenticated
LinkedIn session: when you "Connect LinkedIn" in Plot, the desktop or
mobile app opens an in-app webview pointed at `linkedin.com/login`. Once
you log in, Plot captures the `li_at` session cookie (and your browser's
User-Agent) and stores them encrypted at rest. All subsequent API calls go
through Plot's privileged server-side LinkedIn tool, which keeps the
Voyager wire format, request signing, and rate limiting hidden from this
open-source connector.

## What's synced

- **1:1 direct messages.** Each conversation becomes a Plot thread; each
message becomes a note.
- **Group conversations.** Same model — the thread's participants
populate from the conversation.
- **Inbound connection requests with notes.** Surfaces as a separate
link type so you can reply or accept directly from Plot.

Replies you send from Plot are written back to LinkedIn; marking a
conversation read in Plot marks it read on LinkedIn.

## Sync cadence

LinkedIn does not offer webhooks for personal messaging, so this connector
polls with an adaptive cadence:

- **5 minutes** when you (or LinkedIn) were active in the last hour.
- **15 minutes** when activity is within the last six hours.
- **30 minutes** when the inbox is idle.

The cadence resets to "fresh" each time you send a reply through Plot.

## Privacy / safety

Your `li_at` cookie grants full access to your LinkedIn account. Plot
treats it like any other access token:

- Encrypted at rest, never logged.
- Never passed into this open-source connector — only Plot's server can
read it.
- One-tap disconnect in Plot also stops all sync immediately.

LinkedIn may invalidate your session at any time; Plot will prompt you to
reconnect when that happens.

## License

MIT
49 changes: 49 additions & 0 deletions connectors/linkedin-messaging/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
{
"name": "@plotday/connector-linkedin-messaging",
"displayName": "LinkedIn",
"description": "LinkedIn messages and connection requests in your Plot inbox",
"logoUrl": "https://api.iconify.design/logos/linkedin-icon.svg",
"publisher": "Plot",
"publisherUrl": "https://plot.day",
"author": "Plot <team@plot.day> (https://plot.day)",
"license": "MIT",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"@plotday/connector": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"private": true,
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"deploy": "plot deploy",
"lint": "plot lint"
},
"dependencies": {
"@plotday/twister": "workspace:^"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "https://github.com/plotday/plot.git",
"directory": "connectors/linkedin-messaging"
},
"homepage": "https://plot.day",
"bugs": {
"url": "https://github.com/plotday/plot/issues"
},
"keywords": [
"plot",
"connector",
"linkedin",
"messaging"
]
}
1 change: 1 addition & 0 deletions connectors/linkedin-messaging/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export { default, LinkedInMessaging } from "./linkedin";
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add LinkedIn auth provider, built-in tool type, and messaging connector by KrisBraun · Pull Request #140 · plotday/plot · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/linkedin-tool.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@plotday/twister": minor
---

Added: `AuthProvider.LinkedIn` and a built-in `LinkedIn` tool that exposes a
provider-agnostic surface for the LinkedIn Voyager messaging API. The tool's
implementation lives in the API worker so request signing, header spoofing,
rate limiting, and cookie management stay out of open-source connector code.
21 changes: 21 additions & 0 deletions connectors/linkedin-messaging/LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Plot Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 56 additions & 0 deletions connectors/linkedin-messaging/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# @plotday/connector-linkedin-messaging

LinkedIn direct messages and inbound connection requests, surfaced in your
Plot inbox.

## How it works

LinkedIn does not expose a public OAuth scope for personal messaging. This
connector — like Beeper, Unipile, and Kondo — reuses your authenticated
LinkedIn session: when you "Connect LinkedIn" in Plot, the desktop or
mobile app opens an in-app webview pointed at `linkedin.com/login`. Once
you log in, Plot captures the `li_at` session cookie (and your browser's
User-Agent) and stores them encrypted at rest. All subsequent API calls go
through Plot's privileged server-side LinkedIn tool, which keeps the
Voyager wire format, request signing, and rate limiting hidden from this
open-source connector.

## What's synced

- **1:1 direct messages.** Each conversation becomes a Plot thread; each
message becomes a note.
- **Group conversations.** Same model — the thread's participants
populate from the conversation.
- **Inbound connection requests with notes.** Surfaces as a separate
link type so you can reply or accept directly from Plot.

Replies you send from Plot are written back to LinkedIn; marking a
conversation read in Plot marks it read on LinkedIn.

## Sync cadence

LinkedIn does not offer webhooks for personal messaging, so this connector
polls with an adaptive cadence:

- **5 minutes** when you (or LinkedIn) were active in the last hour.
- **15 minutes** when activity is within the last six hours.
- **30 minutes** when the inbox is idle.

The cadence resets to "fresh" each time you send a reply through Plot.

## Privacy / safety

Your `li_at` cookie grants full access to your LinkedIn account. Plot
treats it like any other access token:

- Encrypted at rest, never logged.
- Never passed into this open-source connector — only Plot's server can
read it.
- One-tap disconnect in Plot also stops all sync immediately.

LinkedIn may invalidate your session at any time; Plot will prompt you to
reconnect when that happens.

## License

MIT
49 changes: 49 additions & 0 deletions connectors/linkedin-messaging/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
{
"name": "@plotday/connector-linkedin-messaging",
"displayName": "LinkedIn",
"description": "LinkedIn messages and connection requests in your Plot inbox",
"logoUrl": "https://api.iconify.design/logos/linkedin-icon.svg",
"publisher": "Plot",
"publisherUrl": "https://plot.day",
"author": "Plot <team@plot.day> (https://plot.day)",
"license": "MIT",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"@plotday/connector": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"private": true,
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"deploy": "plot deploy",
"lint": "plot lint"
},
"dependencies": {
"@plotday/twister": "workspace:^"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "https://github.com/plotday/plot.git",
"directory": "connectors/linkedin-messaging"
},
"homepage": "https://plot.day",
"bugs": {
"url": "https://github.com/plotday/plot/issues"
},
"keywords": [
"plot",
"connector",
"linkedin",
"messaging"
]
}
1 change: 1 addition & 0 deletions connectors/linkedin-messaging/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export { default, LinkedInMessaging } from "./linkedin";
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add LinkedIn auth provider, built-in tool type, and messaging connector by KrisBraun · Pull Request #140 · plotday/plot · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/linkedin-tool.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@plotday/twister": minor
---

Added: `AuthProvider.LinkedIn` and a built-in `LinkedIn` tool that exposes a
provider-agnostic surface for the LinkedIn Voyager messaging API. The tool's
implementation lives in the API worker so request signing, header spoofing,
rate limiting, and cookie management stay out of open-source connector code.
21 changes: 21 additions & 0 deletions connectors/linkedin-messaging/LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Plot Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 56 additions & 0 deletions connectors/linkedin-messaging/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# @plotday/connector-linkedin-messaging

LinkedIn direct messages and inbound connection requests, surfaced in your
Plot inbox.

## How it works

LinkedIn does not expose a public OAuth scope for personal messaging. This
connector — like Beeper, Unipile, and Kondo — reuses your authenticated
LinkedIn session: when you "Connect LinkedIn" in Plot, the desktop or
mobile app opens an in-app webview pointed at `linkedin.com/login`. Once
you log in, Plot captures the `li_at` session cookie (and your browser's
User-Agent) and stores them encrypted at rest. All subsequent API calls go
through Plot's privileged server-side LinkedIn tool, which keeps the
Voyager wire format, request signing, and rate limiting hidden from this
open-source connector.

## What's synced

- **1:1 direct messages.** Each conversation becomes a Plot thread; each
message becomes a note.
- **Group conversations.** Same model — the thread's participants
populate from the conversation.
- **Inbound connection requests with notes.** Surfaces as a separate
link type so you can reply or accept directly from Plot.

Replies you send from Plot are written back to LinkedIn; marking a
conversation read in Plot marks it read on LinkedIn.

## Sync cadence

LinkedIn does not offer webhooks for personal messaging, so this connector
polls with an adaptive cadence:

- **5 minutes** when you (or LinkedIn) were active in the last hour.
- **15 minutes** when activity is within the last six hours.
- **30 minutes** when the inbox is idle.

The cadence resets to "fresh" each time you send a reply through Plot.

## Privacy / safety

Your `li_at` cookie grants full access to your LinkedIn account. Plot
treats it like any other access token:

- Encrypted at rest, never logged.
- Never passed into this open-source connector — only Plot's server can
read it.
- One-tap disconnect in Plot also stops all sync immediately.

LinkedIn may invalidate your session at any time; Plot will prompt you to
reconnect when that happens.

## License

MIT
49 changes: 49 additions & 0 deletions connectors/linkedin-messaging/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
{
"name": "@plotday/connector-linkedin-messaging",
"displayName": "LinkedIn",
"description": "LinkedIn messages and connection requests in your Plot inbox",
"logoUrl": "https://api.iconify.design/logos/linkedin-icon.svg",
"publisher": "Plot",
"publisherUrl": "https://plot.day",
"author": "Plot <team@plot.day> (https://plot.day)",
"license": "MIT",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"@plotday/connector": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"private": true,
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"deploy": "plot deploy",
"lint": "plot lint"
},
"dependencies": {
"@plotday/twister": "workspace:^"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "https://github.com/plotday/plot.git",
"directory": "connectors/linkedin-messaging"
},
"homepage": "https://plot.day",
"bugs": {
"url": "https://github.com/plotday/plot/issues"
},
"keywords": [
"plot",
"connector",
"linkedin",
"messaging"
]
}
1 change: 1 addition & 0 deletions connectors/linkedin-messaging/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export { default, LinkedInMessaging } from "./linkedin";
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add LinkedIn auth provider, built-in tool type, and messaging connector by KrisBraun · Pull Request #140 · plotday/plot · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/linkedin-tool.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@plotday/twister": minor
---

Added: `AuthProvider.LinkedIn` and a built-in `LinkedIn` tool that exposes a
provider-agnostic surface for the LinkedIn Voyager messaging API. The tool's
implementation lives in the API worker so request signing, header spoofing,
rate limiting, and cookie management stay out of open-source connector code.
21 changes: 21 additions & 0 deletions connectors/linkedin-messaging/LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Plot Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 56 additions & 0 deletions connectors/linkedin-messaging/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# @plotday/connector-linkedin-messaging

LinkedIn direct messages and inbound connection requests, surfaced in your
Plot inbox.

## How it works

LinkedIn does not expose a public OAuth scope for personal messaging. This
connector — like Beeper, Unipile, and Kondo — reuses your authenticated
LinkedIn session: when you "Connect LinkedIn" in Plot, the desktop or
mobile app opens an in-app webview pointed at `linkedin.com/login`. Once
you log in, Plot captures the `li_at` session cookie (and your browser's
User-Agent) and stores them encrypted at rest. All subsequent API calls go
through Plot's privileged server-side LinkedIn tool, which keeps the
Voyager wire format, request signing, and rate limiting hidden from this
open-source connector.

## What's synced

- **1:1 direct messages.** Each conversation becomes a Plot thread; each
message becomes a note.
- **Group conversations.** Same model — the thread's participants
populate from the conversation.
- **Inbound connection requests with notes.** Surfaces as a separate
link type so you can reply or accept directly from Plot.

Replies you send from Plot are written back to LinkedIn; marking a
conversation read in Plot marks it read on LinkedIn.

## Sync cadence

LinkedIn does not offer webhooks for personal messaging, so this connector
polls with an adaptive cadence:

- **5 minutes** when you (or LinkedIn) were active in the last hour.
- **15 minutes** when activity is within the last six hours.
- **30 minutes** when the inbox is idle.

The cadence resets to "fresh" each time you send a reply through Plot.

## Privacy / safety

Your `li_at` cookie grants full access to your LinkedIn account. Plot
treats it like any other access token:

- Encrypted at rest, never logged.
- Never passed into this open-source connector — only Plot's server can
read it.
- One-tap disconnect in Plot also stops all sync immediately.

LinkedIn may invalidate your session at any time; Plot will prompt you to
reconnect when that happens.

## License

MIT
49 changes: 49 additions & 0 deletions connectors/linkedin-messaging/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
{
"name": "@plotday/connector-linkedin-messaging",
"displayName": "LinkedIn",
"description": "LinkedIn messages and connection requests in your Plot inbox",
"logoUrl": "https://api.iconify.design/logos/linkedin-icon.svg",
"publisher": "Plot",
"publisherUrl": "https://plot.day",
"author": "Plot <team@plot.day> (https://plot.day)",
"license": "MIT",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"@plotday/connector": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"private": true,
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"deploy": "plot deploy",
"lint": "plot lint"
},
"dependencies": {
"@plotday/twister": "workspace:^"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "https://github.com/plotday/plot.git",
"directory": "connectors/linkedin-messaging"
},
"homepage": "https://plot.day",
"bugs": {
"url": "https://github.com/plotday/plot/issues"
},
"keywords": [
"plot",
"connector",
"linkedin",
"messaging"
]
}
1 change: 1 addition & 0 deletions connectors/linkedin-messaging/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export { default, LinkedInMessaging } from "./linkedin";
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add LinkedIn auth provider, built-in tool type, and messaging connector by KrisBraun · Pull Request #140 · plotday/plot · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/linkedin-tool.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@plotday/twister": minor
---

Added: `AuthProvider.LinkedIn` and a built-in `LinkedIn` tool that exposes a
provider-agnostic surface for the LinkedIn Voyager messaging API. The tool's
implementation lives in the API worker so request signing, header spoofing,
rate limiting, and cookie management stay out of open-source connector code.
21 changes: 21 additions & 0 deletions connectors/linkedin-messaging/LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Plot Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 56 additions & 0 deletions connectors/linkedin-messaging/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# @plotday/connector-linkedin-messaging

LinkedIn direct messages and inbound connection requests, surfaced in your
Plot inbox.

## How it works

LinkedIn does not expose a public OAuth scope for personal messaging. This
connector — like Beeper, Unipile, and Kondo — reuses your authenticated
LinkedIn session: when you "Connect LinkedIn" in Plot, the desktop or
mobile app opens an in-app webview pointed at `linkedin.com/login`. Once
you log in, Plot captures the `li_at` session cookie (and your browser's
User-Agent) and stores them encrypted at rest. All subsequent API calls go
through Plot's privileged server-side LinkedIn tool, which keeps the
Voyager wire format, request signing, and rate limiting hidden from this
open-source connector.

## What's synced

- **1:1 direct messages.** Each conversation becomes a Plot thread; each
message becomes a note.
- **Group conversations.** Same model — the thread's participants
populate from the conversation.
- **Inbound connection requests with notes.** Surfaces as a separate
link type so you can reply or accept directly from Plot.

Replies you send from Plot are written back to LinkedIn; marking a
conversation read in Plot marks it read on LinkedIn.

## Sync cadence

LinkedIn does not offer webhooks for personal messaging, so this connector
polls with an adaptive cadence:

- **5 minutes** when you (or LinkedIn) were active in the last hour.
- **15 minutes** when activity is within the last six hours.
- **30 minutes** when the inbox is idle.

The cadence resets to "fresh" each time you send a reply through Plot.

## Privacy / safety

Your `li_at` cookie grants full access to your LinkedIn account. Plot
treats it like any other access token:

- Encrypted at rest, never logged.
- Never passed into this open-source connector — only Plot's server can
read it.
- One-tap disconnect in Plot also stops all sync immediately.

LinkedIn may invalidate your session at any time; Plot will prompt you to
reconnect when that happens.

## License

MIT
49 changes: 49 additions & 0 deletions connectors/linkedin-messaging/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
{
"name": "@plotday/connector-linkedin-messaging",
"displayName": "LinkedIn",
"description": "LinkedIn messages and connection requests in your Plot inbox",
"logoUrl": "https://api.iconify.design/logos/linkedin-icon.svg",
"publisher": "Plot",
"publisherUrl": "https://plot.day",
"author": "Plot <team@plot.day> (https://plot.day)",
"license": "MIT",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"@plotday/connector": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"private": true,
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"deploy": "plot deploy",
"lint": "plot lint"
},
"dependencies": {
"@plotday/twister": "workspace:^"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "https://github.com/plotday/plot.git",
"directory": "connectors/linkedin-messaging"
},
"homepage": "https://plot.day",
"bugs": {
"url": "https://github.com/plotday/plot/issues"
},
"keywords": [
"plot",
"connector",
"linkedin",
"messaging"
]
}
1 change: 1 addition & 0 deletions connectors/linkedin-messaging/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export { default, LinkedInMessaging } from "./linkedin";
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add LinkedIn auth provider, built-in tool type, and messaging connector by KrisBraun · Pull Request #140 · plotday/plot · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/linkedin-tool.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@plotday/twister": minor
---

Added: `AuthProvider.LinkedIn` and a built-in `LinkedIn` tool that exposes a
provider-agnostic surface for the LinkedIn Voyager messaging API. The tool's
implementation lives in the API worker so request signing, header spoofing,
rate limiting, and cookie management stay out of open-source connector code.
21 changes: 21 additions & 0 deletions connectors/linkedin-messaging/LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Plot Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 56 additions & 0 deletions connectors/linkedin-messaging/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# @plotday/connector-linkedin-messaging

LinkedIn direct messages and inbound connection requests, surfaced in your
Plot inbox.

## How it works

LinkedIn does not expose a public OAuth scope for personal messaging. This
connector — like Beeper, Unipile, and Kondo — reuses your authenticated
LinkedIn session: when you "Connect LinkedIn" in Plot, the desktop or
mobile app opens an in-app webview pointed at `linkedin.com/login`. Once
you log in, Plot captures the `li_at` session cookie (and your browser's
User-Agent) and stores them encrypted at rest. All subsequent API calls go
through Plot's privileged server-side LinkedIn tool, which keeps the
Voyager wire format, request signing, and rate limiting hidden from this
open-source connector.

## What's synced

- **1:1 direct messages.** Each conversation becomes a Plot thread; each
message becomes a note.
- **Group conversations.** Same model — the thread's participants
populate from the conversation.
- **Inbound connection requests with notes.** Surfaces as a separate
link type so you can reply or accept directly from Plot.

Replies you send from Plot are written back to LinkedIn; marking a
conversation read in Plot marks it read on LinkedIn.

## Sync cadence

LinkedIn does not offer webhooks for personal messaging, so this connector
polls with an adaptive cadence:

- **5 minutes** when you (or LinkedIn) were active in the last hour.
- **15 minutes** when activity is within the last six hours.
- **30 minutes** when the inbox is idle.

The cadence resets to "fresh" each time you send a reply through Plot.

## Privacy / safety

Your `li_at` cookie grants full access to your LinkedIn account. Plot
treats it like any other access token:

- Encrypted at rest, never logged.
- Never passed into this open-source connector — only Plot's server can
read it.
- One-tap disconnect in Plot also stops all sync immediately.

LinkedIn may invalidate your session at any time; Plot will prompt you to
reconnect when that happens.

## License

MIT
49 changes: 49 additions & 0 deletions connectors/linkedin-messaging/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
{
"name": "@plotday/connector-linkedin-messaging",
"displayName": "LinkedIn",
"description": "LinkedIn messages and connection requests in your Plot inbox",
"logoUrl": "https://api.iconify.design/logos/linkedin-icon.svg",
"publisher": "Plot",
"publisherUrl": "https://plot.day",
"author": "Plot <team@plot.day> (https://plot.day)",
"license": "MIT",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"@plotday/connector": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"private": true,
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"deploy": "plot deploy",
"lint": "plot lint"
},
"dependencies": {
"@plotday/twister": "workspace:^"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "https://github.com/plotday/plot.git",
"directory": "connectors/linkedin-messaging"
},
"homepage": "https://plot.day",
"bugs": {
"url": "https://github.com/plotday/plot/issues"
},
"keywords": [
"plot",
"connector",
"linkedin",
"messaging"
]
}
1 change: 1 addition & 0 deletions connectors/linkedin-messaging/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export { default, LinkedInMessaging } from "./linkedin";
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add LinkedIn auth provider, built-in tool type, and messaging connector by KrisBraun · Pull Request #140 · plotday/plot · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/linkedin-tool.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@plotday/twister": minor
---

Added: `AuthProvider.LinkedIn` and a built-in `LinkedIn` tool that exposes a
provider-agnostic surface for the LinkedIn Voyager messaging API. The tool's
implementation lives in the API worker so request signing, header spoofing,
rate limiting, and cookie management stay out of open-source connector code.
21 changes: 21 additions & 0 deletions connectors/linkedin-messaging/LICENSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Plot Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 56 additions & 0 deletions connectors/linkedin-messaging/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
# @plotday/connector-linkedin-messaging

LinkedIn direct messages and inbound connection requests, surfaced in your
Plot inbox.

## How it works

LinkedIn does not expose a public OAuth scope for personal messaging. This
connector — like Beeper, Unipile, and Kondo — reuses your authenticated
LinkedIn session: when you "Connect LinkedIn" in Plot, the desktop or
mobile app opens an in-app webview pointed at `linkedin.com/login`. Once
you log in, Plot captures the `li_at` session cookie (and your browser's
User-Agent) and stores them encrypted at rest. All subsequent API calls go
through Plot's privileged server-side LinkedIn tool, which keeps the
Voyager wire format, request signing, and rate limiting hidden from this
open-source connector.

## What's synced

- **1:1 direct messages.** Each conversation becomes a Plot thread; each
message becomes a note.
- **Group conversations.** Same model — the thread's participants
populate from the conversation.
- **Inbound connection requests with notes.** Surfaces as a separate
link type so you can reply or accept directly from Plot.

Replies you send from Plot are written back to LinkedIn; marking a
conversation read in Plot marks it read on LinkedIn.

## Sync cadence

LinkedIn does not offer webhooks for personal messaging, so this connector
polls with an adaptive cadence:

- **5 minutes** when you (or LinkedIn) were active in the last hour.
- **15 minutes** when activity is within the last six hours.
- **30 minutes** when the inbox is idle.

The cadence resets to "fresh" each time you send a reply through Plot.

## Privacy / safety

Your `li_at` cookie grants full access to your LinkedIn account. Plot
treats it like any other access token:

- Encrypted at rest, never logged.
- Never passed into this open-source connector — only Plot's server can
read it.
- One-tap disconnect in Plot also stops all sync immediately.

LinkedIn may invalidate your session at any time; Plot will prompt you to
reconnect when that happens.

## License

MIT
49 changes: 49 additions & 0 deletions connectors/linkedin-messaging/package.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
{
"name": "@plotday/connector-linkedin-messaging",
"displayName": "LinkedIn",
"description": "LinkedIn messages and connection requests in your Plot inbox",
"logoUrl": "https://api.iconify.design/logos/linkedin-icon.svg",
"publisher": "Plot",
"publisherUrl": "https://plot.day",
"author": "Plot <team@plot.day> (https://plot.day)",
"license": "MIT",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"@plotday/connector": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"private": true,
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"deploy": "plot deploy",
"lint": "plot lint"
},
"dependencies": {
"@plotday/twister": "workspace:^"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"repository": {
"type": "git",
"url": "https://github.com/plotday/plot.git",
"directory": "connectors/linkedin-messaging"
},
"homepage": "https://plot.day",
"bugs": {
"url": "https://github.com/plotday/plot/issues"
},
"keywords": [
"plot",
"connector",
"linkedin",
"messaging"
]
}
1 change: 1 addition & 0 deletions connectors/linkedin-messaging/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
export { default, LinkedInMessaging } from "./linkedin";
Loading
Loading