@sellable/mcp 0.1.0

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.
Files changed (200) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.mcp.json +9 -0
  3. package/README.md +355 -0
  4. package/dist/api.d.ts +21 -0
  5. package/dist/api.js +73 -0
  6. package/dist/auth.d.ts +60 -0
  7. package/dist/auth.js +246 -0
  8. package/dist/engage-memory.d.ts +63 -0
  9. package/dist/engage-memory.js +354 -0
  10. package/dist/index-dev.d.ts +2 -0
  11. package/dist/index-dev.js +17 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.js +8 -0
  14. package/dist/server.d.ts +1 -0
  15. package/dist/server.js +499 -0
  16. package/dist/skills.d.ts +11 -0
  17. package/dist/skills.js +97 -0
  18. package/dist/tools/auth.d.ts +30 -0
  19. package/dist/tools/auth.js +124 -0
  20. package/dist/tools/blueprint-commit.d.ts +174 -0
  21. package/dist/tools/blueprint-commit.js +286 -0
  22. package/dist/tools/bootstrap.d.ts +64 -0
  23. package/dist/tools/bootstrap.js +246 -0
  24. package/dist/tools/campaigns.d.ts +589 -0
  25. package/dist/tools/campaigns.js +892 -0
  26. package/dist/tools/cells.d.ts +58 -0
  27. package/dist/tools/cells.js +48 -0
  28. package/dist/tools/context.d.ts +88 -0
  29. package/dist/tools/context.js +271 -0
  30. package/dist/tools/csv-domains.d.ts +73 -0
  31. package/dist/tools/csv-domains.js +464 -0
  32. package/dist/tools/csv-linkedin.d.ts +102 -0
  33. package/dist/tools/csv-linkedin.js +712 -0
  34. package/dist/tools/direct-campaigns.d.ts +240 -0
  35. package/dist/tools/direct-campaigns.js +250 -0
  36. package/dist/tools/engage-bootstrap.d.ts +94 -0
  37. package/dist/tools/engage-bootstrap.js +205 -0
  38. package/dist/tools/engage-discovery.d.ts +78 -0
  39. package/dist/tools/engage-discovery.js +150 -0
  40. package/dist/tools/engage-memory.d.ts +181 -0
  41. package/dist/tools/engage-memory.js +143 -0
  42. package/dist/tools/engage-state.d.ts +72 -0
  43. package/dist/tools/engage-state.js +62 -0
  44. package/dist/tools/enrichment.d.ts +167 -0
  45. package/dist/tools/enrichment.js +174 -0
  46. package/dist/tools/flow-preflight.d.ts +68 -0
  47. package/dist/tools/flow-preflight.js +138 -0
  48. package/dist/tools/framework.d.ts +44 -0
  49. package/dist/tools/framework.js +153 -0
  50. package/dist/tools/interaction-mode.d.ts +27 -0
  51. package/dist/tools/interaction-mode.js +102 -0
  52. package/dist/tools/leads.d.ts +2417 -0
  53. package/dist/tools/leads.js +2307 -0
  54. package/dist/tools/linkedin.d.ts +210 -0
  55. package/dist/tools/linkedin.js +229 -0
  56. package/dist/tools/navigation.d.ts +91 -0
  57. package/dist/tools/navigation.js +381 -0
  58. package/dist/tools/one-off.d.ts +229 -0
  59. package/dist/tools/one-off.js +273 -0
  60. package/dist/tools/processing.d.ts +70 -0
  61. package/dist/tools/processing.js +56 -0
  62. package/dist/tools/prompts.d.ts +211 -0
  63. package/dist/tools/prompts.js +210 -0
  64. package/dist/tools/provider-preflight.d.ts +21 -0
  65. package/dist/tools/provider-preflight.js +59 -0
  66. package/dist/tools/readiness.d.ts +261 -0
  67. package/dist/tools/readiness.js +510 -0
  68. package/dist/tools/rows.d.ts +126 -0
  69. package/dist/tools/rows.js +105 -0
  70. package/dist/tools/rubrics.d.ts +497 -0
  71. package/dist/tools/rubrics.js +681 -0
  72. package/dist/tools/senders.d.ts +44 -0
  73. package/dist/tools/senders.js +69 -0
  74. package/dist/tools/sequencer.d.ts +127 -0
  75. package/dist/tools/sequencer.js +194 -0
  76. package/dist/tools/tables.d.ts +35 -0
  77. package/dist/tools/tables.js +36 -0
  78. package/dist/tools/verify-row.d.ts +36 -0
  79. package/dist/tools/verify-row.js +38 -0
  80. package/dist/tools/workspaces.d.ts +140 -0
  81. package/dist/tools/workspaces.js +139 -0
  82. package/dist/utils/workspace-root.d.ts +1 -0
  83. package/dist/utils/workspace-root.js +39 -0
  84. package/package.json +46 -0
  85. package/skills/building-gtm-tables/SKILL.md +216 -0
  86. package/skills/building-gtm-tables/core/auto-execute.yaml +19 -0
  87. package/skills/building-gtm-tables/core/blueprint-schema.json +72 -0
  88. package/skills/building-gtm-tables/references/brief-to-blueprint.md +334 -0
  89. package/skills/building-gtm-tables/references/column-type-catalog.md +318 -0
  90. package/skills/building-gtm-tables/references/common-blueprints.fixtures.ts +199 -0
  91. package/skills/building-gtm-tables/references/common-blueprints.md +44 -0
  92. package/skills/building-gtm-tables/references/failure-taxonomy.md +197 -0
  93. package/skills/building-gtm-tables/references/uat-seed-prompts.md +37 -0
  94. package/skills/building-gtm-tables/references/verify-loop.md +74 -0
  95. package/skills/campaign-messages/SKILL.md +173 -0
  96. package/skills/campaign-messages/flow.v1.json +75 -0
  97. package/skills/craft-message/SKILL.md +401 -0
  98. package/skills/create-campaign/ARCHITECTURE.md +232 -0
  99. package/skills/create-campaign/DISCUSS.md +296 -0
  100. package/skills/create-campaign/FLOW_ASCII.md +240 -0
  101. package/skills/create-campaign/HOST-PARITY-CHECKLIST.md +49 -0
  102. package/skills/create-campaign/README.md +142 -0
  103. package/skills/create-campaign/SKILL.md +286 -0
  104. package/skills/create-campaign/context/README.md +67 -0
  105. package/skills/create-campaign/context/_TEMPLATE.md +12 -0
  106. package/skills/create-campaign/context/context.md +35 -0
  107. package/skills/create-campaign/context/learnings.md +16 -0
  108. package/skills/create-campaign/context/registry.json +19 -0
  109. package/skills/create-campaign/core/flow.v1.json +217 -0
  110. package/skills/create-campaign/core/policy.md +191 -0
  111. package/skills/create-campaign/core/providers/apollo.json +35 -0
  112. package/skills/create-campaign/core/providers/prospeo.json +34 -0
  113. package/skills/create-campaign/core/providers/registry.json +31 -0
  114. package/skills/create-campaign/core/providers/sales-nav.json +37 -0
  115. package/skills/create-campaign/core/providers/signal-discovery.json +42 -0
  116. package/skills/create-campaign/references/brief-template.md +64 -0
  117. package/skills/create-campaign/references/campaign-quality.md +84 -0
  118. package/skills/create-campaign/references/copy-calibration-examples.md +120 -0
  119. package/skills/create-campaign/references/offer-patterns.md +108 -0
  120. package/skills/create-campaign/references/provider-selection-strategy.md +212 -0
  121. package/skills/create-campaign/references/question-examples.md +167 -0
  122. package/skills/create-campaign/references/token-fill-examples.md +81 -0
  123. package/skills/create-campaign-brief/ARCHITECTURE.md +72 -0
  124. package/skills/create-campaign-brief/DISCUSS.md +64 -0
  125. package/skills/create-campaign-brief/README.md +176 -0
  126. package/skills/create-campaign-brief/SKILL.md +537 -0
  127. package/skills/create-campaign-brief/references/brief-synthesis-rules.md +100 -0
  128. package/skills/create-campaign-brief/references/brief-template.md +220 -0
  129. package/skills/create-campaign-brief/references/campaign-idea-options.md +30 -0
  130. package/skills/create-campaign-brief/references/copy-appendix-template.md +62 -0
  131. package/skills/create-campaign-brief/references/draft-lifecycle.md +23 -0
  132. package/skills/create-campaign-brief/references/examples/MANIFEST.json +89 -0
  133. package/skills/create-campaign-brief/references/examples/briefs/clover.md +223 -0
  134. package/skills/create-campaign-brief/references/examples/briefs/galley.md +222 -0
  135. package/skills/create-campaign-brief/references/examples/briefs/gelee.md +220 -0
  136. package/skills/create-campaign-brief/references/examples/briefs/hey-digital.md +234 -0
  137. package/skills/create-campaign-brief/references/examples/briefs/persona.md +231 -0
  138. package/skills/create-campaign-brief/references/examples/briefs/revvix.md +220 -0
  139. package/skills/create-campaign-brief/references/examples/briefs/sellable-dev.md +220 -0
  140. package/skills/create-campaign-brief/references/examples/briefs/superposition.md +233 -0
  141. package/skills/create-campaign-brief/references/examples/briefs/superpower.md +219 -0
  142. package/skills/create-campaign-brief/references/examples/briefs/westpark-villas.md +220 -0
  143. package/skills/create-campaign-brief/references/icp-lock-question-bank.md +43 -0
  144. package/skills/create-campaign-brief/references/messaging-inputs.md +58 -0
  145. package/skills/create-campaign-brief/references/output-acceptance-rubric.md +62 -0
  146. package/skills/create-campaign-brief/references/phase75-active-runtime-message-pack.md +248 -0
  147. package/skills/create-campaign-brief/references/phase75-canonical-brief-template.md +319 -0
  148. package/skills/create-campaign-brief/references/phase75-good-brief-and-messaging-examples.md +445 -0
  149. package/skills/create-campaign-brief/references/quick-research-protocol.md +39 -0
  150. package/skills/create-campaign-brief/references/reference-sheet-protocol.md +60 -0
  151. package/skills/create-campaign-brief/references/zero-shot-iteration-rules.md +66 -0
  152. package/skills/create-campaign-v2/SKILL.md +1619 -0
  153. package/skills/create-campaign-v2/core/auto-execute.README.md +219 -0
  154. package/skills/create-campaign-v2/core/auto-execute.yaml +121 -0
  155. package/skills/create-campaign-v2/core/flow.v2.json +1643 -0
  156. package/skills/create-campaign-v2/core/policy.md +82 -0
  157. package/skills/create-campaign-v2/references/ai-tells.md +253 -0
  158. package/skills/create-campaign-v2/references/approval-gate-framing.md +346 -0
  159. package/skills/create-campaign-v2/references/draft-lifecycle.md +110 -0
  160. package/skills/create-campaign-v2/references/escalation-ladder.md +119 -0
  161. package/skills/create-campaign-v2/references/filter-leads.md +495 -0
  162. package/skills/create-campaign-v2/references/final-handoff-contract.md +176 -0
  163. package/skills/create-campaign-v2/references/gold-standard-message-examples.md +394 -0
  164. package/skills/create-campaign-v2/references/gold-standard-message-patterns.md +314 -0
  165. package/skills/create-campaign-v2/references/gold-standard-message-validation-example.md +212 -0
  166. package/skills/create-campaign-v2/references/lead-validation-preview.md +172 -0
  167. package/skills/create-campaign-v2/references/parallel-critique-protocol.md +368 -0
  168. package/skills/create-campaign-v2/references/sample-validation-loop.md +289 -0
  169. package/skills/create-campaign-v2/references/step-13-import-leads.md +151 -0
  170. package/skills/create-campaign-v2/references/step-15-re-cascade.md +90 -0
  171. package/skills/create-campaign-v2/references/thomas-revision-filters.md +521 -0
  172. package/skills/create-campaign-v2/references/thomas-variant-selection.md +202 -0
  173. package/skills/create-campaign-v2/references/tier-routing-matrix.md +66 -0
  174. package/skills/create-campaign-v2/references/validation-criteria.md +367 -0
  175. package/skills/create-campaign-v2/references/watch-link-handoff.md +106 -0
  176. package/skills/create-campaign-v2-validation/SKILL.md +296 -0
  177. package/skills/create-post/SKILL.md +1308 -0
  178. package/skills/create-rubric/SKILL.md +251 -0
  179. package/skills/engage/SKILL.md +549 -0
  180. package/skills/engage/core/README.md +23 -0
  181. package/skills/engage/core/proven-searches.json +11 -0
  182. package/skills/engage/core/style-guide.template.md +47 -0
  183. package/skills/engage/core/tracked-people.json +10 -0
  184. package/skills/enrich-prospects/SKILL.md +97 -0
  185. package/skills/find-leads/SKILL.md +467 -0
  186. package/skills/generate-messages/SKILL.md +2361 -0
  187. package/skills/interview/SKILL.md +132 -0
  188. package/skills/interview/core/ENGAGE_STYLE_GUIDE.template.md +54 -0
  189. package/skills/interview/core/ICP.template.md +54 -0
  190. package/skills/interview/core/VOICE_PROFILE.template.md +101 -0
  191. package/skills/providers/apollo.md +520 -0
  192. package/skills/providers/prospeo.md +398 -0
  193. package/skills/providers/sales-nav.md +372 -0
  194. package/skills/providers/signal-discovery.md +495 -0
  195. package/skills/research/SKILL.md +258 -0
  196. package/skills/research/config.json +9 -0
  197. package/skills/research/override.md +13 -0
  198. package/skills/research-prospect/SKILL.md +99 -0
  199. package/skills/research-sender/SKILL.md +158 -0
  200. package/skills/workflow-sequences/SKILL.md +85 -0
