salesprompter-cli 0.1.41 → 0.1.42
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 +0 -11
- package/dist/cli.js +3629 -3048
- package/dist/sales-navigator.js +519 -12
- package/package.json +3 -1
package/dist/sales-navigator.js
CHANGED
|
@@ -8,6 +8,16 @@ export class SalesNavigatorSliceTooBroadError extends Error {
|
|
|
8
8
|
this.details = options?.details;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
export class SalesNavigatorCrawlStopError extends Error {
|
|
12
|
+
totalResults;
|
|
13
|
+
details;
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "SalesNavigatorCrawlStopError";
|
|
17
|
+
this.totalResults = options?.totalResults ?? null;
|
|
18
|
+
this.details = options?.details;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
export const DEFAULT_SALES_NAVIGATOR_PEOPLE_SLICE_FILTERS = [
|
|
12
22
|
{
|
|
13
23
|
type: "FUNCTION",
|
|
@@ -81,6 +91,7 @@ export const DEFAULT_SALES_NAVIGATOR_PEOPLE_SLICE_FILTERS = [
|
|
|
81
91
|
},
|
|
82
92
|
];
|
|
83
93
|
const LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL = "https://www.linkedin.com/sales/search/people";
|
|
94
|
+
const LINKEDIN_SALES_NAVIGATOR_ACCOUNT_SEARCH_URL = "https://www.linkedin.com/sales/search/company";
|
|
84
95
|
export const DEFAULT_SALES_NAVIGATOR_CRAWL_BASELINE_FILTERS = [
|
|
85
96
|
{
|
|
86
97
|
type: "FUNCTION",
|
|
@@ -215,6 +226,46 @@ export const DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS = [
|
|
|
215
226
|
],
|
|
216
227
|
},
|
|
217
228
|
];
|
|
229
|
+
export const DEFAULT_SALES_NAVIGATOR_ACCOUNT_CRAWL_DIMENSIONS = [
|
|
230
|
+
{
|
|
231
|
+
key: "company-headcount",
|
|
232
|
+
filterType: "COMPANY_HEADCOUNT",
|
|
233
|
+
values: [
|
|
234
|
+
{ id: "B", text: "1-10", selectionType: "INCLUDED" },
|
|
235
|
+
{ id: "C", text: "11-50", selectionType: "INCLUDED" },
|
|
236
|
+
{ id: "D", text: "51-200", selectionType: "INCLUDED" },
|
|
237
|
+
{ id: "E", text: "201-500", selectionType: "INCLUDED" },
|
|
238
|
+
{ id: "F", text: "501-1000", selectionType: "INCLUDED" },
|
|
239
|
+
{ id: "G", text: "1001-5000", selectionType: "INCLUDED" },
|
|
240
|
+
{ id: "H", text: "5001-10,000", selectionType: "INCLUDED" },
|
|
241
|
+
{ id: "I", text: "10,000+", selectionType: "INCLUDED" },
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
key: "number-of-followers",
|
|
246
|
+
filterType: "NUM_OF_FOLLOWERS",
|
|
247
|
+
values: [
|
|
248
|
+
{ id: "NFR1", text: "1-10", selectionType: "INCLUDED" },
|
|
249
|
+
{ id: "NFR2", text: "11-100", selectionType: "INCLUDED" },
|
|
250
|
+
{ id: "NFR3", text: "101-1000", selectionType: "INCLUDED" },
|
|
251
|
+
{ id: "NFR4", text: "1001-5000", selectionType: "INCLUDED" },
|
|
252
|
+
{ id: "NFR5", text: "5001+", selectionType: "INCLUDED" },
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
key: "company-type",
|
|
257
|
+
filterType: "COMPANY_TYPE",
|
|
258
|
+
values: [
|
|
259
|
+
{ id: "P", text: "Privately Held", selectionType: "INCLUDED" },
|
|
260
|
+
{ id: "C", text: "Public Company", selectionType: "INCLUDED" },
|
|
261
|
+
{ text: "Partnership", selectionType: "INCLUDED" },
|
|
262
|
+
{ text: "Self Owned", selectionType: "INCLUDED" },
|
|
263
|
+
{ text: "Non Profit", selectionType: "INCLUDED" },
|
|
264
|
+
{ text: "Educational Institution", selectionType: "INCLUDED" },
|
|
265
|
+
{ text: "Government Agency", selectionType: "INCLUDED" },
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
];
|
|
218
269
|
function ensureLinkedInSalesPeopleSearchUrl(url) {
|
|
219
270
|
if (!/(^|\.)linkedin\.com$/i.test(url.hostname)) {
|
|
220
271
|
throw new Error("Expected a linkedin.com Sales Navigator URL.");
|
|
@@ -226,6 +277,24 @@ function ensureLinkedInSalesPeopleSearchUrl(url) {
|
|
|
226
277
|
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
227
278
|
}
|
|
228
279
|
}
|
|
280
|
+
function ensureLinkedInSalesAccountSearchUrl(url) {
|
|
281
|
+
if (!/(^|\.)linkedin\.com$/i.test(url.hostname)) {
|
|
282
|
+
throw new Error("Expected a linkedin.com Sales Navigator URL.");
|
|
283
|
+
}
|
|
284
|
+
if (!/^\/sales\/search\/company/i.test(url.pathname)) {
|
|
285
|
+
throw new Error("Expected a LinkedIn Sales Navigator account search URL.");
|
|
286
|
+
}
|
|
287
|
+
if (!url.searchParams.has("query")) {
|
|
288
|
+
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function ensureLinkedInSalesSearchUrl(url, searchType) {
|
|
292
|
+
if (searchType === "account") {
|
|
293
|
+
ensureLinkedInSalesAccountSearchUrl(url);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
ensureLinkedInSalesPeopleSearchUrl(url);
|
|
297
|
+
}
|
|
229
298
|
function splitTopLevelCommaSeparated(value) {
|
|
230
299
|
const parts = [];
|
|
231
300
|
let depth = 0;
|
|
@@ -357,19 +426,36 @@ function canonicalizeSalesNavigatorQueryValue(queryValue) {
|
|
|
357
426
|
const { filtersContent } = findFiltersListBounds(queryValue);
|
|
358
427
|
return `(filters:List(${filtersContent}))`;
|
|
359
428
|
}
|
|
360
|
-
function
|
|
429
|
+
function canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, searchType = "people") {
|
|
361
430
|
const url = new URL(sourceQueryUrl);
|
|
362
|
-
|
|
431
|
+
ensureLinkedInSalesSearchUrl(url, searchType);
|
|
363
432
|
const queryValue = url.searchParams.get("query");
|
|
364
433
|
if (!queryValue) {
|
|
365
434
|
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
366
435
|
}
|
|
367
|
-
const canonicalUrl = new URL(
|
|
436
|
+
const canonicalUrl = new URL(searchType === "account"
|
|
437
|
+
? LINKEDIN_SALES_NAVIGATOR_ACCOUNT_SEARCH_URL
|
|
438
|
+
: LINKEDIN_SALES_NAVIGATOR_PEOPLE_SEARCH_URL);
|
|
368
439
|
canonicalUrl.searchParams.set("query", canonicalizeSalesNavigatorQueryValue(queryValue));
|
|
369
440
|
return canonicalUrl;
|
|
370
441
|
}
|
|
371
442
|
export function buildSalesNavigatorPeopleQuery(sourceQueryUrl, filters) {
|
|
372
|
-
const url =
|
|
443
|
+
const url = canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, "people");
|
|
444
|
+
const queryValue = url.searchParams.get("query");
|
|
445
|
+
if (!queryValue) {
|
|
446
|
+
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
447
|
+
}
|
|
448
|
+
const appliedFilters = dedupeSalesNavigatorFilters(filters);
|
|
449
|
+
const slicedQueryValue = applySalesNavigatorFiltersToQueryValue(queryValue, appliedFilters);
|
|
450
|
+
url.searchParams.set("query", slicedQueryValue);
|
|
451
|
+
return {
|
|
452
|
+
sourceQueryUrl: canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, "people").toString(),
|
|
453
|
+
slicedQueryUrl: url.toString(),
|
|
454
|
+
appliedFilters,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
export function buildSalesNavigatorAccountQuery(sourceQueryUrl, filters) {
|
|
458
|
+
const url = canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, "account");
|
|
373
459
|
const queryValue = url.searchParams.get("query");
|
|
374
460
|
if (!queryValue) {
|
|
375
461
|
throw new Error("Sales Navigator URL is missing the query parameter.");
|
|
@@ -378,7 +464,7 @@ export function buildSalesNavigatorPeopleQuery(sourceQueryUrl, filters) {
|
|
|
378
464
|
const slicedQueryValue = applySalesNavigatorFiltersToQueryValue(queryValue, appliedFilters);
|
|
379
465
|
url.searchParams.set("query", slicedQueryValue);
|
|
380
466
|
return {
|
|
381
|
-
sourceQueryUrl:
|
|
467
|
+
sourceQueryUrl: canonicalizeSalesNavigatorSearchUrl(sourceQueryUrl, "account").toString(),
|
|
382
468
|
slicedQueryUrl: url.toString(),
|
|
383
469
|
appliedFilters,
|
|
384
470
|
};
|
|
@@ -392,6 +478,15 @@ export function buildSalesNavigatorPeopleSearchUrl(filters) {
|
|
|
392
478
|
url.searchParams.set("query", `(filters:List(${appliedFilters.map((filter) => buildFilterBlock(filter)).join(",")}))`);
|
|
393
479
|
return url.toString();
|
|
394
480
|
}
|
|
481
|
+
export function buildSalesNavigatorAccountSearchUrl(filters) {
|
|
482
|
+
const appliedFilters = dedupeSalesNavigatorFilters(filters);
|
|
483
|
+
if (appliedFilters.length === 0) {
|
|
484
|
+
throw new Error("Sales Navigator account search requires at least one filter.");
|
|
485
|
+
}
|
|
486
|
+
const url = new URL(LINKEDIN_SALES_NAVIGATOR_ACCOUNT_SEARCH_URL);
|
|
487
|
+
url.searchParams.set("query", `(filters:List(${appliedFilters.map((filter) => buildFilterBlock(filter)).join(",")}))`);
|
|
488
|
+
return url.toString();
|
|
489
|
+
}
|
|
395
490
|
function inferSalesNavigatorFunctionFilters(options) {
|
|
396
491
|
const titleHaystack = options.title.trim();
|
|
397
492
|
const categoryHaystack = [options.categoryName, options.categorySlug]
|
|
@@ -507,8 +602,13 @@ export function deriveSalesNavigatorTitleQuerySeeds(options) {
|
|
|
507
602
|
export function buildSalesNavigatorPeopleSlice(sourceQueryUrl, filters = DEFAULT_SALES_NAVIGATOR_PEOPLE_SLICE_FILTERS) {
|
|
508
603
|
return buildSalesNavigatorPeopleQuery(sourceQueryUrl, filters);
|
|
509
604
|
}
|
|
605
|
+
export function buildSalesNavigatorAccountSlice(sourceQueryUrl, filters = []) {
|
|
606
|
+
return buildSalesNavigatorAccountQuery(sourceQueryUrl, filters);
|
|
607
|
+
}
|
|
510
608
|
export function createSalesNavigatorCrawlSeed(options) {
|
|
511
|
-
const sliced =
|
|
609
|
+
const sliced = options.searchType === "account"
|
|
610
|
+
? buildSalesNavigatorAccountQuery(options.sourceQueryUrl, options.baselineFilters ?? [])
|
|
611
|
+
: buildSalesNavigatorPeopleQuery(options.sourceQueryUrl, options.baselineFilters ?? []);
|
|
512
612
|
return {
|
|
513
613
|
...sliced,
|
|
514
614
|
depth: 0,
|
|
@@ -519,7 +619,7 @@ export function createSalesNavigatorCrawlSeed(options) {
|
|
|
519
619
|
splitTrail: [],
|
|
520
620
|
};
|
|
521
621
|
}
|
|
522
|
-
export function expandSalesNavigatorCrawlAttempt(attempt, dimension) {
|
|
622
|
+
export function expandSalesNavigatorCrawlAttempt(attempt, dimension, searchType = "people") {
|
|
523
623
|
return dimension.values.map((value) => {
|
|
524
624
|
const filters = dedupeSalesNavigatorFilters([
|
|
525
625
|
...attempt.appliedFilters.filter((filter) => filter.type !== dimension.filterType),
|
|
@@ -533,7 +633,9 @@ export function expandSalesNavigatorCrawlAttempt(attempt, dimension) {
|
|
|
533
633
|
],
|
|
534
634
|
},
|
|
535
635
|
]);
|
|
536
|
-
const sliced =
|
|
636
|
+
const sliced = searchType === "account"
|
|
637
|
+
? buildSalesNavigatorAccountQuery(attempt.sourceQueryUrl, filters)
|
|
638
|
+
: buildSalesNavigatorPeopleQuery(attempt.sourceQueryUrl, filters);
|
|
537
639
|
return {
|
|
538
640
|
...sliced,
|
|
539
641
|
depth: attempt.depth + 1,
|
|
@@ -555,12 +657,364 @@ export function expandSalesNavigatorCrawlAttempt(attempt, dimension) {
|
|
|
555
657
|
};
|
|
556
658
|
});
|
|
557
659
|
}
|
|
660
|
+
export function buildSalesNavigatorResidualCrawlAttempt(attempt, dimension, searchType = "people") {
|
|
661
|
+
const residualValues = dimension.values.map((value) => ({
|
|
662
|
+
...value,
|
|
663
|
+
selectionType: "EXCLUDED",
|
|
664
|
+
}));
|
|
665
|
+
const filters = dedupeSalesNavigatorFilters([
|
|
666
|
+
...attempt.appliedFilters.filter((filter) => filter.type !== dimension.filterType),
|
|
667
|
+
{
|
|
668
|
+
type: dimension.filterType,
|
|
669
|
+
values: residualValues,
|
|
670
|
+
},
|
|
671
|
+
]);
|
|
672
|
+
const sliced = searchType === "account"
|
|
673
|
+
? buildSalesNavigatorAccountQuery(attempt.sourceQueryUrl, filters)
|
|
674
|
+
: buildSalesNavigatorPeopleQuery(attempt.sourceQueryUrl, filters);
|
|
675
|
+
return {
|
|
676
|
+
...sliced,
|
|
677
|
+
depth: attempt.depth + 1,
|
|
678
|
+
retryCount: 0,
|
|
679
|
+
maxResultsPerSearch: attempt.maxResultsPerSearch,
|
|
680
|
+
numberOfProfiles: attempt.numberOfProfiles,
|
|
681
|
+
slicePreset: attempt.slicePreset,
|
|
682
|
+
splitTrail: [
|
|
683
|
+
...attempt.splitTrail,
|
|
684
|
+
{
|
|
685
|
+
key: `${dimension.key}-residual`,
|
|
686
|
+
filterType: dimension.filterType,
|
|
687
|
+
value: {
|
|
688
|
+
text: `Not in ${dimension.key}`,
|
|
689
|
+
selectionType: "EXCLUDED",
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
};
|
|
694
|
+
}
|
|
558
695
|
export function buildSalesNavigatorCrawlPreview(options) {
|
|
559
696
|
const root = createSalesNavigatorCrawlSeed(options);
|
|
560
697
|
const dimensions = options.dimensions ?? DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS;
|
|
561
|
-
const firstSplit = dimensions[0]
|
|
698
|
+
const firstSplit = dimensions[0]
|
|
699
|
+
? expandSalesNavigatorCrawlAttempt(root, dimensions[0], options.searchType)
|
|
700
|
+
: [];
|
|
562
701
|
return { root, firstSplit, dimensions };
|
|
563
702
|
}
|
|
703
|
+
function salesNavigatorDimensionKeyFromTrailKey(key) {
|
|
704
|
+
return key.endsWith("-residual") ? key.slice(0, -"-residual".length) : key;
|
|
705
|
+
}
|
|
706
|
+
function getUnusedSalesNavigatorDimensions(attempt, dimensions) {
|
|
707
|
+
const usedKeys = new Set(attempt.splitTrail.map((entry) => salesNavigatorDimensionKeyFromTrailKey(entry.key)));
|
|
708
|
+
return dimensions.filter((dimension) => !usedKeys.has(dimension.key));
|
|
709
|
+
}
|
|
710
|
+
function buildSalesNavigatorAdaptiveChildren(attempt, dimension, options) {
|
|
711
|
+
const expandedChildren = expandSalesNavigatorCrawlAttempt(attempt, dimension, options.searchType);
|
|
712
|
+
const residualChildren = options.includeResidualExclusionSlices
|
|
713
|
+
? [
|
|
714
|
+
buildSalesNavigatorResidualCrawlAttempt(attempt, dimension, options.searchType),
|
|
715
|
+
]
|
|
716
|
+
: [];
|
|
717
|
+
return [...expandedChildren, ...residualChildren];
|
|
718
|
+
}
|
|
719
|
+
function getSalesNavigatorAttemptDimensionValues(attempt, dimension) {
|
|
720
|
+
const trailEntry = attempt.splitTrail.at(-1);
|
|
721
|
+
if (trailEntry?.key !== dimension.key) {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
const filter = attempt.appliedFilters.find((entry) => entry.type === dimension.filterType);
|
|
725
|
+
if (!filter || filter.values.length !== 1) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
if (filter.values.some((value) => (value.selectionType ?? "INCLUDED") !== "INCLUDED")) {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
return filter.values;
|
|
732
|
+
}
|
|
733
|
+
function buildSalesNavigatorCombinedCrawlAttempt(attempt, dimension, children, searchType = "people") {
|
|
734
|
+
const values = children.flatMap((child) => getSalesNavigatorAttemptDimensionValues(child.attempt, dimension) ?? []);
|
|
735
|
+
const filters = dedupeSalesNavigatorFilters([
|
|
736
|
+
...attempt.appliedFilters.filter((filter) => filter.type !== dimension.filterType),
|
|
737
|
+
{
|
|
738
|
+
type: dimension.filterType,
|
|
739
|
+
values: values.map((value) => ({
|
|
740
|
+
...value,
|
|
741
|
+
selectionType: value.selectionType ?? "INCLUDED",
|
|
742
|
+
})),
|
|
743
|
+
},
|
|
744
|
+
]);
|
|
745
|
+
const sliced = searchType === "account"
|
|
746
|
+
? buildSalesNavigatorAccountQuery(attempt.sourceQueryUrl, filters)
|
|
747
|
+
: buildSalesNavigatorPeopleQuery(attempt.sourceQueryUrl, filters);
|
|
748
|
+
const valueText = values.map((value) => value.text).join(" + ");
|
|
749
|
+
return {
|
|
750
|
+
...sliced,
|
|
751
|
+
depth: attempt.depth + 1,
|
|
752
|
+
retryCount: 0,
|
|
753
|
+
maxResultsPerSearch: attempt.maxResultsPerSearch,
|
|
754
|
+
numberOfProfiles: attempt.numberOfProfiles,
|
|
755
|
+
slicePreset: attempt.slicePreset,
|
|
756
|
+
splitTrail: [
|
|
757
|
+
...attempt.splitTrail,
|
|
758
|
+
{
|
|
759
|
+
key: dimension.key,
|
|
760
|
+
filterType: dimension.filterType,
|
|
761
|
+
value: {
|
|
762
|
+
text: valueText,
|
|
763
|
+
selectionType: "INCLUDED",
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
],
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
async function coalesceSalesNavigatorUnderCapChildren(options) {
|
|
770
|
+
const packable = [];
|
|
771
|
+
const fixed = [];
|
|
772
|
+
for (const summary of options.summaries) {
|
|
773
|
+
const values = getSalesNavigatorAttemptDimensionValues(summary.attempt, options.dimension);
|
|
774
|
+
if (!values ||
|
|
775
|
+
summary.totalResults == null ||
|
|
776
|
+
summary.totalResults > summary.attempt.maxResultsPerSearch) {
|
|
777
|
+
fixed.push(summary);
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
packable.push(summary);
|
|
781
|
+
}
|
|
782
|
+
if (packable.length <= 1) {
|
|
783
|
+
return options.summaries;
|
|
784
|
+
}
|
|
785
|
+
const bins = [];
|
|
786
|
+
const ordered = [...packable].sort((left, right) => (right.totalResults ?? 0) - (left.totalResults ?? 0));
|
|
787
|
+
for (const item of ordered) {
|
|
788
|
+
let placed = false;
|
|
789
|
+
for (const bin of bins) {
|
|
790
|
+
const combinedAttempt = buildSalesNavigatorCombinedCrawlAttempt(options.parentAttempt, options.dimension, [...bin.children, item], options.searchType);
|
|
791
|
+
const combinedSummary = await probeSalesNavigatorAdaptiveAttempt(combinedAttempt, options.probeSlice, options.probeCache);
|
|
792
|
+
if (combinedSummary.totalResults != null &&
|
|
793
|
+
combinedSummary.totalResults <= combinedAttempt.maxResultsPerSearch) {
|
|
794
|
+
bin.children.push(item);
|
|
795
|
+
bin.summary = {
|
|
796
|
+
attempt: combinedAttempt,
|
|
797
|
+
totalResults: combinedSummary.totalResults,
|
|
798
|
+
};
|
|
799
|
+
placed = true;
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (!placed) {
|
|
804
|
+
bins.push({
|
|
805
|
+
children: [item],
|
|
806
|
+
summary: item,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return [
|
|
811
|
+
...bins.map((bin) => bin.summary),
|
|
812
|
+
...fixed,
|
|
813
|
+
];
|
|
814
|
+
}
|
|
815
|
+
function summarizeSalesNavigatorAdaptiveSplitCandidate(summaries, maxResultsPerSearch, estimate) {
|
|
816
|
+
const knownTotals = summaries
|
|
817
|
+
.map((summary) => summary.totalResults)
|
|
818
|
+
.filter((total) => typeof total === "number");
|
|
819
|
+
const knownChildren = knownTotals.length;
|
|
820
|
+
const oversizedChildren = knownTotals.filter((total) => total > maxResultsPerSearch).length;
|
|
821
|
+
const largestChildTotal = knownTotals.length > 0 ? Math.max(...knownTotals) : null;
|
|
822
|
+
const underCapTotals = knownTotals.filter((total) => total <= maxResultsPerSearch);
|
|
823
|
+
const utilization = underCapTotals.length === 0
|
|
824
|
+
? 0
|
|
825
|
+
: underCapTotals.reduce((sum, total) => sum + total / maxResultsPerSearch, 0) / underCapTotals.length;
|
|
826
|
+
const largestPenalty = largestChildTotal == null
|
|
827
|
+
? maxResultsPerSearch * 2
|
|
828
|
+
: Math.max(0, largestChildTotal - maxResultsPerSearch) /
|
|
829
|
+
maxResultsPerSearch;
|
|
830
|
+
return {
|
|
831
|
+
knownChildren,
|
|
832
|
+
oversizedChildren,
|
|
833
|
+
largestChildTotal,
|
|
834
|
+
estimatedFinalBatches: estimate.estimatedFinalBatches,
|
|
835
|
+
unresolvedChildren: estimate.unresolvedChildren,
|
|
836
|
+
score: estimate.unresolvedChildren * 1_000_000 +
|
|
837
|
+
estimate.estimatedFinalBatches * 1_000 +
|
|
838
|
+
oversizedChildren * 100 +
|
|
839
|
+
largestPenalty * 10 -
|
|
840
|
+
utilization,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
async function estimateSalesNavigatorMinimumFinalBatches(options) {
|
|
844
|
+
const remainingDimensions = getUnusedSalesNavigatorDimensions(options.attempt, options.dimensions);
|
|
845
|
+
if (remainingDimensions.length === 0 ||
|
|
846
|
+
options.attempt.depth >= options.maxSplitDepth) {
|
|
847
|
+
return {
|
|
848
|
+
estimatedFinalBatches: 1,
|
|
849
|
+
unresolvedChildren: 1,
|
|
850
|
+
largestChildTotal: null,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
const candidateEstimates = [];
|
|
854
|
+
for (const dimension of remainingDimensions) {
|
|
855
|
+
const rawChildren = buildSalesNavigatorAdaptiveChildren(options.attempt, dimension, options);
|
|
856
|
+
const rawSummaries = [];
|
|
857
|
+
for (const child of rawChildren) {
|
|
858
|
+
rawSummaries.push({
|
|
859
|
+
attempt: child,
|
|
860
|
+
...(await probeSalesNavigatorAdaptiveAttempt(child, options.probeSlice, options.probeCache)),
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
const summaries = await coalesceSalesNavigatorUnderCapChildren({
|
|
864
|
+
parentAttempt: options.attempt,
|
|
865
|
+
dimension,
|
|
866
|
+
summaries: rawSummaries,
|
|
867
|
+
searchType: options.searchType,
|
|
868
|
+
probeSlice: options.probeSlice,
|
|
869
|
+
probeCache: options.probeCache,
|
|
870
|
+
});
|
|
871
|
+
let estimatedFinalBatches = 0;
|
|
872
|
+
let unresolvedChildren = 0;
|
|
873
|
+
let largestChildTotal = null;
|
|
874
|
+
for (const summary of summaries) {
|
|
875
|
+
if (summary.totalResults != null) {
|
|
876
|
+
largestChildTotal =
|
|
877
|
+
largestChildTotal == null
|
|
878
|
+
? summary.totalResults
|
|
879
|
+
: Math.max(largestChildTotal, summary.totalResults);
|
|
880
|
+
}
|
|
881
|
+
if (summary.totalResults != null &&
|
|
882
|
+
summary.totalResults <= summary.attempt.maxResultsPerSearch) {
|
|
883
|
+
estimatedFinalBatches += 1;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const childEstimate = await estimateSalesNavigatorMinimumFinalBatches({
|
|
887
|
+
...options,
|
|
888
|
+
attempt: summary.attempt,
|
|
889
|
+
});
|
|
890
|
+
estimatedFinalBatches += childEstimate.estimatedFinalBatches;
|
|
891
|
+
unresolvedChildren += childEstimate.unresolvedChildren;
|
|
892
|
+
if (childEstimate.largestChildTotal != null) {
|
|
893
|
+
largestChildTotal =
|
|
894
|
+
largestChildTotal == null
|
|
895
|
+
? childEstimate.largestChildTotal
|
|
896
|
+
: Math.max(largestChildTotal, childEstimate.largestChildTotal);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
candidateEstimates.push({
|
|
900
|
+
estimatedFinalBatches,
|
|
901
|
+
unresolvedChildren,
|
|
902
|
+
largestChildTotal,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
candidateEstimates.sort((left, right) => {
|
|
906
|
+
if (left.unresolvedChildren !== right.unresolvedChildren) {
|
|
907
|
+
return left.unresolvedChildren - right.unresolvedChildren;
|
|
908
|
+
}
|
|
909
|
+
if (left.estimatedFinalBatches !== right.estimatedFinalBatches) {
|
|
910
|
+
return left.estimatedFinalBatches - right.estimatedFinalBatches;
|
|
911
|
+
}
|
|
912
|
+
return dimensionsSortValue(right.largestChildTotal) -
|
|
913
|
+
dimensionsSortValue(left.largestChildTotal);
|
|
914
|
+
});
|
|
915
|
+
return candidateEstimates[0] ?? {
|
|
916
|
+
estimatedFinalBatches: 1,
|
|
917
|
+
unresolvedChildren: 1,
|
|
918
|
+
largestChildTotal: null,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
async function probeSalesNavigatorAdaptiveAttempt(attempt, probeSlice, probeCache) {
|
|
922
|
+
let cached = probeCache.get(attempt.slicedQueryUrl);
|
|
923
|
+
if (!cached) {
|
|
924
|
+
cached = probeSlice(attempt);
|
|
925
|
+
probeCache.set(attempt.slicedQueryUrl, cached);
|
|
926
|
+
}
|
|
927
|
+
return await cached;
|
|
928
|
+
}
|
|
929
|
+
async function chooseSalesNavigatorAdaptiveSplitCandidate(options) {
|
|
930
|
+
const candidates = [];
|
|
931
|
+
const probeCache = new Map();
|
|
932
|
+
for (const dimension of options.dimensions) {
|
|
933
|
+
const children = buildSalesNavigatorAdaptiveChildren(options.attempt, dimension, options).filter((child) => !options.seenQueryUrls.has(child.slicedQueryUrl));
|
|
934
|
+
if (children.length === 0) {
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
const summaries = [];
|
|
938
|
+
for (const child of children) {
|
|
939
|
+
summaries.push({
|
|
940
|
+
attempt: child,
|
|
941
|
+
...(await probeSalesNavigatorAdaptiveAttempt(child, options.probeSlice, probeCache)),
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
const coalescedSummaries = await coalesceSalesNavigatorUnderCapChildren({
|
|
945
|
+
parentAttempt: options.attempt,
|
|
946
|
+
dimension,
|
|
947
|
+
summaries,
|
|
948
|
+
searchType: options.searchType,
|
|
949
|
+
probeSlice: options.probeSlice,
|
|
950
|
+
probeCache,
|
|
951
|
+
});
|
|
952
|
+
const coalescedChildren = coalescedSummaries.map((summary) => summary.attempt);
|
|
953
|
+
let estimatedFinalBatches = 0;
|
|
954
|
+
let unresolvedChildren = 0;
|
|
955
|
+
let largestChildTotal = null;
|
|
956
|
+
for (const summary of coalescedSummaries) {
|
|
957
|
+
if (summary.totalResults != null) {
|
|
958
|
+
largestChildTotal =
|
|
959
|
+
largestChildTotal == null
|
|
960
|
+
? summary.totalResults
|
|
961
|
+
: Math.max(largestChildTotal, summary.totalResults);
|
|
962
|
+
}
|
|
963
|
+
if (summary.totalResults != null &&
|
|
964
|
+
summary.totalResults <= summary.attempt.maxResultsPerSearch) {
|
|
965
|
+
estimatedFinalBatches += 1;
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
const childEstimate = await estimateSalesNavigatorMinimumFinalBatches({
|
|
969
|
+
attempt: summary.attempt,
|
|
970
|
+
dimensions: options.dimensions,
|
|
971
|
+
maxSplitDepth: options.maxSplitDepth,
|
|
972
|
+
searchType: options.searchType,
|
|
973
|
+
includeResidualExclusionSlices: options.includeResidualExclusionSlices,
|
|
974
|
+
probeSlice: options.probeSlice,
|
|
975
|
+
probeCache,
|
|
976
|
+
});
|
|
977
|
+
estimatedFinalBatches += childEstimate.estimatedFinalBatches;
|
|
978
|
+
unresolvedChildren += childEstimate.unresolvedChildren;
|
|
979
|
+
if (childEstimate.largestChildTotal != null) {
|
|
980
|
+
largestChildTotal =
|
|
981
|
+
largestChildTotal == null
|
|
982
|
+
? childEstimate.largestChildTotal
|
|
983
|
+
: Math.max(largestChildTotal, childEstimate.largestChildTotal);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
const score = summarizeSalesNavigatorAdaptiveSplitCandidate(coalescedSummaries, options.attempt.maxResultsPerSearch, {
|
|
987
|
+
estimatedFinalBatches,
|
|
988
|
+
unresolvedChildren,
|
|
989
|
+
});
|
|
990
|
+
const largestTotal = largestChildTotal == null
|
|
991
|
+
? score.largestChildTotal
|
|
992
|
+
: score.largestChildTotal == null
|
|
993
|
+
? largestChildTotal
|
|
994
|
+
: Math.max(largestChildTotal, score.largestChildTotal);
|
|
995
|
+
candidates.push({
|
|
996
|
+
dimension,
|
|
997
|
+
children: coalescedChildren,
|
|
998
|
+
summaries: coalescedSummaries,
|
|
999
|
+
...score,
|
|
1000
|
+
largestChildTotal: largestTotal,
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
candidates.sort((left, right) => {
|
|
1004
|
+
if (left.score !== right.score) {
|
|
1005
|
+
return left.score - right.score;
|
|
1006
|
+
}
|
|
1007
|
+
if (left.oversizedChildren !== right.oversizedChildren) {
|
|
1008
|
+
return left.oversizedChildren - right.oversizedChildren;
|
|
1009
|
+
}
|
|
1010
|
+
return dimensionsSortValue(right.largestChildTotal) -
|
|
1011
|
+
dimensionsSortValue(left.largestChildTotal);
|
|
1012
|
+
});
|
|
1013
|
+
return candidates[0] ?? null;
|
|
1014
|
+
}
|
|
1015
|
+
function dimensionsSortValue(value) {
|
|
1016
|
+
return value ?? -1;
|
|
1017
|
+
}
|
|
564
1018
|
export async function executeSalesNavigatorAdaptiveCrawl(options) {
|
|
565
1019
|
const dimensions = options.dimensions ?? DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS;
|
|
566
1020
|
const maxSplitDepth = Math.min(options.maxSplitDepth ?? dimensions.length, dimensions.length);
|
|
@@ -577,6 +1031,7 @@ export async function executeSalesNavigatorAdaptiveCrawl(options) {
|
|
|
577
1031
|
maxResultsPerSearch: options.maxResultsPerSearch,
|
|
578
1032
|
numberOfProfiles: options.numberOfProfiles,
|
|
579
1033
|
slicePreset: options.slicePreset,
|
|
1034
|
+
searchType: options.searchType,
|
|
580
1035
|
}),
|
|
581
1036
|
];
|
|
582
1037
|
const seenQueryUrls = new Set(queue.map((attempt) => attempt.slicedQueryUrl));
|
|
@@ -597,9 +1052,18 @@ export async function executeSalesNavigatorAdaptiveCrawl(options) {
|
|
|
597
1052
|
exported.push({ attempt, result });
|
|
598
1053
|
}
|
|
599
1054
|
catch (error) {
|
|
1055
|
+
if (error instanceof SalesNavigatorCrawlStopError) {
|
|
1056
|
+
failed.push({
|
|
1057
|
+
attempt,
|
|
1058
|
+
error: error.message,
|
|
1059
|
+
totalResults: error.totalResults,
|
|
1060
|
+
});
|
|
1061
|
+
truncated = true;
|
|
1062
|
+
break;
|
|
1063
|
+
}
|
|
600
1064
|
if (error instanceof SalesNavigatorSliceTooBroadError) {
|
|
601
|
-
const
|
|
602
|
-
if (
|
|
1065
|
+
const remainingDimensions = getUnusedSalesNavigatorDimensions(attempt, dimensions);
|
|
1066
|
+
if (remainingDimensions.length === 0 || attempt.depth >= maxSplitDepth) {
|
|
603
1067
|
unresolved.push({
|
|
604
1068
|
attempt,
|
|
605
1069
|
error: error.message,
|
|
@@ -607,7 +1071,48 @@ export async function executeSalesNavigatorAdaptiveCrawl(options) {
|
|
|
607
1071
|
});
|
|
608
1072
|
continue;
|
|
609
1073
|
}
|
|
610
|
-
|
|
1074
|
+
let nextDimension = remainingDimensions[0];
|
|
1075
|
+
let children = [];
|
|
1076
|
+
let probeSummary;
|
|
1077
|
+
let strategy = "fixed-order";
|
|
1078
|
+
if (options.adaptiveSplitProbing && options.probeSlice) {
|
|
1079
|
+
const candidate = await chooseSalesNavigatorAdaptiveSplitCandidate({
|
|
1080
|
+
attempt,
|
|
1081
|
+
dimensions: remainingDimensions,
|
|
1082
|
+
searchType: options.searchType,
|
|
1083
|
+
includeResidualExclusionSlices: options.includeResidualExclusionSlices,
|
|
1084
|
+
seenQueryUrls,
|
|
1085
|
+
maxSplitDepth,
|
|
1086
|
+
probeSlice: options.probeSlice,
|
|
1087
|
+
});
|
|
1088
|
+
if (candidate) {
|
|
1089
|
+
nextDimension = candidate.dimension;
|
|
1090
|
+
children = candidate.children;
|
|
1091
|
+
strategy = "adaptive-probe";
|
|
1092
|
+
probeSummary = [
|
|
1093
|
+
{
|
|
1094
|
+
dimension: candidate.dimension.key,
|
|
1095
|
+
childCount: candidate.children.length,
|
|
1096
|
+
knownChildren: candidate.knownChildren,
|
|
1097
|
+
oversizedChildren: candidate.oversizedChildren,
|
|
1098
|
+
largestChildTotal: candidate.largestChildTotal,
|
|
1099
|
+
estimatedFinalBatches: candidate.estimatedFinalBatches,
|
|
1100
|
+
unresolvedChildren: candidate.unresolvedChildren,
|
|
1101
|
+
score: candidate.score,
|
|
1102
|
+
},
|
|
1103
|
+
];
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (children.length === 0) {
|
|
1107
|
+
const expandedChildren = expandSalesNavigatorCrawlAttempt(attempt, nextDimension, options.searchType);
|
|
1108
|
+
const residualChildren = options.includeResidualExclusionSlices
|
|
1109
|
+
? [
|
|
1110
|
+
buildSalesNavigatorResidualCrawlAttempt(attempt, nextDimension, options.searchType),
|
|
1111
|
+
]
|
|
1112
|
+
: [];
|
|
1113
|
+
children = [...expandedChildren, ...residualChildren];
|
|
1114
|
+
}
|
|
1115
|
+
children = children.filter((child) => {
|
|
611
1116
|
if (seenQueryUrls.has(child.slicedQueryUrl)) {
|
|
612
1117
|
return false;
|
|
613
1118
|
}
|
|
@@ -627,6 +1132,8 @@ export async function executeSalesNavigatorAdaptiveCrawl(options) {
|
|
|
627
1132
|
totalResults: error.totalResults,
|
|
628
1133
|
nextDimension: nextDimension.key,
|
|
629
1134
|
childCount: children.length,
|
|
1135
|
+
strategy,
|
|
1136
|
+
probeSummary,
|
|
630
1137
|
});
|
|
631
1138
|
queue.push(...children);
|
|
632
1139
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "salesprompter-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"description": "Sales workflow CLI for guided lead generation, enrichment, scoring, and sync.",
|
|
5
5
|
"author": "Daniel Sinewe <hello@danielsinewe.com>",
|
|
6
6
|
"type": "module",
|
|
@@ -66,8 +66,10 @@
|
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@google-cloud/bigquery": "^8.1.1",
|
|
68
68
|
"@supabase/supabase-js": "^2.99.1",
|
|
69
|
+
"@types/pg": "^8.20.0",
|
|
69
70
|
"cheerio": "^1.2.0",
|
|
70
71
|
"commander": "^14.0.1",
|
|
72
|
+
"pg": "^8.21.0",
|
|
71
73
|
"zod": "^4.1.5"
|
|
72
74
|
},
|
|
73
75
|
"overrides": {
|