gtm-now 0.1.0 → 0.3.0
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 +955 -97
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -203,15 +203,19 @@ var init_config = __esm({
|
|
|
203
203
|
GTM_ATTIO_API_KEY: "attio",
|
|
204
204
|
GTM_APOLLO_API_KEY: "apollo",
|
|
205
205
|
GTM_INSTANTLY_API_KEY: "instantly",
|
|
206
|
-
GTM_SMARTLEAD_API_KEY: "smartlead"
|
|
206
|
+
GTM_SMARTLEAD_API_KEY: "smartlead",
|
|
207
|
+
GTM_HARVEST_API_KEY: "harvest",
|
|
208
|
+
GTM_STORELEADS_API_KEY: "storeleads"
|
|
207
209
|
};
|
|
208
210
|
ENV_WORKSPACE_MAP = {
|
|
209
211
|
GTM_PLUSVIBE_WORKSPACE_ID: "plusvibe"
|
|
210
212
|
};
|
|
211
213
|
providerConfigSchema = z.object({
|
|
212
|
-
api_key: z.string(),
|
|
214
|
+
api_key: z.string().optional(),
|
|
213
215
|
workspace_id: z.string().optional(),
|
|
214
|
-
account_id: z.string().optional()
|
|
216
|
+
account_id: z.string().optional(),
|
|
217
|
+
supabase_url: z.string().optional(),
|
|
218
|
+
supabase_key: z.string().optional()
|
|
215
219
|
});
|
|
216
220
|
clientConfigSchema = z.object({
|
|
217
221
|
providers: z.record(providerConfigSchema).optional(),
|
|
@@ -312,7 +316,8 @@ var init_registry = __esm({
|
|
|
312
316
|
list() {
|
|
313
317
|
return [...this.providers.values()].map((p) => ({
|
|
314
318
|
name: p.name,
|
|
315
|
-
capabilities: p.capabilities
|
|
319
|
+
capabilities: p.capabilities,
|
|
320
|
+
notes: p.notes
|
|
316
321
|
}));
|
|
317
322
|
}
|
|
318
323
|
deregisterAll() {
|
|
@@ -8128,6 +8133,21 @@ var init_client12 = __esm({
|
|
|
8128
8133
|
method: "GET"
|
|
8129
8134
|
});
|
|
8130
8135
|
}
|
|
8136
|
+
async getGrowth(domain) {
|
|
8137
|
+
return this.request(`/growth?domain=${encodeURIComponent(domain)}`, {
|
|
8138
|
+
method: "GET"
|
|
8139
|
+
});
|
|
8140
|
+
}
|
|
8141
|
+
async getVendors(domain) {
|
|
8142
|
+
return this.request(`/vendors?domain=${encodeURIComponent(domain)}`, {
|
|
8143
|
+
method: "GET"
|
|
8144
|
+
});
|
|
8145
|
+
}
|
|
8146
|
+
async getMetrics(domain) {
|
|
8147
|
+
return this.request(`/metrics?domain=${encodeURIComponent(domain)}`, {
|
|
8148
|
+
method: "GET"
|
|
8149
|
+
});
|
|
8150
|
+
}
|
|
8131
8151
|
// ─── Match ─────────────────────────────────────────────
|
|
8132
8152
|
async match(name) {
|
|
8133
8153
|
return this.request(`/match?name=${encodeURIComponent(name)}`, {
|
|
@@ -8410,6 +8430,7 @@ var init_client18 = __esm({
|
|
|
8410
8430
|
var init_client19 = __esm({
|
|
8411
8431
|
"../integrations/dist/twilio/client.js"() {
|
|
8412
8432
|
"use strict";
|
|
8433
|
+
init_dist();
|
|
8413
8434
|
init_base_client();
|
|
8414
8435
|
}
|
|
8415
8436
|
});
|
|
@@ -8446,6 +8467,66 @@ var init_factory = __esm({
|
|
|
8446
8467
|
}
|
|
8447
8468
|
});
|
|
8448
8469
|
|
|
8470
|
+
// ../integrations/dist/harvest/client.js
|
|
8471
|
+
var HarvestClient;
|
|
8472
|
+
var init_client22 = __esm({
|
|
8473
|
+
"../integrations/dist/harvest/client.js"() {
|
|
8474
|
+
"use strict";
|
|
8475
|
+
init_base_client();
|
|
8476
|
+
HarvestClient = class extends BaseApiClient {
|
|
8477
|
+
constructor(config) {
|
|
8478
|
+
super({
|
|
8479
|
+
name: "Harvest",
|
|
8480
|
+
baseUrl: config.baseUrl ?? "https://api.harvest-api.com",
|
|
8481
|
+
headers: { "X-API-Key": config.apiKey },
|
|
8482
|
+
timeoutMs: config.timeoutMs
|
|
8483
|
+
});
|
|
8484
|
+
}
|
|
8485
|
+
// ─── Posts ──────────────────────────────────────────────
|
|
8486
|
+
async searchPosts(options) {
|
|
8487
|
+
const params = this.buildParams(options);
|
|
8488
|
+
return this.request(`/linkedin/post-search?${params}`);
|
|
8489
|
+
}
|
|
8490
|
+
async getProfilePosts(options) {
|
|
8491
|
+
const params = this.buildParams(options);
|
|
8492
|
+
return this.request(`/linkedin/profile-posts?${params}`);
|
|
8493
|
+
}
|
|
8494
|
+
async getCompanyPosts(options) {
|
|
8495
|
+
const params = this.buildParams(options);
|
|
8496
|
+
return this.request(`/linkedin/company-posts?${params}`);
|
|
8497
|
+
}
|
|
8498
|
+
// ─── Engagement ─────────────────────────────────────────
|
|
8499
|
+
async getPostComments(postUrl, options) {
|
|
8500
|
+
const params = this.buildParams({ post: postUrl, ...options });
|
|
8501
|
+
return this.request(`/linkedin/post-comments?${params}`);
|
|
8502
|
+
}
|
|
8503
|
+
async getPostReactions(postUrl, options) {
|
|
8504
|
+
const params = this.buildParams({ post: postUrl, ...options });
|
|
8505
|
+
return this.request(`/linkedin/post-reactions?${params}`);
|
|
8506
|
+
}
|
|
8507
|
+
// ─── Profiles ───────────────────────────────────────────
|
|
8508
|
+
async getProfile(identifier, options) {
|
|
8509
|
+
const isUrl = identifier.includes("linkedin.com");
|
|
8510
|
+
const params = this.buildParams({
|
|
8511
|
+
...isUrl ? { url: identifier } : { publicIdentifier: identifier },
|
|
8512
|
+
...options?.findEmail != null && { findEmail: options.findEmail }
|
|
8513
|
+
});
|
|
8514
|
+
return this.request(`/linkedin/profile?${params}`);
|
|
8515
|
+
}
|
|
8516
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
8517
|
+
buildParams(obj) {
|
|
8518
|
+
const params = new URLSearchParams();
|
|
8519
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
8520
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
8521
|
+
params.set(key, String(value));
|
|
8522
|
+
}
|
|
8523
|
+
}
|
|
8524
|
+
return params;
|
|
8525
|
+
}
|
|
8526
|
+
};
|
|
8527
|
+
}
|
|
8528
|
+
});
|
|
8529
|
+
|
|
8449
8530
|
// ../integrations/dist/index.js
|
|
8450
8531
|
var init_dist2 = __esm({
|
|
8451
8532
|
"../integrations/dist/index.js"() {
|
|
@@ -8482,6 +8563,7 @@ var init_dist2 = __esm({
|
|
|
8482
8563
|
init_client21();
|
|
8483
8564
|
init_parser();
|
|
8484
8565
|
init_factory();
|
|
8566
|
+
init_client22();
|
|
8485
8567
|
}
|
|
8486
8568
|
});
|
|
8487
8569
|
|
|
@@ -8502,6 +8584,7 @@ var init_bettercontact = __esm({
|
|
|
8502
8584
|
"verify_email",
|
|
8503
8585
|
"find_people"
|
|
8504
8586
|
];
|
|
8587
|
+
notes = "Best for: email + phone waterfall. Charges only on successful find.";
|
|
8505
8588
|
client;
|
|
8506
8589
|
constructor(apiKey) {
|
|
8507
8590
|
this.client = new BetterContactClient({ apiKey });
|
|
@@ -8664,6 +8747,10 @@ function parseName(fullName) {
|
|
|
8664
8747
|
if (parts.length === 1) return { first: parts[0], last: "" };
|
|
8665
8748
|
return { first: parts[0], last: parts.slice(1).join(" ") };
|
|
8666
8749
|
}
|
|
8750
|
+
function domainToCompanyName(domain) {
|
|
8751
|
+
const name = domain.split(".")[0] ?? domain;
|
|
8752
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
8753
|
+
}
|
|
8667
8754
|
var DiscoAdapter;
|
|
8668
8755
|
var init_disco = __esm({
|
|
8669
8756
|
"src/providers/adapters/disco.ts"() {
|
|
@@ -8673,6 +8760,7 @@ var init_disco = __esm({
|
|
|
8673
8760
|
DiscoAdapter = class {
|
|
8674
8761
|
name = "disco";
|
|
8675
8762
|
capabilities = ["find_companies", "find_people"];
|
|
8763
|
+
notes = "Best for: lookalike discovery, ICP text matching, tech stack filtering, website-content businesses. 60M+ company database from web crawl.";
|
|
8676
8764
|
client;
|
|
8677
8765
|
constructor(apiKey) {
|
|
8678
8766
|
this.client = new DiscoClient({ apiKey });
|
|
@@ -8689,13 +8777,17 @@ var init_disco = __esm({
|
|
|
8689
8777
|
// ─── CompanyFinder ──────────────────────────────────────
|
|
8690
8778
|
async findCompanies(query) {
|
|
8691
8779
|
const response = await this.client.discover({
|
|
8692
|
-
domain: query.domain,
|
|
8780
|
+
domain: query.domains ?? query.domain,
|
|
8693
8781
|
icp_text: query.icp_text,
|
|
8694
8782
|
max_records: query.limit,
|
|
8695
8783
|
country: query.country,
|
|
8696
8784
|
...query.min_employees != null && query.max_employees != null && {
|
|
8697
8785
|
employee_range: `${query.min_employees},${query.max_employees}`
|
|
8698
|
-
}
|
|
8786
|
+
},
|
|
8787
|
+
tech_stack: query.tech_stack,
|
|
8788
|
+
category: query.category?.toUpperCase(),
|
|
8789
|
+
negate_domain: query.negate_domains,
|
|
8790
|
+
offset: query.offset
|
|
8699
8791
|
});
|
|
8700
8792
|
return response.map((c) => ({
|
|
8701
8793
|
domain: c.domain,
|
|
@@ -8705,23 +8797,40 @@ var init_disco = __esm({
|
|
|
8705
8797
|
employees: c.employees ? parseInt(c.employees, 10) || void 0 : void 0,
|
|
8706
8798
|
country: c.address?.country,
|
|
8707
8799
|
linkedin_url: c.social_urls?.find((u) => u.includes("linkedin")),
|
|
8708
|
-
score: c.score
|
|
8800
|
+
score: c.score,
|
|
8801
|
+
tech_stack: c.keywords ? Object.keys(c.keywords) : void 0,
|
|
8802
|
+
sources: [this.name]
|
|
8709
8803
|
}));
|
|
8710
8804
|
}
|
|
8711
8805
|
async countCompanies(query) {
|
|
8712
8806
|
const response = await this.client.count({
|
|
8713
|
-
domain: query.domain,
|
|
8807
|
+
domain: query.domains ?? query.domain,
|
|
8714
8808
|
icp_text: query.icp_text,
|
|
8715
8809
|
country: query.country,
|
|
8716
8810
|
...query.min_employees != null && query.max_employees != null && {
|
|
8717
8811
|
employee_range: `${query.min_employees},${query.max_employees}`
|
|
8718
|
-
}
|
|
8812
|
+
},
|
|
8813
|
+
tech_stack: query.tech_stack,
|
|
8814
|
+
category: query.category?.toUpperCase(),
|
|
8815
|
+
negate_domain: query.negate_domains
|
|
8816
|
+
// offset intentionally omitted — count is always total, not paged
|
|
8719
8817
|
});
|
|
8720
8818
|
return response.count;
|
|
8721
8819
|
}
|
|
8722
8820
|
async getCompany(domain) {
|
|
8723
8821
|
try {
|
|
8724
8822
|
const data = await this.client.getBizData(domain);
|
|
8823
|
+
const [growth, vendors, metrics] = await Promise.allSettled([
|
|
8824
|
+
this.client.getGrowth(domain),
|
|
8825
|
+
this.client.getVendors(domain),
|
|
8826
|
+
this.client.getMetrics(domain)
|
|
8827
|
+
]);
|
|
8828
|
+
const growthData = growth.status === "fulfilled" ? growth.value : void 0;
|
|
8829
|
+
const vendorsData = vendors.status === "fulfilled" ? vendors.value : void 0;
|
|
8830
|
+
const metricsData = metrics.status === "fulfilled" ? metrics.value : void 0;
|
|
8831
|
+
const keywordTech = data.keywords ? Object.keys(data.keywords) : [];
|
|
8832
|
+
const vendorTech = vendorsData?.vendors?.map((v) => v.name) ?? [];
|
|
8833
|
+
const mergedTech = [.../* @__PURE__ */ new Set([...keywordTech, ...vendorTech])];
|
|
8725
8834
|
return {
|
|
8726
8835
|
domain: data.domain,
|
|
8727
8836
|
name: data.name,
|
|
@@ -8729,10 +8838,22 @@ var init_disco = __esm({
|
|
|
8729
8838
|
industry: data.industry_groups ? Object.keys(data.industry_groups)[0] : void 0,
|
|
8730
8839
|
employees: data.employees ? parseInt(data.employees, 10) || void 0 : void 0,
|
|
8731
8840
|
country: data.address?.country,
|
|
8732
|
-
linkedin_url: data.social_urls?.find((u) => u.includes("linkedin"))
|
|
8841
|
+
linkedin_url: data.social_urls?.find((u) => u.includes("linkedin")),
|
|
8842
|
+
tech_stack: mergedTech.length > 0 ? mergedTech : void 0,
|
|
8843
|
+
growth_signals: growthData ? {
|
|
8844
|
+
quarterly_growth: growthData.quarterly_growth,
|
|
8845
|
+
digital_footprint_trend: growthData.digital_footprint_trend
|
|
8846
|
+
} : void 0,
|
|
8847
|
+
engagement_metrics: metricsData ? {
|
|
8848
|
+
traffic_estimate: metricsData.traffic_estimate,
|
|
8849
|
+
social_presence: metricsData.social_presence
|
|
8850
|
+
} : void 0,
|
|
8851
|
+
sources: [this.name]
|
|
8733
8852
|
};
|
|
8734
|
-
} catch (
|
|
8735
|
-
|
|
8853
|
+
} catch (error) {
|
|
8854
|
+
logError("disco:getCompany", error instanceof Error ? error : new Error(String(error)), {
|
|
8855
|
+
domain
|
|
8856
|
+
});
|
|
8736
8857
|
return null;
|
|
8737
8858
|
}
|
|
8738
8859
|
}
|
|
@@ -8744,9 +8865,15 @@ var init_disco = __esm({
|
|
|
8744
8865
|
icp_text: !query.company_domain && query.company_name ? query.company_name : void 0,
|
|
8745
8866
|
title: query.job_title,
|
|
8746
8867
|
person_country: query.location,
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8868
|
+
has_email: query.has_email ?? true,
|
|
8869
|
+
max_records: query.limit ?? 25,
|
|
8870
|
+
// Cast to DiscoSeniority — PeopleQuery uses string[] for cross-provider compat
|
|
8871
|
+
seniority: query.seniority,
|
|
8872
|
+
department: query.department,
|
|
8873
|
+
has_phone: query.has_phone,
|
|
8874
|
+
has_linkedin: query.has_linkedin,
|
|
8875
|
+
min_connections: query.min_connections,
|
|
8876
|
+
offset: query.offset
|
|
8750
8877
|
});
|
|
8751
8878
|
return response.map((c) => {
|
|
8752
8879
|
const nameParts = parseName(c.name);
|
|
@@ -8754,10 +8881,17 @@ var init_disco = __esm({
|
|
|
8754
8881
|
first_name: nameParts.first,
|
|
8755
8882
|
last_name: nameParts.last,
|
|
8756
8883
|
job_title: c.title,
|
|
8757
|
-
company: c.domain,
|
|
8884
|
+
company: domainToCompanyName(c.domain),
|
|
8885
|
+
company_domain: c.domain,
|
|
8758
8886
|
linkedin_url: c.social_urls?.find((u) => u.includes("linkedin")),
|
|
8759
8887
|
email: c.email,
|
|
8760
|
-
phone: c.phone
|
|
8888
|
+
phone: c.phone,
|
|
8889
|
+
seniority: c.seniority,
|
|
8890
|
+
department: c.department,
|
|
8891
|
+
skills: c.skills,
|
|
8892
|
+
connections: c.connections,
|
|
8893
|
+
location: c.country,
|
|
8894
|
+
sources: [this.name]
|
|
8761
8895
|
};
|
|
8762
8896
|
});
|
|
8763
8897
|
}
|
|
@@ -8775,6 +8909,7 @@ var init_prospeo2 = __esm({
|
|
|
8775
8909
|
ProspeoMcpAdapter = class {
|
|
8776
8910
|
name = "prospeo";
|
|
8777
8911
|
capabilities = ["enrich_email"];
|
|
8912
|
+
notes = "Best for: email finding from LinkedIn URLs. Good hit rate on LinkedIn-active professionals.";
|
|
8778
8913
|
client;
|
|
8779
8914
|
constructor(apiKey) {
|
|
8780
8915
|
this.client = new ProspeoAdapter(apiKey);
|
|
@@ -8830,6 +8965,7 @@ var init_plusvibe = __esm({
|
|
|
8830
8965
|
PlusVibeAdapter = class {
|
|
8831
8966
|
name = "plusvibe";
|
|
8832
8967
|
capabilities = ["campaign_email"];
|
|
8968
|
+
notes = "Best for: cold email campaign management, inbox rotation, analytics.";
|
|
8833
8969
|
client;
|
|
8834
8970
|
constructor(apiKey, workspaceId) {
|
|
8835
8971
|
this.client = new PlusVibeClient(apiKey, workspaceId);
|
|
@@ -8896,6 +9032,7 @@ var init_heyreach = __esm({
|
|
|
8896
9032
|
HeyReachAdapter = class {
|
|
8897
9033
|
name = "heyreach";
|
|
8898
9034
|
capabilities = ["outreach_linkedin"];
|
|
9035
|
+
notes = "Best for: LinkedIn outreach automation, connection requests, messaging campaigns.";
|
|
8899
9036
|
client;
|
|
8900
9037
|
constructor(apiKey) {
|
|
8901
9038
|
this.client = new HeyReachClient(apiKey);
|
|
@@ -8963,6 +9100,7 @@ var init_attio = __esm({
|
|
|
8963
9100
|
AttioAdapter = class {
|
|
8964
9101
|
name = "attio";
|
|
8965
9102
|
capabilities = ["crm_read", "crm_write"];
|
|
9103
|
+
notes = "Best for: CRM operations \u2014 people, companies, deals, tags.";
|
|
8966
9104
|
client;
|
|
8967
9105
|
constructor(apiKey) {
|
|
8968
9106
|
this.client = new AttioClient(apiKey);
|
|
@@ -9063,6 +9201,7 @@ var init_apollo = __esm({
|
|
|
9063
9201
|
ApolloAdapter = class {
|
|
9064
9202
|
name = "apollo";
|
|
9065
9203
|
capabilities = ["find_companies", "find_people", "enrich_email"];
|
|
9204
|
+
notes = "Best for: people search by job title/company, org enrichment, email finding. 275M+ contact database.";
|
|
9066
9205
|
client;
|
|
9067
9206
|
constructor(apiKey) {
|
|
9068
9207
|
this.client = new ApolloClient({ apiKey });
|
|
@@ -9081,6 +9220,8 @@ var init_apollo = __esm({
|
|
|
9081
9220
|
}
|
|
9082
9221
|
// ─── CompanyFinder ──────────────────────────────────────
|
|
9083
9222
|
async findCompanies(query) {
|
|
9223
|
+
const perPage = Math.min(query.limit ?? 25, 100);
|
|
9224
|
+
const page = query.offset ? Math.floor(query.offset / perPage) + 1 : 1;
|
|
9084
9225
|
const response = await this.client.searchOrganizations({
|
|
9085
9226
|
...query.domain && { q_organization_domains: query.domain },
|
|
9086
9227
|
...query.icp_text && { q_organization_keyword_tags: [query.icp_text] },
|
|
@@ -9088,8 +9229,8 @@ var init_apollo = __esm({
|
|
|
9088
9229
|
...query.min_employees != null && query.max_employees != null && {
|
|
9089
9230
|
organization_num_employees_ranges: [`${query.min_employees},${query.max_employees}`]
|
|
9090
9231
|
},
|
|
9091
|
-
per_page:
|
|
9092
|
-
page
|
|
9232
|
+
per_page: perPage,
|
|
9233
|
+
page
|
|
9093
9234
|
});
|
|
9094
9235
|
return response.organizations.map((org) => ({
|
|
9095
9236
|
domain: org.primary_domain ?? org.website_url ?? "",
|
|
@@ -9098,7 +9239,11 @@ var init_apollo = __esm({
|
|
|
9098
9239
|
industry: org.industry ?? void 0,
|
|
9099
9240
|
employees: org.estimated_num_employees ?? void 0,
|
|
9100
9241
|
country: org.country ?? void 0,
|
|
9101
|
-
linkedin_url: org.linkedin_url ?? void 0
|
|
9242
|
+
linkedin_url: org.linkedin_url ?? void 0,
|
|
9243
|
+
tech_stack: org.technology_names?.length ? org.technology_names : void 0,
|
|
9244
|
+
funding: org.total_funding_printed ?? void 0,
|
|
9245
|
+
revenue_estimate: org.annual_revenue ?? void 0,
|
|
9246
|
+
sources: [this.name]
|
|
9102
9247
|
}));
|
|
9103
9248
|
}
|
|
9104
9249
|
async getCompany(domain) {
|
|
@@ -9113,22 +9258,31 @@ var init_apollo = __esm({
|
|
|
9113
9258
|
industry: org.industry ?? void 0,
|
|
9114
9259
|
employees: org.estimated_num_employees ?? void 0,
|
|
9115
9260
|
country: org.country ?? void 0,
|
|
9116
|
-
linkedin_url: org.linkedin_url ?? void 0
|
|
9261
|
+
linkedin_url: org.linkedin_url ?? void 0,
|
|
9262
|
+
tech_stack: org.technology_names?.length ? org.technology_names : void 0,
|
|
9263
|
+
funding: org.total_funding_printed ?? void 0,
|
|
9264
|
+
revenue_estimate: org.annual_revenue ?? void 0,
|
|
9265
|
+
sources: [this.name]
|
|
9117
9266
|
};
|
|
9118
|
-
} catch (
|
|
9119
|
-
|
|
9267
|
+
} catch (error) {
|
|
9268
|
+
logError("apollo:getCompany", error instanceof Error ? error : new Error(String(error)), {
|
|
9269
|
+
domain
|
|
9270
|
+
});
|
|
9120
9271
|
return null;
|
|
9121
9272
|
}
|
|
9122
9273
|
}
|
|
9123
9274
|
// ─── PeopleFinder ───────────────────────────────────────
|
|
9124
9275
|
async findPeople(query) {
|
|
9276
|
+
const perPage = Math.min(query.limit ?? 25, 100);
|
|
9277
|
+
const page = query.offset ? Math.floor(query.offset / perPage) + 1 : 1;
|
|
9125
9278
|
const response = await this.client.searchPeople({
|
|
9126
9279
|
...query.job_title && { person_titles: [query.job_title] },
|
|
9127
9280
|
...query.company_domain && { q_organization_domains: query.company_domain },
|
|
9128
9281
|
...query.company_name && { q_keywords: query.company_name },
|
|
9129
9282
|
...query.location && { person_locations: [query.location] },
|
|
9130
|
-
|
|
9131
|
-
|
|
9283
|
+
...query.seniority && { person_seniorities: query.seniority },
|
|
9284
|
+
per_page: perPage,
|
|
9285
|
+
page
|
|
9132
9286
|
});
|
|
9133
9287
|
return response.people.map((p) => ({
|
|
9134
9288
|
first_name: p.first_name,
|
|
@@ -9137,7 +9291,13 @@ var init_apollo = __esm({
|
|
|
9137
9291
|
company: p.organization_name ?? void 0,
|
|
9138
9292
|
linkedin_url: p.linkedin_url ?? void 0,
|
|
9139
9293
|
// People search does NOT return email — null by design
|
|
9140
|
-
email: p.email ?? void 0
|
|
9294
|
+
email: p.email ?? void 0,
|
|
9295
|
+
phone: p.phone_number ?? void 0,
|
|
9296
|
+
seniority: p.seniority ?? void 0,
|
|
9297
|
+
department: p.departments?.[0] ?? void 0,
|
|
9298
|
+
headline: p.headline ?? void 0,
|
|
9299
|
+
location: [p.city, p.state, p.country].filter(Boolean).join(", ") || void 0,
|
|
9300
|
+
sources: [this.name]
|
|
9141
9301
|
}));
|
|
9142
9302
|
}
|
|
9143
9303
|
// ─── EmailEnricher ──────────────────────────────────────
|
|
@@ -9171,6 +9331,7 @@ var init_instantly = __esm({
|
|
|
9171
9331
|
InstantlyAdapter = class {
|
|
9172
9332
|
name = "instantly";
|
|
9173
9333
|
capabilities = ["campaign_email"];
|
|
9334
|
+
notes = "Best for: cold email campaigns with built-in warmup, deliverability tools, and analytics.";
|
|
9174
9335
|
client;
|
|
9175
9336
|
constructor(apiKey) {
|
|
9176
9337
|
this.client = new InstantlyClient(apiKey);
|
|
@@ -9238,6 +9399,7 @@ var init_smartlead = __esm({
|
|
|
9238
9399
|
SmartLeadAdapter = class {
|
|
9239
9400
|
name = "smartlead";
|
|
9240
9401
|
capabilities = ["campaign_email"];
|
|
9402
|
+
notes = "Best for: cold email campaigns with multi-mailbox rotation and AI warmup.";
|
|
9241
9403
|
client;
|
|
9242
9404
|
constructor(apiKey) {
|
|
9243
9405
|
this.client = new SmartLeadClient(apiKey);
|
|
@@ -9276,13 +9438,18 @@ var init_smartlead = __esm({
|
|
|
9276
9438
|
};
|
|
9277
9439
|
}
|
|
9278
9440
|
async addLeadsToCampaign(campaignId, leads) {
|
|
9279
|
-
const smartLeadLeads = leads.map((l) =>
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9441
|
+
const smartLeadLeads = leads.map((l) => {
|
|
9442
|
+
const { phone_number, linkedin_profile, ...remainingCustom } = l.custom_fields ?? {};
|
|
9443
|
+
return {
|
|
9444
|
+
email: l.email,
|
|
9445
|
+
first_name: l.first_name ?? "",
|
|
9446
|
+
last_name: l.last_name ?? "",
|
|
9447
|
+
company_name: l.company ?? "",
|
|
9448
|
+
...phone_number && { phone_number },
|
|
9449
|
+
...linkedin_profile && { linkedin_profile },
|
|
9450
|
+
...Object.keys(remainingCustom).length > 0 && { custom_fields: remainingCustom }
|
|
9451
|
+
};
|
|
9452
|
+
});
|
|
9286
9453
|
const response = await this.client.addLeadsToCampaign(Number(campaignId), smartLeadLeads);
|
|
9287
9454
|
return {
|
|
9288
9455
|
added: response.added_count,
|
|
@@ -9295,6 +9462,159 @@ var init_smartlead = __esm({
|
|
|
9295
9462
|
}
|
|
9296
9463
|
});
|
|
9297
9464
|
|
|
9465
|
+
// src/providers/adapters/storeleads.ts
|
|
9466
|
+
var BASE_URL, MAX_PAGE_SIZE, REQUEST_TIMEOUT_MS, FIELDS, StoreLeadsAdapter;
|
|
9467
|
+
var init_storeleads = __esm({
|
|
9468
|
+
"src/providers/adapters/storeleads.ts"() {
|
|
9469
|
+
"use strict";
|
|
9470
|
+
init_dist();
|
|
9471
|
+
BASE_URL = "https://storeleads.app/json/api/v1/all/domain";
|
|
9472
|
+
MAX_PAGE_SIZE = 50;
|
|
9473
|
+
REQUEST_TIMEOUT_MS = 15e3;
|
|
9474
|
+
FIELDS = [
|
|
9475
|
+
"domain",
|
|
9476
|
+
"merchant_name",
|
|
9477
|
+
"categories",
|
|
9478
|
+
"country_code",
|
|
9479
|
+
"administrative_area_level_1",
|
|
9480
|
+
"employee_count",
|
|
9481
|
+
"estimated_yearly_sales",
|
|
9482
|
+
"description",
|
|
9483
|
+
"contact_info",
|
|
9484
|
+
"platform"
|
|
9485
|
+
].join(",");
|
|
9486
|
+
StoreLeadsAdapter = class {
|
|
9487
|
+
name = "storeleads";
|
|
9488
|
+
capabilities = ["find_companies"];
|
|
9489
|
+
notes = "Best for: DTC, e-commerce, Shopify brands. 1.2M+ active stores with revenue estimates and categories. Requires API key from storeleads.app.";
|
|
9490
|
+
apiKey;
|
|
9491
|
+
constructor(apiKey) {
|
|
9492
|
+
this.apiKey = apiKey;
|
|
9493
|
+
}
|
|
9494
|
+
async checkCredentials() {
|
|
9495
|
+
try {
|
|
9496
|
+
const url = `${BASE_URL}?page_size=1&fields=domain`;
|
|
9497
|
+
const res = await fetch(url, {
|
|
9498
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
9499
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
9500
|
+
});
|
|
9501
|
+
return res.ok;
|
|
9502
|
+
} catch (error) {
|
|
9503
|
+
logError(
|
|
9504
|
+
"storeleads:checkCredentials",
|
|
9505
|
+
error instanceof Error ? error : new Error(String(error))
|
|
9506
|
+
);
|
|
9507
|
+
return false;
|
|
9508
|
+
}
|
|
9509
|
+
}
|
|
9510
|
+
async search(query) {
|
|
9511
|
+
const params = new URLSearchParams();
|
|
9512
|
+
params.set("fields", FIELDS);
|
|
9513
|
+
params.set("sort", "-estimated_yearly_sales");
|
|
9514
|
+
params.set("page_size", String(Math.min(query.limit ?? 50, MAX_PAGE_SIZE)));
|
|
9515
|
+
if (query.search) params.set("q", query.search);
|
|
9516
|
+
if (query.category) params.set("f:categories", query.category);
|
|
9517
|
+
if (query.country) params.set("f:country_code", query.country);
|
|
9518
|
+
if (query.state) params.set("f:administrative_area_level_1", query.state);
|
|
9519
|
+
if (query.platform) params.set("f:platform", query.platform);
|
|
9520
|
+
if (query.min_revenue != null)
|
|
9521
|
+
params.set("f:estimated_yearly_sales:min", String(query.min_revenue));
|
|
9522
|
+
if (query.max_revenue != null)
|
|
9523
|
+
params.set("f:estimated_yearly_sales:max", String(query.max_revenue));
|
|
9524
|
+
if (query.min_employees != null) params.set("f:ec:min", String(query.min_employees));
|
|
9525
|
+
if (query.max_employees != null) params.set("f:ec:max", String(query.max_employees));
|
|
9526
|
+
const url = `${BASE_URL}?${params.toString()}`;
|
|
9527
|
+
const res = await fetch(url, {
|
|
9528
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
9529
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
9530
|
+
});
|
|
9531
|
+
if (!res.ok) {
|
|
9532
|
+
const body = await res.text().catch(() => "");
|
|
9533
|
+
throw Object.assign(
|
|
9534
|
+
new Error(`StoreLeads API error ${res.status}: ${body || res.statusText}`),
|
|
9535
|
+
{ statusCode: res.status === 429 ? 429 : 502 }
|
|
9536
|
+
);
|
|
9537
|
+
}
|
|
9538
|
+
const data = await res.json();
|
|
9539
|
+
return data.map((d) => ({
|
|
9540
|
+
domain: d.domain ?? "",
|
|
9541
|
+
name: d.merchant_name ?? d.domain ?? "",
|
|
9542
|
+
description: d.description ?? void 0,
|
|
9543
|
+
industry: d.categories?.join(", ") ?? void 0,
|
|
9544
|
+
employees: d.employee_count ?? void 0,
|
|
9545
|
+
country: d.country_code ?? void 0,
|
|
9546
|
+
linkedin_url: d.contact_info?.linkedin ?? void 0,
|
|
9547
|
+
score: d.estimated_yearly_sales ? Number(d.estimated_yearly_sales) : void 0
|
|
9548
|
+
}));
|
|
9549
|
+
}
|
|
9550
|
+
};
|
|
9551
|
+
}
|
|
9552
|
+
});
|
|
9553
|
+
|
|
9554
|
+
// src/providers/adapters/harvest.ts
|
|
9555
|
+
var HarvestApiAdapter;
|
|
9556
|
+
var init_harvest = __esm({
|
|
9557
|
+
"src/providers/adapters/harvest.ts"() {
|
|
9558
|
+
"use strict";
|
|
9559
|
+
init_dist();
|
|
9560
|
+
init_dist2();
|
|
9561
|
+
HarvestApiAdapter = class {
|
|
9562
|
+
name = "harvest";
|
|
9563
|
+
capabilities = [];
|
|
9564
|
+
// Empty capabilities array — handler calls by name via registry.get('harvest'),
|
|
9565
|
+
// not by capability lookup. This avoids collision with extract handler's
|
|
9566
|
+
// getForCapability('extract_linkedin') which would try to cast to Extractor interface.
|
|
9567
|
+
notes = "Best for: LinkedIn profile scraping, post search, engagement mining, activity signals. Called directly by gtm_linkedin tool.";
|
|
9568
|
+
client;
|
|
9569
|
+
credentialsCached = null;
|
|
9570
|
+
constructor(apiKey) {
|
|
9571
|
+
this.client = new HarvestClient({ apiKey });
|
|
9572
|
+
}
|
|
9573
|
+
async checkCredentials() {
|
|
9574
|
+
if (this.credentialsCached !== null) return this.credentialsCached;
|
|
9575
|
+
try {
|
|
9576
|
+
await this.client.searchPosts({ search: "test", postedLimit: "24h" });
|
|
9577
|
+
this.credentialsCached = true;
|
|
9578
|
+
return true;
|
|
9579
|
+
} catch (error) {
|
|
9580
|
+
logError(
|
|
9581
|
+
"harvest:checkCredentials",
|
|
9582
|
+
error instanceof Error ? error : new Error(String(error))
|
|
9583
|
+
);
|
|
9584
|
+
this.credentialsCached = false;
|
|
9585
|
+
return false;
|
|
9586
|
+
}
|
|
9587
|
+
}
|
|
9588
|
+
// ─── Profile ────────────────────────────────────────────
|
|
9589
|
+
async getProfile(identifier, findEmail) {
|
|
9590
|
+
return this.client.getProfile(identifier, findEmail ? { findEmail: true } : void 0);
|
|
9591
|
+
}
|
|
9592
|
+
// ─── Posts ──────────────────────────────────────────────
|
|
9593
|
+
async searchPosts(query, options) {
|
|
9594
|
+
return this.client.searchPosts({ search: query, ...options });
|
|
9595
|
+
}
|
|
9596
|
+
async getProfilePosts(options) {
|
|
9597
|
+
return this.client.getProfilePosts(options);
|
|
9598
|
+
}
|
|
9599
|
+
async getCompanyPosts(options) {
|
|
9600
|
+
return this.client.getCompanyPosts(options);
|
|
9601
|
+
}
|
|
9602
|
+
// ─── Engagement ─────────────────────────────────────────
|
|
9603
|
+
async getPostEngagement(postUrl, type, options) {
|
|
9604
|
+
const [comments, reactions] = await Promise.all([
|
|
9605
|
+
type === "reactions" ? Promise.resolve({ elements: [] }) : this.client.getPostComments(postUrl, options),
|
|
9606
|
+
type === "comments" ? Promise.resolve({ elements: [] }) : this.client.getPostReactions(postUrl, options)
|
|
9607
|
+
]);
|
|
9608
|
+
return {
|
|
9609
|
+
comments: comments.elements,
|
|
9610
|
+
reactions: reactions.elements,
|
|
9611
|
+
post_url: postUrl
|
|
9612
|
+
};
|
|
9613
|
+
}
|
|
9614
|
+
};
|
|
9615
|
+
}
|
|
9616
|
+
});
|
|
9617
|
+
|
|
9298
9618
|
// src/providers/adapters/index.ts
|
|
9299
9619
|
var init_adapters = __esm({
|
|
9300
9620
|
"src/providers/adapters/index.ts"() {
|
|
@@ -9308,6 +9628,8 @@ var init_adapters = __esm({
|
|
|
9308
9628
|
init_apollo();
|
|
9309
9629
|
init_instantly();
|
|
9310
9630
|
init_smartlead();
|
|
9631
|
+
init_storeleads();
|
|
9632
|
+
init_harvest();
|
|
9311
9633
|
}
|
|
9312
9634
|
});
|
|
9313
9635
|
|
|
@@ -9315,23 +9637,27 @@ var init_adapters = __esm({
|
|
|
9315
9637
|
function createAdapter(name, config) {
|
|
9316
9638
|
switch (name) {
|
|
9317
9639
|
case "bettercontact":
|
|
9318
|
-
return new BetterContactAdapter(config.api_key);
|
|
9640
|
+
return config.api_key ? new BetterContactAdapter(config.api_key) : null;
|
|
9319
9641
|
case "disco":
|
|
9320
|
-
return new DiscoAdapter(config.api_key);
|
|
9642
|
+
return config.api_key ? new DiscoAdapter(config.api_key) : null;
|
|
9321
9643
|
case "prospeo":
|
|
9322
|
-
return new ProspeoMcpAdapter(config.api_key);
|
|
9644
|
+
return config.api_key ? new ProspeoMcpAdapter(config.api_key) : null;
|
|
9323
9645
|
case "plusvibe":
|
|
9324
|
-
return new PlusVibeAdapter(config.api_key, config.workspace_id ?? "");
|
|
9646
|
+
return config.api_key ? new PlusVibeAdapter(config.api_key, config.workspace_id ?? "") : null;
|
|
9325
9647
|
case "heyreach":
|
|
9326
|
-
return new HeyReachAdapter(config.api_key);
|
|
9648
|
+
return config.api_key ? new HeyReachAdapter(config.api_key) : null;
|
|
9327
9649
|
case "attio":
|
|
9328
|
-
return new AttioAdapter(config.api_key);
|
|
9650
|
+
return config.api_key ? new AttioAdapter(config.api_key) : null;
|
|
9329
9651
|
case "apollo":
|
|
9330
|
-
return new ApolloAdapter(config.api_key);
|
|
9652
|
+
return config.api_key ? new ApolloAdapter(config.api_key) : null;
|
|
9331
9653
|
case "instantly":
|
|
9332
|
-
return new InstantlyAdapter(config.api_key);
|
|
9654
|
+
return config.api_key ? new InstantlyAdapter(config.api_key) : null;
|
|
9333
9655
|
case "smartlead":
|
|
9334
|
-
return new SmartLeadAdapter(config.api_key);
|
|
9656
|
+
return config.api_key ? new SmartLeadAdapter(config.api_key) : null;
|
|
9657
|
+
case "storeleads":
|
|
9658
|
+
return config.api_key ? new StoreLeadsAdapter(config.api_key) : null;
|
|
9659
|
+
case "harvest":
|
|
9660
|
+
return config.api_key ? new HarvestApiAdapter(config.api_key) : null;
|
|
9335
9661
|
default:
|
|
9336
9662
|
return null;
|
|
9337
9663
|
}
|
|
@@ -9339,7 +9665,6 @@ function createAdapter(name, config) {
|
|
|
9339
9665
|
function registerProviders(registry, clientConfig) {
|
|
9340
9666
|
registry.deregisterAll();
|
|
9341
9667
|
for (const [name, config] of Object.entries(clientConfig.providers)) {
|
|
9342
|
-
if (!config.api_key) continue;
|
|
9343
9668
|
const adapter = createAdapter(name, config);
|
|
9344
9669
|
if (adapter) {
|
|
9345
9670
|
registry.register(adapter);
|
|
@@ -9347,7 +9672,7 @@ function registerProviders(registry, clientConfig) {
|
|
|
9347
9672
|
capabilities: adapter.capabilities
|
|
9348
9673
|
});
|
|
9349
9674
|
} else {
|
|
9350
|
-
logWarn("provider:register", `
|
|
9675
|
+
logWarn("provider:register", `Could not create adapter for "${name}" \u2014 skipped`);
|
|
9351
9676
|
}
|
|
9352
9677
|
}
|
|
9353
9678
|
}
|
|
@@ -9364,6 +9689,15 @@ var prospect_exports = {};
|
|
|
9364
9689
|
__export(prospect_exports, {
|
|
9365
9690
|
handleProspect: () => handleProspect
|
|
9366
9691
|
});
|
|
9692
|
+
function deduplicatePeople(people) {
|
|
9693
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9694
|
+
return people.filter((p) => {
|
|
9695
|
+
const key = p.email ?? p.linkedin_url ?? `${p.first_name}_${p.last_name}_${p.company}`;
|
|
9696
|
+
if (seen.has(key)) return false;
|
|
9697
|
+
seen.add(key);
|
|
9698
|
+
return true;
|
|
9699
|
+
});
|
|
9700
|
+
}
|
|
9367
9701
|
async function handleProspect(name, args, ctx) {
|
|
9368
9702
|
if (name === "gtm_find_companies") {
|
|
9369
9703
|
return findCompanies(args, ctx);
|
|
@@ -9380,16 +9714,43 @@ async function findCompanies(args, ctx) {
|
|
|
9380
9714
|
}
|
|
9381
9715
|
const provider = providers[0];
|
|
9382
9716
|
const finder = provider;
|
|
9717
|
+
const domain = args.domain;
|
|
9718
|
+
const isDirectLookup = domain && !args.domains && !args.icp_text && !args.category && !args.tech_stack && !args.country && finder.getCompany;
|
|
9719
|
+
if (isDirectLookup) {
|
|
9720
|
+
const company = await finder.getCompany(domain);
|
|
9721
|
+
if (!company) {
|
|
9722
|
+
throw new Error(`Company not found: ${domain}`);
|
|
9723
|
+
}
|
|
9724
|
+
return { provider: provider.name, count: 1, has_more: false, offset: 0, companies: [company] };
|
|
9725
|
+
}
|
|
9383
9726
|
const query = {
|
|
9384
|
-
domain
|
|
9727
|
+
domain,
|
|
9385
9728
|
icp_text: args.icp_text,
|
|
9386
9729
|
limit: args.limit,
|
|
9387
9730
|
country: args.country,
|
|
9388
9731
|
min_employees: args.min_employees,
|
|
9389
|
-
max_employees: args.max_employees
|
|
9732
|
+
max_employees: args.max_employees,
|
|
9733
|
+
// ─── Expanded params ───
|
|
9734
|
+
domains: args.domains,
|
|
9735
|
+
tech_stack: args.tech_stack,
|
|
9736
|
+
category: args.category,
|
|
9737
|
+
negate_domains: args.negate_domains,
|
|
9738
|
+
min_digital_footprint: args.min_digital_footprint,
|
|
9739
|
+
max_digital_footprint: args.max_digital_footprint,
|
|
9740
|
+
offset: args.offset
|
|
9741
|
+
// Note: args.provider is NOT forwarded into the query — it's used by the handler
|
|
9742
|
+
// for provider routing (selecting which adapter to call), not as a search parameter.
|
|
9390
9743
|
};
|
|
9391
9744
|
const results = await finder.findCompanies(query);
|
|
9392
|
-
|
|
9745
|
+
const limit = args.limit ?? 50;
|
|
9746
|
+
const offset = args.offset ?? 0;
|
|
9747
|
+
return {
|
|
9748
|
+
provider: provider.name,
|
|
9749
|
+
count: results.length,
|
|
9750
|
+
has_more: results.length >= limit,
|
|
9751
|
+
offset,
|
|
9752
|
+
companies: results
|
|
9753
|
+
};
|
|
9393
9754
|
}
|
|
9394
9755
|
async function findPeople(args, ctx) {
|
|
9395
9756
|
const providerName = args.provider || void 0;
|
|
@@ -9406,10 +9767,28 @@ async function findPeople(args, ctx) {
|
|
|
9406
9767
|
company_name: args.company_name,
|
|
9407
9768
|
company_domain: args.company_domain,
|
|
9408
9769
|
location: args.location,
|
|
9409
|
-
limit: args.limit
|
|
9770
|
+
limit: args.limit,
|
|
9771
|
+
// ─── Expanded params ───
|
|
9772
|
+
seniority: args.seniority,
|
|
9773
|
+
department: args.department,
|
|
9774
|
+
has_email: args.has_email,
|
|
9775
|
+
has_phone: args.has_phone,
|
|
9776
|
+
has_linkedin: args.has_linkedin,
|
|
9777
|
+
min_connections: args.min_connections,
|
|
9778
|
+
offset: args.offset
|
|
9779
|
+
// Note: args.provider is NOT forwarded into the query — used for provider routing only.
|
|
9410
9780
|
};
|
|
9411
9781
|
const results = await finder.findPeople(query);
|
|
9412
|
-
|
|
9782
|
+
const dedupedResults = deduplicatePeople(results);
|
|
9783
|
+
const limit = args.limit ?? 25;
|
|
9784
|
+
const offset = args.offset ?? 0;
|
|
9785
|
+
return {
|
|
9786
|
+
provider: provider.name,
|
|
9787
|
+
count: dedupedResults.length,
|
|
9788
|
+
has_more: dedupedResults.length >= limit,
|
|
9789
|
+
offset,
|
|
9790
|
+
people: dedupedResults
|
|
9791
|
+
};
|
|
9413
9792
|
}
|
|
9414
9793
|
var init_prospect = __esm({
|
|
9415
9794
|
"src/handlers/prospect.ts"() {
|
|
@@ -9554,6 +9933,16 @@ async function enrichContacts(args, ctx) {
|
|
|
9554
9933
|
const find = args.find ?? ["email"];
|
|
9555
9934
|
const verify = args.verify ?? true;
|
|
9556
9935
|
const dryRun = args.dry_run ?? false;
|
|
9936
|
+
const WATERFALL_FIELDS = ["email", "phone"];
|
|
9937
|
+
const richFields = find.filter((f) => !WATERFALL_FIELDS.includes(f));
|
|
9938
|
+
if (richFields.length > 0) {
|
|
9939
|
+
return {
|
|
9940
|
+
error: "rich_enrichment_not_available",
|
|
9941
|
+
supported_fields: WATERFALL_FIELDS,
|
|
9942
|
+
requested_rich_fields: richFields,
|
|
9943
|
+
message: `Rich enrichment fields (${richFields.join(", ")}) require the Enricher interface, coming in Phase 2. Currently supported: ${WATERFALL_FIELDS.join(", ")}.`
|
|
9944
|
+
};
|
|
9945
|
+
}
|
|
9557
9946
|
const clientConfig = ctx.session.getActiveContext().config;
|
|
9558
9947
|
const waterfallProviders = args.waterfall ?? clientConfig.waterfall.email;
|
|
9559
9948
|
const config = {
|
|
@@ -9988,22 +10377,68 @@ async function handleStatus(args, ctx) {
|
|
|
9988
10377
|
return {
|
|
9989
10378
|
provider: summary.name,
|
|
9990
10379
|
capabilities: summary.capabilities,
|
|
10380
|
+
notes: summary.notes,
|
|
9991
10381
|
healthy,
|
|
9992
10382
|
credits
|
|
9993
10383
|
};
|
|
9994
10384
|
})
|
|
9995
10385
|
);
|
|
9996
10386
|
const client = ctx.session.getActiveContext();
|
|
10387
|
+
const configuredCapabilities = new Set(results.flatMap((r) => r.capabilities));
|
|
10388
|
+
const unconfigured = TOOL_CAPABILITY_REQUIREMENTS.filter(
|
|
10389
|
+
(req) => !configuredCapabilities.has(req.capability)
|
|
10390
|
+
).map(({ tool, requires, setup }) => ({ tool, requires, setup }));
|
|
9997
10391
|
return {
|
|
9998
10392
|
client: client.clientName,
|
|
9999
10393
|
providers: results,
|
|
10000
|
-
waterfall: client.config.waterfall
|
|
10394
|
+
waterfall: client.config.waterfall,
|
|
10395
|
+
unconfigured: unconfigured.length > 0 ? unconfigured : void 0
|
|
10001
10396
|
};
|
|
10002
10397
|
}
|
|
10398
|
+
var TOOL_CAPABILITY_REQUIREMENTS;
|
|
10003
10399
|
var init_status = __esm({
|
|
10004
10400
|
"src/handlers/status.ts"() {
|
|
10005
10401
|
"use strict";
|
|
10006
10402
|
init_dist();
|
|
10403
|
+
TOOL_CAPABILITY_REQUIREMENTS = [
|
|
10404
|
+
{
|
|
10405
|
+
tool: "gtm_linkedin",
|
|
10406
|
+
requires: "harvest",
|
|
10407
|
+
capability: "extract_linkedin",
|
|
10408
|
+
setup: "Set GTM_HARVEST_API_KEY or add harvest.api_key to config.yaml"
|
|
10409
|
+
},
|
|
10410
|
+
{
|
|
10411
|
+
tool: "gtm_storeleads",
|
|
10412
|
+
requires: "storeleads",
|
|
10413
|
+
capability: "find_companies",
|
|
10414
|
+
setup: "Set SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY or add storeleads config"
|
|
10415
|
+
},
|
|
10416
|
+
{
|
|
10417
|
+
tool: "gtm_enrich",
|
|
10418
|
+
requires: "prospeo or bettercontact",
|
|
10419
|
+
capability: "enrich_email",
|
|
10420
|
+
setup: "Set GTM_PROSPEO_API_KEY or GTM_BETTERCONTACT_API_KEY"
|
|
10421
|
+
},
|
|
10422
|
+
{
|
|
10423
|
+
tool: "gtm_verify",
|
|
10424
|
+
requires: "bettercontact",
|
|
10425
|
+
capability: "verify_email",
|
|
10426
|
+
setup: "Set GTM_BETTERCONTACT_API_KEY"
|
|
10427
|
+
},
|
|
10428
|
+
{
|
|
10429
|
+
tool: "gtm_campaign",
|
|
10430
|
+
requires: "plusvibe, instantly, or smartlead",
|
|
10431
|
+
capability: "campaign_email",
|
|
10432
|
+
setup: "Set GTM_PLUSVIBE_API_KEY, GTM_INSTANTLY_API_KEY, or GTM_SMARTLEAD_API_KEY"
|
|
10433
|
+
},
|
|
10434
|
+
{
|
|
10435
|
+
tool: "gtm_outreach",
|
|
10436
|
+
requires: "heyreach",
|
|
10437
|
+
capability: "outreach_linkedin",
|
|
10438
|
+
setup: "Set GTM_HEYREACH_API_KEY"
|
|
10439
|
+
},
|
|
10440
|
+
{ tool: "gtm_crm", requires: "attio", capability: "crm_read", setup: "Set GTM_ATTIO_API_KEY" }
|
|
10441
|
+
];
|
|
10007
10442
|
}
|
|
10008
10443
|
});
|
|
10009
10444
|
|
|
@@ -10065,25 +10500,176 @@ var init_configure = __esm({
|
|
|
10065
10500
|
}
|
|
10066
10501
|
});
|
|
10067
10502
|
|
|
10503
|
+
// src/handlers/storeleads.ts
|
|
10504
|
+
var storeleads_exports = {};
|
|
10505
|
+
__export(storeleads_exports, {
|
|
10506
|
+
handleStoreleads: () => handleStoreleads
|
|
10507
|
+
});
|
|
10508
|
+
async function handleStoreleads(args, ctx) {
|
|
10509
|
+
const adapter = ctx.registry.get("storeleads");
|
|
10510
|
+
if (!adapter) {
|
|
10511
|
+
throw Object.assign(
|
|
10512
|
+
new Error(
|
|
10513
|
+
"StorLeads provider not configured. Set GTM_STORELEADS_API_KEY env var or add storeleads.api_key to your config.yaml. Get a key at storeleads.app."
|
|
10514
|
+
),
|
|
10515
|
+
{ statusCode: 503 }
|
|
10516
|
+
);
|
|
10517
|
+
}
|
|
10518
|
+
const query = {
|
|
10519
|
+
search: args.search,
|
|
10520
|
+
category: args.category,
|
|
10521
|
+
country: args.country,
|
|
10522
|
+
state: args.state,
|
|
10523
|
+
min_revenue: args.min_revenue,
|
|
10524
|
+
max_revenue: args.max_revenue,
|
|
10525
|
+
min_employees: args.min_employees,
|
|
10526
|
+
max_employees: args.max_employees,
|
|
10527
|
+
platform: args.platform,
|
|
10528
|
+
limit: args.limit
|
|
10529
|
+
};
|
|
10530
|
+
const results = await adapter.search(query);
|
|
10531
|
+
return {
|
|
10532
|
+
provider: "storeleads",
|
|
10533
|
+
count: results.length,
|
|
10534
|
+
companies: results
|
|
10535
|
+
};
|
|
10536
|
+
}
|
|
10537
|
+
var init_storeleads2 = __esm({
|
|
10538
|
+
"src/handlers/storeleads.ts"() {
|
|
10539
|
+
"use strict";
|
|
10540
|
+
}
|
|
10541
|
+
});
|
|
10542
|
+
|
|
10543
|
+
// src/handlers/linkedin.ts
|
|
10544
|
+
var linkedin_exports = {};
|
|
10545
|
+
__export(linkedin_exports, {
|
|
10546
|
+
handleLinkedin: () => handleLinkedin
|
|
10547
|
+
});
|
|
10548
|
+
async function handleLinkedin(args, ctx) {
|
|
10549
|
+
const adapter = ctx.registry.get("harvest");
|
|
10550
|
+
if (!adapter) {
|
|
10551
|
+
throw Object.assign(
|
|
10552
|
+
new Error(
|
|
10553
|
+
"HarvestAPI provider not configured. Add harvest.api_key to your config.yaml or set GTM_HARVEST_API_KEY environment variable."
|
|
10554
|
+
),
|
|
10555
|
+
{ statusCode: 503 }
|
|
10556
|
+
);
|
|
10557
|
+
}
|
|
10558
|
+
const action = args.action;
|
|
10559
|
+
switch (action) {
|
|
10560
|
+
case "profile": {
|
|
10561
|
+
const profile = await adapter.getProfile(
|
|
10562
|
+
args.linkedin_url,
|
|
10563
|
+
args.find_email
|
|
10564
|
+
);
|
|
10565
|
+
return { action: "profile", provider: "harvest", profile };
|
|
10566
|
+
}
|
|
10567
|
+
case "posts": {
|
|
10568
|
+
const result = await adapter.getProfilePosts({
|
|
10569
|
+
profile: args.profile,
|
|
10570
|
+
postedLimit: args.posted_within
|
|
10571
|
+
});
|
|
10572
|
+
return {
|
|
10573
|
+
action: "posts",
|
|
10574
|
+
provider: "harvest",
|
|
10575
|
+
count: result.elements.length,
|
|
10576
|
+
posts: result.elements,
|
|
10577
|
+
pagination: result.pagination
|
|
10578
|
+
};
|
|
10579
|
+
}
|
|
10580
|
+
case "company_posts": {
|
|
10581
|
+
const result = await adapter.getCompanyPosts({
|
|
10582
|
+
company: args.company,
|
|
10583
|
+
postedLimit: args.posted_within
|
|
10584
|
+
});
|
|
10585
|
+
return {
|
|
10586
|
+
action: "company_posts",
|
|
10587
|
+
provider: "harvest",
|
|
10588
|
+
count: result.elements.length,
|
|
10589
|
+
posts: result.elements,
|
|
10590
|
+
pagination: result.pagination
|
|
10591
|
+
};
|
|
10592
|
+
}
|
|
10593
|
+
case "search_posts": {
|
|
10594
|
+
const result = await adapter.searchPosts(args.query, {
|
|
10595
|
+
sortBy: args.sort,
|
|
10596
|
+
postedLimit: args.posted_within
|
|
10597
|
+
});
|
|
10598
|
+
return {
|
|
10599
|
+
action: "search_posts",
|
|
10600
|
+
provider: "harvest",
|
|
10601
|
+
count: result.elements.length,
|
|
10602
|
+
posts: result.elements,
|
|
10603
|
+
pagination: result.pagination
|
|
10604
|
+
};
|
|
10605
|
+
}
|
|
10606
|
+
case "engagement": {
|
|
10607
|
+
const result = await adapter.getPostEngagement(
|
|
10608
|
+
args.post_url,
|
|
10609
|
+
args.engagement_type ?? "both"
|
|
10610
|
+
);
|
|
10611
|
+
return {
|
|
10612
|
+
action: "engagement",
|
|
10613
|
+
provider: "harvest",
|
|
10614
|
+
comments_count: result.comments.length,
|
|
10615
|
+
reactions_count: result.reactions.length,
|
|
10616
|
+
comments: result.comments,
|
|
10617
|
+
reactions: result.reactions
|
|
10618
|
+
};
|
|
10619
|
+
}
|
|
10620
|
+
default:
|
|
10621
|
+
throw new Error(`Unknown linkedin action: ${action}`);
|
|
10622
|
+
}
|
|
10623
|
+
}
|
|
10624
|
+
var init_linkedin = __esm({
|
|
10625
|
+
"src/handlers/linkedin.ts"() {
|
|
10626
|
+
"use strict";
|
|
10627
|
+
}
|
|
10628
|
+
});
|
|
10629
|
+
|
|
10068
10630
|
// src/handlers/index.ts
|
|
10631
|
+
function isRateLimitError(error) {
|
|
10632
|
+
if (error instanceof Error && "status" in error) {
|
|
10633
|
+
return error.status === 429;
|
|
10634
|
+
}
|
|
10635
|
+
return false;
|
|
10636
|
+
}
|
|
10637
|
+
async function sleep(ms) {
|
|
10638
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10639
|
+
}
|
|
10069
10640
|
async function handleToolCall(name, args, ctx) {
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
}
|
|
10641
|
+
let lastError;
|
|
10642
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
10643
|
+
try {
|
|
10644
|
+
const result = await dispatch(name, args, ctx);
|
|
10645
|
+
return {
|
|
10646
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) ?? "{}" }]
|
|
10647
|
+
};
|
|
10648
|
+
} catch (error) {
|
|
10649
|
+
lastError = error;
|
|
10650
|
+
if (isRateLimitError(error) && attempt < MAX_RETRIES) {
|
|
10651
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
10652
|
+
await sleep(delay);
|
|
10653
|
+
continue;
|
|
10654
|
+
}
|
|
10655
|
+
break;
|
|
10656
|
+
}
|
|
10086
10657
|
}
|
|
10658
|
+
logError(
|
|
10659
|
+
`handler:${name}`,
|
|
10660
|
+
lastError instanceof Error ? lastError : new Error(String(lastError))
|
|
10661
|
+
);
|
|
10662
|
+
return {
|
|
10663
|
+
content: [
|
|
10664
|
+
{
|
|
10665
|
+
type: "text",
|
|
10666
|
+
text: JSON.stringify({
|
|
10667
|
+
error: lastError instanceof Error ? lastError.message : String(lastError)
|
|
10668
|
+
})
|
|
10669
|
+
}
|
|
10670
|
+
],
|
|
10671
|
+
isError: true
|
|
10672
|
+
};
|
|
10087
10673
|
}
|
|
10088
10674
|
async function dispatch(name, args, ctx) {
|
|
10089
10675
|
switch (name) {
|
|
@@ -10129,14 +10715,25 @@ async function dispatch(name, args, ctx) {
|
|
|
10129
10715
|
const { handleConfigure: handleConfigure2 } = await Promise.resolve().then(() => (init_configure(), configure_exports));
|
|
10130
10716
|
return handleConfigure2(args, ctx);
|
|
10131
10717
|
}
|
|
10718
|
+
case "gtm_storeleads": {
|
|
10719
|
+
const { handleStoreleads: handleStoreleads2 } = await Promise.resolve().then(() => (init_storeleads2(), storeleads_exports));
|
|
10720
|
+
return handleStoreleads2(args, ctx);
|
|
10721
|
+
}
|
|
10722
|
+
case "gtm_linkedin": {
|
|
10723
|
+
const { handleLinkedin: handleLinkedin2 } = await Promise.resolve().then(() => (init_linkedin(), linkedin_exports));
|
|
10724
|
+
return handleLinkedin2(args, ctx);
|
|
10725
|
+
}
|
|
10132
10726
|
default:
|
|
10133
10727
|
throw new Error(`Unknown tool: ${name}`);
|
|
10134
10728
|
}
|
|
10135
10729
|
}
|
|
10730
|
+
var MAX_RETRIES, BASE_DELAY_MS;
|
|
10136
10731
|
var init_handlers = __esm({
|
|
10137
10732
|
"src/handlers/index.ts"() {
|
|
10138
10733
|
"use strict";
|
|
10139
10734
|
init_dist();
|
|
10735
|
+
MAX_RETRIES = 3;
|
|
10736
|
+
BASE_DELAY_MS = 1e3;
|
|
10140
10737
|
}
|
|
10141
10738
|
});
|
|
10142
10739
|
|
|
@@ -10148,7 +10745,7 @@ var init_prospect2 = __esm({
|
|
|
10148
10745
|
prospectTools = [
|
|
10149
10746
|
{
|
|
10150
10747
|
name: "gtm_find_companies",
|
|
10151
|
-
description: "Discover companies matching ICP criteria.
|
|
10748
|
+
description: "Discover companies matching your ICP criteria. Powered by DiscoLike (60M+ companies from web/SSL crawl data) and optionally Apollo.\n\nDiscoLike strengths:\n- Lookalike discovery: give it 1-10 seed domains, get similar companies\n- ICP text matching: describe your ideal customer in plain English\n- Tech stack filtering: find companies using specific technologies\n- Website content analysis: matches against actual site content\n- Best for: B2B companies, local businesses, professional services, healthcare\n\nFor DTC/e-commerce brands, use gtm_storeleads instead \u2014 it has revenue estimates and category data that neither DiscoLike nor Apollo provides.\n\nTip: DiscoLike lookalike mode is extremely powerful. If you have 2-3 example companies, use the domains parameter to find hundreds of similar ones.\n\nSingle domain lookup: passing just domain (no icp_text, domains, category, tech_stack, or country) does a direct company profile fetch with rich data (growth signals, engagement metrics, tech stack from vendors). This is for researching ONE specific company, not discovery.",
|
|
10152
10749
|
inputSchema: {
|
|
10153
10750
|
type: "object",
|
|
10154
10751
|
properties: {
|
|
@@ -10156,15 +10753,15 @@ var init_prospect2 = __esm({
|
|
|
10156
10753
|
type: "string",
|
|
10157
10754
|
description: 'Natural language ICP description (e.g., "B2B SaaS startups in US, 50-200 employees")'
|
|
10158
10755
|
},
|
|
10756
|
+
domain: {
|
|
10757
|
+
type: "string",
|
|
10758
|
+
description: "Single domain for direct company lookup"
|
|
10759
|
+
},
|
|
10159
10760
|
domains: {
|
|
10160
10761
|
type: "array",
|
|
10161
10762
|
items: { type: "string" },
|
|
10162
10763
|
description: 'Lookalike domains to find similar companies (e.g., ["stripe.com", "plaid.com"])'
|
|
10163
10764
|
},
|
|
10164
|
-
industry: {
|
|
10165
|
-
type: "string",
|
|
10166
|
-
description: 'Industry filter (e.g., "SaaS", "FinTech", "Healthcare")'
|
|
10167
|
-
},
|
|
10168
10765
|
country: {
|
|
10169
10766
|
type: "string",
|
|
10170
10767
|
description: 'Country filter (e.g., "US", "UK", "DE")'
|
|
@@ -10177,13 +10774,39 @@ var init_prospect2 = __esm({
|
|
|
10177
10774
|
type: "number",
|
|
10178
10775
|
description: "Maximum employee count"
|
|
10179
10776
|
},
|
|
10777
|
+
tech_stack: {
|
|
10778
|
+
type: "array",
|
|
10779
|
+
items: { type: "string" },
|
|
10780
|
+
description: 'Filter by technology stack (e.g., ["React", "AWS"])'
|
|
10781
|
+
},
|
|
10782
|
+
category: {
|
|
10783
|
+
type: "string",
|
|
10784
|
+
description: 'Industry category filter (e.g., "SaaS", "FinTech", "Healthcare")'
|
|
10785
|
+
},
|
|
10786
|
+
negate_domains: {
|
|
10787
|
+
type: "array",
|
|
10788
|
+
items: { type: "string" },
|
|
10789
|
+
description: "Exclude these domains from results"
|
|
10790
|
+
},
|
|
10791
|
+
min_digital_footprint: {
|
|
10792
|
+
type: "number",
|
|
10793
|
+
description: "Minimum DiscoLike digital footprint score"
|
|
10794
|
+
},
|
|
10795
|
+
max_digital_footprint: {
|
|
10796
|
+
type: "number",
|
|
10797
|
+
description: "Maximum DiscoLike digital footprint score"
|
|
10798
|
+
},
|
|
10799
|
+
offset: {
|
|
10800
|
+
type: "number",
|
|
10801
|
+
description: "Pagination offset (0-based)"
|
|
10802
|
+
},
|
|
10180
10803
|
limit: {
|
|
10181
10804
|
type: "number",
|
|
10182
|
-
description: "Max results to return (default: 50, max:
|
|
10805
|
+
description: "Max results to return (default: 50, max: 1000)"
|
|
10183
10806
|
},
|
|
10184
10807
|
provider: {
|
|
10185
10808
|
type: "string",
|
|
10186
|
-
description: "Provider override (uses first available
|
|
10809
|
+
description: "Provider override: disco, apollo, storeleads (uses first available if omitted)"
|
|
10187
10810
|
}
|
|
10188
10811
|
},
|
|
10189
10812
|
required: []
|
|
@@ -10191,7 +10814,7 @@ var init_prospect2 = __esm({
|
|
|
10191
10814
|
},
|
|
10192
10815
|
{
|
|
10193
10816
|
name: "gtm_find_people",
|
|
10194
|
-
description: "Find contacts
|
|
10817
|
+
description: "Find contacts at companies by job title, company, or domain. Returns name, title, email (when available), phone, and LinkedIn URL.\n\nPowered by DiscoLike Contacts, Apollo, or BetterContact depending on configuration.\n\nDiscoLike strengths:\n- Domain-based search: pass company domains to find people there\n- Seniority filtering (owner, c_suite, vp, director, manager, etc.)\n- Returns validated emails when available\n- Best for: finding decision-makers at known companies\n\nAfter finding people, use:\n- gtm_enrich to get verified email/phone if not returned\n- gtm_linkedin to check LinkedIn activity and engagement signals",
|
|
10195
10818
|
inputSchema: {
|
|
10196
10819
|
type: "object",
|
|
10197
10820
|
properties: {
|
|
@@ -10211,13 +10834,43 @@ var init_prospect2 = __esm({
|
|
|
10211
10834
|
type: "string",
|
|
10212
10835
|
description: 'Location filter (e.g., "San Francisco", "United States")'
|
|
10213
10836
|
},
|
|
10837
|
+
seniority: {
|
|
10838
|
+
type: "array",
|
|
10839
|
+
items: { type: "string" },
|
|
10840
|
+
description: "Filter by seniority: owner, c_suite, vp, director, manager, senior, entry"
|
|
10841
|
+
},
|
|
10842
|
+
department: {
|
|
10843
|
+
type: "array",
|
|
10844
|
+
items: { type: "string" },
|
|
10845
|
+
description: "Filter by department: engineering, sales, marketing, finance, hr, etc."
|
|
10846
|
+
},
|
|
10847
|
+
has_email: {
|
|
10848
|
+
type: "boolean",
|
|
10849
|
+
description: "Filter to contacts with known email (default: true)"
|
|
10850
|
+
},
|
|
10851
|
+
has_phone: {
|
|
10852
|
+
type: "boolean",
|
|
10853
|
+
description: "Filter to contacts with known phone number"
|
|
10854
|
+
},
|
|
10855
|
+
has_linkedin: {
|
|
10856
|
+
type: "boolean",
|
|
10857
|
+
description: "Filter to contacts with LinkedIn profile"
|
|
10858
|
+
},
|
|
10859
|
+
min_connections: {
|
|
10860
|
+
type: "number",
|
|
10861
|
+
description: "Minimum LinkedIn connections"
|
|
10862
|
+
},
|
|
10863
|
+
offset: {
|
|
10864
|
+
type: "number",
|
|
10865
|
+
description: "Pagination offset (0-based)"
|
|
10866
|
+
},
|
|
10214
10867
|
limit: {
|
|
10215
10868
|
type: "number",
|
|
10216
|
-
description: "Max results to return (default: 25, max:
|
|
10869
|
+
description: "Max results to return (default: 25, max: 1000)"
|
|
10217
10870
|
},
|
|
10218
10871
|
provider: {
|
|
10219
10872
|
type: "string",
|
|
10220
|
-
description: "Provider override (uses first available
|
|
10873
|
+
description: "Provider override: disco, apollo (uses first available if omitted)"
|
|
10221
10874
|
}
|
|
10222
10875
|
},
|
|
10223
10876
|
required: []
|
|
@@ -10235,7 +10888,7 @@ var init_enrich2 = __esm({
|
|
|
10235
10888
|
enrichTools = [
|
|
10236
10889
|
{
|
|
10237
10890
|
name: "gtm_enrich",
|
|
10238
|
-
description: "
|
|
10891
|
+
description: "Email and phone enrichment via waterfall \u2014 tries multiple providers in order, stops on first match. Supports 1-100 contacts per call.\n\nAlways do a dry_run first for batches > 5 to preview cost.\n\nProviders (in configured waterfall order):\n- Prospeo: email finder, good hit rate on LinkedIn-active professionals\n- BetterContact: email + phone, charges only on successful find\n\nInput needs: first_name + last_name minimum. Adding company_domain dramatically improves hit rates. LinkedIn URL is the gold standard input.\n\nTip: If you got contacts from gtm_find_people with DiscoLike and they already have emails, you may not need enrichment \u2014 check the results first.",
|
|
10239
10892
|
inputSchema: {
|
|
10240
10893
|
type: "object",
|
|
10241
10894
|
properties: {
|
|
@@ -10257,9 +10910,12 @@ var init_enrich2 = __esm({
|
|
|
10257
10910
|
},
|
|
10258
10911
|
find: {
|
|
10259
10912
|
type: "array",
|
|
10260
|
-
items: {
|
|
10913
|
+
items: {
|
|
10914
|
+
type: "string",
|
|
10915
|
+
enum: ["email", "phone", "title", "company", "funding", "tech_stack"]
|
|
10916
|
+
},
|
|
10261
10917
|
default: ["email"],
|
|
10262
|
-
description: "
|
|
10918
|
+
description: "Fields to find. Currently supported: email, phone. Additional fields (title, company, funding, tech_stack) are accepted but return an informative error until Phase 2."
|
|
10263
10919
|
},
|
|
10264
10920
|
waterfall: {
|
|
10265
10921
|
type: "array",
|
|
@@ -10626,6 +11282,118 @@ var init_configure2 = __esm({
|
|
|
10626
11282
|
}
|
|
10627
11283
|
});
|
|
10628
11284
|
|
|
11285
|
+
// src/tools/storeleads.ts
|
|
11286
|
+
var storeleadsTools;
|
|
11287
|
+
var init_storeleads3 = __esm({
|
|
11288
|
+
"src/tools/storeleads.ts"() {
|
|
11289
|
+
"use strict";
|
|
11290
|
+
storeleadsTools = [
|
|
11291
|
+
{
|
|
11292
|
+
name: "gtm_storeleads",
|
|
11293
|
+
description: 'Search 1.2M+ active e-commerce stores (primarily Shopify) by category, revenue, country, and more. This is a curated database with estimated revenue data that no other provider has.\n\nWhen to use:\n- DTC, e-commerce, consumer brand, or Shopify queries \u2014 this is your primary source\n- When you need revenue-based filtering (e.g. "$1M-$10M/year brands")\n- When you need category-specific results (Apparel, Beauty, Food & Drink, etc.)\n\nWhen NOT to use:\n- B2B/SaaS companies \u2014 use gtm_find_companies with DiscoLike instead\n- When you need contact/people data \u2014 use gtm_find_people after getting domains\n- When you need lookalike discovery \u2014 use gtm_find_companies with seed domains',
|
|
11294
|
+
inputSchema: {
|
|
11295
|
+
type: "object",
|
|
11296
|
+
properties: {
|
|
11297
|
+
search: {
|
|
11298
|
+
type: "string",
|
|
11299
|
+
description: "Fuzzy text match on name, description, categories"
|
|
11300
|
+
},
|
|
11301
|
+
category: {
|
|
11302
|
+
type: "string",
|
|
11303
|
+
description: 'Filter by category (e.g. "Apparel", "Beauty & Fitness", "Food & Drink")'
|
|
11304
|
+
},
|
|
11305
|
+
country: { type: "string", description: 'ISO country code (e.g. "US", "GB")' },
|
|
11306
|
+
state: { type: "string", description: "US state or equivalent" },
|
|
11307
|
+
min_revenue: {
|
|
11308
|
+
type: "number",
|
|
11309
|
+
description: "Minimum estimated yearly sales in USD"
|
|
11310
|
+
},
|
|
11311
|
+
max_revenue: {
|
|
11312
|
+
type: "number",
|
|
11313
|
+
description: "Maximum estimated yearly sales in USD"
|
|
11314
|
+
},
|
|
11315
|
+
min_employees: { type: "number", description: "Minimum employee count" },
|
|
11316
|
+
max_employees: { type: "number", description: "Maximum employee count" },
|
|
11317
|
+
platform: {
|
|
11318
|
+
type: "string",
|
|
11319
|
+
description: 'E-commerce platform (e.g. "Shopify", "Shopify Plus")'
|
|
11320
|
+
},
|
|
11321
|
+
limit: {
|
|
11322
|
+
type: "number",
|
|
11323
|
+
description: "Results to return (default 50, max 50)"
|
|
11324
|
+
}
|
|
11325
|
+
}
|
|
11326
|
+
}
|
|
11327
|
+
}
|
|
11328
|
+
];
|
|
11329
|
+
}
|
|
11330
|
+
});
|
|
11331
|
+
|
|
11332
|
+
// src/tools/linkedin.ts
|
|
11333
|
+
var linkedinTools;
|
|
11334
|
+
var init_linkedin2 = __esm({
|
|
11335
|
+
"src/tools/linkedin.ts"() {
|
|
11336
|
+
"use strict";
|
|
11337
|
+
linkedinTools = [
|
|
11338
|
+
{
|
|
11339
|
+
name: "gtm_linkedin",
|
|
11340
|
+
description: "LinkedIn intelligence \u2014 profile scraping, post search, and engagement mining. Powered by HarvestAPI.\n\nWhen to use:\n- Scrape a LinkedIn profile for headline, experience, skills, follower count\n- Check if someone is active on LinkedIn (recent posts, engagement levels)\n- Find people who comment on or react to specific posts (warm lead mining)\n- Search LinkedIn posts by keyword to find thought leaders\n- Monitor competitor or industry content for engagers\n\nWhen NOT to use:\n- Finding people by job title at a company \u2014 use gtm_find_people instead\n- Email enrichment \u2014 use gtm_enrich instead\n- Non-LinkedIn web scraping \u2014 use gtm_extract instead\n\nPairs well with:\n- gtm_find_people \u2192 gtm_linkedin (find contacts, then check LinkedIn activity)\n- gtm_linkedin engagement \u2192 gtm_enrich (find engagers, then get emails)\n- gtm_storeleads \u2192 gtm_find_people \u2192 gtm_linkedin (DTC brands \u2192 CEOs \u2192 activity check)\n\nNote: HarvestAPI charges per request. For engagement mining, each post lookup counts as a request. Use limit to control costs.",
|
|
11341
|
+
inputSchema: {
|
|
11342
|
+
type: "object",
|
|
11343
|
+
required: ["action"],
|
|
11344
|
+
properties: {
|
|
11345
|
+
action: {
|
|
11346
|
+
type: "string",
|
|
11347
|
+
enum: ["profile", "posts", "engagement", "company_posts", "search_posts"],
|
|
11348
|
+
description: "Action to perform"
|
|
11349
|
+
},
|
|
11350
|
+
linkedin_url: {
|
|
11351
|
+
type: "string",
|
|
11352
|
+
description: "LinkedIn profile URL (required for profile action)"
|
|
11353
|
+
},
|
|
11354
|
+
find_email: {
|
|
11355
|
+
type: "boolean",
|
|
11356
|
+
description: "Find email via SMTP verification \u2014 uses extra credits (profile action only)"
|
|
11357
|
+
},
|
|
11358
|
+
profile: {
|
|
11359
|
+
type: "string",
|
|
11360
|
+
description: "LinkedIn URL or publicIdentifier (required for posts action)"
|
|
11361
|
+
},
|
|
11362
|
+
company: {
|
|
11363
|
+
type: "string",
|
|
11364
|
+
description: "Company LinkedIn URL or universalName (required for company_posts action)"
|
|
11365
|
+
},
|
|
11366
|
+
posted_within: {
|
|
11367
|
+
type: "string",
|
|
11368
|
+
enum: ["24h", "week", "month"],
|
|
11369
|
+
description: "Filter posts by recency"
|
|
11370
|
+
},
|
|
11371
|
+
post_url: {
|
|
11372
|
+
type: "string",
|
|
11373
|
+
description: "LinkedIn post URL (required for engagement action)"
|
|
11374
|
+
},
|
|
11375
|
+
engagement_type: {
|
|
11376
|
+
type: "string",
|
|
11377
|
+
enum: ["comments", "reactions", "both"],
|
|
11378
|
+
description: "Type of engagement to retrieve (default: both)"
|
|
11379
|
+
},
|
|
11380
|
+
query: {
|
|
11381
|
+
type: "string",
|
|
11382
|
+
description: "Keyword search (required for search_posts action)"
|
|
11383
|
+
},
|
|
11384
|
+
sort: {
|
|
11385
|
+
type: "string",
|
|
11386
|
+
enum: ["relevance", "date"],
|
|
11387
|
+
description: "Sort order for search_posts"
|
|
11388
|
+
},
|
|
11389
|
+
limit: { type: "number", description: "Max results to return (max 100)" }
|
|
11390
|
+
}
|
|
11391
|
+
}
|
|
11392
|
+
}
|
|
11393
|
+
];
|
|
11394
|
+
}
|
|
11395
|
+
});
|
|
11396
|
+
|
|
10629
11397
|
// src/tools/index.ts
|
|
10630
11398
|
var capabilityTools, guideTool, toolHelpTool, executeTool, metaTools, tools, toolsByName, toolCategories, workflowRecipes;
|
|
10631
11399
|
var init_tools = __esm({
|
|
@@ -10641,6 +11409,8 @@ var init_tools = __esm({
|
|
|
10641
11409
|
init_crm2();
|
|
10642
11410
|
init_status2();
|
|
10643
11411
|
init_configure2();
|
|
11412
|
+
init_storeleads3();
|
|
11413
|
+
init_linkedin2();
|
|
10644
11414
|
init_prospect2();
|
|
10645
11415
|
init_enrich2();
|
|
10646
11416
|
init_extract2();
|
|
@@ -10651,6 +11421,8 @@ var init_tools = __esm({
|
|
|
10651
11421
|
init_crm2();
|
|
10652
11422
|
init_status2();
|
|
10653
11423
|
init_configure2();
|
|
11424
|
+
init_storeleads3();
|
|
11425
|
+
init_linkedin2();
|
|
10654
11426
|
capabilityTools = [
|
|
10655
11427
|
...prospectTools,
|
|
10656
11428
|
...enrichTools,
|
|
@@ -10661,7 +11433,9 @@ var init_tools = __esm({
|
|
|
10661
11433
|
...outreachTools,
|
|
10662
11434
|
...crmTools,
|
|
10663
11435
|
...statusTools,
|
|
10664
|
-
...configureTools
|
|
11436
|
+
...configureTools,
|
|
11437
|
+
...storeleadsTools,
|
|
11438
|
+
...linkedinTools
|
|
10665
11439
|
];
|
|
10666
11440
|
guideTool = {
|
|
10667
11441
|
name: "gtm_guide",
|
|
@@ -10731,28 +11505,46 @@ var init_tools = __esm({
|
|
|
10731
11505
|
crm: crmTools.map((t) => t.name),
|
|
10732
11506
|
status: statusTools.map((t) => t.name),
|
|
10733
11507
|
configure: configureTools.map((t) => t.name),
|
|
11508
|
+
storeleads: storeleadsTools.map((t) => t.name),
|
|
11509
|
+
linkedin: linkedinTools.map((t) => t.name),
|
|
10734
11510
|
meta: metaTools.map((t) => t.name)
|
|
10735
11511
|
};
|
|
10736
11512
|
workflowRecipes = {
|
|
10737
11513
|
build_tam: `# Build a TAM List \u2014 Workflow
|
|
10738
11514
|
|
|
10739
|
-
|
|
11515
|
+
Start by identifying what kind of companies you're targeting:
|
|
10740
11516
|
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
2.
|
|
11517
|
+
## DTC / E-commerce / Consumer Brands
|
|
11518
|
+
1. gtm_storeleads \u2014 filter by category, revenue, country
|
|
11519
|
+
\u2192 gtm_storeleads({ category: "Beauty", min_revenue: 1000000, country: "US" })
|
|
11520
|
+
2. gtm_find_people \u2014 find decision-makers at those domains
|
|
10745
11521
|
\u2192 gtm_find_people({ company_domain: "...", job_title: "CEO" })
|
|
10746
|
-
|
|
10747
|
-
3. ENRICH \u2014 Get email addresses (DRY RUN FIRST for batches > 5)
|
|
11522
|
+
3. gtm_enrich (dry_run first) \u2014 get verified emails
|
|
10748
11523
|
\u2192 gtm_enrich({ contacts: [...], find: ["email"], dry_run: true })
|
|
10749
|
-
|
|
11524
|
+
4. Optional: gtm_linkedin \u2014 check if they're active on LinkedIn
|
|
11525
|
+
\u2192 gtm_linkedin({ action: "profile", linkedin_url: "..." })
|
|
10750
11526
|
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
11527
|
+
## B2B / SaaS / Services with websites
|
|
11528
|
+
1. gtm_find_companies \u2014 use DiscoLike ICP text or lookalikes
|
|
11529
|
+
\u2192 gtm_find_companies({ icp_text: "...", limit: 100 })
|
|
11530
|
+
2. gtm_find_people \u2014 find contacts by title
|
|
11531
|
+
3. gtm_enrich \u2014 get verified emails
|
|
11532
|
+
4. gtm_campaign or gtm_outreach \u2014 push to campaigns
|
|
11533
|
+
|
|
11534
|
+
## LinkedIn-native prospects
|
|
11535
|
+
1. gtm_find_people \u2014 search by title + company
|
|
11536
|
+
2. gtm_linkedin \u2014 scrape profiles for activity signals
|
|
11537
|
+
\u2192 gtm_linkedin({ action: "profile", linkedin_url: "..." })
|
|
11538
|
+
3. gtm_enrich \u2014 get emails for active prospects
|
|
11539
|
+
4. gtm_outreach \u2014 LinkedIn campaign, or gtm_campaign for email
|
|
11540
|
+
|
|
11541
|
+
## Engagement-based (warm leads)
|
|
11542
|
+
1. gtm_linkedin (search_posts) \u2014 find relevant content
|
|
11543
|
+
\u2192 gtm_linkedin({ action: "search_posts", query: "...", sort: "date" })
|
|
11544
|
+
2. gtm_linkedin (engagement) \u2014 extract commenters/reactors
|
|
11545
|
+
\u2192 gtm_linkedin({ action: "engagement", post_url: "...", engagement_type: "both" })
|
|
11546
|
+
3. gtm_enrich \u2014 get emails
|
|
11547
|
+
4. gtm_campaign \u2014 personalized outreach referencing their engagement
|
|
10756
11548
|
|
|
10757
11549
|
## Key principle
|
|
10758
11550
|
Always dry_run enrichment first. Credits are spent per successful find \u2014 previewing cost avoids surprises.`,
|
|
@@ -10833,12 +11625,16 @@ Always search before creating to avoid duplicates. Most CRMs have built-in email
|
|
|
10833
11625
|
|
|
10834
11626
|
Call gtm_guide with one of these tasks:
|
|
10835
11627
|
|
|
10836
|
-
- build_tam \u2014
|
|
11628
|
+
- build_tam \u2014 Build a TAM list (DTC/e-commerce, B2B, LinkedIn-native, or engagement-based paths)
|
|
10837
11629
|
- enrich_list \u2014 Take a contact list and enrich with email/phone via waterfall
|
|
10838
11630
|
- cold_outreach \u2014 Enrich + personalize + push to cold email campaign
|
|
10839
11631
|
- linkedin_outreach \u2014 Extract profiles + personalize + push to LinkedIn campaign
|
|
10840
11632
|
- crm_sync \u2014 Find/create/update people and deals in CRM
|
|
10841
11633
|
|
|
11634
|
+
New tools available:
|
|
11635
|
+
- gtm_storeleads \u2014 Search 1.2M+ e-commerce stores by category, revenue, platform
|
|
11636
|
+
- gtm_linkedin \u2014 LinkedIn profile scraping, post search, engagement mining
|
|
11637
|
+
|
|
10842
11638
|
For any task: always dry_run enrichment batches > 5 to preview cost before executing.`
|
|
10843
11639
|
};
|
|
10844
11640
|
}
|
|
@@ -10890,22 +11686,84 @@ var init_validation = __esm({
|
|
|
10890
11686
|
gtm_find_companies: z2.object({
|
|
10891
11687
|
icp_text: z2.string().optional(),
|
|
10892
11688
|
domain: z2.string().optional(),
|
|
11689
|
+
domains: z2.array(z2.string()).optional(),
|
|
10893
11690
|
limit: z2.number().int().min(1).max(1e3).optional(),
|
|
10894
11691
|
country: z2.string().optional(),
|
|
10895
11692
|
min_employees: z2.number().int().min(0).optional(),
|
|
10896
|
-
max_employees: z2.number().int().min(1).optional()
|
|
11693
|
+
max_employees: z2.number().int().min(1).optional(),
|
|
11694
|
+
tech_stack: z2.array(z2.string()).optional(),
|
|
11695
|
+
category: z2.string().optional(),
|
|
11696
|
+
negate_domains: z2.array(z2.string()).optional(),
|
|
11697
|
+
min_digital_footprint: z2.number().min(0).optional(),
|
|
11698
|
+
max_digital_footprint: z2.number().min(0).optional(),
|
|
11699
|
+
offset: z2.number().int().min(0).optional(),
|
|
11700
|
+
provider: z2.string().optional()
|
|
10897
11701
|
}),
|
|
10898
11702
|
// 2. People/contact search
|
|
10899
11703
|
gtm_find_people: z2.object({
|
|
10900
11704
|
job_title: z2.string().optional(),
|
|
10901
11705
|
company_name: z2.string().optional(),
|
|
10902
11706
|
company_domain: z2.string().optional(),
|
|
10903
|
-
|
|
11707
|
+
location: z2.string().optional(),
|
|
11708
|
+
limit: z2.number().int().min(1).max(1e3).optional(),
|
|
11709
|
+
seniority: z2.array(z2.string()).optional(),
|
|
11710
|
+
department: z2.array(z2.string()).optional(),
|
|
11711
|
+
has_email: z2.boolean().optional(),
|
|
11712
|
+
has_phone: z2.boolean().optional(),
|
|
11713
|
+
has_linkedin: z2.boolean().optional(),
|
|
11714
|
+
min_connections: z2.number().int().min(0).optional(),
|
|
11715
|
+
offset: z2.number().int().min(0).optional(),
|
|
11716
|
+
provider: z2.string().optional()
|
|
10904
11717
|
}),
|
|
11718
|
+
// StorLeads — DTC/e-commerce company search
|
|
11719
|
+
gtm_storeleads: z2.object({
|
|
11720
|
+
search: z2.string().optional(),
|
|
11721
|
+
category: z2.string().optional(),
|
|
11722
|
+
country: z2.string().optional(),
|
|
11723
|
+
state: z2.string().optional(),
|
|
11724
|
+
min_revenue: z2.number().min(0).optional(),
|
|
11725
|
+
max_revenue: z2.number().min(0).optional(),
|
|
11726
|
+
min_employees: z2.number().int().min(0).optional(),
|
|
11727
|
+
max_employees: z2.number().int().min(1).optional(),
|
|
11728
|
+
platform: z2.string().optional(),
|
|
11729
|
+
limit: z2.number().int().min(1).max(500).optional()
|
|
11730
|
+
}),
|
|
11731
|
+
// LinkedIn intelligence — HarvestAPI
|
|
11732
|
+
gtm_linkedin: z2.object({
|
|
11733
|
+
action: z2.enum(["profile", "posts", "engagement", "company_posts", "search_posts"]),
|
|
11734
|
+
linkedin_url: z2.string().optional(),
|
|
11735
|
+
find_email: z2.boolean().optional(),
|
|
11736
|
+
profile: z2.string().optional(),
|
|
11737
|
+
company: z2.string().optional(),
|
|
11738
|
+
posted_within: z2.enum(["24h", "week", "month"]).optional(),
|
|
11739
|
+
post_url: z2.string().optional(),
|
|
11740
|
+
engagement_type: z2.enum(["comments", "reactions", "both"]).optional(),
|
|
11741
|
+
query: z2.string().optional(),
|
|
11742
|
+
sort: z2.enum(["relevance", "date"]).optional(),
|
|
11743
|
+
limit: z2.number().int().min(1).max(100).optional()
|
|
11744
|
+
}).refine(
|
|
11745
|
+
(data) => {
|
|
11746
|
+
switch (data.action) {
|
|
11747
|
+
case "profile":
|
|
11748
|
+
return !!data.linkedin_url;
|
|
11749
|
+
case "engagement":
|
|
11750
|
+
return !!data.post_url;
|
|
11751
|
+
case "search_posts":
|
|
11752
|
+
return !!data.query;
|
|
11753
|
+
case "posts":
|
|
11754
|
+
return !!data.profile;
|
|
11755
|
+
case "company_posts":
|
|
11756
|
+
return !!data.company;
|
|
11757
|
+
default:
|
|
11758
|
+
return true;
|
|
11759
|
+
}
|
|
11760
|
+
},
|
|
11761
|
+
{ message: "Missing required field for the specified action" }
|
|
11762
|
+
),
|
|
10905
11763
|
// 3. Enrichment waterfall
|
|
10906
11764
|
gtm_enrich: z2.object({
|
|
10907
11765
|
contacts: z2.array(contactInputSchema).min(1, "contacts must contain at least 1 contact").max(100, "contacts must contain at most 100 contacts"),
|
|
10908
|
-
find: z2.array(z2.enum(["email", "phone"])).optional(),
|
|
11766
|
+
find: z2.array(z2.enum(["email", "phone", "title", "company", "funding", "tech_stack"])).optional(),
|
|
10909
11767
|
waterfall: z2.array(z2.string()).optional(),
|
|
10910
11768
|
verify: z2.boolean().optional(),
|
|
10911
11769
|
dry_run: z2.boolean().optional()
|