salesprompter-cli 0.1.27 → 0.1.29
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/CODE_OF_CONDUCT.md +35 -0
- package/CONTRIBUTING.md +89 -0
- package/README.md +30 -1
- package/SECURITY.md +35 -0
- package/dist/cli.js +3309 -166
- package/dist/deel-outreach.js +16 -1
- package/dist/direct-path.js +16 -1
- package/dist/domainfinder.js +132 -0
- package/dist/linkedin-companies.js +3 -3
- package/dist/linkedin-products.js +2 -2
- package/dist/linkedin-session-contracts.js +3 -0
- package/dist/linkedin-session.js +8 -9
- package/dist/vendor/salesprompter-shared/extension-session-contracts.js +29 -0
- package/dist/vendor/salesprompter-shared/linkedin-session.js +22 -0
- package/dist/vendor/salesprompter-shared/phantombuster-contracts.js +16 -0
- package/dist/vendor/salesprompter-shared/session-vault-contracts.js +17 -0
- package/package.json +17 -4
package/dist/deel-outreach.js
CHANGED
|
@@ -12,6 +12,19 @@ function marketCountries(market) {
|
|
|
12
12
|
function sqlStringList(values) {
|
|
13
13
|
return values.map((value) => `'${value.replaceAll("'", "\\'")}'`).join(", ");
|
|
14
14
|
}
|
|
15
|
+
function companySizeEmployeeCountSql(field) {
|
|
16
|
+
return `CASE TRIM(${field})
|
|
17
|
+
WHEN '1-10' THEN 10
|
|
18
|
+
WHEN '11-50' THEN 30
|
|
19
|
+
WHEN '51-200' THEN 125
|
|
20
|
+
WHEN '201-500' THEN 350
|
|
21
|
+
WHEN '501-1000' THEN 750
|
|
22
|
+
WHEN '1001-5000' THEN 2500
|
|
23
|
+
WHEN '5001-10.000' THEN 7500
|
|
24
|
+
WHEN '10.000+' THEN 10000
|
|
25
|
+
ELSE NULL
|
|
26
|
+
END`;
|
|
27
|
+
}
|
|
15
28
|
function broadTitlePredicateSql(field) {
|
|
16
29
|
return [
|
|
17
30
|
`LOWER(${field}) LIKE '%head of hr%'`,
|
|
@@ -54,6 +67,7 @@ function titleExclusionPredicateSql(field) {
|
|
|
54
67
|
export function buildDeelOutreachExportSql(options) {
|
|
55
68
|
const countries = marketCountries(options.market);
|
|
56
69
|
const countryClause = countries.length > 0 ? `UPPER(CAST(p.company_countryCode AS STRING)) IN (${sqlStringList(countries)})` : "TRUE";
|
|
70
|
+
const companySizeEmployeeCount = companySizeEmployeeCountSql("CAST(p.companySize AS STRING)");
|
|
57
71
|
return `WITH hr_leadlists AS (
|
|
58
72
|
SELECT
|
|
59
73
|
CAST(leadListId AS STRING) AS leadListId,
|
|
@@ -120,7 +134,8 @@ WHERE CAST(p.functionId AS STRING) = '12'
|
|
|
120
134
|
AND CAST(p.firstName_cleaned AS STRING) IS NOT NULL
|
|
121
135
|
AND CAST(p.lastName_cleaned AS STRING) IS NOT NULL
|
|
122
136
|
AND SAFE_CAST(p.emailScore AS INT64) >= ${Math.trunc(options.minEmailScore)}
|
|
123
|
-
AND
|
|
137
|
+
AND ${companySizeEmployeeCount} >= 100
|
|
138
|
+
AND ${companySizeEmployeeCount} <= 2500
|
|
124
139
|
AND (
|
|
125
140
|
${broadTitlePredicateSql("CAST(p.jobTitle AS STRING)")}
|
|
126
141
|
)
|
package/dist/direct-path.js
CHANGED
|
@@ -11,6 +11,19 @@ function marketCountries(market) {
|
|
|
11
11
|
function sqlStringList(values) {
|
|
12
12
|
return values.map((value) => `'${value.replaceAll("'", "\\'")}'`).join(", ");
|
|
13
13
|
}
|
|
14
|
+
function companySizeEmployeeCountSql(field) {
|
|
15
|
+
return `CASE TRIM(${field})
|
|
16
|
+
WHEN '1-10' THEN 10
|
|
17
|
+
WHEN '11-50' THEN 30
|
|
18
|
+
WHEN '51-200' THEN 125
|
|
19
|
+
WHEN '201-500' THEN 350
|
|
20
|
+
WHEN '501-1000' THEN 750
|
|
21
|
+
WHEN '1001-5000' THEN 2500
|
|
22
|
+
WHEN '5001-10.000' THEN 7500
|
|
23
|
+
WHEN '10.000+' THEN 10000
|
|
24
|
+
ELSE NULL
|
|
25
|
+
END`;
|
|
26
|
+
}
|
|
14
27
|
function broadTitlePredicateSql() {
|
|
15
28
|
return [
|
|
16
29
|
"LOWER(c.jobTitle) LIKE '%head of hr%'",
|
|
@@ -58,6 +71,7 @@ export function buildDirectPathLeadExportSql(vendor, market, limit) {
|
|
|
58
71
|
const countryClause = countries.length > 0 ? `co.countryCode IN (${sqlStringList(countries)})` : "TRUE";
|
|
59
72
|
const broadTitlePredicate = broadTitlePredicateSql();
|
|
60
73
|
const titleExclusions = titleExclusionPredicateSql();
|
|
74
|
+
const companySizeEmployeeCount = companySizeEmployeeCountSql("co.companySize");
|
|
61
75
|
return `WITH hr_leadlists AS (
|
|
62
76
|
SELECT
|
|
63
77
|
CAST(leadListId AS STRING) AS leadListId,
|
|
@@ -132,7 +146,8 @@ JOIN hr_leadlists l ON c.leadListId = l.leadListId
|
|
|
132
146
|
LEFT JOIN companies co ON c.companyId = co.companyId
|
|
133
147
|
WHERE ${countryClause}
|
|
134
148
|
AND co.domain IS NOT NULL
|
|
135
|
-
AND
|
|
149
|
+
AND ${companySizeEmployeeCount} >= 100
|
|
150
|
+
AND ${companySizeEmployeeCount} <= 2500
|
|
136
151
|
AND (
|
|
137
152
|
${broadTitlePredicate}
|
|
138
153
|
)
|
package/dist/domainfinder.js
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
const DOMAIN_BLACKLIST = new Set(["linkedin.com", "bit.ly", "linktr.ee", "facebook.com"]);
|
|
2
|
+
const COMPANY_NAME_STOPWORDS = new Set([
|
|
3
|
+
"group",
|
|
4
|
+
"gmbh",
|
|
5
|
+
"ag",
|
|
6
|
+
"kg",
|
|
7
|
+
"co",
|
|
8
|
+
"company",
|
|
9
|
+
"holding",
|
|
10
|
+
"holdings",
|
|
11
|
+
"solutions",
|
|
12
|
+
"systems",
|
|
13
|
+
"services",
|
|
14
|
+
"international",
|
|
15
|
+
"global",
|
|
16
|
+
"the",
|
|
17
|
+
"und",
|
|
18
|
+
"and",
|
|
19
|
+
"de",
|
|
20
|
+
"of"
|
|
21
|
+
]);
|
|
2
22
|
function marketCountries(market) {
|
|
3
23
|
if (market === "dach") {
|
|
4
24
|
return ["DE", "AT", "CH"];
|
|
@@ -39,6 +59,90 @@ function rootDomain(value) {
|
|
|
39
59
|
}
|
|
40
60
|
return parts.slice(-2).join(".");
|
|
41
61
|
}
|
|
62
|
+
function domainLabel(value) {
|
|
63
|
+
const root = rootDomain(value);
|
|
64
|
+
if (!root) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return root.split(".")[0] ?? null;
|
|
68
|
+
}
|
|
69
|
+
function companyNameTokens(companyName) {
|
|
70
|
+
return (companyName ?? "")
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
73
|
+
.split(/\s+/)
|
|
74
|
+
.map((token) => token.trim())
|
|
75
|
+
.filter((token) => token.length >= 3 && !COMPANY_NAME_STOPWORDS.has(token));
|
|
76
|
+
}
|
|
77
|
+
function domainCompanyMatchScore(domain, companyName) {
|
|
78
|
+
const label = domainLabel(domain);
|
|
79
|
+
if (!label) {
|
|
80
|
+
return -1;
|
|
81
|
+
}
|
|
82
|
+
const normalizedLabel = label.replace(/[^a-z0-9]/g, "");
|
|
83
|
+
if (normalizedLabel.length === 0) {
|
|
84
|
+
return -1;
|
|
85
|
+
}
|
|
86
|
+
const tokens = companyNameTokens(companyName);
|
|
87
|
+
if (tokens.length === 0) {
|
|
88
|
+
return normalizedLabel.length;
|
|
89
|
+
}
|
|
90
|
+
let score = 0;
|
|
91
|
+
for (const token of tokens) {
|
|
92
|
+
const normalizedToken = token.replace(/[^a-z0-9]/g, "");
|
|
93
|
+
if (normalizedToken.length === 0) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (normalizedLabel === normalizedToken) {
|
|
97
|
+
score += 100 + normalizedToken.length;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (normalizedLabel.startsWith(normalizedToken)) {
|
|
101
|
+
score += 60 + normalizedToken.length;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (normalizedLabel.includes(normalizedToken)) {
|
|
105
|
+
score += 40 + normalizedToken.length;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (normalizedToken.startsWith(normalizedLabel) && normalizedLabel.length >= 4) {
|
|
109
|
+
score += 20 + normalizedLabel.length;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return score;
|
|
113
|
+
}
|
|
114
|
+
function chooseBetterCompanyMatchCandidate(candidates, baselineDomain) {
|
|
115
|
+
const baselineScore = domainCompanyMatchScore(baselineDomain, candidates[0]?.companyName);
|
|
116
|
+
const ranked = [...candidates]
|
|
117
|
+
.filter((candidate) => normalizeDomain(candidate.domain) !== null)
|
|
118
|
+
.filter((candidate) => !isBlacklistedDomain(candidate.domain))
|
|
119
|
+
.filter((candidate) => candidate.source !== "linkedin")
|
|
120
|
+
.sort((a, b) => {
|
|
121
|
+
const scoreDelta = domainCompanyMatchScore(b.domain, b.companyName) -
|
|
122
|
+
domainCompanyMatchScore(a.domain, a.companyName);
|
|
123
|
+
if (scoreDelta !== 0) {
|
|
124
|
+
return scoreDelta;
|
|
125
|
+
}
|
|
126
|
+
const hunterDelta = (b.hunterEmailCount ?? -1) - (a.hunterEmailCount ?? -1);
|
|
127
|
+
if (hunterDelta !== 0) {
|
|
128
|
+
return hunterDelta;
|
|
129
|
+
}
|
|
130
|
+
return (a.source ?? "").localeCompare(b.source ?? "");
|
|
131
|
+
});
|
|
132
|
+
const best = ranked[0] ?? null;
|
|
133
|
+
if (!best) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const bestScore = domainCompanyMatchScore(best.domain, best.companyName);
|
|
137
|
+
const hasPositiveHunterSignal = (best.hunterEmailCount ?? 0) > 0;
|
|
138
|
+
if (hasPositiveHunterSignal && bestScore > baselineScore) {
|
|
139
|
+
return {
|
|
140
|
+
...best,
|
|
141
|
+
domain: normalizeDomain(best.domain)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
42
146
|
function isBlacklistedDomain(value) {
|
|
43
147
|
const normalized = normalizeDomain(value);
|
|
44
148
|
if (!normalized) {
|
|
@@ -109,6 +213,20 @@ export function chooseBestDomain(candidates) {
|
|
|
109
213
|
const nonNullCandidates = candidates.filter((candidate) => normalizeDomain(candidate.domain) !== null);
|
|
110
214
|
const linkedinDomain = normalizeDomain(candidates[0].linkedinDomain);
|
|
111
215
|
const linkedinWebsite = normalizeDomain(candidates[0].linkedinWebsite);
|
|
216
|
+
const betterThanLinkedinDomain = linkedinDomain
|
|
217
|
+
? chooseBetterCompanyMatchCandidate(nonNullCandidates, linkedinDomain)
|
|
218
|
+
: null;
|
|
219
|
+
const betterThanLinkedinWebsite = !linkedinDomain && linkedinWebsite
|
|
220
|
+
? chooseBetterCompanyMatchCandidate(nonNullCandidates, linkedinWebsite)
|
|
221
|
+
: null;
|
|
222
|
+
if (betterThanLinkedinDomain) {
|
|
223
|
+
return {
|
|
224
|
+
companyKey,
|
|
225
|
+
selected: betterThanLinkedinDomain,
|
|
226
|
+
reason: "better-company-match",
|
|
227
|
+
candidates
|
|
228
|
+
};
|
|
229
|
+
}
|
|
112
230
|
if (linkedinDomain && !isBlacklistedDomain(linkedinDomain)) {
|
|
113
231
|
const selectedCandidate = nonNullCandidates.find((candidate) => normalizeDomain(candidate.domain) === linkedinDomain) ?? {
|
|
114
232
|
...candidates[0],
|
|
@@ -128,6 +246,14 @@ export function chooseBestDomain(candidates) {
|
|
|
128
246
|
candidates
|
|
129
247
|
};
|
|
130
248
|
}
|
|
249
|
+
if (betterThanLinkedinWebsite) {
|
|
250
|
+
return {
|
|
251
|
+
companyKey,
|
|
252
|
+
selected: betterThanLinkedinWebsite,
|
|
253
|
+
reason: "better-company-match",
|
|
254
|
+
candidates
|
|
255
|
+
};
|
|
256
|
+
}
|
|
131
257
|
if (linkedinWebsite && !isBlacklistedDomain(linkedinWebsite)) {
|
|
132
258
|
const websiteRoot = rootDomain(linkedinWebsite);
|
|
133
259
|
const selectedCandidate = nonNullCandidates.find((candidate) => rootDomain(candidate.domain) === websiteRoot) ?? {
|
|
@@ -234,6 +360,9 @@ export function compareDomainSelectionStrategies(candidates) {
|
|
|
234
360
|
if (improved.reason === "linkedin-domain" || improved.reason === "linkedin-website") {
|
|
235
361
|
flags.push("preferred-linkedin");
|
|
236
362
|
}
|
|
363
|
+
if (improved.reason === "better-company-match") {
|
|
364
|
+
flags.push("preferred-hunter-company-match");
|
|
365
|
+
}
|
|
237
366
|
for (const flag of flags) {
|
|
238
367
|
changeFlags[flag] = (changeFlags[flag] ?? 0) + 1;
|
|
239
368
|
}
|
|
@@ -310,6 +439,9 @@ export function auditDomainDecisions(decisions) {
|
|
|
310
439
|
if (decision.reason === "fallback-first-non-null") {
|
|
311
440
|
flags.push("fallback-selected");
|
|
312
441
|
}
|
|
442
|
+
if (decision.reason === "better-company-match") {
|
|
443
|
+
flags.push("better-company-match-selected");
|
|
444
|
+
}
|
|
313
445
|
if (decision.reason === "highest-hunter-count") {
|
|
314
446
|
flags.push("hunter-selected");
|
|
315
447
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { load } from "cheerio";
|
|
2
2
|
import { BigQuery } from "@google-cloud/bigquery";
|
|
3
|
+
import { DEFAULT_LINKEDIN_SCRAPER_USER_AGENT } from "./linkedin-session-contracts.js";
|
|
3
4
|
const DEFAULT_LINKEDIN_BASE_URL = "https://www.linkedin.com";
|
|
4
|
-
const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
|
|
5
5
|
const DEFAULT_RAPIDAPI_LINKEDIN_COMPANY_HOST = "web-scraping-api2.p.rapidapi.com";
|
|
6
6
|
const DEFAULT_RAPIDAPI_LINKEDIN_COMPANY_ENDPOINT = "https://web-scraping-api2.p.rapidapi.com/get-company-by-linkedinurl";
|
|
7
7
|
const DEFAULT_BIGQUERY_PROJECT_ID = process.env.BQ_PROJECT_ID ?? process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCLOUD_PROJECT ?? "icpidentifier";
|
|
@@ -429,7 +429,7 @@ export async function fetchLinkedInCompanyBackfillCandidates(bigQuery, clientId,
|
|
|
429
429
|
async function fetchHtml(url) {
|
|
430
430
|
const response = await fetch(url, {
|
|
431
431
|
headers: {
|
|
432
|
-
"User-Agent":
|
|
432
|
+
"User-Agent": DEFAULT_LINKEDIN_SCRAPER_USER_AGENT
|
|
433
433
|
}
|
|
434
434
|
});
|
|
435
435
|
return {
|
|
@@ -446,7 +446,7 @@ export async function scrapeLinkedInCompany(candidate) {
|
|
|
446
446
|
headers: {
|
|
447
447
|
"x-rapidapi-key": rapidApiConfig.apiKey,
|
|
448
448
|
"x-rapidapi-host": rapidApiConfig.host,
|
|
449
|
-
"User-Agent":
|
|
449
|
+
"User-Agent": DEFAULT_LINKEDIN_SCRAPER_USER_AGENT
|
|
450
450
|
}
|
|
451
451
|
});
|
|
452
452
|
const text = await response.text();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { load } from "cheerio";
|
|
2
|
+
import { DEFAULT_LINKEDIN_SCRAPER_USER_AGENT } from "./linkedin-session-contracts.js";
|
|
2
3
|
const DEFAULT_LINKEDIN_BASE_URL = "https://www.linkedin.com";
|
|
3
|
-
const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
|
|
4
4
|
function normalizeWhitespace(value) {
|
|
5
5
|
return (value ?? "").replace(/\s+/g, " ").trim();
|
|
6
6
|
}
|
|
@@ -525,7 +525,7 @@ export function createLinkedInHtmlFetcher(fetchImpl = fetch) {
|
|
|
525
525
|
return async (url) => {
|
|
526
526
|
const response = await fetchImpl(url, {
|
|
527
527
|
headers: {
|
|
528
|
-
"User-Agent":
|
|
528
|
+
"User-Agent": DEFAULT_LINKEDIN_SCRAPER_USER_AGENT,
|
|
529
529
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
530
530
|
},
|
|
531
531
|
redirect: "follow"
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export * from "./vendor/salesprompter-shared/linkedin-session.js";
|
|
2
|
+
export { DEFAULT_LINKEDIN_USER_AGENT as DEFAULT_LINKEDIN_SESSION_USER_AGENT } from "./vendor/salesprompter-shared/linkedin-session.js";
|
|
3
|
+
export const DEFAULT_LINKEDIN_SCRAPER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
|
package/dist/linkedin-session.js
CHANGED
|
@@ -3,10 +3,10 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { createClient } from "@supabase/supabase-js";
|
|
6
|
+
import { DEFAULT_LINKEDIN_SESSION_USER_AGENT, formatLiAtCookieHeader } from "./linkedin-session-contracts.js";
|
|
6
7
|
const COOKIE_VAULT_KEY_CONTEXT = "salesprompter:linkedin-session-cookie:v1";
|
|
7
8
|
const AES_256_KEY_BYTES = 32;
|
|
8
9
|
const REQUEST_TIMEOUT_MS = 20_000;
|
|
9
|
-
const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36";
|
|
10
10
|
const MAX_COOKIE_VALIDATION_ATTEMPTS = 8;
|
|
11
11
|
const COOKIE_VALIDATION_TIMEOUT_MS = 75_000;
|
|
12
12
|
const COOKIE_PROBE_TIMEOUT_MS = 8_000;
|
|
@@ -68,7 +68,7 @@ function parseDotEnvFile(filePath) {
|
|
|
68
68
|
envFileCache.set(filePath, parsed);
|
|
69
69
|
return parsed;
|
|
70
70
|
}
|
|
71
|
-
function resolveConfiguredEnvValue(env, key) {
|
|
71
|
+
export function resolveConfiguredEnvValue(env, key) {
|
|
72
72
|
const directValue = env[key]?.trim() || "";
|
|
73
73
|
if (directValue.length > 0) {
|
|
74
74
|
return directValue;
|
|
@@ -230,6 +230,7 @@ export async function recordLinkedInSessionCookieAudit(supabase, input) {
|
|
|
230
230
|
last_sales_navigator_status: input.salesNavigatorStatus,
|
|
231
231
|
last_recruiter_status: input.recruiterStatus,
|
|
232
232
|
last_product_class: input.productClass,
|
|
233
|
+
last_ingested_source: input.source,
|
|
233
234
|
last_validation_error: input.validationError ?? null,
|
|
234
235
|
last_validation_details: input.details ?? {}
|
|
235
236
|
})
|
|
@@ -270,6 +271,7 @@ async function listLinkedInSessionCookieCandidates(supabase, options) {
|
|
|
270
271
|
"session_cookie_auth_tag",
|
|
271
272
|
"last_user_email",
|
|
272
273
|
"last_user_handle",
|
|
274
|
+
"last_ingested_source",
|
|
273
275
|
"is_active",
|
|
274
276
|
"inactive_reason",
|
|
275
277
|
"last_validation_at",
|
|
@@ -338,6 +340,7 @@ async function buildClaimedLinkedInSessionCookieFromRow(supabase, row, source, e
|
|
|
338
340
|
sessionCookie,
|
|
339
341
|
userEmail: claimedIdentity.userEmail,
|
|
340
342
|
userHandle: claimedIdentity.userHandle,
|
|
343
|
+
lastIngestedSource: row.last_ingested_source ?? null,
|
|
341
344
|
claimStrategy: options?.claimStrategy,
|
|
342
345
|
previousInactiveReason: row.inactive_reason ?? null
|
|
343
346
|
};
|
|
@@ -513,10 +516,6 @@ function extractTitle(body) {
|
|
|
513
516
|
const match = body.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
514
517
|
return match ? match[1].trim().replace(/\s+/g, " ") : null;
|
|
515
518
|
}
|
|
516
|
-
function normalizeLiAtCookie(sessionCookie) {
|
|
517
|
-
const trimmed = sessionCookie.trim();
|
|
518
|
-
return trimmed.startsWith("li_at=") ? trimmed.slice("li_at=".length) : trimmed;
|
|
519
|
-
}
|
|
520
519
|
function includesAny(value, patterns) {
|
|
521
520
|
const normalized = value ?? "";
|
|
522
521
|
return patterns.some((pattern) => pattern.test(normalized));
|
|
@@ -558,7 +557,7 @@ function hasSearchEntitlementSignals(result) {
|
|
|
558
557
|
]));
|
|
559
558
|
}
|
|
560
559
|
export async function fetchHtmlWithSessionCookie(url, sessionCookie, options) {
|
|
561
|
-
const
|
|
560
|
+
const cookieHeader = formatLiAtCookieHeader(sessionCookie);
|
|
562
561
|
const redirectChain = [];
|
|
563
562
|
let currentUrl = url;
|
|
564
563
|
const seenUrls = new Set([url]);
|
|
@@ -571,8 +570,8 @@ export async function fetchHtmlWithSessionCookie(url, sessionCookie, options) {
|
|
|
571
570
|
redirect: "manual",
|
|
572
571
|
signal: controller.signal,
|
|
573
572
|
headers: {
|
|
574
|
-
Cookie:
|
|
575
|
-
"User-Agent": options?.userAgent ??
|
|
573
|
+
Cookie: cookieHeader ?? "",
|
|
574
|
+
"User-Agent": options?.userAgent ?? DEFAULT_LINKEDIN_SESSION_USER_AGENT,
|
|
576
575
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
577
576
|
"Accept-Language": "en-US,en;q=0.9",
|
|
578
577
|
"Cache-Control": "no-cache",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const linkedInCapturedSessionSchema = z.object({
|
|
3
|
+
sessionCookie: z.string().min(1),
|
|
4
|
+
csrfToken: z.string().min(1),
|
|
5
|
+
linkedInIdentity: z.string().min(1),
|
|
6
|
+
userAgent: z.string().min(1),
|
|
7
|
+
requestId: z.string().min(1).nullable().optional(),
|
|
8
|
+
timestamp: z.string().min(1).nullable().optional(),
|
|
9
|
+
extractedFrom: z.string().min(1).nullable().optional(),
|
|
10
|
+
});
|
|
11
|
+
export const extensionSessionSyncPayloadSchema = z.object({
|
|
12
|
+
sessionCookie: z.string().min(1),
|
|
13
|
+
csrfToken: z.string().min(1),
|
|
14
|
+
linkedInIdentity: z.string().min(1),
|
|
15
|
+
userAgent: z.string().min(1),
|
|
16
|
+
requestId: z.string().min(1).nullable().optional(),
|
|
17
|
+
userEmail: z.string().email().nullable().optional(),
|
|
18
|
+
userFullName: z.string().min(1).nullable().optional(),
|
|
19
|
+
userHandle: z.string().min(1).nullable().optional(),
|
|
20
|
+
});
|
|
21
|
+
export const extensionSessionSyncSuccessSchema = z.object({
|
|
22
|
+
success: z.literal(true),
|
|
23
|
+
result: z.unknown().nullable().optional(),
|
|
24
|
+
persisted: z.boolean().nullable().optional(),
|
|
25
|
+
storageReady: z.boolean().nullable().optional(),
|
|
26
|
+
});
|
|
27
|
+
export const extensionSessionSyncErrorSchema = z.object({
|
|
28
|
+
error: z.string().min(1),
|
|
29
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const DEFAULT_LINKEDIN_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';
|
|
2
|
+
function normalizeWhitespace(value) {
|
|
3
|
+
return (value ?? '').replace(/\s+/g, ' ').trim();
|
|
4
|
+
}
|
|
5
|
+
export function extractLiAtCookieValue(sessionCookie) {
|
|
6
|
+
const trimmed = normalizeWhitespace(sessionCookie);
|
|
7
|
+
if (!trimmed) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const liAtMatch = trimmed.match(/(?:^|[;\s])li_at="?([^";]+)"?/i);
|
|
11
|
+
if (liAtMatch?.[1]) {
|
|
12
|
+
return liAtMatch[1];
|
|
13
|
+
}
|
|
14
|
+
return trimmed.startsWith('li_at=') ? trimmed.slice('li_at='.length) : trimmed;
|
|
15
|
+
}
|
|
16
|
+
export function normalizeLiAtCookie(sessionCookie) {
|
|
17
|
+
return extractLiAtCookieValue(sessionCookie);
|
|
18
|
+
}
|
|
19
|
+
export function formatLiAtCookieHeader(sessionCookie) {
|
|
20
|
+
const normalized = normalizeLiAtCookie(sessionCookie);
|
|
21
|
+
return normalized ? `li_at=${normalized}` : null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const DEFAULT_PHANTOMBUSTER_API_BASE = 'https://api.phantombuster.com/api/v2';
|
|
3
|
+
export const DEFAULT_LINKEDIN_COMPANY_SCRAPER_AGENT_ID = '3415615142546934';
|
|
4
|
+
export const LINKEDIN_COMPANY_SCRAPER_COLUMN_NAME = 'Url';
|
|
5
|
+
export const linkedInCompanyScraperLaunchOptionsSchema = z.object({
|
|
6
|
+
spreadsheetUrl: z.string().url(),
|
|
7
|
+
columnName: z.string().min(1).nullable().optional(),
|
|
8
|
+
webhookUrl: z.string().url().nullable().optional(),
|
|
9
|
+
sessionCookie: z.string().min(1).nullable().optional(),
|
|
10
|
+
userAgent: z.string().min(1).nullable().optional(),
|
|
11
|
+
companiesPerLaunch: z.number().int().positive().nullable().optional(),
|
|
12
|
+
delayBetween: z.number().nonnegative().nullable().optional(),
|
|
13
|
+
});
|
|
14
|
+
export const phantombusterLaunchResponseSchema = z.object({
|
|
15
|
+
containerId: z.string().min(1),
|
|
16
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const linkedInSessionSelectionSourceSchema = z.enum([
|
|
3
|
+
'cli_linkedin_company_backfill',
|
|
4
|
+
'cli_linkedin_company_backfill_preflight',
|
|
5
|
+
'cli_linkedin_company_backfill_invalid_session',
|
|
6
|
+
'cli_linkedin_company_backfill_decrypt_failure',
|
|
7
|
+
'extension_user_profile',
|
|
8
|
+
'extension_user_profile_li_at_fallback',
|
|
9
|
+
'operator_env_seed',
|
|
10
|
+
'pipedream_raw_manual_seed',
|
|
11
|
+
]);
|
|
12
|
+
export const linkedInSessionInactiveReasonSchema = z.enum([
|
|
13
|
+
'linkedin_session_invalid',
|
|
14
|
+
'decrypt_failed',
|
|
15
|
+
'excluded_linkedin_session_identity',
|
|
16
|
+
'quarantined_browser_unverified',
|
|
17
|
+
]);
|
package/package.json
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "salesprompter-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"description": "Sales workflow CLI for guided lead generation, enrichment, scoring, and sync.",
|
|
5
|
+
"author": "Daniel Sinewe <hello@danielsinewe.com>",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
8
|
"salesprompter": "dist/cli.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"dist",
|
|
11
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
"CONTRIBUTING.md",
|
|
14
|
+
"CODE_OF_CONDUCT.md",
|
|
15
|
+
"SECURITY.md"
|
|
12
16
|
],
|
|
13
17
|
"scripts": {
|
|
14
18
|
"build": "tsc -p tsconfig.json",
|
|
19
|
+
"benchmark:linkedin:direct": "node ./scripts/benchmark-linkedin-direct-strategies.mjs",
|
|
15
20
|
"build:docs:site": "node ./scripts/build-docs-site.mjs",
|
|
16
21
|
"check": "tsc --noEmit -p tsconfig.json",
|
|
22
|
+
"supabase:migrations:check": "node ./scripts/check-app-supabase-migrations.mjs",
|
|
23
|
+
"supabase:migrations:sync": "node ./scripts/sync-app-supabase-migrations.mjs",
|
|
24
|
+
"shared:sync": "node ./scripts/sync-shared.mjs",
|
|
17
25
|
"docs:dev": "mint dev",
|
|
18
26
|
"docs:broken-links": "mint broken-links",
|
|
19
27
|
"docs:a11y": "mint a11y",
|
|
@@ -42,12 +50,17 @@
|
|
|
42
50
|
"tooling"
|
|
43
51
|
],
|
|
44
52
|
"homepage": "https://salesprompter.ai/docs",
|
|
53
|
+
"funding": {
|
|
54
|
+
"type": "other",
|
|
55
|
+
"url": "https://salesprompter.ai"
|
|
56
|
+
},
|
|
45
57
|
"repository": {
|
|
46
58
|
"type": "git",
|
|
47
59
|
"url": "git+https://github.com/danielsinewe/salesprompter-cli.git"
|
|
48
60
|
},
|
|
49
61
|
"bugs": {
|
|
50
|
-
"url": "https://github.com/danielsinewe/salesprompter-cli/issues"
|
|
62
|
+
"url": "https://github.com/danielsinewe/salesprompter-cli/issues",
|
|
63
|
+
"email": "hello@danielsinewe.com"
|
|
51
64
|
},
|
|
52
65
|
"license": "MIT",
|
|
53
66
|
"dependencies": {
|
|
@@ -64,7 +77,7 @@
|
|
|
64
77
|
"@types/node": "^24.3.0",
|
|
65
78
|
"gray-matter": "^4.0.3",
|
|
66
79
|
"marked": "^16.3.0",
|
|
67
|
-
"mint": "^4.2.
|
|
80
|
+
"mint": "^4.2.216",
|
|
68
81
|
"typescript": "^5.9.2"
|
|
69
82
|
}
|
|
70
83
|
}
|