@sellable/mcp 0.1.238 → 0.1.239

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.
@@ -58,6 +58,7 @@ export declare const contentPostToolDefinitions: ({
58
58
  keywords?: undefined;
59
59
  sourcePosts?: undefined;
60
60
  selectedPatterns?: undefined;
61
+ previewBudget?: undefined;
61
62
  notes?: undefined;
62
63
  createdAt?: undefined;
63
64
  draftId?: undefined;
@@ -130,6 +131,11 @@ export declare const contentPostToolDefinitions: ({
130
131
  type: string;
131
132
  };
132
133
  };
134
+ previewBudget: {
135
+ type: string;
136
+ description: string;
137
+ additionalProperties: boolean;
138
+ };
133
139
  notes: {
134
140
  type: string;
135
141
  };
@@ -192,6 +198,7 @@ export declare const contentPostToolDefinitions: ({
192
198
  keywords?: undefined;
193
199
  sourcePosts?: undefined;
194
200
  selectedPatterns?: undefined;
201
+ previewBudget?: undefined;
195
202
  notes?: undefined;
196
203
  };
197
204
  required: string[];
@@ -245,6 +252,7 @@ export declare function saveHookResearchTool(input: {
245
252
  keywords?: string[];
246
253
  sourcePosts?: Array<Record<string, unknown>>;
247
254
  selectedPatterns?: string[];
255
+ previewBudget?: Record<string, unknown>;
248
256
  notes?: string;
249
257
  createdAt?: string;
250
258
  }): {
@@ -66,6 +66,11 @@ export const contentPostToolDefinitions = [
66
66
  type: "array",
67
67
  items: { type: "string" },
68
68
  },
69
+ previewBudget: {
70
+ type: "object",
71
+ description: "Optional structured LinkedIn preview-budget summary for studied hooks and selected patterns.",
72
+ additionalProperties: true,
73
+ },
69
74
  notes: { type: "string" },
70
75
  createdAt: { type: "string" },
71
76
  },
@@ -202,11 +207,14 @@ export function capturePostIdeaTool(input) {
202
207
  const markdown = buildMarkdown(metadata, [
203
208
  ["Distilled Brief", input.distilledBrief || ""],
204
209
  ["Raw Source", rawBlock(input.rawSource)],
205
- ["Source Metadata", jsonBlock(stripUndefined({
210
+ [
211
+ "Source Metadata",
212
+ jsonBlock(stripUndefined({
206
213
  sourceType: input.sourceType,
207
214
  sourceUrl: input.sourceUrl,
208
215
  capturedAt: now,
209
- }))],
216
+ })),
217
+ ],
210
218
  ]);
211
219
  writeArtifact(relativePath, markdown);
212
220
  return {
@@ -246,6 +254,7 @@ export function saveHookResearchTool(input) {
246
254
  const markdown = buildMarkdown(metadata, [
247
255
  ["Keywords", listBlock(input.keywords ?? [])],
248
256
  ["Selected Patterns", listBlock(input.selectedPatterns ?? [])],
257
+ ["Preview Budget", jsonBlock(input.previewBudget ?? {})],
249
258
  ["Source Posts", jsonBlock(input.sourcePosts ?? [])],
250
259
  ["Notes", input.notes || ""],
251
260
  ]);
@@ -332,18 +341,24 @@ export function markPostPublishedTool(input) {
332
341
  };
333
342
  const markdown = buildMarkdown(metadata, [
334
343
  ["Final Text", input.finalText || ""],
335
- ["Publish Metadata", jsonBlock(stripUndefined({
344
+ [
345
+ "Publish Metadata",
346
+ jsonBlock(stripUndefined({
336
347
  draftId: safeDraftId,
337
348
  publishUrl: input.publishUrl,
338
349
  activityId: activityId || undefined,
339
350
  publishedAt,
340
- }))],
341
- ["Future Metrics", jsonBlock({
351
+ })),
352
+ ],
353
+ [
354
+ "Future Metrics",
355
+ jsonBlock({
342
356
  impressions: null,
343
357
  reactions: null,
344
358
  comments: null,
345
359
  snapshots: [],
346
- })],
360
+ }),
361
+ ],
347
362
  ]);
348
363
  writeArtifact(relativePath, markdown);
349
364
  return {
@@ -483,7 +498,8 @@ function validateRelativePath(relativePath) {
483
498
  }
484
499
  function isPathInside(candidate, root) {
485
500
  const relative = path.relative(root, candidate);
486
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
501
+ return (relative === "" ||
502
+ (!relative.startsWith("..") && !path.isAbsolute(relative)));
487
503
  }
488
504
  function normalizeArtifactId(id, label) {
489
505
  requireString(id, label);
@@ -1214,6 +1214,7 @@ export declare const allTools: ({
1214
1214
  keywords?: undefined;
1215
1215
  sourcePosts?: undefined;
1216
1216
  selectedPatterns?: undefined;
1217
+ previewBudget?: undefined;
1217
1218
  notes?: undefined;
1218
1219
  createdAt?: undefined;
1219
1220
  draftId?: undefined;
@@ -1279,6 +1280,7 @@ export declare const allTools: ({
1279
1280
  keywords?: undefined;
1280
1281
  sourcePosts?: undefined;
1281
1282
  selectedPatterns?: undefined;
1283
+ previewBudget?: undefined;
1282
1284
  notes?: undefined;
1283
1285
  };
1284
1286
  required: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.238",
3
+ "version": "0.1.239",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "build": "tsc",
14
+ "prepack": "npm run build",
14
15
  "start": "node dist/index.js",
15
16
  "start:dev": "node dist/index-dev.js",
16
17
  "dev": "ts-node src/index.ts"
@@ -93,10 +93,10 @@ Required:
93
93
  select a blocked candidate. If all 3 candidates fail, route to
94
94
  `revise-message` with the failure reasons enumerated per candidate (cite the
95
95
  rule/filter name and the offending line).
96
- - **Concrete Language Audit.** The selected draft must highlight every abstract
97
- verb, abstract noun, abstract adjective, abstract adverb, and cliche, then
98
- either replace it with a buyer-visible action, object, workflow, artifact,
99
- risk, result, or next step from the approved evidence, cut it, or block with
96
+ - **Concrete Language Audit.** The selected draft must highlight every abstract verb,
97
+ abstract noun, abstract adjective, abstract adverb, and cliche, then either
98
+ replace it with a buyer-visible action, object, workflow, artifact, risk,
99
+ result, or next step from the approved evidence, cut it, or block with
100
100
  `revise-message` / `revise-filter`. Do not leave a vague phrase in place just
101
101
  because it sounds polished.
102
102
 
@@ -20,7 +20,7 @@ Hard fail patterns:
20
20
  - drafts that skip raw idea capture
21
21
  - hooks copied verbatim from another creator
22
22
  - using `~/.sellable/configs/content/linkedin-posts-drafts.md` as the normal save target
23
- </role>
23
+ </role>
24
24
 
25
25
  <scope>
26
26
  V1 is posts-only and hooks-first.
@@ -42,7 +42,7 @@ Do not:
42
42
  - optimize a comment workflow
43
43
  - move identity/proof/story memory into content files
44
44
  - append new drafts to the legacy flat file
45
- </scope>
45
+ </scope>
46
46
 
47
47
  <required_assets>
48
48
  Before drafting, load all required assets with `mcp__sellable__get_subskill_asset`:
@@ -90,7 +90,7 @@ Do not call outbound/campaign tools from this skill. Do not call message-generat
90
90
  Load user memory before hook generation or drafting:
91
91
 
92
92
  1. Call `mcp__sellable__get_engage_memory`.
93
- 2. Read or use the returned core memory fields:
93
+ 2. Read or use the returned core identity memory and company memory fields:
94
94
  - `core/about-me.md`
95
95
  - `core/my-company.md`
96
96
  - `core/anti-ai-writing-style.md`
@@ -115,6 +115,30 @@ Load user memory before hook generation or drafting:
115
115
  New users may have blank or missing core files until they run `$sellable:interview`. If story, proof, identity, or company memory is missing, ask for the missing material or ask the user to run `$sellable:interview`. Never fabricate what should have come from `story-bank.md`, `proof-ledger.md`, or `answer-bank.md`.
116
116
 
117
117
  The interview/core memory files remain the durable source for stories and proof. This skill may read them, but it must not move them into `~/.sellable/content/**`.
118
+
119
+ Use core context modes as drafting constraints, not decorative labels. If a
120
+ post idea exposes durable memory that should help future writing, propose a
121
+ core write-back before saving it. The user must be able to approve, edit, or
122
+ reject each proposed memory update before it is written.
123
+
124
+ Durable write-back targets:
125
+
126
+ - `core/answer-bank.md` for reusable user answers, positioning, or taste rules
127
+ - `core/wins-ledger.md` for verified wins and outcomes
128
+ - `core/change-log.md` for approved corrections, rejected-but-useful proposals,
129
+ privacy decisions, and downstream prompt/operator notes
130
+ - `core/story-bank.md` for reusable first-person stories
131
+ - `core/transcripts/INDEX.md` for source interview or voice-memo references
132
+ - `core/references/linkedin-posts/INDEX.md` and adjacent files for approved
133
+ gold-standard post references
134
+
135
+ Write-backs must use stable source keys such as
136
+ `create-post:{date}:{ideaId}:{slug}` or
137
+ `create-post-research:{date}:{sourcePostHash}:{slug}`. Check existing copied
138
+ paths before adding references, create no duplicate reference rows, and keep
139
+ manual sections preserved. If the user says to mark private, store only the
140
+ minimum private-safe metadata and do not promote it into public proof,
141
+ references, or examples.
118
142
  </memory_contract>
119
143
 
120
144
  <modes>
@@ -221,6 +245,7 @@ Record provenance:
221
245
  - lead-magnet or engagement-bait penalties
222
246
  - story mechanism when relevant
223
247
  - full-text match status
248
+ - source hook preview measurements and whether they came from full text or a search preview
224
249
  - selected hook patterns
225
250
  - why each pattern fits the user's idea and voice
226
251
 
@@ -233,8 +258,14 @@ Each hook must include:
233
258
  - source hook pattern
234
259
  - why it fits this idea
235
260
  - `charCount`
261
+ - `charCountIncludingNewlines`
236
262
  - `firstLineChars`
237
263
  - `firstTwoLinesChars`
264
+ - `physicalLineCount`
265
+ - `contentLineCount`
266
+ - `longestNonblankLineChars`
267
+ - `blankLineVisualRisk`
268
+ - `previewBudgetStatus`
238
269
  - `mobilePreviewFit`
239
270
  - `desktopPreviewFit`
240
271
  - `lineCountEstimate`
@@ -246,14 +277,22 @@ Each hook must include:
246
277
 
247
278
  Do not copy source wording. Copy only the structure.
248
279
 
249
- Use these LinkedIn preview budgets as conservative scoring constraints. LinkedIn rendering varies by device, font, and line break, so these are planning budgets, not exact guarantees:
280
+ Use this conservative mobile-first LinkedIn preview gate. LinkedIn does not
281
+ publish exact "see more" cutoff rules, and rendering varies by device, app
282
+ version, font, media, and line break. These are v1 safety budgets, not claims
283
+ about an official LinkedIn limit:
284
+
285
+ - `pass`: hook is <= 110 chars including newlines, every nonblank line is <= 45 chars, and the hook's core point lands before likely truncation.
286
+ - `warn`: hook is 111-140 chars including newlines, any nonblank line is 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed, but they count as physical lines.
287
+ - `fail`: hook is > 140 chars including newlines, any nonblank line is > 55 chars, or the hook's point depends on text after likely truncation.
250
288
 
251
- - mobile first line: 35-45 chars
252
- - mobile first two lines: 80-100 chars
253
- - desktop first line: 70-90 chars
254
- - desktop first two lines: 140-180 chars
289
+ Desktop preview usually has more room. Still record `desktopPreviewFit`, but
290
+ never let desktop fit compensate for a mobile `fail`.
255
291
 
256
- If a hook's point depends on text after the likely preview, rewrite it before selecting it.
292
+ If a hook's point depends on text after the likely preview, rewrite it before
293
+ selecting it. A selected hook may carry a `warn` only when the warning is about
294
+ intentional blank-line rhythm or a slight line-length overage; include a compact
295
+ fallback in the validation receipt.
257
296
 
258
297
  ## Step 3: Draft
259
298
 
@@ -287,6 +326,7 @@ Every saved draft needs a validation receipt with:
287
326
  - voice audit findings
288
327
  - anti-AI audit findings
289
328
  - outbound AI-tell audit findings
329
+ - hook preview pass/warn/fail status and compact fallback when warned
290
330
  - finalizer changes
291
331
  - blocked/retry-needed reasons, if any
292
332
 
@@ -319,6 +359,7 @@ Published post records live under:
319
359
  ```text
320
360
  ~/.sellable/content/linkedin/published/
321
361
  ```
362
+
322
363
  </pipeline>
323
364
 
324
365
  <legacy>
@@ -341,8 +382,9 @@ validation_summary:
341
382
  proof: pass | needs_user_input | blocked
342
383
  voice: pass | needs_revision
343
384
  anti_ai: pass | needs_revision
344
- linkedin_preview: pass | needs_revision
385
+ linkedin_preview: pass | warn | needs_revision
345
386
  concrete_language: pass | needs_revision
346
387
  next_step: <what the user should do next>
347
388
  ```
389
+
348
390
  </response_shape>
@@ -78,6 +78,7 @@ Show compact candidate cards. Include:
78
78
  - engagement breakdown when available
79
79
  - why it might belong in the pack
80
80
  - hook mechanism
81
+ - hook preview budget status and measurement basis
81
82
  - content/body mechanism
82
83
  - rhythm notes
83
84
  - sentence-structure notes
@@ -144,8 +145,15 @@ Tags:
144
145
 
145
146
  - hook text:
146
147
  - char count:
148
+ - char count including newlines:
147
149
  - first-line chars:
148
150
  - first-two-line chars:
151
+ - physical line count:
152
+ - content line count:
153
+ - longest nonblank line chars:
154
+ - blank-line visual risk:
155
+ - source text basis:
156
+ - preview budget status:
149
157
  - mobile preview fit:
150
158
  - desktop preview fit:
151
159
  - hook mechanism:
@@ -74,6 +74,47 @@ When full hook/body text matters:
74
74
 
75
75
  If full text is unavailable, record `full_text_unavailable`. Use only the preview for hook analysis and do not infer missing body details.
76
76
 
77
+ ## Opening Preview Measurement
78
+
79
+ Measure the visible opening for every shortlisted source post before extracting
80
+ the hook pattern. This makes the study useful for LinkedIn, not just generally
81
+ "good writing."
82
+
83
+ LinkedIn does not publish exact "see more" cutoff rules. Treat these as
84
+ conservative v1 planning budgets:
85
+
86
+ - `pass`: opening hook is <= 110 chars including newlines, every nonblank line
87
+ is <= 45 chars, and the hook's core point lands before likely truncation.
88
+ - `warn`: opening hook is 111-140 chars including newlines, any nonblank line is
89
+ 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed,
90
+ but they count as physical lines.
91
+ - `fail`: opening hook is > 140 chars including newlines, any nonblank line is
92
+ > 55 chars, or the hook's core point depends on text after likely truncation.
93
+
94
+ Desktop preview has more room, so record it separately, but never let desktop
95
+ fit compensate for a mobile `fail`.
96
+
97
+ For each source, record:
98
+
99
+ - `sourceTextBasis`: `full_text`, `search_preview`, or `manual_user_source`
100
+ - `openingTextUsed`
101
+ - `charCountIncludingNewlines`
102
+ - `physicalLineCount`
103
+ - `contentLineCount`
104
+ - `firstLineChars`
105
+ - `firstTwoPhysicalLinesChars`
106
+ - `firstTwoContentLinesChars`
107
+ - `longestNonblankLineChars`
108
+ - `blankLineCountBeforeFold`
109
+ - `mobilePreviewBudget`: `pass`, `warn`, or `fail`
110
+ - `desktopPreviewBudget`: `pass`, `warn`, or `fail`
111
+ - `blankLineVisualRisk`
112
+ - `corePointBeforeLikelyTruncation`
113
+
114
+ If only a search preview is available, do not pretend the opening is complete.
115
+ Record `sourceTextBasis: search_preview` and lower confidence when the hook
116
+ appears cut off or body context is unavailable.
117
+
77
118
  ## Hook Extraction
78
119
 
79
120
  Extract structure, not wording.
@@ -85,7 +126,7 @@ For each shortlisted source post, record:
85
126
  - engagement totals and available likes/comments/shares breakdown
86
127
  - creator repeat evidence
87
128
  - visible hook text or preview
88
- - line count and mobile preview fit
129
+ - opening preview measurement fields from the section above
89
130
  - hook mechanism
90
131
  - story mechanism when the post is a story
91
132
  - internal question created
@@ -59,6 +59,9 @@ Hook research files must preserve:
59
59
  - author/profile URLs
60
60
  - engagement totals
61
61
  - full-text availability
62
+ - source hook preview measurements, including text basis, char count including
63
+ newlines, physical/content line counts, longest nonblank line, blank-line
64
+ visual risk, and mobile/desktop preview budget status
62
65
  - extracted hook patterns
63
66
  - selected hook basis
64
67
 
@@ -67,7 +70,8 @@ Draft files must preserve:
67
70
  - source idea ID
68
71
  - hook research ID
69
72
  - draft body
70
- - validation receipt
73
+ - validation receipt, including LinkedIn preview pass/warn/fail status and
74
+ compact fallback when the selected hook carries a warning
71
75
  - status: `draft`, `ready`, or `needs_revision`
72
76
 
73
77
  Published files must preserve:
@@ -32,7 +32,9 @@ Each candidate should include:
32
32
  - hook text
33
33
  - source pattern
34
34
  - score
35
- - char count and first-line / first-two-line preview measurements
35
+ - char count including newlines and first-line / first-two-line preview measurements
36
+ - physical line count, content line count, longest nonblank line, and blank-line risk
37
+ - `previewBudgetStatus`: `pass`, `warn`, or `fail`
36
38
  - mobile and desktop preview fit
37
39
  - proof/story dependency
38
40
  - AI-tell risk
@@ -52,25 +54,44 @@ After the first draft:
52
54
 
53
55
  ## LinkedIn Preview Audit
54
56
 
55
- Audit the selected hook and top candidates against conservative LinkedIn preview budgets:
57
+ Audit the selected hook and top candidates against conservative LinkedIn
58
+ preview budgets. LinkedIn does not publish exact "see more" cutoff rules, and
59
+ rendering varies by device, app version, font, media, and line break. This audit
60
+ is a mobile-first safety gate, not a claim about an official LinkedIn limit.
56
61
 
57
- - mobile first line: 35-45 chars
58
- - mobile first two lines: 80-100 chars
59
- - desktop first line: 70-90 chars
60
- - desktop first two lines: 140-180 chars
62
+ Use:
63
+
64
+ - `pass`: hook is <= 110 chars including newlines, every nonblank line is <= 45 chars, and the hook's core point lands before likely truncation.
65
+ - `warn`: hook is 111-140 chars including newlines, any nonblank line is 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed, but they count as physical lines.
66
+ - `fail`: hook is > 140 chars including newlines, any nonblank line is > 55 chars, or the hook's point depends on text after likely truncation.
67
+
68
+ Desktop preview usually has more room. Still record desktop fit, but never let
69
+ desktop fit compensate for a mobile `fail`.
61
70
 
62
71
  Record:
63
72
 
64
73
  - `charCount`
74
+ - `charCountIncludingNewlines`
65
75
  - `firstLineChars`
66
76
  - `firstTwoLinesChars`
77
+ - `physicalLineCount`
78
+ - `contentLineCount`
79
+ - `longestNonblankLineChars`
80
+ - `blankLineCountBeforeFold`
81
+ - `blankLineVisualRisk`
82
+ - `corePointBeforeLikelyTruncation`
83
+ - `previewBudgetStatus`
67
84
  - `mobilePreviewFit`
68
85
  - `desktopPreviewFit`
69
86
  - `lineCountEstimate`
70
87
  - `truncationRisk`
71
88
  - `rewriteIfTruncated`
89
+ - `compactFallback` when `previewBudgetStatus` is `warn`
72
90
 
73
- LinkedIn rendering varies by device and line break, so this is a planning audit. If the hook only works after likely truncation, rewrite it.
91
+ If the hook only works after likely truncation, rewrite it. A draft cannot be
92
+ `ready` with `previewBudgetStatus: fail`. A draft may be `ready` with
93
+ `previewBudgetStatus: warn` only when the warning is explicit, usually because
94
+ the user prefers blank-line rhythm, and the receipt includes a compact fallback.
74
95
 
75
96
  ## Simplifier / Concrete-Language Audit
76
97
 
@@ -45,6 +45,11 @@ message assets through Sellable MCP tools:
45
45
  After candidate generation and revision, and before returning `ready`, load
46
46
  `create-campaign-v2-validation` through Sellable MCP as the final gate.
47
47
 
48
+ Both live and dry runs must produce or validate `message-validation.md` with
49
+ the same quality gates. The selected winner must pass the `Concrete Language Audit`:
50
+ identify and replace or cut abstract verbs, abstract nouns, abstract adjectives,
51
+ abstract adverbs, and cliches before the message is treated as ready.
52
+
48
53
  Never reconstruct that branch from `brief.md`, `lead-review.md`,
49
54
  `lead-sample.json`, `lead-filter.md`, `message-validation.md`, local files, or
50
55
  direct database reads. If any required message asset cannot be loaded through MCP