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.
Files changed (2) hide show
  1. package/dist/cli.js +148 -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,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 a specific company",
378
- 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"]
379
489
  },
380
490
  {
381
491
  value: "vendor-lookup",
382
- label: "From BigQuery",
383
- 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"]
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 vendor = "deel";
395
- writeWizardLine("Using the built-in Deel ICP template.");
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-${market}.sql`,
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-${market}.json`,
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-${market}.json`,
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(`Saved vendor ICP to ${icpPath}.`);
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 commands:");
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 = await promptChoice(rl, "Which outreach tool do you want to use?", [
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 Wizard");
534
- 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.");
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: "Define my ICP",
546
- description: "Build the kind of company profile your product 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"]
547
644
  },
548
645
  {
549
646
  value: "lead-generation",
550
647
  label: "Generate leads",
551
- 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"]
552
650
  },
553
651
  {
554
652
  value: "outreach-sync",
555
- label: "Send leads to outreach",
556
- 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"]
557
656
  }
558
657
  ], "vendor-icp");
559
658
  writeWizardLine();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.13",
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": {