salesprompter-cli 0.1.64 → 0.1.66

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 +5 -0
  2. package/dist/cli.js +57 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -73,6 +73,11 @@ salesprompter contacts:resolve-emails --in ./contacts.tsv --out-dir ./email-run
73
73
  salesprompter leads:collect \
74
74
  --linkedin-url "$SALES_NAV_PEOPLE_URL"
75
75
 
76
+ # For large discovery runs, store the leads now and enrich selected imports later
77
+ salesprompter leads:collect \
78
+ --linkedin-url "$SALES_NAV_PEOPLE_URL" \
79
+ --defer-email-enrichment
80
+
76
81
  # Enrich one or more stored imports: Sales Navigator company -> official domain -> Hunter email
77
82
  # This updates workspace data only. It does not create or start outreach.
78
83
  # Stale ids and raw legal-name misses use conservative exact-name recovery.
package/dist/cli.js CHANGED
@@ -186,21 +186,6 @@ const AffiliateStrictSequenceSchema = z.tuple([
186
186
  AffiliateStrictSequenceStepSchema.extend({ step: z.literal(2) }),
187
187
  AffiliateStrictSequenceStepSchema.extend({ step: z.literal(3) })
188
188
  ]);
189
- const AffiliateOutreachLeadSchema = z.object({
190
- rowIndex: z.number().int().nonnegative(),
191
- fullName: z.string().min(1),
192
- firstName: z.string().min(1),
193
- lastName: z.string().min(1),
194
- jobTitle: z.string().nullable(),
195
- companyName: z.string().min(1),
196
- companyDomain: z.string().min(1),
197
- location: z.string().nullable(),
198
- profileUrl: z.string().nullable(),
199
- email: z.string().email(),
200
- emailScore: z.number().min(0).max(100),
201
- acceptAll: z.boolean(),
202
- emailSource: z.enum(["hunter", "phantombuster"]).optional()
203
- });
204
189
  const AffiliateOutreachRunBaseSchema = z.object({
205
190
  id: z.string().uuid(),
206
191
  audience_run_id: z.string().uuid(),
@@ -210,7 +195,6 @@ const AffiliateOutreachRunBaseSchema = z.object({
210
195
  instantly_campaign_id: z.string().min(1).nullable(),
211
196
  instantly_campaign_name: z.string().min(1).nullable(),
212
197
  sequence: z.array(AffiliateHistoricalSequenceStepSchema),
213
- enriched_leads: z.array(AffiliateOutreachLeadSchema),
214
198
  stats: z.record(z.string(), z.unknown()),
215
199
  settings: z.record(z.string(), z.unknown()),
216
200
  error_message: z.string().nullable(),
@@ -234,9 +218,7 @@ const AffiliateOutreachResponseSchema = z.object({
234
218
  status: z.literal("ok"),
235
219
  outreach: AffiliateOutreachRunSchema
236
220
  });
237
- const AffiliateOutreachSummarySchema = AffiliateOutreachRunBaseSchema.omit({
238
- enriched_leads: true
239
- });
221
+ const AffiliateOutreachSummarySchema = AffiliateOutreachRunBaseSchema;
240
222
  const AffiliateAudienceSummarySchema = z.object({
241
223
  public_token: z.string().min(1),
242
224
  status: z.enum(["launching", "running", "finished", "failed"]),
@@ -314,6 +296,23 @@ const SalesNavigatorLocalImportResponseSchema = z.object({
314
296
  })
315
297
  .nullable()
316
298
  .optional(),
299
+ emailEnrichment: z
300
+ .object({
301
+ scheduled: z.boolean(),
302
+ deferred: z.boolean().optional(),
303
+ provider: z.string().min(1).optional(),
304
+ fallbackProvider: z.string().min(1).optional(),
305
+ })
306
+ .optional(),
307
+ enrichmentDeferred: z.boolean().optional(),
308
+ outreachStarted: z.literal(false).optional(),
309
+ });
310
+ const SalesNavigatorLocalImportCapabilitiesResponseSchema = z.object({
311
+ status: z.literal("ok"),
312
+ capabilities: z.object({
313
+ deferredEmailEnrichment: z.literal(true),
314
+ }),
315
+ outreachStarted: z.literal(false),
317
316
  });
318
317
  const PhantombusterContainersSyncResponseSchema = z.object({
319
318
  status: z.literal("ok"),
@@ -9293,6 +9292,9 @@ async function importLocalSalesNavigatorPeopleViaApp(session, payload) {
9293
9292
  totalResults: payload.totalResults,
9294
9293
  fetchedPages: payload.fetchedPages,
9295
9294
  people: payload.people,
9295
+ ...(payload.startEmailEnrichment === false
9296
+ ? { startEmailEnrichment: false }
9297
+ : {}),
9296
9298
  rawPayload: {
9297
9299
  ...payload.rawPayload,
9298
9300
  clientId: workspaceClientId ?? undefined,
@@ -9323,6 +9325,20 @@ async function importLocalSalesNavigatorPeopleViaApp(session, payload) {
9323
9325
  }, SalesNavigatorLocalImportResponseSchema);
9324
9326
  return value;
9325
9327
  }
9328
+ async function requireDeferredEmailEnrichmentCapability(session) {
9329
+ try {
9330
+ const result = await fetchCliJson(session, (currentSession) => fetch(`${currentSession.apiBaseUrl}/api/cli/salesnav/local-import`, {
9331
+ method: "GET",
9332
+ headers: {
9333
+ Authorization: `Bearer ${currentSession.accessToken}`,
9334
+ },
9335
+ }), SalesNavigatorLocalImportCapabilitiesResponseSchema);
9336
+ return result.session;
9337
+ }
9338
+ catch {
9339
+ throw new Error("This Salesprompter workspace cannot safely defer email enrichment yet. Update the app before collecting leads with --defer-email-enrichment.");
9340
+ }
9341
+ }
9326
9342
  const SALESPROMPTER_SUPABASE_TABLES = {
9327
9343
  accountSearchRuns: "salesprompter_linkedin_sales_nav_account_search_runs",
9328
9344
  accountSearchSlices: "salesprompter_linkedin_sales_nav_account_search_slices",
@@ -13831,6 +13847,7 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
13831
13847
  .max(accessibleResultLimit)
13832
13848
  .parse(options.maxResults);
13833
13849
  const complete = Boolean(options.complete || options.maxResults == null);
13850
+ const emailEnrichmentDeferred = Boolean(options.deferEmailEnrichment);
13834
13851
  const checkpointPath = complete
13835
13852
  ? options.checkpoint
13836
13853
  ? path.resolve(options.checkpoint)
@@ -13845,13 +13862,18 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
13845
13862
  accessibleResultLimit,
13846
13863
  complete,
13847
13864
  resumable: complete,
13865
+ emailEnrichmentDeferred,
13866
+ outreachStarted: false,
13848
13867
  destination: "/leads/cli-imports"
13849
13868
  };
13850
13869
  if (options.out)
13851
13870
  await writeJsonFile(options.out, payload);
13852
13871
  return payload;
13853
13872
  }
13854
- const session = await requireAuthSession();
13873
+ let session = await requireAuthSession();
13874
+ if (emailEnrichmentDeferred) {
13875
+ session = await requireDeferredEmailEnrichmentCapability(session);
13876
+ }
13855
13877
  const pageDelayMinMs = z.coerce.number().int().min(0).parse(options.pageDelayMinMs);
13856
13878
  const pageDelayMaxMs = z.coerce.number().int().min(pageDelayMinMs).parse(options.pageDelayMaxMs);
13857
13879
  const browserRelayPort = options.browserRelayPort == null
@@ -13917,6 +13939,9 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
13917
13939
  slicePreset: adaptiveCollection
13918
13940
  ? "local-salesnav-people-adaptive"
13919
13941
  : "local-salesnav-people",
13942
+ ...(emailEnrichmentDeferred
13943
+ ? { startEmailEnrichment: false }
13944
+ : {}),
13920
13945
  rawPayload: adaptiveCollection
13921
13946
  ? {
13922
13947
  collectionMode: adaptiveCollection.collectionMode,
@@ -13932,6 +13957,13 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
13932
13957
  accessibleResultLimit,
13933
13958
  },
13934
13959
  });
13960
+ if (emailEnrichmentDeferred &&
13961
+ !(imported.enrichmentDeferred === true &&
13962
+ imported.emailEnrichment?.deferred === true &&
13963
+ imported.emailEnrichment.scheduled === false &&
13964
+ imported.outreachStarted === false)) {
13965
+ throw new Error(`Import ${imported.runId} was stored, but the workspace did not confirm that email enrichment was deferred. Do not repeat the collection until that run's enrichment state is verified.`);
13966
+ }
13935
13967
  const payload = {
13936
13968
  status: "ok",
13937
13969
  dryRun: false,
@@ -13960,6 +13992,10 @@ async function runSalesNavigatorPeopleCollectCommand(options) {
13960
13992
  runId: imported.runId,
13961
13993
  imported: imported.imported,
13962
13994
  upserted: imported.upserted,
13995
+ emailEnrichmentDeferred: imported.enrichmentDeferred ??
13996
+ imported.emailEnrichment?.deferred ??
13997
+ false,
13998
+ outreachStarted: imported.outreachStarted ?? false,
13963
13999
  workspaceUrl: `${session.apiBaseUrl}/leads/cli-imports?runId=` +
13964
14000
  encodeURIComponent(imported.runId)
13965
14001
  };
@@ -14825,6 +14861,7 @@ program
14825
14861
  .option("--max-results <number>", "Bounded collection size; defaults to exhaustive collection with a 2500-person window")
14826
14862
  .option("--complete", "Split oversized searches by company headcount and deduplicate the complete result", false)
14827
14863
  .option("--checkpoint <path>", "Optional resume checkpoint; exhaustive runs use a per-query checkpoint by default")
14864
+ .option("--defer-email-enrichment", "Store leads without starting email enrichment", false)
14828
14865
  .option("--curl-file <path>", "Optional copied Sales Navigator Lead Search curl request")
14829
14866
  .option("--browser-relay-port <number>", "Use the signed-in browser through a loopback relay")
14830
14867
  .option("--page-size <number>", "Direct Sales Navigator page size", "100")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.64",
3
+ "version": "0.1.66",
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",