bereach-openclaw 0.2.0
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.
- package/README.md +70 -0
- package/openclaw.plugin.json +24 -0
- package/package.json +17 -0
- package/skills/bereach/SKILL.md +137 -0
- package/skills/bereach/openclaw-optimization.md +91 -0
- package/skills/bereach/sdk-reference.md +390 -0
- package/skills/bereach/sub/lead-magnet.md +200 -0
- package/src/client.ts +16 -0
- package/src/commands/index.ts +75 -0
- package/src/index.ts +11 -0
- package/src/services/campaign-monitor.ts +59 -0
- package/src/tools/definitions.ts +567 -0
- package/src/tools/index.ts +23 -0
- package/src/types/bereach.d.ts +15 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bereach-sdk-reference
|
|
3
|
+
description: "Complete SDK method reference — parameters, types, and descriptions for all BeReach operations."
|
|
4
|
+
lastUpdatedAt: 1772619338
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# BeReach SDK Reference
|
|
8
|
+
|
|
9
|
+
> Auto-generated from OpenAPI spec. This is the single source of truth for SDK method signatures.
|
|
10
|
+
> Use `client.<resource>.<method>(params)` — see [BeReach Skill](bereach-skill.md) for behavioral rules.
|
|
11
|
+
|
|
12
|
+
## linkedinScrapers
|
|
13
|
+
|
|
14
|
+
### collectLikes
|
|
15
|
+
|
|
16
|
+
Scrape LinkedIn post likes. Returns paginated list of profiles who liked a post.
|
|
17
|
+
|
|
18
|
+
`client.linkedinScrapers.collectLikes(params)`
|
|
19
|
+
|
|
20
|
+
- **postUrl** (string, required) — LinkedIn post URL to inspect for reactions.
|
|
21
|
+
- **start** (integer, min 0) — Pagination offset (multiples of 200).
|
|
22
|
+
- **count** (integer, 0-200) — Number of likes to fetch per page (0-200, default 200). Use count=0 for a free total-only check.
|
|
23
|
+
|
|
24
|
+
### collectComments
|
|
25
|
+
|
|
26
|
+
Scrape LinkedIn post comments. Returns paginated list of commenters with comment text and URNs.
|
|
27
|
+
|
|
28
|
+
`client.linkedinScrapers.collectComments(params)`
|
|
29
|
+
|
|
30
|
+
- **postUrl** (string, required) — LinkedIn post URL to inspect for comments.
|
|
31
|
+
- **start** (integer, min 0) — Pagination offset (multiples of 100).
|
|
32
|
+
- **count** (integer, 0-100) — Number of comments to fetch per page (0-100, default 100). Use count=0 for a free total-only check.
|
|
33
|
+
- **campaignSlug** (string) — When provided, each profile includes actionsCompleted and knownDistance for campaign-aware scraping.
|
|
34
|
+
|
|
35
|
+
### collectCommentReplies
|
|
36
|
+
|
|
37
|
+
Scrape replies to a LinkedIn comment. Returns paginated replies for a specific comment URN.
|
|
38
|
+
|
|
39
|
+
`client.linkedinScrapers.collectCommentReplies(params)`
|
|
40
|
+
|
|
41
|
+
- **commentUrn** (string, required) — Comment URN returned by the comments endpoint (e.g., 'urn:li:comment:(activity:123456,789)').
|
|
42
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
43
|
+
- **count** (integer, 0-100) — Number of replies to fetch per page (0-100, default 100). Use count=0 for a free total-only check.
|
|
44
|
+
|
|
45
|
+
### collectPosts
|
|
46
|
+
|
|
47
|
+
Scrape LinkedIn profile posts. Returns paginated list of posts from a profile.
|
|
48
|
+
|
|
49
|
+
`client.linkedinScrapers.collectPosts(params)`
|
|
50
|
+
|
|
51
|
+
- **profileUrl** (string, required) — LinkedIn profile URL to fetch posts from.
|
|
52
|
+
- **count** (integer, 0-100) — Number of posts to fetch (0-100, default 20). Use count=0 for a free total-only check.
|
|
53
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
54
|
+
- **paginationToken** (string) — Pagination token from a previous response to fetch the next page.
|
|
55
|
+
|
|
56
|
+
### visitProfile
|
|
57
|
+
|
|
58
|
+
Visit LinkedIn profile and extract contact data. Distance-1 profiles cached 24h. No dedup — always executes.
|
|
59
|
+
|
|
60
|
+
`client.linkedinScrapers.visitProfile(params)`
|
|
61
|
+
|
|
62
|
+
- **profile** (string, required) — LinkedIn profile URL or profile URN.
|
|
63
|
+
- **campaignSlug** (string) — Optional campaign identifier for tracking only. No dedup — visit always executes.
|
|
64
|
+
- **includePosts** (boolean) — When true, fetches the last 5 posts from the profile. Defaults to false.
|
|
65
|
+
|
|
66
|
+
### visitCompany
|
|
67
|
+
|
|
68
|
+
Visit LinkedIn company page and extract profile data including description, industry, employee count, headquarters, and more.
|
|
69
|
+
|
|
70
|
+
`client.linkedinScrapers.visitCompany(params)`
|
|
71
|
+
|
|
72
|
+
- **companyUrl** (string, required) — LinkedIn company URL (e.g., 'https://www.linkedin.com/company/openai') or universal name (e.g., 'openai').
|
|
73
|
+
- **includeWorkplacePolicy** (boolean) — Include workplace policy data such as hybrid/remote status and benefits. Costs 1 extra credit.
|
|
74
|
+
|
|
75
|
+
## linkedinSearch
|
|
76
|
+
|
|
77
|
+
### unifiedSearch
|
|
78
|
+
|
|
79
|
+
Unified LinkedIn Search — search posts, people, companies, or jobs with a single endpoint. Supports all filter types via category selection.
|
|
80
|
+
|
|
81
|
+
`client.linkedinSearch.unifiedSearch(params)`
|
|
82
|
+
|
|
83
|
+
- **category** (string) — Type of search to perform (required unless url is provided). Values: posts, people, companies, jobs.
|
|
84
|
+
- **url** (string) — LinkedIn search URL — category and filters are extracted automatically.
|
|
85
|
+
- **keywords** (string) — Search keywords. Supports LinkedIn Boolean syntax.
|
|
86
|
+
- **sortBy** (string) — Sort order (posts & jobs). Values: relevance, date.
|
|
87
|
+
- **datePosted** (string) — Time filter (posts & jobs). Values: past-24h, past-week, past-month.
|
|
88
|
+
- **contentType** (string) — Media type filter (posts only). Values: images, videos, documents.
|
|
89
|
+
- **authorIndustry** (string[]) — Author industry IDs (posts only, resolve via /search/parameters).
|
|
90
|
+
- **authorCompany** (string[]) — Author company IDs (posts only, resolve via /search/parameters).
|
|
91
|
+
- **connectionDegree** (string[]) — Connection degree: F=1st, S=2nd, O=3rd+ (people only). Values: F, S, O.
|
|
92
|
+
- **firstName** (string) — First name filter (people only).
|
|
93
|
+
- **lastName** (string) — Last name filter (people only).
|
|
94
|
+
- **title** (string) — Job title filter, supports OR syntax (people only).
|
|
95
|
+
- **connectionOf** (string) — Profile URN to find connections of (people only).
|
|
96
|
+
- **profileLanguage** (string[]) — Profile language codes e.g. ['en','fr'] (people only).
|
|
97
|
+
- **school** (string[]) — School IDs (people only, resolve via /search/parameters).
|
|
98
|
+
- **location** (string[]) — Geo IDs (people, companies, jobs — resolve via /search/parameters).
|
|
99
|
+
- **industry** (string[]) — Industry IDs (people, companies — resolve via /search/parameters).
|
|
100
|
+
- **currentCompany** (string[]) — Current company IDs (people only, resolve via /search/parameters).
|
|
101
|
+
- **pastCompany** (string[]) — Past company IDs (people only, resolve via /search/parameters).
|
|
102
|
+
- **companySize** (string[]) — Company size: A=1-10, B=11-50, C=51-200, D=201-500, E=501-1K, F=1K-5K, G=5K-10K, H=10K+, I=self (companies only). Values: A, B, C, D, E, F, G, H, I.
|
|
103
|
+
- **jobType** (string[]) — Job type: F=Full-time, P=Part-time, C=Contract, T=Temporary, I=Internship, V=Volunteer, O=Other (jobs only). Values: F, P, C, T, I, V, O.
|
|
104
|
+
- **experienceLevel** (string[]) — Experience level: 1=Internship, 2=Entry, 3=Associate, 4=Mid-Senior, 5=Director, 6=Executive (jobs only). Values: 1, 2, 3, 4, 5, 6.
|
|
105
|
+
- **workplaceType** (string[]) — Workplace type: 1=On-site, 2=Remote, 3=Hybrid (jobs only). Values: 1, 2, 3.
|
|
106
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
107
|
+
- **count** (integer, 1-50) — Results per page (default 10, max 50).
|
|
108
|
+
|
|
109
|
+
### searchPosts
|
|
110
|
+
|
|
111
|
+
Search LinkedIn posts by keywords with optional filters for date, content type, author industry, and author company.
|
|
112
|
+
|
|
113
|
+
`client.linkedinSearch.searchPosts(params)`
|
|
114
|
+
|
|
115
|
+
- **keywords** (string, required) — Search keywords (required). Supports LinkedIn Boolean syntax.
|
|
116
|
+
- **url** (string) — Optional LinkedIn search URL. Explicit params override URL-derived values.
|
|
117
|
+
- **sortBy** (string) — Sort order. Values: relevance, date.
|
|
118
|
+
- **datePosted** (string) — Filter by publication date. Values: past-24h, past-week, past-month.
|
|
119
|
+
- **contentType** (string) — Filter by media type. Values: images, videos, documents.
|
|
120
|
+
- **authorIndustry** (string[]) — Author industry IDs (resolve via bereach_resolve_parameters).
|
|
121
|
+
- **authorCompany** (string[]) — Author company IDs (resolve via bereach_resolve_parameters).
|
|
122
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
123
|
+
- **count** (integer, 1-50) — Results per page (default 10, max 50).
|
|
124
|
+
|
|
125
|
+
### searchPeople
|
|
126
|
+
|
|
127
|
+
Search LinkedIn people by keywords, connection degree, name, title, location, industry, company, and school.
|
|
128
|
+
|
|
129
|
+
`client.linkedinSearch.searchPeople(params)`
|
|
130
|
+
|
|
131
|
+
- **keywords** (string) — Search keywords. Matches against name, headline, company, skills, and bio.
|
|
132
|
+
- **url** (string) — Optional LinkedIn search URL.
|
|
133
|
+
- **connectionDegree** (string[]) — Connection degree: F=1st, S=2nd, O=3rd+. Values: F, S, O.
|
|
134
|
+
- **firstName** (string) — Filter by first name (case-insensitive).
|
|
135
|
+
- **lastName** (string) — Filter by last name (case-insensitive).
|
|
136
|
+
- **title** (string) — Filter by job title. Supports OR syntax with '|' separator.
|
|
137
|
+
- **connectionOf** (string) — Profile URN to find connections of.
|
|
138
|
+
- **profileLanguage** (string[]) — Profile language codes (ISO 639-1).
|
|
139
|
+
- **school** (string[]) — School IDs (resolve via bereach_resolve_parameters).
|
|
140
|
+
- **location** (string[]) — Geo IDs (resolve via bereach_resolve_parameters).
|
|
141
|
+
- **industry** (string[]) — Industry IDs (resolve via bereach_resolve_parameters).
|
|
142
|
+
- **currentCompany** (string[]) — Current company IDs (resolve via bereach_resolve_parameters).
|
|
143
|
+
- **pastCompany** (string[]) — Past company IDs (resolve via bereach_resolve_parameters).
|
|
144
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
145
|
+
- **count** (integer, 1-50) — Results per page (default 10, max 50).
|
|
146
|
+
|
|
147
|
+
### searchCompanies
|
|
148
|
+
|
|
149
|
+
Search LinkedIn companies by keywords, location, industry, and company size.
|
|
150
|
+
|
|
151
|
+
`client.linkedinSearch.searchCompanies(params)`
|
|
152
|
+
|
|
153
|
+
- **keywords** (string) — Search keywords. Matches against company name, description, and specialties.
|
|
154
|
+
- **url** (string) — Optional LinkedIn search URL.
|
|
155
|
+
- **location** (string[]) — Geo IDs (resolve via bereach_resolve_parameters).
|
|
156
|
+
- **industry** (string[]) — Industry IDs (resolve via bereach_resolve_parameters).
|
|
157
|
+
- **companySize** (string[]) — Employee count: A=1-10, B=11-50, C=51-200, D=201-500, E=501-1K, F=1K-5K, G=5K-10K, H=10K+, I=self. Values: A, B, C, D, E, F, G, H, I.
|
|
158
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
159
|
+
- **count** (integer, 1-50) — Results per page (default 10, max 50).
|
|
160
|
+
|
|
161
|
+
### searchJobs
|
|
162
|
+
|
|
163
|
+
Search LinkedIn jobs by keywords, location, job type, experience level, and workplace type.
|
|
164
|
+
|
|
165
|
+
`client.linkedinSearch.searchJobs(params)`
|
|
166
|
+
|
|
167
|
+
- **keywords** (string) — Search keywords. Matches against job title, company name, and description.
|
|
168
|
+
- **url** (string) — Optional LinkedIn search URL.
|
|
169
|
+
- **location** (string[]) — Geo IDs (resolve via bereach_resolve_parameters).
|
|
170
|
+
- **datePosted** (string) — Filter by posting date. Values: past-24h, past-week, past-month.
|
|
171
|
+
- **sortBy** (string) — Sort order. Values: relevance, date.
|
|
172
|
+
- **jobType** (string[]) — Employment type: F=Full-time, P=Part-time, C=Contract, T=Temporary, I=Internship, V=Volunteer, O=Other. Values: F, P, C, T, I, V, O.
|
|
173
|
+
- **experienceLevel** (string[]) — Seniority: 1=Internship, 2=Entry, 3=Associate, 4=Mid-Senior, 5=Director, 6=Executive. Values: 1, 2, 3, 4, 5, 6.
|
|
174
|
+
- **workplaceType** (string[]) — Workplace: 1=On-site, 2=Remote, 3=Hybrid. Values: 1, 2, 3.
|
|
175
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
176
|
+
- **count** (integer, 1-50) — Results per page (default 10, max 50).
|
|
177
|
+
|
|
178
|
+
### searchByUrl
|
|
179
|
+
|
|
180
|
+
Search LinkedIn by URL. Automatically extracts category, keywords, and filters from a LinkedIn search URL.
|
|
181
|
+
|
|
182
|
+
`client.linkedinSearch.searchByUrl(params)`
|
|
183
|
+
|
|
184
|
+
- **url** (string, required) — A LinkedIn search URL. Category and filters are extracted automatically.
|
|
185
|
+
- **start** (integer, min 0) — Override pagination offset.
|
|
186
|
+
- **count** (integer, 1-50) — Override results per page (default 10, max 50).
|
|
187
|
+
|
|
188
|
+
### resolveParameters
|
|
189
|
+
|
|
190
|
+
Resolve text to LinkedIn search parameter IDs (typeahead). Prerequisite for using location, industry, company, or school filters.
|
|
191
|
+
|
|
192
|
+
`client.linkedinSearch.resolveParameters(params)`
|
|
193
|
+
|
|
194
|
+
- **type** (string, required) — Parameter type to resolve. Values: GEO, COMPANY, INDUSTRY, SCHOOL, CONNECTIONS, PEOPLE.
|
|
195
|
+
- **keywords** (string, required) — Text to resolve into LinkedIn IDs.
|
|
196
|
+
- **limit** (integer, 1-50) — Max results (default 10, max 50).
|
|
197
|
+
|
|
198
|
+
## linkedinActions
|
|
199
|
+
|
|
200
|
+
### connectProfile
|
|
201
|
+
|
|
202
|
+
Send LinkedIn connection request. Deduplicates by profile within a campaign.
|
|
203
|
+
|
|
204
|
+
`client.linkedinActions.connectProfile(params)`
|
|
205
|
+
|
|
206
|
+
- **profile** (string, required) — LinkedIn profile URL or profile URN.
|
|
207
|
+
- **campaignSlug** (string) — Campaign identifier for deduplication.
|
|
208
|
+
|
|
209
|
+
### listInvitations
|
|
210
|
+
|
|
211
|
+
List received LinkedIn connection invitations. Returns pending invitations with IDs needed to accept them.
|
|
212
|
+
|
|
213
|
+
`client.linkedinActions.listInvitations(params)`
|
|
214
|
+
|
|
215
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
216
|
+
- **count** (integer, 1-100) — Number of invitations to return (default 10, max 100).
|
|
217
|
+
|
|
218
|
+
### acceptInvitation
|
|
219
|
+
|
|
220
|
+
Accept a LinkedIn connection invitation. Requires invitationId and sharedSecret from the list invitations endpoint.
|
|
221
|
+
|
|
222
|
+
`client.linkedinActions.acceptInvitation(params)`
|
|
223
|
+
|
|
224
|
+
- **invitationId** (string, required) — Invitation ID (from the list invitations endpoint).
|
|
225
|
+
- **sharedSecret** (string, required) — Shared secret / validation token (from the list invitations endpoint).
|
|
226
|
+
- **senderProfileId** (string) — Sender's profile ID. Recommended for reliability.
|
|
227
|
+
- **firstName** (string) — Sender's first name. Recommended for reliability.
|
|
228
|
+
- **lastName** (string) — Sender's last name. Recommended for reliability.
|
|
229
|
+
|
|
230
|
+
### sendMessage
|
|
231
|
+
|
|
232
|
+
Send LinkedIn message. Rate limited to 80 messages per day.
|
|
233
|
+
|
|
234
|
+
`client.linkedinActions.sendMessage(params)`
|
|
235
|
+
|
|
236
|
+
- **profile** (string, required) — LinkedIn profile URL or profile URN.
|
|
237
|
+
- **message** (string, required) — Message content to send.
|
|
238
|
+
- **campaignSlug** (string) — Campaign identifier for deduplication.
|
|
239
|
+
|
|
240
|
+
### replyToComment
|
|
241
|
+
|
|
242
|
+
Reply to a LinkedIn comment. Use the commentUrn from the comments endpoint directly.
|
|
243
|
+
|
|
244
|
+
`client.linkedinActions.replyToComment(params)`
|
|
245
|
+
|
|
246
|
+
- **commentUrn** (string, required) — LinkedIn comment URN (e.g., 'urn:li:comment:(activity:123,456)').
|
|
247
|
+
- **message** (string, required) — Reply message text.
|
|
248
|
+
- **campaignSlug** (string) — Campaign identifier for deduplication.
|
|
249
|
+
|
|
250
|
+
### likeComment
|
|
251
|
+
|
|
252
|
+
Like (react to) a LinkedIn comment. Use the commentUrn from the comments endpoint directly.
|
|
253
|
+
|
|
254
|
+
`client.linkedinActions.likeComment(params)`
|
|
255
|
+
|
|
256
|
+
- **commentUrn** (string, required) — LinkedIn comment URN.
|
|
257
|
+
- **reactionType** (string) — Reaction type (default: LIKE). Values: LIKE, LOVE, CELEBRATE, SUPPORT, FUNNY, INSIGHTFUL.
|
|
258
|
+
- **campaignSlug** (string) — Campaign identifier for deduplication.
|
|
259
|
+
|
|
260
|
+
### publishPost
|
|
261
|
+
|
|
262
|
+
Publish or schedule a LinkedIn post. Supports text, images, mentions, and visibility control.
|
|
263
|
+
|
|
264
|
+
`client.linkedinActions.publishPost(params)`
|
|
265
|
+
|
|
266
|
+
- **text** (string, required) — Post commentary text.
|
|
267
|
+
- **mode** (string, required) — Publish mode: 'instant' publishes immediately, 'scheduled' schedules for later. Values: scheduled, instant.
|
|
268
|
+
- **scheduledAt** (integer) — Timestamp in milliseconds for scheduled posts (required when mode='scheduled').
|
|
269
|
+
- **imageUrl** (string) — URL of an image to attach to the post.
|
|
270
|
+
- **visibility** (string) — Post visibility (default: ANYONE). Values: ANYONE, CONNECTIONS.
|
|
271
|
+
- **mentions** (object[]) — Profile mentions with text positions.
|
|
272
|
+
- **campaignSlug** (string) — Campaign identifier for deduplication.
|
|
273
|
+
|
|
274
|
+
## linkedinChat
|
|
275
|
+
|
|
276
|
+
### listConversations
|
|
277
|
+
|
|
278
|
+
List LinkedIn inbox conversations with participants, last message, and read status.
|
|
279
|
+
|
|
280
|
+
`client.linkedinChat.listConversations(params)`
|
|
281
|
+
|
|
282
|
+
- **nextCursor** (string) — Pagination cursor from a previous response.
|
|
283
|
+
|
|
284
|
+
### searchConversations
|
|
285
|
+
|
|
286
|
+
Search LinkedIn inbox conversations by keyword. 0 credits.
|
|
287
|
+
|
|
288
|
+
`client.linkedinChat.searchConversations(params)`
|
|
289
|
+
|
|
290
|
+
- **keywords** (string, required) — Search keywords.
|
|
291
|
+
- **nextCursor** (string) — Pagination cursor from a previous response.
|
|
292
|
+
|
|
293
|
+
### findConversation
|
|
294
|
+
|
|
295
|
+
Find a conversation with a specific person. Direct O(1) lookup via LinkedIn's compose API. 0 credits.
|
|
296
|
+
|
|
297
|
+
`client.linkedinChat.findConversation(params)`
|
|
298
|
+
|
|
299
|
+
- **profile** (string, required) — Profile URL or URN for direct conversation lookup.
|
|
300
|
+
- **includeMessages** (boolean) — If true, also return the conversation's recent messages (0 extra credits). Default: false.
|
|
301
|
+
|
|
302
|
+
### getMessages
|
|
303
|
+
|
|
304
|
+
Read messages from a LinkedIn conversation. 0 credits.
|
|
305
|
+
|
|
306
|
+
`client.linkedinChat.getMessages(params)`
|
|
307
|
+
|
|
308
|
+
- **conversationUrn** (string, required) — Full conversation URN as returned by list/search conversations.
|
|
309
|
+
- **deliveredAt** (integer) — Timestamp (ms) of the oldest message from previous page — pass this to load older messages.
|
|
310
|
+
|
|
311
|
+
## profile
|
|
312
|
+
|
|
313
|
+
### getLinkedInProfile
|
|
314
|
+
|
|
315
|
+
Get authenticated user's LinkedIn profile from the database. No LinkedIn API call, 0 credits.
|
|
316
|
+
|
|
317
|
+
`client.profile.getLinkedInProfile(params)`
|
|
318
|
+
|
|
319
|
+
No parameters.
|
|
320
|
+
|
|
321
|
+
### refresh
|
|
322
|
+
|
|
323
|
+
Refresh authenticated user's LinkedIn profile by fetching latest data from LinkedIn.
|
|
324
|
+
|
|
325
|
+
`client.profile.refresh(params)`
|
|
326
|
+
|
|
327
|
+
No parameters.
|
|
328
|
+
|
|
329
|
+
### getPosts
|
|
330
|
+
|
|
331
|
+
Get authenticated user's LinkedIn posts.
|
|
332
|
+
|
|
333
|
+
`client.profile.getPosts(params)`
|
|
334
|
+
|
|
335
|
+
- **count** (integer, 1-100) — Number of posts to fetch (default 20, max 100).
|
|
336
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
337
|
+
- **paginationToken** (string) — Pagination token from a previous response.
|
|
338
|
+
|
|
339
|
+
### getFollowers
|
|
340
|
+
|
|
341
|
+
Get authenticated user's LinkedIn followers.
|
|
342
|
+
|
|
343
|
+
`client.profile.getFollowers(params)`
|
|
344
|
+
|
|
345
|
+
- **start** (integer, min 0) — Pagination offset (default 0).
|
|
346
|
+
- **count** (integer, 1-50) — Number of followers to fetch per page (default 10, max 50).
|
|
347
|
+
|
|
348
|
+
### getLimits
|
|
349
|
+
|
|
350
|
+
Get current LinkedIn rate limit status for all action types. 0 credits.
|
|
351
|
+
|
|
352
|
+
`client.profile.getLimits(params)`
|
|
353
|
+
|
|
354
|
+
No parameters.
|
|
355
|
+
|
|
356
|
+
### getCredits
|
|
357
|
+
|
|
358
|
+
Get current BeReach credit balance including used, remaining, and percentage. 0 credits.
|
|
359
|
+
|
|
360
|
+
`client.profile.getCredits(params)`
|
|
361
|
+
|
|
362
|
+
No parameters.
|
|
363
|
+
|
|
364
|
+
## campaigns
|
|
365
|
+
|
|
366
|
+
### getStatus
|
|
367
|
+
|
|
368
|
+
Query per-profile action status within a campaign. Returns which actions have been completed for each profile. 0 credits.
|
|
369
|
+
|
|
370
|
+
`client.campaigns.getStatus(params)`
|
|
371
|
+
|
|
372
|
+
- **campaignSlug** (string, required) — Campaign identifier.
|
|
373
|
+
- **profiles** (string[], required) — LinkedIn profile URLs or URNs to check status for.
|
|
374
|
+
|
|
375
|
+
### syncActions
|
|
376
|
+
|
|
377
|
+
Mark actions as completed without performing them on LinkedIn. Use when actions were performed outside the API. 0 credits.
|
|
378
|
+
|
|
379
|
+
`client.campaigns.syncActions(params)`
|
|
380
|
+
|
|
381
|
+
- **campaignSlug** (string, required) — Campaign identifier.
|
|
382
|
+
- **profiles** (object[], required) — Profiles and actions to mark as completed. Values: message, reply, like, visit, connect.
|
|
383
|
+
|
|
384
|
+
### getStats
|
|
385
|
+
|
|
386
|
+
Get aggregate campaign statistics: per-action counts, unique profiles, and total credits used. 0 credits.
|
|
387
|
+
|
|
388
|
+
`client.campaigns.getStats(params)`
|
|
389
|
+
|
|
390
|
+
- **campaignSlug** (string, required) — Campaign identifier.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bereach-lead-magnet
|
|
3
|
+
description: "Lead magnet workflow — deliver a resource to everyone who engages with a LinkedIn post."
|
|
4
|
+
lastUpdatedAt: 1772619338
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# BeReach Lead Magnet Skill
|
|
8
|
+
|
|
9
|
+
> Sub-skill of [BeReach](bereach-skill.md). Deliver a resource to everyone who engages with a LinkedIn post.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
Requires [BeReach SKILL.md](bereach-skill.md) (constraints, SDK setup, anti-hallucination rules) and [SDK Reference](sdk-reference.md) for method signatures and parameters. Do NOT generate code without both loaded.
|
|
14
|
+
|
|
15
|
+
## Script generation rules
|
|
16
|
+
|
|
17
|
+
All lead magnet scripts MUST be TypeScript using the `bereach` SDK (see main skill for SDK setup and rules).
|
|
18
|
+
|
|
19
|
+
## Tone
|
|
20
|
+
|
|
21
|
+
All script output and chatbot messages are **for the end user**. Write like you're talking to a non-technical person. Never mention internal concepts like "Track A", "Layer 2", "DM guard", "server-side dedup", "retryAfter", "URN", "O(1) lookup", etc. The campaign slug is fine to show — it helps the user know which campaign is running. Just say what happened in plain language: "Sent the resource to 12 people", "Skipped 3 (already received it)", "Outside active hours, will resume at 7:00".
|
|
22
|
+
|
|
23
|
+
## Objective
|
|
24
|
+
|
|
25
|
+
**Every person who engages with the post must receive the resource via DM as soon as they are distance 1.** Comment, like, invitation — the channel doesn't matter. The person gets the resource the moment a DM is possible.
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
Only ask for these on activation. If `POST_URL` is omitted, call `getPosts` and let the user pick.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
POST_URL=<LinkedIn post URL>
|
|
33
|
+
CAMPAIGN_SLUG=<unique name, e.g. "lm-feb-2026">
|
|
34
|
+
DM_TEMPLATE=<DM with {firstName} and {link} placeholders, e.g. "{firstName}, voici la ressource: {link}">
|
|
35
|
+
RESOURCE_LINK=<URL to share, or empty if DM is plain text>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`DM_TEMPLATE` supports `{firstName}` (commenter's first name) and `{link}` (the `RESOURCE_LINK` value). For richer personalization (headline, company), use visit data from the profile response when available.
|
|
39
|
+
|
|
40
|
+
Ask the user if their DM includes a link to share. If yes, store it in `RESOURCE_LINK` — this enables the DM guard (inbox dedup). If no link, set `RESOURCE_LINK` to empty — the DM guard is disabled and only server-side `campaignSlug` dedup applies.
|
|
41
|
+
|
|
42
|
+
## Tracking
|
|
43
|
+
|
|
44
|
+
BeReach is the source of truth for action tracking (`campaignSlug` dedup).
|
|
45
|
+
|
|
46
|
+
For the incremental comment check (`previousTotal`), use a local state file **per campaign** as a fallback — this survives process restarts and avoids re-processing if the cache resets. Only store the comment total counter locally; all action tracking stays on BeReach.
|
|
47
|
+
|
|
48
|
+
- **Check status**: `getStatus` with `campaignSlug` and `profiles` → returns per-profile booleans (`message`, `reply`, `like`, `visit`, `connect`)
|
|
49
|
+
- **Manual sync**: `syncActions` with `campaignSlug` and `profiles: [{ profile, actions }]` → mark actions as completed without performing them (e.g. manual DMs done outside the bot)
|
|
50
|
+
- **Automatic dedup**: every action with `campaignSlug` is tracked by BeReach. Duplicates return `duplicate: true` and cost nothing.
|
|
51
|
+
|
|
52
|
+
## Pre-flight
|
|
53
|
+
|
|
54
|
+
All 3 scripts must perform pre-flight at the start of each run:
|
|
55
|
+
|
|
56
|
+
1. `getLinkedInProfile` → cache own profileUrl/URN
|
|
57
|
+
2. First run of day: `getLimits` → check remaining quotas
|
|
58
|
+
|
|
59
|
+
## Architecture
|
|
60
|
+
|
|
61
|
+
Split into 3 independent scripts. Each script:
|
|
62
|
+
|
|
63
|
+
1. Acquires a file lock **scoped to the campaign slug + script** (e.g. using a lock file with `fs.open` exclusive flag). If locked, exit immediately — a previous run is still active.
|
|
64
|
+
2. Runs its logic.
|
|
65
|
+
3. Prints recap and releases lock on exit.
|
|
66
|
+
|
|
67
|
+
The 3 scripts share `CAMPAIGN_SLUG` — BeReach dedup ensures no action is performed twice across scripts.
|
|
68
|
+
|
|
69
|
+
### Multiple campaigns
|
|
70
|
+
|
|
71
|
+
A user can run lead magnets on several posts simultaneously. Each campaign has its own set of 3 scripts and 3 crons. Lock files and state files must include the campaign slug to avoid collisions between campaigns.
|
|
72
|
+
|
|
73
|
+
All campaigns share the same LinkedIn rate limits. If multiple campaigns run at the same time, they compete for the same quota. BeReach handles conflicts automatically.
|
|
74
|
+
|
|
75
|
+
### Script 1 — Comments
|
|
76
|
+
|
|
77
|
+
Scrape commenters and engage them.
|
|
78
|
+
|
|
79
|
+
1. **Check first** (mandatory): `collectComments` with `count: 0` → store `total`. On subsequent rounds, compare with previous `total`. If unchanged, skip this round. This saves credits and rate limit slots.
|
|
80
|
+
2. **Fetch**: `collectComments` with `campaignSlug` → response: `profiles[]` (NOT "comments"). Paginate: while `hasMore`, increment `start` by `count`.
|
|
81
|
+
3. Each profile has: `profileUrl`, `commentUrn`, `name`, `profileUrn`, `actionsCompleted: { message, reply, like, visit, connect }`, `knownDistance`, `hasReplyFromPostAuthor`
|
|
82
|
+
4. For each profile (skip own URN, skip `actionsCompleted.message === true`):
|
|
83
|
+
a. If `knownDistance` is set, use it (skip visit, saves 1 credit). Otherwise: `visitProfile` with `campaignSlug` → `memberDistance`, `pendingConnection`
|
|
84
|
+
b. Distance 1:
|
|
85
|
+
- **DM dedup** (see "DM dedup" section): check both layers. If either says "already sent", skip. Otherwise: `sendMessage` with `campaignSlug`.
|
|
86
|
+
- Skip reply if `actionsCompleted.reply === true` or `hasReplyFromPostAuthor === true`. Otherwise: `replyToComment` with `commentUrn` and `campaignSlug`
|
|
87
|
+
- Skip like if `actionsCompleted.like === true`. Otherwise: `likeComment` with `commentUrn` and `campaignSlug`
|
|
88
|
+
c. Distance 2+:
|
|
89
|
+
- Skip reply if `actionsCompleted.reply === true` or `hasReplyFromPostAuthor === true`. Otherwise: `replyToComment` with `commentUrn` and `campaignSlug` (CTA to connect)
|
|
90
|
+
- Skip like if `actionsCompleted.like === true`. Otherwise: `likeComment` with `commentUrn` and `campaignSlug`
|
|
91
|
+
- Do NOT call `connectProfile`. Script 2 delivers the resource when they connect.
|
|
92
|
+
|
|
93
|
+
### Script 2 — Invitations
|
|
94
|
+
|
|
95
|
+
Accept pending invitations and deliver the resource.
|
|
96
|
+
|
|
97
|
+
- `listInvitations` → response: `invitations[]`. Each has: `invitationId`, `sharedSecret`, `fromMember: { name, profileUrl }`. Paginate: while `total > start + count`.
|
|
98
|
+
- For each invitation:
|
|
99
|
+
1. `acceptInvitation` with `invitationId` and `sharedSecret` — only these 2 required params
|
|
100
|
+
2. `visitProfile` with `fromMember.profileUrl` and `campaignSlug`
|
|
101
|
+
3. If `memberDistance === 1`: **DM dedup** (see "DM dedup" section) → DM if not already sent.
|
|
102
|
+
|
|
103
|
+
### Script 3 — New connections
|
|
104
|
+
|
|
105
|
+
Check if distance 2+ commenters have now connected.
|
|
106
|
+
|
|
107
|
+
- Re-fetch commenters: `collectComments` with `campaignSlug` — paginate all
|
|
108
|
+
- Filter: `actionsCompleted.message === false` and `knownDistance > 1` (skip profiles where `knownDistance` is null — they haven't been visited yet and belong to Script 1)
|
|
109
|
+
- For each: `visitProfile` with `campaignSlug` → check `memberDistance` and `pendingConnection`
|
|
110
|
+
- `memberDistance === 1`: **DM dedup** (see "DM dedup" section) → DM the resource if not already sent.
|
|
111
|
+
- `pendingConnection === "pending"`: not connected yet, skip
|
|
112
|
+
|
|
113
|
+
## Dedup
|
|
114
|
+
|
|
115
|
+
### Reply guard
|
|
116
|
+
|
|
117
|
+
`hasReplyFromPostAuthor` from the comment scrape (0 credits). If `true`, skip reply.
|
|
118
|
+
|
|
119
|
+
### DM dedup (2 layers)
|
|
120
|
+
|
|
121
|
+
Check in order. If either says "already sent", skip the DM.
|
|
122
|
+
|
|
123
|
+
**Layer 1 — `actionsCompleted.message`** (free, within campaign). Already checked in the script loop — `true` → skip.
|
|
124
|
+
|
|
125
|
+
**Layer 2 — `/find` inbox lookup** (0 credits, cross-campaign). Only when `RESOURCE_LINK` is set. Call `findConversation` with `includeMessages: true` → `{ found, messages }`. `messages` is at the **top level**, not nested. If any message text contains `RESOURCE_LINK`: skip DM, call `syncActions` to mark done. If found but no link in messages: pass `messages` as context for DM tone adaptation. The function must return both skip decision and messages.
|
|
126
|
+
|
|
127
|
+
**Fail-safe**: if `findConversation` errors or all retries fail, **skip this profile** — do NOT send the DM. A failed lookup treated as "not found" causes duplicates. The profile will be retried next cron run.
|
|
128
|
+
|
|
129
|
+
## Language
|
|
130
|
+
|
|
131
|
+
For lead magnet, the default language is the **post language** — everyone commenting on a French post speaks French. Detect it from the post content, or let the user specify it. This overrides the general user-language detection from `getLinkedInProfile` (which still applies for other workflows outside lead magnet).
|
|
132
|
+
|
|
133
|
+
## Comment replies
|
|
134
|
+
|
|
135
|
+
- Distance 1: acknowledge, tell them you're sending it / it's in their DMs
|
|
136
|
+
- Distance 2+: tell them to connect with you to receive it. Never mention DMs.
|
|
137
|
+
- Language = post language (see above). The `generateReply()` function should accept a `language` parameter and maintain separate pools per language (at minimum FR and EN).
|
|
138
|
+
- Short, casual, 3-5 words.
|
|
139
|
+
- Use random selection over a pool of varied templates (at least 5 per distance per language). Vary phrasing and tone to avoid LinkedIn spam detection.
|
|
140
|
+
|
|
141
|
+
## DM content
|
|
142
|
+
|
|
143
|
+
- Use `DM_TEMPLATE` as the **base pattern** — same meaning, same `{link}`, but **always randomize phrasing**. Generate at least 10 variations of the template at boot (different wording, different greeting, different emoji, different sentence structure). Pick a random variation for each DM. Never send the same literal text twice in a row.
|
|
144
|
+
- Language = post language.
|
|
145
|
+
- **When conversation history exists** (DM guard fetched messages, no `RESOURCE_LINK` found): adapt the tone — more casual, shorter. The conversation messages are context.
|
|
146
|
+
|
|
147
|
+
## Time window
|
|
148
|
+
|
|
149
|
+
Scripts only run between **7:00 and 23:00** in the user's timezone (default). At the start of each run, check the current local time. If outside the window, exit immediately with a short log ("Outside active hours, skipping"). The user can override the window if they ask.
|
|
150
|
+
|
|
151
|
+
Detect timezone from `getLinkedInProfile` → `location` (e.g. "Paris, France" → Europe/Paris). If ambiguous, ask the user once and store it in the campaign state file.
|
|
152
|
+
|
|
153
|
+
## Pacing
|
|
154
|
+
|
|
155
|
+
Follow pacing rules from the main skill (Constraints #2). No exceptions — sleep after every SDK call.
|
|
156
|
+
|
|
157
|
+
## Cron
|
|
158
|
+
|
|
159
|
+
Three independent crons per campaign. All run every 1h by default. Cron IDs must include the campaign slug to avoid collisions when running multiple campaigns.
|
|
160
|
+
|
|
161
|
+
All crons MUST use `"sessionTarget": "spawn"` to avoid blocking the main session. Each spawned sub-agent runs the script independently and pushes the recap back to the user when done.
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{ "id": "lm-{slug}-comments", "every": "1h", "skill": "bereach", "sessionTarget": "spawn", "prompt": "Run lm-{slug}-comments.ts and report recap" }
|
|
165
|
+
{ "id": "lm-{slug}-invitations", "every": "1h", "skill": "bereach", "sessionTarget": "spawn", "prompt": "Run lm-{slug}-invitations.ts and report recap" }
|
|
166
|
+
{ "id": "lm-{slug}-connections", "every": "1h", "skill": "bereach", "sessionTarget": "spawn", "prompt": "Run lm-{slug}-connections.ts and report recap" }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Each script acquires a file lock scoped to campaign + script before running. If the lock is held (previous run still active), exit immediately — do NOT queue or wait.
|
|
170
|
+
|
|
171
|
+
### Pause / Resume
|
|
172
|
+
|
|
173
|
+
The user can pause or resume any script independently. When multiple campaigns are active, each campaign's scripts are paused/resumed separately.
|
|
174
|
+
|
|
175
|
+
### Recap (print after each run)
|
|
176
|
+
|
|
177
|
+
At the end of every run, call these two methods and print the recap:
|
|
178
|
+
|
|
179
|
+
1. `getStats` with `campaignSlug` → `{ stats: { message, reply, like, visit, connect }, totalProfiles, creditsUsed }`
|
|
180
|
+
2. `getCredits` → `{ credits: { current, limit, remaining, percentage } }`
|
|
181
|
+
|
|
182
|
+
Human-friendly, no jargon. Format:
|
|
183
|
+
|
|
184
|
+
{slug} — Recap
|
|
185
|
+
{totalProfiles} people reached
|
|
186
|
+
{stats.message} DMs sent · {stats.reply} replies · {stats.like} likes
|
|
187
|
+
Credits: {credits.current} used · {credits.remaining}/{credits.limit} remaining
|
|
188
|
+
|
|
189
|
+
You can ask me to disable features to save credits.
|
|
190
|
+
|
|
191
|
+
### Toggleable features
|
|
192
|
+
|
|
193
|
+
All enabled by default. The user can ask to disable any of these to save credits:
|
|
194
|
+
|
|
195
|
+
- **Visits** for profiles with knownDistance already set (saves 1 credit/profile)
|
|
196
|
+
- **Likes** on comments (saves 1 credit/comment)
|
|
197
|
+
- **Connection re-checks** (saves visit + DM credits)
|
|
198
|
+
- **Comment replies** (saves 1 credit/comment)
|
|
199
|
+
- **DM guard** — conversation lookup before DMing (0 credits, auto-disabled when no `RESOURCE_LINK`)
|
|
200
|
+
- **Reply guard** — hasReplyFromPostAuthor check (free, but can be skipped if user wants to re-reply)
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Bereach } from "bereach";
|
|
2
|
+
|
|
3
|
+
let instance: Bereach | null = null;
|
|
4
|
+
|
|
5
|
+
export function createClient(key?: string): Bereach {
|
|
6
|
+
if (!instance) {
|
|
7
|
+
const apiKey = key || process.env.BEREACH_API_KEY;
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"BEREACH_API_KEY is required (set in plugin config or environment)",
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
instance = new Bereach({ token: apiKey });
|
|
14
|
+
}
|
|
15
|
+
return instance;
|
|
16
|
+
}
|