salesprompter-cli 0.1.37 → 0.1.39
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/dist/cli.js +189 -11
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import { access, appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import os from "node:os";
|
|
@@ -25,7 +26,8 @@ import { InstantlySyncProvider } from "./instantly.js";
|
|
|
25
26
|
import { backfillLinkedInCompanies } from "./linkedin-companies.js";
|
|
26
27
|
import { parseLinkedInCompanyPage } from "./linkedin-companies.js";
|
|
27
28
|
import { crawlLinkedInProductCategory } from "./linkedin-products.js";
|
|
28
|
-
import { claimValidatedSalesNavigatorSessionCookieForCli, createLinkedInSessionSupabaseClient, resolveConfiguredEnvValue } from "./linkedin-session.js";
|
|
29
|
+
import { claimValidatedSalesNavigatorSessionCookieForCli, createLinkedInSessionSupabaseClient, probeSalesNavigatorSearchSession, resolveConfiguredEnvValue } from "./linkedin-session.js";
|
|
30
|
+
import { extractLiAtCookieValue } from "./linkedin-session-contracts.js";
|
|
29
31
|
import { buildLeadlistsFunnelQueries } from "./leadlists-funnel.js";
|
|
30
32
|
import { readJsonFile, splitCsv, writeJsonFile, writeTextFile } from "./io.js";
|
|
31
33
|
import { buildSalesNavigatorCrawlPreview, createSalesNavigatorCrawlSeed, DEFAULT_SALES_NAVIGATOR_CRAWL_DIMENSIONS, buildSalesNavigatorPeopleSlice, deriveSalesNavigatorTitleQuerySeeds, expandSalesNavigatorCrawlAttempt, SalesNavigatorSliceTooBroadError } from "./sales-navigator.js";
|
|
@@ -122,6 +124,20 @@ const LinkedInCompanyBackfillStatusResponseSchema = z.object({
|
|
|
122
124
|
failureCode: z.string().nullable().optional(),
|
|
123
125
|
failureMessage: z.string().nullable().optional()
|
|
124
126
|
});
|
|
127
|
+
const LeadPoolEmailFinderProcessResponseSchema = z.object({
|
|
128
|
+
success: z.boolean(),
|
|
129
|
+
provider: z.string().optional(),
|
|
130
|
+
selected: z.number().int().nonnegative(),
|
|
131
|
+
found: z.number().int().nonnegative(),
|
|
132
|
+
notFound: z.number().int().nonnegative(),
|
|
133
|
+
failed: z.number().int().nonnegative(),
|
|
134
|
+
durationMs: z.number().nonnegative(),
|
|
135
|
+
launched: z.boolean().optional(),
|
|
136
|
+
agentId: z.string().nullable().optional(),
|
|
137
|
+
containerId: z.string().nullable().optional(),
|
|
138
|
+
csvInputUrl: z.string().nullable().optional(),
|
|
139
|
+
processedAt: z.string()
|
|
140
|
+
});
|
|
125
141
|
const PhantombusterContainersSyncResponseSchema = z.object({
|
|
126
142
|
status: z.literal("ok"),
|
|
127
143
|
agentIds: z.array(z.string().min(1)),
|
|
@@ -5717,6 +5733,24 @@ async function syncPhantombusterContainersViaApp(session, payload) {
|
|
|
5717
5733
|
}), PhantombusterContainersSyncResponseSchema);
|
|
5718
5734
|
return value;
|
|
5719
5735
|
}
|
|
5736
|
+
async function runLeadPoolEmailFinderViaApp(session, payload) {
|
|
5737
|
+
const url = new URL("/api/leadpool/process-hunter-emails", session.apiBaseUrl);
|
|
5738
|
+
url.searchParams.set("provider", payload.provider);
|
|
5739
|
+
url.searchParams.set("limit", String(payload.limit));
|
|
5740
|
+
if (payload.concurrency != null) {
|
|
5741
|
+
url.searchParams.set("concurrency", String(payload.concurrency));
|
|
5742
|
+
}
|
|
5743
|
+
if (payload.retryNotFoundAfterDays != null) {
|
|
5744
|
+
url.searchParams.set("retryNotFoundAfterDays", String(payload.retryNotFoundAfterDays));
|
|
5745
|
+
}
|
|
5746
|
+
const { value } = await fetchCliJson(session, (currentSession) => fetch(url.toString(), {
|
|
5747
|
+
method: "POST",
|
|
5748
|
+
headers: {
|
|
5749
|
+
Authorization: `Bearer ${currentSession.accessToken}`
|
|
5750
|
+
}
|
|
5751
|
+
}), LeadPoolEmailFinderProcessResponseSchema);
|
|
5752
|
+
return value;
|
|
5753
|
+
}
|
|
5720
5754
|
function serializeSalesNavigatorFiltersForApi(filters) {
|
|
5721
5755
|
return filters.map((filter) => ({
|
|
5722
5756
|
type: filter.type,
|
|
@@ -5869,6 +5903,15 @@ async function runSalesNavigatorExport(session, payload, traceId, logOptions = {
|
|
|
5869
5903
|
}
|
|
5870
5904
|
}
|
|
5871
5905
|
async function startSalesNavigatorExport(session, payload, traceId) {
|
|
5906
|
+
const exportSession = await resolveSalesNavigatorExportSessionOverride(payload.slicedQueryUrl, {
|
|
5907
|
+
source: "cli_salesnav_export_start"
|
|
5908
|
+
});
|
|
5909
|
+
const sessionOverridePayload = exportSession
|
|
5910
|
+
? {
|
|
5911
|
+
sessionCookie: exportSession.sessionCookie,
|
|
5912
|
+
selectedSessionCookieSha256: exportSession.selectedSessionCookieSha256
|
|
5913
|
+
}
|
|
5914
|
+
: {};
|
|
5872
5915
|
return await fetchCliJson(session, (currentSession) => fetch(`${currentSession.apiBaseUrl}/api/cli/salesnav/export`, {
|
|
5873
5916
|
method: "POST",
|
|
5874
5917
|
signal: AbortSignal.timeout(SALES_NAVIGATOR_EXPORT_START_TIMEOUT_MS),
|
|
@@ -5879,7 +5922,8 @@ async function startSalesNavigatorExport(session, payload, traceId) {
|
|
|
5879
5922
|
},
|
|
5880
5923
|
body: JSON.stringify({
|
|
5881
5924
|
...payload,
|
|
5882
|
-
appliedFilters: serializeSalesNavigatorFiltersForApi(payload.appliedFilters)
|
|
5925
|
+
appliedFilters: serializeSalesNavigatorFiltersForApi(payload.appliedFilters),
|
|
5926
|
+
...sessionOverridePayload
|
|
5883
5927
|
})
|
|
5884
5928
|
}), SalesNavigatorExportStartResponseSchema);
|
|
5885
5929
|
}
|
|
@@ -6686,25 +6730,95 @@ function buildSalesNavigatorSliceFailureReport(slice, error, options) {
|
|
|
6686
6730
|
function formatSalesNavigatorSplitTrail(splitTrail) {
|
|
6687
6731
|
return splitTrail.map((entry) => `${entry.key}:${entry.value.text}`);
|
|
6688
6732
|
}
|
|
6733
|
+
let cachedSalesNavigatorExportSessionOverride = null;
|
|
6734
|
+
function hashSalesNavigatorSessionCookieForPhantombuster(sessionCookie) {
|
|
6735
|
+
const liAt = extractLiAtCookieValue(sessionCookie);
|
|
6736
|
+
if (!liAt) {
|
|
6737
|
+
return null;
|
|
6738
|
+
}
|
|
6739
|
+
return createHash("sha256").update(liAt).digest("hex");
|
|
6740
|
+
}
|
|
6741
|
+
function normalizeSalesNavigatorSessionCookieForPhantombuster(sessionCookie) {
|
|
6742
|
+
return extractLiAtCookieValue(sessionCookie) ?? sessionCookie;
|
|
6743
|
+
}
|
|
6744
|
+
function cacheSalesNavigatorExportSessionOverride(queryUrl, value) {
|
|
6745
|
+
cachedSalesNavigatorExportSessionOverride = {
|
|
6746
|
+
queryUrl,
|
|
6747
|
+
expiresAt: Date.now() + 120_000,
|
|
6748
|
+
value
|
|
6749
|
+
};
|
|
6750
|
+
return value;
|
|
6751
|
+
}
|
|
6752
|
+
async function resolveSalesNavigatorExportSessionOverride(queryUrl, options) {
|
|
6753
|
+
if (cachedSalesNavigatorExportSessionOverride &&
|
|
6754
|
+
cachedSalesNavigatorExportSessionOverride.queryUrl === queryUrl &&
|
|
6755
|
+
cachedSalesNavigatorExportSessionOverride.expiresAt > Date.now()) {
|
|
6756
|
+
return cachedSalesNavigatorExportSessionOverride.value;
|
|
6757
|
+
}
|
|
6758
|
+
const localExtensionConfig = await readLocalLinkedInExtensionDirectLookupConfig();
|
|
6759
|
+
if (localExtensionConfig?.cookie) {
|
|
6760
|
+
const selectedSessionCookieSha256 = hashSalesNavigatorSessionCookieForPhantombuster(localExtensionConfig.cookie);
|
|
6761
|
+
if (process.env.SALESPROMPTER_SALESNAV_EXPORT_SKIP_EXTENSION_PREFLIGHT === "1") {
|
|
6762
|
+
return cacheSalesNavigatorExportSessionOverride(queryUrl, {
|
|
6763
|
+
sessionCookie: normalizeSalesNavigatorSessionCookieForPhantombuster(localExtensionConfig.cookie),
|
|
6764
|
+
selectedSessionCookieSha256,
|
|
6765
|
+
source: "chrome_extension"
|
|
6766
|
+
});
|
|
6767
|
+
}
|
|
6768
|
+
const probe = await probeSalesNavigatorSearchSession(localExtensionConfig.cookie, queryUrl, {
|
|
6769
|
+
timeoutMs: 8000
|
|
6770
|
+
});
|
|
6771
|
+
await options.logger?.log("salesnav.export.session.chrome_extension.preflight", {
|
|
6772
|
+
source: options.source,
|
|
6773
|
+
queryUrl,
|
|
6774
|
+
status: probe.status,
|
|
6775
|
+
selectedSessionCookieSha256,
|
|
6776
|
+
finalUrl: probe.finalUrl,
|
|
6777
|
+
validationError: probe.validationError
|
|
6778
|
+
});
|
|
6779
|
+
if (probe.status === "ok") {
|
|
6780
|
+
return cacheSalesNavigatorExportSessionOverride(queryUrl, {
|
|
6781
|
+
sessionCookie: normalizeSalesNavigatorSessionCookieForPhantombuster(localExtensionConfig.cookie),
|
|
6782
|
+
selectedSessionCookieSha256,
|
|
6783
|
+
source: "chrome_extension"
|
|
6784
|
+
});
|
|
6785
|
+
}
|
|
6786
|
+
}
|
|
6787
|
+
const claimed = await claimValidatedSalesNavigatorSessionCookieForCli({
|
|
6788
|
+
queryUrl,
|
|
6789
|
+
source: options.source,
|
|
6790
|
+
env: process.env
|
|
6791
|
+
});
|
|
6792
|
+
if (claimed?.sessionCookie) {
|
|
6793
|
+
return cacheSalesNavigatorExportSessionOverride(queryUrl, {
|
|
6794
|
+
sessionCookie: normalizeSalesNavigatorSessionCookieForPhantombuster(claimed.sessionCookie),
|
|
6795
|
+
selectedSessionCookieSha256: claimed.sessionCookieSha256 ??
|
|
6796
|
+
hashSalesNavigatorSessionCookieForPhantombuster(claimed.sessionCookie),
|
|
6797
|
+
source: "session_vault"
|
|
6798
|
+
});
|
|
6799
|
+
}
|
|
6800
|
+
if (localExtensionConfig?.cookie &&
|
|
6801
|
+
process.env.SALESPROMPTER_SALESNAV_EXPORT_REQUIRE_FRESH_SESSION === "1") {
|
|
6802
|
+
throw new Error("The local Salesprompter Chrome extension session cookie is not valid for Sales Navigator. Reconnect the extension, then retry the CLI command.");
|
|
6803
|
+
}
|
|
6804
|
+
if (process.env.SALESPROMPTER_CLI_MANAGE_LINKEDIN_SESSIONS === "1") {
|
|
6805
|
+
throw new Error("No validated LinkedIn Sales Navigator session cookie is available from the CLI-managed session pool.");
|
|
6806
|
+
}
|
|
6807
|
+
return null;
|
|
6808
|
+
}
|
|
6689
6809
|
async function ensureSalesNavigatorSessionPoolReady(queryUrl, options) {
|
|
6690
6810
|
try {
|
|
6691
6811
|
await options.logger?.log("salesnav.session_pool.preflight.started", {
|
|
6692
6812
|
source: options.source,
|
|
6693
6813
|
queryUrl
|
|
6694
6814
|
});
|
|
6695
|
-
const claimed = await
|
|
6696
|
-
queryUrl,
|
|
6697
|
-
source: options.source,
|
|
6698
|
-
env: process.env
|
|
6699
|
-
});
|
|
6815
|
+
const claimed = await resolveSalesNavigatorExportSessionOverride(queryUrl, options);
|
|
6700
6816
|
await options.logger?.log("salesnav.session_pool.preflight.completed", {
|
|
6701
6817
|
source: options.source,
|
|
6702
6818
|
queryUrl,
|
|
6703
6819
|
status: claimed ? "ok" : "skipped",
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
selectedSessionCookieSha256: claimed?.sessionCookieSha256 ?? null,
|
|
6707
|
-
selectedSessionLastIngestedSource: claimed?.lastIngestedSource ?? null
|
|
6820
|
+
selectedSessionSource: claimed?.source ?? "app_managed",
|
|
6821
|
+
selectedSessionCookieSha256: claimed?.selectedSessionCookieSha256 ?? null
|
|
6708
6822
|
});
|
|
6709
6823
|
return {
|
|
6710
6824
|
ready: true
|
|
@@ -10681,6 +10795,8 @@ program
|
|
|
10681
10795
|
.requiredOption("--out-dir <path>", "Output directory for artifacts")
|
|
10682
10796
|
.option("--trace-id <traceId>", "Trace id for this batch")
|
|
10683
10797
|
.option("--endpoint-url <url>", "Override the enrichment workflow endpoint")
|
|
10798
|
+
.option("--provider <provider>", "Email finder provider for workspace queue: hunter or phantombuster", "hunter")
|
|
10799
|
+
.option("--retry-not-found-after-days <number>", "Retry stale Email not found rows after this many days", "1")
|
|
10684
10800
|
.option("--timeout-ms <number>", "Workflow trigger timeout in milliseconds", "30000")
|
|
10685
10801
|
.option("--company-cleaning <mode>", "Company cleaning mode for direct input: off, basic, or ai", "basic")
|
|
10686
10802
|
.option("--dry-run", "Preview the next batch without starting background processing", false)
|
|
@@ -10689,7 +10805,69 @@ program
|
|
|
10689
10805
|
const outDir = z.string().min(1).parse(options.outDir);
|
|
10690
10806
|
const timeoutMs = z.coerce.number().int().min(1000).max(300000).parse(options.timeoutMs);
|
|
10691
10807
|
const usingDirectInput = typeof options.in === "string" && options.in.trim().length > 0;
|
|
10808
|
+
const provider = z.enum(["hunter", "phantombuster"]).parse(String(options.provider ?? "hunter").trim().toLowerCase());
|
|
10809
|
+
const retryNotFoundAfterDays = z.coerce.number().int().min(0).max(30).parse(options.retryNotFoundAfterDays);
|
|
10692
10810
|
const companyCleaningMode = resolveCompanyCleaningMode(String(options.companyCleaning ?? process.env.SALESPROMPTER_COMPANY_CLEANING_MODE ?? "basic"));
|
|
10811
|
+
if (provider === "phantombuster" && !usingDirectInput) {
|
|
10812
|
+
await mkdir(outDir, { recursive: true });
|
|
10813
|
+
const traceId = z.string().min(1).parse(options.traceId ?? `salesprompter-cli-email-phantombuster-${Date.now()}`);
|
|
10814
|
+
const requestPath = `${outDir}/email-enrichment-request-phantombuster.json`;
|
|
10815
|
+
const responsePath = `${outDir}/email-enrichment-response-phantombuster.json`;
|
|
10816
|
+
const requestPayload = {
|
|
10817
|
+
traceId,
|
|
10818
|
+
provider,
|
|
10819
|
+
limit,
|
|
10820
|
+
retryNotFoundAfterDays
|
|
10821
|
+
};
|
|
10822
|
+
await writeJsonFile(requestPath, requestPayload);
|
|
10823
|
+
if (options.dryRun) {
|
|
10824
|
+
printOutput({
|
|
10825
|
+
status: "ok",
|
|
10826
|
+
provider,
|
|
10827
|
+
limit,
|
|
10828
|
+
traceId,
|
|
10829
|
+
dryRun: true,
|
|
10830
|
+
mode: "workspace-queue",
|
|
10831
|
+
contactsQueued: null,
|
|
10832
|
+
started: false,
|
|
10833
|
+
artifacts: {
|
|
10834
|
+
inputSqlPath: null,
|
|
10835
|
+
inputRowsPath: null,
|
|
10836
|
+
directReportPath: null,
|
|
10837
|
+
requestPath,
|
|
10838
|
+
responsePath: null
|
|
10839
|
+
}
|
|
10840
|
+
});
|
|
10841
|
+
return;
|
|
10842
|
+
}
|
|
10843
|
+
const session = await requireAuthSession();
|
|
10844
|
+
const result = await runLeadPoolEmailFinderViaApp(session, {
|
|
10845
|
+
provider,
|
|
10846
|
+
limit,
|
|
10847
|
+
retryNotFoundAfterDays
|
|
10848
|
+
});
|
|
10849
|
+
await writeJsonFile(responsePath, result);
|
|
10850
|
+
printOutput({
|
|
10851
|
+
status: result.success ? "ok" : "partial",
|
|
10852
|
+
provider,
|
|
10853
|
+
limit,
|
|
10854
|
+
traceId,
|
|
10855
|
+
dryRun: false,
|
|
10856
|
+
mode: "workspace-queue",
|
|
10857
|
+
contactsQueued: result.selected,
|
|
10858
|
+
started: Boolean(result.launched),
|
|
10859
|
+
agentId: result.agentId ?? null,
|
|
10860
|
+
containerId: result.containerId ?? null,
|
|
10861
|
+
artifacts: {
|
|
10862
|
+
inputSqlPath: null,
|
|
10863
|
+
inputRowsPath: null,
|
|
10864
|
+
directReportPath: null,
|
|
10865
|
+
requestPath,
|
|
10866
|
+
responsePath
|
|
10867
|
+
}
|
|
10868
|
+
});
|
|
10869
|
+
return;
|
|
10870
|
+
}
|
|
10693
10871
|
let clientId;
|
|
10694
10872
|
let queueRows;
|
|
10695
10873
|
let directReportRows = null;
|
package/package.json
CHANGED