@sellable/mcp 0.1.254 → 0.1.256
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.
- package/dist/api.d.ts +3 -0
- package/dist/api.js +24 -13
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/server.js +13 -4
- package/dist/tools/campaigns.d.ts +41 -0
- package/dist/tools/campaigns.js +22 -3
- package/dist/tools/registry.d.ts +483 -412
- package/dist/tools/tables.d.ts +53 -2
- package/dist/tools/tables.js +78 -0
- package/package.json +1 -1
- package/skills/create-post/SKILL.md +52 -17
- package/skills/create-post/references/gold-standard-post-pack.md +11 -0
- package/skills/create-post/references/hook-research-playbook.md +57 -14
- package/skills/create-post/references/linkedin-preview-rendering.md +163 -0
- package/skills/create-post/references/post-file-contract.md +7 -0
- package/skills/create-post/references/post-validation.md +68 -15
- package/skills/research/config.json +9 -0
package/dist/tools/tables.d.ts
CHANGED
|
@@ -14,7 +14,13 @@ export interface ListTablesInput {
|
|
|
14
14
|
limit?: number;
|
|
15
15
|
hasSequence?: boolean;
|
|
16
16
|
}
|
|
17
|
-
export
|
|
17
|
+
export interface ExportTableCsvInput {
|
|
18
|
+
tableId: string;
|
|
19
|
+
filters?: unknown[];
|
|
20
|
+
sort?: Record<string, unknown>;
|
|
21
|
+
splitRows?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const tableToolDefinitions: ({
|
|
18
24
|
name: string;
|
|
19
25
|
description: string;
|
|
20
26
|
inputSchema: {
|
|
@@ -28,8 +34,53 @@ export declare const tableToolDefinitions: {
|
|
|
28
34
|
type: string;
|
|
29
35
|
description: string;
|
|
30
36
|
};
|
|
37
|
+
tableId?: undefined;
|
|
38
|
+
filters?: undefined;
|
|
39
|
+
sort?: undefined;
|
|
40
|
+
splitRows?: undefined;
|
|
31
41
|
};
|
|
32
42
|
required: never[];
|
|
43
|
+
additionalProperties?: undefined;
|
|
44
|
+
};
|
|
45
|
+
} | {
|
|
46
|
+
name: string;
|
|
47
|
+
description: string;
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: string;
|
|
50
|
+
properties: {
|
|
51
|
+
tableId: {
|
|
52
|
+
type: string;
|
|
53
|
+
};
|
|
54
|
+
filters: {
|
|
55
|
+
type: string;
|
|
56
|
+
description: string;
|
|
57
|
+
items: {
|
|
58
|
+
type: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
sort: {
|
|
62
|
+
type: string;
|
|
63
|
+
description: string;
|
|
64
|
+
};
|
|
65
|
+
splitRows: {
|
|
66
|
+
type: string;
|
|
67
|
+
description: string;
|
|
68
|
+
};
|
|
69
|
+
limit?: undefined;
|
|
70
|
+
hasSequence?: undefined;
|
|
71
|
+
};
|
|
72
|
+
required: string[];
|
|
73
|
+
additionalProperties: boolean;
|
|
33
74
|
};
|
|
34
|
-
}[];
|
|
75
|
+
})[];
|
|
35
76
|
export declare function listTables(input: ListTablesInput): Promise<WorkflowTableListItem[]>;
|
|
77
|
+
export declare function exportTableCsv(input: ExportTableCsvInput): Promise<{
|
|
78
|
+
tableId: string;
|
|
79
|
+
endpoint: string;
|
|
80
|
+
totalRows: number;
|
|
81
|
+
files: Array<{
|
|
82
|
+
path: string;
|
|
83
|
+
rows: number;
|
|
84
|
+
}>;
|
|
85
|
+
splitRows: number | null;
|
|
86
|
+
}>;
|
package/dist/tools/tables.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { parse } from "csv-parse/sync";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
1
5
|
import { getApi } from "../api.js";
|
|
2
6
|
export const tableToolDefinitions = [
|
|
3
7
|
{
|
|
@@ -19,6 +23,31 @@ export const tableToolDefinitions = [
|
|
|
19
23
|
required: [],
|
|
20
24
|
},
|
|
21
25
|
},
|
|
26
|
+
{
|
|
27
|
+
name: "export_table_csv",
|
|
28
|
+
description: "Export a workflow table using the existing Sellable Export CSV endpoint. Optional filters/sort are passed through and splitRows writes deterministic CSV batches.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
tableId: { type: "string" },
|
|
33
|
+
filters: {
|
|
34
|
+
type: "array",
|
|
35
|
+
description: "Optional workflow-table filters to pass through.",
|
|
36
|
+
items: { type: "object" },
|
|
37
|
+
},
|
|
38
|
+
sort: {
|
|
39
|
+
type: "object",
|
|
40
|
+
description: "Optional workflow-table sort object to pass through.",
|
|
41
|
+
},
|
|
42
|
+
splitRows: {
|
|
43
|
+
type: "number",
|
|
44
|
+
description: "Optional data rows per split file.",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ["tableId"],
|
|
48
|
+
additionalProperties: false,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
22
51
|
];
|
|
23
52
|
export async function listTables(input) {
|
|
24
53
|
const api = getApi();
|
|
@@ -34,3 +63,52 @@ export async function listTables(input) {
|
|
|
34
63
|
const result = await api.get(path);
|
|
35
64
|
return result.tables;
|
|
36
65
|
}
|
|
66
|
+
const csvEscape = (value) => {
|
|
67
|
+
const text = value == null ? "" : String(value);
|
|
68
|
+
if (/[",\n\r]/.test(text)) {
|
|
69
|
+
return `"${text.replace(/"/g, '""')}"`;
|
|
70
|
+
}
|
|
71
|
+
return text;
|
|
72
|
+
};
|
|
73
|
+
const toCsv = (records) => records.map((row) => row.map(csvEscape).join(",")).join("\n") + "\n";
|
|
74
|
+
export async function exportTableCsv(input) {
|
|
75
|
+
if (!input.tableId) {
|
|
76
|
+
throw new Error("tableId is required");
|
|
77
|
+
}
|
|
78
|
+
const params = new URLSearchParams();
|
|
79
|
+
if (input.filters)
|
|
80
|
+
params.set("filters", JSON.stringify(input.filters));
|
|
81
|
+
if (input.sort)
|
|
82
|
+
params.set("sort", JSON.stringify(input.sort));
|
|
83
|
+
const endpoint = `/api/v3/workflow-tables/${input.tableId}/export-csv${params.toString() ? `?${params.toString()}` : ""}`;
|
|
84
|
+
const api = getApi();
|
|
85
|
+
const csv = await api.getText(endpoint);
|
|
86
|
+
const records = parse(csv, {
|
|
87
|
+
bom: true,
|
|
88
|
+
relax_column_count: true,
|
|
89
|
+
});
|
|
90
|
+
const header = records[0] ?? [];
|
|
91
|
+
const dataRows = records.slice(1);
|
|
92
|
+
const splitRows = Number.isInteger(input.splitRows) && input.splitRows > 0
|
|
93
|
+
? input.splitRows
|
|
94
|
+
: null;
|
|
95
|
+
const outputDir = path.join(tmpdir(), "sellable-mcp-exports", `${input.tableId}-${Date.now()}`);
|
|
96
|
+
await mkdir(outputDir, { recursive: true });
|
|
97
|
+
const chunks = splitRows && dataRows.length > 0
|
|
98
|
+
? Array.from({ length: Math.ceil(dataRows.length / splitRows) }, (_, index) => dataRows.slice(index * splitRows, index * splitRows + splitRows))
|
|
99
|
+
: [dataRows];
|
|
100
|
+
const files = [];
|
|
101
|
+
for (let index = 0; index < chunks.length; index++) {
|
|
102
|
+
const chunk = chunks[index];
|
|
103
|
+
const filePath = path.join(outputDir, chunks.length === 1 ? "export.csv" : `export-${index + 1}.csv`);
|
|
104
|
+
await writeFile(filePath, toCsv([header, ...chunk]), "utf8");
|
|
105
|
+
files.push({ path: filePath, rows: chunk.length });
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
tableId: input.tableId,
|
|
109
|
+
endpoint,
|
|
110
|
+
totalRows: dataRows.length,
|
|
111
|
+
files,
|
|
112
|
+
splitRows,
|
|
113
|
+
};
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -84,6 +84,7 @@ Before drafting, load all required assets with `mcp__sellable__get_subskill_asse
|
|
|
84
84
|
3. `subskillName: "create-post", assetPath: "references/premise-development.md"`
|
|
85
85
|
4. `subskillName: "create-post", assetPath: "references/post-validation.md"`
|
|
86
86
|
5. `subskillName: "create-post", assetPath: "references/gold-standard-post-pack.md"`
|
|
87
|
+
6. `subskillName: "create-post", assetPath: "references/linkedin-preview-rendering.md"`
|
|
87
88
|
|
|
88
89
|
If any required asset is missing, unreadable, truncated without continuation, or internally inconsistent, return:
|
|
89
90
|
|
|
@@ -304,7 +305,8 @@ If local idea capture succeeds but auth/workspace is missing, keep the idea and
|
|
|
304
305
|
|
|
305
306
|
## Step 1: Hook Research
|
|
306
307
|
|
|
307
|
-
Use `references/hook-research-playbook.md
|
|
308
|
+
Use `references/hook-research-playbook.md` and
|
|
309
|
+
`references/linkedin-preview-rendering.md`.
|
|
308
310
|
|
|
309
311
|
If the host supports background agents, delegate the search/fetch/autopsy work
|
|
310
312
|
to one bounded `research-worker` before hook candidate generation. The worker
|
|
@@ -321,7 +323,7 @@ The research worker must return a compact packet only:
|
|
|
321
323
|
- premise inputs: real scenes, observed tensions, reader value openings, and proof gaps
|
|
322
324
|
- exact phrase patterns and sentence shapes
|
|
323
325
|
- body structures and exact body language moves
|
|
324
|
-
- preview
|
|
326
|
+
- rendered mobile and desktop preview records
|
|
325
327
|
- track-person and gold-standard recommendations
|
|
326
328
|
- blocked states or confidence gaps
|
|
327
329
|
|
|
@@ -343,7 +345,9 @@ Default flow:
|
|
|
343
345
|
10. For story posts, extract the story mechanism that made the post work, not just the first line.
|
|
344
346
|
11. Extract hook structures plus specific reusable words, phrases, sentence
|
|
345
347
|
shapes, transitions, and body language patterns.
|
|
346
|
-
12.
|
|
348
|
+
12. Render each kept source hook and adapted hook block through the LinkedIn
|
|
349
|
+
preview rendering contract. Character counts alone are not enough.
|
|
350
|
+
13. Save the research with `mcp__sellable__save_hook_research`.
|
|
347
351
|
|
|
348
352
|
Record provenance:
|
|
349
353
|
|
|
@@ -357,7 +361,8 @@ Record provenance:
|
|
|
357
361
|
- lead-magnet or engagement-bait penalties
|
|
358
362
|
- story mechanism when relevant
|
|
359
363
|
- full-text match status
|
|
360
|
-
- source hook preview
|
|
364
|
+
- source hook rendered preview records and whether they came from full text,
|
|
365
|
+
authenticated LinkedIn screenshots, or a search preview
|
|
361
366
|
- selected hook patterns
|
|
362
367
|
- market belief map and selected controversy
|
|
363
368
|
- premise cards and selected premise
|
|
@@ -409,7 +414,10 @@ Premise cards:
|
|
|
409
414
|
Hook patterns learned:
|
|
410
415
|
1. full adapted hook block
|
|
411
416
|
- source mechanism:
|
|
412
|
-
- preview
|
|
417
|
+
- rendered preview:
|
|
418
|
+
- mobile visible block:
|
|
419
|
+
- desktop visible block:
|
|
420
|
+
- verdict:
|
|
413
421
|
- internal question:
|
|
414
422
|
- why it fits / why it does not:
|
|
415
423
|
|
|
@@ -484,6 +492,15 @@ Each hook must include:
|
|
|
484
492
|
- reader value implied
|
|
485
493
|
- source hook pattern
|
|
486
494
|
- why it fits this idea
|
|
495
|
+
- `renderedPreview` using `references/linkedin-preview-rendering.md`
|
|
496
|
+
- `mobileRenderedPreviewBlock`
|
|
497
|
+
- `desktopRenderedPreviewBlock`
|
|
498
|
+
- `mobileRenderedLines`
|
|
499
|
+
- `desktopRenderedLines`
|
|
500
|
+
- `firstScreenPromise`
|
|
501
|
+
- `corePainProofOrCuriosityVisibleMobile`
|
|
502
|
+
- `corePointVisibleMobile`
|
|
503
|
+
- `pointAfterMobileClamp`
|
|
487
504
|
- `charCount`
|
|
488
505
|
- `charCountIncludingNewlines`
|
|
489
506
|
- `firstLineChars`
|
|
@@ -504,22 +521,39 @@ Each hook must include:
|
|
|
504
521
|
|
|
505
522
|
Do not copy source wording. Copy only the structure.
|
|
506
523
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
-
|
|
513
|
-
-
|
|
514
|
-
-
|
|
524
|
+
Render every candidate before scoring it. The LinkedIn preview rendering
|
|
525
|
+
contract is the gate; character counts are secondary diagnostics only. Use the
|
|
526
|
+
deterministic CSS contract when no authenticated LinkedIn screenshot is
|
|
527
|
+
available:
|
|
528
|
+
|
|
529
|
+
- mobile text width: `308px`
|
|
530
|
+
- desktop text width: `582px`
|
|
531
|
+
- font size: `14px`
|
|
532
|
+
- line height: `21px`
|
|
533
|
+
- white space: `pre-wrap`
|
|
534
|
+
- overflow wrap: `break-word`
|
|
535
|
+
- review clamp: first 3 rendered text lines
|
|
536
|
+
|
|
537
|
+
Use the rendered gates from `references/linkedin-preview-rendering.md`:
|
|
538
|
+
|
|
539
|
+
- `pass`: the mobile rendered preview shows the pain, proof, or curiosity by
|
|
540
|
+
the end of the first 3 rendered lines, and the core point is understandable
|
|
541
|
+
without opening "see more".
|
|
542
|
+
- `warn`: the mobile rendered preview creates useful curiosity but the core
|
|
543
|
+
point is slightly softened by wrapping, blank-line rhythm, or one missing
|
|
544
|
+
context word. A compact fallback is required.
|
|
545
|
+
- `fail`: the hook's real point appears after the first 3 mobile rendered
|
|
546
|
+
lines, the first rendered line is generic setup, blank lines consume the
|
|
547
|
+
preview before the reader sees the point, or desktop fit is the only reason it
|
|
548
|
+
looks good.
|
|
515
549
|
|
|
516
550
|
Desktop preview usually has more room. Still record `desktopPreviewFit`, but
|
|
517
551
|
never let desktop fit compensate for a mobile `fail`.
|
|
518
552
|
|
|
519
|
-
If a hook's point depends on text after the
|
|
520
|
-
selecting it. A selected hook may carry a `warn` only when
|
|
521
|
-
|
|
522
|
-
|
|
553
|
+
If a hook's point depends on text after the rendered mobile preview clamp,
|
|
554
|
+
rewrite it before selecting it. A selected hook may carry a `warn` only when
|
|
555
|
+
the warning is explicit and the validation receipt includes a compact fallback.
|
|
556
|
+
A hook with no rendered mobile and desktop preview record cannot be selected.
|
|
523
557
|
|
|
524
558
|
## Step 3: Draft
|
|
525
559
|
|
|
@@ -553,6 +587,7 @@ Every saved draft needs a validation receipt with:
|
|
|
553
587
|
- story/proof files consulted
|
|
554
588
|
- gold standards consulted
|
|
555
589
|
- LinkedIn preview audit
|
|
590
|
+
- rendered mobile and desktop hook preview blocks
|
|
556
591
|
- premise/value audit findings
|
|
557
592
|
- simplifier/concrete-language audit findings
|
|
558
593
|
- voice audit findings
|
|
@@ -119,6 +119,8 @@ Show compact candidate cards. Include:
|
|
|
119
119
|
- why it might belong in the pack
|
|
120
120
|
- hook mechanism
|
|
121
121
|
- hook preview budget status and measurement basis
|
|
122
|
+
- rendered mobile preview block and verdict
|
|
123
|
+
- rendered desktop preview block and verdict
|
|
122
124
|
- content/body mechanism
|
|
123
125
|
- rhythm notes
|
|
124
126
|
- sentence-structure notes
|
|
@@ -194,6 +196,15 @@ Tags:
|
|
|
194
196
|
- longest nonblank line chars:
|
|
195
197
|
- blank-line visual risk:
|
|
196
198
|
- source text basis:
|
|
199
|
+
- render basis:
|
|
200
|
+
- css contract version:
|
|
201
|
+
- mobile rendered preview block:
|
|
202
|
+
- desktop rendered preview block:
|
|
203
|
+
- mobile rendered lines:
|
|
204
|
+
- desktop rendered lines:
|
|
205
|
+
- core pain/proof/curiosity visible on mobile:
|
|
206
|
+
- core point visible on mobile:
|
|
207
|
+
- point after mobile clamp:
|
|
197
208
|
- preview budget status:
|
|
198
209
|
- mobile preview fit:
|
|
199
210
|
- desktop preview fit:
|
|
@@ -31,7 +31,7 @@ Worker owns:
|
|
|
31
31
|
- lead-magnet, giveaway, engagement-bait, and off-voice filtering
|
|
32
32
|
- market belief mapping across kept and rejected examples
|
|
33
33
|
- premise input extraction: real scenes, observed tensions, reader value, proof gaps
|
|
34
|
-
- hook opening measurement
|
|
34
|
+
- rendered hook opening measurement
|
|
35
35
|
- exact phrase-pattern extraction
|
|
36
36
|
- body-structure extraction
|
|
37
37
|
- rejected-example notes
|
|
@@ -58,7 +58,7 @@ Research packet:
|
|
|
58
58
|
- exact phrase patterns: max 20
|
|
59
59
|
- body patterns: max 8
|
|
60
60
|
- source URLs and author profile URLs
|
|
61
|
-
- preview
|
|
61
|
+
- rendered preview records
|
|
62
62
|
- confidence gaps
|
|
63
63
|
- save recommendations
|
|
64
64
|
```
|
|
@@ -198,24 +198,48 @@ Measure the visible opening for every shortlisted source post before extracting
|
|
|
198
198
|
the hook pattern. This makes the study useful for LinkedIn, not just generally
|
|
199
199
|
"good writing."
|
|
200
200
|
|
|
201
|
-
LinkedIn does not publish exact
|
|
202
|
-
|
|
201
|
+
Use `references/linkedin-preview-rendering.md`. LinkedIn does not publish exact
|
|
202
|
+
"see more" cutoff rules. Character counts are diagnostics only. A source post
|
|
203
|
+
or generated hook is not properly studied until it has a rendered mobile and
|
|
204
|
+
desktop preview record.
|
|
203
205
|
|
|
204
|
-
|
|
205
|
-
is <= 45 chars, and the hook's core point lands before likely truncation.
|
|
206
|
-
- `warn`: opening hook is 111-140 chars including newlines, any nonblank line is
|
|
207
|
-
46-55 chars, or blank lines create visual-line risk. Blank lines are allowed,
|
|
208
|
-
but they count as physical lines.
|
|
209
|
-
- `fail`: opening hook is > 140 chars including newlines, any nonblank line is
|
|
210
|
-
> 55 chars, or the hook's core point depends on text after likely truncation.
|
|
206
|
+
Default deterministic renderer for unpublished drafts and research comparison:
|
|
211
207
|
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
```text
|
|
209
|
+
cssContractVersion: linkedin-preview-rendering/v1
|
|
210
|
+
font size: 14px
|
|
211
|
+
line height: 21px
|
|
212
|
+
white space: pre-wrap
|
|
213
|
+
overflow wrap: break-word
|
|
214
|
+
mobile text width: 308px
|
|
215
|
+
desktop text width: 582px
|
|
216
|
+
review clamp: first 3 rendered text lines
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Authenticated LinkedIn feed/composer screenshots are stronger evidence when
|
|
220
|
+
available. If a screenshot is used, still record the wrapped lines and verdicts
|
|
221
|
+
below so another agent can compare hooks without re-opening LinkedIn.
|
|
222
|
+
|
|
223
|
+
Desktop preview has more room, but never let desktop fit compensate for a
|
|
224
|
+
mobile `fail`.
|
|
214
225
|
|
|
215
226
|
For each source, record:
|
|
216
227
|
|
|
217
228
|
- `sourceTextBasis`: `full_text`, `search_preview`, or `manual_user_source`
|
|
218
229
|
- `openingTextUsed`
|
|
230
|
+
- `renderedPreview`
|
|
231
|
+
- `cssContractVersion`
|
|
232
|
+
- `mobileRenderedPreviewBlock`
|
|
233
|
+
- `desktopRenderedPreviewBlock`
|
|
234
|
+
- `mobileRenderedLines`
|
|
235
|
+
- `desktopRenderedLines`
|
|
236
|
+
- `mobileRenderedLineCount`
|
|
237
|
+
- `desktopRenderedLineCount`
|
|
238
|
+
- `corePainProofOrCuriosityVisibleMobile`
|
|
239
|
+
- `corePainProofOrCuriosityVisibleDesktop`
|
|
240
|
+
- `corePointVisibleMobile`
|
|
241
|
+
- `corePointVisibleDesktop`
|
|
242
|
+
- `pointAfterMobileClamp`
|
|
219
243
|
- `charCountIncludingNewlines`
|
|
220
244
|
- `physicalLineCount`
|
|
221
245
|
- `contentLineCount`
|
|
@@ -228,11 +252,25 @@ For each source, record:
|
|
|
228
252
|
- `desktopPreviewBudget`: `pass`, `warn`, or `fail`
|
|
229
253
|
- `blankLineVisualRisk`
|
|
230
254
|
- `corePointBeforeLikelyTruncation`
|
|
255
|
+
- `renderedPreviewVerdict`: `pass`, `warn`, or `fail`
|
|
231
256
|
|
|
232
257
|
If only a search preview is available, do not pretend the opening is complete.
|
|
233
258
|
Record `sourceTextBasis: search_preview` and lower confidence when the hook
|
|
234
259
|
appears cut off or body context is unavailable.
|
|
235
260
|
|
|
261
|
+
Pass/warn/fail is based on rendered output:
|
|
262
|
+
|
|
263
|
+
- `pass`: the mobile rendered preview shows the pain, proof, or curiosity by
|
|
264
|
+
the end of the first 3 rendered lines, and the core point is understandable
|
|
265
|
+
without opening "see more".
|
|
266
|
+
- `warn`: the mobile rendered preview creates useful curiosity but the core
|
|
267
|
+
point is slightly softened by wrapping, blank-line rhythm, or one missing
|
|
268
|
+
context word. A compact fallback is required.
|
|
269
|
+
- `fail`: the hook's real point appears after the first 3 mobile rendered
|
|
270
|
+
lines, the first rendered line is generic setup, blank lines consume the
|
|
271
|
+
preview before the reader sees the point, or desktop fit is the only reason it
|
|
272
|
+
looks good.
|
|
273
|
+
|
|
236
274
|
## Hook Extraction
|
|
237
275
|
|
|
238
276
|
Extract structure and reusable language patterns, not copied prose. The goal is
|
|
@@ -248,7 +286,7 @@ For each shortlisted source post, record:
|
|
|
248
286
|
- engagement totals and available likes/comments/shares breakdown
|
|
249
287
|
- creator repeat evidence
|
|
250
288
|
- visible hook text or preview
|
|
251
|
-
-
|
|
289
|
+
- rendered preview fields from the section above
|
|
252
290
|
- hook mechanism
|
|
253
291
|
- exact hook language patterns: reusable words, phrase shapes, contrast forms,
|
|
254
292
|
sentence shapes, and transition moves
|
|
@@ -262,6 +300,11 @@ For each shortlisted source post, record:
|
|
|
262
300
|
- track person recommendation: `yes`, `no`, or `ask_user`
|
|
263
301
|
- tracking reason when recommended
|
|
264
302
|
|
|
303
|
+
Do not call a source hook a keeper because the full text is strong if the
|
|
304
|
+
rendered mobile opening is weak. Study what a LinkedIn reader sees before
|
|
305
|
+
"see more"; the body structure can still be useful, but the hook should not be
|
|
306
|
+
copied into the hook pattern set.
|
|
307
|
+
|
|
265
308
|
## Specific Language Extraction
|
|
266
309
|
|
|
267
310
|
For each keeper, extract the source's specific language mechanics in this
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# LinkedIn Preview Rendering
|
|
2
|
+
|
|
3
|
+
This asset defines the required rendered-preview contract for create-post hook
|
|
4
|
+
research, hook candidate generation, gold-standard decomposition, and draft
|
|
5
|
+
validation.
|
|
6
|
+
|
|
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.
|
|
10
|
+
|
|
11
|
+
## Rendering Basis
|
|
12
|
+
|
|
13
|
+
LinkedIn does not publish exact feed truncation rules, and rendering can vary by
|
|
14
|
+
surface, app version, device, media attachment, and account state. Use this
|
|
15
|
+
deterministic renderer as the MCP review gate for unpublished drafts.
|
|
16
|
+
|
|
17
|
+
When an authenticated LinkedIn feed/composer/browser screenshot is available,
|
|
18
|
+
that screenshot is the strongest evidence. Still record the fields below so
|
|
19
|
+
future agents can compare candidates without redoing the visual inspection.
|
|
20
|
+
|
|
21
|
+
Observed public LinkedIn post text style for feed-style post pages:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
selector basis: p.attributed-text-segment-list__content
|
|
25
|
+
font family: -apple-system, system-ui, "Segoe UI", Roboto, "Helvetica Neue",
|
|
26
|
+
"Fira Sans", Ubuntu, "Oxygen Sans", Cantarell, "Droid Sans",
|
|
27
|
+
"Lucida Grande", Helvetica, Arial, sans-serif
|
|
28
|
+
font size: 14px
|
|
29
|
+
line height: 21px
|
|
30
|
+
font weight: 400
|
|
31
|
+
letter spacing: normal
|
|
32
|
+
white space: pre-wrap
|
|
33
|
+
overflow wrap: break-word
|
|
34
|
+
text color: rgba(0, 0, 0, 0.9)
|
|
35
|
+
mobile text width: 308px
|
|
36
|
+
desktop text width: 582px
|
|
37
|
+
review clamp: first 3 rendered text lines
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The `review clamp` is not an official LinkedIn rule. It is the conservative
|
|
41
|
+
apples-to-apples region create-post must use when comparing hooks. Do not let a
|
|
42
|
+
desktop pass rescue a mobile fail.
|
|
43
|
+
|
|
44
|
+
## Required Rendering Record
|
|
45
|
+
|
|
46
|
+
Every shortlisted source hook, adapted hook block, generated hook candidate, and
|
|
47
|
+
selected hook must include a `renderedPreview` record:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
renderedPreview:
|
|
51
|
+
basis: linkedin_css_contract | authenticated_linkedin_screenshot | manual_user_source
|
|
52
|
+
cssContractVersion: linkedin-preview-rendering/v1
|
|
53
|
+
mobile:
|
|
54
|
+
textWidthPx: 308
|
|
55
|
+
fontSizePx: 14
|
|
56
|
+
lineHeightPx: 21
|
|
57
|
+
visibleTextBlock: <literal first rendered review-clamp block>
|
|
58
|
+
renderedLines:
|
|
59
|
+
- <line 1 exactly as wrapped>
|
|
60
|
+
- <line 2 exactly as wrapped>
|
|
61
|
+
- <line 3 exactly as wrapped>
|
|
62
|
+
lineCountBeforeClamp: <number>
|
|
63
|
+
blankLinesBeforeClamp: <number>
|
|
64
|
+
corePainProofOrCuriosityVisible: true | false
|
|
65
|
+
corePointVisible: true | false
|
|
66
|
+
seeMoreRisk: pass | warn | fail
|
|
67
|
+
screenshotPath: <optional local path>
|
|
68
|
+
desktop:
|
|
69
|
+
textWidthPx: 582
|
|
70
|
+
fontSizePx: 14
|
|
71
|
+
lineHeightPx: 21
|
|
72
|
+
visibleTextBlock: <literal first rendered review-clamp block>
|
|
73
|
+
renderedLines:
|
|
74
|
+
- <line 1 exactly as wrapped>
|
|
75
|
+
- <line 2 exactly as wrapped>
|
|
76
|
+
- <line 3 exactly as wrapped>
|
|
77
|
+
lineCountBeforeClamp: <number>
|
|
78
|
+
blankLinesBeforeClamp: <number>
|
|
79
|
+
corePainProofOrCuriosityVisible: true | false
|
|
80
|
+
corePointVisible: true | false
|
|
81
|
+
seeMoreRisk: pass | warn | fail
|
|
82
|
+
screenshotPath: <optional local path>
|
|
83
|
+
diagnostics:
|
|
84
|
+
charCount: <number>
|
|
85
|
+
charCountIncludingNewlines: <number>
|
|
86
|
+
firstLineChars: <number>
|
|
87
|
+
firstTwoPhysicalLinesChars: <number>
|
|
88
|
+
longestNonblankLineChars: <number>
|
|
89
|
+
blankLineVisualRisk: none | low | medium | high
|
|
90
|
+
pointAfterMobileClamp: true | false
|
|
91
|
+
rewriteIfTruncated: <short fallback>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If a host cannot produce screenshots, it must still produce the literal wrapped
|
|
95
|
+
line blocks using the CSS contract. If it cannot produce either screenshots or
|
|
96
|
+
literal line wraps, return `blocked` or `needs_revision`; do not claim the hook
|
|
97
|
+
passed preview validation from character counts alone.
|
|
98
|
+
|
|
99
|
+
## Study Rules
|
|
100
|
+
|
|
101
|
+
For source-post research:
|
|
102
|
+
|
|
103
|
+
- Render the exact visible opening from full text when full text is available.
|
|
104
|
+
- If only a search preview is available, render only the preview text and set
|
|
105
|
+
`basis: manual_user_source` or record `sourceTextBasis: search_preview`.
|
|
106
|
+
- Lower confidence when the search preview is cut off or the body is
|
|
107
|
+
unavailable.
|
|
108
|
+
- Extract hook lessons from the rendered first-screen experience, not from the
|
|
109
|
+
full post in isolation.
|
|
110
|
+
|
|
111
|
+
For generated hooks:
|
|
112
|
+
|
|
113
|
+
- Generate the hook from the selected premise first.
|
|
114
|
+
- Render the hook for mobile and desktop before scoring it.
|
|
115
|
+
- Score the rendered first-screen promise before scoring cleverness.
|
|
116
|
+
- Rewrite any candidate whose real point appears after the mobile clamp.
|
|
117
|
+
|
|
118
|
+
## Pass, Warn, Fail
|
|
119
|
+
|
|
120
|
+
Use these rendered gates:
|
|
121
|
+
|
|
122
|
+
- `pass`: the mobile rendered preview shows the pain, proof, or curiosity by the
|
|
123
|
+
end of the first 3 rendered lines, and the core point is understandable
|
|
124
|
+
without opening "see more".
|
|
125
|
+
- `warn`: the mobile rendered preview creates useful curiosity but the core
|
|
126
|
+
point is slightly softened by wrapping, blank-line rhythm, or one missing
|
|
127
|
+
context word. A compact fallback is required.
|
|
128
|
+
- `fail`: the hook's real point appears after the first 3 mobile rendered lines,
|
|
129
|
+
the first rendered line is generic setup, blank lines consume the preview
|
|
130
|
+
before the reader sees the point, or desktop fit is the only reason it looks
|
|
131
|
+
good.
|
|
132
|
+
|
|
133
|
+
A draft cannot be `ready` when the selected hook has:
|
|
134
|
+
|
|
135
|
+
- no `renderedPreview`
|
|
136
|
+
- `mobile.seeMoreRisk: fail`
|
|
137
|
+
- `mobile.corePainProofOrCuriosityVisible: false`
|
|
138
|
+
- `mobile.corePointVisible: false`
|
|
139
|
+
- `pointAfterMobileClamp: true`
|
|
140
|
+
|
|
141
|
+
## Report Format
|
|
142
|
+
|
|
143
|
+
Research reports must show rendered preview blocks for the best examples and
|
|
144
|
+
recommended draft directions:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
Rendered preview:
|
|
148
|
+
mobile:
|
|
149
|
+
<line 1>
|
|
150
|
+
<line 2>
|
|
151
|
+
<line 3>
|
|
152
|
+
|
|
153
|
+
desktop:
|
|
154
|
+
<line 1>
|
|
155
|
+
<line 2>
|
|
156
|
+
<line 3>
|
|
157
|
+
|
|
158
|
+
verdict: pass | warn | fail
|
|
159
|
+
why: <what is visible before the fold>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Do not say "it fits on mobile" without showing what the mobile reader actually
|
|
163
|
+
sees.
|
|
@@ -63,6 +63,10 @@ Hook research files must preserve:
|
|
|
63
63
|
- source hook preview measurements, including text basis, char count including
|
|
64
64
|
newlines, physical/content line counts, longest nonblank line, blank-line
|
|
65
65
|
visual risk, and mobile/desktop preview budget status
|
|
66
|
+
- rendered preview records for every kept source hook and adapted hook block,
|
|
67
|
+
including literal mobile/desktop preview blocks, rendered line wraps, render
|
|
68
|
+
basis, CSS contract version, text widths, first-screen promise visibility,
|
|
69
|
+
core point visibility, and whether the point lands after the mobile clamp
|
|
66
70
|
- extracted hook patterns
|
|
67
71
|
- selected hook basis
|
|
68
72
|
|
|
@@ -83,6 +87,9 @@ Draft files must preserve:
|
|
|
83
87
|
- draft body
|
|
84
88
|
- validation receipt, including LinkedIn preview pass/warn/fail status and
|
|
85
89
|
compact fallback when the selected hook carries a warning
|
|
90
|
+
- rendered mobile and desktop preview blocks for the selected hook; drafts
|
|
91
|
+
cannot be ready when this rendered-preview audit is missing or fails mobile
|
|
92
|
+
visibility
|
|
86
93
|
- status: `draft`, `ready`, `needs_revision`, `published`, or `archived`
|
|
87
94
|
|
|
88
95
|
Multiple drafts for the same idea are expected. Keep the `ideaId` stable and
|