@sellable/mcp 0.1.262 → 0.1.264

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.
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.262",
3
+ "version": "0.1.264",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -108,6 +108,8 @@ Use these MCP tools when available:
108
108
  - `mcp__sellable__get_post_idea`
109
109
  - `mcp__sellable__list_post_ideas`
110
110
  - `mcp__sellable__save_hook_research`
111
+ - `mcp__sellable__calculate_linkedin_hook_preview`
112
+ - `mcp__sellable__render_linkedin_post_preview`
111
113
  - `mcp__sellable__save_post_draft`
112
114
  - `mcp__sellable__update_post_draft`
113
115
  - `mcp__sellable__list_post_draft_iterations`
@@ -342,7 +344,9 @@ Visible Flow Trace
342
344
 
343
345
  4. Hook Autopsies
344
346
  - source hook:
345
- - rendered/mobile preview or preview-basis:
347
+ - calculated mobile/desktop visible blocks from
348
+ `calculate_linkedin_hook_preview` or authenticated LinkedIn screenshot:
349
+ - optional visual artifact from `render_linkedin_post_preview`:
346
350
  - see-more tension:
347
351
  - curiosity debt:
348
352
  - body promise:
@@ -402,7 +406,8 @@ Visible Flow Trace
402
406
 
403
407
  11. Hook Lab
404
408
  - at least 12 hooks:
405
- - rendered preview record or explicit preview-basis:
409
+ - calculated preview from `calculate_linkedin_hook_preview`:
410
+ - optional visual artifact from `render_linkedin_post_preview` for finalists:
406
411
  - hook-to-body promise:
407
412
  - score:
408
413
  - selected hook:
@@ -829,17 +834,36 @@ Each hook must include:
829
834
 
830
835
  Do not copy source wording. Copy only the structure.
831
836
 
