gtm-now 0.9.0 → 0.10.1
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 +784 -381
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10339,7 +10339,7 @@ var init_harvest = __esm({
|
|
|
10339
10339
|
HarvestApiAdapter = class {
|
|
10340
10340
|
name = "harvest";
|
|
10341
10341
|
capabilities = ["extract_linkedin"];
|
|
10342
|
-
notes = "Best for: LinkedIn profile scraping, post search, engagement mining, activity signals. Called
|
|
10342
|
+
notes = "Best for: LinkedIn profile scraping, post search, engagement mining, activity signals. Called by gtm_extract for LinkedIn types.";
|
|
10343
10343
|
client;
|
|
10344
10344
|
credentialsCached = null;
|
|
10345
10345
|
constructor(apiKey) {
|
|
@@ -10621,7 +10621,7 @@ var init_prospect = __esm({
|
|
|
10621
10621
|
prospectTools = [
|
|
10622
10622
|
{
|
|
10623
10623
|
name: "gtm_find_companies",
|
|
10624
|
-
description:
|
|
10624
|
+
description: "Discover companies matching ICP criteria via DiscoLike, Apollo, or StoreLeads. Supports lookalike discovery, ICP text, tech stack filtering. Use gtm_tool_help for full parameter details.",
|
|
10625
10625
|
inputSchema: {
|
|
10626
10626
|
type: "object",
|
|
10627
10627
|
properties: {
|
|
@@ -10713,8 +10713,13 @@ var init_prospect = __esm({
|
|
|
10713
10713
|
},
|
|
10714
10714
|
output: {
|
|
10715
10715
|
type: "string",
|
|
10716
|
-
enum: ["file", "inline"],
|
|
10717
|
-
description: 'Output mode. "file" writes
|
|
10716
|
+
enum: ["file", "inline", "csv"],
|
|
10717
|
+
description: 'Output mode. "file" writes JSONL (auto when >20 results). "csv" writes a CSV file for spreadsheet review. "inline" returns in response.'
|
|
10718
|
+
},
|
|
10719
|
+
detail: {
|
|
10720
|
+
type: "string",
|
|
10721
|
+
enum: ["summary", "full"],
|
|
10722
|
+
description: 'Response detail level. "summary" (default) returns slim results. "full" includes applied_filters and all metadata.'
|
|
10718
10723
|
}
|
|
10719
10724
|
},
|
|
10720
10725
|
required: []
|
|
@@ -10722,7 +10727,7 @@ var init_prospect = __esm({
|
|
|
10722
10727
|
},
|
|
10723
10728
|
{
|
|
10724
10729
|
name: "gtm_find_people",
|
|
10725
|
-
description: 'Find contacts
|
|
10730
|
+
description: 'Find contacts by job title, company, or domain. Returns name, title, email, LinkedIn URL. Slim by default; pass fields: "all" for full payload.',
|
|
10726
10731
|
inputSchema: {
|
|
10727
10732
|
type: "object",
|
|
10728
10733
|
properties: {
|
|
@@ -10820,8 +10825,13 @@ var init_prospect = __esm({
|
|
|
10820
10825
|
},
|
|
10821
10826
|
output: {
|
|
10822
10827
|
type: "string",
|
|
10823
|
-
enum: ["file", "inline"],
|
|
10824
|
-
description: 'Output mode. "file" writes
|
|
10828
|
+
enum: ["file", "inline", "csv"],
|
|
10829
|
+
description: 'Output mode. "file" writes JSONL (auto when >20 results). "csv" writes a CSV file for spreadsheet review. "inline" returns in response.'
|
|
10830
|
+
},
|
|
10831
|
+
detail: {
|
|
10832
|
+
type: "string",
|
|
10833
|
+
enum: ["summary", "full"],
|
|
10834
|
+
description: 'Response detail level. "summary" (default) returns slim results. "full" includes applied_filters and all metadata.'
|
|
10825
10835
|
}
|
|
10826
10836
|
},
|
|
10827
10837
|
required: []
|
|
@@ -10839,7 +10849,7 @@ var init_enrich = __esm({
|
|
|
10839
10849
|
enrichTools = [
|
|
10840
10850
|
{
|
|
10841
10851
|
name: "gtm_enrich",
|
|
10842
|
-
description:
|
|
10852
|
+
description: 'Email enrichment via waterfall \u2014 tries providers in order, stops on first match. Always dry_run first for batches > 5. Defaults to email only. Phone costs extra \u2014 only add find: ["email", "phone"] when the user explicitly requests phone numbers.',
|
|
10843
10853
|
inputSchema: {
|
|
10844
10854
|
type: "object",
|
|
10845
10855
|
properties: {
|
|
@@ -10866,7 +10876,7 @@ var init_enrich = __esm({
|
|
|
10866
10876
|
enum: ["email", "phone", "title", "company", "funding", "tech_stack"]
|
|
10867
10877
|
},
|
|
10868
10878
|
default: ["email"],
|
|
10869
|
-
description:
|
|
10879
|
+
description: 'Fields to find. Defaults to ["email"] only. Add "phone" ONLY when explicitly requested \u2014 phone lookups cost extra. Additional fields (title, company, funding, tech_stack) return an informative error until Phase 2.'
|
|
10870
10880
|
},
|
|
10871
10881
|
waterfall: {
|
|
10872
10882
|
type: "array",
|
|
@@ -10874,7 +10884,12 @@ var init_enrich = __esm({
|
|
|
10874
10884
|
description: "Provider order override (uses client default if omitted)"
|
|
10875
10885
|
},
|
|
10876
10886
|
verify: { type: "boolean", default: true },
|
|
10877
|
-
dry_run: { type: "boolean", default: false }
|
|
10887
|
+
dry_run: { type: "boolean", default: false },
|
|
10888
|
+
detail: {
|
|
10889
|
+
type: "string",
|
|
10890
|
+
enum: ["summary", "full"],
|
|
10891
|
+
description: 'Response detail level. "summary" (default) returns email/phone/providers per contact. "full" includes waterfall attempt details (provider, duration, errors) per contact.'
|
|
10892
|
+
}
|
|
10878
10893
|
},
|
|
10879
10894
|
required: ["contacts"]
|
|
10880
10895
|
}
|
|
@@ -10905,30 +10920,78 @@ var init_extract = __esm({
|
|
|
10905
10920
|
extractTools = [
|
|
10906
10921
|
{
|
|
10907
10922
|
name: "gtm_extract",
|
|
10908
|
-
description: "Extract
|
|
10923
|
+
description: "Extract data from LinkedIn (profiles, posts, engagement, search) or web pages. HarvestAPI charges per request.",
|
|
10909
10924
|
inputSchema: {
|
|
10910
10925
|
type: "object",
|
|
10926
|
+
required: ["type"],
|
|
10911
10927
|
properties: {
|
|
10928
|
+
type: {
|
|
10929
|
+
type: "string",
|
|
10930
|
+
enum: [
|
|
10931
|
+
"linkedin_profile",
|
|
10932
|
+
"linkedin_posts",
|
|
10933
|
+
"linkedin_engagement",
|
|
10934
|
+
"linkedin_search",
|
|
10935
|
+
"linkedin_company_posts",
|
|
10936
|
+
"web_page"
|
|
10937
|
+
],
|
|
10938
|
+
description: "Content type to extract"
|
|
10939
|
+
},
|
|
10912
10940
|
url: {
|
|
10913
10941
|
type: "string",
|
|
10914
|
-
description: "URL to extract data from (
|
|
10942
|
+
description: "URL to extract data from (required for web_page)"
|
|
10915
10943
|
},
|
|
10916
|
-
|
|
10944
|
+
linkedin_url: {
|
|
10917
10945
|
type: "string",
|
|
10918
|
-
|
|
10919
|
-
description: "Content type (auto-detected from URL if omitted)"
|
|
10946
|
+
description: "LinkedIn profile URL (required for linkedin_profile)"
|
|
10920
10947
|
},
|
|
10948
|
+
find_email: {
|
|
10949
|
+
type: "boolean",
|
|
10950
|
+
description: "Find email via SMTP verification \u2014 uses extra credits (linkedin_profile only)"
|
|
10951
|
+
},
|
|
10952
|
+
profile: {
|
|
10953
|
+
type: "string",
|
|
10954
|
+
description: "LinkedIn URL or publicIdentifier (required for linkedin_posts)"
|
|
10955
|
+
},
|
|
10956
|
+
company: {
|
|
10957
|
+
type: "string",
|
|
10958
|
+
description: "Company LinkedIn URL or universalName (required for linkedin_company_posts)"
|
|
10959
|
+
},
|
|
10960
|
+
posted_within: {
|
|
10961
|
+
type: "string",
|
|
10962
|
+
enum: ["24h", "week", "month"],
|
|
10963
|
+
description: "Filter posts by recency (linkedin_posts, linkedin_search, linkedin_company_posts)"
|
|
10964
|
+
},
|
|
10965
|
+
post_url: {
|
|
10966
|
+
type: "string",
|
|
10967
|
+
description: "LinkedIn post URL (required for linkedin_engagement)"
|
|
10968
|
+
},
|
|
10969
|
+
engagement_type: {
|
|
10970
|
+
type: "string",
|
|
10971
|
+
enum: ["comments", "reactions", "both"],
|
|
10972
|
+
description: "Type of engagement to retrieve (default: both)"
|
|
10973
|
+
},
|
|
10974
|
+
query: {
|
|
10975
|
+
type: "string",
|
|
10976
|
+
description: "Keyword search (required for linkedin_search)"
|
|
10977
|
+
},
|
|
10978
|
+
sort: {
|
|
10979
|
+
type: "string",
|
|
10980
|
+
enum: ["relevance", "date"],
|
|
10981
|
+
description: "Sort order for linkedin_search"
|
|
10982
|
+
},
|
|
10983
|
+
limit: { type: "number", description: "Max results to return (max 100)" },
|
|
10921
10984
|
fields: {
|
|
10922
10985
|
type: "array",
|
|
10923
10986
|
items: { type: "string" },
|
|
10924
10987
|
description: 'Specific fields to extract (e.g., ["name", "title", "company", "about"]). Extracts all available fields if omitted.'
|
|
10925
10988
|
},
|
|
10926
|
-
|
|
10989
|
+
detail: {
|
|
10927
10990
|
type: "string",
|
|
10928
|
-
|
|
10991
|
+
enum: ["summary", "full"],
|
|
10992
|
+
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.'
|
|
10929
10993
|
}
|
|
10930
|
-
}
|
|
10931
|
-
required: ["url"]
|
|
10994
|
+
}
|
|
10932
10995
|
}
|
|
10933
10996
|
}
|
|
10934
10997
|
];
|
|
@@ -10964,6 +11027,11 @@ var init_fetch = __esm({
|
|
|
10964
11027
|
params: {
|
|
10965
11028
|
type: "object",
|
|
10966
11029
|
description: "URL query parameters"
|
|
11030
|
+
},
|
|
11031
|
+
detail: {
|
|
11032
|
+
type: "string",
|
|
11033
|
+
enum: ["summary", "full"],
|
|
11034
|
+
description: 'Response detail level. "summary" (default) omits response headers and truncates large bodies. "full" returns complete response including all headers.'
|
|
10967
11035
|
}
|
|
10968
11036
|
},
|
|
10969
11037
|
required: ["url"]
|
|
@@ -11017,7 +11085,7 @@ var init_campaign = __esm({
|
|
|
11017
11085
|
campaignTools = [
|
|
11018
11086
|
{
|
|
11019
11087
|
name: "gtm_campaign",
|
|
11020
|
-
description: "Manage cold email campaigns. List
|
|
11088
|
+
description: "Manage cold email and LinkedIn outreach campaigns. List, stats, add_leads, or create. Supports PlusVibe, Instantly, SmartLead, HeyReach.",
|
|
11021
11089
|
inputSchema: {
|
|
11022
11090
|
type: "object",
|
|
11023
11091
|
properties: {
|
|
@@ -11035,7 +11103,14 @@ var init_campaign = __esm({
|
|
|
11035
11103
|
items: {
|
|
11036
11104
|
type: "object",
|
|
11037
11105
|
properties: {
|
|
11038
|
-
email: {
|
|
11106
|
+
email: {
|
|
11107
|
+
type: "string",
|
|
11108
|
+
description: "Email address (required for email campaigns)"
|
|
11109
|
+
},
|
|
11110
|
+
linkedin_url: {
|
|
11111
|
+
type: "string",
|
|
11112
|
+
description: "LinkedIn profile URL (required for LinkedIn outreach campaigns)"
|
|
11113
|
+
},
|
|
11039
11114
|
first_name: { type: "string" },
|
|
11040
11115
|
last_name: { type: "string" },
|
|
11041
11116
|
company: { type: "string" },
|
|
@@ -11043,10 +11118,9 @@ var init_campaign = __esm({
|
|
|
11043
11118
|
type: "object",
|
|
11044
11119
|
description: "Custom merge fields for email personalization"
|
|
11045
11120
|
}
|
|
11046
|
-
}
|
|
11047
|
-
required: ["email"]
|
|
11121
|
+
}
|
|
11048
11122
|
},
|
|
11049
|
-
description: "Leads to add (required for add_leads action)"
|
|
11123
|
+
description: "Leads to add (required for add_leads action). Each lead needs at least email or linkedin_url."
|
|
11050
11124
|
},
|
|
11051
11125
|
name: {
|
|
11052
11126
|
type: "string",
|
|
@@ -11054,54 +11128,12 @@ var init_campaign = __esm({
|
|
|
11054
11128
|
},
|
|
11055
11129
|
provider: {
|
|
11056
11130
|
type: "string",
|
|
11057
|
-
description: "Provider override (
|
|
11058
|
-
}
|
|
11059
|
-
},
|
|
11060
|
-
required: ["action"]
|
|
11061
|
-
}
|
|
11062
|
-
}
|
|
11063
|
-
];
|
|
11064
|
-
}
|
|
11065
|
-
});
|
|
11066
|
-
|
|
11067
|
-
// src/tools/outreach.ts
|
|
11068
|
-
var outreachTools;
|
|
11069
|
-
var init_outreach = __esm({
|
|
11070
|
-
"src/tools/outreach.ts"() {
|
|
11071
|
-
"use strict";
|
|
11072
|
-
outreachTools = [
|
|
11073
|
-
{
|
|
11074
|
-
name: "gtm_outreach",
|
|
11075
|
-
description: "Manage LinkedIn outreach campaigns. List campaigns, get stats, or add leads to a LinkedIn outreach campaign. Leads require a LinkedIn profile URL.",
|
|
11076
|
-
inputSchema: {
|
|
11077
|
-
type: "object",
|
|
11078
|
-
properties: {
|
|
11079
|
-
action: {
|
|
11080
|
-
type: "string",
|
|
11081
|
-
enum: ["list", "stats", "add_leads"],
|
|
11082
|
-
description: "Action to perform: list (all campaigns), stats (campaign metrics), add_leads (push contacts)"
|
|
11083
|
-
},
|
|
11084
|
-
campaign_id: {
|
|
11085
|
-
type: "string",
|
|
11086
|
-
description: "Campaign ID (required for stats and add_leads)"
|
|
11087
|
-
},
|
|
11088
|
-
leads: {
|
|
11089
|
-
type: "array",
|
|
11090
|
-
items: {
|
|
11091
|
-
type: "object",
|
|
11092
|
-
properties: {
|
|
11093
|
-
linkedin_url: { type: "string" },
|
|
11094
|
-
first_name: { type: "string" },
|
|
11095
|
-
last_name: { type: "string" },
|
|
11096
|
-
company: { type: "string" }
|
|
11097
|
-
},
|
|
11098
|
-
required: ["linkedin_url"]
|
|
11099
|
-
},
|
|
11100
|
-
description: "Leads to add (required for add_leads action). Each lead must have a LinkedIn profile URL."
|
|
11131
|
+
description: "Provider override (auto-selects based on available campaign_email or outreach_linkedin provider if omitted)"
|
|
11101
11132
|
},
|
|
11102
|
-
|
|
11133
|
+
detail: {
|
|
11103
11134
|
type: "string",
|
|
11104
|
-
|
|
11135
|
+
enum: ["summary", "full"],
|
|
11136
|
+
description: 'Response detail level. "summary" (default) returns key stats only. "full" returns all campaign data.'
|
|
11105
11137
|
}
|
|
11106
11138
|
},
|
|
11107
11139
|
required: ["action"]
|
|
@@ -11177,7 +11209,7 @@ var init_status = __esm({
|
|
|
11177
11209
|
statusTools = [
|
|
11178
11210
|
{
|
|
11179
11211
|
name: "gtm_status",
|
|
11180
|
-
description: "Check provider health, credit balances, and active configuration.
|
|
11212
|
+
description: "Check provider health, credit balances, and active configuration.",
|
|
11181
11213
|
inputSchema: {
|
|
11182
11214
|
type: "object",
|
|
11183
11215
|
properties: {
|
|
@@ -11190,6 +11222,11 @@ var init_status = __esm({
|
|
|
11190
11222
|
provider: {
|
|
11191
11223
|
type: "string",
|
|
11192
11224
|
description: "Check a specific provider only (checks all configured providers if omitted)"
|
|
11225
|
+
},
|
|
11226
|
+
detail: {
|
|
11227
|
+
type: "string",
|
|
11228
|
+
enum: ["summary", "full"],
|
|
11229
|
+
description: 'Response detail level. "summary" (default) returns provider name, health, and credits only. "full" includes capabilities, notes, and unconfigured tool details.'
|
|
11193
11230
|
}
|
|
11194
11231
|
},
|
|
11195
11232
|
required: []
|
|
@@ -11237,71 +11274,127 @@ var init_configure = __esm({
|
|
|
11237
11274
|
}
|
|
11238
11275
|
});
|
|
11239
11276
|
|
|
11240
|
-
// src/tools/
|
|
11241
|
-
var
|
|
11242
|
-
var
|
|
11243
|
-
"src/tools/
|
|
11277
|
+
// src/tools/feedback.ts
|
|
11278
|
+
var feedbackTools;
|
|
11279
|
+
var init_feedback = __esm({
|
|
11280
|
+
"src/tools/feedback.ts"() {
|
|
11244
11281
|
"use strict";
|
|
11245
|
-
|
|
11282
|
+
feedbackTools = [
|
|
11246
11283
|
{
|
|
11247
|
-
name: "
|
|
11248
|
-
description: "
|
|
11284
|
+
name: "gtm_feedback",
|
|
11285
|
+
description: "Submit feedback, bug reports, feature requests, or provider requests. Use this to report issues with gtm-now or request new data providers and capabilities. Feedback is stored for the gtm-now maintainers to review.",
|
|
11249
11286
|
inputSchema: {
|
|
11250
11287
|
type: "object",
|
|
11251
|
-
required: ["action"],
|
|
11252
11288
|
properties: {
|
|
11253
|
-
|
|
11254
|
-
type: "string",
|
|
11255
|
-
enum: ["profile", "posts", "engagement", "company_posts", "search_posts"],
|
|
11256
|
-
description: "Action to perform"
|
|
11257
|
-
},
|
|
11258
|
-
linkedin_url: {
|
|
11259
|
-
type: "string",
|
|
11260
|
-
description: "LinkedIn profile URL (required for profile action)"
|
|
11261
|
-
},
|
|
11262
|
-
find_email: {
|
|
11263
|
-
type: "boolean",
|
|
11264
|
-
description: "Find email via SMTP verification \u2014 uses extra credits (profile action only)"
|
|
11265
|
-
},
|
|
11266
|
-
profile: {
|
|
11267
|
-
type: "string",
|
|
11268
|
-
description: "LinkedIn URL or publicIdentifier (required for posts action)"
|
|
11269
|
-
},
|
|
11270
|
-
company: {
|
|
11271
|
-
type: "string",
|
|
11272
|
-
description: "Company LinkedIn URL or universalName (required for company_posts action)"
|
|
11273
|
-
},
|
|
11274
|
-
posted_within: {
|
|
11275
|
-
type: "string",
|
|
11276
|
-
enum: ["24h", "week", "month"],
|
|
11277
|
-
description: "Filter posts by recency"
|
|
11278
|
-
},
|
|
11279
|
-
post_url: {
|
|
11280
|
-
type: "string",
|
|
11281
|
-
description: "LinkedIn post URL (required for engagement action)"
|
|
11282
|
-
},
|
|
11283
|
-
engagement_type: {
|
|
11284
|
-
type: "string",
|
|
11285
|
-
enum: ["comments", "reactions", "both"],
|
|
11286
|
-
description: "Type of engagement to retrieve (default: both)"
|
|
11287
|
-
},
|
|
11288
|
-
query: {
|
|
11289
|
+
type: {
|
|
11289
11290
|
type: "string",
|
|
11290
|
-
|
|
11291
|
+
enum: ["bug", "feature_request", "provider_request"],
|
|
11292
|
+
description: 'Type of feedback: "bug" for something broken, "feature_request" for new capabilities, "provider_request" for requesting a new data provider integration'
|
|
11291
11293
|
},
|
|
11292
|
-
|
|
11294
|
+
message: {
|
|
11293
11295
|
type: "string",
|
|
11294
|
-
|
|
11295
|
-
description: "Sort order for search_posts"
|
|
11296
|
+
description: "Describe the issue, request, or suggestion in detail"
|
|
11296
11297
|
},
|
|
11297
|
-
|
|
11298
|
-
|
|
11298
|
+
context: {
|
|
11299
|
+
type: "object",
|
|
11300
|
+
description: "Optional context \u2014 tool name, error_code, args used, provider, etc. Include anything that helps reproduce or understand the feedback."
|
|
11301
|
+
}
|
|
11302
|
+
},
|
|
11303
|
+
required: ["type", "message"]
|
|
11299
11304
|
}
|
|
11300
11305
|
}
|
|
11301
11306
|
];
|
|
11302
11307
|
}
|
|
11303
11308
|
});
|
|
11304
11309
|
|
|
11310
|
+
// src/tools/descriptions.ts
|
|
11311
|
+
var TOOL_DESCRIPTIONS;
|
|
11312
|
+
var init_descriptions = __esm({
|
|
11313
|
+
"src/tools/descriptions.ts"() {
|
|
11314
|
+
"use strict";
|
|
11315
|
+
TOOL_DESCRIPTIONS = {
|
|
11316
|
+
gtm_find_companies: `Discover companies matching your ICP criteria. Powered by DiscoLike (60M+ companies from web/SSL crawl data) and optionally Apollo.
|
|
11317
|
+
|
|
11318
|
+
DiscoLike strengths:
|
|
11319
|
+
- Lookalike discovery: give it 1-10 seed domains, get similar companies
|
|
11320
|
+
- ICP text matching: describe your ideal customer in plain English
|
|
11321
|
+
- Tech stack filtering: find companies using specific technologies
|
|
11322
|
+
- Website content analysis: matches against actual site content
|
|
11323
|
+
- Best for: B2B companies, local businesses, professional services, healthcare
|
|
11324
|
+
|
|
11325
|
+
For DTC/e-commerce brands, use provider: "storeleads" \u2014 it has revenue estimates and category data that neither DiscoLike nor Apollo provides.
|
|
11326
|
+
|
|
11327
|
+
Tip: DiscoLike lookalike mode is extremely powerful. If you have 2-3 example companies, use the domains parameter to find hundreds of similar ones.
|
|
11328
|
+
|
|
11329
|
+
Single 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.`,
|
|
11330
|
+
gtm_find_people: `Find contacts at companies by job title, company, or domain. Returns name, title, email (when available), phone, and LinkedIn URL.
|
|
11331
|
+
|
|
11332
|
+
Powered by DiscoLike Contacts, Apollo, or BetterContact depending on configuration.
|
|
11333
|
+
|
|
11334
|
+
DiscoLike strengths:
|
|
11335
|
+
- Domain-based search: pass company_domain or company_domains[] for multi-domain batch
|
|
11336
|
+
- Seniority filtering (owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern)
|
|
11337
|
+
- Industry + skills filtering to narrow results without post-processing
|
|
11338
|
+
- email_validated flag for pre-verified emails only
|
|
11339
|
+
- Employee/revenue range filters for company-level targeting
|
|
11340
|
+
- Returns validated emails when available
|
|
11341
|
+
|
|
11342
|
+
Response is slim by default (name, title, email, company, company_domain, linkedin). Pass fields: "all" for full payload.
|
|
11343
|
+
|
|
11344
|
+
After finding people, use:
|
|
11345
|
+
- gtm_enrich to get verified email/phone if not returned (company_domain field is pipeline-compatible)
|
|
11346
|
+
- gtm_extract to check LinkedIn activity and engagement signals
|
|
11347
|
+
|
|
11348
|
+
Known limitations:
|
|
11349
|
+
- department + seniority filters combined may return 0 results (Disco API uses strict AND). Use one or the other.`,
|
|
11350
|
+
gtm_enrich: `Email enrichment via waterfall \u2014 tries multiple providers in order, stops on first match. Supports 1-100 contacts per call.
|
|
11351
|
+
|
|
11352
|
+
IMPORTANT: Defaults to email only. Phone enrichment costs extra per lookup \u2014 only include when the user explicitly asks for phone numbers.
|
|
11353
|
+
|
|
11354
|
+
Always do a dry_run first for batches > 5 to preview cost.
|
|
11355
|
+
|
|
11356
|
+
Providers (in configured waterfall order):
|
|
11357
|
+
- Prospeo: email finder, good hit rate on LinkedIn-active professionals
|
|
11358
|
+
- BetterContact: email + phone, charges only on successful find
|
|
11359
|
+
|
|
11360
|
+
Input needs: first_name + last_name minimum. Adding company_domain dramatically improves hit rates. LinkedIn URL is the gold standard input.
|
|
11361
|
+
|
|
11362
|
+
Tip: 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.`,
|
|
11363
|
+
gtm_verify: "Verify a single email address for deliverability. Returns verification status: deliverable, catch_all_safe, catch_all_not_safe, undeliverable, or unknown. Use this after enrichment or to validate an existing email.",
|
|
11364
|
+
gtm_extract: `Extract structured data from any URL or platform. LinkedIn profiles, posts, engagement, search results, company posts, and web pages.
|
|
11365
|
+
|
|
11366
|
+
When to use:
|
|
11367
|
+
- Scrape a LinkedIn profile for headline, experience, skills, follower count
|
|
11368
|
+
- Check if someone is active on LinkedIn (recent posts, engagement levels)
|
|
11369
|
+
- Find people who comment on or react to specific posts (warm lead mining)
|
|
11370
|
+
- Search LinkedIn posts by keyword to find thought leaders
|
|
11371
|
+
- Monitor competitor or industry content for engagers
|
|
11372
|
+
- Scrape any web page for structured data
|
|
11373
|
+
|
|
11374
|
+
When NOT to use:
|
|
11375
|
+
- Finding people by job title at a company \u2014 use gtm_find_people instead
|
|
11376
|
+
- Email enrichment \u2014 use gtm_enrich instead
|
|
11377
|
+
|
|
11378
|
+
Pairs well with:
|
|
11379
|
+
- gtm_find_people \u2192 gtm_extract (find contacts, then check LinkedIn activity)
|
|
11380
|
+
- gtm_extract linkedin_engagement \u2192 gtm_enrich (find engagers, then get emails)
|
|
11381
|
+
- gtm_storeleads \u2192 gtm_find_people \u2192 gtm_extract (DTC brands \u2192 CEOs \u2192 activity check)
|
|
11382
|
+
|
|
11383
|
+
Note: HarvestAPI charges per request. For engagement mining, each post lookup counts as a request. Use limit to control costs.`,
|
|
11384
|
+
gtm_fetch: "Make an HTTP call to any API endpoint. Use this to pull data from services that don't have a dedicated provider.",
|
|
11385
|
+
gtm_ai: "AI-powered data operations: personalize outreach, classify leads, clean data, score prospects, or write copy. Provide an action, input data, and optional prompt. Uses your configured AI provider (Anthropic or OpenAI).",
|
|
11386
|
+
gtm_campaign: "Manage cold email AND LinkedIn outreach campaigns. List campaigns, get stats, add leads, or create a new campaign. Supports PlusVibe, Instantly, and Smartlead (email) and HeyReach (LinkedIn). For email campaigns, leads need an email. For LinkedIn campaigns, leads need a linkedin_url. Each lead must have at least one of email or linkedin_url.",
|
|
11387
|
+
gtm_crm: "Read and write CRM data. Find, create, or update people and companies. Create deals. Supports Attio and other CRM providers.",
|
|
11388
|
+
gtm_status: "Check provider health, credit balances, and active configuration. Shows which providers are configured, their capabilities, remaining credits, and the current client context.",
|
|
11389
|
+
gtm_configure: "Manage GTM configuration. Switch between clients, view active config, set waterfall order, list available providers.",
|
|
11390
|
+
gtm_guide: "CALL THIS FIRST before any multi-step GTM task. Returns step-by-step workflow recipes for common tasks like building a TAM list, running enrichment, or launching cold outreach. Always check the guide before starting work to ensure you follow the correct sequence and avoid wasting credits.",
|
|
11391
|
+
gtm_tool_help: "Get the full parameter schema for a specific GTM tool. Use this to see exactly what arguments a tool accepts before calling gtm_execute.",
|
|
11392
|
+
gtm_execute: "Gateway to execute any GTM tool by name. Use gtm_guide for workflow recipes, gtm_tool_help for parameter schemas, then this to execute.",
|
|
11393
|
+
gtm_feedback: "Submit feedback, bug reports, feature requests, or provider requests. Feedback is stored for the gtm-now maintainers to review."
|
|
11394
|
+
};
|
|
11395
|
+
}
|
|
11396
|
+
});
|
|
11397
|
+
|
|
11305
11398
|
// src/tools/index.ts
|
|
11306
11399
|
var capabilityTools, guideTool, toolHelpTool, executeTool, metaTools, tools, toolsByName, toolCategories, workflowRecipes;
|
|
11307
11400
|
var init_tools = __esm({
|
|
@@ -11313,22 +11406,20 @@ var init_tools = __esm({
|
|
|
11313
11406
|
init_fetch();
|
|
11314
11407
|
init_ai();
|
|
11315
11408
|
init_campaign();
|
|
11316
|
-
init_outreach();
|
|
11317
11409
|
init_crm();
|
|
11318
11410
|
init_status();
|
|
11319
11411
|
init_configure();
|
|
11320
|
-
|
|
11412
|
+
init_feedback();
|
|
11321
11413
|
init_prospect();
|
|
11322
11414
|
init_enrich();
|
|
11323
11415
|
init_extract();
|
|
11324
11416
|
init_fetch();
|
|
11325
11417
|
init_ai();
|
|
11326
11418
|
init_campaign();
|
|
11327
|
-
init_outreach();
|
|
11328
11419
|
init_crm();
|
|
11329
11420
|
init_status();
|
|
11330
11421
|
init_configure();
|
|
11331
|
-
|
|
11422
|
+
init_descriptions();
|
|
11332
11423
|
capabilityTools = [
|
|
11333
11424
|
...prospectTools,
|
|
11334
11425
|
...enrichTools,
|
|
@@ -11336,11 +11427,10 @@ var init_tools = __esm({
|
|
|
11336
11427
|
...fetchTools,
|
|
11337
11428
|
...aiTools,
|
|
11338
11429
|
...campaignTools,
|
|
11339
|
-
...outreachTools,
|
|
11340
11430
|
...crmTools,
|
|
11341
11431
|
...statusTools,
|
|
11342
11432
|
...configureTools,
|
|
11343
|
-
...
|
|
11433
|
+
...feedbackTools
|
|
11344
11434
|
];
|
|
11345
11435
|
guideTool = {
|
|
11346
11436
|
name: "gtm_guide",
|
|
@@ -11356,6 +11446,7 @@ var init_tools = __esm({
|
|
|
11356
11446
|
"cold_outreach",
|
|
11357
11447
|
"linkedin_outreach",
|
|
11358
11448
|
"crm_sync",
|
|
11449
|
+
"export_to_webhook",
|
|
11359
11450
|
"list_tasks"
|
|
11360
11451
|
],
|
|
11361
11452
|
description: "The workflow to get guidance for"
|
|
@@ -11406,11 +11497,9 @@ var init_tools = __esm({
|
|
|
11406
11497
|
fetch: fetchTools.map((t) => t.name),
|
|
11407
11498
|
ai: aiTools.map((t) => t.name),
|
|
11408
11499
|
campaign: campaignTools.map((t) => t.name),
|
|
11409
|
-
outreach: outreachTools.map((t) => t.name),
|
|
11410
11500
|
crm: crmTools.map((t) => t.name),
|
|
11411
11501
|
status: statusTools.map((t) => t.name),
|
|
11412
11502
|
configure: configureTools.map((t) => t.name),
|
|
11413
|
-
linkedin: linkedinTools.map((t) => t.name),
|
|
11414
11503
|
meta: metaTools.map((t) => t.name)
|
|
11415
11504
|
};
|
|
11416
11505
|
workflowRecipes = {
|
|
@@ -11425,31 +11514,37 @@ Start by identifying what kind of companies you're targeting:
|
|
|
11425
11514
|
\u2192 gtm_find_people({ company_domain: "...", job_title: "CEO" })
|
|
11426
11515
|
3. gtm_enrich (dry_run first) \u2014 get verified emails
|
|
11427
11516
|
\u2192 gtm_enrich({ contacts: [...], find: ["email"], dry_run: true })
|
|
11428
|
-
4. Optional:
|
|
11429
|
-
\u2192
|
|
11517
|
+
4. Optional: gtm_extract \u2014 check if they're active on LinkedIn
|
|
11518
|
+
\u2192 gtm_extract({ type: "linkedin_profile", linkedin_url: "..." })
|
|
11430
11519
|
|
|
11431
11520
|
## B2B / SaaS / Services with websites
|
|
11432
11521
|
1. gtm_find_companies \u2014 use DiscoLike ICP text or lookalikes
|
|
11433
11522
|
\u2192 gtm_find_companies({ icp_text: "...", limit: 100 })
|
|
11434
11523
|
2. gtm_find_people \u2014 find contacts by title
|
|
11435
11524
|
3. gtm_enrich \u2014 get verified emails
|
|
11436
|
-
4. gtm_campaign
|
|
11525
|
+
4. gtm_campaign \u2014 push to email or LinkedIn campaigns
|
|
11437
11526
|
|
|
11438
11527
|
## LinkedIn-native prospects
|
|
11439
11528
|
1. gtm_find_people \u2014 search by title + company
|
|
11440
|
-
2.
|
|
11441
|
-
\u2192
|
|
11529
|
+
2. gtm_extract \u2014 scrape profiles for activity signals
|
|
11530
|
+
\u2192 gtm_extract({ type: "linkedin_profile", linkedin_url: "..." })
|
|
11442
11531
|
3. gtm_enrich \u2014 get emails for active prospects
|
|
11443
|
-
4.
|
|
11532
|
+
4. gtm_campaign \u2014 push to LinkedIn or email campaign
|
|
11444
11533
|
|
|
11445
11534
|
## Engagement-based (warm leads)
|
|
11446
|
-
1.
|
|
11447
|
-
\u2192
|
|
11448
|
-
2.
|
|
11449
|
-
\u2192
|
|
11535
|
+
1. gtm_extract (linkedin_search) \u2014 find relevant content
|
|
11536
|
+
\u2192 gtm_extract({ type: "linkedin_search", query: "...", sort: "date" })
|
|
11537
|
+
2. gtm_extract (linkedin_engagement) \u2014 extract commenters/reactors
|
|
11538
|
+
\u2192 gtm_extract({ type: "linkedin_engagement", post_url: "...", engagement_type: "both" })
|
|
11450
11539
|
3. gtm_enrich \u2014 get emails
|
|
11451
11540
|
4. gtm_campaign \u2014 personalized outreach referencing their engagement
|
|
11452
11541
|
|
|
11542
|
+
## Review before enriching
|
|
11543
|
+
After finding companies, review the list before spending enrichment credits:
|
|
11544
|
+
- Use output: 'csv' to write results to a spreadsheet for visual review
|
|
11545
|
+
- Or export to Clay/webhook via gtm_fetch POST for team review (see export_to_webhook guide)
|
|
11546
|
+
- Use gtm_ai with action: 'classify' to auto-filter competitors or bad fits
|
|
11547
|
+
|
|
11453
11548
|
## Key principle
|
|
11454
11549
|
Always dry_run enrichment first. Credits are spent per successful find \u2014 previewing cost avoids surprises.`,
|
|
11455
11550
|
enrich_list: `# Enrich a Contact List \u2014 Workflow
|
|
@@ -11502,10 +11597,10 @@ Always verify emails before pushing to campaigns. Bounced emails hurt sender rep
|
|
|
11502
11597
|
\u2192 gtm_ai({ action: "personalize", data: [...], prompt: "Write a LinkedIn connection request referencing their recent post" })
|
|
11503
11598
|
|
|
11504
11599
|
3. LIST CAMPAIGNS \u2014 Find or identify the target outreach campaign
|
|
11505
|
-
\u2192
|
|
11600
|
+
\u2192 gtm_campaign({ action: "list", provider: "heyreach" })
|
|
11506
11601
|
|
|
11507
11602
|
4. PUSH \u2014 Add leads to LinkedIn outreach campaign
|
|
11508
|
-
\u2192
|
|
11603
|
+
\u2192 gtm_campaign({ action: "add_leads", campaign_id: "...", provider: "heyreach", leads: [{ linkedin_url: "..." }] })
|
|
11509
11604
|
|
|
11510
11605
|
## Key principle
|
|
11511
11606
|
LinkedIn outreach requires LinkedIn profile URLs \u2014 not emails. Extract profile data first for personalization.`,
|
|
@@ -11525,6 +11620,26 @@ LinkedIn outreach requires LinkedIn profile URLs \u2014 not emails. Extract prof
|
|
|
11525
11620
|
|
|
11526
11621
|
## Key principle
|
|
11527
11622
|
Always search before creating to avoid duplicates. Most CRMs have built-in email dedup but better to check first.`,
|
|
11623
|
+
export_to_webhook: `# Export Data to Webhook \u2014 Workflow
|
|
11624
|
+
|
|
11625
|
+
Push GTM data to Clay, Make, Zapier, or any webhook endpoint for visual review or further processing.
|
|
11626
|
+
|
|
11627
|
+
## Steps
|
|
11628
|
+
1. FIND \u2014 Get your companies or contacts
|
|
11629
|
+
\u2192 gtm_find_companies({ icp_text: "...", limit: 50 })
|
|
11630
|
+
\u2192 gtm_find_people({ company_domain: "...", job_title: "CEO" })
|
|
11631
|
+
|
|
11632
|
+
2. EXPORT \u2014 Push to webhook via gtm_fetch
|
|
11633
|
+
\u2192 gtm_fetch({ url: "https://your-webhook-url", method: "POST", body: { results: [...] } })
|
|
11634
|
+
|
|
11635
|
+
## Clay Integration
|
|
11636
|
+
Clay table webhooks accept JSON directly:
|
|
11637
|
+
\u2192 gtm_fetch({ url: "https://api.clay.com/v3/sources/webhook/...", method: "POST", body: { first_name: "...", last_name: "...", company: "...", ... } })
|
|
11638
|
+
|
|
11639
|
+
Send one record per request, or batch them depending on the webhook's format.
|
|
11640
|
+
|
|
11641
|
+
## Key principle
|
|
11642
|
+
Review data visually before spending enrichment credits. Export \u2192 review \u2192 enrich only the good leads.`,
|
|
11528
11643
|
list_tasks: `# Available GTM Workflows
|
|
11529
11644
|
|
|
11530
11645
|
Call gtm_guide with one of these tasks:
|
|
@@ -11534,10 +11649,11 @@ Call gtm_guide with one of these tasks:
|
|
|
11534
11649
|
- cold_outreach \u2014 Enrich + personalize + push to cold email campaign
|
|
11535
11650
|
- linkedin_outreach \u2014 Extract profiles + personalize + push to LinkedIn campaign
|
|
11536
11651
|
- crm_sync \u2014 Find/create/update people and deals in CRM
|
|
11652
|
+
- export_to_webhook \u2014 Push data to Clay, Make, Zapier, or any webhook for review
|
|
11537
11653
|
|
|
11538
11654
|
New tools available:
|
|
11539
11655
|
- gtm_find_companies (provider: storeleads) \u2014 Search 1.2M+ e-commerce stores by category, revenue, platform
|
|
11540
|
-
-
|
|
11656
|
+
- gtm_extract \u2014 LinkedIn profiles, posts, engagement mining, search + web page scraping
|
|
11541
11657
|
|
|
11542
11658
|
For any task: always dry_run enrichment batches > 5 to preview cost before executing.`
|
|
11543
11659
|
};
|
|
@@ -11617,6 +11733,7 @@ import * as os from "os";
|
|
|
11617
11733
|
function shouldWriteToFile(count, output) {
|
|
11618
11734
|
if (output === "file") return true;
|
|
11619
11735
|
if (output === "inline") return false;
|
|
11736
|
+
if (output === "csv") return false;
|
|
11620
11737
|
return count > FILE_OUTPUT_THRESHOLD;
|
|
11621
11738
|
}
|
|
11622
11739
|
function writeResultsFile(tool, results, meta, resultsDir = DEFAULT_RESULTS_DIR) {
|
|
@@ -11634,6 +11751,40 @@ function writeResultsFile(tool, results, meta, resultsDir = DEFAULT_RESULTS_DIR)
|
|
|
11634
11751
|
}
|
|
11635
11752
|
return filePath;
|
|
11636
11753
|
}
|
|
11754
|
+
function writeResultsCsv(tool, results, resultsDir = DEFAULT_RESULTS_DIR) {
|
|
11755
|
+
fs.mkdirSync(resultsDir, { recursive: true });
|
|
11756
|
+
const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
|
|
11757
|
+
const filename = `${tool}-${timestamp}.csv`;
|
|
11758
|
+
const filePath = path.join(resultsDir, filename);
|
|
11759
|
+
if (results.length === 0) {
|
|
11760
|
+
fs.writeFileSync(filePath, "", "utf-8");
|
|
11761
|
+
return filePath;
|
|
11762
|
+
}
|
|
11763
|
+
const headers = Object.keys(results[0]);
|
|
11764
|
+
const headerLine = headers.map(escapeCsvField).join(",");
|
|
11765
|
+
const dataLines = results.map(
|
|
11766
|
+
(row) => headers.map((h) => escapeCsvField(formatCsvValue(row[h]))).join(",")
|
|
11767
|
+
);
|
|
11768
|
+
const content = [headerLine, ...dataLines].join("\n") + "\n";
|
|
11769
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
11770
|
+
try {
|
|
11771
|
+
cleanOldResults(7, resultsDir);
|
|
11772
|
+
} catch {
|
|
11773
|
+
}
|
|
11774
|
+
return filePath;
|
|
11775
|
+
}
|
|
11776
|
+
function formatCsvValue(value) {
|
|
11777
|
+
if (value === null || value === void 0) return "";
|
|
11778
|
+
if (Array.isArray(value)) return value.join(";");
|
|
11779
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
11780
|
+
return String(value);
|
|
11781
|
+
}
|
|
11782
|
+
function escapeCsvField(value) {
|
|
11783
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
11784
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
11785
|
+
}
|
|
11786
|
+
return value;
|
|
11787
|
+
}
|
|
11637
11788
|
function cleanOldResults(maxAgeDays, resultsDir = DEFAULT_RESULTS_DIR) {
|
|
11638
11789
|
let entries;
|
|
11639
11790
|
try {
|
|
@@ -11662,7 +11813,7 @@ var FILE_OUTPUT_THRESHOLD, DEFAULT_RESULTS_DIR;
|
|
|
11662
11813
|
var init_result_writer = __esm({
|
|
11663
11814
|
"src/utils/result-writer.ts"() {
|
|
11664
11815
|
"use strict";
|
|
11665
|
-
FILE_OUTPUT_THRESHOLD =
|
|
11816
|
+
FILE_OUTPUT_THRESHOLD = 20;
|
|
11666
11817
|
DEFAULT_RESULTS_DIR = path.join(os.homedir(), ".gtm", "results");
|
|
11667
11818
|
}
|
|
11668
11819
|
});
|
|
@@ -11757,6 +11908,19 @@ async function findCompanies(args, ctx) {
|
|
|
11757
11908
|
if (query.max_revenue != null) applied_filters.max_revenue = query.max_revenue;
|
|
11758
11909
|
const providerUsed = provider.name;
|
|
11759
11910
|
const output = args.output;
|
|
11911
|
+
if (output === "csv") {
|
|
11912
|
+
const filePath = writeResultsCsv("find_companies", slimmedResults);
|
|
11913
|
+
return {
|
|
11914
|
+
provider: providerUsed,
|
|
11915
|
+
count: results.length,
|
|
11916
|
+
has_more,
|
|
11917
|
+
output: "csv",
|
|
11918
|
+
file: filePath,
|
|
11919
|
+
preview: slimmedResults.slice(0, 3),
|
|
11920
|
+
columns: slimmedResults.length > 0 ? Object.keys(slimmedResults[0]) : [],
|
|
11921
|
+
...warnings?.length ? { warnings } : {}
|
|
11922
|
+
};
|
|
11923
|
+
}
|
|
11760
11924
|
if (shouldWriteToFile(results.length, output)) {
|
|
11761
11925
|
const meta = {
|
|
11762
11926
|
query: applied_filters,
|
|
@@ -11850,6 +12014,19 @@ async function findPeople(args, ctx) {
|
|
|
11850
12014
|
if (query.revenue_range) applied_filters.revenue_range = query.revenue_range;
|
|
11851
12015
|
const providerUsed = provider.name;
|
|
11852
12016
|
const output = args.output;
|
|
12017
|
+
if (output === "csv") {
|
|
12018
|
+
const filePath = writeResultsCsv("find_people", slimmedResults);
|
|
12019
|
+
return {
|
|
12020
|
+
provider: providerUsed,
|
|
12021
|
+
count: dedupedResults.length,
|
|
12022
|
+
has_more,
|
|
12023
|
+
output: "csv",
|
|
12024
|
+
file: filePath,
|
|
12025
|
+
preview: slimmedResults.slice(0, 3),
|
|
12026
|
+
columns: slimmedResults.length > 0 ? Object.keys(slimmedResults[0]) : [],
|
|
12027
|
+
...warnings?.length ? { warnings } : {}
|
|
12028
|
+
};
|
|
12029
|
+
}
|
|
11853
12030
|
if (shouldWriteToFile(dedupedResults.length, output)) {
|
|
11854
12031
|
const meta = {
|
|
11855
12032
|
query: applied_filters,
|
|
@@ -11907,10 +12084,46 @@ function isRateLimitError(error) {
|
|
|
11907
12084
|
const msg = error instanceof Error ? error.message : String(error);
|
|
11908
12085
|
return msg.includes("429") || msg.toLowerCase().includes("rate limit");
|
|
11909
12086
|
}
|
|
12087
|
+
function withTimeout(promise, ms, label) {
|
|
12088
|
+
return new Promise((resolve, reject) => {
|
|
12089
|
+
const timer = setTimeout(
|
|
12090
|
+
() => reject(new Error(`Provider timeout: ${label} did not respond within ${ms}ms`)),
|
|
12091
|
+
ms
|
|
12092
|
+
);
|
|
12093
|
+
promise.then(
|
|
12094
|
+
(val) => {
|
|
12095
|
+
clearTimeout(timer);
|
|
12096
|
+
resolve(val);
|
|
12097
|
+
},
|
|
12098
|
+
(err) => {
|
|
12099
|
+
clearTimeout(timer);
|
|
12100
|
+
reject(err);
|
|
12101
|
+
}
|
|
12102
|
+
);
|
|
12103
|
+
});
|
|
12104
|
+
}
|
|
11910
12105
|
async function runWaterfall(contacts, config, registry) {
|
|
11911
12106
|
const results = [];
|
|
11912
12107
|
const exhausted = /* @__PURE__ */ new Set();
|
|
12108
|
+
const batchDeadline = Date.now() + (config.batchTimeoutMs ?? DEFAULT_BATCH_TIMEOUT_MS);
|
|
11913
12109
|
for (const contact of contacts) {
|
|
12110
|
+
if (Date.now() > batchDeadline) {
|
|
12111
|
+
results.push({
|
|
12112
|
+
contact,
|
|
12113
|
+
attempts: [
|
|
12114
|
+
{
|
|
12115
|
+
provider: "waterfall",
|
|
12116
|
+
capability: "enrich_email",
|
|
12117
|
+
found: false,
|
|
12118
|
+
charged: false,
|
|
12119
|
+
duration_ms: 0,
|
|
12120
|
+
error: "Batch timeout exceeded \u2014 skipped remaining contacts"
|
|
12121
|
+
}
|
|
12122
|
+
],
|
|
12123
|
+
charged: false
|
|
12124
|
+
});
|
|
12125
|
+
continue;
|
|
12126
|
+
}
|
|
11914
12127
|
const result = await enrichContact(contact, config, registry, exhausted);
|
|
11915
12128
|
results.push(result);
|
|
11916
12129
|
}
|
|
@@ -11999,11 +12212,20 @@ async function enrichField(contact, field, config, registry, exhausted) {
|
|
|
11999
12212
|
const start = Date.now();
|
|
12000
12213
|
try {
|
|
12001
12214
|
let value;
|
|
12215
|
+
const providerTimeoutMs = config.providerTimeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
12002
12216
|
if (field === "email" && "enrichEmail" in provider) {
|
|
12003
|
-
const result = await
|
|
12217
|
+
const result = await withTimeout(
|
|
12218
|
+
provider.enrichEmail(contact),
|
|
12219
|
+
providerTimeoutMs,
|
|
12220
|
+
providerName
|
|
12221
|
+
);
|
|
12004
12222
|
value = result?.email;
|
|
12005
12223
|
} else if (field === "phone" && "enrichPhone" in provider) {
|
|
12006
|
-
const result = await
|
|
12224
|
+
const result = await withTimeout(
|
|
12225
|
+
provider.enrichPhone(contact),
|
|
12226
|
+
providerTimeoutMs,
|
|
12227
|
+
providerName
|
|
12228
|
+
);
|
|
12007
12229
|
value = result?.phone;
|
|
12008
12230
|
}
|
|
12009
12231
|
const found = !!value;
|
|
@@ -12079,7 +12301,7 @@ function dryRunWaterfall(contactsCount, config, registry) {
|
|
|
12079
12301
|
confirm: "Call gtm_enrich again without dry_run to execute."
|
|
12080
12302
|
};
|
|
12081
12303
|
}
|
|
12082
|
-
var enrichmentCache, DEFAULT_PRICING, EXHAUSTION_PATTERNS, WATERFALL_RETRY_DELAYS;
|
|
12304
|
+
var enrichmentCache, DEFAULT_PRICING, EXHAUSTION_PATTERNS, WATERFALL_RETRY_DELAYS, DEFAULT_PROVIDER_TIMEOUT_MS, DEFAULT_BATCH_TIMEOUT_MS;
|
|
12083
12305
|
var init_waterfall = __esm({
|
|
12084
12306
|
"src/providers/waterfall.ts"() {
|
|
12085
12307
|
"use strict";
|
|
@@ -12101,6 +12323,8 @@ var init_waterfall = __esm({
|
|
|
12101
12323
|
"not accessible with this api_key on a free plan"
|
|
12102
12324
|
];
|
|
12103
12325
|
WATERFALL_RETRY_DELAYS = [2e3, 5e3, 1e4];
|
|
12326
|
+
DEFAULT_PROVIDER_TIMEOUT_MS = 15e3;
|
|
12327
|
+
DEFAULT_BATCH_TIMEOUT_MS = 3e5;
|
|
12104
12328
|
}
|
|
12105
12329
|
});
|
|
12106
12330
|
|
|
@@ -12109,6 +12333,19 @@ var enrich_exports = {};
|
|
|
12109
12333
|
__export(enrich_exports, {
|
|
12110
12334
|
handleEnrich: () => handleEnrich
|
|
12111
12335
|
});
|
|
12336
|
+
function slimEnrichResults(results) {
|
|
12337
|
+
return results.map((r) => {
|
|
12338
|
+
const slim = {};
|
|
12339
|
+
if (r.email) slim.email = r.email;
|
|
12340
|
+
if (r.phone) slim.phone = r.phone;
|
|
12341
|
+
if (r.email_status) slim.email_status = r.email_status;
|
|
12342
|
+
if (r.email_provider) slim.email_provider = r.email_provider;
|
|
12343
|
+
if (r.phone_provider) slim.phone_provider = r.phone_provider;
|
|
12344
|
+
slim.charged = r.charged;
|
|
12345
|
+
slim.name = `${r.contact.first_name} ${r.contact.last_name}`;
|
|
12346
|
+
return slim;
|
|
12347
|
+
});
|
|
12348
|
+
}
|
|
12112
12349
|
async function handleEnrich(name, args, ctx) {
|
|
12113
12350
|
if (name === "gtm_verify") {
|
|
12114
12351
|
return verifyEmail(args, ctx);
|
|
@@ -12152,11 +12389,12 @@ async function enrichContacts(args, ctx) {
|
|
|
12152
12389
|
}
|
|
12153
12390
|
const results = await runWaterfall(contacts, config, ctx.registry);
|
|
12154
12391
|
const found = results.filter((r) => r.email || r.phone).length;
|
|
12392
|
+
const detail = args.detail ?? "summary";
|
|
12155
12393
|
return {
|
|
12156
12394
|
total: results.length,
|
|
12157
12395
|
found,
|
|
12158
12396
|
missed: results.length - found,
|
|
12159
|
-
results
|
|
12397
|
+
results: detail === "full" ? results : slimEnrichResults(results)
|
|
12160
12398
|
};
|
|
12161
12399
|
}
|
|
12162
12400
|
async function verifyEmail(args, ctx) {
|
|
@@ -12185,45 +12423,156 @@ var extract_exports = {};
|
|
|
12185
12423
|
__export(extract_exports, {
|
|
12186
12424
|
handleExtract: () => handleExtract
|
|
12187
12425
|
});
|
|
12426
|
+
function slimPost(post) {
|
|
12427
|
+
const slim = {};
|
|
12428
|
+
for (const [key, value] of Object.entries(post)) {
|
|
12429
|
+
if (key === "text" && typeof value === "string") {
|
|
12430
|
+
slim.text = value.length > POST_TEXT_LIMIT ? value.slice(0, POST_TEXT_LIMIT) + "..." : value;
|
|
12431
|
+
} else if (key === "images" || key === "documents" || key === "media") {
|
|
12432
|
+
if (Array.isArray(value) && value.length > 0) slim[`${key}_count`] = value.length;
|
|
12433
|
+
} else {
|
|
12434
|
+
slim[key] = value;
|
|
12435
|
+
}
|
|
12436
|
+
}
|
|
12437
|
+
return slim;
|
|
12438
|
+
}
|
|
12439
|
+
function slimProfile(profile) {
|
|
12440
|
+
const PROFILE_FIELDS = [
|
|
12441
|
+
"firstName",
|
|
12442
|
+
"lastName",
|
|
12443
|
+
"headline",
|
|
12444
|
+
"location",
|
|
12445
|
+
"summary",
|
|
12446
|
+
"followerCount",
|
|
12447
|
+
"connectionCount",
|
|
12448
|
+
"email",
|
|
12449
|
+
"publicIdentifier",
|
|
12450
|
+
"industryName",
|
|
12451
|
+
"company"
|
|
12452
|
+
];
|
|
12453
|
+
const slim = {};
|
|
12454
|
+
for (const field of PROFILE_FIELDS) {
|
|
12455
|
+
if (field in profile && profile[field] != null) slim[field] = profile[field];
|
|
12456
|
+
}
|
|
12457
|
+
return slim;
|
|
12458
|
+
}
|
|
12188
12459
|
async function handleExtract(args, ctx) {
|
|
12460
|
+
const type = args.type;
|
|
12461
|
+
if (type.startsWith("linkedin_")) {
|
|
12462
|
+
return handleLinkedinExtract(type, args, ctx);
|
|
12463
|
+
}
|
|
12464
|
+
return handleWebExtract(args, ctx);
|
|
12465
|
+
}
|
|
12466
|
+
async function handleWebExtract(args, ctx) {
|
|
12189
12467
|
const target = {
|
|
12190
12468
|
url: args.url,
|
|
12191
|
-
type:
|
|
12469
|
+
type: "web_page",
|
|
12192
12470
|
fields: args.fields
|
|
12193
12471
|
};
|
|
12194
|
-
const capability = target.type === "web_page" ? "extract_web" : "extract_linkedin";
|
|
12195
12472
|
const providerName = args.provider || void 0;
|
|
12196
|
-
const providers = providerName ? [ctx.registry.get(providerName)].filter(Boolean) : ctx.registry.getForCapability(
|
|
12473
|
+
const providers = providerName ? [ctx.registry.get(providerName)].filter(Boolean) : ctx.registry.getForCapability("extract_web");
|
|
12197
12474
|
if (providers.length === 0) {
|
|
12198
12475
|
throw createGtmError(
|
|
12199
12476
|
"EXTRACTION_FAILED",
|
|
12200
|
-
|
|
12477
|
+
"No providers configured for extract_web. Add a provider with that capability."
|
|
12201
12478
|
);
|
|
12202
12479
|
}
|
|
12203
|
-
const
|
|
12204
|
-
if (provider.name === "harvest" && capability === "extract_linkedin") {
|
|
12205
|
-
return extractViaHarvest(provider, target);
|
|
12206
|
-
}
|
|
12207
|
-
const extractor = provider;
|
|
12480
|
+
const extractor = providers[0];
|
|
12208
12481
|
return extractor.extract(target);
|
|
12209
12482
|
}
|
|
12210
|
-
async function
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12483
|
+
async function handleLinkedinExtract(type, args, ctx) {
|
|
12484
|
+
const adapter = ctx.registry.get("harvest");
|
|
12485
|
+
if (!adapter) {
|
|
12486
|
+
throw Object.assign(
|
|
12487
|
+
new Error(
|
|
12488
|
+
"HarvestAPI provider not configured. Add harvest.api_key to your config.yaml or set GTM_HARVEST_API_KEY environment variable."
|
|
12489
|
+
),
|
|
12490
|
+
{ statusCode: 503 }
|
|
12491
|
+
);
|
|
12492
|
+
}
|
|
12493
|
+
const detail = args.detail ?? "summary";
|
|
12494
|
+
const isSummary = detail !== "full";
|
|
12495
|
+
switch (type) {
|
|
12496
|
+
case "linkedin_profile": {
|
|
12497
|
+
const url = args.linkedin_url || args.url;
|
|
12498
|
+
const profile = await adapter.getProfile(url, args.find_email);
|
|
12499
|
+
return {
|
|
12500
|
+
type: "linkedin_profile",
|
|
12501
|
+
provider: "harvest",
|
|
12502
|
+
profile: isSummary ? slimProfile(profile) : profile
|
|
12503
|
+
};
|
|
12504
|
+
}
|
|
12505
|
+
case "linkedin_posts": {
|
|
12506
|
+
const result = await adapter.getProfilePosts({
|
|
12507
|
+
profile: args.profile,
|
|
12508
|
+
postedLimit: args.posted_within
|
|
12509
|
+
});
|
|
12510
|
+
return {
|
|
12511
|
+
type: "linkedin_posts",
|
|
12512
|
+
provider: "harvest",
|
|
12513
|
+
count: result.elements.length,
|
|
12514
|
+
posts: isSummary ? result.elements.map((p) => slimPost(p)) : result.elements,
|
|
12515
|
+
...detail === "full" && { pagination: result.pagination }
|
|
12516
|
+
};
|
|
12517
|
+
}
|
|
12518
|
+
case "linkedin_company_posts": {
|
|
12519
|
+
const result = await adapter.getCompanyPosts({
|
|
12520
|
+
company: args.company,
|
|
12521
|
+
postedLimit: args.posted_within
|
|
12522
|
+
});
|
|
12523
|
+
return {
|
|
12524
|
+
type: "linkedin_company_posts",
|
|
12525
|
+
provider: "harvest",
|
|
12526
|
+
count: result.elements.length,
|
|
12527
|
+
posts: isSummary ? result.elements.map((p) => slimPost(p)) : result.elements,
|
|
12528
|
+
...detail === "full" && { pagination: result.pagination }
|
|
12529
|
+
};
|
|
12530
|
+
}
|
|
12531
|
+
case "linkedin_search": {
|
|
12532
|
+
const result = await adapter.searchPosts(args.query, {
|
|
12533
|
+
sortBy: args.sort,
|
|
12534
|
+
postedLimit: args.posted_within
|
|
12535
|
+
});
|
|
12536
|
+
return {
|
|
12537
|
+
type: "linkedin_search",
|
|
12538
|
+
provider: "harvest",
|
|
12539
|
+
count: result.elements.length,
|
|
12540
|
+
posts: isSummary ? result.elements.map((p) => slimPost(p)) : result.elements,
|
|
12541
|
+
...detail === "full" && { pagination: result.pagination }
|
|
12542
|
+
};
|
|
12543
|
+
}
|
|
12544
|
+
case "linkedin_engagement": {
|
|
12545
|
+
const result = await adapter.getPostEngagement(
|
|
12546
|
+
args.post_url,
|
|
12547
|
+
args.engagement_type ?? "both"
|
|
12548
|
+
);
|
|
12549
|
+
if (isSummary) {
|
|
12550
|
+
return {
|
|
12551
|
+
type: "linkedin_engagement",
|
|
12552
|
+
provider: "harvest",
|
|
12553
|
+
comments_count: result.comments.length,
|
|
12554
|
+
reactions_count: result.reactions.length
|
|
12555
|
+
};
|
|
12556
|
+
}
|
|
12557
|
+
return {
|
|
12558
|
+
type: "linkedin_engagement",
|
|
12559
|
+
provider: "harvest",
|
|
12560
|
+
comments_count: result.comments.length,
|
|
12561
|
+
reactions_count: result.reactions.length,
|
|
12562
|
+
comments: result.comments,
|
|
12563
|
+
reactions: result.reactions
|
|
12564
|
+
};
|
|
12565
|
+
}
|
|
12566
|
+
default:
|
|
12567
|
+
throw new Error(`Unknown extract type: ${type}`);
|
|
12218
12568
|
}
|
|
12219
|
-
throw new Error(
|
|
12220
|
-
'LinkedIn post extraction is not supported by harvest. Use gtm_linkedin with action "posts" instead.'
|
|
12221
|
-
);
|
|
12222
12569
|
}
|
|
12570
|
+
var POST_TEXT_LIMIT;
|
|
12223
12571
|
var init_extract2 = __esm({
|
|
12224
12572
|
"src/handlers/extract.ts"() {
|
|
12225
12573
|
"use strict";
|
|
12226
12574
|
init_errors();
|
|
12575
|
+
POST_TEXT_LIMIT = 200;
|
|
12227
12576
|
}
|
|
12228
12577
|
});
|
|
12229
12578
|
|
|
@@ -12273,11 +12622,19 @@ async function handleFetch(args) {
|
|
|
12273
12622
|
} else {
|
|
12274
12623
|
responseBody = await response.text();
|
|
12275
12624
|
}
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12625
|
+
const detail = args.detail ?? "summary";
|
|
12626
|
+
if (detail === "full") {
|
|
12627
|
+
return {
|
|
12628
|
+
status: response.status,
|
|
12629
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
12630
|
+
body: responseBody
|
|
12631
|
+
};
|
|
12632
|
+
}
|
|
12633
|
+
let slimBody = responseBody;
|
|
12634
|
+
if (typeof responseBody === "string" && responseBody.length > BODY_TRUNCATE_LIMIT) {
|
|
12635
|
+
slimBody = responseBody.slice(0, BODY_TRUNCATE_LIMIT) + `... (truncated, ${responseBody.length} chars total)`;
|
|
12636
|
+
}
|
|
12637
|
+
return { status: response.status, body: slimBody };
|
|
12281
12638
|
} catch (error) {
|
|
12282
12639
|
logError("handler:gtm_fetch", error instanceof Error ? error : new Error(String(error)), {
|
|
12283
12640
|
url: fullUrl,
|
|
@@ -12288,7 +12645,7 @@ async function handleFetch(args) {
|
|
|
12288
12645
|
clearTimeout(timeout);
|
|
12289
12646
|
}
|
|
12290
12647
|
}
|
|
12291
|
-
var BLOCKED_PATTERNS, BLOCKED_HOSTS;
|
|
12648
|
+
var BLOCKED_PATTERNS, BLOCKED_HOSTS, BODY_TRUNCATE_LIMIT;
|
|
12292
12649
|
var init_fetch2 = __esm({
|
|
12293
12650
|
"src/handlers/fetch.ts"() {
|
|
12294
12651
|
"use strict";
|
|
@@ -12305,6 +12662,7 @@ var init_fetch2 = __esm({
|
|
|
12305
12662
|
/^fe80:/
|
|
12306
12663
|
];
|
|
12307
12664
|
BLOCKED_HOSTS = ["localhost", "0.0.0.0", "[::1]"];
|
|
12665
|
+
BODY_TRUNCATE_LIMIT = 2e3;
|
|
12308
12666
|
}
|
|
12309
12667
|
});
|
|
12310
12668
|
|
|
@@ -12426,36 +12784,95 @@ var campaign_exports = {};
|
|
|
12426
12784
|
__export(campaign_exports, {
|
|
12427
12785
|
handleCampaign: () => handleCampaign
|
|
12428
12786
|
});
|
|
12429
|
-
|
|
12430
|
-
const action = args.action;
|
|
12787
|
+
function resolveChannel(args, ctx) {
|
|
12431
12788
|
const providerName = args.provider || void 0;
|
|
12432
|
-
|
|
12433
|
-
|
|
12789
|
+
if (providerName) {
|
|
12790
|
+
const provider = ctx.registry.get(providerName);
|
|
12791
|
+
if (!provider) {
|
|
12792
|
+
throw createGtmError(
|
|
12793
|
+
"PROVIDER_NOT_CONFIGURED",
|
|
12794
|
+
`Provider "${providerName}" not found. Check your config or use action: "list" without a provider to discover available campaigns.`
|
|
12795
|
+
);
|
|
12796
|
+
}
|
|
12797
|
+
if (provider.capabilities.includes("outreach_linkedin")) {
|
|
12798
|
+
return {
|
|
12799
|
+
type: "linkedin",
|
|
12800
|
+
manager: provider,
|
|
12801
|
+
providerName: provider.name
|
|
12802
|
+
};
|
|
12803
|
+
}
|
|
12804
|
+
if (provider.capabilities.includes("campaign_email")) {
|
|
12805
|
+
return {
|
|
12806
|
+
type: "email",
|
|
12807
|
+
manager: provider,
|
|
12808
|
+
providerName: provider.name
|
|
12809
|
+
};
|
|
12810
|
+
}
|
|
12434
12811
|
throw createGtmError(
|
|
12435
12812
|
"PROVIDER_NOT_CONFIGURED",
|
|
12436
|
-
"
|
|
12813
|
+
`Provider "${providerName}" does not support campaign_email or outreach_linkedin.`
|
|
12437
12814
|
);
|
|
12438
12815
|
}
|
|
12439
|
-
const
|
|
12440
|
-
|
|
12816
|
+
const emailProviders = ctx.registry.getForCapability("campaign_email");
|
|
12817
|
+
if (emailProviders.length > 0) {
|
|
12818
|
+
const provider = emailProviders[0];
|
|
12819
|
+
return {
|
|
12820
|
+
type: "email",
|
|
12821
|
+
manager: provider,
|
|
12822
|
+
providerName: provider.name
|
|
12823
|
+
};
|
|
12824
|
+
}
|
|
12825
|
+
const linkedinProviders = ctx.registry.getForCapability("outreach_linkedin");
|
|
12826
|
+
if (linkedinProviders.length > 0) {
|
|
12827
|
+
const provider = linkedinProviders[0];
|
|
12828
|
+
return {
|
|
12829
|
+
type: "linkedin",
|
|
12830
|
+
manager: provider,
|
|
12831
|
+
providerName: provider.name
|
|
12832
|
+
};
|
|
12833
|
+
}
|
|
12834
|
+
throw createGtmError(
|
|
12835
|
+
"PROVIDER_NOT_CONFIGURED",
|
|
12836
|
+
"No providers configured for campaigns. Add a provider with campaign_email or outreach_linkedin capability."
|
|
12837
|
+
);
|
|
12838
|
+
}
|
|
12839
|
+
async function handleCampaign(args, ctx) {
|
|
12840
|
+
const action = args.action;
|
|
12841
|
+
const channel = resolveChannel(args, ctx);
|
|
12441
12842
|
switch (action) {
|
|
12442
12843
|
case "list":
|
|
12443
|
-
return { provider:
|
|
12844
|
+
return { provider: channel.providerName, campaigns: await channel.manager.listCampaigns() };
|
|
12444
12845
|
case "stats": {
|
|
12445
12846
|
const campaignId = args.campaign_id;
|
|
12446
12847
|
if (!campaignId)
|
|
12447
12848
|
throw createGtmError("QUERY_INVALID", "campaign_id is required for stats action");
|
|
12448
|
-
return manager.getCampaignStats(campaignId);
|
|
12849
|
+
return channel.manager.getCampaignStats(campaignId);
|
|
12449
12850
|
}
|
|
12450
12851
|
case "add_leads": {
|
|
12451
12852
|
const campaignId = args.campaign_id;
|
|
12452
|
-
const
|
|
12853
|
+
const rawLeads = args.leads;
|
|
12453
12854
|
if (!campaignId)
|
|
12454
12855
|
throw createGtmError("QUERY_INVALID", "campaign_id is required for add_leads action");
|
|
12455
|
-
if (!
|
|
12856
|
+
if (!rawLeads?.length)
|
|
12456
12857
|
throw createGtmError("QUERY_INVALID", "leads array is required for add_leads action");
|
|
12858
|
+
if (channel.type === "linkedin") {
|
|
12859
|
+
const leads2 = rawLeads.map((l) => ({
|
|
12860
|
+
linkedin_url: l.linkedin_url,
|
|
12861
|
+
first_name: l.first_name,
|
|
12862
|
+
last_name: l.last_name,
|
|
12863
|
+
company: l.company
|
|
12864
|
+
}));
|
|
12865
|
+
return channel.manager.addLeadsToCampaign(campaignId, leads2);
|
|
12866
|
+
}
|
|
12867
|
+
const leads = rawLeads.map((l) => ({
|
|
12868
|
+
email: l.email,
|
|
12869
|
+
first_name: l.first_name,
|
|
12870
|
+
last_name: l.last_name,
|
|
12871
|
+
company: l.company,
|
|
12872
|
+
custom_fields: l.custom_fields
|
|
12873
|
+
}));
|
|
12457
12874
|
try {
|
|
12458
|
-
return await manager.addLeadsToCampaign(campaignId, leads);
|
|
12875
|
+
return await channel.manager.addLeadsToCampaign(campaignId, leads);
|
|
12459
12876
|
} catch (error) {
|
|
12460
12877
|
if (error instanceof ApiClientError && (error.message.includes("Validation error") || error.statusCode === 400 || error.statusCode === 422)) {
|
|
12461
12878
|
throw createGtmError(
|
|
@@ -12468,15 +12885,21 @@ async function handleCampaign(args, ctx) {
|
|
|
12468
12885
|
}
|
|
12469
12886
|
}
|
|
12470
12887
|
case "create": {
|
|
12888
|
+
if (channel.type === "linkedin") {
|
|
12889
|
+
throw createGtmError(
|
|
12890
|
+
"PROVIDER_NOT_CONFIGURED",
|
|
12891
|
+
"LinkedIn outreach providers do not support campaign creation via API. Create campaigns directly in the provider dashboard."
|
|
12892
|
+
);
|
|
12893
|
+
}
|
|
12471
12894
|
const name = args.name;
|
|
12472
12895
|
if (!name) throw createGtmError("QUERY_INVALID", "name is required for create action");
|
|
12473
|
-
if (!manager.createCampaign) {
|
|
12896
|
+
if (!channel.manager.createCampaign) {
|
|
12474
12897
|
throw createGtmError(
|
|
12475
12898
|
"PROVIDER_NOT_CONFIGURED",
|
|
12476
|
-
`Provider "${
|
|
12899
|
+
`Provider "${channel.providerName}" does not support campaign creation. Currently only PlusVibe supports this action.`
|
|
12477
12900
|
);
|
|
12478
12901
|
}
|
|
12479
|
-
return manager.createCampaign(name);
|
|
12902
|
+
return channel.manager.createCampaign(name);
|
|
12480
12903
|
}
|
|
12481
12904
|
default:
|
|
12482
12905
|
throw createGtmError("QUERY_INVALID", `Unknown campaign action: ${action}`);
|
|
@@ -12490,47 +12913,6 @@ var init_campaign2 = __esm({
|
|
|
12490
12913
|
}
|
|
12491
12914
|
});
|
|
12492
12915
|
|
|
12493
|
-
// src/handlers/outreach.ts
|
|
12494
|
-
var outreach_exports = {};
|
|
12495
|
-
__export(outreach_exports, {
|
|
12496
|
-
handleOutreach: () => handleOutreach
|
|
12497
|
-
});
|
|
12498
|
-
async function handleOutreach(args, ctx) {
|
|
12499
|
-
const action = args.action;
|
|
12500
|
-
const providerName = args.provider || void 0;
|
|
12501
|
-
const providers = providerName ? [ctx.registry.get(providerName)].filter(Boolean) : ctx.registry.getForCapability("outreach_linkedin");
|
|
12502
|
-
if (providers.length === 0) {
|
|
12503
|
-
throw new Error(
|
|
12504
|
-
"No providers configured for LinkedIn outreach. Add a provider with outreach_linkedin capability."
|
|
12505
|
-
);
|
|
12506
|
-
}
|
|
12507
|
-
const provider = providers[0];
|
|
12508
|
-
const manager = provider;
|
|
12509
|
-
switch (action) {
|
|
12510
|
-
case "list":
|
|
12511
|
-
return { provider: provider.name, campaigns: await manager.listCampaigns() };
|
|
12512
|
-
case "stats": {
|
|
12513
|
-
const campaignId = args.campaign_id;
|
|
12514
|
-
if (!campaignId) throw new Error("campaign_id is required for stats action");
|
|
12515
|
-
return manager.getCampaignStats(campaignId);
|
|
12516
|
-
}
|
|
12517
|
-
case "add_leads": {
|
|
12518
|
-
const campaignId = args.campaign_id;
|
|
12519
|
-
const leads = args.leads;
|
|
12520
|
-
if (!campaignId) throw new Error("campaign_id is required for add_leads action");
|
|
12521
|
-
if (!leads?.length) throw new Error("leads array is required for add_leads action");
|
|
12522
|
-
return manager.addLeadsToCampaign(campaignId, leads);
|
|
12523
|
-
}
|
|
12524
|
-
default:
|
|
12525
|
-
throw new Error(`Unknown outreach action: ${action}`);
|
|
12526
|
-
}
|
|
12527
|
-
}
|
|
12528
|
-
var init_outreach2 = __esm({
|
|
12529
|
-
"src/handlers/outreach.ts"() {
|
|
12530
|
-
"use strict";
|
|
12531
|
-
}
|
|
12532
|
-
});
|
|
12533
|
-
|
|
12534
12916
|
// src/handlers/crm.ts
|
|
12535
12917
|
var crm_exports = {};
|
|
12536
12918
|
__export(crm_exports, {
|
|
@@ -12631,6 +13013,17 @@ async function handleStatus(args, ctx) {
|
|
|
12631
13013
|
})
|
|
12632
13014
|
);
|
|
12633
13015
|
const client = ctx.session.getActiveContext();
|
|
13016
|
+
const detail = args.detail ?? "summary";
|
|
13017
|
+
if (detail !== "full") {
|
|
13018
|
+
return {
|
|
13019
|
+
client: client.clientName,
|
|
13020
|
+
providers: results.map(({ provider, healthy, credits }) => ({
|
|
13021
|
+
provider,
|
|
13022
|
+
healthy,
|
|
13023
|
+
...credits && { credits }
|
|
13024
|
+
}))
|
|
13025
|
+
};
|
|
13026
|
+
}
|
|
12634
13027
|
const configuredCapabilities = new Set(results.flatMap((r) => r.capabilities));
|
|
12635
13028
|
const unconfigured = TOOL_CAPABILITY_REQUIREMENTS.filter(
|
|
12636
13029
|
(req) => !configuredCapabilities.has(req.capability)
|
|
@@ -12649,7 +13042,7 @@ var init_status2 = __esm({
|
|
|
12649
13042
|
init_dist();
|
|
12650
13043
|
TOOL_CAPABILITY_REQUIREMENTS = [
|
|
12651
13044
|
{
|
|
12652
|
-
tool: "
|
|
13045
|
+
tool: "gtm_extract",
|
|
12653
13046
|
requires: "harvest",
|
|
12654
13047
|
capability: "extract_linkedin",
|
|
12655
13048
|
setup: "Set GTM_HARVEST_API_KEY or add harvest.api_key to config.yaml"
|
|
@@ -12679,10 +13072,10 @@ var init_status2 = __esm({
|
|
|
12679
13072
|
setup: "Set GTM_PLUSVIBE_API_KEY, GTM_INSTANTLY_API_KEY, or GTM_SMARTLEAD_API_KEY"
|
|
12680
13073
|
},
|
|
12681
13074
|
{
|
|
12682
|
-
tool: "
|
|
13075
|
+
tool: "gtm_campaign",
|
|
12683
13076
|
requires: "heyreach",
|
|
12684
13077
|
capability: "outreach_linkedin",
|
|
12685
|
-
setup: "Set GTM_HEYREACH_API_KEY"
|
|
13078
|
+
setup: "Set GTM_HEYREACH_API_KEY (LinkedIn outreach via gtm_campaign)"
|
|
12686
13079
|
},
|
|
12687
13080
|
{ tool: "gtm_crm", requires: "attio", capability: "crm_read", setup: "Set GTM_ATTIO_API_KEY" },
|
|
12688
13081
|
{
|
|
@@ -12753,90 +13146,59 @@ var init_configure2 = __esm({
|
|
|
12753
13146
|
}
|
|
12754
13147
|
});
|
|
12755
13148
|
|
|
12756
|
-
// src/handlers/
|
|
12757
|
-
var
|
|
12758
|
-
__export(
|
|
12759
|
-
|
|
13149
|
+
// src/handlers/feedback.ts
|
|
13150
|
+
var feedback_exports = {};
|
|
13151
|
+
__export(feedback_exports, {
|
|
13152
|
+
handleFeedback: () => handleFeedback
|
|
12760
13153
|
});
|
|
12761
|
-
async function
|
|
12762
|
-
const
|
|
12763
|
-
if (!
|
|
12764
|
-
throw
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
),
|
|
12768
|
-
{ statusCode: 503 }
|
|
13154
|
+
async function handleFeedback(args) {
|
|
13155
|
+
const input = args;
|
|
13156
|
+
if (!SUPABASE_KEY) {
|
|
13157
|
+
throw createGtmError(
|
|
13158
|
+
"PROVIDER_NOT_CONFIGURED",
|
|
13159
|
+
"Supabase not configured for feedback. Set GTM_SUPABASE_KEY or SUPABASE_SERVICE_ROLE_KEY env var."
|
|
12769
13160
|
);
|
|
12770
13161
|
}
|
|
12771
|
-
const
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
|
|
12783
|
-
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
company: args.company,
|
|
12796
|
-
postedLimit: args.posted_within
|
|
12797
|
-
});
|
|
12798
|
-
return {
|
|
12799
|
-
action: "company_posts",
|
|
12800
|
-
provider: "harvest",
|
|
12801
|
-
count: result.elements.length,
|
|
12802
|
-
posts: result.elements,
|
|
12803
|
-
pagination: result.pagination
|
|
12804
|
-
};
|
|
12805
|
-
}
|
|
12806
|
-
case "search_posts": {
|
|
12807
|
-
const result = await adapter.searchPosts(args.query, {
|
|
12808
|
-
sortBy: args.sort,
|
|
12809
|
-
postedLimit: args.posted_within
|
|
12810
|
-
});
|
|
12811
|
-
return {
|
|
12812
|
-
action: "search_posts",
|
|
12813
|
-
provider: "harvest",
|
|
12814
|
-
count: result.elements.length,
|
|
12815
|
-
posts: result.elements,
|
|
12816
|
-
pagination: result.pagination
|
|
12817
|
-
};
|
|
12818
|
-
}
|
|
12819
|
-
case "engagement": {
|
|
12820
|
-
const result = await adapter.getPostEngagement(
|
|
12821
|
-
args.post_url,
|
|
12822
|
-
args.engagement_type ?? "both"
|
|
12823
|
-
);
|
|
12824
|
-
return {
|
|
12825
|
-
action: "engagement",
|
|
12826
|
-
provider: "harvest",
|
|
12827
|
-
comments_count: result.comments.length,
|
|
12828
|
-
reactions_count: result.reactions.length,
|
|
12829
|
-
comments: result.comments,
|
|
12830
|
-
reactions: result.reactions
|
|
12831
|
-
};
|
|
12832
|
-
}
|
|
12833
|
-
default:
|
|
12834
|
-
throw new Error(`Unknown linkedin action: ${action}`);
|
|
13162
|
+
const row = {
|
|
13163
|
+
type: input.type,
|
|
13164
|
+
message: input.message.trim(),
|
|
13165
|
+
context: input.context ?? {}
|
|
13166
|
+
};
|
|
13167
|
+
const response = await fetch(`${SUPABASE_URL}/rest/v1/gtm_feedback`, {
|
|
13168
|
+
method: "POST",
|
|
13169
|
+
headers: {
|
|
13170
|
+
"Content-Type": "application/json",
|
|
13171
|
+
apikey: SUPABASE_KEY,
|
|
13172
|
+
Authorization: `Bearer ${SUPABASE_KEY}`,
|
|
13173
|
+
Prefer: "return=representation"
|
|
13174
|
+
},
|
|
13175
|
+
body: JSON.stringify(row)
|
|
13176
|
+
});
|
|
13177
|
+
if (!response.ok) {
|
|
13178
|
+
const body = await response.text().catch(() => "unknown");
|
|
13179
|
+
throw createGtmError(
|
|
13180
|
+
"PROVIDER_REQUEST_FAILED",
|
|
13181
|
+
`Failed to submit feedback: ${response.status}`,
|
|
13182
|
+
{
|
|
13183
|
+
details: { status: response.status, body }
|
|
13184
|
+
}
|
|
13185
|
+
);
|
|
12835
13186
|
}
|
|
13187
|
+
const [created] = await response.json();
|
|
13188
|
+
return {
|
|
13189
|
+
submitted: true,
|
|
13190
|
+
id: created?.id,
|
|
13191
|
+
type: input.type,
|
|
13192
|
+
message: `Feedback submitted. Thank you! (ID: ${created?.id ?? "unknown"})`
|
|
13193
|
+
};
|
|
12836
13194
|
}
|
|
12837
|
-
var
|
|
12838
|
-
|
|
13195
|
+
var SUPABASE_URL, SUPABASE_KEY;
|
|
13196
|
+
var init_feedback2 = __esm({
|
|
13197
|
+
"src/handlers/feedback.ts"() {
|
|
12839
13198
|
"use strict";
|
|
13199
|
+
init_errors();
|
|
13200
|
+
SUPABASE_URL = process.env.GTM_SUPABASE_URL ?? process.env.SUPABASE_URL ?? "https://qvawbxpijxlwdkolmjrs.supabase.co";
|
|
13201
|
+
SUPABASE_KEY = process.env.GTM_SUPABASE_KEY ?? process.env.SUPABASE_SERVICE_ROLE_KEY ?? "";
|
|
12840
13202
|
}
|
|
12841
13203
|
});
|
|
12842
13204
|
|
|
@@ -12850,13 +13212,34 @@ function isRateLimitError2(error) {
|
|
|
12850
13212
|
async function sleep(ms) {
|
|
12851
13213
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12852
13214
|
}
|
|
13215
|
+
function withHandlerTimeout(promise, ms) {
|
|
13216
|
+
return new Promise((resolve, reject) => {
|
|
13217
|
+
const timer = setTimeout(
|
|
13218
|
+
() => reject(
|
|
13219
|
+
Object.assign(new Error(`Tool call timed out after ${ms}ms`), { code: "HANDLER_TIMEOUT" })
|
|
13220
|
+
),
|
|
13221
|
+
ms
|
|
13222
|
+
);
|
|
13223
|
+
promise.then(
|
|
13224
|
+
(val) => {
|
|
13225
|
+
clearTimeout(timer);
|
|
13226
|
+
resolve(val);
|
|
13227
|
+
},
|
|
13228
|
+
(err) => {
|
|
13229
|
+
clearTimeout(timer);
|
|
13230
|
+
reject(err);
|
|
13231
|
+
}
|
|
13232
|
+
);
|
|
13233
|
+
});
|
|
13234
|
+
}
|
|
12853
13235
|
async function handleToolCall(name, args, ctx) {
|
|
12854
13236
|
let lastError;
|
|
12855
13237
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
12856
13238
|
try {
|
|
12857
|
-
const
|
|
13239
|
+
const timeoutMs = args._testTimeoutMs ?? HANDLER_TIMEOUT_MS;
|
|
13240
|
+
const result = await withHandlerTimeout(dispatch(name, args, ctx), timeoutMs);
|
|
12858
13241
|
return {
|
|
12859
|
-
content: [{ type: "text", text: JSON.stringify(result
|
|
13242
|
+
content: [{ type: "text", text: JSON.stringify(result) ?? "{}" }]
|
|
12860
13243
|
};
|
|
12861
13244
|
} catch (error) {
|
|
12862
13245
|
lastError = error;
|
|
@@ -12868,6 +13251,13 @@ async function handleToolCall(name, args, ctx) {
|
|
|
12868
13251
|
break;
|
|
12869
13252
|
}
|
|
12870
13253
|
}
|
|
13254
|
+
if (lastError instanceof Error && "code" in lastError && lastError.code === "HANDLER_TIMEOUT") {
|
|
13255
|
+
const wrapped2 = createGtmError("HANDLER_TIMEOUT", lastError.message);
|
|
13256
|
+
return {
|
|
13257
|
+
content: [{ type: "text", text: JSON.stringify(wrapped2) }],
|
|
13258
|
+
isError: true
|
|
13259
|
+
};
|
|
13260
|
+
}
|
|
12871
13261
|
if (isGtmError(lastError)) {
|
|
12872
13262
|
return {
|
|
12873
13263
|
content: [{ type: "text", text: JSON.stringify(lastError) }],
|
|
@@ -12915,10 +13305,6 @@ async function dispatch(name, args, ctx) {
|
|
|
12915
13305
|
const { handleCampaign: handleCampaign2 } = await Promise.resolve().then(() => (init_campaign2(), campaign_exports));
|
|
12916
13306
|
return handleCampaign2(args, ctx);
|
|
12917
13307
|
}
|
|
12918
|
-
case "gtm_outreach": {
|
|
12919
|
-
const { handleOutreach: handleOutreach2 } = await Promise.resolve().then(() => (init_outreach2(), outreach_exports));
|
|
12920
|
-
return handleOutreach2(args, ctx);
|
|
12921
|
-
}
|
|
12922
13308
|
case "gtm_crm": {
|
|
12923
13309
|
const { handleCrm: handleCrm2 } = await Promise.resolve().then(() => (init_crm2(), crm_exports));
|
|
12924
13310
|
return handleCrm2(args, ctx);
|
|
@@ -12931,9 +13317,9 @@ async function dispatch(name, args, ctx) {
|
|
|
12931
13317
|
const { handleConfigure: handleConfigure2 } = await Promise.resolve().then(() => (init_configure2(), configure_exports));
|
|
12932
13318
|
return handleConfigure2(args, ctx);
|
|
12933
13319
|
}
|
|
12934
|
-
case "
|
|
12935
|
-
const {
|
|
12936
|
-
return
|
|
13320
|
+
case "gtm_feedback": {
|
|
13321
|
+
const { handleFeedback: handleFeedback2 } = await Promise.resolve().then(() => (init_feedback2(), feedback_exports));
|
|
13322
|
+
return handleFeedback2(args);
|
|
12937
13323
|
}
|
|
12938
13324
|
// ─── Meta-Tools ───────────────────────────────────────
|
|
12939
13325
|
case "gtm_guide": {
|
|
@@ -12949,7 +13335,7 @@ async function dispatch(name, args, ctx) {
|
|
|
12949
13335
|
}
|
|
12950
13336
|
const result = {
|
|
12951
13337
|
name: tool.name,
|
|
12952
|
-
description: tool.description,
|
|
13338
|
+
description: TOOL_DESCRIPTIONS[tool.name] ?? tool.description,
|
|
12953
13339
|
inputSchema: tool.inputSchema
|
|
12954
13340
|
};
|
|
12955
13341
|
const toolKey = toolName.replace("gtm_", "");
|
|
@@ -12972,7 +13358,7 @@ async function dispatch(name, args, ctx) {
|
|
|
12972
13358
|
throw new Error(`Unknown tool: ${name}`);
|
|
12973
13359
|
}
|
|
12974
13360
|
}
|
|
12975
|
-
var MAX_RETRIES, BASE_DELAY_MS;
|
|
13361
|
+
var MAX_RETRIES, BASE_DELAY_MS, HANDLER_TIMEOUT_MS;
|
|
12976
13362
|
var init_handlers = __esm({
|
|
12977
13363
|
"src/handlers/index.ts"() {
|
|
12978
13364
|
"use strict";
|
|
@@ -12980,8 +13366,10 @@ var init_handlers = __esm({
|
|
|
12980
13366
|
init_errors();
|
|
12981
13367
|
init_manifests();
|
|
12982
13368
|
init_tools();
|
|
13369
|
+
init_descriptions();
|
|
12983
13370
|
MAX_RETRIES = 3;
|
|
12984
13371
|
BASE_DELAY_MS = 1e3;
|
|
13372
|
+
HANDLER_TIMEOUT_MS = 12e4;
|
|
12985
13373
|
}
|
|
12986
13374
|
});
|
|
12987
13375
|
|
|
@@ -13002,7 +13390,7 @@ function validateToolArgs(toolName, args) {
|
|
|
13002
13390
|
}
|
|
13003
13391
|
return { success: true, data: result.data };
|
|
13004
13392
|
}
|
|
13005
|
-
var contactInputSchema, campaignLeadSchema,
|
|
13393
|
+
var contactInputSchema, campaignLeadSchema, detailSchema, toolSchemas, TOOL_NAMES;
|
|
13006
13394
|
var init_validation = __esm({
|
|
13007
13395
|
"src/validation.ts"() {
|
|
13008
13396
|
"use strict";
|
|
@@ -13014,18 +13402,16 @@ var init_validation = __esm({
|
|
|
13014
13402
|
linkedin_url: z2.string().optional()
|
|
13015
13403
|
});
|
|
13016
13404
|
campaignLeadSchema = z2.object({
|
|
13017
|
-
email: z2.string().email("email must be a valid email address"),
|
|
13405
|
+
email: z2.string().email("email must be a valid email address").optional(),
|
|
13406
|
+
linkedin_url: z2.string().min(1, "linkedin_url is required").optional(),
|
|
13018
13407
|
first_name: z2.string().optional(),
|
|
13019
13408
|
last_name: z2.string().optional(),
|
|
13020
13409
|
company: z2.string().optional(),
|
|
13021
13410
|
custom_fields: z2.record(z2.string()).optional()
|
|
13411
|
+
}).refine((data) => !!data.email || !!data.linkedin_url, {
|
|
13412
|
+
message: "At least one of email or linkedin_url is required"
|
|
13022
13413
|
});
|
|
13023
|
-
|
|
13024
|
-
linkedin_url: z2.string().min(1, "linkedin_url is required"),
|
|
13025
|
-
first_name: z2.string().optional(),
|
|
13026
|
-
last_name: z2.string().optional(),
|
|
13027
|
-
company: z2.string().optional()
|
|
13028
|
-
});
|
|
13414
|
+
detailSchema = z2.enum(["summary", "full"]).optional();
|
|
13029
13415
|
toolSchemas = {
|
|
13030
13416
|
// 1. Company discovery
|
|
13031
13417
|
gtm_find_companies: z2.object({
|
|
@@ -13049,7 +13435,8 @@ var init_validation = __esm({
|
|
|
13049
13435
|
platform: z2.string().optional(),
|
|
13050
13436
|
min_revenue: z2.number().optional(),
|
|
13051
13437
|
max_revenue: z2.number().optional(),
|
|
13052
|
-
output: z2.enum(["file", "inline"]).optional()
|
|
13438
|
+
output: z2.enum(["file", "inline", "csv"]).optional(),
|
|
13439
|
+
detail: detailSchema
|
|
13053
13440
|
}),
|
|
13054
13441
|
// 2. People/contact search
|
|
13055
13442
|
gtm_find_people: z2.object({
|
|
@@ -13089,16 +13476,43 @@ var init_validation = __esm({
|
|
|
13089
13476
|
max_employees: z2.number().int().min(1).optional(),
|
|
13090
13477
|
revenue_range: z2.string().optional(),
|
|
13091
13478
|
fields: z2.union([z2.literal("all"), z2.array(z2.string())]).optional(),
|
|
13092
|
-
output: z2.enum(["file", "inline"]).optional()
|
|
13479
|
+
output: z2.enum(["file", "inline", "csv"]).optional(),
|
|
13480
|
+
detail: detailSchema
|
|
13093
13481
|
}).refine(
|
|
13094
13482
|
(data) => !!(data.job_title || data.company_name || data.company_domain || data.company_domains && data.company_domains.length > 0 || data.seniority && data.seniority.length > 0 || data.filter_industry && data.filter_industry.length > 0 || data.skills && data.skills.length > 0 || data.location),
|
|
13095
13483
|
{
|
|
13096
13484
|
message: "At least one search filter required (job_title, company_name, company_domain, company_domains, seniority, filter_industry, skills, or location)"
|
|
13097
13485
|
}
|
|
13098
13486
|
),
|
|
13099
|
-
//
|
|
13100
|
-
|
|
13101
|
-
|
|
13487
|
+
// 3. Enrichment waterfall
|
|
13488
|
+
gtm_enrich: z2.object({
|
|
13489
|
+
contacts: z2.array(contactInputSchema).min(1, "contacts must contain at least 1 contact").max(100, "contacts must contain at most 100 contacts"),
|
|
13490
|
+
find: z2.array(z2.enum(["email", "phone", "title", "company", "funding", "tech_stack"])).optional(),
|
|
13491
|
+
waterfall: z2.array(z2.string()).optional(),
|
|
13492
|
+
verify: z2.boolean().optional(),
|
|
13493
|
+
dry_run: z2.boolean().optional(),
|
|
13494
|
+
detail: detailSchema
|
|
13495
|
+
}),
|
|
13496
|
+
// 4. Email verification
|
|
13497
|
+
gtm_verify: z2.object({
|
|
13498
|
+
email: z2.string().email("email must be a valid email address")
|
|
13499
|
+
}),
|
|
13500
|
+
// 5. Extract/scrape — web pages + all LinkedIn intelligence
|
|
13501
|
+
gtm_extract: z2.object({
|
|
13502
|
+
type: z2.enum(
|
|
13503
|
+
[
|
|
13504
|
+
"linkedin_profile",
|
|
13505
|
+
"linkedin_posts",
|
|
13506
|
+
"linkedin_engagement",
|
|
13507
|
+
"linkedin_search",
|
|
13508
|
+
"linkedin_company_posts",
|
|
13509
|
+
"web_page"
|
|
13510
|
+
],
|
|
13511
|
+
{
|
|
13512
|
+
message: "type must be one of: linkedin_profile, linkedin_posts, linkedin_engagement, linkedin_search, linkedin_company_posts, web_page"
|
|
13513
|
+
}
|
|
13514
|
+
),
|
|
13515
|
+
url: z2.string().url("url must be a valid URL").optional(),
|
|
13102
13516
|
linkedin_url: z2.string().optional(),
|
|
13103
13517
|
find_email: z2.boolean().optional(),
|
|
13104
13518
|
profile: z2.string().optional(),
|
|
@@ -13108,53 +13522,38 @@ var init_validation = __esm({
|
|
|
13108
13522
|
engagement_type: z2.enum(["comments", "reactions", "both"]).optional(),
|
|
13109
13523
|
query: z2.string().optional(),
|
|
13110
13524
|
sort: z2.enum(["relevance", "date"]).optional(),
|
|
13111
|
-
limit: z2.number().int().min(1).max(100).optional()
|
|
13525
|
+
limit: z2.number().int().min(1).max(100).optional(),
|
|
13526
|
+
fields: z2.array(z2.string()).optional(),
|
|
13527
|
+
detail: detailSchema
|
|
13112
13528
|
}).refine(
|
|
13113
13529
|
(data) => {
|
|
13114
|
-
switch (data.
|
|
13115
|
-
case "
|
|
13116
|
-
return !!data.linkedin_url;
|
|
13117
|
-
case "
|
|
13530
|
+
switch (data.type) {
|
|
13531
|
+
case "linkedin_profile":
|
|
13532
|
+
return !!(data.linkedin_url || data.url);
|
|
13533
|
+
case "linkedin_posts":
|
|
13534
|
+
return !!data.profile;
|
|
13535
|
+
case "linkedin_engagement":
|
|
13118
13536
|
return !!data.post_url;
|
|
13119
|
-
case "
|
|
13537
|
+
case "linkedin_search":
|
|
13120
13538
|
return !!data.query;
|
|
13121
|
-
case "
|
|
13122
|
-
return !!data.profile;
|
|
13123
|
-
case "company_posts":
|
|
13539
|
+
case "linkedin_company_posts":
|
|
13124
13540
|
return !!data.company;
|
|
13541
|
+
case "web_page":
|
|
13542
|
+
return !!data.url;
|
|
13125
13543
|
default:
|
|
13126
13544
|
return true;
|
|
13127
13545
|
}
|
|
13128
13546
|
},
|
|
13129
|
-
{ message: "Missing required field for the specified
|
|
13547
|
+
{ message: "Missing required field for the specified type" }
|
|
13130
13548
|
),
|
|
13131
|
-
// 3. Enrichment waterfall
|
|
13132
|
-
gtm_enrich: z2.object({
|
|
13133
|
-
contacts: z2.array(contactInputSchema).min(1, "contacts must contain at least 1 contact").max(100, "contacts must contain at most 100 contacts"),
|
|
13134
|
-
find: z2.array(z2.enum(["email", "phone", "title", "company", "funding", "tech_stack"])).optional(),
|
|
13135
|
-
waterfall: z2.array(z2.string()).optional(),
|
|
13136
|
-
verify: z2.boolean().optional(),
|
|
13137
|
-
dry_run: z2.boolean().optional()
|
|
13138
|
-
}),
|
|
13139
|
-
// 4. Email verification
|
|
13140
|
-
gtm_verify: z2.object({
|
|
13141
|
-
email: z2.string().email("email must be a valid email address")
|
|
13142
|
-
}),
|
|
13143
|
-
// 5. Extract/scrape
|
|
13144
|
-
gtm_extract: z2.object({
|
|
13145
|
-
url: z2.string().url("url must be a valid URL"),
|
|
13146
|
-
type: z2.enum(["linkedin_profile", "linkedin_post", "web_page"], {
|
|
13147
|
-
message: "type must be one of: linkedin_profile, linkedin_post, web_page"
|
|
13148
|
-
}),
|
|
13149
|
-
fields: z2.array(z2.string()).optional()
|
|
13150
|
-
}),
|
|
13151
13549
|
// 6. Raw HTTP fetch
|
|
13152
13550
|
gtm_fetch: z2.object({
|
|
13153
13551
|
url: z2.string().url("url must be a valid URL"),
|
|
13154
13552
|
method: z2.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).optional(),
|
|
13155
13553
|
headers: z2.record(z2.string()).optional(),
|
|
13156
13554
|
body: z2.record(z2.unknown()).optional(),
|
|
13157
|
-
params: z2.record(z2.string()).optional()
|
|
13555
|
+
params: z2.record(z2.string()).optional(),
|
|
13556
|
+
detail: detailSchema
|
|
13158
13557
|
}),
|
|
13159
13558
|
// 7. AI data operations
|
|
13160
13559
|
gtm_ai: z2.object({
|
|
@@ -13167,7 +13566,7 @@ var init_validation = __esm({
|
|
|
13167
13566
|
prompt: z2.string().optional(),
|
|
13168
13567
|
model: z2.string().optional()
|
|
13169
13568
|
}),
|
|
13170
|
-
// 8.
|
|
13569
|
+
// 8. Campaign management (cold email + LinkedIn outreach)
|
|
13171
13570
|
gtm_campaign: z2.object({
|
|
13172
13571
|
action: z2.enum(["list", "stats", "add_leads", "create"], {
|
|
13173
13572
|
message: "action must be one of: list, stats, add_leads, create"
|
|
@@ -13175,15 +13574,8 @@ var init_validation = __esm({
|
|
|
13175
13574
|
campaign_id: z2.string().optional(),
|
|
13176
13575
|
leads: z2.array(campaignLeadSchema).optional(),
|
|
13177
13576
|
name: z2.string().optional(),
|
|
13178
|
-
provider: z2.string().optional()
|
|
13179
|
-
|
|
13180
|
-
// 9. LinkedIn outreach management
|
|
13181
|
-
gtm_outreach: z2.object({
|
|
13182
|
-
action: z2.enum(["list", "stats", "add_leads"], {
|
|
13183
|
-
message: "action must be one of: list, stats, add_leads"
|
|
13184
|
-
}),
|
|
13185
|
-
campaign_id: z2.string().optional(),
|
|
13186
|
-
leads: z2.array(outreachLeadSchema).optional()
|
|
13577
|
+
provider: z2.string().optional(),
|
|
13578
|
+
detail: detailSchema
|
|
13187
13579
|
}),
|
|
13188
13580
|
// 10. CRM read/write
|
|
13189
13581
|
gtm_crm: z2.object({
|
|
@@ -13196,7 +13588,8 @@ var init_validation = __esm({
|
|
|
13196
13588
|
}),
|
|
13197
13589
|
// 11. Provider status/credits
|
|
13198
13590
|
gtm_status: z2.object({
|
|
13199
|
-
provider: z2.string().optional()
|
|
13591
|
+
provider: z2.string().optional(),
|
|
13592
|
+
detail: detailSchema
|
|
13200
13593
|
}),
|
|
13201
13594
|
// 12. Configuration management
|
|
13202
13595
|
gtm_configure: z2.object({
|
|
@@ -13218,6 +13611,7 @@ var init_validation = __esm({
|
|
|
13218
13611
|
"cold_outreach",
|
|
13219
13612
|
"linkedin_outreach",
|
|
13220
13613
|
"crm_sync",
|
|
13614
|
+
"export_to_webhook",
|
|
13221
13615
|
"list_tasks"
|
|
13222
13616
|
])
|
|
13223
13617
|
}),
|
|
@@ -13229,6 +13623,14 @@ var init_validation = __esm({
|
|
|
13229
13623
|
gtm_execute: z2.object({
|
|
13230
13624
|
tool: z2.string().min(1, "tool is required"),
|
|
13231
13625
|
arguments: z2.record(z2.unknown()).optional()
|
|
13626
|
+
}),
|
|
13627
|
+
// 16. Feedback
|
|
13628
|
+
gtm_feedback: z2.object({
|
|
13629
|
+
type: z2.enum(["bug", "feature_request", "provider_request"], {
|
|
13630
|
+
message: "type must be one of: bug, feature_request, provider_request"
|
|
13631
|
+
}),
|
|
13632
|
+
message: z2.string().min(1, "message is required"),
|
|
13633
|
+
context: z2.record(z2.unknown()).optional()
|
|
13232
13634
|
})
|
|
13233
13635
|
};
|
|
13234
13636
|
TOOL_NAMES = Object.keys(toolSchemas);
|
|
@@ -13299,15 +13701,16 @@ function createServer() {
|
|
|
13299
13701
|
isError: true
|
|
13300
13702
|
};
|
|
13301
13703
|
}
|
|
13704
|
+
const fullDescription = TOOL_DESCRIPTIONS[tool.name] ?? tool.description;
|
|
13302
13705
|
return {
|
|
13303
13706
|
content: [
|
|
13304
13707
|
{
|
|
13305
13708
|
type: "text",
|
|
13306
|
-
text: JSON.stringify(
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
)
|
|
13709
|
+
text: JSON.stringify({
|
|
13710
|
+
name: tool.name,
|
|
13711
|
+
description: fullDescription,
|
|
13712
|
+
inputSchema: tool.inputSchema
|
|
13713
|
+
})
|
|
13311
13714
|
}
|
|
13312
13715
|
]
|
|
13313
13716
|
};
|