salesprompter-cli 0.1.53 → 0.1.54

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 +2 -1
  2. package/dist/cli.js +83 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,7 +72,8 @@ salesprompter leads:collect \
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.
75
- # Stale imported company ids fall back to exact-name Account Search automatically.
75
+ # Stale ids and raw legal-name misses use conservative exact-name recovery.
76
+ # Hunter prefers official domains and can fall back to the exact company name.
76
77
  salesprompter leads:enrich-import \
77
78
  --run-id "$CLI_IMPORT_RUN_ID"
78
79
 
package/dist/cli.js CHANGED
@@ -284,6 +284,8 @@ const CliImportHunterBatchResponseSchema = z.object({
284
284
  found: z.number().int().nonnegative(),
285
285
  notFound: z.number().int().nonnegative(),
286
286
  failed: z.number().int().nonnegative(),
287
+ domainLookups: z.number().int().nonnegative().default(0),
288
+ companyLookups: z.number().int().nonnegative().default(0),
287
289
  halted: z.boolean(),
288
290
  error: z.string().nullable()
289
291
  });
@@ -6756,7 +6758,11 @@ async function runCliImportEnrichmentAction(session, body, schema) {
6756
6758
  return value;
6757
6759
  }
6758
6760
  async function getCliImportCompanyCandidatesViaApp(session, input) {
6759
- return runCliImportEnrichmentAction(session, { action: "company-candidates", ...input }, CliImportCompanyCandidatesResponseSchema);
6761
+ return runCliImportEnrichmentAction(session, {
6762
+ action: "company-candidates",
6763
+ ...input,
6764
+ retry: input.retryMode === "all",
6765
+ }, CliImportCompanyCandidatesResponseSchema);
6760
6766
  }
6761
6767
  async function saveCliImportCompanyProfilesViaApp(session, profiles) {
6762
6768
  return runCliImportEnrichmentAction(session, { action: "save-companies", profiles }, CliImportCompanySaveResponseSchema);
@@ -13446,6 +13452,7 @@ function normalizeCompanyWebsite(value) {
13446
13452
  }
13447
13453
  }
13448
13454
  const CLI_IMPORT_COMPANY_DECORATION = "(entityUrn,name,employeeCount,employeeDisplayCount,employeeCountRange,account(saved,noteCount,listCount,crmStatus,starred),pictureInfo,companyPictureDisplayImage,companyBackgroundCoverImage,description,industry,location,headquarters,website,revenueRange,crmOpportunities,flagshipCompanyUrl,employeeGrowthPercentages,employees*~fs_salesProfile(entityUrn,firstName,lastName,fullName,pictureInfo,profilePictureDisplayImage),specialties,type,yearFounded)";
