@sellable/mcp 0.1.261 → 0.1.263
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/server.js +4 -1
- package/dist/tools/content-posts.d.ts +222 -1
- package/dist/tools/content-posts.js +504 -0
- package/dist/tools/registry.d.ts +33 -0
- package/package.json +1 -1
- package/skills/create-post/SKILL.md +58 -21
- package/skills/create-post/references/hook-research-playbook.md +93 -18
- package/skills/create-post/references/linkedin-preview-rendering.md +51 -10
- package/skills/create-post/references/post-file-contract.md +6 -0
- package/skills/create-post/references/post-validation.md +31 -9
- package/skills/create-post/references/premise-development.md +10 -6
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
4
6
|
const CONTENT_ROOT_ENV = "SELLABLE_CONTENT_DIR";
|
|
5
7
|
const DEFAULT_PREVIEW_CHARS = 220;
|
|
6
8
|
const RELATIVE_DIRS = {
|
|
@@ -8,6 +10,7 @@ const RELATIVE_DIRS = {
|
|
|
8
10
|
hookResearch: "linkedin/research/hooks",
|
|
9
11
|
drafts: "linkedin/drafts",
|
|
10
12
|
published: "linkedin/published",
|
|
13
|
+
previews: "linkedin/previews",
|
|
11
14
|
commentLibrary: "linkedin/comments/library",
|
|
12
15
|
};
|
|
13
16
|
export const contentPostToolDefinitions = [
|
|
@@ -104,6 +107,43 @@ export const contentPostToolDefinitions = [
|
|
|
104
107
|
additionalProperties: false,
|
|
105
108
|
},
|
|
106
109
|
},
|
|
110
|
+
{
|
|
111
|
+
name: "render_linkedin_post_preview",
|
|
112
|
+
description: "Render any LinkedIn post text through the Sellable LinkedIn preview CSS contract for mobile and desktop. Writes HTML artifacts and, when local Chrome is available, browser screenshots under ~/.sellable/content/linkedin/previews.",
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
postText: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description: "Full post text to render. Provide either postText or draftId.",
|
|
119
|
+
},
|
|
120
|
+
draftId: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "Optional saved draft ID. When postText is omitted, the tool renders this draft's Draft Body.",
|
|
123
|
+
},
|
|
124
|
+
renderId: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "Optional stable render ID. Must be a safe filename without slashes.",
|
|
127
|
+
},
|
|
128
|
+
sourceLabel: { type: "string" },
|
|
129
|
+
createdAt: { type: "string" },
|
|
130
|
+
renderScreenshots: {
|
|
131
|
+
type: "boolean",
|
|
132
|
+
description: "Defaults to true. When true, tries to use local Chrome headless to write PNG screenshots.",
|
|
133
|
+
},
|
|
134
|
+
requireScreenshots: {
|
|
135
|
+
type: "boolean",
|
|
136
|
+
description: "Defaults to false. When true, fail if local Chrome cannot produce PNG screenshots.",
|
|
137
|
+
},
|
|
138
|
+
reviewClampLines: {
|
|
139
|
+
type: "number",
|
|
140
|
+
description: "Visible review lines before see-more. Defaults to 3.",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
required: [],
|
|
144
|
+
additionalProperties: false,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
107
147
|
{
|
|
108
148
|
name: "update_post_draft",
|
|
109
149
|
description: "Update an existing LinkedIn post draft in place while preserving omitted fields. Useful for adding iteration receipts, status changes, or final copy edits without rewriting the whole artifact.",
|
|
@@ -389,6 +429,109 @@ export function savePostDraftTool(input) {
|
|
|
389
429
|
preview: sanitizedPreview(input.body),
|
|
390
430
|
};
|
|
391
431
|
}
|
|
432
|
+
export function renderLinkedInPostPreviewTool(input) {
|
|
433
|
+
const now = normalizeDate(input.createdAt);
|
|
434
|
+
const draftId = input.draftId
|
|
435
|
+
? normalizeArtifactId(input.draftId, "draftId")
|
|
436
|
+
: undefined;
|
|
437
|
+
const draftBody = draftId
|
|
438
|
+
? extractMarkdownSection(getArtifact(RELATIVE_DIRS.drafts, draftId, "draftId").markdown, "Draft Body")
|
|
439
|
+
: undefined;
|
|
440
|
+
const postText = input.postText ?? draftBody;
|
|
441
|
+
requireString(postText, "postText");
|
|
442
|
+
const reviewClampLines = typeof input.reviewClampLines === "number" &&
|
|
443
|
+
Number.isFinite(input.reviewClampLines)
|
|
444
|
+
? Math.max(1, Math.min(Math.floor(input.reviewClampLines), 8))
|
|
445
|
+
: 3;
|
|
446
|
+
const id = input.renderId ??
|
|
447
|
+
`preview_${dateStamp(now)}_${slugify(input.sourceLabel || draftId || postText)}`;
|
|
448
|
+
const safeId = normalizeArtifactId(id, "renderId");
|
|
449
|
+
const baseDir = `${RELATIVE_DIRS.previews}/${safeId}`;
|
|
450
|
+
const title = input.sourceLabel || draftId || safeId;
|
|
451
|
+
const renderScreenshots = input.renderScreenshots !== false;
|
|
452
|
+
const mobile = buildPreviewSurface({
|
|
453
|
+
label: "mobile",
|
|
454
|
+
text: postText,
|
|
455
|
+
baseDir,
|
|
456
|
+
textWidthPx: 308,
|
|
457
|
+
viewportWidthPx: 390,
|
|
458
|
+
viewportHeightPx: 760,
|
|
459
|
+
reviewClampLines,
|
|
460
|
+
});
|
|
461
|
+
const desktop = buildPreviewSurface({
|
|
462
|
+
label: "desktop",
|
|
463
|
+
text: postText,
|
|
464
|
+
baseDir,
|
|
465
|
+
textWidthPx: 582,
|
|
466
|
+
viewportWidthPx: 744,
|
|
467
|
+
viewportHeightPx: 760,
|
|
468
|
+
reviewClampLines,
|
|
469
|
+
});
|
|
470
|
+
const combinedHtmlRelativePath = `${baseDir}/preview.html`;
|
|
471
|
+
const combinedHtml = buildCombinedPreviewHtml({
|
|
472
|
+
title,
|
|
473
|
+
postText,
|
|
474
|
+
mobile,
|
|
475
|
+
desktop,
|
|
476
|
+
});
|
|
477
|
+
writeContentFile(combinedHtmlRelativePath, combinedHtml);
|
|
478
|
+
const screenshotResult = renderScreenshots
|
|
479
|
+
? renderPreviewScreenshots([mobile, desktop], input.requireScreenshots)
|
|
480
|
+
: { status: "skipped", reason: "renderScreenshots=false" };
|
|
481
|
+
const metadata = {
|
|
482
|
+
id: safeId,
|
|
483
|
+
type: "linkedin_preview",
|
|
484
|
+
status: screenshotResult.status === "rendered" ? "rendered" : "html_rendered",
|
|
485
|
+
title,
|
|
486
|
+
draftId,
|
|
487
|
+
createdAt: now,
|
|
488
|
+
updatedAt: now,
|
|
489
|
+
};
|
|
490
|
+
const renderedPreview = {
|
|
491
|
+
basis: screenshotResult.status === "rendered"
|
|
492
|
+
? "local_chrome_headless_screenshot"
|
|
493
|
+
: "linkedin_css_contract_html",
|
|
494
|
+
cssContractVersion: "linkedin-preview-rendering/v1",
|
|
495
|
+
generatedAt: now,
|
|
496
|
+
sourceLabel: title,
|
|
497
|
+
mobile: surfaceRecord(mobile),
|
|
498
|
+
desktop: surfaceRecord(desktop),
|
|
499
|
+
diagnostics: previewDiagnostics(postText),
|
|
500
|
+
screenshotStatus: screenshotResult,
|
|
501
|
+
};
|
|
502
|
+
const recordRelativePath = `${baseDir}/render.md`;
|
|
503
|
+
const metadataJsonRelativePath = `${baseDir}/metadata.json`;
|
|
504
|
+
writeContentFile(metadataJsonRelativePath, `${JSON.stringify(renderedPreview, null, 2)}\n`);
|
|
505
|
+
writeArtifact(recordRelativePath, buildMarkdown(metadata, [
|
|
506
|
+
["Rendered Preview Record", jsonBlock(renderedPreview)],
|
|
507
|
+
[
|
|
508
|
+
"Artifacts",
|
|
509
|
+
[
|
|
510
|
+
`- combinedHtml: ${combinedHtmlRelativePath}`,
|
|
511
|
+
`- mobileHtml: ${mobile.htmlPath}`,
|
|
512
|
+
mobile.screenshotPath
|
|
513
|
+
? `- mobileScreenshot: ${mobile.screenshotPath}`
|
|
514
|
+
: "- mobileScreenshot: none",
|
|
515
|
+
`- desktopHtml: ${desktop.htmlPath}`,
|
|
516
|
+
desktop.screenshotPath
|
|
517
|
+
? `- desktopScreenshot: ${desktop.screenshotPath}`
|
|
518
|
+
: "- desktopScreenshot: none",
|
|
519
|
+
].join("\n"),
|
|
520
|
+
],
|
|
521
|
+
]));
|
|
522
|
+
return {
|
|
523
|
+
id: safeId,
|
|
524
|
+
path: recordRelativePath,
|
|
525
|
+
artifactDir: baseDir,
|
|
526
|
+
absoluteArtifactDir: safePath(resolveContentRoot(), baseDir),
|
|
527
|
+
combinedHtmlPath: combinedHtmlRelativePath,
|
|
528
|
+
absoluteCombinedHtmlPath: safePath(resolveContentRoot(), combinedHtmlRelativePath),
|
|
529
|
+
mobile: surfaceReturnRecord(mobile),
|
|
530
|
+
desktop: surfaceReturnRecord(desktop),
|
|
531
|
+
screenshotStatus: screenshotResult,
|
|
532
|
+
renderedPreview,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
392
535
|
export function updatePostDraftTool(input) {
|
|
393
536
|
const safeDraftId = normalizeArtifactId(input.draftId, "draftId");
|
|
394
537
|
const relativePath = `${RELATIVE_DIRS.drafts}/${safeDraftId}.md`;
|
|
@@ -617,6 +760,362 @@ export function listPublishedPostsTool(input) {
|
|
|
617
760
|
}
|
|
618
761
|
return summarizeArtifacts(artifacts, input?.limit);
|
|
619
762
|
}
|
|
763
|
+
function buildPreviewSurface(input) {
|
|
764
|
+
const estimatedLines = estimateRenderedLines(input.text, input.textWidthPx);
|
|
765
|
+
const htmlPath = `${input.baseDir}/${input.label}.html`;
|
|
766
|
+
const surface = {
|
|
767
|
+
label: input.label,
|
|
768
|
+
textWidthPx: input.textWidthPx,
|
|
769
|
+
viewportWidthPx: input.viewportWidthPx,
|
|
770
|
+
viewportHeightPx: input.viewportHeightPx,
|
|
771
|
+
fontSizePx: 14,
|
|
772
|
+
lineHeightPx: 21,
|
|
773
|
+
reviewClampLines: input.reviewClampLines,
|
|
774
|
+
estimatedLines,
|
|
775
|
+
estimatedVisibleTextBlock: estimatedLines
|
|
776
|
+
.slice(0, input.reviewClampLines)
|
|
777
|
+
.join("\n"),
|
|
778
|
+
estimatedLineCountBeforeClamp: estimatedLines.length,
|
|
779
|
+
estimatedBlankLinesBeforeClamp: estimatedLines
|
|
780
|
+
.slice(0, input.reviewClampLines)
|
|
781
|
+
.filter((line) => line.trim().length === 0).length,
|
|
782
|
+
htmlPath,
|
|
783
|
+
absoluteHtmlPath: safePath(resolveContentRoot(), htmlPath),
|
|
784
|
+
};
|
|
785
|
+
writeContentFile(htmlPath, buildSinglePreviewHtml({
|
|
786
|
+
title: `${input.label} LinkedIn preview`,
|
|
787
|
+
postText: input.text,
|
|
788
|
+
surface,
|
|
789
|
+
}));
|
|
790
|
+
return surface;
|
|
791
|
+
}
|
|
792
|
+
function buildSinglePreviewHtml(input) {
|
|
793
|
+
return htmlDocument({
|
|
794
|
+
title: input.title,
|
|
795
|
+
bodyClass: input.surface.label,
|
|
796
|
+
body: linkedInCardHtml(input.postText, input.surface),
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
function buildCombinedPreviewHtml(input) {
|
|
800
|
+
return htmlDocument({
|
|
801
|
+
title: `LinkedIn preview: ${input.title}`,
|
|
802
|
+
bodyClass: "combined",
|
|
803
|
+
body: [
|
|
804
|
+
`<main class="combined-grid">`,
|
|
805
|
+
`<section><h1>Mobile preview</h1>${linkedInCardHtml(input.postText, input.mobile)}</section>`,
|
|
806
|
+
`<section><h1>Desktop preview</h1>${linkedInCardHtml(input.postText, input.desktop)}</section>`,
|
|
807
|
+
`</main>`,
|
|
808
|
+
].join("\n"),
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
function htmlDocument(input) {
|
|
812
|
+
return [
|
|
813
|
+
"<!doctype html>",
|
|
814
|
+
'<html lang="en">',
|
|
815
|
+
"<head>",
|
|
816
|
+
'<meta charset="utf-8">',
|
|
817
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
818
|
+
`<title>${escapeHtml(input.title)}</title>`,
|
|
819
|
+
"<style>",
|
|
820
|
+
linkedinPreviewCss(),
|
|
821
|
+
"</style>",
|
|
822
|
+
"</head>",
|
|
823
|
+
`<body class="${escapeHtml(input.bodyClass)}">`,
|
|
824
|
+
input.body,
|
|
825
|
+
"</body>",
|
|
826
|
+
"</html>",
|
|
827
|
+
"",
|
|
828
|
+
].join("\n");
|
|
829
|
+
}
|
|
830
|
+
function linkedInCardHtml(postText, surface) {
|
|
831
|
+
const escapedText = escapeHtml(postText);
|
|
832
|
+
const cardWidth = surface.textWidthPx + 32;
|
|
833
|
+
return [
|
|
834
|
+
`<article class="linkedin-card" data-surface="${surface.label}" style="width:${cardWidth}px">`,
|
|
835
|
+
'<header class="post-header" aria-hidden="true">',
|
|
836
|
+
'<div class="avatar"></div>',
|
|
837
|
+
'<div class="byline"><div class="name">Preview Author</div><div class="meta">LinkedIn preview contract</div></div>',
|
|
838
|
+
"</header>",
|
|
839
|
+
`<div class="post-text" style="width:${surface.textWidthPx}px;-webkit-line-clamp:${surface.reviewClampLines}">${escapedText}</div>`,
|
|
840
|
+
surface.estimatedLineCountBeforeClamp > surface.reviewClampLines
|
|
841
|
+
? '<div class="see-more">...see more</div>'
|
|
842
|
+
: "",
|
|
843
|
+
'<div class="post-actions" aria-hidden="true"><span>Like</span><span>Comment</span><span>Repost</span></div>',
|
|
844
|
+
"</article>",
|
|
845
|
+
].join("\n");
|
|
846
|
+
}
|
|
847
|
+
function linkedinPreviewCss() {
|
|
848
|
+
return `
|
|
849
|
+
:root {
|
|
850
|
+
color-scheme: light;
|
|
851
|
+
--linkedin-text: rgba(0, 0, 0, 0.9);
|
|
852
|
+
--linkedin-muted: rgba(0, 0, 0, 0.6);
|
|
853
|
+
--linkedin-border: #e0dfdc;
|
|
854
|
+
--linkedin-bg: #f3f2ef;
|
|
855
|
+
}
|
|
856
|
+
* { box-sizing: border-box; }
|
|
857
|
+
body {
|
|
858
|
+
margin: 0;
|
|
859
|
+
min-height: 100vh;
|
|
860
|
+
background: var(--linkedin-bg);
|
|
861
|
+
font-family: -apple-system, system-ui, "Segoe UI", Roboto, "Helvetica Neue",
|
|
862
|
+
"Fira Sans", Ubuntu, "Oxygen Sans", Cantarell, "Droid Sans",
|
|
863
|
+
"Lucida Grande", Helvetica, Arial, sans-serif;
|
|
864
|
+
}
|
|
865
|
+
body.mobile, body.desktop {
|
|
866
|
+
display: flex;
|
|
867
|
+
align-items: flex-start;
|
|
868
|
+
justify-content: center;
|
|
869
|
+
padding: 20px;
|
|
870
|
+
}
|
|
871
|
+
.combined-grid {
|
|
872
|
+
display: grid;
|
|
873
|
+
grid-template-columns: max-content max-content;
|
|
874
|
+
gap: 28px;
|
|
875
|
+
align-items: start;
|
|
876
|
+
padding: 20px;
|
|
877
|
+
}
|
|
878
|
+
h1 {
|
|
879
|
+
margin: 0 0 10px;
|
|
880
|
+
font-size: 14px;
|
|
881
|
+
line-height: 20px;
|
|
882
|
+
font-weight: 600;
|
|
883
|
+
color: var(--linkedin-muted);
|
|
884
|
+
}
|
|
885
|
+
.linkedin-card {
|
|
886
|
+
background: #fff;
|
|
887
|
+
border: 1px solid var(--linkedin-border);
|
|
888
|
+
border-radius: 8px;
|
|
889
|
+
padding: 12px 16px 10px;
|
|
890
|
+
color: var(--linkedin-text);
|
|
891
|
+
}
|
|
892
|
+
.post-header {
|
|
893
|
+
display: flex;
|
|
894
|
+
align-items: center;
|
|
895
|
+
gap: 8px;
|
|
896
|
+
margin-bottom: 8px;
|
|
897
|
+
}
|
|
898
|
+
.avatar {
|
|
899
|
+
width: 40px;
|
|
900
|
+
height: 40px;
|
|
901
|
+
border-radius: 50%;
|
|
902
|
+
background: linear-gradient(135deg, #d7dce0, #eef0f2);
|
|
903
|
+
}
|
|
904
|
+
.byline { min-width: 0; }
|
|
905
|
+
.name {
|
|
906
|
+
height: 16px;
|
|
907
|
+
font-size: 14px;
|
|
908
|
+
line-height: 16px;
|
|
909
|
+
font-weight: 600;
|
|
910
|
+
}
|
|
911
|
+
.meta {
|
|
912
|
+
font-size: 12px;
|
|
913
|
+
line-height: 14px;
|
|
914
|
+
color: var(--linkedin-muted);
|
|
915
|
+
}
|
|
916
|
+
.post-text {
|
|
917
|
+
font-size: 14px;
|
|
918
|
+
line-height: 21px;
|
|
919
|
+
font-weight: 400;
|
|
920
|
+
letter-spacing: normal;
|
|
921
|
+
white-space: pre-wrap;
|
|
922
|
+
overflow-wrap: break-word;
|
|
923
|
+
color: var(--linkedin-text);
|
|
924
|
+
overflow: hidden;
|
|
925
|
+
display: -webkit-box;
|
|
926
|
+
-webkit-box-orient: vertical;
|
|
927
|
+
}
|
|
928
|
+
.see-more {
|
|
929
|
+
margin-top: 2px;
|
|
930
|
+
font-size: 14px;
|
|
931
|
+
line-height: 21px;
|
|
932
|
+
color: var(--linkedin-muted);
|
|
933
|
+
}
|
|
934
|
+
.post-actions {
|
|
935
|
+
display: flex;
|
|
936
|
+
gap: 18px;
|
|
937
|
+
border-top: 1px solid var(--linkedin-border);
|
|
938
|
+
margin-top: 10px;
|
|
939
|
+
padding-top: 8px;
|
|
940
|
+
color: var(--linkedin-muted);
|
|
941
|
+
font-size: 13px;
|
|
942
|
+
line-height: 18px;
|
|
943
|
+
}
|
|
944
|
+
`;
|
|
945
|
+
}
|
|
946
|
+
function renderPreviewScreenshots(surfaces, requireScreenshots) {
|
|
947
|
+
const chromePath = findChromeExecutable();
|
|
948
|
+
if (!chromePath) {
|
|
949
|
+
if (requireScreenshots) {
|
|
950
|
+
throw new Error("Chrome executable not found. Set CHROME_PATH or install Google Chrome to render LinkedIn preview screenshots.");
|
|
951
|
+
}
|
|
952
|
+
return { status: "skipped", reason: "chrome_not_found" };
|
|
953
|
+
}
|
|
954
|
+
try {
|
|
955
|
+
for (const surface of surfaces) {
|
|
956
|
+
const screenshotPath = surface.htmlPath.replace(/\.html$/, ".png");
|
|
957
|
+
const absoluteScreenshotPath = safePath(resolveContentRoot(), screenshotPath);
|
|
958
|
+
execFileSync(chromePath, [
|
|
959
|
+
"--headless=new",
|
|
960
|
+
"--disable-gpu",
|
|
961
|
+
"--no-sandbox",
|
|
962
|
+
"--disable-dev-shm-usage",
|
|
963
|
+
"--hide-scrollbars",
|
|
964
|
+
`--screenshot=${absoluteScreenshotPath}`,
|
|
965
|
+
`--window-size=${surface.viewportWidthPx},${surface.viewportHeightPx}`,
|
|
966
|
+
pathToFileURL(surface.absoluteHtmlPath).href,
|
|
967
|
+
], { stdio: "ignore", timeout: 20000 });
|
|
968
|
+
surface.screenshotPath = screenshotPath;
|
|
969
|
+
surface.absoluteScreenshotPath = absoluteScreenshotPath;
|
|
970
|
+
}
|
|
971
|
+
return { status: "rendered", chromePath };
|
|
972
|
+
}
|
|
973
|
+
catch (error) {
|
|
974
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
975
|
+
if (requireScreenshots) {
|
|
976
|
+
throw new Error(`Chrome screenshot render failed: ${reason}`);
|
|
977
|
+
}
|
|
978
|
+
return { status: "failed", reason, chromePath };
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function findChromeExecutable() {
|
|
982
|
+
const candidates = [
|
|
983
|
+
process.env.CHROME_PATH,
|
|
984
|
+
process.env.GOOGLE_CHROME_BIN,
|
|
985
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
986
|
+
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
|
987
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
988
|
+
"/usr/bin/google-chrome",
|
|
989
|
+
"/usr/bin/google-chrome-stable",
|
|
990
|
+
"/usr/bin/chromium",
|
|
991
|
+
"/usr/bin/chromium-browser",
|
|
992
|
+
"/snap/bin/chromium",
|
|
993
|
+
].filter(Boolean);
|
|
994
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
|
|
995
|
+
}
|
|
996
|
+
function surfaceRecord(surface) {
|
|
997
|
+
return {
|
|
998
|
+
textWidthPx: surface.textWidthPx,
|
|
999
|
+
viewportWidthPx: surface.viewportWidthPx,
|
|
1000
|
+
fontSizePx: surface.fontSizePx,
|
|
1001
|
+
lineHeightPx: surface.lineHeightPx,
|
|
1002
|
+
reviewClampLines: surface.reviewClampLines,
|
|
1003
|
+
estimatedVisibleTextBlock: surface.estimatedVisibleTextBlock,
|
|
1004
|
+
estimatedRenderedLines: surface.estimatedLines.slice(0, surface.reviewClampLines),
|
|
1005
|
+
estimatedLineCountBeforeClamp: surface.estimatedLineCountBeforeClamp,
|
|
1006
|
+
estimatedBlankLinesBeforeClamp: surface.estimatedBlankLinesBeforeClamp,
|
|
1007
|
+
htmlPath: surface.htmlPath,
|
|
1008
|
+
screenshotPath: surface.screenshotPath,
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function surfaceReturnRecord(surface) {
|
|
1012
|
+
return {
|
|
1013
|
+
...surfaceRecord(surface),
|
|
1014
|
+
absoluteHtmlPath: surface.absoluteHtmlPath,
|
|
1015
|
+
absoluteScreenshotPath: surface.absoluteScreenshotPath,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
function previewDiagnostics(text) {
|
|
1019
|
+
const physicalLines = text.split("\n");
|
|
1020
|
+
const nonblankLines = physicalLines.filter((line) => line.trim().length > 0);
|
|
1021
|
+
return {
|
|
1022
|
+
charCount: text.replace(/\s/g, "").length,
|
|
1023
|
+
charCountIncludingNewlines: text.length,
|
|
1024
|
+
firstLineChars: physicalLines[0]?.length ?? 0,
|
|
1025
|
+
firstTwoPhysicalLinesChars: physicalLines.slice(0, 2).join("\n").length,
|
|
1026
|
+
physicalLineCount: physicalLines.length,
|
|
1027
|
+
contentLineCount: nonblankLines.length,
|
|
1028
|
+
longestNonblankLineChars: nonblankLines.reduce((max, line) => Math.max(max, line.length), 0),
|
|
1029
|
+
blankLineCount: physicalLines.length - nonblankLines.length,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function estimateRenderedLines(text, textWidthPx) {
|
|
1033
|
+
const physicalLines = text.split("\n");
|
|
1034
|
+
const rendered = [];
|
|
1035
|
+
for (const physicalLine of physicalLines) {
|
|
1036
|
+
if (physicalLine.length === 0) {
|
|
1037
|
+
rendered.push("");
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
rendered.push(...wrapPhysicalLine(physicalLine, textWidthPx));
|
|
1041
|
+
}
|
|
1042
|
+
return rendered;
|
|
1043
|
+
}
|
|
1044
|
+
function wrapPhysicalLine(line, textWidthPx) {
|
|
1045
|
+
const tokens = line.match(/\S+\s*/g) ?? [line];
|
|
1046
|
+
const wrapped = [];
|
|
1047
|
+
let current = "";
|
|
1048
|
+
for (const token of tokens) {
|
|
1049
|
+
const candidate = `${current}${token}`;
|
|
1050
|
+
if (!current || measureTextPx(candidate) <= textWidthPx) {
|
|
1051
|
+
current = candidate;
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
wrapped.push(current.trimEnd());
|
|
1055
|
+
if (measureTextPx(token) <= textWidthPx) {
|
|
1056
|
+
current = token;
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
const broken = breakLongToken(token.trimEnd(), textWidthPx);
|
|
1060
|
+
wrapped.push(...broken.slice(0, -1));
|
|
1061
|
+
current = broken.at(-1) ?? "";
|
|
1062
|
+
if (token.endsWith(" "))
|
|
1063
|
+
current += " ";
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (current.length > 0)
|
|
1067
|
+
wrapped.push(current.trimEnd());
|
|
1068
|
+
return wrapped.length > 0 ? wrapped : [""];
|
|
1069
|
+
}
|
|
1070
|
+
function breakLongToken(token, textWidthPx) {
|
|
1071
|
+
const parts = [];
|
|
1072
|
+
let current = "";
|
|
1073
|
+
for (const char of [...token]) {
|
|
1074
|
+
const candidate = `${current}${char}`;
|
|
1075
|
+
if (!current || measureTextPx(candidate) <= textWidthPx) {
|
|
1076
|
+
current = candidate;
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
parts.push(current);
|
|
1080
|
+
current = char;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (current)
|
|
1084
|
+
parts.push(current);
|
|
1085
|
+
return parts;
|
|
1086
|
+
}
|
|
1087
|
+
function measureTextPx(value) {
|
|
1088
|
+
let width = 0;
|
|
1089
|
+
for (const char of [...value]) {
|
|
1090
|
+
width += estimateCharWidthPx(char);
|
|
1091
|
+
}
|
|
1092
|
+
return width;
|
|
1093
|
+
}
|
|
1094
|
+
function estimateCharWidthPx(char) {
|
|
1095
|
+
if (char === " ")
|
|
1096
|
+
return 3.6;
|
|
1097
|
+
if (/[ilI|.,:;!'`]/.test(char))
|
|
1098
|
+
return 3.8;
|
|
1099
|
+
if (/[fjrt()[\]{}]/.test(char))
|
|
1100
|
+
return 5.2;
|
|
1101
|
+
if (/[mwMW@#%&]/.test(char))
|
|
1102
|
+
return 10.2;
|
|
1103
|
+
if (/[A-Z]/.test(char))
|
|
1104
|
+
return 8.2;
|
|
1105
|
+
if (/[0-9]/.test(char))
|
|
1106
|
+
return 7.3;
|
|
1107
|
+
if (/[^\x00-\x7F]/.test(char))
|
|
1108
|
+
return 8.4;
|
|
1109
|
+
return 7.0;
|
|
1110
|
+
}
|
|
1111
|
+
function escapeHtml(value) {
|
|
1112
|
+
return value
|
|
1113
|
+
.replace(/&/g, "&")
|
|
1114
|
+
.replace(/</g, "<")
|
|
1115
|
+
.replace(/>/g, ">")
|
|
1116
|
+
.replace(/"/g, """)
|
|
1117
|
+
.replace(/'/g, "'");
|
|
1118
|
+
}
|
|
620
1119
|
function getArtifact(relativeDir, id, label) {
|
|
621
1120
|
const safeId = normalizeArtifactId(id, label);
|
|
622
1121
|
const relativePath = `${relativeDir}/${safeId}.md`;
|
|
@@ -810,6 +1309,11 @@ function writeArtifact(relativePath, markdown) {
|
|
|
810
1309
|
const filePath = safeWritablePath(root, relativePath);
|
|
811
1310
|
fs.writeFileSync(filePath, markdown);
|
|
812
1311
|
}
|
|
1312
|
+
function writeContentFile(relativePath, contents) {
|
|
1313
|
+
const root = resolveContentRoot();
|
|
1314
|
+
const filePath = safeWritablePath(root, relativePath);
|
|
1315
|
+
fs.writeFileSync(filePath, contents);
|
|
1316
|
+
}
|
|
813
1317
|
function safePath(root, relativePath) {
|
|
814
1318
|
validateRelativePath(relativePath);
|
|
815
1319
|
const fullPath = path.resolve(root, ...relativePath.split("/"));
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1260,6 +1260,12 @@ export declare const allTools: ({
|
|
|
1260
1260
|
body?: undefined;
|
|
1261
1261
|
validationReceipt?: undefined;
|
|
1262
1262
|
status?: undefined;
|
|
1263
|
+
postText?: undefined;
|
|
1264
|
+
renderId?: undefined;
|
|
1265
|
+
sourceLabel?: undefined;
|
|
1266
|
+
renderScreenshots?: undefined;
|
|
1267
|
+
requireScreenshots?: undefined;
|
|
1268
|
+
reviewClampLines?: undefined;
|
|
1263
1269
|
updatedAt?: undefined;
|
|
1264
1270
|
publishUrl?: undefined;
|
|
1265
1271
|
activityId?: undefined;
|
|
@@ -1295,6 +1301,7 @@ export declare const allTools: ({
|
|
|
1295
1301
|
properties: {
|
|
1296
1302
|
draftId: {
|
|
1297
1303
|
type: string;
|
|
1304
|
+
description?: undefined;
|
|
1298
1305
|
};
|
|
1299
1306
|
ideaId: {
|
|
1300
1307
|
type: string;
|
|
@@ -1339,6 +1346,12 @@ export declare const allTools: ({
|
|
|
1339
1346
|
selectedPatterns?: undefined;
|
|
1340
1347
|
previewBudget?: undefined;
|
|
1341
1348
|
notes?: undefined;
|
|
1349
|
+
postText?: undefined;
|
|
1350
|
+
renderId?: undefined;
|
|
1351
|
+
sourceLabel?: undefined;
|
|
1352
|
+
renderScreenshots?: undefined;
|
|
1353
|
+
requireScreenshots?: undefined;
|
|
1354
|
+
reviewClampLines?: undefined;
|
|
1342
1355
|
updatedAt?: undefined;
|
|
1343
1356
|
publishUrl?: undefined;
|
|
1344
1357
|
activityId?: undefined;
|
|
@@ -1361,6 +1374,7 @@ export declare const allTools: ({
|
|
|
1361
1374
|
properties: {
|
|
1362
1375
|
draftId: {
|
|
1363
1376
|
type: string;
|
|
1377
|
+
description?: undefined;
|
|
1364
1378
|
};
|
|
1365
1379
|
hookResearchId: {
|
|
1366
1380
|
type: string;
|
|
@@ -1403,6 +1417,12 @@ export declare const allTools: ({
|
|
|
1403
1417
|
previewBudget?: undefined;
|
|
1404
1418
|
notes?: undefined;
|
|
1405
1419
|
createdAt?: undefined;
|
|
1420
|
+
postText?: undefined;
|
|
1421
|
+
renderId?: undefined;
|
|
1422
|
+
sourceLabel?: undefined;
|
|
1423
|
+
renderScreenshots?: undefined;
|
|
1424
|
+
requireScreenshots?: undefined;
|
|
1425
|
+
reviewClampLines?: undefined;
|
|
1406
1426
|
publishUrl?: undefined;
|
|
1407
1427
|
activityId?: undefined;
|
|
1408
1428
|
publishedAt?: undefined;
|
|
@@ -1424,6 +1444,7 @@ export declare const allTools: ({
|
|
|
1424
1444
|
properties: {
|
|
1425
1445
|
draftId: {
|
|
1426
1446
|
type: string;
|
|
1447
|
+
description?: undefined;
|
|
1427
1448
|
};
|
|
1428
1449
|
publishUrl: {
|
|
1429
1450
|
type: string;
|
|
@@ -1464,6 +1485,12 @@ export declare const allTools: ({
|
|
|
1464
1485
|
body?: undefined;
|
|
1465
1486
|
validationReceipt?: undefined;
|
|
1466
1487
|
status?: undefined;
|
|
1488
|
+
postText?: undefined;
|
|
1489
|
+
renderId?: undefined;
|
|
1490
|
+
sourceLabel?: undefined;
|
|
1491
|
+
renderScreenshots?: undefined;
|
|
1492
|
+
requireScreenshots?: undefined;
|
|
1493
|
+
reviewClampLines?: undefined;
|
|
1467
1494
|
updatedAt?: undefined;
|
|
1468
1495
|
publishedPostId?: undefined;
|
|
1469
1496
|
year?: undefined;
|
|
@@ -1516,6 +1543,12 @@ export declare const allTools: ({
|
|
|
1516
1543
|
body?: undefined;
|
|
1517
1544
|
validationReceipt?: undefined;
|
|
1518
1545
|
status?: undefined;
|
|
1546
|
+
postText?: undefined;
|
|
1547
|
+
renderId?: undefined;
|
|
1548
|
+
sourceLabel?: undefined;
|
|
1549
|
+
renderScreenshots?: undefined;
|
|
1550
|
+
requireScreenshots?: undefined;
|
|
1551
|
+
reviewClampLines?: undefined;
|
|
1519
1552
|
updatedAt?: undefined;
|
|
1520
1553
|
publishUrl?: undefined;
|
|
1521
1554
|
activityId?: undefined;
|