@@ -0,0 +1,205 @@
1
+ import { getEngageState, updateEngageState } from "../auth.js";
2
+ import { senderHasConfig } from "../engage-memory.js";
3
+ import { getAuthStatus } from "./auth.js";
4
+ import { getEngagedPosts, getOrCreateDirectCampaignTable, } from "./direct-campaigns.js";
5
+ import { listSenders } from "./senders.js";
6
+ // ── Tool definitions ──────────────────────────────────────────────────────
7
+ export const engageBootstrapToolDefinitions = [
8
+ {
9
+ name: "bootstrap_engage",
10
+ description: "Engage preflight: verify auth/workspace, resolve sender (workspace-first), ensure comment campaign table exists, fetch engaged posts for dedupe, and persist defaults. Minimizes back-and-forth before discovery. IMPORTANT: After auth succeeds, ALWAYS tell the user which workspace is active (from authStatus._userNotice).",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ senderId: {
15
+ type: "string",
16
+ description: "Optional senderId override. If omitted, uses lastSenderId for the active workspace when available, otherwise auto-selects only sender or requests a choice.",
17
+ },
18
+ },
19
+ required: [],
20
+ additionalProperties: false,
21
+ },
22
+ },
23
+ {
24
+ name: "bootstrap_engage_multi",
25
+ description: "Multi-sender engage preflight: resolve multiple senders in parallel, ensure each has a comment campaign table, fetch engaged posts for all, and return a globalEngagedPostUrls union for cross-sender dedup. Use for multi-sender engage sessions (autonomous or interactive).",
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ senderIds: {
30
+ type: "array",
31
+ items: { type: "string" },
32
+ description: "Specific sender IDs to bootstrap. Mutually exclusive with allSenders.",
33
+ },
34
+ allSenders: {
35
+ type: "boolean",
36
+ description: "If true, bootstrap ALL senders in the workspace. Mutually exclusive with senderIds.",
37
+ },
38
+ },
39
+ required: [],
40
+ additionalProperties: false,
41
+ },
42
+ },
43
+ ];
44
+ function senderSummary(s) {
45
+ return {
46
+ id: String(s?.id || ""),
47
+ displayName: String(s?.displayName || s?.id || ""),
48
+ linkedinProfileUrl: s?.linkedinProfileUrl ?? null,
49
+ };
50
+ }
51
+ export async function bootstrapEngage(input = {}) {
52
+ const authStatus = await getAuthStatus();
53
+ if (!authStatus.ok) {
54
+ return { ok: false, authStatus };
55
+ }
56
+ const engageState = getEngageState();
57
+ const senderList = await listSenders();
58
+ const senders = (senderList.senders || []).map(senderSummary);
59
+ if (senders.length === 0) {
60
+ return {
61
+ ok: true,
62
+ authStatus,
63
+ needsUserChoice: true,
64
+ reason: "no_senders",
65
+ senders,
66
+ addSenderPath: "/outbound/senders",
67
+ };
68
+ }
69
+ const overrideSenderId = input.senderId?.trim() || null;
70
+ const preferredSenderId = overrideSenderId || engageState.state?.lastSenderId || null;
71
+ const preferred = preferredSenderId && senders.find((s) => s.id === preferredSenderId);
72
+ let selectedSender = preferred || null;
73
+ if (!selectedSender) {
74
+ if (overrideSenderId) {
75
+ return {
76
+ ok: true,
77
+ authStatus,
78
+ needsUserChoice: true,
79
+ reason: "unknown_sender",
80
+ senders,
81
+ addSenderPath: "/outbound/senders",
82
+ };
83
+ }
84
+ if (senders.length === 1) {
85
+ selectedSender = senders[0] || null;
86
+ }
87
+ else {
88
+ return {
89
+ ok: true,
90
+ authStatus,
91
+ needsUserChoice: true,
92
+ reason: "multiple_senders",
93
+ senders,
94
+ addSenderPath: "/outbound/senders",
95
+ };
96
+ }
97
+ }
98
+ const table = await getOrCreateDirectCampaignTable({
99
+ senderId: selectedSender.id,
100
+ type: "comment_campaign",
101
+ });
102
+ const engaged = await getEngagedPosts({ senderId: selectedSender.id });
103
+ const engagedPostUrls = Array.isArray(engaged?.postUrls)
104
+ ? engaged.postUrls
105
+ : [];
106
+ updateEngageState({
107
+ lastSenderId: selectedSender.id,
108
+ lastCommentCampaignTableId: table.tableId,
109
+ });
110
+ return {
111
+ ok: true,
112
+ authStatus,
113
+ needsUserChoice: false,
114
+ selected: {
115
+ senderId: selectedSender.id,
116
+ senderDisplayName: selectedSender.displayName,
117
+ senderLinkedinProfileUrl: selectedSender.linkedinProfileUrl,
118
+ commentCampaignTableId: table.tableId,
119
+ tableCreated: Boolean(table.created),
120
+ engagedPostUrls,
121
+ },
122
+ };
123
+ }
124
+ export async function bootstrapEngageMulti(input = {}) {
125
+ const authStatus = await getAuthStatus();
126
+ if (!authStatus.ok) {
127
+ return { ok: false, error: "Auth failed. Check token and workspace." };
128
+ }
129
+ const senderList = await listSenders();
130
+ const allSenders = (senderList.senders || []).map(senderSummary);
131
+ if (allSenders.length === 0) {
132
+ return {
133
+ ok: false,
134
+ error: "No senders in workspace. Add one at https://app.sellable.dev/linkedin-accounts",
135
+ };
136
+ }
137
+ // Determine which senders to bootstrap
138
+ let targetSenders;
139
+ if (input.allSenders) {
140
+ targetSenders = allSenders;
141
+ }
142
+ else if (input.senderIds && input.senderIds.length > 0) {
143
+ targetSenders = [];
144
+ for (const id of input.senderIds) {
145
+ const found = allSenders.find((s) => s.id === id);
146
+ if (!found) {
147
+ return {
148
+ ok: false,
149
+ error: `Unknown senderId: ${id}. Available: ${allSenders.map((s) => `${s.displayName} (${s.id})`).join(", ")}`,
150
+ };
151
+ }
152
+ targetSenders.push(found);
153
+ }
154
+ }
155
+ else {
156
+ // Default: use all senders
157
+ targetSenders = allSenders;
158
+ }
159
+ // Bootstrap each sender in parallel
160
+ const senderResults = await Promise.all(targetSenders.map(async (sender) => {
161
+ const [table, engaged] = await Promise.all([
162
+ getOrCreateDirectCampaignTable({
163
+ senderId: sender.id,
164
+ type: "comment_campaign",
165
+ }),
166
+ getEngagedPosts({ senderId: sender.id }),
167
+ ]);
168
+ const engagedPostUrls = Array.isArray(engaged?.postUrls)
169
+ ? engaged.postUrls
170
+ : [];
171
+ return {
172
+ senderId: sender.id,
173
+ senderDisplayName: sender.displayName,
174
+ senderLinkedinProfileUrl: sender.linkedinProfileUrl,
175
+ commentCampaignTableId: table.tableId,
176
+ tableCreated: Boolean(table.created),
177
+ engagedPostUrls,
178
+ hasConfig: senderHasConfig(sender.id),
179
+ };
180
+ }));
181
+ // Build global engaged post URLs (union of all senders)
182
+ const globalSet = new Set();
183
+ for (const sr of senderResults) {
184
+ for (const url of sr.engagedPostUrls) {
185
+ globalSet.add(url);
186
+ }
187
+ }
188
+ // Persist multi-sender state
189
+ const tableIdMap = {};
190
+ for (const sr of senderResults) {
191
+ tableIdMap[sr.senderId] = sr.commentCampaignTableId;
192
+ }
193
+ updateEngageState({
194
+ lastSenderIds: senderResults.map((sr) => sr.senderId),
195
+ lastCommentCampaignTableIds: tableIdMap,
196
+ // Also set lastSenderId to first sender for backward compat
197
+ lastSenderId: senderResults[0]?.senderId,
198
+ lastCommentCampaignTableId: senderResults[0]?.commentCampaignTableId,
199
+ });
200
+ return {
201
+ ok: true,
202
+ senders: senderResults,
203
+ globalEngagedPostUrls: Array.from(globalSet),
204
+ };
205
+ }
@@ -0,0 +1,78 @@
1
+ export type EngagementPost = {
2
+ postId: string;
3
+ url: string;
4
+ postedAt: string | null;
5
+ matchedKeyword: string | null;
6
+ author: {
7
+ name: string;
8
+ headline: string;
9
+ profileUrl: string;
10
+ };
11
+ engagement: {
12
+ likes: number;
13
+ comments: number;
14
+ shares: number;
15
+ total: number;
16
+ };
17
+ contentPreview: string;
18
+ };
19
+ export type SearchEngagementPostsInput = {
20
+ keywords: string[];
21
+ page?: number;
22
+ maxAgeDays?: number;
23
+ minTotalEngagement?: number;
24
+ maxPosts?: number;
25
+ excludePostUrls?: string[];
26
+ };
27
+ export type SearchEngagementPostsResponse = {
28
+ success: boolean;
29
+ posts: EngagementPost[];
30
+ totalReturned: number;
31
+ filteredOut: {
32
+ tooOld: number;
33
+ excluded: number;
34
+ tooLowEngagement: number;
35
+ };
36
+ };
37
+ export declare const engageDiscoveryToolDefinitions: {
38
+ name: string;
39
+ description: string;
40
+ inputSchema: {
41
+ type: string;
42
+ properties: {
43
+ keywords: {
44
+ type: string;
45
+ items: {
46
+ type: string;
47
+ };
48
+ description: string;
49
+ };
50
+ page: {
51
+ type: string;
52
+ description: string;
53
+ };
54
+ maxAgeDays: {
55
+ type: string;
56
+ description: string;
57
+ };
58
+ minTotalEngagement: {
59
+ type: string;
60
+ description: string;
61
+ };
62
+ maxPosts: {
63
+ type: string;
64
+ description: string;
65
+ };
66
+ excludePostUrls: {
67
+ type: string;
68
+ items: {
69
+ type: string;
70
+ };
71
+ description: string;
72
+ };
73
+ };
74
+ required: string[];
75
+ additionalProperties: boolean;
76
+ };
77
+ }[];
78
+ export declare function searchEngagementPosts(input: SearchEngagementPostsInput): Promise<SearchEngagementPostsResponse>;
@@ -0,0 +1,150 @@
1
+ import { getApi } from "../api.js";
2
+ export const engageDiscoveryToolDefinitions = [
3
+ {
4
+ name: "search_engagement_posts",
5
+ description: "Search for high-signal LinkedIn posts by keyword for engagement/comment drafting. Designed for the engage skill (no campaignOfferId required). Filters by recency and engagement, and can exclude already-engaged URLs.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ keywords: {
10
+ type: "array",
11
+ items: { type: "string" },
12
+ description: "Search keywords (max 5 recommended).",
13
+ },
14
+ page: {
15
+ type: "number",
16
+ description: "Page number (default 1).",
17
+ },
18
+ maxAgeDays: {
19
+ type: "number",
20
+ description: "Max post age in days (default 5).",
21
+ },
22
+ minTotalEngagement: {
23
+ type: "number",
24
+ description: "Minimum total engagement (likes+comments+shares) to keep (default 30).",
25
+ },
26
+ maxPosts: {
27
+ type: "number",
28
+ description: "Max posts to return after filtering (default 25).",
29
+ },
30
+ excludePostUrls: {
31
+ type: "array",
32
+ items: { type: "string" },
33
+ description: "Optional list of post URLs to exclude (e.g. already engaged/scheduled).",
34
+ },
35
+ },
36
+ required: ["keywords"],
37
+ additionalProperties: false,
38
+ },
39
+ },
40
+ ];
41
+ function normalizePostUrl(url) {
42
+ if (!url)
43
+ return null;
44
+ try {
45
+ const parsed = new URL(url);
46
+ parsed.hash = "";
47
+ parsed.search = "";
48
+ return `${parsed.origin}${parsed.pathname}`.toLowerCase();
49
+ }
50
+ catch {
51
+ return url.trim().toLowerCase();
52
+ }
53
+ }
54
+ function previewText(text, max) {
55
+ const cleaned = String(text || "")
56
+ .replace(/\s+/g, " ")
57
+ .trim();
58
+ if (cleaned.length <= max)
59
+ return cleaned;
60
+ return `${cleaned.slice(0, max - 1)}…`;
61
+ }
62
+ function safeNumber(value) {
63
+ const n = typeof value === "number" ? value : Number(value);
64
+ return Number.isFinite(n) ? n : 0;
65
+ }
66
+ export async function searchEngagementPosts(input) {
67
+ const api = getApi();
68
+ const keywords = (input.keywords || [])
69
+ .map((k) => String(k || "").trim())
70
+ .filter(Boolean)
71
+ .slice(0, 5);
72
+ const page = input.page && input.page > 0 ? input.page : 1;
73
+ const maxAgeDays = typeof input.maxAgeDays === "number" && input.maxAgeDays > 0
74
+ ? input.maxAgeDays
75
+ : 5;
76
+ const minTotalEngagement = typeof input.minTotalEngagement === "number"
77
+ ? input.minTotalEngagement
78
+ : 30;
79
+ const maxPosts = typeof input.maxPosts === "number" && input.maxPosts > 0
80
+ ? Math.min(50, input.maxPosts)
81
+ : 25;
82
+ const exclude = new Set((input.excludePostUrls || [])
83
+ .map((u) => normalizePostUrl(u))
84
+ .filter(Boolean));
85
+ const response = await api.post("/api/v1/signal-discovery/search-signals", {
86
+ type: "keywords",
87
+ keywords: keywords.map((keyword) => ({ keyword })),
88
+ page,
89
+ });
90
+ const rawPosts = Array.isArray(response?.topPostsForLLM)
91
+ ? response.topPostsForLLM
92
+ : Array.isArray(response?.posts)
93
+ ? response.posts
94
+ : [];
95
+ const now = Date.now();
96
+ const oldestMs = now - maxAgeDays * 24 * 60 * 60 * 1000;
97
+ let tooOld = 0;
98
+ let excluded = 0;
99
+ let tooLowEngagement = 0;
100
+ const kept = [];
101
+ for (const p of rawPosts) {
102
+ const url = String(p?.url || "").trim();
103
+ if (!url)
104
+ continue;
105
+ const norm = normalizePostUrl(url);
106
+ if (norm && exclude.has(norm)) {
107
+ excluded += 1;
108
+ continue;
109
+ }
110
+ const likes = safeNumber(p?.engagement?.likes);
111
+ const comments = safeNumber(p?.engagement?.comments);
112
+ const shares = safeNumber(p?.engagement?.shares);
113
+ const total = likes + comments + shares;
114
+ if (total < minTotalEngagement) {
115
+ tooLowEngagement += 1;
116
+ continue;
117
+ }
118
+ const postedAtRaw = p?.postedAt ? String(p.postedAt) : "";
119
+ const postedMs = postedAtRaw ? Date.parse(postedAtRaw) : NaN;
120
+ if (Number.isFinite(postedMs) && postedMs < oldestMs) {
121
+ tooOld += 1;
122
+ continue;
123
+ }
124
+ kept.push({
125
+ postId: String(p?.id || ""),
126
+ url,
127
+ postedAt: Number.isFinite(postedMs)
128
+ ? new Date(postedMs).toISOString()
129
+ : null,
130
+ matchedKeyword: p?.matchedKeyword ? String(p.matchedKeyword) : null,
131
+ author: {
132
+ name: String(p?.author?.name || ""),
133
+ headline: String(p?.author?.headline || ""),
134
+ profileUrl: String(p?.author?.profileUrl || ""),
135
+ },
136
+ engagement: { likes, comments, shares, total },
137
+ contentPreview: previewText(String(p?.content || ""), 220),
138
+ });
139
+ if (kept.length >= maxPosts)
140
+ break;
141
+ }
142
+ // Sort by total engagement desc for a predictable shortlist.
143
+ kept.sort((a, b) => b.engagement.total - a.engagement.total);
144
+ return {
145
+ success: true,
146
+ posts: kept,
147
+ totalReturned: rawPosts.length,
148
+ filteredOut: { tooOld, excluded, tooLowEngagement },
149
+ };
150
+ }
@@ -0,0 +1,181 @@
1
+ export declare const engageMemoryToolDefinitions: ({
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: string;
6
+ properties: {
7
+ markdown: {
8
+ type: string;
9
+ description: string;
10
+ };
11
+ senderId: {
12
+ type: string;
13
+ description: string;
14
+ };
15
+ keyword?: undefined;
16
+ totalReturned?: undefined;
17
+ postsYielded?: undefined;
18
+ name?: undefined;
19
+ linkedinUrl?: undefined;
20
+ reason?: undefined;
21
+ fromSenderId?: undefined;
22
+ toSenderId?: undefined;
23
+ };
24
+ required: string[];
25
+ additionalProperties: boolean;
26
+ };
27
+ } | {
28
+ name: string;
29
+ description: string;
30
+ inputSchema: {
31
+ type: string;
32
+ properties: {
33
+ keyword: {
34
+ type: string;
35
+ description: string;
36
+ };
37
+ totalReturned: {
38
+ type: string;
39
+ description: string;
40
+ };
41
+ postsYielded: {
42
+ type: string;
43
+ description: string;
44
+ };
45
+ senderId: {
46
+ type: string;
47
+ description: string;
48
+ };
49
+ markdown?: undefined;
50
+ name?: undefined;
51
+ linkedinUrl?: undefined;
52
+ reason?: undefined;
53
+ fromSenderId?: undefined;
54
+ toSenderId?: undefined;
55
+ };
56
+ required: string[];
57
+ additionalProperties: boolean;
58
+ };
59
+ } | {
60
+ name: string;
61
+ description: string;
62
+ inputSchema: {
63
+ type: string;
64
+ properties: {
65
+ name: {
66
+ type: string;
67
+ description: string;
68
+ };
69
+ linkedinUrl: {
70
+ type: string;
71
+ description: string;
72
+ };
73
+ reason: {
74
+ type: string;
75
+ description: string;
76
+ };
77
+ senderId: {
78
+ type: string;
79
+ description: string;
80
+ };
81
+ markdown?: undefined;
82
+ keyword?: undefined;
83
+ totalReturned?: undefined;
84
+ postsYielded?: undefined;
85
+ fromSenderId?: undefined;
86
+ toSenderId?: undefined;
87
+ };
88
+ required: string[];
89
+ additionalProperties: boolean;
90
+ };
91
+ } | {
92
+ name: string;
93
+ description: string;
94
+ inputSchema: {
95
+ type: string;
96
+ properties: {
97
+ fromSenderId: {
98
+ type: string;
99
+ description: string;
100
+ };
101
+ toSenderId: {
102
+ type: string;
103
+ description: string;
104
+ };
105
+ markdown?: undefined;
106
+ senderId?: undefined;
107
+ keyword?: undefined;
108
+ totalReturned?: undefined;
109
+ postsYielded?: undefined;
110
+ name?: undefined;
111
+ linkedinUrl?: undefined;
112
+ reason?: undefined;
113
+ };
114
+ required: string[];
115
+ additionalProperties: boolean;
116
+ };
117
+ } | {
118
+ name: string;
119
+ description: string;
120
+ inputSchema: {
121
+ type: string;
122
+ properties: {
123
+ senderId: {
124
+ type: string;
125
+ description: string;
126
+ };
127
+ markdown?: undefined;
128
+ keyword?: undefined;
129
+ totalReturned?: undefined;
130
+ postsYielded?: undefined;
131
+ name?: undefined;
132
+ linkedinUrl?: undefined;
133
+ reason?: undefined;
134
+ fromSenderId?: undefined;
135
+ toSenderId?: undefined;
136
+ };
137
+ required: string[];
138
+ additionalProperties: boolean;
139
+ };
140
+ })[];
141
+ export declare function getEngageMemoryTool(input?: {
142
+ senderId?: string;
143
+ }): {
144
+ memory: import("../engage-memory.js").EngageMemory;
145
+ };
146
+ export declare function setEngageStyleGuideTool(input: {
147
+ markdown: string;
148
+ senderId?: string;
149
+ }): {
150
+ styleGuide: import("../engage-memory.js").EngageStyleGuide;
151
+ };
152
+ export declare function recordEngageProvenSearchTool(input: {
153
+ keyword: string;
154
+ totalReturned: number;
155
+ postsYielded: number;
156
+ senderId?: string;
157
+ }): {
158
+ search: import("../engage-memory.js").ProvenSearch;
159
+ };
160
+ export declare function upsertEngageTrackedPersonTool(input: {
161
+ name: string;
162
+ linkedinUrl: string;
163
+ reason: string;
164
+ senderId?: string;
165
+ }): {
166
+ person: import("../engage-memory.js").TrackedPerson;
167
+ created: boolean;
168
+ };
169
+ export declare function copySenderConfigTool(input: {
170
+ fromSenderId: string;
171
+ toSenderId: string;
172
+ }): {
173
+ copied: string[];
174
+ skipped: string[];
175
+ };
176
+ export declare function migrateFlatConfigsTool(input: {
177
+ senderId: string;
178
+ }): {
179
+ migrated: string[];
180
+ skipped: string[];
181
+ };