gtm-now 0.10.10 → 0.10.11
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/index.js +564 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -205,7 +205,8 @@ var init_config = __esm({
|
|
|
205
205
|
GTM_SMARTLEAD_API_KEY: "smartlead",
|
|
206
206
|
GTM_HARVEST_API_KEY: "harvest",
|
|
207
207
|
GTM_STORELEADS_API_KEY: "storeleads",
|
|
208
|
-
GTM_EXA_API_KEY: "exa"
|
|
208
|
+
GTM_EXA_API_KEY: "exa",
|
|
209
|
+
GTM_LEADMAGIC_API_KEY: "leadmagic"
|
|
209
210
|
};
|
|
210
211
|
ENV_WORKSPACE_MAP = {
|
|
211
212
|
GTM_PLUSVIBE_WORKSPACE_ID: "plusvibe"
|
|
@@ -8738,10 +8739,107 @@ var init_client25 = __esm({
|
|
|
8738
8739
|
});
|
|
8739
8740
|
|
|
8740
8741
|
// ../integrations/dist/leadmagic.js
|
|
8742
|
+
var LeadMagicClient;
|
|
8741
8743
|
var init_leadmagic2 = __esm({
|
|
8742
8744
|
"../integrations/dist/leadmagic.js"() {
|
|
8743
8745
|
"use strict";
|
|
8744
8746
|
init_base_client();
|
|
8747
|
+
LeadMagicClient = class extends BaseApiClient {
|
|
8748
|
+
constructor(config) {
|
|
8749
|
+
super({
|
|
8750
|
+
name: "LeadMagic",
|
|
8751
|
+
baseUrl: config.baseUrl ?? "https://api.leadmagic.io",
|
|
8752
|
+
headers: { "X-API-Key": config.apiKey },
|
|
8753
|
+
timeoutMs: config.timeoutMs
|
|
8754
|
+
});
|
|
8755
|
+
}
|
|
8756
|
+
// ─── People ──────────────────────────────────────────────────────────────────
|
|
8757
|
+
async findEmail(params) {
|
|
8758
|
+
return this.request("/v1/people/email-finder", {
|
|
8759
|
+
method: "POST",
|
|
8760
|
+
body: JSON.stringify(params)
|
|
8761
|
+
});
|
|
8762
|
+
}
|
|
8763
|
+
async validateEmail(params) {
|
|
8764
|
+
return this.request("/v1/people/email-validation", {
|
|
8765
|
+
method: "POST",
|
|
8766
|
+
body: JSON.stringify(params)
|
|
8767
|
+
});
|
|
8768
|
+
}
|
|
8769
|
+
async findMobile(params) {
|
|
8770
|
+
return this.request("/v1/people/mobile-finder", {
|
|
8771
|
+
method: "POST",
|
|
8772
|
+
body: JSON.stringify(params)
|
|
8773
|
+
});
|
|
8774
|
+
}
|
|
8775
|
+
async searchProfile(params) {
|
|
8776
|
+
return this.request("/v1/people/profile-search", {
|
|
8777
|
+
method: "POST",
|
|
8778
|
+
body: JSON.stringify(params)
|
|
8779
|
+
});
|
|
8780
|
+
}
|
|
8781
|
+
async findEmployees(params) {
|
|
8782
|
+
return this.request("/v1/people/employee-finder", {
|
|
8783
|
+
method: "POST",
|
|
8784
|
+
body: JSON.stringify(params)
|
|
8785
|
+
});
|
|
8786
|
+
}
|
|
8787
|
+
async findRole(params) {
|
|
8788
|
+
return this.request("/v1/people/role-finder", {
|
|
8789
|
+
method: "POST",
|
|
8790
|
+
body: JSON.stringify(params)
|
|
8791
|
+
});
|
|
8792
|
+
}
|
|
8793
|
+
async emailToProfile(params) {
|
|
8794
|
+
return this.request("/v1/people/b2b-profile", {
|
|
8795
|
+
method: "POST",
|
|
8796
|
+
body: JSON.stringify(params)
|
|
8797
|
+
});
|
|
8798
|
+
}
|
|
8799
|
+
async profileToEmail(params) {
|
|
8800
|
+
return this.request("/v1/people/b2b-profile-email", {
|
|
8801
|
+
method: "POST",
|
|
8802
|
+
body: JSON.stringify(params)
|
|
8803
|
+
});
|
|
8804
|
+
}
|
|
8805
|
+
async findPersonalEmail(params) {
|
|
8806
|
+
return this.request("/v1/people/personal-email-finder", {
|
|
8807
|
+
method: "POST",
|
|
8808
|
+
body: JSON.stringify(params)
|
|
8809
|
+
});
|
|
8810
|
+
}
|
|
8811
|
+
async detectJobChange(params) {
|
|
8812
|
+
return this.request("/v1/people/job-change-detector", {
|
|
8813
|
+
method: "POST",
|
|
8814
|
+
body: JSON.stringify(params)
|
|
8815
|
+
});
|
|
8816
|
+
}
|
|
8817
|
+
// ─── Companies ───────────────────────────────────────────────────────────────
|
|
8818
|
+
async searchCompany(params) {
|
|
8819
|
+
return this.request("/v1/companies/company-search", {
|
|
8820
|
+
method: "POST",
|
|
8821
|
+
body: JSON.stringify(params)
|
|
8822
|
+
});
|
|
8823
|
+
}
|
|
8824
|
+
async searchCompetitors(params) {
|
|
8825
|
+
return this.request("/v1/companies/competitors-search", {
|
|
8826
|
+
method: "POST",
|
|
8827
|
+
body: JSON.stringify(params)
|
|
8828
|
+
});
|
|
8829
|
+
}
|
|
8830
|
+
async getCompanyFunding(params) {
|
|
8831
|
+
return this.request("/v1/companies/company-funding", {
|
|
8832
|
+
method: "POST",
|
|
8833
|
+
body: JSON.stringify(params)
|
|
8834
|
+
});
|
|
8835
|
+
}
|
|
8836
|
+
async getTechnographics(params) {
|
|
8837
|
+
return this.request("/v1/companies/technographics", {
|
|
8838
|
+
method: "POST",
|
|
8839
|
+
body: JSON.stringify(params)
|
|
8840
|
+
});
|
|
8841
|
+
}
|
|
8842
|
+
};
|
|
8745
8843
|
}
|
|
8746
8844
|
});
|
|
8747
8845
|
|
|
@@ -10559,6 +10657,195 @@ var init_exa = __esm({
|
|
|
10559
10657
|
}
|
|
10560
10658
|
});
|
|
10561
10659
|
|
|
10660
|
+
// src/providers/adapters/leadmagic.ts
|
|
10661
|
+
function mapEmailQuality(status) {
|
|
10662
|
+
switch (status) {
|
|
10663
|
+
case "valid":
|
|
10664
|
+
return "95%";
|
|
10665
|
+
case "valid_catch_all":
|
|
10666
|
+
return "80%";
|
|
10667
|
+
case "catch_all":
|
|
10668
|
+
return "60%";
|
|
10669
|
+
default:
|
|
10670
|
+
return "50%";
|
|
10671
|
+
}
|
|
10672
|
+
}
|
|
10673
|
+
function mapVerificationStatus(status) {
|
|
10674
|
+
switch (status) {
|
|
10675
|
+
case "valid":
|
|
10676
|
+
return "deliverable";
|
|
10677
|
+
case "valid_catch_all":
|
|
10678
|
+
return "catch_all_safe";
|
|
10679
|
+
case "catch_all":
|
|
10680
|
+
return "catch_all_not_safe";
|
|
10681
|
+
case "invalid":
|
|
10682
|
+
return "undeliverable";
|
|
10683
|
+
default:
|
|
10684
|
+
return "unknown";
|
|
10685
|
+
}
|
|
10686
|
+
}
|
|
10687
|
+
var LeadMagicAdapter2;
|
|
10688
|
+
var init_leadmagic3 = __esm({
|
|
10689
|
+
"src/providers/adapters/leadmagic.ts"() {
|
|
10690
|
+
"use strict";
|
|
10691
|
+
init_dist();
|
|
10692
|
+
init_dist2();
|
|
10693
|
+
LeadMagicAdapter2 = class {
|
|
10694
|
+
name = "leadmagic";
|
|
10695
|
+
capabilities = [
|
|
10696
|
+
"enrich_email",
|
|
10697
|
+
"enrich_phone",
|
|
10698
|
+
"verify_email",
|
|
10699
|
+
"find_people"
|
|
10700
|
+
];
|
|
10701
|
+
notes = "Best for: email enrichment (name+domain), email validation (0.05 credits), mobile finder (needs LinkedIn URL, 5 credits), employee lists (0.05/employee). Also supports: profile search, company search, technographics, funding, competitors, job change detection. Pay-on-find pricing \u2014 free when no result.";
|
|
10702
|
+
client;
|
|
10703
|
+
constructor(apiKey) {
|
|
10704
|
+
this.client = new LeadMagicClient({ apiKey });
|
|
10705
|
+
}
|
|
10706
|
+
async checkCredentials() {
|
|
10707
|
+
try {
|
|
10708
|
+
await this.client.validateEmail({ email: "test@example.com" });
|
|
10709
|
+
return true;
|
|
10710
|
+
} catch (error) {
|
|
10711
|
+
logError(
|
|
10712
|
+
"leadmagic:checkCredentials",
|
|
10713
|
+
error instanceof Error ? error : new Error(String(error))
|
|
10714
|
+
);
|
|
10715
|
+
return false;
|
|
10716
|
+
}
|
|
10717
|
+
}
|
|
10718
|
+
async checkCredits() {
|
|
10719
|
+
return null;
|
|
10720
|
+
}
|
|
10721
|
+
// ─── EmailEnricher ──────────────────────────────────────
|
|
10722
|
+
async enrichEmail(contact) {
|
|
10723
|
+
try {
|
|
10724
|
+
if (contact.first_name && contact.last_name && (contact.company_domain || contact.company)) {
|
|
10725
|
+
const response = await this.client.findEmail({
|
|
10726
|
+
first_name: contact.first_name,
|
|
10727
|
+
last_name: contact.last_name,
|
|
10728
|
+
domain: contact.company_domain,
|
|
10729
|
+
company_name: !contact.company_domain ? contact.company : void 0
|
|
10730
|
+
});
|
|
10731
|
+
if (response.email && response.status !== "not_found") {
|
|
10732
|
+
return {
|
|
10733
|
+
email: response.email,
|
|
10734
|
+
quality: mapEmailQuality(response.status),
|
|
10735
|
+
provider: this.name,
|
|
10736
|
+
credits_consumed: response.credits_consumed
|
|
10737
|
+
};
|
|
10738
|
+
}
|
|
10739
|
+
}
|
|
10740
|
+
if (contact.linkedin_url) {
|
|
10741
|
+
const response = await this.client.profileToEmail({
|
|
10742
|
+
profile_url: contact.linkedin_url
|
|
10743
|
+
});
|
|
10744
|
+
if (response.email) {
|
|
10745
|
+
return {
|
|
10746
|
+
email: response.email,
|
|
10747
|
+
quality: "80%",
|
|
10748
|
+
// Profile-to-email doesn't return validation status
|
|
10749
|
+
provider: this.name,
|
|
10750
|
+
credits_consumed: response.credits_consumed
|
|
10751
|
+
};
|
|
10752
|
+
}
|
|
10753
|
+
}
|
|
10754
|
+
return null;
|
|
10755
|
+
} catch (error) {
|
|
10756
|
+
logError("leadmagic:enrichEmail", error instanceof Error ? error : new Error(String(error)));
|
|
10757
|
+
return null;
|
|
10758
|
+
}
|
|
10759
|
+
}
|
|
10760
|
+
// ─── PhoneEnricher ──────────────────────────────────────
|
|
10761
|
+
async enrichPhone(contact) {
|
|
10762
|
+
try {
|
|
10763
|
+
const response = await this.client.findMobile({
|
|
10764
|
+
profile_url: contact.linkedin_url,
|
|
10765
|
+
work_email: void 0,
|
|
10766
|
+
// Could pass email if available
|
|
10767
|
+
personal_email: void 0
|
|
10768
|
+
});
|
|
10769
|
+
if (!response.mobile_number) return null;
|
|
10770
|
+
return {
|
|
10771
|
+
phone: response.mobile_number,
|
|
10772
|
+
provider: this.name
|
|
10773
|
+
};
|
|
10774
|
+
} catch (error) {
|
|
10775
|
+
logError("leadmagic:enrichPhone", error instanceof Error ? error : new Error(String(error)));
|
|
10776
|
+
return null;
|
|
10777
|
+
}
|
|
10778
|
+
}
|
|
10779
|
+
// ─── EmailVerifier ──────────────────────────────────────
|
|
10780
|
+
async verifyEmail(email) {
|
|
10781
|
+
try {
|
|
10782
|
+
const response = await this.client.validateEmail({ email });
|
|
10783
|
+
return {
|
|
10784
|
+
email: response.email ?? email,
|
|
10785
|
+
valid: response.email_status === "valid" || response.email_status === "valid_catch_all",
|
|
10786
|
+
status: mapVerificationStatus(response.email_status),
|
|
10787
|
+
provider: this.name
|
|
10788
|
+
};
|
|
10789
|
+
} catch (error) {
|
|
10790
|
+
logError("leadmagic:verifyEmail", error instanceof Error ? error : new Error(String(error)));
|
|
10791
|
+
return {
|
|
10792
|
+
email,
|
|
10793
|
+
valid: false,
|
|
10794
|
+
status: "unknown",
|
|
10795
|
+
provider: this.name
|
|
10796
|
+
};
|
|
10797
|
+
}
|
|
10798
|
+
}
|
|
10799
|
+
// ─── PeopleFinder ───────────────────────────────────────
|
|
10800
|
+
async findPeople(query) {
|
|
10801
|
+
try {
|
|
10802
|
+
if (query.job_title && (query.company_domain || query.company_name)) {
|
|
10803
|
+
const response = await this.client.findRole({
|
|
10804
|
+
job_title: query.job_title,
|
|
10805
|
+
company_domain: query.company_domain,
|
|
10806
|
+
company_name: !query.company_domain ? query.company_name : void 0
|
|
10807
|
+
});
|
|
10808
|
+
if (response.first_name && response.last_name) {
|
|
10809
|
+
return [
|
|
10810
|
+
{
|
|
10811
|
+
first_name: response.first_name,
|
|
10812
|
+
last_name: response.last_name,
|
|
10813
|
+
job_title: response.job_title ?? void 0,
|
|
10814
|
+
company: response.company_name ?? void 0,
|
|
10815
|
+
company_domain: response.company_website ?? query.company_domain ?? void 0,
|
|
10816
|
+
linkedin_url: response.profile_url ?? void 0,
|
|
10817
|
+
sources: [this.name]
|
|
10818
|
+
}
|
|
10819
|
+
];
|
|
10820
|
+
}
|
|
10821
|
+
return [];
|
|
10822
|
+
}
|
|
10823
|
+
if (query.company_domain || query.company_name) {
|
|
10824
|
+
const response = await this.client.findEmployees({
|
|
10825
|
+
company_domain: query.company_domain,
|
|
10826
|
+
company_name: !query.company_domain ? query.company_name : void 0,
|
|
10827
|
+
limit: query.limit ?? 20
|
|
10828
|
+
});
|
|
10829
|
+
if (!response.data?.length) return [];
|
|
10830
|
+
return response.data.map((emp) => ({
|
|
10831
|
+
first_name: emp.first_name ?? "",
|
|
10832
|
+
last_name: emp.last_name ?? "",
|
|
10833
|
+
job_title: emp.title ?? void 0,
|
|
10834
|
+
company: emp.company_name ?? void 0,
|
|
10835
|
+
company_domain: emp.website ?? query.company_domain ?? void 0,
|
|
10836
|
+
sources: [this.name]
|
|
10837
|
+
}));
|
|
10838
|
+
}
|
|
10839
|
+
return [];
|
|
10840
|
+
} catch (error) {
|
|
10841
|
+
logError("leadmagic:findPeople", error instanceof Error ? error : new Error(String(error)));
|
|
10842
|
+
return [];
|
|
10843
|
+
}
|
|
10844
|
+
}
|
|
10845
|
+
};
|
|
10846
|
+
}
|
|
10847
|
+
});
|
|
10848
|
+
|
|
10562
10849
|
// src/providers/adapters/index.ts
|
|
10563
10850
|
var init_adapters = __esm({
|
|
10564
10851
|
"src/providers/adapters/index.ts"() {
|
|
@@ -10576,6 +10863,7 @@ var init_adapters = __esm({
|
|
|
10576
10863
|
init_storeleads();
|
|
10577
10864
|
init_harvest();
|
|
10578
10865
|
init_exa();
|
|
10866
|
+
init_leadmagic3();
|
|
10579
10867
|
}
|
|
10580
10868
|
});
|
|
10581
10869
|
|
|
@@ -10608,6 +10896,8 @@ function createAdapter(name, config) {
|
|
|
10608
10896
|
return config.api_key ? new HarvestApiAdapter(config.api_key) : null;
|
|
10609
10897
|
case "exa":
|
|
10610
10898
|
return config.api_key ? new ExaAdapter(config.api_key) : null;
|
|
10899
|
+
case "leadmagic":
|
|
10900
|
+
return config.api_key ? new LeadMagicAdapter2(config.api_key) : null;
|
|
10611
10901
|
default:
|
|
10612
10902
|
return null;
|
|
10613
10903
|
}
|
|
@@ -11154,7 +11444,20 @@ var init_extract = __esm({
|
|
|
11154
11444
|
},
|
|
11155
11445
|
profile: {
|
|
11156
11446
|
type: "string",
|
|
11157
|
-
description: "LinkedIn URL or publicIdentifier (required for linkedin_posts)"
|
|
11447
|
+
description: "LinkedIn URL or publicIdentifier (required for linkedin_posts, single profile)"
|
|
11448
|
+
},
|
|
11449
|
+
profiles: {
|
|
11450
|
+
type: "array",
|
|
11451
|
+
items: { type: "string" },
|
|
11452
|
+
description: "Batch mode: array of LinkedIn URLs or publicIdentifiers. Checks each for recent posts concurrently. Returns lightweight results (posted/not, count, last date). Use with posted_within to filter for recent activity."
|
|
11453
|
+
},
|
|
11454
|
+
csv_file: {
|
|
11455
|
+
type: "string",
|
|
11456
|
+
description: 'Path to a CSV file containing LinkedIn profile URLs. Reads the "LinkedIn Profile" or "linkedin_url" column automatically. Alternative to profiles[] for large lists.'
|
|
11457
|
+
},
|
|
11458
|
+
concurrency: {
|
|
11459
|
+
type: "number",
|
|
11460
|
+
description: "Max concurrent API requests for batch mode (default: 5, max: 20). Higher = faster but more likely to hit rate limits."
|
|
11158
11461
|
},
|
|
11159
11462
|
company: {
|
|
11160
11463
|
type: "string",
|
|
@@ -11193,6 +11496,15 @@ var init_extract = __esm({
|
|
|
11193
11496
|
type: "string",
|
|
11194
11497
|
enum: ["summary", "full"],
|
|
11195
11498
|
description: 'Response detail level. "summary" (default) returns counts and key fields only \u2014 truncates post text, omits individual engagement items. "full" returns complete data including all post content and engager lists.'
|
|
11499
|
+
},
|
|
11500
|
+
output: {
|
|
11501
|
+
type: "string",
|
|
11502
|
+
enum: ["file", "csv", "inline"],
|
|
11503
|
+
description: 'Output mode for batch results. "csv" writes a CSV file (default for batch). "file" writes JSONL. "inline" returns in response (only for small batches).'
|
|
11504
|
+
},
|
|
11505
|
+
review: {
|
|
11506
|
+
type: "boolean",
|
|
11507
|
+
description: "Create a viewer table with batch results for human review."
|
|
11196
11508
|
}
|
|
11197
11509
|
}
|
|
11198
11510
|
}
|
|
@@ -13017,6 +13329,7 @@ var extract_exports = {};
|
|
|
13017
13329
|
__export(extract_exports, {
|
|
13018
13330
|
handleExtract: () => handleExtract
|
|
13019
13331
|
});
|
|
13332
|
+
import * as fs2 from "fs";
|
|
13020
13333
|
function slimPost(post) {
|
|
13021
13334
|
const slim = {};
|
|
13022
13335
|
for (const [key, value] of Object.entries(post)) {
|
|
@@ -13097,6 +13410,11 @@ async function handleLinkedinExtract(type, args, ctx) {
|
|
|
13097
13410
|
};
|
|
13098
13411
|
}
|
|
13099
13412
|
case "linkedin_posts": {
|
|
13413
|
+
const profiles = args.profiles;
|
|
13414
|
+
const csvFile = args.csv_file;
|
|
13415
|
+
if (profiles?.length || csvFile) {
|
|
13416
|
+
return handleBatchLinkedinPosts(args, adapter);
|
|
13417
|
+
}
|
|
13100
13418
|
const result = await adapter.getProfilePosts({
|
|
13101
13419
|
profile: args.profile,
|
|
13102
13420
|
postedLimit: args.posted_within
|
|
@@ -13161,12 +13479,234 @@ async function handleLinkedinExtract(type, args, ctx) {
|
|
|
13161
13479
|
throw new Error(`Unknown extract type: ${type}`);
|
|
13162
13480
|
}
|
|
13163
13481
|
}
|
|
13164
|
-
|
|
13482
|
+
async function handleBatchLinkedinPosts(args, adapter) {
|
|
13483
|
+
const profilesArg = args.profiles;
|
|
13484
|
+
const csvFile = args.csv_file;
|
|
13485
|
+
const postedWithin = args.posted_within ?? "month";
|
|
13486
|
+
const concurrency = Math.min(
|
|
13487
|
+
args.concurrency ?? DEFAULT_BATCH_CONCURRENCY,
|
|
13488
|
+
MAX_BATCH_CONCURRENCY
|
|
13489
|
+
);
|
|
13490
|
+
const output = args.output;
|
|
13491
|
+
const shouldReview = args.review ?? false;
|
|
13492
|
+
let profiles;
|
|
13493
|
+
if (csvFile) {
|
|
13494
|
+
profiles = parseCsvProfiles(csvFile);
|
|
13495
|
+
} else {
|
|
13496
|
+
profiles = (profilesArg ?? []).map((url) => ({ linkedin_url: url }));
|
|
13497
|
+
}
|
|
13498
|
+
if (profiles.length === 0) {
|
|
13499
|
+
throw createGtmError("EXTRACTION_FAILED", "No LinkedIn profiles found in input.");
|
|
13500
|
+
}
|
|
13501
|
+
const results = new Array(profiles.length);
|
|
13502
|
+
let nextIndex = 0;
|
|
13503
|
+
let completed = 0;
|
|
13504
|
+
const deadline = Date.now() + BATCH_TIMEOUT_MS;
|
|
13505
|
+
async function worker() {
|
|
13506
|
+
while (nextIndex < profiles.length) {
|
|
13507
|
+
const i = nextIndex++;
|
|
13508
|
+
const entry = profiles[i];
|
|
13509
|
+
if (Date.now() > deadline) {
|
|
13510
|
+
results[i] = {
|
|
13511
|
+
linkedin_url: entry.linkedin_url,
|
|
13512
|
+
name: entry.name,
|
|
13513
|
+
company: entry.company,
|
|
13514
|
+
has_posts: false,
|
|
13515
|
+
post_count: 0,
|
|
13516
|
+
error: "timeout"
|
|
13517
|
+
};
|
|
13518
|
+
completed++;
|
|
13519
|
+
continue;
|
|
13520
|
+
}
|
|
13521
|
+
try {
|
|
13522
|
+
const result = await adapter.getProfilePosts({
|
|
13523
|
+
profile: entry.linkedin_url,
|
|
13524
|
+
postedLimit: postedWithin
|
|
13525
|
+
});
|
|
13526
|
+
const posts = result.elements;
|
|
13527
|
+
let lastPostDate;
|
|
13528
|
+
if (posts.length > 0) {
|
|
13529
|
+
const first = posts[0];
|
|
13530
|
+
lastPostDate = first.postedAt ?? first.postedDate ?? first.posted_at;
|
|
13531
|
+
}
|
|
13532
|
+
results[i] = {
|
|
13533
|
+
linkedin_url: entry.linkedin_url,
|
|
13534
|
+
name: entry.name,
|
|
13535
|
+
company: entry.company,
|
|
13536
|
+
has_posts: posts.length > 0,
|
|
13537
|
+
post_count: posts.length,
|
|
13538
|
+
last_post_date: lastPostDate
|
|
13539
|
+
};
|
|
13540
|
+
} catch (error) {
|
|
13541
|
+
logError("extract:batchLinkedinPosts", error instanceof Error ? error : new Error(String(error)), {
|
|
13542
|
+
profile: entry.linkedin_url
|
|
13543
|
+
});
|
|
13544
|
+
results[i] = {
|
|
13545
|
+
linkedin_url: entry.linkedin_url,
|
|
13546
|
+
name: entry.name,
|
|
13547
|
+
company: entry.company,
|
|
13548
|
+
has_posts: false,
|
|
13549
|
+
post_count: 0,
|
|
13550
|
+
error: error instanceof Error ? error.message : String(error)
|
|
13551
|
+
};
|
|
13552
|
+
}
|
|
13553
|
+
completed++;
|
|
13554
|
+
}
|
|
13555
|
+
}
|
|
13556
|
+
await Promise.all(
|
|
13557
|
+
Array.from({ length: Math.min(concurrency, profiles.length) }, () => worker())
|
|
13558
|
+
);
|
|
13559
|
+
const activeProfiles = results.filter((r) => r.has_posts);
|
|
13560
|
+
const errors = results.filter((r) => r.error);
|
|
13561
|
+
let tableId;
|
|
13562
|
+
let tableUrl;
|
|
13563
|
+
if (shouldReview) {
|
|
13564
|
+
const tableResult = await createReviewTable(
|
|
13565
|
+
`LinkedIn Activity Check (${postedWithin})`,
|
|
13566
|
+
results
|
|
13567
|
+
);
|
|
13568
|
+
if (tableResult) {
|
|
13569
|
+
tableId = tableResult.table_id;
|
|
13570
|
+
tableUrl = tableResult.url;
|
|
13571
|
+
}
|
|
13572
|
+
}
|
|
13573
|
+
const effectiveOutput = output ?? (profiles.length > 20 ? "csv" : "inline");
|
|
13574
|
+
if (effectiveOutput === "csv") {
|
|
13575
|
+
const filePath = writeResultsCsv(
|
|
13576
|
+
"linkedin_activity",
|
|
13577
|
+
results
|
|
13578
|
+
);
|
|
13579
|
+
return {
|
|
13580
|
+
type: "linkedin_posts_batch",
|
|
13581
|
+
provider: "harvest",
|
|
13582
|
+
total: profiles.length,
|
|
13583
|
+
active: activeProfiles.length,
|
|
13584
|
+
inactive: profiles.length - activeProfiles.length - errors.length,
|
|
13585
|
+
errors: errors.length,
|
|
13586
|
+
posted_within: postedWithin,
|
|
13587
|
+
output: "csv",
|
|
13588
|
+
file: filePath,
|
|
13589
|
+
preview: activeProfiles.slice(0, 5),
|
|
13590
|
+
...tableId && { table_id: tableId, url: tableUrl }
|
|
13591
|
+
};
|
|
13592
|
+
}
|
|
13593
|
+
if (effectiveOutput === "file") {
|
|
13594
|
+
const filePath = writeResultsFile(
|
|
13595
|
+
"linkedin_activity",
|
|
13596
|
+
results,
|
|
13597
|
+
{ query: { posted_within: postedWithin }, provider: "harvest", count: results.length, has_more: false }
|
|
13598
|
+
);
|
|
13599
|
+
return {
|
|
13600
|
+
type: "linkedin_posts_batch",
|
|
13601
|
+
provider: "harvest",
|
|
13602
|
+
total: profiles.length,
|
|
13603
|
+
active: activeProfiles.length,
|
|
13604
|
+
inactive: profiles.length - activeProfiles.length - errors.length,
|
|
13605
|
+
errors: errors.length,
|
|
13606
|
+
posted_within: postedWithin,
|
|
13607
|
+
output: "file",
|
|
13608
|
+
file: filePath,
|
|
13609
|
+
preview: activeProfiles.slice(0, 5),
|
|
13610
|
+
...tableId && { table_id: tableId, url: tableUrl }
|
|
13611
|
+
};
|
|
13612
|
+
}
|
|
13613
|
+
return {
|
|
13614
|
+
type: "linkedin_posts_batch",
|
|
13615
|
+
provider: "harvest",
|
|
13616
|
+
total: profiles.length,
|
|
13617
|
+
active: activeProfiles.length,
|
|
13618
|
+
inactive: profiles.length - activeProfiles.length - errors.length,
|
|
13619
|
+
errors: errors.length,
|
|
13620
|
+
posted_within: postedWithin,
|
|
13621
|
+
results,
|
|
13622
|
+
...tableId && { table_id: tableId, url: tableUrl }
|
|
13623
|
+
};
|
|
13624
|
+
}
|
|
13625
|
+
function parseCsvProfiles(filePath) {
|
|
13626
|
+
if (!fs2.existsSync(filePath)) {
|
|
13627
|
+
throw createGtmError("EXTRACTION_FAILED", `CSV file not found: ${filePath}`);
|
|
13628
|
+
}
|
|
13629
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
13630
|
+
const lines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
13631
|
+
if (lines.length < 2) {
|
|
13632
|
+
throw createGtmError("EXTRACTION_FAILED", "CSV file has no data rows.");
|
|
13633
|
+
}
|
|
13634
|
+
const headerLine = lines[0];
|
|
13635
|
+
const headers = parseCsvLine(headerLine).map((h) => h.toLowerCase().trim());
|
|
13636
|
+
const linkedinIdx = headers.findIndex((h) => LINKEDIN_URL_COLUMNS.includes(h));
|
|
13637
|
+
if (linkedinIdx === -1) {
|
|
13638
|
+
throw createGtmError(
|
|
13639
|
+
"EXTRACTION_FAILED",
|
|
13640
|
+
`No LinkedIn URL column found in CSV. Expected one of: ${LINKEDIN_URL_COLUMNS.join(", ")}. Found columns: ${headers.join(", ")}`
|
|
13641
|
+
);
|
|
13642
|
+
}
|
|
13643
|
+
const nameIdx = headers.findIndex((h) => NAME_COLUMNS.includes(h));
|
|
13644
|
+
const companyIdx = headers.findIndex((h) => COMPANY_COLUMNS.includes(h));
|
|
13645
|
+
const entries = [];
|
|
13646
|
+
for (let i = 1; i < lines.length; i++) {
|
|
13647
|
+
const fields = parseCsvLine(lines[i]);
|
|
13648
|
+
const url = fields[linkedinIdx]?.trim();
|
|
13649
|
+
if (!url || !url.includes("linkedin.com")) continue;
|
|
13650
|
+
entries.push({
|
|
13651
|
+
linkedin_url: url,
|
|
13652
|
+
name: nameIdx >= 0 ? fields[nameIdx]?.trim() : void 0,
|
|
13653
|
+
company: companyIdx >= 0 ? fields[companyIdx]?.trim() : void 0
|
|
13654
|
+
});
|
|
13655
|
+
}
|
|
13656
|
+
return entries;
|
|
13657
|
+
}
|
|
13658
|
+
function parseCsvLine(line) {
|
|
13659
|
+
const fields = [];
|
|
13660
|
+
let current = "";
|
|
13661
|
+
let inQuotes = false;
|
|
13662
|
+
for (let i = 0; i < line.length; i++) {
|
|
13663
|
+
const char = line[i];
|
|
13664
|
+
if (inQuotes) {
|
|
13665
|
+
if (char === '"') {
|
|
13666
|
+
if (i + 1 < line.length && line[i + 1] === '"') {
|
|
13667
|
+
current += '"';
|
|
13668
|
+
i++;
|
|
13669
|
+
} else {
|
|
13670
|
+
inQuotes = false;
|
|
13671
|
+
}
|
|
13672
|
+
} else {
|
|
13673
|
+
current += char;
|
|
13674
|
+
}
|
|
13675
|
+
} else if (char === '"') {
|
|
13676
|
+
inQuotes = true;
|
|
13677
|
+
} else if (char === ",") {
|
|
13678
|
+
fields.push(current);
|
|
13679
|
+
current = "";
|
|
13680
|
+
} else {
|
|
13681
|
+
current += char;
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
fields.push(current);
|
|
13685
|
+
return fields;
|
|
13686
|
+
}
|
|
13687
|
+
var POST_TEXT_LIMIT, DEFAULT_BATCH_CONCURRENCY, MAX_BATCH_CONCURRENCY, BATCH_TIMEOUT_MS, LINKEDIN_URL_COLUMNS, NAME_COLUMNS, COMPANY_COLUMNS;
|
|
13165
13688
|
var init_extract2 = __esm({
|
|
13166
13689
|
"src/handlers/extract.ts"() {
|
|
13167
13690
|
"use strict";
|
|
13691
|
+
init_dist();
|
|
13168
13692
|
init_errors();
|
|
13693
|
+
init_table_bridge();
|
|
13694
|
+
init_result_writer();
|
|
13169
13695
|
POST_TEXT_LIMIT = 200;
|
|
13696
|
+
DEFAULT_BATCH_CONCURRENCY = 5;
|
|
13697
|
+
MAX_BATCH_CONCURRENCY = 20;
|
|
13698
|
+
BATCH_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
13699
|
+
LINKEDIN_URL_COLUMNS = [
|
|
13700
|
+
"linkedin profile",
|
|
13701
|
+
"linkedin_profile",
|
|
13702
|
+
"linkedin url",
|
|
13703
|
+
"linkedin_url",
|
|
13704
|
+
"linkedin",
|
|
13705
|
+
"profile url",
|
|
13706
|
+
"profile_url"
|
|
13707
|
+
];
|
|
13708
|
+
NAME_COLUMNS = ["full name", "full_name", "name"];
|
|
13709
|
+
COMPANY_COLUMNS = ["company name", "company_name", "company"];
|
|
13170
13710
|
}
|
|
13171
13711
|
});
|
|
13172
13712
|
|
|
@@ -13821,9 +14361,13 @@ async function handleFeedback(args) {
|
|
|
13821
14361
|
});
|
|
13822
14362
|
if (!response.ok) {
|
|
13823
14363
|
const body = await response.text().catch(() => "unknown");
|
|
13824
|
-
throw createGtmError(
|
|
13825
|
-
|
|
13826
|
-
|
|
14364
|
+
throw createGtmError(
|
|
14365
|
+
"PROVIDER_REQUEST_FAILED",
|
|
14366
|
+
`Failed to submit feedback: ${response.status}`,
|
|
14367
|
+
{
|
|
14368
|
+
details: { status: response.status, body }
|
|
14369
|
+
}
|
|
14370
|
+
);
|
|
13827
14371
|
}
|
|
13828
14372
|
const data = await response.json();
|
|
13829
14373
|
const created = data[0];
|
|
@@ -13836,7 +14380,10 @@ async function handleFeedback(args) {
|
|
|
13836
14380
|
} catch (error) {
|
|
13837
14381
|
if (error && typeof error === "object" && "error_code" in error) throw error;
|
|
13838
14382
|
logError("feedback:submit", error instanceof Error ? error : new Error(String(error)));
|
|
13839
|
-
throw createGtmError(
|
|
14383
|
+
throw createGtmError(
|
|
14384
|
+
"PROVIDER_REQUEST_FAILED",
|
|
14385
|
+
"Failed to submit feedback. Please try again later."
|
|
14386
|
+
);
|
|
13840
14387
|
}
|
|
13841
14388
|
}
|
|
13842
14389
|
var VIEWER_URL2;
|
|
@@ -14249,6 +14796,9 @@ var init_validation = __esm({
|
|
|
14249
14796
|
linkedin_url: z2.string().optional(),
|
|
14250
14797
|
find_email: z2.boolean().optional(),
|
|
14251
14798
|
profile: z2.string().optional(),
|
|
14799
|
+
profiles: z2.array(z2.string()).optional(),
|
|
14800
|
+
csv_file: z2.string().optional(),
|
|
14801
|
+
concurrency: z2.number().int().min(1).max(20).optional(),
|
|
14252
14802
|
company: z2.string().optional(),
|
|
14253
14803
|
posted_within: z2.enum(["24h", "week", "month"]).optional(),
|
|
14254
14804
|
post_url: z2.string().optional(),
|
|
@@ -14257,14 +14807,16 @@ var init_validation = __esm({
|
|
|
14257
14807
|
sort: z2.enum(["relevance", "date"]).optional(),
|
|
14258
14808
|
limit: z2.number().int().min(1).max(100).optional(),
|
|
14259
14809
|
fields: z2.array(z2.string()).optional(),
|
|
14260
|
-
detail: detailSchema
|
|
14810
|
+
detail: detailSchema,
|
|
14811
|
+
output: z2.enum(["file", "csv", "inline"]).optional(),
|
|
14812
|
+
review: z2.boolean().optional()
|
|
14261
14813
|
}).refine(
|
|
14262
14814
|
(data) => {
|
|
14263
14815
|
switch (data.type) {
|
|
14264
14816
|
case "linkedin_profile":
|
|
14265
14817
|
return !!(data.linkedin_url || data.url);
|
|
14266
14818
|
case "linkedin_posts":
|
|
14267
|
-
return !!data.profile;
|
|
14819
|
+
return !!(data.profile || data.profiles?.length || data.csv_file);
|
|
14268
14820
|
case "linkedin_engagement":
|
|
14269
14821
|
return !!data.post_url;
|
|
14270
14822
|
case "linkedin_search":
|
|
@@ -14525,7 +15077,7 @@ var init_server = __esm({
|
|
|
14525
15077
|
});
|
|
14526
15078
|
|
|
14527
15079
|
// src/cli-commands.ts
|
|
14528
|
-
import { readFileSync as
|
|
15080
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
14529
15081
|
function registerCommands(program, exec2) {
|
|
14530
15082
|
buildServeCommand(program);
|
|
14531
15083
|
buildStatusCommand(program, exec2);
|
|
@@ -14654,11 +15206,11 @@ function parseContactsArg(value, writeError2) {
|
|
|
14654
15206
|
writeError2("Failed to parse --contacts as JSON", { received: trimmed.slice(0, 200) });
|
|
14655
15207
|
}
|
|
14656
15208
|
}
|
|
14657
|
-
if (!
|
|
15209
|
+
if (!existsSync3(value)) {
|
|
14658
15210
|
writeError2(`Contacts file not found: ${value}`);
|
|
14659
15211
|
}
|
|
14660
15212
|
try {
|
|
14661
|
-
const content =
|
|
15213
|
+
const content = readFileSync3(value, "utf-8");
|
|
14662
15214
|
const parsed = JSON.parse(content);
|
|
14663
15215
|
return Array.isArray(parsed) ? parsed : [parsed];
|
|
14664
15216
|
} catch (err) {
|