salesprompter-cli 0.1.19 → 0.1.22
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 +29 -280
- package/dist/cli.js +1618 -128
- package/dist/linkedin-products.js +29 -8
- package/dist/linkedin-session.js +751 -0
- package/dist/sales-navigator.js +207 -36
- package/dist/salesnav-backfill.js +710 -0
- package/package.json +13 -1
package/dist/sales-navigator.js
CHANGED
|
@@ -80,6 +80,7 @@ export const DEFAULT_SALES_NAVIGATOR_PEOPLE_SLICE_FILTERS = [
|
|
|
80
80
|
],
|
|
81
81
|
},
|
|
82
82
|
];
|
|
83
|
+
const LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL = "https://www.linkedin.com/sales/search/people";
|
|
83
84
|
export const DEFAULT_SALES_NAVIGATOR_CRAWL_BASELINE_FILTERS = [
|
|
84
85
|
{
|
|
85
86
|
type: "FUNCTION",
|
|
@@ -92,27 +93,74 @@ export const DEFAULT_SALES_NAVIGATOR_CRAWL_BASELINE_FILTERS = [
|
|
|
92
93
|
],
|
|
93
94
|
},
|
|
94
95
|
];
|
|
96
|
+
const SALES_NAVIGATOR_FUNCTION_HINTS = [
|
|
97
|
+
{
|
|
98
|
+
name: "Human Resources",
|
|
99
|
+
filterValue: { id: "12", text: "Human Resources", selectionType: "INCLUDED" },
|
|
100
|
+
patterns: [/(^|[^a-z])(human resources|hr\b|people\b|recruit|talent acquisition|talent management)/i],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "Finance",
|
|
104
|
+
filterValue: { text: "Finance", selectionType: "INCLUDED" },
|
|
105
|
+
patterns: [/(^|[^a-z])(finance|accounting|accounts payable|accounts receivable|controller|payroll)/i],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "Information Technology",
|
|
109
|
+
filterValue: { text: "Information Technology", selectionType: "INCLUDED" },
|
|
110
|
+
patterns: [/(^|[^a-z])(information technology|it\b|devops|infrastructure|security|help desk|service desk)/i],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "Engineering",
|
|
114
|
+
filterValue: { text: "Engineering", selectionType: "INCLUDED" },
|
|
115
|
+
patterns: [/(^|[^a-z])(engineering|developer|software|platform engineer|technical lead|cto\b)/i],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "Sales",
|
|
119
|
+
filterValue: { text: "Sales", selectionType: "INCLUDED" },
|
|
120
|
+
patterns: [/(^|[^a-z])(sales|revenue|account executive|business development|sales enablement)/i],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "Marketing",
|
|
124
|
+
filterValue: { text: "Marketing", selectionType: "INCLUDED" },
|
|
125
|
+
patterns: [/(^|[^a-z])(marketing|demand generation|growth|brand|campaign)/i],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "Operations",
|
|
129
|
+
filterValue: { text: "Operations", selectionType: "INCLUDED" },
|
|
130
|
+
patterns: [/(^|[^a-z])(operations|ops\b|supply chain|procurement|vendor management)/i],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "Legal",
|
|
134
|
+
filterValue: { text: "Legal", selectionType: "INCLUDED" },
|
|
135
|
+
patterns: [/(^|[^a-z])(legal|compliance|general counsel|privacy)/i],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "Support",
|
|
139
|
+
filterValue: { text: "Support", selectionType: "INCLUDED" },
|
|
140
|
+
patterns: [/(^|[^a-z])(support|customer service|customer success|help center)/i],
|
|
141
|
+
},
|
|
142
|
+
];
|
|
95
143
|
export const DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS = [
|
|
96
144
|
{
|
|
97
145
|
key: "company-headcount",
|
|
98
146
|
filterType: "COMPANY_HEADCOUNT",
|
|
99
147
|
values: [
|
|
100
|
-
{ text: "
|
|
101
|
-
{ text: "11-50", selectionType: "INCLUDED" },
|
|
102
|
-
{ text: "
|
|
103
|
-
{ text: "
|
|
104
|
-
{ text: "501-1000", selectionType: "INCLUDED" },
|
|
105
|
-
{ text: "1001-5000", selectionType: "INCLUDED" },
|
|
106
|
-
{ text: "5001-10,000", selectionType: "INCLUDED" },
|
|
107
|
-
{ text: "10,
|
|
148
|
+
{ id: "D", text: "51-200", selectionType: "INCLUDED" },
|
|
149
|
+
{ id: "C", text: "11-50", selectionType: "INCLUDED" },
|
|
150
|
+
{ id: "E", text: "201-500", selectionType: "INCLUDED" },
|
|
151
|
+
{ id: "B", text: "1-10", selectionType: "INCLUDED" },
|
|
152
|
+
{ id: "F", text: "501-1000", selectionType: "INCLUDED" },
|
|
153
|
+
{ id: "G", text: "1001-5000", selectionType: "INCLUDED" },
|
|
154
|
+
{ id: "H", text: "5001-10,000", selectionType: "INCLUDED" },
|
|
155
|
+
{ id: "I", text: "10,000+", selectionType: "INCLUDED" },
|
|
108
156
|
],
|
|
109
157
|
},
|
|
110
158
|
{
|
|
111
159
|
key: "company-type",
|
|
112
160
|
filterType: "COMPANY_TYPE",
|
|
113
161
|
values: [
|
|
114
|
-
{ text: "Privately Held", selectionType: "INCLUDED" },
|
|
115
|
-
{ text: "Public Company", selectionType: "INCLUDED" },
|
|
162
|
+
{ id: "P", text: "Privately Held", selectionType: "INCLUDED" },
|
|
163
|
+
{ id: "C", text: "Public Company", selectionType: "INCLUDED" },
|
|
116
164
|
{ text: "Partnership", selectionType: "INCLUDED" },
|
|
117
165
|
{ text: "Self Owned", selectionType: "INCLUDED" },
|
|
118
166
|
{ text: "Non Profit", selectionType: "INCLUDED" },
|
|
@@ -124,47 +172,46 @@ export const DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS = [
|
|
|
124
172
|
key: "seniority-level",
|
|
125
173
|
filterType: "SENIORITY_LEVEL",
|
|
126
174
|
values: [
|
|
127
|
-
{ text: "Owner", selectionType: "INCLUDED" },
|
|
128
|
-
{ text: "
|
|
129
|
-
{ text: "
|
|
130
|
-
{ text: "
|
|
131
|
-
{ text: "
|
|
132
|
-
{ text: "
|
|
133
|
-
{ text: "
|
|
134
|
-
{ text: "Entry", selectionType: "INCLUDED" },
|
|
175
|
+
{ id: "320", text: "Owner / Partner", selectionType: "INCLUDED" },
|
|
176
|
+
{ id: "310", text: "CXO", selectionType: "INCLUDED" },
|
|
177
|
+
{ id: "300", text: "Vice President", selectionType: "INCLUDED" },
|
|
178
|
+
{ id: "220", text: "Director", selectionType: "INCLUDED" },
|
|
179
|
+
{ id: "210", text: "Experienced Manager", selectionType: "INCLUDED" },
|
|
180
|
+
{ id: "120", text: "Senior", selectionType: "INCLUDED" },
|
|
181
|
+
{ id: "110", text: "Entry Level", selectionType: "INCLUDED" },
|
|
135
182
|
],
|
|
136
183
|
},
|
|
137
184
|
{
|
|
138
185
|
key: "years-at-company",
|
|
139
186
|
filterType: "YEARS_AT_CURRENT_COMPANY",
|
|
140
187
|
values: [
|
|
141
|
-
{ text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
142
|
-
{ text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
143
|
-
{ text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
144
|
-
{ text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
145
|
-
{ text: "More than 10 years", selectionType: "INCLUDED" },
|
|
188
|
+
{ id: "1", text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
189
|
+
{ id: "2", text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
190
|
+
{ id: "3", text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
191
|
+
{ id: "4", text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
192
|
+
{ id: "5", text: "More than 10 years", selectionType: "INCLUDED" },
|
|
146
193
|
],
|
|
147
194
|
},
|
|
148
195
|
{
|
|
149
196
|
key: "years-in-position",
|
|
150
197
|
filterType: "YEARS_IN_CURRENT_POSITION",
|
|
151
198
|
values: [
|
|
152
|
-
{ text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
153
|
-
{ text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
154
|
-
{ text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
155
|
-
{ text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
156
|
-
{ text: "More than 10 years", selectionType: "INCLUDED" },
|
|
199
|
+
{ id: "1", text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
200
|
+
{ id: "2", text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
201
|
+
{ id: "3", text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
202
|
+
{ id: "4", text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
203
|
+
{ id: "5", text: "More than 10 years", selectionType: "INCLUDED" },
|
|
157
204
|
],
|
|
158
205
|
},
|
|
159
206
|
{
|
|
160
207
|
key: "years-of-experience",
|
|
161
208
|
filterType: "YEARS_OF_EXPERIENCE",
|
|
162
209
|
values: [
|
|
163
|
-
{ text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
164
|
-
{ text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
165
|
-
{ text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
166
|
-
{ text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
167
|
-
{ text: "More than 10 years", selectionType: "INCLUDED" },
|
|
210
|
+
{ id: "1", text: "Less than 1 year", selectionType: "INCLUDED" },
|
|
211
|
+
{ id: "2", text: "1 to 2 years", selectionType: "INCLUDED" },
|
|
212
|
+
{ id: "3", text: "3 to 5 years", selectionType: "INCLUDED" },
|
|
213
|
+
{ id: "4", text: "6 to 10 years", selectionType: "INCLUDED" },
|
|
214
|
+
{ id: "5", text: "More than 10 years", selectionType: "INCLUDED" },
|
|
168
215
|
],
|
|
169
216
|
},
|
|
170
217
|
];
|
|
@@ -306,27 +353,151 @@ export function applySalesNavigatorFiltersToQueryValue(queryValue, filters) {
|
|
|
306
353
|
}
|
|
307
354
|
return `${before}filters:List(${nextBlocks.join(",")})${after}`;
|
|
308
355
|
}
|
|
309
|
-
|
|
356
|
+
function canonicalizeSalesNavigatorQueryValue(queryValue) {
|
|
357
|
+
const { filtersContent } = findFiltersListBounds(queryValue);
|
|
358
|
+
return `(filters:List(${filtersContent}))`;
|
|
359
|
+
}
|
|
360
|
+
function canonicalizeSalesNavigatorPeopleSearchUrl(sourceQueryUrl) {
|
|
310
361
|
const url = new URL(sourceQueryUrl);
|
|
311
362
|
ensureLinkedInSalesPeopleSearchUrl(url);
|
|
312
363
|
const queryValue = url.searchParams.get("query");
|
|
313
364
|
if (!queryValue) {
|
|
314
365
|
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
315
366
|
}
|
|
367
|
+
const canonicalUrl = new URL(LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL);
|
|
368
|
+
canonicalUrl.searchParams.set("query", canonicalizeSalesNavigatorQueryValue(queryValue));
|
|
369
|
+
return canonicalUrl;
|
|
370
|
+
}
|
|
371
|
+
export function buildSalesNavigatorPeopleQuery(sourceQueryUrl, filters) {
|
|
372
|
+
const url = canonicalizeSalesNavigatorPeopleSearchUrl(sourceQueryUrl);
|
|
373
|
+
const queryValue = url.searchParams.get("query");
|
|
374
|
+
if (!queryValue) {
|
|
375
|
+
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
376
|
+
}
|
|
316
377
|
const appliedFilters = dedupeSalesNavigatorFilters(filters);
|
|
317
378
|
const slicedQueryValue = applySalesNavigatorFiltersToQueryValue(queryValue, appliedFilters);
|
|
318
379
|
url.searchParams.set("query", slicedQueryValue);
|
|
319
380
|
return {
|
|
320
|
-
sourceQueryUrl,
|
|
381
|
+
sourceQueryUrl: canonicalizeSalesNavigatorPeopleSearchUrl(sourceQueryUrl).toString(),
|
|
321
382
|
slicedQueryUrl: url.toString(),
|
|
322
383
|
appliedFilters,
|
|
323
384
|
};
|
|
324
385
|
}
|
|
386
|
+
export function buildSalesNavigatorPeopleSearchUrl(filters) {
|
|
387
|
+
const appliedFilters = dedupeSalesNavigatorFilters(filters);
|
|
388
|
+
if (appliedFilters.length === 0) {
|
|
389
|
+
throw new Error("Sales Navigator people search requires at least one filter.");
|
|
390
|
+
}
|
|
391
|
+
const url = new URL(LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL);
|
|
392
|
+
url.searchParams.set("query", `(filters:List(${appliedFilters.map((filter) => buildFilterBlock(filter)).join(",")}))`);
|
|
393
|
+
return url.toString();
|
|
394
|
+
}
|
|
395
|
+
function inferSalesNavigatorFunctionFilters(options) {
|
|
396
|
+
const haystack = [options.title, options.categoryName, options.categorySlug]
|
|
397
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
398
|
+
.join(" ");
|
|
399
|
+
for (const hint of SALES_NAVIGATOR_FUNCTION_HINTS) {
|
|
400
|
+
if (hint.patterns.some((pattern) => pattern.test(haystack))) {
|
|
401
|
+
return [
|
|
402
|
+
{
|
|
403
|
+
type: "FUNCTION",
|
|
404
|
+
values: [hint.filterValue]
|
|
405
|
+
}
|
|
406
|
+
];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return [];
|
|
410
|
+
}
|
|
411
|
+
function normalizeSalesNavigatorTitle(title) {
|
|
412
|
+
return title
|
|
413
|
+
.replace(/[“”"']+/g, "")
|
|
414
|
+
.replace(/\s*[|/]+\s*/g, " / ")
|
|
415
|
+
.replace(/\s*&\s*/g, " & ")
|
|
416
|
+
.replace(/[–—]+/g, "-")
|
|
417
|
+
.replace(/\s+/g, " ")
|
|
418
|
+
.replace(/^[\s\-:;,]+|[\s\-:;,]+$/g, "")
|
|
419
|
+
.trim();
|
|
420
|
+
}
|
|
421
|
+
function canonicalizeSalesNavigatorTitleKey(title) {
|
|
422
|
+
return normalizeSalesNavigatorTitle(title)
|
|
423
|
+
.toLowerCase()
|
|
424
|
+
.replace(/&/g, " and ")
|
|
425
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
426
|
+
.replace(/\s+/g, " ")
|
|
427
|
+
.trim();
|
|
428
|
+
}
|
|
429
|
+
export function deriveSalesNavigatorTitleQuerySeeds(options) {
|
|
430
|
+
const rankedTitles = new Map();
|
|
431
|
+
let firstSeenIndex = 0;
|
|
432
|
+
for (const item of options.items) {
|
|
433
|
+
const sourceProduct = options.sourceProductUrl === item.productUrl;
|
|
434
|
+
for (const intendedRole of item.intendedRoles) {
|
|
435
|
+
const title = normalizeSalesNavigatorTitle(intendedRole);
|
|
436
|
+
if (title.length === 0) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const normalizedTitle = canonicalizeSalesNavigatorTitleKey(title);
|
|
440
|
+
const existing = rankedTitles.get(normalizedTitle);
|
|
441
|
+
if (existing) {
|
|
442
|
+
existing.matchedProductCount += 1;
|
|
443
|
+
existing.sourceProduct = existing.sourceProduct || sourceProduct;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
rankedTitles.set(normalizedTitle, {
|
|
447
|
+
title,
|
|
448
|
+
matchedProductCount: 1,
|
|
449
|
+
sourceProduct,
|
|
450
|
+
firstSeenIndex,
|
|
451
|
+
categoryName: item.category?.name,
|
|
452
|
+
categorySlug: item.category?.slug
|
|
453
|
+
});
|
|
454
|
+
firstSeenIndex += 1;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const orderedTitles = [...rankedTitles.values()].sort((left, right) => {
|
|
458
|
+
if (left.sourceProduct !== right.sourceProduct) {
|
|
459
|
+
return left.sourceProduct ? -1 : 1;
|
|
460
|
+
}
|
|
461
|
+
if (left.sourceProduct && right.sourceProduct && left.firstSeenIndex !== right.firstSeenIndex) {
|
|
462
|
+
return left.firstSeenIndex - right.firstSeenIndex;
|
|
463
|
+
}
|
|
464
|
+
if (left.matchedProductCount !== right.matchedProductCount) {
|
|
465
|
+
return right.matchedProductCount - left.matchedProductCount;
|
|
466
|
+
}
|
|
467
|
+
if (left.firstSeenIndex !== right.firstSeenIndex) {
|
|
468
|
+
return left.firstSeenIndex - right.firstSeenIndex;
|
|
469
|
+
}
|
|
470
|
+
return left.title.localeCompare(right.title);
|
|
471
|
+
});
|
|
472
|
+
const limitedTitles = options.titleLimit && options.titleLimit > 0
|
|
473
|
+
? orderedTitles.slice(0, options.titleLimit)
|
|
474
|
+
: orderedTitles;
|
|
475
|
+
return limitedTitles.map((entry) => {
|
|
476
|
+
const appliedFilters = dedupeSalesNavigatorFilters([
|
|
477
|
+
{
|
|
478
|
+
type: "CURRENT_TITLE",
|
|
479
|
+
values: [{ text: entry.title, selectionType: "INCLUDED" }]
|
|
480
|
+
},
|
|
481
|
+
...inferSalesNavigatorFunctionFilters({
|
|
482
|
+
title: entry.title,
|
|
483
|
+
categoryName: entry.categoryName,
|
|
484
|
+
categorySlug: entry.categorySlug
|
|
485
|
+
})
|
|
486
|
+
]);
|
|
487
|
+
return {
|
|
488
|
+
title: entry.title,
|
|
489
|
+
queryUrl: buildSalesNavigatorPeopleSearchUrl(appliedFilters),
|
|
490
|
+
appliedFilters,
|
|
491
|
+
matchedProductCount: entry.matchedProductCount,
|
|
492
|
+
sourceProduct: entry.sourceProduct
|
|
493
|
+
};
|
|
494
|
+
});
|
|
495
|
+
}
|
|
325
496
|
export function buildSalesNavigatorPeopleSlice(sourceQueryUrl, filters = DEFAULT_SALES_NAVIGATOR_PEOPLE_SLICE_FILTERS) {
|
|
326
497
|
return buildSalesNavigatorPeopleQuery(sourceQueryUrl, filters);
|
|
327
498
|
}
|
|
328
499
|
export function createSalesNavigatorCrawlSeed(options) {
|
|
329
|
-
const sliced = buildSalesNavigatorPeopleQuery(options.sourceQueryUrl, options.baselineFilters ??
|
|
500
|
+
const sliced = buildSalesNavigatorPeopleQuery(options.sourceQueryUrl, options.baselineFilters ?? []);
|
|
330
501
|
return {
|
|
331
502
|
...sliced,
|
|
332
503
|
depth: 0,
|