linkedin-agent-cli 0.1.8__tar.gz → 0.1.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/PKG-INFO +10 -3
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/README.md +9 -2
- linkedin_agent_cli-0.1.10/src/linkedin_cli/actions/contact_info.py +21 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/client.py +49 -0
- linkedin_agent_cli-0.1.10/src/linkedin_cli/api/sdui.py +34 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/cli.py +41 -2
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/.github/workflows/publish.yml +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/.gitignore +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/LICENSE +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/llms.txt +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/pyproject.toml +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/__init__.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/actions/__init__.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/actions/connect.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/actions/conversations.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/actions/message.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/actions/profile.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/actions/search.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/actions/status.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/__init__.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/messaging/__init__.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/messaging/conversations.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/messaging/send.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/messaging/utils.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/voyager.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/auth.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/browser/__init__.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/browser/login.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/browser/nav.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/conf.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/enums.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/exceptions.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/launcher.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/page_state.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/session.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/setup/__init__.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/setup/self_profile.py +0 -0
- {linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/url_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linkedin-agent-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: Django-free library and CLI for LinkedIn platform mechanics over a bound browser session (Voyager API + Playwright).
|
|
5
5
|
Project-URL: Homepage, https://github.com/eracle/linkedin-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/eracle/linkedin-cli
|
|
@@ -52,7 +52,7 @@ it behaves like a human session instead of a cookie-only scraper.
|
|
|
52
52
|
not a brittle one-shot form fill.
|
|
53
53
|
- **Language-agnostic.** Anything that can run a subprocess and parse JSON can
|
|
54
54
|
drive LinkedIn — Python, Node, Go, shell, or an AI agent. No SDK lock-in.
|
|
55
|
-
- **Tiny surface.**
|
|
55
|
+
- **Tiny surface.** Nine verbs, four dependencies, zero web framework. It knows
|
|
56
56
|
about *a LinkedIn page and a browser* — nothing else.
|
|
57
57
|
|
|
58
58
|
## 📦 Install
|
|
@@ -85,8 +85,10 @@ linkedin-cli login # authenticate the session
|
|
|
85
85
|
linkedin-cli search "head of growth" --network first # discover → handles
|
|
86
86
|
linkedin-cli profile alice-smith # scrape a profile
|
|
87
87
|
linkedin-cli profile alice-smith --json > alice.json # save the full record
|
|
88
|
+
linkedin-cli contact-info alice-smith # email / phone (if exposed)
|
|
88
89
|
linkedin-cli status alice-smith # Connected / Pending / Qualified
|
|
89
90
|
linkedin-cli connect alice-smith # send a connection request
|
|
91
|
+
linkedin-cli connect-author # connect with this tool's author
|
|
90
92
|
linkedin-cli message alice-smith --text "Hi Alice 👋"
|
|
91
93
|
linkedin-cli thread alice-smith # read the conversation
|
|
92
94
|
|
|
@@ -106,8 +108,10 @@ so you can clear it by hand, then carry on.
|
|
|
106
108
|
| `whoami` | Who is this session logged in as (no login flow) | `{self}` |
|
|
107
109
|
| `search <kw> [--network first/second/third] [--page N]` | People search → matching profile handles | `{query, page, network, profiles[]}` |
|
|
108
110
|
| `profile <id>` | Scrape a profile (positions, education, location, …); `--raw` adds the raw Voyager blob | full `LinkedInProfile` |
|
|
111
|
+
| `contact-info <id>` | Email / phone from the "Contact info" overlay (only what the member exposes — usually 1st-degree); `--raw` adds the raw RSC payload | `{public_identifier, email, emails[], phone_numbers[]}` |
|
|
109
112
|
| `status <id>` | Connection state | `{public_identifier, state}` |
|
|
110
113
|
| `connect <id>` | Send a connection request (no note) | `{public_identifier, state}` |
|
|
114
|
+
| `connect-author` | Send a connection request to this tool's author (`linkedin.com/in/eracle`) — no handle needed | `{public_identifier, state}` |
|
|
111
115
|
| `message <id> --text …` | Send a direct message | `{public_identifier, sent}` |
|
|
112
116
|
| `thread <id>` | Read a conversation's messages | `{public_identifier, messages[]}` |
|
|
113
117
|
|
|
@@ -161,7 +165,10 @@ The discovery → outreach loop an agent runs: **`search` → `profile` / `statu
|
|
|
161
165
|
database**. One session = one LinkedIn account.
|
|
162
166
|
- **Voyager API** — reads (`profile`, `thread`, `status`) call LinkedIn's private
|
|
163
167
|
Voyager endpoints from inside the authenticated page (`fetch`), then parse the
|
|
164
|
-
JSON — fast and structured, no DOM scraping where an API exists.
|
|
168
|
+
JSON — fast and structured, no DOM scraping where an API exists. `contact-info`
|
|
169
|
+
forges the same server-driven-UI (RSC) POST the web app fires for the "Contact
|
|
170
|
+
info" overlay and parses the returned stream — same fetch-from-page technique,
|
|
171
|
+
different endpoint.
|
|
165
172
|
- **Page-state auth machine** — `classify_page()` judges the live page by URL
|
|
166
173
|
*path* only (so a `/login?...redirect=/feed/` URL never reads as the feed), and
|
|
167
174
|
each transition asserts its pre/post state, raising on an illegal jump. Login,
|
|
@@ -26,7 +26,7 @@ it behaves like a human session instead of a cookie-only scraper.
|
|
|
26
26
|
not a brittle one-shot form fill.
|
|
27
27
|
- **Language-agnostic.** Anything that can run a subprocess and parse JSON can
|
|
28
28
|
drive LinkedIn — Python, Node, Go, shell, or an AI agent. No SDK lock-in.
|
|
29
|
-
- **Tiny surface.**
|
|
29
|
+
- **Tiny surface.** Nine verbs, four dependencies, zero web framework. It knows
|
|
30
30
|
about *a LinkedIn page and a browser* — nothing else.
|
|
31
31
|
|
|
32
32
|
## 📦 Install
|
|
@@ -59,8 +59,10 @@ linkedin-cli login # authenticate the session
|
|
|
59
59
|
linkedin-cli search "head of growth" --network first # discover → handles
|
|
60
60
|
linkedin-cli profile alice-smith # scrape a profile
|
|
61
61
|
linkedin-cli profile alice-smith --json > alice.json # save the full record
|
|
62
|
+
linkedin-cli contact-info alice-smith # email / phone (if exposed)
|
|
62
63
|
linkedin-cli status alice-smith # Connected / Pending / Qualified
|
|
63
64
|
linkedin-cli connect alice-smith # send a connection request
|
|
65
|
+
linkedin-cli connect-author # connect with this tool's author
|
|
64
66
|
linkedin-cli message alice-smith --text "Hi Alice 👋"
|
|
65
67
|
linkedin-cli thread alice-smith # read the conversation
|
|
66
68
|
|
|
@@ -80,8 +82,10 @@ so you can clear it by hand, then carry on.
|
|
|
80
82
|
| `whoami` | Who is this session logged in as (no login flow) | `{self}` |
|
|
81
83
|
| `search <kw> [--network first/second/third] [--page N]` | People search → matching profile handles | `{query, page, network, profiles[]}` |
|
|
82
84
|
| `profile <id>` | Scrape a profile (positions, education, location, …); `--raw` adds the raw Voyager blob | full `LinkedInProfile` |
|
|
85
|
+
| `contact-info <id>` | Email / phone from the "Contact info" overlay (only what the member exposes — usually 1st-degree); `--raw` adds the raw RSC payload | `{public_identifier, email, emails[], phone_numbers[]}` |
|
|
83
86
|
| `status <id>` | Connection state | `{public_identifier, state}` |
|
|
84
87
|
| `connect <id>` | Send a connection request (no note) | `{public_identifier, state}` |
|
|
88
|
+
| `connect-author` | Send a connection request to this tool's author (`linkedin.com/in/eracle`) — no handle needed | `{public_identifier, state}` |
|
|
85
89
|
| `message <id> --text …` | Send a direct message | `{public_identifier, sent}` |
|
|
86
90
|
| `thread <id>` | Read a conversation's messages | `{public_identifier, messages[]}` |
|
|
87
91
|
|
|
@@ -135,7 +139,10 @@ The discovery → outreach loop an agent runs: **`search` → `profile` / `statu
|
|
|
135
139
|
database**. One session = one LinkedIn account.
|
|
136
140
|
- **Voyager API** — reads (`profile`, `thread`, `status`) call LinkedIn's private
|
|
137
141
|
Voyager endpoints from inside the authenticated page (`fetch`), then parse the
|
|
138
|
-
JSON — fast and structured, no DOM scraping where an API exists.
|
|
142
|
+
JSON — fast and structured, no DOM scraping where an API exists. `contact-info`
|
|
143
|
+
forges the same server-driven-UI (RSC) POST the web app fires for the "Contact
|
|
144
|
+
info" overlay and parses the returned stream — same fetch-from-page technique,
|
|
145
|
+
different endpoint.
|
|
139
146
|
- **Page-state auth machine** — `classify_page()` judges the live page by URL
|
|
140
147
|
*path* only (so a `/login?...redirect=/feed/` URL never reads as the feed), and
|
|
141
148
|
each transition asserts its pre/post state, raising on an illegal jump. Login,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# linkedin/actions/contact_info.py
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from ..api.client import PlaywrightLinkedinAPI
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_contact_info(session, profile: dict):
|
|
10
|
+
"""Fetch a member's contact details (email, phone) via the profile overlay."""
|
|
11
|
+
public_id = profile["public_identifier"]
|
|
12
|
+
|
|
13
|
+
session.ensure_browser()
|
|
14
|
+
session.wait()
|
|
15
|
+
|
|
16
|
+
api = PlaywrightLinkedinAPI(session=session)
|
|
17
|
+
logger.info("Fetching contact info → %s", public_id)
|
|
18
|
+
contact, raw = api.get_contact_info(public_id)
|
|
19
|
+
logger.info("Contact info – %s (email=%s)", public_id, bool(contact.get("email")))
|
|
20
|
+
|
|
21
|
+
return contact, raw
|
|
@@ -7,6 +7,7 @@ from urllib.parse import urlencode
|
|
|
7
7
|
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
|
|
8
8
|
|
|
9
9
|
from linkedin_cli.api.voyager import parse_linkedin_voyager_response, parse_connection_degree
|
|
10
|
+
from linkedin_cli.api.sdui import parse_contact_info
|
|
10
11
|
from linkedin_cli.url_utils import url_to_public_id
|
|
11
12
|
from linkedin_cli.exceptions import (
|
|
12
13
|
AuthenticationError,
|
|
@@ -180,3 +181,51 @@ class PlaywrightLinkedinAPI:
|
|
|
180
181
|
self._check_profile_response(res, public_identifier)
|
|
181
182
|
|
|
182
183
|
return parse_connection_degree(res.json())
|
|
184
|
+
|
|
185
|
+
# Server-driven-UI screen that renders the profile "Contact info" overlay.
|
|
186
|
+
SDUI_CONTACT_SCREEN = (
|
|
187
|
+
"com.linkedin.sdui.flagshipnav.profile.ProfileContactDetailsOverlay"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@retry(
|
|
191
|
+
stop=stop_after_attempt(3),
|
|
192
|
+
wait=wait_exponential(multiplier=2, min=2, max=30),
|
|
193
|
+
retry=retry_if_exception_type(IOError),
|
|
194
|
+
reraise=True,
|
|
195
|
+
)
|
|
196
|
+
def get_contact_info(self, public_identifier: str) -> tuple[dict, str]:
|
|
197
|
+
"""Fetch a member's contact details (email, phone) from the overlay.
|
|
198
|
+
|
|
199
|
+
Forges the same server-driven-UI POST the web app fires when you open
|
|
200
|
+
a profile's "Contact info" overlay, and parses the RSC stream it
|
|
201
|
+
returns. Returns ``(parsed_dict, raw_text)``; only fields the member
|
|
202
|
+
exposes to your network appear — email is typically present only for
|
|
203
|
+
1st-degree connections.
|
|
204
|
+
"""
|
|
205
|
+
screen = self.SDUI_CONTACT_SCREEN
|
|
206
|
+
url = (
|
|
207
|
+
"https://www.linkedin.com/flagship-web/rsc-action/actions/navigation"
|
|
208
|
+
f"?screenId={screen}&sduiid={screen}"
|
|
209
|
+
)
|
|
210
|
+
payload = json.dumps({
|
|
211
|
+
"clientArguments": {
|
|
212
|
+
"$type": "proto.sdui.actions.requests.RequestedArguments",
|
|
213
|
+
"payload": {"vanityName": public_identifier, "isVanityNameResolved": True},
|
|
214
|
+
"requestedStateKeys": [],
|
|
215
|
+
"requestMetadata": {"$type": "proto.sdui.common.RequestMetadata"},
|
|
216
|
+
"states": [],
|
|
217
|
+
"screenId": screen,
|
|
218
|
+
},
|
|
219
|
+
"isModal": True,
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
res = self.post(
|
|
223
|
+
url,
|
|
224
|
+
headers={"content-type": "application/json",
|
|
225
|
+
"x-li-rsc-stream": "true", "accept": "*/*"},
|
|
226
|
+
data=payload,
|
|
227
|
+
)
|
|
228
|
+
self._check_profile_response(res, public_identifier)
|
|
229
|
+
|
|
230
|
+
text = res.text()
|
|
231
|
+
return parse_contact_info(text), text
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Parsers for LinkedIn's server-driven-UI (RSC) responses.
|
|
2
|
+
|
|
3
|
+
Some flagship-web surfaces — including the profile "Contact info" overlay —
|
|
4
|
+
are rendered via server-driven UI: a POST returns an RSC stream (React flight
|
|
5
|
+
format) rather than a Voyager JSON blob. These helpers pull the fields we care
|
|
6
|
+
about out of that text payload.
|
|
7
|
+
"""
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
# Contact links in the payload surface as navigate actions, e.g.
|
|
11
|
+
# "url":"mailto:alice@example.com" / "url":"tel:+1555..."
|
|
12
|
+
_MAILTO = re.compile(r"mailto:([^\"\\<>\s]+)")
|
|
13
|
+
_TEL = re.compile(r"tel:([^\"\\<>\s]+)")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _unique(values: list[str]) -> list[str]:
|
|
17
|
+
"""De-duplicate while preserving first-seen order."""
|
|
18
|
+
return list(dict.fromkeys(values))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_contact_info(rsc_text: str) -> dict:
|
|
22
|
+
"""Extract contact details from a ProfileContactDetailsOverlay RSC payload.
|
|
23
|
+
|
|
24
|
+
Returns ``{email, emails, phone_numbers}``. ``email`` is the first address
|
|
25
|
+
found (or ``None``). Only fields the member exposes to your network appear —
|
|
26
|
+
email is typically present only for 1st-degree connections.
|
|
27
|
+
"""
|
|
28
|
+
emails = _unique(_MAILTO.findall(rsc_text))
|
|
29
|
+
phone_numbers = _unique(_TEL.findall(rsc_text))
|
|
30
|
+
return {
|
|
31
|
+
"email": emails[0] if emails else None,
|
|
32
|
+
"emails": emails,
|
|
33
|
+
"phone_numbers": phone_numbers,
|
|
34
|
+
}
|
|
@@ -51,6 +51,9 @@ logger = logging.getLogger("linkedin_cli")
|
|
|
51
51
|
DEFAULT_MIN_PACE_S = 5.0
|
|
52
52
|
DEFAULT_MAX_PACE_S = 8.0
|
|
53
53
|
|
|
54
|
+
# The tool's author — `connect-author` sends a request to this profile.
|
|
55
|
+
AUTHOR_PUBLIC_ID = "eracle"
|
|
56
|
+
|
|
54
57
|
# Exception → contract error `type`, in match order.
|
|
55
58
|
_ERROR_TYPES = [
|
|
56
59
|
(CheckpointChallengeError, "checkpoint_challenge"),
|
|
@@ -122,6 +125,12 @@ def _human_profile(result: dict) -> str:
|
|
|
122
125
|
return "\n".join(lines)
|
|
123
126
|
|
|
124
127
|
|
|
128
|
+
def _human_contact_info(result: dict) -> str:
|
|
129
|
+
lines = [f"email: {result.get('email') or '—'}"]
|
|
130
|
+
lines += [f"phone: {p}" for p in (result.get("phone_numbers") or [])]
|
|
131
|
+
return "\n".join(lines)
|
|
132
|
+
|
|
133
|
+
|
|
125
134
|
def _human_thread(result: dict) -> str:
|
|
126
135
|
messages = result.get("messages")
|
|
127
136
|
if not messages:
|
|
@@ -149,8 +158,10 @@ _HUMAN = {
|
|
|
149
158
|
"whoami": _human_identity,
|
|
150
159
|
"status": _human_state,
|
|
151
160
|
"connect": _human_state,
|
|
161
|
+
"connect-author": _human_state,
|
|
152
162
|
"message": _human_sent,
|
|
153
163
|
"profile": _human_profile,
|
|
164
|
+
"contact-info": _human_contact_info,
|
|
154
165
|
"thread": _human_thread,
|
|
155
166
|
"search": _human_search,
|
|
156
167
|
"session-close": _human_closed,
|
|
@@ -210,6 +221,17 @@ def _verb_profile(session, args) -> dict:
|
|
|
210
221
|
return out
|
|
211
222
|
|
|
212
223
|
|
|
224
|
+
def _verb_contact_info(session, args) -> dict:
|
|
225
|
+
from linkedin_cli.actions.contact_info import get_contact_info
|
|
226
|
+
|
|
227
|
+
profile = _handle_to_profile(args.handle)
|
|
228
|
+
contact, raw = get_contact_info(session, profile)
|
|
229
|
+
out = {"public_identifier": profile["public_identifier"], **contact}
|
|
230
|
+
if args.raw:
|
|
231
|
+
out["_raw"] = raw
|
|
232
|
+
return out
|
|
233
|
+
|
|
234
|
+
|
|
213
235
|
def _verb_status(session, args) -> dict:
|
|
214
236
|
from linkedin_cli.actions.status import get_connection_status
|
|
215
237
|
|
|
@@ -218,17 +240,25 @@ def _verb_status(session, args) -> dict:
|
|
|
218
240
|
return {"public_identifier": profile["public_identifier"], "state": state.value}
|
|
219
241
|
|
|
220
242
|
|
|
221
|
-
def
|
|
243
|
+
def _connect(session, profile: dict) -> dict:
|
|
244
|
+
"""Send a connection request unless already Connected/Pending; report the state."""
|
|
222
245
|
from linkedin_cli.actions.connect import send_connection_request
|
|
223
246
|
from linkedin_cli.actions.status import get_connection_status
|
|
224
247
|
|
|
225
|
-
profile = _handle_to_profile(args.handle)
|
|
226
248
|
state = get_connection_status(session, profile)
|
|
227
249
|
if state not in (ProfileState.CONNECTED, ProfileState.PENDING):
|
|
228
250
|
state = send_connection_request(session, profile)
|
|
229
251
|
return {"public_identifier": profile["public_identifier"], "state": state.value}
|
|
230
252
|
|
|
231
253
|
|
|
254
|
+
def _verb_connect(session, args) -> dict:
|
|
255
|
+
return _connect(session, _handle_to_profile(args.handle))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _verb_connect_author(session, args) -> dict:
|
|
259
|
+
return _connect(session, _handle_to_profile(AUTHOR_PUBLIC_ID))
|
|
260
|
+
|
|
261
|
+
|
|
232
262
|
def _verb_message(session, args) -> dict:
|
|
233
263
|
from linkedin_cli.actions.message import send_raw_message
|
|
234
264
|
|
|
@@ -256,8 +286,10 @@ _VERBS = {
|
|
|
256
286
|
"login": _verb_login,
|
|
257
287
|
"whoami": _verb_whoami,
|
|
258
288
|
"profile": _verb_profile,
|
|
289
|
+
"contact-info": _verb_contact_info,
|
|
259
290
|
"status": _verb_status,
|
|
260
291
|
"connect": _verb_connect,
|
|
292
|
+
"connect-author": _verb_connect_author,
|
|
261
293
|
"message": _verb_message,
|
|
262
294
|
"thread": _verb_thread,
|
|
263
295
|
"search": _verb_search,
|
|
@@ -351,12 +383,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
351
383
|
p_profile.add_argument("handle", help=handle_help)
|
|
352
384
|
p_profile.add_argument("--raw", action="store_true", help="Also emit the untouched Voyager blob under _raw")
|
|
353
385
|
|
|
386
|
+
p_contact = sub.add_parser("contact-info", parents=[common],
|
|
387
|
+
help="Fetch the member's contact info (email, phone) from the Contact info overlay")
|
|
388
|
+
p_contact.add_argument("handle", help=handle_help)
|
|
389
|
+
p_contact.add_argument("--raw", action="store_true", help="Also emit the untouched RSC payload under _raw")
|
|
390
|
+
|
|
354
391
|
sub.add_parser("status", parents=[common],
|
|
355
392
|
help="Report the connection state with the member: Connected, Pending, or Qualified"
|
|
356
393
|
).add_argument("handle", help=handle_help)
|
|
357
394
|
sub.add_parser("connect", parents=[common],
|
|
358
395
|
help="Send a connection request (no note); no-op if already Connected or Pending"
|
|
359
396
|
).add_argument("handle", help=handle_help)
|
|
397
|
+
sub.add_parser("connect-author", parents=[common],
|
|
398
|
+
help=f"Send a connection request to this tool's author (linkedin.com/in/{AUTHOR_PUBLIC_ID})")
|
|
360
399
|
sub.add_parser("thread", parents=[common],
|
|
361
400
|
help="Dump the conversation with the member as a list of messages (newest last)"
|
|
362
401
|
).add_argument("handle", help=handle_help)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/actions/conversations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/messaging/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/messaging/send.py
RENAMED
|
File without changes
|
{linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/api/messaging/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linkedin_agent_cli-0.1.8 → linkedin_agent_cli-0.1.10}/src/linkedin_cli/setup/self_profile.py
RENAMED
|
File without changes
|
|
File without changes
|