salesprompter-cli 0.1.15 → 0.1.17
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/dist/cli.js +185 -114
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { access } from "node:fs/promises";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { emitKeypressEvents } from "node:readline";
|
|
5
6
|
import { createInterface } from "node:readline/promises";
|
|
@@ -176,6 +177,13 @@ function deriveCompanyNameFromDomain(domain) {
|
|
|
176
177
|
function writeWizardLine(message = "") {
|
|
177
178
|
process.stdout.write(`${message}\n`);
|
|
178
179
|
}
|
|
180
|
+
function writeWizardSection(title, description) {
|
|
181
|
+
writeWizardLine(title);
|
|
182
|
+
if (description) {
|
|
183
|
+
writeWizardLine(description);
|
|
184
|
+
}
|
|
185
|
+
writeWizardLine();
|
|
186
|
+
}
|
|
179
187
|
function isOpaqueOrgId(value) {
|
|
180
188
|
return /^org_[A-Za-z0-9]+$/.test(value);
|
|
181
189
|
}
|
|
@@ -196,6 +204,22 @@ function writeSessionSummary(session) {
|
|
|
196
204
|
writeWizardLine(`Workspace: ${orgLabel}`);
|
|
197
205
|
}
|
|
198
206
|
}
|
|
207
|
+
async function fileExists(filePath) {
|
|
208
|
+
try {
|
|
209
|
+
await access(filePath);
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function buildLeadOutputPaths(baseSlug) {
|
|
217
|
+
return {
|
|
218
|
+
leadsPath: `./data/${baseSlug}-leads.json`,
|
|
219
|
+
enrichedPath: `./data/${baseSlug}-enriched.json`,
|
|
220
|
+
scoredPath: `./data/${baseSlug}-scored.json`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
199
223
|
function normalizeChoiceText(value) {
|
|
200
224
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
201
225
|
}
|
|
@@ -385,6 +409,46 @@ async function promptYesNo(rl, prompt, defaultValue) {
|
|
|
385
409
|
writeWizardLine("Please answer yes or no.");
|
|
386
410
|
}
|
|
387
411
|
}
|
|
412
|
+
async function maybeSearchLeadDataNow(rl, options) {
|
|
413
|
+
const shouldSearch = await promptYesNo(rl, "Do you want to search your lead data for matches now?", false);
|
|
414
|
+
writeWizardLine();
|
|
415
|
+
if (!shouldSearch) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
writeWizardSection("Search your lead data", "I will use the ICP you just saved.");
|
|
419
|
+
await runVendorLookupWizard(rl, { icpPath: options.icpPath });
|
|
420
|
+
}
|
|
421
|
+
async function maybePrepareLeadsForOutreach(rl, options) {
|
|
422
|
+
const shouldScore = await promptYesNo(rl, "Do you want me to score these leads for outreach?", false);
|
|
423
|
+
writeWizardLine();
|
|
424
|
+
if (!shouldScore) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
writeWizardSection("Prepare for outreach", "I will enrich and score the leads you just saved.");
|
|
428
|
+
const { enrichedPath, scoredPath } = buildLeadOutputPaths(options.baseSlug);
|
|
429
|
+
const enriched = await enrichmentProvider.enrichLeads(options.leads);
|
|
430
|
+
const scored = await scoringProvider.scoreLeads(options.icp, enriched);
|
|
431
|
+
await writeJsonFile(enrichedPath, enriched);
|
|
432
|
+
await writeJsonFile(scoredPath, scored);
|
|
433
|
+
writeWizardLine(`Saved enriched leads to ${enrichedPath}.`);
|
|
434
|
+
writeWizardLine(`Saved scored leads to ${scoredPath}.`);
|
|
435
|
+
writeWizardLine();
|
|
436
|
+
writeWizardLine("Equivalent raw commands:");
|
|
437
|
+
writeWizardLine(` ${buildCommandLine(["salesprompter", "leads:enrich", "--in", options.leadPath, "--out", enrichedPath])}`);
|
|
438
|
+
writeWizardLine(` ${buildCommandLine(["salesprompter", "leads:score", "--icp", options.icpPath, "--in", enrichedPath, "--out", scoredPath])}`);
|
|
439
|
+
if (!process.env.INSTANTLY_API_KEY || process.env.INSTANTLY_API_KEY.trim().length === 0) {
|
|
440
|
+
writeWizardLine();
|
|
441
|
+
writeWizardLine("You can send the scored leads to Instantly later from the main menu.");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
writeWizardLine();
|
|
445
|
+
const shouldSync = await promptYesNo(rl, "Do you want to send these leads to Instantly now?", false);
|
|
446
|
+
writeWizardLine();
|
|
447
|
+
if (!shouldSync) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
await runOutreachSyncWizard(rl, { inputPath: scoredPath });
|
|
451
|
+
}
|
|
388
452
|
async function ensureWizardSession(options) {
|
|
389
453
|
if (shouldBypassAuth()) {
|
|
390
454
|
return null;
|
|
@@ -412,11 +476,11 @@ async function ensureWizardSession(options) {
|
|
|
412
476
|
return result.session;
|
|
413
477
|
}
|
|
414
478
|
async function runVendorIcpWizard(rl) {
|
|
415
|
-
const startPoint = await promptChoice(rl, "How do you want to
|
|
479
|
+
const startPoint = await promptChoice(rl, "How do you want to build your ICP?", [
|
|
416
480
|
{
|
|
417
481
|
value: "custom",
|
|
418
|
-
label: "
|
|
419
|
-
description: "Answer a few questions about
|
|
482
|
+
label: "Start from scratch",
|
|
483
|
+
description: "Answer a few questions about the companies you want to sell to",
|
|
420
484
|
aliases: ["custom", "from scratch", "new profile"]
|
|
421
485
|
},
|
|
422
486
|
{
|
|
@@ -428,21 +492,29 @@ async function runVendorIcpWizard(rl) {
|
|
|
428
492
|
], "custom");
|
|
429
493
|
writeWizardLine();
|
|
430
494
|
if (startPoint === "custom") {
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
const companySizes = await promptText(rl, "Target company sizes (optional, comma-separated)");
|
|
435
|
-
const regions = await promptText(rl, "Target regions (optional, comma-separated)");
|
|
436
|
-
const countries = await promptText(rl, "Target countries (optional, comma-separated)");
|
|
437
|
-
const titles = await promptText(rl, "Target job titles (optional, comma-separated)");
|
|
438
|
-
const keywords = await promptText(rl, "Keywords or buying signals (optional, comma-separated)");
|
|
495
|
+
writeWizardSection("Define your ICP", "Start with the basics. You can add more filters if you want.");
|
|
496
|
+
const productName = await promptText(rl, "What do you sell?", { required: true });
|
|
497
|
+
const description = await promptText(rl, "Short description (optional)");
|
|
439
498
|
writeWizardLine();
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
499
|
+
let industries = "";
|
|
500
|
+
let companySizes = "";
|
|
501
|
+
let regions = "";
|
|
502
|
+
let countries = "";
|
|
503
|
+
let titles = "";
|
|
504
|
+
let keywords = "";
|
|
505
|
+
const addDetails = await promptYesNo(rl, "Do you want to add industries, company size, region, or title filters?", false);
|
|
445
506
|
writeWizardLine();
|
|
507
|
+
if (addDetails) {
|
|
508
|
+
industries = await promptText(rl, "Industries to target (optional, comma-separated)");
|
|
509
|
+
companySizes = await promptText(rl, "Company sizes to target (optional, comma-separated)");
|
|
510
|
+
regions = await promptText(rl, "Regions to target (optional, comma-separated)");
|
|
511
|
+
countries = await promptText(rl, "Countries to target (optional, comma-separated)");
|
|
512
|
+
titles = await promptText(rl, "Job titles to target (optional, comma-separated)");
|
|
513
|
+
keywords = await promptText(rl, "Keywords or buying signals (optional, comma-separated)");
|
|
514
|
+
writeWizardLine();
|
|
515
|
+
}
|
|
516
|
+
const slug = slugify(productName) || "icp";
|
|
517
|
+
const outPath = `./data/${slug}-icp.json`;
|
|
446
518
|
const icp = IcpSchema.parse({
|
|
447
519
|
name: `${productName} ICP`,
|
|
448
520
|
description,
|
|
@@ -455,7 +527,7 @@ async function runVendorIcpWizard(rl) {
|
|
|
455
527
|
});
|
|
456
528
|
await writeJsonFile(outPath, icp);
|
|
457
529
|
writeWizardLine(`Created ${icp.name}.`);
|
|
458
|
-
writeWizardLine(`Saved
|
|
530
|
+
writeWizardLine(`Saved profile to ${outPath}.`);
|
|
459
531
|
writeWizardLine();
|
|
460
532
|
writeWizardLine("Equivalent raw command:");
|
|
461
533
|
const defineArgs = ["salesprompter", "icp:define", "--name", icp.name];
|
|
@@ -483,73 +555,51 @@ async function runVendorIcpWizard(rl) {
|
|
|
483
555
|
defineArgs.push("--out", outPath);
|
|
484
556
|
writeWizardLine(` ${buildCommandLine(defineArgs)}`);
|
|
485
557
|
writeWizardLine();
|
|
486
|
-
|
|
487
|
-
writeWizardLine(` ${buildCommandLine([
|
|
488
|
-
"salesprompter",
|
|
489
|
-
"leads:generate",
|
|
490
|
-
"--icp",
|
|
491
|
-
outPath,
|
|
492
|
-
"--count",
|
|
493
|
-
"5",
|
|
494
|
-
"--out",
|
|
495
|
-
`./data/${slug}-leads.json`
|
|
496
|
-
])}`);
|
|
558
|
+
await maybeSearchLeadDataNow(rl, { icpPath: outPath });
|
|
497
559
|
return;
|
|
498
560
|
}
|
|
499
561
|
const vendor = "deel";
|
|
562
|
+
writeWizardSection("Define your ICP", "Use the built-in Deel template and choose a market.");
|
|
500
563
|
writeWizardLine("Using the built-in Deel ICP template.");
|
|
501
564
|
writeWizardLine();
|
|
502
|
-
const market = await promptChoice(rl, "Which market do you want to
|
|
565
|
+
const market = await promptChoice(rl, "Which market do you want to focus on?", [
|
|
503
566
|
{ value: "dach", label: "DACH", description: "Germany, Austria, Switzerland" },
|
|
504
567
|
{ value: "europe", label: "Europe" },
|
|
505
568
|
{ value: "global", label: "Global" }
|
|
506
569
|
], "dach");
|
|
507
570
|
writeWizardLine();
|
|
508
|
-
const outPath =
|
|
509
|
-
defaultValue: `./data/${slugify(vendor)}-icp-${market}.json`,
|
|
510
|
-
required: true
|
|
511
|
-
});
|
|
512
|
-
writeWizardLine();
|
|
571
|
+
const outPath = `./data/${slugify(vendor)}-icp-${market}.json`;
|
|
513
572
|
const icp = buildVendorIcp(vendor, market);
|
|
514
573
|
await writeJsonFile(outPath, icp);
|
|
515
574
|
writeWizardLine(`Created ${icp.name}.`);
|
|
516
|
-
writeWizardLine(`Saved
|
|
575
|
+
writeWizardLine(`Saved profile to ${outPath}.`);
|
|
517
576
|
writeWizardLine();
|
|
518
577
|
writeWizardLine("Equivalent raw command:");
|
|
519
578
|
writeWizardLine(` ${buildCommandLine(["salesprompter", "icp:vendor", "--vendor", vendor, "--market", market, "--out", outPath])}`);
|
|
520
579
|
writeWizardLine();
|
|
521
|
-
|
|
522
|
-
writeWizardLine(` ${buildCommandLine([
|
|
523
|
-
"salesprompter",
|
|
524
|
-
"leads:lookup:bq",
|
|
525
|
-
"--icp",
|
|
526
|
-
outPath,
|
|
527
|
-
"--limit",
|
|
528
|
-
"100",
|
|
529
|
-
"--lead-out",
|
|
530
|
-
`./data/${slugify(vendor)}-leads.json`
|
|
531
|
-
])}`);
|
|
580
|
+
await maybeSearchLeadDataNow(rl, { icpPath: outPath });
|
|
532
581
|
}
|
|
533
582
|
async function runTargetAccountWizard(rl) {
|
|
534
|
-
|
|
583
|
+
writeWizardSection("Pick a company", "Start with the company and how many people you want.");
|
|
584
|
+
const domain = normalizeDomainInput(await promptText(rl, "Which company do you want leads from? Enter the domain", { required: true }));
|
|
535
585
|
writeWizardLine();
|
|
536
|
-
const
|
|
537
|
-
const
|
|
538
|
-
const leadCount = z.coerce.number().int().min(1).max(1000).parse(await promptText(rl, "How many leads should I generate?", { defaultValue: "5", required: true }));
|
|
539
|
-
const region = await promptText(rl, "Primary region hint", { defaultValue: "Global", required: true });
|
|
540
|
-
const industries = await promptText(rl, "Industry hint (optional, comma-separated)");
|
|
541
|
-
const titles = await promptText(rl, "Target titles (optional, comma-separated)");
|
|
586
|
+
const displayName = deriveCompanyNameFromDomain(domain);
|
|
587
|
+
const leadCount = z.coerce.number().int().min(1).max(1000).parse(await promptText(rl, "How many people do you want?", { defaultValue: "5", required: true }));
|
|
542
588
|
writeWizardLine();
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
});
|
|
548
|
-
const leadsPath = await promptText(rl, "Where should I save the generated leads JSON?", {
|
|
549
|
-
defaultValue: `./data/${slug}-leads.json`,
|
|
550
|
-
required: true
|
|
551
|
-
});
|
|
589
|
+
let region = "Global";
|
|
590
|
+
let industries = "";
|
|
591
|
+
let titles = "";
|
|
592
|
+
const narrowResults = await promptYesNo(rl, "Do you want to narrow the results by region, industry, or title?", false);
|
|
552
593
|
writeWizardLine();
|
|
594
|
+
if (narrowResults) {
|
|
595
|
+
region = await promptText(rl, "Region", { defaultValue: "Global", required: true });
|
|
596
|
+
industries = await promptText(rl, "Industries (optional, comma-separated)");
|
|
597
|
+
titles = await promptText(rl, "Job titles (optional, comma-separated)");
|
|
598
|
+
writeWizardLine();
|
|
599
|
+
}
|
|
600
|
+
const slug = slugify(domain);
|
|
601
|
+
const icpPath = `./data/${slug}-target-icp.json`;
|
|
602
|
+
const { leadsPath } = buildLeadOutputPaths(slug);
|
|
553
603
|
const icp = IcpSchema.parse({
|
|
554
604
|
name: `${displayName} target account`,
|
|
555
605
|
regions: region.length > 0 ? [region] : [],
|
|
@@ -558,12 +608,11 @@ async function runTargetAccountWizard(rl) {
|
|
|
558
608
|
});
|
|
559
609
|
await writeJsonFile(icpPath, icp);
|
|
560
610
|
const result = await leadProvider.generateLeads(icp, leadCount, {
|
|
561
|
-
companyDomain: domain
|
|
562
|
-
companyName: companyName || undefined
|
|
611
|
+
companyDomain: domain
|
|
563
612
|
});
|
|
564
613
|
await writeJsonFile(leadsPath, result.leads);
|
|
565
614
|
writeWizardLine(`Generated ${result.leads.length} leads for ${result.account.companyName} (${result.account.domain}).`);
|
|
566
|
-
writeWizardLine(`Saved
|
|
615
|
+
writeWizardLine(`Saved profile to ${icpPath}.`);
|
|
567
616
|
writeWizardLine(`Saved leads to ${leadsPath}.`);
|
|
568
617
|
if (result.warnings.length > 0) {
|
|
569
618
|
writeWizardLine();
|
|
@@ -584,11 +633,16 @@ async function runTargetAccountWizard(rl) {
|
|
|
584
633
|
defineArgs.push("--out", icpPath);
|
|
585
634
|
writeWizardLine(` ${buildCommandLine(defineArgs)}`);
|
|
586
635
|
const leadArgs = ["salesprompter", "leads:generate", "--icp", icpPath, "--count", String(leadCount), "--domain", domain];
|
|
587
|
-
if (companyName.trim().length > 0) {
|
|
588
|
-
leadArgs.push("--company-name", companyName);
|
|
589
|
-
}
|
|
590
636
|
leadArgs.push("--out", leadsPath);
|
|
591
637
|
writeWizardLine(` ${buildCommandLine(leadArgs)}`);
|
|
638
|
+
writeWizardLine();
|
|
639
|
+
await maybePrepareLeadsForOutreach(rl, {
|
|
640
|
+
baseSlug: slug,
|
|
641
|
+
icp,
|
|
642
|
+
icpPath,
|
|
643
|
+
leadPath: leadsPath,
|
|
644
|
+
leads: result.leads
|
|
645
|
+
});
|
|
592
646
|
}
|
|
593
647
|
async function runLeadGenerationWizard(rl) {
|
|
594
648
|
const source = await promptChoice(rl, "How do you want to generate leads?", [
|
|
@@ -600,8 +654,8 @@ async function runLeadGenerationWizard(rl) {
|
|
|
600
654
|
},
|
|
601
655
|
{
|
|
602
656
|
value: "vendor-lookup",
|
|
603
|
-
label: "From my
|
|
604
|
-
description: "
|
|
657
|
+
label: "From my own lead data",
|
|
658
|
+
description: "Search leads you already have in BigQuery",
|
|
605
659
|
aliases: ["bigquery", "warehouse", "my data", "from bigquery"]
|
|
606
660
|
}
|
|
607
661
|
], "target-account");
|
|
@@ -612,33 +666,27 @@ async function runLeadGenerationWizard(rl) {
|
|
|
612
666
|
}
|
|
613
667
|
await runVendorLookupWizard(rl);
|
|
614
668
|
}
|
|
615
|
-
async function runVendorLookupWizard(rl) {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
669
|
+
async function runVendorLookupWizard(rl, options) {
|
|
670
|
+
if (!options?.icpPath) {
|
|
671
|
+
writeWizardSection("Search your lead data", "Use a saved ICP to build the lookup.");
|
|
672
|
+
}
|
|
673
|
+
const icpPath = options?.icpPath
|
|
674
|
+
? options.icpPath
|
|
675
|
+
: await promptText(rl, "Which saved ICP should I use?", {
|
|
676
|
+
defaultValue: "./data/icp.json",
|
|
677
|
+
required: true
|
|
678
|
+
});
|
|
679
|
+
if (options?.icpPath) {
|
|
680
|
+
writeWizardLine(`Using profile from ${icpPath}.`);
|
|
681
|
+
}
|
|
682
|
+
const limit = z.coerce.number().int().min(1).max(5000).parse(await promptText(rl, "How many leads do you want?", { defaultValue: "100", required: true }));
|
|
683
|
+
const execute = await promptYesNo(rl, "Do you want me to run the BigQuery search now?", false);
|
|
622
684
|
writeWizardLine();
|
|
623
685
|
const icp = await readJsonFile(icpPath, IcpSchema);
|
|
624
686
|
const slug = slugify(icp.name) || "icp";
|
|
625
|
-
const sqlPath =
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
});
|
|
629
|
-
const rawPath = execute
|
|
630
|
-
? await promptText(rl, "Where should I save raw BigQuery rows?", {
|
|
631
|
-
defaultValue: `./data/${slug}-leads-raw.json`,
|
|
632
|
-
required: true
|
|
633
|
-
})
|
|
634
|
-
: "";
|
|
635
|
-
const leadPath = execute
|
|
636
|
-
? await promptText(rl, "Where should I save normalized leads?", {
|
|
637
|
-
defaultValue: `./data/${slug}-leads.json`,
|
|
638
|
-
required: true
|
|
639
|
-
})
|
|
640
|
-
: "";
|
|
641
|
-
writeWizardLine();
|
|
687
|
+
const sqlPath = `./data/${slug}-lookup.sql`;
|
|
688
|
+
const rawPath = `./data/${slug}-leads-raw.json`;
|
|
689
|
+
const { leadsPath: leadPath } = buildLeadOutputPaths(slug);
|
|
642
690
|
const sql = buildBigQueryLeadLookupSql(icp, {
|
|
643
691
|
table: "icpidentifier.SalesGPT.leadPool_new",
|
|
644
692
|
companyField: "companyName",
|
|
@@ -658,19 +706,19 @@ async function runVendorLookupWizard(rl) {
|
|
|
658
706
|
});
|
|
659
707
|
await writeTextFile(sqlPath, `${sql}\n`);
|
|
660
708
|
let executedRowCount = null;
|
|
709
|
+
let normalizedLeads = [];
|
|
661
710
|
if (execute) {
|
|
662
711
|
const rows = await runBigQueryQuery(sql, { maxRows: limit });
|
|
663
712
|
const parsedRows = z.array(z.record(z.string(), z.unknown())).parse(rows);
|
|
664
713
|
await writeJsonFile(rawPath, parsedRows);
|
|
665
|
-
|
|
714
|
+
normalizedLeads = normalizeBigQueryLeadRows(parsedRows);
|
|
666
715
|
await writeJsonFile(leadPath, normalizedLeads);
|
|
667
716
|
executedRowCount = parsedRows.length;
|
|
668
717
|
}
|
|
669
|
-
writeWizardLine(`
|
|
670
|
-
writeWizardLine(`Saved lookup SQL to ${sqlPath}.`);
|
|
718
|
+
writeWizardLine(`Saved search SQL to ${sqlPath}.`);
|
|
671
719
|
if (execute) {
|
|
672
720
|
writeWizardLine(`Saved ${executedRowCount ?? 0} raw rows to ${rawPath}.`);
|
|
673
|
-
writeWizardLine(`Saved
|
|
721
|
+
writeWizardLine(`Saved leads to ${leadPath}.`);
|
|
674
722
|
}
|
|
675
723
|
writeWizardLine();
|
|
676
724
|
writeWizardLine("Equivalent raw command:");
|
|
@@ -679,25 +727,48 @@ async function runVendorLookupWizard(rl) {
|
|
|
679
727
|
lookupArgs.push("--execute", "--out", rawPath, "--lead-out", leadPath);
|
|
680
728
|
}
|
|
681
729
|
writeWizardLine(` ${buildCommandLine(lookupArgs)}`);
|
|
730
|
+
if (!execute) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
writeWizardLine();
|
|
734
|
+
await maybePrepareLeadsForOutreach(rl, {
|
|
735
|
+
baseSlug: slug,
|
|
736
|
+
icp,
|
|
737
|
+
icpPath,
|
|
738
|
+
leadPath,
|
|
739
|
+
leads: normalizedLeads
|
|
740
|
+
});
|
|
682
741
|
}
|
|
683
|
-
async function runOutreachSyncWizard(rl) {
|
|
742
|
+
async function runOutreachSyncWizard(rl, options) {
|
|
684
743
|
const target = "instantly";
|
|
685
|
-
|
|
744
|
+
const targetLabel = "Instantly";
|
|
745
|
+
writeWizardSection("Send to Instantly", options?.inputPath ? "I already have the scored leads ready." : "Use a scored leads file to fill a campaign.");
|
|
746
|
+
writeWizardLine(`Using ${targetLabel}.`);
|
|
686
747
|
writeWizardLine();
|
|
687
748
|
if (!process.env.INSTANTLY_API_KEY || process.env.INSTANTLY_API_KEY.trim().length === 0) {
|
|
688
749
|
throw new Error("INSTANTLY_API_KEY is required for the Instantly sync flow.");
|
|
689
750
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}
|
|
751
|
+
let inputPath = options?.inputPath?.trim() ?? "";
|
|
752
|
+
if (inputPath.length === 0 && (await fileExists("./data/scored.json"))) {
|
|
753
|
+
inputPath = "./data/scored.json";
|
|
754
|
+
}
|
|
755
|
+
if (inputPath.length === 0) {
|
|
756
|
+
inputPath = await promptText(rl, "Where is your scored leads file?", {
|
|
757
|
+
defaultValue: "./data/scored.json",
|
|
758
|
+
required: true
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
writeWizardLine(`Using scored leads from ${inputPath}.`);
|
|
763
|
+
writeWizardLine();
|
|
764
|
+
}
|
|
694
765
|
const defaultCampaignId = process.env.INSTANTLY_CAMPAIGN_ID?.trim();
|
|
695
|
-
const campaignId = await promptText(rl, "Instantly campaign
|
|
766
|
+
const campaignId = await promptText(rl, "Which Instantly campaign should I use?", {
|
|
696
767
|
defaultValue: defaultCampaignId && defaultCampaignId.length > 0 ? defaultCampaignId : undefined,
|
|
697
768
|
required: defaultCampaignId === undefined || defaultCampaignId.length === 0
|
|
698
769
|
});
|
|
699
|
-
const apply = await promptYesNo(rl, "
|
|
700
|
-
const allowDuplicates = await promptYesNo(rl, "
|
|
770
|
+
const apply = await promptYesNo(rl, "Add these leads to Instantly now?", false);
|
|
771
|
+
const allowDuplicates = await promptYesNo(rl, "Keep leads that are already in the campaign?", false);
|
|
701
772
|
writeWizardLine();
|
|
702
773
|
const leads = await readJsonFile(inputPath, z.array(ScoredLeadSchema));
|
|
703
774
|
const result = await syncProvider.sync(target, leads, {
|
|
@@ -706,12 +777,12 @@ async function runOutreachSyncWizard(rl) {
|
|
|
706
777
|
allowDuplicates
|
|
707
778
|
});
|
|
708
779
|
const skipped = result.skipped ?? 0;
|
|
709
|
-
writeWizardLine(`${apply ? "Sent" : "Prepared"} ${result.synced} lead${result.synced === 1 ? "" : "s"} for ${
|
|
780
|
+
writeWizardLine(`${apply ? "Sent" : "Prepared"} ${result.synced} lead${result.synced === 1 ? "" : "s"} for ${targetLabel}.`);
|
|
710
781
|
if (skipped > 0) {
|
|
711
782
|
writeWizardLine(`Skipped ${skipped} duplicate lead${skipped === 1 ? "" : "s"}.`);
|
|
712
783
|
}
|
|
713
784
|
if (result.dryRun) {
|
|
714
|
-
writeWizardLine("
|
|
785
|
+
writeWizardLine("Nothing was sent yet. Re-run and confirm when you are ready.");
|
|
715
786
|
}
|
|
716
787
|
writeWizardLine();
|
|
717
788
|
writeWizardLine("Equivalent raw command:");
|
|
@@ -749,20 +820,20 @@ async function runWizard(options) {
|
|
|
749
820
|
const flow = await promptChoice(rl, "What do you want help with?", [
|
|
750
821
|
{
|
|
751
822
|
value: "vendor-icp",
|
|
752
|
-
label: "
|
|
753
|
-
description: "
|
|
823
|
+
label: "Define my ICP",
|
|
824
|
+
description: "Build the company profile you want to sell to",
|
|
754
825
|
aliases: ["icp", "ideal customer", "who to target", "targeting", "profile"]
|
|
755
826
|
},
|
|
756
827
|
{
|
|
757
828
|
value: "lead-generation",
|
|
758
829
|
label: "Generate leads",
|
|
759
|
-
description: "Find people at one company or from your
|
|
830
|
+
description: "Find people at one company or from your own lead data",
|
|
760
831
|
aliases: ["leads", "find leads", "lead generation", "find people"]
|
|
761
832
|
},
|
|
762
833
|
{
|
|
763
834
|
value: "outreach-sync",
|
|
764
|
-
label: "
|
|
765
|
-
description: "
|
|
835
|
+
label: "Send leads to Instantly",
|
|
836
|
+
description: "Use a scored leads file to fill an Instantly campaign",
|
|
766
837
|
aliases: ["instantly", "outreach", "send leads", "campaign"]
|
|
767
838
|
}
|
|
768
839
|
], "vendor-icp");
|
package/package.json
CHANGED