salesprompter-cli 0.1.47 → 0.1.48
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/README.md +5 -1
- package/dist/cli.js +124 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,11 @@ salesprompter companies:resolve-linkedin-urls --in ./companies.txt --out ./compa
|
|
|
65
65
|
# When authenticated, Salesprompter first enriches company domains in the app, then the CLI builds the email batch.
|
|
66
66
|
salesprompter contacts:resolve-emails --in ./contacts.tsv --out-dir ./email-run --dry-run
|
|
67
67
|
|
|
68
|
-
#
|
|
68
|
+
# Collect a Sales Navigator people search into the active workspace
|
|
69
|
+
salesprompter leads:collect \
|
|
70
|
+
--linkedin-url "$SALES_NAV_PEOPLE_URL" \
|
|
71
|
+
--max-results 1000
|
|
72
|
+
|
|
69
73
|
salesprompter affiliate:launch \
|
|
70
74
|
--affiliate-link "https://example.com/?ref=you" \
|
|
71
75
|
--linkedin-url "https://www.linkedin.com/sales/search/people?query=..." \
|
package/dist/cli.js
CHANGED
|
@@ -147,6 +147,14 @@ const AffiliateCampaignLaunchResponseSchema = z.object({
|
|
|
147
147
|
})
|
|
148
148
|
.passthrough()
|
|
149
149
|
.optional(),
|
|
150
|
+
workspaceImport: z
|
|
151
|
+
.object({
|
|
152
|
+
runId: z.string().uuid(),
|
|
153
|
+
imported: z.number().int().nonnegative(),
|
|
154
|
+
upserted: z.number().int().nonnegative(),
|
|
155
|
+
previewUrl: z.string().min(1)
|
|
156
|
+
})
|
|
157
|
+
.optional(),
|
|
150
158
|
previewUrl: z.string().nullable().optional(),
|
|
151
159
|
next: z.string().optional()
|
|
152
160
|
});
|
|
@@ -469,6 +477,7 @@ const cliPacks = [
|
|
|
469
477
|
summary: "Find leads from product and market inputs.",
|
|
470
478
|
commands: [
|
|
471
479
|
"leads:discover",
|
|
480
|
+
"leads:collect",
|
|
472
481
|
"search:run",
|
|
473
482
|
"search:status",
|
|
474
483
|
"search:export",
|
|
@@ -506,6 +515,7 @@ const helpAliasByCommandName = new Map([
|
|
|
506
515
|
["dealroom-companies:scrape-local", "companies:scrape-dealroom"],
|
|
507
516
|
["linkedin-products:scrape", "market:scrape"],
|
|
508
517
|
["salesnav:from-product-category", "leads:discover"],
|
|
518
|
+
["salesnav:people:collect", "leads:collect"],
|
|
509
519
|
["salesnav:crawl", "search:run"],
|
|
510
520
|
["salesnav:crawl:status", "search:status"],
|
|
511
521
|
["salesnav:export", "search:export"],
|
|
@@ -550,6 +560,7 @@ const helpVisibleCommandNames = new Set([
|
|
|
550
560
|
"dealroom-companies:scrape-local",
|
|
551
561
|
"linkedin-products:scrape",
|
|
552
562
|
"salesnav:from-product-category",
|
|
563
|
+
"salesnav:people:collect",
|
|
553
564
|
"salesnav:crawl",
|
|
554
565
|
"salesnav:crawl:status",
|
|
555
566
|
"salesnav:export",
|
|
@@ -6793,6 +6804,10 @@ function buildSalesNavigatorApiRequestFromSearchUrl(searchUrl, config, count) {
|
|
|
6793
6804
|
}
|
|
6794
6805
|
function buildSalesNavigatorLeadApiUrlFromSearchUrl(searchUrl, count) {
|
|
6795
6806
|
const source = new URL(searchUrl);
|
|
6807
|
+
if (!source.searchParams.has("query") && source.hash.startsWith("#query=")) {
|
|
6808
|
+
source.search = `?${source.hash.slice(1)}`;
|
|
6809
|
+
source.hash = "";
|
|
6810
|
+
}
|
|
6796
6811
|
if (source.pathname.includes("/sales-api/salesApiLeadSearch")) {
|
|
6797
6812
|
return source.toString();
|
|
6798
6813
|
}
|
|
@@ -13198,6 +13213,99 @@ program
|
|
|
13198
13213
|
}
|
|
13199
13214
|
});
|
|
13200
13215
|
});
|
|
13216
|
+
async function runSalesNavigatorPeopleCollectCommand(options) {
|
|
13217
|
+
const linkedInUrl = z.string().url().parse(options.linkedinUrl);
|
|
13218
|
+
const maxResults = z.coerce.number().int().min(1).max(100000).parse(options.maxResults);
|
|
13219
|
+
const pageSize = z.coerce.number().int().min(1).max(100).parse(options.pageSize);
|
|
13220
|
+
const normalizedSourceUrl = (() => {
|
|
13221
|
+
const source = new URL(linkedInUrl);
|
|
13222
|
+
if (!source.searchParams.has("query") && source.hash.startsWith("#query=")) {
|
|
13223
|
+
source.search = `?${source.hash.slice(1)}`;
|
|
13224
|
+
source.hash = "";
|
|
13225
|
+
}
|
|
13226
|
+
return source.toString();
|
|
13227
|
+
})();
|
|
13228
|
+
if (options.dryRun) {
|
|
13229
|
+
const payload = {
|
|
13230
|
+
status: "ok",
|
|
13231
|
+
dryRun: true,
|
|
13232
|
+
linkedInUrl: normalizedSourceUrl,
|
|
13233
|
+
maxResults,
|
|
13234
|
+
destination: "/leads/cli-imports"
|
|
13235
|
+
};
|
|
13236
|
+
if (options.out)
|
|
13237
|
+
await writeJsonFile(options.out, payload);
|
|
13238
|
+
return payload;
|
|
13239
|
+
}
|
|
13240
|
+
const session = await requireAuthSession();
|
|
13241
|
+
const pageDelayMinMs = z.coerce.number().int().min(0).parse(options.pageDelayMinMs);
|
|
13242
|
+
const pageDelayMaxMs = z.coerce.number().int().min(pageDelayMinMs).parse(options.pageDelayMaxMs);
|
|
13243
|
+
const browserRelayPort = options.browserRelayPort == null
|
|
13244
|
+
? null
|
|
13245
|
+
: z.coerce.number().int().min(0).max(65535).parse(options.browserRelayPort);
|
|
13246
|
+
if (options.curlFile && browserRelayPort != null) {
|
|
13247
|
+
throw new Error("Use either --curl-file or --browser-relay-port, not both.");
|
|
13248
|
+
}
|
|
13249
|
+
const parsedRequest = options.curlFile
|
|
13250
|
+
? parseSalesNavigatorCurlRequest(await readFile(path.resolve(String(options.curlFile)), "utf8"))
|
|
13251
|
+
: browserRelayPort != null
|
|
13252
|
+
? {
|
|
13253
|
+
url: buildSalesNavigatorLeadApiUrlFromSearchUrl(normalizedSourceUrl, pageSize),
|
|
13254
|
+
headers: {}
|
|
13255
|
+
}
|
|
13256
|
+
: buildSalesNavigatorApiRequestFromSearchUrl(normalizedSourceUrl, await readLinkedInDirectLookupConfig(), pageSize);
|
|
13257
|
+
if (!new URL(parsedRequest.url).pathname.includes("/sales-api/salesApiLeadSearch")) {
|
|
13258
|
+
throw new Error("--curl-file must contain a /sales-api/salesApiLeadSearch request.");
|
|
13259
|
+
}
|
|
13260
|
+
const browserRelay = browserRelayPort == null
|
|
13261
|
+
? null
|
|
13262
|
+
: await createLocalAccountSearchBrowserRelay(browserRelayPort);
|
|
13263
|
+
let collected;
|
|
13264
|
+
try {
|
|
13265
|
+
collected = await fetchAllLocalSalesNavigatorPeople(parsedRequest, {
|
|
13266
|
+
requestedProfiles: maxResults,
|
|
13267
|
+
pageSize,
|
|
13268
|
+
pageDelayMinMs,
|
|
13269
|
+
pageDelayMaxMs,
|
|
13270
|
+
retry: {
|
|
13271
|
+
maxRetries: 2,
|
|
13272
|
+
retryBaseDelayMs: 2_000,
|
|
13273
|
+
retryMaxDelayMs: 10_000
|
|
13274
|
+
},
|
|
13275
|
+
executeRequest: browserRelay
|
|
13276
|
+
? (request) => browserRelay.request(request.url)
|
|
13277
|
+
: undefined
|
|
13278
|
+
});
|
|
13279
|
+
}
|
|
13280
|
+
finally {
|
|
13281
|
+
await browserRelay?.close();
|
|
13282
|
+
}
|
|
13283
|
+
const imported = await importLocalSalesNavigatorPeopleViaApp(session, {
|
|
13284
|
+
sourceQueryUrl: normalizedSourceUrl,
|
|
13285
|
+
people: collected.people,
|
|
13286
|
+
totalResults: collected.totalResults,
|
|
13287
|
+
fetchedPages: collected.fetchedPages,
|
|
13288
|
+
pacing: collected.pacing,
|
|
13289
|
+
maxResultsPerSearch: maxResults,
|
|
13290
|
+
numberOfProfiles: maxResults,
|
|
13291
|
+
slicePreset: "local-salesnav-people"
|
|
13292
|
+
});
|
|
13293
|
+
const payload = {
|
|
13294
|
+
status: "ok",
|
|
13295
|
+
dryRun: false,
|
|
13296
|
+
collected: collected.people.length,
|
|
13297
|
+
totalResults: collected.totalResults,
|
|
13298
|
+
fetchedPages: collected.fetchedPages,
|
|
13299
|
+
runId: imported.runId,
|
|
13300
|
+
imported: imported.imported,
|
|
13301
|
+
upserted: imported.upserted,
|
|
13302
|
+
workspaceUrl: `${session.apiBaseUrl}/leads/cli-imports?runId=` +
|
|
13303
|
+
encodeURIComponent(imported.runId)
|
|
13304
|
+
};
|
|
13305
|
+
if (options.out)
|
|
13306
|
+
await writeJsonFile(options.out, payload);
|
|
13307
|
+
return payload;
|
|
13308
|
+
}
|
|
13201
13309
|
async function runAffiliateLaunchCommand(options) {
|
|
13202
13310
|
const affiliateLink = z.string().url().parse(options.affiliateLink);
|
|
13203
13311
|
const linkedInUrl = z.string().url().parse(options.linkedinUrl);
|
|
@@ -13283,6 +13391,22 @@ function addAffiliateAudienceOptions(command) {
|
|
|
13283
13391
|
.option("--dry-run", "Validate and preview without creating remote resources", false)
|
|
13284
13392
|
.option("--out <path>", "Optional local JSON output path");
|
|
13285
13393
|
}
|
|
13394
|
+
program
|
|
13395
|
+
.command("salesnav:people:collect")
|
|
13396
|
+
.alias("leads:collect")
|
|
13397
|
+
.description("Collect a Sales Navigator people search into the active workspace.")
|
|
13398
|
+
.requiredOption("--linkedin-url <url>", "Sales Navigator people search URL")
|
|
13399
|
+
.option("--max-results <number>", "Maximum people to collect", "1000")
|
|
13400
|
+
.option("--curl-file <path>", "Optional copied Sales Navigator Lead Search curl request")
|
|
13401
|
+
.option("--browser-relay-port <number>", "Use the signed-in browser through a loopback relay")
|
|
13402
|
+
.option("--page-size <number>", "Direct Sales Navigator page size", "100")
|
|
13403
|
+
.option("--page-delay-min-ms <number>", "Minimum delay between direct pages", "5000")
|
|
13404
|
+
.option("--page-delay-max-ms <number>", "Maximum delay between direct pages", "8000")
|
|
13405
|
+
.option("--dry-run", "Validate without collecting or importing", false)
|
|
13406
|
+
.option("--out <path>", "Optional local JSON output path")
|
|
13407
|
+
.action(async (options) => {
|
|
13408
|
+
printOutput(await runSalesNavigatorPeopleCollectCommand(options));
|
|
13409
|
+
});
|
|
13286
13410
|
addAffiliateAudienceOptions(program
|
|
13287
13411
|
.command("affiliate:launch")
|
|
13288
13412
|
.description("Build an affiliate audience from an affiliate link and LinkedIn search URL."))
|
package/package.json
CHANGED