@sellable/mcp 0.1.269 → 0.1.271

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.
@@ -1,3 +1,14 @@
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
+ };
1
12
  export type EngagementPost = {
2
13
  postId: string;
3
14
  url: string;
@@ -7,6 +18,7 @@ export type EngagementPost = {
7
18
  name: string;
8
19
  headline: string;
9
20
  profileUrl: string;
21
+ followerCount?: number;
10
22
  };
11
23
  engagement: {
12
24
  likes: number;
@@ -14,6 +26,7 @@ export type EngagementPost = {
14
26
  shares: number;
15
27
  total: number;
16
28
  };
29
+ reachSignals?: ReachSignals;
17
30
  contentPreview: string;
18
31
  };
19
32
  export type SearchEngagementPostsInput = {
@@ -23,6 +36,8 @@ export type SearchEngagementPostsInput = {
23
36
  minTotalEngagement?: number;
24
37
  maxPosts?: number;
25
38
  excludePostUrls?: string[];
39
+ targetFollowerMin?: number;
40
+ targetFollowerMax?: number;
26
41
  };
27
42
  export type SearchEngagementPostsResponse = {
28
43
  success: boolean;
@@ -70,9 +85,18 @@ export declare const engageDiscoveryToolDefinitions: {
70
85
  };
71
86
  description: string;
72
87
  };
88
+ targetFollowerMin: {
89
+ type: string;
90
+ description: string;
91
+ };
92
+ targetFollowerMax: {
93
+ type: string;
94
+ description: string;
95
+ };
73
96
  };
74
97
  required: string[];
75
98
  additionalProperties: boolean;
76
99
  };
77
100
  }[];
78
101
  export declare function searchEngagementPosts(input: SearchEngagementPostsInput): Promise<SearchEngagementPostsResponse>;
102
+ export {};
@@ -32,6 +32,14 @@ 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
+ },
35
43
  },
36
44
  required: ["keywords"],
37
45
  additionalProperties: false,
@@ -63,6 +71,79 @@ function safeNumber(value) {
63
71
  const n = typeof value === "number" ? value : Number(value);
64
72
  return Number.isFinite(n) ? n : 0;
65
73
  }
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
+ }
66
147
  export async function searchEngagementPosts(input) {
67
148
  const api = getApi();
68
149
  const keywords = (input.keywords || [])
@@ -79,6 +160,12 @@ export async function searchEngagementPosts(input) {
79
160
  const maxPosts = typeof input.maxPosts === "number" && input.maxPosts > 0
80
161
  ? Math.min(50, input.maxPosts)
81
162
  : 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;
82
169
  const exclude = new Set((input.excludePostUrls || [])
83
170
  .map((u) => normalizePostUrl(u))
84
171
  .filter(Boolean));
@@ -87,10 +174,10 @@ export async function searchEngagementPosts(input) {
87
174
  keywords: keywords.map((keyword) => ({ keyword })),
88
175
  page,
89
176
  });
90
- const rawPosts = Array.isArray(response?.topPostsForLLM)
91
- ? response.topPostsForLLM
92
- : Array.isArray(response?.posts)
93
- ? response.posts
177
+ const rawPosts = Array.isArray(response?.posts)
178
+ ? response.posts
179
+ : Array.isArray(response?.topPostsForLLM)
180
+ ? response.topPostsForLLM
94
181
  : [];
95
182
  const now = Date.now();
96
183
  const oldestMs = now - maxAgeDays * 24 * 60 * 60 * 1000;
@@ -121,6 +208,16 @@ export async function searchEngagementPosts(input) {
121
208
  tooOld += 1;
122
209
  continue;
123
210
  }
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
+ });
124
221
  kept.push({
125
222
  postId: String(p?.id || ""),
126
223
  url,
@@ -132,18 +229,26 @@ export async function searchEngagementPosts(input) {
132
229
  name: String(p?.author?.name || ""),
133
230
  headline: String(p?.author?.headline || ""),
134
231
  profileUrl: String(p?.author?.profileUrl || ""),
232
+ ...(followerCount ? { followerCount } : {}),
135
233
  },
136
234
  engagement: { likes, comments, shares, total },
235
+ ...(reachSignals ? { reachSignals } : {}),
137
236
  contentPreview: previewText(String(p?.content || ""), 220),
138
237
  });
139
- if (kept.length >= maxPosts)
140
- break;
141
238
  }
