@sellable/mcp 0.1.268 → 0.1.269
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-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/server.js +8 -7
- package/dist/tools/content-posts.d.ts +1 -421
- package/dist/tools/content-posts.js +0 -802
- package/dist/tools/engage-discovery.d.ts +0 -24
- package/dist/tools/engage-discovery.js +9 -114
- package/dist/tools/harvest-jobs.d.ts +182 -0
- package/dist/tools/harvest-jobs.js +429 -0
- package/dist/tools/leads.js +1 -1
- package/dist/tools/registry.d.ts +47 -76
- package/dist/tools/registry.js +2 -0
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +10 -0
- package/skills/create-campaign/core/providers/prospeo.json +5 -2
- package/skills/create-post/SKILL.md +36 -622
- package/skills/create-post/references/hook-research-playbook.md +31 -464
- package/skills/create-post/references/post-file-contract.md +0 -36
- package/skills/create-post/references/post-validation.md +27 -260
- package/skills/create-post/references/premise-development.md +7 -298
- package/skills/providers/prospeo.md +21 -0
- package/skills/create-post/references/linkedin-preview-rendering.md +0 -221
- package/skills/research/config.json +0 -9
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
type FollowerBandFit = "in_target_band" | "below_target_band" | "above_target_band" | "unknown";
|
|
2
|
-
type ReachSignals = {
|
|
3
|
-
targetFollowerMin?: number;
|
|
4
|
-
targetFollowerMax?: number;
|
|
5
|
-
followerBandFit: FollowerBandFit;
|
|
6
|
-
engagementPer1kFollowers: number | null;
|
|
7
|
-
weightedEngagementPer1kFollowers: number | null;
|
|
8
|
-
reachPenaltyMultiplier: number;
|
|
9
|
-
reachAdjustedScore: number;
|
|
10
|
-
confidence: "high" | "medium" | "low";
|
|
11
|
-
};
|
|
12
1
|
export type EngagementPost = {
|
|
13
2
|
postId: string;
|
|
14
3
|
url: string;
|
|
@@ -18,7 +7,6 @@ export type EngagementPost = {
|
|
|
18
7
|
name: string;
|
|
19
8
|
headline: string;
|
|
20
9
|
profileUrl: string;
|
|
21
|
-
followerCount?: number;
|
|
22
10
|
};
|
|
23
11
|
engagement: {
|
|
24
12
|
likes: number;
|
|
@@ -26,7 +14,6 @@ export type EngagementPost = {
|
|
|
26
14
|
shares: number;
|
|
27
15
|
total: number;
|
|
28
16
|
};
|
|
29
|
-
reachSignals?: ReachSignals;
|
|
30
17
|
contentPreview: string;
|
|
31
18
|
};
|
|
32
19
|
export type SearchEngagementPostsInput = {
|
|
@@ -36,8 +23,6 @@ export type SearchEngagementPostsInput = {
|
|
|
36
23
|
minTotalEngagement?: number;
|
|
37
24
|
maxPosts?: number;
|
|
38
25
|
excludePostUrls?: string[];
|
|
39
|
-
targetFollowerMin?: number;
|
|
40
|
-
targetFollowerMax?: number;
|
|
41
26
|
};
|
|
42
27
|
export type SearchEngagementPostsResponse = {
|
|
43
28
|
success: boolean;
|
|
@@ -85,18 +70,9 @@ export declare const engageDiscoveryToolDefinitions: {
|
|
|
85
70
|
};
|
|
86
71
|
description: string;
|
|
87
72
|
};
|
|
88
|
-
targetFollowerMin: {
|
|
89
|
-
type: string;
|
|
90
|
-
description: string;
|
|
91
|
-
};
|
|
92
|
-
targetFollowerMax: {
|
|
93
|
-
type: string;
|
|
94
|
-
description: string;
|
|
95
|
-
};
|
|
96
73
|
};
|
|
97
74
|
required: string[];
|
|
98
75
|
additionalProperties: boolean;
|
|
99
76
|
};
|
|
100
77
|
}[];
|
|
101
78
|
export declare function searchEngagementPosts(input: SearchEngagementPostsInput): Promise<SearchEngagementPostsResponse>;
|
|
102
|
-
export {};
|
|
@@ -32,14 +32,6 @@ export const engageDiscoveryToolDefinitions = [
|
|
|
32
32
|
items: { type: "string" },
|
|
33
33
|
description: "Optional list of post URLs to exclude (e.g. already engaged/scheduled).",
|
|
34
34
|
},
|
|
35
|
-
targetFollowerMin: {
|
|
36
|
-
type: "number",
|
|
37
|
-
description: "Optional lower bound for creator follower count when reach-normalizing hook/source quality.",
|
|
38
|
-
},
|
|
39
|
-
targetFollowerMax: {
|
|
40
|
-
type: "number",
|
|
41
|
-
description: "Optional upper bound for creator follower count when reach-normalizing hook/source quality.",
|
|
42
|
-
},
|
|
43
35
|
},
|
|
44
36
|
required: ["keywords"],
|
|
45
37
|
additionalProperties: false,
|
|
@@ -71,79 +63,6 @@ function safeNumber(value) {
|
|
|
71
63
|
const n = typeof value === "number" ? value : Number(value);
|
|
72
64
|
return Number.isFinite(n) ? n : 0;
|
|
73
65
|
}
|
|
74
|
-
function round3(value) {
|
|
75
|
-
return Number(value.toFixed(3));
|
|
76
|
-
}
|
|
77
|
-
function parseFollowerCount(author) {
|
|
78
|
-
const direct = safeNumber(author?.followerCount);
|
|
79
|
-
if (direct > 0)
|
|
80
|
-
return direct;
|
|
81
|
-
const text = String(author?.followers || author?.followerCountText || "");
|
|
82
|
-
const match = text.match(/([\d,.]+)\s*([kKmM])?/);
|
|
83
|
-
if (!match)
|
|
84
|
-
return undefined;
|
|
85
|
-
const base = Number(match[1].replace(/,/g, ""));
|
|
86
|
-
if (!Number.isFinite(base) || base <= 0)
|
|
87
|
-
return undefined;
|
|
88
|
-
const suffix = match[2]?.toLowerCase();
|
|
89
|
-
if (suffix === "m")
|
|
90
|
-
return Math.round(base * 1_000_000);
|
|
91
|
-
if (suffix === "k")
|
|
92
|
-
return Math.round(base * 1_000);
|
|
93
|
-
return Math.round(base);
|
|
94
|
-
}
|
|
95
|
-
function reachPenaltyMultiplier(followerCount, targetFollowerMin, targetFollowerMax) {
|
|
96
|
-
if (!followerCount || !targetFollowerMin || !targetFollowerMax)
|
|
97
|
-
return 0.4;
|
|
98
|
-
if (followerCount >= targetFollowerMin && followerCount <= targetFollowerMax) {
|
|
99
|
-
return 1;
|
|
100
|
-
}
|
|
101
|
-
if (followerCount < targetFollowerMin)
|
|
102
|
-
return 0.75;
|
|
103
|
-
if (followerCount <= targetFollowerMax * 2)
|
|
104
|
-
return 0.65;
|
|
105
|
-
if (followerCount <= targetFollowerMax * 5)
|
|
106
|
-
return 0.35;
|
|
107
|
-
return 0.15;
|
|
108
|
-
}
|
|
109
|
-
function followerBandFit(followerCount, targetFollowerMin, targetFollowerMax) {
|
|
110
|
-
if (!followerCount || !targetFollowerMin || !targetFollowerMax) {
|
|
111
|
-
return "unknown";
|
|
112
|
-
}
|
|
113
|
-
if (followerCount >= targetFollowerMin && followerCount <= targetFollowerMax) {
|
|
114
|
-
return "in_target_band";
|
|
115
|
-
}
|
|
116
|
-
if (followerCount < targetFollowerMin)
|
|
117
|
-
return "below_target_band";
|
|
118
|
-
return "above_target_band";
|
|
119
|
-
}
|
|
120
|
-
function buildReachSignals(params) {
|
|
121
|
-
const { followerCount, likes, comments, shares, total } = params;
|
|
122
|
-
const hasTarget = Boolean(params.targetFollowerMin && params.targetFollowerMax);
|
|
123
|
-
if (!hasTarget && !followerCount)
|
|
124
|
-
return undefined;
|
|
125
|
-
const weightedEngagement = likes + comments * 4 + shares * 12;
|
|
126
|
-
const penalty = reachPenaltyMultiplier(followerCount, params.targetFollowerMin, params.targetFollowerMax);
|
|
127
|
-
const engagementPer1kFollowers = followerCount
|
|
128
|
-
? round3((total / followerCount) * 1000)
|
|
129
|
-
: null;
|
|
130
|
-
const weightedEngagementPer1kFollowers = followerCount
|
|
131
|
-
? round3((weightedEngagement / followerCount) * 1000)
|
|
132
|
-
: null;
|
|
133
|
-
const reachAdjustedScore = weightedEngagementPer1kFollowers === null
|
|
134
|
-
? 0
|
|
135
|
-
: round3(weightedEngagementPer1kFollowers * penalty);
|
|
136
|
-
return {
|
|
137
|
-
targetFollowerMin: params.targetFollowerMin,
|
|
138
|
-
targetFollowerMax: params.targetFollowerMax,
|
|
139
|
-
followerBandFit: followerBandFit(followerCount, params.targetFollowerMin, params.targetFollowerMax),
|
|
140
|
-
engagementPer1kFollowers,
|
|
141
|
-
weightedEngagementPer1kFollowers,
|
|
142
|
-
reachPenaltyMultiplier: penalty,
|
|
143
|
-
reachAdjustedScore,
|
|
144
|
-
confidence: followerCount ? (hasTarget ? "high" : "medium") : "low",
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
66
|
export async function searchEngagementPosts(input) {
|
|
148
67
|
const api = getApi();
|
|
149
68
|
const keywords = (input.keywords || [])
|
|
@@ -160,12 +79,6 @@ export async function searchEngagementPosts(input) {
|
|
|
160
79
|
const maxPosts = typeof input.maxPosts === "number" && input.maxPosts > 0
|
|
161
80
|
? Math.min(50, input.maxPosts)
|
|
162
81
|
: 25;
|
|
163
|
-
const targetFollowerMin = typeof input.targetFollowerMin === "number" && input.targetFollowerMin > 0
|
|
164
|
-
? input.targetFollowerMin
|
|
165
|
-
: undefined;
|
|
166
|
-
const targetFollowerMax = typeof input.targetFollowerMax === "number" && input.targetFollowerMax > 0
|
|
167
|
-
? input.targetFollowerMax
|
|
168
|
-
: undefined;
|
|
169
82
|
const exclude = new Set((input.excludePostUrls || [])
|
|
170
83
|
.map((u) => normalizePostUrl(u))
|
|
171
84
|
.filter(Boolean));
|
|
@@ -174,10 +87,10 @@ export async function searchEngagementPosts(input) {
|
|
|
174
87
|
keywords: keywords.map((keyword) => ({ keyword })),
|
|
175
88
|
page,
|
|
176
89
|
});
|
|
177
|
-
const rawPosts = Array.isArray(response?.
|
|
178
|
-
? response.
|
|
179
|
-
: Array.isArray(response?.
|
|
180
|
-
? response.
|
|
90
|
+
const rawPosts = Array.isArray(response?.topPostsForLLM)
|
|
91
|
+
? response.topPostsForLLM
|
|
92
|
+
: Array.isArray(response?.posts)
|
|
93
|
+
? response.posts
|
|
181
94
|
: [];
|
|
182
95
|
const now = Date.now();
|
|
183
96
|
const oldestMs = now - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
@@ -208,16 +121,6 @@ export async function searchEngagementPosts(input) {
|
|
|
208
121
|
tooOld += 1;
|
|
209
122
|
continue;
|
|
210
123
|
}
|
|
211
|
-
const followerCount = parseFollowerCount(p?.author);
|
|
212
|
-
const reachSignals = buildReachSignals({
|
|
213
|
-
followerCount,
|
|
214
|
-
likes,
|
|
215
|
-
comments,
|
|
216
|
-
shares,
|
|
217
|
-
total,
|
|
218
|
-
targetFollowerMin,
|
|
219
|
-
targetFollowerMax,
|
|
220
|
-
});
|
|
221
124
|
kept.push({
|
|
222
125
|
postId: String(p?.id || ""),
|
|
223
126
|
url,
|
|
@@ -229,26 +132,18 @@ export async function searchEngagementPosts(input) {
|
|
|
229
132
|
name: String(p?.author?.name || ""),
|
|
230
133
|
headline: String(p?.author?.headline || ""),
|
|
231
134
|
profileUrl: String(p?.author?.profileUrl || ""),
|
|
232
|
-
...(followerCount ? { followerCount } : {}),
|
|
233
135
|
},
|
|
234
136
|
engagement: { likes, comments, shares, total },
|
|
235
|
-
...(reachSignals ? { reachSignals } : {}),
|
|
236
137
|
contentPreview: previewText(String(p?.content || ""), 220),
|
|
237
138
|
});
|
|
139
|
+
if (kept.length >= maxPosts)
|
|
140
|
+
break;
|
|
238
141
|
}
|
|
239
|
-
|
|
240
|
-
kept.sort((a, b) =>
|
|
241
|
-
if (hasReachTarget) {
|
|
242
|
-
const reachDelta = (b.reachSignals?.reachAdjustedScore || 0) -
|
|
243
|
-
(a.reachSignals?.reachAdjustedScore || 0);
|
|
244
|
-
if (reachDelta !== 0)
|
|
245
|
-
return reachDelta;
|
|
246
|
-
}
|
|
247
|
-
return b.engagement.total - a.engagement.total;
|
|
248
|
-
});
|
|
142
|
+
// Sort by total engagement desc for a predictable shortlist.
|
|
143
|
+
kept.sort((a, b) => b.engagement.total - a.engagement.total);
|
|
249
144
|
return {
|
|
250
145
|
success: true,
|
|
251
|
-
posts: kept
|
|
146
|
+
posts: kept,
|
|
252
147
|
totalReturned: rawPosts.length,
|
|
253
148
|
filteredOut: { tooOld, excluded, tooLowEngagement },
|
|
254
149
|
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
type ArtifactFormat = "markdown" | "csv" | "both";
|
|
2
|
+
export interface SearchHarvestJobsInput {
|
|
3
|
+
search?: string;
|
|
4
|
+
searches?: string[];
|
|
5
|
+
location?: string;
|
|
6
|
+
geoId?: string;
|
|
7
|
+
postedLimit?: "24h" | "week" | "month";
|
|
8
|
+
sortBy?: "relevance" | "date";
|
|
9
|
+
workplaceType?: string | string[];
|
|
10
|
+
employmentType?: string | string[];
|
|
11
|
+
experienceLevel?: string | string[];
|
|
12
|
+
easyApply?: boolean;
|
|
13
|
+
under10Applicants?: boolean;
|
|
14
|
+
salary?: string;
|
|
15
|
+
pages?: number;
|
|
16
|
+
maxRows?: number;
|
|
17
|
+
artifactFormat?: ArtifactFormat;
|
|
18
|
+
outputDir?: string;
|
|
19
|
+
fileBaseName?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ConfirmHarvestJobCompaniesInput {
|
|
22
|
+
searchToken?: string;
|
|
23
|
+
selectedJobIds?: string[];
|
|
24
|
+
name?: string;
|
|
25
|
+
outputDir?: string;
|
|
26
|
+
fileBaseName?: string;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
export declare const harvestJobToolDefinitions: ({
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: string;
|
|
34
|
+
properties: {
|
|
35
|
+
search: {
|
|
36
|
+
type: string;
|
|
37
|
+
};
|
|
38
|
+
searches: {
|
|
39
|
+
type: string;
|
|
40
|
+
items: {
|
|
41
|
+
type: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
location: {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
geoId: {
|
|
48
|
+
type: string;
|
|
49
|
+
};
|
|
50
|
+
postedLimit: {
|
|
51
|
+
type: string;
|
|
52
|
+
enum: string[];
|
|
53
|
+
};
|
|
54
|
+
sortBy: {
|
|
55
|
+
type: string;
|
|
56
|
+
enum: string[];
|
|
57
|
+
};
|
|
58
|
+
workplaceType: {
|
|
59
|
+
oneOf: ({
|
|
60
|
+
type: string;
|
|
61
|
+
items?: undefined;
|
|
62
|
+
} | {
|
|
63
|
+
type: string;
|
|
64
|
+
items: {
|
|
65
|
+
type: string;
|
|
66
|
+
};
|
|
67
|
+
})[];
|
|
68
|
+
};
|
|
69
|
+
employmentType: {
|
|
70
|
+
oneOf: ({
|
|
71
|
+
type: string;
|
|
72
|
+
items?: undefined;
|
|
73
|
+
} | {
|
|
74
|
+
type: string;
|
|
75
|
+
items: {
|
|
76
|
+
type: string;
|
|
77
|
+
};
|
|
78
|
+
})[];
|
|
79
|
+
};
|
|
80
|
+
experienceLevel: {
|
|
81
|
+
oneOf: ({
|
|
82
|
+
type: string;
|
|
83
|
+
items?: undefined;
|
|
84
|
+
} | {
|
|
85
|
+
type: string;
|
|
86
|
+
items: {
|
|
87
|
+
type: string;
|
|
88
|
+
};
|
|
89
|
+
})[];
|
|
90
|
+
};
|
|
91
|
+
easyApply: {
|
|
92
|
+
type: string;
|
|
93
|
+
};
|
|
94
|
+
under10Applicants: {
|
|
95
|
+
type: string;
|
|
96
|
+
};
|
|
97
|
+
salary: {
|
|
98
|
+
type: string;
|
|
99
|
+
};
|
|
100
|
+
pages: {
|
|
101
|
+
type: string;
|
|
102
|
+
description: string;
|
|
103
|
+
};
|
|
104
|
+
maxRows: {
|
|
105
|
+
type: string;
|
|
106
|
+
description: string;
|
|
107
|
+
};
|
|
108
|
+
artifactFormat: {
|
|
109
|
+
type: string;
|
|
110
|
+
enum: string[];
|
|
111
|
+
description: string;
|
|
112
|
+
};
|
|
113
|
+
outputDir: {
|
|
114
|
+
type: string;
|
|
115
|
+
description: string;
|
|
116
|
+
};
|
|
117
|
+
fileBaseName: {
|
|
118
|
+
type: string;
|
|
119
|
+
description: string;
|
|
120
|
+
};
|
|
121
|
+
searchToken?: undefined;
|
|
122
|
+
selectedJobIds?: undefined;
|
|
123
|
+
name?: undefined;
|
|
124
|
+
};
|
|
125
|
+
required: never[];
|
|
126
|
+
additionalProperties: boolean;
|
|
127
|
+
};
|
|
128
|
+
} | {
|
|
129
|
+
name: string;
|
|
130
|
+
description: string;
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: string;
|
|
133
|
+
properties: {
|
|
134
|
+
searchToken: {
|
|
135
|
+
type: string;
|
|
136
|
+
description: string;
|
|
137
|
+
};
|
|
138
|
+
selectedJobIds: {
|
|
139
|
+
type: string;
|
|
140
|
+
items: {
|
|
141
|
+
type: string;
|
|
142
|
+
};
|
|
143
|
+
description: string;
|
|
144
|
+
};
|
|
145
|
+
name: {
|
|
146
|
+
type: string;
|
|
147
|
+
};
|
|
148
|
+
outputDir: {
|
|
149
|
+
type: string;
|
|
150
|
+
description: string;
|
|
151
|
+
};
|
|
152
|
+
fileBaseName: {
|
|
153
|
+
type: string;
|
|
154
|
+
description: string;
|
|
155
|
+
};
|
|
156
|
+
search?: undefined;
|
|
157
|
+
searches?: undefined;
|
|
158
|
+
location?: undefined;
|
|
159
|
+
geoId?: undefined;
|
|
160
|
+
postedLimit?: undefined;
|
|
161
|
+
sortBy?: undefined;
|
|
162
|
+
workplaceType?: undefined;
|
|
163
|
+
employmentType?: undefined;
|
|
164
|
+
experienceLevel?: undefined;
|
|
165
|
+
easyApply?: undefined;
|
|
166
|
+
under10Applicants?: undefined;
|
|
167
|
+
salary?: undefined;
|
|
168
|
+
pages?: undefined;
|
|
169
|
+
maxRows?: undefined;
|
|
170
|
+
artifactFormat?: undefined;
|
|
171
|
+
};
|
|
172
|
+
required: string[];
|
|
173
|
+
additionalProperties: boolean;
|
|
174
|
+
};
|
|
175
|
+
})[];
|
|
176
|
+
export declare function searchHarvestJobs(input: SearchHarvestJobsInput): Promise<{
|
|
177
|
+
[k: string]: unknown;
|
|
178
|
+
}>;
|
|
179
|
+
export declare function confirmHarvestJobCompanies(input: ConfirmHarvestJobCompaniesInput): Promise<{
|
|
180
|
+
[k: string]: unknown;
|
|
181
|
+
}>;
|
|
182
|
+
export {};
|