gtm-now 0.10.8 → 0.10.9
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 +408 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9737,7 +9737,8 @@ var init_prospeo2 = __esm({
|
|
|
9737
9737
|
return {
|
|
9738
9738
|
email: response.person.email.email,
|
|
9739
9739
|
quality: response.person.email.status === "verified" ? "95%" : "70%",
|
|
9740
|
-
provider: this.name
|
|
9740
|
+
provider: this.name,
|
|
9741
|
+
credits_consumed: 1
|
|
9741
9742
|
};
|
|
9742
9743
|
} catch (error) {
|
|
9743
9744
|
logError("prospeo:enrichEmail", error instanceof Error ? error : new Error(String(error)));
|
|
@@ -10731,7 +10732,8 @@ var init_leadmagic3 = __esm({
|
|
|
10731
10732
|
return {
|
|
10732
10733
|
email: response.email,
|
|
10733
10734
|
quality: mapEmailQuality(response.status),
|
|
10734
|
-
provider: this.name
|
|
10735
|
+
provider: this.name,
|
|
10736
|
+
credits_consumed: response.credits_consumed
|
|
10735
10737
|
};
|
|
10736
10738
|
}
|
|
10737
10739
|
}
|
|
@@ -10744,7 +10746,8 @@ var init_leadmagic3 = __esm({
|
|
|
10744
10746
|
email: response.email,
|
|
10745
10747
|
quality: "80%",
|
|
10746
10748
|
// Profile-to-email doesn't return validation status
|
|
10747
|
-
provider: this.name
|
|
10749
|
+
provider: this.name,
|
|
10750
|
+
credits_consumed: response.credits_consumed
|
|
10748
10751
|
};
|
|
10749
10752
|
}
|
|
10750
10753
|
}
|
|
@@ -11190,6 +11193,10 @@ var init_prospect = __esm({
|
|
|
11190
11193
|
type: "string",
|
|
11191
11194
|
enum: ["summary", "full"],
|
|
11192
11195
|
description: 'Response detail level. "summary" (default) returns slim results. "full" includes applied_filters and all metadata.'
|
|
11196
|
+
},
|
|
11197
|
+
review: {
|
|
11198
|
+
type: "boolean",
|
|
11199
|
+
description: "Create a viewer table with results. Returns table_id and URL for human review. Other tools can read from this table via table_id parameter."
|
|
11193
11200
|
}
|
|
11194
11201
|
},
|
|
11195
11202
|
required: []
|
|
@@ -11306,6 +11313,10 @@ var init_prospect = __esm({
|
|
|
11306
11313
|
type: "string",
|
|
11307
11314
|
enum: ["summary", "full"],
|
|
11308
11315
|
description: 'Response detail level. "summary" (default) returns slim results. "full" includes applied_filters and all metadata.'
|
|
11316
|
+
},
|
|
11317
|
+
review: {
|
|
11318
|
+
type: "boolean",
|
|
11319
|
+
description: "Create a viewer table with results. Returns table_id and URL for human review. Other tools can read from this table via table_id parameter."
|
|
11309
11320
|
}
|
|
11310
11321
|
},
|
|
11311
11322
|
required: []
|
|
@@ -11363,9 +11374,17 @@ var init_enrich = __esm({
|
|
|
11363
11374
|
type: "string",
|
|
11364
11375
|
enum: ["summary", "full"],
|
|
11365
11376
|
description: 'Response detail level. "summary" (default) returns email/phone/providers per contact. "full" includes waterfall attempt details (provider, duration, errors) per contact.'
|
|
11377
|
+
},
|
|
11378
|
+
table_id: {
|
|
11379
|
+
type: "string",
|
|
11380
|
+
description: "Read contacts from a viewer table instead of passing inline. Fetches selected rows, enriches them, and writes results back to the table."
|
|
11381
|
+
},
|
|
11382
|
+
review: {
|
|
11383
|
+
type: "boolean",
|
|
11384
|
+
description: "Create a viewer table with enrichment results."
|
|
11366
11385
|
}
|
|
11367
11386
|
},
|
|
11368
|
-
required: [
|
|
11387
|
+
required: []
|
|
11369
11388
|
}
|
|
11370
11389
|
},
|
|
11371
11390
|
{
|
|
@@ -11608,6 +11627,10 @@ var init_campaign = __esm({
|
|
|
11608
11627
|
type: "string",
|
|
11609
11628
|
enum: ["summary", "full"],
|
|
11610
11629
|
description: 'Response detail level. "summary" (default) returns key stats only. "full" returns all campaign data.'
|
|
11630
|
+
},
|
|
11631
|
+
table_id: {
|
|
11632
|
+
type: "string",
|
|
11633
|
+
description: "Read leads from a viewer table instead of passing inline. Fetches selected rows and maps them to campaign leads."
|
|
11611
11634
|
}
|
|
11612
11635
|
},
|
|
11613
11636
|
required: ["action"]
|
|
@@ -11666,6 +11689,10 @@ var init_crm = __esm({
|
|
|
11666
11689
|
provider: {
|
|
11667
11690
|
type: "string",
|
|
11668
11691
|
description: "CRM provider override (uses first available if omitted)"
|
|
11692
|
+
},
|
|
11693
|
+
table_id: {
|
|
11694
|
+
type: "string",
|
|
11695
|
+
description: "Read contacts from a viewer table for bulk create/update. Fetches selected rows and maps them to CRM person records."
|
|
11669
11696
|
}
|
|
11670
11697
|
},
|
|
11671
11698
|
required: ["action"]
|
|
@@ -11804,12 +11831,13 @@ var init_review = __esm({
|
|
|
11804
11831
|
items: { type: "object" },
|
|
11805
11832
|
description: "Array of row objects (for create)"
|
|
11806
11833
|
},
|
|
11807
|
-
table_id: { type: "string", description: "Table ID (for update, get_selected)" },
|
|
11834
|
+
table_id: { type: "string", description: "Table ID (for update, get_selected, or optional client-provided ID on create)" },
|
|
11808
11835
|
rows: {
|
|
11809
11836
|
type: "array",
|
|
11810
11837
|
items: { type: "object" },
|
|
11811
11838
|
description: "Updated row objects with _row_id (for update)"
|
|
11812
|
-
}
|
|
11839
|
+
},
|
|
11840
|
+
ttl_seconds: { type: "number", description: "Optional TTL override in seconds (default: 24h). For create only." }
|
|
11813
11841
|
},
|
|
11814
11842
|
required: ["action"]
|
|
11815
11843
|
}
|
|
@@ -11839,6 +11867,8 @@ Tip: DiscoLike lookalike mode is extremely powerful. If you have 2-3 example com
|
|
|
11839
11867
|
|
|
11840
11868
|
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.
|
|
11841
11869
|
|
|
11870
|
+
Pass review: true to push discovery results to a visual review table at view.gtmnow.com so the user can select companies before enrichment or outreach.
|
|
11871
|
+
|
|
11842
11872
|
Exa (provider: "exa") \u2014 semantic/neural search:
|
|
11843
11873
|
- Meaning-based ICP matching: "B2B SaaS companies selling to healthcare, not healthtech vendors" actually works
|
|
11844
11874
|
- Best for: nuanced ICP descriptions where keyword matching fails
|
|
@@ -11865,7 +11895,9 @@ After finding people, use:
|
|
|
11865
11895
|
- gtm_extract to check LinkedIn activity and engagement signals
|
|
11866
11896
|
|
|
11867
11897
|
Known limitations:
|
|
11868
|
-
- department + seniority filters combined may return 0 results (Disco API uses strict AND). Use one or the other
|
|
11898
|
+
- department + seniority filters combined may return 0 results (Disco API uses strict AND). Use one or the other.
|
|
11899
|
+
|
|
11900
|
+
Pass review: true to push results to a visual review table at view.gtmnow.com so the user can select rows before proceeding.`,
|
|
11869
11901
|
gtm_enrich: `Email enrichment via waterfall \u2014 tries multiple providers in order, stops on first match. Supports 1-100 contacts per call.
|
|
11870
11902
|
|
|
11871
11903
|
IMPORTANT: Defaults to email only. Phone enrichment costs extra per lookup \u2014 only include when the user explicitly asks for phone numbers.
|
|
@@ -11878,7 +11910,9 @@ Providers (in configured waterfall order):
|
|
|
11878
11910
|
|
|
11879
11911
|
Input needs: first_name + last_name minimum. Adding company_domain dramatically improves hit rates. LinkedIn URL is the gold standard input.
|
|
11880
11912
|
|
|
11881
|
-
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
|
|
11913
|
+
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.
|
|
11914
|
+
|
|
11915
|
+
Pass table_id to read contacts from an existing review table (selected rows only). Pass review: true to push enrichment results to a new review table.`,
|
|
11882
11916
|
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.",
|
|
11883
11917
|
gtm_extract: `Extract structured data from any URL or platform. LinkedIn profiles, posts, engagement, search results, company posts, and web pages.
|
|
11884
11918
|
|
|
@@ -11902,8 +11936,8 @@ Pairs well with:
|
|
|
11902
11936
|
Note: HarvestAPI charges per request. For engagement mining, each post lookup counts as a request. Use limit to control costs.`,
|
|
11903
11937
|
gtm_fetch: "Make an HTTP call to any API endpoint. Use this to pull data from services that don't have a dedicated provider.",
|
|
11904
11938
|
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).",
|
|
11905
|
-
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.",
|
|
11906
|
-
gtm_crm: "Read and write CRM data. Find, create, or update people and companies. Create deals. Supports Attio and other CRM providers.",
|
|
11939
|
+
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. Pass table_id to read leads directly from a review table (selected rows only).",
|
|
11940
|
+
gtm_crm: "Read and write CRM data. Find, create, or update people and companies. Create deals. Supports Attio and other CRM providers. For the create action, pass table_id to bulk-create contacts from a review table (selected rows only).",
|
|
11907
11941
|
gtm_status: "Check provider health, credit balances, and active configuration. Shows which providers are configured, their capabilities, remaining credits, and the current client context.",
|
|
11908
11942
|
gtm_configure: "Manage GTM configuration. Switch between clients, view active config, set waterfall order, list available providers.",
|
|
11909
11943
|
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.",
|
|
@@ -11916,7 +11950,7 @@ Note: HarvestAPI charges per request. For engagement mining, each post lookup co
|
|
|
11916
11950
|
});
|
|
11917
11951
|
|
|
11918
11952
|
// src/tools/index.ts
|
|
11919
|
-
var capabilityTools, guideTool, toolHelpTool, executeTool, metaTools, tools, toolsByName, toolCategories, workflowRecipes;
|
|
11953
|
+
var capabilityTools, guideTool, toolHelpTool, executeTool, metaTools, tools, MCP_HIDDEN_TOOLS, mcpTools, toolsByName, toolCategories, workflowRecipes;
|
|
11920
11954
|
var init_tools = __esm({
|
|
11921
11955
|
"src/tools/index.ts"() {
|
|
11922
11956
|
"use strict";
|
|
@@ -12012,6 +12046,8 @@ var init_tools = __esm({
|
|
|
12012
12046
|
};
|
|
12013
12047
|
metaTools = [guideTool, toolHelpTool, executeTool];
|
|
12014
12048
|
tools = [...capabilityTools, ...metaTools];
|
|
12049
|
+
MCP_HIDDEN_TOOLS = /* @__PURE__ */ new Set(["gtm_execute", "gtm_guide", "gtm_tool_help"]);
|
|
12050
|
+
mcpTools = tools.filter((t) => !MCP_HIDDEN_TOOLS.has(t.name));
|
|
12015
12051
|
toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
|
|
12016
12052
|
toolCategories = {
|
|
12017
12053
|
prospect: prospectTools.map((t) => t.name),
|
|
@@ -12271,6 +12307,7 @@ async function enrichContact(contact, config, registry, exhausted) {
|
|
|
12271
12307
|
let phone;
|
|
12272
12308
|
let phoneProvider;
|
|
12273
12309
|
let charged = false;
|
|
12310
|
+
let creditsConsumed;
|
|
12274
12311
|
const cacheKey = getCacheKey(contact);
|
|
12275
12312
|
const cached = cacheKey ? enrichmentCache.get(cacheKey) : null;
|
|
12276
12313
|
for (const field of config.find) {
|
|
@@ -12301,6 +12338,9 @@ async function enrichContact(contact, config, registry, exhausted) {
|
|
|
12301
12338
|
email = fieldResult.value;
|
|
12302
12339
|
emailProvider = fieldResult.provider;
|
|
12303
12340
|
charged = true;
|
|
12341
|
+
if (fieldResult.credits_consumed) {
|
|
12342
|
+
creditsConsumed = (creditsConsumed ?? 0) + fieldResult.credits_consumed;
|
|
12343
|
+
}
|
|
12304
12344
|
} else if (field === "phone" && fieldResult.value) {
|
|
12305
12345
|
phone = fieldResult.value;
|
|
12306
12346
|
phoneProvider = fieldResult.provider;
|
|
@@ -12323,7 +12363,8 @@ async function enrichContact(contact, config, registry, exhausted) {
|
|
|
12323
12363
|
email_provider: emailProvider,
|
|
12324
12364
|
phone_provider: phoneProvider,
|
|
12325
12365
|
attempts,
|
|
12326
|
-
charged
|
|
12366
|
+
charged,
|
|
12367
|
+
...creditsConsumed !== void 0 && { credits_consumed: creditsConsumed }
|
|
12327
12368
|
};
|
|
12328
12369
|
}
|
|
12329
12370
|
async function enrichField(contact, field, config, registry, exhausted) {
|
|
@@ -12348,6 +12389,7 @@ async function enrichField(contact, field, config, registry, exhausted) {
|
|
|
12348
12389
|
try {
|
|
12349
12390
|
let value;
|
|
12350
12391
|
const providerTimeoutMs = config.providerTimeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
12392
|
+
let creditsConsumed;
|
|
12351
12393
|
if (field === "email" && "enrichEmail" in provider) {
|
|
12352
12394
|
const result = await withTimeout(
|
|
12353
12395
|
provider.enrichEmail(contact),
|
|
@@ -12355,6 +12397,7 @@ async function enrichField(contact, field, config, registry, exhausted) {
|
|
|
12355
12397
|
providerName
|
|
12356
12398
|
);
|
|
12357
12399
|
value = result?.email;
|
|
12400
|
+
creditsConsumed = result?.credits_consumed;
|
|
12358
12401
|
} else if (field === "phone" && "enrichPhone" in provider) {
|
|
12359
12402
|
const result = await withTimeout(
|
|
12360
12403
|
provider.enrichPhone(contact),
|
|
@@ -12372,7 +12415,7 @@ async function enrichField(contact, field, config, registry, exhausted) {
|
|
|
12372
12415
|
duration_ms: Date.now() - start
|
|
12373
12416
|
});
|
|
12374
12417
|
if (found && config.stopOnFirst) {
|
|
12375
|
-
return { value, provider: providerName, attempts };
|
|
12418
|
+
return { value, provider: providerName, credits_consumed: creditsConsumed, attempts };
|
|
12376
12419
|
}
|
|
12377
12420
|
break;
|
|
12378
12421
|
} catch (error) {
|
|
@@ -12464,6 +12507,123 @@ var init_waterfall = __esm({
|
|
|
12464
12507
|
}
|
|
12465
12508
|
});
|
|
12466
12509
|
|
|
12510
|
+
// src/utils/table-bridge.ts
|
|
12511
|
+
function asString(value) {
|
|
12512
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
12513
|
+
}
|
|
12514
|
+
function splitName(full) {
|
|
12515
|
+
const idx = full.indexOf(" ");
|
|
12516
|
+
if (idx === -1) return { first_name: full, last_name: "" };
|
|
12517
|
+
return { first_name: full.slice(0, idx), last_name: full.slice(idx + 1) };
|
|
12518
|
+
}
|
|
12519
|
+
async function createReviewTable(title, data, options) {
|
|
12520
|
+
try {
|
|
12521
|
+
const rows = data.map((row, i) => ({
|
|
12522
|
+
_row_id: `r${i + 1}`,
|
|
12523
|
+
...row
|
|
12524
|
+
}));
|
|
12525
|
+
const body = { title, rows };
|
|
12526
|
+
if (options?.table_id !== void 0) body.table_id = options.table_id;
|
|
12527
|
+
if (options?.ttl_seconds !== void 0) body.ttl_seconds = options.ttl_seconds;
|
|
12528
|
+
const response = await fetch(`${VIEWER_URL}/api/tables`, {
|
|
12529
|
+
method: "POST",
|
|
12530
|
+
headers: { "Content-Type": "application/json" },
|
|
12531
|
+
body: JSON.stringify(body)
|
|
12532
|
+
});
|
|
12533
|
+
if (!response.ok) {
|
|
12534
|
+
logError("table-bridge.createReviewTable", new Error(`Viewer returned ${response.status}`), {
|
|
12535
|
+
title,
|
|
12536
|
+
status: response.status
|
|
12537
|
+
});
|
|
12538
|
+
return null;
|
|
12539
|
+
}
|
|
12540
|
+
return await response.json();
|
|
12541
|
+
} catch (error) {
|
|
12542
|
+
logError("table-bridge.createReviewTable", error, { title });
|
|
12543
|
+
return null;
|
|
12544
|
+
}
|
|
12545
|
+
}
|
|
12546
|
+
async function fetchTableRows(tableId) {
|
|
12547
|
+
try {
|
|
12548
|
+
const response = await fetch(`${VIEWER_URL}/api/tables/${tableId}/selected`);
|
|
12549
|
+
if (!response.ok) {
|
|
12550
|
+
logError("table-bridge.fetchTableRows", new Error(`Viewer returned ${response.status}`), {
|
|
12551
|
+
tableId,
|
|
12552
|
+
status: response.status
|
|
12553
|
+
});
|
|
12554
|
+
return [];
|
|
12555
|
+
}
|
|
12556
|
+
const json = await response.json();
|
|
12557
|
+
return json.rows ?? [];
|
|
12558
|
+
} catch (error) {
|
|
12559
|
+
logError("table-bridge.fetchTableRows", error, { tableId });
|
|
12560
|
+
return [];
|
|
12561
|
+
}
|
|
12562
|
+
}
|
|
12563
|
+
async function patchTableRows(tableId, rows) {
|
|
12564
|
+
try {
|
|
12565
|
+
const response = await fetch(`${VIEWER_URL}/api/tables/${tableId}`, {
|
|
12566
|
+
method: "PATCH",
|
|
12567
|
+
headers: { "Content-Type": "application/json" },
|
|
12568
|
+
body: JSON.stringify({ rows })
|
|
12569
|
+
});
|
|
12570
|
+
if (!response.ok) {
|
|
12571
|
+
logError("table-bridge.patchTableRows", new Error(`Viewer returned ${response.status}`), {
|
|
12572
|
+
tableId,
|
|
12573
|
+
status: response.status
|
|
12574
|
+
});
|
|
12575
|
+
return false;
|
|
12576
|
+
}
|
|
12577
|
+
return true;
|
|
12578
|
+
} catch (error) {
|
|
12579
|
+
logError("table-bridge.patchTableRows", error, { tableId });
|
|
12580
|
+
return false;
|
|
12581
|
+
}
|
|
12582
|
+
}
|
|
12583
|
+
function mapRowsToContacts(rows) {
|
|
12584
|
+
return rows.map((row) => {
|
|
12585
|
+
let firstName = asString(row.first_name);
|
|
12586
|
+
let lastName = asString(row.last_name);
|
|
12587
|
+
if (firstName === void 0 || lastName === void 0) {
|
|
12588
|
+
const fullName = asString(row.name) ?? asString(row.full_name);
|
|
12589
|
+
if (fullName) {
|
|
12590
|
+
const parts = splitName(fullName);
|
|
12591
|
+
firstName ??= parts.first_name;
|
|
12592
|
+
lastName ??= parts.last_name;
|
|
12593
|
+
}
|
|
12594
|
+
}
|
|
12595
|
+
return {
|
|
12596
|
+
first_name: firstName ?? "",
|
|
12597
|
+
last_name: lastName ?? "",
|
|
12598
|
+
company: asString(row.company) ?? asString(row.company_name),
|
|
12599
|
+
company_domain: asString(row.company_domain) ?? asString(row.domain),
|
|
12600
|
+
linkedin_url: asString(row.linkedin_url) ?? asString(row.linkedin)
|
|
12601
|
+
};
|
|
12602
|
+
});
|
|
12603
|
+
}
|
|
12604
|
+
function mapRowsToCampaignLeads(rows) {
|
|
12605
|
+
const leads = [];
|
|
12606
|
+
for (const row of rows) {
|
|
12607
|
+
const email = asString(row.email);
|
|
12608
|
+
if (!email || email.includes("*")) continue;
|
|
12609
|
+
leads.push({
|
|
12610
|
+
email,
|
|
12611
|
+
first_name: asString(row.first_name),
|
|
12612
|
+
last_name: asString(row.last_name),
|
|
12613
|
+
company: asString(row.company) ?? asString(row.company_name)
|
|
12614
|
+
});
|
|
12615
|
+
}
|
|
12616
|
+
return leads;
|
|
12617
|
+
}
|
|
12618
|
+
var VIEWER_URL;
|
|
12619
|
+
var init_table_bridge = __esm({
|
|
12620
|
+
"src/utils/table-bridge.ts"() {
|
|
12621
|
+
"use strict";
|
|
12622
|
+
init_dist();
|
|
12623
|
+
VIEWER_URL = process.env.GTM_VIEWER_URL ?? "https://gtm-viewer-production.up.railway.app";
|
|
12624
|
+
}
|
|
12625
|
+
});
|
|
12626
|
+
|
|
12467
12627
|
// src/providers/types.ts
|
|
12468
12628
|
function applySlimFields(obj, fields) {
|
|
12469
12629
|
const result = {};
|
|
@@ -12696,6 +12856,27 @@ async function findCompanies(args, ctx) {
|
|
|
12696
12856
|
});
|
|
12697
12857
|
const offset = args.offset ?? 0;
|
|
12698
12858
|
const has_more = results.length >= effectiveLimit;
|
|
12859
|
+
const hints = [];
|
|
12860
|
+
if (results.length >= effectiveLimit) {
|
|
12861
|
+
hints.push(
|
|
12862
|
+
`Returned ${results.length} results (limit reached). Increase limit or use offset for more.`
|
|
12863
|
+
);
|
|
12864
|
+
}
|
|
12865
|
+
const shouldReview = args.review ?? false;
|
|
12866
|
+
let tableId;
|
|
12867
|
+
let tableUrl;
|
|
12868
|
+
if (shouldReview) {
|
|
12869
|
+
const tableResult = await createReviewTable(
|
|
12870
|
+
`Find Companies: ${query.icp_text ?? query.domain ?? "results"}`,
|
|
12871
|
+
slimmedResults
|
|
12872
|
+
);
|
|
12873
|
+
if (tableResult) {
|
|
12874
|
+
tableId = tableResult.table_id;
|
|
12875
|
+
tableUrl = tableResult.url;
|
|
12876
|
+
} else {
|
|
12877
|
+
hints.push("Review table creation failed \u2014 results returned inline only.");
|
|
12878
|
+
}
|
|
12879
|
+
}
|
|
12699
12880
|
const applied_filters = {};
|
|
12700
12881
|
if (query.icp_text) applied_filters.icp_text = query.icp_text;
|
|
12701
12882
|
if (query.domain) applied_filters.domain = query.domain;
|
|
@@ -12750,9 +12931,11 @@ async function findCompanies(args, ctx) {
|
|
|
12750
12931
|
count: results.length,
|
|
12751
12932
|
has_more,
|
|
12752
12933
|
offset,
|
|
12934
|
+
...tableId && { table_id: tableId, url: tableUrl },
|
|
12753
12935
|
...Object.keys(applied_filters).length > 0 && { applied_filters },
|
|
12754
12936
|
companies: slimmedResults,
|
|
12755
|
-
...warnings?.length ? { warnings } : {}
|
|
12937
|
+
...warnings?.length ? { warnings } : {},
|
|
12938
|
+
...hints.length > 0 && { hints }
|
|
12756
12939
|
};
|
|
12757
12940
|
}
|
|
12758
12941
|
async function findPeople(args, ctx) {
|
|
@@ -12793,10 +12976,10 @@ async function findPeople(args, ctx) {
|
|
|
12793
12976
|
const dedupedResults = deduplicatePeople(results);
|
|
12794
12977
|
const shouldEnrich = args.enrich ?? false;
|
|
12795
12978
|
let enrichedCount = 0;
|
|
12979
|
+
let needsEnrichmentCount = 0;
|
|
12796
12980
|
if (shouldEnrich) {
|
|
12797
|
-
const needsEnrichment = dedupedResults.filter(
|
|
12798
|
-
|
|
12799
|
-
);
|
|
12981
|
+
const needsEnrichment = dedupedResults.filter((r) => !r.email || r.email.includes("*"));
|
|
12982
|
+
needsEnrichmentCount = needsEnrichment.length;
|
|
12800
12983
|
if (needsEnrichment.length > 0) {
|
|
12801
12984
|
try {
|
|
12802
12985
|
const contacts = needsEnrichment.map((r) => ({
|
|
@@ -12824,7 +13007,10 @@ async function findPeople(args, ctx) {
|
|
|
12824
13007
|
}
|
|
12825
13008
|
}
|
|
12826
13009
|
} catch (error) {
|
|
12827
|
-
logError(
|
|
13010
|
+
logError(
|
|
13011
|
+
"prospect:inlineEnrich",
|
|
13012
|
+
error instanceof Error ? error : new Error(String(error))
|
|
13013
|
+
);
|
|
12828
13014
|
}
|
|
12829
13015
|
}
|
|
12830
13016
|
}
|
|
@@ -12853,6 +13039,37 @@ async function findPeople(args, ctx) {
|
|
|
12853
13039
|
if (query.min_employees != null) applied_filters.min_employees = query.min_employees;
|
|
12854
13040
|
if (query.max_employees != null) applied_filters.max_employees = query.max_employees;
|
|
12855
13041
|
if (query.revenue_range) applied_filters.revenue_range = query.revenue_range;
|
|
13042
|
+
const hints = [];
|
|
13043
|
+
const maskedCount = dedupedResults.filter((r) => r.email?.includes("*")).length;
|
|
13044
|
+
if (maskedCount > 0) {
|
|
13045
|
+
hints.push(
|
|
13046
|
+
`${maskedCount} emails are masked by the provider. Pass enrich: true to resolve them.`
|
|
13047
|
+
);
|
|
13048
|
+
}
|
|
13049
|
+
if (dedupedResults.length >= effectiveLimit) {
|
|
13050
|
+
hints.push(
|
|
13051
|
+
`Returned ${dedupedResults.length} results (limit reached). Increase limit or use offset for more.`
|
|
13052
|
+
);
|
|
13053
|
+
}
|
|
13054
|
+
if (shouldEnrich && enrichedCount < needsEnrichmentCount) {
|
|
13055
|
+
const failed = needsEnrichmentCount - enrichedCount;
|
|
13056
|
+
hints.push(`${failed} contacts could not be enriched \u2014 missing LinkedIn URL or domain.`);
|
|
13057
|
+
}
|
|
13058
|
+
const shouldReview = args.review ?? false;
|
|
13059
|
+
let tableId;
|
|
13060
|
+
let tableUrl;
|
|
13061
|
+
if (shouldReview) {
|
|
13062
|
+
const tableResult = await createReviewTable(
|
|
13063
|
+
`Find People: ${query.job_title ?? query.company_domain ?? "results"}`,
|
|
13064
|
+
slimmedResults
|
|
13065
|
+
);
|
|
13066
|
+
if (tableResult) {
|
|
13067
|
+
tableId = tableResult.table_id;
|
|
13068
|
+
tableUrl = tableResult.url;
|
|
13069
|
+
} else {
|
|
13070
|
+
hints.push("Review table creation failed \u2014 results returned inline only.");
|
|
13071
|
+
}
|
|
13072
|
+
}
|
|
12856
13073
|
const providerUsed = provider.name;
|
|
12857
13074
|
const output = args.output;
|
|
12858
13075
|
if (output === "csv") {
|
|
@@ -12893,10 +13110,12 @@ async function findPeople(args, ctx) {
|
|
|
12893
13110
|
count: dedupedResults.length,
|
|
12894
13111
|
has_more,
|
|
12895
13112
|
offset,
|
|
13113
|
+
...tableId && { table_id: tableId, url: tableUrl },
|
|
12896
13114
|
...Object.keys(applied_filters).length > 0 && { applied_filters },
|
|
12897
13115
|
...shouldEnrich && { enriched: enrichedCount },
|
|
12898
13116
|
people: slimmedResults,
|
|
12899
|
-
...warnings?.length ? { warnings } : {}
|
|
13117
|
+
...warnings?.length ? { warnings } : {},
|
|
13118
|
+
...hints.length > 0 && { hints }
|
|
12900
13119
|
};
|
|
12901
13120
|
}
|
|
12902
13121
|
var init_prospect2 = __esm({
|
|
@@ -12905,6 +13124,7 @@ var init_prospect2 = __esm({
|
|
|
12905
13124
|
init_dist();
|
|
12906
13125
|
init_errors();
|
|
12907
13126
|
init_waterfall();
|
|
13127
|
+
init_table_bridge();
|
|
12908
13128
|
init_types3();
|
|
12909
13129
|
init_manifest_validation();
|
|
12910
13130
|
init_result_writer();
|
|
@@ -12937,10 +13157,29 @@ async function handleEnrich(name, args, ctx) {
|
|
|
12937
13157
|
return enrichContacts(args, ctx);
|
|
12938
13158
|
}
|
|
12939
13159
|
async function enrichContacts(args, ctx) {
|
|
12940
|
-
|
|
13160
|
+
let contacts = args.contacts;
|
|
12941
13161
|
const find = args.find ?? ["email"];
|
|
12942
13162
|
const verify = args.verify ?? true;
|
|
12943
13163
|
const dryRun = args.dry_run ?? false;
|
|
13164
|
+
const detail = args.detail ?? "summary";
|
|
13165
|
+
const tableId = args.table_id;
|
|
13166
|
+
let rowIds = [];
|
|
13167
|
+
if (tableId) {
|
|
13168
|
+
const tableRows = await fetchTableRows(tableId);
|
|
13169
|
+
if (tableRows.length === 0) {
|
|
13170
|
+
return {
|
|
13171
|
+
total: 0,
|
|
13172
|
+
found: 0,
|
|
13173
|
+
missed: 0,
|
|
13174
|
+
results: [],
|
|
13175
|
+
hints: [
|
|
13176
|
+
"No rows selected in table. User may not have reviewed yet, or may have deselected all."
|
|
13177
|
+
]
|
|
13178
|
+
};
|
|
13179
|
+
}
|
|
13180
|
+
rowIds = tableRows.map((r) => r._row_id);
|
|
13181
|
+
contacts = mapRowsToContacts(tableRows);
|
|
13182
|
+
}
|
|
12944
13183
|
const WATERFALL_FIELDS = ["email", "phone"];
|
|
12945
13184
|
const richFields = find.filter((f) => !WATERFALL_FIELDS.includes(f));
|
|
12946
13185
|
if (richFields.length > 0) {
|
|
@@ -12973,11 +13212,65 @@ async function enrichContacts(args, ctx) {
|
|
|
12973
13212
|
}
|
|
12974
13213
|
const results = await runWaterfall(contacts, config, ctx.registry);
|
|
12975
13214
|
const found = results.filter((r) => r.email || r.phone).length;
|
|
12976
|
-
|
|
13215
|
+
if (tableId) {
|
|
13216
|
+
const patchPayload = results.map((r, i) => ({
|
|
13217
|
+
_row_id: rowIds[i],
|
|
13218
|
+
...r.email && { email: r.email },
|
|
13219
|
+
...r.email_provider && { email_provider: r.email_provider },
|
|
13220
|
+
...r.email_status && { email_status: r.email_status }
|
|
13221
|
+
}));
|
|
13222
|
+
await patchTableRows(tableId, patchPayload);
|
|
13223
|
+
}
|
|
13224
|
+
const hints = [];
|
|
13225
|
+
const exhaustedAttempts = results.flatMap(
|
|
13226
|
+
(r) => r.attempts.filter((a) => a.error?.includes("exhausted"))
|
|
13227
|
+
);
|
|
13228
|
+
if (exhaustedAttempts.length > 0) {
|
|
13229
|
+
const provider = exhaustedAttempts[0].provider;
|
|
13230
|
+
hints.push(
|
|
13231
|
+
`${provider} credits exhausted mid-batch. Remaining contacts fell through to next provider.`
|
|
13232
|
+
);
|
|
13233
|
+
}
|
|
13234
|
+
const noLinkedin = contacts.filter((c) => !c.linkedin_url).length;
|
|
13235
|
+
if (noLinkedin > 0) {
|
|
13236
|
+
hints.push(`${noLinkedin} contacts had no LinkedIn URL \u2014 some providers skipped them.`);
|
|
13237
|
+
}
|
|
13238
|
+
const timedOut = results.filter(
|
|
13239
|
+
(r) => r.attempts.some((a) => a.error?.includes("Batch timeout"))
|
|
13240
|
+
).length;
|
|
13241
|
+
if (timedOut > 0) {
|
|
13242
|
+
hints.push(`Batch timed out with ${timedOut} contacts remaining. Re-run with those contacts.`);
|
|
13243
|
+
}
|
|
13244
|
+
const costMap = /* @__PURE__ */ new Map();
|
|
13245
|
+
for (const r of results) {
|
|
13246
|
+
if (r.credits_consumed && r.email_provider) {
|
|
13247
|
+
costMap.set(r.email_provider, (costMap.get(r.email_provider) ?? 0) + r.credits_consumed);
|
|
13248
|
+
}
|
|
13249
|
+
}
|
|
13250
|
+
const cost = costMap.size > 0 ? Array.from(costMap.entries()).map(([provider, credits]) => ({ provider, credits })) : void 0;
|
|
13251
|
+
const shouldReview = args.review ?? false;
|
|
13252
|
+
let reviewTableId;
|
|
13253
|
+
let reviewUrl;
|
|
13254
|
+
if (shouldReview) {
|
|
13255
|
+
const tableResult = await createReviewTable(
|
|
13256
|
+
"Enrichment Results",
|
|
13257
|
+
detail === "full" ? results : slimEnrichResults(results)
|
|
13258
|
+
);
|
|
13259
|
+
if (tableResult) {
|
|
13260
|
+
reviewTableId = tableResult.table_id;
|
|
13261
|
+
reviewUrl = tableResult.url;
|
|
13262
|
+
} else {
|
|
13263
|
+
hints.push("Review table creation failed \u2014 results returned inline only.");
|
|
13264
|
+
}
|
|
13265
|
+
}
|
|
12977
13266
|
return {
|
|
12978
13267
|
total: results.length,
|
|
12979
13268
|
found,
|
|
12980
13269
|
missed: results.length - found,
|
|
13270
|
+
...tableId && { table_id: tableId },
|
|
13271
|
+
...reviewTableId && { table_id: reviewTableId, url: reviewUrl },
|
|
13272
|
+
...hints.length > 0 && { hints },
|
|
13273
|
+
...cost && { cost },
|
|
12981
13274
|
results: detail === "full" ? results : slimEnrichResults(results)
|
|
12982
13275
|
};
|
|
12983
13276
|
}
|
|
@@ -12999,6 +13292,7 @@ var init_enrich2 = __esm({
|
|
|
12999
13292
|
"use strict";
|
|
13000
13293
|
init_waterfall();
|
|
13001
13294
|
init_errors();
|
|
13295
|
+
init_table_bridge();
|
|
13002
13296
|
}
|
|
13003
13297
|
});
|
|
13004
13298
|
|
|
@@ -13434,11 +13728,38 @@ async function handleCampaign(args, ctx) {
|
|
|
13434
13728
|
}
|
|
13435
13729
|
case "add_leads": {
|
|
13436
13730
|
const campaignId = args.campaign_id;
|
|
13437
|
-
const
|
|
13731
|
+
const tableId = args.table_id;
|
|
13438
13732
|
if (!campaignId)
|
|
13439
13733
|
throw createGtmError("QUERY_INVALID", "campaign_id is required for add_leads action");
|
|
13734
|
+
const hints = [];
|
|
13735
|
+
let rawLeads = args.leads;
|
|
13736
|
+
if (tableId) {
|
|
13737
|
+
const tableRows = await fetchTableRows(tableId);
|
|
13738
|
+
if (tableRows.length === 0) {
|
|
13739
|
+
return {
|
|
13740
|
+
provider: channel.providerName,
|
|
13741
|
+
added: 0,
|
|
13742
|
+
hints: [
|
|
13743
|
+
"No rows selected in table. User may not have reviewed yet, or may have deselected all."
|
|
13744
|
+
]
|
|
13745
|
+
};
|
|
13746
|
+
}
|
|
13747
|
+
if (channel.type === "linkedin") {
|
|
13748
|
+
rawLeads = tableRows;
|
|
13749
|
+
} else {
|
|
13750
|
+
const mapped = mapRowsToCampaignLeads(tableRows);
|
|
13751
|
+
const skipped = tableRows.length - mapped.length;
|
|
13752
|
+
if (skipped > 0) {
|
|
13753
|
+
hints.push(`${skipped} rows skipped \u2014 missing email (required for campaign).`);
|
|
13754
|
+
}
|
|
13755
|
+
rawLeads = mapped;
|
|
13756
|
+
}
|
|
13757
|
+
}
|
|
13440
13758
|
if (!rawLeads?.length)
|
|
13441
|
-
throw createGtmError(
|
|
13759
|
+
throw createGtmError(
|
|
13760
|
+
"QUERY_INVALID",
|
|
13761
|
+
"leads array is required for add_leads action (or provide table_id)"
|
|
13762
|
+
);
|
|
13442
13763
|
if (channel.type === "linkedin") {
|
|
13443
13764
|
const leads2 = rawLeads.map((l) => ({
|
|
13444
13765
|
linkedin_url: l.linkedin_url,
|
|
@@ -13446,7 +13767,11 @@ async function handleCampaign(args, ctx) {
|
|
|
13446
13767
|
last_name: l.last_name,
|
|
13447
13768
|
company: l.company
|
|
13448
13769
|
}));
|
|
13449
|
-
|
|
13770
|
+
const result = await channel.manager.addLeadsToCampaign(campaignId, leads2);
|
|
13771
|
+
return {
|
|
13772
|
+
...result,
|
|
13773
|
+
...hints.length > 0 && { hints }
|
|
13774
|
+
};
|
|
13450
13775
|
}
|
|
13451
13776
|
const leads = rawLeads.map((l) => ({
|
|
13452
13777
|
email: l.email,
|
|
@@ -13456,7 +13781,11 @@ async function handleCampaign(args, ctx) {
|
|
|
13456
13781
|
custom_fields: l.custom_fields
|
|
13457
13782
|
}));
|
|
13458
13783
|
try {
|
|
13459
|
-
|
|
13784
|
+
const result = await channel.manager.addLeadsToCampaign(campaignId, leads);
|
|
13785
|
+
return {
|
|
13786
|
+
...result,
|
|
13787
|
+
...hints.length > 0 && { hints }
|
|
13788
|
+
};
|
|
13460
13789
|
} catch (error) {
|
|
13461
13790
|
if (error instanceof ApiClientError && (error.message.includes("Validation error") || error.statusCode === 400 || error.statusCode === 422)) {
|
|
13462
13791
|
throw createGtmError(
|
|
@@ -13494,6 +13823,7 @@ var init_campaign2 = __esm({
|
|
|
13494
13823
|
"use strict";
|
|
13495
13824
|
init_dist2();
|
|
13496
13825
|
init_errors();
|
|
13826
|
+
init_table_bridge();
|
|
13497
13827
|
}
|
|
13498
13828
|
});
|
|
13499
13829
|
|
|
@@ -13521,6 +13851,28 @@ async function handleCrm(args, ctx) {
|
|
|
13521
13851
|
return manager.findPerson(query);
|
|
13522
13852
|
}
|
|
13523
13853
|
case "create": {
|
|
13854
|
+
const tableId = args.table_id;
|
|
13855
|
+
if (tableId) {
|
|
13856
|
+
const rows = await fetchTableRows(tableId);
|
|
13857
|
+
if (rows.length === 0) {
|
|
13858
|
+
return {
|
|
13859
|
+
created: 0,
|
|
13860
|
+
hints: ["No selected rows found in the review table. Select rows in the viewer and retry."]
|
|
13861
|
+
};
|
|
13862
|
+
}
|
|
13863
|
+
const contacts = mapRowsToContacts(rows);
|
|
13864
|
+
const results = await Promise.allSettled(
|
|
13865
|
+
contacts.map((contact) => manager.createPerson(contact))
|
|
13866
|
+
);
|
|
13867
|
+
const created = results.filter((r) => r.status === "fulfilled").length;
|
|
13868
|
+
const failed = results.filter((r) => r.status === "rejected").length;
|
|
13869
|
+
return {
|
|
13870
|
+
created,
|
|
13871
|
+
failed,
|
|
13872
|
+
total: contacts.length,
|
|
13873
|
+
hints: failed > 0 ? [`${failed} contact(s) failed to create \u2014 check for duplicates or missing fields.`] : void 0
|
|
13874
|
+
};
|
|
13875
|
+
}
|
|
13524
13876
|
const data = args.data;
|
|
13525
13877
|
if (!data) throw new Error("data is required for create action");
|
|
13526
13878
|
return manager.createPerson(data);
|
|
@@ -13552,6 +13904,7 @@ async function handleCrm(args, ctx) {
|
|
|
13552
13904
|
var init_crm2 = __esm({
|
|
13553
13905
|
"src/handlers/crm.ts"() {
|
|
13554
13906
|
"use strict";
|
|
13907
|
+
init_table_bridge();
|
|
13555
13908
|
}
|
|
13556
13909
|
});
|
|
13557
13910
|
|
|
@@ -13807,14 +14160,21 @@ async function handleReview(args) {
|
|
|
13807
14160
|
async function createTable(args) {
|
|
13808
14161
|
const data = args.data;
|
|
13809
14162
|
const title = args.title ?? "Review Table";
|
|
14163
|
+
const tableId = args.table_id;
|
|
14164
|
+
const ttlSeconds = args.ttl_seconds;
|
|
13810
14165
|
const rows = data.map((row, i) => ({
|
|
13811
14166
|
...row,
|
|
13812
14167
|
_row_id: row._row_id ?? `r${i + 1}`
|
|
13813
14168
|
}));
|
|
13814
|
-
const response = await fetch(`${
|
|
14169
|
+
const response = await fetch(`${VIEWER_URL2}/api/tables`, {
|
|
13815
14170
|
method: "POST",
|
|
13816
14171
|
headers: { "Content-Type": "application/json" },
|
|
13817
|
-
body: JSON.stringify({
|
|
14172
|
+
body: JSON.stringify({
|
|
14173
|
+
title,
|
|
14174
|
+
rows,
|
|
14175
|
+
...tableId && { table_id: tableId },
|
|
14176
|
+
...ttlSeconds && { ttl_seconds: ttlSeconds }
|
|
14177
|
+
})
|
|
13818
14178
|
});
|
|
13819
14179
|
if (!response.ok) {
|
|
13820
14180
|
const body = await response.json().catch(() => ({}));
|
|
@@ -13826,7 +14186,7 @@ async function createTable(args) {
|
|
|
13826
14186
|
async function updateTable(args) {
|
|
13827
14187
|
const tableId = args.table_id;
|
|
13828
14188
|
const rows = args.rows;
|
|
13829
|
-
const response = await fetch(`${
|
|
14189
|
+
const response = await fetch(`${VIEWER_URL2}/api/tables/${tableId}`, {
|
|
13830
14190
|
method: "PATCH",
|
|
13831
14191
|
headers: { "Content-Type": "application/json" },
|
|
13832
14192
|
body: JSON.stringify({ rows })
|
|
@@ -13839,18 +14199,18 @@ async function updateTable(args) {
|
|
|
13839
14199
|
}
|
|
13840
14200
|
async function getSelected(args) {
|
|
13841
14201
|
const tableId = args.table_id;
|
|
13842
|
-
const response = await fetch(`${
|
|
14202
|
+
const response = await fetch(`${VIEWER_URL2}/api/tables/${tableId}/selected`);
|
|
13843
14203
|
if (!response.ok) {
|
|
13844
14204
|
const body = await response.json().catch(() => ({}));
|
|
13845
14205
|
throw new Error(`Viewer API error ${response.status}: ${JSON.stringify(body)}`);
|
|
13846
14206
|
}
|
|
13847
14207
|
return response.json();
|
|
13848
14208
|
}
|
|
13849
|
-
var
|
|
14209
|
+
var VIEWER_URL2;
|
|
13850
14210
|
var init_review2 = __esm({
|
|
13851
14211
|
"src/handlers/review.ts"() {
|
|
13852
14212
|
"use strict";
|
|
13853
|
-
|
|
14213
|
+
VIEWER_URL2 = process.env.GTM_VIEWER_URL ?? "https://gtm-viewer-production.up.railway.app";
|
|
13854
14214
|
}
|
|
13855
14215
|
});
|
|
13856
14216
|
|
|
@@ -14092,7 +14452,8 @@ var init_validation = __esm({
|
|
|
14092
14452
|
min_revenue: z2.number().optional(),
|
|
14093
14453
|
max_revenue: z2.number().optional(),
|
|
14094
14454
|
output: z2.enum(["file", "inline", "csv"]).optional(),
|
|
14095
|
-
detail: detailSchema
|
|
14455
|
+
detail: detailSchema,
|
|
14456
|
+
review: z2.boolean().optional()
|
|
14096
14457
|
}),
|
|
14097
14458
|
// 2. People/contact search
|
|
14098
14459
|
gtm_find_people: z2.object({
|
|
@@ -14132,6 +14493,7 @@ var init_validation = __esm({
|
|
|
14132
14493
|
max_employees: z2.number().int().min(1).optional(),
|
|
14133
14494
|
revenue_range: z2.string().optional(),
|
|
14134
14495
|
enrich: z2.boolean().optional(),
|
|
14496
|
+
review: z2.boolean().optional(),
|
|
14135
14497
|
fields: z2.union([z2.literal("all"), z2.array(z2.string())]).optional(),
|
|
14136
14498
|
output: z2.enum(["file", "inline", "csv"]).optional(),
|
|
14137
14499
|
detail: detailSchema
|
|
@@ -14143,12 +14505,16 @@ var init_validation = __esm({
|
|
|
14143
14505
|
),
|
|
14144
14506
|
// 3. Enrichment waterfall
|
|
14145
14507
|
gtm_enrich: z2.object({
|
|
14146
|
-
contacts: z2.array(contactInputSchema).
|
|
14508
|
+
contacts: z2.array(contactInputSchema).max(100, "contacts must contain at most 100 contacts").optional(),
|
|
14147
14509
|
find: z2.array(z2.enum(["email", "phone", "title", "company", "funding", "tech_stack"])).optional(),
|
|
14148
14510
|
waterfall: z2.array(z2.string()).optional(),
|
|
14149
14511
|
verify: z2.boolean().optional(),
|
|
14150
14512
|
dry_run: z2.boolean().optional(),
|
|
14151
|
-
detail: detailSchema
|
|
14513
|
+
detail: detailSchema,
|
|
14514
|
+
table_id: z2.string().optional(),
|
|
14515
|
+
review: z2.boolean().optional()
|
|
14516
|
+
}).refine((data) => !!(data.contacts?.length || data.table_id), {
|
|
14517
|
+
message: "At least one of contacts or table_id is required"
|
|
14152
14518
|
}),
|
|
14153
14519
|
// 4. Email verification
|
|
14154
14520
|
gtm_verify: z2.object({
|
|
@@ -14232,7 +14598,8 @@ var init_validation = __esm({
|
|
|
14232
14598
|
leads: z2.array(campaignLeadSchema).optional(),
|
|
14233
14599
|
name: z2.string().optional(),
|
|
14234
14600
|
provider: z2.string().optional(),
|
|
14235
|
-
detail: detailSchema
|
|
14601
|
+
detail: detailSchema,
|
|
14602
|
+
table_id: z2.string().optional()
|
|
14236
14603
|
}),
|
|
14237
14604
|
// 10. CRM read/write
|
|
14238
14605
|
gtm_crm: z2.object({
|
|
@@ -14241,7 +14608,8 @@ var init_validation = __esm({
|
|
|
14241
14608
|
}),
|
|
14242
14609
|
query: z2.record(z2.unknown()).optional(),
|
|
14243
14610
|
data: z2.record(z2.unknown()).optional(),
|
|
14244
|
-
id: z2.string().optional()
|
|
14611
|
+
id: z2.string().optional(),
|
|
14612
|
+
table_id: z2.string().optional()
|
|
14245
14613
|
}),
|
|
14246
14614
|
// 11. Provider status/credits
|
|
14247
14615
|
gtm_status: z2.object({
|
|
@@ -14294,7 +14662,9 @@ var init_validation = __esm({
|
|
|
14294
14662
|
z2.object({
|
|
14295
14663
|
action: z2.literal("create"),
|
|
14296
14664
|
title: z2.string().optional(),
|
|
14297
|
-
data: z2.array(z2.record(z2.unknown())).min(1, "data must contain at least 1 row")
|
|
14665
|
+
data: z2.array(z2.record(z2.unknown())).min(1, "data must contain at least 1 row"),
|
|
14666
|
+
table_id: z2.string().optional(),
|
|
14667
|
+
ttl_seconds: z2.number().optional()
|
|
14298
14668
|
}),
|
|
14299
14669
|
z2.object({
|
|
14300
14670
|
action: z2.literal("update"),
|
|
@@ -14333,7 +14703,7 @@ function createServer() {
|
|
|
14333
14703
|
{ capabilities: { tools: {} } }
|
|
14334
14704
|
);
|
|
14335
14705
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
14336
|
-
tools
|
|
14706
|
+
tools: mcpTools
|
|
14337
14707
|
}));
|
|
14338
14708
|
server.setRequestHandler(CallToolRequestSchema, async (request, _extra) => {
|
|
14339
14709
|
const { name, arguments: args = {} } = request.params;
|