142
- // Sort by total engagement desc for a predictable shortlist.
143
- kept.sort((a, b) => b.engagement.total - a.engagement.total);
239
+ const hasReachTarget = Boolean(targetFollowerMin && targetFollowerMax);
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
+ });
144
249
  return {
145
250
  success: true,
146
- posts: kept,
251
+ posts: kept.slice(0, maxPosts),
147
252
  totalReturned: rawPosts.length,
148
253
  filteredOut: { tooOld, excluded, tooLowEngagement },
149
254
  };
@@ -1813,7 +1813,7 @@ export const leadToolDefinitions = [
1813
1813
  },
1814
1814
  {
1815
1815
  name: "search_prospeo",
1816
- description: 'Search Prospeo for people using filters and optional domainFilterId. Requires get_provider_prompt({ provider: "prospeo" }) first. Use Prospeo first for hiring-led targeting because it supports company_job_posting_hiring_for and company_job_posting_quantity; Sales Nav does not filter companies by hiring role. When targeting known accounts, call load_csv_domains for CSV-on-disk workflows or save_domain_filters for pasted/raw domain lists, then pass domainFilterId. For current LinkedIn job-post account-source asks, call search_harvest_jobs first, review the artifact, then confirm_harvest_job_companies to create a domainFilterId before using search_prospeo for people. For company lookalike/account asks, call search_prospeo_companies first, review accounts, then confirm_prospeo_company_accounts to create a domainFilterId before using search_prospeo for people. Raw domain inputs, LinkedIn company URLs as domains, and company-name targeting are NOT supported in this MCP tool. Strategy: start with 2-3 high-signal filters (title/seniority + industry or domainFilterId + headcount), add job-posting filters for hiring-led campaigns, then tighten one filter at a time. For security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not widen with bare seniority labels like "Head" or "Director" alone; pair them with explicit function-title keywords and inspect the sample for off-function `Head of X` leakage. If output includes warnings/fallback, report that the MCP retried a rejected precise title request with a safer domain-filter people search instead of claiming the original precise filter succeeded. Prefer person location over company HQ unless HQ is explicitly needed. `campaignOfferId` routing rule: OMIT campaignOfferId ONLY in pre-mint Phase 84 `find leads` discovery mode (validating ICP before the commit gate). In every other context — post-mint lead additions, operator-driven searches on a live campaign, any search where the intent is to persist results to a specific campaign — you MUST pass campaignOfferId so the search shows up in that campaign\'s Contact Search panel. Post-mint create-campaign-v2 watch runs MUST pass currentStep: "prospeo". Omitting campaignOfferId post-mint orphans the search from the UI. Returns normalized results with pagination.',
1816
+ description: 'Search Prospeo for people using filters and optional domainFilterId. Requires get_provider_prompt({ provider: "prospeo" }) first. Use Prospeo first for hiring-led targeting because it supports company_job_posting_hiring_for and company_job_posting_quantity; Sales Nav does not filter companies by hiring role. When targeting known accounts, call load_csv_domains for CSV-on-disk workflows or save_domain_filters for pasted/raw domain lists, then pass domainFilterId. For company lookalike/account asks, call search_prospeo_companies first, review accounts, then confirm_prospeo_company_accounts to create a domainFilterId before using search_prospeo for people. Raw domain inputs and company-name targeting are NOT supported in this MCP tool. Strategy: start with 2-3 high-signal filters (title/seniority + industry or domainFilterId + headcount), add job-posting filters for hiring-led campaigns, then tighten one filter at a time. For security, AppSec, SOC, RevOps, Demand Gen, and similar function-specific lanes, do not widen with bare seniority labels like "Head" or "Director" alone; pair them with explicit function-title keywords and inspect the sample for off-function `Head of X` leakage. If output includes warnings/fallback, report that the MCP retried a rejected precise title request with a safer domain-filter people search instead of claiming the original precise filter succeeded. Prefer person location over company HQ unless HQ is explicitly needed. `campaignOfferId` routing rule: OMIT campaignOfferId ONLY in pre-mint Phase 84 `find leads` discovery mode (validating ICP before the commit gate). In every other context — post-mint lead additions, operator-driven searches on a live campaign, any search where the intent is to persist results to a specific campaign — you MUST pass campaignOfferId so the search shows up in that campaign\'s Contact Search panel. Post-mint create-campaign-v2 watch runs MUST pass currentStep: "prospeo". Omitting campaignOfferId post-mint orphans the search from the UI. Returns normalized results with pagination.',
1817
1817
  inputSchema: {
1818
1818
  type: "object",
1819
1819
  properties: {
@@ -1260,6 +1260,19 @@ export declare const allTools: ({
1260
1260
  body?: undefined;
1261
1261
  validationReceipt?: undefined;
1262
1262
  status?: undefined;
1263
+ postText?: undefined;
1264
+ sourceLabel?: undefined;
1265
+ measurementMode?: undefined;
1266
+ requireBrowserMeasurement?: undefined;
1267
+ clampLines?: undefined;
1268
+ mobileClampLines?: undefined;
1269
+ desktopClampLines?: undefined;
1270
+ mobileTextWidthPx?: undefined;
1271
+ desktopTextWidthPx?: undefined;
1272
+ renderId?: undefined;
1273
+ renderScreenshots?: undefined;
1274
+ requireScreenshots?: undefined;
1275
+ reviewClampLines?: undefined;
1263
1276
  updatedAt?: undefined;
1264
1277
  publishUrl?: undefined;
1265
1278
  activityId?: undefined;
@@ -1295,6 +1308,7 @@ export declare const allTools: ({
1295
1308
  properties: {
1296
1309
  draftId: {
1297
1310
  type: string;
1311
+ description?: undefined;
1298
1312
  };
1299
1313
  ideaId: {
1300
1314
  type: string;
@@ -1339,6 +1353,19 @@ export declare const allTools: ({
1339
1353
  selectedPatterns?: undefined;
1340
1354
  previewBudget?: undefined;
1341
1355
  notes?: undefined;
1356
+ postText?: undefined;
1357
+ sourceLabel?: undefined;
1358
+ measurementMode?: undefined;
1359
+ requireBrowserMeasurement?: undefined;
1360
+ clampLines?: undefined;
1361
+ mobileClampLines?: undefined;
1362
+ desktopClampLines?: undefined;
1363
+ mobileTextWidthPx?: undefined;
1364
+ desktopTextWidthPx?: undefined;
1365
+ renderId?: undefined;
1366
+ renderScreenshots?: undefined;
1367
+ requireScreenshots?: undefined;
1368
+ reviewClampLines?: undefined;
1342
1369
  updatedAt?: undefined;
1343
1370
  publishUrl?: undefined;
1344
1371
  activityId?: undefined;
@@ -1361,6 +1388,7 @@ export declare const allTools: ({
1361
1388
  properties: {
1362
1389
  draftId: {
1363
1390
  type: string;
1391
+ description?: undefined;
1364
1392
  };
1365
1393
  hookResearchId: {
1366
1394
  type: string;
@@ -1403,6 +1431,19 @@ export declare const allTools: ({
1403
1431
  previewBudget?: undefined;
1404
1432
  notes?: undefined;
1405
1433
  createdAt?: undefined;
1434
+ postText?: undefined;
1435
+ sourceLabel?: undefined;
1436
+ measurementMode?: undefined;
1437
+ requireBrowserMeasurement?: undefined;
1438
+ clampLines?: undefined;
1439
+ mobileClampLines?: undefined;
1440
+ desktopClampLines?: undefined;
1441
+ mobileTextWidthPx?: undefined;
1442
+ desktopTextWidthPx?: undefined;
1443
+ renderId?: undefined;
1444
+ renderScreenshots?: undefined;
1445
+ requireScreenshots?: undefined;
1446
+ reviewClampLines?: undefined;
1406
1447
  publishUrl?: undefined;
1407
1448
  activityId?: undefined;
1408
1449
  publishedAt?: undefined;
@@ -1424,6 +1465,7 @@ export declare const allTools: ({
1424
1465
  properties: {
1425
1466
  draftId: {
1426
1467
  type: string;
1468
+ description?: undefined;
1427
1469
  };
1428
1470
  publishUrl: {
1429
1471
  type: string;
@@ -1464,6 +1506,19 @@ export declare const allTools: ({
1464
1506
  body?: undefined;
1465
1507
  validationReceipt?: undefined;
1466
1508
  status?: undefined;
1509
+ postText?: undefined;
1510
+ sourceLabel?: undefined;
1511
+ measurementMode?: undefined;
1512
+ requireBrowserMeasurement?: undefined;
1513
+ clampLines?: undefined;
1514
+ mobileClampLines?: undefined;
1515
+ desktopClampLines?: undefined;
1516
+ mobileTextWidthPx?: undefined;
1517
+ desktopTextWidthPx?: undefined;
1518
+ renderId?: undefined;
1519
+ renderScreenshots?: undefined;
1520
+ requireScreenshots?: undefined;
1521
+ reviewClampLines?: undefined;
1467
1522
  updatedAt?: undefined;
1468
1523
  publishedPostId?: undefined;
1469
1524
  year?: undefined;
@@ -1516,6 +1571,19 @@ export declare const allTools: ({
1516
1571
  body?: undefined;
1517
1572
  validationReceipt?: undefined;
1518
1573
  status?: undefined;
1574
+ postText?: undefined;
1575
+ sourceLabel?: undefined;
1576
+ measurementMode?: undefined;
1577
+ requireBrowserMeasurement?: undefined;
1578
+ clampLines?: undefined;
1579
+ mobileClampLines?: undefined;
1580
+ desktopClampLines?: undefined;
1581
+ mobileTextWidthPx?: undefined;
1582
+ desktopTextWidthPx?: undefined;
1583
+ renderId?: undefined;
1584
+ renderScreenshots?: undefined;
1585
+ requireScreenshots?: undefined;
1586
+ reviewClampLines?: undefined;
1519
1587
  updatedAt?: undefined;
1520
1588
  publishUrl?: undefined;
1521
1589
  activityId?: undefined;
@@ -1749,6 +1817,14 @@ export declare const allTools: ({
1749
1817
  };
1750
1818
  description: string;
1751
1819
  };
1820
+ targetFollowerMin: {
1821
+ type: string;
1822
+ description: string;
1823
+ };
1824
+ targetFollowerMax: {
1825
+ type: string;
1826
+ description: string;
1827
+ };
1752
1828
  };
1753
1829
  required: string[];
1754
1830
  additionalProperties: boolean;
@@ -1880,53 +1956,6 @@ export declare const allTools: ({
1880
1956
  };
1881
1957
  required: never[];
1882
1958
  };
1883
- } | {
1884
- name: string;
1885
- description: string;
1886
- inputSchema: {
1887
- type: string;
1888
- properties: {
1889
- searchToken: {
1890
- type: string;
1891
- description: string;
1892
- };
1893
- selectedJobIds: {
1894
- type: string;
1895
- items: {
1896
- type: string;
1897
- };
1898
- description: string;
1899
- };
1900
- name: {
1901
- type: string;
1902
- };
1903
- outputDir: {
1904
- type: string;
1905
- description: string;
1906
- };
1907
- fileBaseName: {
1908
- type: string;
1909
- description: string;
1910
- };
1911
- search?: undefined;
1912
- searches?: undefined;
1913
- location?: undefined;
1914
- geoId?: undefined;
1915
- postedLimit?: undefined;
1916
- sortBy?: undefined;
1917
- workplaceType?: undefined;
1918
- employmentType?: undefined;
1919
- experienceLevel?: undefined;
1920
- easyApply?: undefined;
1921
- under10Applicants?: undefined;
1922
- salary?: undefined;
1923
- pages?: undefined;
1924
- maxRows?: undefined;
1925
- artifactFormat?: undefined;
1926
- };
1927
- required: string[];
1928
- additionalProperties: boolean;
1929
- };
1930
1959
  } | {
1931
1960
  name: string;
1932
1961
  description: string;
@@ -14,7 +14,6 @@ import { engageMemoryToolDefinitions } from "./engage-memory.js";
14
14
  import { engageStateToolDefinitions } from "./engage-state.js";
15
15
  import { enrichmentToolDefinitions } from "./enrichment.js";
16
16
  import { frameworkToolDefinitions } from "./framework.js";
17
- import { harvestJobToolDefinitions } from "./harvest-jobs.js";
18
17
  import { leadToolDefinitions } from "./leads.js";
19
18
  import { linkedinToolDefinitions } from "./linkedin.js";
20
19
  import { navigationToolDefinitions } from "./navigation.js";
@@ -44,7 +43,6 @@ export const allTools = [
44
43
  ...contentPostToolDefinitions,
45
44
  ...navigationToolDefinitions,
46
45
  ...leadToolDefinitions,
47
- ...harvestJobToolDefinitions,
48
46
  ...enrichmentToolDefinitions,
49
47
  ...processingToolDefinitions,
50
48
  ...rubricToolDefinitions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.269",
3
+ "version": "0.1.271",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -29,8 +29,6 @@ allowed-tools:
29
29
  - mcp__sellable__search_prospeo
30
30
  - mcp__sellable__search_prospeo_companies
31
31
  - mcp__sellable__confirm_prospeo_company_accounts
32
- - mcp__sellable__search_harvest_jobs
33
- - mcp__sellable__confirm_harvest_job_companies
34
32
  - mcp__sellable__search_signals
35
33
  - mcp__sellable__fetch_post_engagers
36
34
  - mcp__sellable__enrich_with_prospeo
@@ -229,14 +227,6 @@ are likely. Sales Nav is useful for recent LinkedIn activity, role/title
229
227
  precision, and referral paths, but it does not provide hiring-by-role filters;
230
228
  say that distinction plainly in the source-plan gate.
231
229
 
232
- When the brief asks for current LinkedIn job-post intent, such as companies
233
- hiring Power BI developers this month, use Harvest jobs as the account source:
234
- `search_harvest_jobs -> confirm_harvest_job_companies -> search_prospeo`.
235
- First write and review the Harvest job artifact. Then confirm selected Harvest
236
- job IDs into a `domainFilterId`; Prospeo remains the people-search provider.
237
- Do not paste LinkedIn company URLs as domains. Do not fetch full job details for
238
- every search row by default; selected batches only.
239
-
240
230
  For company lookalikes, best-customer lookalikes, "companies like X",
241
231
  lookalike accounts, companies that use AI, companies with API/SSO/Chrome
242
232
  extension, news/award/integration/key-customer filters, or account discovery
@@ -7,8 +7,6 @@
7
7
  "requiredTools": [
8
8
  "update_campaign",
9
9
  "get_provider_prompt",
10
- "search_harvest_jobs",
11
- "confirm_harvest_job_companies",
12
10
  "search_prospeo_companies",
13
11
  "confirm_prospeo_company_accounts",
14
12
  "search_prospeo",
@@ -26,14 +24,13 @@
26
24
  "useWhen": [
27
25
  "You want Prospeo filters or domain-based search",
28
26
  "You need company lookalike account discovery before finding people",
29
- "You need current LinkedIn job-post evidence before finding hiring stakeholders",
30
27
  "You want companies like X, target-domain, best-customer/top-customer, or job-search employer lookalikes, or accounts using AI/API/SSO/Chrome extensions",
31
28
  "You need companies hiring for specific roles using job-posting filters",
32
29
  "You need high deliverability from Prospeo"
33
30
  ],
34
31
  "avoidWhen": ["You need LinkedIn activity filters"],
35
- "reason": "Strong for hiring-led search, current LinkedIn job-post account sourcing, company/account lookalikes, Prospeo-specific filters, verified contacts, and domain lists.",
36
- "prose": "Since you're targeting **{icp}**, I'd recommend **Prospeo**.\n\nHere's why:\n- Prospeo can discover lookalike accounts first, then turn approved accounts into a domainFilterId for people search\n- Harvest job search can source companies from current LinkedIn job posts, then confirm selected jobs into a domainFilterId for Prospeo people search\n- Prospeo can filter for companies hiring specific roles with job-posting filters\n- We can pair those company signals with buyer/referrer titles and verified-contact coverage\n\nFor current LinkedIn job-post evidence, I will use search_harvest_jobs -> confirm_harvest_job_companies -> search_prospeo. I will not paste LinkedIn company URLs as domains, and I will only fetch job details for selected batches. For lookalike accounts, I will show an account sample first; those account rows are not people leads yet. After approval, I will use the companySearchToken to confirm the accounts, then search people at the returned domainFilterId. For outbound lookalikes, I will use target/account/customer domains or verified past-customer accounts, not the sender's company. For job-search/application lookalikes, I can use current or past employers as existing-company seeds.\n\nShould I search Prospeo for {icp}?"
32
+ "reason": "Strong for hiring-led search, company/account lookalikes, Prospeo-specific filters, verified contacts, and domain lists.",
33
+ "prose": "Since you're targeting **{icp}**, I'd recommend **Prospeo**.\n\nHere's why:\n- Prospeo can discover lookalike accounts first, then turn approved accounts into a domainFilterId for people search\n- Prospeo can filter for companies hiring specific roles with job-posting filters\n- We can pair those company signals with buyer/referrer titles and verified-contact coverage\n\nFor lookalike accounts, I will show an account sample first; those account rows are not people leads yet. After approval, I will use the companySearchToken to confirm the accounts, then search people at the returned domainFilterId. For outbound lookalikes, I will use target/account/customer domains or verified past-customer accounts, not the sender's company. For job-search/application lookalikes, I can use current or past employers as existing-company seeds.\n\nShould I search Prospeo for {icp}?"
37
34
  },
38
35
  "askOption": {
39
36
  "label": "Prospeo",