salesprompter-cli 0.1.57 → 0.1.59

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 +1 -1
  2. package/dist/cli.js +110 -74
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -66,7 +66,7 @@ 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
- # Searches above the native window are split by company headcount and deduplicated.
69
+ # Oversized or pagination-short searches are split by company headcount and deduplicated.
70
70
  salesprompter leads:collect \
71
71
  --linkedin-url "$SALES_NAV_PEOPLE_URL"
72
72
 
package/dist/cli.js CHANGED
@@ -8752,99 +8752,135 @@ async function fetchCompleteLocalSalesNavigatorPeople(sourceQueryUrl, requestHea
8752
8752
  const dimensions = localPeopleSplitDimensions(root.sourceQueryUrl);
8753
8753
  const queue = [root];
8754
8754
  const queuedUrls = new Set([root.slicedQueryUrl]);
8755
+ const rootRecoveryDimensionKeys = new Set();
8755
8756
  const peopleByKey = new Map();
8756
8757
  const slices = [];
8757
8758
  let rootTotalResults = null;
8758
8759
  let fetchedPages = 0;
8759
8760
  let totalDelayMs = 0;
8760
8761
  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;
8762
+ while (true) {
8763
+ while (queue.length > 0) {
8764
+ const attempt = queue.shift();
8765
+ const parsedRequest = {
8766
+ url: buildSalesNavigatorLeadApiUrlFromSearchUrl(attempt.slicedQueryUrl, options.pageSize),
8767
+ headers: requestHeaders,
8768
+ };
8769
+ let collected = null;
8770
+ let tooBroadTotal = null;
8771
+ let probeFetchedPages = 0;
8772
+ try {
8773
+ collected = await fetchAllLocalSalesNavigatorPeople(parsedRequest, {
8774
+ requestedProfiles: options.maxResultsPerSearch,
8775
+ maxAllowedResults: options.maxResultsPerSearch,
8776
+ pageSize: options.pageSize,
8777
+ pageDelayMinMs: options.pageDelayMinMs,
8778
+ pageDelayMaxMs: options.pageDelayMaxMs,
8779
+ retry: options.retry,
8780
+ executeRequest: options.executeRequest,
8781
+ });
8782
+ fetchedPages += collected.fetchedPages;
8783
+ totalDelayMs += collected.pacing.totalDelayMs;
8784
+ retryCount += collected.pacing.retryCount;
8785
+ if (attempt.depth === 0) {
8786
+ rootTotalResults = collected.totalResults;
8787
+ }
8790
8788
  }
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;
8789
+ catch (error) {
8790
+ if (!(error instanceof SalesNavigatorSliceTooBroadError)) {
8791
+ throw error;
8792
+ }
8793
+ tooBroadTotal = error.totalResults;
8794
+ const details = error.details && typeof error.details === "object"
8795
+ ? error.details
8796
+ : {};
8797
+ probeFetchedPages = Number(details.fetchedPages ?? 1);
8798
+ fetchedPages += probeFetchedPages;
8799
+ retryCount += Number(details.retryCount ?? 0);
8800
+ totalDelayMs += Number(details.totalDelayMs ?? 0);
8801
+ if (attempt.depth === 0) {
8802
+ rootTotalResults = tooBroadTotal;
8803
+ }
8801
8804
  }
8802
- }
8803
- const needsSplit = tooBroadTotal != null ||
8804
- (collected?.totalResults == null &&
8805
- collected != null &&
8806
- collected.people.length >= options.maxResultsPerSearch);
8807
- if (needsSplit) {
8808
- const usedDimensionKeys = new Set(attempt.splitTrail.map((entry) => entry.key.replace(/-residual$/, "")));
8809
- const nextDimension = dimensions.find((dimension) => !usedDimensionKeys.has(dimension.key));
8810
- if (!nextDimension) {
8811
- 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.`);
8805
+ const needsSplit = tooBroadTotal != null ||
8806
+ (collected?.totalResults == null &&
8807
+ collected != null &&
8808
+ collected.people.length >= options.maxResultsPerSearch) ||
8809
+ (attempt.depth === 0 &&
8810
+ collected?.totalResults != null &&
8811
+ collected.people.length < collected.totalResults);
8812
+ if (needsSplit) {
8813
+ const usedDimensionKeys = new Set(attempt.splitTrail.map((entry) => entry.key.replace(/-residual$/, "")));
8814
+ const nextDimension = dimensions.find((dimension) => !usedDimensionKeys.has(dimension.key));
8815
+ if (!nextDimension) {
8816
+ throw new Error(`Sales Navigator search could not be completed within the ${options.maxResultsPerSearch}-person window after all safe split dimensions were used. Narrow the source search further.`);
8817
+ }
8818
+ if (attempt.depth === 0) {
8819
+ rootRecoveryDimensionKeys.add(nextDimension.key);
8820
+ }
8821
+ const children = expandSalesNavigatorCrawlAttempt(attempt, nextDimension, "people");
8822
+ if (nextDimension.supportsResidualExclusion !== false) {
8823
+ children.push(buildSalesNavigatorResidualCrawlAttempt(attempt, nextDimension, "people"));
8824
+ }
8825
+ for (const child of children) {
8826
+ if (queuedUrls.has(child.slicedQueryUrl))
8827
+ continue;
8828
+ queuedUrls.add(child.slicedQueryUrl);
8829
+ queue.push(child);
8830
+ }
8831
+ slices.push({
8832
+ depth: attempt.depth,
8833
+ splitTrail: attempt.splitTrail,
8834
+ totalResults: tooBroadTotal ?? collected?.totalResults ?? null,
8835
+ collected: 0,
8836
+ fetchedPages: probeFetchedPages || collected?.fetchedPages || 0,
8837
+ status: "split",
8838
+ splitDimension: nextDimension.key,
8839
+ });
8840
+ continue;
8812
8841
  }
8813
- const children = expandSalesNavigatorCrawlAttempt(attempt, nextDimension, "people");
8814
- if (nextDimension.supportsResidualExclusion !== false) {
8815
- children.push(buildSalesNavigatorResidualCrawlAttempt(attempt, nextDimension, "people"));
8842
+ if (!collected) {
8843
+ throw new Error("Sales Navigator slice did not return a collection result.");
8816
8844
  }
8817
- for (const child of children) {
8818
- if (queuedUrls.has(child.slicedQueryUrl))
8819
- continue;
8820
- queuedUrls.add(child.slicedQueryUrl);
8821
- queue.push(child);
8845
+ for (const person of collected.people) {
8846
+ peopleByKey.set(canonicalLocalSalesNavigatorLeadKey(person), person);
8822
8847
  }
8823
8848
  slices.push({
8824
8849
  depth: attempt.depth,
8825
8850
  splitTrail: attempt.splitTrail,
8826
- totalResults: tooBroadTotal ?? collected?.totalResults ?? null,
8827
- collected: 0,
8828
- fetchedPages: probeFetchedPages || collected?.fetchedPages || 0,
8829
- status: "split",
8830
- splitDimension: nextDimension.key,
8851
+ totalResults: collected.totalResults,
8852
+ collected: collected.people.length,
8853
+ fetchedPages: collected.fetchedPages,
8854
+ status: "collected",
8855
+ splitDimension: null,
8831
8856
  });
8832
- continue;
8833
8857
  }
8834
- if (!collected) {
8835
- throw new Error("Sales Navigator slice did not return a collection result.");
8858
+ if (rootTotalResults == null || peopleByKey.size >= rootTotalResults) {
8859
+ break;
8860
+ }
8861
+ const nextRecoveryDimension = dimensions.find((dimension) => !rootRecoveryDimensionKeys.has(dimension.key));
8862
+ if (!nextRecoveryDimension) {
8863
+ break;
8836
8864
  }
8837
- for (const person of collected.people) {
8838
- peopleByKey.set(canonicalLocalSalesNavigatorLeadKey(person), person);
8865
+ rootRecoveryDimensionKeys.add(nextRecoveryDimension.key);
8866
+ const recoveryChildren = expandSalesNavigatorCrawlAttempt(root, nextRecoveryDimension, "people");
8867
+ if (nextRecoveryDimension.supportsResidualExclusion !== false) {
8868
+ recoveryChildren.push(buildSalesNavigatorResidualCrawlAttempt(root, nextRecoveryDimension, "people"));
8869
+ }
8870
+ for (const child of recoveryChildren) {
8871
+ if (queuedUrls.has(child.slicedQueryUrl))
8872
+ continue;
8873
+ queuedUrls.add(child.slicedQueryUrl);
8874
+ queue.push(child);
8839
8875
  }
8840
8876
  slices.push({
8841
- depth: attempt.depth,
8842
- splitTrail: attempt.splitTrail,
8843
- totalResults: collected.totalResults,
8844
- collected: collected.people.length,
8845
- fetchedPages: collected.fetchedPages,
8846
- status: "collected",
8847
- splitDimension: null,
8877
+ depth: 0,
8878
+ splitTrail: [],
8879
+ totalResults: rootTotalResults,
8880
+ collected: 0,
8881
+ fetchedPages: 0,
8882
+ status: "split",
8883
+ splitDimension: nextRecoveryDimension.key,
8848
8884
  });
8849
8885
  }
8850
8886
  const people = [...peopleByKey.values()];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
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",