salesprompter-cli 0.1.47 → 0.1.49
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 +155 -23
- 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
|
@@ -8,6 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import { emitKeypressEvents } from "node:readline";
|
|
9
9
|
import { createInterface } from "node:readline/promises";
|
|
10
10
|
import { setTimeout as delay } from "node:timers/promises";
|
|
11
|
+
import { gzipSync } from "node:zlib";
|
|
11
12
|
import { createClient } from "@supabase/supabase-js";
|
|
12
13
|
import pg from "pg";
|
|
13
14
|
import { Command } from "commander";
|
|
@@ -147,6 +148,14 @@ const AffiliateCampaignLaunchResponseSchema = z.object({
|
|
|
147
148
|
})
|
|
148
149
|
.passthrough()
|
|
149
150
|
.optional(),
|
|
151
|
+
workspaceImport: z
|
|
152
|
+
.object({
|
|
153
|
+
runId: z.string().uuid(),
|
|
154
|
+
imported: z.number().int().nonnegative(),
|
|
155
|
+
upserted: z.number().int().nonnegative(),
|
|
156
|
+
previewUrl: z.string().min(1)
|
|
157
|
+
})
|
|
158
|
+
.optional(),
|
|
150
159
|
previewUrl: z.string().nullable().optional(),
|
|
151
160
|
next: z.string().optional()
|
|
152
161
|
});
|
|
@@ -469,6 +478,7 @@ const cliPacks = [
|
|
|
469
478
|
summary: "Find leads from product and market inputs.",
|
|
470
479
|
commands: [
|
|
471
480
|
"leads:discover",
|
|
481
|
+
"leads:collect",
|
|
472
482
|
"search:run",
|
|
473
483
|
"search:status",
|
|
474
484
|
"search:export",
|
|
@@ -506,6 +516,7 @@ const helpAliasByCommandName = new Map([
|
|
|
506
516
|
["dealroom-companies:scrape-local", "companies:scrape-dealroom"],
|
|
507
517
|
["linkedin-products:scrape", "market:scrape"],
|
|
508
518
|
["salesnav:from-product-category", "leads:discover"],
|
|
519
|
+
["salesnav:people:collect", "leads:collect"],
|
|
509
520
|
["salesnav:crawl", "search:run"],
|
|
510
521
|
["salesnav:crawl:status", "search:status"],
|
|
511
522
|
["salesnav:export", "search:export"],
|
|
@@ -550,6 +561,7 @@ const helpVisibleCommandNames = new Set([
|
|
|
550
561
|
"dealroom-companies:scrape-local",
|
|
551
562
|
"linkedin-products:scrape",
|
|
552
563
|
"salesnav:from-product-category",
|
|
564
|
+
"salesnav:people:collect",
|
|
553
565
|
"salesnav:crawl",
|
|
554
566
|
"salesnav:crawl:status",
|
|
555
567
|
"salesnav:export",
|
|
@@ -6793,6 +6805,10 @@ function buildSalesNavigatorApiRequestFromSearchUrl(searchUrl, config, count) {
|
|
|
6793
6805
|
}
|
|
6794
6806
|
function buildSalesNavigatorLeadApiUrlFromSearchUrl(searchUrl, count) {
|
|
6795
6807
|
const source = new URL(searchUrl);
|
|
6808
|
+
if (!source.searchParams.has("query") && source.hash.startsWith("#query=")) {
|
|
6809
|
+
source.search = `?${source.hash.slice(1)}`;
|
|
6810
|
+
source.hash = "";
|
|
6811
|
+
}
|
|
6796
6812
|
if (source.pathname.includes("/sales-api/salesApiLeadSearch")) {
|
|
6797
6813
|
return source.toString();
|
|
6798
6814
|
}
|
|
@@ -8724,34 +8740,41 @@ function describeLocalSalesNavigatorAccountPartialError(fetchResult) {
|
|
|
8724
8740
|
async function importLocalSalesNavigatorPeopleViaApp(session, payload) {
|
|
8725
8741
|
const { value } = await fetchCliJson(session, (currentSession) => {
|
|
8726
8742
|
const workspaceClientId = currentSession.user.workspaceClientId?.trim() || null;
|
|
8743
|
+
const serializedPayload = JSON.stringify({
|
|
8744
|
+
sourceQueryUrl: payload.sourceQueryUrl,
|
|
8745
|
+
slicedQueryUrl: payload.sourceQueryUrl,
|
|
8746
|
+
appliedFilters: [],
|
|
8747
|
+
maxResultsPerSearch: payload.maxResultsPerSearch,
|
|
8748
|
+
numberOfProfiles: payload.numberOfProfiles,
|
|
8749
|
+
slicePreset: payload.slicePreset,
|
|
8750
|
+
totalResults: payload.totalResults,
|
|
8751
|
+
fetchedPages: payload.fetchedPages,
|
|
8752
|
+
people: payload.people,
|
|
8753
|
+
rawPayload: {
|
|
8754
|
+
clientId: workspaceClientId ?? undefined,
|
|
8755
|
+
client_id: workspaceClientId ?? undefined,
|
|
8756
|
+
leadList: workspaceClientId
|
|
8757
|
+
? { clientId: workspaceClientId }
|
|
8758
|
+
: undefined,
|
|
8759
|
+
workflow: "salesnav:local-import",
|
|
8760
|
+
localPeopleCount: payload.people.length,
|
|
8761
|
+
fetchedPages: payload.fetchedPages,
|
|
8762
|
+
pacing: payload.pacing,
|
|
8763
|
+
},
|
|
8764
|
+
});
|
|
8765
|
+
const shouldCompress = Buffer.byteLength(serializedPayload) >= 512 * 1024;
|
|
8727
8766
|
return fetch(`${currentSession.apiBaseUrl}/api/cli/salesnav/local-import`, {
|
|
8728
8767
|
method: "POST",
|
|
8729
8768
|
headers: {
|
|
8730
|
-
"Content-Type":
|
|
8769
|
+
"Content-Type": shouldCompress
|
|
8770
|
+
? "application/octet-stream"
|
|
8771
|
+
: "application/json",
|
|
8731
8772
|
Authorization: `Bearer ${currentSession.accessToken}`,
|
|
8773
|
+
...(shouldCompress
|
|
8774
|
+
? { "X-Salesprompter-Content-Encoding": "gzip" }
|
|
8775
|
+
: {}),
|
|
8732
8776
|
},
|
|
8733
|
-
body:
|
|
8734
|
-
sourceQueryUrl: payload.sourceQueryUrl,
|
|
8735
|
-
slicedQueryUrl: payload.sourceQueryUrl,
|
|
8736
|
-
appliedFilters: [],
|
|
8737
|
-
maxResultsPerSearch: payload.maxResultsPerSearch,
|
|
8738
|
-
numberOfProfiles: payload.numberOfProfiles,
|
|
8739
|
-
slicePreset: payload.slicePreset,
|
|
8740
|
-
totalResults: payload.totalResults,
|
|
8741
|
-
fetchedPages: payload.fetchedPages,
|
|
8742
|
-
people: payload.people,
|
|
8743
|
-
rawPayload: {
|
|
8744
|
-
clientId: workspaceClientId ?? undefined,
|
|
8745
|
-
client_id: workspaceClientId ?? undefined,
|
|
8746
|
-
leadList: workspaceClientId
|
|
8747
|
-
? { clientId: workspaceClientId }
|
|
8748
|
-
: undefined,
|
|
8749
|
-
workflow: "salesnav:local-import",
|
|
8750
|
-
localPeopleCount: payload.people.length,
|
|
8751
|
-
fetchedPages: payload.fetchedPages,
|
|
8752
|
-
pacing: payload.pacing,
|
|
8753
|
-
},
|
|
8754
|
-
}),
|
|
8777
|
+
body: shouldCompress ? gzipSync(serializedPayload) : serializedPayload,
|
|
8755
8778
|
});
|
|
8756
8779
|
}, SalesNavigatorLocalImportResponseSchema);
|
|
8757
8780
|
return value;
|
|
@@ -13198,6 +13221,99 @@ program
|
|
|
13198
13221
|
}
|
|
13199
13222
|
});
|
|
13200
13223
|
});
|
|
13224
|
+
async function runSalesNavigatorPeopleCollectCommand(options) {
|
|
13225
|
+
const linkedInUrl = z.string().url().parse(options.linkedinUrl);
|
|
13226
|
+
const maxResults = z.coerce.number().int().min(1).max(100000).parse(options.maxResults);
|
|
13227
|
+
const pageSize = z.coerce.number().int().min(1).max(100).parse(options.pageSize);
|
|
13228
|
+
const normalizedSourceUrl = (() => {
|
|
13229
|
+
const source = new URL(linkedInUrl);
|
|
13230
|
+
if (!source.searchParams.has("query") && source.hash.startsWith("#query=")) {
|
|
13231
|
+
source.search = `?${source.hash.slice(1)}`;
|
|
13232
|
+
source.hash = "";
|
|
13233
|
+
}
|
|
13234
|
+
return source.toString();
|
|
13235
|
+
})();
|
|
13236
|
+
if (options.dryRun) {
|
|
13237
|
+
const payload = {
|
|
13238
|
+
status: "ok",
|
|
13239
|
+
dryRun: true,
|
|
13240
|
+
linkedInUrl: normalizedSourceUrl,
|
|
13241
|
+
maxResults,
|
|
13242
|
+
destination: "/leads/cli-imports"
|
|
13243
|
+
};
|
|
13244
|
+
if (options.out)
|
|
13245
|
+
await writeJsonFile(options.out, payload);
|
|
13246
|
+
return payload;
|
|
13247
|
+
}
|
|
13248
|
+
const session = await requireAuthSession();
|
|
13249
|
+
const pageDelayMinMs = z.coerce.number().int().min(0).parse(options.pageDelayMinMs);
|
|
13250
|
+
const pageDelayMaxMs = z.coerce.number().int().min(pageDelayMinMs).parse(options.pageDelayMaxMs);
|
|
13251
|
+
const browserRelayPort = options.browserRelayPort == null
|
|
13252
|
+
? null
|
|
13253
|
+
: z.coerce.number().int().min(0).max(65535).parse(options.browserRelayPort);
|
|
13254
|
+
if (options.curlFile && browserRelayPort != null) {
|
|
13255
|
+
throw new Error("Use either --curl-file or --browser-relay-port, not both.");
|
|
13256
|
+
}
|
|
13257
|
+
const parsedRequest = options.curlFile
|
|
13258
|
+
? parseSalesNavigatorCurlRequest(await readFile(path.resolve(String(options.curlFile)), "utf8"))
|
|
13259
|
+
: browserRelayPort != null
|
|
13260
|
+
? {
|
|
13261
|
+
url: buildSalesNavigatorLeadApiUrlFromSearchUrl(normalizedSourceUrl, pageSize),
|
|
13262
|
+
headers: {}
|
|
13263
|
+
}
|
|
13264
|
+
: buildSalesNavigatorApiRequestFromSearchUrl(normalizedSourceUrl, await readLinkedInDirectLookupConfig(), pageSize);
|
|
13265
|
+
if (!new URL(parsedRequest.url).pathname.includes("/sales-api/salesApiLeadSearch")) {
|
|
13266
|
+
throw new Error("--curl-file must contain a /sales-api/salesApiLeadSearch request.");
|
|
13267
|
+
}
|
|
13268
|
+
const browserRelay = browserRelayPort == null
|
|
13269
|
+
? null
|
|
13270
|
+
: await createLocalAccountSearchBrowserRelay(browserRelayPort);
|
|
13271
|
+
let collected;
|
|
13272
|
+
try {
|
|
13273
|
+
collected = await fetchAllLocalSalesNavigatorPeople(parsedRequest, {
|
|
13274
|
+
requestedProfiles: maxResults,
|
|
13275
|
+
pageSize,
|
|
13276
|
+
pageDelayMinMs,
|
|
13277
|
+
pageDelayMaxMs,
|
|
13278
|
+
retry: {
|
|
13279
|
+
maxRetries: 2,
|
|
13280
|
+
retryBaseDelayMs: 2_000,
|
|
13281
|
+
retryMaxDelayMs: 10_000
|
|
13282
|
+
},
|
|
13283
|
+
executeRequest: browserRelay
|
|
13284
|
+
? (request) => browserRelay.request(request.url)
|
|
13285
|
+
: undefined
|
|
13286
|
+
});
|
|
13287
|
+
}
|
|
13288
|
+
finally {
|
|
13289
|
+
await browserRelay?.close();
|
|
13290
|
+
}
|
|
13291
|
+
const imported = await importLocalSalesNavigatorPeopleViaApp(session, {
|
|
13292
|
+
sourceQueryUrl: normalizedSourceUrl,
|
|
13293
|
+
people: collected.people,
|
|
13294
|
+
totalResults: collected.totalResults,
|
|
13295
|
+
fetchedPages: collected.fetchedPages,
|
|
13296
|
+
pacing: collected.pacing,
|
|
13297
|
+
maxResultsPerSearch: maxResults,
|
|
13298
|
+
numberOfProfiles: maxResults,
|
|
13299
|
+
slicePreset: "local-salesnav-people"
|
|
13300
|
+
});
|
|
13301
|
+
const payload = {
|
|
13302
|
+
status: "ok",
|
|
13303
|
+
dryRun: false,
|
|
13304
|
+
collected: collected.people.length,
|
|
13305
|
+
totalResults: collected.totalResults,
|
|
13306
|
+
fetchedPages: collected.fetchedPages,
|
|
13307
|
+
runId: imported.runId,
|
|
13308
|
+
imported: imported.imported,
|
|
13309
|
+
upserted: imported.upserted,
|
|
13310
|
+
workspaceUrl: `${session.apiBaseUrl}/leads/cli-imports?runId=` +
|
|
13311
|
+
encodeURIComponent(imported.runId)
|
|
13312
|
+
};
|
|
13313
|
+
if (options.out)
|
|
13314
|
+
await writeJsonFile(options.out, payload);
|
|
13315
|
+
return payload;
|
|
13316
|
+
}
|
|
13201
13317
|
async function runAffiliateLaunchCommand(options) {
|
|
13202
13318
|
const affiliateLink = z.string().url().parse(options.affiliateLink);
|
|
13203
13319
|
const linkedInUrl = z.string().url().parse(options.linkedinUrl);
|
|
@@ -13283,6 +13399,22 @@ function addAffiliateAudienceOptions(command) {
|
|
|
13283
13399
|
.option("--dry-run", "Validate and preview without creating remote resources", false)
|
|
13284
13400
|
.option("--out <path>", "Optional local JSON output path");
|
|
13285
13401
|
}
|
|
13402
|
+
program
|
|
13403
|
+
.command("salesnav:people:collect")
|
|
13404
|
+
.alias("leads:collect")
|
|
13405
|
+
.description("Collect a Sales Navigator people search into the active workspace.")
|
|
13406
|
+
.requiredOption("--linkedin-url <url>", "Sales Navigator people search URL")
|
|
13407
|
+
.option("--max-results <number>", "Maximum people to collect", "1000")
|
|
13408
|
+
.option("--curl-file <path>", "Optional copied Sales Navigator Lead Search curl request")
|
|
13409
|
+
.option("--browser-relay-port <number>", "Use the signed-in browser through a loopback relay")
|
|
13410
|
+
.option("--page-size <number>", "Direct Sales Navigator page size", "100")
|
|
13411
|
+
.option("--page-delay-min-ms <number>", "Minimum delay between direct pages", "5000")
|
|
13412
|
+
.option("--page-delay-max-ms <number>", "Maximum delay between direct pages", "8000")
|
|
13413
|
+
.option("--dry-run", "Validate without collecting or importing", false)
|
|
13414
|
+
.option("--out <path>", "Optional local JSON output path")
|
|
13415
|
+
.action(async (options) => {
|
|
13416
|
+
printOutput(await runSalesNavigatorPeopleCollectCommand(options));
|
|
13417
|
+
});
|
|
13286
13418
|
addAffiliateAudienceOptions(program
|
|
13287
13419
|
.command("affiliate:launch")
|
|
13288
13420
|
.description("Build an affiliate audience from an affiliate link and LinkedIn search URL."))
|
package/package.json
CHANGED