salesprompter-cli 0.1.54 → 0.1.56

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 +12 -1
  2. package/dist/cli.js +81 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -68,7 +68,7 @@ salesprompter contacts:resolve-emails --in ./contacts.tsv --out-dir ./email-run
68
68
  # Collect a Sales Navigator people search into the active workspace
69
69
  salesprompter leads:collect \
70
70
  --linkedin-url "$SALES_NAV_PEOPLE_URL" \
71
- --max-results 1000
71
+ --max-results 2000
72
72
 
73
73
  # Enrich one or more stored imports: Sales Navigator company -> official domain -> Hunter email
74
74
  # This updates workspace data only. It does not create or start outreach.
@@ -110,6 +110,17 @@ salesprompter packs:list
110
110
  salesprompter --help
111
111
  ```
112
112
 
113
+ Download stored workspace leads without starting a new Sales Navigator scrape:
114
+
115
+ ```bash
116
+ salesprompter leads:download --out ./salesprompter-leads.csv
117
+ salesprompter leads:download --run-id <uuid> --emails-only --out ./run-leads.csv
118
+ ```
119
+
120
+ Use `--search <value>` to match a person, title, or company. The command uses the
121
+ active Salesprompter workspace and downloads the same filtered data available in
122
+ the app's CLI imports view.
123
+
113
124
  ## Output modes
114
125
 
115
126
  - `--json` for machine-readable output
package/dist/cli.js CHANGED
@@ -526,6 +526,7 @@ const cliPacks = [
526
526
  commands: [
527
527
  "leads:discover",
528
528
  "leads:collect",
529
+ "leads:download",
529
530
  "leads:enrich-import",
530
531
  "search:run",
531
532
  "search:status",
@@ -587,6 +588,7 @@ const helpVisibleCommandNames = new Set([
587
588
  "wizard",
588
589
  "auth:whoami",
589
590
  "llm:ready",
591
+ "leads:download",
590
592
  "contacts:find-linkedin-urls",
591
593
  "companies:find-linkedin-urls",
592
594
  "contacts:process-emails",
@@ -6757,6 +6759,59 @@ async function runCliImportEnrichmentAction(session, body, schema) {
6757
6759
  }), schema);
6758
6760
  return value;
6759
6761
  }
6762
+ function parseDownloadFilename(contentDisposition) {
6763
+ const match = contentDisposition?.match(/filename="?([^";]+)"?/i);
6764
+ return match?.[1]?.trim() || `salesprompter-leads-${new Date().toISOString().slice(0, 10)}.csv`;
6765
+ }
6766
+ async function runLeadsDownloadCommand(options) {
6767
+ const session = await requireAuthSession();
6768
+ const runId = options.runId ? z.string().uuid().parse(options.runId) : null;
6769
+ const search = options.search?.trim() || null;
6770
+ const { value } = await withRefreshableAuthSession(session, async (currentSession) => {
6771
+ const url = new URL('/api/cli/leads/imports/export', currentSession.apiBaseUrl);
6772
+ if (runId)
6773
+ url.searchParams.set('runId', runId);
6774
+ if (search)
6775
+ url.searchParams.set('q', search);
6776
+ if (options.emailsOnly)
6777
+ url.searchParams.set('emailsOnly', '1');
6778
+ const response = await fetch(url, {
6779
+ headers: { Authorization: `Bearer ${currentSession.accessToken}` },
6780
+ });
6781
+ if (!response.ok) {
6782
+ const raw = await response.text();
6783
+ let message = `lead download failed (${response.status})`;
6784
+ try {
6785
+ const payload = JSON.parse(raw);
6786
+ if (typeof payload.error === 'string')
6787
+ message = payload.error;
6788
+ }
6789
+ catch {
6790
+ if (raw.trim())
6791
+ message = raw.trim();
6792
+ }
6793
+ throw new CliApiRequestError(message, { statusCode: response.status });
6794
+ }
6795
+ const countHeader = response.headers.get('x-salesprompter-lead-count');
6796
+ return {
6797
+ bytes: Buffer.from(await response.arrayBuffer()),
6798
+ count: countHeader == null ? null : Number.parseInt(countHeader, 10),
6799
+ filename: parseDownloadFilename(response.headers.get('content-disposition')),
6800
+ };
6801
+ });
6802
+ const outputPath = path.resolve(options.out?.trim() || value.filename);
6803
+ await mkdir(path.dirname(outputPath), { recursive: true });
6804
+ await writeFile(outputPath, value.bytes);
6805
+ return {
6806
+ status: 'ok',
6807
+ output: outputPath,
6808
+ leads: Number.isFinite(value.count) ? value.count : null,
6809
+ bytes: value.bytes.length,
6810
+ runId,
6811
+ search,
6812
+ emailsOnly: options.emailsOnly,
6813
+ };
6814
+ }
6760
6815
  async function getCliImportCompanyCandidatesViaApp(session, input) {
6761
6816
  return runCliImportEnrichmentAction(session, {
6762
6817
  action: "company-candidates",
@@ -13328,9 +13383,15 @@ program
13328
13383
  }
13329
13384
  });
13330
13385
  });
13386
+ const SALES_NAVIGATOR_PEOPLE_MAX_RESULTS = 2_000;
13331
13387
  async function runSalesNavigatorPeopleCollectCommand(options) {
13332
13388
  const linkedInUrl = z.string().url().parse(options.linkedinUrl);
13333
- const maxResults = z.coerce.number().int().min(1).max(100000).parse(options.maxResults);
13389
+ const maxResults = z.coerce
13390
+ .number()
13391
+ .int()
13392
+ .min(1)
13393
+ .max(SALES_NAVIGATOR_PEOPLE_MAX_RESULTS)
13394
+ .parse(options.maxResults);
13334
13395
  const pageSize = z.coerce.number().int().min(1).max(100).parse(options.pageSize);
13335
13396
  const normalizedSourceUrl = (() => {
13336
13397
  const source = new URL(linkedInUrl);
@@ -14186,7 +14247,12 @@ async function runSalesNavigatorImportEnrichmentCommand(options) {
14186
14247
  async function runAffiliateLaunchCommand(options) {
14187
14248
  const affiliateLink = z.string().url().parse(options.affiliateLink);
14188
14249
  const linkedInUrl = z.string().url().parse(options.linkedinUrl);
14189
- const maxResults = z.coerce.number().int().min(1).max(1000).parse(options.maxResults);
14250
+ const maxResults = z.coerce
14251
+ .number()
14252
+ .int()
14253
+ .min(1)
14254
+ .max(SALES_NAVIGATOR_PEOPLE_MAX_RESULTS)
14255
+ .parse(options.maxResults);
14190
14256
  const session = await requireAuthSession();
14191
14257
  const isSalesNavigatorPeopleSearch = new URL(linkedInUrl).pathname.includes("/sales/search/people");
14192
14258
  let localResults;
@@ -14259,7 +14325,7 @@ function addAffiliateAudienceOptions(command) {
14259
14325
  return command
14260
14326
  .requiredOption("--affiliate-link <url>", "Affiliate link to promote")
14261
14327
  .requiredOption("--linkedin-url <url>", "Sales Navigator people or LinkedIn content search URL")
14262
- .option("--max-results <number>", "Maximum audience results", "1000")
14328
+ .option("--max-results <number>", "Maximum audience results (people searches: 2000)", String(SALES_NAVIGATOR_PEOPLE_MAX_RESULTS))
14263
14329
  .option("--curl-file <path>", "Optional copied Sales Navigator Lead Search curl request")
14264
14330
  .option("--browser-relay-port <number>", "Use the signed-in browser through a loopback relay")
14265
14331
  .option("--page-size <number>", "Direct Sales Navigator page size", "100")
@@ -14273,7 +14339,7 @@ program
14273
14339
  .alias("leads:collect")
14274
14340
  .description("Collect a Sales Navigator people search into the active workspace.")
14275
14341
  .requiredOption("--linkedin-url <url>", "Sales Navigator people search URL")
14276
- .option("--max-results <number>", "Maximum people to collect", "1000")
14342
+ .option("--max-results <number>", "Maximum people to collect", String(SALES_NAVIGATOR_PEOPLE_MAX_RESULTS))
14277
14343
  .option("--curl-file <path>", "Optional copied Sales Navigator Lead Search curl request")
14278
14344
  .option("--browser-relay-port <number>", "Use the signed-in browser through a loopback relay")
14279
14345
  .option("--page-size <number>", "Direct Sales Navigator page size", "100")
@@ -14284,6 +14350,17 @@ program
14284
14350
  .action(async (options) => {
14285
14351
  printOutput(await runSalesNavigatorPeopleCollectCommand(options));
14286
14352
  });
14353
+ program
14354
+ .command("leads:download")
14355
+ .alias("leads:export-csv")
14356
+ .description("Download stored leads from the active Salesprompter workspace as CSV.")
14357
+ .option("--run-id <uuid>", "Limit the CSV to one CLI import run")
14358
+ .option("--search <value>", "Filter by person, title, or company")
14359
+ .option("--emails-only", "Include only leads with a found email", false)
14360
+ .option("--out <path>", "CSV output path; defaults to a dated filename")
14361
+ .action(async (options) => {
14362
+ printOutput(await runLeadsDownloadCommand(options));
14363
+ });
14287
14364
  program
14288
14365
  .command("salesnav:people:enrich")
14289
14366
  .alias("leads:enrich-import")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
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",