salesprompter-cli 0.1.56 → 0.1.58
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 +2 -2
- package/dist/cli.js +218 -23
- package/dist/sales-navigator.js +39 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,9 +66,9 @@ salesprompter companies:resolve-linkedin-urls --in ./companies.txt --out ./compa
|
|
|
66
66
|
salesprompter contacts:resolve-emails --in ./contacts.tsv --out-dir ./email-run --dry-run
|
|
67
67
|
|
|
68
68
|
# Collect a Sales Navigator people search into the active workspace
|
|
69
|
+
# Oversized or pagination-short searches are split by company headcount and deduplicated.
|
|
69
70
|
salesprompter leads:collect \
|
|
70
|
-
--linkedin-url "$SALES_NAV_PEOPLE_URL"
|
|
71
|
-
--max-results 2000
|
|
71
|
+
--linkedin-url "$SALES_NAV_PEOPLE_URL"
|
|
72
72
|
|
|
73
73
|
# Enrich one or more stored imports: Sales Navigator company -> official domain -> Hunter email
|
|
74
74
|
# This updates workspace data only. It does not create or start outreach.
|
package/dist/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ import { crawlLinkedInProductCategory } from "./linkedin-products.js";
|
|
|
31
31
|
import { claimLinkedInSessionCookieForCli, claimValidatedSalesNavigatorSessionCookieForCli, createLinkedInSessionSupabaseClient, recordLinkedInSessionCookieAudit, resolveConfiguredEnvValue } from "./linkedin-session.js";
|
|
32
32
|
import { buildLeadlistsFunnelQueries } from "./leadlists-funnel.js";
|
|
33
33
|
import { readJsonFile, splitCsv, writeJsonFile, writeTextFile } from "./io.js";
|
|
34
|
-
import { buildSalesNavigatorCrawlPreview, buildSalesNavigatorAccountQuery, createSalesNavigatorCrawlSeed, DEFAULT_SALES_NAVIGATOR_ACCOUNT_CRAWL_DIMENSIONS, DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS, buildSalesNavigatorPeopleSearchUrl, buildSalesNavigatorPeopleSlice, deriveSalesNavigatorTitleQuerySeeds, executeSalesNavigatorAdaptiveCrawl, expandSalesNavigatorCrawlAttempt, SalesNavigatorCrawlStopError, SalesNavigatorSliceTooBroadError } from "./sales-navigator.js";
|
|
34
|
+
import { buildSalesNavigatorCrawlPreview, buildSalesNavigatorAccountQuery, buildSalesNavigatorResidualCrawlAttempt, createSalesNavigatorCrawlSeed, DEFAULT_SALES_NAVIGATOR_ACCOUNT_CRAWL_DIMENSIONS, DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS, buildSalesNavigatorPeopleSearchUrl, buildSalesNavigatorPeopleSlice, deriveSalesNavigatorTitleQuerySeeds, executeSalesNavigatorAdaptiveCrawl, expandSalesNavigatorCrawlAttempt, getSalesNavigatorPeopleAccessibleResultLimit, salesNavigatorPeopleSearchHasFilter, SalesNavigatorCrawlStopError, SalesNavigatorSliceTooBroadError } from "./sales-navigator.js";
|
|
35
35
|
import { buildSalesNavigatorHistoricalBackfillPlan, ensureSalesNavigatorPeopleCount, resolveSalesNavigatorHistoricalBackfillConfig, resolveSalesNavigatorHistoricalBackfillResumeState, resolveSalesNavigatorHistoricalBackfillOrgId, salesNavigatorHistoricalBackfillDefaults } from "./salesnav-backfill.js";
|
|
36
36
|
const require = createRequire(import.meta.url);
|
|
37
37
|
const { version: packageVersion } = require("../package.json");
|
|
@@ -8676,6 +8676,18 @@ async function fetchAllLocalSalesNavigatorPeople(parsedRequest, options) {
|
|
|
8676
8676
|
fetchedPages += 1;
|
|
8677
8677
|
totalResults =
|
|
8678
8678
|
totalResults ?? extractLocalSalesNavigatorTotalResults(responseBody);
|
|
8679
|
+
if (totalResults != null &&
|
|
8680
|
+
options.maxAllowedResults != null &&
|
|
8681
|
+
totalResults > Math.max(1, Math.trunc(options.maxAllowedResults))) {
|
|
8682
|
+
throw new SalesNavigatorSliceTooBroadError(`People slice has ${totalResults} results, above ${options.maxAllowedResults}.`, {
|
|
8683
|
+
totalResults,
|
|
8684
|
+
details: {
|
|
8685
|
+
fetchedPages,
|
|
8686
|
+
retryCount,
|
|
8687
|
+
totalDelayMs,
|
|
8688
|
+
},
|
|
8689
|
+
});
|
|
8690
|
+
}
|
|
8679
8691
|
const pagePeople = normalizeLocalSalesNavigatorPeople(responseBody, pageRequest.url);
|
|
8680
8692
|
let added = 0;
|
|
8681
8693
|
for (const person of pagePeople) {
|
|
@@ -8712,6 +8724,151 @@ async function fetchAllLocalSalesNavigatorPeople(parsedRequest, options) {
|
|
|
8712
8724
|
},
|
|
8713
8725
|
};
|
|
8714
8726
|
}
|
|
8727
|
+
function canonicalLocalSalesNavigatorLeadKey(person) {
|
|
8728
|
+
try {
|
|
8729
|
+
const url = new URL(person.profileUrl);
|
|
8730
|
+
url.hash = "";
|
|
8731
|
+
url.search = "";
|
|
8732
|
+
url.hostname = url.hostname.toLowerCase();
|
|
8733
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
8734
|
+
return url.toString();
|
|
8735
|
+
}
|
|
8736
|
+
catch {
|
|
8737
|
+
return person.profileUrl.trim().toLowerCase();
|
|
8738
|
+
}
|
|
8739
|
+
}
|
|
8740
|
+
function localPeopleSplitDimensions(sourceQueryUrl) {
|
|
8741
|
+
return DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS.filter((dimension) => !salesNavigatorPeopleSearchHasFilter(sourceQueryUrl, dimension.filterType));
|
|
8742
|
+
}
|
|
8743
|
+
async function fetchCompleteLocalSalesNavigatorPeople(sourceQueryUrl, requestHeaders, options) {
|
|
8744
|
+
const root = createSalesNavigatorCrawlSeed({
|
|
8745
|
+
sourceQueryUrl,
|
|
8746
|
+
baselineFilters: [],
|
|
8747
|
+
maxResultsPerSearch: options.maxResultsPerSearch,
|
|
8748
|
+
numberOfProfiles: options.maxResultsPerSearch,
|
|
8749
|
+
slicePreset: "local-salesnav-people-adaptive",
|
|
8750
|
+
searchType: "people",
|
|
8751
|
+
});
|
|
8752
|
+
const dimensions = localPeopleSplitDimensions(root.sourceQueryUrl);
|
|
8753
|
+
const queue = [root];
|
|
8754
|
+
const queuedUrls = new Set([root.slicedQueryUrl]);
|
|
8755
|
+
const peopleByKey = new Map();
|
|
8756
|
+
const slices = [];
|
|
8757
|
+
let rootTotalResults = null;
|
|
8758
|
+
let fetchedPages = 0;
|
|
8759
|
+
let totalDelayMs = 0;
|
|
8760
|
+
let retryCount = 0;
|
|
8761
|
+
while (queue.length > 0) {
|
|
8762
|
+
const attempt = queue.shift();
|
|
8763
|
+
const parsedRequest = {
|
|
8764
|
+
url: buildSalesNavigatorLeadApiUrlFromSearchUrl(attempt.slicedQueryUrl, options.pageSize),
|
|
8765
|
+
headers: requestHeaders,
|
|
8766
|
+
};
|
|
8767
|
+
let collected = null;
|
|
8768
|
+
let tooBroadTotal = null;
|
|
8769
|
+
let probeFetchedPages = 0;
|
|
8770
|
+
try {
|
|
8771
|
+
collected = await fetchAllLocalSalesNavigatorPeople(parsedRequest, {
|
|
8772
|
+
requestedProfiles: options.maxResultsPerSearch,
|
|
8773
|
+
maxAllowedResults: options.maxResultsPerSearch,
|
|
8774
|
+
pageSize: options.pageSize,
|
|
8775
|
+
pageDelayMinMs: options.pageDelayMinMs,
|
|
8776
|
+
pageDelayMaxMs: options.pageDelayMaxMs,
|
|
8777
|
+
retry: options.retry,
|
|
8778
|
+
executeRequest: options.executeRequest,
|
|
8779
|
+
});
|
|
8780
|
+
fetchedPages += collected.fetchedPages;
|
|
8781
|
+
totalDelayMs += collected.pacing.totalDelayMs;
|
|
8782
|
+
retryCount += collected.pacing.retryCount;
|
|
8783
|
+
if (attempt.depth === 0) {
|
|
8784
|
+
rootTotalResults = collected.totalResults;
|
|
8785
|
+
}
|
|
8786
|
+
}
|
|
8787
|
+
catch (error) {
|
|
8788
|
+
if (!(error instanceof SalesNavigatorSliceTooBroadError)) {
|
|
8789
|
+
throw error;
|
|
8790
|
+
}
|
|
8791
|
+
tooBroadTotal = error.totalResults;
|
|
8792
|
+
const details = error.details && typeof error.details === "object"
|
|
8793
|
+
? error.details
|
|
8794
|
+
: {};
|
|
8795
|
+
probeFetchedPages = Number(details.fetchedPages ?? 1);
|
|
8796
|
+
fetchedPages += probeFetchedPages;
|
|
8797
|
+
retryCount += Number(details.retryCount ?? 0);
|
|
8798
|
+
totalDelayMs += Number(details.totalDelayMs ?? 0);
|
|
8799
|
+
if (attempt.depth === 0) {
|
|
8800
|
+
rootTotalResults = tooBroadTotal;
|
|
8801
|
+
}
|
|
8802
|
+
}
|
|
8803
|
+
const needsSplit = tooBroadTotal != null ||
|
|
8804
|
+
(collected?.totalResults == null &&
|
|
8805
|
+
collected != null &&
|
|
8806
|
+
collected.people.length >= options.maxResultsPerSearch) ||
|
|
8807
|
+
(collected?.totalResults != null &&
|
|
8808
|
+
collected.people.length < collected.totalResults);
|
|
8809
|
+
if (needsSplit) {
|
|
8810
|
+
const usedDimensionKeys = new Set(attempt.splitTrail.map((entry) => entry.key.replace(/-residual$/, "")));
|
|
8811
|
+
const nextDimension = dimensions.find((dimension) => !usedDimensionKeys.has(dimension.key));
|
|
8812
|
+
if (!nextDimension) {
|
|
8813
|
+
throw new Error(`Sales Navigator search still exceeds the ${options.maxResultsPerSearch}-person window after all safe split dimensions were used. Narrow the source search further.`);
|
|
8814
|
+
}
|
|
8815
|
+
const children = expandSalesNavigatorCrawlAttempt(attempt, nextDimension, "people");
|
|
8816
|
+
if (nextDimension.supportsResidualExclusion !== false) {
|
|
8817
|
+
children.push(buildSalesNavigatorResidualCrawlAttempt(attempt, nextDimension, "people"));
|
|
8818
|
+
}
|
|
8819
|
+
for (const child of children) {
|
|
8820
|
+
if (queuedUrls.has(child.slicedQueryUrl))
|
|
8821
|
+
continue;
|
|
8822
|
+
queuedUrls.add(child.slicedQueryUrl);
|
|
8823
|
+
queue.push(child);
|
|
8824
|
+
}
|
|
8825
|
+
slices.push({
|
|
8826
|
+
depth: attempt.depth,
|
|
8827
|
+
splitTrail: attempt.splitTrail,
|
|
8828
|
+
totalResults: tooBroadTotal ?? collected?.totalResults ?? null,
|
|
8829
|
+
collected: 0,
|
|
8830
|
+
fetchedPages: probeFetchedPages || collected?.fetchedPages || 0,
|
|
8831
|
+
status: "split",
|
|
8832
|
+
splitDimension: nextDimension.key,
|
|
8833
|
+
});
|
|
8834
|
+
continue;
|
|
8835
|
+
}
|
|
8836
|
+
if (!collected) {
|
|
8837
|
+
throw new Error("Sales Navigator slice did not return a collection result.");
|
|
8838
|
+
}
|
|
8839
|
+
for (const person of collected.people) {
|
|
8840
|
+
peopleByKey.set(canonicalLocalSalesNavigatorLeadKey(person), person);
|
|
8841
|
+
}
|
|
8842
|
+
slices.push({
|
|
8843
|
+
depth: attempt.depth,
|
|
8844
|
+
splitTrail: attempt.splitTrail,
|
|
8845
|
+
totalResults: collected.totalResults,
|
|
8846
|
+
collected: collected.people.length,
|
|
8847
|
+
fetchedPages: collected.fetchedPages,
|
|
8848
|
+
status: "collected",
|
|
8849
|
+
splitDimension: null,
|
|
8850
|
+
});
|
|
8851
|
+
}
|
|
8852
|
+
const people = [...peopleByKey.values()];
|
|
8853
|
+
if (rootTotalResults != null && people.length < rootTotalResults) {
|
|
8854
|
+
throw new Error(`Adaptive collection found ${people.length} unique people but the root search reported ${rootTotalResults}. No import was written because coverage is incomplete.`);
|
|
8855
|
+
}
|
|
8856
|
+
return {
|
|
8857
|
+
people,
|
|
8858
|
+
totalResults: rootTotalResults,
|
|
8859
|
+
rootTotalResults,
|
|
8860
|
+
fetchedPages,
|
|
8861
|
+
sourceQueryUrl,
|
|
8862
|
+
collectionMode: "adaptive",
|
|
8863
|
+
slices,
|
|
8864
|
+
pacing: {
|
|
8865
|
+
delayMinMs: options.pageDelayMinMs,
|
|
8866
|
+
delayMaxMs: options.pageDelayMaxMs,
|
|
8867
|
+
totalDelayMs,
|
|
8868
|
+
retryCount,
|
|
8869
|
+
},
|
|
8870
|
+
};
|
|
8871
|
+
}
|
|
8715
8872
|
async function fetchAllLocalSalesNavigatorAccounts(parsedRequest, options) {
|
|
8716
8873
|
const accounts = [];
|
|
8717
8874
|
const seen = new Set();
|
|
@@ -8913,6 +9070,7 @@ async function importLocalSalesNavigatorPeopleViaApp(session, payload) {
|
|
|
8913
9070
|
fetchedPages: payload.fetchedPages,
|
|
8914
9071
|
people: payload.people,
|
|
8915
9072
|
rawPayload: {
|
|
9073
|
+
...payload.rawPayload,
|
|
8916
9074
|
clientId: workspaceClientId ?? undefined,
|
|
8917
9075
|
client_id: workspaceClientId ?? undefined,
|
|
8918
9076
|
leadList: workspaceClientId
|
|
@@ -13383,15 +13541,8 @@ program
|
|
|
13383
13541
|
}
|
|
13384
13542
|
});
|
|
13385
13543
|
});
|
|
13386
|
-
const SALES_NAVIGATOR_PEOPLE_MAX_RESULTS = 2_000;
|
|
13387
13544
|
async function runSalesNavigatorPeopleCollectCommand(options) {
|
|
13388
13545
|
const linkedInUrl = z.string().url().parse(options.linkedinUrl);
|
|
13389
|
-
const maxResults = z.coerce
|
|
13390
|
-
.number()
|
|
13391
|
-
.int()
|
|
13392
|
-
.min(1)
|
|
13393
|
-
.max(SALES_NAVIGATOR_PEOPLE_MAX_RESULTS)
|
|
13394
|
-
.parse(options.maxResults);
|
|
13395
13546
|
const pageSize = z.coerce.number().int().min(1).max(100).parse(options.pageSize);
|
|
13396
13547
|
const normalizedSourceUrl = (() => {
|
|
13397
13548
|
const source = new URL(linkedInUrl);
|
|
@@ -13401,12 +13552,24 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
|
|
|
13401
13552
|
}
|
|
13402
13553
|
return source.toString();
|
|
13403
13554
|
})();
|
|
13555
|
+
const accessibleResultLimit = getSalesNavigatorPeopleAccessibleResultLimit(normalizedSourceUrl);
|
|
13556
|
+
const maxResults = options.maxResults == null
|
|
13557
|
+
? accessibleResultLimit
|
|
13558
|
+
: z.coerce
|
|
13559
|
+
.number()
|
|
13560
|
+
.int()
|
|
13561
|
+
.min(1)
|
|
13562
|
+
.max(accessibleResultLimit)
|
|
13563
|
+
.parse(options.maxResults);
|
|
13564
|
+
const complete = Boolean(options.complete || options.maxResults == null);
|
|
13404
13565
|
if (options.dryRun) {
|
|
13405
13566
|
const payload = {
|
|
13406
13567
|
status: "ok",
|
|
13407
13568
|
dryRun: true,
|
|
13408
13569
|
linkedInUrl: normalizedSourceUrl,
|
|
13409
13570
|
maxResults,
|
|
13571
|
+
accessibleResultLimit,
|
|
13572
|
+
complete,
|
|
13410
13573
|
destination: "/leads/cli-imports"
|
|
13411
13574
|
};
|
|
13412
13575
|
if (options.out)
|
|
@@ -13438,24 +13601,35 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
|
|
|
13438
13601
|
: await createLocalAccountSearchBrowserRelay(browserRelayPort);
|
|
13439
13602
|
let collected;
|
|
13440
13603
|
try {
|
|
13441
|
-
|
|
13442
|
-
requestedProfiles: maxResults,
|
|
13604
|
+
const collectorOptions = {
|
|
13443
13605
|
pageSize,
|
|
13444
13606
|
pageDelayMinMs,
|
|
13445
13607
|
pageDelayMaxMs,
|
|
13446
13608
|
retry: {
|
|
13447
13609
|
maxRetries: 2,
|
|
13448
13610
|
retryBaseDelayMs: 2_000,
|
|
13449
|
-
retryMaxDelayMs: 10_000
|
|
13611
|
+
retryMaxDelayMs: 10_000,
|
|
13450
13612
|
},
|
|
13451
13613
|
executeRequest: browserRelay
|
|
13452
13614
|
? (request) => browserRelay.request(request)
|
|
13453
|
-
: undefined
|
|
13454
|
-
}
|
|
13615
|
+
: undefined,
|
|
13616
|
+
};
|
|
13617
|
+
collected = complete
|
|
13618
|
+
? await fetchCompleteLocalSalesNavigatorPeople(normalizedSourceUrl, parsedRequest.headers, {
|
|
13619
|
+
maxResultsPerSearch: maxResults,
|
|
13620
|
+
...collectorOptions,
|
|
13621
|
+
})
|
|
13622
|
+
: await fetchAllLocalSalesNavigatorPeople(parsedRequest, {
|
|
13623
|
+
requestedProfiles: maxResults,
|
|
13624
|
+
...collectorOptions,
|
|
13625
|
+
});
|
|
13455
13626
|
}
|
|
13456
13627
|
finally {
|
|
13457
13628
|
await browserRelay?.close();
|
|
13458
13629
|
}
|
|
13630
|
+
const adaptiveCollection = "collectionMode" in collected && collected.collectionMode === "adaptive"
|
|
13631
|
+
? collected
|
|
13632
|
+
: null;
|
|
13459
13633
|
const imported = await importLocalSalesNavigatorPeopleViaApp(session, {
|
|
13460
13634
|
sourceQueryUrl: normalizedSourceUrl,
|
|
13461
13635
|
people: collected.people,
|
|
@@ -13463,8 +13637,21 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
|
|
|
13463
13637
|
fetchedPages: collected.fetchedPages,
|
|
13464
13638
|
pacing: collected.pacing,
|
|
13465
13639
|
maxResultsPerSearch: maxResults,
|
|
13466
|
-
numberOfProfiles: maxResults,
|
|
13467
|
-
slicePreset:
|
|
13640
|
+
numberOfProfiles: adaptiveCollection ? collected.people.length : maxResults,
|
|
13641
|
+
slicePreset: adaptiveCollection
|
|
13642
|
+
? "local-salesnav-people-adaptive"
|
|
13643
|
+
: "local-salesnav-people",
|
|
13644
|
+
rawPayload: adaptiveCollection
|
|
13645
|
+
? {
|
|
13646
|
+
collectionMode: adaptiveCollection.collectionMode,
|
|
13647
|
+
accessibleResultLimit,
|
|
13648
|
+
sliceCount: adaptiveCollection.slices.filter((slice) => slice.status === "collected").length,
|
|
13649
|
+
slices: adaptiveCollection.slices,
|
|
13650
|
+
}
|
|
13651
|
+
: {
|
|
13652
|
+
collectionMode: "bounded",
|
|
13653
|
+
accessibleResultLimit,
|
|
13654
|
+
},
|
|
13468
13655
|
});
|
|
13469
13656
|
const payload = {
|
|
13470
13657
|
status: "ok",
|
|
@@ -13480,6 +13667,10 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
|
|
|
13480
13667
|
? null
|
|
13481
13668
|
: Math.max(0, collected.totalResults - collected.people.length),
|
|
13482
13669
|
fetchedPages: collected.fetchedPages,
|
|
13670
|
+
accessibleResultLimit,
|
|
13671
|
+
collectionMode: adaptiveCollection ? "adaptive" : "bounded",
|
|
13672
|
+
sliceCount: adaptiveCollection?.slices.filter((slice) => slice.status === "collected")
|
|
13673
|
+
.length ?? 1,
|
|
13483
13674
|
runId: imported.runId,
|
|
13484
13675
|
imported: imported.imported,
|
|
13485
13676
|
upserted: imported.upserted,
|
|
@@ -14247,12 +14438,15 @@ async function runSalesNavigatorImportEnrichmentCommand(options) {
|
|
|
14247
14438
|
async function runAffiliateLaunchCommand(options) {
|
|
14248
14439
|
const affiliateLink = z.string().url().parse(options.affiliateLink);
|
|
14249
14440
|
const linkedInUrl = z.string().url().parse(options.linkedinUrl);
|
|
14250
|
-
const
|
|
14251
|
-
|
|
14252
|
-
|
|
14253
|
-
.
|
|
14254
|
-
|
|
14255
|
-
|
|
14441
|
+
const accessibleResultLimit = getSalesNavigatorPeopleAccessibleResultLimit(linkedInUrl);
|
|
14442
|
+
const maxResults = options.maxResults == null
|
|
14443
|
+
? accessibleResultLimit
|
|
14444
|
+
: z.coerce
|
|
14445
|
+
.number()
|
|
14446
|
+
.int()
|
|
14447
|
+
.min(1)
|
|
14448
|
+
.max(accessibleResultLimit)
|
|
14449
|
+
.parse(options.maxResults);
|
|
14256
14450
|
const session = await requireAuthSession();
|
|
14257
14451
|
const isSalesNavigatorPeopleSearch = new URL(linkedInUrl).pathname.includes("/sales/search/people");
|
|
14258
14452
|
let localResults;
|
|
@@ -14325,7 +14519,7 @@ function addAffiliateAudienceOptions(command) {
|
|
|
14325
14519
|
return command
|
|
14326
14520
|
.requiredOption("--affiliate-link <url>", "Affiliate link to promote")
|
|
14327
14521
|
.requiredOption("--linkedin-url <url>", "Sales Navigator people or LinkedIn content search URL")
|
|
14328
|
-
.option("--max-results <number>", "Maximum audience results (people searches:
|
|
14522
|
+
.option("--max-results <number>", "Maximum audience results (people searches: 2500; Connections of: 1000)")
|
|
14329
14523
|
.option("--curl-file <path>", "Optional copied Sales Navigator Lead Search curl request")
|
|
14330
14524
|
.option("--browser-relay-port <number>", "Use the signed-in browser through a loopback relay")
|
|
14331
14525
|
.option("--page-size <number>", "Direct Sales Navigator page size", "100")
|
|
@@ -14339,7 +14533,8 @@ program
|
|
|
14339
14533
|
.alias("leads:collect")
|
|
14340
14534
|
.description("Collect a Sales Navigator people search into the active workspace.")
|
|
14341
14535
|
.requiredOption("--linkedin-url <url>", "Sales Navigator people search URL")
|
|
14342
|
-
.option("--max-results <number>", "
|
|
14536
|
+
.option("--max-results <number>", "Bounded collection size; defaults to exhaustive collection with a 2500-person window")
|
|
14537
|
+
.option("--complete", "Split oversized searches by company headcount and deduplicate the complete result", false)
|
|
14343
14538
|
.option("--curl-file <path>", "Optional copied Sales Navigator Lead Search curl request")
|
|
14344
14539
|
.option("--browser-relay-port <number>", "Use the signed-in browser through a loopback relay")
|
|
14345
14540
|
.option("--page-size <number>", "Direct Sales Navigator page size", "100")
|
package/dist/sales-navigator.js
CHANGED
|
@@ -92,6 +92,45 @@ export const DEFAULT_SALES_NAVIGATOR_PEOPLE_SLICE_FILTERS = [
|
|
|
92
92
|
];
|
|
93
93
|
const LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL = "https://www.linkedin.com/sales/search/people";
|
|
94
94
|
const LINKEDIN_SALES_NAVIGATOR_ACCOUNT_SEARCH_URL = "https://www.linkedin.com/sales/search/company";
|
|
95
|
+
export const SALES_NAVIGATOR_PEOPLE_MAX_RESULTS = 2_500;
|
|
96
|
+
export const SALES_NAVIGATOR_CONNECTIONS_OF_MAX_RESULTS = 1_000;
|
|
97
|
+
export const SALES_NAVIGATOR_ACCOUNT_MAX_RESULTS = 1_000;
|
|
98
|
+
function decodeSalesNavigatorQueryValue(value) {
|
|
99
|
+
let decoded = value;
|
|
100
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
101
|
+
try {
|
|
102
|
+
const next = decodeURIComponent(decoded);
|
|
103
|
+
if (next === decoded)
|
|
104
|
+
break;
|
|
105
|
+
decoded = next;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return decoded;
|
|
112
|
+
}
|
|
113
|
+
function getSalesNavigatorQueryValue(sourceQueryUrl) {
|
|
114
|
+
const url = new URL(sourceQueryUrl);
|
|
115
|
+
const queryFromSearch = url.searchParams.get("query");
|
|
116
|
+
const queryFromHash = url.hash.startsWith("#query=")
|
|
117
|
+
? new URLSearchParams(url.hash.slice(1)).get("query")
|
|
118
|
+
: null;
|
|
119
|
+
return decodeSalesNavigatorQueryValue(queryFromSearch ?? queryFromHash ?? "");
|
|
120
|
+
}
|
|
121
|
+
export function isSalesNavigatorConnectionsOfSearch(sourceQueryUrl) {
|
|
122
|
+
const queryValue = getSalesNavigatorQueryValue(sourceQueryUrl);
|
|
123
|
+
return /(?:type\s*:\s*)?(?:CONNECTIONS?_OF|CONNECTION_OF|TEAMLINK_CONNECTION_OF)\b/i.test(queryValue) || /\bconnections?\s+of\b/i.test(queryValue);
|
|
124
|
+
}
|
|
125
|
+
export function getSalesNavigatorPeopleAccessibleResultLimit(sourceQueryUrl) {
|
|
126
|
+
return isSalesNavigatorConnectionsOfSearch(sourceQueryUrl)
|
|
127
|
+
? SALES_NAVIGATOR_CONNECTIONS_OF_MAX_RESULTS
|
|
128
|
+
: SALES_NAVIGATOR_PEOPLE_MAX_RESULTS;
|
|
129
|
+
}
|
|
130
|
+
export function salesNavigatorPeopleSearchHasFilter(sourceQueryUrl, filterType) {
|
|
131
|
+
const escapedFilterType = filterType.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
132
|
+
return new RegExp(`\\btype\\s*:\\s*${escapedFilterType}\\b`, "i").test(getSalesNavigatorQueryValue(sourceQueryUrl));
|
|
133
|
+
}
|
|
95
134
|
export const DEFAULT_SALES_NAVIGATOR_CRAWL_BASELINE_FILTERS = [
|
|
96
135
|
{
|
|
97
136
|
type: "FUNCTION",
|
package/package.json
CHANGED