@sellable/mcp 0.1.190 → 0.1.191

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,304 +0,0 @@
1
- import { getApi } from "../api.js";
2
- import { resolveWaitTimeout } from "./readiness.js";
3
- const DEFAULT_TIMEOUT_MS = 90000;
4
- const DEFAULT_INTERVAL_MS = 2000;
5
- function sleep(ms) {
6
- return new Promise((resolve) => setTimeout(resolve, ms));
7
- }
8
- function stableHash(value) {
9
- const text = JSON.stringify(value, Object.keys(value).sort());
10
- let hash = 2166136261;
11
- for (let index = 0; index < text.length; index += 1) {
12
- hash ^= text.charCodeAt(index);
13
- hash = Math.imul(hash, 16777619);
14
- }
15
- return (hash >>> 0).toString(16).padStart(8, "0");
16
- }
17
- async function postCampaignProcessing(body) {
18
- const api = getApi();
19
- return api.post("/api/v3/mcp/campaign-processing", body);
20
- }
21
- async function resolveSchema(input) {
22
- return postCampaignProcessing({
23
- action: "schema",
24
- campaignId: input.campaignId,
25
- tableId: input.tableId,
26
- });
27
- }
28
- export const campaignProcessingToolDefinitions = [
29
- {
30
- name: "get_campaign_table_schema",
31
- description: "Return compact semantic campaign-table schema: column roles, row count, review-batch metadata, and current approved brief hash. Use for debugging or recovery, not normal queueing.",
32
- inputSchema: {
33
- type: "object",
34
- properties: {
35
- campaignId: { type: "string" },
36
- tableId: { type: "string" },
37
- },
38
- additionalProperties: false,
39
- },
40
- },
41
- {
42
- name: "select_campaign_cells",
43
- description: "Dry-run semantic campaign-cell selection. Returns compact counts and a tiny sample, never bulky row data. Use before queue_campaign_cells only when debugging or explaining a recovery action.",
44
- inputSchema: {
45
- type: "object",
46
- properties: {
47
- campaignId: { type: "string" },
48
- tableId: { type: "string" },
49
- columnRole: {
50
- type: "string",
51
- enum: [
52
- "enrich",
53
- "passesRubric",
54
- "icpScore",
55
- "generateMessage",
56
- "approved",
57
- ],
58
- },
59
- rowSelector: {
60
- type: "object",
61
- properties: {
62
- type: {
63
- type: "string",
64
- enum: [
65
- "reviewBatch",
66
- "rowIds",
67
- "passedRows",
68
- "needsGeneratedMessage",
69
- "staleGeneratedMessages",
70
- ],
71
- },
72
- basisHash: { type: "string" },
73
- rowIds: { type: "array", items: { type: "string" } },
74
- limit: { type: "number" },
75
- },
76
- required: ["type"],
77
- additionalProperties: false,
78
- },
79
- limit: { type: "number" },
80
- },
81
- required: ["columnRole", "rowSelector"],
82
- additionalProperties: false,
83
- },
84
- },
85
- {
86
- name: "queue_campaign_cells",
87
- description: "Resolve and queue campaign cells by semantic role and row selector. Normal create-campaign tail should use this instead of fetching rows only to discover cell IDs. Use forceRerun:true for message-template revisions.",
88
- inputSchema: {
89
- type: "object",
90
- properties: {
91
- campaignId: { type: "string" },
92
- tableId: { type: "string" },
93
- columnRole: {
94
- type: "string",
95
- enum: ["enrich", "generateMessage", "approved"],
96
- },
97
- rowSelector: {
98
- type: "object",
99
- properties: {
100
- type: {
101
- type: "string",
102
- enum: [
103
- "reviewBatch",
104
- "rowIds",
105
- "passedRows",
106
- "needsGeneratedMessage",
107
- "staleGeneratedMessages",
108
- ],
109
- },
110
- basisHash: { type: "string" },
111
- rowIds: { type: "array", items: { type: "string" } },
112
- limit: { type: "number" },
113
- },
114
- required: ["type"],
115
- additionalProperties: false,
116
- },
117
- forceRerun: { type: "boolean" },
118
- limit: { type: "number" },
119
- reason: { type: "string" },
120
- },
121
- required: ["columnRole", "rowSelector"],
122
- additionalProperties: false,
123
- },
124
- },
125
- {
126
- name: "wait_for_campaign_processing",
127
- description: "Stats-only campaign processing wait. Use for fit/message readiness instead of wait_for_rubric_results when waiting on campaign-table processing. Generated messages are counted separately from rubric passes; current template revision is enforced through messageDraftBasis.approvedBriefHash.",
128
- inputSchema: {
129
- type: "object",
130
- properties: {
131
- campaignId: { type: "string" },
132
- tableId: { type: "string" },
133
- minPassedCount: { type: "number" },
134
- minGeneratedMessages: { type: "number" },
135
- templateRevision: {
136
- type: "string",
137
- description: '"current" (default when minGeneratedMessages is set) or an explicit current approved brief hash.',
138
- },
139
- timeoutMs: { type: "number" },
140
- intervalMs: { type: "number" },
141
- },
142
- additionalProperties: false,
143
- },
144
- },
145
- {
146
- name: "revise_message_template_and_rerun",
147
- description: "Update the approved message template in the campaign brief, mark prior generated messages stale, reset unsent approvals, and force-rerun Generate Message cells. Does not directly overwrite row message cells.",
148
- inputSchema: {
149
- type: "object",
150
- properties: {
151
- campaignId: { type: "string" },
152
- tableId: { type: "string" },
153
- templateMarkdown: { type: "string" },
154
- approvedMessageTemplate: { type: "string" },
155
- rowSelector: {
156
- type: "object",
157
- properties: {
158
- type: {
159
- type: "string",
160
- enum: [
161
- "passedRows",
162
- "needsGeneratedMessage",
163
- "staleGeneratedMessages",
164
- "rowIds",
165
- ],
166
- },
167
- rowIds: { type: "array", items: { type: "string" } },
168
- limit: { type: "number" },
169
- },
170
- required: ["type"],
171
- additionalProperties: false,
172
- },
173
- limit: { type: "number" },
174
- },
175
- required: ["campaignId"],
176
- additionalProperties: false,
177
- },
178
- },
179
- ];
180
- export async function getCampaignTableSchema(input) {
181
- return resolveSchema(input);
182
- }
183
- export async function selectCampaignCells(input) {
184
- return postCampaignProcessing({
185
- action: "select",
186
- ...input,
187
- });
188
- }
189
- export async function queueCampaignCells(input) {
190
- return postCampaignProcessing({
191
- action: "queue",
192
- ...input,
193
- });
194
- }
195
- export async function reviseMessageTemplateAndRerun(input) {
196
- return postCampaignProcessing({
197
- action: "reviseTemplateAndRerun",
198
- ...input,
199
- });
200
- }
201
- export async function waitForCampaignProcessing(input) {
202
- const api = getApi();
203
- const schema = await resolveSchema(input);
204
- const { effectiveTimeoutMs, guardApplied, requestedTimeoutMs } = resolveWaitTimeout(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
205
- const intervalMs = Math.max(500, input.intervalMs ?? DEFAULT_INTERVAL_MS);
206
- const minPassedCount = typeof input.minPassedCount === "number" &&
207
- Number.isFinite(input.minPassedCount)
208
- ? Math.max(1, Math.floor(input.minPassedCount))
209
- : null;
210
- const minGeneratedMessages = typeof input.minGeneratedMessages === "number" &&
211
- Number.isFinite(input.minGeneratedMessages)
212
- ? Math.max(1, Math.floor(input.minGeneratedMessages))
213
- : null;
214
- const templateRevision = input.templateRevision ?? (minGeneratedMessages !== null ? "current" : null);
215
- const pollKey = stableHash({
216
- tableId: schema.tableId,
217
- minPassedCount,
218
- minGeneratedMessages,
219
- templateRevision,
220
- });
221
- const start = Date.now();
222
- let attempts = 0;
223
- let lastStats = null;
224
- while (Date.now() - start <= effectiveTimeoutMs) {
225
- attempts += 1;
226
- const stats = await api.get(`/api/v3/workflow-tables/${schema.tableId}/stats`);
227
- lastStats = stats;
228
- const currentRevisionHash = stats.currentTemplateRevisionHash ?? null;
229
- if (templateRevision &&
230
- templateRevision !== "current" &&
231
- currentRevisionHash &&
232
- templateRevision !== currentRevisionHash) {
233
- return {
234
- ready: false,
235
- reason: "template_revision_mismatch",
236
- attempts,
237
- elapsedMs: Date.now() - start,
238
- tableId: schema.tableId,
239
- pollKey,
240
- requestedTemplateRevision: templateRevision,
241
- currentTemplateRevisionHash: currentRevisionHash,
242
- guidance: "The requested templateRevision is not current. Re-read campaign state or wait using templateRevision:'current'.",
243
- stats,
244
- };
245
- }
246
- const passed = stats.passRate?.passed ?? 0;
247
- const generated = templateRevision === "current" || templateRevision !== null
248
- ? (stats.currentRevisionGeneratedMessagesCount ?? 0)
249
- : (stats.generatedMessagesCount ?? stats.messagesCount ?? 0);
250
- const passFloorMet = minPassedCount === null || passed >= minPassedCount;
251
- const generatedFloorMet = minGeneratedMessages === null || generated >= minGeneratedMessages;
252
- if (passFloorMet && generatedFloorMet) {
253
- return {
254
- ready: true,
255
- attempts,
256
- elapsedMs: Date.now() - start,
257
- tableId: schema.tableId,
258
- pollKey,
259
- floors: {
260
- minPassedCount,
261
- minGeneratedMessages,
262
- templateRevision,
263
- },
264
- result: {
265
- passedCount: passed,
266
- generatedMessagesCount: stats.generatedMessagesCount ?? stats.messagesCount ?? 0,
267
- currentRevisionGeneratedMessagesCount: stats.currentRevisionGeneratedMessagesCount ?? 0,
268
- staleGeneratedMessagesCount: stats.staleGeneratedMessagesCount ?? 0,
269
- generatedWithoutPassCount: stats.generatedWithoutPassCount ?? 0,
270
- currentTemplateRevisionHash: currentRevisionHash,
271
- },
272
- stats,
273
- };
274
- }
275
- await sleep(intervalMs);
276
- }
277
- const passed = lastStats?.passRate?.passed ?? 0;
278
- return {
279
- ready: false,
280
- reason: guardApplied ? "tool_timeout_guard" : "timeout",
281
- attempts,
282
- elapsedMs: Date.now() - start,
283
- tableId: schema.tableId,
284
- pollKey,
285
- warning: guardApplied
286
- ? `Stopped after ${effectiveTimeoutMs}ms to avoid host tool timeout (requested ${requestedTimeoutMs}ms).`
287
- : undefined,
288
- partialResult: {
289
- passedCount: passed,
290
- generatedMessagesCount: lastStats?.generatedMessagesCount ?? lastStats?.messagesCount ?? 0,
291
- currentRevisionGeneratedMessagesCount: lastStats?.currentRevisionGeneratedMessagesCount ?? 0,
292
- staleGeneratedMessagesCount: lastStats?.staleGeneratedMessagesCount ?? 0,
293
- generatedWithoutPassCount: lastStats?.generatedWithoutPassCount ?? 0,
294
- currentTemplateRevisionHash: lastStats?.currentTemplateRevisionHash ??
295
- schema.currentApprovedBriefHash,
296
- passFloorMet: minPassedCount === null || passed >= minPassedCount,
297
- generatedFloorMet: minGeneratedMessages === null ||
298
- (lastStats?.currentRevisionGeneratedMessagesCount ?? 0) >=
299
- minGeneratedMessages,
300
- },
301
- guidance: "Do not repoll with identical arguments after this timeout. Use the partial diagnostics to revise filters/messages, queue missing work, or surface the blocked sample state.",
302
- stats: lastStats,
303
- };
304
- }
@@ -1,100 +0,0 @@
1
- ---
2
- name: generate-messages-compact
3
- description: Compact required Message Draft Builder prompt for create-campaign-v2; load lazy references only for examples, critique, or revision.
4
- visibility: internal
5
- ---
6
-
7
- # Generate Messages Compact
8
-
9
- Use this prompt for create-campaign-v2 Message Draft Builder. It replaces the
10
- old normal-path requirement to load the full long-form `generate-messages`
11
- prompt before writing a reusable template. The full prompt remains available for
12
- legacy dry-mode validation and deep debugging.
13
-
14
- ## Mode
15
-
16
- Draft one reusable first-message template for a live campaign after:
17
-
18
- - `confirm_lead_list` copied a non-empty source into the campaign table
19
- - the first review batch exists in campaign state
20
- - the parent recorded the filter choice
21
-
22
- This is not the product Generate Message cell path. Do not write row messages,
23
- queue cells, update the campaign brief, attach sequence, or launch.
24
-
25
- ## Source Of Truth
26
-
27
- Use only live campaign inputs supplied by the parent or scoped MCP tools:
28
-
29
- - `campaignId`
30
- - campaign revision or `updatedAt`
31
- - campaign brief content and brief hash
32
- - selected source decision and `selectedLeadListId`
33
- - `workflowTableId`
34
- - review-batch row ids/hash and compact sample rows
35
- - filter choice and saved rubric summary when available
36
- - explicit user/operator message direction
37
-
38
- Reject as `blocked` or `stale` if campaign id, selected list, table id, brief
39
- hash, or review-batch basis do not match. Do not reconstruct state from local
40
- markdown/json files, database reads, or stale tool output.
41
-
42
- ## Required Output
43
-
44
- Return compact structured output to the parent:
45
-
46
- - `templateRecommendation`: tokenized first-send template
47
- - `tokenFillRules`: safe fill rules, fallbacks, and unsupported fills
48
- - `renderedSample`: one good passing-row example
49
- - `omitSample`: one example where weak personalization is omitted
50
- - `concerns`: quality risks or blockers
51
- - `status`: `ready`, `blocked`, `retry-needed`, or `stale`
52
- - `basisStatus`, `basisToken`, `outputAt`, `outputHash`
53
-
54
- The parent owns approval and `update_campaign_brief`.
55
-
56
- ## Drafting Rules
57
-
58
- - Keep it clear, concrete, and product-specific.
59
- - Use simple language and short lines.
60
- - Default to one blank line per sentence in the body.
61
- - Explain what the product is before what it does.
62
- - Use proof only when the campaign brief provides it.
63
- - Use the sender's voice, not third-person notes about the sender.
64
- - Use supported tokens such as `{{first_name}}` only.
65
- - Prefer omitting weak personalization over forcing a creepy line.
66
- - Do not claim the prospect needs outbound, automation, AI, or the product.
67
- - Do not claim private intent from a reaction/comment.
68
- - Do not write "saw you engaging", "clearly focused", or similar certainty.
69
- - Do not include bracketed instructions in the outbound message body.
70
- - Do not include sequence, follow-up, post-accept DM, launch, or sender setup.
71
-
72
- Engagement-source personalization is allowed only when it is cautious and
73
- self-aware, for example: `you might not remember the thread, but I found you
74
- through a [topic] discussion and your [role/company context] looked close to
75
- [problem]`. Otherwise omit the source signal and start from role, company,
76
- problem, or product relevance.
77
-
78
- ## Quality Gate
79
-
80
- Before returning `ready`, verify:
81
-
82
- - first name and company/person match
83
- - tokens have deterministic fallbacks
84
- - proof claims are in the brief or omitted
85
- - the prospect angle follows from role/source context
86
- - unsupported claims are removed
87
- - the template is usable for the whole review batch, not only one row
88
- - at least one rendered example reads like a message a real sender would send
89
-
90
- ## Lazy References
91
-
92
- Load `references/examples-critique-revision.md` only when:
93
-
94
- - the parent explicitly asks for a critique pass
95
- - the first draft fails the quality gate
96
- - the user asks to revise the message template
97
- - you need more examples to resolve a close call
98
-
99
- Do not load the old full `generate-messages` prompt in the normal create-campaign
100
- path unless the parent asks for legacy dry-mode validation or deep debugging.
@@ -1,37 +0,0 @@
1
- # Examples, Critique, And Revision
2
-
3
- Load this only when the compact prompt is insufficient.
4
-
5
- ## Good Pattern
6
-
7
- ```text
8
- Hey {{first_name}},
9
-
10
- You might not remember the thread, but I found you through a Claude GTM
11
- discussion and your founder/operator background looked close enough to send this.
12
-
13
- I am building Sellable so Claude Code can run LinkedIn outbound without
14
- stitching together Clay, Apollo, HeyReach, and spreadsheets.
15
-
16
- It finds people from live LinkedIn signals.
17
-
18
- It filters for fit.
19
-
20
- It writes the sequence and keeps the campaign moving from chat.
21
-
22
- Open to seeing what this would look like for your own outbound workflow?
23
- ```
24
-
25
- ## Bad Patterns To Block
26
-
27
- - "Saw you engaging with Debbie's post, so AI-GTM is clearly on your mind."
28
- - "You need help with outbound."
29
- - "I noticed you are struggling with..."
30
- - "[PERSONALIZATION_LINE]"
31
- - "Christian's approach is..."
32
-
33
- ## Revision Rule
34
-
35
- When the user asks to change the message after generation, update the approved
36
- template in the campaign brief and rerun Generate Message cells. Do not directly
37
- overwrite generated row messages.