salesprompter-cli 0.1.13 → 0.1.14
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 +148 -49
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -175,17 +175,28 @@ function deriveCompanyNameFromDomain(domain) {
|
|
|
175
175
|
function writeWizardLine(message = "") {
|
|
176
176
|
process.stdout.write(`${message}\n`);
|
|
177
177
|
}
|
|
178
|
+
function isOpaqueOrgId(value) {
|
|
179
|
+
return /^org_[A-Za-z0-9]+$/.test(value);
|
|
180
|
+
}
|
|
178
181
|
function getOrgLabel(session) {
|
|
179
|
-
|
|
182
|
+
const label = session.user.orgName ?? session.user.orgSlug ?? session.user.orgId ?? null;
|
|
183
|
+
if (label && isOpaqueOrgId(label)) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
return label;
|
|
180
187
|
}
|
|
181
188
|
function writeSessionSummary(session) {
|
|
189
|
+
const identity = session.user.name?.trim()
|
|
190
|
+
? `${session.user.name} (${session.user.email})`
|
|
191
|
+
: session.user.email;
|
|
192
|
+
writeWizardLine(`Signed in as ${identity}.`);
|
|
182
193
|
const orgLabel = getOrgLabel(session);
|
|
183
194
|
if (orgLabel) {
|
|
184
|
-
writeWizardLine(`
|
|
185
|
-
return;
|
|
195
|
+
writeWizardLine(`Workspace: ${orgLabel}`);
|
|
186
196
|
}
|
|
187
|
-
|
|
188
|
-
|
|
197
|
+
}
|
|
198
|
+
function normalizeChoiceText(value) {
|
|
199
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
189
200
|
}
|
|
190
201
|
async function promptChoice(rl, prompt, options, defaultValue) {
|
|
191
202
|
const defaultIndex = options.findIndex((option) => option.value === defaultValue);
|
|
@@ -198,7 +209,7 @@ async function promptChoice(rl, prompt, options, defaultValue) {
|
|
|
198
209
|
const description = option.description ? ` - ${option.description}` : "";
|
|
199
210
|
writeWizardLine(` ${index + 1}. ${option.label}${description}`);
|
|
200
211
|
}
|
|
201
|
-
const answer = (await rl.question(`
|
|
212
|
+
const answer = (await rl.question(`Select an option (press Enter for ${defaultIndex + 1}): `)).trim();
|
|
202
213
|
if (answer.length === 0) {
|
|
203
214
|
return defaultValue;
|
|
204
215
|
}
|
|
@@ -210,11 +221,25 @@ async function promptChoice(rl, prompt, options, defaultValue) {
|
|
|
210
221
|
}
|
|
211
222
|
return selected.value;
|
|
212
223
|
}
|
|
213
|
-
const
|
|
224
|
+
const normalizedAnswer = normalizeChoiceText(answer);
|
|
225
|
+
const matched = options.find((option) => {
|
|
226
|
+
if (normalizeChoiceText(option.value) === normalizedAnswer) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (normalizeChoiceText(option.label) === normalizedAnswer) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
return (option.aliases ?? []).some((alias) => normalizeChoiceText(alias) === normalizedAnswer);
|
|
233
|
+
});
|
|
214
234
|
if (matched) {
|
|
215
235
|
return matched.value;
|
|
216
236
|
}
|
|
217
|
-
|
|
237
|
+
if (/^(npx|salesprompter)\b/i.test(answer)) {
|
|
238
|
+
writeWizardLine("You are already in the wizard. Pick an option here, or press Ctrl-C to exit.");
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
writeWizardLine("Please choose one of the options above.");
|
|
242
|
+
}
|
|
218
243
|
writeWizardLine();
|
|
219
244
|
}
|
|
220
245
|
}
|
|
@@ -265,7 +290,7 @@ async function ensureWizardSession(options) {
|
|
|
265
290
|
throw error;
|
|
266
291
|
}
|
|
267
292
|
}
|
|
268
|
-
writeWizardLine("
|
|
293
|
+
writeWizardLine("First, sign in to Salesprompter.");
|
|
269
294
|
writeWizardLine();
|
|
270
295
|
const result = await performLogin({
|
|
271
296
|
apiUrl: options?.apiUrl,
|
|
@@ -276,6 +301,90 @@ async function ensureWizardSession(options) {
|
|
|
276
301
|
return result.session;
|
|
277
302
|
}
|
|
278
303
|
async function runVendorIcpWizard(rl) {
|
|
304
|
+
const startPoint = await promptChoice(rl, "How do you want to start?", [
|
|
305
|
+
{
|
|
306
|
+
value: "custom",
|
|
307
|
+
label: "Create a custom profile",
|
|
308
|
+
description: "Answer a few questions about who you want to sell to",
|
|
309
|
+
aliases: ["custom", "from scratch", "new profile"]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
value: "template",
|
|
313
|
+
label: "Use the Deel template",
|
|
314
|
+
description: "Quick start from the current built-in template",
|
|
315
|
+
aliases: ["template", "deel", "use template"]
|
|
316
|
+
}
|
|
317
|
+
], "custom");
|
|
318
|
+
writeWizardLine();
|
|
319
|
+
if (startPoint === "custom") {
|
|
320
|
+
const productName = await promptText(rl, "What are you selling?", { required: true });
|
|
321
|
+
const description = await promptText(rl, "Who is this for? (optional)");
|
|
322
|
+
const industries = await promptText(rl, "Target industries (optional, comma-separated)");
|
|
323
|
+
const companySizes = await promptText(rl, "Target company sizes (optional, comma-separated)");
|
|
324
|
+
const regions = await promptText(rl, "Target regions (optional, comma-separated)");
|
|
325
|
+
const countries = await promptText(rl, "Target countries (optional, comma-separated)");
|
|
326
|
+
const titles = await promptText(rl, "Target job titles (optional, comma-separated)");
|
|
327
|
+
const keywords = await promptText(rl, "Keywords or buying signals (optional, comma-separated)");
|
|
328
|
+
writeWizardLine();
|
|
329
|
+
const slug = slugify(productName) || "icp";
|
|
330
|
+
const outPath = await promptText(rl, "Where should I save the ICP JSON?", {
|
|
331
|
+
defaultValue: `./data/${slug}-icp.json`,
|
|
332
|
+
required: true
|
|
333
|
+
});
|
|
334
|
+
writeWizardLine();
|
|
335
|
+
const icp = IcpSchema.parse({
|
|
336
|
+
name: `${productName} ICP`,
|
|
337
|
+
description,
|
|
338
|
+
industries: splitCsv(industries),
|
|
339
|
+
companySizes: splitCsv(companySizes),
|
|
340
|
+
regions: splitCsv(regions),
|
|
341
|
+
countries: splitCsv(countries),
|
|
342
|
+
titles: splitCsv(titles),
|
|
343
|
+
keywords: splitCsv(keywords)
|
|
344
|
+
});
|
|
345
|
+
await writeJsonFile(outPath, icp);
|
|
346
|
+
writeWizardLine(`Created ${icp.name}.`);
|
|
347
|
+
writeWizardLine(`Saved ICP to ${outPath}.`);
|
|
348
|
+
writeWizardLine();
|
|
349
|
+
writeWizardLine("Equivalent raw command:");
|
|
350
|
+
const defineArgs = ["salesprompter", "icp:define", "--name", icp.name];
|
|
351
|
+
if (description.trim().length > 0) {
|
|
352
|
+
defineArgs.push("--description", description);
|
|
353
|
+
}
|
|
354
|
+
if (industries.trim().length > 0) {
|
|
355
|
+
defineArgs.push("--industries", industries);
|
|
356
|
+
}
|
|
357
|
+
if (companySizes.trim().length > 0) {
|
|
358
|
+
defineArgs.push("--company-sizes", companySizes);
|
|
359
|
+
}
|
|
360
|
+
if (regions.trim().length > 0) {
|
|
361
|
+
defineArgs.push("--regions", regions);
|
|
362
|
+
}
|
|
363
|
+
if (countries.trim().length > 0) {
|
|
364
|
+
defineArgs.push("--countries", countries);
|
|
365
|
+
}
|
|
366
|
+
if (titles.trim().length > 0) {
|
|
367
|
+
defineArgs.push("--titles", titles);
|
|
368
|
+
}
|
|
369
|
+
if (keywords.trim().length > 0) {
|
|
370
|
+
defineArgs.push("--keywords", keywords);
|
|
371
|
+
}
|
|
372
|
+
defineArgs.push("--out", outPath);
|
|
373
|
+
writeWizardLine(` ${buildCommandLine(defineArgs)}`);
|
|
374
|
+
writeWizardLine();
|
|
375
|
+
writeWizardLine("Next suggested command:");
|
|
376
|
+
writeWizardLine(` ${buildCommandLine([
|
|
377
|
+
"salesprompter",
|
|
378
|
+
"leads:generate",
|
|
379
|
+
"--icp",
|
|
380
|
+
outPath,
|
|
381
|
+
"--count",
|
|
382
|
+
"5",
|
|
383
|
+
"--out",
|
|
384
|
+
`./data/${slug}-leads.json`
|
|
385
|
+
])}`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
279
388
|
const vendor = "deel";
|
|
280
389
|
writeWizardLine("Using the built-in Deel ICP template.");
|
|
281
390
|
writeWizardLine();
|
|
@@ -374,13 +483,15 @@ async function runLeadGenerationWizard(rl) {
|
|
|
374
483
|
const source = await promptChoice(rl, "How do you want to generate leads?", [
|
|
375
484
|
{
|
|
376
485
|
value: "target-account",
|
|
377
|
-
label: "At
|
|
378
|
-
description: "Example: find people at
|
|
486
|
+
label: "At one company",
|
|
487
|
+
description: "Example: find people at acme.com",
|
|
488
|
+
aliases: ["company", "one company", "specific company", "account"]
|
|
379
489
|
},
|
|
380
490
|
{
|
|
381
491
|
value: "vendor-lookup",
|
|
382
|
-
label: "From BigQuery",
|
|
383
|
-
description: "Use
|
|
492
|
+
label: "From my BigQuery data",
|
|
493
|
+
description: "Use a saved profile to search the lead data you already have",
|
|
494
|
+
aliases: ["bigquery", "warehouse", "my data", "from bigquery"]
|
|
384
495
|
}
|
|
385
496
|
], "target-account");
|
|
386
497
|
writeWizardLine();
|
|
@@ -391,41 +502,32 @@ async function runLeadGenerationWizard(rl) {
|
|
|
391
502
|
await runVendorLookupWizard(rl);
|
|
392
503
|
}
|
|
393
504
|
async function runVendorLookupWizard(rl) {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
writeWizardLine();
|
|
397
|
-
const market = await promptChoice(rl, "Which market should the BigQuery lookup target?", [
|
|
398
|
-
{ value: "dach", label: "DACH", description: "Germany, Austria, Switzerland" },
|
|
399
|
-
{ value: "europe", label: "Europe" },
|
|
400
|
-
{ value: "global", label: "Global" }
|
|
401
|
-
], "dach");
|
|
402
|
-
const limit = z.coerce.number().int().min(1).max(5000).parse(await promptText(rl, "How many rows should the lookup return?", { defaultValue: "100", required: true }));
|
|
403
|
-
const execute = await promptYesNo(rl, "Execute the BigQuery lookup now?", false);
|
|
404
|
-
writeWizardLine();
|
|
405
|
-
const slug = slugify(vendor);
|
|
406
|
-
const icpPath = await promptText(rl, "Where should I save the ICP JSON?", {
|
|
407
|
-
defaultValue: `./data/${slug}-icp-${market}.json`,
|
|
505
|
+
const icpPath = await promptText(rl, "Where is your ICP JSON file?", {
|
|
506
|
+
defaultValue: "./data/icp.json",
|
|
408
507
|
required: true
|
|
409
508
|
});
|
|
509
|
+
const limit = z.coerce.number().int().min(1).max(5000).parse(await promptText(rl, "How many leads should I look up?", { defaultValue: "100", required: true }));
|
|
510
|
+
const execute = await promptYesNo(rl, "Execute the BigQuery lookup now?", false);
|
|
511
|
+
writeWizardLine();
|
|
512
|
+
const icp = await readJsonFile(icpPath, IcpSchema);
|
|
513
|
+
const slug = slugify(icp.name) || "icp";
|
|
410
514
|
const sqlPath = await promptText(rl, "Where should I save the generated SQL?", {
|
|
411
|
-
defaultValue: `./data/${slug}-lookup
|
|
515
|
+
defaultValue: `./data/${slug}-lookup.sql`,
|
|
412
516
|
required: true
|
|
413
517
|
});
|
|
414
518
|
const rawPath = execute
|
|
415
519
|
? await promptText(rl, "Where should I save raw BigQuery rows?", {
|
|
416
|
-
defaultValue: `./data/${slug}-leads-raw
|
|
520
|
+
defaultValue: `./data/${slug}-leads-raw.json`,
|
|
417
521
|
required: true
|
|
418
522
|
})
|
|
419
523
|
: "";
|
|
420
524
|
const leadPath = execute
|
|
421
525
|
? await promptText(rl, "Where should I save normalized leads?", {
|
|
422
|
-
defaultValue: `./data/${slug}-leads
|
|
526
|
+
defaultValue: `./data/${slug}-leads.json`,
|
|
423
527
|
required: true
|
|
424
528
|
})
|
|
425
529
|
: "";
|
|
426
530
|
writeWizardLine();
|
|
427
|
-
const icp = buildVendorIcp(vendor, market);
|
|
428
|
-
await writeJsonFile(icpPath, icp);
|
|
429
531
|
const sql = buildBigQueryLeadLookupSql(icp, {
|
|
430
532
|
table: "icpidentifier.SalesGPT.leadPool_new",
|
|
431
533
|
companyField: "companyName",
|
|
@@ -453,15 +555,14 @@ async function runVendorLookupWizard(rl) {
|
|
|
453
555
|
await writeJsonFile(leadPath, normalizedLeads);
|
|
454
556
|
executedRowCount = parsedRows.length;
|
|
455
557
|
}
|
|
456
|
-
writeWizardLine(`
|
|
558
|
+
writeWizardLine(`Using ICP from ${icpPath}.`);
|
|
457
559
|
writeWizardLine(`Saved lookup SQL to ${sqlPath}.`);
|
|
458
560
|
if (execute) {
|
|
459
561
|
writeWizardLine(`Saved ${executedRowCount ?? 0} raw rows to ${rawPath}.`);
|
|
460
562
|
writeWizardLine(`Saved normalized leads to ${leadPath}.`);
|
|
461
563
|
}
|
|
462
564
|
writeWizardLine();
|
|
463
|
-
writeWizardLine("Equivalent raw
|
|
464
|
-
writeWizardLine(` ${buildCommandLine(["salesprompter", "icp:vendor", "--vendor", vendor, "--market", market, "--out", icpPath])}`);
|
|
565
|
+
writeWizardLine("Equivalent raw command:");
|
|
465
566
|
const lookupArgs = ["salesprompter", "leads:lookup:bq", "--icp", icpPath, "--limit", String(limit), "--sql-out", sqlPath];
|
|
466
567
|
if (execute) {
|
|
467
568
|
lookupArgs.push("--execute", "--out", rawPath, "--lead-out", leadPath);
|
|
@@ -469,13 +570,8 @@ async function runVendorLookupWizard(rl) {
|
|
|
469
570
|
writeWizardLine(` ${buildCommandLine(lookupArgs)}`);
|
|
470
571
|
}
|
|
471
572
|
async function runOutreachSyncWizard(rl) {
|
|
472
|
-
const target =
|
|
473
|
-
|
|
474
|
-
value: "instantly",
|
|
475
|
-
label: "Instantly",
|
|
476
|
-
description: "Create campaign leads from a scored leads JSON file"
|
|
477
|
-
}
|
|
478
|
-
], "instantly");
|
|
573
|
+
const target = "instantly";
|
|
574
|
+
writeWizardLine("Using Instantly.");
|
|
479
575
|
writeWizardLine();
|
|
480
576
|
if (!process.env.INSTANTLY_API_KEY || process.env.INSTANTLY_API_KEY.trim().length === 0) {
|
|
481
577
|
throw new Error("INSTANTLY_API_KEY is required for the Instantly sync flow.");
|
|
@@ -530,8 +626,8 @@ async function runWizard(options) {
|
|
|
530
626
|
if (runtimeOutputOptions.json || runtimeOutputOptions.quiet) {
|
|
531
627
|
throw new Error("wizard does not support --json or --quiet.");
|
|
532
628
|
}
|
|
533
|
-
writeWizardLine("Salesprompter
|
|
534
|
-
writeWizardLine("
|
|
629
|
+
writeWizardLine("Salesprompter");
|
|
630
|
+
writeWizardLine("Tell me what you want to do, and I will guide you through it.");
|
|
535
631
|
writeWizardLine();
|
|
536
632
|
await ensureWizardSession(options);
|
|
537
633
|
const rl = createInterface({
|
|
@@ -542,18 +638,21 @@ async function runWizard(options) {
|
|
|
542
638
|
const flow = await promptChoice(rl, "What do you want help with?", [
|
|
543
639
|
{
|
|
544
640
|
value: "vendor-icp",
|
|
545
|
-
label: "
|
|
546
|
-
description: "
|
|
641
|
+
label: "Figure out who to target",
|
|
642
|
+
description: "Create an ideal customer profile for your product",
|
|
643
|
+
aliases: ["icp", "ideal customer", "who to target", "targeting", "profile"]
|
|
547
644
|
},
|
|
548
645
|
{
|
|
549
646
|
value: "lead-generation",
|
|
550
647
|
label: "Generate leads",
|
|
551
|
-
description: "Find people at
|
|
648
|
+
description: "Find people at one company or from your BigQuery data",
|
|
649
|
+
aliases: ["leads", "find leads", "lead generation", "find people"]
|
|
552
650
|
},
|
|
553
651
|
{
|
|
554
652
|
value: "outreach-sync",
|
|
555
|
-
label: "
|
|
556
|
-
description: "
|
|
653
|
+
label: "Add leads to Instantly",
|
|
654
|
+
description: "Send a scored leads file to an Instantly campaign",
|
|
655
|
+
aliases: ["instantly", "outreach", "send leads", "campaign"]
|
|
557
656
|
}
|
|
558
657
|
], "vendor-icp");
|
|
559
658
|
writeWizardLine();
|
package/package.json
CHANGED