salesprompter-cli 0.1.12 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +150 -49
  2. 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
- return session.user.orgName ?? session.user.orgSlug ?? session.user.orgId ?? null;
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(`Signed in as ${session.user.email} for ${orgLabel}.`);
185
- return;
195
+ writeWizardLine(`Workspace: ${orgLabel}`);
186
196
  }
187
- writeWizardLine(`Signed in as ${session.user.email}.`);
188
- writeWizardLine("No organization is attached to this CLI session.");
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(`Choose [1-${options.length}] (default ${defaultIndex + 1}): `)).trim();
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 matched = options.find((option) => option.value.toLowerCase() === answer.toLowerCase());
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
- writeWizardLine("Please choose one of the numbered options.");
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("No active Salesprompter session found. Starting login...");
293
+ writeWizardLine("First, sign in to Salesprompter.");
269
294
  writeWizardLine();
270
295
  const result = await performLogin({
271
296
  apiUrl: options?.apiUrl,
@@ -276,7 +301,92 @@ async function ensureWizardSession(options) {
276
301
  return result.session;
277
302
  }
278
303
  async function runVendorIcpWizard(rl) {
279
- const vendor = await promptChoice(rl, "Which product are you selling?", [{ value: "deel", label: "Deel", description: "Use Deel's built-in ICP template" }], "deel");
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
+ }
388
+ const vendor = "deel";
389
+ writeWizardLine("Using the built-in Deel ICP template.");
280
390
  writeWizardLine();
281
391
  const market = await promptChoice(rl, "Which market do you want to target?", [
282
392
  { value: "dach", label: "DACH", description: "Germany, Austria, Switzerland" },
@@ -373,13 +483,15 @@ async function runLeadGenerationWizard(rl) {
373
483
  const source = await promptChoice(rl, "How do you want to generate leads?", [
374
484
  {
375
485
  value: "target-account",
376
- label: "At a specific company",
377
- description: "Example: find people at deel.com"
486
+ label: "At one company",
487
+ description: "Example: find people at acme.com",
488
+ aliases: ["company", "one company", "specific company", "account"]
378
489
  },
379
490
  {
380
491
  value: "vendor-lookup",
381
- label: "From BigQuery",
382
- description: "Use an ICP to search lead data you already have in BigQuery"
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"]
383
495
  }
384
496
  ], "target-account");
385
497
  writeWizardLine();
@@ -390,40 +502,32 @@ async function runLeadGenerationWizard(rl) {
390
502
  await runVendorLookupWizard(rl);
391
503
  }
392
504
  async function runVendorLookupWizard(rl) {
393
- const vendor = await promptChoice(rl, "Which product are you selling?", [{ value: "deel", label: "Deel", description: "Use Deel's built-in ICP template" }], "deel");
394
- writeWizardLine();
395
- const market = await promptChoice(rl, "Which market should the BigQuery lookup target?", [
396
- { value: "dach", label: "DACH", description: "Germany, Austria, Switzerland" },
397
- { value: "europe", label: "Europe" },
398
- { value: "global", label: "Global" }
399
- ], "dach");
400
- 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 }));
401
- const execute = await promptYesNo(rl, "Execute the BigQuery lookup now?", false);
402
- writeWizardLine();
403
- const slug = slugify(vendor);
404
- const icpPath = await promptText(rl, "Where should I save the ICP JSON?", {
405
- defaultValue: `./data/${slug}-icp-${market}.json`,
505
+ const icpPath = await promptText(rl, "Where is your ICP JSON file?", {
506
+ defaultValue: "./data/icp.json",
406
507
  required: true
407
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";
408
514
  const sqlPath = await promptText(rl, "Where should I save the generated SQL?", {
409
- defaultValue: `./data/${slug}-lookup-${market}.sql`,
515
+ defaultValue: `./data/${slug}-lookup.sql`,
410
516
  required: true
411
517
  });
412
518
  const rawPath = execute
413
519
  ? await promptText(rl, "Where should I save raw BigQuery rows?", {
414
- defaultValue: `./data/${slug}-leads-raw-${market}.json`,
520
+ defaultValue: `./data/${slug}-leads-raw.json`,
415
521
  required: true
416
522
  })
417
523
  : "";
418
524
  const leadPath = execute
419
525
  ? await promptText(rl, "Where should I save normalized leads?", {
420
- defaultValue: `./data/${slug}-leads-${market}.json`,
526
+ defaultValue: `./data/${slug}-leads.json`,
421
527
  required: true
422
528
  })
423
529
  : "";
424
530
  writeWizardLine();
425
- const icp = buildVendorIcp(vendor, market);
426
- await writeJsonFile(icpPath, icp);
427
531
  const sql = buildBigQueryLeadLookupSql(icp, {
428
532
  table: "icpidentifier.SalesGPT.leadPool_new",
429
533
  companyField: "companyName",
@@ -451,15 +555,14 @@ async function runVendorLookupWizard(rl) {
451
555
  await writeJsonFile(leadPath, normalizedLeads);
452
556
  executedRowCount = parsedRows.length;
453
557
  }
454
- writeWizardLine(`Saved vendor ICP to ${icpPath}.`);
558
+ writeWizardLine(`Using ICP from ${icpPath}.`);
455
559
  writeWizardLine(`Saved lookup SQL to ${sqlPath}.`);
456
560
  if (execute) {
457
561
  writeWizardLine(`Saved ${executedRowCount ?? 0} raw rows to ${rawPath}.`);
458
562
  writeWizardLine(`Saved normalized leads to ${leadPath}.`);
459
563
  }
460
564
  writeWizardLine();
461
- writeWizardLine("Equivalent raw commands:");
462
- writeWizardLine(` ${buildCommandLine(["salesprompter", "icp:vendor", "--vendor", vendor, "--market", market, "--out", icpPath])}`);
565
+ writeWizardLine("Equivalent raw command:");
463
566
  const lookupArgs = ["salesprompter", "leads:lookup:bq", "--icp", icpPath, "--limit", String(limit), "--sql-out", sqlPath];
464
567
  if (execute) {
465
568
  lookupArgs.push("--execute", "--out", rawPath, "--lead-out", leadPath);
@@ -467,13 +570,8 @@ async function runVendorLookupWizard(rl) {
467
570
  writeWizardLine(` ${buildCommandLine(lookupArgs)}`);
468
571
  }
469
572
  async function runOutreachSyncWizard(rl) {
470
- const target = await promptChoice(rl, "Which outreach tool do you want to use?", [
471
- {
472
- value: "instantly",
473
- label: "Instantly",
474
- description: "Create campaign leads from a scored leads JSON file"
475
- }
476
- ], "instantly");
573
+ const target = "instantly";
574
+ writeWizardLine("Using Instantly.");
477
575
  writeWizardLine();
478
576
  if (!process.env.INSTANTLY_API_KEY || process.env.INSTANTLY_API_KEY.trim().length === 0) {
479
577
  throw new Error("INSTANTLY_API_KEY is required for the Instantly sync flow.");
@@ -528,8 +626,8 @@ async function runWizard(options) {
528
626
  if (runtimeOutputOptions.json || runtimeOutputOptions.quiet) {
529
627
  throw new Error("wizard does not support --json or --quiet.");
530
628
  }
531
- writeWizardLine("Salesprompter Wizard");
532
- writeWizardLine("Choose the outcome you want. I will ask a few questions and run the matching CLI workflow.");
629
+ writeWizardLine("Salesprompter");
630
+ writeWizardLine("Tell me what you want to do, and I will guide you through it.");
533
631
  writeWizardLine();
534
632
  await ensureWizardSession(options);
535
633
  const rl = createInterface({
@@ -540,18 +638,21 @@ async function runWizard(options) {
540
638
  const flow = await promptChoice(rl, "What do you want help with?", [
541
639
  {
542
640
  value: "vendor-icp",
543
- label: "Define my ICP",
544
- description: "Example: I sell Deel and want the kind of companies Deel sells to"
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"]
545
644
  },
546
645
  {
547
646
  value: "lead-generation",
548
647
  label: "Generate leads",
549
- description: "Find people at a target company or from your BigQuery data"
648
+ description: "Find people at one company or from your BigQuery data",
649
+ aliases: ["leads", "find leads", "lead generation", "find people"]
550
650
  },
551
651
  {
552
652
  value: "outreach-sync",
553
- label: "Send leads to outreach",
554
- description: "Push scored leads into Instantly"
653
+ label: "Add leads to Instantly",
654
+ description: "Send a scored leads file to an Instantly campaign",
655
+ aliases: ["instantly", "outreach", "send leads", "campaign"]
555
656
  }
556
657
  ], "vendor-icp");
557
658
  writeWizardLine();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "JSON-first sales prospecting CLI for ICP definition, lead generation, enrichment, scoring, and CRM/outreach sync.",
5
5
  "type": "module",
6
6
  "bin": {