salesprompter-cli 0.1.22 → 0.1.23
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 +6 -0
- package/dist/cli.js +379 -0
- package/dist/deel-outreach.js +454 -0
- package/dist/deel-salesnav.js +368 -0
- package/dist/direct-path.js +6 -5
- package/dist/domain.js +3 -0
- package/dist/instantly.js +2 -1
- package/dist/leadlists-funnel.js +30 -13
- package/package.json +10 -17
package/README.md
CHANGED
|
@@ -33,6 +33,12 @@ salesprompter auth:whoami
|
|
|
33
33
|
# Product/category to leads workflow
|
|
34
34
|
salesprompter salesnav:from-product-category --input "https://www.linkedin.com/company/example/"
|
|
35
35
|
|
|
36
|
+
# Build Instantly-ready Deel outreach batches with German vs English splits
|
|
37
|
+
salesprompter leadlists:deel-outreach:bq --market global --out-dir ./data/deel-outreach
|
|
38
|
+
|
|
39
|
+
# Export the Supabase Sales Navigator Deel corpus into German vs English backlog files
|
|
40
|
+
salesprompter salesnav:deel-locale-export --org-id "<org-id>" --out-dir ./data/deel-salesnav
|
|
41
|
+
|
|
36
42
|
# Run a Sales Navigator crawl from a query URL
|
|
37
43
|
salesprompter salesnav:crawl --query-url "https://www.linkedin.com/sales/search/people?query=..."
|
|
38
44
|
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,8 @@ import { clearAuthSession, loginWithBrowserConnect, loginWithDeviceFlow, loginWi
|
|
|
14
14
|
import { buildBigQueryLeadLookupSql, executeBigQuerySql, normalizeBigQueryLeadRows, runBigQueryQuery, runBigQueryRows } from "./bigquery.js";
|
|
15
15
|
import { AccountProfileSchema, EnrichedLeadSchema, IcpSchema, LeadSchema, ScoredLeadSchema, SyncTargetSchema } from "./domain.js";
|
|
16
16
|
import { auditDomainDecisions, buildDomainfinderBacklogQueries, buildDomainfinderCandidatesSql, buildDomainfinderInputSql, buildDomainfinderWritebackSql, buildExistingDomainRepairSql, buildExistingDomainAuditQueries, compareDomainSelectionStrategies, selectBestDomains } from "./domainfinder.js";
|
|
17
|
+
import { buildDeelSalesNavCsvHeader, buildDeelSalesNavCsvLines, isDeelRelevantSalesNavTitle, normalizeDeelSalesNavRow } from "./deel-salesnav.js";
|
|
18
|
+
import { buildDeelOutreachExportSql, buildDeelOutreachPack, normalizeDeelOutreachRows } from "./deel-outreach.js";
|
|
17
19
|
import { buildDirectPathLeadExportSql, normalizeDirectPathRows, segmentDirectPathRows } from "./direct-path.js";
|
|
18
20
|
import { AccountLeadProvider, DryRunSyncProvider, HeuristicCompanyProvider, HeuristicEnrichmentProvider, HeuristicPeopleSearchProvider, HeuristicScoringProvider, RoutedSyncProvider } from "./engine.js";
|
|
19
21
|
import { analyzeHistoricalQueries } from "./historical-queries.js";
|
|
@@ -359,6 +361,24 @@ function slugify(value) {
|
|
|
359
361
|
.replace(/^-+|-+$/g, "")
|
|
360
362
|
.replace(/-{2,}/g, "-");
|
|
361
363
|
}
|
|
364
|
+
function resolveSalesNavigatorSupabaseConfig(env = process.env) {
|
|
365
|
+
const supabaseUrl = env.SALESPROMPTER_SUPABASE_URL?.trim() || env.NEXT_PUBLIC_SUPABASE_URL?.trim() || "";
|
|
366
|
+
const supabaseServiceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY?.trim() || "";
|
|
367
|
+
const missing = [];
|
|
368
|
+
if (supabaseUrl.length === 0) {
|
|
369
|
+
missing.push("SALESPROMPTER_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_URL");
|
|
370
|
+
}
|
|
371
|
+
if (supabaseServiceRoleKey.length === 0) {
|
|
372
|
+
missing.push("SUPABASE_SERVICE_ROLE_KEY");
|
|
373
|
+
}
|
|
374
|
+
if (missing.length > 0) {
|
|
375
|
+
throw new Error(`Missing required environment variables for Sales Navigator Supabase export: ${missing.join(", ")}`);
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
supabaseUrl,
|
|
379
|
+
supabaseServiceRoleKey
|
|
380
|
+
};
|
|
381
|
+
}
|
|
362
382
|
function normalizeDomainInput(value) {
|
|
363
383
|
return value
|
|
364
384
|
.trim()
|
|
@@ -441,6 +461,26 @@ function parseCompanyReference(value) {
|
|
|
441
461
|
})
|
|
442
462
|
};
|
|
443
463
|
}
|
|
464
|
+
function chunkArray(values, size) {
|
|
465
|
+
const chunks = [];
|
|
466
|
+
for (let index = 0; index < values.length; index += size) {
|
|
467
|
+
chunks.push(values.slice(index, index + size));
|
|
468
|
+
}
|
|
469
|
+
return chunks;
|
|
470
|
+
}
|
|
471
|
+
function extractSalesNavContactId(url) {
|
|
472
|
+
if (!url) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
const parsed = new URL(url);
|
|
477
|
+
const segments = parsed.pathname.split("/").filter((segment) => segment.length > 0);
|
|
478
|
+
return segments.length > 0 ? segments[segments.length - 1] : null;
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
444
484
|
function writeWizardLine(message = "") {
|
|
445
485
|
process.stdout.write(`${message}\n`);
|
|
446
486
|
}
|
|
@@ -3264,6 +3304,257 @@ program
|
|
|
3264
3304
|
}
|
|
3265
3305
|
printOutput(payload);
|
|
3266
3306
|
});
|
|
3307
|
+
program
|
|
3308
|
+
.command("salesnav:deel-locale-export")
|
|
3309
|
+
.description("Export the Supabase Sales Navigator Deel corpus into German-vs-English outreach backlog files.")
|
|
3310
|
+
.option("--org-id <id>", "Workspace org id. Defaults to the active CLI org.")
|
|
3311
|
+
.option("--limit <number>", "Maximum number of Supabase rows to process", "250000")
|
|
3312
|
+
.option("--page-size <number>", "Supabase page size per request", "1000")
|
|
3313
|
+
.option("--title-filter <mode>", "deel-hr|all", "deel-hr")
|
|
3314
|
+
.requiredOption("--out-dir <path>", "Output directory for summary and locale CSV files")
|
|
3315
|
+
.action(async (options) => {
|
|
3316
|
+
const limit = z.coerce.number().int().min(1).max(500000).parse(options.limit);
|
|
3317
|
+
const pageSize = z.coerce.number().int().min(1).max(1000).parse(options.pageSize);
|
|
3318
|
+
const titleFilter = z.enum(["deel-hr", "all"]).parse(options.titleFilter);
|
|
3319
|
+
let sessionOrgId = null;
|
|
3320
|
+
if (!shouldBypassAuth()) {
|
|
3321
|
+
const session = await requireAuthSession();
|
|
3322
|
+
sessionOrgId = session.user.orgId ?? null;
|
|
3323
|
+
}
|
|
3324
|
+
const orgId = resolveSalesNavigatorHistoricalBackfillOrgId({
|
|
3325
|
+
explicitOrgId: options.orgId,
|
|
3326
|
+
env: process.env,
|
|
3327
|
+
sessionOrgId
|
|
3328
|
+
});
|
|
3329
|
+
const config = resolveSalesNavigatorSupabaseConfig(process.env);
|
|
3330
|
+
const supabase = createClient(config.supabaseUrl, config.supabaseServiceRoleKey, {
|
|
3331
|
+
auth: { persistSession: false }
|
|
3332
|
+
});
|
|
3333
|
+
const countResponse = await supabase
|
|
3334
|
+
.from("linkedin_sales_nav_people")
|
|
3335
|
+
.select("id", { count: "exact", head: true })
|
|
3336
|
+
.eq("org_id", orgId);
|
|
3337
|
+
if (countResponse.error) {
|
|
3338
|
+
throw new Error(`Failed to count linkedin_sales_nav_people rows: ${countResponse.error.message}`);
|
|
3339
|
+
}
|
|
3340
|
+
const totalInOrg = countResponse.count ?? 0;
|
|
3341
|
+
const totalToProcess = Math.min(totalInOrg, limit);
|
|
3342
|
+
const baseSlug = `deel-salesnav-${slugify(orgId) || "workspace"}`;
|
|
3343
|
+
const deCsvPath = path.join(options.outDir, `${baseSlug}-de.csv`);
|
|
3344
|
+
const enCsvPath = path.join(options.outDir, `${baseSlug}-en.csv`);
|
|
3345
|
+
const summaryPath = path.join(options.outDir, `${baseSlug}-summary.json`);
|
|
3346
|
+
const samplesPath = path.join(options.outDir, `${baseSlug}-samples.json`);
|
|
3347
|
+
await mkdir(options.outDir, { recursive: true });
|
|
3348
|
+
await writeFile(deCsvPath, `${buildDeelSalesNavCsvHeader()}\n`, "utf8");
|
|
3349
|
+
await writeFile(enCsvPath, `${buildDeelSalesNavCsvHeader()}\n`, "utf8");
|
|
3350
|
+
const localeCounts = { de: 0, en: 0 };
|
|
3351
|
+
let titleMatchedCount = 0;
|
|
3352
|
+
let titleFilteredOutCount = 0;
|
|
3353
|
+
const fieldCounts = {
|
|
3354
|
+
firstName: 0,
|
|
3355
|
+
lastName: 0,
|
|
3356
|
+
fullName: 0,
|
|
3357
|
+
companyName: 0,
|
|
3358
|
+
companyNameCleaned: 0,
|
|
3359
|
+
preferredProfileUrl: 0,
|
|
3360
|
+
linkedinProfileUrl: 0,
|
|
3361
|
+
companyLinkedInHandle: 0,
|
|
3362
|
+
location: 0,
|
|
3363
|
+
companyLocation: 0,
|
|
3364
|
+
searchQuery: 0
|
|
3365
|
+
};
|
|
3366
|
+
const signalFieldCounts = {
|
|
3367
|
+
location: 0,
|
|
3368
|
+
companyLocation: 0,
|
|
3369
|
+
searchQuery: 0,
|
|
3370
|
+
none: 0
|
|
3371
|
+
};
|
|
3372
|
+
const titleCounts = new Map();
|
|
3373
|
+
const samples = {
|
|
3374
|
+
de: [],
|
|
3375
|
+
en: []
|
|
3376
|
+
};
|
|
3377
|
+
const selectFields = [
|
|
3378
|
+
"id",
|
|
3379
|
+
"org_id",
|
|
3380
|
+
"run_id",
|
|
3381
|
+
"sales_nav_profile_url",
|
|
3382
|
+
"linkedin_profile_url",
|
|
3383
|
+
"default_profile_url",
|
|
3384
|
+
"full_name",
|
|
3385
|
+
"first_name",
|
|
3386
|
+
"last_name",
|
|
3387
|
+
"company_name",
|
|
3388
|
+
"company_url",
|
|
3389
|
+
"regular_company_url",
|
|
3390
|
+
"title",
|
|
3391
|
+
"industry",
|
|
3392
|
+
"location",
|
|
3393
|
+
"company_location",
|
|
3394
|
+
"search_query",
|
|
3395
|
+
"scraped_at"
|
|
3396
|
+
].join(", ");
|
|
3397
|
+
let processed = 0;
|
|
3398
|
+
let lastSeenId = null;
|
|
3399
|
+
while (processed < totalToProcess) {
|
|
3400
|
+
let query = supabase
|
|
3401
|
+
.from("linkedin_sales_nav_people")
|
|
3402
|
+
.select(selectFields)
|
|
3403
|
+
.eq("org_id", orgId)
|
|
3404
|
+
.order("id", { ascending: true })
|
|
3405
|
+
.limit(Math.min(pageSize, totalToProcess - processed));
|
|
3406
|
+
if (lastSeenId) {
|
|
3407
|
+
query = query.gt("id", lastSeenId);
|
|
3408
|
+
}
|
|
3409
|
+
const response = await query;
|
|
3410
|
+
if (response.error) {
|
|
3411
|
+
throw new Error(`Failed to read linkedin_sales_nav_people rows after ${lastSeenId ?? "start"}: ${response.error.message}`);
|
|
3412
|
+
}
|
|
3413
|
+
const pageRows = (response.data ?? []);
|
|
3414
|
+
if (pageRows.length === 0) {
|
|
3415
|
+
break;
|
|
3416
|
+
}
|
|
3417
|
+
const relevantRows = pageRows.filter((row) => {
|
|
3418
|
+
if (titleFilter === "all") {
|
|
3419
|
+
titleMatchedCount += 1;
|
|
3420
|
+
return true;
|
|
3421
|
+
}
|
|
3422
|
+
const matches = isDeelRelevantSalesNavTitle(row.title);
|
|
3423
|
+
if (matches) {
|
|
3424
|
+
titleMatchedCount += 1;
|
|
3425
|
+
return true;
|
|
3426
|
+
}
|
|
3427
|
+
titleFilteredOutCount += 1;
|
|
3428
|
+
return false;
|
|
3429
|
+
});
|
|
3430
|
+
const preparedRows = relevantRows.map((row) => normalizeDeelSalesNavRow(row));
|
|
3431
|
+
const deRows = preparedRows.filter((row) => row.language === "de");
|
|
3432
|
+
const enRows = preparedRows.filter((row) => row.language === "en");
|
|
3433
|
+
if (deRows.length > 0) {
|
|
3434
|
+
await appendFile(deCsvPath, `${buildDeelSalesNavCsvLines(deRows)}\n`, "utf8");
|
|
3435
|
+
}
|
|
3436
|
+
if (enRows.length > 0) {
|
|
3437
|
+
await appendFile(enCsvPath, `${buildDeelSalesNavCsvLines(enRows)}\n`, "utf8");
|
|
3438
|
+
}
|
|
3439
|
+
for (const row of preparedRows) {
|
|
3440
|
+
localeCounts[row.language] += 1;
|
|
3441
|
+
if (row.signalFields.length === 0) {
|
|
3442
|
+
signalFieldCounts.none += 1;
|
|
3443
|
+
}
|
|
3444
|
+
else {
|
|
3445
|
+
for (const field of row.signalFields) {
|
|
3446
|
+
if (field === "location") {
|
|
3447
|
+
signalFieldCounts.location += 1;
|
|
3448
|
+
}
|
|
3449
|
+
else if (field === "companyLocation") {
|
|
3450
|
+
signalFieldCounts.companyLocation += 1;
|
|
3451
|
+
}
|
|
3452
|
+
else if (field === "searchQuery") {
|
|
3453
|
+
signalFieldCounts.searchQuery += 1;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
if (row.firstName)
|
|
3458
|
+
fieldCounts.firstName += 1;
|
|
3459
|
+
if (row.lastName)
|
|
3460
|
+
fieldCounts.lastName += 1;
|
|
3461
|
+
if (row.fullName)
|
|
3462
|
+
fieldCounts.fullName += 1;
|
|
3463
|
+
if (row.companyName)
|
|
3464
|
+
fieldCounts.companyName += 1;
|
|
3465
|
+
if (row.companyNameCleaned)
|
|
3466
|
+
fieldCounts.companyNameCleaned += 1;
|
|
3467
|
+
if (row.preferredProfileUrl)
|
|
3468
|
+
fieldCounts.preferredProfileUrl += 1;
|
|
3469
|
+
if (row.linkedinProfileUrl)
|
|
3470
|
+
fieldCounts.linkedinProfileUrl += 1;
|
|
3471
|
+
if (row.companyLinkedInHandle)
|
|
3472
|
+
fieldCounts.companyLinkedInHandle += 1;
|
|
3473
|
+
if (row.location)
|
|
3474
|
+
fieldCounts.location += 1;
|
|
3475
|
+
if (row.companyLocation)
|
|
3476
|
+
fieldCounts.companyLocation += 1;
|
|
3477
|
+
if (row.searchQuery)
|
|
3478
|
+
fieldCounts.searchQuery += 1;
|
|
3479
|
+
if (row.title) {
|
|
3480
|
+
titleCounts.set(row.title, (titleCounts.get(row.title) ?? 0) + 1);
|
|
3481
|
+
}
|
|
3482
|
+
if (samples[row.language].length < 25) {
|
|
3483
|
+
samples[row.language].push(row);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
processed += pageRows.length;
|
|
3487
|
+
lastSeenId = pageRows[pageRows.length - 1]?.id ?? lastSeenId;
|
|
3488
|
+
const completedPages = Math.ceil(processed / pageSize);
|
|
3489
|
+
if (completedPages === 1 || processed === totalToProcess || completedPages % 10 === 0) {
|
|
3490
|
+
writeProgress(`Processed ${processed}/${totalToProcess} Deel Sales Navigator rows for org ${orgId}; kept ${titleMatchedCount} after ${titleFilter} title filtering.`);
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
const keptTotal = localeCounts.de + localeCounts.en;
|
|
3494
|
+
const percentage = (count, base = keptTotal) => base > 0 ? Number(((count / base) * 100).toFixed(2)) : 0;
|
|
3495
|
+
const payload = {
|
|
3496
|
+
status: "ok",
|
|
3497
|
+
vendor: "deel",
|
|
3498
|
+
source: "salesnav-supabase",
|
|
3499
|
+
recommendedRouting: "separate-campaigns-by-language",
|
|
3500
|
+
orgId,
|
|
3501
|
+
totalInOrg,
|
|
3502
|
+
scanned: processed,
|
|
3503
|
+
titleFilter,
|
|
3504
|
+
keptAfterTitleFilter: keptTotal,
|
|
3505
|
+
titleMatchedCount,
|
|
3506
|
+
titleFilteredOutCount,
|
|
3507
|
+
truncatedByLimit: totalInOrg > processed,
|
|
3508
|
+
localeCounts,
|
|
3509
|
+
localePercentages: {
|
|
3510
|
+
de: percentage(localeCounts.de),
|
|
3511
|
+
en: percentage(localeCounts.en)
|
|
3512
|
+
},
|
|
3513
|
+
fieldCoverage: {
|
|
3514
|
+
firstName: { count: fieldCounts.firstName, percentage: percentage(fieldCounts.firstName) },
|
|
3515
|
+
lastName: { count: fieldCounts.lastName, percentage: percentage(fieldCounts.lastName) },
|
|
3516
|
+
fullName: { count: fieldCounts.fullName, percentage: percentage(fieldCounts.fullName) },
|
|
3517
|
+
companyName: { count: fieldCounts.companyName, percentage: percentage(fieldCounts.companyName) },
|
|
3518
|
+
companyNameCleaned: {
|
|
3519
|
+
count: fieldCounts.companyNameCleaned,
|
|
3520
|
+
percentage: percentage(fieldCounts.companyNameCleaned)
|
|
3521
|
+
},
|
|
3522
|
+
preferredProfileUrl: {
|
|
3523
|
+
count: fieldCounts.preferredProfileUrl,
|
|
3524
|
+
percentage: percentage(fieldCounts.preferredProfileUrl)
|
|
3525
|
+
},
|
|
3526
|
+
linkedinProfileUrl: {
|
|
3527
|
+
count: fieldCounts.linkedinProfileUrl,
|
|
3528
|
+
percentage: percentage(fieldCounts.linkedinProfileUrl)
|
|
3529
|
+
},
|
|
3530
|
+
companyLinkedInHandle: {
|
|
3531
|
+
count: fieldCounts.companyLinkedInHandle,
|
|
3532
|
+
percentage: percentage(fieldCounts.companyLinkedInHandle)
|
|
3533
|
+
},
|
|
3534
|
+
location: { count: fieldCounts.location, percentage: percentage(fieldCounts.location) },
|
|
3535
|
+
companyLocation: {
|
|
3536
|
+
count: fieldCounts.companyLocation,
|
|
3537
|
+
percentage: percentage(fieldCounts.companyLocation)
|
|
3538
|
+
},
|
|
3539
|
+
searchQuery: { count: fieldCounts.searchQuery, percentage: percentage(fieldCounts.searchQuery) }
|
|
3540
|
+
},
|
|
3541
|
+
signalFieldCounts,
|
|
3542
|
+
topTitles: [...titleCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20),
|
|
3543
|
+
campaignRecommendation: {
|
|
3544
|
+
de: "Only rows with clear DACH signals should enter the German Deel campaign.",
|
|
3545
|
+
en: "Route everything else to a separate English Deel campaign. English is the safer fallback for ambiguous locales."
|
|
3546
|
+
},
|
|
3547
|
+
files: {
|
|
3548
|
+
deCsv: deCsvPath,
|
|
3549
|
+
enCsv: enCsvPath,
|
|
3550
|
+
summary: summaryPath,
|
|
3551
|
+
samples: samplesPath
|
|
3552
|
+
}
|
|
3553
|
+
};
|
|
3554
|
+
await writeJsonFile(summaryPath, payload);
|
|
3555
|
+
await writeJsonFile(samplesPath, samples);
|
|
3556
|
+
printOutput(payload);
|
|
3557
|
+
});
|
|
3267
3558
|
program
|
|
3268
3559
|
.command("salesnav:crawl")
|
|
3269
3560
|
.description("Adaptively split broad LinkedIn Sales Navigator people searches into exportable slices and store every finished slice through Salesprompter.")
|
|
@@ -3733,6 +4024,94 @@ program
|
|
|
3733
4024
|
sqlOut: options.sqlOut ?? null
|
|
3734
4025
|
});
|
|
3735
4026
|
});
|
|
4027
|
+
program
|
|
4028
|
+
.command("leadlists:deel-outreach:bq")
|
|
4029
|
+
.description("Build Instantly-ready Deel outreach batches from leadPool_new with lead-list provenance, split into German vs English.")
|
|
4030
|
+
.option("--market <market>", "global|europe|dach", "global")
|
|
4031
|
+
.option("--limit <number>", "Max rows to export", "200000")
|
|
4032
|
+
.option("--min-email-score <number>", "Minimum email score to keep", "70")
|
|
4033
|
+
.requiredOption("--out-dir <path>", "Output directory for raw rows, packs, and locale batches")
|
|
4034
|
+
.option("--sql-out <path>", "Optional file path for the generated SQL")
|
|
4035
|
+
.option("--campaign-id <id>", "Fallback Instantly campaign id for all locales")
|
|
4036
|
+
.option("--campaign-id-de <id>", "Instantly campaign id for German/DACH leads")
|
|
4037
|
+
.option("--campaign-id-en <id>", "Instantly campaign id for English/non-DACH leads")
|
|
4038
|
+
.option("--apply", "Create leads in Instantly instead of export-only mode", false)
|
|
4039
|
+
.option("--allow-duplicates", "Do not skip emails already present in the Instantly campaign", false)
|
|
4040
|
+
.action(async (options) => {
|
|
4041
|
+
const market = z.enum(["global", "europe", "dach"]).parse(options.market);
|
|
4042
|
+
const limit = z.coerce.number().int().min(1).max(500000).parse(options.limit);
|
|
4043
|
+
const minEmailScore = z.coerce.number().int().min(0).max(100).parse(options.minEmailScore);
|
|
4044
|
+
const sql = buildDeelOutreachExportSql({ market, limit, minEmailScore });
|
|
4045
|
+
if (options.sqlOut) {
|
|
4046
|
+
await writeTextFile(options.sqlOut, `${sql}\n`);
|
|
4047
|
+
}
|
|
4048
|
+
const rows = await runBigQueryRows(sql, { maxRows: limit });
|
|
4049
|
+
const normalizedRows = normalizeDeelOutreachRows(rows);
|
|
4050
|
+
const pack = buildDeelOutreachPack(market, normalizedRows);
|
|
4051
|
+
const baseSlug = `deel-outreach-${market}`;
|
|
4052
|
+
const rawPath = path.join(options.outDir, `${baseSlug}-raw.json`);
|
|
4053
|
+
const packPath = path.join(options.outDir, `${baseSlug}-pack.json`);
|
|
4054
|
+
const allPath = path.join(options.outDir, `${baseSlug}-all.json`);
|
|
4055
|
+
const dePath = path.join(options.outDir, `${baseSlug}-de.json`);
|
|
4056
|
+
const enPath = path.join(options.outDir, `${baseSlug}-en.json`);
|
|
4057
|
+
await writeJsonFile(rawPath, normalizedRows);
|
|
4058
|
+
await writeJsonFile(packPath, pack);
|
|
4059
|
+
await writeJsonFile(allPath, [...pack.locales.de, ...pack.locales.en]);
|
|
4060
|
+
await writeJsonFile(dePath, pack.locales.de);
|
|
4061
|
+
await writeJsonFile(enPath, pack.locales.en);
|
|
4062
|
+
const syncResults = [];
|
|
4063
|
+
const routes = [
|
|
4064
|
+
{
|
|
4065
|
+
locale: "de",
|
|
4066
|
+
campaignId: options.campaignIdDe ?? options.campaignId,
|
|
4067
|
+
leads: pack.locales.de
|
|
4068
|
+
},
|
|
4069
|
+
{
|
|
4070
|
+
locale: "en",
|
|
4071
|
+
campaignId: options.campaignIdEn ?? options.campaignId,
|
|
4072
|
+
leads: pack.locales.en
|
|
4073
|
+
}
|
|
4074
|
+
];
|
|
4075
|
+
for (const route of routes) {
|
|
4076
|
+
if (!route.campaignId || route.leads.length === 0) {
|
|
4077
|
+
continue;
|
|
4078
|
+
}
|
|
4079
|
+
const result = await syncProvider.sync("instantly", route.leads, {
|
|
4080
|
+
apply: Boolean(options.apply),
|
|
4081
|
+
instantlyCampaignId: route.campaignId,
|
|
4082
|
+
allowDuplicates: Boolean(options.allowDuplicates)
|
|
4083
|
+
});
|
|
4084
|
+
syncResults.push({
|
|
4085
|
+
locale: route.locale,
|
|
4086
|
+
campaignId: route.campaignId,
|
|
4087
|
+
synced: result.synced,
|
|
4088
|
+
skipped: result.skipped ?? 0,
|
|
4089
|
+
dryRun: result.dryRun,
|
|
4090
|
+
provider: result.provider ?? "instantly"
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
printOutput({
|
|
4094
|
+
status: "ok",
|
|
4095
|
+
vendor: "deel",
|
|
4096
|
+
market,
|
|
4097
|
+
limit,
|
|
4098
|
+
minEmailScore,
|
|
4099
|
+
rowCount: normalizedRows.length,
|
|
4100
|
+
hitLimit: normalizedRows.length === limit,
|
|
4101
|
+
localeCounts: pack.summary.localeCounts,
|
|
4102
|
+
segmentCounts: pack.summary.segmentCounts,
|
|
4103
|
+
averageEmailScoreByLocale: pack.summary.averageEmailScoreByLocale,
|
|
4104
|
+
recommendedRouting: "separate-campaigns-by-language",
|
|
4105
|
+
outDir: options.outDir,
|
|
4106
|
+
raw: rawPath,
|
|
4107
|
+
pack: packPath,
|
|
4108
|
+
all: allPath,
|
|
4109
|
+
german: dePath,
|
|
4110
|
+
english: enPath,
|
|
4111
|
+
syncResults,
|
|
4112
|
+
sqlOut: options.sqlOut ?? null
|
|
4113
|
+
});
|
|
4114
|
+
});
|
|
3736
4115
|
program
|
|
3737
4116
|
.command("leadlists:funnel:bq")
|
|
3738
4117
|
.description("Build an upstream lead-list funnel report for a vendor/market.")
|