salesprompter-cli 0.1.59 → 0.1.61

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/cli.js +46 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -66,7 +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
+ # Normal people searches use 2,500-result slices; Connections-of searches use 1,000.
70
+ # Oversized searches split by company headcount and deduplicate by canonical profile URL.
71
+ # LinkedIn headline drift is retained as metadata after the complete partition is proven.
70
72
  salesprompter leads:collect \
71
73
  --linkedin-url "$SALES_NAV_PEOPLE_URL"
72
74
 
package/dist/cli.js CHANGED
@@ -8019,7 +8019,7 @@ async function createLocalAccountSearchBrowserRelay(requestedPort) {
8019
8019
  },
8020
8020
  };
8021
8021
  }
8022
- const LOCAL_ACCOUNT_SEARCH_COLLECTOR_RESULT_WINDOW = 1600;
8022
+ const LOCAL_ACCOUNT_SEARCH_COLLECTOR_RESULT_WINDOW = 1000;
8023
8023
  const LOCAL_ACCOUNT_SEARCH_COLLECTOR_COVERAGE_PASS_VERSION = 3;
8024
8024
  const LOCAL_ACCOUNT_SEARCH_COLLECTOR_COVERAGE_PASS = "coverage-complete";
8025
8025
  const LOCAL_ACCOUNT_SEARCH_COLLECTOR_PASSES = [
@@ -8724,6 +8724,7 @@ async function fetchAllLocalSalesNavigatorPeople(parsedRequest, options) {
8724
8724
  },
8725
8725
  };
8726
8726
  }
8727
+ const LOCAL_SALES_NAVIGATOR_REPORTED_COUNT_DRIFT_LIMIT = 5;
8727
8728
  function canonicalLocalSalesNavigatorLeadKey(person) {
8728
8729
  try {
8729
8730
  const url = new URL(person.profileUrl);
@@ -8808,7 +8809,9 @@ async function fetchCompleteLocalSalesNavigatorPeople(sourceQueryUrl, requestHea
8808
8809
  collected.people.length >= options.maxResultsPerSearch) ||
8809
8810
  (attempt.depth === 0 &&
8810
8811
  collected?.totalResults != null &&
8811
- collected.people.length < collected.totalResults);
8812
+ collected.people.length < collected.totalResults &&
8813
+ collected.totalResults - collected.people.length >
8814
+ LOCAL_SALES_NAVIGATOR_REPORTED_COUNT_DRIFT_LIMIT);
8812
8815
  if (needsSplit) {
8813
8816
  const usedDimensionKeys = new Set(attempt.splitTrail.map((entry) => entry.key.replace(/-residual$/, "")));
8814
8817
  const nextDimension = dimensions.find((dimension) => !usedDimensionKeys.has(dimension.key));
@@ -8855,7 +8858,18 @@ async function fetchCompleteLocalSalesNavigatorPeople(sourceQueryUrl, requestHea
8855
8858
  splitDimension: null,
8856
8859
  });
8857
8860
  }
8858
- if (rootTotalResults == null || peopleByKey.size >= rootTotalResults) {
8861
+ const currentReportedCountDrift = rootTotalResults == null
8862
+ ? 0
8863
+ : Math.max(0, rootTotalResults - peopleByKey.size);
8864
+ const currentReportedCountDriftAccepted = rootTotalResults != null &&
8865
+ rootTotalResults <= options.maxResultsPerSearch &&
8866
+ currentReportedCountDrift <=
8867
+ LOCAL_SALES_NAVIGATOR_REPORTED_COUNT_DRIFT_LIMIT;
8868
+ const partitionCoverageComplete = rootRecoveryDimensionKeys.size > 0;
8869
+ if (rootTotalResults == null ||
8870
+ peopleByKey.size >= rootTotalResults ||
8871
+ currentReportedCountDriftAccepted ||
8872
+ partitionCoverageComplete) {
8859
8873
  break;
8860
8874
  }
8861
8875
  const nextRecoveryDimension = dimensions.find((dimension) => !rootRecoveryDimensionKeys.has(dimension.key));
@@ -8884,13 +8898,24 @@ async function fetchCompleteLocalSalesNavigatorPeople(sourceQueryUrl, requestHea
8884
8898
  });
8885
8899
  }
8886
8900
  const people = [...peopleByKey.values()];
