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 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
- var POST_TEXT_LIMIT;
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("PROVIDER_REQUEST_FAILED", `Failed to submit feedback: ${response.status}`, {
13825
- details: { status: response.status, body }
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("PROVIDER_REQUEST_FAILED", "Failed to submit feedback. Please try again later.");
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 readFileSync2, existsSync as existsSync2 } from "fs";
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 (!existsSync2(value)) {
15209
+ if (!existsSync3(value)) {
14658
15210
  writeError2(`Contacts file not found: ${value}`);
14659
15211
  }
14660
15212
  try {
14661
- const content = readFileSync2(value, "utf-8");
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) {