@sellable/mcp 0.1.318 → 0.1.320
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/README.md +17 -0
- package/dist/generated/column-schema-manifest.js +1 -1
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/server.js +28 -0
- package/dist/tools/blueprint-commit.js +1 -1
- package/dist/tools/inbox.d.ts +308 -0
- package/dist/tools/inbox.js +576 -0
- package/dist/tools/linkedin.d.ts +76 -0
- package/dist/tools/linkedin.js +351 -18
- package/dist/tools/readiness.js +44 -13
- package/dist/tools/registry.d.ts +239 -0
- package/dist/tools/registry.js +2 -0
- package/package.json +1 -1
- package/skills/building-gtm-tables/SKILL.md +26 -0
- package/skills/building-gtm-tables/references/column-type-catalog.md +30 -1
- package/skills/building-gtm-tables/references/common-blueprints.fixtures.ts +54 -5
- package/skills/building-gtm-tables/references/common-blueprints.md +9 -0
- package/skills/create-campaign/SKILL.md +8 -0
- package/skills/create-campaign-v2/SKILL.md +7 -0
- package/skills/research/config.json +9 -0
package/dist/tools/linkedin.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare const linkedinToolDefinitions: ({
|
|
|
16
16
|
postUrl?: undefined;
|
|
17
17
|
sources?: undefined;
|
|
18
18
|
full?: undefined;
|
|
19
|
+
fetchMinimal?: undefined;
|
|
19
20
|
companyUrl?: undefined;
|
|
20
21
|
sortBy?: undefined;
|
|
21
22
|
linkedin_url?: undefined;
|
|
@@ -46,6 +47,7 @@ export declare const linkedinToolDefinitions: ({
|
|
|
46
47
|
};
|
|
47
48
|
linkedinUrl?: undefined;
|
|
48
49
|
full?: undefined;
|
|
50
|
+
fetchMinimal?: undefined;
|
|
49
51
|
companyUrl?: undefined;
|
|
50
52
|
sortBy?: undefined;
|
|
51
53
|
linkedin_url?: undefined;
|
|
@@ -68,6 +70,11 @@ export declare const linkedinToolDefinitions: ({
|
|
|
68
70
|
description: string;
|
|
69
71
|
default: boolean;
|
|
70
72
|
};
|
|
73
|
+
fetchMinimal: {
|
|
74
|
+
type: string;
|
|
75
|
+
description: string;
|
|
76
|
+
default: boolean;
|
|
77
|
+
};
|
|
71
78
|
limit?: undefined;
|
|
72
79
|
postUrl?: undefined;
|
|
73
80
|
sources?: undefined;
|
|
@@ -93,6 +100,7 @@ export declare const linkedinToolDefinitions: ({
|
|
|
93
100
|
postUrl?: undefined;
|
|
94
101
|
sources?: undefined;
|
|
95
102
|
full?: undefined;
|
|
103
|
+
fetchMinimal?: undefined;
|
|
96
104
|
sortBy?: undefined;
|
|
97
105
|
linkedin_url?: undefined;
|
|
98
106
|
max_posts?: undefined;
|
|
@@ -124,6 +132,7 @@ export declare const linkedinToolDefinitions: ({
|
|
|
124
132
|
postUrl?: undefined;
|
|
125
133
|
sources?: undefined;
|
|
126
134
|
full?: undefined;
|
|
135
|
+
fetchMinimal?: undefined;
|
|
127
136
|
linkedin_url?: undefined;
|
|
128
137
|
max_posts?: undefined;
|
|
129
138
|
};
|
|
@@ -149,6 +158,7 @@ export declare const linkedinToolDefinitions: ({
|
|
|
149
158
|
postUrl?: undefined;
|
|
150
159
|
sources?: undefined;
|
|
151
160
|
full?: undefined;
|
|
161
|
+
fetchMinimal?: undefined;
|
|
152
162
|
companyUrl?: undefined;
|
|
153
163
|
sortBy?: undefined;
|
|
154
164
|
};
|
|
@@ -169,6 +179,7 @@ export declare const linkedinToolDefinitions: ({
|
|
|
169
179
|
postUrl?: undefined;
|
|
170
180
|
sources?: undefined;
|
|
171
181
|
full?: undefined;
|
|
182
|
+
fetchMinimal?: undefined;
|
|
172
183
|
companyUrl?: undefined;
|
|
173
184
|
sortBy?: undefined;
|
|
174
185
|
max_posts?: undefined;
|
|
@@ -237,6 +248,70 @@ interface SerializedPostMedia {
|
|
|
237
248
|
articleUrl?: string;
|
|
238
249
|
mediaTypes: string[];
|
|
239
250
|
}
|
|
251
|
+
interface CompactLinkedInProfile {
|
|
252
|
+
id?: string;
|
|
253
|
+
publicIdentifier?: string;
|
|
254
|
+
linkedinUrl?: string;
|
|
255
|
+
firstName?: string;
|
|
256
|
+
lastName?: string;
|
|
257
|
+
fullName?: string;
|
|
258
|
+
headline?: string;
|
|
259
|
+
about?: string;
|
|
260
|
+
location?: string;
|
|
261
|
+
followerCount?: number;
|
|
262
|
+
connectionCount?: number;
|
|
263
|
+
isPremium?: boolean;
|
|
264
|
+
isCreator?: boolean;
|
|
265
|
+
isVerified?: boolean;
|
|
266
|
+
currentRole?: CompactExperience;
|
|
267
|
+
company?: CompactCompany;
|
|
268
|
+
experience?: CompactExperience[];
|
|
269
|
+
education?: CompactEducation[];
|
|
270
|
+
skills?: string[];
|
|
271
|
+
languages?: CompactLanguage[];
|
|
272
|
+
certifications?: CompactNamedItem[];
|
|
273
|
+
projects?: CompactNamedItem[];
|
|
274
|
+
}
|
|
275
|
+
interface CompactCompany {
|
|
276
|
+
name?: string;
|
|
277
|
+
linkedinUrl?: string;
|
|
278
|
+
website?: string;
|
|
279
|
+
domain?: string;
|
|
280
|
+
industry?: string;
|
|
281
|
+
employeeCount?: number;
|
|
282
|
+
employeeRange?: string;
|
|
283
|
+
description?: string;
|
|
284
|
+
founded?: string | number;
|
|
285
|
+
}
|
|
286
|
+
interface CompactExperience {
|
|
287
|
+
title?: string;
|
|
288
|
+
companyName?: string;
|
|
289
|
+
companyLinkedinUrl?: string;
|
|
290
|
+
dateRange?: string;
|
|
291
|
+
duration?: string;
|
|
292
|
+
location?: string;
|
|
293
|
+
description?: string;
|
|
294
|
+
isCurrent?: boolean;
|
|
295
|
+
}
|
|
296
|
+
interface CompactEducation {
|
|
297
|
+
schoolName?: string;
|
|
298
|
+
schoolLinkedinUrl?: string;
|
|
299
|
+
degree?: string;
|
|
300
|
+
fieldOfStudy?: string;
|
|
301
|
+
dateRange?: string;
|
|
302
|
+
}
|
|
303
|
+
interface CompactLanguage {
|
|
304
|
+
name?: string;
|
|
305
|
+
proficiency?: string;
|
|
306
|
+
}
|
|
307
|
+
interface CompactNamedItem {
|
|
308
|
+
name?: string;
|
|
309
|
+
organization?: string;
|
|
310
|
+
dateRange?: string;
|
|
311
|
+
description?: string;
|
|
312
|
+
url?: string;
|
|
313
|
+
}
|
|
314
|
+
export declare function compactLinkedInProfile(profile: unknown): CompactLinkedInProfile;
|
|
240
315
|
export declare function serializeLinkedInPosts(rawPosts: RawLinkedInPost[] | undefined, context: string): SerializedPost[];
|
|
241
316
|
export declare function fetchLinkedInPosts(linkedinUrl: string, limit?: number): Promise<{
|
|
242
317
|
posts: SerializedPost[];
|
|
@@ -244,6 +319,7 @@ export declare function fetchLinkedInPosts(linkedinUrl: string, limit?: number):
|
|
|
244
319
|
}>;
|
|
245
320
|
export declare function fetchLinkedInProfile(linkedinUrl: string, options?: {
|
|
246
321
|
full?: boolean;
|
|
322
|
+
fetchMinimal?: boolean;
|
|
247
323
|
}): Promise<any>;
|
|
248
324
|
export declare function fetchCompany(companyUrl: string): Promise<unknown>;
|
|
249
325
|
export declare function fetchCompanyPosts(companyUrl: string, limit?: number, sortBy?: "recent" | "top"): Promise<{
|
package/dist/tools/linkedin.js
CHANGED
|
@@ -47,7 +47,7 @@ export const linkedinToolDefinitions = [
|
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
name: "fetch_linkedin_profile",
|
|
50
|
-
description: "Fetch LinkedIn profile details. Defaults to
|
|
50
|
+
description: "Fetch LinkedIn profile details. Defaults to compact context (fetchMinimal=true) that strips images/media/provider metadata and returns identity, headline, about, current role, company, limited experience, education, skills, certifications, languages, and projects. Pass fetchMinimal=false only when you need raw provider fields or media URLs. Pass full=true ONLY when you need the long-tail experience history (positions 6+) or complete skill list (skills 3+) for deep research; full still returns compact output unless fetchMinimal=false.",
|
|
51
51
|
inputSchema: {
|
|
52
52
|
type: "object",
|
|
53
53
|
properties: {
|
|
@@ -60,6 +60,11 @@ export const linkedinToolDefinitions = [
|
|
|
60
60
|
description: "Opt into the full payload (~38% more expensive, slightly slower). Set true only when you specifically need the long-tail experience history or full skill list. Default false (main payload).",
|
|
61
61
|
default: false,
|
|
62
62
|
},
|
|
63
|
+
fetchMinimal: {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
description: "Return compact agent context by stripping images/media/provider metadata and limiting long lists. Default true. Set false only when raw provider fields or media URLs are needed.",
|
|
66
|
+
default: true,
|
|
67
|
+
},
|
|
63
68
|
},
|
|
64
69
|
required: ["linkedinUrl"],
|
|
65
70
|
},
|
|
@@ -187,6 +192,348 @@ function collectImageUrls(value, urls = new Set()) {
|
|
|
187
192
|
function pickString(value) {
|
|
188
193
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
189
194
|
}
|
|
195
|
+
function isRecord(value) {
|
|
196
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
197
|
+
}
|
|
198
|
+
function pickRecord(value, keys) {
|
|
199
|
+
if (!isRecord(value))
|
|
200
|
+
return undefined;
|
|
201
|
+
for (const key of keys) {
|
|
202
|
+
const candidate = value[key];
|
|
203
|
+
if (isRecord(candidate))
|
|
204
|
+
return candidate;
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function pickStringFrom(value, keys, maxLength = 1200) {
|
|
209
|
+
if (!isRecord(value))
|
|
210
|
+
return undefined;
|
|
211
|
+
for (const key of keys) {
|
|
212
|
+
const candidate = value[key];
|
|
213
|
+
const picked = typeof candidate === "string" || typeof candidate === "number"
|
|
214
|
+
? compactString(candidate, maxLength)
|
|
215
|
+
: undefined;
|
|
216
|
+
if (picked)
|
|
217
|
+
return picked;
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
function pickNumberFrom(value, keys) {
|
|
222
|
+
if (!isRecord(value))
|
|
223
|
+
return undefined;
|
|
224
|
+
for (const key of keys) {
|
|
225
|
+
const candidate = value[key];
|
|
226
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
227
|
+
return candidate;
|
|
228
|
+
}
|
|
229
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
230
|
+
const parsed = Number(candidate.replace(/,/g, ""));
|
|
231
|
+
if (Number.isFinite(parsed))
|
|
232
|
+
return parsed;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
function pickBooleanFrom(value, keys) {
|
|
238
|
+
if (!isRecord(value))
|
|
239
|
+
return undefined;
|
|
240
|
+
for (const key of keys) {
|
|
241
|
+
const candidate = value[key];
|
|
242
|
+
if (typeof candidate === "boolean")
|
|
243
|
+
return candidate;
|
|
244
|
+
}
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
function compactString(value, maxLength = 1200) {
|
|
248
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
const compacted = String(value)
|
|
252
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
253
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
254
|
+
.trim();
|
|
255
|
+
if (!compacted)
|
|
256
|
+
return undefined;
|
|
257
|
+
return compacted.length > maxLength
|
|
258
|
+
? `${compacted.slice(0, maxLength - 3).trim()}...`
|
|
259
|
+
: compacted;
|
|
260
|
+
}
|
|
261
|
+
function stripEmpty(value) {
|
|
262
|
+
if (Array.isArray(value)) {
|
|
263
|
+
return value
|
|
264
|
+
.map((item) => stripEmpty(item))
|
|
265
|
+
.filter((item) => {
|
|
266
|
+
if (item == null)
|
|
267
|
+
return false;
|
|
268
|
+
if (Array.isArray(item))
|
|
269
|
+
return item.length > 0;
|
|
270
|
+
if (isRecord(item))
|
|
271
|
+
return Object.keys(item).length > 0;
|
|
272
|
+
return true;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (!isRecord(value))
|
|
276
|
+
return value;
|
|
277
|
+
const entries = Object.entries(value)
|
|
278
|
+
.map(([key, item]) => [key, stripEmpty(item)])
|
|
279
|
+
.filter(([, item]) => {
|
|
280
|
+
if (item == null || item === "")
|
|
281
|
+
return false;
|
|
282
|
+
if (Array.isArray(item))
|
|
283
|
+
return item.length > 0;
|
|
284
|
+
if (isRecord(item))
|
|
285
|
+
return Object.keys(item).length > 0;
|
|
286
|
+
return true;
|
|
287
|
+
});
|
|
288
|
+
return Object.fromEntries(entries);
|
|
289
|
+
}
|
|
290
|
+
function asArray(value) {
|
|
291
|
+
if (Array.isArray(value))
|
|
292
|
+
return value;
|
|
293
|
+
return value == null ? [] : [value];
|
|
294
|
+
}
|
|
295
|
+
function pickProfileArray(profile, keys) {
|
|
296
|
+
for (const key of keys) {
|
|
297
|
+
const value = profile[key];
|
|
298
|
+
if (Array.isArray(value))
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
function datePartText(value) {
|
|
304
|
+
if (typeof value === "string")
|
|
305
|
+
return compactString(value, 80);
|
|
306
|
+
if (!isRecord(value))
|
|
307
|
+
return undefined;
|
|
308
|
+
const text = pickStringFrom(value, ["text", "date"], 80);
|
|
309
|
+
if (text)
|
|
310
|
+
return text;
|
|
311
|
+
const year = pickNumberFrom(value, ["year"]);
|
|
312
|
+
const month = pickStringFrom(value, ["month"], 20) || pickNumberFrom(value, ["month"]);
|
|
313
|
+
if (!year)
|
|
314
|
+
return undefined;
|
|
315
|
+
return month ? `${month} ${year}` : String(year);
|
|
316
|
+
}
|
|
317
|
+
function dateRangeFromItem(item) {
|
|
318
|
+
const explicit = pickStringFrom(item, ["dateRange", "date_range", "period"], 120);
|
|
319
|
+
if (explicit)
|
|
320
|
+
return explicit;
|
|
321
|
+
const startText = datePartText(item.startDate) ||
|
|
322
|
+
datePartText(stripEmpty({
|
|
323
|
+
month: item.start_month ?? item.startMonth,
|
|
324
|
+
year: item.start_year ?? item.startYear,
|
|
325
|
+
}));
|
|
326
|
+
const endText = datePartText(item.endDate) ||
|
|
327
|
+
datePartText(stripEmpty({
|
|
328
|
+
month: item.end_month ?? item.endMonth,
|
|
329
|
+
year: item.end_year ?? item.endYear,
|
|
330
|
+
}));
|
|
331
|
+
return [startText, endText].filter(Boolean).join(" - ") || undefined;
|
|
332
|
+
}
|
|
333
|
+
function compactExperienceItem(value) {
|
|
334
|
+
if (!isRecord(value))
|
|
335
|
+
return {};
|
|
336
|
+
const companyRecord = pickRecord(value, ["company"]);
|
|
337
|
+
return stripEmpty({
|
|
338
|
+
title: pickStringFrom(value, ["title", "position", "job_title"], 160),
|
|
339
|
+
companyName: pickStringFrom(value, ["companyName", "company_name", "company"], 160) ||
|
|
340
|
+
pickStringFrom(companyRecord, ["name"], 160),
|
|
341
|
+
companyLinkedinUrl: pickStringFrom(value, [
|
|
342
|
+
"companyLinkedinUrl",
|
|
343
|
+
"companyLinkedInUrl",
|
|
344
|
+
"company_linkedin_url",
|
|
345
|
+
"companyLink",
|
|
346
|
+
"company_public_url",
|
|
347
|
+
], 240),
|
|
348
|
+
dateRange: dateRangeFromItem(value),
|
|
349
|
+
duration: pickStringFrom(value, ["duration", "current_job_duration"], 80),
|
|
350
|
+
location: pickStringFrom(value, ["location"], 180),
|
|
351
|
+
description: pickStringFrom(value, ["description", "summary"], 700),
|
|
352
|
+
isCurrent: pickBooleanFrom(value, ["isCurrent", "is_current"]),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function compactEducationItem(value) {
|
|
356
|
+
if (!isRecord(value))
|
|
357
|
+
return {};
|
|
358
|
+
return stripEmpty({
|
|
359
|
+
schoolName: pickStringFrom(value, ["schoolName", "school_name", "school"], 180),
|
|
360
|
+
schoolLinkedinUrl: pickStringFrom(value, ["schoolLinkedinUrl", "schoolLinkedInUrl", "school_linkedin_url"], 240),
|
|
361
|
+
degree: pickStringFrom(value, ["degree"], 180),
|
|
362
|
+
fieldOfStudy: pickStringFrom(value, ["fieldOfStudy", "field_of_study"], 180),
|
|
363
|
+
dateRange: dateRangeFromItem(value),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function compactStringList(value, limit) {
|
|
367
|
+
return asArray(value)
|
|
368
|
+
.map((item) => {
|
|
369
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
370
|
+
return compactString(item, 120);
|
|
371
|
+
}
|
|
372
|
+
return pickStringFrom(item, ["name", "title", "skill"], 120);
|
|
373
|
+
})
|
|
374
|
+
.filter((item) => Boolean(item))
|
|
375
|
+
.slice(0, limit);
|
|
376
|
+
}
|
|
377
|
+
function compactLanguages(value) {
|
|
378
|
+
return asArray(value)
|
|
379
|
+
.map((item) => {
|
|
380
|
+
if (typeof item === "string")
|
|
381
|
+
return { name: compactString(item, 80) };
|
|
382
|
+
if (!isRecord(item))
|
|
383
|
+
return {};
|
|
384
|
+
return stripEmpty({
|
|
385
|
+
name: pickStringFrom(item, ["name", "language"], 80),
|
|
386
|
+
proficiency: pickStringFrom(item, ["proficiency"], 120),
|
|
387
|
+
});
|
|
388
|
+
})
|
|
389
|
+
.filter((item) => Object.keys(item).length > 0)
|
|
390
|
+
.slice(0, 10);
|
|
391
|
+
}
|
|
392
|
+
function compactNamedItems(value) {
|
|
393
|
+
return asArray(value)
|
|
394
|
+
.map((item) => {
|
|
395
|
+
if (typeof item === "string")
|
|
396
|
+
return { name: compactString(item, 160) };
|
|
397
|
+
if (!isRecord(item))
|
|
398
|
+
return {};
|
|
399
|
+
return stripEmpty({
|
|
400
|
+
name: pickStringFrom(item, ["name", "title"], 180),
|
|
401
|
+
organization: pickStringFrom(item, ["organization", "authority", "issuer", "companyName"], 180),
|
|
402
|
+
dateRange: dateRangeFromItem(item),
|
|
403
|
+
description: pickStringFrom(item, ["description"], 500),
|
|
404
|
+
url: pickStringFrom(item, ["url", "link"], 240),
|
|
405
|
+
});
|
|
406
|
+
})
|
|
407
|
+
.filter((item) => Object.keys(item).length > 0)
|
|
408
|
+
.slice(0, 5);
|
|
409
|
+
}
|
|
410
|
+
function firstCurrentRole(profile) {
|
|
411
|
+
const current = pickProfileArray(profile, [
|
|
412
|
+
"currentPosition",
|
|
413
|
+
"current_position",
|
|
414
|
+
])
|
|
415
|
+
.map(compactExperienceItem)
|
|
416
|
+
.find((item) => Object.keys(item).length > 0);
|
|
417
|
+
if (current)
|
|
418
|
+
return current;
|
|
419
|
+
const firstExperience = pickProfileArray(profile, [
|
|
420
|
+
"experience",
|
|
421
|
+
"experiences",
|
|
422
|
+
])
|
|
423
|
+
.map(compactExperienceItem)
|
|
424
|
+
.find((item) => Object.keys(item).length > 0);
|
|
425
|
+
if (firstExperience)
|
|
426
|
+
return firstExperience;
|
|
427
|
+
return stripEmpty({
|
|
428
|
+
title: pickStringFrom(profile, ["job_title", "jobTitle", "title"], 160),
|
|
429
|
+
companyName: pickStringFrom(profile, ["company", "companyName"], 160),
|
|
430
|
+
dateRange: dateRangeFromItem(stripEmpty({
|
|
431
|
+
start_month: profile.current_company_join_month ?? profile.currentCompanyJoinMonth,
|
|
432
|
+
start_year: profile.current_company_join_year ?? profile.currentCompanyJoinYear,
|
|
433
|
+
})),
|
|
434
|
+
duration: pickStringFrom(profile, ["current_job_duration", "currentJobDuration"], 80),
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
function compactCompanyFromProfile(profile, currentRole) {
|
|
438
|
+
return stripEmpty({
|
|
439
|
+
name: pickStringFrom(profile, ["company", "companyName"], 180) ||
|
|
440
|
+
currentRole.companyName,
|
|
441
|
+
linkedinUrl: pickStringFrom(profile, [
|
|
442
|
+
"company_linkedin_url",
|
|
443
|
+
"companyLinkedinUrl",
|
|
444
|
+
"companyLinkedInUrl",
|
|
445
|
+
"currentCompanyLinkedinUrl",
|
|
446
|
+
], 240) || currentRole.companyLinkedinUrl,
|
|
447
|
+
website: pickStringFrom(profile, ["company_website", "companyWebsite", "website"], 240),
|
|
448
|
+
domain: pickStringFrom(profile, ["company_domain", "companyDomain", "currentCompanyDomain"], 120),
|
|
449
|
+
industry: pickStringFrom(profile, ["company_industry", "industry"], 160),
|
|
450
|
+
employeeCount: pickNumberFrom(profile, [
|
|
451
|
+
"company_employee_count",
|
|
452
|
+
"companyEmployeeCount",
|
|
453
|
+
"employeeCount",
|
|
454
|
+
]),
|
|
455
|
+
employeeRange: pickStringFrom(profile, ["company_employee_range", "companyEmployeeRange", "employeeCountRange"], 80),
|
|
456
|
+
description: pickStringFrom(profile, ["company_description", "companyDescription"], 700),
|
|
457
|
+
founded: pickStringFrom(profile, ["company_year_founded", "founded"], 80) ||
|
|
458
|
+
pickNumberFrom(profile, ["company_year_founded", "founded"]),
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
export function compactLinkedInProfile(profile) {
|
|
462
|
+
if (!isRecord(profile))
|
|
463
|
+
return {};
|
|
464
|
+
const currentRole = firstCurrentRole(profile);
|
|
465
|
+
const experience = pickProfileArray(profile, ["experience", "experiences"])
|
|
466
|
+
.map(compactExperienceItem)
|
|
467
|
+
.filter((item) => Object.keys(item).length > 0)
|
|
468
|
+
.slice(0, 5);
|
|
469
|
+
const education = pickProfileArray(profile, ["education", "educations"])
|
|
470
|
+
.map(compactEducationItem)
|
|
471
|
+
.filter((item) => Object.keys(item).length > 0)
|
|
472
|
+
.slice(0, 3);
|
|
473
|
+
const skills = compactStringList(profile.topSkills ?? profile.skills, 12);
|
|
474
|
+
return stripEmpty({
|
|
475
|
+
id: pickStringFrom(profile, ["id", "profile_id"], 180),
|
|
476
|
+
publicIdentifier: pickStringFrom(profile, ["publicIdentifier", "public_id", "publicIdentifier"], 120),
|
|
477
|
+
linkedinUrl: pickStringFrom(profile, ["linkedinUrl", "linkedin_url", "profile_url"], 240),
|
|
478
|
+
firstName: pickStringFrom(profile, ["firstName", "first_name"], 120),
|
|
479
|
+
lastName: pickStringFrom(profile, ["lastName", "last_name"], 120),
|
|
480
|
+
fullName: pickStringFrom(profile, ["fullName", "full_name", "name"], 180) ||
|
|
481
|
+
[
|
|
482
|
+
pickStringFrom(profile, ["firstName", "first_name"], 120),
|
|
483
|
+
pickStringFrom(profile, ["lastName", "last_name"], 120),
|
|
484
|
+
]
|
|
485
|
+
.filter(Boolean)
|
|
486
|
+
.join(" "),
|
|
487
|
+
headline: pickStringFrom(profile, ["headline"], 320),
|
|
488
|
+
about: pickStringFrom(profile, ["about", "summary"], 1600),
|
|
489
|
+
location: pickStringFrom(profile, ["location"], 180),
|
|
490
|
+
followerCount: pickNumberFrom(profile, ["followerCount", "follower_count"]),
|
|
491
|
+
connectionCount: pickNumberFrom(profile, [
|
|
492
|
+
"connectionsCount",
|
|
493
|
+
"connection_count",
|
|
494
|
+
"connectionCount",
|
|
495
|
+
]),
|
|
496
|
+
isPremium: pickBooleanFrom(profile, ["premium", "is_premium", "isPremium"]),
|
|
497
|
+
isCreator: pickBooleanFrom(profile, ["is_creator", "isCreator"]),
|
|
498
|
+
isVerified: pickBooleanFrom(profile, [
|
|
499
|
+
"verified",
|
|
500
|
+
"is_verified",
|
|
501
|
+
"isVerified",
|
|
502
|
+
]),
|
|
503
|
+
currentRole,
|
|
504
|
+
company: compactCompanyFromProfile(profile, currentRole),
|
|
505
|
+
experience,
|
|
506
|
+
education,
|
|
507
|
+
skills,
|
|
508
|
+
languages: compactLanguages(profile.languages),
|
|
509
|
+
certifications: compactNamedItems(profile.certifications),
|
|
510
|
+
projects: compactNamedItems(profile.projects),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
function canonicalizeLinkedInProfileResponse(raw) {
|
|
514
|
+
if (raw &&
|
|
515
|
+
typeof raw === "object" &&
|
|
516
|
+
raw.status === "success" &&
|
|
517
|
+
(raw.data || raw.person || raw.profile?.person)) {
|
|
518
|
+
const canonical = raw.data ?? raw.person ?? raw.profile?.person;
|
|
519
|
+
const { status, data, person, profile, ...rest } = raw;
|
|
520
|
+
return { status, profile: canonical, ...rest };
|
|
521
|
+
}
|
|
522
|
+
return raw;
|
|
523
|
+
}
|
|
524
|
+
function compactLinkedInProfileResponse(raw) {
|
|
525
|
+
const normalized = canonicalizeLinkedInProfileResponse(raw);
|
|
526
|
+
if (normalized &&
|
|
527
|
+
typeof normalized === "object" &&
|
|
528
|
+
normalized.status === "success" &&
|
|
529
|
+
normalized.profile) {
|
|
530
|
+
return {
|
|
531
|
+
status: normalized.status,
|
|
532
|
+
profile: compactLinkedInProfile(normalized.profile),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return normalized;
|
|
536
|
+
}
|
|
190
537
|
function serializePostMedia(post) {
|
|
191
538
|
const media = post.media && typeof post.media === "object"
|
|
192
539
|
? post.media
|
|
@@ -295,23 +642,9 @@ export async function fetchLinkedInProfile(linkedinUrl, options = {}) {
|
|
|
295
642
|
params.set("full", "true");
|
|
296
643
|
}
|
|
297
644
|
const raw = await api.get(`/api/v1/scrape/linkedin/profile?${params.toString()}`);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// For the MCP layer, that ~3xs the JSON size and routinely breaches
|
|
302
|
-
// Claude Code's max-token cap on tool results — which triggers the
|
|
303
|
-
// harness's "save to file + read in chunks via python3" overflow
|
|
304
|
-
// fallback. Strip the duplicates here so the agent gets one clean
|
|
305
|
-
// copy of the data under a single `profile` key.
|
|
306
|
-
if (raw &&
|
|
307
|
-
typeof raw === "object" &&
|
|
308
|
-
raw.status === "success" &&
|
|
309
|
-
(raw.data || raw.person || raw.profile?.person)) {
|
|
310
|
-
const canonical = raw.data ?? raw.person ?? raw.profile?.person;
|
|
311
|
-
const { status, data, person, profile, ...rest } = raw;
|
|
312
|
-
return { status, profile: canonical, ...rest };
|
|
313
|
-
}
|
|
314
|
-
return raw;
|
|
645
|
+
return options.fetchMinimal === false
|
|
646
|
+
? canonicalizeLinkedInProfileResponse(raw)
|
|
647
|
+
: compactLinkedInProfileResponse(raw);
|
|
315
648
|
}
|
|
316
649
|
export async function fetchCompany(companyUrl) {
|
|
317
650
|
const api = getApi();
|
package/dist/tools/readiness.js
CHANGED
|
@@ -20,6 +20,23 @@ function normalizeLeadSourceProvider(provider) {
|
|
|
20
20
|
return "csv-linkedin";
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
23
|
+
function isPositiveNumber(value) {
|
|
24
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
25
|
+
}
|
|
26
|
+
function hasSignalDiscoveryProgressEvidence(rowCount, importProgress) {
|
|
27
|
+
const phase = importProgress?.phase ?? null;
|
|
28
|
+
return (rowCount > 0 ||
|
|
29
|
+
isPositiveNumber(importProgress?.processed) ||
|
|
30
|
+
isPositiveNumber(importProgress?.leadsImported) ||
|
|
31
|
+
isPositiveNumber(importProgress?.scrapedEngagers) ||
|
|
32
|
+
isPositiveNumber(importProgress?.evaluatedEngagers) ||
|
|
33
|
+
isPositiveNumber(importProgress?.evaluationTotal) ||
|
|
34
|
+
isPositiveNumber(importProgress?.activeEvaluationBatches) ||
|
|
35
|
+
isPositiveNumber(importProgress?.queuedForEvaluation) ||
|
|
36
|
+
isPositiveNumber(importProgress?.postsScraped) ||
|
|
37
|
+
phase === "scraping" ||
|
|
38
|
+
phase === "processing");
|
|
39
|
+
}
|
|
23
40
|
function sleep(ms) {
|
|
24
41
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
42
|
}
|
|
@@ -233,6 +250,8 @@ export async function waitForLeadListReady(input) {
|
|
|
233
250
|
if (typeof configTarget === "number" && !targetLeadCount) {
|
|
234
251
|
targetLeadCount = configTarget;
|
|
235
252
|
}
|
|
253
|
+
const signalDiscoveryHasProgressEvidence = provider === "signal-discovery" &&
|
|
254
|
+
hasSignalDiscoveryProgressEvidence(rowCount, importProgress);
|
|
236
255
|
let jobId = input.jobId;
|
|
237
256
|
if (!jobId && typeof config?.importJobId === "string") {
|
|
238
257
|
jobId = config.importJobId;
|
|
@@ -253,7 +272,9 @@ export async function waitForLeadListReady(input) {
|
|
|
253
272
|
targetLeadCount,
|
|
254
273
|
};
|
|
255
274
|
}
|
|
256
|
-
if (configError &&
|
|
275
|
+
if (configError &&
|
|
276
|
+
configStatus !== "complete" &&
|
|
277
|
+
!signalDiscoveryHasProgressEvidence) {
|
|
257
278
|
return {
|
|
258
279
|
ready: false,
|
|
259
280
|
reason: "import_failed",
|
|
@@ -267,21 +288,31 @@ export async function waitForLeadListReady(input) {
|
|
|
267
288
|
error: configError,
|
|
268
289
|
};
|
|
269
290
|
}
|
|
291
|
+
if (configError &&
|
|
292
|
+
configStatus !== "complete" &&
|
|
293
|
+
signalDiscoveryHasProgressEvidence) {
|
|
294
|
+
lastError = configError;
|
|
295
|
+
}
|
|
270
296
|
if (configStatus) {
|
|
271
297
|
lastStatus = configStatus;
|
|
272
298
|
if (configStatus === "error") {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
299
|
+
if (signalDiscoveryHasProgressEvidence) {
|
|
300
|
+
lastError = configError ?? lastError;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
return {
|
|
304
|
+
ready: false,
|
|
305
|
+
reason: "import_failed",
|
|
306
|
+
leadListId,
|
|
307
|
+
provider: provider ?? null,
|
|
308
|
+
attempts,
|
|
309
|
+
elapsedMs: Date.now() - start,
|
|
310
|
+
rowCount,
|
|
311
|
+
status: configStatus,
|
|
312
|
+
targetLeadCount,
|
|
313
|
+
error: configError,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
285
316
|
}
|
|
286
317
|
if (configStatus === "complete") {
|
|
287
318
|
importComplete = true;
|