@sellable/mcp 0.1.219 → 0.1.221
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/server.js +7 -1
- package/dist/tools/csv-linkedin.js +149 -22
- package/dist/tools/leads.d.ts +1721 -220
- package/dist/tools/leads.js +509 -3
- package/dist/tools/linkedin.d.ts +37 -0
- package/dist/tools/linkedin.js +47 -17
- package/dist/tools/registry.d.ts +1655 -176
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +14 -0
- package/skills/create-campaign/core/flow.v1.json +2 -0
- package/skills/create-campaign/core/providers/prospeo.json +6 -2
- package/skills/create-campaign/references/provider-selection-strategy.md +8 -0
- package/skills/create-campaign-v2/SKILL.md +9 -1
- package/skills/create-campaign-v2/core/flow.v2.json +1 -1
- package/skills/find-leads/SKILL.md +13 -0
- package/skills/providers/prospeo.md +109 -1
- package/skills/research/config.json +0 -9
package/dist/server.js
CHANGED
|
@@ -17,7 +17,7 @@ import { copySenderConfigTool, getEngageMemoryTool, migrateFlatConfigsTool, reco
|
|
|
17
17
|
import { getEngageStateTool, setEngageStateTool, } from "./tools/engage-state.js";
|
|
18
18
|
import { bulkEnrichWithProspeo, enrichWithProspeo, getProspeoCredits, } from "./tools/enrichment.js";
|
|
19
19
|
import { getCampaignFramework } from "./tools/framework.js";
|
|
20
|
-
import { cancelLeadImport, confirmLeadList, getProviderPrompt, importLeads, loadCsvDomains, loadCsvLinkedinLeads, lookupSalesNavFilter, saveDomainFilters, searchApollo, searchProspeo, searchSalesNav, searchSignals, selectPromisingPosts, setHeadlineICPCriteria, } from "./tools/leads.js";
|
|
20
|
+
import { cancelLeadImport, confirmProspeoCompanyAccounts, confirmLeadList, getProviderPrompt, importLeads, loadCsvDomains, loadCsvLinkedinLeads, lookupSalesNavFilter, saveDomainFilters, searchApollo, searchProspeoCompanies, searchProspeo, searchSalesNav, searchSignals, selectPromisingPosts, setHeadlineICPCriteria, } from "./tools/leads.js";
|
|
21
21
|
import { fetchCompany, fetchCompanyPosts, fetchLinkedInPosts, fetchLinkedInProfile, fetchPostEngagers, getLinkedInProfile, getUserPosts, } from "./tools/linkedin.js";
|
|
22
22
|
import { getCampaignNavigationState } from "./tools/navigation.js";
|
|
23
23
|
import { addOnDemandLeads, createOnDemandCampaign, createOnDemandTable, initOnDemandSequence, pauseOnDemandCampaign, startOnDemandCampaign, } from "./tools/one-off.js";
|
|
@@ -311,6 +311,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
311
311
|
case "search_prospeo":
|
|
312
312
|
result = await searchProspeo(args);
|
|
313
313
|
break;
|
|
314
|
+
case "search_prospeo_companies":
|
|
315
|
+
result = await searchProspeoCompanies(args);
|
|
316
|
+
break;
|
|
317
|
+
case "confirm_prospeo_company_accounts":
|
|
318
|
+
result = await confirmProspeoCompanyAccounts(args);
|
|
319
|
+
break;
|
|
314
320
|
case "load_csv_domains":
|
|
315
321
|
result = await loadCsvDomains(args);
|
|
316
322
|
break;
|
|
@@ -41,6 +41,9 @@ const RESERVED_TARGET_COLUMNS = new Set([
|
|
|
41
41
|
"Open Link",
|
|
42
42
|
"Enrich Prospect",
|
|
43
43
|
]);
|
|
44
|
+
const COLUMN_MAPPING_SAMPLE_SIZE = 50;
|
|
45
|
+
const MIN_COLUMN_CONFIDENCE = 0.65;
|
|
46
|
+
const MIN_DOMAIN_COLUMN_CONFIDENCE = 0.8;
|
|
44
47
|
const CSV_LINKEDIN_LIMITS = {
|
|
45
48
|
maxBytes: MAX_CSV_LINKEDIN_UPLOAD_BYTES,
|
|
46
49
|
maxRows: MAX_CSV_LINKEDIN_UPLOAD_ROWS,
|
|
@@ -187,38 +190,166 @@ function findHeader(csvHeaders, normalizedHeaders, candidates) {
|
|
|
187
190
|
}
|
|
188
191
|
return undefined;
|
|
189
192
|
}
|
|
190
|
-
function
|
|
193
|
+
function getSampleValues(rows, header) {
|
|
194
|
+
return rows
|
|
195
|
+
.slice(0, COLUMN_MAPPING_SAMPLE_SIZE)
|
|
196
|
+
.map((row) => String(row[header] ?? "").trim())
|
|
197
|
+
.filter(Boolean);
|
|
198
|
+
}
|
|
199
|
+
function getValueMatchStats(rows, header, predicate) {
|
|
200
|
+
const values = getSampleValues(rows, header);
|
|
201
|
+
const matchCount = values.filter(predicate).length;
|
|
202
|
+
return {
|
|
203
|
+
nonEmptyCount: values.length,
|
|
204
|
+
matchCount,
|
|
205
|
+
confidence: values.length > 0 ? matchCount / values.length : 0,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function hasConfidentValues(rows, header, predicate, options = {}) {
|
|
209
|
+
if (rows.length === 0) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
const stats = getValueMatchStats(rows, header, predicate);
|
|
213
|
+
if (stats.nonEmptyCount === 0) {
|
|
214
|
+
return options.allowEmpty ?? false;
|
|
215
|
+
}
|
|
216
|
+
const minMatches = Math.min(options.minMatches ?? 2, stats.nonEmptyCount);
|
|
217
|
+
const minConfidence = options.minConfidence ?? MIN_COLUMN_CONFIDENCE;
|
|
218
|
+
return stats.matchCount >= minMatches && stats.confidence >= minConfidence;
|
|
219
|
+
}
|
|
220
|
+
function findConfidentHeader(csvHeaders, normalizedHeaders, candidates, rows, predicate, options) {
|
|
221
|
+
const header = findHeader(csvHeaders, normalizedHeaders, candidates);
|
|
222
|
+
if (!header) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return hasConfidentValues(rows, header, predicate, options)
|
|
226
|
+
? header
|
|
227
|
+
: undefined;
|
|
228
|
+
}
|
|
229
|
+
function findBestHeaderByContent(headers, rows, predicate, options = {}) {
|
|
230
|
+
if (rows.length === 0) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
const candidates = headers
|
|
234
|
+
.map((header) => ({
|
|
235
|
+
header,
|
|
236
|
+
...getValueMatchStats(rows, header, predicate),
|
|
237
|
+
}))
|
|
238
|
+
.filter((entry) => {
|
|
239
|
+
const minMatches = Math.min(options.minMatches ?? 2, entry.nonEmptyCount);
|
|
240
|
+
const minConfidence = options.minConfidence ?? MIN_COLUMN_CONFIDENCE;
|
|
241
|
+
return (entry.nonEmptyCount > 0 &&
|
|
242
|
+
entry.matchCount >= minMatches &&
|
|
243
|
+
entry.confidence >= minConfidence);
|
|
244
|
+
})
|
|
245
|
+
.sort((left, right) => {
|
|
246
|
+
if (right.confidence !== left.confidence) {
|
|
247
|
+
return right.confidence - left.confidence;
|
|
248
|
+
}
|
|
249
|
+
return right.matchCount - left.matchCount;
|
|
250
|
+
});
|
|
251
|
+
if (candidates.length === 0) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const [best, second] = candidates;
|
|
255
|
+
if (second &&
|
|
256
|
+
second.confidence === best.confidence &&
|
|
257
|
+
second.matchCount === best.matchCount) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return best.header;
|
|
261
|
+
}
|
|
262
|
+
function looksLikePersonName(value) {
|
|
263
|
+
const trimmed = value.trim();
|
|
264
|
+
if (trimmed.length < 2 || trimmed.length > 120) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (/[0-9@/:]/.test(trimmed) || sanitizeCompanyWebsite(trimmed)) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
const words = trimmed.split(/\s+/).filter(Boolean);
|
|
271
|
+
if (words.length === 0 || words.length > 5) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
if (/\b(inc|llc|ltd|corp|company|technologies|technology|security|systems|labs|software|group|studio|studios|ventures|capital|partners|agency|solutions|platform|cloud|data)\b/i.test(trimmed)) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
return words.every((word) => /\p{L}/u.test(word));
|
|
278
|
+
}
|
|
279
|
+
function looksLikeJobTitle(value) {
|
|
280
|
+
const trimmed = value.trim();
|
|
281
|
+
if (trimmed.length < 2 || trimmed.length > 160) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
if (/@|https?:\/\//i.test(trimmed) || sanitizeCompanyWebsite(trimmed)) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const words = trimmed.split(/\s+/).filter(Boolean);
|
|
288
|
+
return words.length > 0 && words.length <= 12 && /\p{L}/u.test(trimmed);
|
|
289
|
+
}
|
|
290
|
+
function looksLikeLocation(value) {
|
|
291
|
+
const trimmed = value.trim();
|
|
292
|
+
if (trimmed.length < 2 || trimmed.length > 120) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
if (/[0-9@/]/.test(trimmed) || sanitizeCompanyWebsite(trimmed)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return /\p{L}/u.test(trimmed);
|
|
299
|
+
}
|
|
300
|
+
function looksLikeCompanyWebsite(value) {
|
|
301
|
+
return sanitizeCompanyWebsite(value) !== null;
|
|
302
|
+
}
|
|
303
|
+
function isWebsiteContentFallbackHeader(header) {
|
|
304
|
+
const key = normalizeHeaderKey(header);
|
|
305
|
+
if (key.includes("linkedin") ||
|
|
306
|
+
key.includes("profile") ||
|
|
307
|
+
key.includes("email")) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
const normalized = normalizeHeaderForLookup(header);
|
|
311
|
+
return (normalized.includes("company") ||
|
|
312
|
+
normalized.includes("website") ||
|
|
313
|
+
normalized.includes("domain") ||
|
|
314
|
+
normalized.includes("url") ||
|
|
315
|
+
normalized.includes("site"));
|
|
316
|
+
}
|
|
317
|
+
function getSuggestedStandardMappings(headers, linkedInColumn, rows) {
|
|
191
318
|
const filteredHeaders = headers.filter((header) => header !== linkedInColumn);
|
|
192
319
|
const normalizedHeaders = filteredHeaders.map(normalizeHeaderForLookup);
|
|
193
320
|
const mappings = {};
|
|
194
|
-
const directNameHeader =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
321
|
+
const directNameHeader = findConfidentHeader(filteredHeaders, normalizedHeaders, ["name", "full name", "contact name", "contact full name"], rows, looksLikePersonName, { allowEmpty: true }) ??
|
|
322
|
+
findConfidentHeader(filteredHeaders, normalizedHeaders, [
|
|
323
|
+
"person",
|
|
324
|
+
"person name",
|
|
325
|
+
"prospect",
|
|
326
|
+
"prospect name",
|
|
327
|
+
"lead",
|
|
328
|
+
"lead name",
|
|
329
|
+
"recipient",
|
|
330
|
+
"recipient name",
|
|
331
|
+
], rows, looksLikePersonName);
|
|
199
332
|
if (directNameHeader) {
|
|
200
333
|
mappings["Name"] = directNameHeader;
|
|
201
334
|
}
|
|
202
335
|
else {
|
|
203
336
|
const firstNameIndex = normalizedHeaders.findIndex((header) => header === "first name");
|
|
204
337
|
const lastNameIndex = normalizedHeaders.findIndex((header) => header === "last name");
|
|
205
|
-
if (firstNameIndex !== -1 &&
|
|
338
|
+
if (firstNameIndex !== -1 &&
|
|
339
|
+
lastNameIndex !== -1 &&
|
|
340
|
+
hasConfidentValues(rows, filteredHeaders[firstNameIndex], looksLikePersonName, { allowEmpty: true }) &&
|
|
341
|
+
hasConfidentValues(rows, filteredHeaders[lastNameIndex], looksLikePersonName, { allowEmpty: true })) {
|
|
206
342
|
mappings["Name"] = [
|
|
207
343
|
filteredHeaders[firstNameIndex],
|
|
208
344
|
filteredHeaders[lastNameIndex],
|
|
209
345
|
];
|
|
210
346
|
}
|
|
211
347
|
}
|
|
212
|
-
const titleHeader =
|
|
213
|
-
"title",
|
|
214
|
-
"job title",
|
|
215
|
-
"position",
|
|
216
|
-
"role",
|
|
217
|
-
]);
|
|
348
|
+
const titleHeader = findConfidentHeader(filteredHeaders, normalizedHeaders, ["title", "job title", "position", "role"], rows, looksLikeJobTitle, { allowEmpty: true });
|
|
218
349
|
if (titleHeader) {
|
|
219
350
|
mappings["Title"] = titleHeader;
|
|
220
351
|
}
|
|
221
|
-
const companyWebsiteHeader =
|
|
352
|
+
const companyWebsiteHeader = findConfidentHeader(filteredHeaders, normalizedHeaders, [
|
|
222
353
|
"company website",
|
|
223
354
|
"website",
|
|
224
355
|
"domain",
|
|
@@ -226,16 +357,12 @@ function getSuggestedStandardMappings(headers, linkedInColumn) {
|
|
|
226
357
|
"website url",
|
|
227
358
|
"company url",
|
|
228
359
|
"organization website",
|
|
229
|
-
])
|
|
360
|
+
], rows, looksLikeCompanyWebsite, { allowEmpty: true, minConfidence: MIN_DOMAIN_COLUMN_CONFIDENCE }) ??
|
|
361
|
+
findBestHeaderByContent(filteredHeaders.filter(isWebsiteContentFallbackHeader), rows, looksLikeCompanyWebsite, { minConfidence: MIN_DOMAIN_COLUMN_CONFIDENCE, minMatches: 3 });
|
|
230
362
|
if (companyWebsiteHeader) {
|
|
231
363
|
mappings["Company Website"] = companyWebsiteHeader;
|
|
232
364
|
}
|
|
233
|
-
const locationHeader =
|
|
234
|
-
"location",
|
|
235
|
-
"city",
|
|
236
|
-
"geography",
|
|
237
|
-
"region",
|
|
238
|
-
]);
|
|
365
|
+
const locationHeader = findConfidentHeader(filteredHeaders, normalizedHeaders, ["location", "city", "geography", "region"], rows, looksLikeLocation, { allowEmpty: true });
|
|
239
366
|
if (locationHeader) {
|
|
240
367
|
mappings["Location"] = locationHeader;
|
|
241
368
|
}
|
|
@@ -595,7 +722,7 @@ export function buildCsvLinkedinPreview(input) {
|
|
|
595
722
|
}
|
|
596
723
|
}
|
|
597
724
|
const candidateLinkedInColumns = getCandidateLinkedInColumns(headers, parsed.rows);
|
|
598
|
-
const standardMappings = getSuggestedStandardMappings(headers, resolvedLinkedInColumn);
|
|
725
|
+
const standardMappings = getSuggestedStandardMappings(headers, resolvedLinkedInColumn, parsed.rows);
|
|
599
726
|
let selectedColumns = getSuggestedCarryoverColumns(headers, resolvedLinkedInColumn, standardMappings);
|
|
600
727
|
if (input.selectedColumns) {
|
|
601
728
|
if (input.selectedColumns.length > MAX_CSV_LINKEDIN_UPLOAD_CARRY_COLUMNS) {
|