832
- Use this conservative mobile-first LinkedIn preview gate. LinkedIn does not
833
- publish exact "see more" cutoff rules, and rendering varies by device, app
834
- version, font, media, and line break. These are v1 safety budgets, not claims
835
- about an official LinkedIn limit:
836
-
837
- - `pass`: hook is <= 110 chars including newlines, every nonblank line is <= 45 chars, and the hook's core point lands before likely truncation.
838
- - `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.
839
- - `fail`: hook is > 140 chars including newlines, any nonblank line is > 55 chars, or the hook's point depends on text after likely truncation.
837
+ Use the rendered-preview contract from
838
+ `references/linkedin-preview-rendering.md`. LinkedIn does not publish exact
839
+ "see more" cutoff rules, and rendering varies by device, app version, font,
840
+ media, and line break. Treat character counts as diagnostics only, not as proof
841
+ that the hook will render before "see more."
842
+
843
+ The selected hook and top candidates must include literal mobile and desktop
844
+ visible blocks from `mcp__sellable__calculate_linkedin_hook_preview`.
845
+ Observed LinkedIn screenshots and current third-party preview tools support a
846
+ line-count model: review the first 3 rendered visual lines, not the first 210
847
+ characters. Blank lines and `--` separators consume visible preview lines.
848
+ Use `mcp__sellable__render_linkedin_post_preview` only when a visual QA artifact
849
+ is useful.
850
+
851
+ Use:
852
+
853
+ - `pass`: rendered mobile preview shows the pain, proof, or curiosity by the end
854
+ of the first 3 rendered lines, and either the core point is visible or a
855
+ specific intentional open loop is visible with immediate body payoff planned
856
+ - `warn`: rendered mobile preview creates useful curiosity but wrapping,
857
+ blank-line rhythm, media risk, or one missing context word weakens it; include
858
+ a compact fallback
859
+ - `fail`: the hook's real point appears after the rendered mobile review clamp,
860
+ the visible open loop is vague, blank lines consume the preview before the
861
+ point, or desktop fit is the only reason it looks good
840
862
 
841
863
  Desktop preview usually has more room. Still record `desktopPreviewFit`, but
842
- never let desktop fit compensate for a mobile `fail`.
864
+ never let desktop fit compensate for a mobile `fail`. Do not tell the user "we
865
+ know" how LinkedIn will render unless there is an authenticated LinkedIn
866
+ screenshot; say it passes the renderer and show the visible blocks.
843
867
 
844
868
  If a hook's point depends on text after the likely preview, rewrite it before
845
869
  selecting it. A selected hook may carry a `warn` only when the warning is about
@@ -256,19 +256,33 @@ Measure the visible opening for every shortlisted source post before extracting
256
256
  the hook pattern. This makes the study useful for LinkedIn, not just generally
257
257
  "good writing."
258
258
 
259
- LinkedIn does not publish exact "see more" cutoff rules. Treat these as
260
- conservative v1 planning budgets:
261
-
262
- - `pass`: opening hook is <= 110 chars including newlines, every nonblank line
263
- is <= 45 chars, and the hook's core point lands before likely truncation.
264
- - `warn`: opening hook is 111-140 chars including newlines, any nonblank line is
265
- 46-55 chars, or blank lines create visual-line risk. Blank lines are allowed,
266
- but they count as physical lines.
267
- - `fail`: opening hook is > 140 chars including newlines, any nonblank line is
268
- > 55 chars, or the hook's core point depends on text after likely truncation.
259
+ LinkedIn does not publish exact "see more" cutoff rules. Treat preview fit as a
260
+ rendered-line problem first, not a fixed character-count rule. Current
261
+ third-party preview tools and 2026 creator references generally cluster around:
262
+
263
+ - mobile feed: about 2-3 visible text lines, often around 140 characters when
264
+ there is no media
265
+ - desktop feed: about 3-4 visible text lines, often around 210 characters when
266
+ there is no media
267
+ - media posts can show fewer text lines before truncation
268
+ - blank lines and `--` style separators consume visible preview lines
269
+
270
+ Use `references/linkedin-preview-rendering.md` as the required gate. Character
271
+ budgets are diagnostics only:
272
+
273
+ - `pass`: rendered mobile preview shows the pain, proof, or curiosity by the end
274
+ of the first 3 rendered lines, and the hook creates a specific click reason
275
+ - `warn`: rendered mobile preview creates useful curiosity but loses one useful
276
+ context word, wraps awkwardly, or spends a visible line on blank space or a
277
+ separator; include a compact fallback
278
+ - `fail`: the hook's real point appears after the rendered mobile review clamp,
279
+ the visible open loop is vague, blank lines consume the preview before the
280
+ point, or desktop fit is the only reason it looks good
269
281
 
270
282
  Desktop preview has more room, so record it separately, but never let desktop
271
- fit compensate for a mobile `fail`.
283
+ fit compensate for a mobile `fail`. Never say "we know" how LinkedIn will render
284
+ the hook unless there is an authenticated LinkedIn screenshot. Say the hook
285
+ `passes the renderer` and show the mobile/desktop visible blocks.
272
286
 
273
287
  For each source, record:
274
288
 
@@ -282,6 +296,8 @@ For each source, record:
282
296
  - `firstTwoContentLinesChars`
283
297
  - `longestNonblankLineChars`
284
298
  - `blankLineCountBeforeFold`
299
+ - `renderedPreview`: literal mobile and desktop visible blocks from
300
+ `references/linkedin-preview-rendering.md`
285
301
  - `mobilePreviewBudget`: `pass`, `warn`, or `fail`
286
302
  - `desktopPreviewBudget`: `pass`, `warn`, or `fail`
287
303
  - `blankLineVisualRisk`
@@ -5,14 +5,56 @@ research, hook candidate generation, gold-standard decomposition, and draft
5
5
  validation.
6
6
 
7
7
  Character budgets are only diagnostics. They are not the preview gate. A hook is
8
- not studied, selected, or ready until it has been rendered through this contract
9
- or through a stricter authenticated LinkedIn screenshot.
8
+ not studied, selected, or ready until its visible mobile and desktop blocks come
9
+ from `mcp__sellable__calculate_linkedin_hook_preview` or from a stricter
10
+ authenticated LinkedIn screenshot.
11
+
12
+ Do not let the LLM imagine wrapping. The normal path is a function call that
13
+ returns rendered mobile and desktop lines. HTML and PNG outputs from
14
+ `mcp__sellable__render_linkedin_post_preview` are optional visual QA artifacts,
15
+ not the primary interface.
10
16
 
11
17
  ## Rendering Basis
12
18
 
13
19
  LinkedIn does not publish exact feed truncation rules, and rendering can vary by
14
20
  surface, app version, device, media attachment, and account state. Use this
15
- deterministic renderer as the MCP review gate for unpublished drafts.
21
+ deterministic function as the MCP review gate for unpublished drafts.
22
+
23
+ Simple operating policy: judge the hook by the first 3 rendered lines. Use ~140
24
+ characters on mobile and ~210 characters on desktop as guardrails only. If the
25
+ 3-line preview and the character guardrail disagree, trust the rendered lines.
26
+
27
+ Call:
28
+
29
+ ```json
30
+ mcp__sellable__calculate_linkedin_hook_preview({
31
+ "postText": "<full post text or candidate hook block>",
32
+ "sourceLabel": "<draft id, hook id, or source label>",
33
+ "measurementMode": "browser"
34
+ })
35
+ ```
36
+
37
+ When local Chrome is available, browser measurement is the preferred functional
38
+ basis because it asks the browser layout engine to wrap the text under the
39
+ contract. When Chrome is unavailable, the tool falls back to the deterministic
40
+ width model and must mark that basis.
41
+
42
+ Treat "see more" as a rendered-line problem first and a character-count problem
43
+ second. Observed LinkedIn screenshots show the current desktop feed behaving
44
+ like a first-3-rendered-lines clamp: a first line, a blank line, and a third
45
+ line can be all that appears before `... more`.
46
+
47
+ - review gate: first 3 rendered visual lines
48
+ - mobile feed: tighter because the text column is narrower
49
+ - desktop feed: more characters can fit per line, but still review line count
50
+ - posts with media may show fewer text lines before truncation
51
+ - blank lines and intentional separators consume visible preview lines
52
+ - device width, font scaling, app version, browser, and profile/page context can
53
+ move the cutoff
54
+
55
+ Because of that variation, never say a hook is guaranteed to render before
56
+ "see more." Say `pass`, `warn`, or `fail` under the rendered-preview contract,
57
+ and show the visible mobile and desktop blocks.
16
58
 
17
59
  When an authenticated LinkedIn feed/composer/browser screenshot is available,
18
60
  that screenshot is the strongest evidence. Still record the fields below so
@@ -48,13 +90,16 @@ selected hook must include a `renderedPreview` record:
48
90
 
49
91
  ```text