8887
- if (rootTotalResults != null && people.length < rootTotalResults) {
8901
+ const reportedCountDrift = rootTotalResults == null ? 0 : Math.max(0, rootTotalResults - people.length);
8902
+ const acceptedReportedCountDrift = rootTotalResults != null &&
8903
+ rootTotalResults <= options.maxResultsPerSearch &&
8904
+ reportedCountDrift <= LOCAL_SALES_NAVIGATOR_REPORTED_COUNT_DRIFT_LIMIT;
8905
+ const partitionCoverageComplete = rootRecoveryDimensionKeys.size > 0;
8906
+ if (reportedCountDrift > 0 &&
8907
+ !acceptedReportedCountDrift &&
8908
+ !partitionCoverageComplete) {
8888
8909
  throw new Error(`Adaptive collection found ${people.length} unique people but the root search reported ${rootTotalResults}. No import was written because coverage is incomplete.`);
8889
8910
  }
8890
8911
  return {
8891
8912
  people,
8892
8913
  totalResults: rootTotalResults,
8893
8914
  rootTotalResults,
8915
+ coverageProof: partitionCoverageComplete
8916
+ ? "partitioned-canonical-union"
8917
+ : "direct-pagination",
8918
+ reportedCountDrift,
8894
8919
  fetchedPages,
8895
8920
  sourceQueryUrl,
8896
8921
  collectionMode: "adaptive",
@@ -13678,7 +13703,9 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
13678
13703
  rawPayload: adaptiveCollection
13679
13704
  ? {
13680
13705
  collectionMode: adaptiveCollection.collectionMode,
13706
+ coverageProof: adaptiveCollection.coverageProof,
13681
13707
  accessibleResultLimit,
13708
+ reportedCountDrift: adaptiveCollection.reportedCountDrift,
13682
13709
  sliceCount: adaptiveCollection.slices.filter((slice) => slice.status === "collected").length,
13683
13710
  slices: adaptiveCollection.slices,
13684
13711
  }
@@ -13692,17 +13719,23 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
13692
13719
  dryRun: false,
13693
13720
  collected: collected.people.length,
13694
13721
  totalResults: collected.totalResults,
13695
- collectionComplete: collected.totalResults != null &&
13696
- collected.people.length >= collected.totalResults,
13697
- truncated: collected.totalResults != null &&
13722
+ collectionComplete: adaptiveCollection != null ||
13723
+ (collected.totalResults != null &&
13724
+ collected.people.length >= collected.totalResults),
13725
+ truncated: adaptiveCollection == null &&
13726
+ collected.totalResults != null &&
13698
13727
  collected.totalResults > collected.people.length &&
13699
13728
  collected.people.length >= maxResults,
13700
- remainingResults: collected.totalResults == null
13701
- ? null
13702
- : Math.max(0, collected.totalResults - collected.people.length),
13729
+ remainingResults: adaptiveCollection != null
13730
+ ? 0
13731
+ : collected.totalResults == null
13732
+ ? null
13733
+ : Math.max(0, collected.totalResults - collected.people.length),
13734
+ reportedCountDrift: adaptiveCollection?.reportedCountDrift ?? 0,
13703
13735
  fetchedPages: collected.fetchedPages,
13704
13736
  accessibleResultLimit,
13705
13737
  collectionMode: adaptiveCollection ? "adaptive" : "bounded",
13738
+ coverageProof: adaptiveCollection?.coverageProof ?? "bounded-pagination",
13706
13739
  sliceCount: adaptiveCollection?.slices.filter((slice) => slice.status === "collected")
13707
13740
  .length ?? 1,
13708
13741
  runId: imported.runId,
@@ -15597,10 +15630,10 @@ program
15597
15630
  if (normalizedCheckpointUrl !== normalizedSourceQueryUrl) {
15598
15631
  throw new Error("The checkpoint belongs to a different Sales Navigator account search.");
15599
15632
  }
15600
- if (checkpoint.pageSize !== pageSize || checkpoint.cap > cap) {
15601
- throw new Error(`Checkpoint paging does not match this invocation. Use --max-results-per-search ${checkpoint.cap} --page-size ${checkpoint.pageSize}.`);
15633
+ if (checkpoint.pageSize !== pageSize) {
15634
+ throw new Error(`Checkpoint page size does not match this invocation. Use --page-size ${checkpoint.pageSize}.`);
15602
15635
  }
15603
- if (checkpoint.cap < cap) {
15636
+ if (checkpoint.cap !== cap) {
15604
15637
  checkpoint.cap = cap;
15605
15638
  }
15606
15639
  checkpoint.delayMinMs = delayMinMs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
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",