13455
+ const CLI_IMPORT_COMPANY_RESOLUTION_VERSION = "cleaned-search-v1";
13449
13456
  class CliImportCompanySessionError extends Error {
13450
13457
  constructor(message) {
13451
13458
  super(message);
@@ -13591,24 +13598,39 @@ async function resolveCliImportCompanyIdentity(candidate, config, timeoutMs, bro
13591
13598
  let account = null;
13592
13599
  if (!resolvedCompanyId && candidate.companyName) {
13593
13600
  accountSearchUsed = true;
13594
- const queryUrl = buildCliImportAccountSearchUrl(candidate.companyName);
13595
- const response = await fetchCliImportSalesNavigatorJson({
13596
- url: queryUrl,
13597
- config,
13598
- browserRelay,
13599
- timeoutMs,
13600
- label: `Sales Navigator Account Search for ${candidate.companyName}`,
13601
+ const seenSearchNames = new Set();
13602
+ const searchNames = [
13603
+ candidate.companyName,
13604
+ aggressivelyCleanLookupCompanyName(candidate.companyName),
13605
+ ].filter((value) => {
13606
+ const key = normalizeLookupWhitespace(value).toLowerCase();
13607
+ if (!key || seenSearchNames.has(key))
13608
+ return false;
13609
+ seenSearchNames.add(key);
13610
+ return true;
13601
13611
  });
13602
- if (response.status < 200 || response.status >= 300) {
13603
- throw new Error(`Sales Navigator Account Search for ${candidate.companyName} returned ${response.status}.`);
13604
- }
13605
- if (response.status >= 200 && response.status < 300 && response.body) {
13606
- const accounts = extractLocalSalesNavigatorElements(response.body)
13607
- .map((element) => typeof element === "object" && element !== null && !Array.isArray(element)
13608
- ? normalizeLocalSalesNavigatorAccount(element, queryUrl)
13609
- : null)
13610
- .filter((value) => value !== null);
13611
- account = exactCliImportAccountMatch(candidate, accounts);
13612
+ for (const searchName of searchNames) {
13613
+ const queryUrl = buildCliImportAccountSearchUrl(searchName);
13614
+ const response = await fetchCliImportSalesNavigatorJson({
13615
+ url: queryUrl,
13616
+ config,
13617
+ browserRelay,
13618
+ timeoutMs,
13619
+ label: `Sales Navigator Account Search for ${searchName}`,
13620
+ });
13621
+ if (response.status < 200 || response.status >= 300) {
13622
+ throw new Error(`Sales Navigator Account Search for ${searchName} returned ${response.status}.`);
13623
+ }
13624
+ if (response.body) {
13625
+ const accounts = extractLocalSalesNavigatorElements(response.body)
13626
+ .map((element) => typeof element === "object" && element !== null && !Array.isArray(element)
13627
+ ? normalizeLocalSalesNavigatorAccount(element, queryUrl)
13628
+ : null)
13629
+ .filter((value) => value !== null);
13630
+ account = exactCliImportAccountMatch(candidate, accounts);
13631
+ }
13632
+ if (account)
13633
+ break;
13612
13634
  }
13613
13635
  salesNavCompanyUrl =
13614
13636
  (typeof account?.salesNavCompanyUrl === "string"
@@ -13708,6 +13730,7 @@ function buildCliImportCompanyProfile(resolution, detail, input) {
13708
13730
  error: input.error ??
13709
13731
  `Sales Navigator company detail returned ${input.status || "no result"}`,
13710
13732
  rawPayload: {
13733
+ resolutionVersion: CLI_IMPORT_COMPANY_RESOLUTION_VERSION,
13711
13734
  accountSearchUsed: resolution.accountSearchUsed,
13712
13735
  httpStatus: input.status,
13713
13736
  },
@@ -13780,6 +13803,7 @@ function buildCliImportCompanyProfile(resolution, detail, input) {
13780
13803
  status: "enriched",
13781
13804
  error: null,
13782
13805
  rawPayload: {
13806
+ resolutionVersion: CLI_IMPORT_COMPANY_RESOLUTION_VERSION,
13783
13807
  accountSearchUsed: resolution.accountSearchUsed,
13784
13808
  httpStatus: input.status,
13785
13809
  detail,
@@ -13791,6 +13815,12 @@ async function runSalesNavigatorImportEnrichmentCommand(options) {
13791
13815
  if (options.companiesOnly && options.hunterOnly) {
13792
13816
  throw new Error("Use either --companies-only or --hunter-only, not both.");
13793
13817
  }
13818
+ if (options.retryCompanies && options.retryNotFoundCompanies) {
13819
+ throw new Error("Use either --retry-companies or --retry-not-found-companies, not both.");
13820
+ }
13821
+ if (options.retryEmails && options.retryFailedEmails) {
13822
+ throw new Error("Use either --retry-emails or --retry-failed-emails, not both.");
13823
+ }
13794
13824
  const runIds = Array.from(new Set(options.runId.map((runId) => z.string().uuid().parse(runId))));
13795
13825
  const companyTimeoutMs = z.coerce.number().int().min(1000).max(120000).parse(options.companyTimeoutMs);
13796
13826
  const companyDelayMinMs = z.coerce.number().int().min(0).parse(options.companyDelayMinMs);
@@ -13805,13 +13835,24 @@ async function runSalesNavigatorImportEnrichmentCommand(options) {
13805
13835
  const session = await requireAuthSession();
13806
13836
  const startedAt = new Date().toISOString();
13807
13837
  const initialStatus = await getCliImportEnrichmentStatusViaApp(session, runIds);
13838
+ const companyRetryMode = options.retryCompanies
13839
+ ? "all"
13840
+ : options.retryNotFoundCompanies
13841
+ ? "not_found"
13842
+ : "default";
13843
+ const retryStatuses = options.retryEmails
13844
+ ? ["not_found", "failed"]
13845
+ : options.retryFailedEmails
13846
+ ? ["failed"]
13847
+ : [];
13808
13848
  if (options.dryRun) {
13809
13849
  const companyCandidates = options.hunterOnly
13810
13850
  ? []
13811
13851
  : (await getCliImportCompanyCandidatesViaApp(session, {
13812
13852
  runIds,
13813
13853
  limit: 1000,
13814
- retry: options.retryCompanies
13854
+ retryMode: companyRetryMode,
13855
+ resolutionVersion: CLI_IMPORT_COMPANY_RESOLUTION_VERSION,
13815
13856
  })).companies.slice(0, maxCompanies || undefined);
13816
13857
  return {
13817
13858
  status: "ok",
@@ -13819,6 +13860,9 @@ async function runSalesNavigatorImportEnrichmentCommand(options) {
13819
13860
  runIds,
13820
13861
  initial: initialStatus,
13821
13862
  companyCandidates: companyCandidates.length,
13863
+ companyRetryMode,
13864
+ hunterCompanyFallback: options.hunterCompanyFallback,
13865
+ retryEmailStatuses: retryStatuses,
13822
13866
  next: options.companiesOnly
13823
13867
  ? "company enrichment only"
13824
13868
  : options.hunterOnly
@@ -13838,7 +13882,8 @@ async function runSalesNavigatorImportEnrichmentCommand(options) {
13838
13882
  const companyCandidates = (await getCliImportCompanyCandidatesViaApp(session, {
13839
13883
  runIds,
13840
13884
  limit: 1000,
13841
- retry: options.retryCompanies
13885
+ retryMode: companyRetryMode,
13886
+ resolutionVersion: CLI_IMPORT_COMPANY_RESOLUTION_VERSION,
13842
13887
  })).companies.slice(0, maxCompanies || undefined);
13843
13888
  companies.selected = companyCandidates.length;
13844
13889
  if (companyCandidates.length > 0) {
@@ -14088,15 +14133,21 @@ async function runSalesNavigatorImportEnrichmentCommand(options) {
14088
14133
  processed: 0,
14089
14134
  found: 0,
14090
14135
  notFound: 0,
14091
- failed: 0
14136
+ failed: 0,
14137
+ domainLookups: 0,
14138
+ companyLookups: 0,
14092
14139
  };
14093
14140
  if (!options.companiesOnly) {
14094
- const retryBefore = options.retryEmails ? startedAt : null;
14141
+ const retryBefore = options.retryEmails || options.retryFailedEmails
14142
+ ? startedAt
14143
+ : null;
14095
14144
  while (maxHunterBatches === 0 || hunter.batches < maxHunterBatches) {
14096
14145
  const batch = await runCliImportHunterBatchViaApp(session, {
14097
14146
  runIds,
14098
14147
  limit: hunterBatchSize,
14099
- retryBefore
14148
+ retryBefore,
14149
+ allowCompanyFallback: options.hunterCompanyFallback,
14150
+ retryStatuses,
14100
14151
  });
14101
14152
  if (batch.selected === 0)
14102
14153
  break;
@@ -14106,7 +14157,9 @@ async function runSalesNavigatorImportEnrichmentCommand(options) {
14106
14157
  processed: hunter.processed + batch.processed,
14107
14158
  found: hunter.found + batch.found,
14108
14159
  notFound: hunter.notFound + batch.notFound,
14109
- failed: hunter.failed + batch.failed
14160
+ failed: hunter.failed + batch.failed,
14161
+ domainLookups: hunter.domainLookups + batch.domainLookups,
14162
+ companyLookups: hunter.companyLookups + batch.companyLookups,
14110
14163
  };
14111
14164
  writeProgress(`Hunter enrichment: ${hunter.processed} checked, ${hunter.found} emails found.`);
14112
14165
  if (batch.halted) {
@@ -14245,9 +14298,12 @@ program
14245
14298
  .option("--hunter-batch-size <number>", "Hunter contacts checked per request", "100")
14246
14299
  .option("--max-hunter-batches <number>", "Maximum Hunter batches; 0 continues until no eligible people remain", "0")
14247
14300
  .option("--companies-only", "Stop after company profiles and domains", false)
14248
- .option("--hunter-only", "Skip company lookup and use already stored domains", false)
14249
- .option("--retry-companies", "Retry previously saved company failures", false)
14250
- .option("--retry-emails", "Retry previously checked unresolved emails once", false)
14301
+ .option("--hunter-only", "Skip company lookup and use stored domains or company-name fallback", false)
14302
+ .option("--retry-companies", "Refresh every previously saved company profile", false)
14303
+ .option("--retry-not-found-companies", "Retry only previous exact company misses with the current resolver", false)
14304
+ .option("--retry-emails", "Retry previous Hunter misses and failures once", false)
14305
+ .option("--retry-failed-emails", "Retry only previous Hunter failures once", false)
14306
+ .option("--no-hunter-company-fallback", "Require a verified domain and skip Hunter company-name fallback")
14251
14307
  .option("--dry-run", "Show scope without LinkedIn or Hunter requests", false)
14252
14308
  .option("--out <path>", "Optional JSON result path")
14253
14309
  .action(async (options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
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",