50
92
  renderedPreview:
51
- basis: linkedin_css_contract | authenticated_linkedin_screenshot | manual_user_source
52
- cssContractVersion: linkedin-preview-rendering/v1
93
+ basis: linkedin_rendering_rule_function | authenticated_linkedin_screenshot | manual_user_source
94
+ cssContractVersion: linkedin-preview-rendering/v2
53
95
  mobile:
54
96
  textWidthPx: 308
55
97
  fontSizePx: 14
56
98
  lineHeightPx: 21
57
- visibleTextBlock: <literal first rendered review-clamp block>
99
+ clampLines: 3
100
+ charGuardrail: 140
101
+ measurementBasis: local_chrome_browser_layout | estimated_width_model
102
+ visibleTextBlock: <literal first 3 rendered lines>
58
103
  renderedLines:
59
104
  - <line 1 exactly as wrapped>
60
105
  - <line 2 exactly as wrapped>
@@ -68,12 +113,14 @@ renderedPreview:
68
113
  payoffPlannedImmediatelyAfterClamp: true | false
69
114
  seeMoreClickReason: <why a reader would click see more>
70
115
  seeMoreRisk: pass | warn | fail
71
- screenshotPath: <optional local path>
72
116
  desktop:
73
117
  textWidthPx: 582
74
118
  fontSizePx: 14
75
119
  lineHeightPx: 21
76
- visibleTextBlock: <literal first rendered review-clamp block>
120
+ clampLines: 3
121
+ charGuardrail: 210
122
+ measurementBasis: local_chrome_browser_layout | estimated_width_model
123
+ visibleTextBlock: <literal first 3 rendered lines>
77
124
  renderedLines:
78
125
  - <line 1 exactly as wrapped>
79
126
  - <line 2 exactly as wrapped>
@@ -83,7 +130,6 @@ renderedPreview:
83
130
  corePainProofOrCuriosityVisible: true | false
84
131
  corePointVisible: true | false
85
132
  seeMoreRisk: pass | warn | fail
86
- screenshotPath: <optional local path>
87
133
  diagnostics:
88
134
  charCount: <number>
89
135
  charCountIncludingNewlines: <number>
@@ -96,10 +142,9 @@ renderedPreview:
96
142
  rewriteIfTruncated: <short fallback>
97
143
  ```
98
144
 
99
- If a host cannot produce screenshots, it must still produce the literal wrapped
100
- line blocks using the CSS contract. If it cannot produce either screenshots or
101
- literal line wraps, return `blocked` or `needs_revision`; do not claim the hook
102
- passed preview validation from character counts alone.
145
+ If a host cannot produce literal wrapped line blocks from
146
+ `calculate_linkedin_hook_preview`, return `blocked` or `needs_revision`; do not
147
+ claim the hook passed preview validation from character counts alone.
103
148
 
104
149
  ## Study Rules
105
150
 
@@ -320,22 +320,38 @@ proof from another creator, save as `needs_revision`.
320
320
 
321
321
  ## LinkedIn Preview Audit
322
322
 
323
- Audit the selected hook and top candidates against conservative LinkedIn
324
- preview budgets. LinkedIn does not publish exact "see more" cutoff rules, and
325
- rendering varies by device, app version, font, media, and line break. This audit
326
- is a mobile-first safety gate, not a claim about an official LinkedIn limit.
323
+ Audit the selected hook and top candidates against the rendered-preview
324
+ contract. LinkedIn does not publish exact "see more" cutoff rules, and rendering
325
+ varies by device, app version, font, media, line break, and whether the post has
326
+ media attached. This audit is a mobile-first safety gate, not a claim about an
327
+ official LinkedIn limit.
328
+
329
+ Treat character counts as diagnostics only. The gate is the literal rendered
330
+ mobile and desktop visible block from `references/linkedin-preview-rendering.md`
331
+ or an authenticated LinkedIn screenshot.
327
332
 
328
333
  Use:
329
334
 
330
- - `pass`: hook is <= 110 chars including newlines, every nonblank line is <= 45 chars, and the hook's core point lands before likely truncation.
331
- - `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.
332
- - `fail`: hook is > 140 chars including newlines, any nonblank line is > 55 chars, or the hook's point depends on text after likely truncation.
335
+ - `pass`: the rendered mobile preview shows the pain, proof, or curiosity by the
336
+ end of the first 3 rendered lines, and either the core point is visible or a
337
+ specific intentional open loop is visible with immediate body payoff planned
338
+ - `warn`: the rendered mobile preview creates useful curiosity but wrapping,
339
+ blank-line rhythm, media risk, or one missing context word weakens it; include
340
+ a compact fallback
341
+ - `fail`: the hook's real point appears after the rendered mobile review clamp,
342
+ the visible open loop is vague, blank lines consume the preview before the
343
+ point, or desktop fit is the only reason it looks good
333
344
 
334
345
  Desktop preview usually has more room. Still record desktop fit, but never let
335
346
  desktop fit compensate for a mobile `fail`.
336
347
 
337
348
  Record:
338
349
 
350
+ - `renderedPreview`
351
+ - `renderedPreviewBasis`: `linkedin_css_contract` |
352
+ `authenticated_linkedin_screenshot` | `manual_user_source`
353
+ - literal mobile visible block
354
+ - literal desktop visible block
339
355
  - `charCount`
340
356
  - `charCountIncludingNewlines`
341
357
  - `firstLineChars`
@@ -355,9 +371,11 @@ Record:
355
371
  - `compactFallback` when `previewBudgetStatus` is `warn`
356
372
 
357
373
  If the hook only works after likely truncation, rewrite it. A draft cannot be
358
- `ready` with `previewBudgetStatus: fail`. A draft may be `ready` with
374
+ `ready` with `previewBudgetStatus: fail`, with no rendered preview block, or
375
+ with only a character-count claim. A draft may be `ready` with
359
376
  `previewBudgetStatus: warn` only when the warning is explicit, usually because
360
- the user prefers blank-line rhythm, and the receipt includes a compact fallback.
377
+ the user prefers blank-line rhythm, separators, or a deliberate open loop, and
378
+ the receipt includes a compact fallback.
361
379
 
362
380
  ## Simplifier / Concrete-Language Audit
363
381